TDT4120 Algoritmer og datastrukturer Interaktiv pensumguide · H2026
Fremgang
0/15

01

Algoritmer og kompleksitet

Kjernepensum

To metoder kan løse samme problem og likevel ikke være i samme klasse. Skal vi pare givere og mottakere i et donasjonsregister, kan vi prøve alle koblinger etter tur, eller vi kan kjøre Ford-Fulkerson. Med fire personer på hver side har opptellingen 24 permutasjoner å gå gjennom, mens Ford-Fulkerson bruker rundt 36 operasjoner. Opptellingen ser billigst ut. Dobler vi til åtte, står det 40 320 mot 70.

Problem, instans og maskin

Et problem er en relasjon mellom input og output. Sortering krever at outputen er en stigende omordning av inputen, og sier ingenting om hvilke tall som kommer inn. En instans er én bestemt input, for eksempel 6,8,3,5\langle 6, 8, 3, 5 \rangle. Problemstørrelsen nn er plassen en instans trenger, som regel antall elementer i den.

Kjøretiden er en funksjon av nn. For at den funksjonen skal si noe som gjelder på andre maskiner enn din egen, regner vi i RAM-modellen: aritmetikk, sammenligning, hopp og oppslag i én celle koster én tidsenhet hver, og instruksjonene utføres etter hverandre. Heltallene har begrenset bredde, om lag clgnc \lg n bits, akkurat nok til å indeksere hele inputen. Vi kan regne med større tall enn det, slik kryptografi gjør, men da holder ikke antagelsen om konstant tid per operasjon lenger.

Vekstrater

Anta ett mikrosekund per operasjon og hundre år til rådighet. Da rekker vi disse problemstørrelsene:

Alt på formen nkn^k kaller vi polynomisk.
KjøretidNavnStørste nn på hundre år
lgn\lg nlogaritmisk23,210152^{3{,}2 \cdot 10^{15}}
nnlineær3,210153{,}2 \cdot 10^{15}
nlgnn \lg nlinearitmisk6,910136{,}9 \cdot 10^{13}
n2n^2kvadratisk5600000056\,000\,000
n3n^3kubisk146000146\,000
2n2^neksponentiell5151
n!n!faktoriell1717
Logaritmisk plott av antall operasjoner mot problemstørrelse for n, n lg n, n i andre, n i tredje, to opphøyd i n og n fakultet. De to siste stiger langt raskere enn de fire første.
Figur 1. Antall operasjoner mot nn. Y-aksen er logaritmisk, så avstanden mellom to kurver er faktoren mellom dem.

En raskere maskin hjelper mindre enn en bedre algoritme. Kjøper du en maskin som er tusen ganger raskere, flytter du de 17 faktorielle elementene til 20. Bytter du til en kvadratisk algoritme, flytter du dem til titalls millioner.

Asymptotisk notasjon

Vi vil sammenligne vekst uten å måtte telle enkeltoperasjoner. Asymptotisk notasjon gjør det ved å se bort fra konstantfaktorer og lavere ordens ledd.

Grensen trenger bare å holde fra en eller annen n0n_0 og oppover, og gg kan skaleres med en konstant først. Derfor er 3n2+100n=Θ(n2)3n^2 + 100n = \Theta(n^2). For stor nok nn er 100n100n lite mot det kvadratiske leddet, og faktoren 33 går inn i cc.

Tre kurver mot n. Fra n lik hundre ligger f mellom n i andre og to n i andre. Til venstre for hundre ligger f over begge.
Figur 2. f(n)=n2+100nf(n) = n^2 + 100n mot g(n)=n2g(n) = n^2. Den nedre grensen holder med c1=1c_1 = 1 for alle nn, den øvre først med c2=2c_2 = 2 fra n0=100n_0 = 100. Til venstre for n0n_0 bryter ff gjennom, og det er tillatt. Begge sammen gir Θ(n2)\Theta(n^2).

De to strenge operatorene

OO sier at ff vokser høyst så raskt som gg, og tillater at de to vokser like raskt. 2n2=O(n2)2n^2 = O(n^2) og 2n=O(n2)2n = O(n^2) er begge sanne, men bare den første er stram. For en øvre grense som ikke er stram bruker vi oo, og der må f(n)<cg(n)f(n) < c\,g(n) holde for enhver c>0c > 0, ikke bare for én eneste. Det er en langt sterkere påstand, og den er ekvivalent med at f(n)/g(n)f(n)/g(n) går mot null. Så 2n=o(n2)2n = o(n^2), mens 2n2o(n2)2n^2 \ne o(n^2).

