From d8ac6fab3f9d9e9a3983f6531dbc8522a91aa9bf Mon Sep 17 00:00:00 2001 From: Hargun Kaur Date: Thu, 13 Mar 2025 18:22:56 +0530 Subject: [PATCH 1/2] Added bridge-finding algorithm Signed-off-by: Hargun Kaur --- pydatastructs/graphs/__init__.py | 3 +- pydatastructs/graphs/algorithms.py | 106 +++++++++++++++++- pydatastructs/graphs/tests/test_algorithms.py | 56 ++++++++- 3 files changed, 162 insertions(+), 3 deletions(-) diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py index 9c00ca0aa..21e0a5f35 100644 --- a/pydatastructs/graphs/__init__.py +++ b/pydatastructs/graphs/__init__.py @@ -21,7 +21,8 @@ all_pair_shortest_paths, topological_sort, topological_sort_parallel, - max_flow + max_flow, + find_bridges ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 9de50e7cc..e2077db8d 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -23,7 +23,8 @@ 'all_pair_shortest_paths', 'topological_sort', 'topological_sort_parallel', - 'max_flow' + 'max_flow', + 'find_bridges' ] Stack = Queue = deque @@ -1216,3 +1217,106 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs): f"Currently {algorithm} algorithm isn't implemented for " "performing max flow on graphs.") return getattr(algorithms, func)(graph, source, sink) + + +def find_bridges(graph): + """ + Finds all bridges in an undirected graph using Tarjan's Algorithm. + + Parameters + ========== + graph : Graph + An undirected graph instance. + + Returns + ========== + List[tuple] + A list of bridges, where each bridge is represented as a tuple (u, v) + with u <= v. + + Example + ======== + >>> from pydatastructs import Graph, AdjacencyListGraphNode, find_bridges + >>> v0 = AdjacencyListGraphNode(0) + >>> v1 = AdjacencyListGraphNode(1) + >>> v2 = AdjacencyListGraphNode(2) + >>> v3 = AdjacencyListGraphNode(3) + >>> v4 = AdjacencyListGraphNode(4) + >>> graph = Graph(v0, v1, v2, v3, v4, implementation='adjacency_list') + >>> graph.add_edge(v0.name, v1.name) + >>> graph.add_edge(v1.name, v2.name) + >>> graph.add_edge(v2.name, v3.name) + >>> graph.add_edge(v3.name, v4.name) + >>> find_bridges(graph) + [('0', '1'), ('1', '2'), ('2', '3'), ('3', '4')] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Bridge_(graph_theory) + """ + + vertices = list(graph.vertices) + processed_vertices = [] + for v in vertices: + if hasattr(v, "name"): + processed_vertices.append(v.name) + else: + processed_vertices.append(v) + + n = len(processed_vertices) + adj = {v: [] for v in processed_vertices} + for v in processed_vertices: + for neighbor in graph.neighbors(v): + if hasattr(neighbor, "name"): + nbr = neighbor.name + else: + nbr = neighbor + adj[v].append(nbr) + + mapping = {v: idx for idx, v in enumerate(processed_vertices)} + inv_mapping = {idx: v for v, idx in mapping.items()} + + n_adj = [[] for _ in range(n)] + for v in processed_vertices: + idx_v = mapping[v] + for u in adj[v]: + idx_u = mapping[u] + n_adj[idx_v].append(idx_u) + + visited = [False] * n + disc = [0] * n + low = [0] * n + parent = [-1] * n + bridges_idx = [] + time = 0 + + def dfs(u): + nonlocal time + visited[u] = True + disc[u] = low[u] = time + time += 1 + for v in n_adj[u]: + if not visited[v]: + parent[v] = u + dfs(v) + low[u] = min(low[u], low[v]) + if low[v] > disc[u]: + bridges_idx.append((u, v)) + elif v != parent[u]: + low[u] = min(low[u], disc[v]) + + for i in range(n): + if not visited[i]: + dfs(i) + + bridges = [] + for u, v in bridges_idx: + a = inv_mapping[u] + b = inv_mapping[v] + if a <= b: + bridges.append((a, b)) + else: + bridges.append((b, a)) + bridges.sort() + return bridges diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index f1586f512..25a2ab5e3 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -2,7 +2,7 @@ breadth_first_search_parallel, minimum_spanning_tree, minimum_spanning_tree_parallel, strongly_connected_components, depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort, -topological_sort_parallel, max_flow) +topological_sort_parallel, max_flow, find_bridges) from pydatastructs.utils.raises_util import raises def test_breadth_first_search(): @@ -448,3 +448,57 @@ def _test_max_flow(ds, algorithm): _test_max_flow("Matrix", "edmonds_karp") _test_max_flow("List", "dinic") _test_max_flow("Matrix", "dinic") + + +def test_find_bridges(): + def _test_find_bridges(ds): + import pydatastructs.utils.misc_util as utils + GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") + + impl = 'adjacency_list' if ds == "List" else 'adjacency_matrix' + + v0 = GraphNode(0) + v1 = GraphNode(1) + v2 = GraphNode(2) + v3 = GraphNode(3) + v4 = GraphNode(4) + + G1 = Graph(v0, v1, v2, v3, v4, implementation=impl) + G1.add_edge(v0.name, v1.name) + G1.add_edge(v1.name, v2.name) + G1.add_edge(v2.name, v3.name) + G1.add_edge(v3.name, v4.name) + + bridges = find_bridges(G1) + expected_bridges = [('0', '1'), ('1', '2'), ('2', '3'), ('3', '4')] + assert sorted(bridges) == sorted(expected_bridges) + + u0 = GraphNode(0) + u1 = GraphNode(1) + u2 = GraphNode(2) + + G2 = Graph(u0, u1, u2, implementation=impl) + G2.add_edge(u0.name, u1.name) + G2.add_edge(u1.name, u2.name) + G2.add_edge(u2.name, u0.name) + + bridges = find_bridges(G2) + assert bridges == [] + + w0 = GraphNode(0) + w1 = GraphNode(1) + w2 = GraphNode(2) + w3 = GraphNode(3) + w4 = GraphNode(4) + + G3 = Graph(w0, w1, w2, w3, w4, implementation=impl) + G3.add_edge(w0.name, w1.name) + G3.add_edge(w1.name, w2.name) + G3.add_edge(w3.name, w4.name) + + bridges = find_bridges(G3) + expected_bridges = [('0', '1'), ('1', '2'), ('3', '4')] + assert sorted(bridges) == sorted(expected_bridges) + + _test_find_bridges("List") + _test_find_bridges("Matrix") From 9271cb4a5e4d28200a57eb19b0a00de4a8480dcd Mon Sep 17 00:00:00 2001 From: Hargun Kaur Date: Thu, 13 Mar 2025 18:37:25 +0530 Subject: [PATCH 2/2] Added bridge-finding algorithm Signed-off-by: Hargun Kaur --- docs/source/pydatastructs/graphs/algorithms.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/pydatastructs/graphs/algorithms.rst b/docs/source/pydatastructs/graphs/algorithms.rst index 1749e3a4c..c508421ab 100644 --- a/docs/source/pydatastructs/graphs/algorithms.rst +++ b/docs/source/pydatastructs/graphs/algorithms.rst @@ -20,3 +20,5 @@ Algorithms .. autofunction:: pydatastructs.topological_sort .. autofunction:: pydatastructs.topological_sort_parallel + +.. autofunction:: pydatastructs.find_bridges \ No newline at end of file