Tilbake
6.3

6.3 Minimale spenntrær — Prim, Kruskal, Borůvka

Minimale spenntrær (MST) — Prim med prioritetskø, gjenkjenning av Kruskal/Borůvka, og hvorfor BFS/DFS *ikke* gir MST.

50 min
6 oppgaver
Minimale spenntrærPrimKruskalBorůvka
Din fremgang i kapitlet
0 / 6 oppgaver

Forkunnskaper

- kap. 6.2 — prioritetskøen som verktøy, og
DecreaseKey. Prim er bygget nøyaktig som Dijkstra, men med én avgjørende
forskjell i hva som legges i køen.
- kap. 4.4 — min-heapen, som er prioritetskøen. RemoveMin
og DecreaseKey er O(logn)O(\log n) hver.
- kap. 5.2 — BFS og DFS. De brukes både som kontrast (de
finner ikke minimale spenntrær) og som verktøy i selve modelleringen.
- kap. 5.1 — grafnotasjonen og nabolistene.

Notasjons- og pseudokodeliste

Løkke 1 — å koble alt sammen så billig som mulig (ca. 12 min)

Sju gårder i en dal skal få fiber. Mellom noen av gårdene er det mulig å grave, og
hver mulig grøft har en pris i hundretusen kroner. Alle gårdene må ende opp
tilkoblet — men det er likegyldig hvordan de henger sammen, så lenge det går an
å komme fra hvilken som helst gård til hvilken som helst annen gjennom nettet.

Hva er den billigste måten?

To observasjoner løser halve problemet. For det første: løsningen kan aldri
inneholde en sykel. Har du en sykel, kan du fjerne den dyreste kanten i den, og
alt henger fortsatt sammen — bare billigere. For det andre: løsningen må ha
nøyaktig V1|V| - 1 kanter. Færre, og noe henger løst; flere, og du har en sykel.

En sammenhengende graf uten sykler er et tre. Vi leter altså etter et tre.

Spenntre

En delmengde av kantene som holder alle nodene sammenhengende, og som ikke
inneholder noen sykel.

Et spenntre over V|V| noder har alltid nøyaktig V1|V| - 1 kanter. En
sammenhengende graf har som regel mange forskjellige spenntrær — både BFS og DFS
produserer ett hver, uten at noen av dem trenger å være billig.

Minimalt spenntre (MST)

Det spenntreet som har lavest samlet kantvekt av alle spenntrær i grafen.

Er alle kantvektene forskjellige, er det minimale spenntreet entydig — det
finnes bare ett. Er noen vekter like, kan det finnes flere, men de har alle samme
totalvekt.

Merk hva som ikke er kravet: et minimalt spenntre gir ingen garanti om korteste
vei mellom to bestemte noder. Det minimerer summen, ikke enkeltavstander.

📜Hvilke algoritmer finner et minimalt spenntre — og hvilke gjør det ikke

Dette er avkryssingen som går igjen, og den har ett fast mønster:

AlgoritmeFinner MST?Merknad
Primjavokser ett tre fra en startnode
Kruskaljatar kanter i vektrekkefølge, hopper over dem som lager sykel
Borůvkajahver komponent velger sin letteste utkant, i runder
BFSneifinner et spenntre, men ser ikke på vektene i det hele tatt
DFSneisamme sak — et spenntre, valgt vilkårlig
Dijkstraneifinner korteste veier fra én node, ikke billigste nett

De tre øverste er alle grådige: de tar den billigste lovlige kanten som er
tilgjengelig, og angrer aldri. At det faktisk gir et globalt minimum, er et
resultat du skal kjenne, ikke bevise.
De tre nederste er fellene. BFS og DFS produserer et spenntre som en
bieffekt av traverseringen — kantene de bruker, er de kantene de tilfeldigvis kom
til først. Dijkstra minimerer noe helt annet: avstanden fra én bestemt node til
alle andre. Et Dijkstra-tre og et minimalt spenntre er sjelden det samme treet.

