Tilbake
3.1

3.1 Hauger og Heapsort

Maks-haugen som array, `Max-Heapify`, `Build-Max-Heap`, `Heap-Extract-Max` og `Heapsort` — inkludert at `Build-Max-Heap` er `Θ(n)`, ikke `Θ(n\lg n)`.

55 min
9 oppgaver
HaugerHeapsort
Din fremgang i kapitlet
0 / 9 oppgaver

Forkunnskaper

- kap. 2.1 — sammenligningsbaserte sorteringer.
Heapsort er en av dem, og den måles mot de andre. Dette sto der:
Merge-Sort er Θ(nlgn)\Theta(n\lg n) men bruker ekstra plass, Insertion-Sort
er Θ(n2)\Theta(n^2) i verste tilfelle, og ingen sortering som bare sammenligner
elementer kan gjøre det bedre enn Ω(nlgn)\Omega(n\lg n) i verste tilfelle.
- kap. 1.1 — asymptotisk notasjon. Du trenger
forskjellen mellom OO (bare øvre grense vist) og Θ\Theta (tett grense,
både øvre og nedre), fordi kapitlet her bruker begge bevisst.

Er logaritmen fersk: Potenser og logaritmer. I hele denne boka
betyr lgn\lg n det samme som log2n\log_2 n, altså «hvor mange ganger må du halvere
nn før du er nede på 1». Det er den samme konvensjonen CLRS bruker.

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

Notasjons- og pseudokodeliste

Haugegenskapen og arrayet (~12 min)

Se for deg vaktlista på en legevakt. Pasientene står ikke i den rekkefølgen de
kom; den mest alvorlige skal alltid tas først. Du trenger derfor en struktur
som gjør én ting veldig godt: gi meg den viktigste akkurat nå, og la meg
legge til nye pasienter underveis uten å sortere hele lista på nytt.

En sortert liste ville løst det, men den er dyr å holde ved like: hver
innsetting må skyve resten. En maks-haug gir deg det viktigste elementet
umiddelbart, og betaler bare en logaritmisk pris per endring. Prisen er at
resten av strukturen er nesten usortert — og det er nettopp det som gjør den
billig.

Det smarte er at haugen ikke trenger pekere i det hele tatt. Den ER et vanlig
array. Treet du tegner på papiret finnes bare i hodet ditt; på maskinen ligger
alt etter hverandre, nivå for nivå.

Haugegenskapen (maks-haug)

I en maks-haug er hver forelder større enn eller lik begge barna sine.

Ordningen går opp og ned, aldri sideveis. Det står ingenting om forholdet
mellom to søsken: venstre barn kan godt være større enn høyre barn, og to
noder på samme nivå kan stå i hvilken som helst rekkefølge.

Konsekvensen er at det største elementet garantert ligger på rota — og
bare det. Resten av arrayet er så godt som usortert, og det er meningen: en
haug er billig nettopp fordi den lover lite.

Formelt, for hver indeks ii som har barn: A[i/2]A[i]A[\lfloor i/2\rfloor] \ge A[i].

Array-representasjonen med indeks fra 1

En maks-haug lagres som et helt vanlig array A[1..n], fylt nivå for nivå
ovenfra og ned, og venstre før høyre innenfor hvert nivå. Rota ligger på
A[1].

Denne boka indekserer fra 1, slik CLRS gjør, og slik oppgavene og
løsningsforslagene i TDT4120 gjør. Det er ikke en smakssak: hele
indeksregningen under henger på det, og en formel fra en annen
indekseringsvane gir feil node hver eneste gang.

Fordi treet alltid fylles nivå for nivå uten hull, er høyden lgn\lfloor \lg n \rfloor — den lavest mulige for nn noder. Alt som koster «én sti opp eller
ned» er derfor O(lgn)O(\lg n).

Forelder- og barneindeksene

Fra en indeks ii finner du hele familien med tre regnestykker, uten å tegne
noe:

forelderen ligger på i/2\lfloor i/2\rfloor, venstre barn på 2i2i og høyre barn
2i+12i+1.

Kanttilfellene er der oppgaver felles. Indeks 1 er rota og har ingen
forelder. Er 2i>n2i > n, har noden ingen barn i det hele tatt — den er et blad.
Er 2in2i \le n men 2i+1>n2i+1 > n, har den bare venstre barn, og da skal du bare
sammenligne med det ene.

Alle tre regnestykkene er Θ(1)\Theta(1): konstant tid, uansett hvor stor haugen
er.

Haugstørrelsen `A.heap-size`
A.heap-size er hvor mange av arrayets elementer som teller som en del av
haugen akkurat nå. Den er ikke det samme som arrayets lengde.

Skillet finnes fordi to av operasjonene krymper haugen uten å krympe arrayet:
Heap-Extract-Max flytter siste haugelement til rota og setter A.heap-size
ned med én, og Heapsort legger det største elementet bakerst i arrayet og
tar det ut av haugen ved å gjøre det samme.

Praktisk konsekvens for håndkjøring: elementene bak A.heap-size ligger
fortsatt i arrayet, men de er usynlige for Max-Heapify. Oppgaven ber
som regel om hele arrayet, og da skal de med — men de skal ikke sammenlignes.

✏️Eksempel 1: Familien til hver indeks

Arrayet A = 41, 27, 33, 19, 22, 12, 30, 7, 15 er oppgitt med indeks fra 1.

a) Er det en gyldig maks-haug?

b) Sett opp forelderen og de to barna for hver indeks, og oppgi hvilke
indekser som er blader.

a) Ja. Kontrollen er mekanisk: for hver indeks ii med barn, sjekk at
A[i]A[i] er større enn eller lik begge barna.

A[1]=4127,33A[1]=41 \ge 27, 33. A[2]=2719,22A[2]=27 \ge 19, 22. A[3]=3312,30A[3]=33 \ge 12, 30.
A[4]=197,15A[4]=19 \ge 7, 15. Indeksene 5 til 9 har ingen barn, siden 25=10>92 \cdot 5 = 10 > 9. Ingen brudd, altså gyldig.

b) Med i/2\lfloor i/2\rfloor, 2i2i og 2i+12i+1:

iiA[i]forelder i/2\lfloor i/2\rfloorvenstre 2i2ihøyre 2i+12i+1
141ingen (rot)A[2] = 27A[3] = 33
227A[1] = 41A[4] = 19A[5] = 22
333A[1] = 41A[6] = 12A[7] = 30
419A[2] = 27A[8] = 7A[9] = 15
522A[2] = 27ingeningen
612A[3] = 33ingeningen
730A[3] = 33ingeningen
87A[4] = 19ingeningen
915A[4] = 19ingeningen

Bladene er indeksene 5, 6, 7, 8 og 9 — alle ii med 2i>92i > 9, altså alle
i>9/2=4i > \lfloor 9/2 \rfloor = 4.
Tegnet som tre ser den samme haugen slik ut:
                               41
                               (1)
               27                              33
               (2)                             (3)
       19              22              12              30
       (4)             (5)             (6)             (7)
    7      15
   (8)     (9)
Legg merke til to ting som ofte forvirrer. A[4] = 19 er mindre enn
søskenet A[5] = 22, og A[7] = 30 er større enn A[2] = 27 som ligger et
nivå høyere. Begge deler er helt greit: haugegenskapen sier bare noe om
forelder mot barn, ikke noe om søsken og ikke noe om noder i ulike grener.
📝Oppgave 1
Sjanger D

Bruk haugen A = 41, 27, 33, 19, 22, 12, 30, 7, 15 fra Eksempel 1.

a) Hvilken indeks ligger forelderen til A[7] på, og hvilken verdi står
der?

b) Har A[3] to barn, ett barn eller ingen barn? Begrunn med
indeksregning.

c) Forklar med én setning hvorfor A[5] = 22 godt kan være større enn
A[4] = 19 uten at haugegenskapen er brutt.

📝Oppgave 2
Sjanger F

Avgjør for hvert array om det er en gyldig maks-haug med indeks fra 1. Svar
ja eller nei, og oppgi det første bruddet du finner.

a) 48, 35, 40, 12, 30, 21, 9
b) 48, 35, 40, 12, 30, 44, 9
c) 9, 12, 21, 30, 35, 40, 48
d) 50, 50, 22, 18, 50, 7

Max-Heapify — å sive ett element ned (~12 min)

Alle haugoperasjonene bygger på én eneste reparasjonsrutine. Situasjonen den
takler er alltid den samme: ett element står på feil plass, og alt under
det er i orden.

Det er en veldig spesifikk kontrakt, og den er verdt å lese to ganger.
Max-Heapify(A, i) forutsetter at de to deltrærne under A[i] allerede er
gyldige maks-hauger. Bare A[i] selv kan være for liten. Rutinen lar da det
elementet synke nedover langs én sti til det finner plassen sin.

Retningen er hele poenget: elementet bytter med det største av de to
barna. Bytter du med det minste, lager du et nytt brudd rett under deg — og
da har du ikke reparert noe, bare flyttet feilen.

📜Pseudokode-kontrakt: `Max-Heapify`
Antagelser om representasjon. Arrayet er A[1..n], indeks fra 1.
A.heap-size sier hvor mange elementer som er med i haugen. Divisjonen
floor(i/2) er heltallsdivisjon.

Prebetingelse: deltrærne med rot i 2i og 2i+1 er begge gyldige
maks-hauger; bare A[i] kan bryte haugegenskapen.
Postbetingelse: deltreet med rot i i er en gyldig maks-haug, og det
inneholder nøyaktig de samme elementene som før.

Max-Heapify(A, i)
  Input:  array A[1..n], indeks i der begge deltrarne allerede er maks-hauger
  Output: deltreet med rot i i er en gyldig maks-haug
  l = 2*i
  r = 2*i + 1
  storst = i
  if l <= A.heap-size and A[l] > A[storst]
      storst = l
  if r <= A.heap-size and A[r] > A[storst]
      storst = r
  if storst != i
      bytt A[i] og A[storst]
      Max-Heapify(A, storst)
  Kjoeretid: O(lg n)

Grunnideen i én setning: når det største av de tre elementene A[i],
A[2i] og A[2i+1] er løftet opp til i, kan det eneste gjenværende
bruddet ligge i deltreet vi nettopp byttet med — og der løser det samme
kallet problemet på nytt, ett nivå lenger ned.

Legg merke til l <= A.heap-size som første ledd i begge testene. Uten
den ville rutinen lese utenfor haugen. Rekkefølgen er ikke likegyldig:
grensesjekken må stå først, slik at sammenligningen aldri utføres på en
indeks som ikke finnes.

Kjøretid: O(lgn)O(\lg n), der nn er antall elementer i haugen. Elementet
flytter seg høyst ett nivå per bytte, og treet har lgn\lfloor \lg n\rfloor
nivåer — én sti nedover, ingen forgrening. Kontrollert numerisk: over 20 000
tilfeldige hauger gjorde ingen kjøring flere enn lgn\lfloor \lg n\rfloor
bytter.

✏️Eksempel 2: Én `Max-Heapify`

Utfør Max-Heapify(A, 1) på arrayet A = 16, 38, 29, 31, 20, 11, 9
(indeks fra 1, A.heap-size = 7). Oppgi arrayet etterpå.

Start: 16, 38, 29, 31, 20, 11, 9 med n=7n = 7. Gyldig maks-haug: nei — men
prebetingelsen er oppfylt, for begge deltrærne under rota er i orden. Bare
A[1] bryter.

StegKallSammenligningArray etter stegetHandling
1Max-Heapify(A, 1)A[1]=16 mot venstre barn A[2]=38, mot høyre barn A[3]=2938, 16, 29, 31, 20, 11, 9bytt A[1] og A[2]
2Max-Heapify(A, 2)A[2]=16 mot venstre barn A[4]=31, mot høyre barn A[5]=2038, 31, 29, 16, 20, 11, 9bytt A[2] og A[4]
3Max-Heapify(A, 4)indeks 4 har ingen barn (2*4 = 8 > 7)38, 31, 29, 16, 20, 11, 9stopp

