Tilbake
4.4

4.4 Heap og prioritetskø

Min-heapen som array (indeks fra 0), Insert (sift-up) og RemoveMin (down-heap) med håndkjøring, og de faste heap-faktaene.

55 min
8 oppgaver
Heapprioritetskø
Din fremgang i kapitlet
0 / 8 oppgaver

Forkunnskaper

- kap. 2.2 — heapsort ble nevnt der, med løftet om at selve
strukturen kom i Del 4. Dette er den delen. Du trenger derfra bare at heapsort
er O(nlogn)O(n \log n) og at det å bygge heapen er O(n)O(n).
- kap. 4.1 — BST-egenskapen. Den er heapens motstykke, og
de to forveksles systematisk. Har du BST-egenskapen klart for deg, er
heap-egenskapen lett å holde adskilt.
- kap. 1.1 — hva O(logn)O(\log n) betyr. Heapens høyde er
logaritmisk, og det er derfor alle operasjonene er det.
- kap. 1.4 — der sto heap-faktaene som løsrevne påstander.
Her får de begrunnelsene sine.

Er array-tenkning ferskt: Lister — indeksering fra 0 er
alt heapens smarteste triks bygger på.

Notasjons- og pseudokodeliste

Løkke 1 — alltid-minste-først-køen (ca. 13 min)

Et kommunalt vaktlag tar imot meldinger om hull i veien. Hver melding får et
hastetall: 1 er «gjennomgående hovedvei, må fikses i dag», 9 er «grusvei til en
hytteklynge». Meldingene kommer inn hulter til bulter gjennom dagen, og laget
skal alltid ta den mest hastende først.

En vanlig kø hjelper ikke — den gir deg den eldste meldingen, ikke den mest
hastende. En sortert liste hjelper heller ikke helt: hver nye melding må settes
inn på riktig plass, og det koster O(n)O(n) når lista er lang.

Det du trenger, er en struktur der du kan gjøre to ting billig: legge inn en
ny melding, og plukke ut den med lavest hastetall. Det er en prioritetskø, og
den vanligste måten å bygge den på er en heap.

Trikset i heapen er at den ikke holder alt sortert. Den holder bare akkurat nok
orden til at det minste elementet alltid ligger øverst — og «akkurat nok» er
billig å vedlikeholde.

Prioritetskø

En samling der du kan legge inn elementer og alltid hente ut det med høyest
prioritet
— i en min-prioritetskø betyr det det minste elementet.

Kontrakten er tre operasjoner: Insert (legg inn), RemoveMin (fjern og
returner det minste) og FindMin (les det minste). Med en min-heap koster de
O(logn)O(\log n), O(logn)O(\log n) og O(1)O(1). Prioritetskøen er ikke en datastruktur i seg
selv, men en kontrakt som kan oppfylles på flere måter — se
kap. 1.4 om at et balansert søketre gir samme orden.

Heap-egenskapen (min-heap)

I en min-heap er hver forelder mindre enn eller lik begge barna sine.

Ordningen går opp og ned, aldri sideveis: det står ingenting om forholdet
mellom to søsken, og ingenting om forholdet mellom to noder på samme nivå.
Venstre barn kan godt være større enn høyre barn.

Konsekvensen er at det minste elementet garantert ligger på rota — og bare
det. Resten av strukturen er så godt som usortert.

Strukturkravet — et komplett binært tre

En heap er alltid et komplett binært tre: alle nivåer er helt fylt, bortsett
fra det nederste, som fylles fra venstre.

Dette er ikke pynt. Det er strukturkravet som gjør at treet kan lagres som ett
array uten hull, og som holder høyden på log2n\lfloor \log_2 n \rfloor uansett
hvilke verdier som legges inn. En heap kan aldri bli skjev slik et vanlig
søketre kan.

📜Heap-egenskapen er ikke BST-egenskapen

Dette er den vanligste forvekslingen i hele Del 4, og den har eget nummer i bokas
feilregister: felle #9.

BST (kap. 4.1)Min-heap
Retning på ordningenvenstre–høyreopp–ned
Regelenalt i venstre subtre er mindre enn noden, alt i høyre er størreforelderen er mindre enn eller lik begge barna
Hvor ligger det minste?lengst til venstrepå rota, indeks 0
Hva gir in-order?den sorterte rekkefølgeningenting nyttig
Formkan bli en skjev kjedealltid komplett

Kontrollspørsmålet som skiller dem på to sekunder: sier regelen noe om
forholdet mellom søsken? I et BST gjør den det indirekte — alt til venstre
for en node er mindre enn alt til høyre. I en heap gjør den det ikke. Er du
usikker på om 9 kan stå til venstre for 4 i en min-heap: ja, hvis de har hver
sin forelder som begge er små nok.
Og den direkte følgen: påstanden «i en min-heap er venstre barn alltid mindre

enn høyre barn» er usann, og den er en fast distraktor på Del 1.

Array-representasjonen med indeks fra 0
Heapen lagres som ett array, uten pekere. Rota ligger på indeks 0, og
nivåene legges etter hverandre fra venstre mot høyre.

forelder(i)=(i1)/2,barn(i)=2i+1 og 2i+2\text{forelder}(i) = \lfloor (i-1)/2 \rfloor, \qquad \text{barn}(i) = 2i+1 \ \text{og} \ 2i+2

