Tilbake
2.1

2.1 Sammenligningsbaserte sorteringer

`Insertion-Sort`, `Merge-Sort`, `Quicksort` og `Randomized-Quicksort` — mekanikk, kjøretider (beste/verste/forventet) og `Θ(n\lg n)`-nedre grensen.

55 min
9 oppgaver
Sammenligningsbaserte sorteringer
Din fremgang i kapitlet
0 / 9 oppgaver

Forkunnskaper

- kap. 1.1 — de asymptotiske symbolene. Du trenger
skillet mellom OO (bare øvre grense er vist), Ω\Omega (bare nedre) og
Θ\Theta (tett grense begge veier), fordi dette kapitlet bruker alle tre
bevisst. I hele boka betyr lgn\lg n det samme som log2n\log_2 n.
- kap. 1.2 — forenkling. Kjøretidssvar oppgis alltid i
det strammeste uttrykket som er riktig.
- kap. 1.4 — masterteoremet. Merge-Sorts kjøretid
utledes her ved å løse rekurrensen T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) + \Theta(n), og det gjør
vi med masterteoremet.

Vil du se sortering konkret i kode først, er
Sortering: boblesortering, .sort() og gitt sort_list
et mykere første møte. Du trenger ikke mer enn å kunne følge en løkke med
blyant.

Notasjons- og pseudokodeliste

Insertion-Sort og løkkeinvarianten (~12 min)

Du har fjorten pasientjournaler i en bunke og skal legge dem i stigende
rekkefølge etter registreringsnummer. Den naturlige framgangsmåten er å ta én
journal av gangen, holde den i hånden, og skyve den inn på riktig plass i den
delen av bunken du allerede har ordnet. Det er nøyaktig Insertion-Sort.

Algoritmen er interessant på eksamen av to grunner. Den er den ene sorteringen
som er raskere på pen input — allerede sortert input gir lineær tid — og
den er den letteste å begrunne, fordi begrunnelsen er en løkkeinvariant:
en påstand om tilstanden som er sann før løkka begynner, forblir sann gjennom
hver runde, og gir det du vil ha når løkka er ferdig.

Løkkeinvariant

En løkkeinvariant er en påstand om tilstanden som gjelder rett før hver
runde i en løkke.

Den brukes til å vise at en algoritme er riktig, og har alltid tre ledd:
initialisering (påstanden er sann før første runde), vedlikehold (er
den sann før en runde, er den sann før den neste), og terminering (når
løkka stopper, gir påstanden nettopp det resultatet vi ville ha).

Ordet «invariant» betyr her det som ikke endrer seg — ikke tilstanden selv,
men påstanden om den.

📜Pseudokode-kontrakt: `Insertion-Sort`
Antagelser om representasjon. Arrayet er A[1..n], indeks fra 1.
Elementene kan sammenlignes med < og >; ingenting annet antas om dem.
Sorteringen skjer på stedet (in-place): utenom noen få hjelpevariabler
brukes ingen ekstra plass som vokser med nn.

Prebetingelse: A[1..n] inneholder nn elementer i vilkårlig rekkefølge.
Postbetingelse: A[1..n] inneholder de samme elementene i stigende
rekkefølge.

Insertion-Sort(A)
  Input:  array A[1..n]
  Output: A sortert stigende, paa stedet
  for j = 2 to A.length
      key = A[j]
      i = j - 1
      while i >= 1 and A[i] > key
          A[i+1] = A[i]
          i = i - 1
      A[i+1] = key
  Kjoeretid: Theta(n) beste, Theta(n^2) verste

Invarianten i én setning: rett før hver runde med indeks j er
A[1..j-1] sortert og inneholder nøyaktig de elementene som lå der fra start.

Kjøretid: den ytre løkka går n1n-1 ganger. Den indre while-løkka gjør
ingen runder når A[j] allerede er større enn alt til venstre — da blir
totalen Θ(n)\Theta(n). Er arrayet sortert synkende, må hvert element skyves
helt fram til indeks 1, og totalen blir 1+2++(n1)=n(n1)/21 + 2 + \dots + (n-1) = n(n-1)/2,
altså Θ(n2)\Theta(n^2).

✏️Eksempel 1: `Insertion-Sort` runde for runde

