【从蛋壳到满天飞】JS 数据结构解析和算法实现-AVL树(二)

思惟导图

前言

【从蛋壳到满天飞】JS 数据结构解析和算法实现,所有文章大概的内容以下: Arrays(数组)、Stacks(栈)、Queues(队列)、LinkedList(链表)、Recursion(递归思想)、BinarySearchTree(二分搜索树)、Set(集合)、Map(映射)、Heap(堆)、PriorityQueue(优先队列)、SegmentTree(线段树)、Trie(字典树)、UnionFind(并查集)、AVLTree(AVL 平衡树)、RedBlackTree(红黑平衡树)、HashTable(哈希表)html

源代码有三个:ES6(单个单个的 class 类型的 js 文件) | JS + HTML(一个 js 配合一个 html)| JAVA (一个一个的工程)node

所有源代码已上传 github,点击我吧,光看文章可以掌握两成,动手敲代码、动脑思考、画图才能够掌握八成。git

本文章适合 对数据结构想了解而且感兴趣的人群,文章风格一如既往如此,就以为手机上看起来比较方便,这样显得比较有条理,整理这些笔记加源码,时间跨度也算将近半年时间了,但愿对想学习数据结构的人或者正在学习数据结构的人群有帮助。github

AVL 树 平衡机制的四种处理

  1. 左旋转和右旋转的状况算法

    1. 当插入的元素在不平衡的节点的左侧的左侧的时候,
    2. 就须要向右旋转。
    3. 当插入的元素在不平衡的节点的右侧的右侧的时候,
    4. 就须要向左旋转。
    5. 其实对于这两种状况来讲它们是对称的,
    6. 因此处理的思路彻底是一致的。
  2. 其它两种状况api

    1. 插入的元素在不平衡的节点的左侧的右侧,
    2. 也仍是会让祖先节点不知足平衡二叉树的性质,
    3. 由于这个不平衡节点的高度仍是+1 了,因此平衡因子仍是大于 1 了,
    4. 因此不能单纯的简单的进行右旋转或者左旋转了。
  3. 不一样状况的区分数组

    1. 一共分为 4 种状况,LL、RR、LR、RL,数据结构

    2. LL 表示的是插入的元素在不平衡节点的左侧的左侧的时候,dom

    3. RR 表示的是插入的元素在不平衡节点的右侧的右侧的时候,ide

    4. LR 表示的是插入的元素在不平衡节点的左侧的右侧的时候,

    5. RL 表示的是插入的元素在不平衡节点的右侧的左侧的时候。

      // RR的状况 新插入的节点在Y的右侧的右侧
      // (Y)
      // / \
      // (T4) (X)
      // / \
      // (T3) (Z)
      // / \
      // (T1) (T2)
      
      // LL的状况 新插入的节点在Y的左侧的左侧
      // (Y)
      // / \
      // (X) (T4)
      // / \
      // (Z) (T3)
      // / \
      // (T1) (T2)
      
      // LR的状况 新插入的节点在Y的左侧的右侧
      // (Y)
      // / \
      // (X) (T4)
      // / \
      // (T1) (Z)
      // / \
      // (T2)(T3)
      
      // RL的状况 新插入的节点在Y的右侧的左侧
      // (Y)
      // / \
      // (T1) (X)
      // / \
      // (Z) (T4)
      // / \
      // (T2)(T3)
      复制代码
  4. LR 的处理方式

    1. 首先须要对节点 X 进行左旋转,
    2. 以前的 LL 和 RR 都是对 X 和 Y 这两个节点作改变,并无改变节点 Z,
    3. 如今对节点 X 进行左旋转就会相迎的改变 X 和 Z 这两个节点,
    4. 旋转以后,就将 LR 转换为了 LL 的状况,
    5. 此时只须要继续使用 LL 的方式进行处理便可。
    6. LR 出现的状况是,当前节点的平衡因子大于 1 而且
    7. 当前节点的左子树的平衡因子小于 0,也就是当前节点的左子树的右孩子要比左孩子要高,
    8. 因此相应的就是小于 0 的。
    9. 先对当前节点的左孩子进行一个左旋转,而后从新赋值给当前节点的左孩子,
    10. 而后对当前节点进行一个右旋转,直接返回当前节点便可。
    // LR的状况 新插入的节点在Y的左侧的右侧
    // (Y)
    // / \
    // (X) (T4)
    // / \
    // (T1) (Z)
    // / \
    // (T2)(T3)
    
    // 对X节点进行左旋转,就将LR转换为了LL的状况
    // (Y)
    // / \
    // (Z) (T4)
    // / \
    // (X) (T3)
    // / \
    // (T1) (T2)
    复制代码
  5. RL 的处理方式

    1. RL 就是新插入的节点在 Y 这个不平衡的点的右子树的左侧,
    2. 先右后左,先 R 后 L,这种状况就叫作 RL,
    3. 对于 RL 这种状况的处理方式和刚才对 LR 这种的处理方式是彻底对称的,
    4. 首先须要对节点 X 进行右旋转,旋转以后,就将 LR 转换为了 RR 的状况,
    5. 此时只须要继续使用 RR 的方式进行处理便可。
    6. RL 出现的状况是,当前节点的平衡因子小于负一而且
    7. 当前节点的右子树的平衡因子大于 0,也就是当前节点的右子树的右孩子要比左孩子要高,
    8. 因此相应的就是大于 0 的。
    9. 先对当前节点的右孩子进行一个右旋转,而后从新赋值给当前节点的右孩子,
    10. 而后对当前节点进行一个左旋转,直接返回当前节点便可。
    // RL的状况 新插入的节点在Y的右侧的左侧
    // (Y)
    // / \
    // (T1) (X)
    // / \
    // (Z) (T4)
    // / \
    // (T2)(T3)
    
    // 对X节点进行右旋转,就将LR转换为了RR的状况
    // (Y)
    // / \
    // (T1) (Z)
    // / \
    // (T2) (X)
    // / \
    // (T3)(T4)
    复制代码
  6. 已经对 LL 和 RR 这两种状况进行了处理

    1. 在这个处理过程当中,涉猎了左旋转和右旋转这样两个子过程,
    2. 在吃力 LR 和 RL 这两种状况的时候,直接复用那两个子过程,
    3. 就能够很是容易的处理 LR 和 RL 这两种状况,
    4. 这样就对一个节点全部的不平衡的可能性进行了处理,
    5. 处理完了以后继续向上回溯,寻找上面的节点,
    6. 看看是否还有不平衡的状况,整个处理过程依次类推,
    7. 直至根节点就行了。
  7. 对 LL、RR、LR、RL 这四种状况进行了处理

    1. 若是这四种状况都不知足的话,
    2. 那么说明这个节点符合平衡二叉树的性质,
    3. 那么直接返回这个节点,而后递归回溯的返回给了上一层节点,
    4. 那么在上一层继续来处理更新完这个节点相应的高度以后,
    5. 继续判断它的平衡因子从而看是否进行平衡的维护,
    6. 整个过程依次类推。
    7. 通过这样的处理以后这棵 AVL 树就基本实现的差很少了,
    8. 这棵 AVL 树的添加操做目前是知足了平衡二叉树的性质了,
    9. 同时它也知足了二分搜索树的性质。
  8. 将二分搜树改形成了 AVL 树

    1. 这么作是为了让整个树可以保持平衡,
    2. 保持平衡的目的就是由于原先二分搜索树可能退化成链表,
    3. 如今 AVL 树确定不会退化成链表,
    4. 相应的全部的操做的时间复杂度都是O(logn)这个级别的,
    5. 如今这个 AVL 树比以前的二分搜索树确定是更加平衡的,
    6. 因此总体在性能上也应该更加有优点。
  9. 性能测试

    1. 在随机数据的测试中,AVLTree 比 BSTTree 要快一点,
    2. 若是是最坏的数据的测试的话,AVLTree 比 BSTTree 要快很是多,
    3. 由于 BSTTree 退化成了一个链表,时间复杂度是O(n)级别的了,
    4. 而 AVLTree 是O(logn)这个级别的,
    5. 这就是平衡二叉树的威力,因为有了自平衡的这种机制,
    6. 因此整棵树不会退化成链表,在最差的状况下这个 BST 将会表现的很是慢,
    7. 可是平衡二叉树 AVL 却很是的快。

