【從蛋殼到滿天飛】JS 數據結構解析和算法實現-並查集(二)

思惟導圖

前言

【從蛋殼到滿天飛】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 (一個一個的工程)git

所有源代碼已上傳 github,點擊我吧,光看文章可以掌握兩成,動手敲代碼、動腦思考、畫圖才能夠掌握八成。github

本文章適合 對數據結構想了解而且感興趣的人羣,文章風格一如既往如此,就以爲手機上看起來比較方便,這樣顯得比較有條理,整理這些筆記加源碼,時間跨度也算將近半年時間了,但願對想學習數據結構的人或者正在學習數據結構的人羣有幫助。面試

並查集 路徑壓縮 Path compression

  1. 並查集的一個很是重要的優化 路徑壓縮算法

    1. 如下三種方式都是徹底同樣的,
    2. 均可以表示這五個節點是相互鏈接的,
    3. 也就是說這三種方式是等效的,
    4. 在具體的查詢過程當中,不管是調用 find 仍是 isConnected,
    5. 在這三種不一樣的方式查詢這五個節點中任意兩個節點都是相鏈接的,
    6. 可是因爲這三種樹它們的深度不一樣,因此效率是存在不一樣的,
    7. 顯然第一種樹的高度達到了 5,因此執行 find(4)這個操做,
    8. 那相應的時間性能會相對的慢一些,而第三種樹它的高度只有 2,
    9. 在這棵樹中 find 任意一個節點它響應的時間性能就會比較高,
    10. 在以前實現的 union 中,是讓根節點去指向另一個根節點,
    11. 這樣的一個過程免不了構建出來的樹愈來愈高,
    12. 路徑壓縮所解決的問題就是讓一棵比較高的樹可以壓縮成爲一棵比較矮的樹,
    13. 對於並查集來講每個節點的子樹的個數是沒有限制的,
    14. 因此最理想的狀況下其實但願每一棵樹都是直接指向某一個根節點,
    15. 也就是說這個樹它只有兩層,根節點在第一層,其它的全部的節點都在第二層,
    16. 達到這種最理想的狀況可能相對比較困難,因此退而求其次,
    17. 只要可以讓這棵樹的高度下降,那麼對整個並查集的總體性能都是好的。
    //// 第一種鏈接方式 的樹
    // (0)
    // /
    // (1)
    // /
    // (2)
    // /
    // (3)
    // /
    //(4)
    
    //// 第二種鏈接方式 的樹
    // (0)
    // / \
    //(1) (2)
    // / \
    // (3) (4)
    
    //// 第三種鏈接方式 的樹
    // (0)
    // / | \ \
    //(1)(2)(3)(4)
    複製代碼
  2. 路徑壓縮api

    1. 路徑壓縮是發生在執行 find 這個操做中,也就是查找一個節點對應的根節點的過程當中,
    2. 須要從這個節點不斷的向上直到找到這個根節點,那麼能夠在尋找的這個過程當中,
    3. 順便讓這個節點的深度下降,順便進行路徑壓縮的過程,
    4. 只須要在向上遍歷的時候同時執行parent[p] = parent[parent[p]]
    5. 也就是將 p 這個節點的父親設置成這個節點父親的父親,
    6. 這樣一來每次執行 find 都會讓你的樹下降高度,
    7. 以下圖,整棵樹原來的深度爲 5,通過一輪遍歷後,
    8. 深度降到了 3,這個過程就叫作路徑壓縮,在查詢節點 4 的時候,
    9. 順便整棵樹的結構改變,讓它的深度更加的淺了,
    10. 路徑壓縮是並查集這種數據結構相對比較經典,
    11. 也是比較廣泛的一種優化思路,
    12. 在算法競賽中一般實現並查集都要添加上路徑壓縮這樣的優化。
    // // 原來的樹是這個樣子
    // (0)
    // /
    // (1)
    // /
    // (2)
    // /
    // (3)
    // /
    // (4)
    
    // // 執行一次find(4) 使用了 parent[p] = parent[parent[p]]
    // (0)
    // /
    // (1)
    // |
    // (2)
    // / \
    // (3) (4)
    
    // // 而後再從2開始向上遍歷 再使用 parent[p] = parent[parent[p]]
    // (0)
    // / \
    // (1) (2)
    // / \
    // (3) (4)
    
    // 最後數組就是這個樣子
    // 0 1 2 3 4
    // -----------------
    // prent 0 0 0 2 2
    複製代碼
  3. 這個 rank 就是指樹的高度或樹的深度數組

    1. 之因此不叫作 height 和 depth,
    2. 是由於進行路徑壓縮的時候並不會維護這個 rank 了,
    3. 每個節點都在 rank 中記錄了
    4. 以這個節點 i 爲根的這個集合所表示的這棵樹相應的層數,
    5. 在路徑壓縮的過程當中,節點的層數其實發生了改變,
    6. 不過並無這 find 中去維護 rank 數組,
    7. 這麼作是合理的,這就是爲何管這個數組叫作 rank
    8. 而不叫作深度 depth 或高度 height 的緣由,
    9. 它實際在添加上路徑壓縮這樣的一個優化以後,
    10. 就再也不表示當前這個節點的高度或者是深度了,
    11. rank 這個詞就是排名或者序的意思,
    12. 給每個節點其實相應的都有這樣一個排名,
    13. 當你添加上了路徑壓縮以後,
    14. 依然是這個 rank 值相對比較低的這些節點在下面,
    15. rank 值相對比較高的節點在上面,
    16. 只不過可能出現同層的節點它們的 rank 值其實是不一樣的,
    17. 不過它們總體之間的大小關係依然是存在的,
    18. 因此 rank 值只是做爲 union 合併操做的時候進行的一個參考,
    19. 它依然能夠勝任這樣的一個參考的工做,
    20. 可是它並不實際反應每個節點所對應的那個高度值或者深度值,
    21. 實際上就算你不作這樣的一個 rank 維護也是性能上的考慮,
    22. 若是要想把每個節點的具體高度或者深度維護住,
    23. 相應的性能消耗是比較高的,在整個並查集的使用過程當中,
    24. 其實對於每個節點很是精準的知道這個階段所處的高度或者深度是多少,
    25. 並無必要那樣去作,
    26. 使用這樣一個比較粗略的 rank 值就能夠徹底勝任整個並查集運行的工做了。