Seks måleverdier fra en værstasjon ligger i den rekkefølgen de ble avlest:
A = 34, 12, 47, 8, 23, 19 (indeks fra 1).

Kjør Insertion-Sort(A) og oppgi arrayet etter hver runde i den ytre løkka.
Tell til slutt hvor mange enkeltskyv den indre løkka gjorde til sammen.

Løkka starter på j=2j = 2, fordi A[1..1] er sortert allerede.

Runde jkeyAntall skyv i while-løkkaArray etter runden
212112, 34, 47, 8, 23, 19
347012, 34, 47, 8, 23, 19
4838, 12, 34, 47, 23, 19
52328, 12, 23, 34, 47, 19
61938, 12, 19, 23, 34, 47

Til sammen 9 skyv i den indre løkka.
Sluttilstanden — det du ville levert på eksamen:
8, 12, 19, 23, 34, 47
Legg merke til runde 3: key = 47 er større enn alt til venstre, så while-løkka
gjør null runder. Det er nettopp det som skjer i hver runde når inputen
allerede er sortert, og det er derfor beste tilfelle er Θ(n)\Theta(n).

På eksamen leverer du bare sluttarrayet — tavlen er her for å vise hvordan du

kommer dit.

📝Oppgave 1

(Innstegsoppgave, sjanger D — definisjon med egne ord, altså én presis setning
med hovedpoenget først.)

Hva er beste tilfelle for Insertion-Sort, hvilken input gir det, og hva er
verste tilfelle?

📝Oppgave 2
Sjanger C

Kjør Insertion-SortA = 5, 3, 9, 1 og oppgi arrayet etter hver runde i
den ytre løkka.

Merge-Sort og rekurrensen bak den (~14 min)

Insertion-Sort bruker kvadratisk tid på store arrayer, og det er for dyrt.
Den andre ideen er splitt og hersk: del arrayet i to like store halvdeler,
sorter hver halvdel for seg, og flett de to sorterte halvdelene sammen. Selve
arbeidet ligger i flettingen.

Flettingen er lettere enn den ser ut. Har du to sorterte bunker foran deg, er
det minste elementet i unionen alltid det øverste i én av de to bunkene. Du
sammenligner de to toppene, tar den minste, og gjentar.

📜Pseudokode-kontrakt: `Merge`
Antagelser om representasjon. Arrayet er A[1..n], indeks fra 1.
Indeksene tilfredsstiller p <= q < r. Rutinen bruker to hjelpearrayer L og
R, og er derfor ikke på stedet.

Prebetingelse: A[p..q] er sortert, og A[q+1..r] er sortert.
Postbetingelse: A[p..r] er sortert og inneholder de samme elementene.

Merge(A, p, q, r)
  Input:  A[p..q] og A[q+1..r] er hver for seg sorterte
  Output: A[p..r] sortert
  kopier A[p..q] til L og A[q+1..r] til R
  legg en vaktpost stoerre enn alle elementer bakerst i L og i R
  i = 1
  j = 1
  for k = p to r
      if L[i] <= R[j]
          A[k] = L[i]
          i = i + 1
      else
          A[k] = R[j]
          j = j + 1
  Kjoeretid: Theta(r - p + 1)

Invarianten i én setning: rett før hver runde med indeks k inneholder
A[p..k-1] de kpk-p minste elementene fra L og R, i sortert rekkefølge,
og L[i] og R[j] er de minste elementene som ennå ikke er kopiert tilbake.

Merk <= og ikke < i testen. Ved likhet plukkes elementet fra
venstre bunke først. Det er den lille detaljen som gjør Merge-Sort
stabil: like elementer beholder sin innbyrdes rekkefølge, fordi venstre
bunke lå først i arrayet.

Kjøretid: hver runde i for-løkka plasserer nøyaktig ett element, og det er
rp+1r-p+1 elementer å plassere — altså Θ(rp+1)\Theta(r-p+1), lineært i lengden på
delen som flettes.

✏️Eksempel 2: Flettesteget, sammenligning for sammenligning

To sorterte halvdeler skal flettes: venstre er 2, 5, 9, høyre er
3, 4, 11.

