Skip to content

Commit 513ba92

Browse files
Added bridge-finding algorithm (#643)
* Added bridge-finding algorithm Signed-off-by: Hargun Kaur <[email protected]> * Added bridge-finding algorithm Signed-off-by: Hargun Kaur <[email protected]> --------- Signed-off-by: Hargun Kaur <[email protected]>
1 parent f34b7d6 commit 513ba92

File tree

4 files changed

+164
-3
lines changed

4 files changed

+164
-3
lines changed

docs/source/pydatastructs/graphs/algorithms.rst

+2
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ Algorithms
2020
.. autofunction:: pydatastructs.topological_sort
2121

2222
.. autofunction:: pydatastructs.topological_sort_parallel
23+
24+
.. autofunction:: pydatastructs.find_bridges

pydatastructs/graphs/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
all_pair_shortest_paths,
2222
topological_sort,
2323
topological_sort_parallel,
24-
max_flow
24+
max_flow,
25+
find_bridges
2526
)
2627

2728
__all__.extend(algorithms.__all__)

pydatastructs/graphs/algorithms.py

+105-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
'all_pair_shortest_paths',
2424
'topological_sort',
2525
'topological_sort_parallel',
26-
'max_flow'
26+
'max_flow',
27+
'find_bridges'
2728
]
2829

2930
Stack = Queue = deque
@@ -1216,3 +1217,106 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
12161217
f"Currently {algorithm} algorithm isn't implemented for "
12171218
"performing max flow on graphs.")
12181219
return getattr(algorithms, func)(graph, source, sink)
1220+
1221+
1222+
def find_bridges(graph):
1223+
"""
1224+
Finds all bridges in an undirected graph using Tarjan's Algorithm.
1225+
1226+
Parameters
1227+
==========
1228+
graph : Graph
1229+
An undirected graph instance.
1230+
1231+
Returns
1232+
==========
1233+
List[tuple]
1234+
A list of bridges, where each bridge is represented as a tuple (u, v)
1235+
with u <= v.
1236+
1237+
Example
1238+
========
1239+
>>> from pydatastructs import Graph, AdjacencyListGraphNode, find_bridges
1240+
>>> v0 = AdjacencyListGraphNode(0)
1241+
>>> v1 = AdjacencyListGraphNode(1)
1242+
>>> v2 = AdjacencyListGraphNode(2)
1243+
>>> v3 = AdjacencyListGraphNode(3)
1244+
>>> v4 = AdjacencyListGraphNode(4)
1245+
>>> graph = Graph(v0, v1, v2, v3, v4, implementation='adjacency_list')
1246+
>>> graph.add_edge(v0.name, v1.name)
1247+
>>> graph.add_edge(v1.name, v2.name)
1248+
>>> graph.add_edge(v2.name, v3.name)
1249+
>>> graph.add_edge(v3.name, v4.name)
1250+
>>> find_bridges(graph)
1251+
[('0', '1'), ('1', '2'), ('2', '3'), ('3', '4')]
1252+
1253+
References
1254+
==========
1255+
1256+
.. [1] https://en.wikipedia.org/wiki/Bridge_(graph_theory)
1257+
"""
1258+
1259+
vertices = list(graph.vertices)
1260+
processed_vertices = []
1261+
for v in vertices:
1262+
if hasattr(v, "name"):
1263+
processed_vertices.append(v.name)
1264+
else:
1265+
processed_vertices.append(v)
1266+
1267+
n = len(processed_vertices)
1268+
adj = {v: [] for v in processed_vertices}
1269+
for v in processed_vertices:
1270+
for neighbor in graph.neighbors(v):
1271+
if hasattr(neighbor, "name"):
1272+
nbr = neighbor.name
1273+
else:
1274+
nbr = neighbor
1275+
adj[v].append(nbr)
1276+
1277+
mapping = {v: idx for idx, v in enumerate(processed_vertices)}
1278+
inv_mapping = {idx: v for v, idx in mapping.items()}
1279+
1280+
n_adj = [[] for _ in range(n)]
1281+
for v in processed_vertices:
1282+
idx_v = mapping[v]
1283+
for u in adj[v]:
1284+
idx_u = mapping[u]
1285+
n_adj[idx_v].append(idx_u)
1286+
1287+
visited = [False] * n
1288+
disc = [0] * n
1289+
low = [0] * n
1290+
parent = [-1] * n
1291+
bridges_idx = []
1292+
time = 0
1293+
1294+
def dfs(u):
1295+
nonlocal time
1296+
visited[u] = True
1297+
disc[u] = low[u] = time
1298+
time += 1
1299+
for v in n_adj[u]:
1300+
if not visited[v]:
1301+
parent[v] = u
1302+
dfs(v)
1303+
low[u] = min(low[u], low[v])
1304+
if low[v] > disc[u]:
1305+
bridges_idx.append((u, v))
1306+
elif v != parent[u]:
1307+
low[u] = min(low[u], disc[v])
1308+
1309+
for i in range(n):
1310+
if not visited[i]:
1311+
dfs(i)
1312+
1313+
bridges = []
1314+
for u, v in bridges_idx:
1315+
a = inv_mapping[u]
1316+
b = inv_mapping[v]
1317+
if a <= b:
1318+
bridges.append((a, b))
1319+
else:
1320+
bridges.append((b, a))
1321+
bridges.sort()
1322+
return bridges

