55from collections import deque
66from concurrent .futures import ThreadPoolExecutor
77from pydatastructs .utils .misc_util import (
8- _comp , raise_if_backend_is_not_python , Backend )
8+ _comp , raise_if_backend_is_not_python , Backend , AdjacencyListGraphNode )
99from pydatastructs .miscellaneous_data_structures import (
1010 DisjointSetForest , PriorityQueue )
1111from pydatastructs .graphs .graph import Graph
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 (
@@ -697,7 +746,7 @@ def shortest_paths(graph: Graph, algorithm: str,
697746 The algorithm to be used. Currently, the following algorithms
698747 are implemented,
699748
700- 'bellman_ford' -> Bellman-Ford algorithm as given in [1].
749+ 'bellman_ford' -> Bellman-Ford algorithm as given in [1]
701750
702751 'dijkstra' -> Dijkstra algorithm as given in [2].
703752 source: str
@@ -754,27 +803,34 @@ def shortest_paths(graph: Graph, algorithm: str,
754803 return getattr (algorithms , func )(graph , source , target )
755804
756805def _bellman_ford_adjacency_list (graph : Graph , source : str , target : str ) -> tuple :
757- distances , predecessor = {}, {}
806+ distances , predecessor , visited , cnts = {}, {}, {}, {}
758807
759808 for v in graph .vertices :
760809 distances [v ] = float ('inf' )
761810 predecessor [v ] = None
811+ visited [v ] = False
812+ cnts [v ] = 0
762813 distances [source ] = 0
814+ verticy_num = len (graph .vertices )
763815
764- edges = graph .edge_weights .values ()
765- for _ in range (len (graph .vertices ) - 1 ):
766- for edge in edges :
767- u , v = edge .source .name , edge .target .name
768- w = edge .value
769- if distances [u ] + edge .value < distances [v ]:
770- distances [v ] = distances [u ] + w
771- predecessor [v ] = u
816+ que = Queue ([source ])
772817
773- for edge in edges :
774- u , v = edge .source .name , edge .target .name
775- w = edge .value
776- if distances [u ] + w < distances [v ]:
777- raise ValueError ("Graph contains a negative weight cycle." )
818+ while que :
819+ u = que .popleft ()
820+ visited [u ] = False
821+ neighbors = graph .neighbors (u )
822+ for neighbor in neighbors :
823+ v = neighbor .name
824+ edge_str = u + '_' + v
825+ if distances [u ] != float ('inf' ) and distances [u ] + graph .edge_weights [edge_str ].value < distances [v ]:
826+ distances [v ] = distances [u ] + graph .edge_weights [edge_str ].value
827+ predecessor [v ] = u
828+ cnts [v ] = cnts [u ] + 1
829+ if cnts [v ] >= verticy_num :
830+ raise ValueError ("Graph contains a negative weight cycle." )
831+ if not visited [v ]:
832+ que .append (v )
833+ visited [v ] = True
778834
779835 if target != "" :
780836 return (distances [target ], predecessor )
@@ -799,7 +855,7 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str):
799855 visited [u ] = True
800856 for v in graph .vertices :
801857 edge_str = u + '_' + v
802- if (edge_str in graph .edge_weights and graph .edge_weights [edge_str ].value > 0 and
858+ if (edge_str in graph .edge_weights and graph .edge_weights [edge_str ].value >= 0 and
803859 visited [v ] is False and dist [v ] > dist [u ] + graph .edge_weights [edge_str ].value ):
804860 dist [v ] = dist [u ] + graph .edge_weights [edge_str ].value
805861 pred [v ] = u
@@ -826,6 +882,7 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str,
826882 are implemented,
827883
828884 'floyd_warshall' -> Floyd Warshall algorithm as given in [1].
885+ 'johnson' -> Johnson's Algorithm as given in [2]
829886 backend: pydatastructs.Backend
830887 The backend to be used.
831888 Optional, by default, the best available
@@ -858,6 +915,7 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str,
858915 ==========
859916
860917 .. [1] https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm
918+ .. [2] https://en.wikipedia.org/wiki/Johnson's_algorithm
861919 """
862920 raise_if_backend_is_not_python (
863921 all_pair_shortest_paths , kwargs .get ('backend' , Backend .PYTHON ))
@@ -900,6 +958,51 @@ def _floyd_warshall_adjacency_list(graph: Graph):
900958
901959_floyd_warshall_adjacency_matrix = _floyd_warshall_adjacency_list
902960
961+ def _johnson_adjacency_list (graph : Graph ):
962+ new_vertex = AdjacencyListGraphNode ('__q__' )
963+ graph .add_vertex (new_vertex )
964+
965+ for vertex in graph .vertices :
966+ if vertex != '__q__' :
967+ graph .add_edge ('__q__' , vertex , 0 )
968+
969+ distances , predecessors = shortest_paths (graph , 'bellman_ford' , '__q__' )
970+
971+ edges_to_remove = []
972+ for edge in graph .edge_weights :
973+ edge_node = graph .edge_weights [edge ]
974+ if edge_node .source .name == '__q__' :
975+ edges_to_remove .append ((edge_node .source .name , edge_node .target .name ))
976+
977+ for u , v in edges_to_remove :
978+ graph .remove_edge (u , v )
979+ graph .remove_vertex ('__q__' )
980+
981+ for edge in graph .edge_weights :
982+ edge_node = graph .edge_weights [edge ]
983+ u , v = edge_node .source .name , edge_node .target .name
984+ graph .edge_weights [edge ].value += (distances [u ] - distances [v ])
985+
986+ all_distances = {}
987+ all_next_vertex = {}
988+
989+ for vertex in graph .vertices :
990+ u = vertex
991+ dijkstra_dist , dijkstra_pred = shortest_paths (graph , 'dijkstra' , u )
992+ all_distances [u ] = {}
993+ all_next_vertex [u ] = {}
994+ for v in graph .vertices :
995+ if dijkstra_pred [v ] is None or dijkstra_pred [v ] == u :
996+ all_next_vertex [u ][v ] = u
997+ else :
998+ all_next_vertex [u ][v ] = None
999+ if v in dijkstra_dist :
1000+ all_distances [u ][v ] = dijkstra_dist [v ] - distances [u ] + distances [v ]
1001+ else :
1002+ all_distances [u ][v ] = float ('inf' )
1003+
1004+ return (all_distances , all_next_vertex )
1005+
9031006def topological_sort (graph : Graph , algorithm : str ,
9041007 ** kwargs ) -> list :
9051008 """
@@ -1162,3 +1265,106 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
11621265 f"Currently { algorithm } algorithm isn't implemented for "
11631266 "performing max flow on graphs." )
11641267 return getattr (algorithms , func )(graph , source , sink )
1268+
1269+
1270+ def find_bridges (graph ):
1271+ """
1272+ Finds all bridges in an undirected graph using Tarjan's Algorithm.
1273+
1274+ Parameters
1275+ ==========
1276+ graph : Graph
1277+ An undirected graph instance.
1278+
1279+ Returns
1280+ ==========
1281+ List[tuple]
1282+ A list of bridges, where each bridge is represented as a tuple (u, v)
1283+ with u <= v.
1284+
1285+ Example
1286+ ========
1287+ >>> from pydatastructs import Graph, AdjacencyListGraphNode, find_bridges
1288+ >>> v0 = AdjacencyListGraphNode(0)
1289+ >>> v1 = AdjacencyListGraphNode(1)
1290+ >>> v2 = AdjacencyListGraphNode(2)
1291+ >>> v3 = AdjacencyListGraphNode(3)
1292+ >>> v4 = AdjacencyListGraphNode(4)
1293+ >>> graph = Graph(v0, v1, v2, v3, v4, implementation='adjacency_list')
1294+ >>> graph.add_edge(v0.name, v1.name)
1295+ >>> graph.add_edge(v1.name, v2.name)
1296+ >>> graph.add_edge(v2.name, v3.name)
1297+ >>> graph.add_edge(v3.name, v4.name)
1298+ >>> find_bridges(graph)
1299+ [('0', '1'), ('1', '2'), ('2', '3'), ('3', '4')]
1300+
1301+ References
1302+ ==========
1303+
1304+ .. [1] https://en.wikipedia.org/wiki/Bridge_(graph_theory)
1305+ """
1306+
1307+ vertices = list (graph .vertices )
1308+ processed_vertices = []
1309+ for v in vertices :
1310+ if hasattr (v , "name" ):
1311+ processed_vertices .append (v .name )
1312+ else :
1313+ processed_vertices .append (v )
1314+
1315+ n = len (processed_vertices )
1316+ adj = {v : [] for v in processed_vertices }
1317+ for v in processed_vertices :
1318+ for neighbor in graph .neighbors (v ):
1319+ if hasattr (neighbor , "name" ):
1320+ nbr = neighbor .name
1321+ else :
1322+ nbr = neighbor
1323+ adj [v ].append (nbr )
1324+
1325+ mapping = {v : idx for idx , v in enumerate (processed_vertices )}
1326+ inv_mapping = {idx : v for v , idx in mapping .items ()}
1327+
1328+ n_adj = [[] for _ in range (n )]
1329+ for v in processed_vertices :
1330+ idx_v = mapping [v ]
1331+ for u in adj [v ]:
1332+ idx_u = mapping [u ]
1333+ n_adj [idx_v ].append (idx_u )
1334+
1335+ visited = [False ] * n
1336+ disc = [0 ] * n
1337+ low = [0 ] * n
1338+ parent = [- 1 ] * n
1339+ bridges_idx = []
1340+ time = 0
1341+
1342+ def dfs (u ):
1343+ nonlocal time
1344+ visited [u ] = True
1345+ disc [u ] = low [u ] = time
1346+ time += 1
1347+ for v in n_adj [u ]:
1348+ if not visited [v ]:
1349+ parent [v ] = u
1350+ dfs (v )
1351+ low [u ] = min (low [u ], low [v ])
1352+ if low [v ] > disc [u ]:
1353+ bridges_idx .append ((u , v ))
1354+ elif v != parent [u ]:
1355+ low [u ] = min (low [u ], disc [v ])
1356+
1357+ for i in range (n ):
1358+ if not visited [i ]:
1359+ dfs (i )
1360+
1361+ bridges = []
1362+ for u , v in bridges_idx :
1363+ a = inv_mapping [u ]
1364+ b = inv_mapping [v ]
1365+ if a <= b :
1366+ bridges .append ((a , b ))
1367+ else :
1368+ bridges .append ((b , a ))
1369+ bridges .sort ()
1370+ return bridges
0 commit comments