Utfør Merge og oppgi hvilken sammenligning som gjøres i hvert steg, og hvor
mange sammenligninger flettingen koster til sammen.

StegSammenligningPlukketA etter steget
12 mot 32 (venstre)2
25 mot 33 (høyre)2, 3
35 mot 44 (høyre)2, 3, 4
45 mot 115 (venstre)2, 3, 4, 5
59 mot 119 (venstre)2, 3, 4, 5, 9
6venstre bunke tom11 (høyre)2, 3, 4, 5, 9, 11

Sluttilstanden: 2, 3, 4, 5, 9, 11.
Seks elementer ut, men bare fem sammenligninger: det siste steget trenger
ingen, fordi den ene bunken er tom og resten kan kopieres rett over. Generelt
koster fletting av nn elementer høyst n1n-1 sammenligninger og nøyaktig nn
plasseringer — begge deler Θ(n)\Theta(n).
Legg merke til at ingen sammenligning noen gang involverer to elementer fra
samme bunke. De er allerede innbyrdes sortert, og den informasjonen kastes

ikke bort. Det er hele gevinsten ved splitt og hersk.

📜Pseudokode-kontrakt: `Merge-Sort`
Antagelser om representasjon. Arrayet er A[1..n], indeks fra 1. Kallet
utenfra er Merge-Sort(A, 1, A.length). Rutinen krever hjelpeplass på
Θ(n)\Theta(n) celler til flettingen, og går derfor ikke på stedet.

Prebetingelse: p <= r, og A[p..r] inneholder elementene som skal
sorteres.
Postbetingelse: A[p..r] er sortert stigende.

Merge-Sort(A, p, r)
  Input:  array A[p..r]
  Output: A[p..r] sortert stigende
  if p < r
      q = floor((p + r) / 2)
      Merge-Sort(A, p, q)
      Merge-Sort(A, q+1, r)
      Merge(A, p, q, r)
  Kjoeretid: Theta(n lg n)

Grunnideen i én setning: en del med ett element er sortert per definisjon,
og to sorterte deler kan flettes til én sortert del i lineær tid — altså
holder det å halvere seg ned til enkeltelementer og flette seg opp igjen.

Kjøretid: delingen koster konstant tid, de to rekursive kallene er hver på
halvparten så stor input, og flettingen koster Θ(n)\Theta(n). Det gir

T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) + \Theta(n)

Her er a=2a = 2 og b=2b = 2, så nlogba=nlog22=n1n^{\log_b a} = n^{\log_2 2} = n^1. Siden
f(n)=Θ(n)=Θ(nlogbalg0n)f(n) = \Theta(n) = \Theta(n^{\log_b a}\lg^{0} n), treffer vi tilfelle 2
i masterteoremet med k=0k = 0, og svaret blir T(n)=Θ(n1lg1n)=Θ(nlgn)T(n) = \Theta(n^{1}\lg^{1} n) = \Theta(n\lg n). Ingen input gjør den raskere, og ingen gjør den tregere:
grensen er tett begge veier.

📝Oppgave 3
Eksamensnivå, sjanger B

En sorteringsrutine deler arrayet i tre like store deler, sorterer hver av
dem rekursivt, og fletter de tre sorterte delene i lineær tid.

a) Sett opp rekurrensen.
b) Løs den, og navngi metoden.

📝Oppgave 4
Eksamensnivå, sjanger F

Ta stilling til hver av påstandene:

a) Merge-Sort sorterer på stedet.
b) Merge-Sort er stabil.
c) Merge-Sort er raskere på et allerede sortert array enn på et
tilfeldig array.

Quicksort og pivotens rolle (~13 min)

Merge-Sort deler alltid midt på, og betaler for det med hjelpeplass.
Quicksort snur det: den deler etter verdi i stedet for etter posisjon, og
slipper dermed flettingen helt. Prisen er at delingen kan bli skjev.

Delingen gjøres av Partition. Den velger et element som pivot — et
skilleelement — og stokker om delen slik at alt som er mindre enn eller lik
pivoten havner til venstre for den, og alt som er større havner til høyre.
Etterpå står pivoten på sin endelige plass, og de to sidene kan sorteres
uavhengig. Mekanikken i Partition tas i detalj i
kap. 2.3; her trenger du kontrakten.

