【從蛋殼到滿天飛】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

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

堆和優先隊列(HeapAndPriorityQueue)

  1. 使用了二分搜索樹實現了集合和映射這兩個相對來說更加高層的數據結構
    1. 樹這種數據結構自己在計算機科學領域佔有重要的地位,
    2. 樹這種形狀自己能夠產生很是多的拓展,
    3. 在面對不一樣的問題的時候能夠稍微改變或者限制樹這種數據結構的性質,
    4. 從而產生不一樣的數據結構,高效的解決不一樣的問題,
    5. 有四個不一樣的例子,堆、線段樹、字典樹、並查集,
    6. 經過這些不一樣的數據結構的學習能夠體會到數據結構的靈活之處,
    7. 以及在設計數據結構的時候其中的一些思考很是重要,
    8. 由於這些思考會讓你對數據結構這個領域有更加深入的認識。
  2. 堆是一個特殊的樹結構

優先隊列(PriorityQueue)

  1. 優先隊列自己就是一種隊列
  2. 普通隊列:先進先出、後進後出
    1. 如排隊買票吃飯同樣。
  3. 優先隊列:出隊順序和入隊順序無關,和優先級相關
    1. 如去醫院看病,牀位很是的緊張,
    2. 須要排隊才能去作手術的話,
    3. 此時作手術的這個隊伍的順序就是一個優先隊列,
    4. 由於醫生是根據每個患者的病症的不一樣以及須要這個手術的緊急程度的不一樣
    5. 來去安排誰先作手術誰後作手術,
    6. 也能夠認爲每個患者是有一個優先級的,
    7. 是優先級高着先得,這樣的一個隊列就叫作優先隊列,
    8. 它和普通隊列最主要的區別是在於出隊這個操做上,
    9. 入隊很簡單,可是優先級高的先出隊。
  4. 優先隊列的應用
    1. 在計算機的世界中優先隊列的使用也是很是多的,
    2. 例如操做系統中進行任務的調度,
    3. 如今的操做系統中會同時執行多個任務,
    4. 操做系統就要爲這多個任務分配計算資源,
    5. 包括去分配 cpu 的時間片,具體去分配這些資源的時候,
    6. 操做系統就要看各個任務的優先級,
    7. 而後動態的去選擇優先級最高的任務來執行。
    8. 這個動態很重要,若是任務數量是固定的,
    9. 那麼就不須要去製做新的數據結構來處理這個問題,
    10. 那麼這個過程就只須要一個排序算法而並非一個優先隊列,
    11. 一般實際狀況不是這樣的,
    12. 假設有一個任務處理中心,有三個任務請求過來,
    13. 那麼這個時候任務處理中心就須要去找出優先級最高的那個請求,
    14. 而後對這個任務進行相應的處理,可是處理完這個任務的同時,
    15. 頗有可能就來了不少新的任務請求,這就是動態的意思,
    16. 並不可以在一開始就肯定任務中心一共須要處理多少個任務,
    17. 其實醫生是不知道天天或者每月每一個季度每年要來多少患者,
    18. 它要隨時根據新來的患者的狀況來調整整個隊列的優先級,
    19. 這就是動態的意思,而不是一開始任務中心就知道全部的任務是什麼,
    20. 而後排排序就行了,就像醫生也不能一開始就把今年要作的全部的手術都列出來,
    21. 而後排排序就行了,隨着時間的推移會不停的有新的元素入隊,
    22. 也就是隊列中的元素是在不斷的變化的,
    23. 因此必須使用優先隊列這樣的數據結構來解決這個問題,
    24. 而不只僅是按照優先級排序。
  5. 好比作一個簡單的 AI
    1. 這個 AI 要自動的幫你打怪,其實 AI 也沒有那麼高級,
    2. 在不少遊戲的底層自己就存在這樣的 AI,
    3. 好比說在一個即時戰略的遊戲中,當你建立了己方軍隊的時候,
    4. 固然能夠經過本身的操做來指揮己方的軍隊去攻擊哪些敵人,
    5. 可是你不去指定,當敵方軍隊接近己方軍隊的時候,
    6. 己方軍隊也會自動的去攻擊敵方軍隊,這也叫作一個 AI,
    7. 這種 AI 實際上是很是常見的,這種時候 AI 可能同時面對不一樣的敵人,
    8. 那麼它就須要選擇優先去打那種敵人,在這種狀況下就須要使用優先隊列,
    9. 其實就是去打威脅程度最高的敵人,也就是去打優先級最高的那個敵人,
    10. 優先級的高低是能夠定義的,多是最強悍的那個敵人、
    11. 也有多是最弱小的敵人、也有多是距離你最近的敵人等等,
    12. AI 自動打怪中的優先隊列也是動態的處理敵人,
    13. 由於敵人是不斷的在接近你,在每一時刻 AI 都須要考慮新的敵人的出現,
    14. 由於新的敵人多是優先級更高的須要被打擊的目標,
    15. 這就是一個動態的過程,因此才須要使用優先隊列。
  6. 實現優先級隊列的時候是不用去管優先高低的
    1. 當用戶具體去使用的時候,纔會去定義優先級。

優先隊列的實現思路

  1. MyQueue
    1. void enqueue(e)
    2. E dequeue()
    3. E getFront()
    4. int getSize()
    5. boolean isEmpty()
  2. MyPriorityQueue
    1. 實現的時候與普通隊列的實現會有些區別,
    2. 主要區別在於出隊操做和獲取隊首元素操做上,
    3. 由於出隊元素是優先級最高的元素,
    4. 隊首的元素也是優先級最高的元素,
    5. 並非普通隊列那樣的選擇最先進入隊列的元素,
    6. 對於優先隊列這樣的數據結構,
    7. 也是可使用不一樣的底層實現的,
    8. 可使用最基礎的普通線性數據結構和順序線性結構來實現,
    9. 也可使用來進行實現。

