【從蛋殼到滿天飛】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();
    };
    複製代碼
相關文章
相關標籤/搜索