Pivot

Et pivot er skilleelementet en partisjonering deler om.

Etter Partition(A, p, r) står pivoten på en indeks q slik at alt i
A[p..q-1] er mindre enn eller lik pivoten, og alt i A[q+1..r] er større.
Pivoten selv er da ferdig plassert og røres aldri igjen.

I denne boka velger Partition siste element i delen som pivot, slik CLRS
gjør. Randomized-Partition bytter først et tilfeldig element inn på siste
plass, og kaller så Partition.

📜Pseudokode-kontrakt: `Quicksort`
Antagelser om representasjon. Arrayet er A[1..n], indeks fra 1. Kallet
utenfra er Quicksort(A, 1, A.length). Partition(A, p, r) bruker siste
element A[r] som pivot og returnerer pivotens sluttindeks q. Sorteringen
går på stedet.

Prebetingelse: A[p..r] inneholder elementene som skal sorteres.
Postbetingelse: A[p..r] er sortert stigende.

Quicksort(A, p, r)
  Input:  array A[p..r]
  Output: A[p..r] sortert stigende, paa stedet
  if p < r
      q = Partition(A, p, r)
      Quicksort(A, p, q-1)
      Quicksort(A, q+1, r)
  Kjoeretid: Theta(n lg n) forventet, Theta(n^2) verste

Grunnideen i én setning: når pivoten står på sin endelige plass, er
problemet redusert til to uavhengige, mindre sorteringsproblemer — og ingen
fletting trengs etterpå, fordi rekkefølgen mellom de to sidene allerede er
riktig.

Kjøretid: partisjoneringen av en del med mm elementer koster
Θ(m)\Theta(m). Blir delingen jevn, får vi T(n)=2T(n/2)+Θ(n)=Θ(nlgn)T(n) = 2T(n/2) + \Theta(n) = \Theta(n\lg n). Blir den maksimalt skjev — pivoten er alltid det største
eller minste elementet — får vi T(n)=T(n1)+Θ(n)T(n) = T(n-1) + \Theta(n), som gir
Θ(n2)\Theta(n^2). Det verste tilfellet inntreffer nettopp på et allerede
sortert
array når pivoten er siste element.

✏️Eksempel 3: `Quicksort` kall for kall

Sju ordrenumre ligger i mottaksrekkefølge: A = 26, 9, 41, 17, 33, 12, 22
(indeks fra 1).

Kjør Quicksort(A, 1, 7) med siste element som pivot. Oppgi arrayet etter
hver partisjonering, og hvor dypt rekursjonen går.

Bare kall som faktisk partisjonerer er tatt med; kall på deler med null eller
ett element gjør ingenting.

KallDelPivotqArray etter partisjoneringen
Quicksort(A, 1, 7)A[1..7]2249, 17, 12, 22, 33, 41, 26
Quicksort(A, 1, 3)A[1..3]1229, 12, 17, 22, 33, 41, 26
Quicksort(A, 5, 7)A[5..7]2659, 12, 17, 22, 26, 41, 33
Quicksort(A, 6, 7)A[6..7]3369, 12, 17, 22, 26, 33, 41

Sluttilstanden: 9, 12, 17, 22, 26, 33, 41.
Dybden er 3: kallet på hele arrayet, deretter på A[5..7], deretter på
A[6..7]. Fire partisjoneringer til sammen på sju elementer.
Legg merke til den første delingen. Pivoten 22 havnet på indeks 4, altså
nesten midt på — det er den heldige varianten. Hadde arrayet vært

9, 12, 17, 22, 26, 33, 41 fra start, ville hver pivot vært det største

elementet i sin del, hver deling ville skilt av nøyaktig ett element, og
dybden ville blitt 6 i stedet for 3.

📝Oppgave 5
Eksamensnivå, sjanger E
a) Hva er verste-tilfelle-kjøretiden til Quicksort med siste element som
pivot, og på hvilken input oppstår den?
b) Hva er kjøretiden i det vanlige tilfellet, og hvilket symbol hører til
det svaret?

Randomized-Quicksort (~6 min)

Problemet med Quicksort er ikke at det finnes en dårlig input — det er at
den dårlige inputen er vanlig. Sorterte og nesten sorterte data dukker opp
overalt.