På eksamen leverer du bare linja under — tavlen er her for å vise hvordan du
kommer dit.
38, 31, 29, 16, 20, 11, 9
Steg 1 er hele poenget. Elementet 16 har barna 38 og 29. Hadde vi byttet

med høyre barn 29, ville arrayet blitt 29, 38, 16, 31, 20, 11, 9, og der
er 29<3829 < 38 — altså et nytt brudd i rota. Max-Heapify bytter alltid med

det største barnet, nettopp for å unngå det.

Steg 3 er sjekken oppgaven egentlig tester. Etter det andre byttet står 16
på indeks 4. Venstre barn ville vært indeks 24=82 \cdot 4 = 8, men haugen har
bare sju elementer. Indeks 8 finnes ikke, og rutinen stopper. Uten
grensesjekken ville algoritmen lest utenfor arrayet.

Kontroll som tar ti sekunder: haugen hadde sju elementer og har fortsatt
sju, alle de opprinnelige tallene er med, ingen er duplisert, og hver forelder
er nå større enn eller lik begge barna.

📝Oppgave 3
Eksamensnivå, sjanger C

Utfør Max-Heapify(A, 1) på arrayet A = 10, 33, 27, 12, 25, 6, 21 (indeks
fra 1, A.heap-size = 7).

Oppgi arrayet etterpå. Oppgaven ber om output, ikke om en forklaring av
algoritmen.

Build-Max-Heap, og hvorfor den er lineær (~14 min)

Nå har vi en rutine som reparerer ett brudd. Spørsmålet er hvordan man gjør et
helt vilkårlig array om til en maks-haug.

Svaret er å kalle Max-Heapify nedenfra og opp. Retningen er
nødvendig: Max-Heapify(A, i) forutsetter at deltrærne under ii allerede er
i orden, og den forutsetningen er bare oppfylt hvis du har behandlet alle
indekser større enn ii først.

Og du kan hoppe over den nederste halvparten helt. Alle indekser i>n/2i > \lfloor n/2 \rfloor har 2i>n2i > n, altså ingen barn. En node uten barn er per
definisjon allerede en gyldig maks-haug med ett element. Derfor starter løkka
n/2\lfloor n/2 \rfloor og teller nedover til 1.

📜Pseudokode-kontrakt: `Build-Max-Heap`
Antagelser om representasjon. Arrayet er A[1..n], indeks fra 1, med
vilkårlig innhold. A.length er nn.

Prebetingelse: ingen — arrayet kan være i hvilken som helst tilstand.
Postbetingelse: hele A[1..n] oppfyller haugegenskapen, og inneholder
nøyaktig de samme elementene som før. Omorganiseringen skjer på stedet,
altså uten et hjelpearray.

Build-Max-Heap(A)
  Input:  vilkaarlig array A[1..n]
  Output: A omorganisert til en maks-haug, paa stedet
  A.heap-size = A.length
  for i = floor(A.length / 2) downto 1
      Max-Heapify(A, i)
  Kjoeretid: Theta(n)

Invarianten i én setning: ved starten av hver runde er hver node med
indeks større enn ii rota i en gyldig maks-haug — så når løkka når i=1i = 1,
er hele arrayet en maks-haug.

Hvorfor løkka teller nedover: Max-Heapify(A, i) krever at deltrærne
under ii allerede er i orden. Teller du oppover, er den forutsetningen brutt
fra første kall, og resultatet blir en haug som ser riktig ut på toppen og er
ødelagt lenger nede.

Kjøretid: Θ(n)\Theta(n). Den løse tellingen «n/2n/2 kall à O(lgn)O(\lg n)» gir
O(nlgn)O(n\lg n), og den er riktig som øvre grense — men den er ikke stram. Den
stramme analysen står i neste avsnitt, og forskjellen er et fast eksamenspoeng.

✏️Eksempel 3: `Build-Max-Heap` på ti tall

En vaktliste registrerer ti hastegrader i den rekkefølgen pasientene meldte
seg: A = 4, 19, 7, 23, 11, 30, 6, 15, 28, 9 (indeks fra 1).

Kjør Build-Max-Heap(A) og oppgi arrayet etterpå.

Start: 4, 19, 7, 23, 11, 30, 6, 15, 28, 9 med n=10n = 10. Gyldig maks-haug:
nei.

Løkka starter på i=10/2=5i = \lfloor 10/2 \rfloor = 5 og teller ned til 1.
Indeksene 6 til 10 er blader og trenger ingen behandling.

StegKallSammenligningArray etter stegetHandling
1Max-Heapify(A, 5)A[5]=11 mot venstre barn A[10]=94, 19, 7, 23, 11, 30, 6, 15, 28, 9ingen bytte, stopp
2Max-Heapify(A, 4)A[4]=23 mot venstre barn A[8]=15, mot høyre barn A[9]=284, 19, 7, 28, 11, 30, 6, 15, 23, 9bytt A[4] og A[9]
3Max-Heapify(A, 9)indeks 9 har ingen barn (2*9 = 18 > 10)4, 19, 7, 28, 11, 30, 6, 15, 23, 9stopp
4Max-Heapify(A, 3)A[3]=7 mot venstre barn A[6]=30, mot høyre barn A[7]=64, 19, 30, 28, 11, 7, 6, 15, 23, 9bytt A[3] og A[6]
5Max-Heapify(A, 6)indeks 6 har ingen barn (2*6 = 12 > 10)4, 19, 30, 28, 11, 7, 6, 15, 23, 9stopp
6Max-Heapify(A, 2)A[2]=19 mot venstre barn A[4]=28, mot høyre barn A[5]=114, 28, 30, 19, 11, 7, 6, 15, 23, 9bytt A[2] og A[4]
7Max-Heapify(A, 4)A[4]=19 mot venstre barn A[8]=15, mot høyre barn A[9]=234, 28, 30, 23, 11, 7, 6, 15, 19, 9bytt A[4] og A[9]
8Max-Heapify(A, 9)indeks 9 har ingen barn (2*9 = 18 > 10)4, 28, 30, 23, 11, 7, 6, 15, 19, 9stopp
9Max-Heapify(A, 1)A[1]=4 mot venstre barn A[2]=28, mot høyre barn A[3]=3030, 28, 4, 23, 11, 7, 6, 15, 19, 9bytt A[1] og A[3]
10Max-Heapify(A, 3)A[3]=4 mot venstre barn A[6]=7, mot høyre barn A[7]=630, 28, 7, 23, 11, 4, 6, 15, 19, 9bytt A[3] og A[6]
11Max-Heapify(A, 6)indeks 6 har ingen barn (2*6 = 12 > 10)30, 28, 7, 23, 11, 4, 6, 15, 19, 9stopp