📝Oppgave 1

(Innstegsoppgave, sjanger F — matriseavkryssing, altså at du kobler egenskap til
algoritme.) Sett kryss: finner algoritmen et minimalt spenntre?

a) Prim
b) Dybde-først-søk
c) Kruskal
d) Dijkstra
e) Borůvka

Løkke 2 — Prim: la treet vokse (ca. 14 min)

Prims algoritme er Dijkstra med én linje endret, og den linja er verdt å stoppe
opp ved.

I Dijkstra er nøkkelen til en node avstanden fra startnoden:
avstand[u] + vekt. I Prim er nøkkelen bare vekten på kanten inn til treet:
vekt. Prim bryr seg ikke om hvor langt det er tilbake til startnoden — den vil
bare koble den neste noden på billigst mulig vis.

Alt annet er likt: en prioritetskø over noder som ikke er i treet ennå, RemoveMin
for å velge den neste, og DecreaseKey når en billigere forbindelse dukker opp.

📜Pseudokode-kontrakt: `Prim`
Antagelser om representasjon. G=(V,E)G = (V, E) er en urettet, vektet og
sammenhengende graf gitt som nabolister, der G.naboer(v) gir parene
(u, vekt). noekkel og fra er arrayer indeksert på node. Prioritetskøen PQ
er en binær min-heap ordnet på noekkel, med RemoveMin og DecreaseKey i
O(logV)O(\log |V|).

Prebetingelse: grafen er sammenhengende (ellers finnes ikke noe spenntre —
da får du et minimalt spennskog per komponent).
Postbetingelse: kantene (fra[v], v) for alle v unntatt s utgjør et
minimalt spenntre.

Procedure Prim(G, s)
  Input:  urettet, sammenhengende, vektet graf G = (V, E) som nabolister;
          startnode s
  Output: for hver node v: kanten (fra[v], v) som knytter v til treet
  for hver v i V:
      noekkel[v] = uendelig
      fra[v] = ingen
  noekkel[s] = 0
  PQ = min-heap med alle noder i V, ordnet paa noekkel
  while PQ er ikke tom:
      u = PQ.RemoveMin()          // u tas inn i treet
      for hver (v, vekt) i G.naboer(u):
          if v er fortsatt i PQ and vekt < noekkel[v]:
              noekkel[v] = vekt
              fra[v] = u
              PQ.DecreaseKey(v, vekt)
  return fra

Grunnideen i én setning: den letteste kanten som går ut av treet, kan alltid
tas med — ethvert spenntre må krysse skillet mellom treet og resten et sted, og
det er billigst å gjøre det her.

Kjøretid: O((V+E)logV)O((|V| + |E|)\log |V|). Tell operasjonene: V|V| kall på
RemoveMin à O(logV)O(\log |V|), og til sammen E|E| gjennomganger av nabolister (hver
kant én gang fra hver ende i en urettet graf, altså 2E2|E| — samme orden) der hver
kan utløse en DecreaseKey à O(logV)O(\log |V|).

På en komplett graf, der alle par er koblet, er
E=V(V1)/2|E| = |V|(|V|-1)/2, og kjøretiden blir O(V2logV)O(|V|^2 \log |V|). Den formen skal du
kunne oppgi når oppgaven sier «alle punkter kan kobles til alle».

Den ene linja som skiller Prim fra Dijkstra er testen vekt < noekkel[v]. I
Dijkstra står det avstand[u] + vekt < avstand[v]. Bytter du dem om, løser du
feil problem — og det er ikke synlig i pseudokoden med mindre du ser etter.

✏️Eksempel 1: Prim på fibernettet

Gravekostnadene mellom de sju gårdene, i hundretusen kroner: AABB 4, AACC 8,
BBCC 2, BBDD 7, CCDD 1, CCEE 5, DDEE 9, DDFF 3, EEFF 6,
EEGG 11, FFGG 10.

