Skip to content

Commit 1ff8f20

Browse files
zFlxwappgurueu
andauthored
feat(data-struct): binary search tree (#106)
* feat(data-struct): binary search tree * fix: improvements part 1 * fix: improvements part 2 * Fix failing tests --------- Co-authored-by: Lars Müller <[email protected]>
1 parent 8e83941 commit 1ff8f20

File tree

2 files changed

+280
-0
lines changed

2 files changed

+280
-0
lines changed

data_structures/binary_search_tree.ts

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
/**
2+
* Represents a node of a binary search tree.
3+
*
4+
* @template T The type of the value stored in the node.
5+
*/
6+
class TreeNode<T> {
7+
constructor(
8+
public data: T,
9+
public leftChild?: TreeNode<T>,
10+
public rightChild?: TreeNode<T>,
11+
) {}
12+
}
13+
14+
/**
15+
* An implementation of a binary search tree.
16+
*
17+
* A binary tree is a tree with only two children per node. A binary search tree on top sorts the children according
18+
* to following rules:
19+
* - left child < parent node
20+
* - right child > parent node
21+
* - all children on the left side < root node
22+
* - all children on the right side > root node
23+
*
24+
* For profound information about trees
25+
* @see https://www.geeksforgeeks.org/introduction-to-tree-data-structure-and-algorithm-tutorials/
26+
*
27+
* @template T The data type of the values in the binary tree.
28+
*/
29+
export class BinarySearchTree<T> {
30+
rootNode?: TreeNode<T>;
31+
32+
/**
33+
* Instantiates the binary search tree.
34+
*
35+
* @param rootNode The root node.
36+
*/
37+
constructor() {
38+
this.rootNode = undefined;
39+
}
40+
41+
/**
42+
* Checks, if the binary search tree is empty, i. e. has no root node.
43+
*
44+
* @returns Whether the binary search tree is empty.
45+
*/
46+
isEmpty(): boolean {
47+
return this.rootNode === undefined;
48+
}
49+
50+
/**
51+
* Checks whether the tree has the given data or not.
52+
*
53+
* @param data The data to check for.
54+
*/
55+
has(data: T): boolean {
56+
if (!this.rootNode) {
57+
return false;
58+
}
59+
60+
let currentNode = this.rootNode;
61+
while (currentNode.data !== data) {
62+
if (data > currentNode.data) {
63+
if (!currentNode.rightChild) {
64+
return false;
65+
}
66+
67+
currentNode = currentNode.rightChild;
68+
} else {
69+
if (!currentNode.leftChild) {
70+
return false;
71+
}
72+
73+
currentNode = currentNode.leftChild;
74+
}
75+
}
76+
77+
return true;
78+
}
79+
80+
/**
81+
* Inserts the given data into the binary search tree.
82+
*
83+
* @param data The data to be stored in the binary search tree.
84+
* @returns
85+
*/
86+
insert(data: T): void {
87+
if (!this.rootNode) {
88+
this.rootNode = new TreeNode<T>(data);
89+
return;
90+
}
91+
92+
let currentNode: TreeNode<T> = this.rootNode;
93+
while (true) {
94+
if (data > currentNode.data) {
95+
if (currentNode.rightChild) {
96+
currentNode = currentNode.rightChild;
97+
} else {
98+
currentNode.rightChild = new TreeNode<T>(data);
99+
return;
100+
}
101+
} else {
102+
if (currentNode.leftChild) {
103+
currentNode = currentNode.leftChild;
104+
} else {
105+
currentNode.leftChild = new TreeNode<T>(data);
106+
return;
107+
}
108+
}
109+
}
110+
}
111+
112+
/**
113+
* Finds the minimum value of the binary search tree.
114+
*
115+
* @returns The minimum value of the binary search tree
116+
*/
117+
findMin(): T {
118+
if (!this.rootNode) {
119+
throw new Error('Empty tree.');
120+
}
121+
122+
const traverse = (node: TreeNode<T>): T => {
123+
return !node.leftChild ? node.data : traverse(node.leftChild);
124+
};
125+
126+
return traverse(this.rootNode);
127+
}
128+
129+
/**
130+
* Finds the maximum value of the binary search tree.
131+
*
132+
* @returns The maximum value of the binary search tree
133+
*/
134+
findMax(): T {
135+
if (!this.rootNode) {
136+
throw new Error('Empty tree.');
137+
}
138+
139+
const traverse = (node: TreeNode<T>): T => {
140+
return !node.rightChild ? node.data : traverse(node.rightChild);
141+
};
142+
143+
return traverse(this.rootNode);
144+
}
145+
146+
/**
147+
* Traverses to the binary search tree in in-order, i. e. it follow the schema of:
148+
* Left Node -> Root Node -> Right Node
149+
*
150+
* @param array The already found node data for recursive access.
151+
* @returns
152+
*/
153+
inOrderTraversal(array: T[] = []): T[] {
154+
if (!this.rootNode) {
155+
return array;
156+
}
157+
158+
const traverse = (node?: TreeNode<T>, array: T[] = []): T[] => {
159+
if (!node) {
160+
return array;
161+
}
162+
163+
traverse(node.leftChild, array);
164+
array.push(node.data);
165+
traverse(node.rightChild, array);
166+
return array;
167+
};
168+
169+
return traverse(this.rootNode);
170+
}
171+
172+
/**
173+
* Traverses to the binary search tree in pre-order, i. e. it follow the schema of:
174+
* Root Node -> Left Node -> Right Node
175+
*
176+
* @param array The already found node data for recursive access.
177+
* @returns
178+
*/
179+
preOrderTraversal(array: T[] = []): T[] {
180+
if (!this.rootNode) {
181+
return array;
182+
}
183+
184+
const traverse = (node?: TreeNode<T>, array: T[] = []): T[] => {
185+
if (!node) {
186+
return array;
187+
}
188+
189+
array.push(node.data);
190+
traverse(node.leftChild, array);
191+
traverse(node.rightChild, array);
192+
193+
return array;
194+
};
195+
196+
return traverse(this.rootNode);
197+
}
198+
199+
/**
200+
* Traverses to the binary search tree in post-order, i. e. it follow the schema of:
201+
* Left Node -> Right Node -> Root Node
202+
*
203+
* @param array The already found node data for recursive access.
204+
* @returns
205+
*/
206+
postOrderTraversal(array: T[] = []): T[] {
207+
if (!this.rootNode) {
208+
return array;
209+
}
210+
211+
const traverse = (node?: TreeNode<T>, array: T[] = []): T[] => {
212+
if (!node) {
213+
return array;
214+
}
215+
216+
traverse(node.leftChild, array);
217+
traverse(node.rightChild, array);
218+
array.push(node.data);
219+
220+
return array;
221+
};
222+
223+
return traverse(this.rootNode);
224+
}
225+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { BinarySearchTree } from '../binary_search_tree';
2+
3+
describe('BinarySearchTree', () => {
4+
describe('with filled binary search tree (insert)', () => {
5+
let binarySearchTree: BinarySearchTree<number>;
6+
7+
beforeEach(() => {
8+
binarySearchTree = new BinarySearchTree<number>();
9+
binarySearchTree.insert(25);
10+
binarySearchTree.insert(80);
11+
binarySearchTree.insert(12);
12+
binarySearchTree.insert(5);
13+
binarySearchTree.insert(64);
14+
});
15+
16+
it('should return false for isEmpty when binary search tree is not empty', () => {
17+
expect(binarySearchTree.isEmpty()).toBeFalsy();
18+
});
19+
20+
it('should return correct root node for search', () => {
21+
expect(binarySearchTree.rootNode?.data).toBe(25);
22+
});
23+
24+
it('should return whether an element is in the set', () => {
25+
expect(binarySearchTree.has(5)).toBe(true);
26+
expect(binarySearchTree.has(42)).toBe(false);
27+
});
28+
29+
it('should traverse in in-order through the tree', () => {
30+
expect(binarySearchTree.inOrderTraversal()).toStrictEqual([5, 12, 25, 64, 80]);
31+
});
32+
33+
it('should traverse in pre-order through the tree', () => {
34+
console.log(binarySearchTree.preOrderTraversal());
35+
36+
expect(
37+
binarySearchTree.preOrderTraversal(),
38+
).toStrictEqual([25, 12, 5, 80, 64]);
39+
});
40+
41+
it('should traverse in post-order through the tree', () => {
42+
expect(
43+
binarySearchTree.postOrderTraversal(),
44+
).toStrictEqual([5, 12, 64, 80, 25]);
45+
});
46+
47+
it('should return the minimum value of the binary search tree', () => {
48+
expect(binarySearchTree.findMin()).toBe(5);
49+
});
50+
51+
it('should return the maximum value of the binary search tree', () => {
52+
expect(binarySearchTree.findMax()).toBe(80);
53+
});
54+
});
55+
});

0 commit comments

Comments
 (0)