TDT4120 Algoritmer og datastrukturer Interaktivt kompendium · H2026
Fremgang
0/15

08

Traversering av grafer

Kjernepensum

Å traversere en graf er å besøke nodene du har oppdaget. I starten har du bare oppdaget startnoden, men hver gang du besøker en node, oppdager du naboene dens. Slik når du alle nodene som kan nås fra startnoden. Traversering er nyttig i seg selv, og mange grafalgoritmer bygger på den. Læreboka kaller traversering søk (search), og derfor heter de to algoritmene bredde-først-søk og dybde-først-søk.

Grafrepresentasjoner

En graf G=(V,E)G = (V, E) har nodene VV og kantene EE. I en rettet graf går kanten (u,v)(u, v) fra uu til vv, og vv er en ut-nabo av uu. I en urettet graf går kanten begge veier. Kjøretiden til en grafalgoritme har to parametre, og inne i asymptotisk notasjon står VV for ∣V∣|V| og EE for ∣E∣|E|, så O(V+E)O(V + E) betyr O(∣V∣+∣E∣)O(|V| + |E|).

Med nabolister har hver node sin egen liste: G.Adj[u]G.\mathit{Adj}[u] inneholder ut-naboene til uu. Grafen er altså en tabell med én liste per node, og ikke én liste. I en rettet graf står kanten (u,v)(u, v) bare i lista til uu, så listene har til sammen ∣E∣|E| elementer (∑u∈V∣G.Adj[u]∣=∣E∣\sum_{u \in V} |G.\mathit{Adj}[u]| = |E|). I en urettet graf står den i begge listene, og summen blir 2∣E∣2|E|. Plassen er Θ(V+E)\Theta(V + E). En nabomatrise er en ∣V∣×∣V∣|V| \times |V|-tabell AA der auv=1a_{uv} = 1 når (u,v)(u, v) er en kant, og 0 ellers. Den tar Θ(V2)\Theta(V^2) plass uansett hvor mange kanter grafen har, og for en urettet graf er den symmetrisk.

En rettet graf med nodene 1 til 6 og kantene 1 til 2, 1 til 3, 3 til 6, 4 til 6, 5 til 3 og 5 til 4. Ved siden av står nabolistene: 1 har 2 og 3, 2 har ingen, 3 har 6, 4 har 6, 5 har 3 og 4, og 6 har ingen. Til høyre er nabomatrisen, en tabell med seks rader og seks kolonner der de seks kantene er enere.
Figur 1. Den samme grafen som nabolister og som nabomatrise. Rad uu, kolonne vv i matrisen er 1 når (u,v)(u, v) er en kant.

En vektet graf lagrer vekten w(u,v)w(u, v) sammen med vv i lista til uu, eller som auva_{uv} i matrisen. Tabellen viser hva de vanligste operasjonene koster.

OperasjonNabolisterNabomatrise
PlassΘ(V+E)\Theta(V + E)Θ(V2)\Theta(V^2)
Er (u,v)(u, v) en kant?Søk i G.Adj[u]G.\mathit{Adj}[u]Θ(1)\Theta(1)
Alle ut-naboene til uuΘ(antall)\Theta(\text{antall})Θ(V)\Theta(V)
Alle kanteneΘ(V+E)\Theta(V + E)Θ(V2)\Theta(V^2)

Nabomatrisen egner seg til direkte oppslag, og nabolistene til traversering. Det sies ofte at matrisen er raskere og listene tar mindre plass, men det stemmer bare delvis. Listene tar mindre plass når grafen har få kanter. Har den nær ∣V∣2|V|^2 kanter, tar begge Θ(V2)\Theta(V^2). Algoritmene i læreboka bruker nabolister med mindre de trenger raske oppslag, og listene kan gjerne byttes ut med hashtabeller.

Traversering

Start i node 5 i figur 1, med 5 alene på en liste. Du tar 5 av lista, besøker den og fører opp naboene 3 og 4. Du besøker 3 og fører opp 6. Når du besøker 4, står 6 alt på lista. Du besøker 6, og lista er tom. Nodene 5, 3, 4 og 6 er besøkt, og 1 og 2 kan ikke nås fra 5.