代码示例

  1. (class: MyBSTMap, class: AVLTree, class: PerformanceTest, class: Main)

  2. MyBSTMap

    // 自定义二分搜索树树映射节点 TreeMapNode
    class MyBinarySearchTreeMapNode {
       constructor(key = null, value = null, left = null, right = null) {
          this.key = key;
          this.value = value;
          this.left = left;
          this.right = right;
       }
    
       // @Override toString 2018-11-5-jwl
       toString() {
          return this.key.toString() + '---------->' + this.value.toString();
       }
    }
    
    // 自定义二分搜索树映射 Map
    class MyBinarySearchTreeMap {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 比较的功能
       compare(keyA, keyB) {
          if (keyA === null || keyB === null)
             throw new Error("key is error. key can't compare.");
          if (keyA > keyB) return 1;
          else if (keyA < keyB) return -1;
          else return 0;
       }
    
       // 根据key获取节点 -
       getNode(node, key) {
          // 先解决最基本的问题
          if (node === null) return null;
    
          // 开始将复杂的问题 逐渐缩小规模
          // 从而求出小问题的解,最后构建出原问题的解
          switch (this.compare(node.key, key)) {
             case 1: // 向左找
                return this.getNode(node.left, key);
                break;
             case -1: // 向右找
                return this.getNode(node.right, key);
                break;
             case 0: // 找到了
                return node;
                break;
             default:
                throw new Error(
                   'compare result is error. compare result : 0、 一、 -1 .'
                );
                break;
          }
       }
    
       // 添加操做 +
       add(key, value) {
          this.root = this.recursiveAdd(this.root, key, value);
       }
    
       // 添加操做 递归算法 -
       recursiveAdd(node, key, value) {
          // 解决最简单的问题
          if (node === null) {
             this.size++;
             return new MyBinarySearchTreeMapNode(key, value);
          }
    
          // 将复杂的问题规模逐渐变小,
          // 从而求出小问题的解,从而构建出原问题的答案
          if (this.compare(node.key, key) > 0)
             node.left = this.recursiveAdd(node.left, key, value);
          else if (this.compare(node.key, key) < 0)
             node.right = this.recursiveAdd(node.right, key, value);
          else node.value = value;
    
          return node;
       }
    
       // 删除操做 返回被删除的元素 +
       remove(key) {
          let node = this.getNode(this.root, key);
          if (node === null) return null;
    
          this.root = this.recursiveRemove(this.root, key);
          return node.value;
       }
    
       // 删除操做 递归算法 +
       recursiveRemove(node, key) {
          // 解决最基本的问题
          if (node === null) return null;
    
          if (this.compare(node.key, key) > 0) {
             node.left = this.recursiveRemove(node.left, key);
             return node;
          } else if (this.compare(node.key, key) < 0) {
             node.right = this.recursiveRemove(node.right, key);
             return node;
          } else {
             // 当前节点的key 与 待删除的key的那个节点相同
             // 有三种状况
             // 1. 当前节点没有左子树,那么只有让当前节点的右子树直接覆盖当前节点,就表示当前节点被删除了
             // 2. 当前节点没有右子树,那么只有让当前节点的左子树直接覆盖当前节点,就表示当前节点被删除了
             // 3. 当前节点左右子树都有, 那么又分两种状况,使用前驱删除法或者后继删除法
             // 1. 前驱删除法:使用当前节点的左子树上最大的那个节点覆盖当前节点
             // 2. 后继删除法:使用当前节点的右子树上最小的那个节点覆盖当前节点
    
             if (node.left === null) {
                let rightNode = node.right;
                node.right = null;
                this.size--;
                return rightNode;
             } else if (node.right === null) {
                let leftNode = node.left;
                node.left = null;
                this.size--;
                return leftNode;
             } else {
                let predecessor = this.maximum(node.left);
                node.left = this.removeMax(node.left);
                this.size++;
    
                // 开始嫁接 当前节点的左右子树
                predecessor.left = node.left;
                predecessor.right = node.right;
    
                // 将当前节点从根节点剔除
                node = node.left = node.right = null;
                this.size--;
    
                // 返回嫁接后的新节点
                return predecessor;
             }
          }
       }
    
       // 删除操做的两个辅助函数
       // 获取最大值、删除最大值
       // 之前驱的方式 来辅助删除操做的函数
    
       // 获取最大值
       maximum(node) {
          // 不再能往右了,说明当前节点已是最大的了
          if (node.right === null) return node;
    
          // 将复杂的问题渐渐减少规模,从而求出小问题的解,最后用小问题的解构建出原问题的答案
          return this.maximum(node.right);
       }
    
       // 删除最大值
       removeMax(node) {
          // 解决最基本的问题
          if (node.right === null) {
             let leftNode = node.left;
             node.left = null;
             this.size--;
             return leftNode;
          }
    
          // 开始化归
          node.right = this.removeMax(node.right);
          return node;
       }
    
       // 查询操做 返回查询到的元素 +
       get(key) {
          let node = this.getNode(this.root, key);
          if (node === null) return null;
          return node.value;
       }
    
       // 修改操做 +
       set(key, value) {
          let node = this.getNode(this.root, key);
          if (node === null) throw new Error(key + " doesn't exist.");
    
          node.value = value;
       }
    
       // 返回是否包含该key的元素的判断值 +
       contains(key) {
          return this.getNode(this.root, key) !== null;
       }
    
       // 返回映射中实际的元素个数 +
       getSize() {
          return this.size;
       }
    
       // 返回映射中是否为空的判断值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // @Override toString() 2018-11-05-jwl
       toString() {
          let mapInfo = `MyBinarySearchTreeMap: size = ${this.size}, data = [ `;
          document.body.innerHTML += `MyBinarySearchTreeMap: size = ${ this.size }, data = [ <br/><br/>`;
    
          // 以非递归的前序遍历 输出字符串
          let stack = new MyLinkedListStack();
    
          stack.push(this.root);
    
          if (this.root === null) stack.pop();
    
          while (!stack.isEmpty()) {
             let node = stack.pop();
    
             if (node.left !== null) stack.push(node.left);
             if (node.right !== null) stack.push(node.right);
    
             if (node.left === null && node.right === null) {
                mapInfo += ` ${node.toString()} \r\n`;
                document.body.innerHTML += ` ${node.toString()} <br/><br/>`;
             } else {
                mapInfo += ` ${node.toString()}, \r\n`;
                document.body.innerHTML += ` ${node.toString()}, <br/><br/>`;
             }
          }
    
          mapInfo += ` ] \r\n`;
          document.body.innerHTML += ` ] <br/><br/>`;
    
          return mapInfo;
       }
    }
    复制代码
  3. AVLTree

    // 自定义AVL树节点 AVLTreeNode
    class MyAVLTreeNode {
       constructor(key = null, value = null, left = null, right = null) {
          this.key = key;
          this.value = value;
          this.left = left;
          this.right = right;
          this.height = 1;
       }
    
       // @Override toString 2018-11-24-jwl
       toString() {
          return this.key + '--->' + this.value + '--->' + this.height;
       }
    }
    
    // 自定义AVL树 AVLTree
    class MyAVLTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 比较的功能
       compare(keyA, keyB) {
          if (keyA === null || keyB === null)
             throw new Error("key is error. key can't compare.");
          if (keyA > keyB) return 1;
          else if (keyA < keyB) return -1;
          else return 0;
       }
    
       // 获取某个节点的高度 -
       getHeight(node) {
          // 节点为空 返回0
          if (!node) return 0;
    
          // 直接返回这个节点的高度
          return node.height;
       }
    
       // 获取一个节点的平衡因子 -
       getBalanceFactor(node) {
          // 节点为空 返回0
          if (!node) return 0;
    
          // 左右子树的高度值
          const leftHeight = this.getHeight(node.left);
          const rightHeight = this.getHeight(node.right);
    
          // 左子树的高度 - 右子树高度的值 = 平衡因子
          return leftHeight - rightHeight;
       }
    
       // 根据key获取节点 -
       getNode(node, key) {
          // 先解决最基本的问题
          if (node === null) return null;
    
          // 开始将复杂的问题 逐渐缩小规模
          // 从而求出小问题的解,最后构建出原问题的解
          switch (this.compare(node.key, key)) {
             case 1: // 向左找
                return this.getNode(node.left, key);
                break;
             case -1: // 向右找
                return this.getNode(node.right, key);
                break;
             case 0: // 找到了
                return node;
                break;
             default:
                throw new Error(
                   'compare result is error. compare result : 0、 一、 -1 .'
                );
                break;
          }
       }
    
       // 对节点y进行向右旋转操做,返回旋转后新的根节点x
       // y x
       // / \ / \
       // x T4 向右旋转 (y) z y
       // / \ - - - - - - - -> / \ / \
       // z T3 T1 T2 T3 T4
       // / \
       // T1 T2
       rightRotate(y) {
          const x = y.left;
          const T3 = x.right;
    
          // 向右旋转的过程
          y.left = T3;
          x.right = y;
    
          // 更新节点的height值 只须要更新x和y的便可
          y.height =
             1 + Math.max(this.getHeight(y.left), this.getHeight(y.right));
          x.height =
             1 + Math.max(this.getHeight(x.left), this.getHeight(x.right));
    
          // 返回 新节点 x
          return x;
       }
    
       // 对节点y进行向左旋转操做,返回旋转后新的根节点x
       // y x
       // / \ / \
       // T1 x 向左旋转 (y) y z
       // / \ - - - - - - - -> / \ / \
       // T2 z T1 T2 T3 T4
       // / \
       // T3 T4
       leftRotate(y) {
          const x = y.right;
          const T2 = x.left;
    
          // 向左旋转的过程
          y.right = T2;
          x.left = y;
    
          // 更新节点的height值 只须要更新x和y的便可
          y.height =
             1 + Math.max(this.getHeight(y.left), this.getHeight(y.right));
          x.height =
             1 + Math.max(this.getHeight(x.left), this.getHeight(x.right));
    
          // 返回 新节点 x
          return x;
       }
    
       // 添加操做 +
       add(key, value) {
          this.root = this.recursiveAdd(this.root, key, value);
       }
    
       // 添加操做 递归算法 -
       recursiveAdd(node, key, value) {
          // 解决最简单的问题
          if (node === null) {
             this.size++;
             return new MyAVLTreeNode(key, value);
          }
    
          // 将复杂的问题规模逐渐变小,
          // 从而求出小问题的解,从而构建出原问题的答案
          if (this.compare(node.key, key) > 0)
             node.left = this.recursiveAdd(node.left, key, value);
          else if (this.compare(node.key, key) < 0)
             node.right = this.recursiveAdd(node.right, key, value);
          else node.value = value;
    
          // 在这里对节点的高度进行从新计算 节点自己高度为1
          // 计算方式: 1 + 左右子树的height值最大的那个height值
          node.height =
             1 + Math.max(this.getHeight(node.left), this.getHeight(node.right));
    
          // 计算一个节点的平衡因子
          const balanceFactor = this.getBalanceFactor(node);
    
          // 若是平衡因子的绝对值大于1 说明不知足AVL平衡二叉树的性质了
          if (Math.abs(balanceFactor) > 1) {
             console.log(
                node.toString() + ' unbalanced : ' + balanceFactor + '\r\n'
             );
             document.body.innerHTML +=
                node.toString() + ' unbalanced : ' + balanceFactor + '<br/>';
          }
    
          // LL状况 平衡维护 右旋转操做 平衡因子为正数则表示左倾 反之为右倾
          if (balanceFactor > 1 && this.getBalanceFactor(node.left) >= 0)
             return this.rightRotate(node);
    
          // RR状况 平衡维护 左旋转操做 平衡因子为负数则表示右倾 反之为左倾
          if (balanceFactor < -1 && this.getBalanceFactor(node.right) <= 0)
             return this.leftRotate(node);
    
          // LR状况 平衡维护 先转换为LL状况 再处理LL状况
          if (balanceFactor > 1 && this.getBalanceFactor(node.left) < 0) {
             node.left = this.leftRotate(node.left);
             return this.rightRotate(node);
          }
    
          // RL状况 平衡维护 先转换为RR状况 再处理RR状况
          if (balanceFactor < -1 && this.getBalanceFactor(node.right) > 0) {
             node.right = this.rightRotate(node.right);
             return this.leftRotate(node);
          }
    
          return node;
       }
    
       // 删除操做 返回被删除的元素 +
       remove(key) {
          let node = this.getNode(this.root, key);
          if (node === null) return null;
    
          this.root = this.recursiveRemove(this.root, key);
          return node.value;
       }
    
       // 删除操做 递归算法 +
       recursiveRemove(node, key) {
          // 解决最基本的问题
          if (node === null) return null;
    
          if (this.compare(node.key, key) > 0) {
             node.left = this.recursiveRemove(node.left, key);
             return node;
          } else if (this.compare(node.key, key) < 0) {
             node.right = this.recursiveRemove(node.right, key);
             return node;
          } else {
             // 当前节点的key 与 待删除的key的那个节点相同
             // 有三种状况
             // 1. 当前节点没有左子树,那么只有让当前节点的右子树直接覆盖当前节点,就表示当前节点被删除了
             // 2. 当前节点没有右子树,那么只有让当前节点的左子树直接覆盖当前节点,就表示当前节点被删除了
             // 3. 当前节点左右子树都有, 那么又分两种状况,使用前驱删除法或者后继删除法
             // 1. 前驱删除法:使用当前节点的左子树上最大的那个节点覆盖当前节点
             // 2. 后继删除法:使用当前节点的右子树上最小的那个节点覆盖当前节点
    
             if (node.left === null) {
                let rightNode = node.right;
                node.right = null;
                this.size--;
                return rightNode;
             } else if (node.right === null) {
                let leftNode = node.left;
                node.left = null;
                this.size--;
                return leftNode;
             } else {
                let predecessor = this.maximum(node.left);
                node.left = this.removeMax(node.left);
                this.size++;
    
                // 开始嫁接 当前节点的左右子树
                predecessor.left = node.left;
                predecessor.right = node.right;
    
                // 将当前节点从根节点剔除
                node = node.left = node.right = null;
                this.size--;
    
                // 返回嫁接后的新节点
                return predecessor;
             }
          }
       }
    
       // 删除操做的两个辅助函数
       // 获取最大值、删除最大值
       // 之前驱的方式 来辅助删除操做的函数
    
       // 获取最大值
       maximum(node) {
          // 不再能往右了,说明当前节点已是最大的了
          if (node.right === null) return node;
    
          // 将复杂的问题渐渐减少规模,从而求出小问题的解,最后用小问题的解构建出原问题的答案
          return this.maximum(node.right);
       }
    
       // 删除最大值
       removeMax(node) {
          // 解决最基本的问题
          if (node.right === null) {
             let leftNode = node.left;
             node.left = null;
             this.size--;
             return leftNode;
          }
    
          // 开始化归
          node.right = this.removeMax(node.right);
          return node;
       }
    
       // 查询操做 返回查询到的元素 +
       get(key) {
          let node = this.getNode(this.root, key);
          if (node === null) return null;
          return node.value;
       }
    
       // 修改操做 +
       set(key, value) {
          let node = this.getNode(this.root, key);
          if (node === null) throw new Error(key + " doesn't exist.");
    
          node.value = value;
       }
    
       // 返回是否包含该key的元素的判断值 +
       contains(key) {
          return this.getNode(this.root, key) !== null;
       }
    
       // 返回映射中实际的元素个数 +
       getSize() {
          return this.size;
       }
    
       // 返回映射中是否为空的判断值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 判断当前这棵树是不是一棵二分搜索树,有二分搜索树顺序性
       isBanarySearchTree() {
          // 若是节点为空 那么这就是一棵空的二分搜索树
          if (!this.root) return true;
    
          // 存储二分搜索树中的key
          const list = new Array();
    
          // 中序遍历后,添加到list中的值会是以从小到大升序的样子排列
          this.inOrder(this.root, list);
    
          // 从前日后判断 list中的值是不是从小到大升序的排列
          // 验证 当前树是否符合二分搜索树的性质
          for (var i = 1; i < list.length; i++)
             if (list[i - 1] > list[i]) return false;
          return true;
       }
    
       // 中序遍历 辅助函数 -
       inOrder(node, list) {
          // 递归到底的状况
          if (!node) return;
    
          // 中序遍历时,添加到数组中的值会是以从小到大升序的样子排列
          this.inOrder(node.left, list);
          list.push(node.key);
          this.inOrder(node.right, list);
       }
    
       // 判断该二叉树是否一棵平衡二叉树
       isBalanced() {
          return this.recursiveIsBalanced(this.root);
       }
    
       // 递归判断某一个节点是否符合平衡二叉树的定义 辅助函数 -
       recursiveIsBalanced(node) {
          // 可以递归到底,说明符合要求
          // 空的节点左右孩子高度差确定为0,
          // 由于空树没有左右子树,更加谈不上下面去判断它的左右子树高度差是否会超过一。
          if (!node) return true;
    
          // 若是当前节点的高度差大于1 说明不符合要求
          if (Math.abs(this.getBalanceFactor(node)) > 1) return false;
    
          // 递归的去判断当前节点的 左右子树是否符合要求
          return (
             this.recursiveIsBalanced(node.left) &&
             this.recursiveIsBalanced(node.right)
          );
       }
    
       // @Override toString() 2018-11-05-jwl
       toString() {
          let mapInfo = `MyBinarySearchTreeMap: size = ${this.size}, data = [ `;
          document.body.innerHTML += `MyBinarySearchTreeMap: size = ${ this.size }, data = [ <br/><br/>`;
    
          // 以非递归的前序遍历 输出字符串
          let stack = new MyLinkedListStack();
    
          stack.push(this.root);
    
          if (this.root === null) stack.pop();
    
          while (!stack.isEmpty()) {
             let node = stack.pop();
    
             if (node.left !== null) stack.push(node.left);
             if (node.right !== null) stack.push(node.right);
    
             if (node.left === null && node.right === null) {
                mapInfo += ` ${node.toString()} \r\n`;
                document.body.innerHTML += ` ${node.toString()} <br/><br/>`;
             } else {
                mapInfo += ` ${node.toString()}, \r\n`;
                document.body.innerHTML += ` ${node.toString()}, <br/><br/>`;
             }
          }
    
          mapInfo += ` ] \r\n`;
          document.body.innerHTML += ` ] <br/><br/>`;
    
          return mapInfo;
       }
    }
    复制代码
  4. PerformanceTest

    // 性能测试
    class PerformanceTest {
       constructor() {}
    
       // 对比队列
       testQueue(queue, openCount) {
          let startTime = Date.now();
    
          let random = Math.random;
          for (var i = 0; i < openCount; i++) {
             queue.enqueue(random() * openCount);
          }
    
          while (!queue.isEmpty()) {
             queue.dequeue();
          }
    
          let endTime = Date.now();
    
          return this.calcTime(endTime - startTime);
       }
    
       // 对比栈
       testStack(stack, openCount) {
          let startTime = Date.now();
    
          let random = Math.random;
          for (var i = 0; i < openCount; i++) {
             stack.push(random() * openCount);
          }
    
          while (!stack.isEmpty()) {
             stack.pop();
          }
    
          let endTime = Date.now();
    
          return this.calcTime(endTime - startTime);
       }
    
       // 对比集合
       testSet(set, openCount) {
          let startTime = Date.now();
    
          let random = Math.random;
          let arr = [];
          let temp = null;
    
          // 第一遍测试
          for (var i = 0; i < openCount; i++) {
             temp = random();
             // 添加剧复元素,从而测试集合去重的能力
             set.add(temp * openCount);
             set.add(temp * openCount);
    
             arr.push(temp * openCount);
          }
    
          for (var i = 0; i < openCount; i++) {
             set.remove(arr[i]);
          }
    
          // 第二遍测试
          for (var i = 0; i < openCount; i++) {
             set.add(arr[i]);
             set.add(arr[i]);
          }
    
          while (!set.isEmpty()) {
             set.remove(arr[set.getSize() - 1]);
          }
    
          let endTime = Date.now();
    
          // 求出两次测试的平均时间
          let avgTime = Math.ceil((endTime - startTime) / 2);
    
          return this.calcTime(avgTime);
       }
    
       // 对比映射
       testMap(map, openCount) {
          let startTime = Date.now();
    
          let array = new MyArray();
          let random = Math.random;
          let temp = null;
          let result = null;
          for (var i = 0; i < openCount; i++) {
             temp = random();
             result = openCount * temp;
             array.add(result);
             array.add(result);
             array.add(result);
             array.add(result);
          }
    
          for (var i = 0; i < array.getSize(); i++) {
             result = array.get(i);
             if (map.contains(result)) map.add(result, map.get(result) + 1);
             else map.add(result, 1);
          }
    
          for (var i = 0; i < array.getSize(); i++) {
             result = array.get(i);
             map.remove(result);
          }
    
          let endTime = Date.now();
    
          return this.calcTime(endTime - startTime);
       }
    
       // 对比堆 主要对比 使用heapify 与 不使用heapify时的性能
       testHeap(heap, array, isHeapify) {
          const startTime = Date.now();
    
          // 是否支持 heapify
          if (isHeapify) heap.heapify(array);
          else {
             for (const element of array) heap.add(element);
          }
    
          console.log('heap size:' + heap.size() + '\r\n');
          document.body.innerHTML += 'heap size:' + heap.size() + '<br /><br />';
    
          // 使用数组取值
          let arr = new Array(heap.size());
          for (let i = 0; i < arr.length; i++) arr[i] = heap.extractMax();
    
          console.log(
             'Array size:' + arr.length + ',heap size:' + heap.size() + '\r\n'
          );
          document.body.innerHTML +=
             'Array size:' +
             arr.length +
             ',heap size:' +
             heap.size() +
             '<br /><br />';
    
          // 检验一下是否符合要求
          for (let i = 1; i < arr.length; i++)
             if (arr[i - 1] < arr[i]) throw new Error('error.');
    
          console.log('test heap completed.' + '\r\n');
          document.body.innerHTML += 'test heap completed.' + '<br /><br />';
    
          const endTime = Date.now();
          return this.calcTime(endTime - startTime);
       }
    
       // 对比并查集
       testUnionFind(unionFind, openCount, primaryArray, secondaryArray) {
          const size = unionFind.getSize();
          const random = Math.random;
    
          return this.testCustomFn(function() {
             // 合并操做
             for (var i = 0; i < openCount; i++) {
                let primaryId = primaryArray[i];
                let secondaryId = secondaryArray[i];
    
                unionFind.unionElements(primaryId, secondaryId);
             }
    
             // 查询链接操做
             for (var i = 0; i < openCount; i++) {
                let primaryRandomId = Math.floor(random() * size);
                let secondaryRandomId = Math.floor(random() * size);
    
                unionFind.unionElements(primaryRandomId, secondaryRandomId);
             }
          });
       }
    
       // 计算运行的时间,转换为 天-小时-分钟-秒-毫秒
       calcTime(result) {
          //获取距离的天数
          var day = Math.floor(result / (24 * 60 * 60 * 1000));
    
          //获取距离的小时数
          var hours = Math.floor((result / (60 * 60 * 1000)) % 24);
    
          //获取距离的分钟数
          var minutes = Math.floor((result / (60 * 1000)) % 60);
    
          //获取距离的秒数
          var seconds = Math.floor((result / 1000) % 60);
    
          //获取距离的毫秒数
          var milliSeconds = Math.floor(result % 1000);
    
          // 计算时间
          day = day < 10 ? '0' + day : day;
          hours = hours < 10 ? '0' + hours : hours;
          minutes = minutes < 10 ? '0' + minutes : minutes;
          seconds = seconds < 10 ? '0' + seconds : seconds;
          milliSeconds =
             milliSeconds < 100
                ? milliSeconds < 10
                   ? '00' + milliSeconds
                   : '0' + milliSeconds
                : milliSeconds;
    
          // 输出耗时字符串
          result =
             day +
             '天' +
             hours +
             '小时' +
             minutes +
             '分' +
             seconds +
             '秒' +
             milliSeconds +
             '毫秒' +
             ' <<<<============>>>> 总毫秒数:' +
             result;
    
          return result;
       }
    
       // 自定义对比
       testCustomFn(fn) {
          let startTime = Date.now();
    
          fn();
    
          let endTime = Date.now();
    
          return this.calcTime(endTime - startTime);
       }
    }
    复制代码
  5. Main

    // main 函数
    class Main {
       constructor() {
          this.alterLine('Map Comparison Area');
          const n = 2000000;
          // const n = 200;
    
          const myBSTMap = new MyBinarySearchTreeMap();
          const myAVLTree = new MyAVLTree();
          let performanceTest1 = new PerformanceTest();
    
          const random = Math.random;
          let arrNumber = new Array(n);
    
          // 循环添加随机数的值
          for (let i = 0; i < n; i++) arrNumber[i] = Math.floor(n * random());
    
          this.alterLine('MyBSTMap Comparison Area');
          const myBSTMapInfo = performanceTest1.testCustomFn(function() {
             // 添加
             for (const word of arrNumber)
                myBSTMap.add(word, String.fromCharCode(word));
    
             // 删除
             for (const word of arrNumber) myBSTMap.remove(word);
    
             // 查找
             for (const word of arrNumber)
                if (myBSTMap.contains(word))
                   throw new Error("doesn't remove ok.");
          });
    
          // 总毫秒数:
          console.log(myBSTMapInfo);
          console.log(myBSTMap);
          this.show(myBSTMapInfo);
    
          this.alterLine('MyAVLTree Comparison Area');
          const that = this;
          const myAVLTreeInfo = performanceTest1.testCustomFn(function() {
             for (const word of arrNumber)
                myAVLTree.add(word, String.fromCharCode(word));
    
             // 输出当前这棵myAVLTree树是不是一个二分搜索树
             that.show(
                'Is Binary Search Tree : ' + myAVLTree.isBanarySearchTree()
             );
             console.log(
                'Is Binary Search Tree : ' + myAVLTree.isBanarySearchTree()
             );
    
             // 输出当前这棵myAVLTree树是不是一个平衡二叉树
             that.show('Is Balanced : ' + myAVLTree.isBalanced());
             console.log('Is Balanced : ' + myAVLTree.isBalanced());
    
             // 删除
             for (const word of arrNumber) {
                myAVLTree.remove(word);
             }
    
             // // 查找
             for (const word of arrNumber)
                if (myAVLTree.contains(word))
                   throw new Error("doesn't remove ok.");
          });
    
          console.log(myAVLTree);
          // 总毫秒数:
          console.log(myAVLTreeInfo);
          this.show(myAVLTreeInfo);
       }
    
       // 将内容显示在页面上
       show(content) {
          document.body.innerHTML += `${content}<br /><br />`;
       }
    
       // 展现分割线
       alterLine(title) {
          let line = `--------------------${title}----------------------`;
          console.log(line);
          document.body.innerHTML += `${line}<br /><br />`;
       }
    }
    
    // 页面加载完毕
    window.onload = function() {
       // 执行主函数
       new Main();
    };
    复制代码

