Home Reference Source

viewer/collections/avltree.js

import {BinarySearchTree} from "./binarysearchtree.js";

/*
 * Copied from https://github.com/monmohan/dsjslib
 * 
 * Converted to ES module, removed debugging stuff/logging statements and got rid of very slow exception throwing bits
 * 
 */

export class AvlTree extends BinarySearchTree {

    /**
     * @class AVLTree
     * @classdesc
     * Extends BinarySearchTree (see src/BinarySearchTree.js) to provide a Map like functionality
     * backed by a balanced Tree. All functionality of BinarySearchTree is available.
     * In addition Tree is height balanced by rotation whenever an insert is done
     * See rotate(), reBalance() and checkAVLProperty() functions for explanation.
     * Caller doesn't need to invoke these functions,
     * they are internally used when an insert or delete violates the AVL property of the tree.
     * The keys are ordered based on the natural ordering or an optional compare function.
     * @augments BinarySearchTree
     * @param compFn {userCompareFn=} external compare function for ordering keys
     * @desc
     * #### Example -
     * ```js
     * var AVLTree = require("dsjslib").AVLTree
     * var avl=new AVLTree(function(k1,k2){...})
     * ```
     */
	constructor(compFn) {
        super(compFn);
    }

    /**
     * Rotate a node
     * @access private
     * @param node
     * @param rL
     * @return {String}
     */
    rotate(node, rL) {
        if (!rL || !node) {
            return "Insufficient parameters";
        }
        var tree = this,
            mvChild;
        switch (rL) {
            case 'r':
                if (node.leftChild) {
                    mvChild = node.leftChild.rightChild;
                    parentChild(node.parent, node.leftChild, node.isLeftChild() ? 'l' : 'r');
                    parentChild(node.leftChild, node, 'r');
                    parentChild(node, mvChild, 'l');
                    this.reCalcHeight(node);
                }
                break;
            case 'l':
                if (node.rightChild) {
                    mvChild = node.rightChild.leftChild;
                    parentChild(node.parent, node.rightChild, node.isRightChild() ? 'r' : 'l');
                    parentChild(node.rightChild, node, 'l');
                    parentChild(node, mvChild, 'r');
                    this.reCalcHeight(node);
                }
        }

        function parentChild(par, ch, rL) {
            if (par) {
                par[rL === 'r' ? "rightChild" : "leftChild"] = ch;
            } else { //we are rotating at the root
                tree.root = ch;
            }

            if (ch) {
                ch.parent = par;
            }
        }
    }
    
    /**
     * @access private
     * @param vError
     */
    rebalance(vError) {
        var balance = vError.hdiff, vNode = vError.node;
        var child = balance > 1/*right heavy*/ ? vNode.rightChild : vNode.leftChild;
        //+ve, right heavy, -ve left heavy
        var childBalance = this._nodeHeight(child);
        /**
         * node is right heavy but child is left heavy and vice-versa
         * @type {Boolean}
         */
        var zigzag = balance > 1 ? childBalance < 0 : childBalance > 0;
        if (zigzag/*Requires double rotation*/) {
            //rotate on child first
            this.rotate(child, childBalance > 0 ? 'l' : 'r');
        }
        //rotation on node where violation occurs
        this.rotate(vNode, balance > 1 ? 'l' : 'r');
    }
    
    /**
     * Insert a key value. It also re-balances the tree
     * @memberof AVLTree.prototype
     * @instance
     * @param key {*}
     * @param value {*}
     * @returns {Object} A closure on the the tree. Calling put() again on this object will
     * insert key value pair in the tree. This is to support easy chaining of put() method.
     * tree.put(k1,v1).put(k2,v2) ...
     */
    put(key, value) {
        var ins = super.put.call(this, key, value);
        var vErr = this.checkAVLProperty(ins.node);
        if (vErr) {
        	this.rebalance(vErr);
        }
        return ins;
    }

    /**
     * Delete a key value pair from the Map. Also re-balances the tree
     * @memberof AVLTree.prototype
     * @instance
     * @function delete
     * @param key {*}
     */
    delete(key) {
        var node = this.getKeyValue(key, this.root), p, cNode/*node where violation check should start*/;
        if (node) {
            var num = node.leftChild ? (node.rightChild ? 2 : 1) : (node.rightChild ? 1 : 0);
            switch (num) {
                case 0:
                    p = node.parent;
                    if (p) {
                        var lc = p.leftChild === node;
                        p[lc ? "leftChild" : "rightChild"] = null;
                        node = null;
                        cNode = p;
                    } else {
                        //root
                        this.root = null;
                    }

                    break;
                case 1:
                    //single subtree, the sibling can't have a subtree because
                    // it would have violated the AVL height diff invariant
                    var child = node.leftChild || node.rightChild;
                    node.key = child.key;
                    node.value = child.value;
                    node.leftChild = node.rightChild = null;
                    cNode = node;
                    break;
                case 2:
                    var nextL = this.successor(node.key);
                    var temp = nextL;
                    this['delete'](nextL.key);
                    node.key = temp.key;
                    node.value = temp.value;
            }

            this.reCalcHeight(cNode);
            var vErr = this.checkAVLProperty(cNode);
            if (vErr) {
            	this.rebalance(vErr);
            }
        }
    }

    /**
     * Validates the tree starting at given node (root otherwise)
     * Validates BST as well as AVL properties.
     * @memberof AVLTree.prototype
     * @instance
     * @param node {Object=} Starting node, if not provided start at root
     */
    checkInvariants(node) {
        if (typeof node === "undefined") {
            node = this.root;
        }
        if (!node) return;
        var lc = node.leftChild, rc = node.rightChild;
        var hdiff = Math.abs((lc ? lc.height : -1) - (rc ? rc.height : -1));
        if (hdiff > 1) {
            throw new Error("Invariant check failed at node " + node + " key=" + node.key);
        }
        this.checkInvariants(lc);
        this.checkInvariants(rc);
    }

    _nodeHeight(node) {
        var lc = node.leftChild, rc = node.rightChild;
        return (rc ? rc.height : -1) - (lc ? lc.height : -1);

    }

    checkAVLProperty(node) {
        if (!node) return;
        var hdiff = this._nodeHeight(node);
        if (Math.abs(hdiff) > 1) {
            return {'node' : node, 'hdiff' : hdiff};
        }
        return this.checkAVLProperty(node.parent);
    }
}