CMD + K
CMD + K
Sortering i lineær tid og utvalg
Enhver sammenligningsbasert sortering må gjøre minst sammenligninger i verste fall. Men hvis vi vet noe om nøklene kan vi gå utenom: Counting-, Radix- og Bucket-Sort sorterer i lineær tid. Vi ser også hvordan vi finner det -te minste elementet uten å sortere alt.
- 01Bevise den nedre grensen Ω(n lg n) for sammenligningssortering ved hjelp av et beslutningstre med minst n! løv
- 02Forklare hvordan Counting-Sort sorterer heltallsnøkler i Θ(n+k) ved å telle forekomster, og når den lønner seg
- 03Begrunne hvorfor Radix-Sort trenger en stabil siffersortering, og regne ut kjøretiden Θ(d(n+k))
- 04Forklare hvordan Randomized-Select finner det k-te minste elementet i forventet Θ(n) ved å forkaste én side per partisjonering
En mur ingen sammenligningssortering kommer forbi
Merge-Sort, Quicksort og Heapsort har én ting til felles: de bestemmer rekkefølgen utelukkende ved å sammenligne par av elementer — er dette mindre enn hint? Vi kaller slike algoritmer sammenligningssortering. Det viser seg at uansett hvor smart en slik algoritme er, kan den aldri komme under i verste fall. Det er ikke et spørsmål om å være flink nok; det er en matematisk mur.
For å bevise muren modellerer vi en hvilken som helst sammenligningssortering som et beslutningstre. Hver indre node er én sammenligning, og de to grenene ut av noden er de to mulige svarene (mindre eller ikke). Hvert løv svarer til én ferdig sortert rekkefølge — én permutasjon av inndataene.
Nå kommer poenget. For at sorteringen skal fungere på alle mulige inndata, må hver av de permutasjonene kunne ende i sitt eget løv. Treet må altså ha minst løv. Et binærtre med høyde har høyst løv, så vi trenger . Antall sammenligninger i verste tilfelle er nettopp høyden :
ƒhøyde av beslutningstreet
Tar vi logaritmen og bruker at (Stirlings formel), får vi den nedre grensen:
ƒnedre grense for sammenligningssortering
Konklusjonen er presis og litt overraskende: ingen algoritme som kun sammenligner elementer kan i verste fall slå . Merge-Sort og Heapsort er altså ikke bare gode — de er asymptotisk optimale i denne klassen.
Snik forbi muren med ekstra antakelser
Muren gjelder kun algoritmer som ikke vet noe om elementene utover hvordan de ordnes innbyrdes. Men hva om vi vet mer? Hva om nøklene er heltall i et lite, kjent område? Da kan vi sortere uten å sammenligne i det hele tatt, og slippe under . Det er ingen motsigelse — vi bryter ikke muren, vi går rundt den ved å bruke informasjon sammenligningssorteringene ikke har.
Den første slike algoritmen er counting-sort. Anta nøklene er heltall i området til . Da teller vi ganske enkelt hvor mange ganger hver verdi forekommer, og bruker tellingene til å plassere hvert element direkte på rett sted.
COUNTING-SORT(A, k):
C = [0, 0, ..., 0] # k+1 tellere, alle 0
for x in A: # tellefasen: O(n)
C[x] = C[x] + 1
for i in 1..k: # akkumuler: prefikssummer, O(k)
C[i] = C[i] + C[i-1]
B = [tom, ..., tom] # resultatarray, lengde n
for x in reversed(A): # plasser bakfra for stabilitet
C[x] = C[x] - 1
B[C[x]] = x
return BEtter tellefasen gjør vi tellerne om til prefikssummer: skal fortelle hvor mange elementer som er mindre enn eller lik . Da vet vi nøyaktig hvilken sluttposisjon hvert element skal ha. For eksempelet over blir den akkumulerte lik — det siste elementet med nøkkel 3 skal altså på indeks 6, det nest siste på indeks 5, og så videre. Vi plasserer hvert element direkte uten en eneste sammenligning mellom to nøkler.
Kjøretiden er summen av to gjennomløp, ett over de elementene og ett over de tellerne:
ƒcounting-sort
Når er dette lineært. Prisen er ekstra minne til tellerne, så Counting-Sort egner seg når tallområdet er lite i forhold til antall elementer. Skal du sortere en million heltall mellom 0 og 100, er den uslåelig. Skal du sortere en million 64-bits tall, er astronomisk, og Counting-Sort kollapser — du ville trengt et tellerarray med flere celler enn det finnes atomer i nærheten. Dette spennet mellom triumf og kollaps er hele poenget: lineær-tid-sorteringene kjøper farten sin ved å anta noe konkret om nøklene, og bryter sammen så snart antakelsen ryker.
Stabilitet — den egenskapen som gjør Radix mulig
Legg merke til siste løkke i Counting-Sort: vi går gjennom inndataene bakfra. Det er ikke tilfeldig. Det gjør sorteringen stabil sortering — to elementer med lik nøkkel beholder sin innbyrdes rekkefølge fra inndataene. Stabilitet høres ut som en detalj, men den er selve forutsetningen for neste algoritme.
radix-sort sorterer flersifrede nøkler ett siffer om gangen, fra det minst signifikante til det mest signifikante. For tallene 329, 457, 657, 839, 436, 720, 355 sorterer vi først på enerne, så på tierne, så på hundrerne. Det kontraintuitive er at vi starter med det minst viktige sifferet. Det fungerer kun fordi hver enkelt siffersortering er stabil: når vi sorterer på tierne, bevarer stabiliteten den rekkefølgen enerne allerede ga, så tall med samme tier kommer riktig sortert på eneren.
ƒradix-sort
Her er antall sifre og basen (for desimaltall ). Hver av de rundene er en Counting-Sort på ett siffer. Med fast antall sifre er Radix-Sort lineær i — nok en gang ved å utnytte at nøklene har struktur sammenligningssorteringer ikke ser.
Prøv selv å se hvorfor rekkefølgen er viktig. Hadde vi sortert mest signifikante siffer først, ville en senere sortering på et mindre viktig siffer stokket om på den grovgrupperingen vi nettopp laget, og resultatet ble galt. Ved å gå fra minst til mest signifikant, og holde hvert steg stabilt, lar vi de viktigere sifrene få siste ord mens de mindre viktige bevares som tie-breaker. Det er et lite, vakkert eksempel på hvordan en tilsynelatende teknisk egenskap — stabilitet — gjør hele algoritmen mulig.
Bucket-Sort: når dataene er jevnt fordelt
En tredje vei rundt muren bygger på en antakelse om fordelingen av nøklene. bucket-sort forutsetter at nøklene er jevnt fordelt over et intervall, for eksempel reelle tall mellom 0 og 1. Vi deler intervallet i like store bøtter, slår hvert element i bøtta det hører hjemme i, sorterer hver bøtte for seg, og leser bøttene i rekkefølge.
Poenget er statistisk. Når nøklene er jevnt fordelt, havner det i snitt konstant mange elementer i hver bøtte, og å sortere en bøtte med konstant størrelse koster konstant tid. Summert over alle bøttene blir det lineært i forventning:
ƒbucket-sort, forventet
Merk ordet «forventet». Bucket-Sort gir ingen garanti i verste fall — er dataene klumpet sammen i én bøtte, degenererer den til den lokale sorteringens kjøretid. Den er rask når antakelsen om jevn fordeling holder, og det er en helt annen kontrakt enn Merge-Sorts ubetingede .
Utvalg: finn det k-te minste uten å sortere
Til slutt et beslektet problem. utvalgsproblemet spør etter det -te minste elementet i en samling — medianen er spesialtilfellet . Den enkle løsningen er å sortere alt og slå opp på indeks , men det koster , og vi gjør mye unødig arbeid: vi ordner hele samlingen bare for å lese ut ett element.
randomized-select gjenbruker partisjoneringen fra Quicksort, men med et avgjørende kutt. Den partisjonerer rundt et tilfeldig pivot, akkurat som Quicksort, men deretter graver den kun videre i den ene siden som inneholder det -te elementet. Den andre siden kastes helt — vi vet svaret ikke kan ligge der.
RANDOMIZED-SELECT(A, lo, hi, k):
if hi - lo == 1:
return A[lo]
p = RANDOMIZED-PARTITION(A, lo, hi)
rank = p - lo # pivotens posisjon i delarrayet
if k == rank:
return A[p] # pivoten ER svaret
elif k < rank:
return RANDOMIZED-SELECT(A, lo, p, k) # bare venstre
else:
return RANDOMIZED-SELECT(A, p + 1, hi, k - rank - 1) # bare høyreFordi vi forkaster én side, blir det forventede arbeidet en geometrisk avtagende rekke i stedet for fulle nivåer. Partisjoneringen kaster i snitt bort en konstant brøk av elementene per steg, og summen konvergerer til :
ƒrandomisert utvalg, forventet
Vi finner altså medianen i forventet lineær tid — raskere enn å sortere. Akkurat som med Quicksort er det det tilfeldige pivotvalget som gjør forventningen god; verste fall er fortsatt , men praktisk talt umulig å fremtvinge.
Mine notater
Skriv egne notater for dette kapittelet. De lagres automatisk og dukker opp i «Mine notater»-oversikten paa fag-siden.