Tilbake
5.3

5.3 Komponenter, 2-fargelegging og sykeldeteksjon

Traverserings-varianter som løser klassiske problemer: finn komponenter, sjekk bipartitthet (2-fargelegging) og oppdag sykler.

50 min
9 oppgaver
Komponenter2-fargeleggingsykeldeteksjon
Din fremgang i kapitlet
0 / 9 oppgaver
Kapitlets plass i kurset

Forkunnskaper

- kap. 5.2 — BFS og DFS. Alle tre algoritmene i dette
kapitlet er en traversering med noen få linjer lagt til, og du bør ha
visited-flagget og den ytre løkka i DFS-full friskt i minne.
- kap. 5.1 — nabolister, grad, sti og sykel. Kjøretidene
her leses ut av hvor mange ganger en kant blir sett på.
- kap. 1.2 — løkketelling. Det er slik O(V+E)O(|V| + |E|)
begrunnes, og en kjøretid uten begrunnelse gir ikke full uttelling.

Er objekt- og feltnotasjonen (v.besokt, v.farge) uvant, hjelper
Klasser og objekter: class, __init__ og self — det er
den samme tanken: en node er et objekt med de feltene algoritmen trenger.

Notasjons- og pseudokodeliste

Løkke 1 — hvor mange atskilte deler består nettet av? (ca. 13 min)

En kommune har lagt fiber mellom koblingsskap rundt om i bygda. Noen skap henger
sammen i en ring, noen i en enkelt streng, og ett skap ble satt opp til et
byggefelt som aldri ble bygget — det står helt for seg selv. Driftsvakta stiller
det samme spørsmålet hver morgen: hvor mange atskilte nett har vi egentlig?
Ryker et skjøtepunkt, hvor langt sprer utfallet seg?

Tegn skapene som noder og fiberstrekkene som kanter, så er spørsmålet hvor mange
sammenhengende komponenter grafen har.

Du kan allerede svare. En DFS fra én node besøker nøyaktig de nodene som er
nåbare derfra — altså nøyaktig én komponent. Det som mangler, er en ytre løkke
som går gjennom alle noder og starter en ny DFS hver gang den treffer en node
ingen har vært innom. Antall slike nystarter er antall komponenter. Det er
DFS-full fra kap. 5.2 med én teller lagt på.

Sammenhengende komponent

En maksimal mengde noder i en urettet graf der det finnes en sti mellom hvert par.

«Maksimal» betyr at du ikke kan legge til én node til uten å bryte egenskapen. En
graf med V|V| noder og ingen kanter har V|V| komponenter; en sammenhengende graf
har nøyaktig 1. En enkelt node uten naboer er en fullverdig komponent — det er den
vanligste tellefeilen.

📜Pseudokode-kontrakt: `CountComponents`
Antagelser om representasjon. G er en urettet graf gitt som nabolister:
for hver node v kan vi iterere over naboene i tid proporsjonal med graden.
Hver node v har feltet v.besokt. Vi antar ingenting om rekkefølgen på
nodene; svaret er det samme uansett.

Prebetingelse: ingen. Postbetingelse: returverdien er antall
sammenhengende komponenter, og alle noder står med v.besokt = sant.

Procedure CountComponents(G)
  Input:  urettet graf G = (V, E) som nabolister
  Output: antall sammenhengende komponenter i G
  for hver node v i V:
      v.besokt = usant
  antall = 0
  for hver node v i V:
      if v.besokt er usant:
          antall = antall + 1
          DFSVisit(v)
  return antall

Procedure DFSVisit(v)
  v.besokt = sant
  for hver nabo u av v:
      if u.besokt er usant:
          DFSVisit(u)

Invarianten i én setning: etter at den ytre løkka har passert node vv, er
alle noder i vv-ens komponent markert som besøkt, og telleren er økt nøyaktig én
gang for den komponenten.