Fordi treet er komplett, er det ingen hull i arrayet, og hver indeks kan regnes
om til forelder og barn med ren aritmetikk. IN2010 indekserer fra 0, og
formlene over er de eneste som gjelder i denne boka. Framstillinger som lar rota
ligge på indeks 1, bruker andre formler, og de gir feil node her.

Barneindeksene og kanttilfellene

Elementet på indeks ii har venstre barn på 2i+12i+1 og høyre barn på 2i+22i+2 — men
bare hvis indeksene finnes.

Med nn elementer i heapen gjelder:

- 2i+1n2i+1 \ge n: noden er et blad og har ingen barn.
- 2i+1<n2i+1 < n men 2i+2n2i+2 \ge n: noden har bare venstre barn.
- ellers: noden har begge barna.

Disse tre tilfellene er hele innholdet i felle #3 — down-heap som ikke
sjekker at barnet finnes.

✏️Eksempel 1: Familien til hver indeks

Min-heapen i figuren over er arrayet 2, 5, 8, 10, 25, 20, 30 med indeks fra 0.
Sett opp forelderen og de to barna til hver indeks, og si hvilke noder som er
blader.

Regn mekanisk: forelder er (i1)/2\lfloor (i-1)/2 \rfloor, barna er 2i+12i+1 og 2i+22i+2,
og en indeks som er 7\ge 7 finnes ikke, siden n=7n = 7.

Indeks iiH[i]ForelderVerdiVenstre barn 2i+12i+1Høyre barn 2i+22i+2
02--1 (verdi 5)2 (verdi 8)
15023 (verdi 10)4 (verdi 25)
28025 (verdi 20)6 (verdi 30)
310157 finnes ikke8 finnes ikke
425159 finnes ikke10 finnes ikke
5202811 finnes ikke12 finnes ikke
6302813 finnes ikke14 finnes ikke

Bladene er indeks 3, 4, 5 og 6 — fire av sju noder. De indre nodene er 0, 1
og 2.
Kontroller heap-egenskapen med tabellen: 252 \le 5 og 282 \le 8;
5105 \le 10 og 5255 \le 25; 8208 \le 20 og 8308 \le 30. Alle seks
forelder–barn-parene holder, så dette er en gyldig min-heap.
Legg merke til indeks 1 og 2. Verdien 5 står til venstre for 8, men det er
ren tilfeldighet — heap-egenskapen sier ingenting om søsken. Hadde arrayet vært
2, 8, 5, 10, 25, 20, 30, ville det fortsatt vært en gyldig min-heap, selv om

det da hadde vært et brudd på BST-egenskapen (felle #9).
Regn med 0-formlene, ikke 1-formlene. Med i/2\lfloor i/2 \rfloor ville

forelderen til indeks 6 blitt 3 i stedet for 2 — altså feil node, feil verdi og
feil svar på resten av oppgaven.

📝Oppgave 1

(Innstegsoppgave.) En min-heap ligger som array med indeks fra 0 og har n=12n = 12
elementer.

a) Hvilken indeks har forelderen til indeks 6?
b) Hvilke indekser har barna til indeks 4?
c) Har indeks 5 to barn, ett barn eller ingen barn?

Løkke 2 — Insert og sift-up (ca. 13 min)

Et nytt element skal inn. Hvor?

Strukturkravet svarer med én gang: heapen er et komplett tre, så det er nøyaktig
én plass det nye elementet kan ligge — den første ledige, altså indeks nn
bakerst i arrayet. Der legger vi det, og så er heap-egenskapen antakelig brutt,
fordi elementet kan være mindre enn forelderen sin.

Reparasjonen heter sift-up: sammenlign med forelderen, bytt hvis elementet er
mindre, og gjenta oppover. Du stopper når forelderen er mindre enn eller lik
elementet, eller når du har nådd rota.

Legg merke til hvor lite som skjer. Du berører én sti fra bunnen opp til
rota, og den er log2n\lfloor \log_2 n \rfloor ledd lang. Resten av heapen røres
ikke.

📜Pseudokode-kontrakt: `Insert`
Antagelser om representasjon. H er et array med indeks fra 0 som allerede
oppfyller heap-egenskapen. |H| er antall elementer. Divisjonen (i-1)/2 er
heltallsdivisjon, altså med nedrunding.

Prebetingelse: H er en gyldig min-heap. Postbetingelse: H inneholder
x i tillegg til alt den hadde før, og er fortsatt en gyldig min-heap.

Procedure Insert(H, x)
  Input:  min-heap H som array (indeks fra 0), element x
  Output: H med x satt inn, heap-egenskapen gjenopprettet
  i = |H|
  H[i] = x
  while i > 0 and H[(i-1)/2] > H[i]:
      bytt H[i] og H[(i-1)/2]
      i = (i-1)/2
  Kjoeretid: O(log n)

Invarianten i én setning: etter hvert bytte er alt under i en gyldig
min-heap, og det eneste mulige bruddet ligger mellom i og forelderen — så når
løkka stopper, finnes det ingen brudd.

