Add BFS and DFS for tree.

This commit is contained in:
Oleksii Trekhleb 2018-04-25 17:35:44 +03:00
parent b175aabf8d
commit 85585769ea
8 changed files with 346 additions and 6 deletions

View File

@ -55,9 +55,12 @@
* [Merge Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/merge-sort)
* [Quicksort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/quick-sort)
* [Shellsort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/shell-sort)
* **Tree**
* [Depth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/depth-first-search) (DFS)
* [Breadth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/breadth-first-search) (BFS)
* **Graph**
* [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)
* [Depth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/depth-first-search) (DFS)
* [Breadth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search) (BFS)
* Detect Cycle
* Topological Sorting
* Dijkstra Algorithm to Find Shortest Path
@ -65,9 +68,6 @@
* Bellman Ford
* Strongly Connected Component algorithm
* Shortest Path Faster Algorithm (SPFA)
* **Tree**
* Depth-First Search (DFS)
* Breadth-First Search (BFS)
* **Minimum Spanning Tree**
* Prims algorithm
* Kruskals algorithm

View File

@ -39,7 +39,7 @@ describe('depthFirstSearch', () => {
const enterVertexCallback = jest.fn();
const leaveVertexCallback = jest.fn();
// Traverse graphs without callbacks first.
// Traverse graphs without callbacks first to check default ones.
depthFirstSearch(graph, vertexA);
// Traverse graph with enterVertex and leaveVertex callbacks.

View File

@ -0,0 +1,13 @@
# 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.
![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/5/5d/Breadth-First-Search-Algorithm.gif)
## References
[Wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search)

View File

@ -0,0 +1,102 @@
import BinaryTreeNode from '../../../../data-structures/tree/BinaryTreeNode';
import breadthFirstSearch from '../breadthFirstSearch';
describe('breadthFirstSearch', () => {
it('should perform DFS operation on tree', () => {
const nodeA = new BinaryTreeNode('A');
const nodeB = new BinaryTreeNode('B');
const nodeC = new BinaryTreeNode('C');
const nodeD = new BinaryTreeNode('D');
const nodeE = new BinaryTreeNode('E');
const nodeF = new BinaryTreeNode('F');
const nodeG = new BinaryTreeNode('G');
nodeA.setLeft(nodeB).setRight(nodeC);
nodeB.setLeft(nodeD).setRight(nodeE);
nodeC.setLeft(nodeF).setRight(nodeG);
// In-order traversing.
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');
const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();
// Traverse tree without callbacks first to check default ones.
breadthFirstSearch(nodeA);
// Traverse tree with callbacks.
breadthFirstSearch(nodeA, {
enterNode: enterNodeCallback,
leaveNode: leaveNodeCallback,
});
expect(enterNodeCallback).toHaveBeenCalledTimes(7);
expect(leaveNodeCallback).toHaveBeenCalledTimes(7);
// Check node entering.
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('B');
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('C');
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('D');
expect(enterNodeCallback.mock.calls[4][0].value).toEqual('E');
expect(enterNodeCallback.mock.calls[5][0].value).toEqual('F');
expect(enterNodeCallback.mock.calls[6][0].value).toEqual('G');
// Check node leaving.
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('B');
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('C');
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('D');
expect(leaveNodeCallback.mock.calls[4][0].value).toEqual('E');
expect(leaveNodeCallback.mock.calls[5][0].value).toEqual('F');
expect(leaveNodeCallback.mock.calls[6][0].value).toEqual('G');
});
it('allow users to redefine node visiting logic', () => {
const nodeA = new BinaryTreeNode('A');
const nodeB = new BinaryTreeNode('B');
const nodeC = new BinaryTreeNode('C');
const nodeD = new BinaryTreeNode('D');
const nodeE = new BinaryTreeNode('E');
const nodeF = new BinaryTreeNode('F');
const nodeG = new BinaryTreeNode('G');
nodeA.setLeft(nodeB).setRight(nodeC);
nodeB.setLeft(nodeD).setRight(nodeE);
nodeC.setLeft(nodeF).setRight(nodeG);
// In-order traversing.
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');
const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();
// Traverse tree without callbacks first to check default ones.
breadthFirstSearch(nodeA);
// Traverse tree with callbacks.
breadthFirstSearch(nodeA, {
allowTraversal: (node, child) => {
// Forbid traversing left half of the tree.
return child.value !== 'B';
},
enterNode: enterNodeCallback,
leaveNode: leaveNodeCallback,
});
expect(enterNodeCallback).toHaveBeenCalledTimes(4);
expect(leaveNodeCallback).toHaveBeenCalledTimes(4);
// Check node entering.
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('C');
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('F');
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('G');
// Check node leaving.
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('C');
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('F');
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('G');
});
});

View File

@ -0,0 +1,58 @@
import Queue from '../../../data-structures/queue/Queue';
/**
* @typedef {Object} Callbacks
* @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal -
* Determines whether DFS should traverse from the node to its child.
* @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node.
* @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node.
*/
/**
* @param {Callbacks} [callbacks]
* @returns {Callbacks}
*/
function initCallbacks(callbacks = {}) {
const initiatedCallback = callbacks;
const stubCallback = () => {};
const defaultAllowTraversal = () => true;
initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal;
initiatedCallback.enterNode = callbacks.enterNode || stubCallback;
initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback;
return initiatedCallback;
}
/**
* @param {BinaryTreeNode} rootNode
* @param {Callbacks} [originalCallbacks]
*/
export default function breadthFirstSearch(rootNode, originalCallbacks) {
const callbacks = initCallbacks(originalCallbacks);
const nodeQueue = new Queue();
// Do initial queue setup.
nodeQueue.enqueue(rootNode);
while (!nodeQueue.isEmpty()) {
const currentNode = nodeQueue.dequeue();
callbacks.enterNode(currentNode);
// Add all children to the queue for future traversals.
// Traverse left branch.
if (currentNode.left && callbacks.allowTraversal(currentNode, currentNode.left)) {
nodeQueue.enqueue(currentNode.left);
}
// Traverse right branch.
if (currentNode.right && callbacks.allowTraversal(currentNode, currentNode.right)) {
nodeQueue.enqueue(currentNode.right);
}
callbacks.leaveNode(currentNode);
}
}

View File

@ -0,0 +1,13 @@
# 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.
![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/7/7f/Depth-First-Search.gif)
## References
[Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search)

View File

@ -0,0 +1,102 @@
import BinaryTreeNode from '../../../../data-structures/tree/BinaryTreeNode';
import depthFirstSearch from '../depthFirstSearch';
describe('depthFirstSearch', () => {
it('should perform DFS operation on tree', () => {
const nodeA = new BinaryTreeNode('A');
const nodeB = new BinaryTreeNode('B');
const nodeC = new BinaryTreeNode('C');
const nodeD = new BinaryTreeNode('D');
const nodeE = new BinaryTreeNode('E');
const nodeF = new BinaryTreeNode('F');
const nodeG = new BinaryTreeNode('G');
nodeA.setLeft(nodeB).setRight(nodeC);
nodeB.setLeft(nodeD).setRight(nodeE);
nodeC.setLeft(nodeF).setRight(nodeG);
// In-order traversing.
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');
const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();
// Traverse tree without callbacks first to check default ones.
depthFirstSearch(nodeA);
// Traverse tree with callbacks.
depthFirstSearch(nodeA, {
enterNode: enterNodeCallback,
leaveNode: leaveNodeCallback,
});
expect(enterNodeCallback).toHaveBeenCalledTimes(7);
expect(leaveNodeCallback).toHaveBeenCalledTimes(7);
// Check node entering.
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('B');
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('D');
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('E');
expect(enterNodeCallback.mock.calls[4][0].value).toEqual('C');
expect(enterNodeCallback.mock.calls[5][0].value).toEqual('F');
expect(enterNodeCallback.mock.calls[6][0].value).toEqual('G');
// Check node leaving.
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('D');
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('E');
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('B');
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('F');
expect(leaveNodeCallback.mock.calls[4][0].value).toEqual('G');
expect(leaveNodeCallback.mock.calls[5][0].value).toEqual('C');
expect(leaveNodeCallback.mock.calls[6][0].value).toEqual('A');
});
it('allow users to redefine node visiting logic', () => {
const nodeA = new BinaryTreeNode('A');
const nodeB = new BinaryTreeNode('B');
const nodeC = new BinaryTreeNode('C');
const nodeD = new BinaryTreeNode('D');
const nodeE = new BinaryTreeNode('E');
const nodeF = new BinaryTreeNode('F');
const nodeG = new BinaryTreeNode('G');
nodeA.setLeft(nodeB).setRight(nodeC);
nodeB.setLeft(nodeD).setRight(nodeE);
nodeC.setLeft(nodeF).setRight(nodeG);
// In-order traversing.
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');
const enterNodeCallback = jest.fn();
const leaveNodeCallback = jest.fn();
// Traverse tree without callbacks first to check default ones.
depthFirstSearch(nodeA);
// Traverse tree with callbacks.
depthFirstSearch(nodeA, {
allowTraversal: (node, child) => {
// Forbid traversing left part of the tree.
return child.value !== 'B';
},
enterNode: enterNodeCallback,
leaveNode: leaveNodeCallback,
});
expect(enterNodeCallback).toHaveBeenCalledTimes(4);
expect(leaveNodeCallback).toHaveBeenCalledTimes(4);
// Check node entering.
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('C');
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('F');
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('G');
// Check node leaving.
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('F');
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('G');
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('C');
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('A');
});
});

View File

@ -0,0 +1,52 @@
/**
* @typedef {Object} Callbacks
* @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal -
* Determines whether DFS should traverse from the node to its child.
* @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node.
* @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node.
*/
/**
* @param {Callbacks} [callbacks]
* @returns {Callbacks}
*/
function initCallbacks(callbacks = {}) {
const initiatedCallback = callbacks;
const stubCallback = () => {};
const defaultAllowTraversal = () => true;
initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal;
initiatedCallback.enterNode = callbacks.enterNode || stubCallback;
initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback;
return initiatedCallback;
}
/**
* @param {BinaryTreeNode} node
* @param {Callbacks} callbacks
*/
export function depthFirstSearchRecursive(node, callbacks) {
callbacks.enterNode(node);
// Traverse left branch.
if (node.left && callbacks.allowTraversal(node, node.left)) {
depthFirstSearchRecursive(node.left, callbacks);
}
// Traverse right branch.
if (node.right && callbacks.allowTraversal(node, node.right)) {
depthFirstSearchRecursive(node.right, callbacks);
}
callbacks.leaveNode(node);
}
/**
* @param {BinaryTreeNode} rootNode
* @param {Callbacks} [callbacks]
*/
export default function depthFirstSearch(rootNode, callbacks) {
depthFirstSearchRecursive(rootNode, initCallbacks(callbacks));
}