AVL 树的删除操做 自平衡

  1. 在添加节点的操做中经过左旋转和右旋转的操做维持的 AVL 树的平衡
    1. AVL 树添加一个节点,
    2. 就是相应的按照二分搜索树的思路添加到适当的位置以后,
    3. 往上回溯,在这个回溯的过程当中,因为添加了一个新的节点,
    4. 因此它的父辈节点相应的就有可能再也不知足平衡二叉树的性质,
    5. 也就是说父辈节点的平衡因子的绝对值是大于一的,
    6. 那么对于这样的节点就须要相应的维护一下平衡,
    7. 这个具体的维护方法分别就是处理 LL、RR、LR、RL 这四种状况,
    8. 其实 AVL 的删除操做的平衡维护也是这四种状况。
  2. AVL 树的删除节点的操做和 AVL 树的添加操做是很是相似的
    1. 在删除的过程当中依然使用二分搜索树的思路把某一个节点删除掉,
    2. 删除掉以后相应的从删除这个节点的子树的根节点出发,向上回溯搜索,
    3. 对于它的父辈节点,因为整棵二分搜索树删除掉了一个元素,
    4. 因此就有可能破坏了平衡性,
    5. 那么对于这些节点怎么维护平衡和具体维护平衡的方式
    6. 和添加操做维护平衡的方式是如出一辙的。
  3. 删除操做逻辑分析
    1. 若是当前这个 node 节点已经为空了,直接返回空,
    2. 由于这就意味着没有找到待删除的节点,也就是说当前二分搜索树中根本就没有。
    3. 若是待删除的这个节点比当前节点的值要小,
    4. 那么就去当前这个节点的左子树继续去删除待删除的节点,
    5. 若是待删除的这个节点比当前节点的值要大,
    6. 那么就去当前这个节点的右子树继续去删除待删除的节点,
    7. 在这个删除的过程当中直接将待删除的节点删除以后,
    8. 仍是将当前这个节点给返回回去了,
    9. 在 AVL 树中有可能当前这个节点的左子树或者右子树
    10. 接到了来自递归的这个 remove 新的根节点以后,
    11. 当前的这个 node 节点的平衡性已经被破坏了,
    12. 因此在后续必须找到一个机会来维护 node 的平衡,
    13. 那么就不能那么早的将 node 给返回回去,
    14. 须要将删除待删除节点后的当前节点 node 保存一下,
    15. 这样作完以后在后续就有机会对这个保存的节点进行一下平衡的维护。
    16. 若是待删除的这个节点与当前这个节点相等的话,
    17. 就须要执行这样的逻辑,
    18. 若是待删除节点的左子树为空的话,那么就保存一下待删除节点的右子树,
    19. 由于当前节点即将被删除掉,左子树为空,那么当前节点就要被右子树覆盖,
    20. 之因此保存当前节点的右子树,
    21. 那么是由于要对这个保存的节点进行一下平衡的维护;
    22. 若是待删除节点的右子树为空的话,也是要保存一下待删除节点的左子树,
    23. 也是要对这个保存的节点进行一下平衡的维护;
    24. 若是待删除的这个节点左右子树均不为空的话,
    25. 那么就须要将待删除节点的右子树中最小的那个节点或者左子树中最大的那个节点
    26. 进行一次删除操做,删除后返回的节点做为新节点来进行保存,
    27. 由于这个节点将会取代当前的这个旧节点,
    28. 取代以前须要将当前这个节点的左右子树拷贝一份给这个新节点,
    29. 这个新的节点也是要进行平衡的维护。
    30. 通过上述的一系列操做以后,获得了保存的那个节点以后,
    31. 在最后对这个节点进行一下判断,看看是否须要根据这个节点来维护一下平衡,
    32. 这个维护的过程其实和添加操做中维护平衡的方式是如出一辙的,
    33. 更新这个节点的高度,计算这个节点的平衡因子,
    34. 处理这个节点不平衡时可能发生的四种状况,
    35. 维护好这个节点以后,最后返回这个节点给上一层的递归调用,
    36. 在上一层的递归调用中依然是这样的一个过程来检查相应的对于这个 node 节点
    37. 是否须要这个后续的这些更新节点的操做,
    38. 那么总体的逻辑其实和以前添加节点是同样的。
  4. 删除操做逻辑的小 bug
    1. 在将待删除节点的右子树中最小的那个节点或者左子树中最大的那个节点
    2. 进行一次删除操做的时候,这一次操做并无维持节点的平衡,
    3. 因此在这一步是有可能要打破 AVL 树的平衡条件的,
    4. 在这里有两个解决方案,
    5. 解决方案一是为这一次操做也添加上平衡维护这样的一个过程;
    6. 解决方案二是 直接将这一次删除操做变动一下,
    7. 也就是直接将 removeMin 改变为 remove,
    8. 在当前节点的右子树中删除最小的那个节点,
    9. 也就是复用 remove 方法中维护平衡的这个过程,
    10. 那样你就不须要在 removeMin 中再添加这样一个过程了;
    11. 解决方案二至关于又递归的调用了一下 remove 这个函数,
    12. 而整个 remove 函数已经添加了对这个节点平衡性的处理,
    13. 因此在整个逻辑中全部的删除操做都对平衡性进行了维护,
    14. 那么此时这个删除代码就彻底正确了,
    15. 那么就能够将 removeMin 从 AVLTree 中删除掉。
  5. 删除操做逻辑整理
    1. 最终删除节点的操做那三个判断是互斥的,
    2. 因此须要经过 if-elseif-else 来分隔开来,
    3. 否则是会出现问题的,
    4. 而后删除节点操做是在以 node 为根的进行节点的删除,
    5. 颇有可能删除掉这个节点以后得到的是空,
    6. 例如删除的这个节点是叶子节点以后就会发生这种状况,
    7. 那么此时在后面更新 height 的时候就会产生空指针的异常,由于 null 没有属性,
    8. 因此就还有一个边界须要处理,
    9. 也就是这个节点为空的时候不须要再维护这个空节点的平衡了,
    10. 只有在这个节点不为空时才去维护这个空姐点的平衡。
  6. 删除操做的测试
    1. 每删除一个节点就判断当前 AVL 树是否符合二分搜索树及 AVL 树的性质,
    2. 同时删除在全部节点以后打印一下 AVL 树中的节点个数。

