blob: 79f3aa4e36d154e9d2dbd9c937c0ca3cf9b2b128 [file] [log] [blame]
/*
* Copyright (C) 2010, 2011, 2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var RedBlackTree = (function(){
function compare(a, b) {
return a.compareTo(b);
}
function treeMinimum(x) {
while (x.left)
x = x.left;
return x;
}
function treeMaximum(x) {
while (x.right)
x = x.right;
return x;
}
function Node(key, value) {
this.key = key;
this.value = value;
this.left = null;
this.right = null;
this.parent = null;
this.color = "red";
}
Node.prototype.successor = function() {
var x = this;
if (x.right)
return treeMinimum(x.right);
var y = x.parent;
while (y && x == y.right) {
x = y;
y = y.parent;
}
return y;
};
Node.prototype.predecessor = function() {
var x = this;
if (x.left)
return treeMaximum(x.left);
var y = x.parent;
while (y && x == y.left) {
x = y;
y = y.parent;
}
return y;
};
Node.prototype.toString = function() {
return this.key + "=>" + this.value + " (" + this.color + ")";
};
function RedBlackTree() {
this._root = null;
}
RedBlackTree.prototype.put = function(key, value) {
var insertionResult = this._treeInsert(key, value);
if (!insertionResult.isNewEntry)
return insertionResult.oldValue;
var x = insertionResult.newNode;
while (x != this._root && x.parent.color == "red") {
if (x.parent == x.parent.parent.left) {
var y = x.parent.parent.right;
if (y && y.color == "red") {
// Case 1
x.parent.color = "black";
y.color = "black";
x.parent.parent.color = "red";
x = x.parent.parent;
} else {
if (x == x.parent.right) {
// Case 2
x = x.parent;
this._leftRotate(x);
}
// Case 3
x.parent.color = "black";
x.parent.parent.color = "red";
this._rightRotate(x.parent.parent);
}
} else {
// Same as "then" clause with "right" and "left" exchanged.
var y = x.parent.parent.left;
if (y && y.color == "red") {
// Case 1
x.parent.color = "black";
y.color = "black";
x.parent.parent.color = "red";
x = x.parent.parent;
} else {
if (x == x.parent.left) {
// Case 2
x = x.parent;
this._rightRotate(x);
}
// Case 3
x.parent.color = "black";
x.parent.parent.color = "red";
this._leftRotate(x.parent.parent);
}
}
}
this._root.color = "black";
return null;
};
RedBlackTree.prototype.remove = function(key) {
var z = this._findNode(key);
if (!z)
return null;
// Y is the node to be unlinked from the tree.
var y;
if (!z.left || !z.right)
y = z;
else
y = z.successor();
// Y is guaranteed to be non-null at this point.
var x;
if (y.left)
x = y.left;
else
x = y.right;
// X is the child of y which might potentially replace y in the tree. X might be null at
// this point.
var xParent;
if (x) {
x.parent = y.parent;
xParent = x.parent;
} else
xParent = y.parent;
if (!y.parent)
this._root = x;
else {
if (y == y.parent.left)
y.parent.left = x;
else
y.parent.right = x;
}
if (y != z) {
if (y.color == "black")
this._removeFixup(x, xParent);
y.parent = z.parent;
y.color = z.color;
y.left = z.left;
y.right = z.right;
if (z.left)
z.left.parent = y;
if (z.right)
z.right.parent = y;
if (z.parent) {
if (z.parent.left == z)
z.parent.left = y;
else
z.parent.right = y;
} else
this._root = y;
} else if (y.color == "black")
this._removeFixup(x, xParent);
return z.value;
};
RedBlackTree.prototype.get = function(key) {
var node = this._findNode(key);
if (!node)
return null;
return node.value;
};
RedBlackTree.prototype.forEach = function(callback) {
if (!this._root)
return;
for (var current = treeMinimum(this._root); current; current = current.successor())
callback(current.key, current.value);
};
RedBlackTree.prototype.asArray = function() {
var result = [];
this.forEach(function(key, value) {
result.push({key: key, value: value});
});
return result;
};
RedBlackTree.prototype.toString = function() {
var result = "[";
var first = true;
this.forEach(function(key, value) {
if (first)
first = false;
else
result += ", ";
result += key + "=>" + value;
});
return result + "]";
};
RedBlackTree.prototype._findNode = function(key) {
for (var current = this._root; current;) {
var comparisonResult = compare(key, current.key);
if (!comparisonResult)
return current;
if (comparisonResult < 0)
current = current.left;
else
current = current.right;
}
return null;
};
RedBlackTree.prototype._treeInsert = function(key, value) {
var y = null;
var x = this._root;
while (x) {
y = x;
var comparisonResult = key.compareTo(x.key);
if (comparisonResult < 0)
x = x.left;
else if (comparisonResult > 0)
x = x.right;
else {
var oldValue = x.value;
x.value = value;
return {isNewEntry:false, oldValue:oldValue};
}
}
var z = new Node(key, value);
z.parent = y;
if (!y)
this._root = z;
else {
if (key.compareTo(y.key) < 0)
y.left = z;
else
y.right = z;
}
return {isNewEntry:true, newNode:z};
};
RedBlackTree.prototype._leftRotate = function(x) {
var y = x.right;
// Turn y's left subtree into x's right subtree.
x.right = y.left;
if (y.left)
y.left.parent = x;
// Link x's parent to y.
y.parent = x.parent;
if (!x.parent)
this._root = y;
else {
if (x == x.parent.left)
x.parent.left = y;
else
x.parent.right = y;
}
// Put x on y's left.
y.left = x;
x.parent = y;
return y;
};
RedBlackTree.prototype._rightRotate = function(y) {
var x = y.left;
// Turn x's right subtree into y's left subtree.
y.left = x.right;
if (x.right)
x.right.parent = y;
// Link y's parent to x;
x.parent = y.parent;
if (!y.parent)
this._root = x;
else {
if (y == y.parent.left)
y.parent.left = x;
else
y.parent.right = x;
}
x.right = y;
y.parent = x;
return x;
};
RedBlackTree.prototype._removeFixup = function(x, xParent) {
while (x != this._root && (!x || x.color == "black")) {
if (x == xParent.left) {
// Note: the text points out that w cannot be null. The reason is not obvious from
// simply looking at the code; it comes about from the properties of the red-black
// tree.
var w = xParent.right;
if (w.color == "red") {
// Case 1
w.color = "black";
xParent.color = "red";
this._leftRotate(xParent);
w = xParent.right;
}
if ((!w.left || w.left.color == "black")
&& (!w.right || w.right.color == "black")) {
// Case 2
w.color = "red";
x = xParent;
xParent = x.parent;
} else {
if (!w.right || w.right.color == "black") {
// Case 3
w.left.color = "black";
w.color = "red";
this._rightRotate(w);
w = xParent.right;
}
// Case 4
w.color = xParent.color;
xParent.color = "black";
if (w.right)
w.right.color = "black";
this._leftRotate(xParent);
x = this._root;
xParent = x.parent;
}
} else {
// Same as "then" clause with "right" and "left" exchanged.
var w = xParent.left;
if (w.color == "red") {
// Case 1
w.color = "black";
xParent.color = "red";
this._rightRotate(xParent);
w = xParent.left;
}
if ((!w.right || w.right.color == "black")
&& (!w.left || w.left.color == "black")) {
// Case 2
w.color = "red";
x = xParent;
xParent = x.parent;
} else {
if (!w.left || w.left.color == "black") {
// Case 3
w.right.color = "black";
w.color = "red";
this._leftRotate(w);
w = xParent.left;
}
// Case 4
w.color = xParent.color;
xParent.color = "black";
if (w.left)
w.left.color = "black";
this._rightRotate(xParent);
x = this._root;
xParent = x.parent;
}
}
}
if (x)
x.color = "black";
};
return RedBlackTree;
})();