From 3f3faad5341e8335dd8cf9aef36f33ade5eef69e Mon Sep 17 00:00:00 2001 From: Kiran-Venkatesh <36192264+Kiran-Venkatesh@users.noreply.github.com> Date: Fri, 20 Nov 2020 14:21:38 +0530 Subject: [PATCH] Added Floyd Warshall algorithm (#302) --- pydatastructs/graphs/__init__.py | 1 + pydatastructs/graphs/algorithms.py | 83 ++++++++++++++++++- pydatastructs/graphs/tests/test_algorithms.py | 57 +++++++++++-- 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py index d43f0981c..bdfebdc72 100644 --- a/pydatastructs/graphs/__init__.py +++ b/pydatastructs/graphs/__init__.py @@ -15,6 +15,7 @@ strongly_connected_components, depth_first_search, shortest_paths, + all_pair_shortest_paths, topological_sort, topological_sort_parallel ) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 29313d157..cd1738a92 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -20,6 +20,7 @@ 'strongly_connected_components', 'depth_first_search', 'shortest_paths', + 'all_pair_shortest_paths', 'topological_sort', 'topological_sort_parallel' ] @@ -672,7 +673,7 @@ def shortest_paths(graph: Graph, algorithm: str, is 'bellman_ford'/'dijkstra'. (distances[target], predecessors): (float, dict) If target is provided and algorithm used is - 'bellman_ford'. + 'bellman_ford'/'dijkstra'. Examples ======== @@ -762,6 +763,86 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str): _dijkstra_adjacency_matrix = _dijkstra_adjacency_list +def all_pair_shortest_paths(graph: Graph, algorithm: str) -> tuple: + """ + Finds shortest paths between all pairs of vertices in the given graph. + + Parameters + ========== + + graph: Graph + The graph under consideration. + algorithm: str + The algorithm to be used. Currently, the following algorithms + are implemented, + 'floyd_warshall' -> Floyd Warshall algorithm as given in [1]. + + Returns + ======= + + (distances, predecessors): (dict, dict) + + Examples + ======== + + >>> from pydatastructs import Graph, AdjacencyListGraphNode + >>> from pydatastructs import all_pair_shortest_paths + >>> V1 = AdjacencyListGraphNode("V1") + >>> V2 = AdjacencyListGraphNode("V2") + >>> V3 = AdjacencyListGraphNode("V3") + >>> G = Graph(V1, V2, V3) + >>> G.add_edge('V2', 'V3', 10) + >>> G.add_edge('V1', 'V2', 11) + >>> G.add_edge('V3', 'V1', 5) + >>> dist, _ = all_pair_shortest_paths(G, 'floyd_warshall') + >>> dist['V1']['V3'] + 21 + >>> dist['V3']['V1'] + 5 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm + """ + import pydatastructs.graphs.algorithms as algorithms + func = "_" + algorithm + "_" + graph._impl + if not hasattr(algorithms, func): + raise NotImplementedError( + "Currently %s algorithm isn't implemented for " + "finding shortest paths in graphs."%(algorithm)) + return getattr(algorithms, func)(graph) + +def _floyd_warshall_adjacency_list(graph: Graph): + dist, next_vertex = dict(), dict() + V, E = graph.vertices, graph.edge_weights + + for v in V: + dist[v] = dict() + next_vertex[v] = dict() + + for name, edge in E.items(): + dist[edge.source.name][edge.target.name] = edge.value + next_vertex[edge.source.name][edge.target.name] = edge.source.name + + for v in V: + dist[v][v] = 0 + next_vertex[v][v] = v + + for k in V: + for i in V: + for j in V: + dist_i_j = dist.get(i, dict()).get(j, float('inf')) + dist_i_k = dist.get(i, dict()).get(k, float('inf')) + dist_k_j = dist.get(k, dict()).get(j, float('inf')) + next_i_k = next_vertex.get(i + '_' + k, None) + if dist_i_j > dist_i_k + dist_k_j: + dist[i][j] = dist_i_k + dist_k_j + next_vertex[i][j] = next_i_k + + return (dist, next_vertex) + +_floyd_warshall_adjacency_matrix = _floyd_warshall_adjacency_list def topological_sort(graph: Graph, algorithm: str) -> list: """ diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 5ebdb3b9f..45ad0f71e 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -262,7 +262,7 @@ def path_finder(curr_node, next_node, dest_node, parent, path): def test_shortest_paths(): - def _test_shortest_paths(ds, algorithm): + def _test_shortest_paths_positive_edges(ds, algorithm): import pydatastructs.utils.misc_util as utils GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") vertices = [GraphNode('S'), GraphNode('C'), @@ -288,10 +288,57 @@ def _test_shortest_paths(ds, algorithm): graph.add_edge('D', 'SLC', -10) assert raises(ValueError, lambda: shortest_paths(graph, 'bellman_ford', 'SLC')) - _test_shortest_paths("List", 'bellman_ford') - _test_shortest_paths("Matrix", 'bellman_ford') - _test_shortest_paths("List", 'dijkstra') - _test_shortest_paths("Matrix", 'dijkstra') + def _test_shortest_paths_negative_edges(ds, algorithm): + import pydatastructs.utils.misc_util as utils + GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") + vertices = [GraphNode('s'), GraphNode('a'), + GraphNode('b'), GraphNode('c'), + GraphNode('d')] + + graph = Graph(*vertices) + graph.add_edge('s', 'a', 3) + graph.add_edge('s', 'b', 2) + graph.add_edge('a', 'c', 1) + graph.add_edge('b', 'd', 1) + graph.add_edge('b', 'a', -2) + graph.add_edge('c', 'd', 1) + dist, pred = shortest_paths(graph, algorithm, 's') + assert dist == {'s': 0, 'a': 0, 'b': 2, 'c': 1, 'd': 2} + assert pred == {'s': None, 'a': 'b', 'b': 's', 'c': 'a', 'd': 'c'} + dist, pred = shortest_paths(graph, algorithm, 's', 'd') + assert dist == 2 + assert pred == {'s': None, 'a': 'b', 'b': 's', 'c': 'a', 'd': 'c'} + + _test_shortest_paths_positive_edges("List", 'bellman_ford') + _test_shortest_paths_positive_edges("Matrix", 'bellman_ford') + _test_shortest_paths_negative_edges("List", 'bellman_ford') + _test_shortest_paths_negative_edges("Matrix", 'bellman_ford') + _test_shortest_paths_positive_edges("List", 'dijkstra') + _test_shortest_paths_positive_edges("Matrix", 'dijkstra') + +def test_all_pair_shortest_paths(): + + def _test_shortest_paths_negative_edges(ds, algorithm): + import pydatastructs.utils.misc_util as utils + GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") + vertices = [GraphNode('1'), GraphNode('2'), + GraphNode('3'), GraphNode('4')] + + graph = Graph(*vertices) + graph.add_edge('1', '3', -2) + graph.add_edge('2', '1', 4) + graph.add_edge('2', '3', 3) + graph.add_edge('3', '4', 2) + graph.add_edge('4', '2', -1) + dist, next_v = shortest_paths(graph, algorithm, 's') + assert dist == {'1': {'3': -2, '1': 0, '4': 0, '2': -1}, + '2': {'1': 4, '3': 2, '2': 0, '4': 4}, + '3': {'4': 2, '3': 0, '1': 5, '2': 1}, + '4': {'2': -1, '4': 0, '1': 3, '3': 1}} + assert next_v == {'1': {'3': '1', '1': '1', '4': None, '2': None}, + '2': {'1': '2', '3': None, '2': '2', '4': None}, + '3': {'4': '3', '3': '3', '1': None, '2': None}, + '4': {'2': '4', '4': '4', '1': None, '3': None}} def test_topological_sort():