Skip to content

Commit 8558576

Browse files
committedApr 25, 2018
Add BFS and DFS for tree.
1 parent b175aab commit 8558576

File tree

8 files changed

+346
-6
lines changed

8 files changed

+346
-6
lines changed
 

‎README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,19 @@
5555
* [Merge Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/merge-sort)
5656
* [Quicksort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/quick-sort)
5757
* [Shellsort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/shell-sort)
58+
* **Tree**
59+
* [Depth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/depth-first-search) (DFS)
60+
* [Breadth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/breadth-first-search) (BFS)
5861
* **Graph**
59-
* [Depth-First Search (DFS)](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/depth-first-search)
60-
* [Breadth-First Search (BFS)](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search)
62+
* [Depth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/depth-first-search) (DFS)
63+
* [Breadth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search) (BFS)
6164
* Detect Cycle
6265
* Topological Sorting
6366
* Dijkstra Algorithm to Find Shortest Path
6467
* Eulerian path, Eulerian circuit
6568
* Bellman Ford
6669
* Strongly Connected Component algorithm
6770
* Shortest Path Faster Algorithm (SPFA)
68-
* **Tree**
69-
* Depth-First Search (DFS)
70-
* Breadth-First Search (BFS)
7171
* **Minimum Spanning Tree**
7272
* Prim’s algorithm
7373
* Kruskal’s algorithm