På eksamen leverer du bare linja under — tavlen er her for å vise hvordan du
kommer dit.
30, 28, 7, 23, 11, 4, 6, 15, 19, 9
Tegnet som tre:
                               30
                               (1)
               28                               7
               (2)                             (3)
       23              11               4               6
       (4)             (5)             (6)             (7)
   15      19       9
   (8)     (9)    (10)
Tell byttene. Elleve rader i tavlen, men bare fem av dem er faktiske
bytter: steg 2, 4, 6, 7, 9 og 10 — seks bytter. Med n=10n = 10 er nlgn33n\lg n \approx 33. Seks er ikke i nærheten, og det er ingen tilfeldighet: se neste
avsnitt.
Legg merke til at det første kallet ikke gjorde noe. Max-Heapify(A, 5)
fant at 11911 \ge 9 og stoppet umiddelbart. Det er typisk: nodene nederst har
lite arbeid å gjøre, og det er dem det er flest av.

📝Oppgave 4
Eksamensnivå, sjanger C

Kjør Build-Max-Heap(A) på arrayet A = 7, 3, 22, 14, 9, 30, 5, 12 (indeks
fra 1).

a) Hvilken indeks starter løkka på, og hvorfor?

b) Oppgi arrayet etter at hele Build-Max-Heap er utført.

📜`Build-Max-Heap` er Θ(n)\Theta(n) — med tellingen
En kjøretidspåstand uten telling er verre enn ingen påstand, så her er
tellingen.

Steg 1 — hvor mange noder har en gitt høyde? I et tre som fylles nivå for
nivå har høyst n/2h+1\lceil n/2^{h+1} \rceil noder høyde hh, der høyden til en
node er antall kanter ned til det dypeste bladet under den. Bladene har
høyde 0, foreldrene deres høyde 1, og så videre.

Intuisjon: omtrent halvparten av alle noder er blader, en firedel ligger ett
nivå over, en åttedel to nivåer over. Antallet halveres for hvert nivå du går
oppover.

Steg 2 — hvor mye arbeid gjør Max-Heapify fra en node med høyde hh?
Høyst hh bytter, for elementet kan ikke synke lenger enn til bunnen av sitt
eget deltre. Kostnaden er altså O(h)O(h).

Intuisjon: arbeidet vokser med én per nivå oppover, mens antallet noder
halveres. Halvering slår en økning på én.

Steg 3 — gang sammen og summer:

h=0lgnn2h+1O(h)=O ⁣(nh=0h2h)=O(2n)=O(n)\sum_{h=0}^{\lfloor \lg n\rfloor} \left\lceil \frac{n}{2^{h+1}} \right\rceil \cdot O(h) = O\!\left(n \sum_{h=0}^{\infty} \frac{h}{2^{h}}\right) = O(2n) = O(n)

Intuisjon: summen h/2h\sum h/2^h konvergerer mot 2 — den blir ikke større
uansett hvor mange ledd du tar med. Derfor er totalarbeidet under 2n2n, ikke
nlgnn\lg n.

Tellingen for tre konkrete størrelser (regnet ut med formelen over):

nnhøydesum av arbeidet2n2nnlgnn\lg nsum/n/n
1531130580,73
31426621530,84
635571263760,91
12761202548870,95
1 00099982 0009 9651,00
1 000 000191 000 0692 000 00019 931 5681,00

Forholdet sum/n/n kryper mot 1 og stopper der. Det gjør ikke nlgn/n=lgnn\lg n/n = \lg n, som vokser uten grense. Det er hele forskjellen.
Og nedre grense: hver av de nn elementene må i det minste leses, så
arbeidet er også Ω(n)\Omega(n). Øvre og nedre grense møtes, og derfor skriver vi
Θ(n)\Theta(n) — ikke bare O(n)O(n).
Avgrensningen, som er like viktig: Heapsort er likevel Θ(nlgn)\Theta(n\lg n).
Ikke fordi byggingen koster det, men fordi de n1n-1 uthentingene etterpå
koster O(lgn)O(\lg n) hver. Det er blandingen av de to fasene som skaper felle

#9.

📝Oppgave 5
Eksamensnivå, sjanger E
a) Hva er kjøretiden til Build-Max-Heap på et array med nn elementer?
Oppgi det strammeste riktige uttrykket.

b) Den løse analysen sier: «n/2n/2 kall til Max-Heapify, hver
O(lgn)O(\lg n), altså O(nlgn)O(n\lg n)». Forklar med to setninger hvorfor denne
grensen er riktig, men ikke stram.

c) Hva er kjøretiden til Heapsort, og hvorfor er den ikke den samme som
til Build-Max-Heap?

Heap-Extract-Max og prioritetskøen (~9 min)

— naturlig pausepunkt —

Tilbake til legevakta. Nå skal den mest alvorlige pasienten inn, og haugen
skal fortsatt være en haug etterpå.

Å lese ut det største er gratis: det ligger på A[1]. Problemet er hullet som
oppstår. Løsningen er å fylle hullet med det siste elementet i haugen,
krympe A.heap-size med én, og så la det nye rotelementet sive ned med
Max-Heapify. Da er prebetingelsen oppfylt: begge deltrærne er urørt og
dermed fortsatt gyldige, og bare rota kan være for liten.

En struktur som støtter «gi meg det viktigste» og «legg til et nytt element»
kalles en prioritetskø. Haugen er standardimplementasjonen, og du møter
den igjen som motor i Dijkstra og i MST-Prim senere i boka — der er det
nettopp «hvilken node er nærmest nå?» som skal besvares millioner av ganger.

