Tilbake
2.2

2.2 Effektive sorteringer og nedre grense

Merge, Heap og Quick sort som O(n log n)-sorteringer (Quick O(n²) verste) — hva de brukes til på Del 2 (sortér-så-skann), ikke håndkjøring.

50 min
8 oppgaver
Effektive sorteringernedre grense
Din fremgang i kapitlet
0 / 8 oppgaver

Forkunnskaper

- kap. 2.1 — stabilitet, in-place og de tre invariantene.
Alle begrepene brukes videre her uten ny forklaring.
- kap. 1.2 — løkketelling. Kjøretidene under regnes ut ved
å telle nivåer og arbeid per nivå, som er den samme tellingen.
- kap. 1.1OO, Ω\Omega og forskjellen mellom verste og
forventet tilfelle.

Vil du se boblesortering kjøre som ekte kode først:
Sortering: boblesortering, .sort() og gitt sort_list.

Notasjons- og pseudokodeliste

Løkke 1 — flettesortering og nivåtellingen (ca. 14 min)

Tenk deg to bunker med søknader, hver allerede sortert på dato. Å slå dem sammen
til én sortert bunke er lett: se på det øverste arket i hver bunke, ta det
eldste, gjenta. Du går gjennom hvert ark nøyaktig én gang.

Det er hele ideen bak flettesortering (merge sort). Del arrayet i to,
sortér hver halvdel — med samme metode — og flett de to sorterte halvdelene
sammen.

Det avgjørende for kjøretiden er ikke rekursjonen i seg selv, men hvor mange
ganger et array på nn elementer kan halveres før bitene har ett element
. Det
er log2n\log_2 n ganger. Og på hvert nivå gjør flettingen til sammen O(n)O(n)
arbeid, fordi hvert element passerer gjennom nøyaktig én fletting per nivå.

log2n\log_2 n nivåer, O(n)O(n) arbeid per nivå. Det gir O(nlogn)O(n \log n). Legg merke til
at dette er ren telling — vi setter ikke opp noen ligning.

Flettesortering

Splitter arrayet i to halvdeler, sorterer hver halvdel rekursivt og fletter de
to sorterte halvdelene sammen.

O(nlogn)O(n \log n) i alle tilfeller — beste, forventet og verste. Stabil, men
ikke in-place: flettingen trenger et hjelpearray på nn plasser.

Den brukes som referansealgoritme fordi den er den eneste av de tre effektive som
kombinerer garantert O(nlogn)O(n \log n) med stabilitet.

📜Pseudokode-kontrakt: `MergeSort` og `Merge`
Antagelser om representasjon. A er et array med nn sammenlignbare
elementer indeksert fra 0. Vi har lov til å opprette nye arrayer.

Prebetingelse: A inneholder nn elementer i vilkårlig rekkefølge.
Postbetingelse: returverdien er et nytt array med de samme elementene, sortert
stigende.

Procedure MergeSort(A)
  Input:  array A med n sammenlignbare elementer, indeksert fra 0
  Output: et sortert array med de samme elementene
  if A.length <= 1:
      return A
  m = A.length / 2
  venstre = MergeSort(A[0 .. m-1])
  hoyre   = MergeSort(A[m .. A.length-1])
  return Merge(venstre, hoyre)

Flettingen er den delen som gjør arbeidet, og den er verdt å kunne skrive:

Procedure Merge(V, H)
  Input:  to sorterte arrayer V og H
  Output: ett sortert array med alle elementene fra V og H
  ut = tomt array
  i = 0
  j = 0
  while i < V.length and j < H.length:
      if V[i] <= H[j]:
          legg V[i] bakerst i ut
          i = i + 1
      else:
          legg H[j] bakerst i ut
          j = j + 1
  legg resten av V (fra i) bakerst i ut
  legg resten av H (fra j) bakerst i ut
  return ut

Grunnideen i én setning: to sorterte biter kan flettes ved å alltid ta det
minste av de to første elementene, fordi det ikke kan finnes noe mindre lenger
bak i en sortert bit.