Finn det billigste nettet som kobler alle sju gårdene sammen, og oppgi
totalkostnaden. Start i AA.

Prioritetskøen inneholder alle noder som ennå ikke er i treet, med nøkkelen «vekten
på den letteste kjente kanten inn til treet». Kolonnen lengst til høyre viser køens
innhold som heap-array med indeks fra 0.

StegNode tatt inn i treetKant lagt tilSum så langtOppdaterte nøkler (DecreaseKey)Prioritetskø etter (heap-array)
1A— (startnode)0B: ∞ -> 4 (via A), C: ∞ -> 8 (via A)(4, B), (∞, G), (8, C), (∞, D), (∞, E), (∞, F)
2BA–B (4)4C: 8 -> 2 (via B), D: ∞ -> 7 (via B)(2, C), (7, D), (∞, F), (∞, G), (∞, E)
3CB–C (2)6D: 7 -> 1 (via C), E: ∞ -> 5 (via C)(1, D), (5, E), (∞, F), (∞, G)
4DC–D (1)7F: ∞ -> 3 (via D)(3, F), (∞, G), (5, E)
5FD–F (3)10G: ∞ -> 10 (via F)(5, E), (10, G)
6EC–E (5)15ingen(10, G)
7GF–G (10)25ingentom

Sluttilstand — dette er svaret du leverer:
Kanter i treet:  A-B (4), B-C (2), C-D (1), C-E (5), D-F (3), F-G (10)
Total kostnad:   25
Kontrollen: treet har 6 kanter, og V1=71=6|V| - 1 = 7 - 1 = 6. Stemmer.
Se på steg 2. Kanten AACC kostet 8, men i det BB kom inn i treet, ble det
klart at CC kunne nås for 2 via BB. DecreaseKey senket nøkkelen fra 8 til 2, og
fra[C] ble BB i stedet for AA. Den dyre kanten AACC havnet aldri i treet.
Se på steg 5. Her tas FF inn med kanten DDFF (3), og først da blir GG
tilgjengelig i det hele tatt — for 10. Det er den nest dyreste kanten i hele

grafen, og den må likevel med: GG har bare to forbindelser, 10 og 11, og én av dem

må brukes for at GG skal henge sammen med resten.

Momentet: et minimalt spenntre er ikke et tre av billige kanter. Det er det

billigste treet — og noen ganger tvinger strukturen deg til å ta en dyr kant.
Fellenote. Fella her er å stoppe når «det ser sammenhengende ut». Tell
kantene: nøyaktig V1|V| - 1, hverken flere eller færre.

📝Oppgave 2
Sjanger E

En urettet vektet graf har kantene AABB 1, AACC 4,
BBCC 2, BBDD 5, CCDD 3, CCEE 7, DDEE 6.

a) Kjør Prim fra AA. Oppgi kantene i treet og totalvekten.
b) Hvor mange kanter skal treet ha, og stemmer det?
c) Hvilke to kanter ble aldri med, og hvorfor?

Løkke 3 — Kruskal: ta de billigste kantene først (ca. 12 min)

Kruskals algoritme angriper problemet fra motsatt kant. I stedet for å la ett tre
vokse, sorterer den alle kantene etter vekt og går gjennom dem fra billigst til
dyrest. Hver kant tas med, med mindre den ville laget en sykel.

Da trenger den ett hjelpemiddel: en måte å svare raskt på spørsmålet «henger disse
to nodene allerede sammen?». Det er union-find: hver node hører til en
komponent, Find(v) gir komponentens navn, og Union(u, v) slår to komponenter
sammen. Lager kanten en sykel? Nøyaktig når Find(u) og Find(v) gir samme svar.

Union-find

En datastruktur for å holde styr på hvilke elementer som hører til samme
komponent. To operasjoner: Find(v) gir navnet på komponenten, Union(u, v)
slår to komponenter sammen.