📜Pseudokode-kontrakt: `Heap-Extract-Max`
Antagelser om representasjon. Arrayet er A[1..n], indeks fra 1, og
A[1..A.heap-size] er en gyldig maks-haug.

Prebetingelse: A.heap-size >= 1.
Postbetingelse: det største elementet er returnert og fjernet fra haugen,
A.heap-size er redusert med én, og resten er fortsatt en gyldig maks-haug.

Heap-Extract-Max(A)
  Input:  maks-haug A[1..A.heap-size]
  Output: det storste elementet, fjernet fra haugen
  if A.heap-size < 1
      feil: haugen er tom
  maks = A[1]
  A[1] = A[A.heap-size]
  A.heap-size = A.heap-size - 1
  Max-Heapify(A, 1)
  return maks
  Kjoeretid: O(lg n)

Grunnideen i én setning: ved å fylle hullet i rota med det siste
haugelementet holder treet seg fylt nivå for nivå, og siden bare rota kan ha
blitt for liten, er nøyaktig ett Max-Heapify-kall nok.

Kjøretid: O(lgn)O(\lg n). Alt bortsett fra Max-Heapify er konstant arbeid,
og Max-Heapify følger én sti fra rota og ned, altså høyst lgn\lfloor \lg n\rfloor nivåer.

Merk rekkefølgen. A.heap-size reduseres før Max-Heapify kalles.
Gjør du det motsatt, sammenligner reparasjonen med et element som ikke lenger
er en del av haugen — og da får du et annet svar. Ved håndkjøring er det
denne detaljen som skiller riktig fra nesten riktig.

✏️Eksempel 4: `Heap-Extract-Max` på et array som ikke er en gyldig haug

Utfør Heap-Extract-Max(A) én gang på arrayet A = 5, 21, 13, 4, 18
(indeks fra 1, A.heap-size = 5). Arrayet er ikke en gyldig maks-haug.

Følg algoritmen mekanisk. Oppgi hvilket element som returneres, og haugen
etterpå.

Start: 5, 21, 13, 4, 18 med n=5n = 5. Gyldig maks-haug: neiA[1]=5A[1]=5 er
mindre enn begge barna.

Vi reparerer ikke først. Algoritmen leser A[1], uansett hva som står der.

StegKallSammenligningArray etter stegetHandling
1Heap-Extract-Maxles A[1] = 55, 21, 13, 4, 18ta vare på maks = 5
2Heap-Extract-Maxflytt siste element til rota, heap-size = 418, 21, 13, 4krymp haugen
3Max-Heapify(A, 1)A[1]=18 mot venstre barn A[2]=21, mot høyre barn A[3]=1321, 18, 13, 4bytt A[1] og A[2]
4Max-Heapify(A, 2)A[2]=18 mot venstre barn A[4]=421, 18, 13, 4ingen bytte, stopp

På eksamen leverer du bare de to linjene under — tavlen er her for å vise
hvordan du kommer dit.
Returnert: 5
Haugen etterpå (A.heap-size = 4): 21, 18, 13, 4
Det som skjer i steg 1 er hele poenget. Algoritmen returnerer 5, selv om

21 er det største tallet i arrayet. Heap-Extract-Max er skrevet under
forutsetningen at rota ER maksimum; er den ikke det, gir algoritmen feil

element. Det er ikke en feil i håndkjøringen din — det er svaret.
Fellenoten: fellen her er #10 — å «reparere» en ugyldig haug før du

utfører operasjonen. Hadde du først kjørt Build-Max-Heap og deretter
uthentingen, ville du returnert 21 og fått en helt annen slutthaug. Det er et
annet spørsmål enn det som ble stilt.
Kontroll: fem elementer inn, ett returnert, fire igjen i haugen. Etter

uthentingen er 21, 18, 13, 4 en gyldig maks-haug — reparasjonen virket, selv
om utgangspunktet var ugyldig.

📝Oppgave 6
Eksamensnivå, sjanger C

Utfør Heap-Extract-Max(A) én gang på arrayet A = 9, 40, 17, 6, 35 (indeks
fra 1, A.heap-size = 5). Arrayet er ikke en gyldig maks-haug — følg
algoritmen mekanisk.

a) Hvilket element returneres?

b) Oppgi haugen etterpå.

Heapsort (~8 min)

Har du en maks-haug, har du nesten en sortering. Det største elementet ligger
A[1], og den siste plassen i arrayet er nøyaktig der det skal ende opp.
Bytt de to, krymp haugen med én, reparer rota — og gjenta.

Etter n1n-1 runder ligger arrayet stigende sortert, og du har ikke brukt en
eneste ekstra celle. Det er det som menes med på stedet (in-place): all
omorganisering skjer inne i det arrayet du fikk, med bare et par
hjelpevariabler ved siden av. Merge-Sort klarer ikke det, og det er
Heapsorts viktigste fortrinn.

Prisen er at sorteringen ikke er stabil. En stabil sortering beholder den
innbyrdes rekkefølgen til elementer med lik nøkkel — sorterer du en vaktliste
etter hastegrad, kommer de som meldte seg først fortsatt først blant de like
alvorlige. Heapsort gir ingen slik garanti, fordi byttene flytter elementer
over lange avstander i arrayet.

📜Pseudokode-kontrakt: `Heapsort`
Antagelser om representasjon. Arrayet er A[1..n], indeks fra 1, med
vilkårlig innhold.

Prebetingelse: ingen.
Postbetingelse: A[1..n] er stigende sortert, og inneholder de samme
elementene som før. Ingen hjelpearray er brukt.

Heapsort(A)
  Input:  vilkaarlig array A[1..n]
  Output: A stigende sortert, paa stedet
  Build-Max-Heap(A)
  for i = A.length downto 2
      bytt A[1] og A[i]
      A.heap-size = A.heap-size - 1
      Max-Heapify(A, 1)
  Kjoeretid: Theta(n lg n)

Invarianten i én setning: ved starten av hver runde er A[i+1..n] de
nin-i største elementene, ferdig sortert på plass, og A[1..i] er en gyldig
maks-haug med resten.

