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

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 .

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?

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

Shortest path to visit all nodes

I'm looking for an algorithm that seems very typical to me, but it seems that the common solutions are all just a little bit different.
In an undirected graph, I want the shortest path that visits every node. Nodes can be revisited and I do not have to return to the start node.
The Travelling Salesman Problem seems to add the restriction that each node can only be visited once and that the path has to return to where it started.
Minimal Spanning Trees may be part of a solution, but such algorithms only provide the tree, not a minimal path. Additionally, because they're trees and therefore have no loops, they force backtracking where a loop may be more efficient.
You can reduce it to the normal Travelling Salesman Problem by transforming the graph.
First, compute the minimum distance for every pair of nodes. You can use Floyd-Warshall algorithm for that. Once you have it, just construct the complete graph where the edge between nodes u and v is the minimum cost from u to v.
Then, you can apply a normal TSP algorithm as you don't have to revisit nodes anymore, that's already hidden in the costs of the edges.
We can use a modified BFS.
Basically from any node during a BFS we need to be able to traverse nodes already travelled but how do we make sure we're not forming infinite cycles.
We store visited state for "ALL" nodes from each node, what this means is if we've walked over node 1 and we need to traverse back over it we can as long as our total state of "ALL" nodes has not been seen before. This is the reason for the bitmask and not a simple Set of Integers. Note you can use a Set of Strings to store the state as well it just runs a slower.
public int shortestPathInSmallGraph(int[][] graph) {
if (graph.length == 1) {
return 0;
}
Set<Integer>[] adj = new HashSet[graph.length];
int n = graph.length;
int endState = (1 << n) - 1;
boolean[][] seen = new boolean[n][endState];
Queue<int[]> queue = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
queue.add(new int[] {i, 1 << i});
seen[i][1 << i] = true;
}
int steps = 0;
while (!queue.isEmpty()) {
int count = queue.size();
for (int i = 0; i < count; i++) {
int[] pair = queue.poll();
int node = pair[0];
int state = pair[1];
for (int neighbor : graph[node]) {
int nextState = state | (1 << neighbor);
if (nextState == endState) {
return 1 + steps;
}
if (!seen[neighbor][nextState]) {
seen[neighbor][nextState] = true;
queue.add(new int[] {neighbor, nextState});
}
}
}
steps++;
}
return -1;
}

Time Complexity O(V^3) or O(V^2)?

I'm new for analyzing the algorithms and the time for them.. This algorithm is posted in http://geeksforgeeks.com and they wrote that the time complexity of the algorithm is O(V^2) which i think that it's O(V^3):
int minDistance(int dist[], bool sptSet[])
{
// Initialize min value
int min = INT_MAX, min_index;
for (int v = 0; v < V; v++)
if (sptSet[v] == false && dist[v] <= min)
min = dist[v], min_index = v;
return min_index;
}
// A utility function to print the constructed distance array
int printSolution(int dist[], int n)
{
printf("Vertex Distance from Source\n");
for (int i = 0; i < V; i++)
printf("%d \t\t %d\n", i, dist[i]);
}
// Funtion that implements Dijkstra's single source shortest path algorithm
// for a graph represented using adjacency matrix representation
void dijkstra(int graph[V][V], int src)
{
int dist[V]; // The output array. dist[i] will hold the shortest
// distance from src to i
bool sptSet[V]; // sptSet[i] will true if vertex i is included in shortest
// path tree or shortest distance from src to i is finalized
// Initialize all distances as INFINITE and stpSet[] as false
for (int i = 0; i < V; i++)
dist[i] = INT_MAX, sptSet[i] = false;
// Distance of source vertex from itself is always 0
dist[src] = 0;
// Find shortest path for all vertices
for (int count = 0; count < V-1; count++)
{
// Pick the minimum distance vertex from the set of vertices not
// yet processed. u is always equal to src in first iteration.
int u = minDistance(dist, sptSet);
// Mark the picked vertex as processed
sptSet[u] = true;
// Update dist value of the adjacent vertices of the picked vertex.
for (int v = 0; v < V; v++)
// Update dist[v] only if is not in sptSet, there is an edge from
// u to v, and total weight of path from src to v through u is
// smaller than current value of dist[v]
if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX
&& dist[u]+graph[u][v] < dist[v])
dist[v] = dist[u] + graph[u][v];
}
// print the constructed distance array
printSolution(dist, V);
}
Where the graph represented inside graph[][] (matrix representation).
Thanks in advance
The solution is indeed O(V^2):
for (int i = 0; i < V; i++)
dist[i] = INT_MAX, sptSet[i] = false;
This part runs BEFORE the main loop, and in complexity of O(V) -.
for (int count = 0; count < V-1; count++)
{
This is the main loop, it runs O(V) times overall, and each time it requires:
int u = minDistance(dist, sptSet);
This runs one time per each different value of count, and its complexity is O(V), so we have O(V^2)` by now.
sptSet[u] = true;
This is O(1), and runs O(V) times.
for (int v = 0; v < V; v++)
This loop runs O(V) times, for each value of count, let's examine what happens each time you run it:
if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX
&& dist[u]+graph[u][v] < dist[v])
dist[v] = dist[u] + graph[u][v];
All of those are O(1), and done per each (count,v) pair, and there are O(V^2) of those pairs.
So, totally O(V^2).
Note that for more efficient graph representation, we can run Dijkstra's algorithm in O(E + VlogV), which might be better in case of sparse graphs.

Bridges in a connected graph

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;
}

Resources