【从蛋壳到满天飞】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. 当你按照顺序添加[1, 2, 3, 4, 5, 6]后,
    2. 二分搜索树会退化成一条链表,
    3. 由于你第一个添加的 1 成为了根节点,
    4. 二分搜索树的性质是,当前节点的左子树小于当前节点,
    5. 当前节点的右子树大于当前节点,
    6. 后面的值都是按照+1 的顺序增大,
    7. 那么都会是在新增长的节点的右子树上不断新增节点,
    8. 这样就会造成一棵向右扩张的树,这棵树就像链表同样。
  2. 在现有的二分搜索树中添加一些机制,从而可以维持平衡二叉树的性质,编程

    1. AVL 树就是一种最为经典的平衡二叉树,
    2. 这种树之因此叫作 AVL 树是由于这三个字母是 AVL 树的发明人
    3. G.M.Adelson-Velsky 和 E.M.Landis,
    4. 就是这两个发明人的名字的首字母的缩写,
    5. 诶地欧斯-喂斯 K 和兰迪斯这两我的,
    6. 这两我的都是俄罗斯的计算机科学家,
    7. AVL 这种树结构是由它们在 1962 年首次发表的论文中提出的,
    8. 一般都认为 AVL 树是一种最先的自平衡二分搜索树结构。
  3. 平衡二叉树数组

    1. 一棵满的二叉树必定是一棵平衡二叉树,
    2. 满的二叉树的高度可让整棵树的高度达到最低的状态,
    3. 所谓的满二叉树是指,除了叶子节点以外,
    4. 全部的其它的节点都有左右两个子树,一般数据不会填满一棵二叉树。
  4. 在本身实现的堆中引入了彻底二叉树的概念,数据结构

    1. 对于一棵彻底二叉树来讲,
    2. 就是将全部的元素按照二叉树的形状一层一层的铺开,
    3. 最终获得的结果就是一棵彻底的二叉树,对于彻底的二叉树来讲,
    4. 有可能有一个非叶子的节点它的右子树是空的,
    5. 总体而言对于彻底二叉树来讲,
    6. 它空缺节点的部分必定在整棵树的右下部分,
    7. 相应的对于一棵彻底二叉树来讲,
    8. 整棵树的叶子节点最大的深度值和最小的深度值相差不会超过一,
    9. 也就是说全部的叶子节点要否则就是在这棵树的最后一层,
    10. 要否则就是在这棵树的倒数第二层。
  5. 本身实现的线段树也是一种平衡二叉树框架

    1. 虽然它不是一个彻底二叉树,
    2. 这是由于线段树空出来的地方不必定是在整棵树的右下角的位置,
    3. 可是总体在一棵线段树上,
    4. 叶子节点或者在最后一层或者在倒数第二层,
    5. 那么对于整棵树来讲全部的叶子节点的深度相差不会超过一。
  6. 不管是堆仍是线段树都是平衡二叉树的例子dom

    1. 不管是满二叉树、彻底二叉树、线段树这样的一种平衡二叉树,
    2. 叶子节点它们之间的差距不会超过一,
    3. 相对来讲都是比较理想的状况,这样的二叉树它的平衡是很是高的,
    4. 在 AVL 树中维护的这种平衡二叉树它的条件更加宽松一些,
  7. 在 AVL 树中定义平衡二叉树

    1. 对于任意一个节点来讲,
    2. 它的左子树的高度和右子树的高度相差不能超过一,
    3. 这句话和以前实现的堆的彻底二叉树仍是线段树这样的一种二叉树
    4. 差很少,
    5. 可是实际上他们是有区别的,对于堆和线段树来讲,
    6. 能够保证任意一个叶子节点相应的高度差都不能超过一,
    7. 而 AVL 定义的任意一个节点它的左右子树高度差不能超过一,
    8. 在这个定义下相应的获得的这棵平衡二叉树,
    9. 有可能看起来不是那么的平衡,由于它只是知足 AVL 定义的平衡二叉树,
    10. 显然这样的状况不会出如今堆或者线段树这两种树结构中,
    11. 这种树看起来稍微有一点的偏斜,可是仔细的去验证每个节点,
    12. 会发现它是知足这个条件的,
    13. 任意一个节点的左子树和右子树的高度差并无超过一,
    14. 以下图,节点 12 的左子树的高度为 3,右子树的高度为 2,
    15. 节点 12 的左右子树高度差没有超过一;节点 8 的左子树高度为 2,
    16. 右子树的高度 1,因此节点 8 的左右子树高度差没有超过一;
    17. 节点 18 的左子树的高度为 1,右子树的高度为 0,
    18. 因此节点 18 的左右子树的高度差没有超过一;
    19. 其它的节点也是同样的性质,虽然这棵树好像有一些偏斜,
    20. 可是 AVL 定义下的一棵平衡二叉树,对于你定义的这种平衡二叉树,
    21. 他也是知足平衡二叉树的高度和节点数量之间的关系是O(logn)级别的。
    // 知足AVL定义的平衡二叉树
    
    // (12)
    // / \
    // (8) (18)
    // /\ /
    // (5)(11)(17)
    // /
    // (4)
    复制代码
  8. 对上面图中的 AVL 平衡二叉树进行添加节点

    1. 好比添加一个节点 2,根据原来本身实现的二分搜索树的性质,
    2. 那么这个节点 2 就会从根节点开始一路找下来,
    3. 最终添加到节点 4 的左子树的位置,
    4. 相应的如何再添加一个节点 7,这个节点 7 会被添加到节点 5 的右子树中,
    5. 当树变成这个样子,那么就再也不是一棵 AVL 平衡二叉树了,
    6. 在节点 8 这个位置,左子树的高度是 3,而右子树的高度是 1,
    7. 左右子树的高度差为 2,因此打破了 AVL 平衡二叉树的条件,
    8. 同理在节点 12 这个位置,左子树的高度是 4,而右子树的高度是 2,
    9. 左右子树的高度差为 2,也打破了这个条件,
    10. 因此如今所绘制的这棵二叉树再也不是一棵 AVL 平衡二叉树。
    // 添加节点2和节点7后的树
    
    // (12)
    // / \
    // (8) (18)
    // / \ /
    // (5) (11) (17)
    // / \
    // (4) (7)
    // /
    // (2)
    //
    复制代码
  9. 让上面图中的树维持一个平衡

    1. 为了让上面图中的树保持一个平衡,
    2. 那么必须保证在插入节点的时候相应的也要顾及这棵树右侧的部分,
    3. 由于如今整棵树看起来是向左偏斜的,
    4. 必须相应的填补这棵树右侧的空间上的一些节点,
    5. 只有这样才可以让整棵树继续维持这个 AVL 平衡二叉树的
    6. 左右子树高度差不超过一这样的性质,
    7. 因此基于这样定义下的平衡二叉树来讲,虽然有可能看起来有一些偏斜,
    8. 可是实际上为了维持住这个性质,
    9. 那么整棵平衡二叉树左右两边都必须有必定数量的节点,
    10. 而不可能太过偏斜,而上图中的那棵树就是太过偏斜了,
    11. 致使打破了这棵 AVL 平衡二叉树左右子树高度差不超过一这样的性质,
    12. 因此总体而言,在这个性质下,
    13. 也能够保证这棵二叉树它的高度和节点数量之间的关系是O(logn)这个级别的,
    14. 在具体编程过程当中因为跟踪每个节点的高度是多少,
    15. 只有这样才方便来判断当前的这棵二叉树是不是平衡的。
  10. 对于以前所实现的二分搜索树来讲,

  11. 实现这个 AVL 平衡二叉树相应的每个节点都记录一下这个节点所在的高度,

  12. 其实这个记录很是的简单,对于叶子节点来讲它所对应的高度就是 1,

  13. 也就是节点 2 对应的高度为 1;节点 4 下面只有一个叶子节点,那么对应的高度就是 2;

  14. 节点 7 也是一个叶子节点,因此它的高度值也是 1;

  15. 可是对于节点 5 来讲它有左右两棵子树,左边的子树高度为 2,右边的子树高度为 1,

  16. 相应的节点 5 这个节点它的高度就是左右两棵子树中最高的那棵树再加上自身的 1,

  17. 那么这样一来节点 5 它的高度就是 2+1=3;很是好理解,节点 11 它的高度就是 1,

  18. 其它的如此类推,以下图的标注所示。

    // 节点名称加高度 小括号中的是节点名称,中括号中的是高度值
    
    // 【5】(12)
    // / \
    // 【4】(8) (18)【2】
    // / \ /
    // 【3】(5) 【1】(11) (17)【1】
    // / \
    // 【2】(4) (7)【1】
    // /
    // 【1】(2)
    //
    复制代码
  19. 平衡因子

  20. 对整棵树每个节点都标注上高度值以后,

  21. 相应的计算一个称之为平衡因子的这样的一个数,

  22. 这个名词虽然听起来比较复杂,实际上很是的简单,

  23. 它就是指对于每个节点而言,它的左右子树的高度差,

  24. 计算高度值的差可使用左子树的高度减去右子树的高度差。

  25. 对于节点 2 来讲它是要给叶子节点,

  26. 它的左右两棵子树至关因而两棵空树,空树的高度值能够记为 0,

  27. 相应的叶子节点的平衡因子其实就是0 - 0 = 0,最后的结果也为 0;

  28. 对于节点 4 来讲它的左子树对应的高度值为 1,右子树为空所对应的高度值为 0,

  29. 那么它的平衡因子就是1 - 0 = 1,最后的结果为 1;

  30. 节点 7 是个叶子节点,那么它的平衡因子就是 0;

  31. 节点 5 两棵子树的高度差是2 - 1 = 1,最终的结果为 1;

  32. 节点 11 是叶子节点,因此他的平衡因子为 0;

  33. 节点 8 的两棵子树的高度查实3 - 1 = 2,最终的结果为 2。

  34. 这就意味着节点 8 这个左右子树的高度差已经超过了一,

  35. 经过这个平衡因子就能够看出来这棵树已经不是一棵平衡二叉树了,

  36. 以下图这样标注出平衡因子以后,

  37. 一旦在一棵树中有一个节点它的平衡因子是大于等于 2 或者小于等于-2 的,

  38. 换句话说它的绝对值是等于 2,那么整棵树就再也不是一棵平衡二叉树了。

  39. 以下图所示,节点 12 和节点 8 的平衡因子的绝对值都是 2,

  40. 那么就说明这两个节点破坏了平衡二叉树对应的那个性质,

  41. 这个对应的性质就是 左右子树的高度差超过了一,

  42. 一旦能够计算出每个节点的平衡因子,

  43. 相应的也能够看出来当前的这棵树是不是一棵平衡二叉树,

  44. 不只如此在后续的 AVL 实现中也会借助每个节点

  45. 它对应的平衡因子来决定是否要进行一些特殊的操做,

  46. 从而来维持整棵树是不是一棵平衡二叉树。

    // 1. 节点名称 加高度值 加平衡因子值
    // 1. 小括号中的是节点名称,
    // 2. 中括号中的是高度值,
    // 3. 大括号中的是平衡因子
    //
    // 2. 平衡因子值 = 左右子树的高度差
    // 3. 左右子树的高度差 = 左子树的高度值 - 右子树的高度值
    
    // {2}【5】(12)
    // / \
    // {2}【4】(8) (18)【2】{1}
    // / \ /
    // {1}【3】(5) {0}【1】(11)(17)【1】{0}
    // / \
    // {1}【2】(4) (7)【1】{1}
    // /
    // {0}【1】(2)
    //
    复制代码
  47. 经过对每个节点标注相应的高度

  48. 而后经过这个高度很是简单的计算出平衡因子,

  49. 最后在经过平衡因子来决定是否进行一些特殊的操做,

  50. 从而维持整棵树是一棵平衡二叉树的性质。

