Skip to content

Commit 3fd898a

Browse files
authored
Merge branch 'main' into matching-algorithm
2 parents 89b43ac + eb5fab1 commit 3fd898a

File tree

4 files changed

+212
-6
lines changed

4 files changed

+212
-6
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
@@ -24,7 +24,8 @@
2424
max_flow,
2525
maximum_matching,
2626
maximum_matching_parallel,
27-
bipartite_coloring
27+
bipartite_coloring,
28+
find_bridges
2829
)
2930

3031
__all__.extend(algorithms.__all__)

pydatastructs/graphs/algorithms.py

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
'max_flow',
3030
'maximum_matching',
3131
'maximum_matching_parallel',
32-
'bipartite_coloring'
32+
'bipartite_coloring',
33+
'find_bridges'
3334
]
3435

3536
Stack = Queue = deque
@@ -536,6 +537,52 @@ def _strongly_connected_components_kosaraju_adjacency_list(graph):
536537
_strongly_connected_components_kosaraju_adjacency_matrix = \
537538
_strongly_connected_components_kosaraju_adjacency_list
538539

540+
def _tarjan_dfs(u, graph, index, stack, indices, low_links, on_stacks, components):
541+
indices[u] = index[0]
542+
low_links[u] = index[0]
543+
index[0] += 1
544+
stack.append(u)
545+
on_stacks[u] = True
546+
547+
for node in graph.neighbors(u):
548+
v = node.name
549+
if indices[v] == -1:
550+
_tarjan_dfs(v, graph, index, stack, indices, low_links, on_stacks, components)
551+
low_links[u] = min(low_links[u], low_links[v])
552+
elif on_stacks[v]:
553+
low_links[u] = min(low_links[u], low_links[v])
554+
555+
if low_links[u] == indices[u]:
556+
component = set()
557+
while stack:
558+
w = stack.pop()
559+
on_stacks[w] = False
560+
component.add(w)
561+
if w == u:
562+
break
563+
components.append(component)
564+
565+
def _strongly_connected_components_tarjan_adjacency_list(graph):
566+
index = [0] # mutable object
567+
stack = Stack([])
568+
indices, low_links, on_stacks = {}, {}, {}
569+
570+
for u in graph.vertices:
571+
indices[u] = -1
572+
low_links[u] = -1
573+
on_stacks[u] = False
574+
575+
components = []
576+
577+
for u in graph.vertices:
578+
if indices[u] == -1:
579+
_tarjan_dfs(u, graph, index, stack, indices, low_links, on_stacks, components)
580+
581+
return components
582+
583+
_strongly_connected_components_tarjan_adjacency_matrix = \
584+
_strongly_connected_components_tarjan_adjacency_list
585+
539586
def strongly_connected_components(graph, algorithm, **kwargs):
540587
"""
541588
Computes strongly connected components for the given
@@ -554,6 +601,7 @@ def strongly_connected_components(graph, algorithm, **kwargs):
554601
supported,
555602
556603
'kosaraju' -> Kosaraju's algorithm as given in [1].
604+
'tarjan' -> Tarjan's algorithm as given in [2].
557605
backend: pydatastructs.Backend
558606
The backend to be used.
559607
Optional, by default, the best available
@@ -583,6 +631,7 @@ def strongly_connected_components(graph, algorithm, **kwargs):
583631
==========
584632
585633
.. [1] https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
634+
.. [2] https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
586635
587636
"""
588637
raise_if_backend_is_not_python(
@@ -1223,6 +1272,7 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
12231272
"performing max flow on graphs.")
12241273
return getattr(algorithms, func)(graph, source, sink)
12251274