代碼示例

  1. (class: MyUnionFindThree, class: MyUnionFindFour, class: MyUnionFindFive, class: PerformanceTest, class: Main)數據結構

  2. MyUnionFindThreedom

    // 自定義並查集 UnionFind 第三個版本 QuickUnion優化版
    // Union 操做變快了
    // 還能夠更快的
    // 解決方案:考慮size 也就是某一棵樹從根節點開始一共有多少個節點
    // 原理:節點少的向節點多的樹進行融合
    // 還能夠更快的
    class MyUnionFindThree {
       constructor(size) {
          // 存儲當前節點所指向的父節點
          this.forest = new Array(size);
          // 以以某個節點爲根的全部子節點的個數
          this.branch = new Array(size);
    
          // 在初始的時候每個節點都指向它本身
          // 也就是每個節點都是獨立的一棵樹
          const len = this.forest.length;
          for (var i = 0; i < len; i++) {
             this.forest[i] = i;
             this.branch[i] = 1; // 默認節點個數爲1
          }
       }
    
       // 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
       // 時間複雜度:O(h) h 爲樹的高度
       unionElements(treePrimary, treeSecondary) {
          const primaryRoot = this.find(treePrimary);
          const secondarRoot = this.find(treeSecondary);
    
          if (primaryRoot === secondarRoot) return;
    
          // 節點少的 樹 往 節點多的樹 進行合併,在必定程度上減小最終樹的高度
          if (this.branch[primaryRoot] < this.branch[secondarRoot]) {
             // 主樹節點上往次樹節點進行合併
             this.forest[primaryRoot] = this.forest[secondarRoot];
             // 次樹的節點個數 += 主樹的節點個數
             this.branch[secondarRoot] += this.branch[primaryRoot];
          } else {
             // branch[primaryRoot] >= branch[secondarRoot]
             // 次樹節點上往主樹節點進行合併
             this.forest[secondarRoot] = this.forest[primaryRoot];
             // 主樹的節點個數 += 次樹的節點個數
             this.branch[primaryRoot] += this.branch[secondarRoot];
          }
       }
    
       // 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
       // 時間複雜度:O(h) h 爲樹的高度
       isConnected(treeQ, treeP) {
          return this.find(treeQ) === this.find(treeP);
       }
    
       // 查找元素所對應的集合編號
       find(id) {
          if (id < 0 || id >= this.forest.length)
             throw new Error('index is out of bound.');
    
          // 不斷的去查查找當前節點的根節點
          // 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
          while (id !== this.forest[id]) id = this.forest[id];
    
          return id;
       }
    
       // 功能:當前並查集一共考慮多少個元素
       getSize() {
          return this.forest.length;
       }
    }
    複製代碼
  3. MyUnionFindFour函數

    // 自定義並查集 UnionFind 第四個版本 QuickUnion優化版
    // Union 操做變快了
    // 還能夠更快的
    // 解決方案:考慮rank 也就是某一棵樹從根節點開始計算最大深度是多少
    // 原理:讓深度比較低的那棵樹向深度比較高的那棵樹進行合併
    // 還能夠更快的
    class MyUnionFindFour {
       constructor(size) {
          // 存儲當前節點所指向的父節點
          this.forest = new Array(size);
          // 記錄某個節點爲根的樹的最大高度或深度
          this.rank = new Array(size);
    
          // 在初始的時候每個節點都指向它本身
          // 也就是每個節點都是獨立的一棵樹
          const len = this.forest.length;
          for (var i = 0; i < len; i++) {
             this.forest[i] = i;
             this.rank[i] = 1; // 默認深度爲1
          }
       }
    
       // 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
       // 時間複雜度:O(h) h 爲樹的高度
       unionElements(treePrimary, treeSecondary) {
          const primaryRoot = this.find(treePrimary);
          const secondarRoot = this.find(treeSecondary);
    
          if (primaryRoot === secondarRoot) return;
    
          // 根據兩個元素所在樹的rank不一樣判斷合併方向
          // 將rank低的集合合併到rank高的集合上
          if (this.rank[primaryRoot] < this.rank[secondarRoot]) {
             // 主樹節點上往次樹節點進行合併
             this.forest[primaryRoot] = this.forest[secondarRoot];
          } else if (this.rank[primaryRoot] > this.rank[secondarRoot]) {
             // 次樹節點上往主樹節點進行合併
             this.forest[secondarRoot] = this.forest[primaryRoot];
          } else {
             // rank[primaryRoot] == rank[secondarRoot]
             // 若是元素個數同樣的根節點,那誰指向誰都無所謂
             // 本質都是同樣的
    
             // primaryRoot合併到secondarRoot上了,qRoot的高度就會增長1
             this.forest[primaryRoot] = this.forest[secondarRoot];
             this.rank[secondarRoot] += 1;
          }
       }
    
       // 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
       // 時間複雜度:O(h) h 爲樹的高度
       isConnected(treeQ, treeP) {
          return this.find(treeQ) === this.find(treeP);
       }
    
       // 查找元素所對應的集合編號
       find(id) {
          if (id < 0 || id >= this.forest.length)
             throw new Error('index is out of bound.');
    
          // 不斷的去查查找當前節點的根節點
          // 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
          while (id !== this.forest[id]) id = this.forest[id];
    
          return id;
       }
    
       // 功能:當前並查集一共考慮多少個元素
       getSize() {
          return this.forest.length;
       }
    }
    複製代碼
  4. MyUnionFindFive

    // 自定義並查集 UnionFind 第五個版本 QuickUnion優化版
    // Union 操做變快了
    // 解決方案:考慮path compression 路徑
    // 原理:在find的時候,循環遍歷操做時,讓當前節點的父節點指向它父親的父親。
    // 還能夠更快的
    class MyUnionFindFive {
       constructor(size) {
          // 存儲當前節點所指向的父節點
          this.forest = new Array(size);
          // 記錄某個節點爲根的樹的最大高度或深度
          this.rank = new Array(size);
    
          // 在初始的時候每個節點都指向它本身
          // 也就是每個節點都是獨立的一棵樹
          const len = this.forest.length;
          for (var i = 0; i < len; i++) {
             this.forest[i] = i;
             this.rank[i] = 1; // 默認深度爲1
          }
       }
    
       // 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
       // 時間複雜度:O(h) h 爲樹的高度
       unionElements(treePrimary, treeSecondary) {
          const primaryRoot = this.find(treePrimary);
          const secondarRoot = this.find(treeSecondary);
    
          if (primaryRoot === secondarRoot) return;
    
          // 根據兩個元素所在樹的rank不一樣判斷合併方向
          // 將rank低的集合合併到rank高的集合上
          if (this.rank[primaryRoot] < this.rank[secondarRoot]) {
             // 主樹節點上往次樹節點進行合併
             this.forest[primaryRoot] = this.forest[secondarRoot];
          } else if (this.rank[primaryRoot] > this.rank[secondarRoot]) {
             // 次樹節點上往主樹節點進行合併
             this.forest[secondarRoot] = this.forest[primaryRoot];
          } else {
             // rank[primaryRoot] == rank[secondarRoot]
             // 若是元素個數同樣的根節點,那誰指向誰都無所謂
             // 本質都是同樣的
    
             // primaryRoot合併到secondarRoot上了,qRoot的高度就會增長1
             this.forest[primaryRoot] = this.forest[secondarRoot];
             this.rank[secondarRoot] += 1;
          }
       }
    
       // 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
       // 時間複雜度:O(h) h 爲樹的高度
       isConnected(treeQ, treeP) {
          return this.find(treeQ) === this.find(treeP);
       }
    
       // 查找元素所對應的集合編號
       find(id) {
          if (id < 0 || id >= this.forest.length)
             throw new Error('index is out of bound.');
    
          // 不斷的去查查找當前節點的根節點
          // 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
          while (id !== this.forest[id]) {
             // 進行一次節點壓縮。
             this.forest[id] = this.forest[this.forest[id]];
             id = this.forest[id];
          }
    
          return id;
       }
    
       // 功能:當前並查集一共考慮多少個元素
       getSize() {
          return this.forest.length;
       }
    }
    複製代碼
  5. 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);
       }
    }
    複製代碼
  6. Main

    // main 函數
    class Main {
       constructor() {
          this.alterLine('UnionFind Comparison Area');
          // 千萬級別
          const size = 10000000; // 並查集維護節點數
          const openCount = 10000000; // 操做數
    
          // 生成同一份測試數據的輔助代碼
          const random = Math.random;
          const primaryArray = new Array(openCount);
          const secondaryArray = new Array(openCount);
    
          // 生成同一份測試數據
          for (var i = 0; i < openCount; i++) {
             primaryArray[i] = Math.floor(random() * size);
             secondaryArray[i] = Math.floor(random() * size);
          }
    
          // 開始測試
          const myUnionFindThree = new MyUnionFindThree(size);
          const myUnionFindFour = new MyUnionFindFour(size);
          const myUnionFindFive = new MyUnionFindFive(size);
          const performanceTest = new PerformanceTest();
    
          // 測試後獲取測試信息
          const myUnionFindThreeInfo = performanceTest.testUnionFind(
             myUnionFindThree,
             openCount,
             primaryArray,
             secondaryArray
          );
          const myUnionFindFourInfo = performanceTest.testUnionFind(
             myUnionFindFour,
             openCount,
             primaryArray,
             secondaryArray
          );
          const myUnionFindFiveInfo = performanceTest.testUnionFind(
             myUnionFindFive,
             openCount,
             primaryArray,
             secondaryArray
          );
    
          // 總毫秒數:8042
          console.log(
             'MyUnionFindThree time:' + myUnionFindThreeInfo,
             myUnionFindThree
          );
          this.show('MyUnionFindThree time:' + myUnionFindThreeInfo);
          // 總毫秒數:7463
          console.log(
             'MyUnionFindFour time:' + myUnionFindFourInfo,
             myUnionFindFour
          );
          this.show('MyUnionFindFour time:' + myUnionFindFourInfo);
          // 總毫秒數:5118
          console.log(
             'MyUnionFindFive time:' + myUnionFindFiveInfo,
             myUnionFindFive
          );
          this.show('MyUnionFindFive time:' + myUnionFindFiveInfo);
       }
    
       // 將內容顯示在頁面上
       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. 路徑壓縮還能夠繼續優化

    1. 能夠將樹壓縮的只剩下最後兩層,
    2. 可是實現到這樣的樣子就須要藉助遞歸來實現了,
    3. 查詢某一個節點的時候,直接讓當前這個節點以及以前全部的節點,
    4. 所有直接指向根節點。
    // // 原來的樹是這個樣子
    // (0)
    // /
    // (1)
    // /
    // (2)
    // /
    // (3)
    // /
    // (4)
    
    // 你能夠優化成這個樣子
    // (0)
    // / | \ \
    // (1)(2)(3)(4)
    
    // 最後數組就是這個樣子
    // 0 1 2 3 4
    // -----------------
    // prent 0 0 0 0 0
    複製代碼
  2. 非遞歸實現的路徑壓縮要比遞歸實現的路徑壓縮相對來講快一點點

    1. 由於遞歸的過程是會有相應的開銷的,因此相對會慢一點,
    2. 可是第五版的非遞歸實現的路徑壓縮也能夠作到遞歸實現的路徑壓縮
    3. 這樣直接讓當前節點及全部的節點指向根節點,只不過是不能一次性的作到,
    4. 第五版的路徑壓縮下圖這樣的,若是在深度爲 3 的樹上再調用一下find(4)
    5. 就會變成第三個樹結構的樣子,它須要多調用幾回find(4)
    6. 可是最終依然可以達到這樣的一個結果,若是再調用一下find(3)
    7. 那麼就會變成最後的和第六版遞歸同樣的樣子,
    8. 此時全部的節點都會指向根節點,
    9. 也就是說所製做的第五版的路徑壓縮也可以達到第六版路徑壓縮的效果,
    10. 只不過須要多調用幾回,再加上第五版的路徑壓縮沒有使用遞歸函數實現,
    11. 而是直接在循環遍歷中實現的,因此總體性能會高一點點。
    // // 原來的樹是這個樣子
    // (0)
    // /
    // (1)
    // /
    // (2)
    // /
    // (3)
    // /
    // (4)
    
    // 優化成這個樣子了
    // (0)
    // / \
    // (1) (2)
    // / \
    // (3) (4)
    
    // 再調用一下find(4),就會變成這個樣子
    // (0)
    // / | \
    // (1)(2) (4)
    // /
    // (3)
    
    // 再調用一下find(3),就優化成這個樣子
    // (0)
    // / | \ \
    // (1)(2)(3)(4)
    
    // 最後數組就是這個樣子
    // 0 1 2 3 4
    // -----------------
    // prent 0 0 0 0 0
    複製代碼

代碼示例

  1. (class: MyUnionFindThree, class: MyUnionFindFour, class: MyUnionFindFive,

    1. class: MyUnionFindSix, class: PerformanceTest, class: Main)
  2. MyUnionFindThree

    // 自定義並查集 UnionFind 第三個版本 QuickUnion優化版
    // Union 操做變快了
    // 還能夠更快的
    // 解決方案:考慮size 也就是某一棵樹從根節點開始一共有多少個節點
    // 原理:節點少的向節點多的樹進行融合
    // 還能夠更快的
    class MyUnionFindThree {
       constructor(size) {
          // 存儲當前節點所指向的父節點
          this.forest = new Array(size);
          // 以以某個節點爲根的全部子節點的個數
          this.branch = new Array(size);
    
          // 在初始的時候每個節點都指向它本身
          // 也就是每個節點都是獨立的一棵樹
          const len = this.forest.length;
          for (var i = 0; i < len; i++) {
             this.forest[i] = i;
             this.branch[i] = 1; // 默認節點個數爲1
          }
       }
    
       // 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
       // 時間複雜度:O(h) h 爲樹的高度
       unionElements(treePrimary, treeSecondary) {
          const primaryRoot = this.find(treePrimary);
          const secondarRoot = this.find(treeSecondary);
    
          if (primaryRoot === secondarRoot) return;
    
          // 節點少的 樹 往 節點多的樹 進行合併,在必定程度上減小最終樹的高度
          if (this.branch[primaryRoot] < this.branch[secondarRoot]) {
             // 主樹節點上往次樹節點進行合併
             this.forest[primaryRoot] = this.forest[secondarRoot];
             // 次樹的節點個數 += 主樹的節點個數
             this.branch[secondarRoot] += this.branch[primaryRoot];
          } else {
             // branch[primaryRoot] >= branch[secondarRoot]
             // 次樹節點上往主樹節點進行合併
             this.forest[secondarRoot] = this.forest[primaryRoot];
             // 主樹的節點個數 += 次樹的節點個數
             this.branch[primaryRoot] += this.branch[secondarRoot];
          }
       }
    
       // 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
       // 時間複雜度:O(h) h 爲樹的高度
       isConnected(treeQ, treeP) {
          return this.find(treeQ) === this.find(treeP);
       }
    
       // 查找元素所對應的集合編號
       find(id) {
          if (id < 0 || id >= this.forest.length)
             throw new Error('index is out of bound.');
    
          // 不斷的去查查找當前節點的根節點
          // 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
          while (id !== this.forest[id]) id = this.forest[id];
    
          return id;
       }
    
       // 功能:當前並查集一共考慮多少個元素
       getSize() {
          return this.forest.length;
       }
    }
    複製代碼
  3. MyUnionFindFour

    // 自定義並查集 UnionFind 第四個版本 QuickUnion優化版
    // Union 操做變快了
    // 還能夠更快的
    // 解決方案:考慮rank 也就是某一棵樹從根節點開始計算最大深度是多少
    // 原理:讓深度比較低的那棵樹向深度比較高的那棵樹進行合併
    // 還能夠更快的
    class MyUnionFindFour {
       constructor(size) {
          // 存儲當前節點所指向的父節點
          this.forest = new Array(size);
          // 記錄某個節點爲根的樹的最大高度或深度
          this.rank = new Array(size);
    
          // 在初始的時候每個節點都指向它本身
          // 也就是每個節點都是獨立的一棵樹
          const len = this.forest.length;
          for (var i = 0; i < len; i++) {
             this.forest[i] = i;
             this.rank[i] = 1; // 默認深度爲1
          }
       }
    
       // 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
       // 時間複雜度:O(h) h 爲樹的高度
       unionElements(treePrimary, treeSecondary) {
          const primaryRoot = this.find(treePrimary);
          const secondarRoot = this.find(treeSecondary);
    
          if (primaryRoot === secondarRoot) return;
    
          // 根據兩個元素所在樹的rank不一樣判斷合併方向
          // 將rank低的集合合併到rank高的集合上
          if (this.rank[primaryRoot] < this.rank[secondarRoot]) {
             // 主樹節點上往次樹節點進行合併
             this.forest[primaryRoot] = this.forest[secondarRoot];
          } else if (this.rank[primaryRoot] > this.rank[secondarRoot]) {
             // 次樹節點上往主樹節點進行合併
             this.forest[secondarRoot] = this.forest[primaryRoot];
          } else {
             // rank[primaryRoot] == rank[secondarRoot]
             // 若是元素個數同樣的根節點,那誰指向誰都無所謂
             // 本質都是同樣的
    
             // primaryRoot合併到secondarRoot上了,qRoot的高度就會增長1
             this.forest[primaryRoot] = this.forest[secondarRoot];
             this.rank[secondarRoot] += 1;
          }
       }
    
       // 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
       // 時間複雜度:O(h) h 爲樹的高度
       isConnected(treeQ, treeP) {
          return this.find(treeQ) === this.find(treeP);
       }
    
       // 查找元素所對應的集合編號
       find(id) {
          if (id < 0 || id >= this.forest.length)
             throw new Error('index is out of bound.');
    
          // 不斷的去查查找當前節點的根節點
          // 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
          while (id !== this.forest[id]) id = this.forest[id];
    
          return id;
       }
    
       // 功能:當前並查集一共考慮多少個元素
       getSize() {
          return this.forest.length;
       }
    }
    複製代碼
  4. MyUnionFindFive

    // 自定義並查集 UnionFind 第五個版本 QuickUnion優化版
    // Union 操做變快了
    // 解決方案:考慮path compression 路徑
    // 原理:在find的時候,循環遍歷操做時,讓當前節點的父節點指向它父親的父親。
    // 還能夠更快的
    class MyUnionFindFive {
       constructor(size) {
          // 存儲當前節點所指向的父節點
          this.forest = new Array(size);
          // 記錄某個節點爲根的樹的最大高度或深度
          this.rank = new Array(size);
    
          // 在初始的時候每個節點都指向它本身
          // 也就是每個節點都是獨立的一棵樹
          const len = this.forest.length;
          for (var i = 0; i < len; i++) {
             this.forest[i] = i;
             this.rank[i] = 1; // 默認深度爲1
          }
       }
    
       // 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
       // 時間複雜度:O(h) h 爲樹的高度
       unionElements(treePrimary, treeSecondary) {
          const primaryRoot = this.find(treePrimary);
          const secondarRoot = this.find(treeSecondary);
    
          if (primaryRoot === secondarRoot) return;
    
          // 根據兩個元素所在樹的rank不一樣判斷合併方向
          // 將rank低的集合合併到rank高的集合上
          if (this.rank[primaryRoot] < this.rank[secondarRoot]) {
             // 主樹節點上往次樹節點進行合併
             this.forest[primaryRoot] = this.forest[secondarRoot];
          } else if (this.rank[primaryRoot] > this.rank[secondarRoot]) {
             // 次樹節點上往主樹節點進行合併
             this.forest[secondarRoot] = this.forest[primaryRoot];
          } else {
             // rank[primaryRoot] == rank[secondarRoot]
             // 若是元素個數同樣的根節點,那誰指向誰都無所謂
             // 本質都是同樣的
    
             // primaryRoot合併到secondarRoot上了,qRoot的高度就會增長1
             this.forest[primaryRoot] = this.forest[secondarRoot];
             this.rank[secondarRoot] += 1;
          }
       }
    
       // 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
       // 時間複雜度:O(h) h 爲樹的高度
       isConnected(treeQ, treeP) {
          return this.find(treeQ) === this.find(treeP);
       }
    
       // 查找元素所對應的集合編號
       find(id) {
          if (id < 0 || id >= this.forest.length)
             throw new Error('index is out of bound.');
    
          // 不斷的去查查找當前節點的根節點
          // 根節點的索引是指向本身,若是根節點爲 1 那麼對應的索引也爲 1。
          while (id !== this.forest[id]) {
             // 進行一次節點壓縮。
             this.forest[id] = this.forest[this.forest[id]];
             id = this.forest[id];
          }
    
          return id;
       }
    
       // 功能:當前並查集一共考慮多少個元素
       getSize() {
          return this.forest.length;
       }
    }
    複製代碼
  5. MyUnionFindSix

    // 自定義並查集 UnionFind 第六個版本 QuickUnion優化版
    // Union 操做變快了
    // 解決方案:考慮path compression 路徑
    // 原理:在find的時候,循環遍歷操做時,讓全部的節點都指向根節點 以遞歸的形式進行。
    // 還能夠更快的
    class MyUnionFindSix {
       constructor(size) {
          // 存儲當前節點所指向的父節點
          this.forest = new Array(size);
          // 記錄某個節點爲根的樹的最大高度或深度
          this.rank = new Array(size);
    
          // 在初始的時候每個節點都指向它本身
          // 也就是每個節點都是獨立的一棵樹
          const len = this.forest.length;
          for (var i = 0; i < len; i++) {
             this.forest[i] = i;
             this.rank[i] = 1; // 默認深度爲1
          }
       }
    
       // 功能:將元素q和元素p這兩個數據以及他們所在的集合進行合併
       // 時間複雜度:O(h) h 爲樹的高度
       unionElements(treePrimary, treeSecondary) {
          const primaryRoot = this.find(treePrimary);
          const secondarRoot = this.find(treeSecondary);
    
          if (primaryRoot === secondarRoot) return;
    
          // 根據兩個元素所在樹的rank不一樣判斷合併方向
          // 將rank低的集合合併到rank高的集合上
          if (this.rank[primaryRoot] < this.rank[secondarRoot]) {
             // 主樹節點上往次樹節點進行合併
             this.forest[primaryRoot] = this.forest[secondarRoot];
          } else if (this.rank[primaryRoot] > this.rank[secondarRoot]) {
             // 次樹節點上往主樹節點進行合併
             this.forest[secondarRoot] = this.forest[primaryRoot];
          } else {
             // rank[primaryRoot] == rank[secondarRoot]
             // 若是元素個數同樣的根節點,那誰指向誰都無所謂
             // 本質都是同樣的
    
             // primaryRoot合併到secondarRoot上了,qRoot的高度就會增長1
             this.forest[primaryRoot] = this.forest[secondarRoot];
             this.rank[secondarRoot] += 1;
          }
       }
    
       // 功能:查詢元素q和元素p這兩個數據是否在同一個集合中
       // 時間複雜度:O(h) h 爲樹的高度
       isConnected(treeQ, treeP) {
          return this.find(treeQ) === this.find(treeP);
       }
    
       // 查找元素所對應的集合編號
       find(id) {
          if (id < 0 || id >= this.forest.length)
             throw new Error('index is out of bound.');
    
          // 若是當前節點不等於根節點,
          // 就找到根節點而且把當前節點及以前的節點所有指向根節點
          if (id !== this.forest[id])
             this.forest[id] = this.find(this.forest[id]);
    
          return this.forest[id];
       }
    
       // 功能:當前並查集一共考慮多少個元素
       getSize() {
          return this.forest.length;
       }
    }
    複製代碼
  6. 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);
       }
    }
    複製代碼
  7. Main

    // main 函數
    class Main {
       constructor() {
          this.alterLine('UnionFind Comparison Area');
          // 千萬級別
          const size = 10000000; // 並查集維護節點數
          const openCount = 10000000; // 操做數
    
          // 生成同一份測試數據的輔助代碼
          const random = Math.random;
          const primaryArray = new Array(openCount);
          const secondaryArray = new Array(openCount);
    
          // 生成同一份測試數據
          for (var i = 0; i < openCount; i++) {
             primaryArray[i] = Math.floor(random() * size);
             secondaryArray[i] = Math.floor(random() * size);
          }
    
          // 開始測試
          const myUnionFindThree = new MyUnionFindThree(size);
          const myUnionFindFour = new MyUnionFindFour(size);
          const myUnionFindFive = new MyUnionFindFive(size);
          const myUnionFindSix = new MyUnionFindSix(size);
          const performanceTest = new PerformanceTest();
    
          // 測試後獲取測試信息
          const myUnionFindThreeInfo = performanceTest.testUnionFind(
             myUnionFindThree,
             openCount,
             primaryArray,
             secondaryArray
          );
          const myUnionFindFourInfo = performanceTest.testUnionFind(
             myUnionFindFour,
             openCount,
             primaryArray,
             secondaryArray
          );
          const myUnionFindFiveInfo = performanceTest.testUnionFind(
             myUnionFindFive,
             openCount,
             primaryArray,
             secondaryArray
          );
          const myUnionFindSixInfo = performanceTest.testUnionFind(
             myUnionFindSix,
             openCount,
             primaryArray,
             secondaryArray
          );
    
          // 總毫秒數:8042
          console.log(
             'MyUnionFindThree time:' + myUnionFindThreeInfo,
             myUnionFindThree
          );
          this.show('MyUnionFindThree time:' + myUnionFindThreeInfo);
          // 總毫秒數:7463
          console.log(
             'MyUnionFindFour time:' + myUnionFindFourInfo,
             myUnionFindFour
          );
          this.show('MyUnionFindFour time:' + myUnionFindFourInfo);
          // 總毫秒數:5118
          console.log(
             'MyUnionFindFive time:' + myUnionFindFiveInfo,
             myUnionFindFive
          );
          this.show('MyUnionFindFive time:' + myUnionFindFiveInfo);
          // 總毫秒數:5852
          console.log(
             'MyUnionFindSix time:' + myUnionFindSixInfo,
             myUnionFindSix
          );
          this.show('MyUnionFindSix time:' + myUnionFindSixInfo);
       }
    
       // 將內容顯示在頁面上
       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. 在並查集使用了這樣一個奇怪的樹結構來實現之後,
    1. 其實並查集的時間複雜度就是O(h)
    2. 不管是查詢操做仍是合併操做它的時間複雜度都是O(h)這個級別的,
    3. 這個 h 就是樹的高度或者深度,可是這個複雜度並不能反映 h 和 n 之間的關係,
    4. 對於並查集來講它並非一個嚴格的二叉樹、三叉樹、幾叉樹,
    5. 因此這個 h 並非嚴格意義上 logn 的級別,
    6. 對於並查集的時間複雜度分析總體在數學上相對比較複雜。
  2. 嚴格意義上來說使用了路徑壓縮以後
    1. 並查集相應的時間複雜度,不管是查詢操做仍是合併操做,
    2. 都是O(log*n)這個級別,這個 log*n 是另一個函數,
    3. 它和 log 函數不同,相應的log*的英文叫作iterated logarithm
    4. 也能夠直接讀成 log star,這個log*n在數學上有一個公式,
    5. log*n= {0 if(n<=1) || 1+log*(logn) if(n>1)}
    6. 也就是當n<=1的時候,log*n爲 0,
    7. n>1的時候,稍微有點複雜了,這是一個遞歸的定義,
    8. 這個log*n = 1 + log*(logn),括號中就是對這個n取一個log值,
    9. 再來看這個log值對應的log*的這個是多少,
    10. 直到這個括號中logn獲得的結果小於等於 1 了,那麼就直接獲得了 0,
    11. 這樣遞歸的定義就到底了,這就是 log*n 這個公式的數學意義,
    12. 這也就證實了加入了路徑壓縮以後,
    13. 對於並查集的時間複雜度爲何是O(log*n)這個級別的,
    14. 就會稍微有些複雜,只須要了解便可,
    15. log*n這樣的時間複雜度能夠經過以上公式能夠看出,
    16. 它是一個比 logn 還要快的這樣一個時間複雜度,總體上近乎是O(1)級別的,
    17. 因此它比O(1)稍微要慢一點點,其實 logn 已是很是快的一個時間複雜度了,
    18. 那麼當並查集添加上了路徑壓縮以後,
    19. 平均來說查詢操做和合並操做是比 logn 這個級別還要快的,
    20. 這就是由於在路徑壓縮以後每個節點都直接指向了根節點,
    21. 近乎每一次查詢都只須要看一次就能夠直接找到這個節點所對應的根節點是誰,
    22. 這就是並查集的時間複雜度。

leetcode 中並查集相應的問題

  1. leetcode 並查集題庫
    1. https://leetcode-cn.com/tag/union-find/
    2. 這些問題不是中等就是困難的題目,
    3. 若是隻是參加面試的話,在算法面試中考察的並查集機率很低很低的,
    4. 若是是參加競賽的話,在一些競賽的問題中可能會使用上並查集,
    5. 對於 leetcode 中的問題,不只僅是使用並查集能夠解決的,
    6. 對於不少問題可使用圖論中的相應的尋路算法或者
    7. 是求連通份量的方式直接進行解決,
    8. 也能夠回答並查集單獨回答的這樣的一個鏈接問題的結果,
    9. 可是對於有一些問題來講不可是高效的並且是有它獨特的優點的,
    10. 尤爲是對於這個問題來講,
    11. 相應的數據之間的合併以及查詢這兩個操做是交替進行的,
    12. 它們是一個動態的過程,在這種時候並查集是能夠發揮最大的優點。
    13. 這些題目是有難度,若是沒有算法競賽的經驗,會花掉不少的時間。

四種樹結構

  1. 並查集是一種很是奇怪的樹結構
    1. 它是一種由孩子指向父親這樣的一種樹結構。
  2. 四個處理不一樣的問題的樹結構
    1. 這些都是樹結構的變種,
    2. 分別是 堆、線段樹、Trie 字典樹、並查集。
  3. 二分搜索樹是最爲普通的樹結構。
    1. 以前本身實現的二分搜索樹有一個很大的問題,
    2. 它可能會退化成爲一個鏈表,
    3. 須要經過新的機制來避免這個問題的發生,
    4. 也就是讓二分搜索樹能夠作到自平衡,
    5. 使得它不會退化成一個鏈表,
    6. 其實這種能夠保持二分搜索樹是自平衡的數據結構有不少,
    7. 最爲經典的,同時也是在歷史上最先實現的能夠達到自平衡的二分搜索樹,
    8. AVL 樹。
相關文章
相關標籤/搜索