计算节点的高度和平衡因子

  1. 对原先实现的二分搜索树的代码进行修改
    1. 让它能够记录每个节点的高度值,
    2. 同时来处理来计算这个平衡因子。
  2. AVL 树总体的框架和二分搜索树映射版类似。
    1. 因此 MyBSTMap 中的代码能够拿过来修改一下,
    2. 总体 AVL 树就是在二分搜索树代码的基础上补充一些代码,
    3. 其实就是添加上自平衡的机制,
    4. 使得原先实现的二分搜索树在对节点进行操做的时候能够保证整棵树是平衡的,
    5. 这里平衡的定义就是对于每个节点左右子树的高度差不超过一。
  3. 在 AVLTree 中进行修改
    1. 在 AVLTree 的 Node 中添加一个新属性 height,
    2. 同时要对这个新的成员变量进行维护,也就是新增一个 getHeight 方法,
    3. 传入一个节点而后返回这个节点对应的高度值;
    4. 添加方法中,每添加一个节点,不管是在左子树中进行添加,
    5. 仍是在右子树中进行添加,添加完了这个节点以后,
    6. 对于当前的这个 node 来讲它的 height 都有可能会发生变化,
    7. 这是由于对于当前的 node 来讲无论是左子树仍是右子树来讲,
    8. 多了一个节点以后,相应的它自身的高度就有可能发生变化,
    9. 因此须要对当前的这个 node 的 height 进行一个更新,
    10. 也就是 1 + 左右子树中 height 值最大的那个 height 值,
    11. 也就是当前节点的 height 等于本身左右子树中最大的那个高度值;
    12. 更新了节点的高度值以后,就能够计算平衡因子了,
    13. 也就是当前节点的左子树的高度减去当前右子树的高度,
    14. 取高度的差的绝对值,若是大于 1,那么就说明不符合 AVL 平衡二叉树的性质了。
  4. 经过对不符合要求的平衡因子值后,你会发现二分搜索树中不符合要求的节点很是多,
    1. 虽然在以前的测试中二分搜索树的性能已经很好了,可是在随机性的角度出发,
    2. 不符合要求的平衡因子也是很是多的,也就是说仍是会出现高度的不平衡的,
    3. 因此也就是说仍是有很大的优化空间的。