I sin enkleste form er hver komponent et tre av pekere til en representant.
Med de vanlige optimaliseringene er begge operasjonene så nær O(1)O(1) som gjør
ingen forskjell — i IN2010 er det nok å vite at de ikke dominerer Kruskals
kjøretid. Det gjør sorteringen.

📜Pseudokode-kontrakt: `Kruskal`
Antagelser om representasjon. G=(V,E)G = (V, E) er en urettet, vektet graf, og
kantene kan listes som trippel (u, v, vekt). En union-find-struktur over VV er
tilgjengelig med Find og Union.

Prebetingelse: ingen. Postbetingelse: T er et minimalt spenntre hvis
grafen er sammenhengende; ellers et minimalt spenntre per komponent.

Procedure Kruskal(G)
  Input:  urettet, vektet graf G = (V, E) som kantliste
  Output: kantmengden T i et minimalt spenntre
  sorter alle kanter i E stigende paa vekt
  lag en union-find-struktur der hver node er sin egen komponent
  T = tom mengde
  for hver kant (u, v, vekt) i sortert rekkefolge:
      if Find(u) er ulik Find(v):
          T.leggTil((u, v))
          Union(u, v)
          if |T| er lik |V| - 1:
              stopp
  return T

Grunnideen i én setning: den billigste kanten som forbinder to komponenter som
ennå ikke henger sammen, kan alltid tas med — det er samme grådighetsargument som
i Prim, bare anvendt på komponenter i stedet for på ett voksende tre.

Kjøretid: O(ElogE)O(|E| \log |E|). Sorteringen av kantene dominerer:
O(ElogE)O(|E| \log |E|). Løkka gjør O(E)O(|E|) Find-par og høyst V1|V| - 1 Union-kall,
og de er så billige at de forsvinner i sammenligning. Siden
EV2|E| \le |V|^2, er logE2logV\log |E| \le 2\log |V|, så uttrykket kan også skrives
O(ElogV)O(|E| \log |V|) — begge former godtas.

Stopp-linja er verdt et delpoeng. Når TT har V1|V| - 1 kanter, er treet
ferdig, og resten av den sorterte lista kan hoppes over.

✏️Eksempel 2: Kruskal på det samme fibernettet

Kjør Kruskals algoritme på fibernettet fra eksempel 1, og sammenlign resultatet
med det Prim ga. Vis union-find-tilstanden underveis.

Kantene sorteres først etter vekt: CCDD 1, BBCC 2, DDFF 3, AABB 4,
CCEE 5, EEFF 6, BBDD 7, AACC 8, DDEE 9, FFGG 10, EEGG 11.

Kolonnen «Komponenter etter» viser union-find-strukturen som en partisjon av
nodene. Find(u) og Find(v) gir navnet på komponenten hver node tilhører — her
representert ved den alfabetisk minste noden i komponenten.

StegKantVektFind(u) / Find(v)BeslutningKomponenter etterSum
1C–D1C / Dvelg — ulike komponenter, slås sammen{A} {B} {C,D} {E} {F} {G}1
2B–C2B / Cvelg — ulike komponenter, slås sammen{A} {B,C,D} {E} {F} {G}3
3D–F3B / Fvelg — ulike komponenter, slås sammen{A} {B,C,D,F} {E} {G}6
4A–B4A / Bvelg — ulike komponenter, slås sammen{A,B,C,D,F} {E} {G}10
5C–E5A / Evelg — ulike komponenter, slås sammen{A,B,C,D,E,F} {G}15
6E–F6A / Aforkast — samme komponent, ville laget sykel{A,B,C,D,E,F} {G}15
7B–D7A / Aforkast — samme komponent, ville laget sykel{A,B,C,D,E,F} {G}15
8A–C8A / Aforkast — samme komponent, ville laget sykel{A,B,C,D,E,F} {G}15
9D–E9A / Aforkast — samme komponent, ville laget sykel{A,B,C,D,E,F} {G}15
10F–G10A / Gvelg — ulike komponenter, slås sammen{A,B,C,D,E,F,G}25

