Skip to content

Commit 2f581d8

Browse files
committed
add DSA Binary Search Tree and its unitest
1 parent 51c1f37 commit 2f581d8

File tree

2 files changed

+188
-15
lines changed

2 files changed

+188
-15
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,71 @@
11

2+
import { describe, it, expect, beforeEach } from 'bun:test';
3+
import { BinarySearchTree } from './binarySearchTree-implement';
4+
5+
6+
describe('BinarySearchTree', () => {
7+
let tree: BinarySearchTree;
8+
9+
beforeEach(() => {
10+
tree = new BinarySearchTree();
11+
});
12+
13+
it('should insert nodes correctly', () => {
14+
tree.insert(10);
15+
tree.insert(5);
16+
tree.insert(15);
17+
expect(tree.lookup(10)).toBeTruthy();
18+
expect(tree.lookup(5)).toBeTruthy();
19+
expect(tree.lookup(15)).toBeTruthy();
20+
expect(tree.lookup(20)).toBeFalsy(); // Not inserted
21+
});
22+
23+
it('should lookup nodes correctly', () => {
24+
tree.insert(10);
25+
tree.insert(5);
26+
tree.insert(15);
27+
expect(tree.lookup(10)).toEqual(expect.objectContaining({ value: 10 }));
28+
expect(tree.lookup(5)).toEqual(expect.objectContaining({ value: 5 }));
29+
expect(tree.lookup(15)).toEqual(expect.objectContaining({ value: 15 }));
30+
expect(tree.lookup(20)).toBeFalsy(); // Not inserted
31+
});
32+
33+
it('should remove nodes correctly', () => {
34+
tree.insert(10);
35+
tree.insert(5);
36+
tree.insert(15);
37+
expect(tree.remove(5)).toBeTruthy();
38+
expect(tree.lookup(5)).toBeFalsy(); // Should be removed
39+
expect(tree.remove(10)).toBeTruthy();
40+
expect(tree.lookup(10)).toBeFalsy(); // Should be removed
41+
expect(tree.remove(15)).toBeTruthy();
42+
expect(tree.lookup(15)).toBeFalsy(); // Should be removed
43+
});
44+
45+
it('should handle removing a node with one child', () => {
46+
tree.insert(10);
47+
tree.insert(5);
48+
tree.insert(15);
49+
tree.insert(12);
50+
expect(tree.remove(15)).toBeTruthy();
51+
expect(tree.lookup(15)).toBeFalsy(); // Should be removed
52+
expect(tree.lookup(12)).toEqual(expect.objectContaining({ value: 12 })); // Should still exist
53+
});
54+
55+
it('should handle removing a node with two children', () => {
56+
tree.insert(10);
57+
tree.insert(5);
58+
tree.insert(15);
59+
tree.insert(12);
60+
tree.insert(17);
61+
expect(tree.remove(15)).toBeTruthy();
62+
expect(tree.lookup(15)).toBeFalsy(); // Should be removed
63+
expect(tree.lookup(12)).toEqual(expect.objectContaining({ value: 12 })); // Should still exist
64+
expect(tree.lookup(17)).toEqual(expect.objectContaining({ value: 17 })); // Should still exist
65+
});
66+
67+
it('should return false when removing a non-existent node', () => {
68+
tree.insert(10);
69+
expect(tree.remove(20)).toBeFalsy(); // Node does not exist
70+
});
71+
});

src/data_structure/binarySearchTree/binarySearchTree-implement.ts