Kjøretid: O(V+E)O(|V| + |E|). Initialiseringsløkka er O(V)O(|V|). Den ytre løkka
går gjennom hver node én gang, men kaller DFSVisit bare for noder som ennå ikke
er besøkt — så på tvers av alle kallene besøkes hver node én gang og hver
kant ses på to ganger, én gang fra hver ende. Summen blir
O(V+2E)O(|V| + 2|E|), som forenkles til O(V+E)O(|V| + |E|), der V|V| er antall noder og
E|E| antall kanter.

Merk hva den ytre løkka koster. Den er grunnen til at DFS-full er
O(V+E)O(|V| + |E|) og ikke O(E)O(|E|): også noder uten en eneste kant må innom. Å
oppgi O(E)O(|E|) her er felle #6 — å forveksle DFS fra én node med DFS over
hele grafen.

✏️Eksempel 1: Fibernettet med tre atskilte deler

Koblingsskapene A til G er forbundet slik nabolistene under viser. A, B og C er
skjøtet sammen i en trekant; D, E og F henger i en streng; G er skapet ved
byggefeltet som aldri ble bygget.

A: B, C
B: A, C
C: A, B
D: E
E: D, F
F: E
G: (ingen)

Kjør CountComponents og oppgi hvor mange atskilte nett kommunen har.

Sporingen av den ytre løkka. Kolonnen «Alt besøkt?» sier om noden allerede
var markert da den ytre løkka kom til den.

Node i ytre løkkeAlt besøkt?Ny komponent funnetTeller
Anei — start DFS herA, B, C1
Bja-1
Cja-1
Dnei — start DFS herD, E, F2
Eja-2
Fja-2
Gnei — start DFS herG3

- antall sammenhengende komponenter: 3
- komponent 1: A, B, C (3 noder)
- komponent 2: D, E, F (3 noder)
- komponent 3: G (1 noder)
Svaret er 3 atskilte nett.
Legg merke til tre ting. For det første at den ytre løkka besøkte alle sju
nodene, men bare startet en ny DFS tre ganger — det er nøyaktig de tre gangene
telleren økte. For det andre at G, som ikke har en eneste kant, er en

komponent; å hoppe over isolerte noder er den vanligste tellefeilen. For det
tredje at rekkefølgen på nodene i den ytre løkka ikke spiller noen rolle for
svaret: hadde vi begynt på G, hadde vi fått komponentene i en annen rekkefølge,
men fortsatt tre.

Fellenote. Fella her er felle #6 — å oppgi kjøretiden O(E)O(|E|), som

gjelder DFS fra én node, i stedet for O(V+E)O(|V| + |E|), som gjelder når den
ytre løkka må innom alle noder. Med G i grafen ser du hvorfor: uten den ytre
løkka hadde skapet ved byggefeltet aldri blitt talt.

📝Oppgave 1

(Innstegsoppgave, sjanger H — grafalgoritme i pseudokode, altså at du får
problemet i ord og skal svare med algoritmen og kjøretiden. Her holder det å
kjøre den for hånd.) Et turkart har seks hytter forbundet med merkede stier:

A: B, C
B: A, D, E
C: A, E
D: B, F
E: B, C, F
F: D, E

a) Kjør den ytre løkka i CountComponents for hånd og fyll ut hvilke noder
som er besøkt når løkka kommer til hver av dem.
b) Hvor mange sammenhengende komponenter har grafen?
c) Hvor mange ganger startet den ytre løkka en ny DFS?

📝Oppgave 2
Sjanger H

Et sensornett i en tunnel har åtte målepunkter forbundet med
kabler:

A: B, C
B: A, D
C: A, D, E
D: B, C, F
E: C, F
F: D, E, G
G: F, H
H: G

a) Kjør den ytre løkka i CountComponents for hånd.
b) Hvor mange komponenter har nettet?
c) Grafen har V=8|V| = 8 og E=9|E| = 9. Hvor mange kanter ville den hatt hvis
den var sammenhengende og uten sykel? Hva forteller differansen deg?

📝Oppgave 3
Sjanger H

Driftsvakta i eksempel 1 vil ikke bare vite hvor mange atskilte
nett de har, men også hvor stort det største er — det avgjør hvor mange kunder
ett enkelt utfall kan ramme.

