Bridges in a connected graph - algorithm

I have a programming task(not homework.) where I have to find the bridges in a graph. I worked on it a bit myself, but could not come up with anything satisfactory. So i googled it , I did find something but I am unable to understand the algorithm as it is presented. Could someone please take a look at this code and give me an explanation.?
public Bridge(Graph G) {
low = new int[G.V()];
pre = new int[G.V()];
for (int v = 0; v < G.V(); v++) low[v] = -1;
for (int v = 0; v < G.V(); v++) pre[v] = -1;
for (int v = 0; v < G.V(); v++)
if (pre[v] == -1)
dfs(G, v, v);
}
public int components() { return bridges + 1; }
private void dfs(Graph G, int u, int v) {
pre[v] = cnt++;
low[v] = pre[v];
for (int w : G.adj(v)) {
if (pre[w] == -1) {
dfs(G, v, w);
low[v] = Math.min(low[v], low[w]);
if (low[w] == pre[w]) {
StdOut.println(v + "-" + w + " is a bridge");
bridges++;
}
}
// update low number - ignore reverse of edge leading to v
else if (w != u)
low[v] = Math.min(low[v], pre[w]);
}
}

Def: Bridge is an edge, when removed, will disconnect the graph (or increase the number of connected components by 1).
One observation regarding bridges in graph; none of the edges that belong to a loop can be a bridge. So in a graph such as A--B--C--A, removing any of the edge A--B, B--C and C--A will not disconnect the graph. But, for an undirected graph, the edge A--B implies B--A; and this edge could still be a bridge, where the only loop it is in is A--B--A. So, we should consider only those loops formed by a back edge. This is where the parent information you've passed in the function argument helps. It will help you to not use the loops such as A--B--A.
Now to identify the back edge (or the loop), A--B--C--A we use the low and pre arrays. The array pre is like the visited array in the dfs algorithm; but instead of just flagging that the vertex as visited, we identify each vertex with a different number (according to its position in the dfs tree). The low array helps to identify if there is a loop. The low array identifies the lowest numbered (from pre array) vertex that the current vertex can reach.
Lets work through this graph A--B--C--D--B.
Starting at A
dfs: ^ ^ ^ ^ ^
pre: 0 -1 -1 -1 -1 0--1 -1 -1 1 0--1--2 -1 1 0--1--2--3 1 0--1--2--3--1
graph: A--B--C--D--B A--B--C--D--B A--B--C--D--B A--B--C--D--B A--B--C--D--B
low: 0 -1 -1 -1 -1 0--1 -1 -1 1 0--1--2 -1 1 0--1--2--3 1 0--1--2--3->1
At this point, you've encountered a cycle/loop in graph. In your code if (pre[w] == -1) will be false this time. So, you'll enter the else part. The if statement there is checking if B is the parent vertex of D. It is not, so D will absorb B's pre value into low. Continuing the example,
dfs: ^
pre: 0--1--2--3
graph: A--B--C--D
low: 0--1--2--1
This low value of D propagates back to C through the code low[v] = Math.min(low[v], low[w]);.
dfs: ^ ^ ^
pre: 0--1--2--3--1 0--1--2--3--1 0--1--2--3--1
graph: A--B--C--D--B A--B--C--D--B A--B--C--D--B
low: 0--1--1--1--1 0--1--1--1--1 0--1--1--1--1
Now, that the cycle/loop is identified, we note that the vertex A is not part of the loop. So, you print out A--B as a bridge. The code low['B'] == pre['B'] means an edge to B will be a bridge. This is because, the lowest vertex we can reach from B is B itself.
Hope this explanation helps.

Not a new answer, but I needed this in Python. Here's a translation of the algorithm for an undirected NetworkX Graph object G:
def bridge_dfs(G,u,v,cnt,low,pre,bridges):
cnt += 1
pre[v] = cnt
low[v] = pre[v]
for w in nx.neighbors(G,v):
if (pre[w] == -1):
bridge_dfs(G,v,w,cnt,low,pre,bridges)
low[v] = min(low[v], low[w])
if (low[w] == pre[w]):
bridges.append((v,w))
elif (w != u):
low[v] = min(low[v], pre[w])
def get_bridges(G):
bridges = []
cnt = 0
low = {n:-1 for n in G.nodes()}
pre = low.copy()
for n in G.nodes():
bridge_dfs(G, n, n, cnt, low, pre, bridges)
return bridges # <- List of (node-node) tuples for all bridges in G
Be careful of Python's recursion depth limiter for large graphs...