兩種基礎實現優先隊列思路

  1. 使用普通的線性結構
    1. 入隊爲O(1)級別的操做
    2. 出隊須要掃描一遍線性結構中全部的元素,
    3. 從而找出其優先級最高的那個元素,
    4. 而後把它拿出隊列,
    5. 因此爲O(n)級別的操做,
    6. 若是某一操做爲O(n)級別的操做,
    7. 那麼就會大大的下降整個數據結構的效率。
    8. 因此普通線性結構的實現方式性能方面會很很差。
  2. 順序線性結構
    1. 整個線性結構維持全部元素的順序,
    2. 整個線性數據結構都是從小到大或者從大到小排列的,
    3. 在這種狀況下出隊將變得很是的容易,
    4. 出隊會是O(1)級別的操做,
    5. 由於只須要拿出當前這個數據結構隊首
    6. 或者隊尾的那個元素就行了,
    7. 那麼入隊的時候就會是一個O(n)級別的操做了,
    8. 在入隊的時候須要找到這個元素在線性結構中應該插入的位置,
    9. 這個找到合適插入位置的操做須要O(n)的複雜度,
    10. 在最差的狀況就須要將整個線性數據結構都掃描一遍,
    11. 因此順序線性結構在出隊的操做上是O(1)的複雜度,
    12. 可是在入隊的操做上是O(n)的複雜度。
    13. 因此不管是普通的線性結構仍是順序的線性結構,
    14. 它們都有必定的劣勢,都會存在一個操做是O(n)級別的操做,
    15. 它們都不夠好。
  3. 可使用動態數組、鏈表這樣的底層實現來進行優先隊列的實現
    1. 雖然這樣實現出來的優先隊列,可能實際不會去應用,
    2. 可是也是一個很好的練習,能夠深刻的理解隊列這樣抽象的數據結構,
    3. 在這個基礎上限制它的性質,就建立出了優先隊列這個概念,
    4. 具體在實現優先隊列這個概念的時候,還可使用不一樣的底層實現,
    5. 這在數據結構領域是很是重要的思想。

使用堆來實現優先隊列思路

  1. 使用堆這種數據結構
    1. 入隊操做和出隊操做都是O(logn)這種級別的操做,
    2. 並且它和二分搜索樹不一樣,
    3. 二分搜索樹是在平均狀況下是O(logn)的時間複雜度,
    4. 而堆是在最差的狀況下是O(logn)的時間複雜度,
    5. 這也使得堆這種數據結構是至關高效的。
  2. 它和前面兩種基礎的實現方式有着天壤之別。

堆(Heap)

  1. 在計算機科學的領域一般你見到了O(logn)這樣的時間複雜度
    1. 那麼近乎必定就和樹這樣的數據結構有關,
    2. 並不必定是顯示的構造出一棵樹,
    3. 不管是排序算法中的歸併排序、快速排序都是O(nlog(n))這個級別的,
    4. 在排序的過程當中沒有使用樹這種數據結構,
    5. 可是這個遞歸的過程當中其實造成了一棵隱形的遞歸樹。

堆的基本結構

  1. 堆這種結構自己也是一棵樹數組

    1. 其實堆也有不少種,
    2. 堆最爲主流的一種實現方式是使用二叉樹來表示一個堆,
    3. 也叫二叉堆(BinaryHeap),
    4. 說白了二叉堆就是知足一些特殊性質的二叉樹。
  2. 滿二叉樹與徹底二叉樹數據結構

    1. 滿二叉樹,就是除了葉子節點以外,
    2. 全部的節點的左右孩子都不爲空。
    3. 徹底二叉樹不必定是一個滿的二叉樹,可是它不滿的那部分,
    4. 也就是在缺失節點的那部分必定是在整顆樹的右下側,
    5. 也就是說把元素按照一層一層的順序排列成
    6. 一棵二叉樹的形狀的時候,獲得的這棵樹就是徹底的二叉樹。
    7. 一個三層的滿二叉樹能裝 7 個節點,一個四層的滿二叉樹能裝 15 個節點,
    8. 10 個節點對於一棵徹底二叉樹來講,前七節點裝滿前三層,
    9. 對於第四層則是從左到右把剩下的三個節點放進去,
    10. 這就是徹底二叉樹的定義。
  3. 教材中對於徹底二叉樹的定義很是的拗口架構

    1. 其實徹底二叉樹很是的簡單的,
    2. 就是把元素一層一層的放置,直到放不下了爲止,
    3. 全部整棵樹的右下角的這部分多是空的,由於缺乏一些元素。
  4. 二叉堆知足的性質dom

    1. 首先它是一棵徹底二叉樹,
    2. 除此以外它還有一個很是重要的性質,
    3. 在堆(樹)中的某個節點的值或者任意一個節點的值
    4. 老是不大於其父節點的值,
    5. 也就是說全部節點的值必定大於或者等於它的孩子節點的值,
    6. 因此根節點的元素必定是最大的元素,它大於它全部左右節點的值,
    7. 同時它的左右子樹也是一個堆,對於樹中任意節點都會知足這個性質,
    8. 這樣獲得的堆一般叫作最大堆,由於根節點是最大的一個元素,
    9. 相應的也能夠定義出最小堆,這個定義方式和最大堆同樣,
    10. 只不過每個節點的值都要小於等於它的孩子節點的值,
    11. 這樣獲得的堆叫作最小堆,
    12. 最大堆和最小堆在某種程度上是能夠統一的,
    13. 由於什麼叫大什麼叫小能夠本身來定義。
    14. 雖然在堆中每個節點的值都大於等於它的左右孩子節點的值
    15. 可是在堆這種數據結構上,
    16. 層次比較低的節點的值不必定大於層次比較高的節點的值,
    17. 由於二叉堆只保證每個節點的父節點比本身大,
    18. 可是節點的大小和節點所在的層次其實沒有必然的聯繫。
  5. 實現二叉堆必須知足的要求,ide

    1. 首先是徹底二叉樹,
    2. 其次對於堆中每個節點相應的元素值都是大於等於它的孩子節點的,
    3. 這樣獲得的就是最大堆,最大堆是一個徹底二叉樹,因此在具體實現上,
    4. 有一個很巧妙的手段,可使用二分搜索樹的方式,
    5. 先定義一個節點而後定義它的左右孩子就能實現這個堆了,
    6. 可是徹底二叉樹的特色其實就是一個一個的節點
    7. 按照順序一層一層的碼放出來。
    8. 可使用數組的方式表現出一顆徹底二叉樹,
    9. 對於數組的表示方式來講,要解決的問題是,
    10. 在數組中的每個節點應該怎麼找到它的左右孩子,
    11. 由於設置一個節點的話直接使用這個節點左右的指針
    12. 去指向左右孩子所在的節點,可是用數組去存儲的話,
    13. 其實存在一些規律。
  6. 以數組的方式表現一棵徹底二叉樹的規律函數

    1. parent(i) = i / 2
    2. 這個節點的父節點所在位置的索引就是當前節點二分之一(忽略小數或向下取整),
    3. left child (i) = 2 * i
    4. 這個節點的左孩子所在位置的索引就是當前節點索引的 2 倍,
    5. right child (i) = 2 * i + 1
    6. 這個節點的右孩子所在位置的索引就是當前節點索引的 2 倍+1,
    7. 這樣一來不只能夠很是方便的去索引這個節點的左右孩子,
    8. 還能夠很是方便的去索引到當前節點的父節點,
    9. 這就是以數組的方式去表現一棵徹底二叉樹的規律和性質。
  7. 徹底二叉樹的好處

    1. 它自己就是那些元素按照順序的一層一層的在這棵樹中排列,
    2. 因此將它標上索引以後,
    3. 每個節點和它的左右孩子節點以及它的父節點在索引之間
    4. 就存在了一種很明顯的邏輯關係,
    5. 這個邏輯關係對於徹底二叉樹來講是成立的。
  8. 在不少教科書中實現堆的時候,

    1. 用數組來存儲,索引都是從 1 開始標,
    2. 這是由於相對來講,計算孩子節點和父親節點會比較方便,
    3. 這樣一來就會出現一個小問題,也就是將 0 這個位置空出來了,
    4. 可是空出來並無什麼影響,例如在循環隊列中、虛擬頭節點鏈表中,
    5. 也都誠心的空出這麼一個位置來方便邏輯的編寫,
    6. 不過對於堆來講,就算不空出這個位置,邏輯同樣是很是簡單的,
    7. 區別在於計算父節點和左右孩子節點索引時相應的公式發生了一點點的改變,
    8. 也就是相應的 i 進行了偏移
    // 原來是這樣的 空了數組中索引爲0的位置
       parent(i) = i / 2
       left child (i) = 2 * i
       right child (i) = 2 * i + 1
    
       // 偏移以後是這樣的 沒空數組中索引爲0的位置
       parent(i) = (i - 1) / 2
       left child (i) = 2 * i + 1
       right child (i) = 2 * i + 2
    複製代碼

