2.2 Lineær sortering og stabilitet
`Counting-Sort`, `Radix-Sort`, `Bucket-Sort` — når lineær sortering er mulig, og hva stabilitet betyr og krever.
spørsmålet «når kan du sortere i lineær tid?» er en av de faste variantene.
Grunnlaget er de 17 settene fra august 2015 til august 2023 som er gjennomgått
tema for tema — det er nevneren hver gang boka oppgir en prosent.
To sjangre henter fra dette kapitlet, og de skrives ut i klarspråk her:
- Sjanger D — definisjon med egne ord, altså at du skriver én presis
setning med hovedpoenget først. «Definér stabil sortering» er den hyppigste
definisjonsoppgaven i sorteringsdelen.
- Sjanger E — kjøretidskunnskap, altså at du oppgir kjøretiden til en
navngitt algoritme i det strammeste uttrykket som er riktig. Her må
betingelsen med: Counting-Sort er , og det er bare lineært
når .
Høyeste prioritet — dette må sitte. Eksamen er hjelpemiddelfri, og både
definisjonen av stabilitet og de tre kjøretidene er ren gjenkalling.
Det som skiller dette kapitlet fra det forrige: algoritmene her
sammenligner ikke elementer med hverandre. De bruker nøkkelverdien direkte som
en indeks i en tabell. Derfor er de ikke bundet av -grensen —
og derfor stiller de til gjengjeld krav til hvordan nøklene ser ut.
Slik er kapitlet lagt opp (45 min):
| Innhold | Tid |
|---|---|
Stabilitet, og Counting-Sort | ca. 16 min |
Radix-Sort og hvorfor stabilitet er et krav | ca. 13 min |
Bucket-Sort | ca. 8 min |
| Hvorfor grensen ikke er brutt | ca. 8 min |
Forkunnskaper
- kap. 2.1 — sammenligningsbaserte sorteringer. Fra det
kapitlet: Merge-Sort er og stabil, Quicksort er
forventet og ustabil, og ingen sortering som bare
sammenligner elementpar kan komme under i verste tilfelle.
Det siste er poenget dette kapitlet spiller mot.
- kap. 1.1 — de asymptotiske symbolene. Du trenger
og , og at brukt om en parameter (som ) betyr «vokser
ikke raskere enn ».
Vil du se sortering konkret i kode først:
Sortering: boblesortering, .sort() og gitt sort_list.
Stabilitet, og Counting-Sort (~16 min)
På et postterminal kommer pakkene inn på et bånd, hver med et rutenummer fra 0
til 40. Skal du legge dem i rutenummerrekkefølge, er det tåpelig å sammenligne
pakker to og to. Du vet jo hvor mange ruter det finnes. Du kan telle hvor mange
pakker som skal til hver rute, og dermed regne ut nøyaktig hvilke plasser i
rekka hver rute skal ha.
Det er Counting-Sort. Den sammenligner aldri to elementer med hverandre — den
bruker nøkkelen som indeks i en telletabell. Prisen er at nøklene må være
heltall i et kjent, ikke altfor stort område.
Men først et begrep som hele resten av kapitlet hviler på.
En sortering er stabil når to elementer med lik nøkkel beholder sin
innbyrdes rekkefølge fra input til output.
Ligger to pakker med rutenummer 12 på båndet i rekkefølgen «først den fra
Tromsø, så den fra Bodø», skal de ligge i den samme rekkefølgen etterpå.
Egenskapen er bare interessant når elementene bærer med seg mer enn nøkkelen —
en pakke er ikke bare et rutenummer. Og den er avgjørende når du sorterer
etter én nøkkel om gangen, slik Radix-Sort gjør.
A[1..n], indeks fra 1. Hvernøkkel
A[j] er et heltall i området , og er kjent påforhånd. Outputen skrives til et eget array
B[1..n]; sorteringen er ikkepå stedet.
Prebetingelse: alle nøkler er heltall i .
Postbetingelse: B[1..n] inneholder de samme elementene i stigende
rekkefølge, og like nøkler står i samme innbyrdes rekkefølge som i A.
Counting-Sort(A, B, k)
Input: array A[1..n] med heltallsnoekler i [0..k]
Output: B[1..n] sortert stigende, stabilt
la C[0..k] vaere et nytt array
for i = 0 to k
C[i] = 0
for j = 1 to A.length
C[A[j]] = C[A[j]] + 1
for i = 1 to k
C[i] = C[i] + C[i-1]
for j = A.length downto 1
B[C[A[j]]] = A[j]
C[A[j]] = C[A[j]] - 1
Kjoeretid: Theta(n + k)Grunnideen i én setning: etter oppsummeringen forteller C[i] hvor mange
elementer som har nøkkel høyst — og det er nøyaktig den siste ledige
plassen i B for nøkkelen .
Hvorfor den siste løkka teller nedover. Går du fra n ned til 1, blir
det siste elementet med en gitt nøkkel plassert sist blant sine like.
Snur du løkka, sorterer algoritmen fortsatt riktig, men stabiliteten
forsvinner — og da kan den ikke brukes inne i Radix-Sort.
Kjøretid: til å nullstille C, til tellingen,
til oppsummeringen og til plasseringen, altså
. Det er lineært bare når . Med nøkler opp til
er algoritmen , og da er Merge-Sort langt bedre.
Fem pakker har rutenumrene A = 2, 1, 2, 3, 1 i denne rekkefølgen. For å
kunne se hva som skjer med like nøkler, merker vi dem: 2a, 1b, 2c, 3d, 1e.
Bokstaven er bare en etikett som følger elementet; sorteringen ser bare tallet.
Her er og .
Kjør Counting-Sort og oppgi telletabellen C etter tellingen, C etter
oppsummeringen, og det ferdige B.
C[0..3] etter tellingen: 0, 2, 2, 1Ingen pakker til rute 0, to til rute 1, to til rute 2, én til rute 3.
C[0..3] etter oppsummeringen: 0, 2, 4, 5
Nå leses tabellen som «antall elementer med nøkkel høyst ». C[2] = 4
betyr at fire elementer har nøkkel 2 eller lavere — altså er plass 4 den siste
ledige plassen for en toer.
Plasseringen, bakfra:
| Steg | j | Element | Plass i B | C etter steget | B etter steget |
|---|---|---|---|---|---|
| 1 | 5 | 1e | 2 | 0, 1, 4, 5 | _, 1, _, _, _ |
| 2 | 4 | 3d | 5 | 0, 1, 4, 4 | _, 1, _, _, 3 |
| 3 | 3 | 2c | 4 | 0, 1, 3, 4 | _, 1, _, 2, 3 |
| 4 | 2 | 1b | 1 | 0, 0, 3, 4 | 1, 1, _, 2, 3 |
| 5 | 1 | 2a | 3 | 0, 0, 2, 4 | 1, 1, 2, 2, 3 |
Sluttilstanden — det du ville levert på eksamen:
B = 1, 1, 2, 2, 3, og med merkelappene:1b, 1e, 2a, 2c, 3d.Se på de to toerne. I input lå
2a før 2c. I output ligger 2a fortsatt før2c — fordi løkka gikk bakfra, ble 2c plassert først, på den siste ledigetoerplassen. Det er stabilitet, konkret.
Merk hva oppgaven ba om: de tre tabellene. Ba den bare om C etter
oppsummeringen, skulle du oppgitt den ene raden og ikke mer.
(Innstegsoppgave, sjanger D — definisjon med egne ord, altså én presis setning
med hovedpoenget først.)
Definér stabil sortering, og gi et eksempel på en situasjon der egenskapen
har betydning.
Et array A = 4, 1, 3, 4, 0, 2, 3, 4 skal sorteres med Counting-Sort med
.
a) Oppgi telletabellen C[0..4] etter tellingen.
b) Oppgi C[0..4] etter oppsummeringen.
c) Oppgi det ferdige B.
Radix-Sort og hvorfor stabilitet er et krav (~13 min)
Counting-Sort er ubrukelig på store nøkler: skal du sortere sekssifrede
ordrenumre, er , og telletabellen blir større enn hele arkivet.
Radix-Sort løser det ved å ta ett siffer om gangen. Nøkkelen har siffer,
hvert siffer ligger i et lite område, og hvert siffer sorteres for seg — med en
stabil delsortering, typisk Counting-Sort.
Det overraskende er rekkefølgen: du starter med det minst signifikante
sifferet.
A[1..n], indeks fra 1. Hvernøkkel har nøyaktig siffer, der siffer 1 er det minst signifikante.
Sifrene ligger i et lite område — for desimaltall er .
Prebetingelse: delsorteringen som brukes per siffer, må være stabil.
Postbetingelse: A[1..n] er sortert stigende etter hele nøkkelen.
Radix-Sort(A, d)
Input: array A[1..n] der hver noekkel har d siffer
Output: A sortert stigende etter hele noekkelen
for i = 1 to d
sorter A stabilt paa siffer i
Kjoeretid: Theta(d(n + k))Invarianten i én setning: etter runde i er arrayet sortert etter de i
minst signifikante sifrene under ett.
Hvorfor stabiliteten er et krav og ikke en bonus. Anta at invarianten
holder etter runde i, og at vi nå sorterer på siffer i+1. To elementer med
forskjellig siffer i+1 havner riktig av seg selv. To elementer med
samme siffer i+1 må avgjøres av de foregående sifrene — altså av
rekkefølgen de allerede har. Er delsorteringen ustabil, kastes den
rekkefølgen, og hele arbeidet fra de tidligere rundene går tapt.
Kjøretid: runder à én Counting-Sort med sifferområde , altså
. Med faste og er dette .
Sju tresifrede lagernumre skal sorteres: `A = 482, 175, 439, 175, 908, 261,
493`. Her er , og sifferområdet er , altså .
Kjør Radix-Sort og oppgi arrayet etter hver runde.
| Runde | Sorterer på | Array etter runden |
|---|---|---|
| 1 | enerne (siffer 1) | 261, 482, 493, 175, 175, 908, 439 |
| 2 | tierne (siffer 2) | 908, 439, 261, 175, 175, 482, 493 |
| 3 | hundrerne (siffer 3) | 175, 175, 261, 439, 482, 493, 908 |
Sluttilstanden:
175, 175, 261, 439, 482, 493, 908.Følg de to like numrene 175 for å se stabiliteten arbeide: de har identiske
siffer hele veien og bytter derfor aldri innbyrdes plass. Følg heller 482 og
493. Etter runde 1 ligger 493 før 482 (3 er mindre enn 2? nei — 482 slutter på
2 og 493 på 3, så 482 kommer først). Etter runde 2 avgjør tierne: 8 mot 9, og
482 blir liggende først. Etter runde 3 er hundrerne like, 4 mot 4 — og da er
det stabiliteten som bevarer resultatet fra runde 2.
Det siste er hele poenget: i den avgjørende runden gjør delsorteringen
ingenting med disse to, og likevel er de riktig ordnet. Hadde delsorteringen
vært ustabil, kunne den snudd dem.
Kjøretid her: , altså konstant
arbeid per element per siffer.
En student foreslår å bruke Quicksort som delsortering inne i Radix-Sort,
«siden den er raskere i praksis enn Counting-Sort».
Vil Radix-Sort da sortere riktig? Svar ja eller nei, og begrunn.
Du skal sortere nøkler som hver har siffer i tallsystem med grunntall
.
a) Oppgi kjøretiden til Radix-Sort og til Merge-Sort på denne
inputen.
b) Finn en betingelse på og som gjør Radix-Sort asymptotisk
raskest, og forklar hva den betyr i praksis.
Bucket-Sort (~8 min)
Den tredje lineære sorteringen stiller en helt annen type krav: ikke til
nøklenes form, men til deres fordeling.
Ideen er å dele verdiområdet i like store intervaller — bøtter — legge hvert
element i sin bøtte, sortere hver bøtte for seg med Insertion-Sort, og
skjøte bøttene sammen i rekkefølge.
fordeler elementer i bøtter etter verdi, sorterer hver bøtte medInsertion-Sort, og setter bøttene sammen i rekkefølge.
Kjøretid forventet, verste.
Krever at inputen er tilnærmet jevnt fordelt over verdiområdet. Da havner
det i gjennomsnitt ett element i hver bøtte, og Insertion-Sort gjør konstant
arbeid per bøtte. Havner alle elementene i samme bøtte, degenererer algoritmen
til én stor Insertion-Sort.
Ta stilling til hver av påstandene:
a) Bucket-Sort er i verste tilfelle.
b) Counting-Sort krever at nøklene er heltall.
c) Radix-Sort kan bruke Counting-Sort som delsortering.
Hvorfor grensen ikke er brutt (~8 min)
Et rimelig spørsmål på dette punktet: kap. 2.1 beviste
at ingen sortering kan komme under . Nå har vi tre som er
lineære. Er beviset galt?
Nei — og det er verdt å ha svaret klart, fordi spørsmålet stilles direkte på
eksamen.
altså de som avgjør rekkefølgen utelukkende ved å spørre «er ?».
Beviset er et beslutningstre der hver node er én binær sammenligning. En
algoritme som slår opp direkte på nøkkelverdien, har ikke et slikt tre — den
gjør et valg blant muligheter i ett steg, ikke blant 2.
Prisen er antagelser om nøklene. Counting-Sort krever heltall i et lite
område, Radix-Sort krever få siffer, Bucket-Sort krever jevn fordeling.
Sammenligningssorteringene krever ingenting.
Fire oppdrag. For hvert av dem: hvilken sortering fra Del 2 velger du, og
hvilken kjøretid får du?
a) 10 millioner eksamensresultater, hver et heltall fra 0 til 100.
b) 500 flyttallsmålinger fra en sensor, jevnt fordelt i .
c) 1 million navn, sortert alfabetisk, der lik nøkkel må beholde
rekkefølgen.
d) 200 000 vilkårlige heltall uten kjent øvre grense, og du må garantere
kjøretiden.
Counting-Sort med . Her er konstant og langt mindre enn, så . Ti millioner sammenligningsbaserte
operasjoner à er sløsing når du kjenner området.
b) Bucket-Sort. Jevn fordeling er nettopp forutsetningen, og forventet
kjøretid er . Merk «forventet»: garantien er .
c) Merge-Sort. Den er stabil og garantert. Radix-Sort
på strenger er også mulig, men krever lik lengde eller polstring, og
oppgaven gir ingen grunn til å tro at er liten.
d) Merge-Sort eller Heapsort, begge garantert.
Ikke Quicksort: den gir bare en forventning. Ikke Counting-Sort: uten
kjent øvre grense på nøklene finnes ingen .
Svarformen på eksamen er algoritmenavnet, kjøretiden og betingelsen som
gjør valget gyldig — én linje per deloppgave. Ikke en gjennomgang av hvordan
algoritmen virker.
En kandidat skriver: «Counting-Sort er , og er derfor alltid
raskere enn Merge-Sort.»
Er utsagnet riktig? Svar ja eller nei, og begrunn.
Et bibliotek har utlån registrert med to felter: dato (et heltall fra 1 til
365) og filialnummer (et heltall fra 1 til 20). Utlånene skal sorteres etter
filial, og innenfor hver filial etter dato.
Beskriv en algoritme som bruker tid.
De to første koster hele oppgaven.
- Å oppgi Counting-Sort som uten betingelsen. Dette er felle
#9 — feil kjøretidsfakta. Riktig er , og lineær bare når
. Betingelsen er en del av svaret.
- Å tro at Radix-Sort virker med en ustabil delsortering. Stabiliteten
er ikke en bonus, den er forutsetningen for at arbeidet fra de foregående
sifferrundene overlever.
- Å sortere sifrene fra det mest signifikante og nedover. Den varianten
finnes, men den er en annen algoritme med en annen struktur.
Radix-Sort slik den er definert her, går fra minst signifikante siffer.
- Å blande stabilitet med «sorterer riktig». En ustabil sortering sorterer
helt riktig — den gir bare ingen garanti om rekkefølgen mellom like nøkler.
- Å tro at binder disse algoritmene. Grensen gjelder bare
sammenligningsbaserte sorteringer. Blir du spurt om hvorfor det ikke er en
selvmotsigelse, er svaret at algoritmene her aldri sammenligner to elementer
med hverandre.
- Å oppgi Bucket-Sort som garantert. Den er
forventet, under antagelsen om jevn fordeling. Verste tilfelle er
.
Og den gjennomgående: å oppgi mer enn det oppgaven ber om. Ber en oppgave
om telletabellen etter oppsummeringen, er det den ene raden som er svaret —
ikke hele kjøringen.
Kjøretidene samlet
Dette er kapitlets puggeflate. Eksamen er hjelpemiddelfri, så tabellen må ligge
i hodet — og betingelsen i høyre kolonne hører med til svaret.
| Algoritme | Kjøretid | Stabil? | På stedet? | Krav |
|---|---|---|---|---|
Counting-Sort | ja | nei | heltallsnøkler i ; lineær bare når | |
Radix-Sort | ja | nei | siffer i området ; krever stabil delsortering | |
Bucket-Sort | forventet, verste | ja | nei | tilnærmet jevn fordeling over verdiområdet |
Insertion-Sort | beste, verste | ja | ja | ingen — brukes som delsortering i bøttene |
Merge-Sort | ja | nei | ingen — sammenligningsbasert, derfor bundet av grensen | |
Quicksort | forventet | nei | ja | ingen; kan ikke brukes som delsortering i Radix-Sort |
Én presisering som er verdt å ta med seg. Alle de tre lineære sorteringene
er stabile, og alle tre trenger hjelpeplass. Det er ingen tilfeldighet: de
plasserer elementene direkte på beregnede plasser i et outputarray, og en slik
plassering er både lett å gjøre stabil og umulig å gjøre på stedet.
Begrepsbank
Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp
har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet
gjelder kjernestoffet.
sorterer heltallsnøkler i ved å telle forekomster, summere
telletabellen opp kumulativt, og plassere elementene bakfra i et outputarray.
Kjøretid — lineær bare når .
Stabil, men ikke på stedet. Stabiliteten kommer av at siste løkke går fran ned til 1.
sorterer -sifrede nøkler ved å kjøre én stabil delsortering per siffer, fra
det minst signifikante og oppover.
Kjøretid med Counting-Sort som delsortering.
Krever at delsorteringen er stabil. Uten stabilitet går rekkefølgen fra de
foregående sifferrundene tapt, og resultatet blir galt.
fordeler elementene i bøtter etter verdi, sorterer hver bøtte medInsertion-Sort og skjøter bøttene sammen.
Kjøretid forventet, verste.
Krever tilnærmet jevn fordeling over verdiområdet. Havner alt i én bøtte,
er den bare en Insertion-Sort med ekstra trinn.
like nøkler beholder sin innbyrdes rekkefølge fra input til output.
Stabile: Insertion-Sort, Merge-Sort, Counting-Sort, Radix-Sort,Bucket-Sort. Ustabile: Quicksort, Heapsort.
Egenskapen er en forutsetning, ikke en bekvemmelighet, når du sorterer
etter én nøkkel om gangen.
hjelpearrayet i Counting-Sort, med én celle per mulig nøkkelverdi.
Etter tellingen inneholder C[i] antall elementer med nøkkel nøyaktig ;
etter oppsummeringen antall elementer med nøkkel høyst .
Kontrollen: siste celle skal være etter oppsummeringen.
løkka C[i] = C[i] + C[i-1] som gjør telletabellen om til en tabell over
plasseringer.
Etter den er C[i] den siste ledige plassen i outputarrayet for nøkkelen
.
Koster og er grunnen til at står i kjøretiden.
plasseringsløkka i Counting-Sort teller fra n ned til 1.
Da blir det siste elementet med en gitt nøkkel plassert på den siste av
plassene som hører til den nøkkelen — og rekkefølgen mellom like nøkler blir
bevart.
Snus løkka, sorterer algoritmen fortsatt riktig, men den mister
stabiliteten og kan ikke lenger brukes i Radix-Sort.
kravet som gjør Counting-Sort lineær.
Er nøkkelområdet mye større enn antall elementer, dominerer og algoritmen
blir tregere enn Merge-Sort.
Betingelsen skal stå i svaret. «Counting-Sort er » uten
forbehold er galt.
desimaltall , for byte-vis lesing .
Kjøretiden viser avveiningen: større grunntall gir færre
siffer, men dyrere delsortering.
Radix-Sort slår Merge-Sort når — typisk når
er konstant og .
rekkefølgen Radix-Sort behandler sifrene i: enerne før tierne før hundrerne.
Etter runde er arrayet sortert etter de minst signifikante sifrene
under ett.
Motsatt rekkefølge gir en helt annen algoritme med rekursiv oppdeling —
den er ikke det denne boka mener med Radix-Sort.
en sortering som bruker selve nøkkelverdien — som indeks eller som siffer — i
stedet for å sammenligne elementpar.
De tre i dette kapitlet er alle av denne typen.
Derfor gjelder ikke -grensen for dem. Prisen er at de
stiller krav til nøklene.
beslutningstre-argumentet forutsetter at hvert steg er én binær sammenligning
mellom to elementer.
Counting-Sort velger blant muligheter i ett steg ved å slå opp på
nøkkelen, og har derfor ikke et binært beslutningstre med løv.
Grensen er ikke brutt — den gjelder en annen klasse av algoritmer.
arrayet Counting-Sort skriver det sorterte resultatet til.
Elementene plasseres direkte på beregnede indekser, ikke ved å bytte om i
inputen.
Derfor er algoritmen ikke på stedet: den trenger ekstra plass i
tillegg til telletabellens .
elementet bærer med seg.
En pakke har rutenummeret som nøkkel og avsender, vekt og innhold som
satellittdata.
Stabilitet er bare synlig når satellittdata finnes — to like tall er ikke
til å skille fra hverandre.
oppgavetypen der du skal forklare et begrep presist og kort.
Svarformen er én til to setninger med hovedpoenget først; eksempler og
presiseringer kommer etter.
Delvis uttelling belønner hovedpoenget, så begynn med det som faktisk
definerer begrepet.
oppgavetypen der du oppgir kjøretiden til en navngitt algoritme.
Svarformen er ett uttrykk i det strammeste som er riktig — og for algoritmene i
dette kapitlet hører betingelsen med.
, ikke , med mindre oppgaven har sagt at
.
oppgavetypen der du får en påstand og skal ta stilling til den.
Svarformen er ja eller nei først, deretter én presis setning.
Et konkret motbevis holder når svaret er nei — én input der påstanden
svikter.
sorteringen Radix-Sort kaller én gang per siffer, og sorteringenBucket-Sort kjører inne i hver bøtte.
I Radix-Sort må den være stabil og bør være lineær — derfor Counting-Sort.
I Bucket-Sort brukes Insertion-Sort, fordi bøttene forventes å være små.
Valget av delsortering er en del av algoritmen, ikke en detalj.
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.
Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Norges teknisk-naturvitenskapelige universitet. Dette er ikke offisielt studiemateriell. Les mer.