Lista er en huskeliste med noder som er oppdaget, men ikke besøkt. Å besøke en node er å ta den av lista og føre opp naboene den har. Når lista er tom, er alle nodene som kan nås fra startnoden, besøkt. En node på en sti fra startnoden føres nemlig opp senest når noden foran den på stien besøkes, og alt som føres opp, tas av lista før den er tom. Hvilken node som tas av lista først, bestemmer hvilken traversering det blir.

Algoritmene i læreboka holder styr på hvilke noder som er oppdaget og besøkt, med farger. En node er hvit før den er oppdaget, grå når den er oppdaget, men ikke ferdig, og svart når den er ferdig. Uten fargene ville en sykel sendt traverseringen rundt og rundt. Med dem behandles hver node én gang, på samme måte som memoisering løser hver delinstans én gang. Når vv oppdages langs kanten (u,v)(u, v), får den forgjengeren v.π=uv.\pi = u. Kantene (v.π,v)(v.\pi, v) danner et traverseringstre med startnoden som rot.

Dybde-først-søk

Dybde-først-søk (DFS) går alltid videre fra den nyeste noden den har oppdaget. DFS-Visit(G,u)(G, u) gjør uu grå og går gjennom naboene. Møter den en hvit nabo vv, setter den v.π=uv.\pi = u og kaller seg selv på vv med en gang. Først når alle naboene er sett, blir uu svart. Kallstakken er huskelista. DFS(G)(G) farger alle nodene hvite og kaller DFS-Visit fra hver node som fortsatt er hvit, i rekkefølge. Den når dermed hele grafen, og hvert kall fra hovedløkka gir et nytt tre. Til sammen blir trærne en dybde-først-skog.

DFS gir hver node to tider fra en felles teller time: starttiden u.du.d når uu oppdages og blir grå, og sluttiden u.fu.f når uu er ferdig og blir svart. Telleren øker med 1 hver gang, så tidene er heltallene fra 1 til 2∣V∣2|V|, og u.d<u.fu.d < u.f.

dfs.py
def dfs(G):  # G[u] er lista med ut-naboene til u
  color = {u: "white" for u in G}
  pi = {u: None for u in G}
  d, f = {}, {}  # u.d og u.f er d[u] og f[u]
  time = 0

  def dfs_visit(u):
      nonlocal time
      time += 1
      d[u] = time
      color[u] = "gray"
      for v in G[u]:
          if color[v] == "white":
              pi[v] = u
              dfs_visit(v)
      time += 1
      f[u] = time
      color[u] = "black"

  for u in G:
      if color[u] == "white":
          dfs_visit(u)
  return d, f, pi
DFS
0 / 0

Grafen fra forelesningen. Hver node har fargen DFS har gitt den og tidene d/f. Tre-kantene har et bånd, og en annen undersøkt kant har en bokstav: B for bakoverkant, F for foroverkant og K for krysskant. Under grafen er hver node en stolpe fra u.du.d til u.fu.f, med én rad per dybde i treet, og de grå stolpene er nodene på kallstakken.

DFS-Visit kalles én gang per node, for den kalles bare på hvite noder, og noden blir grå med en gang. Hvert kall går gjennom nabolista én gang, så alle kallene undersøker til sammen ∣E∣|E| kanter i en rettet graf. Med initialiseringen blir kjøretiden Θ(V+E)\Theta(V + E) (Θ(V)+∑u∈VΘ(1+∣G.Adj[u]∣)=Θ(V+E)\Theta(V) + \sum_{u \in V} \Theta(1 + |G.\mathit{Adj}[u]|) \allowbreak = \Theta(V + E)).

Parentesteoremet og hvit-sti-teoremet

Skriv «(u» når uu oppdages og «u)» når uu er ferdig. Da blir parentesene riktig nøstet. Kallet på en node ligger inni kallene på forfedrene, så noden oppdages etter dem og blir ferdig før dem.

I DFS-stepperen er hver node en stolpe fra u.du.d til u.fu.f, og to stolper overlapper bare når den ene ligger inni den andre. vv er altså en ekte etterkommer av uu hvis og bare hvis u.d<v.d<v.f<u.fu.d < v.d < v.f < u.f (u.d<v.d<v.f<u.fu.d < v.d < v.f < u.f).

