5
5
from collections import deque
6
6
from concurrent .futures import ThreadPoolExecutor
7
7
from pydatastructs .utils .misc_util import (
8
- _comp , raise_if_backend_is_not_python , Backend )
8
+ _comp , raise_if_backend_is_not_python , Backend , AdjacencyListGraphNode )
9
9
from pydatastructs .miscellaneous_data_structures import (
10
10
DisjointSetForest , PriorityQueue )
11
11
from pydatastructs .graphs .graph import Graph
23
23
'all_pair_shortest_paths' ,
24
24
'topological_sort' ,
25
25
'topological_sort_parallel' ,
26
- 'max_flow'
26
+ 'max_flow' ,
27
+ 'find_bridges'
27
28
]
28
29
29
30
Stack = Queue = deque
@@ -530,6 +531,52 @@ def _strongly_connected_components_kosaraju_adjacency_list(graph):
530
531
_strongly_connected_components_kosaraju_adjacency_matrix = \
531
532
_strongly_connected_components_kosaraju_adjacency_list
532
533
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
+
533
580
def strongly_connected_components (graph , algorithm , ** kwargs ):
534
581
"""
535
582
Computes strongly connected components for the given
@@ -548,6 +595,7 @@ def strongly_connected_components(graph, algorithm, **kwargs):
548
595
supported,
549
596
550
597
'kosaraju' -> Kosaraju's algorithm as given in [1].
598
+ 'tarjan' -> Tarjan's algorithm as given in [2].
551
599
backend: pydatastructs.Backend
552
600
The backend to be used.
553
601
Optional, by default, the best available
@@ -577,6 +625,7 @@ def strongly_connected_components(graph, algorithm, **kwargs):
577
625
==========
578
626
579
627
.. [1] https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
628
+ .. [2] https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
580
629
581
630
"""
582
631
raise_if_backend_is_not_python (
@@ -697,7 +746,7 @@ def shortest_paths(graph: Graph, algorithm: str,
697
746
The algorithm to be used. Currently, the following algorithms
698
747
are implemented,
699
748
700
- 'bellman_ford' -> Bellman-Ford algorithm as given in [1].
749
+ 'bellman_ford' -> Bellman-Ford algorithm as given in [1]
701
750
702
751
'dijkstra' -> Dijkstra algorithm as given in [2].
703
752
source: str
@@ -754,27 +803,34 @@ def shortest_paths(graph: Graph, algorithm: str,
754
803
return getattr (algorithms , func )(graph , source , target )
755
804
756
805
def _bellman_ford_adjacency_list (graph : Graph , source : str , target : str ) -> tuple :
757
- distances , predecessor = {}, {}
806
+ distances , predecessor , visited , cnts = {}, {}, {}, {}
758
807
759
808
for v in graph .vertices :
760
809
distances [v ] = float ('inf' )
761
810
predecessor [v ] = None
811
+ visited [v ] = False
812
+ cnts [v ] = 0
762
813
distances [source ] = 0
814
+ verticy_num = len (graph .vertices )
763
815
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 ])
772
817
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
778
834
779
835
if target != "" :
780
836
return (distances [target ], predecessor )
@@ -799,7 +855,7 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str):
799
855
visited [u ] = True
800
856
for v in graph .vertices :
801
857
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
803
859
visited [v ] is False and dist [v ] > dist [u ] + graph .edge_weights [edge_str ].value ):
804
860
dist [v ] = dist [u ] + graph .edge_weights [edge_str ].value
805
861
pred [v ] = u
@@ -826,6 +882,7 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str,
826
882
are implemented,
827
883
828
884
'floyd_warshall' -> Floyd Warshall algorithm as given in [1].
885
+ 'johnson' -> Johnson's Algorithm as given in [2]
829
886
backend: pydatastructs.Backend
830
887
The backend to be used.
831
888
Optional, by default, the best available
@@ -858,6 +915,7 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str,
858
915
==========
859
916
860
917
.. [1] https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm
918
+ .. [2] https://en.wikipedia.org/wiki/Johnson's_algorithm
861
919
"""
862
920
raise_if_backend_is_not_python (
863
921
all_pair_shortest_paths , kwargs .get ('backend' , Backend .PYTHON ))
@@ -900,6 +958,51 @@ def _floyd_warshall_adjacency_list(graph: Graph):
900
958
901
959
_floyd_warshall_adjacency_matrix = _floyd_warshall_adjacency_list
902
960
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
+
903
1006
def topological_sort (graph : Graph , algorithm : str ,
904
1007
** kwargs ) -> list :
905
1008
"""
@@ -1162,3 +1265,106 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
1162
1265
f"Currently { algorithm } algorithm isn't implemented for "
1163
1266
"performing max flow on graphs." )
1164
1267
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