Not a new answer, but I needed this for the JVM/Kotlin. Here's a translation that relies upon com.google.common.graph.Graph.
/**
* [T] The type of key held in the [graph].
*/
private class BridgeComputer<T>(private val graph: ImmutableGraph<T>) {
/**
* Counter.
*/
private var count = 0
/**
* `low[v]` = Lowest preorder of any vertex connected to `v`.
*/
private val low: MutableMap<T, Int> =
graph.nodes().map { it to -1 }.toMap(mutableMapOf())
/**
* `pre[v]` = Order in which [depthFirstSearch] examines `v`.
*/
private val pre: MutableMap<T, Int> =
graph.nodes().map { it to -1 }.toMap(mutableMapOf())
private val foundBridges = mutableSetOf<Pair<T, T>>()
init {
graph.nodes().forEach { v ->
// DO NOT PRE-FILTER!
if (pre[v] == -1) {
depthFirstSearch(v, v)
}
}
}
private fun depthFirstSearch(u: T, v: T) {
pre[v] = count++
low[v] = checkNotNull(pre[v]) { "pre[v]" }
graph.adjacentNodes(v).forEach { w ->
if (pre[w] == -1) {
depthFirstSearch(v, w)
low[v] =
Math.min(checkNotNull(low[v]) { "low[v]" }, checkNotNull(low[w]) { "low[w]" })
if (low[w] == pre[w]) {
println("$v - $w is a bridge")
foundBridges += (v to w)
}
} else if (w != u) {
low[v] =
Math.min(checkNotNull(low[v]) { "low[v]" }, checkNotNull(pre[w]) { "pre[w]" })
}
}
}
/**
* Holds the computed bridges.
*/
fun bridges() = ImmutableSet.copyOf(foundBridges)!!
}
Hopefully this makes someone's life easier.

Lets say you are given an edge (c,d) and you have to find if it is a bridge or not
There are several methods to solve this problem but lets concentrate on one.
Starting from c, you have to do a BFS.
If there is an edge c-d then don't visit it.
Keep a track of vertex by making a boolean visited.
In the end, if you found that d is visited, this means, by removing c-d we can still visit d from the source c, hence c-d is not a bridge.
Here is the short implementation of the above :
int isBridge(int V, ArrayList<ArrayList<Integer>> adj,int c,int d)
{
Queue<Integer> q = new LinkedList<>();
boolean visited[] = new boolean[V];
ArrayList<Integer> ls = new ArrayList<>();
q.add(c);
while(!q.isEmpty()) {
Integer v = q.remove();
if(visited[v])
continue;
visited[v] = true;
ls.add(v);
for(Integer e: adj.get(v)) {
if(visited[e] || (c == v && d == e))
continue;
q.add(e);
}
}
if(visited[d] == true)
return 0;
return 1;
}

Related

What type of edge is it, if node is visited already while finding articulation point using Tarjan's algorithm?

The following dfs code can be used to find articulation points in a graph using Tarjan's algorithm:
void dfs(int u, vector<int> adj[], vector<bool>& visited,vector<int>& disc, vector<int>& low, int p)
{
low[u] = disc[u] = time_++;
visited[u] = true;
int children = 0;
for(auto v: adj[u])
{
if(p == v) // case 1
continue;
if(!visited[v]) // case 2
{
dfs(v, adj, visited, disc, low, u);
children++;
low[u] = min(low[u], low[v]);
if (p != -1 && low[v] >= disc[u])
x[u] = true;
}
else // case 3
{
low[u] = min(low[u], disc[v]);
}
}
if (p == -1 && children > 1)
x[u] = true;
}
I know that case 2 implies, u->v is a tree edge. My intuition is that case 1 will imply that u->v is a back edge and case 3 implies that u->v is either a cross edge, or a back edge or forward edge. Am I right with this claim?
Also if case 3 would have been changed to:
else if(inStack[v]) // where inStack is a boolean array which keeps track of current set of nodes in the dfs stack
{
...
}
Is u->v a backedge here?

