UNPKG

rbtree

Version:

An implementation of a Red-Black Tree for node.js

868 lines (722 loc) 20.2 kB
/** * @module rbtree */ exports = module.exports = RBTree var assert = require('assert') , util = require('util') var RED = 'red' , BLACK = 'black' /** * * @class RBTree * @constructor * @param {Function} cmp */ function RBTree(cmp) { assert.ok(typeof cmp === 'function') this.root = null this.cmp = cmp } RBTree.RBTree = RBTree //RBTree.Node = Node //RBTree.RED = RED //RBTree.BLACK = BLACK // // Utility functions // /** * Reparent's `node` into the position `orig` occupied. * * @method _replaceNode * @private * @param {Node} orig must be non-null * @param {Node} node may be null */ RBTree.prototype._replaceNode = function(orig, node) { assert.ok(orig) if (orig.parent) { if (orig.parent.ln === orig) orig.parent.ln = node else orig.parent.rn = node } else this.root = node if (node) node.parent = orig.parent } /** * Perform a rotate-left operation on the tree * * @method _rotateLeft * @private * @param {Node} node * @return {Node} the new root of this sub-tree */ RBTree.prototype._rotateLeft = function(node) { var parent = node.parent , right = node.rn //re-parent node & right if (parent) { if (node.isLeftChild()) { parent.ln = right } else { parent.rn = right } } else this.root = right right.parent = parent node.parent = right //handle orphaned node node.rn = right.ln if (right.ln) right.ln.parent = node right.ln = node return right //new root of this tree } /** * Perform a rotate-right operation on the tree * * @method _rotateRight * @private * @param {Node} node * @return {Node} the new root of this sub-tree */ RBTree.prototype._rotateRight = function(node) { var parent = node.parent , left = node.ln //re-parent node & left if (parent) { if (node.isLeftChild()) parent.ln = left else parent.rn = left } else this.root = left left.parent = parent node.parent = left //handle orphaned node node.ln = left.rn if (left.rn) left.rn.parent = node left.rn = node return left //new root of this tree } //generalized color getter; nulls are considered BLACK function color(n) { return n === null ? BLACK : n.color } /** * Find the RBTree Node containing `key`. * * @method _find * @private * @param {Key} key * @return {Node} the node containing `key` */ RBTree.prototype._find = function(key){ var cur = this.root while (cur) { var cmp = this.cmp(key, cur.key) if ( cmp < 0 /* key < cur.key */) { cur = cur.ln } else if ( cmp > 0 /* key > cur.key */) { cur = cur.rn } else /* cmp === 0; key === cur.key */ { break } } return cur } /** * Find the least/first node. * * @method _findLeast * @private * @return {Node} Retuns the first node. */ RBTree.prototype._findLeast = function(){ var cur = this.root if (cur) { while (cur.ln) { cur = cur.ln } } return cur } /** * Find the greatest/last node. * * @method _findGreatest * @private * @return {Node} Returns the last node. */ RBTree.prototype._findGreatest = function(){ var cur = this.root if (cur) { while (cur.rn) { cur = cur.rn } } return cur } /** * Find the first RBTree Node coming from the low side that is greater-than * `key`. * * @method _findLeastGT * @private * @param {Key} key * @return {Node} the node containing `key` */ RBTree.prototype._findLeastGT = function(key){ var cmp , cur = this.root , last /* last node satasfying criteria */ while (cur) { cmp = this.cmp(cur.key, key) if ( cmp > 0 /* cur.key > key */) { last = cur cur = cur.ln } else if ( cmp <= 0 /* cur.key <= key */) { cur = cur.rn } } return last } /** * Find the first RBTree Node coming from the low side that is * greater-than-or-equal to `key`. * * @method _findLeastGE * @private * @param {Key} key * @return {Node} the node containing `key` */ RBTree.prototype._findLeastGE = function(key){ var cmp , cur = this.root , last /* last node satasfying criteria */ while (cur) { cmp = this.cmp(cur.key, key) if ( cmp > 0 /* cur.key > key */) { last = cur cur = cur.ln } else if ( cmp < 0 /* cur.key < key */) { cur = cur.rn } else /* cmp == 0; key === cur.key */ { return cur } } return last } /** * Find the first RBTree Node coming from the high side that is less-than `key`. * * @method _findGreatestLT * @private * @param {Key} key * @return {Node} the node containing `key` */ RBTree.prototype._findGreatestLT = function(key){ var cmp , cur = this.root , last /* last seen node that satasfied criteria */ while (cur) { cmp = this.cmp(cur.key, key) if ( cmp < 0 /* cur.key < key */) { last = cur cur = cur.rn } else if ( cmp >= 0 /* cur.key > key */) { cur = cur.ln } } return last } /** * Find the first RBTree Node coming from the high side that is * less-than-or-equal to `key`. * * @method _findGreatestLE * @private * @param {Key} key * @return {Node} the node containing `key` */ RBTree.prototype._findGreatestLE = function(key){ var cmp , cur = this.root , last /* last node satasfying criteria */ while (cur) { cmp = this.cmp(cur.key, key) if ( cmp < 0 /* cur.key < key */) { last = cur cur = cur.rn } else if ( cmp > 0 /* cur.key > key */) { cur = cur.ln } else /* cmp == 0; key == cur.key */ { return cur } } return last } /** * Find the next Node after the given Node. * * @method _findNext * @private * @param {Node} cur * @return {Node} */ RBTree.prototype._findNext = function(cur){ if (cur && cur.rn) { cur = cur.rn while (cur.ln) { cur = cur.ln } return cur } var prev while (cur) { prev = cur cur = cur.parent if (!prev.parent || prev.isLeftChild()) { break } } return cur } /** * Find the previous Node before the given Node. * * @method _findPrev * @private * @param {Node} cur * @return {Node} */ RBTree.prototype._findPrev = function(cur){ if (cur && cur.ln) { cur = cur.ln while (cur.rn) { cur = cur.rn } return cur } var prev while (cur) { prev = cur cur = cur.parent if (!prev.parent || prev.isRightChild()) { break } } return cur } /** * Visit each node in-order an call a function on the `(key,data)`. * * @method inorder * @public * @param {Function} fn fn(key, data) */ RBTree.prototype.inorder = function(fn, reverse){ if (reverse == undefined) reverse = false var stack = [] , cur cur = this.root while ( (cur!==null) || (stack.length>0) ) { while (cur) { stack.push(cur) if (reverse) cur = cur.rn else cur = cur.ln } cur = stack.pop() fn(cur.key, cur.val) if (reverse) cur = cur.ln else cur = cur.rn }//while } /** * Determine if a `key` exists in the tree. * * @method exists * @public * @param {Key} key * @return {Boolean} */ RBTree.prototype.exists = function(key){ var cur = this._find(key) return !!cur } /** * Retreive the data assosiated with `key`. * * @method get * @public * @param {Key} key * @return {Object} */ RBTree.prototype.get = function(key){ var cur = this._find(key) if (cur) return cur.val return undefined } /** * Insert a `(key,value)` pair into the tree. * * @method put * @public * @param {Key} key * @param {Value} val */ RBTree.prototype.put = function(key, val){ if (this.root === null) { this.root = new Node(key, val, null) this.root.setBlack() return } var cur = this.root while (1) { var cmp = this.cmp(key, cur.key) if (cmp === 0) { cur.val = val return //no new Node -> so no balancing -> so return from put() } else if (cmp < 0) { if (cur.ln === null) { cur.ln = new Node(key, val, cur) //new Nodes are always RED cur = cur.ln break } cur = cur.ln } else /* cmp > 0 */ { if (cur.rn === null) { cur.rn = new Node(key, val, cur) //new Nodes are always RED cur = cur.rn break } cur = cur.rn } }//while LOOP //Balance tree //RBT#1 All nodes are either RED or BLACK. //RBT#2 The root node is BLACK. //RBT#3 All leaves (nulls) are considered BLACK. //RBT#4 Both children of RED nodes must be BLACK. // correlary: RBT#4.1 if a node is RED it's parent must be BLACK //RBT#5 Every path from a node to the leaves must have the same number of // BLACK nodes. var uncle, gp while (cur) { //This loop is just in case we have to propengate a color change up the tree //Either we just inserted a new node (always RED), or we are propengating // a subtree's root change to RED up the tree. assert.ok(cur.isRed()) //so-called insert_case1 if (cur.parent === null) { //This is the root node. So According to RBT#2 it must be made BLACK cur.setBlack() return //or break } //so-called insert_case2 if (cur.parent.isBlack()) { //cur is red & parent is black so no RBT criteria is violated // RBT#5 hasn't changed cuz cur is RED return //or break } //From here on we know the parent is RED. //Hence, we have to fix the violation of RBT#4 //Also if the parent is RED there has to be a grandparent, because // if parent was root it would be black. RBT#2 uncle = cur.uncle() //could be null gp = cur.grandparent() //Since we know the parent is RED We know the grandparent is BLACK RBT#4.1 //so-called insert_case3 if (color(uncle) === RED) { //We know the parent is RED. If the uncle is also RED then changing BOTH // of them to black does not violate the RBT#5 for the grandparent tree. cur.parent.setBlack() uncle.setBlack() //grandparent has to be BLACK cuz the parent & uncle are RED // setting parent&uncle BLACK and grandparent RED means the black-height // of the grandparent(inclusive) tree doesn't change. gp.setRed() //Next we check the validity of the grandparent and up, cuz we changed it // and may have violated RBT#2, RBT#4, or RBT#5 cur = gp continue } //What we know: // 1) cur is RED // 2) parent is RED // 3) uncle is BLACK(or null) // 4) grandparent must be BLACK(or null) //in the worst-case we do a preparatory rotate. // worst-case is cur is the "inner" grandchild of gp // aka parent.key < cur.key < gp.key or gp.key < cur.key < parent.key //QUESTION: ok its the inner node; why do we do a prepatory rotate? // observation#1: parent will be the root of the tree after the last rotate // observation#2: the inner grandchild will be moved to the other side of // the new root (parent) as the grandparent's inner node //ANSWER: the whole point of this RED/BLACK shit(aka rules) is to propengate // a signal up the tree to indicate a change in the height of the // tree. Red-Black trees can be out of balance at most by a factor of // two: One side all BLACK and the other side alternating RED-BLACK. // Hence the singal is a RED-RED conflict. //GIVEN this answer we know that the Grandparent got to long on the side // with the RED-RED Child-Parent confict. We are going to rotate the tree // at the grandparent node (and twiddle a few colors) to maintain the // balance of the subtree. But if the tree got longer on the inside of the // parent(the soon to be "subtree root" replacing the grandparent) we need // to shorten that inner subtree of the soon to be rotated grandparent tree. // Therefor we do a prepatory rotate. //so-called insert_case4 if (cur.isRightChild() && cur.parent.isLeftChild()) { this._rotateLeft(cur.parent) cur = cur.ln //cur.ln was cur.parent } else if (cur.isLeftChild() && cur.parent.isRightChild()) { this._rotateRight(cur.parent) cur = cur.rn //cur.rn was cur.parent } //This is unnecessary. prep-rotate gp is still the same as the // no-prep-rotate gp. I'll assert to guarantee it. // gp = cur.grandparent() assert.ok(gp === cur.grandparent()) //In the no-prep-rotate case, we are setting the original cur.parent black //In the prep-rotate case, we are setting the original cur black cur.parent.setBlack() gp.setRed() //so-called insert_node5 if (cur.isLeftChild()) { this._rotateRight(gp) } else { this._rotateLeft(gp) } break; //Man I wish JS had goto's. That fuckwad that wrote gotos-considered-harmful //should be shot. Yes goto, like hammers/screwdrivers/guns, CAN BE harmful //IF you start wacking/stabbing/shooting at random. }//while //return }//put /** * Remove a `key` (and its value) from the tree. * * @method del * @public * @param {Key} key * @return {Boolean} whether or not the key was found and removed. */ /** * **depricated** use: [`tree.del()`](#method_del) * * Remove a `key` (and its value) from the tree. * * @method delete * @deprecated * @param {Key} key * @return {Boolean} whether or not the key was found and removed. */ RBTree.prototype.del = RBTree.prototype.delete = function(key){ var cur = this._find(key) if (!cur) return false //key NOT FOUND //if cur is an internal node if (cur.rn && cur.ln) { //have to find a victim //victim is the previous in-order node var victim = cur.ln while (victim.rn) victim = victim.rn cur.key = victim.key cur.val = victim.val cur = victim } //We know some thing about cur: // 1) it has, AT MOST, only one child; either it is the // a) original node found from the search and failed (cur.rn && cur.ln) // or // b) it is the victim node found by the next-in-order-search // and cur.rn === null. // 2) ?? //if cur is RED can just delete it. // MY REASONING is that if it is RED it can't have any children, cuz the // distance to the null side is 1 by definition and the distance to the child // side must be 2 or higher cuz the child side must be BLACK (+1) and then // null (+1). The child must be BLACK by RBT#4 var child = cur.ln ? cur.ln : cur.rn if (cur.isRed()) { assert(cur.ln === null, "cur has left child") assert(cur.rn === null, "cur has right child") if (cur.isLeftChild()) cur.parent.ln = null else cur.parent.rn = null return true //THE END } //cur is BLACK //if cur has one non-null child if (child) { assert.ok(child.isRed(), "child of node-to-be-deleted is NOT RED! WTF!?!") assert.ok(child.ln === null) assert.ok(child.rn === null) child.setBlack() this._replaceNode(cur, child) return true //THE END } // THE WORST/MOST-COMPLICATED CASE //cur is BLACK with no children assert.ok(cur.isBlack()) assert.ok(cur.ln === null) assert.ok(cur.rn === null) child = new Nil() //place holder the will be eliminated later this._replaceNode(cur, child) //this removes cur from the tree cur = child //child is a magic Nil Node to make sure it doesn't get modified while (cur) { //so-called delete_case1 if (cur.parent === null) {//this is the tree root break } var sib = cur.sibling() assert.ok(sib !== null) //By RBT#5 a BLACK node MUST have a sibling //BULLSHIT! first time thru cur is Nil //DOUBLE BULLSHIT! cur WAS BLACK then replaceNode'd //so-called delete_case2 if ( sib.isRed() ) { //cur is BLACK and sib it RED, so the sib-side is longer //first-time-thru we have deleted cur AND sib-side was already longer //so we rotate away from the sib-side cur.parent.setRed() sib.setBlack() if (cur.isLeftChild()) this._rotateLeft(cur.parent) else this._rotateRight(cur.parent) sib = cur.sibling() //recalculate sibling assert.ok(sib !== null) //if sib.isRed by RBT#4 children of sib must be // BLACK and by RBT#5 there must be two children. // So the rotation made one of those children the // new sibling of cur } //if delete_case2 fires then delete_case3 does not // look at cur.parent.setRed above and cur.parent.isBlack below //so-called delete_case3 if ( cur.parent.isBlack() && sib.isBlack() && color(sib.ln) === BLACK && color(sib.rn) === BLACK ) { sib.setRed() cur = cur.parent continue //We've deleted cur (aka cur-side is -1 BLACK) so we lightened up sib-side //by making sib RED. Hence the cur.parent tree is balanced but lighter! //So we kick the rebalancing up to the cur.parent level. } //so-called delete_case4 if ( cur.parent.isRed() && sib.isBlack() && color(sib.ln) === BLACK && color(sib.rn) === BLACK ) { sib.setRed() cur.parent.setBlack() break //First-time-thru, we deleted cur and now we sib.setRed for balance and //made cur.parent.setBlack so -1 + 1 == 0 and cur.parent is balanced. //Thus we are done. } //so-called delete_case5 if (sib.isBlack()) { if ( cur.isLeftChild() && color(sib.ln) === RED && color(sib.rn) === BLACK ) { sib.setRed() sib.ln.setBlack() this._rotateRight(sib) //rotating away from the RED child sib = cur.sibling() assert.ok(sib !== null) } else if ( cur.isRightChild() && color(sib.ln) === BLACK && color(sib.rn) === RED ) { sib.setRed() sib.rn.setBlack() this._rotateLeft(sib) sib = cur.sibling() assert.ok(sib !== null) } } //so-called delete_case6 sib.color = cur.parent.color cur.parent.setBlack() if (cur.isLeftChild()) { sib.rn.setBlack() this._rotateLeft(cur.parent) } else { sib.ln.setBlack() this._rotateRight(cur.parent) } cur = null //THE END }//while this._replaceNode(child, null) //child was just a place holder for null return true } //delete function Node(key, val, parent, color) { this.key = key this.val = val this.parent = (parent === undefined) ? null : parent this.ln = null this.rn = null this.color = (color === undefined) ? RED : color } Node.prototype.isRed = function(){ return this.color === RED } Node.prototype.isBlack = function(){ return this.color === BLACK } Node.prototype.setRed = function(){ this.color = RED } Node.prototype.setBlack = function(){ this.color = BLACK } Node.prototype.sibling = function(){ return this.isLeftChild() ? this.parent.rn : this.parent.ln } Node.prototype.uncle = function(){ assert.ok(this.parent) return this.parent.sibling() } Node.prototype.grandparent = function(){ assert.ok(this.parent) return this.parent.parent } Node.prototype.isRightChild = function(){ assert.ok(this.parent) return this.parent.rn === this } Node.prototype.isLeftChild = function(){ assert.ok(this.parent) return this.parent.ln === this } var const_null_prop = { value : null , writable : false , configurable : false , enumerable : true } var const_black_prop = { value : BLACK , writable : false , configurable : false , enumerable : true } function Nil() { this.parent = null Object.defineProperty(this, 'key' , const_null_prop) Object.defineProperty(this, 'val' , const_null_prop) Object.defineProperty(this, 'ln' , const_null_prop) Object.defineProperty(this, 'rn' , const_null_prop) Object.defineProperty(this, 'color', const_black_prop) } util.inherits(Nil, Node)