Sluttilstand — dette er svaret du leverer:
Kanter i treet:  C-D (1), B-C (2), D-F (3), A-B (4), C-E (5), F-G (10)
Total kostnad:   25
Sammenlign med Prim. Prim ga kantene AABB, BBCC, CCDD, CCEE,
DDFF, FFGG. Det er nøyaktig de samme seks kantene, bare funnet i en annen
rekkefølge — og med samme totalkostnad 25.
Det er ingen tilfeldighet: alle kantvektene i denne grafen er forskjellige, og da
er det minimale spenntreet entydig. Enhver korrekt MST-algoritme må gi samme
svar.
Se på steg 6 til 9. Fire kanter på rad blir forkastet (EEFF 6, BBDD 7,
AACC 8, DDEE 9). Alle fire har begge endepunktene i den samme komponenten
{A,B,C,D,E,F} — de ville laget en sykel. Det er nettopp den jobben Find gjør,

og den er hele grunnen til at Kruskal trenger union-find.

Se på steg 10. Kanten FFGG (10) er dyr, men GG ligger fortsatt alene i sin

egen komponent, og kanten tas. Etterpå har TT seks kanter, altså V1|V| - 1, og
algoritmen kan stoppe — den siste kanten EEGG (11) trenger aldri å bli sett på.
Fellenote. Fella her er å ta med en kant uten å sjekke komponentene, typisk

den nest billigste kanten ut fra hver node. Da får du sykler, og treet blir
ugyldig. Kontrollen er alltid den samme: nøyaktig V1|V| - 1 kanter til slutt.

📝Oppgave 3
Sjanger E

Kjør Kruskals algoritme på grafen fra oppgave 2: AABB 1,
AACC 4, BBCC 2, BBDD 5, CCDD 3, CCEE 7, DDEE 6.

a) Sett opp kantene i sortert rekkefølge.
b) Kjør algoritmen og vis hvilke kanter som velges og forkastes.
c) Sammenlign svaret med det Prim ga i oppgave 2. Hva forteller sammenligningen
deg?

Borůvkas algoritme

Bør kjenne til. Den tredje MST-algoritmen: i hver runde velger hver komponent
sin egen letteste utkant, og alle de valgte kantene legges til samtidig.

Antall komponenter minst halveres per runde, så det trengs O(logV)O(\log |V|) runder à
O(E)O(|E|) arbeid: O(ElogV)O(|E| \log |V|). På eksamen skal du kunne krysse av at Borůvka
finner et minimalt spenntre — du blir ikke bedt om å håndkjøre den.

Løkke 4 — modelleringen, og fellen som kommer rett etterpå (ca. 12 min)

På eksamen står det aldri «finn et minimalt spenntre». Det står noe slikt som
«kommunen skal legge fiber til alle gårdene så billig som mulig». Jobben din er å
kjenne igjen strukturen.

Mønsteret: når oppgaven ber deg koble alt sammen til lavest mulig samlet
kostnad, og det er likegyldig hvordan forbindelsene går, er svaret et minimalt
spenntre.

Og så kommer oppfølgingsspørsmålet, som er der halve poengsummen ligger: «etterpå
skal en tekniker kjøre fra gård AA til gård GG gjennom det nye nettet.
Hvilken vei?»

Her svarer mange Dijkstra, og det er feil av to grunner.

📜Korteste vei i et ferdig spenntre er BFS eller DFS — ikke Dijkstra

Når du først har bygget spenntreet, og skal finne veien mellom to noder i
treet
, gjelder to ting:

