Tilbake
2.2

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.

45 min
7 oppgaver
Lineær sorteringstabilitet
Din fremgang i kapitlet
0 / 7 oppgaver
Kapitlets plass i kurset

Forkunnskaper

- kap. 2.1 — sammenligningsbaserte sorteringer. Fra det
kapitlet: Merge-Sort er Θ(nlgn)\Theta(n\lg n) og stabil, Quicksort er
Θ(nlgn)\Theta(n\lg n) forventet og ustabil, og ingen sortering som bare
sammenligner elementpar kan komme under Ω(nlgn)\Omega(n\lg n) i verste tilfelle.
Det siste er poenget dette kapitlet spiller mot.
- kap. 1.1 — de asymptotiske symbolene. Du trenger
Θ\Theta og OO, og at O(n)O(n) brukt om en parameter (som kk) betyr «vokser
ikke raskere enn nn».

Vil du se sortering konkret i kode først:
Sortering: boblesortering, .sort() og gitt sort_list.

Notasjons- og pseudokodeliste

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å.

Stabil sortering

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.

📜Pseudokode-kontrakt: `Counting-Sort`
Antagelser om representasjon. Arrayet er A[1..n], indeks fra 1. Hver
nøkkel A[j] er et heltall i området [0..k][0..k], og kk er kjent på
forhånd. Outputen skrives til et eget array B[1..n]; sorteringen er ikke
på stedet.

Prebetingelse: alle nøkler er heltall i [0..k][0..k].
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 ii — og det er nøyaktig den siste ledige
plassen i B for nøkkelen ii.

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: Θ(k)\Theta(k) til å nullstille C, Θ(n)\Theta(n) til tellingen,
Θ(k)\Theta(k) til oppsummeringen og Θ(n)\Theta(n) til plasseringen, altså
Θ(n+k)\Theta(n+k). Det er lineært bare når k=O(n)k = O(n). Med nøkler opp til
k=n2k = n^2 er algoritmen Θ(n2)\Theta(n^2), og da er Merge-Sort langt bedre.

✏️Eksempel 1: `Counting-Sort` med merkede like nøkler

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 n=5n = 5 og k=3k = 3.

Kjør Counting-Sort og oppgi telletabellen C etter tellingen, C etter
oppsummeringen, og det ferdige B.

Telletabellen C[0..3] etter tellingen: 0, 2, 2, 1

Ingen 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 ii». 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:

StegjElementPlass i BC etter stegetB etter steget
151e20, 1, 4, 5_, 1, _, _, _
243d50, 1, 4, 4_, 1, _, _, 3
332c40, 1, 3, 4_, 1, _, 2, 3
421b10, 0, 3, 41, 1, _, 2, 3
512a30, 0, 2, 41, 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ør
2c — fordi løkka gikk bakfra, ble 2c plassert først, på den siste ledige
toerplassen. 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.

📝Oppgave 1

(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.

📝Oppgave 2
Eksamensnivå, sjanger C

Et array A = 4, 1, 3, 4, 0, 2, 3, 4 skal sorteres med Counting-Sort med
k=4k = 4.

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 k=999999k = 999\,999, og telletabellen blir større enn hele arkivet.

Radix-Sort løser det ved å ta ett siffer om gangen. Nøkkelen har dd 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.

📜Pseudokode-kontrakt: `Radix-Sort`
Antagelser om representasjon. Arrayet er A[1..n], indeks fra 1. Hver
nøkkel har nøyaktig dd siffer, der siffer 1 er det minst signifikante.
Sifrene ligger i et lite område [0..k][0..k] — for desimaltall er k=9k = 9.

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: dd runder à én Counting-Sort med sifferområde [0..k][0..k], altså
Θ(d(n+k))\Theta(d(n+k)). Med faste dd og k=O(n)k = O(n) er dette Θ(n)\Theta(n).

✏️Eksempel 2: `Radix-Sort` siffer for siffer

Sju tresifrede lagernumre skal sorteres: `A = 482, 175, 439, 175, 908, 261,
493`. Her er n=7n = 7, d=3d = 3 og sifferområdet er [0..9][0..9], altså k=9k = 9.

Kjør Radix-Sort og oppgi arrayet etter hver runde.

RundeSorterer påArray etter runden
1enerne (siffer 1)261, 482, 493, 175, 175, 908, 439
2tierne (siffer 2)908, 439, 261, 175, 175, 482, 493
3hundrerne (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: Θ(d(n+k))=Θ(3(7+10))\Theta(d(n+k)) = \Theta(3 \cdot (7+10)), altså konstant

arbeid per element per siffer.

📝Oppgave 3
Eksamensnivå, sjanger F

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.

📝Oppgave 4
Eksamensnivå, sjanger E

Du skal sortere nn nøkler som hver har dd siffer i tallsystem med grunntall
kk.

a) Oppgi kjøretiden til Radix-Sort og til Merge-Sort på denne
inputen.
b) Finn en betingelse på dd og kk 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 nn 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.

