JavaScript數據結構與算法(十)自平衡樹

前面介紹了二叉樹和二叉樹搜索樹的創建和使用,接下來我們繼續學習關於樹的更多知識。
BST存在一個問題,就是當我們多次添加節點數,有可能造成一種情況,樹的一條邊可能會非常深,有非常多的層,而另一條分支卻只有幾層。當我們需要進行添加、移除和搜索某一節點時,可能會引起一些性能問題。爲了解決這類問題,我們進行自平衡樹的學習。自平衡樹常見有兩種:AVL樹和紅黑樹。

自平衡樹

準備知識

節點的高度和平衡因子

節點高度:從節點到任意子節點的彼岸的最大值。這個相對來說容易理解。那麼獲得節點高度的代碼實現如下:

getNodeHeight(node) {
    if (node == null) {
      return -1;
    }
    return Math.max(this.getNodeHeight(node.left), this.getNodeHeight(node.right)) + 1;
  }

平衡因子:每個節點左子樹高度和右子樹高度的差值。該值爲0 、 -1、 1 時則爲正常值,說明該二叉樹已經平衡。若果該值不是這三個值之一,則需要平衡該樹。遵循計算一個節點的平衡因子並返回其值的代碼如下:

const BalanceFactor = {
  UNBALANCED_RIGHT: 1,
  SLIGHTLY_UNBALANCED_RIGHT: 2,
  BALANCED: 3,
  SLIGHTLY_UNBALANCED_LEFT: 4,
  UNBALANCED_LEFT: 5
};
getBalanceFactor(node) {
    const heightDifference = this.getNodeHeight(node.left) - this.getNodeHeight(node.right);
    switch (heightDifference) {
      case -2:
        return BalanceFactor.UNBALANCED_RIGHT;
      case -1:
        return BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT;
      case 1:
        return BalanceFactor.SLIGHTLY_UNBALANCED_LEFT;
      case 2:
        return BalanceFactor.UNBALANCED_LEFT;
      default:
        return BalanceFactor.BALANCED;
    }
  }

AVL樹

AVL樹是一種自平衡樹,添加或移除節點時,AVL會嘗試保持自平衡,也就是會可能嘗試轉換爲完全樹。接下來介紹平衡樹進行自平衡的操作,AVL旋轉

AVL旋轉

在對AVL進行添加或者移除節點後,我們需要計算節點的高度並驗證是否需要進行平衡。旋轉操作分爲單旋轉和雙旋轉兩種。

左-左LL(向右的單旋轉)
/**
   * Left left case: rotate right
   *
   *       b                           a
   *      / \                         / \
   *     a   e -> rotationLL(b) ->   c   b
   *    / \                             / \
   *   c   d                           d   e
   *
   * @param node Node<T>
   */
rotationLL(node){
    const tmp = node.right;
    node.left = tmp.right;
    tmp.right = node;
    return tmp;
}
右-右RR(向左的單旋轉)
/**
   * Right right case: rotate left
   *
   *     a                              b
   *    / \                            / \
   *   c   b   -> rotationRR(a) ->    a   e
   *      / \                        / \
   *     d   e                      c   d
   *
   * @param node Node<T>
   */
  rotationLL(node) {
    const tmp = node.left;
    node.left = tmp.right;
    tmp.right = node;
    return tmp;
  }
左-右(LR):向右的雙旋轉
/**
   * Left right case: rotate left then right
   * @param node Node<T>
   */
  rotationLR(node) {
    node.left = this.rotationRR(node.left);
    return this.rotationLL(node);
  }
右-左(RL):向左的雙旋轉
/**
   * Right left case: rotate right then left
   * @param node Node<T>
   */
  rotationRL(node) {
    node.right = this.rotationLL(node.right);
    return this.rotationRR(node);
  }

完成平衡操作(旋轉)的學習後,我們接下來完善一下AVL樹添加或者移除節點的操作

添加節點

insert(key) {
    this.root = this.insertNode(this.root, key);
  }

  insertNode(node, key) {
    if (node == null) {
      return new Node(key);
    } if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
      node.left = this.insertNode(node.left, key);
    } else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
      node.right = this.insertNode(node.right, key);
    } else {
      return node; // duplicated key
    }
    // verify if tree is balanced
    const balanceFactor = this.getBalanceFactor(node);
    if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
      if (this.compareFn(key, node.left.key) === Compare.LESS_THAN) {
        // Left left case
        node = this.rotationLL(node);
      } else {
        // Left right case
        return this.rotationLR(node);
      }
    }
    if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
      if (this.compareFn(key, node.right.key) === Compare.BIGGER_THAN) {
        // Right right case
        node = this.rotationRR(node);
      } else {
        // Right left case
        return this.rotationRL(node);
      }
    }
    return node;
  }

移除節點

 removeNode(node, key) {
    node = super.removeNode(node, key); // {1}
    if (node == null) {
      return node;
    }
    // verify if tree is balanced
    const balanceFactor = this.getBalanceFactor(node);
    if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
      // Left left case
      if (
        this.getBalanceFactor(node.left) === BalanceFactor.BALANCED
        || this.getBalanceFactor(node.left) === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
      ) {
        return this.rotationLL(node);
      }
      // Left right case
      if (this.getBalanceFactor(node.left) === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT) {
        return this.rotationLR(node.left);
      }
    }
    if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
      // Right right case
      if (
        this.getBalanceFactor(node.right) === BalanceFactor.BALANCED
        || this.getBalanceFactor(node.right) === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT
      ) {
        return this.rotationRR(node);
      }
      // Right left case
      if (this.getBalanceFactor(node.right) === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT) {
        return this.rotationRL(node.right);
      }
    }
    return node;
  }
}

