Graphs have vertices and edges. Each edge connects two vertices.
Sometimes graphs are directed (an edge is an ordered pair, (a,b), representing a link from a to b, and not vice versa). Sometimes graphs are undirected ( where an unordered pair, written either (a,b) or (b,a), denotes a bidirectional edge). Graphs may be weighted, with a number, a weight, associated with each edge.
We will stick to directed, weighted graphs. When we want to consider unweighted graphs, we'll just set the weights to 1's.
A path of length k in a graph is a sequence of k+1 vertices,
v0,
v1,
v2, ...
vk,
such that each ( vi, vi+1) is an edge for i in 0..k-1.
A path is simple if no two vi's are the same.
A path is a cycle if vk = v0.
A graph is connected if there is a path between every pair of vertices.
// Some useful type names and types typedef int Vertex; typedef float Weight; class NbrNode { public: Vertex v; Weight w; NbrNode *next; NbrNode(int vv, float ww, NbrNode *nn){v = vv; w = ww, next = nn; } }; typedef NbrNode *List; class Graph { public: // data int V; // number of vertices int E; // number of edges List* adj; // array such that adj[u] is singly linked list of nbrs of u. List nil = 0; // functions void read(string filename){ ifstream infile(filename); // read first line to get sizes, set up empty adj array infile >> V >> E; adj = new List[V]; for (int i = 0; i < V; ++i) adj[i] = nil; // read subsequent lines, each giving one edge. Vertex u, v; Weight w; for (int i = 0; i < E; ++i) { infile >> u >> v >> w; adj[u] = new NbrNode(v, w, adj[u]); } } };
1 --> 2 --> 3A.
2 3 1 2 1 2 3 1B.
3 2 3 2 1 2 1 1C.
2 3 1 2 1 2 3 1D.
2 3 3 2 1 2 1 1
3 4 0 1 1 1 2 1 2 0 1 2 1 1
void BFS(Graph G, Vertex s, int *d) { // d is an array of G.V ints. // For each vertex v make d[v] the length of the shortest path from s to v. QueueLet's see it again with invariants stated. The invariants help us verify the correctness of the algorithm.Q; for (Vertex u = 0; u < G.V; ++u) d[u] = inf; // inf can be V d[s] = 0; Q.enqueue(s); while (Q.size() > 0) do { Vertex u = Q.dequeue(); // For each neighbor v of u we do the if-stmt. for (List p = adj[u]; p != nil; p = p->next) { Vertex v = p->v; if (d[v] == inf) { d[v] = d[u] + 1; Q.enqueue(v); } } }
1 void BFS(Graph G, Vertex s, int *d) { 2 // d is an array of G.V ints. 3 // For each vertex v make d[v] the length of the shortest path from s to v. 4 QueueQ; 5 for (Vertex u = 0; u < G.V; ++u) 6 d[u] = inf; // inf can be V 7 d[s] = 0; 8 Q.enqueue(s); 9 /* Invariants: 10 Every vertex v that has d[v] < inf has d[v] = correct distance to s. 11 Every vertex v that is in Q or has been in Q has d[v] < inf. 12 */ 13 while (Q.size() > 0) do { 14 Vertex u = Q.dequeue(); 15 16 // For each neighbor v of u we do the if-stmt. 17 for (List p = adj[u]; p != nil; p = p->next) { 18 Vertex v = p->v; 19 if (d[v] == inf) { 20 d[v] = d[u] + 1; 21 Q.enqueue(v); } 22 } 23 } 24 /* Upon exit of the while loop, every vertex reachable from s has been in Q. 25 Ergo array d contains correct distances to s.
Analysis: For a connected graph, the runtime of BFS is O(E);
5 9 0 1 10 1 4 5 1 4 2 2 3 4 3 0 7 3 2 8 4 1 3 4 2 9 4 3 2
When the graph is weighted, the straightforward BFS won't solve the shortest path problem. However a similar strategy using a priority queue instead of a FIFO queue works. It is Dijkstra's algorithm.
void DijkstraSSSP(Graph G, Vertex s, int *d) { // G is weighted. d is an array of G.V ints. // Result: For each vertex v, make d[v] the length of the shortest path from s to v. BinaryHeapLet's see it again with invariants stated. The invariants help us verify the correctness of the algorithm.PQ; bool *done = new bool[G.V]; // array to mark finished (we know shortest path) vertices. for (Vertex u = 0; u < G.V; ++u) { d[u] = inf; // inf can be V done[u] = false; PQ.add(u); } d[s] = 0; PQ.decreaseKey(s); while (PQ.size() > 0) do { Vertex u = PQ.extractMin(); done[u] = true; // For each neighbor v of u we do the if-stmt. for (List p = adj[u]; p != nil; p = p->next) { Vertex v = p->v; Weight w = p->w; if (done[v] == false and d[u] + w < d[v]) { d[v] = d[u] + w; // fix heap property because of v's new priority. PQ.decreaseKey(v); } } } }
1 void DijkstraSSSP(Graph G, Vertex s, int *d) { // G is weighted. d is an array of G.V ints. // For each vertex v make d[v] the length of the shortest path from s to v. 2 BinaryHeapAnalysis: For a connected graph, the runtime of DijkstraSSSP is O(E*log(V));PQ; 3 bool *done = new bool[G.V]; // array to mark finished (we know shortest path) vertices. 4 for (Vertex u = 0; u < G.V; ++u) { 5 d[u] = inf; // inf can be V 6 done[u] = false; 7 PQ.add(u); } 8 d[s] = 0; /* Invariants: (black) For done vertex v, d[v] is the shortest path length. (gray) For fringe vertex v (i.e. d[v] < inf) we know the shortest path via the done vertices, but possibly there is a shorter path using some other (white or gray) vertices as well. */ 10 while (PQ.size() > 0) do { 11 Vertex u = PQ.extractMin(); 12 done[u] = true; 13 // For each neighbor v of u we do the if-stmt. 14 for (List p = G.adj[u]; p != nil; p = p->next) { 15 Vertex v = p->v; Weight w = p->w; 16 if (done[v] == false and d[u] + w < d[v]) { 17 d[v] = d[u] + w; // fix heap property because of v's new priority. 18 PQ.decreaseKey(v); } } // Algorithm is correct after the while loop because all vertices are in the done category. } 2 }