Løsningen er å flytte tilfeldigheten fra inputen til algoritmen. I stedet for
alltid å ta siste element som pivot, trekker Randomized-Partition et
tilfeldig element i delen, bytter det til siste plass, og partisjonerer som
før.

📜Pseudokode-kontrakt: `Randomized-Quicksort`
Antagelser om representasjon. Som Quicksort: A[1..n], indeks fra 1, på
stedet. Random(p, r) returnerer et tilfeldig heltall i [p, r], hvert like
sannsynlig.

Prebetingelse: A[p..r] inneholder elementene som skal sorteres.
Postbetingelse: A[p..r] er sortert stigende. Resultatet er alltid
riktig; det er bare kjøretiden som avhenger av de tilfeldige trekkene.

Randomized-Partition(A, p, r)
  Input:  array A[p..r]
  Output: pivotens sluttindeks q
  i = Random(p, r)
  bytt A[i] og A[r]
  return Partition(A, p, r)

Randomized-Quicksort(A, p, r)
  if p < r
      q = Randomized-Partition(A, p, r)
      Randomized-Quicksort(A, p, q-1)
      Randomized-Quicksort(A, q+1, r)
  Kjoeretid: Theta(n lg n) forventet, uansett input

Grunnideen i én setning: når pivoten trekkes tilfeldig, er sannsynligheten
for en rimelig jevn deling stor i hvert kall, og forventningen over alle
trekkene blir Θ(nlgn)\Theta(n\lg n) — uten at noen bestemt input kan framtvinge det
dårlige tilfellet.

Kjøretid: Θ(nlgn)\Theta(n\lg n) forventet, for enhver input. Verste
tilfelle er fortsatt Θ(n2)\Theta(n^2) — trekker du uheldig hver eneste gang, går
det like galt — men nå er «uheldig» et spørsmål om terningkast, ikke om hvem
som leverte dataene.

Den nedre grensen for sammenligningssortering (~10 min)

Alle fire algoritmene over har det til felles at de bare stiller ett slags
spørsmål om dataene: «er dette elementet mindre enn hint?» De kan flytte
elementer rundt, men de kan aldri se på verdien og regne ut hvor den hører
hjemme. Slike algoritmer kalles sammenligningsbaserte.

Det setter en grense som ingen smartere idé kan komme under, og grensen er
verdt å kunne argumentere for på tre linjer.

Sammenligningsbasert sortering

En sortering er sammenligningsbasert når den bare bruker sammenligninger
mellom elementpar — «er aiaja_i \le a_j?» — til å avgjøre rekkefølgen.

Insertion-Sort, Merge-Sort, Quicksort og Heapsort er alle
sammenligningsbaserte. De vet ingenting om hva elementene er, bare hvordan
de forholder seg til hverandre.

Sorteringene i kap. 2.2 er ikke sammenligningsbaserte:
de bruker nøkkelverdien som en indeks, og er derfor ikke bundet av grensen
under.

📜Nedre grense: Ω(nlgn)\Omega(n\lg n) for sammenligningssortering
Påstanden. Enhver sammenligningsbasert sortering bruker Ω(nlgn)\Omega(n\lg n)
sammenligninger i verste tilfelle.

Argumentet, kort. Kjøringen kan tegnes som et beslutningstre: hver
indre node er én sammenligning, og de to grenene er de to svarene. Hver
løvnode er én mulig utgangsrekkefølge av elementene.

Med nn forskjellige elementer finnes n!n! mulige rekkefølger, og algoritmen
må kunne ende opp i hver av dem — ellers finnes en input den sorterer feil.
Altså har treet minst n!n! løvnoder.

Et binært tre med høyde hh har høyst 2h2^h løvnoder. Da må 2hn!2^h \ge n!,
altså hlg(n!)h \ge \lg(n!). Og lg(n!)=Θ(nlgn)\lg(n!) = \Theta(n\lg n): minst halvparten av
faktorene i n!n! er større enn n/2n/2, så n!(n/2)n/2n! \ge (n/2)^{n/2}, som gir
lg(n!)(n/2)lg(n/2)=Ω(nlgn)\lg(n!) \ge (n/2)\lg(n/2) = \Omega(n\lg n).

