      1 var define = require("../define").define,
      2         Tree = require("./Tree"),
      3         base = require("../base"),
      4         multiply = base.string.multiply;
      5
      6 var abs = Math.abs;
      7
      8
      9
     10 var makeNode = function(data) {
     11     return {
     12         data : data,
     13         balance : 0,
     14         left : null,
     15         right : null
     16     }
     17 };
     18
     19 var rotateSingle = function(root, dir, otherDir) {
     20     var save = root[otherDir];
     21     root[otherDir] = save[dir];
     22     save[dir] = root;
     23     return save;
     24 };
     25
     26
     27 var rotateDouble = function(root, dir, otherDir) {
     28     root[otherDir] = rotateSingle(root[otherDir], otherDir, dir);
     29     return rotateSingle(root, dir, otherDir);
     30 };
     31
     32 var adjustBalance = function(root, dir, bal) {
     33     var otherDir = dir == "left" ? "right" : "left";
     34     var n = root[dir], nn = n[otherDir];
     35     if (nn.balance == 0)
     36         root.balance = n.balance = 0;
     37     else if (nn.balance == bal) {
     38         root.balance = -bal;
     39         n.balance = 0;
     40     }
     41     else { /* nn.balance == -bal */
     42         root.balance = 0;
     43         n.balance = bal;
     44     }
     45     nn.balance = 0;
     46 };
     47
     48 var insertAdjustBalance = function(root, dir) {
     49     var otherDir = dir == "left" ? "right" : "left";
     50
     51     var n = root[dir];
     52     var bal = dir == "left" ? -1 : +1;
     53
     54     if (n.balance == bal) {
     55         root.balance = n.balance = 0;
     56         root = rotateSingle(root, otherDir, dir);
     57     }
     58     else {
     59         adjustBalance(root, dir, bal);
     60         root = rotateDouble(root, otherDir, dir);
     61     }
     62
     63     return root;
     64
     65 };
     66
     67 var removeAdjustBalance = function(root, dir, done) {
     68     var otherDir = dir == "left" ? "right" : "left";
     69     var n = root[otherDir];
     70     var bal = dir == "left" ? -1 : 1;
     71     if (n.balance == -bal) {
     72         root.balance = n.balance = 0;
     73         root = rotateSingle(root, dir, otherDir);
     74     }
     75     else if (n.balance == bal) {
     76         adjustBalance(root, otherDir, -bal);
     77         root = rotateDouble(root, dir, otherDir);
     78     }
     79     else { /* n.balance == 0 */
     80         root.balance = -bal;
     81         n.balance = bal;
     82         root = rotateSingle(root, dir, otherDir);
     83         done.done = true;
     84     }
     85     return root;
     86 };
     87
     88 var insert = function(root, data, done, compare) {
     89     if (root == null || root == undefined)
     90         root = makeNode(data);
     91     else {
     92         var dir = compare(data, root.data) == -1 ? "left" : "right";
     93         root[dir] = insert(root[dir], data, done, compare);
     94
     95         if (!done.done) {
     96             /* Update balance factors */
     97             root.balance += dir == "left" ? -1 : 1;
     98             /* Rebalance as necessary and terminate */
     99             if (root.balance == 0)
    100                 done.done = true;
    101             else if (abs(root.balance) > 1) {
    102                 root = insertAdjustBalance(root, dir);
    103                 done.done = true;
    104             }
    105         }
    106     }
    107
    108     return root;
    109 };
    110
    111 var remove = function(root, data, done, compare) {
    112     var dir, cmp, save, b;
    113     if (root) {
    114         //Remove node
    115         cmp = compare(data, root.data);
    116         if (cmp === 0) {
    117             // Unlink and fix parent
    118             var l = root.left, r = root.right;
    119             if (!l || !r) {
    120                 dir = !l ? "right" : "left";
    121                 save = root[dir];
    122                 return save;
    123             }
    124             else {
    125                 var heir = l, r;
    126                 while ((r = heir.right) != null) {
    127                     heir = r;
    128                 }
    129                 root.data = heir.data;
    130                 //reset and start searching
    131                 data = heir.data;
    132             }
    133         }
    134         dir = compare(root.data, data) == -1 ? "right" : "left";
    135         root[dir] = remove(root[dir], data, done, compare);
    136         if (!done.done) {
    137             /* Update balance factors */
    138             b = (root.balance += (dir == "left" ? 1 : -1));
    139             /* Terminate or rebalance as necessary */
    140             var a = abs(b);
    141             if (a === 1)
    142                 done.done = true;
    143             else if (a > 1)
    144                 root = removeAdjustBalance(root, dir, done);
    145         }
    146     }
    147     return root;
    148 };
    149
    150
    151 /**
    152  * @class <p>An AVL tree is a self-balancing binary search tree.
    153  *    In an AVL tree, the heights of the two child subtrees of any node differ by at most one.
    154  *    Lookup, insertion, and deletion all take O(log n) time in both the average and worst cases,
    155  *    where n is the number of nodes in the tree prior to the operation.
    156  *    Insertions and deletions may require the tree to be rebalanced by one or more tree rotations.</p>
    157  * <p>AVL trees are more rigidly balanced than red-black trees, leading to slower insertion and removal but faster retrieval</p>
    158  *
    159  * <b>Performance</b>
    160  * <table>
    161  *     <tr><td></td><td>Best</td><td>Worst</td></tr>
    162  *     <tr><td>Space</td><td>O(n)</td><td>O(n)</td></tr>
    163  *     <tr><td>Search</td><td>O(log n)</td><td>O(log n)</td></tr>
    164  *     <tr><td>Insert</td><td>O(log n)</td><td>O(log n)</td></tr>
    165  *     <tr><td>Delete</td><td>O(log n)</td><td>O(log n)</td></tr>
    166  * <table>
    167  * @name AVLTree
    168  * @augments comb.collections.Tree
    169  * @memberOf comb.collections
    170  */
    171 module.exports = exports  = define(Tree, {
    172     instance : {
    173         /**@lends comb.collections.AVLTree.prototype*/
    174
    175         insert : function(data) {
    176             var done = {done : false};
    177             this.__root = insert(this.__root, data, done, this.compare);
    178         },
    179
    180
    181         remove : function(data) {
    182             this.__root = remove(this.__root, data, {done : false}, this.compare);
    183         },
    184
    185         __printNode : function(node, level) {
    186             var str = [];
    187             if (node == null) {
    188                 str.push(multiply('\t', level));
    189                 str.push("~");
    190                 console.log(str.join(""));
    191             } else {
    192                 this.__printNode(node.right, level + 1);
    193                 str.push(multiply('\t', level));
    194                 str.push(node.data + ":" + node.balance + "\n");
    195                 console.log(str.join(""));
    196                 this.__printNode(node.left, level + 1);
    197             }
    198         }
    199
    200     }
    201 });
    202
    203