Kjøretid: Θ(nlgn)\Theta(n\lg n). Byggingen er Θ(n)\Theta(n), og deretter gjøres
n1n-1 runder à O(lgn)O(\lg n). At grensen er tett og ikke bare øvre, kommer av
at sorteringen gjør like mye arbeid uansett input — det finnes ingen «heldig»
rekkefølge som gjør Heapsort raskere, slik ferdigsortert input gjør
Insertion-Sort rask.

Egenskapene du skal kunne uten å tenke: Θ(nlgn)\Theta(n\lg n), på stedet,
ustabil.

✏️Eksempel 5: `Heapsort` fra start til slutt

Kjør Heapsort(A)A = 12, 26, 5, 31, 18, 8 (indeks fra 1).

a) Oppgi arrayet rett etter Build-Max-Heap.

b) Oppgi arrayet etter den første iterasjonen av for-løkka, med
haugstørrelsen.

c) Oppgi det ferdig sorterte arrayet.

Start: 12, 26, 5, 31, 18, 8 med n=6n = 6.

Steg 1 til 7 nedenfor er byggefasen; fra steg 8 begynner sorteringsfasen.

StegKallSammenligningArray etter stegetHandling
1Max-Heapify(A, 3)A[3]=5 mot venstre barn A[6]=812, 26, 8, 31, 18, 5bytt A[3] og A[6]
2Max-Heapify(A, 6)indeks 6 har ingen barn (2*6 = 12 > 6)12, 26, 8, 31, 18, 5stopp
3Max-Heapify(A, 2)A[2]=26 mot venstre barn A[4]=31, mot høyre barn A[5]=1812, 31, 8, 26, 18, 5bytt A[2] og A[4]
4Max-Heapify(A, 4)indeks 4 har ingen barn (2*4 = 8 > 6)12, 31, 8, 26, 18, 5stopp
5Max-Heapify(A, 1)A[1]=12 mot venstre barn A[2]=31, mot høyre barn A[3]=831, 12, 8, 26, 18, 5bytt A[1] og A[2]
6Max-Heapify(A, 2)A[2]=12 mot venstre barn A[4]=26, mot høyre barn A[5]=1831, 26, 8, 12, 18, 5bytt A[2] og A[4]
7Max-Heapify(A, 4)indeks 4 har ingen barn (2*4 = 8 > 6)31, 26, 8, 12, 18, 5stopp
8Heapsort-iterasjon 1bytt A[1] = 31 og A[6] = 55, 26, 8, 12, 18, 31største element på plass bakerst
9Max-Heapify(A, 1)A[1]=5 mot venstre barn A[2]=26, mot høyre barn A[3]=826, 5, 8, 12, 18, 31bytt A[1] og A[2]
10Max-Heapify(A, 2)A[2]=5 mot venstre barn A[4]=12, mot høyre barn A[5]=1826, 18, 8, 12, 5, 31bytt A[2] og A[5]
11Max-Heapify(A, 5)indeks 5 har ingen barn (2*5 = 10 > 5)26, 18, 8, 12, 5, 31stopp (heap-size = 5)
12Heapsort-iterasjon 2bytt A[1] = 26 og A[5] = 55, 18, 8, 12, 26, 31største element på plass bakerst
13Max-Heapify(A, 1)A[1]=5 mot venstre barn A[2]=18, mot høyre barn A[3]=818, 5, 8, 12, 26, 31bytt A[1] og A[2]
14Max-Heapify(A, 2)A[2]=5 mot venstre barn A[4]=1218, 12, 8, 5, 26, 31bytt A[2] og A[4]
15Max-Heapify(A, 4)indeks 4 har ingen barn (2*4 = 8 > 4)18, 12, 8, 5, 26, 31stopp (heap-size = 4)
16Heapsort-iterasjon 3bytt A[1] = 18 og A[4] = 55, 12, 8, 18, 26, 31største element på plass bakerst
17Max-Heapify(A, 1)A[1]=5 mot venstre barn A[2]=12, mot høyre barn A[3]=812, 5, 8, 18, 26, 31bytt A[1] og A[2]
18Max-Heapify(A, 2)indeks 2 har ingen barn (2*2 = 4 > 3)12, 5, 8, 18, 26, 31stopp (heap-size = 3)
19Heapsort-iterasjon 4bytt A[1] = 12 og A[3] = 88, 5, 12, 18, 26, 31største element på plass bakerst
20Max-Heapify(A, 1)A[1]=8 mot venstre barn A[2]=58, 5, 12, 18, 26, 31ingen bytte, stopp (heap-size = 2)
21Heapsort-iterasjon 5bytt A[1] = 8 og A[2] = 55, 8, 12, 18, 26, 31største element på plass bakerst
22Max-Heapify(A, 1)indeks 1 har ingen barn (2*1 = 2 > 1)5, 8, 12, 18, 26, 31stopp (heap-size = 1)

På eksamen leverer du bare de tre linjene under — tavlen er her for å vise
hvordan du kommer dit.
a) Etter Build-Max-Heap: 31, 26, 8, 12, 18, 5
b) Etter første iterasjon (heap-size = 5): 26, 18, 8, 12, 5, 31
c) Ferdig sortert: 5, 8, 12, 18, 26, 31
Se hvordan halen vokser. Etter iterasjon 1 er A[6] = 31 ferdig. Etter
iterasjon 2 er A[5..6] = 26, 31 ferdig. Den sorterte halen bakerst blir én
lengre for hver runde, og haugen foran den blir én kortere. Ingen ekstra
plass er brukt — derfor «på stedet».
Merk hva delspørsmålene ber om. b) spør bare om én iterasjon. Kjører
du hele sorteringen når bare første iterasjon var spurt, har du svart på et
annet spørsmål — og brukt tid du trengte på de andre oppgavene.

📝Oppgave 7
Eksamensnivå, sjanger C

Arrayet A = 45, 28, 39, 14, 21, 33, 8 (indeks fra 1) er allerede en gyldig
maks-haug med A.heap-size = 7.

Utfør én iterasjon av for-løkka i Heapsort. Oppgi hele arrayet etterpå,
og hva A.heap-size er da. Oppgaven ber om én iterasjon, ikke hele
sorteringen.

📝Oppgave 8
Eksamensnivå, sjanger C

