This commit is contained in:
Oleksii Trekhleb 2018-04-11 09:52:04 +03:00
parent ddd7f9fe0d
commit 7656cfd6ea
4 changed files with 179 additions and 0 deletions

View File

@ -25,6 +25,8 @@
* [Power Set](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/math/power-set)
* String
* [String Permutations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/permutations)
* Graph
* [Depth-First Search (DFS)](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/depth-first-search)
## Useful Links

View File

@ -0,0 +1,7 @@
# Depth-First Search (DFS)
Depth-first search (DFS) is an algorithm for traversing or
searching tree or graph data structures. One starts at
the root (selecting some arbitrary node as the root in
the case of a graph) and explores as far as possible
along each branch before backtracking.

View File

@ -0,0 +1,107 @@
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';
describe('depthFirstSearch', () => {
it('should perform DFS operation 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 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 edgeFD = new GraphEdge(vertexF, vertexD);
const edgeDG = new GraphEdge(vertexD, vertexG);
graph
.addEdge(edgeAB)
.addEdge(edgeBC)
.addEdge(edgeCG)
.addEdge(edgeAD)
.addEdge(edgeAE)
.addEdge(edgeEF)
.addEdge(edgeFD)
.addEdge(edgeDG);
expect(graph.toString()).toBe('A,B,C,G,D,E,F');
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(7);
expect(leaveVertexCallback).toHaveBeenCalledTimes(7);
expect(enterVertexCallback.mock.calls.toString()).toBe('A,B,C,G,D,E,F');
expect(leaveVertexCallback.mock.calls.toString()).toBe('G,C,B,D,F,E,A');
});
it('allow users to redefine vertex visiting logic', () => {
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 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 edgeFD = new GraphEdge(vertexF, vertexD);
const edgeDG = new GraphEdge(vertexD, vertexG);
graph
.addEdge(edgeAB)
.addEdge(edgeBC)
.addEdge(edgeCG)
.addEdge(edgeAD)
.addEdge(edgeAE)
.addEdge(edgeEF)
.addEdge(edgeFD)
.addEdge(edgeDG);
expect(graph.toString()).toBe('A,B,C,G,D,E,F');
const enterVertexCallback = jest.fn();
const leaveVertexCallback = jest.fn();
depthFirstSearch(graph, vertexA, {
enterVertex: enterVertexCallback,
leaveVertex: leaveVertexCallback,
allowTraversal: (vertex, neighbor) => {
return !(vertex === vertexA && neighbor === vertexB);
},
});
expect(enterVertexCallback).toHaveBeenCalledTimes(7);
expect(leaveVertexCallback).toHaveBeenCalledTimes(7);
expect(enterVertexCallback.mock.calls.toString()).toBe('A,D,G,E,F,D,G');
expect(leaveVertexCallback.mock.calls.toString()).toBe('G,D,G,D,F,E,A');
});
});

View File

@ -0,0 +1,63 @@
/**
* @typedef {Object} Callbacks
* @property {function(vertex: GraphVertex, neighbor: GraphVertex): boolean} allowTraversal -
* Determines whether DFS should traverse from the vertex to its neighbor
* (along the edge). By default prohibits visiting the same vertex again.
* @property {function(vertex: GraphVertex)} enterVertex - Called when DFS enters the vertex.
* @property {function(vertex: GraphVertex)} leaveVertex - Called when DFS leaves the vertex.
*/
/**
* @param {Callbacks} [callbacks]
* @returns {Callbacks}
*/
function initCallbacks(callbacks = {}) {
const initiatedCallback = callbacks;
const stubCallback = () => {};
const allowTraversalCallback = (
() => {
const seen = {};
return (vertex, neighbor) => {
if (!seen[neighbor.getKey()]) {
seen[neighbor.getKey()] = true;
return true;
}
return false;
};
}
)();
initiatedCallback.allowTraversal = callbacks.allowTraversal || allowTraversalCallback;
initiatedCallback.enterVertex = callbacks.enterVertex || stubCallback;
initiatedCallback.leaveVertex = callbacks.leaveVertex || stubCallback;
return initiatedCallback;
}
/**
* @param {Graph} graph
* @param {GraphVertex} vertex
* @param {Callbacks} callbacks
*/
function depthFirstSearchRecursive(graph, vertex, callbacks) {
callbacks.enterVertex(vertex);
graph.getNeighbors(vertex).forEach((neighbor) => {
if (callbacks.allowTraversal(vertex, neighbor)) {
depthFirstSearchRecursive(graph, neighbor, callbacks);
}
});
callbacks.leaveVertex(vertex);
}
/**
* @param {Graph} graph
* @param {GraphVertex} startVertex
* @param {Callbacks} [callbacks]
*/
export default function depthFirstSearch(graph, startVertex, callbacks) {
depthFirstSearchRecursive(graph, startVertex, initCallbacks(callbacks));
}