Skip to content

Instantly share code, notes, and snippets.

@subeeshb
Created June 22, 2015 15:03
Show Gist options
  • Save subeeshb/dd338088ab04607b18a1 to your computer and use it in GitHub Desktop.
Save subeeshb/dd338088ab04607b18a1 to your computer and use it in GitHub Desktop.
binary search tree - pretty print
/*
* Binary Search Tree implementation in JavaScript
* Copyright (c) 2009 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* A binary search tree implementation in JavaScript. This implementation
* does not allow duplicate values to be inserted into the tree, ensuring
* that there is just one instance of each value.
* @class BinarySearchTree
* @constructor
*/
function BinarySearchTree() {
/**
* Pointer to root node in the tree.
* @property _root
* @type Object
* @private
*/
this._root = null;
}
BinarySearchTree.prototype = {
//restore constructor
constructor: BinarySearchTree,
//-------------------------------------------------------------------------
// Private members
//-------------------------------------------------------------------------
/**
* Appends some data to the appropriate point in the tree. If there are no
* nodes in the tree, the data becomes the root. If there are other nodes
* in the tree, then the tree must be traversed to find the correct spot
* for insertion.
* @param {variant} value The data to add to the list.
* @return {Void}
* @method add
*/
add: function (value){
//create a new item object, place data in
var node = {
value: value,
left: null,
right: null
},
//used to traverse the structure
current;
//special case: no items in the tree yet
if (this._root === null){
this._root = node;
} else {
current = this._root;
while(true){
//if the new value is less than this node's value, go left
if (value < current.value){
//if there's no left, then the new node belongs there
if (current.left === null){
current.left = node;
break;
} else {
current = current.left;
}
//if the new value is greater than this node's value, go right
} else if (value > current.value){
//if there's no right, then the new node belongs there
if (current.right === null){
current.right = node;
break;
} else {
current = current.right;
}
//if the new value is equal to the current one, just ignore
} else {
break;
}
}
}
},
/**
* Determines if the given value is present in the tree.
* @param {variant} value The value to find.
* @return {Boolean} True if the value is found, false if not.
* @method contains
*/
contains: function(value){
var found = false,
current = this._root
//make sure there's a node to search
while(!found && current){
//if the value is less than the current node's, go left
if (value < current.value){
current = current.left;
//if the value is greater than the current node's, go right
} else if (value > current.value){
current = current.right;
//values are equal, found it!
} else {
found = true;
}
}
//only proceed if the node was found
return found;
},
/**
* Removes the node with the given value from the tree. This may require
* moving around some nodes so that the binary search tree remains
* properly balanced.
* @param {variant} value The value to remove.
* @return {void}
* @method remove
*/
remove: function(value){
var found = false,
parent = null,
current = this._root,
childCount,
replacement,
replacementParent;
//make sure there's a node to search
while(!found && current){
//if the value is less than the current node's, go left
if (value < current.value){
parent = current;
current = current.left;
//if the value is greater than the current node's, go right
} else if (value > current.value){
parent = current;
current = current.right;
//values are equal, found it!
} else {
found = true;
}
}
//only proceed if the node was found
if (found){
//figure out how many children
childCount = (current.left !== null ? 1 : 0) + (current.right !== null ? 1 : 0);
//special case: the value is at the root
if (current === this._root){
switch(childCount){
//no children, just erase the root
case 0:
this._root = null;
break;
//one child, use one as the root
case 1:
this._root = (current.right === null ? current.left : current.right);
break;
//two children, little work to do
case 2:
//new root will be the old root's left child...maybe
replacement = this._root.left;
//find the right-most leaf node to be the real new root
while (replacement.right !== null){
replacementParent = replacement;
replacement = replacement.right;
}
//it's not the first node on the left
if (replacementParent !== null){
//remove the new root from it's previous position
replacementParent.right = replacement.left;
//give the new root all of the old root's children
replacement.right = this._root.right;
replacement.left = this._root.left;
} else {
//just assign the children
replacement.right = this._root.right;
}
//officially assign new root
this._root = replacement;
//no default
}
//non-root values
} else {
switch (childCount){
//no children, just remove it from the parent
case 0:
//if the current value is less than its parent's, null out the left pointer
if (current.value < parent.value){
parent.left = null;
//if the current value is greater than its parent's, null out the right pointer
} else {
parent.right = null;
}
break;
//one child, just reassign to parent
case 1:
//if the current value is less than its parent's, reset the left pointer
if (current.value < parent.value){
parent.left = (current.left === null ? current.right : current.left);
//if the current value is greater than its parent's, reset the right pointer
} else {
parent.right = (current.left === null ? current.right : current.left);
}
break;
//two children, a bit more complicated
case 2:
//reset pointers for new traversal
replacement = current.left;
replacementParent = current;
//find the right-most node
while(replacement.right !== null){
replacementParent = replacement;
replacement = replacement.right;
}
replacementParent.right = replacement.left;
//assign children to the replacement
replacement.right = current.right;
replacement.left = current.left;
//place the replacement in the right spot
if (current.value < parent.value){
parent.left = replacement;
} else {
parent.right = replacement;
}
//no default
}
}
}
},
/**
* Returns the number of items in the tree. To do this, a traversal
* must be executed.
* @return {int} The number of items in the tree.
* @method size
*/
size: function(){
var length = 0;
this.traverse(function(node){
length++;
});
return length;
},
/**
* Converts the tree into an array.
* @return {Array} An array containing all of the data in the tree.
* @method toArray
*/
toArray: function(){
var result = [];
this.traverse(function(node){
result.push(node.value);
});
return result;
},
/**
* Converts the list into a string representation.
* @return {String} A string representation of the list.
* @method toString
*/
toString: function(){
return this.toArray().toString();
},
/**
* Traverses the tree and runs the given method on each node it comes
* across while doing an in-order traversal.
* @param {Function} process The function to run on each node.
* @return {void}
* @method traverse
*/
traverse: function(process){
//helper function
function inOrder(node){
if (node){
//traverse the left subtree
if (node.left !== null){
inOrder(node.left);
}
//call the process method on this node
process.call(this, node);
//traverse the right subtree
if (node.right !== null){
inOrder(node.right);
}
}
}
//start with the root
inOrder(this._root);
},
prettyPrint: function() {
var _printNodes = function(levels) {
for(var i=0; i < levels.length; i++) {
var spacerSize = Math.ceil(40 / ((i+2)*2));
var spacer = (new Array(spacerSize + 1).join(' '));
var lines = levels[i].map(function(val, index) {
return (index % 2 === 0) ? ' /' : '\\ ';
});
levels[i].unshift('');
lines.unshift('');
if (i > 0) {
console.log(lines.join(spacer));
}
console.log(levels[i].join(spacer));
}
};
var _extractNodes = function(node, depth, levels) {
//traverse left branch
if(!!node.left) {
levels = _extractNodes(node.left, depth + 1, levels);
}
levels[depth] = levels[depth] || [];
levels[depth].push(node.value);
//traverse right branch
if(!!node.right) {
levels = _extractNodes(node.right, depth + 1, levels);
}
return levels;
};
// var _prettyPrintNode = function(node, depth) {
// //traverse left branch
// if(!!node.left) {
// _prettyPrintNode(node.left, depth + 1);
// }
// //print this node
// var indent = '';
// // if (depth > 0) indent = '|';
// indent += (new Array(depth+1).join(' ')); //hack to get [depth] number of spaces
// // if (depth > 0) {
// // indent += '|-';
// // }
// console.log(indent + node.value );
// //traverse right branch
// if(!!node.right) {
// _prettyPrintNode(node.right, depth + 1);
// }
// };
// _prettyPrintNode(this._root, 0);
var levels = _extractNodes(this._root, 0, []);
_printNodes(levels);
}
};
////////////////////////////////////
//subeesh:
var bt = new BinarySearchTree();
// //add 10 random numbers to tree
// for(var i = 0; i < 10; i++) {
// var value = Math.ceil(Math.random()*50);
// bt.add(value);
// }
var items = [50,32,26,42,12,7,65,79,59,84,77];
items.forEach(function(item) {
bt.add(item);
});
console.log('Tree contents: ' + bt.toString());
console.log('::prettyprint');
bt.prettyPrint();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment