Sortering i Lineær Tid Lars Vidar Magnusson 5.2.2014 Kapittel 8 Counting Sort Radix Sort Bucket Sort
Sammenligningsbasert Sortering Sorteringsalgoritmene vi har sett på så langt har alle vært sammenligningsbaserte algoritmer i.e de sorterer ved å sammenligne verdiene som skal sorteres. Sammenligningsbaserte sorteringsalgoritmer har en asymptotisk grense på Ω(n log n). Heapsort og Merge-Sort har begge asymptotisk optimal kjøretid. Quicksort har forventet optimal kjøretid.
Sorterings i Lineær Tid Siden Ω(n log n) er nedre grense for hvor raske sorteringsalgoritmer kan bli ved å sammenligne verdier, må vi ty til andre teknikker for å forbedre kjøretiden. Vi skal se på tre algoritmer som alle sammen kan sortere et sett med elementer i lineær tid. Counting-Sort Radix-Sort Bucket-Sort
Counting-Sort Algoritmen Counting-Sort fungerer på det premisset at tallene som skal sorteres er heltall mellom 0 og k i.e. {0, 1, 2,..., k}. Den fungerer ved å telle seg frem til hvilken posisjon hvert element må ha i output array. Input er en array A[1... n] hvor A[j] {0, 1, 2,..., k} for j = 1, 2,..., n. Output er en array B[1... n] med elementene i sortert rekkefølge. Algoritmen benytter seg av en array C[0... k] under kjøring. Counting-Sort er en såkalt stabil sorteringalgoritme i.e at relativ posisjonering mellom like elementer ivaretas. Algoritmen er ikke en in-place algoritme siden den kopierer verdiene over i output array.
Counting-Sort Pseudokode Under er pseudokoden for Counting-Sort algoritmen.
Counting-Sort Kjøring Diagrammene under illustrerer hvordan Counting-Sort kjører på et inputeksempel.
Analyse av Counting-Sort Vi kan enkelt analysere Counting-Sort algoritmen ved å telle løkker. Vi har to løkker gjennom k Vi har to løkker gjennom n Counting-Sort er Θ(n + k), som er Θ(n) hvis k = O(n).
Hvor Stor k Er Effektivt i Praksis? Effektiviteten til Counting-Sort avhenger av størrelsen til k i.e. hvor store tallen kan være. Det er størrelsen på arbeids arrayet C som steller til problemer. Det ville vært ineffektivt å sortere 32-bits verdier da C ville vært 4 2 32 17TB stor, hvis vi bruker 4 bytes per verdi. 16-bits verdier blir vesentlig bedre med en størrelse på C på bare 4 2 16 262MB. 8-bits verdier ville krevd en størrelse på bare 4 2 8 = 1Mb, men vi må være obs på størrelsen til n. 4-bits verdier ville ikke krevd stort av lagringsplass, så her blir fokuset skiftet over til hvorvidt n er liten nok til at Insertion-Sort er et bedre alternativ.
Radix-Sort Algoritmen Radix-Sort algoritmen er hvordan IBM tjente sine første millioner. Kortsorterere fungerte ved å titte på en kolonne om gangen. Nøkkelen i algoritmen ligger i å sortere tallene utifra de ulike sifferne i hvert tall, hvor vi begynner med den med lavest verdi (least significant digit) og jobber oss mot den største i.e vi jobber fra høyre mot venstre.
Kjøring av Radix-Sort Diagrammet under viser hvordan Radix-Sort funger på et inputeksempel.
Analyse av Radix-Sort Radix-Sort krever en stabil sorteringsalgoritme for å sortere på hvert siffer. Counting-Sort er et vanlig valg. Counting-Sort har kjøretid på Θ(n + k) hvor k er maksverdi for et siffer. Vi har d antall siffer i hvert tall Den totale kjøretiden til Radix-Sort blir da Θ(d(n + k)). Hvis k = O(n) så er Radix-Sort = Θ(dn).
Hvordan Finne Et Effektivt Antall Siffer For å få Radix-Sort til å kjøre effektiv må vi finne et effektiv antall siffer. n elementer som skal sorteres b bits per element Del opp tallene/elementene i r-bit siffere. Vi får da d = b/r siffer, og vi får 2 r 1 mulige verdier. Et eksempel: 32-bit tall, 8-bit siffere. Vi får da b = 32, r = 8, d = 32/8 = 4 og k = 2 8 1 = 255. Kjøretiden blir da Θ( b r (n + 2r )) og vi må velge fornuftig r.
Hvordan Finne Fornuftig r Å velge fornuftig verdi for r, antall bits i hvert siffer, må vi balansere b/r og n + 2 r. Uten å gå dypere i grunnlaget så kan vi si at hvis vi velger r log n så får vi Θ( b log n (n + n)) = Θ(bn/ log n). Hvis vi velger r < log n så vil b/r < b/ log n som fører til at n + 2 r leddet ikke forbedres. Hvis vi velger r > log n så vil n + 2 r leddet bli stort, som raskt kan lede til en kvadatisk algoritme.
Bucket-Sort Algoritmen Bucket-Sort er en lineær sorteringsalgoritme for flyttall. Den fungerer ved å anta at input er uniformt fordelt over intervallet [0, 1). Del opp [0, 1) i n like deler, eller bøtter Fordel inputtallene ut i de tilhørende bøttene Sorter hver bøtte med Insertion-Sort Gå gjennom hver bøtte i rekkefølge og sett sammen resultatet. Bucket-Sort bruker en array med lenkede lister for å representere bøttene i.e. den er ikke in-place.
Bucket-Sort Pseudokode Pseudokoden for Bucket-Sort er listet under.
Kjøring av Bucket-Sort Diagrammet under viser hvordan Bucket-Sort kjører på et inputeksempel.
Analyse av Bucket-Sort Analysen av Bucket-Sort er litt mere komplisert enn de andre lineære algoritmene vi har studert pga. sorteringen av bøttene med Insertion-Sort. En analyse er bygget på følgende antagelser og intuisjoner. Algoritmens effektivitet avhenger av at hver bøtte har få elementer. Hvis hver bøtte har et konstant antall elementer vil det ta en konstant tid å sortere dem. Vi forventer at hver bøtte har få elementer siden vi har like mange bøtter som tall i.e gjennomsnittet er et element i hver.
Detaljert Analyse av Bucket-Sort Får å utføre en detaljert analyse kan atter en dra nytte av indikator variable. Vi lar n i være en tilfeldig variabel som angir antallet elementer i bøtte i. Siden Insertion-Sort er kvadratisk får vi at T (n) = Θ(n) + n 1 i=0 O(n2 i ). Da får vi forventet kjøretid [ ] n 1 E[T (n)] = E Θ(n) + O(ni 2 ) i=0 n 1 = Θ(n) + E[O(ni 2 )] i=0 n 1 = Θ(n) + O(E[ni 2 ]) i=0
Detaljert Analyse av Bucket-Sort For å fullføre analysen må vi finne E[n 2 i ]. Vi definerer en indikator variabel X ij = I {A[j] havner i bøtte i}. Sansynligheten for at A[j] havner i bøtte i definerer vi som Pr{X ij } = 1/n. Med dette kan vi definere n i matematisk som n i = n j=1 X ij. [( n ) 2 ] E[ni 2 ] = E X ij j=1 n = E j=1 n 1 ij + 2 X 2 j=1 k=j+1 n X ij X ik = n n 1 E[Xij 2 ] + 2 j=1 j=1 k=j+1 n E[X ij X ik ]
Detaljert Analyse av Bucket-Sort Vi trenger å finne E[X 2 ij ] og E[X ijx ik ] for å fullføre analysen. ( E[Xij 2 ] = 0 2 1 1 ) + 1 2 1 n n = 1 n Siden j k (de er uavhengige) så kan vi si at E[X ij X ik ] = E[X ij ]E[X ik ] = 1 n 1 n = 1 n 2
Detaljert Analyse av Bucket-Sort Da har vi alt vi trenger for å finne det forventede antall elementer i bøtte i (E[n 2 i ]). E[n 2 i ] = n j=1 n 1 1 n + 2 = n 1 n + 2 ( n j=1 k=j+1 n 2 ) 1 n 2 n(n 1) = 1 + 2 2 = 1 + n 1 n = 2 1 n 1 n 2 1 n 2
Detaljert Analyse av Bucket-Sort Da kan vi omsider finne den forventede kjøretiden til Bucket-Sort. n 1 E[T (n)] = Θ(n) + O(2 1/n) i=0 = Θ(n) + O(n) = Θ(n) Så intuisjonen vår om kjøretiden til algoritmen stemte. Legg merke til at dette bare gjelder dersom listen med elementer er uniformt fordelte.