Skip to content

Commit e18ebb4

Browse files
committed
chapter 09: [Trees]
1 parent b6dfa0f commit e18ebb4

File tree

12 files changed

+866
-0
lines changed

12 files changed

+866
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Work in Progress.
1818
* 06: [Sets](https://github.com/loiane/javascript-datastructures-algorithms/tree/third-edition/examples/chapter06)
1919
* 07: [Dictionaries and Hashes](https://github.com/loiane/javascript-datastructures-algorithms/tree/third-edition/examples/chapter07)
2020
* 08: [Recursion](https://github.com/loiane/javascript-datastructures-algorithms/tree/third-edition/examples/chapter08)
21+
* 09: [Trees](https://github.com/loiane/javascript-datastructures-algorithms/tree/third-edition/examples/chapter09)
2122

2223
### Third Edition Updates
2324

src/js/data-structures/avl-tree.js

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { Compare, defaultCompare } from '../util';
2+
import BinarySearchTree from './binary-search-tree';
3+
import { Node } from './models/node';
4+
5+
const BalanceFactor = {
6+
UNBALANCED_RIGHT: 1,
7+
SLIGHTLY_UNBALANCED_RIGHT: 2,
8+
BALANCED: 3,
9+
SLIGHTLY_UNBALANCED_LEFT: 4,
10+
UNBALANCED_LEFT: 5
11+
};
12+
13+
export default class AVLTree extends BinarySearchTree {
14+
constructor(compareFn = defaultCompare) {
15+
super(compareFn);
16+
this.compareFn = compareFn;
17+
this.root = null;
18+
}
19+
getNodeHeight(node) {
20+
if (node == null) {
21+
return -1;
22+
}
23+
return Math.max(this.getNodeHeight(node.left), this.getNodeHeight(node.right)) + 1;
24+
}
25+
/**
26+
* Left left case: rotate right
27+
*
28+
* b a
29+
* / \ / \
30+
* a e -> rotationLL(b) -> c b
31+
* / \ / \
32+
* c d d e
33+
*
34+
* @param node Node<T>
35+
*/
36+
rotationLL(node) {
37+
const tmp = node.left;
38+
node.left = tmp.right;
39+
tmp.right = node;
40+
return tmp;
41+
}
42+
/**
43+
* Right right case: rotate left
44+
*
45+
* a b
46+
* / \ / \
47+
* c b -> rotationRR(a) -> a e
48+
* / \ / \
49+
* d e c d
50+
*
51+
* @param node Node<T>
52+
*/
53+
rotationRR(node) {
54+
const tmp = node.right;
55+
node.right = tmp.left;
56+
tmp.left = node;
57+
return tmp;
58+
}
59+
/**
60+
* Left right case: rotate left then right
61+
* @param node Node<T>
62+
*/
63+
rotationLR(node) {
64+
node.left = this.rotationRR(node.left);
65+
return this.rotationLL(node);
66+
}
67+
/**
68+
* Right left case: rotate right then left
69+
* @param node Node<T>
70+
*/
71+
rotationRL(node) {
72+
node.right = this.rotationLL(node.right);
73+
return this.rotationRR(node);
74+
}
75+
getBalanceFactor(node) {
76+
const heightDifference = this.getNodeHeight(node.left) - this.getNodeHeight(node.right);
77+
switch (heightDifference) {
78+
case -2:
79+
return BalanceFactor.UNBALANCED_RIGHT;
80+
case -1:
81+
return BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT;
82+
case 1:
83+
return BalanceFactor.SLIGHTLY_UNBALANCED_LEFT;
84+
case 2:
85+
return BalanceFactor.UNBALANCED_LEFT;
86+
default:
87+
return BalanceFactor.BALANCED;
88+
}
89+
}
90+
insert(key) {
91+
this.root = this.insertNode(this.root, key);
92+
}
93+
insertNode(node, key) {
94+
if (node == null) {
95+
return new Node(key);
96+
} else if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
97+
node.left = this.insertNode(node.left, key);
98+
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
99+
node.right = this.insertNode(node.right, key);
100+
} else {
101+
return node; // duplicated key
102+
}
103+
// verify if tree is balanced
104+
const balanceFactor = this.getBalanceFactor(node);
105+
if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
106+
if (this.compareFn(key, node.left.key) === Compare.LESS_THAN) {
107+
// Left left case
108+
node = this.rotationLL(node);
109+
} else {
110+
// Left right case
111+
return this.rotationLR(node);
112+
}
113+
}
114+
if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
115+
if (this.compareFn(key, node.right.key) === Compare.BIGGER_THAN) {
116+
// Right right case
117+
node = this.rotationRR(node);
118+
} else {
119+
// Right left case
120+
return this.rotationRL(node);
121+
}
122+
}
123+
return node;
124+
}
125+
removeNode(node, key) {
126+
node = super.removeNode(node, key); // {1}
127+
if (node == null) {
128+
return node;
129+
}
130+
// verify if tree is balanced
131+
const balanceFactor = this.getBalanceFactor(node);
132+
if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
133+
// Left left case
134+
if (
135+
this.getBalanceFactor(node.left) === BalanceFactor.BALANCED ||
136+
this.getBalanceFactor(node.left) === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
137+
) {
138+
return this.rotationLL(node);
139+
}
140+
// Left right case
141+
if (this.getBalanceFactor(node.left) === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT) {
142+
return this.rotationLR(node.left);
143+
}
144+
}
145+
if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
146+
// Right right case
147+
if (
148+
this.getBalanceFactor(node.right) === BalanceFactor.BALANCED ||
149+
this.getBalanceFactor(node.right) === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT
150+
) {
151+
return this.rotationRR(node);
152+
}
153+
// Right left case
154+
if (this.getBalanceFactor(node.right) === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT) {
155+
return this.rotationRL(node.right);
156+
}
157+
}
158+
return node;
159+
}
160+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { Compare, defaultCompare } from '../util';
2+
import { Node } from './models/node';
3+
4+
export default class BinarySearchTree {
5+
constructor(compareFn = defaultCompare) {
6+
this.compareFn = compareFn;
7+
this.root = null;
8+
}
9+
insert(key) {
10+
// special case: first key
11+
if (this.root == null) {
12+
this.root = new Node(key);
13+
} else {
14+
this.insertNode(this.root, key);
15+
}
16+
}
17+
insertNode(node, key) {
18+
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
19+
if (node.left == null) {
20+
node.left = new Node(key);
21+
} else {
22+
this.insertNode(node.left, key);
23+
}
24+
} else {
25+
if (node.right == null) {
26+
node.right = new Node(key);
27+
} else {
28+
this.insertNode(node.right, key);
29+
}
30+
}
31+
}
32+
getRoot() {
33+
return this.root;
34+
}
35+
search(key) {
36+
return this.searchNode(this.root, key);
37+
}
38+
searchNode(node, key) {
39+
if (node == null) {
40+
return false;
41+
}
42+
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
43+
return this.searchNode(node.left, key);
44+
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
45+
return this.searchNode(node.right, key);
46+
} else {
47+
return true;
48+
}
49+
}
50+
inOrderTraverse(callback) {
51+
this.inOrderTraverseNode(this.root, callback);
52+
}
53+
inOrderTraverseNode(node, callback) {
54+
if (node != null) {
55+
this.inOrderTraverseNode(node.left, callback);
56+
callback(node.key);
57+
this.inOrderTraverseNode(node.right, callback);
58+
}
59+
}
60+
preOrderTraverse(callback) {
61+
this.preOrderTraverseNode(this.root, callback);
62+
}
63+
preOrderTraverseNode(node, callback) {
64+
if (node != null) {
65+
callback(node.key);
66+
this.preOrderTraverseNode(node.left, callback);
67+
this.preOrderTraverseNode(node.right, callback);
68+
}
69+
}
70+
postOrderTraverse(callback) {
71+
this.postOrderTraverseNode(this.root, callback);
72+
}
73+
postOrderTraverseNode(node, callback) {
74+
if (node != null) {
75+
this.postOrderTraverseNode(node.left, callback);
76+
this.postOrderTraverseNode(node.right, callback);
77+
callback(node.key);
78+
}
79+
}
80+
min() {
81+
return this.minNode(this.root);
82+
}
83+
minNode(node) {
84+
let current = node;
85+
while (current != null && current.left != null) {
86+
current = current.left;
87+
}
88+
return current;
89+
}
90+
max() {
91+
return this.maxNode(this.root);
92+
}
93+
maxNode(node) {
94+
let current = node;
95+
while (current != null && current.right != null) {
96+
current = current.right;
97+
}
98+
return current;
99+
}
100+
remove(key) {
101+
this.root = this.removeNode(this.root, key);
102+
}
103+
removeNode(node, key) {
104+
if (node == null) {
105+
return null;
106+
}
107+
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
108+
node.left = this.removeNode(node.left, key);
109+
return node;
110+
} else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
111+
node.right = this.removeNode(node.right, key);
112+
return node;
113+
} else {
114+
// key is equal to node.item
115+
// handle 3 special conditions
116+
// 1 - a leaf node
117+
// 2 - a node with only 1 child
118+
// 3 - a node with 2 children
119+
// case 1
120+
if (node.left == null && node.right == null) {
121+
node = null;
122+
return node;
123+
}
124+
// case 2
125+
if (node.left == null) {
126+
node = node.right;
127+
return node;
128+
} else if (node.right == null) {
129+
node = node.left;
130+
return node;
131+
}
132+
// case 3
133+
const aux = this.minNode(node.right);
134+
node.key = aux.key;
135+
node.right = this.removeNode(node.right, aux.key);
136+
return node;
137+
}
138+
}
139+
}

src/js/data-structures/models/node.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export class Node {
2+
constructor(key) {
3+
this.key = key;
4+
this.left = null;
5+
this.right = null;
6+
}
7+
toString() {
8+
return `${this.key}`;
9+
}
10+
}

0 commit comments

Comments
 (0)