Problem Solving Paradigms
Exercise 4.2.6.2: Is this statement true: “Bipartite graph has no odd cycle”?
Exercise 4.2.6.1: Implement bipartite check using DFS instead!
Exercise 4.2.6.2: Is this statement true: “Bipartite graph has no odd cycle”?
4.2.7 Graph Edges Property Check via DFS Spanning Tree
Running DFS on a connected component of a graph forms a DFS spanning tree1 (or spanning forest if the graph has more than one component and DFS is run per component). With one more vertex state: DFS_GRAY = 2 (visited but not yet completed ) on top of DFS_BLACK (visited and completed ), we can use this DFS spanning tree (or forest) to classify graph edges into three types:
1. Tree edges: those traversed by DFS, i.e. from vertex with DFS_GRAY to vertex with DFS_WHITE.
2. Back edges: part of a cycle, i.e. from vertex with DFS_GRAY to vertex with DFS_GRAY too.
This is probably the most frequently used application of this algorithm.
Note that usually we do not count bi-directional edges as having a ‘cycle’
(We need to remember dfs_parent to distinguish this, see the code below).
3. Forward/Cross edges from vertex with DFS_GRAY to vertex with DFS_BLACK.
These two type of edges are not typically used in programming contest problems.
Figure 4.4 shows an animation (from left to right) of calling dfs(0) (shown in more details), then dfs(5), and finally dfs(6) on the sample graph in Figure 4.1. We can see that 1→ 2 → 3 → 1 is a (true) cycle and we classify edge (3→ 1) as a back edge, whereas 0 → 1 → 0 is not a cycle but it is just a bi-directional edge (0-1). The code for this DFS variant is shown below.
Exercise 4.2.7.1: Perform graph edges property check on the graph in Figure 4.8. Assume that you start DFS from vertex 0. How many back edges that you can find this time?
// inside int main()
dfs_num.assign(V, DFS_WHITE); dfs_parent.assign(V, 0);
for (int i = 0; i < V; i++) if (dfs_num[i] == DFS_WHITE)
printf("Component %d:\n", ++numComp), graphCheck(i); // 2 lines in one
1A spanning tree T of a connected undirected graph G is a tree with all vertices but subset of the edges of G.
4.2. GRAPH TRAVERSAL Steven & Felixc
Figure 4.4: Animation of DFS when Run on the Sample Graph in Figure 4.1
void graphCheck(int u) { // DFS for checking graph edge properties dfs_num[u] = DFS_GRAY; // color this as DFS_GRAY (temp) instead of DFS_BLACK for (int j = 0; j < (int)AdjList[u].size(); j++) {
ii v = AdjList[u][j];
if (dfs_num[v->first] == DFS_WHITE) { // Tree Edge, DFS_GRAY to DFS_WHITE dfs_parent[v->first] = u; // parent of this children is me graphCheck(v->first);
}
else if (dfs_num[v->first] == DFS_GRAY) { // DFS_GRAY to DFS_GRAY if (v->first == dfs_parent[u]) // to differentiate these two cases printf(" Bidirectional (%d, %d)-(%d, %d)\n", u, v->first, v->first, u);
else // the most frequent application: check if the given graph is cyclic printf(" Back Edge (%d, %d) (Cycle)\n", u, v->first);
}
else if (dfs_num[v->first] == DFS_BLACK) // DFS_GRAY to DFS_BLACK printf(" Forward/Cross Edge (%d, %d)\n", u, v->first);
}
dfs_num[u] = DFS_BLACK; // after recursion, color this as DFS_BLACK (DONE) }
// For the sample graph in Figure 4.1, the output is like this:
// Component 1:
// Bidirectional (1, 0) - (0, 1) // Bidirectional (2, 1) - (1, 2) // Back Edge (3, 1) (Cycle) // Bidirectional (3, 2) - (2, 3) // Bidirectional (4, 3) - (3, 4) // Forward/Cross Edge (1, 4) // Component 2:
// Component 3:
// Bidirectional (7, 6) - (6, 7) // Bidirectional (8, 6) - (6, 8)
4.2.8 Finding Articulation Points and Bridges (in an Undirected Graph) Motivating problem: Given a road map (undirected graph) with costs associated to all intersections (vertices) and roads (edges), sabotage either a single intersection or a single road that has minimum cost such that the road network breaks down. This is a problem of finding the least cost Articulation Point (intersection) or the least cost Bridge (road) in an undirected graph (road map).
4.2. GRAPH TRAVERSAL Steven & Felixc
An ‘Articulation Point’ is defined as a vertex in a graph G whose removal2 disconnects G. A graph without any articulation point is called ‘Biconnected’. Similarly, a ‘Bridge’ is defined as an edge in a graph G whose removal disconnects G. These two problems are usually defined for undirected graphs (although they are still well defined for directed graphs).
A na¨ıve algorithm to find articulation points is shown below (can be tweaked to find bridges too):
1. Run O(V + E) DFS (or BFS) to count number of connected components of the original graph 2. For each vertex v∈ V // O(V )
(a) Cut (remove) vertex v and its incident edges
(b) Run O(V + E) DFS (or BFS) to check if number of connected components increases (c) If yes, v is an articulation point/cut vertex; Restore v and its incident edges
This na¨ıve algorithm calls DFS (or BFS) O(V ) times, thus it runs in O(V×(V +E)) = O(V2+V E).
But this is not the best algorithm as we can actually just run the O(V + E) DFS once to identify all the articulation points and bridges.
This DFS variant, due to John Edward Hopcroft and Robert Endre Tarjan (see problem 22.2 in [3]), is just another extension from the previous DFS code shown earlier.
This algorithm maintains two numbers: dfs_num(u) and dfs_low(u). Here, dfs_num(u) now stores the iteration counter when the vertex u is visited for the first time (not just for distinguishing DFS_WHITE versus DFS_GRAY/DFS_BLACK). The other number dfs_low(u) stores the lowest dfs_num reachable from DFS spanning sub tree of u. Initially dfs_low(u) = dfs_num(u) when vertex u is first visited. Then, dfs_low(u) can only be made smaller if there is a cycle (one back edge exists).
Note that we do not update dfs_low(u) with a back edge (u, v) if v is a direct parent of u.
See Figure 4.5 for clarity. In these two sample graphs, we run articulationPointAndBridge(0).
Suppose for the graph in Figure 4.5 – left side, the sequence of visitation is 0 (at iteration 0)→ 1 (1)
→ 2 (2) (backtrack to 1) → 4 (3) → 3 (4) (backtrack to 4) → 5 (5). See that these iteration counters are shown correctly in dfs_num. As there is no back edge in this graph, all dfs_low = dfs_num.
Figure 4.5: Introducing two More DFS Attributes: dfs num and dfs low
Suppose for the graph in Figure 4.5 – right side, the sequence of visitation is 0 (at iteration 0)→ 1 (1)→ 2 (2) (backtrack to 1) → 3 (3) (backtrack to 1) → 4 (4) → 5 (5). Here, there is an important back edge that forms a cycle, i.e. edge 5-1 that is part of cycle 1-4-5-1. This causes vertices 1, 4, and 5 to be able to reach vertex 1 (with dfs_num 1). Thus dfs_low of {1, 4, 5} are all 1.
When we are in a vertex u with v as its neighbor and dfs_low(v) ≥ dfs_num(u), then u is an articulation vertex. This is because the fact that dfs_low(v) is not smaller than dfs_num(u) implies that there is no back edge connected to vertex v that can reach vertex w with a lower dfs_num(w) (which further implies that w is the ancestor of u in the DFS spanning tree). Thus, to reach that ancestor of u from v, one must pass through vertex u. This implies that removing the vertex u will disconnect the graph.
However, there is one special case: The root of the DFS spanning tree (the vertex chosen as the start of DFS call) is an articulation point only if it has more than one children (a trivial case that is not detected by this algorithm).
2All edges incident to this vertex are also removed.
4.2. GRAPH TRAVERSAL Steven & Felixc
Figure 4.6: Finding Articulation Points with dfs num and dfs low
See Figure 4.6 for more details. On the graph in Figure 4.6 – left side, vertices 1 and 4 are articulation points, because for example in edge 1-2, we see that dfs_low(2)≥ dfs_num(1) and in edge 4-5, we also see that dfs_low(5)≥ dfs_num(4). On the graph in Figure 4.6 – right side, only vertex 1 is the articulation point, because for example in edge 1-5, dfs_low(5) ≥ dfs_num(1).
The process to find bridges is similar. When dfs_low(v) > dfs_num(u), then edge u-v is a bridge. In Figure 4.7, almost all edges are bridges for the left and right graph. Only edges 1-4, 4-5, and 5-1 are not bridges on the right graph (they actually form a cycle). This is because – for example – edge 4-5, dfs_low(5)≤ dfs_num(4), i.e. even if this edge 4-5 is removed, we know for sure that vertex 5 can still reach vertex 1 via another path that bypass vertex 4 as dfs_low(5) = 1 (that other path is actually edge 5-1).
Figure 4.7: Finding Bridges, also with dfs num and dfs low The snippet of the code for this algorithm is as follow:
void articulationPointAndBridge(int u) {
dfs_low[u] = dfs_num[u] = dfsNumberCounter++; // dfs_low[u] <= dfs_num[u]
for (int j = 0; j < (int)AdjList[u].size(); j++) { ii v = AdjList[u][j];
if (dfs_num[v.first] == DFS_WHITE) { // a tree edge
dfs_parent[v.first] = u;
if (u == dfsRoot) rootChildren++; // special case, count children of root articulationPointAndBridge(v.first);
if (dfs_low[v.first] >= dfs_num[u]) // for articulation point articulation_vertex[u] = true; // store this information first
if (dfs_low[v.first] > dfs_num[u]) // for bridge
printf(" Edge (%d, %d) is a bridge\n", u, v.first);
dfs_low[u] = min(dfs_low[u], dfs_low[v.first]); // update dfs_low[u]
}
else if (v.first != dfs_parent[u]) // a back edge and not direct cycle dfs_low[u] = min(dfs_low[u], dfs_num[v.first]); // update dfs_low[u]
} }
4.2. GRAPH TRAVERSAL Steven & Felixc
// inside int main()
dfsNumberCounter = 0; dfs_num.assign(V, DFS_WHITE); dfs_low.assign(V, 0);
dfs_parent.assign(V, 0); articulation_vertex.assign(V, 0);
printf("Bridges:\n");
for (int i = 0; i < V; i++) if (dfs_num[i] == DFS_WHITE) {
dfsRoot = i; rootChildren = 0;
articulationPointAndBridge(i);
articulation_vertex[dfsRoot] = (rootChildren > 1); } // special case printf("Articulation Points:\n");
for (int i = 0; i < V; i++) if (articulation_vertex[i])
printf(" Vertex %d\n", i);
// For the sample graph in Figure 4.5/4.6/4.7 RIGHT, the output is like this:
// Bridges:
// Edge (1, 2) is a bridge // Edge (1, 3) is a bridge // Edge (0, 1) is a bridge // Articulation Points:
// Vertex 1