pydatastructs/graphs/tests/test_algorithms.py

+55-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
breadth_first_search_parallel, minimum_spanning_tree,
33
minimum_spanning_tree_parallel, strongly_connected_components,
44
depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort,
5-
topological_sort_parallel, max_flow)
5+
topological_sort_parallel, max_flow, find_bridges)
66
from pydatastructs.utils.raises_util import raises
77

88
def test_breadth_first_search():
@@ -448,3 +448,57 @@ def _test_max_flow(ds, algorithm):
448448
_test_max_flow("Matrix", "edmonds_karp")
449449
_test_max_flow("List", "dinic")
450450
_test_max_flow("Matrix", "dinic")
451+
452+
453+
def test_find_bridges():
454+
def _test_find_bridges(ds):
455+
import pydatastructs.utils.misc_util as utils
456+
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
457+
458+
impl = 'adjacency_list' if ds == "List" else 'adjacency_matrix'
459+
460+
v0 = GraphNode(0)
461+
v1 = GraphNode(1)
462+
v2 = GraphNode(2)
463+
v3 = GraphNode(3)
464+
v4 = GraphNode(4)
465+
466+
G1 = Graph(v0, v1, v2, v3, v4, implementation=impl)
467+
G1.add_edge(v0.name, v1.name)
468+
G1.add_edge(v1.name, v2.name)
469+
G1.add_edge(v2.name, v3.name)
470+
G1.add_edge(v3.name, v4.name)
471+
472+
bridges = find_bridges(G1)
473+
expected_bridges = [('0', '1'), ('1', '2'), ('2', '3'), ('3', '4')]
474+
assert sorted(bridges) == sorted(expected_bridges)
475+
476+
u0 = GraphNode(0)
477+
u1 = GraphNode(1)
478+
u2 = GraphNode(2)
479+
480+
G2 = Graph(u0, u1, u2, implementation=impl)
481+
G2.add_edge(u0.name, u1.name)
482+
G2.add_edge(u1.name, u2.name)
483+
G2.add_edge(u2.name, u0.name)
484+
485+
bridges = find_bridges(G2)
486+
assert bridges == []
487+
488+
w0 = GraphNode(0)
489+
w1 = GraphNode(1)
490+
w2 = GraphNode(2)
491+
w3 = GraphNode(3)
492+
w4 = GraphNode(4)
493+
494+
G3 = Graph(w0, w1, w2, w3, w4, implementation=impl)
495+
G3.add_edge(w0.name, w1.name)
496+
G3.add_edge(w1.name, w2.name)
497+
G3.add_edge(w3.name, w4.name)
498+
499+
bridges = find_bridges(G3)
500+
expected_bridges = [('0', '1'), ('1', '2'), ('3', '4')]
501+
assert sorted(bridges) == sorted(expected_bridges)
502+
503+
_test_find_bridges("List")
504+
_test_find_bridges("Matrix")

0 commit comments

Comments
 (0)