4.1 Urettede grafer

Urettede grafer.

En graf består af en mængde af knuder og en samling af kanter, der hver forbinder to knuder. Vi bruger tallene \(\{0,\ldots, V-1\}\) for at navngive knuderne i en graf med \(V\) knuder.

Graph


Glosar.

Vi benytter følgende definitioner og terminologi.

Anatomy of a Graph A tree A spanning forest


Datatype for urettede grafer.

Vi implementerer følgende APG for urettede grafer.

public class Graph
Graph(int V)skab en graf med \(V\) knuder og ingen kanter
Graph(In in)læs en graf fra indputstrømmen in
intV()antal knuder
intE()antal kanter
voidaddEdge(int v, int w)tilføj kanten v-w til denne graf
Iterable<Integer>adj(int v)gennemløb alle naboknuder til \(v\)
StringtoString()tekstuel repræsentation af grafen

Den centrale metode er nabogennemløberen adj(), som tillader klientkoden at gennemløbe alle naboer til en given knude. Bemærkelsesværdigt nok kan vi bygge alle algoritmer i denne sektion ved brug af bare denne centrale mekanisme.

Vores testdata tinyG.txt, mediumG.txt og largeG.txt bruger følgende indputformat:

Graph input format

Programmet GraphClient.java er en typisk klient til grafbehandling.

Repræsentation af grafer.

Vi bruger nabolisterepræsentationen af grafer, dvs. at vi opretholder en knude-indekseret række af lister over hver knudes naboknuder.

Adjacency-lists
representation of an undirected graph

Programmet Graph.java implementer APG’en for urettede grafer ved brug af nabolisterepræsentationen. Programmet AdjMatrixGraph.java implementerer samme APG ved brug af nabomatrixrepræsentationen.

Dybde-først-søgning.

Den klassiske dybde-først-søgning (DFS) er en rekursiv metode som systematisk besøger alle knuder og kanter i en graf. For at besøge en knude, Programmet DepthFirstSearch.java implementerer denne tilgang og den følgende APG for grafgennemløb.

public class Search
Search(Graph G, int s)find knuder forbundet med startknuden \(s\)
booleanmarked(int v)er \(v\) forbundet med \(s\)?
intcount()hvor mange knuder er forbundet med \(s\)?

Finde vej.

Vi udvider nu dybde-først-søgningen til ikke bare at rapportere eksistensen af en vej mellem to givne knuder, men til at finde vejen, forudsat at den findes. Vi vil implementere følgende APG:

public class Paths
Paths(Graph G, int s)find knuder forbundet med startknuden \(s\)
booleanhasPathTo(int v)er der en vej fra \(s\) til \(v\)?
Interabel<Integer>pathTo(int v)vej fra \(s\) til \(v\); eller null hvis ingen vej findes

Hertil husker vi den kant v-w, som for første gang tager os til knuden \(w\), ved at sætte variablen edgeTo[w] til \(v\). Med andre ord er kanten v-w den sidste kant på en vej fra startknuden \(s\) til knuden \(w\). Resultatet af den udvidede dybde-først-søgning er nu et træ med rod i startknuden \(s\); rækken edgeTo[] indeholder en forælderhægtet repræsentation af dette træ. Programmet DepthFirstPaths.java implementerer denne tilgang.

Bredde-først-søgning.

Dybde-først-søgning finder en eller anden vej fra startknuden \(s\) til målknuden \(v\). Ofte ef vi interesserede i en korteste af disse veje, dvs. en vej med et minimalt antal kanter. Bredde-først-søgning (BFS) er den klassiske løsning for denne opgave. For at finde en korteste vej fra startknuden \(s\) til målknuden \(v\), begynder vi i \(s\) og ser om \(v\) findes blandt de knuder, vi kan nå ved at følge bare én kant fra \(s\). begynder vi i \(s\) og ser om \(v\) findes blandt de knuder, vi kan nå ved at følge to kanter fra \(s\), osv.

For at implementere denne strategi opretholder vi en kø af alle knuder, som er blevet markeret, men hvis nabolister endnu ikke er blevet undersøgt. Vi begynder med at markere og kø startknuden, og udfører følgende operationer, indtil køen er tom:

Programmet BreadthFirstPaths.java er en implementation af APG’en Paths til at finde korteste veje. Den bruger Queue.java som kø.

Sammenhængskomponenter.

Vores næste direkte anvendelse af dybde-først-søgning er at finde grafens sammenhængskomponenter. I afsnit 1.5 så vi, at «hænger sammen med» er en ækvivalensrelation, og at sammenhængskomponenterne svarer til knudemængdens inddeling i ækvivalensklasser. Vi definerer følgende APG:

public class CC
CC(Graph G)konstruktøren forbehandler grafen
booleanconnected(int v, int w)er \(v\) og \(w\) forbundet
intcount()antal sammenhængskomponenter
intid(int v)komponenet-id for \(v\), et heltal mellem 0 og count()-1

Programmet CC.java bruger DFS for at implementere APG’en for sammenhængskomponenter.

Påstand. DFS marker alle knuder forbundet til en given startknude i tid proportional med summen af deres grader. DFS rapporterer en vej fra startknuden til en markeret knude i tid proportional med vejens længde.

Påstand. For knude \(v\), der kan nås fra \(s\), beregner BFS en korteste vej (i antal kanter) fra \(s\) til \(v\). Køretiden for BFS er proportional med \(V + E\) i værste fald.

Påstand. DFS bruger forbehandlingstid og -plads proportional med \(V + E\) for at støtte sammenhængsforespørgsler i konstant tid per forespørgsel.

Flere anvendelser af DFS.

Vi kan bruge dybde-først-søgning til at besvare en række spørgsmål udover de fundamentale anvendelser foroven.

Symbolgrafer.

I typiske anvendelser udgøres knudenavnene ikke af heltal, men af strenge. Hertil definerer vi et indputformat med følgende egenskaber.

Arkivet routes.txt indeholder et lille eksempel.

airline routes

Arkivet movies.txt er et større eksempel fra filmdatabasen Internet Movie Database. Hver linje i indput beskriver en film, efterfulgt af skuespillerne i filmen.

movie-performer graph