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.
Queue 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); }
}
}
Let's see it again with invariants stated. The invariants help us verify the correctness of the algorithm.
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 Queue Q;
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.
BinaryHeap 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);
}
}
}
}
Let's see it again with invariants stated. The invariants help us verify the correctness of the algorithm.
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 BinaryHeap 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 }
Analysis: For a connected graph, the runtime of DijkstraSSSP is O(E*log(V));