Kjøretid: Merge går gjennom hvert element én gang, altså O(m)O(m) for samlet
lengde mm. Splittingen gir log2n\log_2 n nivåer, og på hvert nivå flettes til
sammen nn elementer. Totalt O(nlogn)O(n \log n), i alle tilfeller.

Stabiliteten ligger i ett tegn: V[i] <= H[j]. Fordi venstre vinner ved
likhet, beholder like elementer sin innbyrdes rekkefølge. Bytter du til <,
slutter flettesortering å være stabil.

✏️Eksempel 1: Flettesortering nivå for nivå

Kjør MergeSortA = [8, 3, 5, 1, 9, 2]. Vis splittefasen og flettefasen,
og tell antall sammenligninger.

Splittefasen — hvert nivå halverer bitene:

NivåBiter
0[8, 3, 5, 1, 9, 2]
1[8, 3, 5] [1, 9, 2]
2[8] [3, 5] [1] [9, 2]
3[3] [5] [9] [2]

Flettefasen — nedenfra og opp:
NivåVenstreHøyreFlettetSammenligninger
2[3][5][3, 5]1
2[9][2][2, 9]1
1[8][3, 5][3, 5, 8]2
1[1][2, 9][1, 2, 9]1
0[3, 5, 8][1, 2, 9][1, 2, 3, 5, 8, 9]5

Sluttilstand: [1, 2, 3, 5, 8, 9]. I alt 10 sammenligninger.
Sammenlign med kap. 2.1: de kvadratiske sorteringene
brukte 12 til 15 sammenligninger på nøyaktig det samme arrayet. Med seks

elementer er forskjellen liten. Med en million elementer er den ca. 20 millioner

mot ca. 500 milliarder.
Kontrollregning av nivåtellingen. Arrayet har 6 elementer, og log26\log_2 6
ligger mellom 2 og 3 — vi fikk tre flettenivåer, som stemmer. På hvert nivå ble
det flettet til sammen 6 elementer eller færre. Så 3×6=183 \times 6 = 18 er et tak,
og vi brukte 10 sammenligninger. Det er nettopp O(nlogn)O(n \log n).
Merk hjelpearrayet. Merge bygger et nytt ut-array. Det er derfor
flettesortering ikke er in-place, og det er et fast sant/usant-punkt.

📝Oppgave 1

(Innstegsoppgave, sjanger C — kjøretids- og teorifakta, altså rene
sant/usant-påstander om algoritmer.) Avgjør sant eller usant.

a) Flettesortering er O(nlogn)O(n \log n) også i verste tilfelle.
b) Flettesortering er in-place.
c) Flettesortering er stabil.

Løkke 2 — heapsort, kort og presist (ca. 8 min)

Heapsort får en kort omtale her og full behandling i Del 4, der selve
heap-strukturen bygges og håndkjøres. Det du trenger for Del 1 nå, er de tre
linjene i tabellen.

Ideen: gjør arrayet om til en heap — en struktur der du alltid kan plukke ut
det minste elementet raskt — og plukk deretter ut elementene ett for ett.
Uttrekket koster O(logn)O(\log n) og gjøres nn ganger, altså O(nlogn)O(n \log n).

Ett tall her er lett å bomme på, og det er et fast sant/usant-punkt: å bygge
heapen fra et vilkårlig array koster O(n)O(n), ikke O(nlogn)O(n \log n). Det er selve
uttrekksfasen som koster logaritmisk per element.

Heapsort

Bygger en heap av arrayet og henter ut det minste elementet gjentatte ganger til
heapen er tom.

O(nlogn)O(n \log n) i alle tilfeller. In-place — heapen ligger i selve arrayet
— men ikke stabil, fordi uttrekket flytter elementer over lang avstand.

Strukturen og håndkjøringen hører hjemme i Del 4; her trengs bare kjøretiden og
de to egenskapene.

Å bygge en heap er lineært
BuildHeap gjør et vilkårlig array om til en heap i O(n)O(n) — ikke
O(nlogn)O(n \log n).

Grunnen er at de fleste nodene ligger nær bunnen og derfor har kort vei å synke.
Dette er en fast distraktor på sant/usant: påstanden «å bygge en heap fra et
vilkårlig array tar O(nlogn)O(n \log n)» er usann. Selve heapsorteringen er
O(nlogn)O(n \log n) likevel, fordi de nn uttrekkene koster O(logn)O(\log n) hver.