Skriv LargestComponentSize(G) som returnerer antall noder i den største
sammenhengende komponenten. Oppgi antagelser om representasjon og kjøretid.

Løkke 2 — to grupper, og ingen kant innad (ca. 16 min)

— naturlig pausepunkt —

Et meglingskontor skal fordele partene i en nabotvist på to møterom. Kravet er
enkelt: to som står mot hverandre i saken, skal ikke sitte i samme rom. Noen
av partene har flere motparter, og motpartsforholdene krysser hverandre.
Spørsmålet er om en slik fordeling i det hele tatt finnes.

Tegn partene som noder og motpartsforholdene som kanter. Da spør vi om nodene kan
deles i to grupper slik at ingen kant går innad i en gruppe. En graf der det
er mulig, kalles bipartitt, og problemet kalles 2-fargelegging: gi hver
node en av to farger slik at ingen kant forbinder to like farger.

Framgangsmåten er nesten pinlig enkel. Velg en startnode og gi den rødt. Gå
gjennom naboene og gi dem blått. Deres naboer får rødt igjen. Fortsett til hele
komponenten er farget — og hvis du underveis møter en nabo som allerede har
samme farge som deg
, finnes det ingen gyldig fordeling.

Bipartitt graf

En urettet graf der nodene kan deles i to grupper slik at hver kant går mellom
gruppene, aldri innad i én av dem.

Bipartitt er det samme som 2-fargbar: de to gruppene er de to fargene. Merk
at grafen ikke trenger å være sammenhengende — hver komponent farges for seg, og
grafen er bipartitt hvis og bare hvis alle komponentene er det.

📜Pseudokode-kontrakt: `TwoColor`
Antagelser om representasjon. G er en urettet graf gitt som nabolister.
Hver node v har feltet v.farge med tre mulige verdier: ingen, rod eller
bla. Vi bruker en , altså bredde-først, slik at fargeleggingen sprer seg
lagvis; den rekursive dybde-først-varianten er nøyaktig like riktig og bare
bytter ut køen med kallstakken.

Prebetingelse: ingen. Postbetingelse: returverdien er sant hvis og bare
hvis G er bipartitt. Er svaret sant, står hver node med en farge som er
motsatt av alle naboene sine.

Procedure TwoColor(G)
  Input:  urettet graf G = (V, E) som nabolister
  Output: sant hvis G er bipartitt, ellers usant
  for hver node v i V:
      v.farge = ingen
  for hver node s i V:
      if s.farge er ingen:
          s.farge = rod
          Ko = tom ko
          legg s bakerst i Ko
          while Ko er ikke tom:
              v = ta forrest ut av Ko
              for hver nabo u av v:
                  if u.farge er ingen:
                      u.farge = motsatt av v.farge
                      legg u bakerst i Ko
                  else if u.farge er lik v.farge:
                      return usant
  return sant

Grunnideen i én setning, og den er hele begrunnelsen: når startnoden først
har fått rødt, er hvert eneste senere fargevalg tvunget — en nabo av en rød
node kan bare bli blå — så finner algoritmen en konflikt, finnes det ingen gyldig
fargelegging i det hele tatt, ikke bare ingen som denne kjøringen fant.

Kjøretid: O(V+E)O(|V| + |E|). Initialiseringen er O(V)O(|V|); hver node legges i
køen høyst én gang, siden den får farge i samme øyeblikk, og hver kant ses på to
ganger — én gang fra hver ende.

Den ytre løkka over alle noder er ikke pynt. Uten den farger du bare
komponenten du startet i, og en ikke-bipartitt komponent lenger ute i grafen blir
aldri oppdaget.

✏️Eksempel 2: Seks parter i ring — finnes fordelingen?

Seks parter i en nabotvist har motpartsforhold som danner en ring: A mot B, B mot
C, C mot D, D mot E, E mot F, og F mot A.

A: B, F
B: A, C
C: B, D
D: C, E
E: D, F
F: A, E