代码示例

  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 === 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 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/>';
          }
    
          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;
       }
    }
    复制代码
  2. Main

    // main 函数
    class Main {
       constructor() {
          this.alterLine('AVLTree Area');
          // 千级别
          const openCount = 100; // 操做数
          const rank = 10000000;
    
          // 生成同一份测试数据的辅助代码
          const random = Math.random;
          const array = new Array(openCount);
    
          // 生成同一份测试数据
          for (var i = 0; i < openCount; i++)
             array[i] = Math.floor(random() * rank);
    
          // 建立AVL树实例
          const avl = new MyAVLTree();
    
          for (const value of array) avl.add(value);
       }
    
       // 将内容显示在页面上
       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();
    };
    复制代码

检查二分搜索树性能和平衡性

  1. 本身实现的二分搜索树不符合要求 AVL 平衡二叉树性质的节点很是多,
    1. 因此会出现高度的不平衡,也就说明了有很大的优化空间,
    2. 这个优化空间就是,让本身实现的 AVL 树能够维持自平衡。
  2. 改进的方式和原理
    1. 须要对节点进行一下判断,
    2. 是否是二分搜索树同时是否是平衡二叉树,
    3. 对于 AVL 树来讲它是对二分搜索树的一个改进,
    4. 改进的地方是二分搜索树有可能会退化为一个链表的这种状况,
    5. 由于就引入了平衡因子这个概念,
    6. AVL 树要保持对于每个节点来讲左右子树的高度差不能超过一,
    7. 可是在这种状况下要注意,AVL 树同时也是一个二分搜索树,
    8. 因此它也要知足二分搜索树的性质,也就是对于每个节点来讲,
    9. 左子树全部的节点都要小于这个节点的值,右子树全部的节点都要大于这个节点的值,
    10. 也就是说它的左右子树依然是二分搜索树,那么在添加 AVL 树自平衡的机制的时候,
    11. 若是你的代码有问题的话颇有可能会打破这个性质,因此能够设置一个函数,
    12. 可以方便的检查当前的这棵 AVL 树它是否是依然能够保证它是一个棵二分搜索树。
    13. 经过二分搜索树的顺序性,也就是中序遍历,而后将结果存到一个动态数组中,
    14. 数组中的元素会是从小到大升序的排列,
    15. 以循环的方式检查这个数组中前一个元素值是否比当前元素值小,
    16. 若是符合要求就证实这就是一棵二分搜索树,
    17. 不然就不是,经过返回相应的判断值便可。
    18. 除了要判断这棵树是不是一棵二分搜索树以外,还要判断这棵树是否是一棵平衡二叉树,
    19. AVL 平衡二叉树的定义是,对于每个节点的左右子树的高度差不能超过一,
    20. 这个定义是一个递归的定义,须要从根节点开始判断它的左右子树的高度差不能超过一,
    21. 而后再去看它的左右子树的根节点是否一样知足这个条件,
    22. 因此须要又要写一个递归辅助函数,这个辅助函数是之前序遍历的方式
    23. 判断当前节点的及其左右子树是否符合平衡二叉树的定义,
    24. 若是所有符合返回 true,不然就返回 false。
  3. 当你对 AVL 树进行相应的操做以后依然能够维持二分搜索树和平衡二叉树的性质
    1. 以上的操做都是准备工做,只有知足这样的两种性质才能算是 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 === 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 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/>";
          // }
    
          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;
       }
    }
    复制代码
  2. Main

    // main 函数
    class Main {
       constructor() {
          this.alterLine('AVLTree Area');
          // 千级别
          const openCount = 100; // 操做数
          const rank = 10000000;
    
          // 生成同一份测试数据的辅助代码
          const random = Math.random;
          const array = new Array(openCount);
    
          // 生成同一份测试数据
          for (var i = 0; i < openCount; i++)
             array[i] = Math.floor(random() * rank);
    
          // 建立AVL树实例
          const avl = new MyAVLTree();
    
          for (const value of array) avl.add(value);
    
          // 输出当前这棵avl树是不是一个二分搜索树
          this.show('Is Binary Search Tree : ' + avl.isBanarySearchTree());
          console.log('Is Binary Search Tree : ' + avl.isBanarySearchTree());
    
          // 输出当前这棵avl树是不是一个平衡二叉树
          this.show('Is Balanced : ' + avl.isBalanced());
          console.log('Is Balanced : ' + avl.isBalanced());
       }
    
       // 将内容显示在页面上
       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. 左旋转和右旋转
  2. AVL 树维护自平衡在何时发生

    1. 在二分搜索树中若是想要插入一个节点,
    2. 必须从根节点开始一路寻找到这个节点正确的插入位置,
    3. 只有在你新添加一个节点才有可能致使整棵二分搜索树再也不知足平衡性,
    4. 相应的这个不平衡的节点只有可能发生在插入的这个位置一直到其父祖节点这些节点中,
    5. 这是由于是插入了这个节点以后才破坏了整棵树的平衡性,
    6. 破环的这个平衡性会反应到这个节点相应的父亲节点或者祖先节点上,
    7. 由于新插入了这个节点以后,
    8. 它的父亲节点或者祖先节点相应的左右子树的高度值就须要进行更新,
    9. 在更新以后,有可能平衡因子就会大于1或者小于-1
    10. 也就是左右子树的高度差超过了一,
    11. 因此维护平衡的时机应该是加入节点以后,沿着这个节点向上去回溯来维护这个平衡性,
    12. 由于这个总体代码是一个递归代码,
    13. 因此沿着这个节点向上维护平衡性自己也是很是容易的,
    14. 在 add 方法中显示递归到底的代码块儿,而后是选择插入节点的位置进行递归调用,
    15. 最后是更新节点的高度以及计算平衡因子,
    16. 计算出平衡因子以后就能够开始维护这个平衡性等一些特殊的操做,
    17. 维护完平衡性以后对应的 node 进行返回,返回给递归调用的上一层,
    18. 这就是一个回溯的过程,对每个节点都维护一下平衡性,
    19. 维护完平衡性以后,到递归的上一层,也就是看当前处理的这个节点相应的它的父亲节点,
    20. 在递归的上一层若是发现它的平衡因子依然不知足要求,也就是大于一或者小于负一,
    21. 而后再在这里维护平衡性,维护之后再将相应的根节点返回给上一层,依次类推,
    22. 这里就是维护二分搜索树的平衡性相应的时机所在的这个位置,
    23. 可使用宏观的角度去理解一下整个添加节点的递归函数在作什么,
    24. 而后能够再从微观的角度可使用一些相对小的数据集来看一下这个具体的执行过程。
  3. AVL 树维护平衡性的原理

    1. 假如当前这棵树为空,在这种状况下先添加一个节点,这个节点叫节点 12,
    2. 此时对这个节点来讲,它的平衡因子就是 0,
    3. 在这个节点的基础上再添加一个元素,这个节点叫作节点 8,
    4. 由于节点 8 比节点 12 小,因此就在这个节点 12 的左子树上,
    5. 如今节点 8 的平衡因子就变成了 0,相应的节点 12 这个节点它对应的平衡因子就变成了 1。
    6. 这个平衡因子更新的过程就是在模拟程序执行的过程,
    7. 添加这个节点 8 的时候在节点 12 的左子树中添加这个新的节点,
    8. 一个新的节点,它的平衡因子默认就为 0,以后因为新添加了一个节点,
    9. 这个新的节点它的祖辈节点相应的平衡因子就会发生改变,
    10. 因此就回到了节点 12 更新了它的平衡因子,因此节点 12 的平衡因子变成了 1。
    11. 而后再在这棵树中添加一个节点 5,因为 5 比 8 要小,
    12. 因此它会一路下来最终成为节点 8 的左子树,此时节点 5 是一个叶子节点,
    13. 它的平衡因子为 0,那么回到父节点 8 这里,因为节点 8 的左子树多了一个节点,
    14. 它的平衡因子就变成了 1,那么在回到节点 12 这里,节点 12 的平衡因子相应的也要更新,
    15. 此时就变为了 2,那么在节点 12 上,它的平衡因子的绝对值已经大于了 1,
    16. 因此就须要对它进行一个平衡的维护,
    17. 那么这就是对于一个节点它的平衡性被打破的一个最通常的状况,
    18. 也就是添加节点是不停的向一侧添加,造成了一条链表的形状,
    19. 显然它会不平衡,这样的一种状况不只仅可能出如今初始的状况,
    20. 还有可能出如今对一个非空的树进行节点添加的状况,
    21. 其实都是插入的元素在不平衡的节点的左侧的左侧,
    22. 换句话说,就是一直在向整棵树的左侧添加元素,最终致使父祖辈节点的平衡因子大于一,
    23. 也就是左子树的高度比右子树的高度要高,并且高出来的幅度是比一还要大的,
    24. 与此同时这个不平衡节点的左子树它的平衡因子也是大于 0 的,
    25. 换句话来讲,对于这个不平衡节点的左子树的高度也是大于右子树的,
    26. 也就是插入的元素是在最终造成不平衡节点的左侧的左侧,
    27. 那么它就会知足不符合 AVL 树性质的样子,此时就能够进行平衡的维护。
    28. 添加每个节点以后,就会更新这个节点的高度值,同时计算出这个节点的平衡因子,
    29. 这个平衡因子就有可能打破平衡二叉树的条件,也就是平衡因子的绝对值大于一,
    30. 因此就须要在下面进行平衡的维护。
  4. 右旋转操做

    1. 在上面分析的状况中,若是这个节点的平衡因子比一还要大,
    2. 也就是左子树比右子树的高度差是超过了一的,而且是左边要高的,
    3. 与此同时再来看一下当前这个节点的左子树的平衡因子,
    4. 若是左子树的平衡因子对应的是大于等于 0 的,
    5. 在这种状况下也就是说明了当前这个节点不平衡的缘由,
    6. 是在于它的左侧的左侧多添加了一个节点,因此要针对这种状况进行一个平衡的维护,
    7. 那么这种平衡的维护操做叫作右旋转,
    8. 先取出当前节点的左子树的右子树进行保存,
    9. 而后让当前节点变成当前节点的左子树的右子树,
    10. 最后让当前节点的左子树变成以前保存的右子树,
    11. 那么最开始的当前节点左子树就变成了新的二叉树的根节点,
    12. 这样的一个过程就叫作右旋转,
    13. 这个过程其实至关于最开始的当前节点顺时针的转到了
    14. 当前节点的左子树的右子树的位置,
    15. 从当前节点的左子树的角度看就是当前节点顺时针的向右进行了一个旋转,
    16. 此时获得的这棵新的二叉树即知足二分搜索树的性质,
    17. 又知足平衡二叉树的性质,以下图,承载的元素是一致的。
    // 最开始这棵树是这种状况 T1 < Z < T2 < X < T3 < Y < T4
    // (Y)
    // / \
    // (X) (T4)
    // / \
    // (Z) (T3)
    // / \
    // (T1) (T2)
    
    // 右旋转后是这样子,依然是T1 < Z < T2 < X < T3 < Y < T4
    // (X)
    // / \
    // (Z) (Y)
    // / \ / \
    // (T1) (T2)(T3)(T4)
    复制代码

代码示例

  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 === 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 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);

      // 平衡维护 右旋转操做 平衡因子为正数则表示左倾 反之为右倾
      if (balanceFactor > 1 && this.getBalanceFactor(node.left) >= 0) {
      }

      // // 若是平衡因子的绝对值大于1 说明不知足AVL平衡二叉树的性质了
      // if (Math.abs(balanceFactor) > 1) {
      // console.log(node.toString() + " unbalanced : " + balanceFactor + "\r\n");
      // document.body.innerHTML += node.toString() + " unbalanced : " + balanceFactor + "<br/>";
      // }

      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;
   }
}
复制代码
  1. Main
