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
25
25
'all_pair_shortest_paths' ,
26
26
'topological_sort' ,
27
27
'topological_sort_parallel' ,
28
- 'max_flow'
28
+ 'max_flow' ,
29
+ 'find_bridges'
29
30
]
30
31
31
32
Stack = Queue = deque
@@ -532,6 +533,52 @@ def _strongly_connected_components_kosaraju_adjacency_list(graph):
532
533
_strongly_connected_components_kosaraju_adjacency_matrix = \
533
534
_strongly_connected_components_kosaraju_adjacency_list
534
535
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
+
535
582
def strongly_connected_components (graph , algorithm , ** kwargs ):
536
583
"""
537
584
Computes strongly connected components for the given
@@ -550,6 +597,7 @@ def strongly_connected_components(graph, algorithm, **kwargs):
550
597
supported,
551
598
552
599
'kosaraju' -> Kosaraju's algorithm as given in [1].
600
+ 'tarjan' -> Tarjan's algorithm as given in [2].
553
601
backend: pydatastructs.Backend
554
602
The backend to be used.
555
603
Optional, by default, the best available
@@ -579,6 +627,7 @@ def strongly_connected_components(graph, algorithm, **kwargs):
579
627
==========
580
628
581
629
.. [1] https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
630
+ .. [2] https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
582
631
583
632
"""
584
633
raise_if_backend_is_not_python (
@@ -699,7 +748,7 @@ def shortest_paths(graph: Graph, algorithm: str,
699
748
The algorithm to be used. Currently, the following algorithms
700
749
are implemented,
701
750
702
- 'bellman_ford' -> Bellman-Ford algorithm as given in [1].
751
+ 'bellman_ford' -> Bellman-Ford algorithm as given in [1]
703
752
704
753
'dijkstra' -> Dijkstra algorithm as given in [2].
705
754
source: str
@@ -802,27 +851,34 @@ def pedersen_commitment(graph, g, h, p, q, include_weights=True):
802
851
return commitment , r
803
852
804
853
def _bellman_ford_adjacency_list (graph : Graph , source : str , target : str ) -> tuple :
805
- distances , predecessor = {}, {}
854
+ distances , predecessor , visited , cnts = {}, {}, {}, {}
806
855
807
856
for v in graph .vertices :
808
857
distances [v ] = float ('inf' )
809
858
predecessor [v ] = None
859
+ visited [v ] = False
860
+ cnts [v ] = 0
810
861
distances [source ] = 0
862
+ verticy_num = len (graph .vertices )
811
863
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 ])
820
865
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
826
882
827
883
if target != "" :
828
884
return (distances [target ], predecessor )
@@ -847,7 +903,7 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str):
847
903
visited [u ] = True
848
904
for v in graph .vertices :
849
905
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
851
907
visited [v ] is False and dist [v ] > dist [u ] + graph .edge_weights [edge_str ].value ):
852
908
dist [v ] = dist [u ] + graph .edge_weights [edge_str ].value
853
909
pred [v ] = u
@@ -874,6 +930,7 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str,
874
930
are implemented,
875
931
876
932
'floyd_warshall' -> Floyd Warshall algorithm as given in [1].
933
+ 'johnson' -> Johnson's Algorithm as given in [2]
877
934
backend: pydatastructs.Backend
878
935
The backend to be used.
879
936
Optional, by default, the best available
@@ -906,6 +963,7 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str,
906
963
==========
907
964
908
965
.. [1] https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm
966
+ .. [2] https://en.wikipedia.org/wiki/Johnson's_algorithm
909
967
"""
910
968
raise_if_backend_is_not_python (
911
969
all_pair_shortest_paths , kwargs .get ('backend' , Backend .PYTHON ))
@@ -948,6 +1006,51 @@ def _floyd_warshall_adjacency_list(graph: Graph):
948
1006
949
1007
_floyd_warshall_adjacency_matrix = _floyd_warshall_adjacency_list
950
1008
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
+
951
1054
def topological_sort (graph : Graph , algorithm : str ,
952
1055
** kwargs ) -> list :
953
1056
"""
@@ -1210,3 +1313,106 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
1210
1313
f"Currently { algorithm } algorithm isn't implemented for "
1211
1314
"performing max flow on graphs." )
1212
1315
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