代码示例

  1. AVLTree

    // 自定义AVL树节点 AVLTreeNode
    class MyAVLTreeNode {
       constructor(key = null, value = null, left = null, right = null) {
          this.key = key;
          this.value = value;
          this.left = left;
          this.right = right;
          this.height = 1;
       }
    
       // @Override toString 2018-11-24-jwl
       toString() {
          return this.key + '--->' + this.value + '--->' + this.height;
       }
    }
    
    // 自定义AVL树 AVLTree
    class MyAVLTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 比较的功能
       compare(keyA, keyB) {
          if (keyA === null || keyB === null)
             throw new Error("key is error. key can't compare.");
          if (keyA > keyB) return 1;
          else if (keyA < keyB) return -1;
          else return 0;
       }
    
       // 获取某个节点的高度 -
       getHeight(node) {
          // 节点为空 返回0
          if (!node) return 0;
    
          // 直接返回这个节点的高度
          return node.height;
       }
    
       // 获取一个节点的平衡因子 -
       getBalanceFactor(node) {
          // 节点为空 返回0
          if (!node) return 0;
    
          // 左右子树的高度值
          const leftHeight = this.getHeight(node.left);
          const rightHeight = this.getHeight(node.right);
    
          // 左子树的高度 - 右子树高度的值 = 平衡因子
          return leftHeight - rightHeight;
       }
    
       // 根据key获取节点 -
       getNode(node, key) {
          // 先解决最基本的问题
          if (!node) return null;
    
          // 开始将复杂的问题 逐渐缩小规模
          // 从而求出小问题的解,最后构建出原问题的解
          switch (this.compare(node.key, key)) {
             case 1: // 向左找
                return this.getNode(node.left, key);
                break;
             case -1: // 向右找
                return this.getNode(node.right, key);
                break;
             case 0: // 找到了
                return node;
                break;
             default:
                throw new Error(
                   'compare result is error. compare result : 0、 一、 -1 .'
                );
                break;
          }
       }
    
       // 对节点y进行向右旋转操做,返回旋转后新的根节点x
       // y x
       // / \ / \
       // x T4 向右旋转 (y) z y
       // / \ - - - - - - - -> / \ / \
       // z T3 T1 T2 T3 T4
       // / \
       // T1 T2
       rightRotate(y) {
          const x = y.left;
          const T3 = x.right;
    
          // 向右旋转的过程
          y.left = T3;
          x.right = y;
    
          // 更新节点的height值 只须要更新x和y的便可
          y.height =
             1 + Math.max(this.getHeight(y.left), this.getHeight(y.right));
          x.height =
             1 + Math.max(this.getHeight(x.left), this.getHeight(x.right));
    
          // 返回 新节点 x
          return x;
       }
    
       // 对节点y进行向左旋转操做,返回旋转后新的根节点x
       // y x
       // / \ / \
       // T1 x 向左旋转 (y) y z
       // / \ - - - - - - - -> / \ / \
       // T2 z T1 T2 T3 T4
       // / \
       // T3 T4
       leftRotate(y) {
          const x = y.right;
          const T2 = x.left;
    
          // 向左旋转的过程
          y.right = T2;
          x.left = y;
    
          // 更新节点的height值 只须要更新x和y的便可
          y.height =
             1 + Math.max(this.getHeight(y.left), this.getHeight(y.right));
          x.height =
             1 + Math.max(this.getHeight(x.left), this.getHeight(x.right));
    
          // 返回 新节点 x
          return x;
       }
    
       // 添加操做 +
       add(key, value) {
          this.root = this.recursiveAdd(this.root, key, value);
       }
    
       // 添加操做 递归算法 -
       recursiveAdd(node, key, value) {
          // 解决最简单的问题
          if (node === null) {
             this.size++;
             return new MyAVLTreeNode(key, value);
          }
    
          // 将复杂的问题规模逐渐变小,
          // 从而求出小问题的解,从而构建出原问题的答案
          if (this.compare(node.key, key) > 0)
             node.left = this.recursiveAdd(node.left, key, value);
          else if (this.compare(node.key, key) < 0)
             node.right = this.recursiveAdd(node.right, key, value);
          else node.value = value;
    
          // 在这里对节点的高度进行从新计算 节点自己高度为1
          // 计算方式: 1 + 左右子树的height值最大的那个height值
          node.height =
             1 + Math.max(this.getHeight(node.left), this.getHeight(node.right));
    
          // 计算一个节点的平衡因子
          const balanceFactor = this.getBalanceFactor(node);
    
          // // 若是平衡因子的绝对值大于1 说明不知足AVL平衡二叉树的性质了
          // if (Math.abs(balanceFactor) > 1) {
          // console.log(node.toString() + " unbalanced : " + balanceFactor + "\r\n");
          // document.body.innerHTML += node.toString() + " unbalanced : " + balanceFactor + "<br/>";
          // }
    
          // LL状况 平衡维护 右旋转操做 平衡因子为正数则表示左倾 反之为右倾
          if (balanceFactor > 1 && this.getBalanceFactor(node.left) >= 0)
             return this.rightRotate(node);
    
          // RR状况 平衡维护 左旋转操做 平衡因子为负数则表示右倾 反之为左倾
          if (balanceFactor < -1 && this.getBalanceFactor(node.right) <= 0)
             return this.leftRotate(node);
    
          // LR状况 平衡维护 先转换为LL状况 再处理LL状况
          if (balanceFactor > 1 && this.getBalanceFactor(node.left) < 0) {
             node.left = this.leftRotate(node.left);
             return this.rightRotate(node);
          }
    
          // RL状况 平衡维护 先转换为RR状况 再处理RR状况
          if (balanceFactor < -1 && this.getBalanceFactor(node.right) > 0) {
             node.right = this.rightRotate(node.right);
             return this.leftRotate(node);
          }
    
          return node;
       }
    
       // 删除操做 返回被删除的元素 +
       remove(key) {
          let node = this.getNode(this.root, key);
          if (!node) return null;
    
          this.root = this.recursiveRemove(this.root, key);
          return node.value;
       }
    
       // 删除操做 递归算法 +
       recursiveRemove(node, key) {
          // 解决最基本的问题
          if (!node) return null;
    
          // 临时存储待返回的节点,可是返回以前先对它的平衡进行一下维护。
          let returnNode;
    
          if (this.compare(node.key, key) > 0) {
             node.left = this.recursiveRemove(node.left, key);
             returnNode = node;
          } else if (this.compare(node.key, key) < 0) {
             node.right = this.recursiveRemove(node.right, key);
             returnNode = node;
          } else {
             // 当前节点的key 与 待删除的key的那个节点相同
             // 有三种状况
             // 1. 当前节点没有左子树,那么只有让当前节点的右子树直接覆盖当前节点,就表示当前节点被删除了
             // 2. 当前节点没有右子树,那么只有让当前节点的左子树直接覆盖当前节点,就表示当前节点被删除了
             // 3. 当前节点左右子树都有, 那么又分两种状况,使用前驱删除法或者后继删除法
             // 1. 前驱删除法:使用当前节点的左子树上最大的那个节点覆盖当前节点
             // 2. 后继删除法:使用当前节点的右子树上最小的那个节点覆盖当前节点
    
             if (!node.left) {
                let rightNode = node.right;
                node.right = null;
                this.size--;
                returnNode = rightNode;
             } else if (!node.right) {
                let leftNode = node.left;
                node.left = null;
                this.size--;
                returnNode = leftNode;
             } else {
                let predecessor = this.maximum(node.left);
                node.left = this.removeMax(node.left); // this.recursiveRemove(node.left, predecessor.key)
                this.size++;
    
                // 开始嫁接 当前节点的左右子树
                predecessor.left = node.left;
                predecessor.right = node.right;
    
                // 将当前节点从根节点剔除
                node = node.left = node.right = null;
                this.size--;
    
                // 返回嫁接后的新节点
                returnNode = predecessor;
             }
          }
    
          // 若是本来的节点或者新的节点是空 直接返回空便可 不须要下面的平衡维护
          if (!returnNode) return null;
    
          // 在这里对节点的高度进行从新计算 节点自己高度为1
          // 计算方式: 1 + 左右子树的height值最大的那个height值
          returnNode.height =
             1 +
             Math.max(
                this.getHeight(returnNode.left),
                this.getHeight(returnNode.right)
             );
    
          // 删除节点后进行节点的平衡维护
          // 计算一个节点的平衡因子
          const balanceFactor = this.getBalanceFactor(returnNode);
    
          // LL状况 平衡维护 右旋转操做 平衡因子为正数则表示左倾 反之为右倾
          if (balanceFactor > 1 && this.getBalanceFactor(returnNode.left) >= 0)
             return this.rightRotate(returnNode);
    
          // RR状况 平衡维护 左旋转操做 平衡因子为负数则表示右倾 反之为左倾
          if (balanceFactor < -1 && this.getBalanceFactor(returnNode.right) <= 0)
             return this.leftRotate(returnNode);
    
          // LR状况 平衡维护 先转换为LL状况 再处理LL状况
          if (balanceFactor > 1 && this.getBalanceFactor(returnNode.left) < 0) {
             returnNode.left = this.leftRotate(returnNode.left);
             return this.rightRotate(returnNode);
          }
    
          // RL状况 平衡维护 先转换为RR状况 再处理RR状况
          if (
             balanceFactor < -1 &&
             this.getBalanceFactor(returnNode.right) > 0
          ) {
             returnNode.right = this.rightRotate(returnNode.right);
             return this.leftRotate(returnNode);
          }
    
          return returnNode;
       }
    
       // 删除操做的两个辅助函数
       // 获取最大值、删除最大值
       // 之前驱的方式 来辅助删除操做的函数
    
       // 获取最大值
       maximum(node) {
          // 不再能往右了,说明当前节点已是最大的了
          if (!node.right) return node;
    
          // 将复杂的问题渐渐减少规模,从而求出小问题的解,最后用小问题的解构建出原问题的答案
          return this.maximum(node.right);
       }
    
       // 删除最大值
       removeMax(node) {
          // 临时存储待返回的节点,可是返回以前先对它的平衡进行一下维护。
          let returnNode;
    
          // 解决最基本的问题
          if (!node.right) {
             let leftNode = node.left;
             node.left = null;
             this.size--;
             returnNode = leftNode;
          } else {
             // 开始化归
             node.right = this.removeMax(node.right);
             returnNode = node;
          }
    
          // 若是本来的节点或者新的节点是空 直接返回空便可 不须要下面的平衡维护
          if (!returnNode) return null;
    
          // 在这里对节点的高度进行从新计算 节点自己高度为1
          // 计算方式: 1 + 左右子树的height值最大的那个height值
          returnNode.height =
             1 +
             Math.max(
                this.getHeight(returnNode.left),
                this.getHeight(returnNode.right)
             );
    
          // 删除节点后进行节点的平衡维护
          // 计算一个节点的平衡因子
          const balanceFactor = this.getBalanceFactor(returnNode);
    
          // LL状况 平衡维护 右旋转操做 平衡因子为正数则表示左倾 反之为右倾
          if (balanceFactor > 1 && this.getBalanceFactor(returnNode.left) >= 0)
             return this.rightRotate(returnNode);
    
          // RR状况 平衡维护 左旋转操做 平衡因子为负数则表示右倾 反之为左倾
          if (balanceFactor < -1 && this.getBalanceFactor(returnNode.right) <= 0)
             return this.leftRotate(returnNode);
    
          // LR状况 平衡维护 先转换为LL状况 再处理LL状况
          if (balanceFactor > 1 && this.getBalanceFactor(returnNode.left) < 0) {
             returnNode.left = this.leftRotate(returnNode.left);
             return this.rightRotate(returnNode);
          }
    
          // RL状况 平衡维护 先转换为RR状况 再处理RR状况
          if (
             balanceFactor < -1 &&
             this.getBalanceFactor(returnNode.right) > 0
          ) {
             returnNode.right = this.rightRotate(returnNode.right);
             return this.leftRotate(returnNode);
          }
    
          return returnNode;
       }
    
       // 查询操做 返回查询到的元素 +
       get(key) {
          let node = this.getNode(this.root, key);
          if (!node) return null;
          return node.value;
       }
    
       // 修改操做 +
       set(key, value) {
          let node = this.getNode(this.root, key);
          if (!node) throw new Error(key + " doesn't exist.");
    
          node.value = value;
       }
    
       // 返回是否包含该key的元素的判断值 +
       contains(key) {
          return !!this.getNode(this.root, key);
       }
    
       // 返回映射中实际的元素个数 +
       getSize() {
          return this.size;
       }
    
       // 返回映射中是否为空的判断值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 判断当前这棵树是不是一棵二分搜索树,有二分搜索树顺序性
       isBanarySearchTree() {
          // 若是节点为空 那么这就是一棵空的二分搜索树
          if (!this.root) return true;
    
          // 存储二分搜索树中的key
          const list = new Array();
    
          // 中序遍历后,添加到list中的值会是以从小到大升序的样子排列
          this.inOrder(this.root, list);
    
          // 从前日后判断 list中的值是不是从小到大升序的排列
          // 验证 当前树是否符合二分搜索树的性质
          for (var i = 1; i < list.length; i++)
             if (list[i - 1] > list[i]) return false;
          return true;
       }
    
       // 中序遍历 辅助函数 -
       inOrder(node, list) {
          // 递归到底的状况
          if (!node) return;
    
          // 中序遍历时,添加到数组中的值会是以从小到大升序的样子排列
          this.inOrder(node.left, list);
          list.push(node.key);
          this.inOrder(node.right, list);
       }
    
       // 判断该二叉树是否一棵平衡二叉树
       isBalanced() {
          return this.recursiveIsBalanced(this.root);
       }
    
       // 递归判断某一个节点是否符合平衡二叉树的定义 辅助函数 -
       recursiveIsBalanced(node) {
          // 可以递归到底,说明符合要求
          // 空的节点左右孩子高度差确定为0,
          // 由于空树没有左右子树,更加谈不上下面去判断它的左右子树高度差是否会超过一。
          if (!node) return true;
    
          // 若是当前节点的高度差大于1 说明不符合要求
          if (Math.abs(this.getBalanceFactor(node)) > 1) return false;
    
          // 递归的去判断当前节点的 左右子树是否符合要求
          return (
             this.recursiveIsBalanced(node.left) &&
             this.recursiveIsBalanced(node.right)
          );
       }
    
       // @Override toString() 2018-11-05-jwl
       toString() {
          let mapInfo = `MyBinarySearchTreeMap: size = ${this.size}, data = [ `;
          document.body.innerHTML += `MyBinarySearchTreeMap: size = ${ this.size }, data = [ <br/><br/>`;
    
          // 以非递归的前序遍历 输出字符串
          let stack = new MyLinkedListStack();
    
          stack.push(this.root);
    
          if (!this.root === null) stack.pop();
    
          while (!stack.isEmpty()) {
             let node = stack.pop();
    
             if (node.left !== null) stack.push(node.left);
             if (node.right !== null) stack.push(node.right);
    
             if (node.left === null && node.right === null) {
                mapInfo += ` ${node.toString()} \r\n`;
                document.body.innerHTML += ` ${node.toString()} <br/><br/>`;
             } else {
                mapInfo += ` ${node.toString()}, \r\n`;
                document.body.innerHTML += ` ${node.toString()}, <br/><br/>`;
             }
          }
    
          mapInfo += ` ] \r\n`;
          document.body.innerHTML += ` ] <br/><br/>`;
    
          return mapInfo;
       }
    }
    复制代码