1275+
12261276
def bipartite_coloring(graph: Graph, **kwargs) -> Tuple[bool, Dict]:
12271277
"""
12281278
Finds a 2-coloring of the given graph if it is bipartite.
@@ -1265,6 +1315,7 @@ def bipartite_coloring(graph: Graph, **kwargs) -> Tuple[bool, Dict]:
12651315
>>> bipartite_coloring(graph, make_undirected=True)
12661316
(True, {'v_1': 0, 'v_2': 1, 'v_4': 1, 'v_3': 0})
12671317
1318+
12681319
References
12691320
==========
12701321
@@ -1615,3 +1666,101 @@ def maximum_matching_parallel(graph: Graph, algorithm: str, num_threads: int, **
16151666
f"Currently {algorithm} algorithm isn't implemented for "
16161667
"finding maximum matching in graphs.")
16171668
return getattr(algorithms, func)(graph, num_threads)
1669+
1670+
def find_bridges(graph):
1671+
"""
1672+
Finds all bridges in an undirected graph using Tarjan's Algorithm.
1673+
1674+
Parameters
1675+
==========
1676+
graph : Graph
1677+
An undirected graph instance.
1678+
1679+
Returns
1680+
==========
1681+
List[tuple]
1682+
A list of bridges, where each bridge is represented as a tuple (u, v)
1683+
with u <= v.
1684+
1685+
Example
1686+
========
1687+
>>> from pydatastructs import Graph, AdjacencyListGraphNode, find_bridges
1688+
>>> v0 = AdjacencyListGraphNode(0)
1689+
>>> v1 = AdjacencyListGraphNode(1)
1690+
>>> v2 = AdjacencyListGraphNode(2)
1691+
>>> v3 = AdjacencyListGraphNode(3)
1692+
>>> v4 = AdjacencyListGraphNode(4)
1693+
>>> graph = Graph(v0, v1, v2, v3, v4, implementation='adjacency_list')
1694+
>>> graph.add_edge(v0.name, v1.name)
1695+
>>> graph.add_edge(v1.name, v2.name)
1696+
>>> graph.add_edge(v2.name, v3.name)
1697+
>>> graph.add_edge(v3.name, v4.name)
1698+
>>> find_bridges(graph)
1699+
[('0', '1'), ('1', '2'), ('2', '3'), ('3', '4')]
1700+
.. [1] https://en.wikipedia.org/wiki/Bridge_(graph_theory)
1701+
"""
1702+
1703+
vertices = list(graph.vertices)
1704+
processed_vertices = []
1705+
for v in vertices:
1706+
if hasattr(v, "name"):
1707+
processed_vertices.append(v.name)
1708+
else:
1709+
processed_vertices.append(v)
1710+
1711+
n = len(processed_vertices)
1712+
adj = {v: [] for v in processed_vertices}
1713+
for v in processed_vertices:
1714+
for neighbor in graph.neighbors(v):
1715+
if hasattr(neighbor, "name"):
1716+
nbr = neighbor.name
1717+
else:
1718+
nbr = neighbor
1719+
adj[v].append(nbr)
1720+
1721+
mapping = {v: idx for idx, v in enumerate(processed_vertices)}
1722+
inv_mapping = {idx: v for v, idx in mapping.items()}
1723+
1724+
n_adj = [[] for _ in range(n)]
1725+
for v in processed_vertices:
1726+
idx_v = mapping[v]
1727+
for u in adj[v]:
1728+
idx_u = mapping[u]
1729+
n_adj[idx_v].append(idx_u)
1730+
1731+
visited = [False] * n
1732+
disc = [0] * n
1733+
low = [0] * n
1734+
parent = [-1] * n
1735+
bridges_idx = []
1736+
time = 0
1737+
1738+
def dfs(u):
1739+
nonlocal time
1740+
visited[u] = True
1741+
disc[u] = low[u] = time
1742+
time += 1
1743+
for v in n_adj[u]:
1744+
if not visited[v]:
1745+
parent[v] = u
1746+
dfs(v)
1747+
low[u] = min(low[u], low[v])
1748+
if low[v] > disc[u]:
1749+
bridges_idx.append((u, v))
1750+
elif v != parent[u]:
1751+
low[u] = min(low[u], disc[v])
1752+
1753+
for i in range(n):
1754+
if not visited[i]:
1755+
dfs(i)
1756+
1757+
bridges = []
1758+
for u, v in bridges_idx:
1759+
a = inv_mapping[u]
1760+
b = inv_mapping[v]
1761+
if a <= b:
1762+
bridges.append((a, b))
1763+
else:
1764+
bridges.append((b, a))
1765+
bridges.sort()
1766+
return bridges

pydatastructs/graphs/tests/test_algorithms.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
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, maximum_matching, maximum_matching_parallel, bipartite_coloring)
5+
topological_sort_parallel, max_flow, maximum_matching, maximum_matching_parallel,
6+
bipartite_coloring, find_bridges)
67
from pydatastructs.utils.raises_util import raises
78

89
def test_breadth_first_search():
@@ -188,11 +189,13 @@ def _test_strongly_connected_components(func, ds, algorithm, *args):
188189
graph.add_edge(h.name, g.name)
189190
comps = func(graph, algorithm)
190191
expected_comps = [{'e', 'a', 'b'}, {'d', 'c', 'h'}, {'g', 'f'}]
191-
assert comps == expected_comps
192+
assert comps.sort() == expected_comps.sort()
192193

193194
scc = strongly_connected_components
194195
_test_strongly_connected_components(scc, "List", "kosaraju")
195196
_test_strongly_connected_components(scc, "Matrix", "kosaraju")
197+
_test_strongly_connected_components(scc, "List", "tarjan")
198+
_test_strongly_connected_components(scc, "Matrix", "tarjan")
196199

197200
def test_depth_first_search():
198201

@@ -449,8 +452,6 @@ def _test_max_flow(ds, algorithm):
449452
_test_max_flow("List", "dinic")
450453
_test_max_flow("Matrix", "dinic")
451454

452-
453-
454455
def test_maximum_matching():
455456
def _test_maximum_matching(func, ds, algorithm, **kwargs):
456457
import pydatastructs.utils.misc_util as utils
@@ -621,3 +622,56 @@ def _test_bipartite_coloring(ds):
621622

622623
assert valid
623624
assert _assert_correctness(G3, coloring)
625+
626+
def test_find_bridges():
627+
def _test_find_bridges(ds):
628+
import pydatastructs.utils.misc_util as utils
629+
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
630+
631+
impl = 'adjacency_list' if ds == "List" else 'adjacency_matrix'
632+
633+
v0 = GraphNode(0)
634+
v1 = GraphNode(1)
635+
v2 = GraphNode(2)
636+
v3 = GraphNode(3)
637+
v4 = GraphNode(4)
638+
639+
G1 = Graph(v0, v1, v2, v3, v4, implementation=impl)
640+
G1.add_edge(v0.name, v1.name)
641+
G1.add_edge(v1.name, v2.name)
642+
G1.add_edge(v2.name, v3.name)
643+
G1.add_edge(v3.name, v4.name)
644+
645+
bridges = find_bridges(G1)
646+
expected_bridges = [('0', '1'), ('1', '2'), ('2', '3'), ('3', '4')]
647+
assert sorted(bridges) == sorted(expected_bridges)
648+
649+
u0 = GraphNode(0)
650+
u1 = GraphNode(1)
651+
u2 = GraphNode(2)
652+
653+
G2 = Graph(u0, u1, u2, implementation=impl)
654+
G2.add_edge(u0.name, u1.name)
655+
G2.add_edge(u1.name, u2.name)
656+
G2.add_edge(u2.name, u0.name)
657+
658+
bridges = find_bridges(G2)
659+
assert bridges == []
660+
661+
w0 = GraphNode(0)
662+
w1 = GraphNode(1)
663+
w2 = GraphNode(2)
664+
w3 = GraphNode(3)
665+
w4 = GraphNode(4)
666+
667+
G3 = Graph(w0, w1, w2, w3, w4, implementation=impl)
668+
G3.add_edge(w0.name, w1.name)
669+
G3.add_edge(w1.name, w2.name)
670+
G3.add_edge(w3.name, w4.name)
671+
672+
bridges = find_bridges(G3)
673+
expected_bridges = [('0', '1'), ('1', '2'), ('3', '4')]
674+
assert sorted(bridges) == sorted(expected_bridges)
675+
676+
_test_find_bridges("List")
677+
_test_find_bridges("Matrix")

0 commit comments

Comments
 (0)