Lines changed: 118 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
21
class Node {
3-
left: any;
4-
right: any;
2+
left: Node | null;
3+
right: Node | null;;
54
value: number;
65
constructor(value: number) {
76
this.left = null;
@@ -10,28 +9,132 @@ class Node {
109
}
1110
}
1211

13-
class BinarySearchTree {
14-
root: any
12+
export class BinarySearchTree {
13+
root: Node | null;
1514
constructor() {
1615
this.root = null;
1716
}
1817
insert(value: number) {
18+
const newNode = new Node(value);
19+
1920
if (this.root === null) {
20-
this.root = new Node(value)
21+
this.root = newNode;
22+
} else {
23+
let currentNode = this.root;
24+
while (true) {
25+
if (currentNode.value > value) {
26+
// Left
27+
if (!currentNode.left) {
28+
currentNode.left = newNode;
29+
return this;
30+
}
31+
currentNode = currentNode.left;
32+
} else {
33+
// Right
34+
if (!currentNode.right) {
35+
currentNode.right = newNode;
36+
return this;
37+
}
38+
currentNode = currentNode.right;
39+
}
40+
}
2141
}
22-
// Làm sao để lặp qua (Chẳng lẽ sử dụng recursion)
23-
24-
// So sánh
25-
26-
// Gán giá trị
27-
2842
}
2943
lookup(value: number) {
44+
if (!this.root) {
45+
return false;
46+
}
47+
let currentNode: Node | null = this.root;
3048

49+
while (currentNode) {
50+
if (currentNode.value > value) {
51+
// Left
52+
currentNode = currentNode.left;
53+
} else if (currentNode.value < value) {
54+
// Right
55+
currentNode = currentNode.right;
56+
} else if (currentNode.value === value) {
57+
return currentNode;
58+
}
59+
}
60+
return false;
3161
}
3262

3363
remove(value: number) {
64+
if (!this.root) {
65+
return false;
66+
}
67+
let currentNode: Node | null = this.root;
68+
let parentNode = null;
69+
while (currentNode) {
70+
if (value < currentNode.value) {
71+
parentNode = currentNode;
72+
currentNode = currentNode.left;
73+
} else if (value > currentNode.value) {
74+
parentNode = currentNode;
75+
currentNode = currentNode.right;
76+
} else if (currentNode.value === value) {
77+
//We have a match, get to work!
3478

79+
//Option 1: No right child:
80+
if (currentNode.right === null) {
81+
if (parentNode === null) {
82+
this.root = currentNode.left;
83+
} else {
84+
//if parent > current value, make current left child a child of parent
85+
if (currentNode.value < parentNode.value) {
86+
parentNode.left = currentNode.left;
87+
88+
//if parent < current value, make left child a right child of parent
89+
} else if (currentNode.value > parentNode.value) {
90+
parentNode.right = currentNode.left;
91+
}
92+
}
93+
94+
//Option 2: Right child which doesnt have a left child
95+
} else if (currentNode.right.left === null) {
96+
currentNode.right.left = currentNode.left;
97+
if (parentNode === null) {
98+
this.root = currentNode.right;
99+
} else {
100+
//if parent > current, make right child of the left the parent
101+
if (currentNode.value < parentNode.value) {
102+
parentNode.left = currentNode.right;
103+
104+
//if parent < current, make right child a right child of the parent
105+
} else if (currentNode.value > parentNode.value) {
106+
parentNode.right = currentNode.right;
107+
}
108+
}
109+
110+
//Option 3: Right child that has a left child
111+
} else {
112+
//find the Right child's left most child
113+
let leftmost = currentNode.right.left;
114+
let leftmostParent = currentNode.right;
115+
while (leftmost.left !== null) {
116+
leftmostParent = leftmost;
117+
leftmost = leftmost.left;
118+
}
119+
120+
//Parent's left subtree is now leftmost's right subtree
121+
leftmostParent.left = leftmost.right;
122+
leftmost.left = currentNode.left;
123+
leftmost.right = currentNode.right;
124+
125+
if (parentNode === null) {
126+
this.root = leftmost;
127+
} else {
128+
if (currentNode.value < parentNode.value) {
129+
parentNode.left = leftmost;
130+
} else if (currentNode.value > parentNode.value) {
131+
parentNode.right = leftmost;
132+
}
133+
}
134+
}
135+
return true;
136+
}
137+
}
35138
}
36139
}
37140

@@ -46,13 +149,13 @@ tree.insert(1);
46149
tree.remove(170);
47150
JSON.stringify(traverse(tree.root));
48151
console.log(tree.lookup(20));
49-
50152
// 9
51153
// 4 20
52154
//1 6 15 170
53155

54-
function traverse(node: any) {
55-
const tree: any = { value: node.value };
156+
function traverse(node: Node | null) {
157+
if (!node) return null
158+
const tree: Node | null = { value: node.value, left: null, right: null };
56159
tree.left = node.left === null ? null : traverse(node.left);
57160
tree.right = node.right === null ? null : traverse(node.right);
58161
return tree;

0 commit comments

Comments
 (0)