Er vv en etterkommer, oppdages den etter uu, og det gjør alle nodene på trestien mellom dem, så de er hvite ved u.du.d. Omvendt, la vv være den første noden på en hvit sti fra uu som ikke blir en etterkommer. Noden før vv på stien er uu eller en etterkommer av uu, og den blir ikke ferdig før den har undersøkt kanten til vv. Da oppdages vv mens uu er grå, og etter parentesteoremet er den en etterkommer likevel.

Kantklassifisering

DFS deler kantene i fire typer etter dybde-først-skogen. Fargen til vv når kanten (u,v)(u, v) undersøkes, avgjør typen.

KantGår tilvv er
Tre-kantet barn i dybde-først-skogenhvit
Bakoverkanten forfader, eller uu selvgrå
Foroverkanten etterkommer, uten å være en tre-kantsvart, u.d<v.d{u.d < v.d}
Krysskanten node som verken er forfader eller etterkommersvart, v.d<u.d{v.d < u.d}

En grå node ligger på kallstakken, så den er en forfader til uu, og kanten lukker en sykel. Omvendt, har grafen en sykel, la vv være den første noden i sykelen som DFS oppdager, og uu noden foran vv i sykelen. Ved v.dv.d er resten av sykelen en hvit sti fra vv til uu, så uu blir en etterkommer av vv etter hvit-sti-teoremet, og kanten (u,v)(u, v) blir en bakoverkant. En rettet graf har altså en sykel hvis og bare hvis DFS finner en bakoverkant.

I en urettet graf er hver kant en tre-kant eller en bakoverkant. Er u.d<v.du.d < v.d, er vv hvit ved u.du.d, og kanten er en hvit sti fra uu til vv. Etter hvit-sti-teoremet blir vv da en etterkommer av uu og ferdig mens uu er grå. Undersøkes kanten første gang fra uu, er vv hvit, og kanten blir en tre-kant. Undersøkes den første gang fra vv, er uu fortsatt grå, og kanten blir en bakoverkant.

Eksamensoppgave Start- og sluttider på en sti
Ordinær eksamen, desember 2024, oppgave 6

Utfør DFS på grafen nedenfor, slik at første kall til DFS-Visit starter i node yy. Hva blir v.dv.d og v.fv.f (discovery time og finish time) for hver node vv?

En rettet sti med fire noder: kanter fra x til y, fra y til z og fra z til w.
Figur 2. Grafen fra oppgavesettet.

Oppgi svaret som to lister med 4 tall på hver sin linje, som f.eks.:

1, 2, 4, 7
5, 6, 3, 8

Første linje er x.dx.d, y.dy.d, z.dz.d, w.dw.d, i rekkefølge, og andre linje er x.fx.f, y.fy.f, z.fz.f, w.fw.f.

Hint: Husk at DFS starter en ny dybde-først-traversering med DFS-Visit fra hver eneste node som ikke allerede er besøkt.

Vis løsning Skjul løsning

yy oppdages først, med y.d=1y.d = 1. Kanten (y,z)(y, z) gir z.d=2z.d = 2, og (z,w)(z, w) gir w.d=3w.d = 3. ww har ingen kanter ut og blir ferdig med w.f=4w.f = 4, og så blir z.f=5z.f = 5 og y.f=6y.f = 6. Hovedløkka i DFS fortsetter med nodene som er hvite, og det er bare xx. Den får x.d=7x.d = 7, kanten (x,y)(x, y) går til en svart node, og x.f=8x.f = 8.

Svar

7, 1, 2, 3 og 8, 6, 5, 4.

Topologisk sortering

En rettet asyklisk graf, en DAG, er en rettet graf uten sykler. En topologisk sortering av en DAG er en rekkefølge av nodene der alle kantene peker fremover: for hver kant (u,v)(u, v) kommer uu før vv. Kantene kan være avhengigheter, som at buksa må på før skoene. Har grafen en sykel, finnes ingen slik rekkefølge, for en av kantene i sykelen må peke bakover.

En klassisk metode tar ut en node uten inn-kanter, fjerner den fra grafen og gjentar. Topological-Sort bruker i stedet DFS. Hver gang en node blir ferdig, settes den først i en lenket liste, så lista ender opp sortert etter synkende sluttid. Innsettingen tar konstant tid, og alt tar Θ(V+E)\Theta(V + E).

Synkende sluttid er riktig fordi v.f<u.fv.f < u.f for hver kant (u,v)(u, v) ((u,v)∈E  ⇒  v.f<u.f(u, v) \in E \;\Rightarrow\; v.f < u.f). Når DFS undersøker kanten, er vv hvit, svart eller grå. Er vv hvit, blir den en etterkommer av uu og ferdig før uu. Er vv svart, er den alt ferdig. Er vv grå, er kanten en bakoverkant, og grafen har en sykel, så i en DAG er vv aldri grå. Argumentet bruker ingenting om rekkefølgen i hovedløkka og nabolistene. En annen rekkefølge kan gi en annen sortering, men den er like riktig.

Koden trenger ikke tidene, bare rekkefølgen nodene blir ferdige i.

topologisk_sortering.py
from collections import deque

def topological_sort(G):
  color = {u: "white" for u in G}
  order = deque()  # appendleft setter inn først i O(1)

  def visit(u):
      color[u] = "gray"
      for v in G[u]:
          if color[v] == "white":
              visit(v)
      color[u] = "black"
      order.appendleft(u)

  for u in G:
      if color[u] == "white":
          visit(u)
  return list(order)
Topological-Sort
0 / 0

Påkledningen fra forelesningen: en kant (u,v)(u, v) betyr at uu må på før vv. Hver boks viser plagget og tidene d/f. Til høyre står lista order med det første plagget øverst, og buene ved siden av den er kantene ut av plaggene i lista.

Eksamensoppgave Synkende starttid
Ordinær eksamen, november 2019, oppgave 4

Vurder følgende utsagn om Topological-Sort:

Nodene sorteres etter synkende starttid (discover-time).

Stemmer dette? Svar ja eller nei og forklar kort.

Vis løsning Skjul løsning

Se på grafen med nodene 2 og 3 og kanten (2,3)(2, 3). Starter DFS i 2, blir 3 en etterkommer med 3.d=23.d = 2 og 2.d=12.d = 1, og synkende starttid setter 3 foran 2. Kanten peker da bakover. Det som er trygt, er sluttiden: v.f<u.fv.f < u.f for hver kant (u,v)(u, v), uansett hvor DFS starter. Stigende starttid er ikke trygt heller, for starter DFS i 3, får 3 den laveste starttiden.

Svar
Nei. Nodene sorteres etter synkende sluttid (finish-time).
Memoisering er DFS

Memoisering i dynamisk programmering er DFS i delinstansgrafen, men mot kantene: et kall går fra en instans til delinstansene den bygger på. Et kall blir ferdig først når alle disse er løst, og en delinstans som alt er løst, er svart og slås bare opp. Rekkefølgen kallene blir ferdige i, er derfor en topologisk sortering av delinstansgrafen, med grunntilfellene først. En løsning nedenfra og opp går gjennom delinstansene i den samme rekkefølgen.

Sterke komponenter

En urettet graf er sammenhengende når det finnes en sti mellom hvert par av noder, og de sammenhengende komponentene er de største delene som er sammenhengende. DFS finner dem direkte: hvert dybde-først-tre er én komponent, for traverseringen fra en node stopper når det ikke er flere kanter å følge.

En rettet graf er sterkt sammenhengende når det for hvert par uu og vv finnes en sti fra uu til vv og en sti fra vv til uu. De sterke komponentene er de største mengdene av noder der alle kan nå alle. Kollapser du hver komponent til én node, får du komponentgrafen. Den er en DAG, for en sykel mellom to komponenter ville gjort dem til én.

Kjører du DFS rett på GG, kan et tre gå fra én komponent og inn i en annen. Starter DFS i en komponent der alle nodene er hvite og alle kantene ut av komponenten går til svarte noder, blir treet nøyaktig den komponenten. Du trenger derfor startnoder i en rekkefølge der hver komponent bare har kanter ut til komponenter som alt er besøkt. Sluttidene fra en første runde med DFS på GG gir en slik rekkefølge, men for den transponerte grafen og ikke for GG selv.