ω\omega er det samme speilvendt: en nedre grense som ikke er stram, med cg(n)<f(n)c\,g(n) < f(n) for enhver cc. Da vokser f(n)/g(n)f(n)/g(n) over alle grenser, og f(n)=o(g(n))f(n) = o(g(n)) er nøyaktig det samme som g(n)=ω(f(n))g(n) = \omega(f(n)).

Alle fem oppfører seg som sammenligning av to tall, og det er den raskeste veien til å huske dem:

Analogien er lærebokas egen (CLRS §3.2). Den holder for transitivitet og for det meste ellers, med ett unntak: to tall kan alltid sammenlignes, men to funksjoner kan mangle et forhold i det hele tatt. nn og n1+sinnn^{1+\sin n} er verken OO eller Ω\Omega av hverandre.
Skriver viLeses somGrensen f(n)/g(n)f(n)/g(n)Som tall
f=O(g)f = O(g)vokser høyst så raskt som ggendeligaba \le b
f=Ω(g)f = \Omega(g)vokser minst så raskt som ggstørre enn nullaba \ge b
f=Θ(g)f = \Theta(g)vokser like raskt som ggen konstant, verken 00 eller \inftya=ba = b
f=o(g)f = o(g)vokser strengt saktere enn gg00a<ba < b
f=ω(g)f = \omega(g)vokser strengt raskere enn gg\inftya>ba > b

Notasjonen dukker også opp midt inne i et uttrykk. Da står den for en ukjent funksjon av den oppgitte ordenen, slik at n+O(n)n + O(n) betyr «nn pluss en eller annen funksjon som er O(n)O(n)». Slike ligninger leses fra venstre mot høyre, og høyresiden må være minst like generell som venstresiden. Derfor stemmer n+O(n)=O(n)n + O(n) = O(n), mens O(n)=n+O(n)O(n) = n + O(n) er galt.

Regneeksempel Forenkle O(na)+Ω(nb)+Θ(nc)O(n^a) + \Omega(n^b) + \Theta(n^c)

Finn det enkleste ekvivalente uttrykket, uten å tape presisjon.

Vis løsning Skjul løsning

Ta de øvre og de nedre grensene hver for seg. O(na)O(n^a) gir en øvre grense nan^a og ingen nedre, så den nedre er 00. Ω(nb)\Omega(n^b) gir en nedre grense nbn^b og ingen øvre. Θ(nc)\Theta(n^c) gir begge, ncn^c.

Summen av de nedre grensene er 0+nb+nc0 + n^b + n^c. Summen av de øvre er ubegrenset, fordi Ω(nb)\Omega(n^b) ikke setter noen tak. Vi står da igjen med en nedre grense alene, og en nedre grense alene skrives med Ω\Omega. Regelen står som én rad i Formelsamlingen.

Svar
Ω(nb+nc)\Omega(n^b + n^c)

Grenser og tilfeller

For hver nn finnes det mange instanser, og de koster ulikt, så størrelsen alene bestemmer ikke kjøretiden. Beste tilfelle er den billigste instansen av størrelse nn, verste tilfelle den dyreste, og gjennomsnittstilfellet er middelet over alle instansene av den størrelsen. Hvert av de tre er en egen funksjon av nn.

Beste, verste og gjennomsnittlige tilfelle

Hvert punkt er én instans, kjørt gjennom Insertion-Sort og talt opp. De mørke punktene er den billigste og den dyreste. Velg tilfelle og grense, og se at alle ni kombinasjonene sier noe sant.

Hvilket tilfelle vi snakker om, og hvor stramt vi begrenser det, er to uavhengige valg. Alle fem operatorene kan brukes på alle tre funksjonene:

Insertion-Sort, stramme grenser i hver rute. Vet vi ikke hvilken instans vi får, må den øvre grensen dekke det verste og den nedre det beste, og da finnes det ingen Θ\Theta.
TilfelleOOΩ\OmegaΘ\Theta
BesteO(n)O(n)Ω(n)\Omega(n)Θ(n)\Theta(n)
GjennomsnittligeO(n2)O(n^2)Ω(n2)\Omega(n^2)Θ(n2)\Theta(n^2)
VersteO(n2)O(n^2)Ω(n2)\Omega(n^2)Θ(n2)\Theta(n^2)
Ukjent inputO(n2)O(n^2)Ω(n)\Omega(n)ingen