實現最大堆基本架構 代碼示例

  1. (class: Myarray, class: MaxHeap)

  2. Myarray

    // 自定義類
    class MyArray {
       // 構造函數,傳入數組的容量capacity構造Array 默認數組的容量capacity=10
       constructor(capacity = 10) {
          this.data = new Array(capacity);
          this.size = 0;
       }
    
       // 獲取數組中的元素實際個數
       getSize() {
          return this.size;
       }
    
       // 獲取數組的容量
       getCapacity() {
          return this.data.length;
       }
    
       // 判斷數組是否爲空
       isEmpty() {
          return this.size === 0;
       }
    
       // 給數組擴容
       resize(capacity) {
          let newArray = new Array(capacity);
          for (var i = 0; i < this.size; i++) {
             newArray[i] = this.data[i];
          }
    
          // let index = this.size - 1;
          // while (index > -1) {
          // newArray[index] = this.data[index];
          // index --;
          // }
    
          this.data = newArray;
       }
    
       // 在指定索引處插入元素
       insert(index, element) {
          // 先判斷數組是否已滿
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // 而後判斷索引是否符合要求
          if (index < 0 || index > this.size) {
             throw new Error(
                'insert error. require index < 0 or index > size.'
             );
          }
    
          // 最後 將指定索引處騰出來
          // 從指定索引處開始,全部數組元素所有日後移動一位
          // 從後往前移動
          for (let i = this.size - 1; i >= index; i--) {
             this.data[i + 1] = this.data[i];
          }
    
          // 在指定索引處插入元素
          this.data[index] = element;
          // 維護一下size
          this.size++;
       }
    
       // 擴展 在數組最前面插入一個元素
       unshift(element) {
          this.insert(0, element);
       }
    
       // 擴展 在數組最後面插入一個元素
       push(element) {
          this.insert(this.size, element);
       }
    
       // 其實在數組中添加元素 就至關於在數組最後面插入一個元素
       add(element) {
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // size其實指向的是 當前數組最後一個元素的 後一個位置的索引。
          this.data[this.size] = element;
          // 維護size
          this.size++;
       }
    
       // get
       get(index) {
          // 不能訪問沒有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('get error. index < 0 or index >= size.');
          }
          return this.data[index];
       }
    
       // 擴展: 獲取數組中第一個元素
       getFirst() {
          return this.get(0);
       }
    
       // 擴展: 獲取數組中最後一個元素
       getLast() {
          return this.get(this.size - 1);
       }
    
       // set
       set(index, newElement) {
          // 不能修改沒有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('set error. index < 0 or index >= size.');
          }
          this.data[index] = newElement;
       }
    
       // contain
       contain(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return true;
             }
          }
          return false;
       }
    
       // find
       find(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return i;
             }
          }
          return -1;
       }
    
       // findAll
       findAll(element) {
          // 建立一個自定義數組來存取這些 元素的索引
          let myarray = new MyArray(this.size);
    
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                myarray.push(i);
             }
          }
    
          // 返回這個自定義數組
          return myarray;
       }
    
       // 刪除指定索引處的元素
       remove(index) {
          // 索引合法性驗證
          if (index < 0 || index >= this.size) {
             throw new Error('remove error. index < 0 or index >= size.');
          }
    
          // 暫存即將要被刪除的元素
          let element = this.data[index];
    
          // 後面的元素覆蓋前面的元素
          for (let i = index; i < this.size - 1; i++) {
             this.data[i] = this.data[i + 1];
          }
    
          this.size--;
          this.data[this.size] = null;
    
          // 若是size 爲容量的四分之一時 就能夠縮容了
          // 防止複雜度震盪
          if (Math.floor(this.getCapacity() / 4) === this.size) {
             // 縮容一半
             this.resize(Math.floor(this.getCapacity() / 2));
          }
    
          return element;
       }
    
       // 擴展:刪除數組中第一個元素
       shift() {
          return this.remove(0);
       }
    
       // 擴展: 刪除數組中最後一個元素
       pop() {
          return this.remove(this.size - 1);
       }
    
       // 擴展: 根據元素來進行刪除
       removeElement(element) {
          let index = this.find(element);
          if (index !== -1) {
             this.remove(index);
          }
       }
    
       // 擴展: 根據元素來刪除全部元素
       removeAllElement(element) {
          let index = this.find(element);
          while (index != -1) {
             this.remove(index);
             index = this.find(element);
          }
    
          // let indexArray = this.findAll(element);
          // let cur, index = 0;
          // for (var i = 0; i < indexArray.getSize(); i++) {
          // // 每刪除一個元素 原數組中就少一個元素,
          // // 索引數組中的索引值是按照大小順序排列的,
          // // 因此 這個cur記錄的是 原數組元素索引的偏移量
          // // 只有這樣纔可以正確的刪除元素。
          // index = indexArray.get(i) - cur++;
          // this.remove(index);
          // }
       }
    
       // @Override toString 2018-10-17-jwl
       toString() {
          let arrInfo = `Array: size = ${this.getSize()},capacity = ${this.getCapacity()},\n`;
          arrInfo += `data = [`;
          for (var i = 0; i < this.size - 1; i++) {
             arrInfo += `${this.data[i]}, `;
          }
          if (!this.isEmpty()) {
             arrInfo += `${this.data[this.size - 1]}`;
          }
          arrInfo += `]`;
    
          // 在頁面上展現
          document.body.innerHTML += `${arrInfo}<br /><br /> `;
    
          return arrInfo;
       }
    }
    複製代碼
  3. MaxHeap

    // 自定義二叉堆之最大堆
    class MyMaxHeap {
       constructor(capacity = 10) {
          this.myArray = new MyArray(capacity);
       }
    
       // 輔助函數 計算出堆中指定索引位置的元素其父節點的索引 -
       calcParentIndex(index) {
          if (index === 0)
             // 索引爲0是根節點,根節點沒有父親節點,小於0就更加不能夠了
             throw new Error("index is 0. doesn't have parent.");
          return Math.floor((index - 1) / 2);
       }
    
       // 輔助函數 計算出堆中指定索引位置的元素其左孩子節點的索引 -
       calcLeftChildIndex(index) {
          return index * 2 + 1;
       }
    
       // 輔助函數 計算出堆中指定索引位置的元素其右孩子節點的索引 -
       calcRightChildIndex(index) {
          return index * 2 + 2;
       }
    
       // 獲取堆中實際的元素個數
       getSize() {
          return this.myArray.getSize();
       }
    
       // 返回堆中元素是否爲空的判斷值
       isEmpty() {
          return this.myArray.isEmpty();
       }
    }
    複製代碼