`Bucket-Sort`

fordeler nn elementer i nn bøtter etter verdi, sorterer hver bøtte med
Insertion-Sort, og setter bøttene sammen i rekkefølge.

Kjøretid Θ(n)\Theta(n) forventet, Θ(n2)\Theta(n^2) 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.

📝Oppgave 5
Eksamensnivå, sjanger F

Ta stilling til hver av påstandene:

a) Bucket-Sort er Θ(n)\Theta(n) 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 Ω(nlgn)\Omega(n\lg n). 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.

Hvorfor lineær sortering ikke bryter grensen
Ω(nlgn)\Omega(n\lg n)-grensen gjelder bare sammenligningsbaserte sorteringer,
altså de som avgjør rekkefølgen utelukkende ved å spørre «er aiaja_i \le a_j?».

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 k+1k+1 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.

✏️Eksempel 3: Hvilken sortering velger du?

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 [0,1)[0, 1).
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.

a) Counting-Sort med k=100k = 100. Her er kk konstant og langt mindre enn
nn, så Θ(n+k)=Θ(n)\Theta(n+k) = \Theta(n). Ti millioner sammenligningsbaserte
operasjoner à lgn23\lg n \approx 23 er sløsing når du kjenner området.

b) Bucket-Sort. Jevn fordeling er nettopp forutsetningen, og forventet
kjøretid er Θ(n)\Theta(n). Merk «forventet»: garantien er Θ(n2)\Theta(n^2).

c) Merge-Sort. Den er stabil og Θ(nlgn)\Theta(n\lg n) garantert. Radix-Sort
på strenger er også mulig, men krever lik lengde eller polstring, og
oppgaven gir ingen grunn til å tro at dd er liten.

d) Merge-Sort eller Heapsort, begge Θ(nlgn)\Theta(n\lg n) garantert.
Ikke Quicksort: den gir bare en forventning. Ikke Counting-Sort: uten
kjent øvre grense på nøklene finnes ingen kk.

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.

📝Oppgave 6
Eksamensnivå, sjanger F

En kandidat skriver: «Counting-Sort er Θ(n)\Theta(n), og er derfor alltid
raskere enn Merge-Sort

Er utsagnet riktig? Svar ja eller nei, og begrunn.

📝Oppgave 7
Eksamensnivå, sjanger H

Et bibliotek har nn 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 Θ(n)\Theta(n) tid.

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.

AlgoritmeKjøretidStabil?På stedet?Krav
Counting-SortΘ(n+k)\Theta(n+k)janeiheltallsnøkler i [0..k][0..k]; lineær bare når k=O(n)k = O(n)
Radix-SortΘ(d(n+k))\Theta(d(n+k))janeidd siffer i området [0..k][0..k]; krever stabil delsortering
Bucket-SortΘ(n)\Theta(n) forventet, Θ(n2)\Theta(n^2) verstejaneitilnærmet jevn fordeling over verdiområdet
Insertion-SortΘ(n)\Theta(n) beste, Θ(n2)\Theta(n^2) verstejajaingen — brukes som delsortering i bøttene
Merge-SortΘ(nlgn)\Theta(n\lg n)janeiingen — sammenligningsbasert, derfor bundet av grensen
QuicksortΘ(nlgn)\Theta(n\lg n) forventetneijaingen; 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.

