5.3 Komponenter, 2-fargelegging og sykeldeteksjon
Traverserings-varianter som løser klassiske problemer: finn komponenter, sjekk bipartitthet (2-fargelegging) og oppdag sykler.
Tre spørsmål som ser helt forskjellige ut — «hvor mange atskilte deler består
nettet av?», «kan disse deles i to grupper uten at to i samme gruppe er koblet
sammen?» og «finnes det en rundtur her?» — besvares med den samme
traverseringen du allerede kan fra kap. 5.2. Det er hele
poenget med kapitlet: du lærer ikke tre nye algoritmer, du lærer å kjenne igjen
tre innpakninger av én.
På eksamen kommer de som sjanger H — grafalgoritme i pseudokode, altså at du
får problemet beskrevet i ord og skal skrive selve algoritmen, med oppgitt
kjøretid. Sjanger H står i hvert eneste sett, med to eller tre oppgaver, og
variantene i dette kapitlet er belagt i 4 av 7 sett (57 %).
Prioriteten er det boka kaller «kunne», altså nivå 2 av tre. Nivå 1 er stoff
som må sitte uten oppslag (traversering, hashing, heap-håndkjøring); nivå 2 er
stoff du skal beherske og kunne skrive ned, men som ikke er det første du pugger;
nivå 3 er stoff du bare skal kjenne igjen. Nivå 2 er likevel høyt her: en enkelt
sjanger H-oppgave er en av de større oppgavene i Del 2, den delen av settet der
du skriver pseudokode og resonnerer, og der karakteren avgjøres.
Slik er kapitlet lagt opp (50 min):
| Løkke | Innhold | Tid |
|---|---|---|
| 1 | Komponenttelling med DFS-full | ca. 13 min |
| 2 | 2-fargelegging: er grafen bipartitt? | ca. 16 min |
| 3 | Sykeldeteksjon i en urettet graf | ca. 13 min |
| 4 | Komponentvis behandling, og de typiske feilene | ca. 8 min |
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
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.
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å.
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 noder og ingen kanter har komponenter; en sammenhengende graf
har nøyaktig 1. En enkelt node uten naboer er en fullverdig komponent — det er den
vanligste tellefeilen.
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 , er
alle noder i -ens komponent markert som besøkt, og telleren er økt nøyaktig én
gang for den komponenten.
Kjøretid: . Initialiseringsløkka er . 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
, som forenkles til , der er antall noder og
antall kanter.
Merk hva den ytre løkka koster. Den er grunnen til at DFS-full er
og ikke : også noder uten en eneste kant må innom. Å
oppgi her er felle #6 — å forveksle DFS fra én node med DFS over
hele grafen.
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.
var markert da den ytre løkka kom til den.
| Node i ytre løkke | Alt besøkt? | Ny komponent funnet | Teller |
|---|---|---|---|
| A | nei — start DFS her | A, B, C | 1 |
| B | ja | - | 1 |
| C | ja | - | 1 |
| D | nei — start DFS her | D, E, F | 2 |
| E | ja | - | 2 |
| F | ja | - | 2 |
| G | nei — start DFS her | G | 3 |
- 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 , som
gjelder DFS fra én node, i stedet for , 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.
(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, Ea) 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?
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: Ga) Kjør den ytre løkka i CountComponents for hånd.
b) Hvor mange komponenter har nettet?
c) Grafen har og . Hvor mange kanter ville den hatt hvis
den var sammenhengende og uten sykel? Hva forteller differansen deg?
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.
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.
G er en urettet graf gitt som nabolister.Hver node
v har feltet v.farge med tre mulige verdier: ingen, rod ellerbla. Vi bruker en kø, altså bredde-først, slik at fargeleggingen sprer seglagvis; 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 santGrunnideen 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: . Initialiseringen er ; 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.
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, EKjør TwoColor fra A og avgjør om partene kan fordeles på to møterom.
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.
| Steg | Handling | Køen etter steget | Status |
|---|---|---|---|
| 1 | start i A, farge rød | A | OK |
| 2 | A -> B: ufarget, gi motsatt farge (blå) | B | OK |
| 3 | A -> F: ufarget, gi motsatt farge (blå) | B, F | OK |
| 4 | B -> A: allerede rød, motsatt farge — ok | F | OK |
| 5 | B -> C: ufarget, gi motsatt farge (rød) | F, C | OK |
| 6 | F -> A: allerede rød, motsatt farge — ok | C | OK |
| 7 | F -> E: ufarget, gi motsatt farge (rød) | C, E | OK |
| 8 | C -> B: allerede blå, motsatt farge — ok | E | OK |
| 9 | C -> D: ufarget, gi motsatt farge (blå) | E, D | OK |
| 10 | E -> D: allerede blå, motsatt farge — ok | D | OK |
| 11 | E -> F: allerede blå, motsatt farge — ok | D | OK |
| 12 | D -> C: allerede rød, motsatt farge — ok | (tom) | OK |
| 13 | D -> 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: , her 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.
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: Da) 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?
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.
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, DKjør TwoColor fra A. Sammenlign med eksempel 2 og forklar hva som er
forskjellen.
| Steg | Handling | Køen etter steget | Status |
|---|---|---|---|
| 1 | start i A, farge rød | A | OK |
| 2 | A -> B: ufarget, gi motsatt farge (blå) | B | OK |
| 3 | A -> E: ufarget, gi motsatt farge (blå) | B, E | OK |
| 4 | B -> A: allerede rød, motsatt farge — ok | E | OK |
| 5 | B -> C: ufarget, gi motsatt farge (rød) | E, C | OK |
| 6 | E -> A: allerede rød, motsatt farge — ok | C | OK |
| 7 | E -> D: ufarget, gi motsatt farge (rød) | C, D | OK |
| 8 | C -> B: allerede blå, motsatt farge — ok | D | OK |
| 9 | C -> D: begge er rød — KONFLIKT | D | KONFLIKT |
- 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: .
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 muligheter. Den tvungne fargeleggingen avgjør det samme
i lineær tid, og det er hele grunnen til at algoritmen finnes.
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, EHyttene 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?
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, Ea) 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.
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
kanter.
G er en urettet graf gitt som nabolister;hver node
v har feltet v.besokt. Grafen er enkel: ingen node har en kanttil 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 usantInvarianten 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 erforelder, gir en andre vei tilbake til den stien — altså en sykel.
Kjøretid: . Hver node besøkes én gang, hver kant ses på to
ganger, og forelder-sammenligningen er .
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.
Seks hytter er forbundet slik:
A: B, C
B: A, D, E
C: A, F
D: B
E: B
F: CKjør HasCycle fra A og avgjør om det finnes en rundtur.
| Steg | Handling | Status |
|---|---|---|
| 1 | besøk A (kom fra -) | - |
| 2 | besøk B (kom fra A) | - |
| 3 | fra B: naboen A ER forelderen — ikke sykel | - |
| 4 | besøk D (kom fra B) | - |
| 5 | fra D: naboen B ER forelderen — ikke sykel | - |
| 6 | besøk E (kom fra B) | - |
| 7 | fra E: naboen B ER forelderen — ikke sykel | - |
| 8 | besøk C (kom fra A) | - |
| 9 | fra C: naboen A ER forelderen — ikke sykel | - |
| 10 | besøk F (kom fra C) | - |
| 11 | fra F: naboen C ER forelderen — ikke sykel | - |
- inneholder sykel: NEI
-
| V | = 6, | E | = 5 (et tre ville hatt | E | = | 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 . 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 kanter, må 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 algoritmenmeldt «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.
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, Ea) Kjør HasCycle fra A og oppgi sporingen.
b) Finnes det en rundtur, og hvilken kant avslører den?
c) Bekreft svaret med kanttellingen.
Fire feil koster mest i dette kapitlet, og de to første går igjen i hvert eneste
sett.
- Å regne kanten tilbake til forelderen som en sykel. Den vanligste feilen i
urettet sykeldeteksjon. Uten else if u er ulik forelder melder algoritmen
sykel på et hvilket som helst tre. Kontrollen: kjør algoritmen din i hodet
på grafen A: B / B: A. Svarer den «sykel», mangler forelder-unntaket.
- Å oppgi i stedet for . Dette er felle #6 — å
forveksle DFS fra én node med en DFS-full som må innom alle noder. Så snart
algoritmen din har en ytre løkke over , er kjøretiden .
- Å glemme den ytre løkka helt. Da behandles bare komponenten du startet i.
En ikke-bipartitt komponent, eller en sykel, et annet sted i grafen blir aldri
oppdaget, og algoritmen svarer feil på grafer med mer enn én komponent.
- Å prøve alle fordelinger i stedet for å farge tvunget. Det er
muligheter, mot for den tvungne fargeleggingen. Dette er felle
#1 — full, naiv gjennomgang der én traversering holdt — og den koster fort
halve poengsummen på en sjanger H-oppgave, fordi sensor eksplisitt gir mer for
lavere kjøretid på samme oppgave.
Og den stille: å hoppe over isolerte noder når du teller komponenter. En node
uten kanter er en komponent. Tell nodene i komponentene dine til slutt, og
kontroller at summen er .
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.
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 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».
Et kjøretidsmiljø har 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 pekere. En liten
rot-mengde 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 . 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.
Antall sammenhengende komponenter er antall ganger den ytre løkka i DFS-full må
starte en ny traversering. Kjøretid .
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 -leddet.
Avgjør om en urettet graf er bipartitt ved å gi hver node motsatt farge av den
noden den ble nådd fra. Kjøretid .
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.
DFS der en kant til en allerede besøkt node som ikke er forelderen, betyr
sykel. Kjøretid .
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.
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.
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.
En traversering ser på hver kant to ganger, én gang fra hver ende. Summen av
nodegradene er derfor .
Det er denne tellingen som gir , og den er
begrunnelsen sensor ber om når du oppgir kjøretiden. En O-påstand uten
løkketelling gir ikke full uttelling.
En sammenhengende urettet graf uten sykel. Har nøyaktig kanter.
To gratis konsekvenser: et tre er alltid bipartitt, og en sammenhengende graf
med flere enn kanter må inneholde minst én sykel. Begge er
kontrollregninger som tar to sekunder.
Løkken som starter en ny traversering for hver node som ennå ikke er behandlet.
Den koster 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.
DFS fra én node er ; DFS-full, med ytre løkke over alle noder, er
.
Forskjellen er initialiseringen og gjennomgangen av noder uten kanter. Har
algoritmen din en ytre løkke over , er svaret . Dette er et
eksplisitt trekkpunkt i faget.
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.
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.