以上就是關於AVL樹基礎知識的學習,接下來我們介紹另一種平衡樹——紅黑樹。
和AVL樹一樣,紅黑樹也是一個自平衡二叉樹。紅黑樹本質上也是AVL樹,但可以包含多次插入和刪除。在紅黑樹中,每個節點都遵循以下規則:

  1. 顧名思義,每個節點不是紅的就是黑的;
  2. 樹的根節點就是黑的;
  3. 所有葉節點都是黑的;
  4. 如果一個節點是紅的,那麼他的兩個子節點都是黑的
  5. 不能有兩個相鄰的紅節點,一個紅節點不能有紅的父節點或子節點;
  6. 從給定的節點到他的後代節點(NULL葉節點)的所有路徑包含相同數量的黑色節點。

紅黑樹

創建紅黑樹的骨架

const BalanceFactor = {
  UNBALANCED_RIGHT: 1,
  SLIGHTLY_UNBALANCED_RIGHT: 2,
  BALANCED: 3,
  SLIGHTLY_UNBALANCED_LEFT: 4,
  UNBALANCED_LEFT: 5
};
//定義顏色類
const Colors = {
  RED:'red',
  BLACK:'black'
};
//創建紅黑樹的節點類型
class RedBlackNode extends Node{
  constructor(key){
    super(key);
    this.key = key;
    this.color = Colors.RED;
    this.parent = null;
  }
  isRed(){
    return this.color === Colors.RED;
  }
};
class RedBlackTree extends BinarySearchTree{
  constructor(compareFn = defaultCompare){
    super(compareFn);
    this.compareFn = compareFn;
    this.root = null;
  };
}

旋轉操作

向右單旋轉
 //rotationLL
  static rotationLL(node){
    const tmp = node.left;
    node.left = tmp.right;
    if(tmp.right && tmp.right.key){
      tmp.right.parent = node;
    }
    tmp.right.parent = node.parent;
    if (!node.parent){
      this.root = tmp;
    }else{
      if(node === node.parent.left){
        node.parent.left = tmp;
      }else{
        node.parent.right = tmp;
      }
      tmp.right = node;
      node.parent = tmp;
    }
  };
向左單旋轉
//rotationRR
  static rotationRR(node){
    const tmp = node.right;
    node.right = tmp.left;
    if (tmp.left && tmp.left.key){
      tmp.left.parent = node;
    }
    tmp.parent = node.parent;
    if (!node.parent){
      this.root = tmp;
    }else{
      if(node === node.parent.left){
        node.parent.left = tmp;
      }else{
        node.parent.right = tmp;
      }
    }
    tmp.left = node;
    node.parent = tmp;
  }

驗證節點顏色屬性

 //插入節點後驗證紅黑樹的屬性
  static fixTreeProperties(node){
    while (node && node.parent && node.parent.color.isRed() && node.color !== Colors.BLACK){
      let parent = node.parent;
      const grandParent = parent.parent;
      //case A:父節點是左側子節點
      if (grandParent && grandParent.left === parent){
        const uncle = grandParent.right;
        //case 1A:叔節點也是紅色——只需要重新填色
        if (uncle && uncle.color === Colors.RED){
          grandParent.color = Colors.RED;
          parent.color  = Colors.BLACK;
          uncle.color = Colors.BLACK;
          node = grandParent;
        }else{
          // case 2A:節點是右側子節點——右旋轉
          if (node === parent.left){
            RedBlackTree.rotationRR(parent);
            node = parent;
            parent = node.parent;
          }
          //case 3A:子節點是左側子節點——左旋轉
          else if(node === parent.right){
            RedBlackTree.rotationRR(grandParent);
            parent.color = Colors.BLACK;
            grandParent.color = Colors.RED;
            node = parent;
          }
        }
      }
      //case B:父節點是右側子節點
      else{
        const uncle = grandParent.left;
        //case1B:叔節點是紅色——只需要重新填色
        if(uncle && uncle.color === Colors.RED){
          grandParent.color = Colors.RED;
          parent.color = Colors.BLACK;
          uncle.color = Colors.BLACK;
          node = grandParent;
        }
        //case2B:節點是左側子節點—右旋轉
        if (node === parent.left){
          RedBlackTree.rotationLL(parent);
          node = parent;
          parent = node.parent;
        }
        //case3B:節點是右側子節點——左旋轉
        else if(node === parent.right){
          RedBlackTree.rotationRR(grandParent);
          parent.color = Colors.BLACK;
          grandParent.color = Colors.RED;
          node = parent;
        }
      }
      this.root.color = Colors.BLACK;
    }
  }

添加新節點

 //向紅黑樹插入新節點
  insertNode(node,key){
    if(this.compareFn(key,node.key) === Compare.LESS_THAN){
      if(node.left === null){
        node.left = new RedBlackNode(key);
        node.left.parent = node;
        return node.left;
      }
      else{
        return this.insertNode(node.left,key);
      }
    }
    else if(node.right === null){
      node.right = new RedBlackNode(key);
      node.right.parent = node;
      return node.right;
    }
  };
  insert(key) {
    if (this.root === null){
      this.root = new RedBlackNode(key);
      this.root.color = Colors.BLACK;
    }else{
      const newNode = this.insertNode(this.root, key);
      RedBlackTree.fixTreeProperties(newNode);
    }
  };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章