Kjør TwoColor fra A og avgjør om partene kan fordeles på to møterom.

Problemet navngitt: dette er 2-fargelegging av en urettet graf, altså
spørsmålet om grafen er bipartitt.

Antagelser om representasjon: nabolister; hver node har feltet v.farge.

Sporingen. Køen vises etter hvert steg; noden som behandles, er tatt ut av
køen først.

StegHandlingKøen etter stegetStatus
1start i A, farge rødAOK
2A -> B: ufarget, gi motsatt farge (blå)BOK
3A -> F: ufarget, gi motsatt farge (blå)B, FOK
4B -> A: allerede rød, motsatt farge — okFOK
5B -> C: ufarget, gi motsatt farge (rød)F, COK
6F -> A: allerede rød, motsatt farge — okCOK
7F -> E: ufarget, gi motsatt farge (rød)C, EOK
8C -> B: allerede blå, motsatt farge — okEOK
9C -> D: ufarget, gi motsatt farge (blå)E, DOK
10E -> D: allerede blå, motsatt farge — okDOK
11E -> F: allerede blå, motsatt farge — okDOK
12D -> C: allerede rød, motsatt farge — ok(tom)OK
13D -> E: allerede rød, motsatt farge — ok(tom)OK

- resultat: BIPARTITT.
- rød: A, C, E
- blå: B, D, F
Svaret: fordelingen finnes. Rom 1 får A, C og E; rom 2 får B, D og F. Ingen
av de seks motpartsforholdene går innad i et rom.
Se på steg 12 og 13. De to siste stegene sjekker kanter der begge endene
allerede er farget. Det er der en konflikt ville dukket opp, og det er derfor
algoritmen ikke kan stoppe så snart alle noder har fått farge — den må se på
hver kant minst én gang.
Kjøretid: O(V+E)O(|V| + |E|), her 6+66 + 6 kanter sett fra begge ender, altså 13
steg i tabellen. Det stemmer med tellingen: hver av de seks nodene ble tatt ut av
køen én gang, og til sammen ble det gjort tolv naboinspeksjoner pluss
startsteget.
Fellenote. Fella her er felle #12 — uklar eller for lang pseudokode. Det

er fristende å skrive fargeleggingen som en lang forklaring med mange
spesialtilfeller; kravet er at algoritmen skal være entydig etter to
gjennomlesninger. Kortformen «gi naboen motsatt farge, meld konflikt hvis den

allerede har din egen» er både kortere og mer presis.

📝Oppgave 4
Sjanger H

I en annen sak står partene slik: A mot B, A mot C, B mot C, C mot
D, og D mot E.

A: B, C
B: A, C
C: A, B, D
D: C, E
E: D

a) Kjør TwoColor fra A og oppgi sporingen steg for steg.
b) Kan partene fordeles på to møterom?
c) Hvilken kant avslører det, og hvorfor er akkurat den avgjørende?

📜Sykler av oddetallslengde er hele hindringen

En urettet graf er bipartitt hvis og bare hvis den ikke inneholder noen sykel
av oddetallslengde.

Hvorfor det er slik, i én setning hver vei. Går du rundt en sykel og veksler
farge for hvert steg, har du etter et partall steg samme farge som du startet
med — det går opp, og ringen kan farges. Etter et oddetall steg har du
motsatt farge av deg selv, og den siste kanten i sykelen forbinder to like
farger; det går ikke opp uansett hvor du begynner.

Slik brukes resultatet på eksamen. Det gir deg en øyeblikkelig test: finner
du en trekant, en femkant eller en hvilken som helst rundtur med et oddetall
kanter, er svaret «ikke bipartitt», og du trenger ikke kjøre algoritmen. Motsatt
er en graf uten sykel — et tre — alltid bipartitt.

Resultatet er kjent og kan brukes uten bevis; skisser gjerne argumentet over i én
setning, men bruk ikke eksamensminutter på mer enn det.

✏️Eksempel 3: Fem parter i ring — og hvorfor det ikke går opp

Samme oppgave, men nå med fem parter i ring: A mot B, B mot C, C mot D, D mot E,
og E mot A.