Arrayet A = 6, 17, 3, 25, 11, 20, 14, 2 (indeks fra 1) skal sorteres med
Heapsort.

a) Oppgi arrayet rett etter Build-Max-Heap.

b) Oppgi arrayet og A.heap-size etter de to første iterasjonene av
for-løkka.

📝Oppgave 9
Eksamensnivå, sjanger F

En medstudent påstår: «Heapsort er en stabil sortering, siden den bare
bytter elementer og aldri kopierer dem til et hjelpearray.»

a) Stemmer påstanden? Svar ja eller nei.

b) Gi et konkret motbevis eller et konkret argument, og oppgi hva som blir
resultatet.

Arrayet A = 3a, 3b, 1c har tre elementer der tallet er nøkkelen som
sorteres på, og bokstaven bare er en merkelapp som viser hvilket element det
er. 3a står altså foran 3b i inputen.

Kjøretidene samlet

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

OperasjonBesteVersteKrav / egenskap
Parent, Left, RightΘ(1)\Theta(1)Θ(1)\Theta(1)ren indeksregning, ingen leting
Heap-MaximumΘ(1)\Theta(1)Θ(1)\Theta(1)maksimum ligger alltid på A[1]
Max-HeapifyΘ(1)\Theta(1)O(lgn)O(\lg n)krever at begge deltrærne allerede er maks-hauger
Build-Max-HeapΘ(n)\Theta(n)Θ(n)\Theta(n)på stedet; ikke Θ(nlgn)\Theta(n\lg n)
Heap-Extract-MaxΘ(1)\Theta(1)O(lgn)O(\lg n)krever A.heap-size 1\ge 1
Max-Heap-InsertΘ(1)\Theta(1)O(lgn)O(\lg n)nytt element legges bakerst og stiger
HeapsortΘ(nlgn)\Theta(n\lg n)Θ(nlgn)\Theta(n\lg n)på stedet, ustabil; ingen input gjør den raskere
Finne minimum i en maks-haugΘ(n)\Theta(n)Θ(n)\Theta(n)haugen sier ingenting om hvor det minste ligger

Den siste raden overrasker mange. En maks-haug lover bare at maksimum ligger
på toppen. Det minste elementet kan ligge hvor som helst blant bladene, og du
må lete gjennom dem alle.
Én presisering som er verdt å ta med seg: Build-Max-Heap er asymptotisk
raskere enn å bygge haugen med nn kall til Max-Heap-Insert, som gir
O(nlgn)O(n\lg n). Men på en enkelt liten input kan de to gjøre like mange
bytter, og innsettingsvarianten kan til og med gjøre færre. Det er derfor
påstanden hører hjemme som en asymptotisk påstand, ikke som «Build-Max-Heap
gjør alltid færre bytter».

Begrepsbank

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

`Max-Heapify`

reparerer ett brudd på haugegenskapen ved å la elementet på indeks ii sive
nedover mot det største barnet så lenge et barn er større.

Kjøretid O(lgn)O(\lg n) — én sti fra ii og ned, aldri forgrening.

Krever at begge deltrærne under ii allerede er gyldige maks-hauger. Er
den betingelsen brutt, reparerer rutinen ikke hele treet.

`Build-Max-Heap`

gjør et vilkårlig array om til en maks-haug på stedet ved å kalle
Max-Heapify for hver indeks fra n/2\lfloor n/2\rfloor og nedover til 1.

Kjøretid Θ(n)\Theta(n)ikke Θ(nlgn)\Theta(n\lg n). Den løse tellingen «n/2n/2
kall à O(lgn)O(\lg n)» er en gyldig øvre grense, men ikke stram: halvparten av
nodene er blader med null arbeid.

Retningen er et krav. Løkka må telle nedover, ellers er Max-Heapifys
prebetingelse brutt.

`Heap-Extract-Max`

fjerner og returnerer det største elementet: leser A[1], flytter det siste
haugelementet dit, reduserer A.heap-size med én og kaller Max-Heapify(A, 1).

Kjøretid O(lgn)O(\lg n) — alt bortsett fra reparasjonen er konstant arbeid.

Krever A.heap-size 1\ge 1. Og merk: rutinen kontrollerer ikke at rota
faktisk er maksimum. Er inputen ikke en gyldig haug, returnerer den likevel
A[1].

`Heap-Maximum`

leser det største elementet uten å fjerne det.

Kjøretid Θ(1)\Theta(1), fordi maksimum per definisjon ligger på A[1] i en
gyldig maks-haug.

Det er denne operasjonen som gjør haugen til en prioritetskø: «hva er det
viktigste akkurat nå?» besvares uten å lete.

`Max-Heap-Insert`

setter inn et nytt element ved å legge det bakerst i haugen og la det stige
oppover mot rota så lenge forelderen er mindre.

Kjøretid O(lgn)O(\lg n) — én sti fra bladet og opp.

Retningen er motsatt av Max-Heapify: her siles det oppover, og
sammenligningen går bare mot forelderen, ikke mot søsken.

`Heapsort`

sorterer stigende ved å bygge en maks-haug og deretter bytte A[1] med
siste haugelement, krympe haugen og reparere rota — n1n-1 ganger.

Kjøretid Θ(nlgn)\Theta(n\lg n) i alle tilfeller. På stedet, og ustabil.

At grensen er tett og ikke bare øvre, kommer av at ingen input gjør
Heapsort raskere: den bygger og tømmer haugen likt uansett.

Haugegenskapen

kravet som gjør et array til en maks-haug: hver forelder er større enn eller
lik begge barna sine, altså A[i/2]A[i]A[\lfloor i/2\rfloor] \ge A[i] for alle i2i \ge 2.

Ordningen er opp–ned. Det finnes ingen venstre–høyre-orden: to søsken
kan stå i hvilken som helst rekkefølge.

Konsekvensen er at bare maksimum har en garantert plass. Resten av arrayet er
tilnærmet usortert.

Array-representasjonen `A[1..n]`

haugen lagres som et vanlig array, fylt nivå for nivå ovenfra og ned og
venstre før høyre. Rota ligger på A[1].

