From cce3e59e5c86507a534ef6fa56305a3ee163a4ee Mon Sep 17 00:00:00 2001 From: FSou1 Date: Wed, 1 Jul 2020 13:03:28 -0500 Subject: [PATCH] Added depth first search algorithm --- .../graph/breadth-first-search/README.md | 11 ++- .../breadthFirstSearch.ts | 2 +- .../graph/depth-first-search/README.md | 20 +++++ .../__tests__/depthFirstSearch.test.ts | 51 ++++++++++++ .../depth-first-search/depthFirstSearch.ts | 80 +++++++++++++++++++ 5 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/algorithms/graph/depth-first-search/README.md create mode 100644 src/algorithms/graph/depth-first-search/__tests__/depthFirstSearch.test.ts create mode 100644 src/algorithms/graph/depth-first-search/depthFirstSearch.ts diff --git a/src/algorithms/graph/breadth-first-search/README.md b/src/algorithms/graph/breadth-first-search/README.md index fe9b614..7c9c057 100644 --- a/src/algorithms/graph/breadth-first-search/README.md +++ b/src/algorithms/graph/breadth-first-search/README.md @@ -1,6 +1,15 @@ # Breadth-first search -TBD. +Breadth-first search (BFS) is an algorithm for traversing or searching tree or graph data structures. It starts at the tree root (or some arbitrary node of a graph, sometimes referred to as a 'search key'), and explores all of the neighbor nodes at the present depth prior to moving on to the nodes at the next depth level. + +## Applications + +* Shortest path - in an unweighted graph, the shortest path is the path with least number of edges. With Breadth First, we always reach a vertex from given source using the minimum number of edges +* Peer to peer networks - find all neighbor nodes +* Crawlers in search engines - build index, start from source page and follow all links +* Social networks - find people within a given distance 'k' +* Broadcasting in networks - find all nodes to be reached by a broadcasting packet +* Path finding - find if there is a path between two vertices ## Complexity diff --git a/src/algorithms/graph/breadth-first-search/breadthFirstSearch.ts b/src/algorithms/graph/breadth-first-search/breadthFirstSearch.ts index d135d33..aec53b1 100644 --- a/src/algorithms/graph/breadth-first-search/breadthFirstSearch.ts +++ b/src/algorithms/graph/breadth-first-search/breadthFirstSearch.ts @@ -34,7 +34,7 @@ export function breadthFirstSearch( const currentVertex = queue.dequeue(); config.enterVertex(currentVertex); - const neighbors = currentVertex.getNeighbors(); + const neighbors = graph.getNeighbors(currentVertex); for (const neighbor of neighbors) { if (config.allowEnterVertex(neighbor)) { queue.enqueue(neighbor); diff --git a/src/algorithms/graph/depth-first-search/README.md b/src/algorithms/graph/depth-first-search/README.md new file mode 100644 index 0000000..b34bf66 --- /dev/null +++ b/src/algorithms/graph/depth-first-search/README.md @@ -0,0 +1,20 @@ +# Breadth-first search + +Depth-first search (DFS) is an algorithm for traversing or searching tree or graph data structures. The algorithm starts at the root node (selecting some arbitrary node as the root node in the case of a graph) and explores as far as possible along each branch before backtracking. + +## Applications + +* Topological sort +* Cycle detection +* Path finding +* Solving puzzles with the only solution (e.g. mazes) + +## Complexity + +**Time Complexity**: TBD. + +**Space Complexity**: TBD. + +## References + +- [Youtube](https://www.youtube.com/watch?v=AfSk24UTFS8); \ No newline at end of file diff --git a/src/algorithms/graph/depth-first-search/__tests__/depthFirstSearch.test.ts b/src/algorithms/graph/depth-first-search/__tests__/depthFirstSearch.test.ts new file mode 100644 index 0000000..69ddaba --- /dev/null +++ b/src/algorithms/graph/depth-first-search/__tests__/depthFirstSearch.test.ts @@ -0,0 +1,51 @@ +import {Graph} from '../../../../data-structures/graph/graph'; +import {GraphVertex} from '../../../../data-structures/graph/graphVertex'; +import {GraphEdge} from '../../../../data-structures/graph/graphEdge'; +import {depthFirstSearch} from '../depthFirstSearch'; + +test('should perform BFS on graph', () => { + const graph = new Graph(true); + + const vertexA = new GraphVertex('A'); + const vertexB = new GraphVertex('B'); + const vertexC = new GraphVertex('C'); + const vertexD = new GraphVertex('D'); + const vertexE = new GraphVertex('E'); + const vertexF = new GraphVertex('F'); + const vertexG = new GraphVertex('G'); + const vertexH = new GraphVertex('H'); + + const edgeAB = new GraphEdge(vertexA, vertexB); + const edgeBC = new GraphEdge(vertexB, vertexC); + const edgeCG = new GraphEdge(vertexC, vertexG); + const edgeAD = new GraphEdge(vertexA, vertexD); + const edgeAE = new GraphEdge(vertexA, vertexE); + const edgeEF = new GraphEdge(vertexE, vertexF); + const edgeDH = new GraphEdge(vertexD, vertexH); + + graph + .addEdge(edgeAB) + .addEdge(edgeBC) + .addEdge(edgeCG) + .addEdge(edgeAD) + .addEdge(edgeAE) + .addEdge(edgeEF) + .addEdge(edgeDH); + + expect(graph.toString()).toBe('A,B,C,G,D,E,F,H'); + + const enterVertexCallback = jest.fn(); + const leaveVertexCallback = jest.fn(); + + // Traverse graphs without callbacks first. + depthFirstSearch(graph, vertexA); + + // Traverse graph with enterVertex and leaveVertex callbacks. + depthFirstSearch(graph, vertexA, { + enterVertex: enterVertexCallback, + leaveVertex: leaveVertexCallback, + }); + + expect(enterVertexCallback).toHaveBeenCalledTimes(8); + expect(leaveVertexCallback).toHaveBeenCalledTimes(8); +}); diff --git a/src/algorithms/graph/depth-first-search/depthFirstSearch.ts b/src/algorithms/graph/depth-first-search/depthFirstSearch.ts new file mode 100644 index 0000000..78000b0 --- /dev/null +++ b/src/algorithms/graph/depth-first-search/depthFirstSearch.ts @@ -0,0 +1,80 @@ +/* eslint-disable no-unused-vars */ +import {Graph} from '../../../data-structures/graph/graph'; +import {GraphVertex} from '../../../data-structures/graph/graphVertex'; +import {GraphConfig} from '../../../data-structures/graph/graphConfig'; + +/** + * + * + * @export + * @param {Graph} graph + * @param {GraphVertex} startVertex + * @param {GraphConfig} config + */ +export function depthFirstSearch( + graph: Graph, + startVertex: GraphVertex, + config: GraphConfig = null, +): void { + if (!graph) { + return; + } + + if (!graph.getVertex(startVertex)) { + return; + } + + config = initConfig(config); + depthFirstSearchRecursive(graph, startVertex, config); +} + +/** + * + * + * @param {Graph} graph + * @param {GraphVertex} currentVertex + * @param {GraphConfig} config + */ +function depthFirstSearchRecursive( + graph: Graph, + currentVertex: GraphVertex, + config: GraphConfig, +) { + config.enterVertex(currentVertex); + + const neighbors = graph.getNeighbors(currentVertex); + for (const neighbor of neighbors) { + depthFirstSearch(graph, neighbor, config); + } + + config.leaveVertex(currentVertex); +} + +/** + * @param {GraphConfig} config + * @return {GraphConfig} + */ +function initConfig(config: GraphConfig): GraphConfig { + config = config || ({} as GraphConfig); + config.enterVertex = config.enterVertex || ((vertex: GraphVertex) => {}); + config.leaveVertex = config.leaveVertex || ((vertex: GraphVertex) => {}); + config.allowEnterVertex = config.allowEnterVertex || isFirstEnter(); + return config; +} + +/** + * @param {GraphVertex} vertex + * @return {boolean} + */ +function isFirstEnter(): (vertex: GraphVertex) => boolean { + const visited = {}; + + return (next: GraphVertex) => { + if (!visited[next.getKey()]) { + visited[next.getKey()] = true; + return true; + } + + return false; + }; +}