`Counting-Sort`

sorterer heltallsnøkler i [0..k][0..k] ved å telle forekomster, summere
telletabellen opp kumulativt, og plassere elementene bakfra i et outputarray.

Kjøretid Θ(n+k)\Theta(n+k) — lineær bare når k=O(n)k = O(n).

Stabil, men ikke på stedet. Stabiliteten kommer av at siste løkke går fra
n ned til 1.

`Radix-Sort`

sorterer dd-sifrede nøkler ved å kjøre én stabil delsortering per siffer, fra
det minst signifikante og oppover.

Kjøretid Θ(d(n+k))\Theta(d(n+k)) 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.

`Bucket-Sort`

fordeler elementene i nn bøtter etter verdi, sorterer hver bøtte med
Insertion-Sort og skjøter bøttene sammen.

Kjøretid Θ(n)\Theta(n) forventet, Θ(n2)\Theta(n^2) verste.

Krever tilnærmet jevn fordeling over verdiområdet. Havner alt i én bøtte,
er den bare en Insertion-Sort med ekstra trinn.

Stabilitet

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.

Telletabellen `C`

hjelpearrayet i Counting-Sort, med én celle per mulig nøkkelverdi.

Etter tellingen inneholder C[i] antall elementer med nøkkel nøyaktig ii;
etter oppsummeringen antall elementer med nøkkel høyst ii.

Kontrollen: siste celle skal være nn etter oppsummeringen.

Den kumulative 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
ii.

Koster Θ(k)\Theta(k) og er grunnen til at kk står i kjøretiden.

Hvorfor siste løkke går bakfra

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.

Betingelsen k=O(n)k = O(n)

kravet som gjør Counting-Sort lineær.

Er nøkkelområdet mye større enn antall elementer, dominerer kk og algoritmen
blir tregere enn Merge-Sort.

Betingelsen skal stå i svaret. «Counting-Sort er Θ(n)\Theta(n)» uten
forbehold er galt.

Siffer og grunntall i `Radix-Sort`
dd er antall siffer per nøkkel, og kk er største sifferverdi — for
desimaltall k=9k = 9, for byte-vis lesing k=255k = 255.

Kjøretiden Θ(d(n+k))\Theta(d(n+k)) viser avveiningen: større grunntall gir færre
siffer, men dyrere delsortering.

Radix-Sort slår Merge-Sort når d(n+k)=o(nlgn)d(n+k) = o(n\lg n) — typisk når dd
er konstant og k=O(n)k = O(n).

Minst signifikante siffer først

rekkefølgen Radix-Sort behandler sifrene i: enerne før tierne før hundrerne.

Etter runde ii er arrayet sortert etter de ii 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.

Ikke-sammenligningsbasert sortering

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 Ω(nlgn)\Omega(n\lg n)-grensen for dem. Prisen er at de
stiller krav til nøklene.

Hvorfor Ω(nlgn)\Omega(n\lg n) ikke gjelder her

beslutningstre-argumentet forutsetter at hvert steg er én binær sammenligning
mellom to elementer.

Counting-Sort velger blant k+1k+1 muligheter i ett steg ved å slå opp på
nøkkelen, og har derfor ikke et binært beslutningstre med n!n! løv.

Grensen er ikke brutt — den gjelder en annen klasse av algoritmer.

Outputarrayet `B`

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 Θ(n)\Theta(n) ekstra plass i
tillegg til telletabellens Θ(k)\Theta(k).

Nøkkel og satellittdata
nøkkelen er verdien det sorteres etter; satellittdataene er alt annet
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.

Sjanger D — definisjon med egne ord

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.

Sjanger E — kjøretidskunnskap

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.

Θ(n+k)\Theta(n+k), ikke Θ(n)\Theta(n), med mindre oppgaven har sagt at
k=O(n)k = O(n).

Sjanger F — «stemmer dette?»

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.

Delsortering

sorteringen Radix-Sort kaller én gang per siffer, og sorteringen
Bucket-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.

Repetisjonsoppgaver

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.