La sluttiden til en komponent være den høyeste sluttiden i den. Går en kant i GG fra komponent CC til en annen komponent C′C', har CC høyere sluttid enn C′C'. Oppdager DFS en node i CC først, når den hele C′C' langs hvite stier før den noden blir ferdig. Oppdager den en node i C′C' først, kan den ikke nå CC derfra, for da ville CC og C′C' vært én komponent. Da er hele C′C' ferdig før CC blir oppdaget.

Komponenten med høyest sluttid har derfor ingen kanter inn fra andre komponenter i GG. Noden med lavest sluttid ligger derimot ikke nødvendigvis i en komponent uten kanter ut. Med kantene (a,b)(a, b), (b,a)(b, a) og (a,c)(a, c) og DFS fra aa, som følger (a,b)(a, b) først, blir bb ferdig først, men komponenten {a,b}\{a, b\} har en kant ut til cc. Snur du alle kantene, får du den transponerte grafen GTG^{\mathrm T}, og der har komponenten ingen kanter ut. GG og GTG^{\mathrm T} har de samme sterke komponentene, for hver sti i den ene er en sti baklengs i den andre. Strongly-Connected-Components kjører derfor en runde til, på GTG^{\mathrm T}.

  1. DFS på GG

    Kjør DFS på GG for å finne sluttiden u.fu.f for hver node.

  2. Snu kantene

    Lag GTG^{\mathrm T}. Med nabolister tar det Θ(V+E)\Theta(V + E).

  3. DFS på GTG^{\mathrm T}

    Kjør DFS på GTG^{\mathrm T}, men la hovedløkka ta nodene etter synkende u.fu.f fra første runde.

  4. Les av trærne

    Hvert dybde-først-tre fra andre runde er én sterk komponent.

Andre runde starter i noden med høyest sluttid, og DFS besøker nøyaktig komponenten dens. Hver ny startnode har høyest sluttid av nodene som er igjen, og i GTG^{\mathrm T} fører kantene ut av komponenten dens bare til komponenter som alt er svarte. Hvert tre blir dermed én komponent, og hele algoritmen tar Θ(V+E)\Theta(V + E).

Koden bruker topological_sort ovenfor som første runde. Grafen kan ha sykler, så lista er ikke en topologisk sortering, men den har fortsatt nodene etter synkende sluttid.

sterke_komponenter.py
def transpose(G):
  GT = {u: [] for u in G}
  for u in G:
      for v in G[u]:
          GT[v].append(u)
  return GT

def strongly_connected_components(G):
  order = topological_sort(G)
  GT = transpose(G)
  color = {u: "white" for u in G}
  components = []

  def visit(u, component):
      color[u] = "gray"
      component.append(u)
      for v in GT[u]:
          if color[v] == "white":
              visit(v, component)
      color[u] = "black"

  for u in order:
      if color[u] == "white":
          component = []
          visit(u, component)
          components.append(component)
  return components
Strongly-Connected-Components
0 / 0

Grafen fra forelesningen, med tidene d/f fra første runde ved hver node og lista order under grafen. I andre runde er kantene snudd, og et ferdig tre fra andre runde har grønn bakgrunn.

Eksamensoppgave En ny kant
Ordinær eksamen, desember 2025, oppgave 10

Hvordan kan antall sterke komponenter (strongly connected components) i en rettet graf endres hvis man legger til en ny kant? Forklar kort.

Vis løsning Skjul løsning

Antallet kan ikke øke, for en ny kant fjerner ingen stier. La den nye kanten være (u,v)(u, v). Kan vv ikke nå uu, lager kanten ingen ny sykel, og ingenting endres. Kan vv nå uu, slås komponenten til uu, komponenten til vv og alle komponentene på stier fra vv til uu sammen til én. Det kan være mange: en rettet sti med nn noder har nn sterke komponenter, og kanten fra den siste noden til den første gjør stien til en sykel med én komponent.

Svar
Antallet blir det samme eller synker. Det kan synke helt fra nn til 1.

Bredde-først-søk