Dijkstra's algorithm to prevent any vertex on re on fire paths

Let G = (V;E) be a directed graph with two weights we and re on each edge e. There
are two special vertices in this graph: c, the chemical plant, and a, the amphitheater. The
task is to create an evacuation plan from a to v for every vertex w in V in the event that
the chemical plant catches re. The time it takes the re to spread along an edge e is re.
The time to travel along an edge is we.
I have to write an algorithm to find the shortest safe path from a to
v for each v in V : a path is safe if it does not include any vertex on re at the time that vertex is traversed.
My trying:
i. I have first computed the time at which each vertex catches re , assuming the chemical plant catches re at time 0.
ii. Then I have modified Dijkstra's algorithm so that it does not use any vertex on re on the paths
it computes.
My Algorithm:
DijkstraShortestPath:
1. graph[][] --> read the graph
2. c <-- chemical plant
3. a <-- amphitheater
4. for i --> 0 to V
queuePriority.add(i);
distance[i] = Integer.MAX_VALUE;
prev[i] = -1;
visitedVertex[i] = false;
}
5. Q <-- queue(c), level[]--> inifinity
6. while Q is not empty:
U <-- Q.pop()
for all edges from u to v in G.adjacentEdges(c)
If level[v] --> inifinity:
level[v] <-- level[u] + re
Q.enqueu(v)
7. distance[a] = 0;
while (!queuePriority.isEmpty()) {
int u = minDistance();
if (u == -1) {
break;
}
queuePriority.remove(u);
visitedVertex[u] = true;
for (int i = 0; i < nodeNumber; i++) {
if (graph[u][i] != 0 && graph[u][i]%re ==0 && graph[u][i] + distance[u] < distance[i]) {
distance[i] = graph[u][i] + distance[u];
prev[i] = u;
}
}
}
printShortestPath(distination);
return distance[distination];
}
Does my algorithm solve the problem ? If not, how can I modify my algorithm to solve the problem ?

articulation point ignores reverse of edge leading to direct parent

From the algorithm course of Princeton Articulation Point, the articulation algorithm dfs() ignores reverse of edge leading to v. But why cannot we use the backward edge when it points to direct parent? Please refer to this part of code
// **low number - ignore reverse of edge leading to v. Why is this???**
else if (w != u)
low[v] = Math.min(low[v], pre[w]);
The full code is below.
private void dfs(Graph G, int u, int v) {
int children = 0;
pre[v] = cnt++;
low[v] = pre[v];
for (int w : G.adj(v)) {
if (pre[w] == -1) {
children++;
dfs(G, v, w);
// update low number
low[v] = Math.min(low[v], low[w]);
// non-root of DFS is an articulation point if low[w] >= pre[v]
if (low[w] >= pre[v] && u != v)
articulation[v] = true;
}
// **low number - ignore reverse of edge leading to v. Why is this???**
else if (w != u)
low[v] = Math.min(low[v], pre[w]);
}
// root of DFS is an articulation point if it has more than 1 child
if (u == v && children > 1)
articulation[v] = true;
}
By the definition of this algorithm (link-1):
pre[v] is the DFS-preorder index of v, in other words, the depth of that vertex in the DFS tree;
low[v] is the lowest depth of neighbors of all descendants of v (including v itself) in the DFS tree
So during the call DFS(G, u, v) we should not check reverse edge (v->u), because the vertex u is an ancestor of v, so it should not be considered in low[v] calculation.
Further explanation could be found here: link-2

Remove degree two vertices from an undirected graph (Skiena TADM 5-22)