A: B, E
B: A, C
C: B, D
D: C, E
E: A, D

Kjør TwoColor fra A. Sammenlign med eksempel 2 og forklar hva som er
forskjellen.

Sporingen:

StegHandlingKøen etter stegetStatus
1start i A, farge rødAOK
2A -> B: ufarget, gi motsatt farge (blå)BOK
3A -> E: ufarget, gi motsatt farge (blå)B, EOK
4B -> A: allerede rød, motsatt farge — okEOK
5B -> C: ufarget, gi motsatt farge (rød)E, COK
6E -> A: allerede rød, motsatt farge — okCOK
7E -> D: ufarget, gi motsatt farge (rød)C, DOK
8C -> B: allerede blå, motsatt farge — okDOK
9C -> D: begge er rød — KONFLIKTDKONFLIKT

- resultat: IKKE bipartitt. Kanten C-D forbinder to noder som begge måtte få fargen rød.
Forskjellen fra eksempel 2 er ett eneste tall: ringen har fem kanter i stedet
for seks.
I sekskanten veksler fargene rødt–blått–rødt–blått–rødt–blått, og den
siste kanten lukker ringen mellom blå og rød. I femkanten veksler de
rødt–blått–rødt–blått–rødt, og den siste kanten skal lukke ringen mellom rød og
rød.
Det er nøyaktig det figuren over viser, og nøyaktig det setningen om
oddetallssykler sier. Ringen på fem er en sykel av oddetallslengde, og én slik
sykel holder for å ødelegge hele fargeleggingen — uansett hvor stor resten av
grafen er.

Kjøretid: algoritmen stoppet etter ni steg, men i verste fall (en bipartitt

graf) må den se på alt: O(V+E)O(|V| + |E|).
Fellenote. Fella her er felle #1 — å gjøre mer arbeid enn nødvendig. Det
er fristende å prøve alle mulige fordelinger av nodene på to grupper for å være
sikker. Det er 2V2^{|V|} muligheter. Den tvungne fargeleggingen avgjør det samme
i lineær tid, og det er hele grunnen til at algoritmen finnes.

📝Oppgave 5
Sjanger H

Tilbake til turkartet fra oppgave 1, med de seks hyttene:

A: B, C
B: A, D, E
C: A, E
D: B, F
E: B, C, F
F: D, E

Hyttene skal males i to farger slik at ingen sti går mellom to hytter med samme
farge.

a) Kjør TwoColor fra A og oppgi sporingen.
b) Er det mulig? Oppgi i så fall hvilke hytter som får hvilken farge.
c) Grafen inneholder en sykel. Hvorfor ødelegger den ikke fargeleggingen?

📝Oppgave 6
Eksamensnivå, sjanger H

Seks servere skal fordeles på to serverrom. To
servere som utveksler data direkte, må stå i hvert sitt rom av hensyn til
redundans. Koblingene er:

A: B, D, E
B: A, C, E
C: B, F
D: A, E
E: A, B, D, F
F: C, E

a) Kjør TwoColor fra A og oppgi sporingen fram til algoritmen konkluderer.
b) Er fordelingen mulig?
c) Pek ut en sykel av oddetallslengde i grafen, og forklar hvorfor akkurat
den er nok til å avgjøre saken.
d) Hvilken kjøretid oppgir du, og hvorfor er ikke «algoritmen stoppet etter
sju steg» et svar på det spørsmålet?

Løkke 3 — finnes det en rundtur? (ca. 13 min)

— naturlig pausepunkt —

Tilbake til turkartet. En hytteeier vil vite om det finnes en rundtur: en tur
som starter og ender i samme hytte uten å gå den samme stien to ganger. I
grafspråk spør hun om grafen inneholder en sykel.

Igjen er svaret en DFS med noen få linjer lagt til. Under en DFS i en urettet
graf møter du før eller siden en nabo som allerede er besøkt. Det betyr at det
går en kant til en node du har vært innom — altså finnes det to veier dit, altså
en sykel.

