Skip to content

Commit ad8daeb

Browse files
committed
Merge branch 'main' into feature/Fusion-Tree
2 parents a4f2d53 + eb5fab1 commit ad8daeb

File tree

4 files changed

+215
-4
lines changed

4 files changed

+215
-4
lines changed

docs/source/pydatastructs/graphs/algorithms.rst

Lines changed: 2 additions & 0 deletions
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 153 additions & 1 deletion
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
@@ -530,6 +531,52 @@ def _strongly_connected_components_kosaraju_adjacency_list(graph):
530531
_strongly_connected_components_kosaraju_adjacency_matrix = \
531532
_strongly_connected_components_kosaraju_adjacency_list
532533

534+
def _tarjan_dfs(u, graph, index, stack, indices, low_links, on_stacks, components):
535+
indices[u] = index[0]
536+
low_links[u] = index[0]
537+
index[0] += 1
538+
stack.append(u)
539+
on_stacks[u] = True
540+
541+
for node in graph.neighbors(u):
542+
v = node.name
543+
if indices[v] == -1:
544+
_tarjan_dfs(v, graph, index, stack, indices, low_links, on_stacks, components)
545+
low_links[u] = min(low_links[u], low_links[v])
546+
elif on_stacks[v]:
547+
low_links[u] = min(low_links[u], low_links[v])
548+
549+
if low_links[u] == indices[u]:
550+
component = set()
551+
while stack:
552+
w = stack.pop()
553+
on_stacks[w] = False
554+
component.add(w)
555+
if w == u:
556+
break
557+
components.append(component)
558+
559+
def _strongly_connected_components_tarjan_adjacency_list(graph):
560+
index = [0] # mutable object
561+
stack = Stack([])
562+
indices, low_links, on_stacks = {}, {}, {}
563+
564+
for u in graph.vertices:
565+
indices[u] = -1
566+
low_links[u] = -1
567+
on_stacks[u] = False
568+
569+
components = []
570+
571+
for u in graph.vertices:
572+
if indices[u] == -1:
573+
_tarjan_dfs(u, graph, index, stack, indices, low_links, on_stacks, components)
574+
575+
return components
576+
577+
_strongly_connected_components_tarjan_adjacency_matrix = \
578+
_strongly_connected_components_tarjan_adjacency_list
579+
533580
def strongly_connected_components(graph, algorithm, **kwargs):
534581
"""
535582
Computes strongly connected components for the given
@@ -548,6 +595,7 @@ def strongly_connected_components(graph, algorithm, **kwargs):
548595
supported,
549596
550597
'kosaraju' -> Kosaraju's algorithm as given in [1].
598+
'tarjan' -> Tarjan's algorithm as given in [2].
551599
backend: pydatastructs.Backend
552600
The backend to be used.
553601
Optional, by default, the best available
@@ -577,6 +625,7 @@ def strongly_connected_components(graph, algorithm, **kwargs):
577625
==========
578626
579627
.. [1] https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
628+
.. [2] https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
580629
581630
"""
582631
raise_if_backend_is_not_python(
@@ -1216,3 +1265,106 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
12161265
f"Currently {algorithm} algorithm isn't implemented for "
12171266
"performing max flow on graphs.")
12181267
return getattr(algorithms, func)(graph, source, sink)
1268+
1269+
1270+
def find_bridges(graph):
1271+
"""
1272+
Finds all bridges in an undirected graph using Tarjan's Algorithm.
1273+
1274+
Parameters
1275+
==========
1276+
graph : Graph
1277+
An undirected graph instance.
1278+
1279+
Returns
1280+
==========
1281+
List[tuple]
1282+
A list of bridges, where each bridge is represented as a tuple (u, v)
1283+
with u <= v.
1284+
1285+
Example
1286+
========
1287+
>>> from pydatastructs import Graph, AdjacencyListGraphNode, find_bridges
1288+
>>> v0 = AdjacencyListGraphNode(0)
1289+
>>> v1 = AdjacencyListGraphNode(1)
1290+
>>> v2 = AdjacencyListGraphNode(2)
1291+
>>> v3 = AdjacencyListGraphNode(3)
1292+
>>> v4 = AdjacencyListGraphNode(4)
1293+
>>> graph = Graph(v0, v1, v2, v3, v4, implementation='adjacency_list')
1294+
>>> graph.add_edge(v0.name, v1.name)
1295+
>>> graph.add_edge(v1.name, v2.name)
1296+
>>> graph.add_edge(v2.name, v3.name)
1297+
>>> graph.add_edge(v3.name, v4.name)
1298+
>>> find_bridges(graph)
1299+
[('0', '1'), ('1', '2'), ('2', '3'), ('3', '4')]
1300+
1301+
References
1302+
==========
1303+
1304+
.. [1] https://en.wikipedia.org/wiki/Bridge_(graph_theory)
1305+
"""
1306+
1307+
vertices = list(graph.vertices)
1308+
processed_vertices = []
1309+
for v in vertices:
1310+
if hasattr(v, "name"):
1311+
processed_vertices.append(v.name)
1312+
else:
1313+
processed_vertices.append(v)
1314+
1315+
n = len(processed_vertices)
1316+
adj = {v: [] for v in processed_vertices}
1317+
for v in processed_vertices:
1318+
for neighbor in graph.neighbors(v):
1319+
if hasattr(neighbor, "name"):
1320+
nbr = neighbor.name
1321+
else:
1322+
nbr = neighbor
1323+
adj[v].append(nbr)
1324+
1325+
mapping = {v: idx for idx, v in enumerate(processed_vertices)}
1326+
inv_mapping = {idx: v for v, idx in mapping.items()}
1327+
1328+
n_adj = [[] for _ in range(n)]
1329+
for v in processed_vertices:
1330+
idx_v = mapping[v]
1331+
for u in adj[v]:
1332+
idx_u = mapping[u]
1333+
n_adj[idx_v].append(idx_u)
1334+
1335+
visited = [False] * n
1336+
disc = [0] * n
1337+
low = [0] * n
1338+
parent = [-1] * n
1339+
bridges_idx = []
1340+
time = 0
1341+
1342+
def dfs(u):
1343+
nonlocal time
1344+
visited[u] = True
1345+
disc[u] = low[u] = time
1346+
time += 1
1347+
for v in n_adj[u]:
1348+
if not visited[v]:
1349+
parent[v] = u
1350+
dfs(v)
1351+
low[u] = min(low[u], low[v])
1352+
if low[v] > disc[u]:
1353+
bridges_idx.append((u, v))
1354+
elif v != parent[u]:
1355+
low[u] = min(low[u], disc[v])
1356+
1357+
for i in range(n):
1358+
if not visited[i]:
1359+
dfs(i)
1360+
1361+
bridges = []
1362+
for u, v in bridges_idx:
1363+
a = inv_mapping[u]
1364+
b = inv_mapping[v]
1365+
if a <= b:
1366+
bridges.append((a, b))
1367+
else:
1368+
bridges.append((b, a))
1369+
bridges.sort()
1370+
return bridges

pydatastructs/graphs/tests/test_algorithms.py

Lines changed: 58 additions & 2 deletions
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():
@@ -188,11 +188,13 @@ def _test_strongly_connected_components(func, ds, algorithm, *args):
188188
graph.add_edge(h.name, g.name)
189189
comps = func(graph, algorithm)
190190
expected_comps = [{'e', 'a', 'b'}, {'d', 'c', 'h'}, {'g', 'f'}]
191-
assert comps == expected_comps
191+
assert comps.sort() == expected_comps.sort()
192192

193193
scc = strongly_connected_components
194194
_test_strongly_connected_components(scc, "List", "kosaraju")
195195
_test_strongly_connected_components(scc, "Matrix", "kosaraju")
196+
_test_strongly_connected_components(scc, "List", "tarjan")
197+
_test_strongly_connected_components(scc, "Matrix", "tarjan")
196198

197199
def test_depth_first_search():
198200

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

0 commit comments

Comments
 (0)