1. Det finnes bare én vei. Et tre har ingen sykler, så mellom to noder finnes
nøyaktig én sti. Det er ingenting å minimere. En enkel traversering — BFS eller
DFS — finner den i O(V+E)O(|V| + |E|), og i et tre er E=V1|E| = |V| - 1, så det er
O(V)O(|V|).

2. Vektene betyr noe annet nå. I det opprinnelige problemet var vekten
byggekostnad — hva det koster å grave grøfta. Å legge sammen byggekostnader
langs en rute gir ikke noe meningsfullt tall: teknikeren betaler ikke for grøfta på
nytt når han kjører gjennom den.

Å kjøre Dijkstra her er derfor ikke bare unødvendig dyrt
(O((V+E)logV)O((|V| + |E|)\log |V|) i stedet for O(V)O(|V|)) — det er å minimere en størrelse
som ikke gir mening i oppgaven. Sensorveiledningene omtaler dette som «ikke
veldefinert», og det gir færre poeng enn traverseringen.

Regelen å ta med seg: når du ser en vekt, spør alltid hva den måler. Er den
en kostnad ved å ha kanten, hører den hjemme i et MST. Er den en kostnad ved å
bruke kanten, hører den hjemme i et korteste-vei-problem.

✏️Eksempel 3: Eksamensnivå — bygg nettet, og finn veien i det

En kommune skal legge fiber mellom sju gårder. Alle mulige grøfter og prisene deres
er kjent (grafen fra eksempel 1).

a) Beskriv en algoritme som finner det billigste nettet som kobler alle gårdene
sammen. Oppgi kjøretid.

b) Etter at nettet er bygget, skal en tekniker kjøre fra gård AA til gård GG
gjennom det nye nettet, og vil vite hvor mange strekk han må innom. Beskriv en
algoritme, og oppgi kjøretid.

a) Problemet navngitt. «Koble alt sammen til lavest samlet kostnad, uten krav
til hvordan» er definisjonen på et minimalt spenntre.

Antagelser om representasjon. Gårdene er nodene i en urettet, vektet,
sammenhengende graf G=(V,E)G = (V, E) gitt som nabolister; V=7|V| = 7 gårder og E|E| er
antall mulige grøfter. Vekten på en kant er gravekostnaden.

Algoritmen: Prim med binær prioritetskø, som i pseudokode-kontrakten over.
Resultatet er de seks kantene AABB (4), BBCC (2), CCDD (1), CCEE (5),
DDFF (3) og FFGG (10), med samlet kostnad 25.

Kjøretid: O((V+E)logV)O((|V| + |E|)\log |V|), der V|V| er antall gårder og E|E| antall
mulige grøfter. Er alle par koblet — en komplett graf — er
E=V(V1)/2|E| = |V|(|V|-1)/2, og kjøretiden blir O(V2logV)O(|V|^2 \log |V|).

Kruskal (O(ElogE)O(|E| \log |E|)) er like riktig og gir samme uttelling.

b) Problemet navngitt. Nå er grafen treet, ikke det opprinnelige nettet.
Mellom to noder i et tre finnes nøyaktig én sti, så det er ingenting å minimere —
det er en ren traverseringsoppgave.

Antagelser om representasjon. Spenntreet TT fra deloppgave a), lagret som
nabolister over de V1|V| - 1 kantene.

Algoritmen:

Procedure VeiITreet(T, a, b)
  Input:  spenntreet T som nabolister, to noder a og b
  Output: antall strekk paa veien fra a til b, og selve veien
  gjor en bredde-forst-traversering fra a i T,
      og noter forgjenger[v] for hver node som oppdages
  les veien baklengs fra b via forgjenger til du naar a
  return lengden paa veien, og veien selv

Kjøring på tallene. Bredde-først fra AA i spenntreet:

StegTatt ut av køenNye noder oppdagetKø etteravstand-tabell etter
1A (avst. 0)B = 1BA=0, B=1, C=∞, D=∞, E=∞, F=∞, G=∞
2B (avst. 1)C = 2CA=0, B=1, C=2, D=∞, E=∞, F=∞, G=∞
3C (avst. 2)D = 3, E = 3D, EA=0, B=1, C=2, D=3, E=3, F=∞, G=∞
4D (avst. 3)F = 4E, FA=0, B=1, C=2, D=3, E=3, F=4, G=∞
5E (avst. 3)ingenFA=0, B=1, C=2, D=3, E=3, F=4, G=∞
6F (avst. 4)G = 5GA=0, B=1, C=2, D=3, E=3, F=4, G=5
7G (avst. 5)ingentomA=0, B=1, C=2, D=3, E=3, F=4, G=5

Antall strekk fra AA til GG er 5, og veien er
A - B - C - D - F - G.
Kjøretid: O(V+ET)O(|V| + |E_T|) der ET=V1|E_T| = |V| - 1, altså O(V)O(|V|) — lineært i
antall gårder.
Hvorfor ikke Dijkstra. To grunner, og begge er verdt å skrive:
1. Det finnes bare én vei mellom to noder i et tre. Det er ingenting å
minimere, så en traversering holder — og den er O(V)O(|V|) mot Dijkstras
O((V+E)logV)O((|V| + |E|)\log |V|).

2. Vektene måler noe annet. De er gravekostnader, ikke reiseavstander. Å
summere dem langs teknikerens rute gir ikke et meningsfullt tall.

Poengtrapp-notat. Deloppgave a) gir uttelling for å navngi MST og velge en
korrekt algoritme med riktig kjøretid — det er hovedmomentet, og det gir mest.

Deloppgave b) skiller: BFS eller DFS i treet er toppsvaret; Dijkstra i treet er

korrekt i den forstand at det finner en vei, men både tregere og basert på en
størrelse oppgaven ikke ba om, og det gir mindre.

📝Oppgave 4
Sjanger H

Et vannverk skal legge rør mellom tolv
tanker. Hver mulig rørstrekning har en anleggskostnad. Alle tankene må henge
sammen.

a) Hvilket problem er dette, og hvilken algoritme velger du?
b) Hvor mange rørstrekninger vil løsningen inneholde?
c) En kollega foreslår å kjøre Dijkstra fra tank 1 og bruke kantene i
resultatet. Hva blir galt?

📝Oppgave 5
Sjanger C

Marker sant eller usant, og begrunn hvert svar med én setning.

a) Et minimalt spenntre inneholder alltid den letteste kanten i grafen.
b) Et minimalt spenntre inneholder aldri den tyngste kanten i grafen.
c) Hvis alle kantvekter er forskjellige, er det minimale spenntreet entydig.
d) Kruskals kjøretid domineres av sorteringen av kantene.
e) Prim og Kruskal kan gi trær med ulik totalvekt på samme graf.

📝Oppgave 6
Sjanger H, krevende

Et selskap har allerede lagt kabel mellom noen av nn
lokasjoner. Nå skal de koble sammen resten, så billig som mulig. De eksisterende
kablene er gratis å bruke; de nye har en kjent gravekostnad hver.

a) Hvordan modellerer du de eksisterende kablene?
b) Skriv algoritmen, og oppgi kjøretiden.
c) Hva blir svaret hvis de eksisterende kablene allerede kobler alle
lokasjonene sammen?

Begrepsbank

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

Prim

Bygger et minimalt spenntre ved å la ett tre vokse fra en startnode: ta alltid den
letteste kanten som går ut av treet.

Kjøretid O((V+E)logV)O((|V| + |E|)\log |V|) med binær prioritetskø, eller
O(V2logV)O(|V|^2 \log |V|) på en komplett graf. Krever at grafen er sammenhengende.

Kruskal

Bygger et minimalt spenntre ved å gå gjennom kantene i stigende vektrekkefølge
og ta med hver kant som ikke lager en sykel.