向堆中添加元素 和 Sift Up

  1. 最大堆是一個徹底二叉樹
    1. 因此能夠很是方便使用數組的方式來表示它。
  2. 向堆中添加元素
    1. 從用戶的角度上來看是添加元素,
    2. 可是從堆的角度上來看,
    3. 會涉及到堆的一個很是基礎的內部操做,
    4. 也就是 Sift Up(堆中元素上浮的過程),
    5. 添加操做必定是往堆的最底層進行添加,
    6. 也就是向數組的尾部進行添加,
    7. 新添加的元素會與其父祖節點進行比較,
    8. 若是新添加的元素比父祖輩元素大,
    9. 則會不斷的交換元素,直到知足徹底二叉樹的性質,
    10. 也就是每個節點都比其孩子節點大,也比其父節點小,
    11. 這個不斷交換元素,就是元素在堆中慢慢從底層
    12. 上浮到合適層的過程就叫 Sift UP,
    13. 計算父祖輩元素的索引能夠經過新增長的元素的索引來進行計算,
    14. 也就是數組中實際個數減去一得到。
  3. SiftUp 操做不管是遞歸寫法仍是非遞歸寫法,他們都一個共同的特色
    1. 元素上浮後結束的條件 當前節點元素值 小於其父節點元素值
    2. 索引越界的終止條件 要上浮的元素索引 小於等於 0
    3. 有了這兩個條件以後,實現上浮操做很簡單。