Merk i > 0 som første ledd i betingelsen. Uten den ville løkka regnet ut
forelderen til rota, og indeks 0 har ingen forelder. Rekkefølgen på de to leddene
er ikke likegyldig: i > 0 må stå først, slik at det andre leddet aldri
evalueres når i er 0.

Kjøretid: O(logn)O(\log n), der nn er antall elementer i heapen. Elementet
flyttes høyst ett nivå per bytte, og treet har log2n\lfloor \log_2 n \rfloor
nivåer over bunnen — én sti, ingen forgrening.

✏️Eksempel 2: Bygg en heap med fem innsettinger

Sett inn 7, 3, 9, 1 og 4 i denne rekkefølgen i en tom min-heap (array,
indeks fra 0). Oppgi arrayet til slutt.

Innsetting i TOM min-heap, indeks fra 0: 7, 3, 9, 1, 4

StegSett innArray før sift-upBytter (indekser)Array etter steget
177ingen7
237, 31<->03, 7
393, 7, 9ingen3, 7, 9
413, 7, 9, 13<->1 ; 1<->01, 3, 9, 7
541, 3, 9, 7, 4ingen1, 3, 9, 7, 4

Sluttilstand (arrayet, indeks fra 0): 1, 3, 9, 7, 4
                1
               (0)
        3               9
       (1)             (2)
    7       4
   (3)     (4)
- gyldig min-heap: JA | antall elementer: 5
Steg 4 er det som er verdt å studere. Etter at 1 er lagt på indeks 3, er
forelderen indeks (31)/2=1\lfloor (3-1)/2 \rfloor = 1 med verdien 7. Siden 1<71 < 7,
byttes de. Nå står 1 på indeks 1, og forelderen er indeks

(11)/2=0\lfloor (1-1)/2 \rfloor = 0 med verdien 3. Siden 1<31 < 3, byttes de igjen, og

1 er på rota. Det er nøyaktig de to byttene figuren over viser.

Steg 5 er det som er lett å overse. 4 legges på indeks 4, forelderen er

indeks 3/2=1\lfloor 3/2 \rfloor = 1 med verdien 3, og 4>34 > 3. Ingen bytte. Det
er helt normalt at en innsetting ikke flytter noe — mange håndkjøringer går galt
fordi kandidaten «føler» at det burde skje noe.
Kontroll: heapen har fem elementer, og de fem tallene du fikk utdelt er alle
med. Sjekk deretter de tre forelder–barn-parene: 131 \le 3, 191 \le 9,

373 \le 7, 343 \le 4. Alt holder.
Fellenote. Fella her er felle #9 — å prøve å holde arrayet sortert, som
om det var et søketre. 1, 3, 9, 7, 4 er ikke sortert, og skal ikke være det. En
heap er sortert oppover, ikke bortover.

📝Oppgave 2
Sjanger E

Sett inn 12, 9, 14, 6, 11 og 3 i denne rekkefølgen i en tom
min-heap (array, indeks fra 0). Oppgi arrayet til slutt.

📝Oppgave 3
Sjanger E

Sett inn 5, 8, 6, 9, 12, 7 og 10 i denne rekkefølgen i en tom
min-heap (array, indeks fra 0).

a) Oppgi arrayet til slutt.
b) Hvor mange bytter ble utført totalt, og hva forteller det om
innsettingsrekkefølgen?

✏️Eksempel 3: Innsetting i en heap som allerede finnes

Min-heapen 4, 6, 5, 9, 8, 7, 10 er gitt (array, indeks fra 0). Sett inn 2, og
oppgi arrayet etterpå.

Startheap: 4, 6, 5, 9, 8, 7, 10. Sett inn 2.

Legg 2 på første ledige indeks, 7: 4, 6, 5, 9, 8, 7, 10, 2

DelstegHandlingArray etter delsteget
1H[7] = 2 < forelder H[3] = 9, bytt4, 6, 5, 2, 8, 7, 10, 9
2H[3] = 2 < forelder H[1] = 6, bytt4, 2, 5, 6, 8, 7, 10, 9
3H[1] = 2 < forelder H[0] = 4, bytt2, 4, 5, 6, 8, 7, 10, 9
4elementet er på rota, stopp2, 4, 5, 6, 8, 7, 10, 9

Sluttilstand: 2, 4, 5, 6, 8, 7, 10, 9
Dette er verste tilfelle for én innsetting. Elementet 2 er mindre enn alt
annet i heapen, så det må helt fra bunnen til rota: tre bytter i en heap med åtte
elementer. Antall nivåer over bunnen er
log28=3\lfloor \log_2 8 \rfloor = 3, som er nøyaktig det vi teller. Det er hele

begrunnelsen for at Insert er O(logn)O(\log n) — du kan aldri gjøre flere bytter enn

det er nivåer.
Følg indeksene, ikke bildet. Fra indeks 7 er forelderen
6/2=3\lfloor 6/2 \rfloor = 3; fra indeks 3 er den 2/2=1\lfloor 2/2 \rfloor = 1; fra
indeks 1 er den 0/2=0\lfloor 0/2 \rfloor = 0. Kjeden 73107 \to 3 \to 1 \to 0 er
stien opp, og den er den eneste delen av arrayet som endres.
Delsteg 4 er ikke overflødig. Løkka må stoppe fordi i = 0, ikke fordi noen