‎src/algorithms/graph/depth-first-search/__test__/depthFirstSearch.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('depthFirstSearch', () => {
3939
const enterVertexCallback = jest.fn();
4040
const leaveVertexCallback = jest.fn();
4141

42-
// Traverse graphs without callbacks first.
42+
// Traverse graphs without callbacks first to check default ones.
4343
depthFirstSearch(graph, vertexA);
4444

4545
// Traverse graph with enterVertex and leaveVertex callbacks.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Breadth-First Search (BFS)
2+
3+
Breadth-first search (BFS) is an algorithm for traversing
4+
or searching tree or graph data structures. It starts at
5+
the tree root (or some arbitrary node of a graph, sometimes
6+
referred to as a 'search key') and explores the neighbor
7+
nodes first, before moving to the next level neighbors.
8+
9+
![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/5/5d/Breadth-First-Search-Algorithm.gif)
10+
11+
## References
12+
13+
[Wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import BinaryTreeNode from '../../../../data-structures/tree/BinaryTreeNode';
2+
import breadthFirstSearch from '../breadthFirstSearch';
3+
4+
describe('breadthFirstSearch', () => {
5+
it('should perform DFS operation on tree', () => {
6+
const nodeA = new BinaryTreeNode('A');
7+
const nodeB = new BinaryTreeNode('B');
8+
const nodeC = new BinaryTreeNode('C');
9+
const nodeD = new BinaryTreeNode('D');
10+
const nodeE = new BinaryTreeNode('E');
11+
const nodeF = new BinaryTreeNode('F');
12+
const nodeG = new BinaryTreeNode('G');
13+
14+
nodeA.setLeft(nodeB).setRight(nodeC);
15+
nodeB.setLeft(nodeD).setRight(nodeE);
16+
nodeC.setLeft(nodeF).setRight(nodeG);
17+
18+
// In-order traversing.
19+
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');
20+
21+
const enterNodeCallback = jest.fn();
22+
const leaveNodeCallback = jest.fn();
23+
24+
// Traverse tree without callbacks first to check default ones.
25+
breadthFirstSearch(nodeA);
26+
27+
// Traverse tree with callbacks.
28+
breadthFirstSearch(nodeA, {
29+
enterNode: enterNodeCallback,
30+
leaveNode: leaveNodeCallback,
31+
});
32+
33+
expect(enterNodeCallback).toHaveBeenCalledTimes(7);
34+
expect(leaveNodeCallback).toHaveBeenCalledTimes(7);
35+
36+
// Check node entering.
37+
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
38+
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('B');
39+
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('C');
40+
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('D');
41+
expect(enterNodeCallback.mock.calls[4][0].value).toEqual('E');
42+
expect(enterNodeCallback.mock.calls[5][0].value).toEqual('F');
43+
expect(enterNodeCallback.mock.calls[6][0].value).toEqual('G');
44+
45+
// Check node leaving.
46+
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('A');
47+
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('B');
48+
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('C');
49+
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('D');
50+
expect(leaveNodeCallback.mock.calls[4][0].value).toEqual('E');
51+
expect(leaveNodeCallback.mock.calls[5][0].value).toEqual('F');
52+
expect(leaveNodeCallback.mock.calls[6][0].value).toEqual('G');
53+
});
54+
55+
it('allow users to redefine node visiting logic', () => {
56+
const nodeA = new BinaryTreeNode('A');
57+
const nodeB = new BinaryTreeNode('B');
58+
const nodeC = new BinaryTreeNode('C');
59+
const nodeD = new BinaryTreeNode('D');
60+
const nodeE = new BinaryTreeNode('E');
61+
const nodeF = new BinaryTreeNode('F');
62+
const nodeG = new BinaryTreeNode('G');
63+
64+
nodeA.setLeft(nodeB).setRight(nodeC);
65+
nodeB.setLeft(nodeD).setRight(nodeE);
66+
nodeC.setLeft(nodeF).setRight(nodeG);
67+
68+
// In-order traversing.
69+
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');
70+
71+
const enterNodeCallback = jest.fn();
72+
const leaveNodeCallback = jest.fn();
73+
74+
// Traverse tree without callbacks first to check default ones.
75+
breadthFirstSearch(nodeA);
76+
77+
// Traverse tree with callbacks.
78+
breadthFirstSearch(nodeA, {
79+
allowTraversal: (node, child) => {
80+
// Forbid traversing left half of the tree.
81+
return child.value !== 'B';
82+
},
83+
enterNode: enterNodeCallback,
84+
leaveNode: leaveNodeCallback,
85+
});
86+
87+
expect(enterNodeCallback).toHaveBeenCalledTimes(4);
88+
expect(leaveNodeCallback).toHaveBeenCalledTimes(4);
89+
90+
// Check node entering.
91+
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
92+
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('C');
93+
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('F');
94+
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('G');
95+
96+
// Check node leaving.
97+
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('A');
98+
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('C');
99+
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('F');
100+
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('G');
101+
});
102+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Queue from '../../../data-structures/queue/Queue';
2+
3+
/**
4+
* @typedef {Object} Callbacks
5+
* @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal -
6+
* Determines whether DFS should traverse from the node to its child.
7+
* @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node.
8+
* @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node.
9+
*/
10+
11+
/**
12+
* @param {Callbacks} [callbacks]
13+
* @returns {Callbacks}
14+
*/
15+
function initCallbacks(callbacks = {}) {
16+
const initiatedCallback = callbacks;
17+
18+
const stubCallback = () => {};
19+
const defaultAllowTraversal = () => true;
20+
21+
initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal;
22+
initiatedCallback.enterNode = callbacks.enterNode || stubCallback;
23+
initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback;
24+
25+
return initiatedCallback;
26+
}
27+
28+
/**
29+
* @param {BinaryTreeNode} rootNode
30+
* @param {Callbacks} [originalCallbacks]
31+
*/
32+
export default function breadthFirstSearch(rootNode, originalCallbacks) {
33+
const callbacks = initCallbacks(originalCallbacks);
34+
const nodeQueue = new Queue();
35+
36+
// Do initial queue setup.
37+
nodeQueue.enqueue(rootNode);
38+
39+
while (!nodeQueue.isEmpty()) {
40+
const currentNode = nodeQueue.dequeue();
41+
42+
callbacks.enterNode(currentNode);
43+
44+
// Add all children to the queue for future traversals.
45+
46+
// Traverse left branch.
47+
if (currentNode.left && callbacks.allowTraversal(currentNode, currentNode.left)) {
48+
nodeQueue.enqueue(currentNode.left);
49+
}
50+
51+
// Traverse right branch.
52+
if (currentNode.right && callbacks.allowTraversal(currentNode, currentNode.right)) {
53+
nodeQueue.enqueue(currentNode.right);
54+
}
55+
56+
callbacks.leaveNode(currentNode);
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Depth-First Search (DFS)
2+
3+
Depth-first search (DFS) is an algorithm for traversing or
4+
searching tree or graph data structures. One starts at
5+
the root (selecting some arbitrary node as the root in
6+
the case of a graph) and explores as far as possible
7+
along each branch before backtracking.
8+
9+
![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/7/7f/Depth-First-Search.gif)
10+
11+
## References
12+
13+
[Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import BinaryTreeNode from '../../../../data-structures/tree/BinaryTreeNode';
2+
import depthFirstSearch from '../depthFirstSearch';
3+
4+
describe('depthFirstSearch', () => {
5+
it('should perform DFS operation on tree', () => {
6+
const nodeA = new BinaryTreeNode('A');
7+
const nodeB = new BinaryTreeNode('B');
8+
const nodeC = new BinaryTreeNode('C');
9+
const nodeD = new BinaryTreeNode('D');
10+
const nodeE = new BinaryTreeNode('E');
11+
const nodeF = new BinaryTreeNode('F');
12+
const nodeG = new BinaryTreeNode('G');
13+
14+
nodeA.setLeft(nodeB).setRight(nodeC);
15+
nodeB.setLeft(nodeD).setRight(nodeE);
16+
nodeC.setLeft(nodeF).setRight(nodeG);
17+
18+
// In-order traversing.
19+
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');
20+
21+
const enterNodeCallback = jest.fn();
22+
const leaveNodeCallback = jest.fn();
23+
24+
// Traverse tree without callbacks first to check default ones.
25+
depthFirstSearch(nodeA);
26+
27+
// Traverse tree with callbacks.
28+
depthFirstSearch(nodeA, {
29+
enterNode: enterNodeCallback,
30+
leaveNode: leaveNodeCallback,
31+
});
32+
33+
expect(enterNodeCallback).toHaveBeenCalledTimes(7);
34+
expect(leaveNodeCallback).toHaveBeenCalledTimes(7);
35+
36+
// Check node entering.
37+
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
38+
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('B');
39+
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('D');
40+
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('E');
41+
expect(enterNodeCallback.mock.calls[4][0].value).toEqual('C');
42+
expect(enterNodeCallback.mock.calls[5][0].value).toEqual('F');
43+
expect(enterNodeCallback.mock.calls[6][0].value).toEqual('G');
44+
45+
// Check node leaving.
46+
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('D');
47+
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('E');
48+
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('B');
49+
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('F');
50+
expect(leaveNodeCallback.mock.calls[4][0].value).toEqual('G');
51+
expect(leaveNodeCallback.mock.calls[5][0].value).toEqual('C');
52+
expect(leaveNodeCallback.mock.calls[6][0].value).toEqual('A');
53+
});
54+
55+
it('allow users to redefine node visiting logic', () => {
56+
const nodeA = new BinaryTreeNode('A');
57+
const nodeB = new BinaryTreeNode('B');
58+
const nodeC = new BinaryTreeNode('C');
59+
const nodeD = new BinaryTreeNode('D');
60+
const nodeE = new BinaryTreeNode('E');
61+
const nodeF = new BinaryTreeNode('F');
62+
const nodeG = new BinaryTreeNode('G');
63+
64+
nodeA.setLeft(nodeB).setRight(nodeC);
65+
nodeB.setLeft(nodeD).setRight(nodeE);
66+
nodeC.setLeft(nodeF).setRight(nodeG);
67+
68+
// In-order traversing.
69+
expect(nodeA.toString()).toBe('D,B,E,A,F,C,G');
70+
71+
const enterNodeCallback = jest.fn();
72+
const leaveNodeCallback = jest.fn();
73+
74+
// Traverse tree without callbacks first to check default ones.
75+
depthFirstSearch(nodeA);
76+
77+
// Traverse tree with callbacks.
78+
depthFirstSearch(nodeA, {
79+
allowTraversal: (node, child) => {
80+
// Forbid traversing left part of the tree.
81+
return child.value !== 'B';
82+
},
83+
enterNode: enterNodeCallback,
84+
leaveNode: leaveNodeCallback,
85+
});
86+
87+
expect(enterNodeCallback).toHaveBeenCalledTimes(4);
88+
expect(leaveNodeCallback).toHaveBeenCalledTimes(4);
89+
90+
// Check node entering.
91+
expect(enterNodeCallback.mock.calls[0][0].value).toEqual('A');
92+
expect(enterNodeCallback.mock.calls[1][0].value).toEqual('C');
93+
expect(enterNodeCallback.mock.calls[2][0].value).toEqual('F');
94+
expect(enterNodeCallback.mock.calls[3][0].value).toEqual('G');
95+
96+
// Check node leaving.
97+
expect(leaveNodeCallback.mock.calls[0][0].value).toEqual('F');
98+
expect(leaveNodeCallback.mock.calls[1][0].value).toEqual('G');
99+
expect(leaveNodeCallback.mock.calls[2][0].value).toEqual('C');
100+
expect(leaveNodeCallback.mock.calls[3][0].value).toEqual('A');
101+
});
102+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @typedef {Object} Callbacks
3+
* @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal -
4+
* Determines whether DFS should traverse from the node to its child.
5+
* @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node.
6+
* @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node.
7+
*/
8+
9+
/**
10+
* @param {Callbacks} [callbacks]
11+
* @returns {Callbacks}
12+
*/
13+
function initCallbacks(callbacks = {}) {
14+
const initiatedCallback = callbacks;
15+
16+
const stubCallback = () => {};
17+
const defaultAllowTraversal = () => true;
18+
19+
initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal;
20+
initiatedCallback.enterNode = callbacks.enterNode || stubCallback;
21+
initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback;
22+
23+
return initiatedCallback;
24+
}
25+
26+
/**
27+
* @param {BinaryTreeNode} node
28+
* @param {Callbacks} callbacks
29+
*/
30+
export function depthFirstSearchRecursive(node, callbacks) {
31+
callbacks.enterNode(node);
32+
33+
// Traverse left branch.
34+
if (node.left && callbacks.allowTraversal(node, node.left)) {
35+
depthFirstSearchRecursive(node.left, callbacks);
36+
}
37+
38+
// Traverse right branch.
39+
if (node.right && callbacks.allowTraversal(node, node.right)) {
40+
depthFirstSearchRecursive(node.right, callbacks);
41+
}
42+
43+
callbacks.leaveNode(node);
44+
}
45+
46+
/**
47+
* @param {BinaryTreeNode} rootNode
48+
* @param {Callbacks} [callbacks]
49+
*/
50+
export default function depthFirstSearch(rootNode, callbacks) {
51+
depthFirstSearchRecursive(rootNode, initCallbacks(callbacks));
52+
}

0 commit comments

Comments
 (0)
Please sign in to comment.