Bredde-først-søk (BFS) bruker en FIFO-kø QQ som huskeliste, så nodene som ble oppdaget først, besøkes først. BFS(G,s)(G, s) farger alle nodene hvite med u.d=∞u.d = \infty, gjør startnoden ss grå med s.d=0s.d = 0 og legger den i køen. Så lenge køen ikke er tom, tas den første noden uu ut. Hver hvite nabo vv blir grå med v.d=u.d+1v.d = u.d + 1 og v.π=uv.\pi = u og settes bakerst i køen. Når alle naboene er sett, blir uu svart. I BFS teller u.du.d kanter fra ss og er ikke en starttid som i DFS.

Avstanden δ(s,v)\delta(s, v) er det minste antallet kanter på en sti fra ss til vv, og ∞\infty hvis vv ikke kan nås. Traverseringen går ut fra ss lag for lag. Hver kant (u,v)(u, v) som gjør vv grå, gir v.d=u.d+1v.d = u.d + 1, så v.dv.d er lengden av stien fra ss til vv i bredde-først-treet. Ingen sti er kortere enn δ(s,v)\delta(s, v), så v.d≥δ(s,v)v.d \ge \delta(s, v).

Køen er FIFO, og en node som settes inn, får dd én høyere enn noden som ble tatt ut. Nodene tas derfor ut i stigende dd, og køen har aldri mer enn to verdier av dd.

At v.d≤δ(s,v)v.d \le \delta(s, v), viser du med induksjon på avstanden kk. Har alle nodene med avstand kk fått d=kd = k, har en node vv med δ(s,v)=k+1\delta(s, v) = k + 1 en kant inn fra en node uu med avstand kk. Når uu tas ut, er vv hvit og får v.d=k+1v.d = k + 1, eller den er alt oppdaget av en node som ble tatt ut før uu, og da er v.d≤u.d+1=k+1v.d \le u.d + 1 = k + 1.

Når BFS er ferdig, er altså v.d=δ(s,v)v.d = \delta(s, v) (v.d=δ(s,v)v.d = \delta(s, v)). Forgjengerkantene danner et bredde-først-tre, og stien i treet fra ss til vv er en korteste vei. Print-Path skriver den ut ved å følge forgjengerne bakover fra vv og skrive ut nodene på vei ut av rekursjonen.

bfs.py
from collections import deque

def bfs(G, s):
  color = {u: "white" for u in G}
  d = {u: float("inf") for u in G}
  pi = {u: None for u in G}
  color[s] = "gray"
  d[s] = 0
  Q = deque([s])
  while Q:
      u = Q.popleft()
      for v in G[u]:
          if color[v] == "white":
              color[v] = "gray"
              d[v] = d[u] + 1
              pi[v] = u
              Q.append(v)
      color[u] = "black"
  return d, pi

def print_path(pi, s, v):
  if v == s:
      print(s)
  elif pi[v] is None:
      print("ingen sti fra", s, "til", v)
  else:
      print_path(pi, s, pi[v])
      print(v)
BFS
0 / 0

Den samme grafen som for DFS, med startnode 1. Tallet ved hver node er dd, og forgjengerkantene har et bånd. Under grafen står alle nodene som har vært i køen QQ, og de som er tatt ut, er bleke. De siste stegene viser stien print_path skriver ut til noden lengst unna.

Ta grafen fra forelesningen, den første grafen i begge stepperne. DFS fra node 1 når node 4 via 2, 3, 7 og 8, fem kanter fra 1, mens BFS når den med to, langs (1,2)(1, 2) og (2,4)(2, 4). DFS følger én sti så langt den kan og kan oppdage en node via en omvei før den undersøker den korte kanten, så dybde-først-treet gir ikke nødvendigvis korteste vei.

Hver node settes i køen høyst én gang, for den blir grå idet den settes inn, og nabolista til en node gås gjennom én gang, når noden tas ut. BFS bruker derfor O(V+E)O(V + E). DFS starter fra alle nodene og bruker alltid Θ(V+E)\Theta(V + E), mens BFS bare starter i ss. Har ss ingen kanter ut, er det bare initialiseringen igjen, og den tar Θ(V)\Theta(V).