基于 AVL 树的集合和映射

  1. 已经基于二分搜索树相应的添加了自平衡的机制
    1. 使其成为了一棵 AVL 树,
    2. 在添加节点和删除节点的时候有了自平衡这样的一个处理机制,
    3. 使得整棵二分搜索树不会退化成为一个链表。

更多 AVL 树的相关问题

  1. 基于 AVL 树的 Set 和 Map
    1. 已经基于链表和二分搜索树这两个数据结构实现了集合和映射,
    2. 在这个封装中,因为以前实现的二分搜索树其中每个节点只承载一个元素,
    3. 因此能够基于这个二分搜索树的代码直接封装出集合这个数据结构,
    4. 对于映射须要从新再写一版二分搜索树,
    5. 这版二分搜索树中承载了 key 和 vlaue 这样的键值数据对,从而实现了相应的映射,
    6. 若是直接有一个二分搜索树的结构,这个二分搜索树的底层就支持键值对的存储,
    7. 那么基于这样的一个二分搜索树就能够直接实现映射,
    8. 与此同时使用这样的一个数据结构只要忽略掉值那一项就能够封装出集合来,
    9. 当前实现的 AVL 树就是从底层这个树结构直接支持了键值这样的数据对,
    10. 那么就能够直接复用数据结构封装出基于 AVL 树的集合和映射这样的两种数据结构。
  2. AVL 树的优化
    1. 因为 AVL 树的实现因为它已经保持了自平衡,
    2. 因此总体它的性能已经很是好了,
    3. 能够作到在最差的状况下在 AVL 树中
    4. 不管是增删改查这些操做全都是Olog(n)这个级别的,
    5. 不过对于 AVL 树还能够进行一些比较细微的优化。
    6. 最典型的就是
    7. 在维护平衡以前都须要对每个节点的高度进行一下从新的计算,
    8. 若是从新计算出的节点的高度和这个节点原先的高度相等的话,
    9. 那么后续对于这个节点的祖先节点就再也不须要维护平衡的操做了,
    10. 这是由于这个节点的高度和原先同样,
    11. 从它的父亲节点或者祖先节点的角度上来看,它的子树的高度并无发生变化,
    12. 也就不须要相应的去维护平衡了,将这个优化添加到 AVL 树中后,
    13. 这个 AVL 树的性能已经很高了。
  3. AVL 树的局限性
    1. 虽然对 AVL 树进行优化后性能已经很高了,可是依然有另一种平衡二叉树,
    2. 它的性能能够和 AVL 树相匹敌,甚至能够说在统计意义上,
    3. 也就是在通常的平均状况下,
    4. 这种平衡二叉树它的总体性能是比 AVL 树更优的一种平衡二叉树,
    5. 就是大名鼎鼎的红黑树,红黑树的平均性能是比 AVL 树更优的,
    6. 这就像平均来说快速排序算法是比归并排序算法更加的快的,
    7. 不过不管是快速排序算法仍是归并排序算法它们两者都是O(nlogn)级别的时间复杂度,
    8. 同理对于红黑树来讲,它的增删改查的操做其实也都是在O(logn)这个级别的,
    9. 也就是在复杂度上和 AVL 树并无大的差别,
    10. 不过在具体的操做中因为红黑树的操做相应的旋转操做会更少一些,
    11. 因此总体的性能比 AVL 树更优一些,
    12. 尽管如此,AVL 树自己因为是第一个自平衡的二分搜索树,
    13. 与此同时这种维护自平衡的方式是基于左旋转和右旋转的方式是一种很是经典的操做,
    14. 因此 AVL 树是很是值得学习的,有了 AVL 树的基础以后再看红黑树,
    15. 在具体的理解上也会容易不少。