Høyden er antall sammenligninger i verste tilfelle, altså er den
Ω(nlgn)\Omega(n\lg n).

Hva grensen ikke sier. Den sier ingenting om sorteringer som ikke
sammenligner, og den sier ingenting om beste tilfelle for en enkelt
algoritme. Merge-Sort og Heapsort når grensen, og er dermed
asymptotisk optimale blant sammenligningssorteringene.

📝Oppgave 6
Eksamensnivå, sjanger F

En kandidat skriver i besvarelsen sin: «Siden enhver sortering bruker minst
Ω(nlgn)\Omega(n\lg n) tid, kan ingen sortering være lineær.»

Er utsagnet riktig? Svar ja eller nei, og forklar kort.

📝Oppgave 7
Eksamensnivå, sjanger E

Fyll ut tabellen for de fire algoritmene i dette kapitlet.

AlgoritmeBesteVerstePå stedet?Stabil?
Insertion-Sort
Merge-Sort
Quicksort
Randomized-Quicksort

📝Oppgave 8
Eksamensnivå, sjanger F

En kollega foreslår å forbedre Quicksort slik: «Før vi partisjonerer,
sjekker vi om delen allerede er sortert. Er den det, hopper vi over hele
rekursjonen. Da blir verste tilfelle Θ(nlgn)\Theta(n\lg n)

Stemmer konklusjonen? Svar ja eller nei, og begrunn.

📝Oppgave 9
Eksamensnivå, sjanger H

Et laboratorium har nn prøverør med hver sin måleverdi. Du skal finne ut om
to av rørene har nøyaktig samme verdi, og i så fall hvilke to.

Beskriv en algoritme som bruker Θ(nlgn)\Theta(n\lg n) tid, og forklar hvorfor
kjøretiden holder.

Kjøretidene samlet

Dette er kapitlets puggeflate. Eksamen er hjelpemiddelfri, så tabellen må ligge
i hodet.

AlgoritmeBesteVersteForventetKrav / egenskap
Insertion-SortΘ(n)\Theta(n)Θ(n2)\Theta(n^2)Θ(n2)\Theta(n^2)på stedet, stabil; beste tilfelle er ferdigsortert input
Merge-SortΘ(nlgn)\Theta(n\lg n)Θ(nlgn)\Theta(n\lg n)Θ(nlgn)\Theta(n\lg n)ikke på stedet (Θ(n)\Theta(n) hjelpeplass), stabil
QuicksortΘ(nlgn)\Theta(n\lg n)Θ(n2)\Theta(n^2)Θ(nlgn)\Theta(n\lg n)på stedet, ustabil; verste tilfelle på sortert input
Randomized-QuicksortΘ(nlgn)\Theta(n\lg n)Θ(n2)\Theta(n^2)Θ(nlgn)\Theta(n\lg n) for enhver inputpå stedet, ustabil
Merge (ett flettesteg)Θ(m)\Theta(m)Θ(m)\Theta(m)Θ(m)\Theta(m)mm = antall elementer som flettes; krever to sorterte deler
Partition (ett steg)Θ(m)\Theta(m)Θ(m)\Theta(m)Θ(m)\Theta(m)ett gjennomløp, på stedet

Heapsort hører også hjemme i denne oversikten: den er Θ(nlgn)\Theta(n\lg n)
garantert, går på stedet og er ustabil. Haugstrukturen den bygger på, tas i
kap. 3.1.
Én presisering som er verdt å ta med seg. Ingen av de fire kan komme under
Ω(nlgn)\Omega(n\lg n) i verste tilfelle, og Merge-Sort når grensen. Det betyr
ikke at Merge-Sort alltid er det beste valget i praksis — Quicksort har
mindre konstanter og trenger ikke hjelpeplass — men på eksamen er det den

asymptotiske garantien som teller.

Begrepsbank

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

`Insertion-Sort`

sorterer A[1..n] ved å ta ett element av gangen og skyve det bakover til sin
plass i den allerede sorterte delen til venstre.

Kjøretid Θ(n)\Theta(n) beste (ferdigsortert input), Θ(n2)\Theta(n^2) verste
(synkende sortert input).