代碼示例

  1. (class: Myarray, class: MaxHeap)

  2. Myarray

    // 自定義類
    class MyArray {
       // 構造函數,傳入數組的容量capacity構造Array 默認數組的容量capacity=10
       constructor(capacity = 10) {
          this.data = new Array(capacity);
          this.size = 0;
       }
    
       // 獲取數組中的元素實際個數
       getSize() {
          return this.size;
       }
    
       // 獲取數組的容量
       getCapacity() {
          return this.data.length;
       }
    
       // 判斷數組是否爲空
       isEmpty() {
          return this.size === 0;
       }
    
       // 給數組擴容
       resize(capacity) {
          let newArray = new Array(capacity);
          for (var i = 0; i < this.size; i++) {
             newArray[i] = this.data[i];
          }
    
          // let index = this.size - 1;
          // while (index > -1) {
          // newArray[index] = this.data[index];
          // index --;
          // }
    
          this.data = newArray;
       }
    
       // 在指定索引處插入元素
       insert(index, element) {
          // 先判斷數組是否已滿
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // 而後判斷索引是否符合要求
          if (index < 0 || index > this.size) {
             throw new Error(
                'insert error. require index < 0 or index > size.'
             );
          }
    
          // 最後 將指定索引處騰出來
          // 從指定索引處開始,全部數組元素所有日後移動一位
          // 從後往前移動
          for (let i = this.size - 1; i >= index; i--) {
             this.data[i + 1] = this.data[i];
          }
    
          // 在指定索引處插入元素
          this.data[index] = element;
          // 維護一下size
          this.size++;
       }
    
       // 擴展 在數組最前面插入一個元素
       unshift(element) {
          this.insert(0, element);
       }
    
       // 擴展 在數組最後面插入一個元素
       push(element) {
          this.insert(this.size, element);
       }
    
       // 其實在數組中添加元素 就至關於在數組最後面插入一個元素
       add(element) {
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // size其實指向的是 當前數組最後一個元素的 後一個位置的索引。
          this.data[this.size] = element;
          // 維護size
          this.size++;
       }
    
       // get
       get(index) {
          // 不能訪問沒有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('get error. index < 0 or index >= size.');
          }
          return this.data[index];
       }
    
       // 擴展: 獲取數組中第一個元素
       getFirst() {
          return this.get(0);
       }
    
       // 擴展: 獲取數組中最後一個元素
       getLast() {
          return this.get(this.size - 1);
       }
    
       // set
       set(index, newElement) {
          // 不能修改沒有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('set error. index < 0 or index >= size.');
          }
          this.data[index] = newElement;
       }
    
       // contain
       contain(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return true;
             }
          }
          return false;
       }
    
       // find
       find(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return i;
             }
          }
          return -1;
       }
    
       // findAll
       findAll(element) {
          // 建立一個自定義數組來存取這些 元素的索引
          let myarray = new MyArray(this.size);
    
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                myarray.push(i);
             }
          }
    
          // 返回這個自定義數組
          return myarray;
       }
    
       // 刪除指定索引處的元素
       remove(index) {
          // 索引合法性驗證
          if (index < 0 || index >= this.size) {
             throw new Error('remove error. index < 0 or index >= size.');
          }
    
          // 暫存即將要被刪除的元素
          let element = this.data[index];
    
          // 後面的元素覆蓋前面的元素
          for (let i = index; i < this.size - 1; i++) {
             this.data[i] = this.data[i + 1];
          }
    
          this.size--;
          this.data[this.size] = null;
    
          // 若是size 爲容量的四分之一時 就能夠縮容了
          // 防止複雜度震盪
          if (Math.floor(this.getCapacity() / 4) === this.size) {
             // 縮容一半
             this.resize(Math.floor(this.getCapacity() / 2));
          }
    
          return element;
       }
    
       // 擴展:刪除數組中第一個元素
       shift() {
          return this.remove(0);
       }
    
       // 擴展: 刪除數組中最後一個元素
       pop() {
          return this.remove(this.size - 1);
       }
    
       // 擴展: 根據元素來進行刪除
       removeElement(element) {
          let index = this.find(element);
          if (index !== -1) {
             this.remove(index);
          }
       }
    
       // 擴展: 根據元素來刪除全部元素
       removeAllElement(element) {
          let index = this.find(element);
          while (index != -1) {
             this.remove(index);
             index = this.find(element);
          }
    
          // let indexArray = this.findAll(element);
          // let cur, index = 0;
          // for (var i = 0; i < indexArray.getSize(); i++) {
          // // 每刪除一個元素 原數組中就少一個元素,
          // // 索引數組中的索引值是按照大小順序排列的,
          // // 因此 這個cur記錄的是 原數組元素索引的偏移量
          // // 只有這樣纔可以正確的刪除元素。
          // index = indexArray.get(i) - cur++;
          // this.remove(index);
          // }
       }
    
       // 新增: 交換兩個索引位置的變量 2018-11-6
       swap(indexA, indexB) {
          if (
             indexA < 0 ||
             indexA >= this.size ||
             indexB < 0 ||
             indexB >= this.size
          )
             throw new Error('Index is Illegal.'); // 索引越界異常
    
          let temp = this.data[indexA];
          this.data[indexA] = this.data[indexB];
          this.data[indexB] = temp;
       }
    
       // @Override toString 2018-10-17-jwl
       toString() {
          let arrInfo = `Array: size = ${this.getSize()},capacity = ${this.getCapacity()},\n`;
          arrInfo += `data = [`;
          for (var i = 0; i < this.size - 1; i++) {
             arrInfo += `${this.data[i]}, `;
          }
          if (!this.isEmpty()) {
             arrInfo += `${this.data[this.size - 1]}`;
          }
          arrInfo += `]`;
    
          // 在頁面上展現
          document.body.innerHTML += `${arrInfo}<br /><br /> `;
    
          return arrInfo;
       }
    }
    複製代碼
  3. MaxHeap

    // 自定義二叉堆之最大堆
    class MyMaxHeap {
       constructor(capacity = 10) {
          this.myArray = new MyArray(capacity);
       }
    
       // 添加操做
       add(element) {
          // 追加元素
          this.myArray.push(element);
    
          // 將追加的元素上浮到堆中合適的位置
          this.siftUp(this.myArray.getSize() - 1);
       }
    
       // 堆的上浮操做 -
       siftUp(index) {
          // this.nonRecursiveSiftUp(index);
          this.recursiveSiftUp(index);
    
          // 不管是遞歸仍是非遞歸都有一個
          // 元素上浮後結束的條件 當前節點元素值 小於其父節點元素值
          // 和
          // 索引即將越界的終止條件 要上浮的元素索引 小於等於0
       }
    
       // 堆的上浮操做 遞歸算法 -
       recursiveSiftUp(index) {
          // 解決最基本的問題, 遞歸終止條件
          if (index <= 0) return;
    
          let currentValue = this.myArray.get(index);
          let parentIndex = this.calcParentIndex(index);
          let parentValue = this.myArray.get(parentIndex);
    
          // 遞歸寫法
          if (this.compare(currentValue, parentValue) > 0) {
             this.swap(index, parentIndex);
             this.recursiveSiftUp(parentIndex);
          }
       }
    
       // 堆的上浮操做 非遞歸算法 -
       nonRecursiveSiftUp(index) {
          if (index <= 0) return;
    
          let currentValue = this.myArray.get(index);
          let parentIndex = this.calcParentIndex(index);
          let parentValue = this.myArray.get(parentIndex);
    
          while (this.compare(currentValue, parentValue) > 0) {
             // 交換堆中兩個元素位置的值
             this.swap(index, parentIndex);
    
             // 交換了位置以後,元素上浮後的索引變量也要進行相應的變動
             index = parentIndex;
             // 若是索引小於等於0了 那就結束循環
             if (index <= 0) break;
             currentValue = this.myArray.get(index);
             parentIndex = this.calcParentIndex(index);
             parentValue = this.myArray.get(parentIndex);
          }
       }
    
       // 堆中兩個元素的位置進行交換
       swap(indexA, indexB) {
          this.myArray.swap(indexA, indexB);
       }
    
       // 輔助函數 計算出堆中指定索引位置的元素其父節點的索引 -
       calcParentIndex(index) {
          if (index === 0)
             // 索引爲0是根節點,根節點沒有父親節點,小於0就更加不能夠了
             throw new Error("index is 0. doesn't have parent.");
          return Math.floor((index - 1) / 2);
       }
    
       // 輔助函數 計算出堆中指定索引位置的元素其左孩子節點的索引 -
       calcLeftChildIndex(index) {
          return index * 2 + 1;
       }
    
       // 輔助函數 計算出堆中指定索引位置的元素其右孩子節點的索引 -
       calcRightChildIndex(index) {
          return index * 2 + 2;
       }
    
       // 比較的功能 -
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is error. element can't compare.");
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    
       // 獲取堆中實際的元素個數
       getSize() {
          return this.myArray.getSize();
       }
    
       // 返回堆中元素是否爲空的判斷值
       isEmpty() {
          return this.myArray.isEmpty();
       }
    }
    複製代碼

