diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py index bdfebdc72..ad6006342 100644 --- a/pydatastructs/graphs/__init__.py +++ b/pydatastructs/graphs/__init__.py @@ -17,7 +17,8 @@ shortest_paths, all_pair_shortest_paths, topological_sort, - topological_sort_parallel + topological_sort_parallel, + max_flow ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 234255008..85a0c6ae7 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -22,7 +22,8 @@ 'shortest_paths', 'all_pair_shortest_paths', 'topological_sort', - 'topological_sort_parallel' + 'topological_sort_parallel', + 'max_flow' ] Stack = Queue = deque @@ -1064,3 +1065,56 @@ def _job(graph: Graph, u: str): if len(L) != num_vertices: raise ValueError("Graph is not acyclic.") return L + + +def _breadth_first_search_max_flow(graph: Graph, source_node, sink_node, flow_passed): + bfs_queue = Queue() + parent, currentPathC = {}, {} + parent[source_node] = -2 + currentPathC[source_node] = float('inf') + bfs_queue.append(source_node) + while len(bfs_queue) != 0: + curr_node = bfs_queue.popleft() + next_nodes = graph.neighbors(curr_node) + if len(next_nodes) != 0: + for next_node in next_nodes: + capacity = graph.get_edge(curr_node, next_node.name).value + fp = flow_passed.get((curr_node, next_node.name), 0) + if capacity and parent.get(next_node.name, False) is False and capacity - fp> 0: + parent[next_node.name] = curr_node + next_flow = min(currentPathC[curr_node], capacity - fp) + currentPathC[next_node.name] = next_flow + if next_node.name == sink_node: + return (next_flow, parent) + bfs_queue.append(next_node.name) + return (0, parent) + + +def _max_flow_edmonds_karp_(graph: Graph, source, sink): + m_flow = 0 + flow_passed = {} + new_flow, parent = _breadth_first_search_max_flow(graph, source, sink, flow_passed) + while new_flow != 0: + m_flow += new_flow + current = sink + while current != source: + prev = parent[current] + fp = flow_passed.get((prev, current), 0) + flow_passed[(prev, current)] = fp + new_flow + fp = flow_passed.get((current, prev), 0) + flow_passed[(current, prev)] = fp - new_flow + current = prev + new_flow, parent = _breadth_first_search_max_flow(graph, source, sink, flow_passed) + return m_flow + +def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs): + raise_if_backend_is_not_python( + max_flow, kwargs.get('backend', Backend.PYTHON)) + + import pydatastructs.graphs.algorithms as algorithms + func = "_max_flow_" + algorithm + "_" + if not hasattr(algorithms, func): + raise NotImplementedError( + f"Currently {algorithm} algorithm isn't implemented for " + "performing max flow on graphs.") + return getattr(algorithms, func)(graph, source, sink) diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index ddf274d1d..af9d13036 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -2,7 +2,7 @@ breadth_first_search_parallel, minimum_spanning_tree, minimum_spanning_tree_parallel, strongly_connected_components, depth_first_search, shortest_paths, topological_sort, -topological_sort_parallel) +topological_sort_parallel, max_flow) from pydatastructs.utils.raises_util import raises def test_breadth_first_search(): @@ -369,3 +369,70 @@ def _test_topological_sort(func, ds, algorithm, threads=None): _test_topological_sort(topological_sort, "List", "kahn") _test_topological_sort(topological_sort_parallel, "List", "kahn", 3) + + +def test_max_flow(): + def _test_max_flow(ds, algorithm): + import pydatastructs.utils.misc_util as utils + GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") + + a = GraphNode('a') + b = GraphNode('b') + c = GraphNode('c') + d = GraphNode('d') + e = GraphNode('e') + + G = Graph(a, b, c, d, e) + + G.add_edge('a', 'b', 3) + G.add_edge('a', 'c', 4) + G.add_edge('b', 'c', 2) + G.add_edge('b', 'd', 3) + G.add_edge('c', 'd', 1) + G.add_edge('d', 'e', 6) + + assert max_flow(G, 'a', 'e', algorithm) == 4 + assert max_flow(G, 'a', 'c', algorithm) == 6 + + a = GraphNode('a') + b = GraphNode('b') + c = GraphNode('c') + d = GraphNode('d') + e = GraphNode('e') + f = GraphNode('f') + + G2 = Graph(a, b, c, d, e, f) + + G2.add_edge('a', 'b', 16) + G2.add_edge('a', 'c', 13) + G2.add_edge('b', 'c', 10) + G2.add_edge('b', 'd', 12) + G2.add_edge('c', 'b', 4) + G2.add_edge('c', 'e', 14) + G2.add_edge('d', 'c', 9) + G2.add_edge('d', 'f', 20) + G2.add_edge('e', 'd', 7) + G2.add_edge('e', 'f', 4) + + assert max_flow(G2, 'a', 'f', algorithm) == 23 + assert max_flow(G2, 'a', 'd', algorithm) == 19 + + a = GraphNode('a') + b = GraphNode('b') + c = GraphNode('c') + d = GraphNode('d') + + G3 = Graph(a, b, c, d) + + G3.add_edge('a', 'b', 3) + G3.add_edge('a', 'c', 2) + G3.add_edge('b', 'c', 2) + G3.add_edge('b', 'd', 3) + G3.add_edge('c', 'd', 2) + + assert max_flow(G3, 'a', 'd', algorithm) == 5 + assert max_flow(G3, 'a', 'b', algorithm) == 3 + + + _test_max_flow("List", "edmonds_karp") + _test_max_flow("Matrix", "edmonds_karp")