Går på stedet og er stabil. Den eneste av de fire i dette kapitlet som
faktisk blir raskere når inputen er pen.

`Merge`

fletter to sorterte deler A[p..q] og A[q+1..r] til én sortert del ved å
plukke det minste av de to fremste elementene om gangen.

Kjøretid Θ(rp+1)\Theta(r-p+1), altså lineært i antallet elementer som flettes.

Krever at begge delene allerede er sortert. Ved likhet plukkes fra venstre
del — det er det som gjør Merge-Sort stabil.

`Merge-Sort`

sorterer ved splitt og hersk: del i to like halvdeler, sorter hver rekursivt,
flett resultatene.

Kjøretid Θ(nlgn)\Theta(n\lg n) i alle tilfeller — ingen input gjør den raskere eller
tregere.

Ikke på stedet: flettingen krever Θ(n)\Theta(n) hjelpeplass. Til gjengjeld er
den stabil, og garantien er tett.

`Partition`

stokker A[p..r] om pivoten slik at alt mindre enn eller lik pivoten står til
venstre for den og alt større til høyre, og returnerer pivotens sluttindeks
q.

Kjøretid Θ(rp+1)\Theta(r-p+1) — ett gjennomløp, på stedet.

Pivoten er ferdig plassert etterpå og røres aldri igjen. I denne boka er
pivoten siste element i delen.

`Quicksort`

partisjonerer A[p..r] om et pivot og sorterer de to sidene rekursivt. Ingen
fletting trengs, fordi rekkefølgen mellom sidene allerede er riktig.

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

Går på stedet, men er ustabil. Verste tilfelle inntreffer på allerede
sortert input når pivoten er siste element.

`Randomized-Quicksort`

samme algoritme, men pivoten trekkes tilfeldig i hvert kall før
partisjoneringen.

Kjøretid Θ(nlgn)\Theta(n\lg n) forventet for enhver input; verste tilfelle er
fortsatt Θ(n2)\Theta(n^2).

Forskjellen er hvem som bestemmer. Ingen input kan lenger framtvinge det
dårlige tilfellet — det avhenger bare av terningkastene.

Løkkeinvariant

en påstand om tilstanden som gjelder rett før hver runde i en løkke, og som
brukes til å vise at algoritmen er riktig.

Har tre ledd: initialisering, vedlikehold og terminering.

For Insertion-Sort: rett før runde j er A[1..j-1] sortert og
inneholder de samme elementene som fra start.

På stedet (*in-place*)

en algoritme går på stedet når den bare bruker et konstant antall
hjelpevariabler utenom selve inputen — altså O(1)O(1) ekstra plass.

Insertion-Sort, Quicksort og Heapsort går på stedet; Merge-Sort gjør
det ikke.

Rekursjonsstakken telles vanligvis ikke med i denne bokas bruk av
begrepet.

Stabil sortering

en sortering er stabil når to elementer med lik nøkkel beholder sin
innbyrdes rekkefølge fra input til output.

Insertion-Sort og Merge-Sort er stabile; Quicksort og Heapsort er det
ikke.

Egenskapen er avgjørende når du sorterer etter én nøkkel om gangen, slik
Radix-Sort gjør. Se kap. 2.2.

Splitt og hersk

designteknikken der problemet deles i mindre deler av samme type, delene
løses rekursivt, og delløsningene settes sammen.

Kjøretiden blir en rekurrens på formen T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n), som løses med
masterteoremet.

Delproblemene overlapper ikke — det er det som skiller teknikken fra
dynamisk programmering.

Beslutningstre for sortering

en modell av en sammenligningsbasert sortering der hver indre node er én
sammenligning og hver løvnode er én mulig utgangsrekkefølge.

Treet må ha minst n!n! løvnoder, og et binært tre med så mange løv har høyde
minst lg(n!)=Θ(nlgn)\lg(n!) = \Theta(n\lg n).

Høyden er antall sammenligninger i verste tilfelle — derav den nedre
grensen.

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

ingen sammenligningsbasert sortering kan bruke færre enn Ω(nlgn)\Omega(n\lg n)
sammenligninger i verste tilfelle.

Følger av beslutningstre-argumentet.