From the Algorithm Design Manual, 2nd edition, question 5-22:
Design a linear-time algorithm to eliminate each vertex v of degree 2 from a graph by replacing edges (u,v) and (v,w) by an edge (u,w). We also seek to eliminate multiple copies of edges by replacing them with a single edge. Note that removing multiple copies of an edge may create a new vertex of degree 2, which has to be removed, and that removing a vertex of degree 2 may create multiple edges, which also must be removed.
Because the question appears in the section on undirected graphs,
assume that our graph is undirected.
Here is an algorithm for removing vertices of degree two as desired, similar to the one given here. The implementation relies on Skiena's graph, queue, and edgenode structs. g->edges[v] is a pointer to the head of v's adjacency list. g->M[u][v] returns the boolean value in row u and column v of g's adjacency matrix.
The problem: according to my analysis, it does not work in linear time, no matter whether we use adjacency lists or adjacency matrices to represent our graph.
process_vertex(graph *g, int v, queue Q) {
int u,w;
if (degree[v] != 2) {return;}
u = pop_first_edge(g,v); // O(n) for AL, O(n) for AM
w = pop_first_edge(g,v); // O(n) for AL, O(n) for AM
if (!edge_exists(g,u,w)) { // O(n) for AL, O(1) for AM
insert_edge(g,u,w);
}
if (degree[u] == 2) {enqueue(Q,u);}
if (degree[w] == 2) {enqueue(Q,w);}
}
remove_degree_twos(graph *g) {
queue Q;
for (int v = 1; v <= g->nvertices; ++v) {
if (degree[v] == 2) {enqueue(Q,v);}
}
while (!Q.empty()) {
process_vertex(g,dequeue(Q),Q);
}
}
There are two required functions that have not yet been implemented:
// removes the first edge in v's adjacency list
// and updates degrees appropriately
// returns the vertex to which that edge points
int pop_first_edge(g,v);
// determines whether edge (u,v) already exists
// in graph g
bool edge_exists(g,u,v);
If g is represented with adjacency lists, then the required functions can be implemented as follows:
// O(n)
int pop_first_edge(g,v) {
int u = -1; // the vertex to which the first outedge from v points
edgenode *p = g->edges[v];
if (p != NULL) {
u = p->y;
g->edges[v] = p->next;
--(g->degree[v]);
// delete v from u's adjacency list
p1 = &g->edges[u];
p2 = g->edges[u];
while (p2 != NULL) {
if (p2->y == v) {
*p1 = p2->next;
--(g->degree[u]);
break;
}
p1 = p2;
p2 = p2->next;
}
}
}
// O(n)
edge_exists(g,u,w) {
edgenode *p = g->edges[u];
while (p != NULL) {
if (p->y == w) {
return true;
}
p = p->next;
}
return false;
}
If g is represented with adjacency matrices, then we have:
// O(n)
int pop_first_edge(v) {
int u = -1;
for (int j = 1; j <= g->nvertices; ++j) {
if (M[v][j]) {
u = j;
break;
}
}
if (u > 0) {
M[v][u] = false;
M[u][v] = false;
--(g->degree[v]);
--(g->degree[u]);
return u;
} else {
return -1;
}
}
// O(1)
edge_exists(g,u,w) {
return g->M[u][w];
}
No matter whether we use adjacency lists or adjacency matrices, the runtime of process_vertex is O(n), where n is the number of vertices in the graph. Because O(n) vertices may be processed, the total runtime is O(n^2).
How can this be done in linear time?
Assume we have graph G=(V,E), where
V={1,...,n}
is set of vertices and E is set of edges, therefore subset of set
{(x,y) : x,y in V}
Usually the graph is given by the list of edges. Let's assume we receive it this way. Now:
make the set of edges distinct (O(m), m = |E|), consider the edges (x,y) and (y,x) equal
create an array representing the degree of each vertex in G (O(m) again)
for each vertex v of degree 2 make a list of those 2 edges (this is O(m), one iteration over the edges is sufficient)
at last iterate over the vertices of degree 2 and replace the related edges with one edge getting rid of vertex of degree 2 (this is O(n) operation)
repeat step 1. and return edges
Here's code written in python:
def remove_2_degree_vertices(n, edges):
adj_matrix = [[0]*n for i in xrange(n)]
#1 O(m)
edges = get_distinct(adj_matrix, edges)
#2 O(m)
degrees = calculate_degrees(n, edges)
#3 O(m)
adj_lists = get_neighbours(degrees, edges)
#4 O(n + m)
to_remove, to_add_candidates = process(n, adj_lists)
edges.extend(to_add_candidates)
result = [(e0,e1) for e0, e1 in edges if to_remove[e0][e1] == 0]
#5 O(m)
adj_matrix = [[0]*n for i in xrange(n)]
result = get_distinct(adj_matrix, result)
return result
def get_distinct(adj_matrix, edges):
result = []
for e0, e1 in edges:
if adj_matrix[e0][e1] == 0:
result.append((e0,e1))
adj_matrix[e0][e1] = adj_matrix[e1][e0] = 1
return result
def calculate_degrees(n, edges):
result = [0]*n
for e0, e1 in edges:
result[e0] += 1
result[e1] += 1
return result
def get_neighbours(degrees, edges):
result = {}
for e0, e1 in edges:
if degrees[e0] == 2:
add_neighbour(result, e0, e1)
if degrees[e1] == 2:
add_neighbour(result, e1, e0)
return result
def add_neighbour(neighbours, key, value):
if not neighbours.has_key(key):
neighbours[key] = set()
neighbours[key].add(value)
else:
neighbours[key].add(value)
def process(n, adj_lists):
to_remove = [[0 for i in xrange(n)] for j in xrange(n)]
to_add_candidates = []
if len(adj_lists) == 0:
return to_remove, to_add_candidates
for key in adj_lists:
neighbours = list(adj_lists[key])
if len(neighbours) == 1:
to_remove[key][neighbours[0]] = to_remove[neighbours[0]][key] = 1
else: # len(neighbours) == 2
remove_edge(adj_lists, to_remove, key, neighbours[0], neighbours[1])
remove_edge(adj_lists, to_remove, key, neighbours[1], neighbours[0])
to_add_candidates.append((neighbours[0], neighbours[1]))
return to_remove, to_add_candidates
def remove_edge(adj_lists, to_remove, key, n0, n1):
to_remove[key][n0] = to_remove[n0][key] = 1
if n0 in adj_lists:
adj_lists[n0].remove(key)
adj_lists[n0].add(n1)