Løkke 3 — kvikksortering og de to kjøretidene (ca. 14 min)

— naturlig pausepunkt —

Kvikksortering (quicksort) angriper problemet fra motsatt kant av
flettesortering. Der flettesortering deler blindt på midten og gjør arbeidet i
flettingen, gjør kvikksortering alt arbeidet i delingen — og trenger ingen
fletting i det hele tatt.

Metoden heter partisjonering: velg ett element som pivot, og still om
arrayet slik at alt som er mindre enn pivoten havner til venstre for den, og alt
som er større havner til høyre. Da står pivoten på sin endelige plass, og du kan
sortere de to sidene hver for seg.

Her ligger både styrken og svakheten. Deler pivoten omtrent på midten hver gang,
får du log2n\log_2 n nivåer og O(nlogn)O(n \log n). Havner pivoten ytterst hver gang,
skreller du av bare ett element per nivå — og da får du nn nivåer og O(n2)O(n^2).

Kvikksortering

Velger et pivotelement, partisjonerer arrayet slik at alt mindre står til
venstre for pivoten og alt større til høyre, og sorterer så hver side rekursivt.

O(nlogn)O(n \log n) forventet, O(n2)O(n^2) i verste tilfelle. In-place og
ikke stabil.

Verste tilfelle inntreffer når pivoten systematisk havner ytterst — for eksempel
hvis du alltid velger siste element og arrayet allerede er sortert. Påstanden
«kvikksortering er O(nlogn)O(n \log n) i verste tilfelle» er usann, og den er
kapitlets hyppigste distraktor.

Pivot

Elementet en partisjonering deler om. Alt mindre enn pivoten legges til venstre
for den, alt større til høyre, og pivoten står deretter på sin endelige plass.

Valget av pivot avgjør kvikksorteringens kjøretid. Siste element er det enkleste
valget, men gir O(n2)O(n^2) på allerede sorterte data. Et tilfeldig valgt element,
eller medianen av tre, gjør verste tilfelle usannsynlig — men fjerner det ikke.

📜Pseudokode-kontrakt: `Partition`
Antagelser om representasjon. A er et array indeksert fra 0. Vi
partisjonerer delen fra indeks lav til og med høy, og bruker A[høy] som
pivot. Sorteringen skjer på stedet.

Prebetingelse: lav <= høy. Postbetingelse: pivoten står på indeksen som
returneres; alt til venstre for den er mindre, alt til høyre er større eller lik.

Procedure Partition(A, lav, hoy)
  Input:  array A, og indeksene lav og hoy som avgrenser biten
  Output: indeksen der pivoten A[hoy] havnet, med alt mindre til venstre
  pivot = A[hoy]
  i = lav
  for j = lav to hoy-1:
      if A[j] < pivot:
          bytt A[i] og A[j]
          i = i + 1
  bytt A[i] og A[hoy]
  return i

Selve sorteringen er da tre linjer:

Procedure QuickSort(A, lav, hoy)
  Input:  array A og indeksene lav og hoy
  Output: A[lav..hoy] sortert stigende, sortert på stedet
  if lav < hoy:
      p = Partition(A, lav, hoy)
      QuickSort(A, lav, p-1)
      QuickSort(A, p+1, hoy)

Invarianten i én setning: rett før hver runde av for-løkka er alt i
A[lav..i-1] mindre enn pivoten, og alt i A[i..j-1] er større eller lik.

Kjøretid: én partisjonering av en bit med mm elementer koster m1m-1
sammenligninger, altså O(m)O(m). Deler pivoten omtrent på midten, blir det
log2n\log_2 n nivåer med O(n)O(n) arbeid hver — O(nlogn)O(n \log n). Havner pivoten ytterst
hver gang, blir det n1n-1 nivåer med n1,n2,n-1, n-2, \ldots sammenligninger, altså
n(n1)2\displaystyle \frac{n(n-1)}{2}O(n2)O(n^2).

✏️Eksempel 2: Én partisjonering, steg for steg