Gjelder bare sammenligningsbaserte sorteringer, og bare verste tilfelle.
Merge-Sort og Heapsort når grensen og er dermed asymptotisk optimale i den
klassen.

Sammenligningsbasert sortering

en sortering som bare bruker sammenligninger mellom elementpar til å avgjøre
rekkefølgen, og som aldri regner på selve nøkkelverdien.

De fire algoritmene i dette kapitlet er alle sammenligningsbaserte, og det er
Heapsort også.

Konsekvensen er Ω(nlgn)\Omega(n\lg n) i verste tilfelle. Sorteringer som bruker
nøkkelen som indeks, faller utenfor.

Pivot

skilleelementet en partisjonering deler om.

Etter partisjoneringen står pivoten på sin endelige plass, med alt mindre eller
likt til venstre og alt større til høyre.

Pivotvalget avgjør kjøretiden. Siste element som pivot gir Θ(n2)\Theta(n^2)
sortert input; tilfeldig valgt pivot gir Θ(nlgn)\Theta(n\lg n) forventet uansett.

Verste tilfelle

den lengste kjøretiden over alle inputer av størrelse nn.

Oppgis med Θ\Theta når grensen er tett, og med OO når bare en øvre grense er
vist.

Dette er standardsvaret når en oppgave sier «kjøretid» uten å presisere
noe.

Forventet kjøretid

gjennomsnittet av kjøretiden over de tilfeldige valgene algoritmen selv gjør,
eller over en antatt fordeling av inputene.

Randomized-Quicksort er Θ(nlgn)\Theta(n\lg n) forventet for enhver input.

Forventet er ikke garantert. Ordet må stå i svaret; utelates det, har du
lovet noe algoritmen ikke holder.

Beste tilfelle

den korteste kjøretiden over alle inputer av størrelse nn.

Bare Insertion-Sort har et beste tilfelle som er asymptotisk bedre enn
verste: Θ(n)\Theta(n) mot Θ(n2)\Theta(n^2).

Felle #9 bor her: å oppgi Ω(nlgn)\Omega(n\lg n) som beste tilfelle for
Insertion-Sort. Riktig svar er Θ(n)\Theta(n).

Rekurrensen T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) + \Theta(n)
Merge-Sort-rekurrensen: to halvdeler, hver av størrelse n/2n/2, pluss lineært
arbeid til flettingen.

Løses med masterteoremets tilfelle 2 (k=0k = 0) og gir Θ(nlgn)\Theta(n\lg n).

Den samme rekurrensen beskriver Quicksort når delingen er jevn.

Rekurrensen T(n)=T(n1)+Θ(n)T(n) = T(n-1) + \Theta(n)
Quicksorts verste tilfelle: hver partisjonering skiller av nøyaktig ett
element.

Løses med iterasjon og gir Θ(n2)\Theta(n^2), siden n+(n1)++1=n(n+1)/2n + (n-1) + \dots + 1 = n(n+1)/2.

Masterteoremet gjelder ikke her — rekurrensen er ikke på formen
aT(n/b)+f(n)aT(n/b) + f(n).

Vaktpost i `Merge`

et hjelpeelement større enn alle virkelige elementer, lagt bakerst i begge
hjelpearrayene.

Det gjør at flettingen slipper å teste om en av bunkene er tom i hver runde —
bunken med vaktposten taper alltid sammenligningen.

Ren forenkling av koden. Kjøretiden blir den samme uten den.

Sjanger E — kjøretidskunnskap

oppgavetypen der du oppgir kjøretiden til en navngitt algoritme.

Svarformen er ett uttrykk, i det strammeste som er riktig, med Θ\Theta der
grensen er tett og OO der bare øvre grense er vist.

Les alltid om spørsmålet gjelder beste, verste eller forventet. Det er der
poengene faller.

Sjanger C — håndkjøring

oppgavetypen der du utfører en navngitt algoritme steg for steg på papir og
oppgir sluttilstanden.

Svarformen er kun sluttilstanden, i det formatet oppgaven ber om.

Ingen ekstra uttelling for å forklare algoritmen. Drillen ligger i
kap. 2.4.

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 som
begrunner.

Et motbevis er en fullgod begrunnelse når svaret er nei — én konkret input
der påstanden svikter, holder.

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.