sammenligning slo feil. Det er i > 0-leddet i pseudokoden som gjør jobben.
Fellenote. Fella her er å legge det nye elementet på «riktig» plass med én
gang, som i en sortert liste. Nye elementer legges alltid på første ledige
indeks, og flyttes deretter. Gjør du noe annet, brytes strukturkravet, og alle

indeksformlene slutter å gjelde.

📝Oppgave 4
Sjanger E

Min-heapen 3, 5, 4, 9, 8, 6, 7 er gitt (array, indeks fra 0). Sett
inn 11, og oppgi arrayet etterpå. Hvor mange bytter kreves?

Løkke 3 — RemoveMin og down-heap (ca. 14 min)

— naturlig pausepunkt —

Nå til den operasjonen prioritetskøen egentlig finnes for: å plukke ut det minste
elementet.

Selve uthentingen er gratis — det minste ligger på indeks 0. Problemet er hullet
som blir igjen. Løsningen er å fylle det med det siste elementet i arrayet
(det eneste som kan fjernes uten å ødelegge strukturkravet), krympe arrayet, og
deretter la det innflyttede elementet synke til det er på plass.

Nedstigningen heter down-heap, og den har to detaljer som er hele forskjellen
mellom full og halv uttelling:

1. Du synker alltid mot det MINSTE av barna. Bytter du med det største, får
du et nytt brudd på veien ned, og heapen blir ugyldig.
2. Du må sjekke at barnet finnes. Nederst i treet har noder ingen barn, og
noen har bare venstre barn. Å lese H[2i+2] uten å sjekke er felle #3 i
bokas feilregister, og den har eksplisitt takpoeng i sensorveiledningene: uten
sjekken får du maks delvis uttelling, uansett hvor riktig resten er.

📜Pseudokode-kontrakt: `RemoveMin` og `DownHeap`
Antagelser om representasjon. H er et array med indeks fra 0 som oppfyller
heap-egenskapen. |H| er antall elementer. Alle divisjoner er
heltallsdivisjoner.

Prebetingelse: H er en gyldig, ikke-tom min-heap. Postbetingelse: det
minste elementet er returnert og fjernet, og H er fortsatt en gyldig min-heap
med ett element færre.

Arbeidet deles i to: RemoveMin gjør selve uthentingen, og overlater
reparasjonen til DownHeap.

Procedure RemoveMin(H)
  Input:  ikke-tom min-heap H som array (indeks fra 0)
  Output: det minste elementet; H er krympet med ett og reparert
  minste = H[0]
  H[0] = H[|H| - 1]
  fjern siste element fra H
  DownHeap(H, 0, |H|)
  return minste
  Kjoeretid: O(log n)

DownHeap(H, i, n) lar elementet paa indeks i synke saa lenge det er stoerre
enn det minste barnet sitt:

Procedure DownHeap(H, i, n)
  Input:  array H, startindeks i, antall elementer n
  Output: H der subtreet med rot i oppfyller heap-egenskapen
  while 2*i + 1 < n:
      minste = 2*i + 1
      if 2*i + 2 < n and H[2*i + 2] < H[minste]:
          minste = 2*i + 2
      if H[i] <= H[minste]:
          bryt ut av loekka
      bytt H[i] og H[minste]
      i = minste
  Kjoeretid: O(log n)

De to sjekkene som utgjør felle #3, står begge i DownHeap:

1. while 2*i + 1 < n — finnes venstre barn i det hele tatt? Er svaret nei,
er noden et blad, og det er ingenting å synke ned til.
2. if 2*i + 2 < n and ... — finnes høyre barn? Er svaret nei, er venstre
barn det eneste kandidaten, og sammenligningen med H[2*i + 2] må ikke
utføres.

Rekkefølgen i det andre leddet er avgjørende: 2*i + 2 < n må stå før
H[2*i + 2] < H[minste], ellers leses en indeks som ikke finnes.

Invarianten i én setning: etter hvert bytte er alt over i en gyldig
min-heap, og det eneste mulige bruddet ligger mellom i og barna — så når løkka
stopper, finnes det ingen brudd.

Kjøretid: O(logn)O(\log n), der nn er antall elementer. Elementet synker høyst
ett nivå per runde, og treet har log2n\lfloor \log_2 n \rfloor nivåer. Selve
uthentingen av H[0] er O(1)O(1) — det er reparasjonen som koster.

✏️Eksempel 4: Én `RemoveMin`

Utfør RemoveMin én gang på min-heapen 2, 5, 3, 9, 8, 4 (array, indeks fra 0).
Oppgi arrayet etterpå.

Startheap (indeks fra 0): 2, 5, 3, 9, 8, 4 — gyldig min-heap: JA

RemoveMin nr. 1 — returnerer 2.

DelstegHandlingArray etter delsteget
1ta vare på H[0] = 2; flytt siste element 4 til rot og krymp arrayet4, 5, 3, 9, 8
2barna er H[1] = 5 og H[2] = 3, minste er H[2] = 3; 4 > 3, bytt3, 5, 4, 9, 8
3indeks 2 har ingen barn (2*2+1 = 5 er utenfor n = 5), stopp3, 5, 4, 9, 8

