import BinarySearchTree from'BinarySearchTree';exportdefaultclassAvlTreeextendsBinarySearchTree{/**
* @param {*} value
*/insert(value){// Do the normal BST insert.super.insert(value);// Let's move up to the root and check balance factors along the way.let currentNode =this.root.find(value);while(currentNode){this.balance(currentNode);
currentNode = currentNode.parent;}}/**
* @param {*} value
* @return {boolean}
*/remove(value){// Do standard BST removal.super.remove(value);// Balance the tree starting from the root node.this.balance(this.root);}/**
* @param {BinarySearchTreeNode} node
*/balance(node){// If balance factor is not OK then try to balance the node.if(node.balanceFactor >1){// Left rotation.if(node.left.balanceFactor >0){// Left-Left rotationthis.rotateLeftLeft(node);}elseif(node.left.balanceFactor <0){// Left-Right rotation.this.rotateLeftRight(node);}}elseif(node.balanceFactor <-1){// Right rotation.if(node.right.balanceFactor <0){// Right-Right rotationthis.rotateRightRight(node);}elseif(node.right.balanceFactor >0){// Right-Left rotation.this.rotateRightLeft(node);}}}/**
* @param {BinarySearchTreeNode} rootNode
*/rotateLeftLeft(rootNode){// Detach left node from root node.const leftNode = rootNode.left;
rootNode.setLeft(null);// Make left node to be a child of rootNode's parent.if(rootNode.parent){
rootNode.parent.setLeft(leftNode);}elseif(rootNode ===this.root){// If root node is root then make left node to be a new root.this.root = leftNode;}// If left node has a right child then detach it and// attach it as a left child for rootNode.if(leftNode.right){
rootNode.setLeft(leftNode.right);}// Attach rootNode to the right of leftNode.
leftNode.setRight(rootNode);}/**
* @param {BinarySearchTreeNode} rootNode
*/rotateLeftRight(rootNode){// Detach left node from rootNode since it is going to be replaced.const leftNode = rootNode.left;
rootNode.setLeft(null);// Detach right node from leftNode.const leftRightNode = leftNode.right;
leftNode.setRight(null);// Preserve leftRightNode's left subtree.if(leftRightNode.left){
leftNode.setRight(leftRightNode.left);
leftRightNode.setLeft(null);}// Attach leftRightNode to the rootNode.
rootNode.setLeft(leftRightNode);// Attach leftNode as left node for leftRight node.
leftRightNode.setLeft(leftNode);// Do left-left rotation.this.rotateLeftLeft(rootNode);}/**
* @param {BinarySearchTreeNode} rootNode
*/rotateRightLeft(rootNode){// Detach right node from rootNode since it is going to be replaced.const rightNode = rootNode.right;
rootNode.setRight(null);// Detach left node from rightNode.const rightLeftNode = rightNode.left;
rightNode.setLeft(null);if(rightLeftNode.right){
rightNode.setLeft(rightLeftNode.right);
rightLeftNode.setRight(null);}// Attach rightLeftNode to the rootNode.
rootNode.setRight(rightLeftNode);// Attach rightNode as right node for rightLeft node.
rightLeftNode.setRight(rightNode);// Do right-right rotation.this.rotateRightRight(rootNode);}/**
* @param {BinarySearchTreeNode} rootNode
*/rotateRightRight(rootNode){// Detach right node from root node.const rightNode = rootNode.right;
rootNode.setRight(null);// Make right node to be a child of rootNode's parent.if(rootNode.parent){
rootNode.parent.setRight(rightNode);}elseif(rootNode ===this.root){// If root node is root then make right node to be a new root.this.root = rightNode;}// If right node has a left child then detach it and// attach it as a right child for rootNode.if(rightNode.left){
rootNode.setRight(rightNode.left);}// Attach rootNode to the left of rightNode.
rightNode.setLeft(rootNode);}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
BinarySearchTree.js完整源代码
import BinarySearchTreeNode from'BinarySearchTreeNode';exportdefaultclassBinarySearchTree{/**
* @param {function} [nodeValueCompareFunction]
*/constructor(nodeValueCompareFunction){this.root =newBinarySearchTreeNode(null, nodeValueCompareFunction);// Steal node comparator from the root.this.nodeComparator =this.root.nodeComparator;}/**
* @param {*} value
* @return {BinarySearchTreeNode}
*/insert(value){returnthis.root.insert(value);}/**
* @param {*} value
* @return {boolean}
*/contains(value){returnthis.root.contains(value);}/**
* @param {*} value
* @return {boolean}
*/remove(value){returnthis.root.remove(value);}/**
* @return {string}
*/toString(){returnthis.root.toString();}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
BinarySearchTreeNode.js完整源代码
import BinaryTreeNode from'BinaryTreeNode';import Comparator from'Comparator';exportdefaultclassBinarySearchTreeNodeextendsBinaryTreeNode{/**
* @param {*} [value] - node value.
* @param {function} [compareFunction] - comparator function for node values.
*/constructor(value =null, compareFunction =undefined){super(value);// This comparator is used to compare node values with each other.this.compareFunction = compareFunction;this.nodeValueComparator =newComparator(compareFunction);}/**
* @param {*} value
* @return {BinarySearchTreeNode}
*/insert(value){if(this.nodeValueComparator.equal(this.value,null)){this.value = value;returnthis;}if(this.nodeValueComparator.lessThan(value,this.value)){// Insert to the left.if(this.left){returnthis.left.insert(value);}const newNode =newBinarySearchTreeNode(value,this.compareFunction);this.setLeft(newNode);return newNode;}if(this.nodeValueComparator.greaterThan(value,this.value)){// Insert to the right.if(this.right){returnthis.right.insert(value);}const newNode =newBinarySearchTreeNode(value,this.compareFunction);this.setRight(newNode);return newNode;}returnthis;}/**
* @param {*} value
* @return {BinarySearchTreeNode}
*/find(value){// Check the root.if(this.nodeValueComparator.equal(this.value, value)){returnthis;}if(this.nodeValueComparator.lessThan(value,this.value)&&this.left){// Check left nodes.returnthis.left.find(value);}if(this.nodeValueComparator.greaterThan(value,this.value)&&this.right){// Check right nodes.returnthis.right.find(value);}returnnull;}/**
* @param {*} value
* @return {boolean}
*/contains(value){return!!this.find(value);}/**
* @param {*} value
* @return {boolean}
*/remove(value){const nodeToRemove =this.find(value);if(!nodeToRemove){thrownewError('Item not found in the tree');}const{ parent }= nodeToRemove;if(!nodeToRemove.left &&!nodeToRemove.right){// Node is a leaf and thus has no children.if(parent){// Node has a parent. Just remove the pointer to this node from the parent.
parent.removeChild(nodeToRemove);}else{// Node has no parent. Just erase current node value.
nodeToRemove.setValue(undefined);}}elseif(nodeToRemove.left && nodeToRemove.right){// Node has two children.// Find the next biggest value (minimum value in the right branch)// and replace current value node with that next biggest value.const nextBiggerNode = nodeToRemove.right.findMin();if(!this.nodeComparator.equal(nextBiggerNode, nodeToRemove.right)){this.remove(nextBiggerNode.value);
nodeToRemove.setValue(nextBiggerNode.value);}else{// In case if next right value is the next bigger one and it doesn't have left child// then just replace node that is going to be deleted with the right node.
nodeToRemove.setValue(nodeToRemove.right.value);
nodeToRemove.setRight(nodeToRemove.right.right);}}else{// Node has only one child.// Make this child to be a direct child of current node's parent./** @var BinarySearchTreeNode */const childNode = nodeToRemove.left || nodeToRemove.right;if(parent){
parent.replaceChild(nodeToRemove, childNode);}else{
BinaryTreeNode.copyNode(childNode, nodeToRemove);}}// Clear the parent of removed node.
nodeToRemove.parent =null;returntrue;}/**
* @return {BinarySearchTreeNode}
*/findMin(){if(!this.left){returnthis;}returnthis.left.findMin();}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
BinaryTreeNode.js 完整源代码
import Comparator from'Comparator';import HashTable from'';exportdefaultclassBinaryTreeNode{/**
* @param {*} [value] - node value.
*/constructor(value =null){this.left =null;this.right =null;this.parent =null;this.value = value;// Any node related meta information may be stored here.this.meta =newHashTable();// This comparator is used to compare binary tree nodes with each other.this.nodeComparator =newComparator();}/**
* @return {number}
*/getleftHeight(){if(!this.left){return0;}returnthis.left.height +1;}/**
* @return {number}
*/getrightHeight(){if(!this.right){return0;}returnthis.right.height +1;}/**
* @return {number}
*/getheight(){return Math.max(this.leftHeight,this.rightHeight);}/**
* @return {number}
*/getbalanceFactor(){returnthis.leftHeight -this.rightHeight;}/**
* Get parent's sibling if it exists.
* @return {BinaryTreeNode}
*/getuncle(){// Check if current node has parent.if(!this.parent){returnundefined;}// Check if current node has grand-parent.if(!this.parent.parent){returnundefined;}// Check if grand-parent has two children.if(!this.parent.parent.left ||!this.parent.parent.right){returnundefined;}// So for now we know that current node has grand-parent and this// grand-parent has two children. Let's find out who is the uncle.if(this.nodeComparator.equal(this.parent,this.parent.parent.left)){// Right one is an uncle.returnthis.parent.parent.right;}// Left one is an uncle.returnthis.parent.parent.left;}/**
* @param {*} value
* @return {BinaryTreeNode}
*/setValue(value){this.value = value;returnthis;}/**
* @param {BinaryTreeNode} node
* @return {BinaryTreeNode}
*/setLeft(node){// Reset parent for left node since it is going to be detached.if(this.left){this.left.parent =null;}// Attach new node to the left.this.left = node;// Make current node to be a parent for new left one.if(this.left){this.left.parent =this;}returnthis;}/**
* @param {BinaryTreeNode} node
* @return {BinaryTreeNode}
*/setRight(node){// Reset parent for right node since it is going to be detached.if(this.right){this.right.parent =null;}// Attach new node to the right.this.right = node;// Make current node to be a parent for new right one.if(node){this.right.parent =this;}returnthis;}/**
* @param {BinaryTreeNode} nodeToRemove
* @return {boolean}
*/removeChild(nodeToRemove){if(this.left &&this.nodeComparator.equal(this.left, nodeToRemove)){this.left =null;returntrue;}if(this.right &&this.nodeComparator.equal(this.right, nodeToRemove)){this.right =null;returntrue;}returnfalse;}/**
* @param {BinaryTreeNode} nodeToReplace
* @param {BinaryTreeNode} replacementNode
* @return {boolean}
*/replaceChild(nodeToReplace, replacementNode){if(!nodeToReplace ||!replacementNode){returnfalse;}if(this.left &&this.nodeComparator.equal(this.left, nodeToReplace)){this.left = replacementNode;returntrue;}if(this.right &&this.nodeComparator.equal(this.right, nodeToReplace)){this.right = replacementNode;returntrue;}returnfalse;}/**
* @param {BinaryTreeNode} sourceNode
* @param {BinaryTreeNode} targetNode
*/staticcopyNode(sourceNode, targetNode){
targetNode.setValue(sourceNode.value);
targetNode.setLeft(sourceNode.left);
targetNode.setRight(sourceNode.right);}/**
* @return {*[]}
*/traverseInOrder(){let traverse =[];// Add left node.if(this.left){
traverse = traverse.concat(this.left.traverseInOrder());}// Add root.
traverse.push(this.value);// Add right node.if(this.right){
traverse = traverse.concat(this.right.traverseInOrder());}return traverse;}/**
* @return {string}
*/toString(){returnthis.traverseInOrder().toString();}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
Comparator.js完整源代码
exportdefaultclassComparator{/**
* Constructor.
* @param {function(a: *, b: *)} [compareFunction] - It may be custom compare function that, let's
* say may compare custom objects together.
*/constructor(compareFunction){this.compare = compareFunction || Comparator.defaultCompareFunction;}/**
* Default comparison function. It just assumes that "a" and "b" are strings or numbers.
* @param {(string|number)} a
* @param {(string|number)} b
* @returns {number}
*/staticdefaultCompareFunction(a, b){if(a === b){return0;}return a < b ?-1:1;}/**
* Checks if two variables are equal.
* @param {*} a
* @param {*} b
* @return {boolean}
*/equal(a, b){returnthis.compare(a, b)===0;}/**
* Checks if variable "a" is less than "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/lessThan(a, b){returnthis.compare(a, b)<0;}/**
* Checks if variable "a" is greater than "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/greaterThan(a, b){returnthis.compare(a, b)>0;}/**
* Checks if variable "a" is less than or equal to "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/lessThanOrEqual(a, b){returnthis.lessThan(a, b)||this.equal(a, b);}/**
* Checks if variable "a" is greater than or equal to "b".
* @param {*} a
* @param {*} b
* @return {boolean}
*/greaterThanOrEqual(a, b){returnthis.greaterThan(a, b)||this.equal(a, b);}/**
* Reverses the comparison order.
*/reverse(){const compareOriginal =this.compare;this.compare=(a, b)=>compareOriginal(b, a);}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
hashtable.js完整源代码
import LinkedList from'../linked-list/LinkedList';// Hash table size directly affects on the number of collisions.// The bigger the hash table size the less collisions you'll get.// For demonstrating purposes hash table size is small to show how collisions// are being handled.const defaultHashTableSize =32;exportdefaultclassHashTable{/**
* @param {number} hashTableSize
*/constructor(hashTableSize = defaultHashTableSize){// Create hash table of certain size and fill each bucket with empty linked list.this.buckets =Array(hashTableSize).fill(null).map(()=>newLinkedList());// Just to keep track of all actual keys in a fast way.this.keys ={};}/**
* Converts key string to hash number.
*
* @param {string} key
* @return {number}
*/hash(key){// For simplicity reasons we will just use character codes sum of all characters of the key// to calculate the hash.//// But you may also use more sophisticated approaches like polynomial string hash to reduce the// number of collisions://// hash = charCodeAt(0) * PRIME^(n-1) + charCodeAt(1) * PRIME^(n-2) + ... + charCodeAt(n-1)//// where charCodeAt(i) is the i-th character code of the key, n is the length of the key and// PRIME is just any prime number like 31.const hash = Array.from(key).reduce((hashAccumulator, keySymbol)=>(hashAccumulator + keySymbol.charCodeAt(0)),0,);// Reduce hash number so it would fit hash table size.return hash %this.buckets.length;}/**
* @param {string} key
* @param {*} value
*/set(key, value){const keyHash =this.hash(key);this.keys[key]= keyHash;const bucketLinkedList =this.buckets[keyHash];const node = bucketLinkedList.find({callback:(nodeValue)=> nodeValue.key === key });if(!node){// Insert new node.
bucketLinkedList.append({ key, value });}else{// Update value of existing node.
node.value.value = value;}}/**
* @param {string} key
* @return {*}
*/delete(key){const keyHash =this.hash(key);deletethis.keys[key];const bucketLinkedList =this.buckets[keyHash];const node = bucketLinkedList.find({callback:(nodeValue)=> nodeValue.key === key });if(node){return bucketLinkedList.delete(node.value);}returnnull;}/**
* @param {string} key
* @return {*}
*/get(key){const bucketLinkedList =this.buckets[this.hash(key)];const node = bucketLinkedList.find({callback:(nodeValue)=> nodeValue.key === key });return node ? node.value.value :undefined;}/**
* @param {string} key
* @return {boolean}
*/has(key){return Object.hasOwnProperty.call(this.keys, key);}/**
* @return {string[]}
*/getKeys(){return Object.keys(this.keys);}/**
* Gets the list of all the stored values in the hash table.
*
* @return {*[]}
*/getValues(){returnthis.buckets.reduce((values, bucket)=>{const bucketValues = bucket.toArray().map((linkedListNode)=> linkedListNode.value.value);return values.concat(bucketValues);},[]);}}