取出堆中的最大元素和 Sift Down

  1. 從堆中取出元素也就是從堆的頂部取出元素
    1. 在最大堆中堆頂的元素就是最大元素,
    2. 也就是堆中根節點的元素,這個過程叫作 Extract Max,
    3. 也就是提取出堆中最大的元素
  2. 從堆中取出元素
    1. 取出了堆頂的元素後,
    2. 堆中就有兩棵子樹了,就不符合一棵徹底二叉樹的性質了,
    3. 這時候就讓最低層的最後一個元素放到最上層的根節點,
    4. 那麼又開始符合一棵徹底二叉樹的性質了,
    5. 只不過根節點的元素不必定符合父節點的值大於全部子節點的值的性質,
    6. 也就是不符合堆的性質了,由於每個節點要大於等於其孩子節點的值,
    7. 那這個根節點就要進行下沉操做了,
    8. 也就是每次下沉的時候都要去和它的兩個孩子節點作比較,
    9. 選擇它的兩個孩子中最大的那個元素,
    10. 若是這兩個孩子元素最大的那個元素比它本身還要大的話,
    11. 那麼它本身就和兩個孩子中最大的那個元素交換一下位置,
    12. 交換過位置以後若是仍是不符合堆的性質,
    13. 那麼就繼續下沉,繼續交換,直到符合堆的性質爲止,
    14. 由於那樣就不須要再下沉了,這時候你須要手動的終止,
    15. 若是你不手動的終止,雖然整個操做到最後也會結束,
    16. 可是自動終止,時間複雜度一直都是最壞的狀況O(logn)
    17. 其實手動終止,最好的狀況是小於O(logn)的,
    18. 因此若是能夠的話儘可能手動終止一下。
  3. 堆排序的基本原理
    1. 大概就是 Extract 這個過程,
    2. 可是真正的堆排序仍是有優化的空間的,
    3. 如今的方式是將數據扔進一個堆,
    4. 而後再從堆中一個一個取出來放入一個數組內,
    5. 還使用了額外的空間,
    6. 可是使用堆這種組織元素的思想徹底能夠將數據進行原地的排序。
  4. 在一個徹底二叉樹中,若是一個節點沒有左孩子節點必然就沒有右孩子節點。