Array etter RemoveMin nr. 1: 3, 5, 4, 9, 8
                3
               (0)
        5               4
       (1)             (2)
    9       8
   (3)     (4)
- gyldig min-heap: JA
Delsteg 2 er hele poenget. Elementet 4 har barna 5 og 3. Hadde vi byttet med

venstre barn (5) fordi det står først, ville arrayet blitt 5, 4, 3, 9, 8

og der er 5>35 > 3, altså et brudd på heap-egenskapen i rota. Down-heap går

alltid mot det minste barnet, og det er derfor.

Delsteg 3 er den sjekken oppgaven egentlig tester. Etter byttet står 4 på
indeks 2. Venstre barn ville vært indeks 22+1=52 \cdot 2 + 1 = 5, men heapen har nå
bare fem elementer, altså indeksene 0 til 4. Indeks 5 finnes ikke, løkka stopper,
og vi er ferdige. Uten den sjekken ville algoritmen lest utenfor arrayet.

Sluttilstanden du leverer, er hele arrayet: 3, 5, 4, 9, 8. Ikke treet, ikke
bare det som ble flyttet — hele arrayet, kommaseparert, med indeks fra 0.
Fellenote. Fella her er felle #3 — down-heap uten å sjekke at barnet
finnes. Den har eksplisitt takpoeng i sensorveiledningene: leser du H[5] i en

heap med fem elementer, er algoritmen din feil, uansett hvor riktig resten ser
ut.

📝Oppgave 5
Sjanger E

Utfør RemoveMin én gang på min-heapen 3, 7, 5, 12, 9, 6 (array,
indeks fra 0).

a) Oppgi arrayet etterpå.
b) Hvilket element ble returnert, og hvor mange bytter kostet reparasjonen?

Løkke 4 — BuildHeap, og hvorfor den er O(n)O(n) (ca. 10 min)

Du har et vilkårlig array og vil gjøre det om til en heap. Den opplagte måten er
å sette inn ett element om gangen: nn innsettinger à O(logn)O(\log n), altså
O(nlogn)O(n \log n).

Det finnes en bedre måte, og forskjellen er ikke bare konstantfaktorer — den er
en hel OO-klasse.

Ideen: i stedet for å bygge ovenfra og ned, går du nedenfra og opp. Den
nederste halvparten av arrayet er blader, og et blad er allerede en gyldig heap i
seg selv. Start derfor på den siste indre noden, indeks
n/21\lfloor n/2 \rfloor - 1, og kjør DownHeap derfra og bakover til indeks 0.

Procedure BuildHeap(A)
  Input:  vilkaarlig array A med n elementer (indeks fra 0)
  Output: A omgjort til en gyldig min-heap
  n = |A|
  for i = n/2 - 1 ned til 0:
      DownHeap(A, i, n)
  Kjoeretid: O(n)

Hvorfor blir dette billigere? Fordi de fleste nodene ligger nær bunnen og har
kort vei å synke.
Den ene noden som kan synke helt gjennom treet, er rota — og
det er én node. Teoremet under teller det opp.

📜`BuildHeap` er O(n)O(n) — med tellingen

En OO-påstand uten telling er verre enn ingen påstand, så her er tellingen.

Et komplett tre med høyde hh har høyst n/2d+1\lceil n / 2^{d+1} \rceil noder på
dybde dd, og DownHeap fra dybde dd gjør høyst hdh - d bytter — den kan ikke
synke lenger enn til bunnen. Ganger vi de to sammen og summerer over alle dybder,
får vi det totale arbeidet.

n=15n = 15 (høyde h=3h = 3):

Dybde ddAntall noderMaks bytter per node (hdh-d)Produkt
0133
1224
2414
3800
Sum1511

Sum av arbeidet er 11, og 11<2n=3011 < 2n = 30. Arbeidet er altså under 2n2n, ikke
nlogn=45n\log n = 45.
n=31n = 31 (høyde h=4h = 4):
Dybde ddAntall noderMaks bytter per node (hdh-d)Produkt
0144
1236
2428
3818
41600
Sum3126

Sum av arbeidet er 26, og 26<2n=6226 < 2n = 62. Arbeidet er altså under 2n2n, ikke
nlogn=124n\log n = 124.

n=63n = 63 (høyde h=5h = 5):

Dybde ddAntall noderMaks bytter per node (hdh-d)Produkt
0155
1248
24312
38216
416116
53200
Sum6357

Sum av arbeidet er 57, og 57<2n=12657 < 2n = 126. Arbeidet er altså under 2n2n, ikke

nlogn=315n\log n = 315.
Les mønsteret i de tre tabellene. Halvparten av nodene ligger på nederste
nivå og gjør null arbeid. En firedel ligger ett nivå over og gjør høyst ett
bytte. En åttedel gjør høyst to. Antall noder halveres for hvert nivå oppover,
mens arbeidet per node bare vokser med én — og en halvering slår en økning på én.
Totalen holder seg under 2n2n for alle tre nn-verdiene, og forholdet mellom sum
og nn synker ikke ut av kontroll når nn vokser: 11/1511/15, 26/3126/31, 57/6357/63
alle under 1.