代码示例

  1. MyAVLTree

    // 自定义AVL树节点 AVLTreeNode
    class MyAVLTreeNode {
       constructor(key = null, value = null, left = null, right = null) {
          this.key = key;
          this.value = value;
          this.left = left;
          this.right = right;
          this.height = 1;
       }
    
       // @Override toString 2018-11-24-jwl
       toString() {
          return this.key + '--->' + this.value + '--->' + this.height;
       }
    }
    
    // 自定义AVL树 AVLTree
    class MyAVLTree {
       constructor() {
          this.root = null;
          this.size = 0;
       }
    
       // 比较的功能
       compare(keyA, keyB) {
          if (keyA === null || keyB === null)
             throw new Error("key is error. key can't compare.");
          if (keyA > keyB) return 1;
          else if (keyA < keyB) return -1;
          else return 0;
       }
    
       // 获取某个节点的高度 -
       getHeight(node) {
          // 节点为空 返回0
          if (!node) return 0;
    
          // 直接返回这个节点的高度
          return node.height;
       }
    
       // 获取一个节点的平衡因子 -
       getBalanceFactor(node) {
          // 节点为空 返回0
          if (!node) return 0;
    
          // 左右子树的高度值
          const leftHeight = this.getHeight(node.left);
          const rightHeight = this.getHeight(node.right);
    
          // 左子树的高度 - 右子树高度的值 = 平衡因子
          return leftHeight - rightHeight;
       }
    
       // 根据key获取节点 -
       getNode(node, key) {
          // 先解决最基本的问题
          if (!node) return null;
    
          // 开始将复杂的问题 逐渐缩小规模
          // 从而求出小问题的解,最后构建出原问题的解
          switch (this.compare(node.key, key)) {
             case 1: // 向左找
                return this.getNode(node.left, key);
                break;
             case -1: // 向右找
                return this.getNode(node.right, key);
                break;
             case 0: // 找到了
                return node;
                break;
             default:
                throw new Error(
                   'compare result is error. compare result : 0、 一、 -1 .'
                );
                break;
          }
       }
    
       // 对节点y进行向右旋转操做,返回旋转后新的根节点x
       // y x
       // / \ / \
       // x T4 向右旋转 (y) z y
       // / \ - - - - - - - -> / \ / \
       // z T3 T1 T2 T3 T4
       // / \
       // T1 T2
       rightRotate(y) {
          const x = y.left;
          const T3 = x.right;
    
          // 向右旋转的过程
          y.left = T3;
          x.right = y;
    
          // 更新节点的height值 只须要更新x和y的便可
          y.height =
             1 + Math.max(this.getHeight(y.left), this.getHeight(y.right));
          x.height =
             1 + Math.max(this.getHeight(x.left), this.getHeight(x.right));
    
          // 返回 新节点 x
          return x;
       }
    
       // 对节点y进行向左旋转操做,返回旋转后新的根节点x
       // y x
       // / \ / \
       // T1 x 向左旋转 (y) y z
       // / \ - - - - - - - -> / \ / \
       // T2 z T1 T2 T3 T4
       // / \
       // T3 T4
       leftRotate(y) {
          const x = y.right;
          const T2 = x.left;
    
          // 向左旋转的过程
          y.right = T2;
          x.left = y;
    
          // 更新节点的height值 只须要更新x和y的便可
          y.height =
             1 + Math.max(this.getHeight(y.left), this.getHeight(y.right));
          x.height =
             1 + Math.max(this.getHeight(x.left), this.getHeight(x.right));
    
          // 返回 新节点 x
          return x;
       }
    
       // 添加操做 +
       add(key, value) {
          this.root = this.recursiveAdd(this.root, key, value);
       }
    
       // 添加操做 递归算法 -
       recursiveAdd(node, key, value) {
          // 解决最简单的问题
          if (node === null) {
             this.size++;
             return new MyAVLTreeNode(key, value);
          }
    
          // 将复杂的问题规模逐渐变小,
          // 从而求出小问题的解,从而构建出原问题的答案
          if (this.compare(node.key, key) > 0)
             node.left = this.recursiveAdd(node.left, key, value);
          else if (this.compare(node.key, key) < 0)
             node.right = this.recursiveAdd(node.right, key, value);
          else node.value = value;
    
          // 在这里对节点的高度进行从新计算 节点自己高度为1
          // 计算方式: 1 + 左右子树的height值最大的那个height值
          node.height =
             1 + Math.max(this.getHeight(node.left), this.getHeight(node.right));
    
          // 计算一个节点的平衡因子
          const balanceFactor = this.getBalanceFactor(node);
    
          // // 若是平衡因子的绝对值大于1 说明不知足AVL平衡二叉树的性质了
          // if (Math.abs(balanceFactor) > 1) {
          // console.log(node.toString() + " unbalanced : " + balanceFactor + "\r\n");
          // document.body.innerHTML += node.toString() + " unbalanced : " + balanceFactor + "<br/>";
          // }
    
          // LL状况 平衡维护 右旋转操做 平衡因子为正数则表示左倾 反之为右倾
          if (balanceFactor > 1 && this.getBalanceFactor(node.left) >= 0)
             return this.rightRotate(node);
    
          // RR状况 平衡维护 左旋转操做 平衡因子为负数则表示右倾 反之为左倾
          if (balanceFactor < -1 && this.getBalanceFactor(node.right) <= 0)
             return this.leftRotate(node);
    
          // LR状况 平衡维护 先转换为LL状况 再处理LL状况
          if (balanceFactor > 1 && this.getBalanceFactor(node.left) < 0) {
             node.left = this.leftRotate(node.left);
             return this.rightRotate(node);
          }
    
          // RL状况 平衡维护 先转换为RR状况 再处理RR状况
          if (balanceFactor < -1 && this.getBalanceFactor(node.right) > 0) {
             node.right = this.rightRotate(node.right);
             return this.leftRotate(node);
          }
    
          return node;
       }
    
       // 删除操做 返回被删除的元素 +
       remove(key) {
          let node = this.getNode(this.root, key);
          if (!node) return null;
    
          this.root = this.recursiveRemove(this.root, key);
          return node.value;
       }
    
       // 删除操做 递归算法 +
       recursiveRemove(node, key) {
          // 解决最基本的问题
          if (!node) return null;
    
          // 临时存储待返回的节点,可是返回以前先对它的平衡进行一下维护。
          let returnNode;
          const originHeight = node.height; // 记录原节点的高度
    
          if (this.compare(node.key, key) > 0) {
             node.left = this.recursiveRemove(node.left, key);
             returnNode = node;
          } else if (this.compare(node.key, key) < 0) {
             node.right = this.recursiveRemove(node.right, key);
             returnNode = node;
          } else {
             // 当前节点的key 与 待删除的key的那个节点相同
             // 有三种状况
             // 1. 当前节点没有左子树,那么只有让当前节点的右子树直接覆盖当前节点,就表示当前节点被删除了
             // 2. 当前节点没有右子树,那么只有让当前节点的左子树直接覆盖当前节点,就表示当前节点被删除了
             // 3. 当前节点左右子树都有, 那么又分两种状况,使用前驱删除法或者后继删除法
             // 1. 前驱删除法:使用当前节点的左子树上最大的那个节点覆盖当前节点
             // 2. 后继删除法:使用当前节点的右子树上最小的那个节点覆盖当前节点
    
             if (!node.left) {
                let rightNode = node.right;
                node.right = null;
                this.size--;
                returnNode = rightNode;
             } else if (!node.right) {
                let leftNode = node.left;
                node.left = null;
                this.size--;
                returnNode = leftNode;
             } else {
                let predecessor = this.maximum(node.left);
                node.left = this.removeMax(node.left); // this.recursiveRemove(node.left, predecessor.key)
                this.size++;
    
                // 开始嫁接 当前节点的左右子树
                predecessor.left = node.left;
                predecessor.right = node.right;
    
                // 将当前节点从根节点剔除
                node = node.left = node.right = null;
                this.size--;
    
                // 返回嫁接后的新节点
                returnNode = predecessor;
             }
          }
    
          // 若是本来的节点或者新的节点是空 直接返回空便可 不须要下面的平衡维护
          if (!returnNode) return null;
    
          // 在这里对节点的高度进行从新计算 节点自己高度为1
          // 计算方式: 1 + 左右子树的height值最大的那个height值
          returnNode.height =
             1 +
             Math.max(
                this.getHeight(returnNode.left),
                this.getHeight(returnNode.right)
             );
    
          // 旧节点的高度若是和新节点的高度一致,就不须要进行节点的平衡维护了
          if (originHeight !== returnNode.height) {
             // 删除节点后进行节点的平衡维护
             // 计算一个节点的平衡因子
             const balanceFactor = this.getBalanceFactor(returnNode);
    
             // LL状况 平衡维护 右旋转操做 平衡因子为正数则表示左倾 反之为右倾
             if (
                balanceFactor > 1 &&
                this.getBalanceFactor(returnNode.left) >= 0
             )
                return this.rightRotate(returnNode);
    
             // RR状况 平衡维护 左旋转操做 平衡因子为负数则表示右倾 反之为左倾
             if (
                balanceFactor < -1 &&
                this.getBalanceFactor(returnNode.right) <= 0
             )
                return this.leftRotate(returnNode);
    
             // LR状况 平衡维护 先转换为LL状况 再处理LL状况
             if (
                balanceFactor > 1 &&
                this.getBalanceFactor(returnNode.left) < 0
             ) {
                returnNode.left = this.leftRotate(returnNode.left);
                return this.rightRotate(returnNode);
             }
    
             // RL状况 平衡维护 先转换为RR状况 再处理RR状况
             if (
                balanceFactor < -1 &&
                this.getBalanceFactor(returnNode.right) > 0
             ) {
                returnNode.right = this.rightRotate(returnNode.right);
                return this.leftRotate(returnNode);
             }
          }
    
          return returnNode;
       }
    
       // 删除操做的两个辅助函数
       // 获取最大值、删除最大值
       // 之前驱的方式 来辅助删除操做的函数
    
       // 获取最大值
       maximum(node) {
          // 不再能往右了,说明当前节点已是最大的了
          if (!node.right) return node;
    
          // 将复杂的问题渐渐减少规模,从而求出小问题的解,最后用小问题的解构建出原问题的答案
          return this.maximum(node.right);
       }
    
       // 删除最大值
       removeMax(node) {
          // 临时存储待返回的节点,可是返回以前先对它的平衡进行一下维护。
          let returnNode;
          const originHeight = node.height; // 记录原节点的高度
    
          // 解决最基本的问题
          if (!node.right) {
             let leftNode = node.left;
             node.left = null;
             this.size--;
             returnNode = leftNode;
          } else {
             // 开始化归
             node.right = this.removeMax(node.right);
             returnNode = node;
          }
    
          // 若是本来的节点或者新的节点是空 直接返回空便可 不须要下面的平衡维护
          if (!returnNode) return null;
    
          // 在这里对节点的高度进行从新计算 节点自己高度为1
          // 计算方式: 1 + 左右子树的height值最大的那个height值
          returnNode.height =
             1 +
             Math.max(
                this.getHeight(returnNode.left),
                this.getHeight(returnNode.right)
             );
    
          // 旧节点的高度若是和新节点的高度一致,就不须要进行节点的平衡维护了
          if (originHeight !== returnNode.height) {
             // 删除节点后进行节点的平衡维护
             // 计算一个节点的平衡因子
             const balanceFactor = this.getBalanceFactor(returnNode);
    
             // LL状况 平衡维护 右旋转操做 平衡因子为正数则表示左倾 反之为右倾
             if (
                balanceFactor > 1 &&
                this.getBalanceFactor(returnNode.left) >= 0
             )
                return this.rightRotate(returnNode);
    
             // RR状况 平衡维护 左旋转操做 平衡因子为负数则表示右倾 反之为左倾
             if (
                balanceFactor < -1 &&
                this.getBalanceFactor(returnNode.right) <= 0
             )
                return this.leftRotate(returnNode);
    
             // LR状况 平衡维护 先转换为LL状况 再处理LL状况
             if (
                balanceFactor > 1 &&
                this.getBalanceFactor(returnNode.left) < 0
             ) {
                returnNode.left = this.leftRotate(returnNode.left);
                return this.rightRotate(returnNode);
             }
    
             // RL状况 平衡维护 先转换为RR状况 再处理RR状况
             if (
                balanceFactor < -1 &&
                this.getBalanceFactor(returnNode.right) > 0
             ) {
                returnNode.right = this.rightRotate(returnNode.right);
                return this.leftRotate(returnNode);
             }
          }
    
          return returnNode;
       }
    
       // 查询操做 返回查询到的元素 +
       get(key) {
          let node = this.getNode(this.root, key);
          if (!node) return null;
          return node.value;
       }
    
       // 修改操做 +
       set(key, value) {
          let node = this.getNode(this.root, key);
          if (!node) throw new Error(key + " doesn't exist.");
    
          node.value = value;
       }
    
       // 返回是否包含该key的元素的判断值 +
       contains(key) {
          return !!this.getNode(this.root, key);
       }
    
       // 返回映射中实际的元素个数 +
       getSize() {
          return this.size;
       }
    
       // 返回映射中是否为空的判断值 +
       isEmpty() {
          return this.size === 0;
       }
    
       // 判断当前这棵树是不是一棵二分搜索树,有二分搜索树顺序性
       isBanarySearchTree() {
          // 若是节点为空 那么这就是一棵空的二分搜索树
          if (!this.root) return true;
    
          // 存储二分搜索树中的key
          const list = new Array();
    
          // 中序遍历后,添加到list中的值会是以从小到大升序的样子排列
          this.inOrder(this.root, list);
    
          // 从前日后判断 list中的值是不是从小到大升序的排列
          // 验证 当前树是否符合二分搜索树的性质
          for (var i = 1; i < list.length; i++)
             if (list[i - 1] > list[i]) return false;
          return true;
       }
    
       // 中序遍历 辅助函数 -
       inOrder(node, list) {
          // 递归到底的状况
          if (!node) return;
    
          // 中序遍历时,添加到数组中的值会是以从小到大升序的样子排列
          this.inOrder(node.left, list);
          list.push(node.key);
          this.inOrder(node.right, list);
       }
    
       // 判断该二叉树是否一棵平衡二叉树
       isBalanced() {
          return this.recursiveIsBalanced(this.root);
       }
    
       // 递归判断某一个节点是否符合平衡二叉树的定义 辅助函数 -
       recursiveIsBalanced(node) {
          // 可以递归到底,说明符合要求
          // 空的节点左右孩子高度差确定为0,
          // 由于空树没有左右子树,更加谈不上下面去判断它的左右子树高度差是否会超过一。
          if (!node) return true;
    
          // 若是当前节点的高度差大于1 说明不符合要求
          if (Math.abs(this.getBalanceFactor(node)) > 1) return false;
    
          // 递归的去判断当前节点的 左右子树是否符合要求
          return (
             this.recursiveIsBalanced(node.left) &&
             this.recursiveIsBalanced(node.right)
          );
       }
    
       // @Override toString() 2018-11-05-jwl
       toString() {
          let mapInfo = `MyBinarySearchTreeMap: size = ${this.size}, data = [ `;
          document.body.innerHTML += `MyBinarySearchTreeMap: size = ${ this.size }, data = [ <br/><br/>`;
    
          // 以非递归的前序遍历 输出字符串
          let stack = new MyLinkedListStack();
    
          stack.push(this.root);
    
          if (!this.root === null) stack.pop();
    
          while (!stack.isEmpty()) {
             let node = stack.pop();
    
             if (node.left !== null) stack.push(node.left);
             if (node.right !== null) stack.push(node.right);
    
             if (node.left === null && node.right === null) {
                mapInfo += ` ${node.toString()} \r\n`;
                document.body.innerHTML += ` ${node.toString()} <br/><br/>`;
             } else {
                mapInfo += ` ${node.toString()}, \r\n`;
                document.body.innerHTML += ` ${node.toString()}, <br/><br/>`;
             }
          }
    
          mapInfo += ` ] \r\n`;
          document.body.innerHTML += ` ] <br/><br/>`;
    
          return mapInfo;
       }
    }
    复制代码
  2. MyAVLTreeMap

    // 自定义AVLTree映射 AVLTreeMap
    class MyAVLTreeMap {
       constructor() {
          this.myAVLTree = new MyAVLTree();
       }
    
       // 添加操做
       add(key, value) {
          this.MyAVLTree.add(key, value);
       }
    
       // 查询操做
       get(key) {
          return this.MyAVLTree.get(key);
       }
    
       // 删除操做
       remove(key) {
          return this.MyAVLTree.remove(key);
       }
    
       // 查看key是否存在
       contains(key) {
          return this.MyAVLTree.contains(key);
       }
    
       // 更新操做
       set(key, value) {
          this.MyAVLTree.set(key, value);
       }
    
       // 获取映射Map中实际元素个数
       getSize() {
          return this.MyAVLTree.getSize();
       }
    
       // 查看映射Map中是否为空
       isEmpty() {
          return this.MyAVLTree.isEmpty();
       }
    }
    复制代码
  3. MyAVLTreeSet

    // 自定义AVLTree集合 AVLTreeSet
    class MyAVLTreeSet {
       //
       constructor() {
          this.myAVLTree = new MyAVLTree();
       }
    
       add(element) {
          this.myAVLTree.add(element, null);
       }
    
       remove(element) {
          this.myAVLTree.remove(element);
       }
    
       contains(element) {
          return this.myAVLTree.contains(element);
       }
    
       getSize() {
          return this.myAVLTree.getSize();
       }
    
       isEmpty() {
          return this.myAVLTree.isEmpty();
       }
    }
    复制代码
  4. Main

    // main 函数
    class Main {
       constructor() {
          this.alterLine('Map Comparison Area');
          const n = 2000000;
          // const n = 200;
    
          const myBSTMap = new MyBinarySearchTreeMap();
          const myAVLTree = new MyAVLTree();
          let performanceTest1 = new PerformanceTest();
    
          const random = Math.random;
          let arrNumber = new Array(n);
    
          // 循环添加随机数的值
          for (let i = 0; i < n; i++) arrNumber[i] = Math.floor(n * random());
    
          this.alterLine('MyBSTMap Comparison Area');
          const myBSTMapInfo = performanceTest1.testCustomFn(function() {
             // 添加
             for (const word of arrNumber)
                myBSTMap.add(word, String.fromCharCode(word));
    
             // 删除
             for (const word of arrNumber) myBSTMap.remove(word);
    
             // 查找
             for (const word of arrNumber)
                if (myBSTMap.contains(word))
                   throw new Error("doesn't remove ok.");
          });
    
          // 总毫秒数:
          console.log(myBSTMapInfo);
          console.log(myBSTMap);
          this.show(myBSTMapInfo);
    
          this.alterLine('MyAVLTree Comparison Area');
          const that = this;
          const myAVLTreeInfo = performanceTest1.testCustomFn(function() {
             for (const word of arrNumber)
                myAVLTree.add(word, String.fromCharCode(word));
    
             // 输出当前这棵myAVLTree树是不是一个二分搜索树
             that.show(
                'Is Binary Search Tree : ' + myAVLTree.isBanarySearchTree()
             );
             console.log(
                'Is Binary Search Tree : ' + myAVLTree.isBanarySearchTree()
             );
    
             // 输出当前这棵myAVLTree树是不是一个平衡二叉树
             that.show('Is Balanced : ' + myAVLTree.isBalanced());
             console.log('Is Balanced : ' + myAVLTree.isBalanced());
    
             // 删除
             for (const word of arrNumber) {
                myAVLTree.remove(word);
             }
    
             // // 查找
             for (const word of arrNumber)
                if (myAVLTree.contains(word))
                   throw new Error("doesn't remove ok.");
          });
    
          console.log(myAVLTree);
          // 总毫秒数:
          console.log(myAVLTreeInfo);
          this.show(myAVLTreeInfo);
       }
    
       // 将内容显示在页面上
       show(content) {
          document.body.innerHTML += `${content}<br /><br />`;
       }
    
       // 展现分割线
       alterLine(title) {
          let line = `--------------------${title}----------------------`;
          console.log(line);
          document.body.innerHTML += `${line}<br /><br />`;
       }
    }
    
    // 页面加载完毕
    window.onload = function() {
       // 执行主函数
       new Main();
    };
    复制代码
相关文章
相关标签/搜索