Med ett unntak, og hele oppgaven henger på det: naboen du nettopp kom fra. I
en urettet graf er kanten mellom deg og forelderen din en helt vanlig kant som
kan gås begge veier. Når du står i B og ser at naboen A er besøkt, er A bare
noden du kom fra — det er ingen rundtur, det er den samme stien sett bakover.
Derfor må sjekken være: en besøkt nabo som ikke er forelderen betyr sykel.

Sykel i en urettet graf

En sti som starter og ender i samme node, uten å bruke den samme kanten mer enn én
gang, og med minst tre kanter.

Kravet om minst tre kanter er nettopp forelder-unntaket: å gå fra A til B og rett
tilbake er ikke en rundtur, det er den samme kanten to ganger. En sammenhengende
urettet graf uten sykel er et tre, og har da nøyaktig E=V1|E| = |V| - 1
kanter.

📜Pseudokode-kontrakt: `HasCycle`
Antagelser om representasjon. G er en urettet graf gitt som nabolister;
hver node v har feltet v.besokt. Grafen er enkel: ingen node har en kant
til seg selv, og det finnes ikke to kanter mellom samme nodepar. Forelderen føres
med som en parameter i det rekursive kallet.

Prebetingelse: ingen. Postbetingelse: returverdien er sant hvis og bare
hvis G inneholder minst én sykel. Grafen er uendret.

Procedure HasCycle(G)
  Input:  urettet graf G = (V, E) som nabolister
  Output: sant hvis G inneholder minst en sykel, ellers usant
  for hver node v i V:
      v.besokt = usant
  for hver node v i V:
      if v.besokt er usant:
          if CycleVisit(v, ingen) er sant:
              return sant
  return usant

Procedure CycleVisit(v, forelder)
  v.besokt = sant
  for hver nabo u av v:
      if u.besokt er usant:
          if CycleVisit(u, v) er sant:
              return sant
      else if u er ulik forelder:
          return sant
  return usant

Invarianten i én setning: når CycleVisit(v, forelder) kalles, er v nådd
langs en sti fra en startnode, og hver besøkt nabo av v som ikke er
forelder, gir en andre vei tilbake til den stien — altså en sykel.

Kjøretid: O(V+E)O(|V| + |E|). Hver node besøkes én gang, hver kant ses på to
ganger, og forelder-sammenligningen er O(1)O(1).

Den ytre løkka er også her nødvendig. En sykel kan ligge i en komponent du
ikke startet i. Uten løkka over alle noder svarer algoritmen «ingen sykel» på en
graf som har én, bare et annet sted.

Avgrensning. Dette er sykeldeteksjon i urettede grafer. I en rettet graf
holder ikke forelder-regelen — der kan to noder peke på hverandre uten at det
utgjør en rundtur i vanlig forstand, og en besøkt node kan ligge i en helt annen
gren. Rettet sykeldeteksjon hører til Del 6, sammen med topologisk sortering, og
skal ikke brukes i dette kapitlet.

✏️Eksempel 4: Et stinett uten rundtur

Seks hytter er forbundet slik:

A: B, C
B: A, D, E
C: A, F
D: B
E: B
F: C

Kjør HasCycle fra A og avgjør om det finnes en rundtur.

Sporingen. Kolonnen «Handling» viser hvert kall og hver naboinspeksjon.

StegHandlingStatus
1besøk A (kom fra -)-
2besøk B (kom fra A)-
3fra B: naboen A ER forelderen — ikke sykel-
4besøk D (kom fra B)-
5fra D: naboen B ER forelderen — ikke sykel-
6besøk E (kom fra B)-
7fra E: naboen B ER forelderen — ikke sykel-
8besøk C (kom fra A)-
9fra C: naboen A ER forelderen — ikke sykel-
10besøk F (kom fra C)-
11fra F: naboen C ER forelderen — ikke sykel-

