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
2525 'all_pair_shortest_paths' ,
2626 'topological_sort' ,
2727 'topological_sort_parallel' ,
28- 'max_flow'
28+ 'max_flow' ,
29+ 'find_bridges'
2930]
3031
3132Stack = Queue = deque
@@ -532,6 +533,52 @@ def _strongly_connected_components_kosaraju_adjacency_list(graph):
532533_strongly_connected_components_kosaraju_adjacency_matrix = \
533534 _strongly_connected_components_kosaraju_adjacency_list
534535
536+ def _tarjan_dfs (u , graph , index , stack , indices , low_links , on_stacks , components ):
537+ indices [u ] = index [0 ]
538+ low_links [u ] = index [0 ]
539+ index [0 ] += 1
540+ stack .append (u )
541+ on_stacks [u ] = True
542+
543+ for node in graph .neighbors (u ):
544+ v = node .name
545+ if indices [v ] == - 1 :
546+ _tarjan_dfs (v , graph , index , stack , indices , low_links , on_stacks , components )
547+ low_links [u ] = min (low_links [u ], low_links [v ])
548+ elif on_stacks [v ]:
549+ low_links [u ] = min (low_links [u ], low_links [v ])
550+
551+ if low_links [u ] == indices [u ]:
552+ component = set ()
553+ while stack :
554+ w = stack .pop ()
555+ on_stacks [w ] = False
556+ component .add (w )
557+ if w == u :
558+ break
559+ components .append (component )
560+
561+ def _strongly_connected_components_tarjan_adjacency_list (graph ):
562+ index = [0 ] # mutable object
563+ stack = Stack ([])
564+ indices , low_links , on_stacks = {}, {}, {}
565+
566+ for u in graph .vertices :
567+ indices [u ] = - 1
568+ low_links [u ] = - 1
569+ on_stacks [u ] = False
570+
571+ components = []
572+
573+ for u in graph .vertices :
574+ if indices [u ] == - 1 :
575+ _tarjan_dfs (u , graph , index , stack , indices , low_links , on_stacks , components )
576+
577+ return components
578+
579+ _strongly_connected_components_tarjan_adjacency_matrix = \
580+ _strongly_connected_components_tarjan_adjacency_list
581+
535582def strongly_connected_components (graph , algorithm , ** kwargs ):
536583 """
537584 Computes strongly connected components for the given
@@ -550,6 +597,7 @@ def strongly_connected_components(graph, algorithm, **kwargs):
550597 supported,
551598
552599 'kosaraju' -> Kosaraju's algorithm as given in [1].
600+ 'tarjan' -> Tarjan's algorithm as given in [2].
553601 backend: pydatastructs.Backend
554602 The backend to be used.
555603 Optional, by default, the best available
@@ -579,6 +627,7 @@ def strongly_connected_components(graph, algorithm, **kwargs):
579627 ==========
580628
581629 .. [1] https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
630+ .. [2] https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
582631
583632 """
584633 raise_if_backend_is_not_python (
@@ -699,7 +748,7 @@ def shortest_paths(graph: Graph, algorithm: str,
699748 The algorithm to be used. Currently, the following algorithms
700749 are implemented,
701750
702- 'bellman_ford' -> Bellman-Ford algorithm as given in [1].
751+ 'bellman_ford' -> Bellman-Ford algorithm as given in [1]
703752
704753 'dijkstra' -> Dijkstra algorithm as given in [2].
705754 source: str
@@ -802,27 +851,34 @@ def pedersen_commitment(graph, g, h, p, q, include_weights=True):
802851 return commitment , r
803852
804853def _bellman_ford_adjacency_list (graph : Graph , source : str , target : str ) -> tuple :
805- distances , predecessor = {}, {}
854+ distances , predecessor , visited , cnts = {}, {}, {}, {}
806855
807856 for v in graph .vertices :
808857 distances [v ] = float ('inf' )
809858 predecessor [v ] = None
859+ visited [v ] = False
860+ cnts [v ] = 0
810861 distances [source ] = 0
862+ verticy_num = len (graph .vertices )
811863
812- edges = graph .edge_weights .values ()
813- for _ in range (len (graph .vertices ) - 1 ):
814- for edge in edges :
815- u , v = edge .source .name , edge .target .name
816- w = edge .value
817- if distances [u ] + edge .value < distances [v ]:
818- distances [v ] = distances [u ] + w
819- predecessor [v ] = u
864+ que = Queue ([source ])
820865
821- for edge in edges :
822- u , v = edge .source .name , edge .target .name
823- w = edge .value
824- if distances [u ] + w < distances [v ]:
825- raise ValueError ("Graph contains a negative weight cycle." )
866+ while que :
867+ u = que .popleft ()
868+ visited [u ] = False
869+ neighbors = graph .neighbors (u )
870+ for neighbor in neighbors :
871+ v = neighbor .name
872+ edge_str = u + '_' + v
873+ if distances [u ] != float ('inf' ) and distances [u ] + graph .edge_weights [edge_str ].value < distances [v ]:
874+ distances [v ] = distances [u ] + graph .edge_weights [edge_str ].value
875+ predecessor [v ] = u
876+ cnts [v ] = cnts [u ] + 1
877+ if cnts [v ] >= verticy_num :
878+ raise ValueError ("Graph contains a negative weight cycle." )
879+ if not visited [v ]:
880+ que .append (v )
881+ visited [v ] = True
826882
827883 if target != "" :
828884 return (distances [target ], predecessor )
@@ -847,7 +903,7 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str):
847903 visited [u ] = True
848904 for v in graph .vertices :
849905 edge_str = u + '_' + v
850- if (edge_str in graph .edge_weights and graph .edge_weights [edge_str ].value > 0 and
906+ if (edge_str in graph .edge_weights and graph .edge_weights [edge_str ].value >= 0 and
851907 visited [v ] is False and dist [v ] > dist [u ] + graph .edge_weights [edge_str ].value ):
852908 dist [v ] = dist [u ] + graph .edge_weights [edge_str ].value
853909 pred [v ] = u
@@ -874,6 +930,7 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str,
874930 are implemented,
875931
876932 'floyd_warshall' -> Floyd Warshall algorithm as given in [1].
933+ 'johnson' -> Johnson's Algorithm as given in [2]
877934 backend: pydatastructs.Backend
878935 The backend to be used.
879936 Optional, by default, the best available
@@ -906,6 +963,7 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str,
906963 ==========
907964
908965 .. [1] https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm
966+ .. [2] https://en.wikipedia.org/wiki/Johnson's_algorithm
909967 """
910968 raise_if_backend_is_not_python (
911969 all_pair_shortest_paths , kwargs .get ('backend' , Backend .PYTHON ))
@@ -948,6 +1006,51 @@ def _floyd_warshall_adjacency_list(graph: Graph):
9481006
9491007_floyd_warshall_adjacency_matrix = _floyd_warshall_adjacency_list
9501008
1009+ def _johnson_adjacency_list (graph : Graph ):
1010+ new_vertex = AdjacencyListGraphNode ('__q__' )
1011+ graph .add_vertex (new_vertex )
1012+
1013+ for vertex in graph .vertices :
1014+ if vertex != '__q__' :
1015+ graph .add_edge ('__q__' , vertex , 0 )
1016+
1017+ distances , predecessors = shortest_paths (graph , 'bellman_ford' , '__q__' )
1018+
1019+ edges_to_remove = []
1020+ for edge in graph .edge_weights :
1021+ edge_node = graph .edge_weights [edge ]
1022+ if edge_node .source .name == '__q__' :
1023+ edges_to_remove .append ((edge_node .source .name , edge_node .target .name ))
1024+
1025+ for u , v in edges_to_remove :
1026+ graph .remove_edge (u , v )
1027+ graph .remove_vertex ('__q__' )
1028+
1029+ for edge in graph .edge_weights :
1030+ edge_node = graph .edge_weights [edge ]
1031+ u , v = edge_node .source .name , edge_node .target .name
1032+ graph .edge_weights [edge ].value += (distances [u ] - distances [v ])
1033+
1034+ all_distances = {}
1035+ all_next_vertex = {}
1036+
1037+ for vertex in graph .vertices :
1038+ u = vertex
1039+ dijkstra_dist , dijkstra_pred = shortest_paths (graph , 'dijkstra' , u )
1040+ all_distances [u ] = {}
1041+ all_next_vertex [u ] = {}
1042+ for v in graph .vertices :
1043+ if dijkstra_pred [v ] is None or dijkstra_pred [v ] == u :
1044+ all_next_vertex [u ][v ] = u
1045+ else :
1046+ all_next_vertex [u ][v ] = None
1047+ if v in dijkstra_dist :
1048+ all_distances [u ][v ] = dijkstra_dist [v ] - distances [u ] + distances [v ]
1049+ else :
1050+ all_distances [u ][v ] = float ('inf' )
1051+
1052+ return (all_distances , all_next_vertex )
1053+
9511054def topological_sort (graph : Graph , algorithm : str ,
9521055 ** kwargs ) -> list :
9531056 """
@@ -1210,3 +1313,106 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
12101313 f"Currently { algorithm } algorithm isn't implemented for "
12111314 "performing max flow on graphs." )
12121315 return getattr (algorithms , func )(graph , source , sink )
1316+
1317+
1318+ def find_bridges (graph ):
1319+ """
1320+ Finds all bridges in an undirected graph using Tarjan's Algorithm.
1321+
1322+ Parameters
1323+ ==========
1324+ graph : Graph
1325+ An undirected graph instance.
1326+
1327+ Returns
1328+ ==========
1329+ List[tuple]
1330+ A list of bridges, where each bridge is represented as a tuple (u, v)
1331+ with u <= v.
1332+
1333+ Example
1334+ ========
1335+ >>> from pydatastructs import Graph, AdjacencyListGraphNode, find_bridges
1336+ >>> v0 = AdjacencyListGraphNode(0)
1337+ >>> v1 = AdjacencyListGraphNode(1)
1338+ >>> v2 = AdjacencyListGraphNode(2)
1339+ >>> v3 = AdjacencyListGraphNode(3)
1340+ >>> v4 = AdjacencyListGraphNode(4)
1341+ >>> graph = Graph(v0, v1, v2, v3, v4, implementation='adjacency_list')
1342+ >>> graph.add_edge(v0.name, v1.name)
1343+ >>> graph.add_edge(v1.name, v2.name)
1344+ >>> graph.add_edge(v2.name, v3.name)
1345+ >>> graph.add_edge(v3.name, v4.name)
1346+ >>> find_bridges(graph)
1347+ [('0', '1'), ('1', '2'), ('2', '3'), ('3', '4')]
1348+
1349+ References
1350+ ==========
1351+
1352+ .. [1] https://en.wikipedia.org/wiki/Bridge_(graph_theory)
1353+ """
1354+
1355+ vertices = list (graph .vertices )
1356+ processed_vertices = []
1357+ for v in vertices :
1358+ if hasattr (v , "name" ):
1359+ processed_vertices .append (v .name )
1360+ else :
1361+ processed_vertices .append (v )
1362+
1363+ n = len (processed_vertices )
1364+ adj = {v : [] for v in processed_vertices }
1365+ for v in processed_vertices :
1366+ for neighbor in graph .neighbors (v ):
1367+ if hasattr (neighbor , "name" ):
1368+ nbr = neighbor .name
1369+ else :
1370+ nbr = neighbor
1371+ adj [v ].append (nbr )
1372+
1373+ mapping = {v : idx for idx , v in enumerate (processed_vertices )}
1374+ inv_mapping = {idx : v for v , idx in mapping .items ()}
1375+
1376+ n_adj = [[] for _ in range (n )]
1377+ for v in processed_vertices :
1378+ idx_v = mapping [v ]
1379+ for u in adj [v ]:
1380+ idx_u = mapping [u ]
1381+ n_adj [idx_v ].append (idx_u )
1382+
1383+ visited = [False ] * n
1384+ disc = [0 ] * n
1385+ low = [0 ] * n
1386+ parent = [- 1 ] * n
1387+ bridges_idx = []
1388+ time = 0
1389+
1390+ def dfs (u ):
1391+ nonlocal time
1392+ visited [u ] = True
1393+ disc [u ] = low [u ] = time
1394+ time += 1
1395+ for v in n_adj [u ]:
1396+ if not visited [v ]:
1397+ parent [v ] = u
1398+ dfs (v )
1399+ low [u ] = min (low [u ], low [v ])
1400+ if low [v ] > disc [u ]:
1401+ bridges_idx .append ((u , v ))
1402+ elif v != parent [u ]:
1403+ low [u ] = min (low [u ], disc [v ])
1404+
1405+ for i in range (n ):
1406+ if not visited [i ]:
1407+ dfs (i )
1408+
1409+ bridges = []
1410+ for u , v in bridges_idx :
1411+ a = inv_mapping [u ]
1412+ b = inv_mapping [v ]
1413+ if a <= b :
1414+ bridges .append ((a , b ))
1415+ else :
1416+ bridges .append ((b , a ))
1417+ bridges .sort ()
1418+ return bridges
0 commit comments