Skip to content

Commit eb237c1

Browse files
authored
Merge branch 'main' into main
2 parents 26ea7b9 + f7a6296 commit eb237c1

19 files changed

+1104
-56
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ Pratik Goyal <[email protected]>
1111
Jay Thorat <[email protected]>
1212
Rajveer Singh Bharadwaj <[email protected]>
1313
Kishan Ved <[email protected]>
14+
Arvinder Singh Dhoul <[email protected]>

docs/source/pydatastructs/graphs/algorithms.rst

+2
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ Algorithms
2020
.. autofunction:: pydatastructs.topological_sort
2121

2222
.. autofunction:: pydatastructs.topological_sort_parallel
23+
24+
.. autofunction:: pydatastructs.find_bridges

docs/source/pydatastructs/linear_data_structures/algorithms.rst

+5-1
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,8 @@ Algorithms
4545

4646
.. autofunction:: pydatastructs.jump_search
4747

48-
.. autofunction:: pydatastructs.intro_sort
48+
.. autofunction:: pydatastructs.intro_sort
49+
50+
.. autofunction:: pydatastructs.shell_sort
51+
52+
.. autofunction:: pydatastructs.radix_sort

pydatastructs/graphs/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
all_pair_shortest_paths,
2222
topological_sort,
2323
topological_sort_parallel,
24-
max_flow
24+
max_flow,
25+
find_bridges
2526
)
2627

2728
__all__.extend(algorithms.__all__)

pydatastructs/graphs/algorithms.py

+224-18
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from collections import deque
66
from concurrent.futures import ThreadPoolExecutor
77
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)
99
from pydatastructs.miscellaneous_data_structures import (
1010
DisjointSetForest, PriorityQueue)
1111
from pydatastructs.graphs.graph import Graph
@@ -23,7 +23,8 @@
2323
'all_pair_shortest_paths',
2424
'topological_sort',
2525
'topological_sort_parallel',
26-
'max_flow'
26+
'max_flow',
27+
'find_bridges'
2728
]
2829

2930
Stack = 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+
533580
def 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

756805
def _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+
9031006
def 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

Comments
 (0)