Skip to content

Commit 6050e1a

Browse files
authored
Merge branch 'main' into feature/graph-time-series
2 parents 214d338 + f7a6296 commit 6050e1a

19 files changed

+1104
-56
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ Pratik Goyal <pratikgoyal2712@gmail.com>
1111
Jay Thorat <j.thorat10@gmail.com>
1212
Rajveer Singh Bharadwaj <rsb3256@gmail.com>
1313
Kishan Ved <kishanved123456@gmail.com>
14+
Arvinder Singh Dhoul <asdhoul004@gmail.com>

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
@@ -25,7 +25,8 @@
2525
'all_pair_shortest_paths',
2626
'topological_sort',
2727
'topological_sort_parallel',
28-
'max_flow'
28+
'max_flow',
29+
'find_bridges'
2930
]
3031

3132
Stack = 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+
535582
def 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

804853
def _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+
9511054
def 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

Comments
 (0)