Kostnaden for å finne familien til en node er Θ(1)\Theta(1): ingen pekere, bare
regning.

Indeks fra 1 er konvensjonen i denne boka, og den som brukes i TDT4120s
oppgaver og løsningsforslag.

Forelder- og barneindeksene

tre regnestykker som erstatter pekere: forelderen ligger på i/2\lfloor i/2\rfloor, venstre barn på 2i2i og høyre barn på 2i+12i+1.

Alle tre er Θ(1)\Theta(1).

Kanttilfellene: indeks 1 har ingen forelder. Er 2i>n2i > n, er noden et
blad. Er 2in2i \le n men 2i+1>n2i+1 > n, har noden bare venstre barn.

`A.heap-size`

antall elementer som teller som en del av haugen akkurat nå — ikke det samme
som arrayets lengde.

Skillet koster ingenting å vedlikeholde (Θ(1)\Theta(1) per endring), men det er
avgjørende ved håndkjøring.

Heap-Extract-Max og Heapsort krymper haugen uten å krympe arrayet.
Elementene bak A.heap-size ligger fortsatt der, men er usynlige for
Max-Heapify.

Høyden til en maks-haug

antall kanter fra rota ned til det dypeste bladet.

For en haug med nn elementer er høyden lgn\lfloor \lg n\rfloor, altså
Θ(lgn)\Theta(\lg n).

Grunnen er at treet alltid fylles nivå for nivå uten hull. Det er denne
høyden som gjør Max-Heapify, Heap-Extract-Max og Max-Heap-Insert til
O(lgn)O(\lg n)-operasjoner.

Prioritetskø

en datastruktur som støtter «gi meg det viktigste elementet» og «legg til et
nytt element», uten å holde alt sortert.

Med en maks-haug koster oppslaget Θ(1)\Theta(1) og de to endringene O(lgn)O(\lg n)
hver.

Du møter den igjen som motor i Dijkstra og MST-Prim i Del 4, der
spørsmålet «hvilken node er nærmest nå?» stilles om og om igjen.

På stedet (*in-place*)

en algoritme er på stedet når den omorganiserer inputarrayet direkte og bare
bruker et konstant antall hjelpevariabler ved siden av.

Ekstra plassbruk: Θ(1)\Theta(1).

Heapsort og Build-Max-Heap er på stedet. Merge-Sort er det ikke — den
trenger et hjelpearray på Θ(n)\Theta(n) celler.

Stabil sortering — og at `Heapsort` ikke er det

en sortering er stabil når to elementer med lik nøkkel beholder den
innbyrdes rekkefølgen de hadde i inputen.

Heapsort er ustabil. Motbevis: 3a, 3b, 1c sorteres til 1c, 3b, 3a,
mens en stabil sortering ville gitt 1c, 3a, 3b.

Grunnen er at byttene flytter elementer over lange avstander i arrayet — i
første runde hopper rota helt bakerst, forbi alt som ligger imellom.

Siving mot det største barnet

regelen som styrer hvilken vei Max-Heapify går: elementet bytter med det
største av de to barna, ikke med venstre barn og ikke med det minste.

Det koster én ekstra sammenligning per nivå, altså fortsatt O(lgn)O(\lg n).

Bytter du med det minste barnet, står du igjen med et nytt brudd rett under
deg — feilen er flyttet, ikke rettet.

Hvorfor `Build-Max-Heap` starter på n/2\lfloor n/2\rfloor

alle indekser i>n/2i > \lfloor n/2\rfloor har 2i>n2i > n og dermed ingen barn. En
node uten barn er allerede en gyldig maks-haug med ett element.

Å hoppe over dem sparer omtrent halvparten av kallene, men endrer ikke
kjøretiden: den er Θ(n)\Theta(n) uansett.

Det er derimot en fast kilde til feil i håndkjøring å starte på nn eller på
1 i stedet.

Bladene i en maks-haug

nodene uten barn, altså alle indekser ii med 2i>n2i > n. De utgjør omtrent
halvparten av haugen.

Antallet er n/2\lceil n/2 \rceil.

De er nøkkelen til Θ(n)\Theta(n)-analysen: halvparten av nodene gjør null
arbeid under Build-Max-Heap, og en firedel gjør høyst ett bytte.

Maks-haug kontra binært søketre

to strukturer som forveksles systematisk, og som ordner i hver sin retning.

Begge har Θ(lgn)\Theta(\lg n) som naturlig arbeidsmengde per operasjon, men de
svarer på ulike spørsmål.

Maks-haugen ordner opp–ned (forelder \ge begge barn) og gir deg
maksimum i Θ(1)\Theta(1). Søketreet ordner venstre–høyre og lar deg søke
etter en bestemt nøkkel. En haug kan ikke søke effektivt; et søketre kan ikke
love deg maksimum uten å gå til høyre spiss.

Den mekaniske håndkjøringsregelen

når en oppgave ber deg utføre en haugoperasjon, følger du pseudokoden linje
for linje — også når inputen ikke er en gyldig maks-haug.

Kostnaden er den samme som ellers; det er svaret som blir et annet.

Reparerer du først, svarer du på et annet spørsmål enn det som ble stilt.
Dette er felle #10 i bokas feilregister.

Svarformatet for en haug-håndkjøring

svaret på en haugoppgave er hele arrayet, kommaseparert, med indeks fra
1 — og A.heap-size når haugen er krympet.

Det tar ett sekund å skrive og gir full uttelling.

Et pent tegnet tre uten arrayet under gjør det ikke, og heller ikke «bare de
elementene som ble flyttet».

Sjanger C — håndkjøring

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

Den er den hyppigste håndkjøringssjangeren i faget, og hauger og søketrær er
de to vanligste strukturene: temaet er registrert i 16 av de 17 settene i
grunnlaget.

Svarformen er kun sluttilstanden, i det formatet oppgaven ber om. En
forklaring av hvordan algoritmen virker, gir ingen ekstra uttelling.

Sjanger E — kjøretidskunnskap

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

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

Her ligger felle #9: å skrive Θ(nlgn)\Theta(n\lg n) for Build-Max-Heap, som er
Θ(n)\Theta(n).

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.