410 likes | 431 Views
Explore various graph algorithms including trees, minimal spanning trees, connectivity, shortest paths, circles, and more in this comprehensive overview.
E N D
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 zusammen-hängender Graph, der keine Kreise enthält. • Jeder Baum mit n Knoten hat also genau n-1 Kanten. • Ein Baum hat oft einen ausge-zeichneten 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 • #define MAXV 100 /* maximum number of vertices */ • #define MAXDEGREE 50 /* maximum outdegree of a vertex */ • typedef struct { • int v; /* neighboring vertex */ • int weight; /* edge weight */ • } edge; • typedef struct { • edge edges[MAXV+1][MAXDEGREE]; /* adjacency info */ • int degree[MAXV+1]; /* outdegree of each vertex */ • int nvertices; /* number of vertices in the graph */ • int nedges; /* number of edges in the graph */ • } graph;
Prims Algorithmus • int parent[MAXV]; /* discovery relation */ • prim(graph *g, int start) • { • int i,j; /* counters */ • bool intree[MAXV]; /* is the vertex in the tree yet? */ • int distance[MAXV]; /* distance vertex is from start */ • int v; /* current vertex to process */ • int w; /* candidate next vertex */ • int weight; /* edge weight */ • int dist; /* best current distance from start */ • for (i=1; i<=g->nvertices; i++) { • intree[i] = FALSE; • distance[i] = MAXINT; • parent[i] = -1; • } • distance[start] = 0; • v = start;
Prims Algorithmus • 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 • main() • { • graph g; • int i; • read_graph(&g,FALSE); • prim(&g,1); • printf("Out of Prim\n"); • 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: DijkstraIdee: arbeite ähnlich zu Prim, um einen kürzeste-Wege-Baum aufzubauen • All-pairs-shortest-path: Floyd-WarshallIdee: verwende Matrixmultiplikation
Dijkstras Algorithmus • int parent[MAXV]; /* discovery relation */ • dijkstra(graph *g, int start) /* was prim(g,start) */ • { • int i,j; /* counters */ • bool intree[MAXV]; /* is the vertex in the tree yet? */ • int distance[MAXV]; /* distance vertex is from start */ • int v; /* current vertex to process */ • int w; /* candidate next vertex */ • int weight; /* edge weight */ • int dist; /* best current distance from start */ • for (i=1; i<=g->nvertices; i++) { • intree[i] = FALSE; • distance[i] = MAXINT; • parent[i] = -1; • } • distance[start] = 0; • v = start;
Dijkstras Algorithmus • 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 • main() • { • graph g; • int i; • read_graph(&g,FALSE); • dijkstra(&g,1); • for (i=1; i<=g.nvertices; i++) • find_path(1,i,parent); • printf("\n"); • }
Floyd-Warshall Algorithmus • #define MAXV 100 /* maximum number of vertices */ • #define MAXDEGREE 50 /* maximum outdegree of a vertex */ • #define MAXINT 100007 • typedef struct { • int v; /* neighboring vertex */ • int weight; /* edge weight */ • bool in; /* is the edge "in" the solution? */ • } edge; • typedef struct { • edge edges[MAXV][MAXDEGREE]; /* adjacency info */ • int degree[MAXV]; /* outdegree of each vertex */ • int nvertices; /* number of vertices in the graph */ • int nedges; /* number of edges in the graph */ • } graph; • typedef struct { • int weight[MAXV+1][MAXV+1]; /* adjacency/weight info */ • int nvertices; /* number of vertices in the graph */ • } adjacency_matrix;
Floyd-Warshall Algorithmus • 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; • } • read_adjacency_matrix(adjacency_matrix *g, bool directed) • { • int i; /* counter */ • int m; /* number of edges */ • int x,y,w; /* placeholder for edge and weight */ • initialize_adjacency_matrix(g); • scanf("%d %d\n",&(g->nvertices),&m); • 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 • print_graph(adjacency_matrix *g) • { • int i,j; /* counters */ • 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"); • } • } • print_adjacency_matrix(adjacency_matrix *g) • { • int i,j; /* 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 • floyd(adjacency_matrix *g) • { • int i,j; /* dimension counters */ • int k; /* intermediate vertex counter */ • int through_k; /* 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 • main() • { • adjacency_matrix g; • read_adjacency_matrix(&g,FALSE); • print_graph(&g); • floyd(&g); • print_adjacency_matrix(&g); • }
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: • 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. neu
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 undf’(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 • #define MAXV 100 /* maximum number of vertices */ • #define MAXDEGREE 50 /* maximum outdegree of a vertex */ • typedef struct { • int v; /* neighboring vertex */ • int capacity; /* capacity of edge */ • int flow; /* flow through edge */ • int residual; /* residual capacity of edge */ • } edge; • typedef struct { • edge edges[MAXV][MAXDEGREE]; /* adjacency info */ • int degree[MAXV]; /* outdegree of each vertex */ • int nvertices; /* number of vertices in the graph */ • int nedges; /* number of edges in the graph */ • } flow_graph;
Ford-Fulkerson Algorithmus • main() • { • flow_graph g; /* graph to analyze */ • int source, sink; /* source and sink vertices */ • int flow; /* total flow */ • int i; /* counter */ • scanf("%d %d",&source,&sink); • read_flow_graph(&g,TRUE); • netflow(&g,source,sink); • print_flow_graph(&g); • flow = 0; • for (i=0; i<g.nvertices; i++) • flow += g.edges[source][i].flow; • printf("total flow = %d\n",flow); • }
Ford-Fulkerson Algorithmus • initialize_graph(g) • flow_graph *g; /* graph to initialize */ • { • int i; /* counter */ • g -> nvertices = 0; • g -> nedges = 0; • for (i=0; i<MAXV; i++) g->degree[i] = 0; • } • read_flow_graph(g,directed) • flow_graph *g; /* graph to initialize */ • bool directed; /* is this graph directed? */ • { • int i; /* counter */ • int m; /* number of edges */ • int x,y,w; /* placeholder for edge and weight */ • 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); • } • }
Ford-Fulkerson Algorithmus • insert_flow_edge(flow_graph *g, int x, int y, bool directed, int w) • { • if (g->degree[x] > MAXDEGREE) • printf("Warning: insertion(%d,%d) exceeds degree bound\n",x,y); • 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] ++; • if (directed == FALSE) • insert_flow_edge(g,y,x,TRUE,w); • else • g->nedges ++; • }
Ford-Fulkerson Algorithmus • edge *find_edge(flow_graph *g, int x, int y) • { • int i; /* counter */ • for (i=0; i<g->degree[x]; i++) • if (g->edges[x][i].v == y) • return( &g->edges[x][i] ); • return(NULL); • } • add_residual_edges(flow_graph *g) • { • int i,j; /* 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 • print_flow_graph(flow_graph *g) • { • int i,j; /* 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"); • } • } • bool processed[MAXV]; /* which vertices have been processed */ • bool discovered[MAXV]; /* which vertices have been found */ • int parent[MAXV]; /* discovery relation */ • bool finished = FALSE; /* if true, cut off search immediately */
Ford-Fulkerson Algorithmus • initialize_search(g) • flow_graph *g; /* graph to traverse */ • { • int i; /* counter */ • for (i=1; i<=g->nvertices; i++) { • processed[i] = FALSE; • discovered[i] = FALSE; • parent[i] = -1; • } • }
Ford-Fulkerson Algorithmus • bfs(flow_graph *g, int start) • { • queue q; /* queue of vertices to visit */ • int v; /* current vertex */ • int i; /* counter */ • init_queue(&q); • enqueue(&q,start); • discovered[start] = TRUE; • 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 • bool valid_edge(edge e) • { • if (e.residual > 0) return (TRUE); • else return(FALSE); • } • process_vertex(v) • int v; /* vertex to process */ • { • } • process_edge(x,y) • int x,y; /* edge to process */ • { • }
Ford-Fulkerson Algorithmus • 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); • } • } • int path_volume(flow_graph *g, int start, int end, int parents[]) • { • edge *e; /* edge in question */ • edge *find_edge(); • 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 • augment_path(flow_graph *g, int start, int end, int parents[], int volume) • { • edge *e; /* edge in question */ • edge *find_edge(); • if (start == end) return; • e = find_edge(g,parents[end],end); • e->flow += volume; • e->residual -= volume; • e = find_edge(g,end,parents[end]); • e->residual += volume; • augment_path(g,start,parents[end],parents,volume); • }
Ford-Fulkerson Algorithmus • netflow(flow_graph *g, int source, int sink) • { • int volume; /* weight of the augmenting path */ • add_residual_edges(g); • initialize_search(g); • bfs(g,source); • volume = path_volume(g, source, sink, parent); • 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)