Tyriar/js-data-structures

View on GitHub
lib/binary-search-tree.js

Summary

Maintainability
C
1 day
Test Coverage
/**
 * @module lib/binary-search-tree
 * @license MIT Copyright 2014 Daniel Imms (http://www.growingwiththeweb.com)
 */
'use strict';

if (typeof exports === 'object' && typeof define !== 'function') {
  var define = function (factory) {
    factory(require, exports, module);
  };
}

define(function (require, exports, module) {
  var BaseBinaryTree = require('./base-binary-tree');
  var BinaryTreeNode = require('./binary-tree-node');

  /**
   * Creates a binary search tree.
   *
   * @constructor
   * @param {function} customCompare An optional custom node comparison
   * function.
   */
  var BinarySearchTree = function (customCompare) {
    BaseBinaryTree.call(this);

    /**
     * The number of nodes in the tree.
     * @private
     */
    this.nodeCount = 0;

    if (customCompare) {
      this.compare = customCompare;
    }
  };

  BinarySearchTree.prototype = Object.create(BaseBinaryTree.prototype);

  BinarySearchTree.prototype.constructor = BinarySearchTree;

  /**
   * Adds a key to the {@link BinarySearchTree}.
   *
   * @param {Object} key The key to add.
   * @return {boolean} Whether the node was added.
   */
  BinarySearchTree.prototype.add = function (key) {
    var newNode = new BinaryTreeNode(key);

    if (!this.root) {
      this.nodeCount++;
      this.root = newNode;
      return true;
    }

    var current = this.root;
    while (true) {
      if (this.compare(key, current.key) < 0) {
        if (!current.left) {
          current.left = newNode;
          this.nodeCount++;
          return true;
        }
        current = current.left;
      } else if (this.compare(key, current.key) > 0) {
        if (!current.right) {
          current.right = newNode;
          this.nodeCount++;
          return true;
        }
        current = current.right;
      } else {
        return false;
      }
    }
  };

  /**
   * Removes a key from the tree.
   *
   * @param {Object} key The key to remove.
   * @return {boolean} Whether the key was removed.
   */
  BinarySearchTree.prototype.remove = function (key) {
    if (!this.root) {
      return false;
    }

    var parent;
    var current = this.root;
    while (true) {
      if (this.compare(key, current.key) < 0) {
        if (!current.left) {
          return false;
        }
        parent = current;
        current = current.left;
      } else if (this.compare(key, current.key) > 0) {
        if (!current.right) {
          return false;
        }
        parent = current;
        current = current.right;
      } else {
        this.nodeCount--;
        deleteNode(current, parent, this);
        return true;
      }
    }
  };

  /**
   * Determines whether the {@link BinarySearchTree} contains a key.
   *
   * @param {Object} key The key to check.
   * @return {boolean} Whether the node contains the key.
   */
  BinarySearchTree.prototype.contains = function (key) {
    if (!this.root) {
      return false;
    }

    var current = this.root;
    while (true) {
      if (this.compare(key, current.key) < 0) {
        if (!current.left) {
          return false;
        }
        current = current.left;
      } else if (this.compare(key, current.key) > 0) {
        if (!current.right) {
          return false;
        }
        current = current.right;
      } else {
        return true;
      }
    }
  };

  /**
   * @return {Object} The maximum key of the tree.
   */
  BinarySearchTree.prototype.findMaximum = function () {
    if (!this.root) {
      return undefined;
    }

    var current = this.root;
    while (true) {
      if (current.right) {
        current = current.right;
      } else {
        return current.key;
      }
    }
  };

  /**
   * @return {Object} The minimum key of the tree.
   */
  BinarySearchTree.prototype.findMinimum = function () {
    if (!this.root) {
      return undefined;
    }

    var current = this.root;
    while (true) {
      if (current.left) {
        current = current.left;
      } else {
        return current.key;
      }
    }
  };

  /**
   * @return {boolean} Whether the tree is empty.
   */
  BinarySearchTree.prototype.isEmpty = function () {
    return !this.root;
  };

  /**
   * @return The size of the tree.
   */
  BinarySearchTree.prototype.size = function () {
    return this.nodeCount;
  };

  /**
   * Compares two nodes with each other.
   *
   * @param {Object} a The first key to compare.
   * @param {Object} b The second key to compare.
   * @return -1, 0 or 1 if a < b, a == b or a > b respectively.
   */
  BinarySearchTree.prototype.compare = function (a, b) {
    if (a > b) {
      return 1;
    }
    if (a < b) {
      return -1;
    }
    return 0;
  };

  /**
   * Deletes a node from the tree.
   *
   * @private
   * @param {BinaryTreeNode} node The node being deleted.
   * @param {BinaryTreeNode} parent The parent of the node being deleted.
   * @param {BinaryTreeNode} tree The root of the tree.
   */
  function deleteNode(node, parent, tree) {
    // No children exist, mark this node as deleted
    if (!node.left && !node.right) {
      if (parent) {
        parent.removeChild(node);
      } else {
        tree.root = undefined;
      }
      return;
    }

    // Only the left child exists, move the left node to this position
    if (node.left && !node.right) {
      node.key = node.left.key;
      node.right = node.left.right;
      node.left = node.left.left;
      return;
    }

    // Only the right child exists, move the right node to this position
    if (node.right && !node.left) {
      node.key = node.right.key;
      node.left = node.right.left;
      node.right = node.right.right;
      return;
    }

    // Both children exist, replace this node with with minimum node from the
    // right sub-tree
    var minParent = findParentOfMinimum(node.right, node);
    var minNode = minParent.left ? minParent.left : minParent.right;
    var newKey = minNode.key;
    deleteNode(minNode, minParent, tree);
    node.key = newKey;
  }

  /**
   * Finds the parent of the minimum node.
   *
   * @private
   * @param {BinaryTreeNode} node The node being searched.
   * @param {BinaryTreeNode} parent The parent of the node being searched.
   * @return The parent of the minimum node.
   */
  function findParentOfMinimum(node, parent) {
    if (!node.left) {
      return parent;
    }

    return findParentOfMinimum(node.left, node);
  }

  module.exports = BinarySearchTree;
});