Skip to content

Commit 03bb9b1

Browse files
committed
Merge branch 'master' of github.com:mgechev/javascript-algorithms
* 'master' of github.com:mgechev/javascript-algorithms: more doc fixes. Added more docs for datastructures. Added linked list spec. added new line. Basic heap specs. typo in heap. BST remove root with children bug fix. AVL Tree balance on removal. Added remove specs.
2 parents ecc523c + 4a2d98e commit 03bb9b1

File tree

16 files changed

+475
-58
lines changed

16 files changed

+475
-58
lines changed

src/data-structures/avl-tree.js

Lines changed: 122 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,94 @@
9393
return true;
9494
};
9595

96+
/**
97+
* Gets the nodes to be restructured during an AVL restructure
98+
* after a remove/delete takes place.
99+
*
100+
* @public
101+
* @method
102+
* @param {Array} traveledNodes Array of previously traveled nodes
103+
* that are used to help determine the nodes to be restructured.
104+
*/
105+
exports.AVLTree.prototype._getNodesToRestructureRemove =
106+
function (traveledNodes) {
107+
// z is last traveled node - imbalance found at z
108+
var zIndex = traveledNodes.length;
109+
zIndex -= 1;
110+
var z = traveledNodes[zIndex];
111+
// y should be child of z with larger height
112+
// (cannot be ancestor of removed node)
113+
var y;
114+
if (z._left !== null && z._right !== null){
115+
y = (z._left === y) ? z._right : z._left;
116+
}else if (z._left !== null && z._right === null){
117+
y = z._left;
118+
}else if (z._right !== null && z._left === null){
119+
y = z._right;
120+
}
121+
// x should be tallest child of y.
122+
// If children same height, x should be child of y
123+
// that has same orientation as z to y.
124+
var x;
125+
if (y._left !== null && y._right !== null){
126+
if (y._left._height > y._right._height){
127+
x = y._left;
128+
}else if (y._left._height < y._right._height){
129+
x = y._right;
130+
}else if (y._left._height === y._right._height){
131+
x = (z._left === y) ? y._left : y._right;
132+
}
133+
}else if (y._left !== null && y._right === null){
134+
x = y._left;
135+
}else if (y._right !== null && y._left === null){
136+
x = y._right;
137+
}
138+
return [x, y, z];
139+
};
140+
141+
/**
142+
* Gets the nodes to be restructured during an AVL restructure
143+
* after an insert takes place.
144+
*
145+
* @public
146+
* @method
147+
* @param {Array} traveledNodes Array of previously traveled nodes
148+
* that are used to help determine the nodes to be restructured.
149+
*/
150+
exports.AVLTree.prototype._getNodesToRestructureInsert =
151+
function (traveledNodes) {
152+
// z is last traveled node - imbalance found at z
153+
var zIndex = traveledNodes.length;
154+
zIndex -= 1;
155+
var z = traveledNodes[zIndex];
156+
// y should be child of z with larger height
157+
// (must be ancestor of inserted node)
158+
// therefore, last traveled node is correct.
159+
var yIndex = traveledNodes.length;
160+
yIndex -= 2;
161+
var y = traveledNodes[yIndex];
162+
// x should be tallest child of y.
163+
// If children same height, x should be ancestor
164+
// of inserted node (in traveled path).
165+
var x;
166+
if (y._left !== null && y._right !== null){
167+
if (y._left._height > y._right._height){
168+
x = y._left;
169+
}else if (y._left._height < y._right._height){
170+
x = y._right;
171+
}else if (y._left._height === y._right._height){
172+
var xIndex = traveledNodes.length;
173+
xIndex -= 3;
174+
x = traveledNodes[xIndex];
175+
}
176+
}else if (y._left !== null && y._right === null){
177+
x = y._left;
178+
}else if (y._right !== null && y._left === null){
179+
x = y._right;
180+
}
181+
return [x, y, z];
182+
};
183+
96184
/**
97185
* Maintains the height balance property by
98186
* walking to root and checking for invalid height
@@ -102,21 +190,20 @@
102190
* @public
103191
* @method
104192
* @param {Node} node Started node.
193+
* @param {Boolean} isRemove Represents if method was called after remove.
105194
*/
106-
exports.AVLTree.prototype._maintainHeightBalanceProperty = function (node) {
195+
exports.AVLTree.prototype._maintainHeightBalanceProperty =
196+
function (node, isRemove) {
107197
var current = node;
108-
var path = []; //During restructure, use last 3 nodes traveled.
198+
var traveledNodes = [];
109199
while (current !== null){
110-
path.push(current);
200+
traveledNodes.push(current);
111201
current._height = this._getHeightAtNode(current);
112202
if (!this._isBalancedAtNode(current)){
113-
if (path.length >= 3){
114-
var nodesToRestructure = path.slice(0, 3);
115-
var x = nodesToRestructure[0];
116-
var y = nodesToRestructure[1];
117-
var z = nodesToRestructure[2];
118-
this._restructure(x, y, z);
119-
}
203+
var nodesToBeRestructured = (isRemove)
204+
? this._getNodesToRestructureRemove(traveledNodes)
205+
: this._getNodesToRestructureInsert(traveledNodes);
206+
this._restructure(nodesToBeRestructured);
120207
}
121208
current = current._parent;
122209
}
@@ -128,11 +215,13 @@
128215
*
129216
* @public
130217
* @method
131-
* @param {Node} x node with lowest height to be restructured.
132-
* @param {Node} y parent of x parameter.
133-
* @param {Node} z grandparent of x, largest height.
218+
* @param {Array} nodesToBeRestructured
219+
* array of nodes, in format, [x, y, z], to be restructured
134220
*/
135-
exports.AVLTree.prototype._restructure = function (x, y, z) {
221+
exports.AVLTree.prototype._restructure = function (nodesToBeRestructured) {
222+
var x = nodesToBeRestructured[0];
223+
var y = nodesToBeRestructured[1];
224+
var z = nodesToBeRestructured[2];
136225
//Determine Rotation Pattern
137226
if (z._right === y && y._right === x){
138227
this._rightRight(x, y, z);
@@ -337,7 +426,7 @@
337426
}
338427
if (!current[insertKey]) {
339428
current[insertKey] = new exports.Node(value, null, null, current);
340-
this._maintainHeightBalanceProperty(current[insertKey]);
429+
this._maintainHeightBalanceProperty(current[insertKey], false);
341430
} else {
342431
this.insert(value, current[insertKey]);
343432
}
@@ -481,9 +570,11 @@
481570
*/
482571
exports.AVLTree.prototype._replaceChild =
483572
function (parent, oldChild, newChild) {
484-
if (!parent) {
573+
if (parent === null) {
485574
this._root = newChild;
486-
this._root._parent = null;
575+
if (this._root !== null){
576+
this._root._parent = null;
577+
}
487578
} else {
488579
if (parent._left === oldChild) {
489580
parent._left = newChild;
@@ -501,33 +592,31 @@
501592
* Average runtime complexity: O(log N).
502593
*
503594
* @public
504-
* @param {Node} node to be removed
595+
* @param {Number|String} value of node to be removed
505596
* @returns {Boolean} True/false depending
506597
* on whether the given node is removed.
507598
*/
508-
exports.AVLTree.prototype.remove = function (node) {
599+
exports.AVLTree.prototype.remove = function (value) {
600+
var node = this.find(value);
509601
if (!node) {
510602
return false;
511603
}
512-
513604
if (node._left && node._right) {
514605
var min = this._findMin(node._right);
515606
var temp = node.value;
516-
517607
node.value = min.value;
518608
min.value = temp;
519609
return this.remove(min);
520610
} else {
521-
if (node._parent !== null) {
522-
if (node._left) {
523-
this._replaceChild(node._parent, node, node._left);
524-
} else if (node._right) {
525-
this._replaceChild(node._parent, node, node._right);
526-
} else {
527-
this._replaceChild(node._parent, node, null);
528-
}
529-
}else {
530-
this._root = null;
611+
if (node._left) {
612+
this._replaceChild(node._parent, node, node._left);
613+
this._maintainHeightBalanceProperty(node._left, true);
614+
} else if (node._right) {
615+
this._replaceChild(node._parent, node, node._right);
616+
this._maintainHeightBalanceProperty(node._right, true);
617+
} else {
618+
this._replaceChild(node._parent, node, null);
619+
this._maintainHeightBalanceProperty(node._parent, true);
531620
}
532621
return true;
533622
}
@@ -656,12 +745,12 @@
656745
* @returns {Node} The lowest common ancestor of the two nodes or null.
657746
*/
658747
exports.AVLTree.prototype.lowestCommonAncestor =
659-
function (firstNode, secondNode) {
748+
function (firstNode, secondNode) {
660749
return this._lowestCommonAncestor(firstNode, secondNode, this._root);
661750
};
662751

663752
exports.AVLTree.prototype._lowestCommonAncestor =
664-
function (firstNode, secondNode, current) {
753+
function (firstNode, secondNode, current) {
665754
var firstNodeInLeft = this._existsInSubtree(firstNode, current._left);
666755
var secondNodeInLeft = this._existsInSubtree(secondNode, current._left);
667756
var firstNodeInRight = this._existsInSubtree(firstNode, current._right);

src/data-structures/binary-search-tree.js

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,9 @@
225225
function (parent, oldChild, newChild) {
226226
if (!parent) {
227227
this._root = newChild;
228-
this._root._parent = null;
228+
if (this._root !== null){
229+
this._root._parent = null;
230+
}
229231
} else {
230232
if (parent._left === oldChild) {
231233
parent._left = newChild;
@@ -251,25 +253,19 @@
251253
if (!node) {
252254
return false;
253255
}
254-
255256
if (node._left && node._right) {
256257
var min = this._findMin(node._right);
257258
var temp = node.value;
258-
259259
node.value = min.value;
260260
min.value = temp;
261261
return this.remove(min);
262262
} else {
263-
if (node._parent !== null) {
264-
if (node._left) {
265-
this._replaceChild(node._parent, node, node._left);
266-
} else if (node._right) {
267-
this._replaceChild(node._parent, node, node._right);
268-
} else {
269-
this._replaceChild(node._parent, node, null);
270-
}
271-
}else {
272-
this._root = null;
263+
if (node._left) {
264+
this._replaceChild(node._parent, node, node._left);
265+
} else if (node._right) {
266+
this._replaceChild(node._parent, node, node._right);
267+
} else {
268+
this._replaceChild(node._parent, node, null);
273269
}
274270
return true;
275271
}
@@ -334,6 +330,13 @@
334330
return this._findMax(this._root);
335331
};
336332

333+
/**
334+
* Checks if a given node is balanced.
335+
*
336+
* @private
337+
* @param {Node} current Node to have balance checked.
338+
* @returns {Boolean} Boolean of whether or not provided node is balanced.
339+
*/
337340
exports.BinaryTree.prototype._isBalanced = function (current) {
338341
if (!current) {
339342
return true;
@@ -383,6 +386,13 @@
383386
return this._getHeight(this._root);
384387
};
385388

389+
/**
390+
* Recursive worker function for getHeight()
391+
*
392+
* @private
393+
* @param {Node} node Node at current recursive frame.
394+
* @returns {Number} Height of the Node in the parameter.
395+
*/
386396
exports.BinaryTree.prototype._getHeight = function (node) {
387397
if (!node) {
388398
return 0;
@@ -395,13 +405,28 @@
395405
* Finds the lowest common ancestor of two nodes.
396406
*
397407
* @public
408+
* @param {Node} firstNode First node to be considered when checking
409+
* for ancestor.
410+
* @param {Node} secondNode Second node to be considered when checking
411+
* for ancestor.
398412
* @returns {Node} The lowest common ancestor of the two nodes or null.
399413
*/
400414
exports.BinaryTree.prototype.lowestCommonAncestor =
401415
function (firstNode, secondNode) {
402416
return this._lowestCommonAncestor(firstNode, secondNode, this._root);
403417
};
404418

419+
/**
420+
* Obtains the lowest common ancestor for the given nodes.
421+
*
422+
* @private
423+
* @param {Node} firstNode First node to be considered when checking
424+
* for ancestor.
425+
* @param {Node} secondNode Second node to be considered when checking
426+
* for ancestor.
427+
* @param {Node} current Current node.
428+
* @returns {Node} The lowest common ancestor of the two nodes or null.
429+
*/
405430
exports.BinaryTree.prototype._lowestCommonAncestor =
406431
function (firstNode, secondNode, current) {
407432
var firstNodeInLeft = this._existsInSubtree(firstNode, current._left);
@@ -421,6 +446,14 @@
421446
return null;
422447
};
423448

449+
/**
450+
* Checks if a given node exists in a subtree.
451+
*
452+
* @private
453+
* @param {Node} node Node to check for.
454+
* @param {Node} root Root node of a given subtree.
455+
* @returns {Node} The lowest common ancestor of the two nodes or null.
456+
*/
424457
exports.BinaryTree.prototype._existsInSubtree = function (node, root) {
425458
if (!root) {
426459
return false;

src/data-structures/heap.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
*
4848
* @public
4949
* @constructor
50-
* @param {Function} cmp Function used for comparition between the elements.
50+
* @param {Function} cmp Function used for comparison between the elements.
5151
*/
5252
exports.Heap = function (cmp) {
5353
this._heap = [];

src/data-structures/linked-list.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@
165165
};
166166

167167
/**
168-
* Check or linked list contains cycle.
168+
* Check if linked list contains cycle.
169169
*
170170
* @public
171171
* @method
@@ -222,6 +222,12 @@
222222
return temp;
223223
};
224224

225+
/**
226+
* Reverses the linked list recursively
227+
*
228+
* @public
229+
* @method
230+
*/
225231
exports.LinkedList.prototype.recursiveReverse = function () {
226232

227233
function inverse(current, next) {
@@ -242,6 +248,12 @@
242248
this.last = temp;
243249
};
244250

251+
/**
252+
* Reverses the linked list iteratively
253+
*
254+
* @public
255+
* @method
256+
*/
245257
exports.LinkedList.prototype.reverse = function () {
246258
if (!this.first || !this.first.next) {
247259
return;

0 commit comments

Comments
 (0)