Partisjonér A = [7, 2, 9, 4, 1, 6] med A[høy] = 6 som pivot. Vis hvert steg
av for-løkka, og oppgi hvor pivoten havner.

jA[j] førA[j] < 6?HandlingArray etteri etter
07neila stå[7, 2, 9, 4, 1, 6]0
12jabytt plass 0 og 1[2, 7, 9, 4, 1, 6]1
29neila stå[2, 7, 9, 4, 1, 6]1
34jabytt plass 1 og 3[2, 4, 9, 7, 1, 6]2
41jabytt plass 2 og 4[2, 4, 1, 7, 9, 6]3
bytt pivoten inn på plass 3[2, 4, 1, 6, 9, 7]3

Sluttilstand: [2, 4, 1, 6, 9, 7]. Pivoten 6 står nå endelig på indeks 3.
Venstre del er [2, 4, 1], høyre del er [9, 7]. Partisjoneringen brukte 5
sammenligninger, altså n1n-1.
Legg merke til to ting.
For det første: venstre og høyre del er ikke sortert. Partisjonering
garanterer bare at alle til venstre er mindre enn pivoten. Sorteringen skjer når
QuickSort kalles rekursivt på hver side.

For det andre: pivoten 6 havnet nesten midt i arrayet, som er heldig. Delingen
ble 3 mot 2, og da får vi det gode tilfellet. Neste eksempel viser det motsatte.
Om stabiliteten. Byttet bytt A[i] og A[j] flytter et element over lang

avstand — akkurat som i utvalgssortering, og med samme konsekvens.

Kvikksortering er ustabil.

📝Oppgave 2
Sjanger D

Partisjonér A = [3, 8, 5, 2, 7] med siste element som pivot.

a) Hvilken indeks havner pivoten på?
b) Hvordan ser arrayet ut rett etter partisjoneringen?
c) Hvor mange sammenligninger brukte partisjoneringen?

✏️Eksempel 3: Verste tilfelle — hvorfor $O(n^2)$

Hvorfor blir kvikksortering med siste element som pivot O(n2)O(n^2) på et array som
allerede er sortert? Vis regnestykket for n=8n = 8.

Partisjonér A = [1, 2, 3, 4, 5] med A[høy] = 5 som pivot:

jA[j] førA[j] < 5?HandlingArray etteri etter
01jabytt plass 0 og 0[1, 2, 3, 4, 5]1
12jabytt plass 1 og 1[1, 2, 3, 4, 5]2
23jabytt plass 2 og 2[1, 2, 3, 4, 5]3
34jabytt plass 3 og 3[1, 2, 3, 4, 5]4
bytt pivoten inn på plass 4[1, 2, 3, 4, 5]4

Pivoten havnet helt til høyre. Venstre del har fire elementer, høyre del er
tom. Neste kall får altså bare ett element færre, ikke halvparten.
Nivåtellingen for n=8n = 8:
NivåStørste bitArbeid på nivået
087 sammenligninger
176 sammenligninger
265 sammenligninger
354 sammenligninger
443 sammenligninger
532 sammenligninger
621 sammenligning

Sju nivåer, og summen er 7+6+5+4+3+2+1=287+6+5+4+3+2+1 = 28 sammenligninger. Det er
n(n1)2\displaystyle \frac{n(n-1)}{2} med n=8n = 8 — trekantsummen fra

kap. 1.2, altså O(n2)O(n^2).
Sammenlign med flettesortering på samme nn: fire nivåer med 8 elementer

hver, altså høyst 32 elementbevegelser, men bare tre flettenivåer med reelt

arbeid. For n=8n = 8 er forskjellen liten. For n=1000000n = 1\,000\,000 er den forskjellen
mellom sekunder og dager.
Dette er kapitlets viktigste enkeltfaktum. Kvikksortering er O(nlogn)O(n \log n)
forventet og O(n2)O(n^2) verste. Skriver du bare «O(nlogn)O(n \log n)» uten å
nevne hvilket tilfelle, er svaret ufullstendig.

📝Oppgave 3
Sjanger C og D

Avgjør sant eller usant, og begrunn hver med én setning.