- inneholder sykel: NEI
-
V= 6,E= 5 (et tre ville hattE=V

Svaret: ingen rundtur finnes. Hver eneste gang algoritmen møtte en besøkt
nabo, var det forelderen — den samme stien sett bakover.
Legg merke til kanttellingen på siste linje. Grafen er sammenhengende, har
seks noder og fem kanter, og E=V1|E| = |V| - 1. Den er altså et tre, og et tre
har per definisjon ingen sykel. Det er en gratis kontrollregning som tar to
sekunder: er grafen sammenhengende og har flere enn V1|V| - 1 kanter, den
ha en sykel, og da vet du svaret før du begynner å traversere.
Fellenote. Fella her er den klassiske i sjangeren: å regne kanten tilbake til
forelderen som en sykel. Uten else if u er ulik forelder ville algoritmen

meldt «sykel» allerede i steg 3, på en graf som er et tre. Da svarer den «ja» på
absolutt hver eneste graf med minst én kant.

📝Oppgave 7
Sjanger H

Det er lagt én ny sti i hyttenettet fra eksempel 4, mellom E og F:

A: B, C
B: A, D, E
C: A, F
D: B
E: B, F
F: C, E

a) Kjør HasCycle fra A og oppgi sporingen.
b) Finnes det en rundtur, og hvilken kant avslører den?
c) Bekreft svaret med kanttellingen.

📝Oppgave 8
Eksamensnivå, sjanger H

En kollega har skrevet denne prosedyren for å
avgjøre om et urettet nett inneholder en rundtur:

Procedure HasCycle(G)
  Input:  urettet graf G som nabolister
  Output: sant hvis G inneholder en sykel
  for hver node v i V:
      v.besokt = usant
  Sjekk(startnode)
  return usant

Procedure Sjekk(v)
  v.besokt = sant
  for hver nabo u av v:
      if u.besokt er sant:
          return sant
      Sjekk(u)

a) Finn alle feilene.
b) Vis konkret hva som går galt på grafen A: B / B: A.
c) Skriv en korrekt versjon, med antagelser og kjøretid.

Løkke 4 — behandle grafen komponent for komponent (ca. 8 min)

Den siste varianten er den mest generelle, og den dukker opp i innpakninger som
ser ut som alt annet enn grafteori.

Et kjøretidsmiljø holder styr på objekter i minnet. Hvert objekt kan peke på
andre objekter, og programmet har en rot-mengde: de objektene som er direkte
tilgjengelige fra kode som kjører nå. Spørsmålet er hvilke objekter som trygt kan
frigjøres.

Svaret er en traversering: start en DFS fra hver node i rot-mengden, og
marker alt du når. Når du er ferdig, kan alt som ikke er markert, frigjøres — det
finnes ingen vei dit fra noe programmet kan se.

Det er det samme mønsteret som komponenttellingen, med én forskjell: den ytre
løkka går over rot-mengden i stedet for over alle noder. Kjenner du igjen
formuleringene «hva kan nås fra …», «hva kan trygt fjernes» eller «behandle hver
del for seg», er dette verktøyet.

Komponentvis behandling fra en rot-mengde

Mønsteret der du kjører DFS fra hver node i en oppgitt rot-mengde, markerer
alt som nås, og deretter behandler de umarkerte nodene samlet.

Kjøretiden er O(V+E)O(|V| + |E|) uansett hvor stor rot-mengden er, fordi hver node og
hver kant behandles høyst én gang på tvers av alle DFS-kallene. Signalordene i en
oppgavetekst er «hva kan nås fra», «hva kan frigjøres» og «behandle hver del for
seg».

📝Oppgave 9
Eksamensnivå, sjanger H

Et kjøretidsmiljø har V|V| objekter i minnet. Hvert
objekt kan peke på andre objekter, og pekerne er toveis i den forstand at
miljøet kan følge dem begge veier; det er totalt E|E| pekere. En liten
rot-mengde RR er oppgitt.

Skriv en algoritme som returnerer mengden objekter som trygt kan frigjøres,
altså de som ikke kan nås fra noen node i RR. Oppgi antagelser om representasjon
og kjøretid, og forklar hvorfor kjøretiden er lavest mulig.

Begrepsbank

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