AlgoritmeVersteBeste
DFSΘ(V+E)\Theta(V + E)Θ(V+E)\Theta(V + E)
BFSΘ(V+E)\Theta(V + E)Θ(V)\Theta(V)
Topological-SortΘ(V+E)\Theta(V + E)Θ(V+E)\Theta(V + E)
Strongly-Connected-ComponentsΘ(V+E)\Theta(V + E)Θ(V+E)\Theta(V + E)

Traversering med andre huskelister

Huskelista i BFS er køen QQ. Nodene er hvite før de settes inn, grå mens de står i køen og svarte etterpå. FIFO-køen gjør at BFS finner korteste veier, men traverseringen besøker alle nodene som kan nås fra ss uansett hvilken node som tas ut i hver runde.

Bytter du bare køen med en stakk, blir traverseringen ikke DFS-Visit. Ta kantene (s,a)(s, a), (s,b)(s, b), (a,b)(a, b) og (b,a)(b, a). BFS med stakk gjør aa og bb grå idet ss legger dem på stakken, så begge får forgjengeren ss, og når bb besøkes, er aa alt grå. DFS-Visit går fra ss til aa og kaller seg selv på bb fra aa, så bb får forgjengeren aa. DFS-Visit gjør en node grå først når kallet på den starter, og ikke når noden oppdages. Versjonen under venter på samme måte og gjør en node grå først når den kommer øverst i stakken.

Løkka ser på noden øverst i stakken. Er den hvit, blir den grå, og alle de hvite naboene legges på stakken. Er den ikke hvit, er alt som ble lagt over den, tatt av igjen, så den blir svart og tas av. En node kan ligge i stakken flere ganger. Den besøkes fra kopien som ble lagt på sist, og forgjengeren er noden som la den der. Naboene besøkes i motsatt rekkefølge av DFS-Visit, og stakken kan bli O(E)O(E) lang.

iterativ_dfs.py
def iter_dfs_visit(G, s):
  color = {u: "white" for u in G}
  pi = {u: None for u in G}
  S = [s]
  while S:
      u = S[-1]
      if color[u] != "white":
          color[u] = "black"
          S.pop()
          continue
      color[u] = "gray"
      for v in G[u]:
          if color[v] == "white":
              pi[v] = u
              S.append(v)
  return pi

BFS prioriterer de eldste nodene i huskelista, og DFS de nyeste. Med en prioritetskø kan du prioritere etter noe annet, og da får du andre algoritmer. Prims algoritme for minimale spenntrær og Dijkstras algoritme for korteste vei er traverseringer av dette slaget, der nøklene i køen oppdateres underveis. Med en binær haug koster hver køoperasjon O(lg⁡V)O(\lg V), og traverseringen tar O(Elg⁡V)O(E \lg V) når alle nodene kan nås fra ss.

Eksamensoppgave Køen er ikke FIFO
Kontinuasjonseksamen, august 2018, oppgave 6

Du prøver å implementere BFS for urettede grafer, men på grunn av en kodefeil, er rekkefølgen på nodene i køen din ikke lenger FIFO, men helt vilkårlig. Kan du nå være sikker på å besøke alle nodene? Forklar.

Vis løsning Skjul løsning

Hver node som oppdages, legges i køen, og traverseringen stopper ikke før køen er tom, så alt som legges i køen, blir besøkt. Anta at en node vv som kan nås fra ss, aldri blir besøkt, og se på en sti fra ss til vv. Den første noden på stien som ikke blir besøkt, har en nabo foran seg på stien som blir besøkt. Da ble den oppdaget og lagt i køen, og det er en motsigelse. Rekkefølgen avgjør bare når hver node besøkes. Det som går tapt, er korteste vei: v.dv.d er ikke lenger nødvendigvis avstanden fra ss.

Svar
Ja, hvis grafen er sammenhengende. Da besøkes de samme nodene som med BFS, men v.dv.d er ikke nødvendigvis den korteste avstanden.

Sjekk deg selv

DFS har gitt u.d=2u.d = 2, u.f=9u.f = 9, v.d=4v.d = 4 og v.f=5v.f = 5, og grafen har kanten (v,u)(v, u). Hva slags kant er (v,u)(v, u)?
En rettet graf er lagret med nabolister. Hvor lang tid tar det å finne inngraden til alle nodene?
Oppdatert 10. oktober 2026 Foreslå endring