Graphalgorithmen

Werbung
ACM ICPC
Praktikum
Kapitel 10: Graphalgorithmen
Übersicht
•
•
•
•
•
•
•
Bäume
Minimale Spannbäume
Zusammenhang
Kürzeste Wege
Kreise
Planare Graphen
Netzwerkfluss und bipartites Matching
Bäume
• Ein Baum ist ein zusammenhängender Graph, der keine
Kreise enthält.
• Jeder Baum mit n Knoten hat
also genau n-1 Kanten.
• Ein Baum hat oft einen ausgezeichneten Knoten, der die
Wurzel repräsentiert.
• Jeder Knoten mit Grad 1 im
Baum heißt Blatt.
Bäume
• Gewurzelter Baum:
gerichteter Baum, in dem
jeder Knoten bis auf Wurzel
Eingrad 1 hat.
• Binärer Baum:
Jeder Knoten hat Ausgrad
2 oder 0.
• Spannbaum eines Graphen
G=(V,E): Teilgraph von G,
der ein Baum ist und alle
Knoten von G enthält.
Minimaler Spannbaum
• Gegeben: Graph G=(V,E) mit Kantenkosten
c:E ! IR
• Gesucht: Spannbaum T in G mit minimaler
Summe der Kantenkosten
• Algorithmen:
Starte mit leerer Kantenmenge. Wiederhole, bis
Spannbaum erreicht:
– Kruskal: füge Kante mit minimalem Gewicht unter
Wahrung der Kreisfreiheit hinzu
– Prim: erweitere Baum um minimale Kante
Prims Algorithmus
1. #define
MAXV 100
2. #define MAXDEGREE 50
3. typedef struct {
4.
int v;
5.
int weight;
6. } edge;
/* maximum number of vertices */
/* maximum outdegree of a vertex */
/* neighboring vertex */
/* edge weight */
7. typedef struct {
8.
edge edges[MAXV+1][MAXDEGREE];
/* adjacency info */
9.
int degree[MAXV+1];
/* outdegree of each vertex */
10.
int nvertices;
/* number of vertices in the graph */
11.
int nedges;
/* number of edges in the graph */
12. } graph;
Prims Algorithmus
1.
int parent[MAXV];
/* discovery relation */
2. prim(graph *g, int start)
3. {
4.
int i,j;
5.
bool intree[MAXV];
6.
int distance[MAXV];
7.
int v;
8.
int w;
9.
int weight;
10.
int dist;
11.
12.
13.
14.
15.
for (i=1; i<=g->nvertices; i++) {
intree[i] = FALSE;
distance[i] = MAXINT;
parent[i] = -1;
}
16.
17.
distance[start] = 0;
v = start;
/* counters */
/* is the vertex in the tree yet? */
/* distance vertex is from start */
/* current vertex to process */
/* candidate next vertex */
/* edge weight */
/* best current distance from start */
Prims Algorithmus
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19. }
while (intree[v] == FALSE) {
intree[v] = TRUE;
for (i=0; i<g->degree[v]; i++) {
w = g->edges[v][i].v;
weight = g->edges[v][i].weight;
if ((distance[w] > weight) && (intree[w] == FALSE)) {
distance[w] = weight;
parent[w] = v;
}
}
v = 1;
dist = MAXINT;
for (i=1; i<=g->nvertices; i++)
if ((intree[i] == FALSE) && (dist > distance[i])) {
dist = distance[i];
v = i;
}
}
Prims Algorithmus
1. main()
2. {
3.
graph g;
4.
int i;
5.
read_graph(&g,FALSE);
6.
prim(&g,1);
7.
printf("Out of Prim\n");
8.
9.
10.
11.
12.
13. }
for (i=1; i<=g.nvertices; i++) {
/*printf(" %d parent=%d\n",i,parent[i]);*/
find_path(1,i,parent);
}
printf("\n");
MST Probleme
• Maximum Spanning Tree: Maximiere
Kosten eines Spannbaums.
• Minimum Product Spanning Tree:
Minimiere Produkt der Kantenkosten
(´ minimiere Summe der Logarithmen)
• Minimum Bottleneck Spanning Tree:
Minimiere maximale Kantenkosten
(MST minimiert auch max. Kantenkosten)
Zusammenhang
• Ein ungerichteter (gerichteter) Graph ist (stark)
zusammenhängend, wenn es einen
ungerichteten (gerichteten) Weg zwischen zwei
beliebigen Knotenpaaren gibt.
• Jeder Graph, der auch nach Löschung eines
beliebigen Knotens noch zusammenhängend ist,
heißt zweifach zusammenhängend.
• Eine Kante, dessen Löschung den Graphen in
zwei Teilgraphen zerteilt, heißt Brücke.
Test auf Zusammenhang
• Einfacher Zusammenhang: DFS, BFS
• Starker Zusammenhang: Finde gerichteten Kreis
mittels DFS. Schrumpfe solch einen Kreis zu
einem einzelnen Knoten und wiederhole, bis
kein gerichteter Kreis mehr gefunden. Ergibt
einzelnen Knoten:
Graph start zusammenhängend.
• k-facher Zusammenhang: Teste, ob jedes
Knotenpaar Fluss der Größe >=k hat.
Kürzeste Wege
• Gegeben: Graph G=(V,E) mit
Kantenkosten c:E ! IR
• Weg von v nach w ist kürzester Weg,
wenn Summe der Kantenkosten minimal.
• Single-source-shortest-path: Dijkstra
Idee: arbeite ähnlich zu Prim, um einen
kürzeste-Wege-Baum aufzubauen
• All-pairs-shortest-path: Floyd-Warshall
Idee: verwende Matrixmultiplikation
Dijkstras Algorithmus
1.
int parent[MAXV];
2. dijkstra(graph *g, int start)
3. {
4.
int i,j;
5.
bool intree[MAXV];
6.
int distance[MAXV];
7.
int v;
8.
int w;
9.
int weight;
10.
int dist;
/* discovery relation */
/* was prim(g,start) */
/* counters */
/* is the vertex in the tree yet? */
/* distance vertex is from start */
/* current vertex to process */
/* candidate next vertex */
/* edge weight */
/* best current distance from start */
11.
12.
13.
14.
15.
for (i=1; i<=g->nvertices; i++) {
intree[i] = FALSE;
distance[i] = MAXINT;
parent[i] = -1;
}
16.
17.
distance[start] = 0;
v = start;
Dijkstras Algorithmus
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20. }
while (intree[v] == FALSE) {
intree[v] = TRUE;
for (i=0; i<g->degree[v]; i++) {
w = g->edges[v][i].v;
weight = g->edges[v][i].weight;
if (distance[w] > (distance[v]+weight)) {
distance[w] = distance[v]+weight;
parent[w] = v;
}
}
v = 1;
dist = MAXINT;
for (i=1; i<=g->nvertices; i++)
if ((intree[i] == FALSE) && (dist > distance[i])) {
dist = distance[i];
v = i;
}
}
/*for (i=1; i<=g->nvertices; i++) printf("%d %d\n",i,distance[i]);*/
Dijkstras Algorithmus
1. main()
2. {
3.
graph g;
4.
int i;
5.
6.
read_graph(&g,FALSE);
dijkstra(&g,1);
7.
8.
9.
for (i=1; i<=g.nvertices; i++)
find_path(1,i,parent);
printf("\n");
10. }
Floyd-Warshall Algorithmus
•
•
#define MAXV
#define MAXDEGREE
•
#define MAXINT
•
•
•
•
•
typedef struct {
int v;
int weight;
bool in;
} edge;
/* neighboring vertex */
/* edge weight */
/* is the edge "in" the solution? */
•
•
•
•
•
•
typedef struct {
edge edges[MAXV][MAXDEGREE];
int degree[MAXV];
int nvertices;
int nedges;
} graph;
/* adjacency info */
/* outdegree of each vertex */
/* number of vertices in the graph */
/* number of edges in the graph */
•
•
•
•
typedef struct {
int weight[MAXV+1][MAXV+1];
int nvertices;
} adjacency_matrix;
/* adjacency/weight info */
/* number of vertices in the graph */
100
50
/* maximum number of vertices */
/* maximum outdegree of a vertex */
100007
Floyd-Warshall Algorithmus
1.
2.
3.
4.
5.
6.
7.
8.
initialize_adjacency_matrix(adjacency_matrix *g)
{
int i,j;
/* counters */
g -> nvertices = 0;
for (i=1; i<=MAXV; i++)
for (j=1; j<=MAXV; j++)
g->weight[i][j] = MAXINT;
}
9. read_adjacency_matrix(adjacency_matrix *g, bool directed)
10. {
11.
int i;
/* counter */
12.
int m;
/* number of edges */
13.
int x,y,w;
/* placeholder for edge and weight */
14.
15.
initialize_adjacency_matrix(g);
scanf("%d %d\n",&(g->nvertices),&m);
16.
17.
18.
19.
20.
21. }
for (i=1; i<=m; i++) {
scanf("%d %d %d\n",&x,&y,&w);
g->weight[x][y] = w;
if (directed==FALSE) g->weight[y][x] = w;
}
Floyd-Warshall Algorithmus
1.
2.
3.
print_graph(adjacency_matrix *g)
{
int i,j;
4.
5.
6.
7.
8.
9.
10.
11. }
for (i=1; i<=g->nvertices; i++) {
printf("%d: ",i);
for (j=1; j<=g->nvertices; j++)
if (g->weight[i][j] < MAXINT)
printf(" %d",j);
printf("\n");
}
12. print_adjacency_matrix(adjacency_matrix *g)
13. {
14.
int i,j;
/* counters */
15.
16.
17.
18.
19.
20.
•
}
/* counters */
for (i=1; i<=g->nvertices; i++) {
printf("%3d: ",i);
for (j=1; j<=g->nvertices; j++)
printf(" %3d",g->weight[i][j]);
printf("\n");
}
Floyd-Warshall Algorithmus
1. floyd(adjacency_matrix *g)
2. {
3.
int i,j;
4.
int k;
5.
int through_k;
6.
7.
8.
9.
10.
11.
12.
13. }
/* dimension counters */
/* intermediate vertex counter */
/* distance through vertex k */
for (k=1; k<=g->nvertices; k++)
for (i=1; i<=g->nvertices; i++)
for (j=1; j<=g->nvertices; j++) {
through_k = g->weight[i][k]+g->weight[k][j];
if (through_k < g->weight[i][j])
g->weight[i][j] = through_k;
}
Floyd-Warshall Algorithmus
1. main()
2. {
3.
adjacency_matrix g;
4.
5.
read_adjacency_matrix(&g,FALSE);
print_graph(&g);
6.
floyd(&g);
7.
print_adjacency_matrix(&g);
8. }
Kreise
• Alle Graphen, die keine Bäume sind,
enthalten Kreise.
• Eulerkreis: Kreis, der jede Kante genau
einmal durchläuft.
• Hamiltonscher Kreis: Kreis, der jeden
Knoten genau einmal durchläuft.
Eulerkreis
• Eulerkreis im ungerichteten Graphen:
Existiert, wenn alle Knoten geraden Grad haben.
Algorithmus: Beginne bei beliebigem Knoten,
erweitere Kreis um beliebige Kante, bis wieder
am Ausgangspunkt.
Wiederholung ergibt kantendisjunkte Kreise, die
beliebige verschmolzen werden können.
• Eulerkreis im gerichteten Graphen:
Existiert, falls für jeden Knoten der Eingrad
gleich dem Ausgrad ist. Dann wie oben.
Hamiltonscher Kreis
• NP-vollständiges Problem, d.h. es gibt
aller Voraussicht nach keinen
Polynomialzeitalgorithmus dafür.
• Backtracking (mittels Durchlauf aller
möglichen Knotenpermutationen) kann
verwendet werden, falls Graph genügend
klein ist.
Planare Graphen
• Ein Graph ist planar, falls die Knoten und Kanten
so in 2D-Raum eingebettet werden können,
dass es keine Kantenüberschneidungen gibt.
• Ein Baum ist ein planarer Graph.
• Sei n Anzahl Knoten, m Anzahl Kanten und f
Anzahl Facetten, dann gilt n-m+f=2.
• Jeder planare Graph erfüllt m <= 3n-6.
• Ein Graph ist planar genau dann, wenn er
keinen K3,3 oder K5 als Graphminor enthält.
Netzwerkfluss
• Gegeben: gerichteter Graph G=(V,E) mit
Kantenkapazitäten c:E ! IR+ und Quell-Ziel-Paar
(s,t)
• Gesucht: Fluss f:E ! IR+ mit maximalem
Flusswert von s nach t.
• Fluss f ist legal, falls
– (u,v) f(u,v) = (v,w) f(v,w) für alle v 2 V n {s,t}
– f(e) <= c(e) für alle e 2 E
• Flusswert von f ist (s,w) f(s,w) - (u,s) f(u,s)
Netzwerkfluss
• O.B.d.A. sei G ein einfacher gerichteter
Graph, ansonsten Transformation:
neu
• Residuales Netzwerk von f: G’=(V,E’) mit
c’:E ! IR+, wobei c’(u,v)=c(u,v)-f(u,v) falls
(u,v) 2 E, c’(u,v) = f(v,u) falls (v,u) 2 E, und
sonst c’(u,v) = 0.
Netzwerkfluss
• Augmentierender Pfad p=(v1,…,vk) in G’:
Pfad mit positiven Kantenkosten
• Residuale Kapazität von p:
cp = mini c’(vi,vi+1)
• Neuer Fluss f’ =f+p:
f’(u,v) = f(u,v)+cp falls (u,v) 2 p und
f’(u,v) = f(u,v)-cp falls (v,u) 2 p
• Es gilt: Flusswert von f’ > Flusswert von f
und f maximal , kein augm. Pfad in G’
Ford-Fulkerson Algorithmus
1. #define
MAXV
2. #define MAXDEGREE
100
50
/* maximum number of vertices */
/* maximum outdegree of a vertex */
3. typedef struct {
4.
int v;
5.
int capacity;
6.
int flow;
7.
int residual;
8. } edge;
/* neighboring vertex */
/* capacity of edge */
/* flow through edge */
/* residual capacity of edge */
9. typedef struct {
10.
edge edges[MAXV][MAXDEGREE];
11.
int degree[MAXV];
12.
int nvertices;
13.
int nedges;
14. } flow_graph;
/* adjacency info */
/* outdegree of each vertex */
/* number of vertices in the graph */
/* number of edges in the graph */
Ford-Fulkerson Algorithmus
1.
2.
3.
4.
5.
6.
main()
{
flow_graph g;
int source, sink;
int flow;
int i;
/* graph to analyze */
/* source and sink vertices */
/* total flow */
/* counter */
7.
8.
scanf("%d %d",&source,&sink);
read_flow_graph(&g,TRUE);
9.
netflow(&g,source,sink);
10.
print_flow_graph(&g);
11.
12.
13.
flow = 0;
for (i=0; i<g.nvertices; i++)
flow += g.edges[source][i].flow;
14.
15. }
printf("total flow = %d\n",flow);
Ford-Fulkerson Algorithmus
1.
2.
3.
4.
initialize_graph(g)
flow_graph *g;
{
int i;
5.
6.
7.
8.
}
9.
10.
11.
12.
13.
14.
15.
read_flow_graph(g,directed)
flow_graph *g;
bool directed;
{
int i;
int m;
int x,y,w;
/* graph to initialize */
/* counter */
g -> nvertices = 0;
g -> nedges = 0;
for (i=0; i<MAXV; i++) g->degree[i] = 0;
16.
17.
18.
19.
20.
21.
22. }
initialize_graph(g);
scanf("%d %d\n",&(g->nvertices),&m);
for (i=1; i<=m; i++) {
scanf("%d %d %d\n",&x,&y,&w);
insert_flow_edge(g,x,y,directed,w);
}
/* graph to initialize */
/* is this graph directed? */
/* counter */
/* number of edges */
/* placeholder for edge and weight */
Ford-Fulkerson Algorithmus
1. insert_flow_edge(flow_graph *g, int x, int y, bool directed, int w)
2. {
3.
if (g->degree[x] > MAXDEGREE)
4.
printf("Warning: insertion(%d,%d) exceeds degree bound\n",x,y);
5.
6.
7.
8.
9.
g->edges[x][g->degree[x]].v = y;
g->edges[x][g->degree[x]].capacity = w;
g->edges[x][g->degree[x]].flow = 0;
g->edges[x][g->degree[x]].residual = w;
g->degree[x] ++;
10.
11.
12.
13.
14. }
if (directed == FALSE)
insert_flow_edge(g,y,x,TRUE,w);
else
g->nedges ++;
Ford-Fulkerson Algorithmus
1.
2.
3.
edge *find_edge(flow_graph *g, int x, int y)
{
int i;
/* counter */
4.
5.
6.
for (i=0; i<g->degree[x]; i++)
if (g->edges[x][i].v == y)
return( &g->edges[x][i] );
7.
8.
return(NULL);
}
9. add_residual_edges(flow_graph *g)
10. {
11.
int i,j;
12.
13.
14.
15.
16. }
/* counters */
for (i=1; i<=g->nvertices; i++)
for (j=0; j<g->degree[i]; j++)
if (find_edge(g,g->edges[i][j].v,i) == NULL)
insert_flow_edge(g,g->edges[i][j].v,i,TRUE,0);
Ford-Fulkerson Algorithmus
1.
2.
3.
print_flow_graph(flow_graph *g)
{
int i,j;
4.
5.
6.
7.
8.
9.
10.
11.
12.
13. }
/* counters */
for (i=1; i<=g->nvertices; i++) {
printf("%d: ",i);
for (j=0; j<g->degree[i]; j++)
printf(" %d(%d,%d,%d)",g->edges[i][j].v,
g->edges[i][j].capacity,
g->edges[i][j].flow,
g->edges[i][j].residual);
printf("\n");
}
14. bool processed[MAXV];
15. bool discovered[MAXV];
16. int parent[MAXV];
17. bool finished = FALSE;
/* which vertices have been processed */
/* which vertices have been found */
/* discovery relation */
/* if true, cut off search immediately */
Ford-Fulkerson Algorithmus
1. initialize_search(g)
2. flow_graph *g;
traverse */
3. {
4.
int i;
5.
6.
7.
8.
9.
10. }
/* graph to
/* counter */
for (i=1; i<=g->nvertices; i++) {
processed[i] = FALSE;
discovered[i] = FALSE;
parent[i] = -1;
}
Ford-Fulkerson Algorithmus
1.
2.
3.
4.
5.
bfs(flow_graph *g, int start)
{
queue q;
int v;
int i;
/* queue of vertices to visit */
/* current vertex */
/* counter */
6.
7.
8.
init_queue(&q);
enqueue(&q,start);
discovered[start] = TRUE;
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24. }
while (empty(&q) == FALSE) {
v = dequeue(&q);
process_vertex(v);
processed[v] = TRUE;
for (i=0; i<g->degree[v]; i++)
if (valid_edge(g->edges[v][i]) == TRUE) {
if (discovered[g->edges[v][i].v] == FALSE) {
enqueue(&q,g->edges[v][i].v);
discovered[g->edges[v][i].v] = TRUE;
parent[g->edges[v][i].v] = v;
}
if (processed[g->edges[v][i].v] == FALSE)
process_edge(v,g->edges[v][i].v);
}
}
Ford-Fulkerson Algorithmus
1. bool valid_edge(edge e)
2. {
3.
if (e.residual > 0) return (TRUE);
4.
else return(FALSE);
5. }
6.
7.
8.
9.
process_vertex(v)
int v;
{
}
10. process_edge(x,y)
11. int x,y;
12. {
13. }
/* vertex to process */
/* edge to process */
Ford-Fulkerson Algorithmus
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
find_path(start,end,parents)
int start;
/* first vertex on path */
int end;
/* last vertex on path */
int parents[];
/* array of parent pointers */
{
if ((start == end) || (end == -1))
printf("\n%d",start);
else {
find_path(start,parents[end],parents);
printf(" %d",end);
}
}
13. int path_volume(flow_graph *g, int start, int end, int parents[])
14. {
15.
edge *e;
/* edge in question */
16.
edge *find_edge();
17.
18.
19.
20.
21.
22.
23. }
if (parents[end] == -1) return(0)
e = find_edge(g,parents[end],end);
if (start == parents[end])
return(e->residual);
else
return( min(path_volume(g,start,parents[end],parents), e->residual) );
Ford-Fulkerson Algorithmus
1. augment_path(flow_graph *g, int start, int end, int parents[], int volume)
2. {
3.
edge *e;
/* edge in question */
4.
edge *find_edge();
5.
if (start == end) return;
6.
7.
8.
9.
10.
11.
e = find_edge(g,parents[end],end);
e->flow += volume;
e->residual -= volume;
12.
13. }
augment_path(g,start,parents[end],parents,volume);
e = find_edge(g,end,parents[end]);
e->residual += volume;
Ford-Fulkerson Algorithmus
1. netflow(flow_graph *g, int source, int sink)
2. {
3.
int volume;
/* weight of the augmenting path */
4.
add_residual_edges(g);
5.
6.
initialize_search(g);
bfs(g,source);
7.
volume = path_volume(g, source, sink, parent);
8.
9.
10.
11.
12.
13.
14. }
while (volume > 0) {
augment_path(g,source,sink,parent,volume);
initialize_search(g);
bfs(g,source);
volume = path_volume(g, source, sink, parent);
}
Anwendungen
• Maximum Matching in bipartiten Graphen.
• Gegeben: Graph G=(V,E)
• Matching: Teilmenge E’ ½ E, so dass jeder
Knoten max. Grad 1 hat.
• G bipartit genau dann, wenn zweifärbbar
(d.h. zwei Farben reichen, Knoten so zu
färben, dass keine zwei adjazenten
Knoten dieselbe Farbe haben)
Herunterladen