a) Kvikksortering har O(nlogn)O(n \log n) i verste tilfelle.
b) Heapsort er stabil.
c) Flettesortering trenger O(n)O(n) ekstra minne.
d) Å bygge en heap fra et vilkårlig array tar O(nlogn)O(n \log n).

Løkke 4 — nedre grensen, tegnet ut (ca. 14 min)

Nå kan vi gjøre ferdig det kap. 2.1 bare skisserte: hvorfor
O(nlogn)O(n \log n) ikke bare er bra, men optimalt for en sammenligningsbasert
sortering.

Tenk på algoritmen som en person som skal gjette hvilken av rekkefølgene input
har, og som bare får stille ja/nei-spørsmål av typen «er xx mindre enn yy?».
Hvert spørsmål deler mulighetene i to. Alle mulige spørsmålsrekker danner til
sammen et beslutningstre: en node per spørsmål, to grener ut, og ett blad per
mulig sluttrekkefølge.

Beslutningstre for sortering

En modell av en sammenligningsbasert sortering: hver indre node er én
sammenligning, hver gren er svaret (ja eller nei), og hvert blad er én mulig
sluttrekkefølge.

Antall sammenligninger i verste tilfelle er høyden på treet — den lengste
veien fra rot til blad. Treet må ha minst n!n! blader for å kunne skille alle
rekkefølger, og et binærtre med n!n! blader har høyde minst log2(n!)\log_2(n!).

📜Nedre grensen: Ω(nlogn)\Omega(n \log n)
Påstand. Enhver sammenligningsbasert sortering bruker minst
Ω(nlogn)\Omega(n \log n) sammenligninger i verste tilfelle.

Bevisskissen, i fire steg — dette er den formen sensor ber om:

1. Tell mulighetene. nn ulike elementer kan stå i n!n! ulike rekkefølger.
Algoritmen må kunne ende opp med hver av dem, avhengig av input.
2. Tell informasjonen. Hver sammenligning gir ett ja/nei-svar. Med kk
sammenligninger finnes det høyst 2k2^k ulike svarsekvenser, altså høyst 2k2^k
ulike blader.
3. Sett dem opp mot hverandre. For å skille alle n!n! rekkefølger må
2kn!2^k \ge n!, altså klog2(n!)k \ge \log_2(n!).
4. Forenkl. log2(n!)\log_2(n!) vokser som nlognn \log n. En rask måte å se det på: mer
enn halvparten av faktorene i n!n! er større enn n/2n/2, så
n!>(n/2)n/2n! > (n/2)^{n/2}, og logaritmen av det er n2log2n2\displaystyle \frac{n}{2}\log_2\frac{n}{2},
som er Ω(nlogn)\Omega(n \log n).

Hva grensen gjelder. Verste tilfelle, og bare for algoritmer som kun
sammenligner. Beste tilfelle kan godt være O(n)O(n) — innsettingssortering på et
ferdigsortert array bruker n1n-1 sammenligninger.

Hva grensen ikke gjelder. Algoritmer som ser på verdiene i stedet for å
sammenligne dem. Counting sort, radix sort og bucket sort i
kap. 2.3 kommer under grensen nettopp fordi de ikke er
sammenligningsbaserte. De betaler for det med en antagelse om verdiområdet.

✏️Eksempel 4: Beslutningstreet for tre elementer

Tegn ut beslutningstreet for å sortere tre ulike elementer aa, bb og cc, og
sjekk at høyden stemmer med formelen.

Med tre elementer finnes det 3!=63! = 6 mulige rekkefølger, altså seks blader.
Her er alle veiene gjennom treet, med den beste spørsmålsstrategien:

BladSluttrekkefølgeSammenligninger på veienAntall
1a<b<ca < b < ca<ba<b? ja; a<ca<c? ja; b<cb<c? ja3
2a<c<ba < c < ba<ba<b? ja; a<ca<c? ja; b<cb<c? nei3
3c<a<bc < a < ba<ba<b? ja; a<ca<c? nei2
4b<a<cb < a < ca<ba<b? nei; a<ca<c? ja2
5b<c<ab < c < aa<ba<b? nei; a<ca<c? nei; b<cb<c? ja3
6c<b<ac < b < aa<ba<b? nei; a<ca<c? nei; b<cb<c? nei3

