This commit is contained in:
Oleksii Trekhleb 2018-04-11 15:57:41 +03:00
parent 62d9747495
commit 6f9600aaa7
5 changed files with 189 additions and 9 deletions

View File

@ -27,6 +27,7 @@
* [String Permutations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/permutations) * [String Permutations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/permutations)
* Graph * Graph
* [Depth-First Search (DFS)](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/depth-first-search) * [Depth-First Search (DFS)](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/depth-first-search)
* [Breadth-First Search (BFS)](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search)
## Useful Links ## Useful Links

View File

@ -0,0 +1,7 @@
# Breadth-First Search (BFS)
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 the neighbor
nodes first, before moving to the next level neighbors.

View File

@ -0,0 +1,114 @@
import Graph from '../../../../data-structures/graph/Graph';
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
import breadthFirstSearch from '../breadthFirstSearch';
describe('breadthFirstSearch', () => {
it('should perform BFS 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 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 edgeFD = new GraphEdge(vertexF, vertexD);
const edgeDH = new GraphEdge(vertexD, vertexH);
const edgeGH = new GraphEdge(vertexG, vertexH);
graph
.addEdge(edgeAB)
.addEdge(edgeBC)
.addEdge(edgeCG)
.addEdge(edgeAD)
.addEdge(edgeAE)
.addEdge(edgeEF)
.addEdge(edgeFD)
.addEdge(edgeDH)
.addEdge(edgeGH);
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.
breadthFirstSearch(graph, vertexA);
// Traverse graph with enterVertex and leaveVertex callbacks.
breadthFirstSearch(graph, vertexA, {
enterVertex: enterVertexCallback,
leaveVertex: leaveVertexCallback,
});
expect(enterVertexCallback).toHaveBeenCalledTimes(8);
expect(leaveVertexCallback).toHaveBeenCalledTimes(8);
expect(enterVertexCallback.mock.calls.toString()).toBe('A,B,D,E,C,H,F,G');
expect(leaveVertexCallback.mock.calls.toString()).toBe('A,B,D,E,C,H,F,G');
});
it('should allow to create custom 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 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 edgeFD = new GraphEdge(vertexF, vertexD);
const edgeDH = new GraphEdge(vertexD, vertexH);
const edgeGH = new GraphEdge(vertexG, vertexH);
graph
.addEdge(edgeAB)
.addEdge(edgeBC)
.addEdge(edgeCG)
.addEdge(edgeAD)
.addEdge(edgeAE)
.addEdge(edgeEF)
.addEdge(edgeFD)
.addEdge(edgeDH)
.addEdge(edgeGH);
expect(graph.toString()).toBe('A,B,C,G,D,E,F,H');
const enterVertexCallback = jest.fn();
const leaveVertexCallback = jest.fn();
// Traverse graph with enterVertex and leaveVertex callbacks.
breadthFirstSearch(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,E,H,F,D,H');
expect(leaveVertexCallback.mock.calls.toString()).toBe('A,D,E,H,F,D,H');
});
});

View File

@ -0,0 +1,67 @@
import Queue from '../../../data-structures/queue/Queue';
/**
* @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} startVertex
* @param {Callbacks} [rawCallbacks]
*/
export default function breadthFirstSearch(graph, startVertex, rawCallbacks) {
const callbacks = initCallbacks(rawCallbacks);
const vertexQueue = new Queue();
// Do initial queue setup.
vertexQueue.enqueue(startVertex);
// Traverse all vertices from the queue.
while (!vertexQueue.isEmpty()) {
const currentVertex = vertexQueue.dequeue();
callbacks.enterVertex(currentVertex);
// Add all neighbors to the queue for future traversals.
graph.getNeighbors(currentVertex).forEach((neighbor) => {
if (callbacks.allowTraversal(currentVertex, neighbor)) {
vertexQueue.enqueue(neighbor);
}
});
callbacks.leaveVertex(currentVertex);
}
}

View File

@ -5,12 +5,3 @@ searching tree or graph data structures. One starts at
the root (selecting some arbitrary node as the root in the root (selecting some arbitrary node as the root in
the case of a graph) and explores as far as possible the case of a graph) and explores as far as possible
along each branch before backtracking. along each branch before backtracking.
## Complexity
* Time: O(|V| + |E|)
* Space: O(|V|)
## References
[Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search)