From 4452dc8ad82db581f528e01f3cdc6a018ce76453 Mon Sep 17 00:00:00 2001 From: Harsheet Kakar <42893005+HarsheetKakar@users.noreply.github.com> Date: Thu, 9 Apr 2020 20:31:04 +0530 Subject: [PATCH] DFS implemented (#253) --- pydatastructs/graphs/__init__.py | 3 +- pydatastructs/graphs/algorithms.py | 93 ++++++++++++++++++- pydatastructs/graphs/tests/test_algorithms.py | 75 ++++++++++++++- 3 files changed, 167 insertions(+), 4 deletions(-) diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py index 1e2eebfe1..d9aa51af0 100644 --- a/pydatastructs/graphs/__init__.py +++ b/pydatastructs/graphs/__init__.py @@ -12,7 +12,8 @@ breadth_first_search_parallel, minimum_spanning_tree, minimum_spanning_tree_parallel, - strongly_connected_components + strongly_connected_components, + depth_first_search ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index a325c2396..39a470c7e 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -2,7 +2,7 @@ Contains all the algorithms associated with graph data structure. """ -from collections import deque as Queue +from collections import deque from concurrent.futures import ThreadPoolExecutor from pydatastructs.utils import GraphEdge from pydatastructs.utils.misc_util import _comp @@ -16,9 +16,12 @@ 'breadth_first_search_parallel', 'minimum_spanning_tree', 'minimum_spanning_tree_parallel', - 'strongly_connected_components' + 'strongly_connected_components', + 'depth_first_search' ] +Stack = Queue = deque + def breadth_first_search( graph, source_node, operation, *args, **kwargs): """ @@ -548,3 +551,89 @@ def strongly_connected_components(graph, algorithm): "isn't implemented for finding strongly connected components." %(algorithm, graph._impl)) return getattr(algorithms, func)(graph) + +def depth_first_search( + graph, source_node, operation, *args, **kwargs): + """ + Implementation of depth first search (DFS) + algorithm. + + Parameters + ========== + + graph: Graph + The graph on which DFS is to be performed. + source_node: str + The name of the source node from where the DFS is + to be initiated. + operation: function + The function which is to be applied + on every node when it is visited. + The prototype which is to be followed is, + `function_name(curr_node, next_node, + arg_1, arg_2, . . ., arg_n)`. + Here, the first two arguments denote, the + current node and the node next to current node. + The rest of the arguments are optional and you can + provide your own stuff there. + + Note + ==== + + You should pass all the arguments which you are going + to use in the prototype of your `operation` after + passing the operation function. + + Examples + ======== + + >>> from pydatastructs import Graph, AdjacencyListGraphNode + >>> V1 = AdjacencyListGraphNode("V1") + >>> V2 = AdjacencyListGraphNode("V2") + >>> V3 = AdjacencyListGraphNode("V3") + >>> G = Graph(V1, V2, V3) + >>> from pydatastructs import depth_first_search + >>> def f(curr_node, next_node, dest_node): + ... return curr_node != dest_node + ... + >>> G.add_edge(V1.name, V2.name) + >>> G.add_edge(V2.name, V3.name) + >>> depth_first_search(G, V1.name, f, V3.name) + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Depth-first_search + """ + import pydatastructs.graphs.algorithms as algorithms + func = "_depth_first_search_" + graph._impl + if not hasattr(algorithms, func): + raise NotImplementedError( + "Currently depth first search isn't implemented for " + "%s graphs."%(graph._impl)) + return getattr(algorithms, func)( + graph, source_node, operation, *args, **kwargs) + +def _depth_first_search_adjacency_list( + graph, source_node, operation, *args, **kwargs): + dfs_stack = Stack() + visited = dict() + dfs_stack.append(source_node) + visited[source_node] = True + while len(dfs_stack) != 0: + curr_node = dfs_stack.pop() + next_nodes = graph.neighbors(curr_node) + if len(next_nodes) != 0: + for next_node in next_nodes: + if next_node.name not in visited: + status = operation(curr_node, next_node.name, *args, **kwargs) + if not status: + return None + dfs_stack.append(next_node.name) + visited[next_node.name] = True + else: + status = operation(curr_node, "", *args, **kwargs) + if not status: + return None + +_depth_first_search_adjacency_matrix = _depth_first_search_adjacency_list diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 8ada73cc8..ab1ecdc47 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -1,6 +1,7 @@ from pydatastructs import (breadth_first_search, Graph, breadth_first_search_parallel, minimum_spanning_tree, -minimum_spanning_tree_parallel, strongly_connected_components) +minimum_spanning_tree_parallel, strongly_connected_components, +depth_first_search) def test_breadth_first_search(): @@ -185,3 +186,75 @@ def _test_strongly_connected_components(func, ds, algorithm, *args): scc = strongly_connected_components _test_strongly_connected_components(scc, "List", "kosaraju") _test_strongly_connected_components(scc, "Matrix", "kosaraju") + +def test_depth_first_search(): + + def _test_depth_first_search(ds): + import pydatastructs.utils.misc_util as utils + GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") + + V1 = GraphNode(0) + V2 = GraphNode(1) + V3 = GraphNode(2) + + G1 = Graph(V1, V2, V3) + + edges = [ + (V1.name, V2.name), + (V2.name, V3.name), + (V1.name, V3.name) + ] + + for edge in edges: + G1.add_edge(*edge) + + parent = dict() + def dfs_tree(curr_node, next_node, parent): + if next_node != "": + parent[next_node] = curr_node + return True + + depth_first_search(G1, V1.name, dfs_tree, parent) + assert (parent[V3.name] == V1.name and parent[V2.name] == V1.name) or \ + (parent[V3.name] == V2.name and parent[V2.name] == V1.name) + + V4 = GraphNode(0) + V5 = GraphNode(1) + V6 = GraphNode(2) + V7 = GraphNode(3) + V8 = GraphNode(4) + + edges = [ + (V4.name, V5.name), + (V5.name, V6.name), + (V6.name, V7.name), + (V6.name, V4.name), + (V7.name, V8.name) + ] + + G2 = Graph(V4, V5, V6, V7, V8) + + for edge in edges: + G2.add_edge(*edge) + + path = [] + def path_finder(curr_node, next_node, dest_node, parent, path): + if next_node != "": + parent[next_node] = curr_node + if curr_node == dest_node: + node = curr_node + path.append(node) + while node is not None: + if parent.get(node, None) is not None: + path.append(parent[node]) + node = parent.get(node, None) + path.reverse() + return False + return True + + parent.clear() + depth_first_search(G2, V4.name, path_finder, V7.name, parent, path) + assert path == [V4.name, V5.name, V6.name, V7.name] + + _test_depth_first_search("List") + _test_depth_first_search("Matrix")