Utelater du tilfellet, gjelder påstanden alle instanser. «Insertion-Sort bruker O(n2)O(n^2)» er da riktig, og «Ω(n)\Omega(n)» også, mens «Θ(n2)\Theta(n^2)» er galt: på en sortert tabell bruker den Θ(n)\Theta(n).

Løkkeinvarianter

En løkke gjør det samme trinnet om og om igjen, og korrekthetsbeviset for den er et induksjonsbevis. Vi trenger en påstand om tilstanden som holder ved starten av hver iterasjon.

Initialisering er grunntilfellet og vedlikehold er induksjonssteget. Terminering er det løkker har i tillegg: beviset er verdiløst om løkka aldri stopper.

Svak induksjon antar bare forrige trinn og passer til løkker. Sterk induksjon antar alle mindre tilfeller, og passer når en algoritme kaller seg selv på flere mindre instanser.

Insertion-Sort

Læreboka skriver algoritmen i pseudokode med indekser fra 1. Under står den i Python, med indekser fra 0, slik øvingen krever.

Invarianten er at A[0 : i] er sortert ved starten av hver iterasjon av for-løkka, og består av de elementene som lå der fra begynnelsen. Før første iterasjon er i=1i = 1, og utsnittet har ett element. Én iterasjon løfter ut A[i], skyver hvert større element i utsnittet ett hakk mot høyre og setter nøkkelen inn i den ledige plassen, slik at A[0 : i+1] er sortert. Når løkka stopper er utsnittet hele tabellen.

insertion_sort.py
def insertion_sort(A, n):
  for i in range(1, n):
      key = A[i]
      # Sett key inn i den sorterte delen A[0 : i]
      j = i - 1
      while j >= 0 and A[j] > key:
          A[j + 1] = A[j]
          j = j - 1
      A[j + 1] = key
  return A
Insertion-Sort
0 / 0

Grønt er den sorterte delen. Nøkkelen løftes ut i boksen over tabellen, den ledige plassen flytter seg mot venstre så lenge naboen er større, og nøkkelen settes inn når naboen ikke er det.

Den ytre løkka går n1n-1 ganger uansett input. Den indre gjør et sted mellom 0 og ii forskyvninger, og det er der forskjellen mellom tilfellene ligger. Er tabellen sortert fra før, er A[j] aldri større enn nøkkelen, while-testen slår feil med én gang, og vi gjør Θ(n)\Theta(n) arbeid totalt.

Regneeksempel Verste tilfelle for Insertion-Sort

Tabellen er sortert synkende, n,n1,,1\langle n, n-1, \ldots, 1 \rangle. Hvor mange forskyvninger gjør algoritmen?

Vis løsning Skjul løsning

I iterasjon ii er A[i] mindre enn hvert av de ii elementene foran seg, så nøkkelen må forbi dem alle. Det er ii forskyvninger. Lagt sammen over iterasjonene i=1,,n1i = 1, \ldots, n-1:

i=1n1i  =  n(n1)2\sum_{i=1}^{n-1} i \;=\; \frac{n(n-1)}{2}

Summen er trekanttallet, et halvt rektangel med sider nn og n1n-1. Uten konstantfaktoren og det lineære leddet står vi igjen med n2n^2.

Svar
n(n1)/2n(n-1)/2 forskyvninger, altså Θ(n2)\Theta(n^2)
Minnebruken er O(1)O(1) ut over selve tabellen, og like elementer beholder rekkefølgen sin.
TilfelleKjøretidInstans
BesteΘ(n)\Theta(n)sortert fra før, ingen forskyvninger
GjennomsnittligeΘ(n2)\Theta(n^2)tilfeldig rekkefølge, halve utsnittet i snitt
VersteΘ(n2)\Theta(n^2)sortert synkende

Sjekk deg selv

En algoritme har kjøretid beskrevet av Ω(n)+Θ(n2lgn)+O(n3)\Omega(n) + \Theta(n^2 \lg n) + O(n^3). Hva kan vi si om kjøretiden W(n)W(n) i verste tilfelle?
Oppdatert 21. august 2026 Foreslå endring