Skip to content

Commit a61bac3

Browse files
authored
Merge branch 'main' into ford_fulkerson
2 parents 786885f + f7a6296 commit a61bac3

File tree

19 files changed

+1027
-37
lines changed

19 files changed

+1027
-37
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ Pratik Goyal <pratikgoyal2712@gmail.com>
1111
Jay Thorat <j.thorat10@gmail.com>
1212
Rajveer Singh Bharadwaj <rsb3256@gmail.com>
1313
Kishan Ved <kishanved123456@gmail.com>
14+
Arvinder Singh Dhoul <asdhoul004@gmail.com>

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

docs/source/pydatastructs/linear_data_structures/algorithms.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,8 @@ Algorithms
4545

4646
.. autofunction:: pydatastructs.jump_search
4747

48-
.. autofunction:: pydatastructs.intro_sort
48+
.. autofunction:: pydatastructs.intro_sort
49+
50+
.. autofunction:: pydatastructs.shell_sort
51+
52+
.. autofunction:: pydatastructs.radix_sort

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(
@@ -1260,3 +1309,106 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
12601309
f"Currently {algorithm} algorithm isn't implemented for "
12611310
"performing max flow on graphs.")
12621311
return getattr(algorithms, func)(graph, source, sink)
1312+
1313+
1314+
def find_bridges(graph):
1315+
"""
1316+
Finds all bridges in an undirected graph using Tarjan's Algorithm.
1317+
1318+
Parameters
1319+
==========
1320+
graph : Graph
1321+
An undirected graph instance.
1322+
1323+
Returns
1324+
==========
1325+
List[tuple]
1326+
A list of bridges, where each bridge is represented as a tuple (u, v)
1327+
with u <= v.
1328+
1329+
Example
1330+
========
1331+
>>> from pydatastructs import Graph, AdjacencyListGraphNode, find_bridges
1332+
>>> v0 = AdjacencyListGraphNode(0)
1333+
>>> v1 = AdjacencyListGraphNode(1)
1334+
>>> v2 = AdjacencyListGraphNode(2)
1335+
>>> v3 = AdjacencyListGraphNode(3)
1336+
>>> v4 = AdjacencyListGraphNode(4)
1337+
>>> graph = Graph(v0, v1, v2, v3, v4, implementation='adjacency_list')
1338+
>>> graph.add_edge(v0.name, v1.name)
1339+
>>> graph.add_edge(v1.name, v2.name)
1340+
>>> graph.add_edge(v2.name, v3.name)
1341+
>>> graph.add_edge(v3.name, v4.name)
1342+
>>> find_bridges(graph)
1343+
[('0', '1'), ('1', '2'), ('2', '3'), ('3', '4')]
1344+
1345+
References
1346+
==========
1347+
1348+
.. [1] https://en.wikipedia.org/wiki/Bridge_(graph_theory)
1349+
"""
1350+
1351+
vertices = list(graph.vertices)
1352+
processed_vertices = []
1353+
for v in vertices:
1354+
if hasattr(v, "name"):
1355+
processed_vertices.append(v.name)
1356+
else:
1357+
processed_vertices.append(v)
1358+
1359+
n = len(processed_vertices)
1360+
adj = {v: [] for v in processed_vertices}
1361+
for v in processed_vertices:
1362+
for neighbor in graph.neighbors(v):
1363+
if hasattr(neighbor, "name"):
1364+
nbr = neighbor.name
1365+
else:
1366+
nbr = neighbor
1367+
adj[v].append(nbr)
1368+
1369+
mapping = {v: idx for idx, v in enumerate(processed_vertices)}
1370+
inv_mapping = {idx: v for v, idx in mapping.items()}
1371+
1372+
n_adj = [[] for _ in range(n)]
1373+
for v in processed_vertices:
1374+
idx_v = mapping[v]
1375+
for u in adj[v]:
1376+
idx_u = mapping[u]
1377+
n_adj[idx_v].append(idx_u)
1378+
1379+
visited = [False] * n
1380+
disc = [0] * n
1381+
low = [0] * n
1382+
parent = [-1] * n
1383+
bridges_idx = []
1384+
time = 0
1385+
1386+
def dfs(u):
1387+
nonlocal time
1388+
visited[u] = True
1389+
disc[u] = low[u] = time
1390+
time += 1
1391+
for v in n_adj[u]:
1392+
if not visited[v]:
1393+
parent[v] = u
1394+
dfs(v)
1395+
low[u] = min(low[u], low[v])
1396+
if low[v] > disc[u]:
1397+
bridges_idx.append((u, v))
1398+
elif v != parent[u]:
1399+
low[u] = min(low[u], disc[v])
1400+
1401+
for i in range(n):
1402+
if not visited[i]:
1403+
dfs(i)
1404+
1405+
bridges = []
1406+
for u, v in bridges_idx:
1407+
a = inv_mapping[u]
1408+
b = inv_mapping[v]
1409+
if a <= b:
1410+
bridges.append((a, b))
1411+
else:
1412+
bridges.append((b, a))
1413+
bridges.sort()
1414+
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

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

pydatastructs/linear_data_structures/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
jump_search,
4848
selection_sort,
4949
insertion_sort,
50-
intro_sort
50+
intro_sort,
51+
shell_sort,
52+
radix_sort
5153
)
5254
__all__.extend(algorithms.__all__)

0 commit comments

Comments
 (0)