• javascript实现白平衡树 --- AVL 树


    AVL树是一种自平衡树。添加或移除节点时,AVL树会尝试保持自平衡。任意一个节点(不论深度)的左子树和右子树高度最多相差 1。添加或移除节点时,AVL树会尽可能尝试转换为完全树。

    AVL树实现代码

    // AVL树 = 平衡树 + 二叉排序树
    /**
     * 
     * @class AVLTree AVL 树是一种自平衡二叉搜索树,意思是任何一个节点左右两侧子树的高度之差最多为 1
     * @extends {BinarySearchTree}
     */
    class AVLTree extends BinarySearchTree {
      constructor(){
        super();
        this.root = null;
      }
    
      // 计算一个节点高度:
      // 可以设置子节点为空的高度为-1,然后在比较左右节点的高度,取最大的然后 +1,这样就可以实现递归查寻高度
      getNodeHeight(node) {
        if(node === null){
          return -1;
        }
        return Math.max(this.getNodeHeight(node.left), this.getNodeHeight(node.right)) + 1;
      }
    
      // 平衡因子 = 左子树高度 - 右子树高度
      getBalance(node){
        return this.getNodeHeight(node.left) - this.getNodeHeight(node.right);
      }
    
      // 左左(LL):向右的单旋转
      // 这种情况出现于节点的左侧子节点的高度大于右侧子节点的高度时,并且左侧子节点也是平衡或左侧较重的
      rotationLL(node){
        const tmp = node.left;
        node.left = tmp.right;
        tmp.right = node;
        return tmp;
      }
    
      // 右右(RR):向左的单旋转
      // 它出现于右侧子节点的高度大于左侧子节点的高度,并且右侧子节点也是平衡或右侧较重的
      rotationRR(node){
        const tmp = node.right;
        node.right = tmp.left;
        tmp.left = node;
        return tmp;
      }
    
      // 左右(LR):向右的双旋转
      // 这种情况出现于左侧子节点的高度大于右侧子节点的高度,并且左侧子节点右侧较重
      rotationLR(node) { 
        node.left = this.rotationRR(node.left); 
        return this.rotationLL(node); 
      }
    
      // 右左(RL):向左的双旋转
      // 这种情况出现于右侧子节点的高度大于左侧子节点的高度,并且右侧子节点左侧较重
      rotationRL(node) { 
        node.right = this.rotationLL(node.right); 
        return this.rotationRR(node);
      }
      
      insert(key) {
        this.root = this.insertNode(this.root, key); 
      }
      insertNode(node, key) {
        debugger
        // 像在 BST 树中一样插入节点
        if(node === null){
          return new Node(key);
        }else if(key < node.key){
          node.left = this.insertNode(node.left, key);
        }else if(key > node.key){
          node.right = this.insertNode(node.right, key);
        }else {
          return node; // 重复的键
        }
        
        // 如果需要,将树进行平衡操作
        // 当前节点的平衡因子为+2时,说明需要调整的节点在左边,然后判断的新增节点值和当前节点的左节点的值的大小来判断是左还是右,左就需要 左左(向右旋转),右的话就 左右(先向左旋转,然后向右)
        const balance = this.getBalance(node);
        if(balance === 2){  // 需要平衡的节点在根节点左边
          if(key < node.left.key){ // 说明是 左左
            node = this.rotationLL(node);
          }else { // 左右 
            return this.rotationLR(node);
          }
        }
    	// 当前节点的平衡因子为-2时,说明需要调整的节点在右边,然后判断的新增节点值和当前节点的右节点的值的大小来判断是左还是右,右就需要 右右(向左旋转),左的话就 有左(先向右旋转,然后向左)
        if(balance === -2){  // 需要平衡的节点根节点右边
          if(key > node.right.key){ // 说明是 右右
            node = this.rotationRR(node);
          }else{ // 右左
            return this.rotationRL(node);
          }
        }
        return node;
      }
    
      remove(key) {
        this.root = this.removeNode(this.root, key); 
      }
      removeNode(node, key) {
        node = super.removeNode(node, key);
        if (node == null){
          return node; // null,不需要进行平衡
        }
    
        // 如果需要,将树进行平衡操作
        const balance = this.getBalance(node);
        if(balance === 2){  // 需要平衡的节点在根节点左边
          const balanceFactorLeft = this.getBalance(node.left);
          // 平衡因子等于0或1的时候,说明左边节点比右边深,所以是 左左
          if(balanceFactorLeft === 0 || balanceFactorLeft === 1){
            return this.rotationLL(node);
          }
          if(balanceFactorLeft === -1){
            return this.rotationLR(node.left);
          }
        }
    
        if(balance === -2){  // 需要平衡的节点根节点右边
          const balanceFactorRight = this.getBalance(node.right);
          // 平衡因子等于0或1的时候,说明右边节点比左边深,所以是 右右
          if(balanceFactorRight === 0 || balanceFactorRight === -1){
            return this.rotationRR(node)
          }
          if(balanceFactorRight === 1){
            return this.rotationRL(node.right);
          }
        }
    
        return node;
      }
    }
    
    // 测试
    const tree = new AVLTree();
    tree.insert(50)
    tree.insert(30)
    tree.insert(70)
    tree.insert(10)
    tree.insert(40)
    tree.remove(70)
    console.log(tree)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141

    剖析

    节点的高度

    平衡因子:左右两边树的节点高度差

    在这里插入图片描述

    左左(LL):向右的单旋转

    新增加的节点在左子节点的,左边

    在这里插入图片描述

    右左(RL):向左的双旋转

    新增加的节点在左子节点的,右边

    在这里插入图片描述

    右右(RR):向左的单旋转

    新增加的节点在右子节点的,右边

    在这里插入图片描述

    左右(LR):向右的双旋转

    新增加的节点在右子节点的,左边
    在这里插入图片描述

  • 相关阅读:
    一个关于wait/notify与锁关系的探究
    EPOLL(C/S模型)实现I/O复用多进程聊天室,通过共享内存、socketpair实现父子进程通信,通过信号量回收进程
    超越npm和yarn的包管理工具,为什么说pnpm才是工程化项目的未来。
    观察者模式
    Mybatis实现(指标状态)的动态sql
    如何处理微服务之间的通信和数据一致性?
    同步和异步的区别
    STM32WB55开发(5)----调整射频功率
    区块链积分系统:革新支付安全与用户体验的未来
    Java基础常见面试题
  • 原文地址:https://blog.csdn.net/qq_45007419/article/details/125893412