// main 函数
class Main {
   constructor() {
      this.alterLine('AVLTree Area');
      // 千级别
      const openCount = 100; // 操做数
      const rank = 10000000;

      // 生成同一份测试数据的辅助代码
      const random = Math.random;
      const array = new Array(openCount);

      // 生成同一份测试数据
      for (var i = 0; i < openCount; i++)
         array[i] = Math.floor(random() * rank);

      // 建立AVL树实例
      const avl = new MyAVLTree();

      for (const value of array) avl.add(value);

      // 输出当前这棵avl树是不是一个二分搜索树
      this.show('Is Binary Search Tree : ' + avl.isBanarySearchTree());
      console.log('Is Binary Search Tree : ' + avl.isBanarySearchTree());

      // 输出当前这棵avl树是不是一个平衡二叉树
      this.show('Is Balanced : ' + avl.isBalanced());
      console.log('Is Balanced : ' + avl.isBalanced());
   }

   // 将内容显示在页面上
   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. 右旋转

    1. 先取出当前节点的左子树的右子树进行保存,
    2. 而后让当前节点变成当前节点的左子树的右子树,
    3. 最后让当前节点的左子树变成以前保存的右子树,
    4. 那么最开始的当前节点左子树就变成了新的二叉树的根节点,
    5. 这样的一个过程就叫作右旋转。
    6. 右旋转是当前节点的平衡因子大于正一而且
    7. 左子树的平衡因子大于等于 0 的时候才进行的特殊操做,
    8. 右旋转以后要更新一下对应节点的高度值,只须要更新 x 和 y 的高度值便可,
    9. 从新计算一下 x 和 y 的左右子树的最大高度值而后+1 便可。
      // 对节点y进行向右旋转操做,返回旋转后新的根节点x
      // y x
      // / \ / \
      // x T4 向右旋转 (y) z y
      // / \ - - - - - - - -> / \ / \
      // z T3 T1 T2 T3 T4
      // / \
      // T1 T2
      复制代码
  2. 左旋转

