Skip to content

Commit 136c3fb

Browse files
committed
AVL Tree balance on removal. Added remove specs.
1 parent c6e3f8e commit 136c3fb

File tree

2 files changed

+142
-33
lines changed

2 files changed

+142
-33
lines changed

src/data-structures/avl-tree.js

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

96+
exports.AVLTree.prototype._getNodesToRestructureRemove =
97+
function (traveledNodes) {
98+
// z is last traveled node - imbalance found at z
99+
var zIndex = traveledNodes.length;
100+
zIndex -= 1;
101+
var z = traveledNodes[zIndex];
102+
// y should be child of z with larger height
103+
// (cannot be ancestor of removed node)
104+
var y;
105+
if (z._left !== null && z._right !== null){
106+
y = (z._left === y) ? z._right : z._left;
107+
}else if (z._left !== null && z._right === null){
108+
y = z._left;
109+
}else if (z._right !== null && z._left === null){
110+
y = z._right;
111+
}
112+
// x should be tallest child of y.
113+
// If children same height, x should be child of y
114+
// that has same orientation as z to y.
115+
var x;
116+
if (y._left !== null && y._right !== null){
117+
if (y._left._height > y._right._height){
118+
x = y._left;
119+
}else if (y._left._height < y._right._height){
120+
x = y._right;
121+
}else if (y._left._height === y._right._height){
122+
x = (z._left === y) ? y._left : y._right;
123+
}
124+
}else if (y._left !== null && y._right === null){
125+
x = y._left;
126+
}else if (y._right !== null && y._left === null){
127+
x = y._right;
128+
}
129+
return [x, y, z];
130+
};
131+
132+
exports.AVLTree.prototype._getNodesToRestructureInsert =
133+
function (traveledNodes) {
134+
// z is last traveled node - imbalance found at z
135+
var zIndex = traveledNodes.length;
136+
zIndex -= 1;
137+
var z = traveledNodes[zIndex];
138+
// y should be child of z with larger height
139+
// (must be ancestor of inserted node)
140+
// therefore, last traveled node is correct.
141+
var yIndex = traveledNodes.length;
142+
yIndex -= 2;
143+
var y = traveledNodes[yIndex];
144+
// x should be tallest child of y.
145+
// If children same height, x should be ancestor
146+
// of inserted node (in traveled path).
147+
var x;
148+
if (y._left !== null && y._right !== null){
149+
if (y._left._height > y._right._height){
150+
x = y._left;
151+
}else if (y._left._height < y._right._height){
152+
x = y._right;
153+
}else if (y._left._height === y._right._height){
154+
var xIndex = traveledNodes.length;
155+
xIndex -= 3;
156+
x = traveledNodes[xIndex];
157+
}
158+
}else if (y._left !== null && y._right === null){
159+
x = y._left;
160+
}else if (y._right !== null && y._left === null){
161+
x = y._right;
162+
}
163+
return [x, y, z];
164+
};
165+
96166
/**
97167
* Maintains the height balance property by
98168
* walking to root and checking for invalid height
@@ -102,21 +172,20 @@
102172
* @public
103173
* @method
104174
* @param {Node} node Started node.
175+
* @param {Boolean} isRemove Represents if method was called after remove.
105176
*/
106-
exports.AVLTree.prototype._maintainHeightBalanceProperty = function (node) {
177+
exports.AVLTree.prototype._maintainHeightBalanceProperty =
178+
function (node, isRemove) {
107179
var current = node;
108-
var path = []; //During restructure, use last 3 nodes traveled.
180+
var traveledNodes = [];
109181
while (current !== null){
110-
path.push(current);
182+
traveledNodes.push(current);
111183
current._height = this._getHeightAtNode(current);
112184
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-
}
185+
var nodesToBeRestructured = (isRemove)
186+
? this._getNodesToRestructureRemove(traveledNodes)
187+
: this._getNodesToRestructureInsert(traveledNodes);
188+
this._restructure(nodesToBeRestructured);
120189
}
121190
current = current._parent;
122191
}
@@ -128,11 +197,13 @@
128197
*
129198
* @public
130199
* @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.
200+
* @param {Array} nodesToBeRestructured
201+
* array of nodes, in format, [x, y, z], to be restructured
134202
*/
135-
exports.AVLTree.prototype._restructure = function (x, y, z) {
203+
exports.AVLTree.prototype._restructure = function (nodesToBeRestructured) {
204+
var x = nodesToBeRestructured[0];
205+
var y = nodesToBeRestructured[1];
206+
var z = nodesToBeRestructured[2];
136207
//Determine Rotation Pattern
137208
if (z._right === y && y._right === x){
138209
this._rightRight(x, y, z);
@@ -337,7 +408,7 @@
337408
}
338409
if (!current[insertKey]) {
339410
current[insertKey] = new exports.Node(value, null, null, current);
340-
this._maintainHeightBalanceProperty(current[insertKey]);
411+
this._maintainHeightBalanceProperty(current[insertKey], false);
341412
} else {
342413
this.insert(value, current[insertKey]);
343414
}
@@ -481,9 +552,11 @@
481552
*/
482553
exports.AVLTree.prototype._replaceChild =
483554
function (parent, oldChild, newChild) {
484-
if (!parent) {
555+
if (parent === null) {
485556
this._root = newChild;
486-
this._root._parent = null;
557+
if (this._root !== null){
558+
this._root._parent = null;
559+
}
487560
} else {
488561
if (parent._left === oldChild) {
489562
parent._left = newChild;
@@ -501,33 +574,31 @@
501574
* Average runtime complexity: O(log N).
502575
*
503576
* @public
504-
* @param {Node} node to be removed
577+
* @param {Number|String} value of node to be removed
505578
* @returns {Boolean} True/false depending
506579
* on whether the given node is removed.
507580
*/
508-
exports.AVLTree.prototype.remove = function (node) {
581+
exports.AVLTree.prototype.remove = function (value) {
582+
var node = this.find(value);
509583
if (!node) {
510584
return false;
511585
}
512-
513586
if (node._left && node._right) {
514587
var min = this._findMin(node._right);
515588
var temp = node.value;
516-
517589
node.value = min.value;
518590
min.value = temp;
519591
return this.remove(min);
520592
} 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;
593+
if (node._left) {
594+
this._replaceChild(node._parent, node, node._left);
595+
this._maintainHeightBalanceProperty(node._left, true);
596+
} else if (node._right) {
597+
this._replaceChild(node._parent, node, node._right);
598+
this._maintainHeightBalanceProperty(node._right, true);
599+
} else {
600+
this._replaceChild(node._parent, node, null);
601+
this._maintainHeightBalanceProperty(node._parent, true);
531602
}
532603
return true;
533604
}
@@ -656,12 +727,12 @@
656727
* @returns {Node} The lowest common ancestor of the two nodes or null.
657728
*/
658729
exports.AVLTree.prototype.lowestCommonAncestor =
659-
function (firstNode, secondNode) {
730+
function (firstNode, secondNode) {
660731
return this._lowestCommonAncestor(firstNode, secondNode, this._root);
661732
};
662733

663734
exports.AVLTree.prototype._lowestCommonAncestor =
664-
function (firstNode, secondNode, current) {
735+
function (firstNode, secondNode, current) {
665736
var firstNodeInLeft = this._existsInSubtree(firstNode, current._left);
666737
var secondNodeInLeft = this._existsInSubtree(secondNode, current._left);
667738
var firstNodeInRight = this._existsInSubtree(firstNode, current._right);

test/data-structures/avl-tree.spec.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,42 @@ describe('AVL Tree', function () {
123123
expect(avlTree._root._left._right._right.value).toBe(27);
124124
expect(avlTree._root._left._right._right._height).toBe(1);
125125
});
126+
it('should remove nodes and balance properly (1)', function () {
127+
var avlTree = new AVLTree();
128+
avlTree.insert(30);
129+
avlTree.insert(15);
130+
avlTree.insert(60);
131+
avlTree.insert(90);
132+
avlTree.insert(100);
133+
avlTree.remove(15);
134+
// depth 1
135+
expect(avlTree._root.value).toBe(90);
136+
expect(avlTree._root._height).toBe(3);
137+
// depth 2
138+
expect(avlTree._root._left.value).toBe(30);
139+
expect(avlTree._root._left._height).toBe(2);
140+
141+
expect(avlTree._root._right.value).toBe(100);
142+
expect(avlTree._root._right._height).toBe(1);
143+
// depth 3
144+
expect(avlTree._root._left._right.value).toBe(60);
145+
expect(avlTree._root._left._right._height).toBe(1);
146+
});
147+
it('should remove nodes and balance properly (2)', function () {
148+
var avlTree = new AVLTree();
149+
avlTree.insert(55);
150+
avlTree.insert(25);
151+
avlTree.insert(11);
152+
avlTree.insert(1);
153+
avlTree.remove(55);
154+
// depth 1
155+
expect(avlTree._root.value).toBe(11);
156+
expect(avlTree._root._height).toBe(2);
157+
// depth 2
158+
expect(avlTree._root._left.value).toBe(1);
159+
expect(avlTree._root._left._height).toBe(1);
160+
161+
expect(avlTree._root._right.value).toBe(25);
162+
expect(avlTree._root._right._height).toBe(1);
163+
});
126164
});

0 commit comments

Comments
 (0)