Konklusjonen: BuildHeap er O(n)O(n), der nn er antall elementer i arrayet.
Og avgrensningen, som er like viktig: heapsort er likevel

O(nlogn)O(n \log n) — ikke fordi byggingen koster det, men fordi de nn uthentingene

etterpå koster O(logn)O(\log n) hver. Det er blandingen av de to fasene som skaper
feilen «BuildHeap er O(nlogn)O(n \log n)». Se
kap. 2.2.

✏️Eksempel 5: `BuildHeap` på ni elementer

Gjør arrayet 9, 4, 7, 1, 8, 3, 6, 2, 5 om til en min-heap med BuildHeap.
Oppgi arrayet til slutt og antall bytter.

Array før: 9, 4, 7, 1, 8, 3, 6, 2, 5 (n = 9)

Down-heap kjøres fra indeks 3 (= n/2 - 1) og nedover til 0. Indeksene 4 til 8 er blader og har ingen barn.

StegIndeks iVerdiBytterArray etter steget
131ingen9, 4, 7, 1, 8, 3, 6, 2, 5
2272<->59, 4, 3, 1, 8, 7, 6, 2, 5
3141<->3 ; 3<->79, 1, 3, 2, 8, 7, 6, 4, 5
4090<->1 ; 1<->3 ; 3<->71, 2, 3, 4, 8, 7, 6, 9, 5

Sluttilstand: 1, 2, 3, 4, 8, 7, 6, 9, 5
                                1
                               (0)
                2                               3
               (1)                             (2)
        4               8               7               6
       (3)             (4)             (5)             (6)
    9       5
   (7)     (8)
- gyldig min-heap: JA
- totalt antall bytter: 6 (mot n = 9 elementer)
Merk hvor løkka starter. Med n=9n = 9 er 9/21=3\lfloor 9/2 \rfloor - 1 = 3.

Indeksene 4 til 8 hoppes helt over, fordi de er blader — fem av ni noder gjør

ingenting overhodet. Det er halve forklaringen på O(n)O(n) i praksis.

Merk arbeidsfordelingen. Steg 1 gir null bytter, steg 2 gir ett, steg 3 gir
to, og steg 4 — rota — gir tre. Nøyaktig det mønsteret teoremet over teller:

mange noder med lite arbeid, én node med mest.
Totalen: 6 bytter for 9 elementer. Til sammenligning ville nlognn \log n her
vært omtrent 93=279 \cdot 3 = 27. Forskjellen er ikke en konstantfaktor du kan se

bort fra, den er en OO-klasse.
Fellenote. Fella her er å kjøre DownHeap ovenfra og ned i stedet for
nedenfra og opp. Går du fra indeks 0 og oppover, kan et element du «reparerte»

tidlig bli feilplassert av en senere reparasjon — og resultatet er ikke en gyldig
heap. Nedenfra og opp virker fordi hvert subtre allerede er en gyldig heap når du
kommer til rota i det.

📝Oppgave 6
Sjanger E

Gjør arrayet 10, 20, 15, 30, 40, 5, 25 om til en min-heap med
BuildHeap.

a) På hvilken indeks starter løkka, og hvorfor?
b) Oppgi arrayet til slutt og antall bytter.

Løkke 5 — de faste heap-faktaene (ca. 5 min)

Fire påstander om heaper går igjen på Del 1, år etter år. Tre av dem er laget for
å fange den som «nesten» husker. Her er de, med begrunnelsene.

Sift-up

Reparasjonen etter en innsetting: det nye elementet sammenlignes med forelderen
sin og bytter oppover så lenge det er mindre.

Én sti fra bunnen mot rota, høyst log2n\lfloor \log_2 n \rfloor bytter, altså
O(logn)O(\log n). Ingen andre deler av heapen berøres. Stopper når forelderen er
mindre enn eller lik elementet, eller når rota er nådd.

Down-heap

Reparasjonen nedover: elementet sammenlignes med det minste av barna sine og
synker så lenge det er størst.

Én sti fra rota mot bunnen, høyst log2n\lfloor \log_2 n \rfloor bytter, altså
O(logn)O(\log n). To sjekker er obligatoriske: at venstre barn finnes
(2i+1<n2i+1 < n), og at høyre barn finnes (2i+2<n2i+2 < n) før det sammenlignes.
Å utelate dem er felle #3.

`Insert` i en min-heap

Legger elementet på første ledige indeks, altså bakerst i arrayet, og lar det
stige med sift-up.

O(logn)O(\log n) i verste tilfelle, der nn er antall elementer. Beste tilfelle er
O(1)O(1) — når elementet allerede er større enn forelderen sin der det landet.
Strukturkravet gjør at det ikke finnes noen annen plass å legge det.

`RemoveMin`

Returnerer H[0], flytter det siste elementet til rota, krymper arrayet og
kjører down-heap fra indeks 0.

O(logn)O(\log n), der nn er antall elementer. Selve uthentingen er O(1)O(1); det er
reparasjonen som koster. Merk at det er det siste elementet som flyttes opp,
ikke et av barna — alt annet ville brutt strukturkravet.

`FindMin`

Leser det minste elementet uten å fjerne det. Det ligger alltid på indeks 0.