    1. 与右旋转对应的是左旋转,
    2. 右旋转是插入的元素在不平衡的节点的左侧的左侧,
    3. 而左旋转是插入的元素在不平衡的节点的右侧的右侧,
    4. 左旋转和右旋转是一个左右对称的状况,
    5. 也就是当前节点的右子树的高度值比左子树的高度值相差大于一,
    6. 换句话说就是左子树的高度值减去右子树的高度值小于了负一,
    7. 在这种状况下就须要进行左旋转,
    8. 先将当前节点的右子树的左子树保存起来,
    9. 而后让当前节点的右子树的左子树等于当前节点,
    10. 最后让当前节点的右子树等于以前保存的左子树,
    11. 这个过程就是左旋转,最终获得的二叉树同样即知足而二分搜索树的性质,
    12. 同时也知足了平衡二叉树的性质。
    13. 左旋转和右旋转是一个彻底对称的关系。
    14. 左旋转是 当前节点的平衡因子小于负一而且
    15. 右子树的平衡因子小于等于 0 的时候才进行的特殊操做,
    16. 也就是整棵树总体上是向右进行倾斜的,
    17. 左旋转以后要更新一下对应节点的高度值,只须要更新 x 和 y 的高度值便可,
    18. 从新计算一下 x 和 y 的左右子树的最大高度值而后+1 便可。
    // 最开始这棵树是这种状况 T4 < Y < T3 < X < T1 < Z < T2
    // (Y)
    // / \
    // (T4) (X)
    // / \
    // (T3) (Z)
    // / \
    // (T1) (T2)
    
