2323 'all_pair_shortest_paths' ,
2424 'topological_sort' ,
2525 'topological_sort_parallel' ,
26- 'max_flow'
26+ 'max_flow' ,
27+ 'find_bridges'
2728]
2829
2930Stack = 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+
533580def 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
0 commit comments