TDT4120 Algoritmer og datastrukturer Interaktiv pensumguide · H2026
Fremgang
0/14
04
Sortering

Rangering i lineær tid

Kjernepensum ~45 min lesing · Nedre grense · Lineær tid

Alle sorteringene vi har sett — Insertion-, Merge- og Quicksort — bestemmer rekkefølgen ved å sammenligne elementer. Det er ikke tilfeldig at ingen av dem slår nlgnn \lg n: det finnes en prinsipiell nedre grense for sammenligningsmodellen. Antar vi derimot mer om inputen, kan vi sortere i lineær tid.

Den nedre grensen for sammenligningssortering

Tenk på en sammenligningssortering som et beslutningstre: hver indre node er en sammenligning «aiaja_i \le a_j?» med to utganger, og hvert blad er én av de mulige sorterte rekkefølgene. Et binært tre med høyde hh har høyst 2h2^h blader, og vi trenger ett blad per mulig permutasjon — altså 2hn!2^h \ge n!. Løser vi for hh og bruker Stirlings approksimasjon, får vi worst-case-grensen.

Ω(nlgn)\Omega(n \lg n)

Grensen gjelder kun sammenligningsbaserte algoritmer. Merge-Sort og randomisert Quicksort treffer den (og er dermed asymptotisk optimale i denne modellen), mens de kvadratiske sorteringene ligger godt over.

Stabilitet

Stabilitet virker som en detalj, men den er selve forutsetningen for at Radix-Sort skal fungere. Insertion- og Merge-Sort er naturlig stabile; Selection- og Quicksort er det ikke (de flytter elementer over lange avstander).

To kvadratiske sammenligningssorteringer

For å se kontrasten til de lineære sorteringene er det nyttig å ha to enkle sammenligningssorteringer friskt i minne. Selection-Sort bygger en sortert prefiks ved gjentatte ganger å hente minimumet av den usorterte halen:

selection_sort.py
def selection_sort(a: list[int]) -> list[int]:
  n = len(a)
  for i in range(n):
      m = i
      for j in range(i + 1, n):
          if a[j] < a[m]:
              m = j
      a[i], a[m] = a[m], a[i]
  return a
Selection-Sort

Interaktiv visualisering krever JavaScript.

0 / 0

Det løpende minimumet er cyan, elementet som sammenlignes oransje, og byttet inn i den grønne prefiksen er aksentfarget. Merk at antallet sammenligninger er det samme uansett input.

Selection-Sort gjør alltid Θ(n2)\Theta(n^2) sammenligninger — den er ikke adaptiv. Bubble-Sort gjør i stedet lokale nabobytter, slik at det største usorterte elementet «bobler» til høyre i hver runde:

bubble_sort.py
def bubble_sort(a: list[int]) -> list[int]:
  n = len(a)
  for i in range(n):
      for j in range(n - i - 1):
          if a[j] > a[j + 1]:
              a[j], a[j + 1] = a[j + 1], a[j]
  return a
Bubble-Sort

Interaktiv visualisering krever JavaScript.

0 / 0

Nabopar sammenlignes (oransje) og byttes ved feil rekkefølge (aksent). Etter runde i er den grønne halen til høyre låst på plass.

AlgoritmeBestAverageWorstMinneStabil
Bubble-SortΘ(n)\Theta(n)Θ(n2)\Theta(n^2)Θ(n2)\Theta(n^2)O(1)O(1)ja
Insertion-SortΘ(n)\Theta(n)Θ(n2)\Theta(n^2)Θ(n2)\Theta(n^2)O(1)O(1)ja
Selection-SortΘ(n2)\Theta(n^2)Θ(n2)\Theta(n^2)Θ(n2)\Theta(n^2)O(1)O(1)nei
QuicksortΘ(nlgn)\Theta(n \lg n)Θ(nlgn)\Theta(n \lg n)Θ(n2)\Theta(n^2)O(lgn)O(\lg n)nei
Counting-SortΘ(n+k)\Theta(n+k)Θ(n+k)\Theta(n+k)Θ(n+k)\Theta(n+k)Θ(n+k)\Theta(n+k)ja
Sorteringene i kurset. De fire øverste er sammenligningsbaserte (Omega(nlgn)\\Omega(n \\lg n) worst-case); Counting-Sort omgår grensen ved å anta et lite nøkkelområde kk.

Lineær tid ved å anta mer

Counting-Sort sorterer heltall i et lite område {0,,k}\{0, \dots, k\} uten en eneste sammenligning: tell forekomstene av hver nøkkel, gjør tellingene om til posisjoner ved en prefikssum, og plasser hvert element direkte. Det koster Θ(n+k)\Theta(n + k), som er lineært når k=O(n)k = O(n). Ved å fylle output fra høyre mot venstre blir Counting-Sort stabil.

Radix-Sort sorterer flersifrede nøkler ett siffer av gangen, fra minst til mest signifikante siffer, med en stabil Counting-Sort som subrutine på hvert siffer. Her er stabiliteten ikke valgfri:

Bucket-Sort utnytter at input er tilnærmet jevnt fordelt: fordel elementene i nn bøtter etter verdi, sorter hver bøtte (typisk med Insertion-Sort), og les bøttene i rekkefølge. Med jevn fordeling blir hver bøtte liten i forventning, og den forventede kjøretiden blir Θ(n)\Theta(n).

Å finne det k-te minste — Select

Skal du bare ha det kk-te minste elementet (for eksempel medianen), trenger du ikke sortere hele tabellen. Randomized-Select gjenbruker Quicksorts partisjonering, men rekurserer bare på den ene siden der det kk-te elementet ligger. Det gir forventet lineær tid, Θ(n)\Theta(n) — vi sparer den logaritmiske faktoren fordi vi kaster bort den halvdelen vi ikke trenger.

Select (median-of-medians) velger pivoten smart nok til å garantere en balansert partisjon, og oppnår Θ(n)\Theta(n) worst-case. Begge brukes til å hente de kk minste elementene: finn grenseverdien på plass kk, og alt til venstre for den er svaret.

Sjekk deg selv

Hvorfor trenger Radix-Sort en stabil subrutine?