Seks blader, største dybde 3.
Kontroll mot formelen. Nedre grensen sier at høyden må være minst
log2(3!)=log262,58\log_2(3!) = \log_2 6 \approx 2{,}58, og siden antall sammenligninger er et
helt tall, minst 3. Treet vårt har høyde nøyaktig 3 — grensen er altså stram
her.
Legg merke til bladene 3 og 4. De nås med bare to sammenligninger. Nedre
grensen sier ikke at hver input krever tre sammenligninger, bare at den

verste gjør det. Det er nettopp derfor grensen gjelder verste tilfelle og ikke

beste.
Prøv formen hvis dette kommer som en Del 2-oppgave: «Det finnes n!n! mulige
rekkefølger. Hver sammenligning gir ett bit, så kk sammenligninger skiller høyst
2k2^k tilfeller. Da må 2kn!2^k \ge n!, altså klog2(n!)=Ω(nlogn)k \ge \log_2(n!) = \Omega(n \log n)

Tre setninger, og alle de tre momentene sensor teller er med.

📝Oppgave 4
Sjanger C, eksamensnivå

Nedre grensen sier Ω(nlogn)\Omega(n \log n).

a) Hvor mange sammenligninger trenger et beslutningstre minst for å sortere
4 ulike elementer?
b) Motsier det nedre grensen at innsettingssortering bruker n1n-1
sammenligninger på et ferdigsortert array?
c) Motsier det nedre grensen at counting sort kan sortere i O(n+k)O(n + k)?

Kjøretidstabellen for alle sju sorteringene

Dette er bokas viktigste puggeflate for Del 1. De tre øverste er fra
kap. 2.1, de tre neste er dette kapitlet, og counting sort
kommer i kap. 2.3.

AlgoritmeBesteVersteForventetStabil?In-place?
BoblesorteringO(n)O(n) med tidlig stoppO(n2)O(n^2)O(n2)O(n^2)jaja
UtvalgssorteringO(n2)O(n^2)O(n2)O(n^2)O(n2)O(n^2)neija
InnsettingssorteringO(n)O(n)O(n2)O(n^2)O(n2)O(n^2)jaja
FlettesorteringO(nlogn)O(n \log n)O(nlogn)O(n \log n)O(nlogn)O(n \log n)janei
HeapsortO(nlogn)O(n \log n)O(nlogn)O(n \log n)O(nlogn)O(n \log n)neija
KvikksorteringO(nlogn)O(n \log n)O(n2)O(n^2)O(nlogn)O(n \log n)neija
Counting sortO(n+k)O(n + k)O(n+k)O(n + k)O(n+k)O(n + k)janei

Tre observasjoner som dekker de fleste sant/usant-punktene:
1. Bare kvikksortering har ulikt verste og forventet tilfelle blant de tre
effektive. Det er derfor den er den hyppigste distraktoren.
2. Bare flettesortering er både O(nlogn)O(n \log n) garantert og stabil — og prisen
er at den ikke er in-place.
3. Counting sort er den eneste under nlognn \log n, og den er der bare fordi den
ikke sammenligner. Merk kk: den er lineær i n+kn + k, ikke i nn alene.

Del 2-bruken: «sortér og skann»

Dette er grunnen til at kapitlet betyr noe utover Del 1.

På en Del 2-oppgave — der du skal skrive pseudokode og oppgi kjøretid — dukker
det stadig opp problemer av typen «finnes det to like elementer?» eller «finnes
det to elementer som summerer til xx?». På et usortert array har du to
lovlige veier:

- Sortér i O(nlogn)O(n \log n) og skann i O(n)O(n). Totalt O(nlogn)O(n \log n).
- Bruk et hash-set i O(n)O(n) forventet. Se
kap. 3.2 og kap. 3.4.

Begge er riktige. Men de gir ulikt antall poeng, fordi sensorveiledningene er
eksplisitte på at lavere kjøretid er mer poenggivende — den regelen kalles
poengtrappen i denne boka. Den lineære løsningen står øverst.

