ACM ICPC Praktikum Kapitel 9: Graphdurchlauf Übersicht • • • • • • Arten von Graphen Datenstrukturen für Graphen Breitensuche Tiefensuche Zusammenhangskomponenten Topologische Sortierung Arten von Graphen • Ein Graph G=(V,E) besteht aus einer Kontenmenge V und Kantenmenge E. • G ungerichtet: E µ { {u,v} | u,v 2 V} • Beispiel eines ungerichteten Graphen: Knoten Kante Arten von Graphen • G gerichtet: E µ V £ V • Beispiel eines gerichteten Graphen: • G gewichtet: Gewichtsfunktion für Knoten oder Kanten Arten von Graphen • G is azyklisch, wenn es keinen (gerichteten) Kreis hat, und sonst zyklisch • Beispiel eines gerichteten azyklischen Graphen (in der Literatur oft als DAG bez.) Arten von Graphen • G heißt einfach, wenn keine Kante doppelt vorkommt • Beispiel eines nicht-einfachen Graphen: • G ist beschriftet, falls die Knoten/Kanten Namen haben, sonst unbeschriftet Datenstrukturen für Graphen • G=(V,E) enthalte n Knoten und m Kanten • Adjazenzmatrix: Verwende n £ n-Matrix A[i,j] 2 {0,1} und setze A[i,j] auf 1 genau dann, wenn (i,j) bzw. {i,j} in E. Geeignet für dichte Graphen, d.h. m groß • Array von Adjazenzlisten: Vewende Array V der Größe n. V[i] speichert die Liste der benachbarten Knoten für Knoten i. Geeignet für dünne Graphen, d.h. m klein Datenstrukturen für Graphen • Array von Kanten: Verwende Array der Größe m, um alle Kanten abzuspeichern. Geeignet für sehr dünne Graphen. • Reine Pointerstruktur: Jeder Knoten ist ein Objekt mit Pointern, die Kanten repräsentieren, zu anderen Knoten. Geeignet für dynamische Graphen. Beispiel für Datenstruktur 1. 2. #define MAXV #define MAXDEGREE 100 50 /* maximum number of vertices */ /* maximum outdegree of a vertex */ 3. 4. 5. 6. 7. 8. typedef struct { int 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; 9. initialize_graph(graph *g) 10. { 11. int i; 12. 13. g -> nvertices = 0; g -> nedges = 0; 14. 15. } for (i=1; i<=MAXV; i++) g->degree[i] = 0; /* counter */ Beispiel für Datenstruktur 1. read_graph(graph *g, bool directed) 2. { 3. int i; /* counter */ 4. int m; /* number of edges */ 5. int x, y; /* vertices in edge (x,y) */ 6. initialize_graph(g); 7. scanf("%d %d",&(g->nvertices),&m); 8. 9. 10. 11. 12. } for (i=1; i<=m; i++) { scanf("%d %d",&x,&y); insert_edge(g,x,y,directed); } Beispiel für Datenstruktur 1. insert_edge(graph *g, int x, int y, bool directed) 2. { 3. if (g->degree[x] > MAXDEGREE) 4. printf("Warning: insertion(%d,%d) exceeds max degree\n",x,y); 5. 6. g->edges[x][g->degree[x]] = y; g->degree[x] ++; 7. 8. 9. 10. 11. } if (directed == FALSE) insert_edge(g,y,x,TRUE); else g->nedges ++; Beispiel für Datenstruktur 1. delete_edge(graph *g, int x, int y, bool directed) 2. { 3. int i; /* counter */ 4. 5. 6. 7. for (i=0; i<g->degree[x]; i++) if (g->edges[x][i] == y) { g->degree[x] --; g->edges[x][i] = g->edges[x][g->degree[x]]; 8. 9. if (directed == FALSE) delete_edge(g,y,x,TRUE); 10. 11. return; 12. 13. } } printf("Warning: deletion(%d,%d) not found in g.\n",x,y); Beispiel für Datenstruktur 1. print_graph(graph *g) 2. { 3. int i,j; /* counters */ 4. 5. 6. 7. 8. 9. 10. } for (i=1; i<=g->nvertices; i++) { printf("%d: ",i); for (j=0; j<g->degree[i]; j++) printf(" %d",g->edges[i][j]); printf("\n"); } Breitensuche • Problem: durchsuche Graph G=(V,E) systematisch nach Eigenschaften • Lösung: Breitensuche (breadth-firstsearch, BFS) oder Tiefensuche (depthfirst-search, DFS). Jeder besuchte Knoten bzw. jede besuchte Kante wird markiert, um doppeltes Besuchen zu vermeiden. Breitensuche 1. bool processed[MAXV]; /* which vertices have been processed */ 2. bool discovered[MAXV]; /* which vertices have been found */ 3. int parent[MAXV]; /* discovery relation */ 4. bool finished = FALSE; /* if true, cut off search immediately */ 5. initialize_search(graph *g) 6. { 7. int i; /* counter */ 8. 9. 10. 11. 12. } for (i=1; i<=g->nvertices; i++) { processed[i] = discovered[i] = FALSE; parent[i] = -1; } Breitensuche 1. 2. 3. 4. 5. bfs(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]] == FALSE) { enqueue(&q,g->edges[v][i]); discovered[g->edges[v][i]] = TRUE; parent[g->edges[v][i]] = v; } if (processed[g->edges[v][i]] == FALSE) process_edge(v,g->edges[v][i]); } } Breitensuche - Demo 1. 2. process_vertex(int v) { printf("processed vertex %d\n",v); } 3. 4. process_edge(int x, int y) { printf("processed edge (%d,%d)\n",x,y); } 5. 6. bool valid_edge(int e) { return (TRUE); } 7. main() 8. { 9. graph g; 10. int i; 11. 12. 13. 14. 15. 16. 17. 18. 19. } read_graph(&g,FALSE); print_graph(&g); initialize_search(&g); bfs(&g,1); for (i=1; i<=g.nvertices; i++) printf(" %d",parent[i]); printf("\n"); for (i=1; i<=g.nvertices; i++) find_path(1,i,parent); printf("\n"); Anwendung: Suche nach Pfad • Problem: finde Pfad von „start” nach “end”. 1. find_path(int start, int end, int parents[]) 2. { 3. if ((start == end) || (end == -1)) 4. printf("\n%d",start); 5. else { 6. find_path(start,parents[end],parents); 7. printf(" %d",end); 8. } 9. } Tiefensuche 1. 2. 3. 4. dfs(graph *g, int v) { int i; int y; /* counter */ /* successor vertex */ 5. if (finished) return; /* allow for search termination */ 6. 7. discovered[v] = TRUE; process_vertex(v); 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. for (i=0; i<g->degree[v]; i++) { y = g->edges[v][i]; if (valid_edge(g->edges[v][i]) == TRUE) { if (discovered[y] == FALSE) { parent[y] = v; dfs(g,y); } else if (processed[y] == FALSE) process_edge(v,y); } if (finished) return; } 20. 21. } processed[v] = TRUE; Tiefensuche - Demo 1. 2. process_vertex(int v) { printf("processed vertex %d\n",v); } 3. 4. 5. 6. 7. process_edge(int x, int y) { if (parent[x] == y) printf("processed tree edge (%d,%d)\n",x,y); else printf("processed back edge (%d,%d)\n",x,y); } 8. 9. bool valid_edge(int e) { return (TRUE); } 10. 11. main() { graph g; 12. int i; 13. 14. 15. 16. read_graph(&g,FALSE); print_graph(&g); initialize_search(&g); dfs(&g,1); 17. 18. 19. for (i=1; i<=g.nvertices; i++) find_path(1,i,parent); printf("\n"); } Anwendung 1: suche Kreis • Problem: suche nach Kreis in ungerichtetem Graph 1. process_edge(int x, int y) 2. { 3. if (parent[x] != y) { /* found back edge! */ 4. printf(“Cycle from %d to %d:”, y, x); 5. find_path(y,x,parent); 6. finished = TRUE; 7. } } 8. process_vertex(int v) { } Anwendung 2: Zusammenhangskomponenten • Problem: finde Zusammenhangskomponenten in ungerichtetem Graph 1. 2. 3. 4. connected_components(graph *g) { int c; /* component number */ int i; /* counter */ 5. 6. 7. 8. 9. 10. 11. 12. 13. } initialize_search(g); c = 0; for (i=1; i<=g->nvertices; i++) if (discovered[i] == FALSE) { c = c+1; printf(“Component %d: “, c); dfs(g,i); printf(“\n”); } 14. process_vertex(int v) { print(“ %d”, v); } 15. process_edge(int x, int y) { } Anwendung 3: Topologisches Sortieren • Problem: sortiere Knoten in Reihe s, so dass für jede Kante (i,j) gilt: s(i) < s(j) • Lösung: 1. compute_indegrees(graph *g, int in[]) 2. { 3. int i,j; /* counters */ 4. for (i=1; i<=g->nvertices; i++) in[i] = 0; 5. 6. 7. } for (i=1; i<=g->nvertices; i++) for (j=0; j<g->degree[i]; j++) in[ g->edges[i][j] ] ++; Anwendung 3: Topologisches Sortieren 1. 2. 3. 4. 5. 6. topsort(graph *g, int sorted[]) { int indegree[MAXV]; queue zeroin; int x, y; int i, j; /* indegree of each vertex */ /* vertices of indegree 0 */ /* current and next vertex */ /* counters */ 7. 8. 9. 10. compute_indegrees(g,indegree); init_queue(&zeroin); for (i=1; i<=g->nvertices; i++) if (indegree[i] == 0) enqueue(&zeroin,i); 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. j=0; while (empty(&zeroin) == FALSE) { j = j+1; x = dequeue(&zeroin); sorted[j] = x; for (i=0; i<g->degree[x]; i++) { y = g->edges[x][i]; indegree[y] --; if (indegree[y] == 0) enqueue(&zeroin,y); } } 22. 23. 24. if (j != g->nvertices) printf("Not a DAG -- only %d vertices found\n",j); } Anwendung 3: Topologisches Sortieren 1. main() 2. { 3. graph g; 4. int out[MAXV]; 5. int i; 6. 7. read_graph(&g,TRUE); print_graph(&g); 8. topsort(&g,out); 9. 10. 11. for (i=1; i<=g.nvertices; i++) printf(" %d",out[i]); printf("\n"); 12. }