O(1)O(1). Dette er heapens billigste operasjon og hele grunnen til at strukturen
brukes som prioritetskø. Kontrast: å finne det største i en min-heap er
O(n)O(n).

`BuildHeap` er O(n)O(n)

Gjør et vilkårlig array om til en heap ved å kjøre down-heap fra indeks
n/21\lfloor n/2 \rfloor - 1 og bakover til 0.

O(n)O(n)ikke O(nlogn)O(n \log n). Grunnen er arbeidsfordelingen: halvparten av
nodene er blader og gjør ingenting, en firedel synker høyst ett nivå, en åttedel
høyst to. Summen holder seg under 2n2n. Påstanden «å bygge en heap fra et
vilkårlig array tar O(nlogn)O(n \log n)» er en fast distraktor, og den er usann.

✏️Eksempel 6: To arrayer — hvilket er en gyldig min-heap?

Avgjør for hvert av arrayene om det er en gyldig min-heap (indeks fra 0).

a) 1, 5, 3, 8, 6, 4, 2
b) 2, 4, 3, 7, 5, 9, 6

Framgangsmåten er mekanisk: gå gjennom hver indre node og sammenlign med begge
barna. Med sju elementer er de indre nodene 0, 1 og 2, altså seks par å sjekke.

a) Array: 1, 5, 3, 8, 6, 4, 2

                1
               (0)
        5               3
       (1)             (2)
    8       6       4       2
   (3)     (4)     (5)     (6)

- gyldig min-heap: NEI — H[2] = 3 har barnet H[6] = 2, og 2 < 3.
- kontroll av alle foreldre-barn-par:
- H[0] = 1 mot H[1] = 5: OK
- H[0] = 1 mot H[2] = 3: OK
- H[1] = 5 mot H[3] = 8: OK
- H[1] = 5 mot H[4] = 6: OK
- H[2] = 3 mot H[5] = 4: OK
- H[2] = 3 mot H[6] = 2: BRUDD

b) Array: 2, 4, 3, 7, 5, 9, 6

                2
               (0)
        4               3
       (1)             (2)
    7       5       9       6
   (3)     (4)     (5)     (6)

- gyldig min-heap: JA — hver forelder er mindre enn eller lik begge barna sine.
- kontroll av alle foreldre-barn-par:
- H[0] = 2 mot H[1] = 4: OK
- H[0] = 2 mot H[2] = 3: OK
- H[1] = 4 mot H[3] = 7: OK
- H[1] = 4 mot H[4] = 5: OK
- H[2] = 3 mot H[5] = 9: OK
- H[2] = 3 mot H[6] = 6: OK

Sammenlign de to. I (b) står 9 til venstre for 6, altså er venstre barn
større enn høyre barn — og det er ikke noe brudd. I (a) er det heller ikke
søskenforholdet som feiler, men at 2 ligger under 3. Ordningen går opp og ned.

Ett par er nok. Så snart du finner ett brudd, er svaret nei, og du kan
stoppe. Sensor ber som regel om hvilket par som bryter — oppgi indeksene, ikke
bare verdiene.

Fellenote. Fella her er felle #9 — å lete etter BST-egenskapen i stedet
for heap-egenskapen. Er du i tvil: sjekk bare forelder mot barn, aldri søsken
mot søsken.

📝Oppgave 7
Sjanger C

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

a) Du kan finne det største elementet i en min-heap i O(logn)O(\log n).
b) Å gjøre et vilkårlig array om til en heap koster O(nlogn)O(n \log n).
c) I et komplett binært tre ligger over halvparten av nodene på de to
nederste nivåene.
d) En min-heap blir en gyldig max-heap hvis du reverserer arrayet.

📝Oppgave 8
Sjanger E, krevende

En kollega har skrevet denne prosedyren og påstår at den
reparerer en min-heap nedover:

Procedure DownHeap(H, i, n)
  Input:  array H, startindeks i, antall elementer n
  Output: H reparert nedover fra i
  while i < n:
      venstre = 2*i + 1
      hoeyre = 2*i + 2
      if H[venstre] < H[hoeyre]:
          minste = venstre
      else:
          minste = hoeyre
      bytt H[i] og H[minste]
      i = minste

a) Finn alle feilene.
b) Vis konkret hva som går galt når prosedyren kjøres med i = 0 på arrayet
4, 5, 3, 9, 8 med n=5n = 5.
c) Skriv en korrekt versjon.

Begrepsbank

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

Barnsjekken i down-heap

De to sjekkene som må stå i enhver down-heap: 2*i + 1 < n for om venstre barn
finnes i det hele tatt, og 2*i + 2 < n før høyre barn brukes i en
sammenligning.

Uten dem leser algoritmen utenfor arrayet. Dette er felle #3 i bokas
feilregister, og den eneste fellen i Del 4 med eksplisitt takpoeng: mangler
sjekken, er full uttelling utelukket uansett hvor riktig resten er.

Down-heap går mot det minste barnet

Når et element skal synke, byttes det med det minste av de to barna — ikke
med venstre barn, og ikke med det største.

Grunnen: bytter du med det største, blir det du flyttet opp større enn søskenet
sitt, og du har laget et nytt brudd rett under deg. Med det minste blir den nye
forelderen mindre enn begge barna, og bruddet forsvinner.