To ting du ikke kan gjøre, og som begge gir trekk:

- Bruke bucket sort eller radix sort som «raskere sortering» når alt du vet er at
elementene kan sammenlignes. De krever et kjent, begrenset verdiområde — se
kap. 2.3.
- Skrive O(nlogn)O(n \log n) uten å si hvilken sortering du bruker. Kjøretiden må matche
algoritmen du faktisk oppga.

📝Oppgave 5
Sjanger D, eksamensnivå

Velg sortering for hver situasjon, og oppgi
kjøretiden.

a) 2 millioner poster som skal sorteres på dato, og rekkefølgen mellom poster
med samme dato må bevares.
b) Et innebygd system med svært lite minne, der ekstra arrayer ikke er mulig,
men kjøretiden må være garantert O(nlogn)O(n \log n).
c) Generell sortering av tall der du bare bryr deg om forventet ytelse, og
minnebruken skal være lav.

📝Oppgave 6
Sjanger C og D

Et array på nn elementer er allerede sortert.

a) Hva er kjøretiden til flettesortering på det?
b) Hva er kjøretiden til kvikksortering med siste element som pivot?
c) Hva er kjøretiden til heapsort?
d) Hva sier dette om påstanden «effektive sorteringer er alltid raskere enn
kvadratiske»?

📝Oppgave 7
Sjanger D, krevende

Du får et usortert array A med nn
heltall og skal avgjøre om det finnes to like elementer.

a) Skriv en løsning som bruker sortering. Oppgi antagelser og kjøretid.
b) En medstudent foreslår å bruke radix sort «fordi den er lineær». Hva er
problemet?
c) Hva er det høyeste trinnet i poengtrappen på denne oppgaven?

📝Oppgave 8
Sjanger C, krevende

Avgjør sant eller usant, og begrunn.

a) Hvis en sortering er O(nlogn)O(n \log n) i verste tilfelle, kan den ikke være
sammenligningsbasert.
b) Nedre grensen Ω(nlogn)\Omega(n \log n) gjelder også for counting sort.
c) En sammenligningsbasert sortering kan avgjøre om et array allerede er
sortert i O(n)O(n).
d) Kvikksortering kan gjøres til O(nlogn)O(n \log n) i verste tilfelle ved å velge
pivoten tilfeldig.

Begrepsbank

Begrepsbanken er flashcard-/repetisjonsstoff — den gjentar det du nettopp har
lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder
kjernestoffet.

Fletting

Operasjonen som slår to ferdig sorterte arrayer sammen til ett sortert array
ved gjentatte ganger å ta det minste av de to første elementene.

O(m)O(m) der mm er samlet lengde: hvert element flyttes nøyaktig én gang.
Flettingen er stabil hvis venstre bit vinner ved likhet — det er hele
begrunnelsen for at flettesortering er stabil.

Nivåtelling

Fagets måte å analysere en algoritme som deler problemet opp: tell hvor mange
nivåer delingen gir, og hvor mye arbeid som gjøres på hvert nivå. Gang sammen.

Flettesortering: log2n\log_2 n nivåer, O(n)O(n) per nivå, altså O(nlogn)O(n \log n).
Kvikksortering i verste tilfelle: n1n-1 nivåer med avtakende arbeid, altså
O(n2)O(n^2). Metoden erstatter rekurrensligninger, som ikke er IN2010-pensum.

Partisjonering

Å stille om en del av arrayet slik at alt som er mindre enn pivoten står til
venstre for den og alt større til høyre. Pivoten står deretter på sin endelige
plass.

O(m)O(m) sammenligninger for en bit med mm elementer — én per element unntatt
pivoten. Delene er ikke sortert etterpå; det skjer i de rekursive kallene.

Kvikksorteringens verste tilfelle
O(n2)O(n^2), og det inntreffer når pivoten systematisk havner ytterst — for eksempel
når siste element velges som pivot på et allerede sortert array.

Dette er bokas hyppigste sant/usant-distraktor. Påstanden «kvikksortering er
O(nlogn)O(n \log n) i verste tilfelle» er usann.