Kjøretid O(ElogE)O(|E| \log |E|) — sorteringen dominerer. Trenger union-find for å
avgjøre om to noder allerede henger sammen.

BFS og DFS gir IKKE minimalt spenntre

Begge produserer et spenntre som en bieffekt av traverseringen, men de leser ikke
kantvektene i det hele tatt.

Dette er den faste avkryssingsfellen på sjanger F. Kontrollen er enkel: en
algoritme som aldri ser på vektene, kan umulig minimere noe som avhenger av dem.

Antall kanter i et spenntre
T=V1|T| = |V| - 1

Alltid, uansett graf og uansett algoritme. Bruk det som kontrollregning etter en
håndkjøring: flere kanter betyr at du har en sykel, færre at noe henger løst.

Prim mot Dijkstra — den ene linja

Prim: if vekt < noekkel[v] — nøkkelen er vekten på kanten inn til treet.
Dijkstra: if avstand[u] + vekt < avstand[v] — nøkkelen er avstanden fra
startnoden.

Alt annet i de to algoritmene er likt. Bytter du om testen, løser du feil problem,
og det er ikke synlig i pseudokoden med mindre du ser etter.

Korteste vei i et ferdig spenntre

Bruk BFS eller DFS, ikke Dijkstra. Et tre har ingen sykler, så mellom to noder
finnes nøyaktig én sti — det er ingenting å minimere.

Kjøretid O(V)O(|V|), siden treet har V1|V| - 1 kanter. Å bruke Dijkstra her er både
tregere og basert på vekter som måler byggekostnad, ikke reiseavstand.

Entydig minimalt spenntre

Er alle kantvektene forskjellige, finnes det bare ett minimalt spenntre, og
alle korrekte algoritmer gir samme svar.

Er noen vekter like, kan det finnes flere minimale spenntrær — men de har alle
nøyaktig samme totalvekt, så svaret på «hva koster det» er uansett entydig.

Grådig algoritme

En algoritme som tar det beste lokale valget i hvert steg og aldri angrer. Prim,
Kruskal, Borůvka og Huffman er alle grådige.

For MST er grådighet beviselig optimalt — den letteste kanten ut av en delvis
bygget struktur kan alltid tas med. At det holder, er et resultat du skal kjenne,
ikke bevise.

Prims kjøretid på en komplett graf

I en komplett urettet graf er E=V(V1)/2|E| = |V|(|V|-1)/2, altså O(V2)O(|V|^2) kanter.

Satt inn i O((V+E)logV)O((|V| + |E|)\log |V|) gir det O(V2logV)O(|V|^2 \log |V|). Denne formen skal
du oppgi når oppgaveteksten sier at «alle punkter kan kobles til alle» — det er en
dokumentert formulering i arkivet.

Kanter med vekt 0

Modelleringsknepet for «denne forbindelsen finnes allerede og er gratis»: gi kanten
vekt 0.

Enhver MST-algoritme tar da kanten med hvis den kan, siden ingen kant er lettere.
Etterpå leser du av hvilke kanter i treet som har positiv vekt — det er dem som
faktisk må bygges.

Mønstergjenkjenning: når er svaret MST?

Når oppgaven ber deg koble alt sammen til lavest mulig samlet kostnad, og
det er likegyldig hvordan forbindelsene går.

Typiske formuleringer: «alle skal kunne nå alle», «billigst mulig nett», «minst
mulig graving totalt». Er spørsmålet derimot «hvor raskt kommer jeg fra AA til
BB», er det korteste vei — se kap. 6.2.

Hva vekten måler

Spørsmålet som avgjør hvilket problem du står overfor: er vekten en kostnad ved å
ha kanten, eller ved å bruke den?

Byggekostnad og anleggspris hører hjemme i et minimalt spenntre. Reisetid og
avstand hører hjemme i et korteste-vei-problem. Å summere byggekostnader langs en
rute er den dokumenterte fellen i oppfølgingsspørsmålet.

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.