Komponenttelling med DFS-full

Antall sammenhengende komponenter er antall ganger den ytre løkka i DFS-full må
starte en ny traversering. Kjøretid O(V+E)O(|V| + |E|).

Egenskapen som gjør den riktig: én DFS besøker nøyaktig én komponent. Isolerte
noder teller som egne komponenter, og den ytre løkka er grunnen til at kjøretiden
har V|V|-leddet.

2-fargelegging (`TwoColor`)

Avgjør om en urettet graf er bipartitt ved å gi hver node motsatt farge av den
noden den ble nådd fra. Kjøretid O(V+E)O(|V| + |E|).

Egenskapen som gjør den riktig: hvert fargevalg etter startnoden er tvunget,
så en konflikt beviser at ingen gyldig fargelegging finnes — ikke bare at denne
kjøringen mislyktes.

Sykeldeteksjon i urettet graf (`HasCycle`)

DFS der en kant til en allerede besøkt node som ikke er forelderen, betyr
sykel. Kjøretid O(V+E)O(|V| + |E|).

Egenskapen som gjør den riktig: den besøkte naboen ligger på stien du kom langs,
så kanten dit gir en andre vei — altså en rundtur. Uten forelder-unntaket svarer
algoritmen «sykel» på et hvilket som helst tre.

Sykler av oddetallslengde

En urettet graf er bipartitt hvis og bare hvis den ikke inneholder noen sykel med
et oddetall kanter.

Praktisk konsekvens: ser du en trekant, er svaret «ikke bipartitt» med én gang.
Et tre, som ikke har sykler i det hele tatt, er alltid bipartitt. Resultatet er
kjent og kan brukes uten bevis.

Forelder-unntaket

I urettet sykeldeteksjon skal kanten tilbake til noden du kom fra, ikke telle som
sykel.

Det er den samme kanten sett bakover, ikke en rundtur. Testen som avslører at
unntaket mangler: kjør algoritmen på grafen med to noder og én kant — svarer den
«sykel», er unntaket glemt.

Kanttelling i en urettet graf

En traversering ser på hver kant to ganger, én gang fra hver ende. Summen av
nodegradene er derfor 2E2|E|.

Det er denne tellingen som gir O(V+2E)=O(V+E)O(|V| + 2|E|) = O(|V| + |E|), og den er
begrunnelsen sensor ber om når du oppgir kjøretiden. En O-påstand uten
løkketelling gir ikke full uttelling.

Tre (urettet)

En sammenhengende urettet graf uten sykel. Har nøyaktig E=V1|E| = |V| - 1 kanter.

To gratis konsekvenser: et tre er alltid bipartitt, og en sammenhengende graf
med flere enn V1|V| - 1 kanter må inneholde minst én sykel. Begge er
kontrollregninger som tar to sekunder.

Den ytre løkka over alle noder

Løkken som starter en ny traversering for hver node som ennå ikke er behandlet.
Den koster O(V)O(|V|) og er det som gjør DFS-full komplett.

Uten den behandles bare komponenten du startet i, og både en sykel og en
ikke-bipartitt del et annet sted i grafen blir usett. Alle tre algoritmene i
kapitlet har den.

Felle #6 — DFS fra én node kontra DFS-full

DFS fra én node er O(E)O(|E|); DFS-full, med ytre løkke over alle noder, er
O(V+E)O(|V| + |E|).

Forskjellen er initialiseringen og gjennomgangen av noder uten kanter. Har
algoritmen din en ytre løkke over VV, er svaret O(V+E)O(|V| + |E|). Dette er et
eksplisitt trekkpunkt i faget.

Sjanger H — grafalgoritme i pseudokode

Del 2-sjangeren der du får et problem i ord og skal skrive grafalgoritmen selv,
med kjøretid. I hvert eneste sett, med to eller tre oppgaver.

Full uttelling krever fire ledd: problemet navngitt, antagelser om representasjon
oppgitt, algoritmen (pseudokode eller en klar forklaring i ord), og kjøretid
som matcher koden med størrelsene definert.

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.