Forventet kontra verste kjøretid
Forventet er gjennomsnittet over inputene (eller over de tilfeldige valgene
algoritmen gjør). Verste er det dårligste som kan skje.

Kvikksortering er den eneste av de sju sorteringene der de to er forskjellige:
O(nlogn)O(n \log n) forventet, O(n2)O(n^2) verste. Oppgaveteksten sier alltid hvilken av
de to den spør om.

Tilfeldig pivotvalg

Å velge pivoten tilfeldig i stedet for alltid å ta siste element. Gjør verste
tilfelle usannsynlig, men ikke umulig.

Forventet kjøretid blir O(nlogn)O(n \log n) for enhver input, og det er en reell
forbedring. Verste tilfelle er fortsatt O(n2)O(n^2) — den nyansen er en fast
distraktor.

Flettesortering er ikke in-place
Merge bygger et nytt array, så flettesortering bruker O(n)O(n) ekstra minne.

Det er den eneste av de sju sorteringene der stabilitet og garantert
O(nlogn)O(n \log n) kommer sammen — og minnebruken er prisen. Heapsort er in-place, men
ustabil; kvikksortering er in-place, men har O(n2)O(n^2) verste.

Heapsort er in-place men ustabil

Heapen ligger i selve arrayet, så heapsort bruker O(1)O(1) ekstra minne. Uttrekket
flytter derimot elementer over lang avstand, så algoritmen er ikke stabil.

Kombinasjonen «in-place og garantert O(nlogn)O(n \log n)» er unik for heapsort, og det
er nettopp den kombinasjonen som gjør den til riktig svar når minnet er knapt og
garantien kreves.

Merge sort i verste tilfelle
O(nlogn)O(n \log n), likt beste og forventet. Splittingen gir alltid log2n\log_2 n
nivåer, uansett hvilke tall arrayet inneholder.

Det er dette som gjør flettesortering til referansealgoritmen i faget: den har
verken en god eller en dårlig dag, og kjøretiden kan siteres uten forbehold.

Sortér-og-skann-strategien

Del 2-mønsteret: står du med et usortert array og trenger en egenskap som er lett
å se i sortert rekkefølge, sortér i O(nlogn)O(n \log n) og skann i O(n)O(n). Totalt
O(nlogn)O(n \log n).

Fullt lovlig og gir god uttelling — men står ett trinn under den lineære
hash-baserte løsningen i poengtrappen. Se
kap. 3.4.

Poengtrappen for sorteringsvalg

Sensorveiledningene sier eksplisitt at lavere kjøretid er mer poenggivende på
samme oppgave. På et duplikatsøk gir O(n)O(n) forventet med hash-set full pott,
O(nlogn)O(n \log n) med sortering noe mindre, og en dobbelt løkke på O(n2)O(n^2) minst.

Alle tre er korrekte løsninger. Å velge riktig algoritme er å velge poeng.

Beslutningstrehøyde

Antall sammenligninger en sammenligningsbasert sortering bruker i verste
tilfelle er høyden på beslutningstreet — den lengste veien fra rot til blad.

Med n!n! blader er høyden minst log2(n!)\log_2(n!). For n=3n = 3 gir det minst 3
sammenligninger, for n=4n = 4 minst 5.

Rekkevidden til en nedre grense
Ω(nlogn)\Omega(n \log n) gjelder verste tilfelle og kun sammenligningsbaserte
algoritmer.

Derfor motsier verken innsettingssorteringens O(n)O(n) i beste tilfelle, counting
sorts O(n+k)O(n + k), eller den lineære sjekken av om et array er sortert, grensen.
Å sitere grensen uten å oppgi rekkevidden er halve svaret.

Sjanger C — kjøretids- og teorifakta

Del 1-sjangeren der du krysser av sant/usant på rene faktapåstander om
kjøretider, sorteringsegenskaper, heap, hashing og P/NP.

Har vært på 7 av 7 sett (100 %). Kombinert med antigjettings-skaleringen betyr
det at kjøretidstabellen må sitte hjelpemiddelfritt: den som er usikker på
halvparten, ender på null.

Repetisjon — kapitlet på ett kort

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 Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.