How can I find bridges in an undirected graph? [duplicate]

This question already has answers here:
Bridges in a connected graph
(4 answers)
Closed 7 years ago.
Given an undirected Graph, how can I find all the bridges? I've only found Tarjan's algorithm which seems rather complicated.
It seems there should be multiple linear time solutions, but I can't find anything.
Tarjan's algorithm was the first bridge finding algorithm in an undirected graph that ran in linear time. However a simpler algorithm exists and you can have a look at its implementation here.
private int bridges; // number of bridges
private int cnt; // counter
private int[] pre; // pre[v] = order in which dfs examines v
private int[] low; // low[v] = lowest preorder of any vertex connected to v
public Bridge(Graph G) {
low = new int[G.V()];
pre = new int[G.V()];
for (int v = 0; v < G.V(); v++) low[v] = -1;
for (int v = 0; v < G.V(); v++) pre[v] = -1;
for (int v = 0; v < G.V(); v++)
if (pre[v] == -1)
dfs(G, v, v);
}
public int components() { return bridges + 1; }
private void dfs(Graph G, int u, int v) {
pre[v] = cnt++;
low[v] = pre[v];
for (int w : G.adj(v)) {
if (pre[w] == -1) {
dfs(G, v, w);
low[v] = Math.min(low[v], low[w]);
if (low[w] == pre[w]) {
StdOut.println(v + "-" + w + " is a bridge");
bridges++;
}
}
// update low number - ignore reverse of edge leading to v
else if (w != u)
low[v] = Math.min(low[v], pre[w]);
}
}
The algorithm does the job by maintaining 2 arrays pre and low. pre holds the pre-order traversal numbering for the nodes. So pre[0] = 2 means that vertex 0 was discovered in the 3rd dfs call. And low[u] holds the smallest pre-order number of any vertex that is reachable from u.
The algorithm detects a bridge whenever for an edge u--v, where u comes first in the preorder numbering, low[v]==pre[v]. This is because if we remove the edge between u--v, v can't reach any vertex that comes before u. Hence removing the edge would split the graph into 2 separate graphs.
For a more elaborate explanation you can also have a look at this answer .

Resources