代碼示例

  1. (class: Myarray, class: MaxHeap, class: Main)

  2. Myarray

    // 自定義類
    class MyArray {
       // 構造函數,傳入數組的容量capacity構造Array 默認數組的容量capacity=10
       constructor(capacity = 10) {
          this.data = new Array(capacity);
          this.size = 0;
       }
    
       // 獲取數組中的元素實際個數
       getSize() {
          return this.size;
       }
    
       // 獲取數組的容量
       getCapacity() {
          return this.data.length;
       }
    
       // 判斷數組是否爲空
       isEmpty() {
          return this.size === 0;
       }
    
       // 給數組擴容
       resize(capacity) {
          let newArray = new Array(capacity);
          for (var i = 0; i < this.size; i++) {
             newArray[i] = this.data[i];
          }
    
          // let index = this.size - 1;
          // while (index > -1) {
          // newArray[index] = this.data[index];
          // index --;
          // }
    
          this.data = newArray;
       }
    
       // 在指定索引處插入元素
       insert(index, element) {
          // 先判斷數組是否已滿
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // 而後判斷索引是否符合要求
          if (index < 0 || index > this.size) {
             throw new Error(
                'insert error. require index < 0 or index > size.'
             );
          }
    
          // 最後 將指定索引處騰出來
          // 從指定索引處開始,全部數組元素所有日後移動一位
          // 從後往前移動
          for (let i = this.size - 1; i >= index; i--) {
             this.data[i + 1] = this.data[i];
          }
    
          // 在指定索引處插入元素
          this.data[index] = element;
          // 維護一下size
          this.size++;
       }
    
       // 擴展 在數組最前面插入一個元素
       unshift(element) {
          this.insert(0, element);
       }
    
       // 擴展 在數組最後面插入一個元素
       push(element) {
          this.insert(this.size, element);
       }
    
       // 其實在數組中添加元素 就至關於在數組最後面插入一個元素
       add(element) {
          if (this.size == this.getCapacity()) {
             // throw new Error("add error. Array is full.");
             this.resize(this.size * 2);
          }
    
          // size其實指向的是 當前數組最後一個元素的 後一個位置的索引。
          this.data[this.size] = element;
          // 維護size
          this.size++;
       }
    
       // get
       get(index) {
          // 不能訪問沒有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('get error. index < 0 or index >= size.');
          }
          return this.data[index];
       }
    
       // 擴展: 獲取數組中第一個元素
       getFirst() {
          return this.get(0);
       }
    
       // 擴展: 獲取數組中最後一個元素
       getLast() {
          return this.get(this.size - 1);
       }
    
       // set
       set(index, newElement) {
          // 不能修改沒有存放元素的位置
          if (index < 0 || index >= this.size) {
             throw new Error('set error. index < 0 or index >= size.');
          }
          this.data[index] = newElement;
       }
    
       // contain
       contain(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return true;
             }
          }
          return false;
       }
    
       // find
       find(element) {
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                return i;
             }
          }
          return -1;
       }
    
       // findAll
       findAll(element) {
          // 建立一個自定義數組來存取這些 元素的索引
          let myarray = new MyArray(this.size);
    
          for (var i = 0; i < this.size; i++) {
             if (this.data[i] === element) {
                myarray.push(i);
             }
          }
    
          // 返回這個自定義數組
          return myarray;
       }
    
       // 刪除指定索引處的元素
       remove(index) {
          // 索引合法性驗證
          if (index < 0 || index >= this.size) {
             throw new Error('remove error. index < 0 or index >= size.');
          }
    
          // 暫存即將要被刪除的元素
          let element = this.data[index];
    
          // 後面的元素覆蓋前面的元素
          for (let i = index; i < this.size - 1; i++) {
             this.data[i] = this.data[i + 1];
          }
    
          this.size--;
          this.data[this.size] = null;
    
          // 若是size 爲容量的四分之一時 就能夠縮容了
          // 防止複雜度震盪
          if (Math.floor(this.getCapacity() / 4) === this.size) {
             // 縮容一半
             this.resize(Math.floor(this.getCapacity() / 2));
          }
    
          return element;
       }
    
       // 擴展:刪除數組中第一個元素
       shift() {
          return this.remove(0);
       }
    
       // 擴展: 刪除數組中最後一個元素
       pop() {
          return this.remove(this.size - 1);
       }
    
       // 擴展: 根據元素來進行刪除
       removeElement(element) {
          let index = this.find(element);
          if (index !== -1) {
             this.remove(index);
          }
       }
    
       // 擴展: 根據元素來刪除全部元素
       removeAllElement(element) {
          let index = this.find(element);
          while (index != -1) {
             this.remove(index);
             index = this.find(element);
          }
    
          // let indexArray = this.findAll(element);
          // let cur, index = 0;
          // for (var i = 0; i < indexArray.getSize(); i++) {
          // // 每刪除一個元素 原數組中就少一個元素,
          // // 索引數組中的索引值是按照大小順序排列的,
          // // 因此 這個cur記錄的是 原數組元素索引的偏移量
          // // 只有這樣纔可以正確的刪除元素。
          // index = indexArray.get(i) - cur++;
          // this.remove(index);
          // }
       }
    
       // 新增: 交換兩個索引位置的變量 2018-11-6
       swap(indexA, indexB) {
          if (
             indexA < 0 ||
             indexA >= this.size ||
             indexB < 0 ||
             indexB >= this.size
          )
             throw new Error('Index is Illegal.'); // 索引越界異常
    
          let temp = this.data[indexA];
          this.data[indexA] = this.data[indexB];
          this.data[indexB] = temp;
       }
    
       // @Override toString 2018-10-17-jwl
       toString() {
          let arrInfo = `Array: size = ${this.getSize()},capacity = ${this.getCapacity()},\n`;
          arrInfo += `data = [`;
          for (var i = 0; i < this.size - 1; i++) {
             arrInfo += `${this.data[i]}, `;
          }
          if (!this.isEmpty()) {
             arrInfo += `${this.data[this.size - 1]}`;
          }
          arrInfo += `]`;
    
          // 在頁面上展現
          document.body.innerHTML += `${arrInfo}<br /><br /> `;
    
          return arrInfo;
       }
    }
    複製代碼
  3. MaxHeap

    // 自定義二叉堆之最大堆
    class MyMaxHeap {
       constructor(capacity = 10) {
          this.myArray = new MyArray(capacity);
       }
    
       // 添加操做
       add(element) {
          // 追加元素
          this.myArray.push(element);
    
          // 將追加的元素上浮到堆中合適的位置
          this.siftUp(this.myArray.getSize() - 1);
       }
    
       // 堆的上浮操做 -
       siftUp(index) {
          // this.nonRecursiveSiftUp(index);
          this.recursiveSiftUp(index);
    
          // 不管是遞歸仍是非遞歸都有一個
          // 元素上浮後結束的條件 當前節點元素值 小於其父節點元素值
          // 和
          // 索引即將越界的終止條件 要上浮的元素索引 小於等於0
       }
    
       // 堆的上浮操做 遞歸算法 -
       recursiveSiftUp(index) {
          // 解決最基本的問題, 遞歸終止條件
          if (index <= 0) return;
    
          let currentValue = this.myArray.get(index);
          let parentIndex = this.calcParentIndex(index);
          let parentValue = this.myArray.get(parentIndex);
    
          // 遞歸寫法
          if (this.compare(currentValue, parentValue) > 0) {
             this.swap(index, parentIndex);
             this.recursiveSiftUp(parentIndex);
          }
       }
    
       // 堆的上浮操做 非遞歸算法 -
       nonRecursiveSiftUp(index) {
          if (index <= 0) return;
    
          let currentValue = this.myArray.get(index);
          let parentIndex = this.calcParentIndex(index);
          let parentValue = this.myArray.get(parentIndex);
    
          while (this.compare(currentValue, parentValue) > 0) {
             // 交換堆中兩個元素位置的值
             this.swap(index, parentIndex);
    
             // 交換了位置以後,元素上浮後的索引變量也要進行相應的變動
             index = parentIndex;
             // 若是索引小於等於0了 那就結束循環
             if (index <= 0) break;
             currentValue = this.myArray.get(index);
             parentIndex = this.calcParentIndex(index);
             parentValue = this.myArray.get(parentIndex);
          }
       }
    
       // 找到優先級最大的元素 (查找元素)操做
       findMax() {
          if (this.myArray.isEmpty())
             throw new Error('can not findMax when heap is empty.');
          return this.myArray.getFirst();
       }
    
       // 提取優先級最大的元素(刪除元素)操做
       extractMax() {
          // 獲取堆頂的元素
          let maxElement = this.findMax();
    
          // 獲取堆底的元素
          let element = this.myArray.getLast();
    
          // 讓堆底的元素替換掉堆頂的元素
          this.myArray.set(0, element);
    
          // 移除堆底的元素
          this.myArray.pop();
    
          // 讓堆頂的元素開始下沉,從而可以正常知足堆的性質
          this.siftDown(0);
    
          // 返回堆頂的元素
          return maxElement;
       }
    
       // 堆的下沉操做 -
       siftDown(index) {
          // this.nonRecursiveSiftDown(index);
          this.recursiveSiftDown(index);
       }
    
       // 堆的下沉操做 遞歸算法 -
       recursiveSiftDown(index) {
          // 遞歸終止條件
          // 若是當前索引位置的元素沒有左孩子就說也沒有右孩子,
          // 那麼能夠直接終止,由於沒法下沉
          if (this.calcLeftChildIndex(index) >= this.myArray.getSize()) return;
    
          const leftChildIndex = this.calcLeftChildIndex(index);
          const leftChildValue = this.myArray.get(leftChildIndex);
          const rightChildIndex = this.calcRightChildIndex(index);
          let rightChildValue = null;
    
          // let maxIndex = 0;
          // if (rightChildIndex >= this.myArray.getSize())
          // maxIndex = leftChildIndex;
          // else {
          // rightChildValue = this.myArray.get(rightChildIndex);
          // if (this.compare(leftChildValue, rightChildValue) > 0)
          // maxIndex = leftChildIndex;
          // else
          // maxIndex = rightChildIndex;
          // }
    
          // 這段代碼是上面註釋代碼的優化
          let maxIndex = leftChildIndex;
          if (rightChildIndex < this.myArray.getSize()) {
             rightChildValue = this.myArray.get(rightChildIndex);
             if (this.compare(leftChildValue, rightChildValue) < 0)
                maxIndex = rightChildIndex;
          }
    
          let maxValue = this.myArray.get(maxIndex);
          let currentValue = this.myArray.get(index);
    
          if (this.compare(maxValue, currentValue) > 0) {
             // 交換位置
             this.swap(maxIndex, index);
             // 繼續下沉
             this.recursiveSiftDown(maxIndex);
          }
       }
    
       // 堆的下沉操做 非遞歸算法 -
       nonRecursiveSiftDown(index) {
          // 該索引位置的元素有左右孩子節點才能夠下沉,
          // 在徹底二叉樹中 若是一個節點沒有左孩子必然沒有右孩子
          while (this.calcLeftChildIndex(index) < this.myArray.getSize()) {
             let leftChildIndex = this.calcLeftChildIndex(index);
             let leftChildValue = this.myArray.get(leftChildIndex);
             let rightChildIndex = this.calcRightChildIndex(index);
             let rightChildValue = null;
             let maxIndex = leftChildIndex;
    
             if (rightChildIndex < this.myArray.getSize()) {
                rightChildValue = this.myArray.get(rightChildIndex);
                if (this.compare(leftChildValue, rightChildValue) <= 0)
                   maxIndex = rightChildIndex;
             }
    
             let maxValue = this.myArray.get(maxIndex);
             let currentValue = this.myArray.get(index);
    
             if (this.compare(maxValue, currentValue) > 0) {
                this.swap(maxIndex, index);
                index = maxIndex;
                continue;
             } else break;
          }
       }
    
       // 堆中兩個元素的位置進行交換
       swap(indexA, indexB) {
          this.myArray.swap(indexA, indexB);
       }
    
       // 輔助函數 計算出堆中指定索引位置的元素其父節點的索引 -
       calcParentIndex(index) {
          if (index === 0)
             // 索引爲0是根節點,根節點沒有父親節點,小於0就更加不能夠了
             throw new Error("index is 0. doesn't have parent.");
          return Math.floor((index - 1) / 2);
       }
    
       // 輔助函數 計算出堆中指定索引位置的元素其左孩子節點的索引 -
       calcLeftChildIndex(index) {
          return index * 2 + 1;
       }
    
       // 輔助函數 計算出堆中指定索引位置的元素其右孩子節點的索引 -
       calcRightChildIndex(index) {
          return index * 2 + 2;
       }
    
       // 比較的功能 -
       compare(elementA, elementB) {
          if (elementA === null || elementB === null)
             throw new Error("element is error. element can't compare.");
          if (elementA > elementB) return 1;
          else if (elementA < elementB) return -1;
          else return 0;
       }
    
       // 獲取堆中實際的元素個數
       size() {
          return this.myArray.getSize();
       }
    
       // 返回堆中元素是否爲空的判斷值
       isEmpty() {
          return this.myArray.isEmpty();
       }
    }
    複製代碼
  4. Main

    // main 函數
    class Main {
       constructor() {
          this.alterLine('MyMaxHeap Area');
          const n = 100;
    
          const maxHeap = new MyMaxHeap();
          const random = Math.random;
    
          // 循環添加隨機數的值
          for (let i = 0; i < n; i++) maxHeap.add(random() * n);
    
          console.log('MaxHeap maxHeap size:' + maxHeap.size());
          this.show('MaxHeap maxHeap size:' + maxHeap.size());
    
          // 使用數組取值
          let arr = [];
          for (let i = 0; i < n; i++) arr[i] = maxHeap.extractMax();
    
          console.log(
             'Array arr size:' +
                arr.length +
                ',MaxHeap maxHeap size:' +
                maxHeap.size()
          );
          this.show(
             'Array arr size:' +
                arr.length +
                ',MaxHeap maxHeap size:' +
                maxHeap.size()
          );
          console.log(arr, maxHeap);
          // 檢驗一下是否符合要求
          for (let i = 1; i < n; i++)
             if (arr[i - 1] < arr[i]) throw new Error('error.');
    
          console.log('test maxHeap completed.');
          this.show('test maxHeap completed.');
       }
    
       // 將內容顯示在頁面上
       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. 堆中的時間複雜度都是O(logn)級別的
    1. 其實仍是二叉樹的高度這個級別的,
    2. 對於堆來講它是一棵徹底二叉樹,
    3. 因此它永遠不會退化成一個鏈表,
    4. 一棵徹底二叉樹它的高度和節點的數量之間的關係必定是logn這個級別的關係,
    5. 這使得堆中相應的 add、extractMax 操做是很是的高效的。
  2. add O(logn)
  3. extractMax O(logn)
  4. 能夠給堆再添加兩個操做,從而對這個堆再進行優化。
相關文章
相關標籤/搜索