    // 左旋转后是这样子,依然是T4 < Y < T3 < X < T1 < Z < T2
    // (X)
    // / \
    // (Y) (Z)
    // / \ / \
    // (T4)(T3)(T1)(T2)
    复制代码
  3. 不管是左旋转仍是右旋转都是一种状况

    1. 就是以当前节点为根的向一侧偏斜,
    2. 不过是向左偏斜仍是向右偏斜对应了是使用左旋转仍是右旋转,
    3. 向左偏斜就向右旋转,向右偏斜就向左旋转。
  4. 使用了左旋转和右旋转来进行平衡性的维护以后

    1. 其实还有另外两种状况须要考虑,
    2. 只有这样才可以将全部的节点的平衡因子的绝对值小于等于一。

代码示例

  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 === 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/>';
          }
    
          // 平衡维护 右旋转操做 平衡因子为正数则表示左倾 反之为右倾
          if (balanceFactor > 1 && this.getBalanceFactor(node.left) >= 0)
             return this.rightRotate(node);
    
          // 平衡维护 右旋转操做 平衡因子为负数则表示右倾 反之为左倾
          if (balanceFactor < -1 && this.getBalanceFactor(node.left) <= 0)
             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;
       }
    }
    复制代码
  2. Main

    // main 函数
    class Main {
       constructor() {
          this.alterLine('AVLTree Area');
          // 千级别
          const openCount = 100; // 操做数
          const rank = 10000000;
    
          // 生成同一份测试数据的辅助代码
          const random = Math.random;
          const array = new Array(openCount);
    
          // 生成同一份测试数据
          for (var i = 0; i < openCount; i++)
             array[i] = Math.floor(random() * rank);
    
          // 建立AVL树实例
          const avl = new MyAVLTree();
    
          for (const value of array) avl.add(value);
    
          // 输出当前这棵avl树是不是一个二分搜索树
          this.show('Is Binary Search Tree : ' + avl.isBanarySearchTree());
          console.log('Is Binary Search Tree : ' + avl.isBanarySearchTree());
    
          // 输出当前这棵avl树是不是一个平衡二叉树
          this.show('Is Balanced : ' + avl.isBalanced());
          console.log('Is Balanced : ' + avl.isBalanced());
       }
    
       // 将内容显示在页面上
       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();
    };
    复制代码
相关文章
相关标签/搜索