2929 'max_flow' ,
3030 'maximum_matching' ,
3131 'maximum_matching_parallel' ,
32- 'bipartite_coloring'
32+ 'bipartite_coloring' ,
33+ 'find_bridges'
3334]
3435
3536Stack = 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+
539586def 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+
12261276def 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
0 commit comments