Max i en min-heap er O(n)O(n)

Det største elementet i en min-heap ligger i et blad, men heap-egenskapen
sier ikke hvilket. Bladene er omtrent halvparten av nodene, og alle må sjekkes.

Kjøretiden er derfor O(n)O(n), ikke O(logn)O(\log n). Påstanden «du kan finne det
største elementet i en min-heap i O(logn)O(\log n)» er usann og er en fast
distraktor på Del 1. Trenger du både min og max billig, trenger du to strukturer
— eller en annen struktur.

Reversering gir ikke en max-heap

Å snu arrayet til en min-heap gir ikke en gyldig max-heap.

Reverseringen snur rekkefølgen i arrayet, men den snur ikke forelder–barn-
forholdene: forelderen til indeks ii blir ikke barnet til den reverserte
indeksen. Konkret gir min-heapen 5, 8, 6, 9, 12, 7, 10 reversert
10, 7, 12, 9, 6, 8, 5, der rota 10 har barnet 12 — brudd med én gang. Vil du ha
en max-heap, må du bygge den med motsatt sammenligning.

De to nederste nivåene

I et komplett binært tre ligger over halvparten av nodene på de to nederste
nivåene.

Antall noder dobles for hvert nivå nedover, så det nederste nivået alene rommer
omtrent halvparten. Konkret, for n=15n = 15: nivåene med indeks 3 til 6 (4 noder) og
7 til 14 (8 noder) utgjør 12 av 15 noder, altså 80 %. Denne fordelingen er hele
grunnen til at BuildHeap er O(n)O(n).

Høyden til en heap
h=log2nh = \lfloor \log_2 n \rfloor, der nn er antall elementer og høyden telles i
kanter fra rota til det dypeste bladet.

Fordi treet alltid er komplett, er dette den lavest mulige høyden for nn noder —
en heap kan aldri bli skjev slik et vanlig søketre kan. Det er derfor Insert og
RemoveMin er O(logn)O(\log n) også i verste tilfelle. Merk skillet: en heap med 8
elementer har høyde 3 og fire nivåer.

Max-heap

Speilbildet av min-heapen: hver forelder er større enn eller lik begge barna,
og det største elementet ligger på rota.

Samme struktur, samme indeksformler, samme kjøretider — bare motsatt
sammenligning i sift-up og down-heap. En max-heap fås ved å bygge på nytt med
snudd sammenligning, ikke ved å reversere arrayet til en min-heap.

Heapsort

Bygger en heap av arrayet (O(n)O(n)) og henter deretter ut elementene ett for ett
(nn ganger O(logn)O(\log n)).

Totalt O(nlogn)O(n \log n) i alle tilfeller, in-place, men ikke stabil —
uthentingen flytter elementer over lang avstand. Merk hvor de to tallene kommer
fra: byggingen er lineær, uthentingen er logaritmisk per element. Se
kap. 2.2.

Svarformatet for en heap-håndkjøring
Hele arrayet, kommaseparert, med indeks fra 0. Ikke treet, ikke bare
elementene som flyttet seg.

Delvis riktig array gir delvis uttelling, så lever alltid noe. Tegner du treet i
tillegg, er det greit — men arrayet må stå der, for det er det sensor ber om.

Heap kontra balansert søketre som prioritetskø

Begge gir O(logn)O(\log n) på innsetting og uthenting av det minste elementet, så i
OO-klasse er de like — påstanden om at et AVL-tre kan brukes som prioritetskø
med samme orden som en heap, er sann.

Heapen vinner likevel i praksis: den er ett array uten pekere, den har lave
konstantfaktorer, og den bygges på O(n)O(n). Søketreet vinner når du i tillegg
trenger sortert traversering eller søk på vilkårlige nøkler. Se
kap. 4.3.

Heap-egenskapen er ikke BST-egenskapen

Heapen ordner opp–ned: forelderen er mindre enn eller lik begge barna. Et
binært søketre ordner venstre–høyre: alt til venstre er mindre, alt til høyre
er større.

Å blande dem er felle #9 i bokas feilregister. To følger å merke seg: i en
min-heap kan venstre barn godt være større enn høyre barn, og arrayet til en heap
er ikke sortert. Se kap. 4.1.

Sjanger E — håndkjøring av datastruktur

Del 1-sjangeren der du får en struktur og en operasjonsrekke og skal oppgi kun
sluttilstanden
.

Minst én per sett, ofte to, og heapen er den hyppigste kandidaten (86 %, 6 av 7
sett). Formatet er strengt: hele arrayet, indeks fra 0. Delvis riktig tilstand
gir delvis uttelling, så la aldri svaret stå tomt.

Sjanger C — kjøretids- og teori-fakta

Del 1-sjangeren med sant/usant-påstander som rettes automatisk, med
antigjettings-skalering: summen skaleres slik at ren gjetting i snitt gir
null poeng.

Heap-faktaene er blant de mest brukte: BuildHeap er O(n)O(n), max i en min-heap
er O(n)O(n), reversering gir ikke en max-heap, venstre barn er ikke alltid minst.
Merk den andre halvdelen av regelen: på vanlige korte svar, som ikke er
strafferammet, teller ubesvart som feil, så der skal du alltid svare.

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.