The Graph Data Structure Mugurel IonuČ› Andreica Spring 2012 The Elements of a Graph • Vertices (nodes) – Numbered from 0 to N-1 (N=total number of vertices) • Edges – Each edge connects two vertices: (i,j) = edge between i and j – Undirected graphs: the edge (i,j) is equivalent to the edge (j,i) – Directed graphs: the edges are directed (they are sometimes called arcs) => i->j is different from j->i (although they both connect i and j) – We will only consider graphs where no edge occurs twice (and there are no loops) • Both vertices and edges can have extra information associated to them (e.g. name, cost) • Graphs can be used for modeling many structures, for example: – Road networks – Social relationships Undirected Graph - example • N=7 vertices (nodes) • Edges 0 1 2 3 4 5 6 – – – – – – – – (0,1) (0,2) (0,3) (1,5) (1,6) (2,5) (3,4) (4,6) Graph Representations • Adjacency matrix – A = an NxN matrix – A[i][j] = 1 if the edge (i,j) occurs in the graph (i.e. the vertices i and j are connected by an edge), and 0 otherwise for undirected graphs – A[i][j] = 1 if there is a directed arc from i to j for directed graphs – A is symmetrical (A[i][j] == A[j][i]) for undirected graphs • Lists of neighbors – An array of N linked-lists – The list L[i] contains all the neighbors of the vertex i – In the case of directed graphs, the neighbors of a vertex are considered to be those nodes j such that the arch i->j exists • (Simplifying) Assumptions – No multiple edges (i,j) – No loops (i,i) Graph Traversals • Depth-First Search (DFS) – Mark all the vertices as not visited – Starting vertex S => call dfs(S) – dfs(x) • Mark x as visited • For y in Neighbors(X): – If (y has not been marked as visited) then call dfs(y) – DFS is the building block for many more advanced algorithms • A simple application: check if a graph is connected • Breadth-First Search (BFS) – Mark all the vertices as not visited and initialize an empty queue Q – Starting vertex S => insert S into a queue Q (Q.enqueue(S)) and mark S as visited – While Q is not empty: • x = Q.dequeue() • For y in Neighbors(X): – If (y has not been marked as visited) then » Mark y as visited » Q.enqueue(y) – BFS can be used for computing shortest paths in the graph Using the Adjacency Matrix (Undirected Graph) for (i = 0; i < N; i++) for (j = 0; j < N; j++) A[i][j] = 0; #include <stdio.h> #include "queue1.h" template<typename TnodeInfo, typenameTedgeInfo> class Graph { public: int N; char **A; TnodeInfo *nodeInfo; TedgeInfo **edgeInfo; Graph(int numNodes) { int i, j; // allocate the array with node information nodeInfo = new TnodeInfo[N]; // allocate the matrix of edge information edgeInfo = new TedgeInfo*[N]; for (i = 0; i < N; i++) edgeInfo[i] = new TedgeInfo[N]; } void setNodeInfo(int i, TnodeInfo info) { nodeInfo[i] = info; } N = numNodes; // allocate the adjacency matrix A = new char*[N]; for (i = 0; i < N; i++) A[i] = new char[N]; TnodeInfo getNodeInfo(int i) { return nodeInfo[i]; } void addEdge(int i, int j) { A[i][j] = A[j][i] = 1; } Using the Adjacency Matrix (cont.) void removeEdge(int i, int j) { A[i][j] = A[j][i] = 0; } void setEdgeInfo(int i, int j, TedgeInfo info) { edgeInfo[i][j] = edgeInfo[j][i] = info; } void dfs(int x) { int y; printf("%d\n", x); visited[x] = 1; for (y = 0; y < g.N; y++) if (g.A[x][y] && !visited[y]) dfs(y); TedgeInfo getEdgeInfo(int i, int j) { return edgeInfo[i][j]; } } ~Graph() { int i; for (i = 0; i < N; i++) { delete A[i]; delete edgeInfo[i]; } delete A; delete edgeInfo; delete nodeInfo; } int *dist; void bfs(int S) { Queue<int> Q; int x, y; Q.enqueue(S); visited[S] = 1; dist[S] = 0; }; Graph<int, int> g(7); char* visited; while (!Q.isEmpty()) { x = Q.dequeue(); printf("%d: dist=%d\n", x, dist[x]); Using the Adjacency Matrix (cont.) visited = new char[g.N]; for (i = 0; i < g.N; i++) visited[i] = 0; for (y = 0; y < g.N; y++) if (g.A[x][y] && !visited[y]) { visited[y] = 1; dist[y] = dist[x] + 1; Q.enqueue(y); } printf("DFS:\n"); dfs(4); } } for (i = 0; i < g.N; i++) visited[i] = 0; int main() { int i; dist = new int[g.N]; g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(0, 3); g.addEdge(1, 6); g.addEdge(2, 5); g.addEdge(3, 4); g.addEdge(4, 6); g.addEdge(5, 1); printf("BFS:\n"); bfs(4); return 0; } Using the Lists of Neighbors (Undirected Graph) #include <stdio.h> #include "linked_list.h" #include "queue1.h" void setNodeInfo(int i, TnodeInfo info) { nodeInfo[i] = info; } template<typename TedgeInfo> struct list_elem_info { int node; TedgeInfo edgeInfo; }; TnodeInfo getNodeInfo(int i) { return nodeInfo[i]; } template<typename TnodeInfo, typename TedgeInfo> class Graph { public: int N; LinkedList<struct list_elem_info<TedgeInfo> > *L; TnodeInfo *nodeInfo; void addEdge(int i, int j) { struct list_elem_info<TedgeInfo> lei_i, lei_j; lei_i.node = j; lei_j.node = i; L[i].addFirst(lei_i); L[j].addFirst(lei_j); } Graph(int numNodes) { N = numNodes; L = new LinkedList<struct list_elem_info<TedgeInfo> > [N]; nodeInfo = new TnodeInfo[N]; } void removeEdge(int i, int j) { struct list_elem<struct list_elem_info<TedgeInfo> > *p; Using the Lists of Neighbors (cont.) p = L[i].pfirst; while (p != NULL) { if (p->info.node == j) break; p = p->next; } p->info.edgeInfo = info; p = L[i].pfirst; while (p != NULL) { if (p->info.node == j) break; p = p->next; } // remove the element pointed to by p from L[i] -- code ommitted p = L[j].pfirst; while (p != NULL) { if (p->info.node == i) break; p = p->next; } p->info.edgeInfo = info; p = L[j].pfirst; while (p != NULL) { if (p->info.node == i) break; p = p->next; } } // remove the element pointed to by p from L[j] -- code ommitted } void setEdgeInfo(int i, int j, TedgeInfo info) { struct list_elem<struct list_elem_info<TedgeInfo> > *p; TedgeInfo getEdgeInfo(int i, int j) { struct list_elem<struct list_elem_info<TedgeInfo> > *p; Using the Lists of Neighbors (cont.) p = L[i].pfirst; while (p != NULL) { if (p->info.node == j) break; p = p->next; } void dfs(int x) { int y; struct list_elem<struct list_elem_info<int> > *p; printf("%d\n", x); visited[x] = 1; p = g.L[x].pfirst; return p->info.edgeInfo; while (p != NULL) { y = p->info.node; if (!visited[y]) dfs(y); p = p->next; } } ~Graph() { int i; delete nodeInfo; for (i = 0; i < N; i++) while (!L[i].isEmpty()) L[i].removeFirst(); delete L; } }; Graph<int, int> g(7); char* visited; } int *dist; void bfs(int S) { Queue<int> Q; int x, y; struct list_elem<struct list_elem_info<int> > *p; Using the Lists of Neighbors (cont.) Q.enqueue(S); visited[S] = 1; dist[S] = 0; int main() { int i; g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(0, 3); g.addEdge(1, 6); g.addEdge(2, 5); g.addEdge(3, 4); g.addEdge(4, 6); g.addEdge(5, 1); while (!Q.isEmpty()) { x = Q.dequeue(); printf("%d: dist=%d\n", x, dist[x]); p = g.L[x].pfirst; while (p != NULL) { y = p->info.node; if (!visited[y]) { visited[y] = 1; dist[y] = dist[x] + 1; Q.enqueue(y); } visited = new char[g.N]; for (i = 0; i < g.N; i++) visited[i] = 0; printf("DFS:\n"); dfs(4); for (i = 0; i < g.N; i++) visited[i] = 0; dist = new int[g.N]; printf("BFS:\n"); bfs(4); p = p->next; } } } return 0; } Implementing Directed Graphs • What needs to be changed in the previous two implementations in order to have directed graphs instead of undirected graphs ?