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

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

堆的另外兩個操做,Heapify 和 replace

  1. 從理論上講這兩個操做
    1. 可使用以前堆中的操做來組合實現出來,
    2. 可是這兩個操做是比較經常使用的,
    3. 並且能夠經過對他們內部的實現來進行優化,
    4. 因此不要使用組合原操做的方式來實現它。

replace

  1. 取出堆中最大元素以後,再放入一個新的元素。
    1. 這個過程至關因而,堆中元素總數是沒有變化的,
    2. 這樣的一個操做其實就是 extractMax 和 add,
    3. 可是這會致使兩次的O(logn)級別的操做。
    4. 因爲整個過程當中堆中的元素個數並無發生改變,
    5. 那麼能夠直接堆頂的元素替換成新的元素,
    6. 替換成新的元素以後,這個新的元素有可能違背了堆的性質,
    7. 那麼直接進行 Sift Down 操做就能夠了,
    8. 那麼就只是一次O(logn)級別的操做。

heapify

  1. 將任意的一個數組整理成堆的形狀
    1. 因爲堆是一棵徹底二叉樹,因此它能夠直接用一個數組來表示,
    2. 因此只要合理的交換數組中元素的位置,也能夠將它整理成堆的形狀,
    3. 你能夠先掃描一下當前的數組,
    4. 將當前數組中全部的元素添加進這個堆所對應的對象中,
    5. 其實也就完成了這個工做,
    6. 可是還有一個更加快速的方式,這個過程就叫作 Heapify,
    7. 首先將當前數組當作一棵徹底二叉樹,
    8. 雖然它目標並不太可能符合堆的性質,
    9. 可是 對於這個徹底二叉樹,
    10. 能夠從最後一個非葉子節點開始進行 Sift Down 這個操做,
    11. 也就是不斷的進行下沉操做就能夠了,
    12. 從後往前不斷的循環進行下沉操做,
    13. 直到全部非葉子節點都符合堆的性質那就 ok 了。
  2. 定位最後一個非葉子節點所處的數組索引
    1. 這是一個很經典的面試題目,在計算機考試中也會有,
    2. 也就是用數組來表示一棵徹底二叉樹,
    3. 那麼它最後一個或者倒數第一個非葉子節點所對應的索引是多少,
    4. 這個問題其實很是簡單,只須要拿到最後一個節點的索引,
    5. 而後使用這個索引計算出它的父親節點,
    6. 若是起始索引是從 0 開始的,
    7. 那就是這個最後一個節點的索引減去一以後再除以二取整便可,
    8. 若是起始索引是從 1 開始的,
    9. 那麼就是這個最後一個節點的索引直接除以二再取整就行了。
  3. heapify 原理
    1. 從倒數第一個非葉子節點向前一直遍歷,
    2. 遍歷出到了第一個非葉子節點(根節點),
    3. 也就是對每個節點都進行了下沉操做,
    4. 而後整棵樹就變成了最大堆,
    5. 這樣一來就將一個普通的數組整理成了一個最大堆的形式,
    6. 從一開始就拋棄了全部的葉子節點,
    7. 那麼幾乎就拋棄了這棵二叉樹中近一半的節點,
    8. 剩下的一半的節點進行了 Sift Down 的操做,
    9. 這比原來直接從一個空堆開始,一個一個添加元素的速度要快一點,
    10. 由於每添加一個元素都會執行一次O(logn)級別的操做。

Heapify 的 算法複雜度

  1. 將 n 個元素逐個插入到一個空堆中,算法複雜度是 O(nlogn)級別。
  2. 若是使用 heapify 的過程,算法複雜度就爲 O(n)級別的。
    1. 一樣把一個數組整理成堆的過程要比以逐個插入到空堆中要快一些。
    2. 其實使用 heapify 與不使用 heapify 是有一個質的提高,
    3. 這個提高是O(n)O(nlogn)的區別。
  3. 上浮操做 Sift Up 與下沉操做 Sift Down
    1. 上浮是當前節點值與其父節點進行對比,若是當前節點大於其父節點就進行位置的交換。
    2. 下沉是當前節點值與其左右兩孩子節點中最大的值的節點進行對比,
    3. 若是當前節點值比左右孩子節點最大值的那個節點值小,
    4. 那麼當前節點就和那個最大值孩子節點交換位置。

代碼示例

  1. (class: Myarray, class: MaxHeap, class: PerformanceTest, class: Main)算法

  2. Myarrayapi

    // 自定義類
    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數組

    // 自定義二叉堆之最大堆 Heap
    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;
    
          let leftChildIndex = this.calcLeftChildIndex(index);
          let leftChildValue = this.myArray.get(leftChildIndex);
          let 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(rightChildValue, leftChildValue) > 0)
          // maxIndex = rightChildIndex;
          // else
          // maxIndex = leftChildIndex;
          // }
    
          // 這段代碼是上面註釋代碼的優化
          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;
          }
       }
    
       // 將堆頂的元素用一個新元素替換出來
       replace(element) {
          let maxElement = this.findMax();
    
          this.myArray.set(0, element);
    
          this.siftDown(0);
    
          return maxElement;
       }
    
       // 將一個數組變成一個最大堆 -
       heapify(array) {
          // 將數組中的元素添加到自定義動態數組裏
          for (const element of array) this.myArray.push(element);
    
          // 減小一個O(n)的操做,否則性能相對來講會差一些
          // this.myArray.data = array;
          // this.myArray.size = array.length;
    
          // 這個動態數組知足了一棵徹底二叉樹的性質
          // 獲取 這棵徹底二叉樹 最後一個非葉子節點的索引
          let index = this.calcParentIndex(this.myArray.getSize() - 1);
    
          // 從最後一個非葉子節點開始遍歷 從後向前遍歷 不停的下沉, 這個就是heapify的過程
          // for (let i = index; i >= 0; i --) { this.siftDown(i);}
          while (0 <= index) this.siftDown(index--);
       }
    
       // 堆中兩個元素的位置進行交換
       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. 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);
       }
    
       // 計算運行的時間,轉換爲 天-小時-分鐘-秒-毫秒
       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;
       }
    }
    複製代碼
  5. Maindom

    // main 函數
    class Main {
       constructor() {
          this.alterLine('MaxHeap Comparison Area');
          const n = 1000000;
    
          const maxHeapIsHeapify = new MyMaxHeap();
          const maxHeapNotHeapify = new MyMaxHeap();
          let performanceTest1 = new PerformanceTest();
    
          const random = Math.random;
          let arr = [];
          let arr1 = [];
    
          // 循環添加隨機數的值
          for (let i = 0; i < n; i++) {
             arr.push(random() * n);
             arr1.push(arr[i]);
          }
    
          this.alterLine('MaxHeap Is Heapify Area');
          const maxHeapIsHeapifyInfo = performanceTest1.testHeap(
             maxHeapIsHeapify,
             arr,
             true
          );
          console.log(maxHeapIsHeapifyInfo);
          this.show(maxHeapIsHeapifyInfo);
    
          this.alterLine('MaxHeap Not Heapify Area');
          const maxHeapNotHeapifyInfo = performanceTest1.testHeap(
             maxHeapNotHeapify,
             arr1,
             false
          );
          console.log(maxHeapNotHeapifyInfo);
          this.show(maxHeapNotHeapifyInfo);
    
          // this.alterLine("MyMaxHeap Replace Area");
          // const n = 20;
    
          // 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.replace(0);
    
          // 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. 優先隊列便可以使用普通的線性數據結構來實現
    1. 動態數組、鏈表。
  2. 優先隊列也可使用一個維護順序的線性數據結構來實現
    1. 動態數組、鏈表。
  3. 隊列的接口是徹底一致的,同時它們的功能也是同樣的
    1. 只不過用的底層的數據結構是不一樣的,
    2. 這樣就會致使一些方法在時間複雜度上產生不一樣的效果,
    3. 在使用普通數組的時候入隊這個操做是O(1)的複雜度,
    4. 可是出隊的那個操做,尋找那個最大值就是O(n)的複雜度,
    5. 若是使用順序的線性結構的時候,那會有點相反,
    6. 入隊會變成O(n)的複雜度,由於要找到待插入的這個元素的正確位置,
    7. 出隊會是O(1)的複雜度。

代碼示例

  1. (class: Myarray, class: MaxHeap, class: MyPriorityQueue)ide

  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

    // 自定義二叉堆之最大堆 Heap
    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;
    
          let leftChildIndex = this.calcLeftChildIndex(index);
          let leftChildValue = this.myArray.get(leftChildIndex);
          let 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(rightChildValue, leftChildValue) > 0)
          // maxIndex = rightChildIndex;
          // else
          // maxIndex = leftChildIndex;
          // }
    
          // 這段代碼是上面註釋代碼的優化
          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;
          }
       }
    
       // 將堆頂的元素用一個新元素替換出來
       replace(element) {
          let maxElement = this.findMax();
    
          this.myArray.set(0, element);
    
          this.siftDown(0);
    
          return maxElement;
       }
    
       // 將一個數組變成一個最大堆 -
       heapify(array) {
          // 將數組中的元素添加到自定義動態數組裏
          for (const element of array) this.myArray.push(element);
    
          // 減小一個O(n)的操做,否則性能相對來講會差一些
          // this.myArray.data = array;
          // this.myArray.size = array.length;
    
          // 這個動態數組知足了一棵徹底二叉樹的性質
          // 獲取 這棵徹底二叉樹 最後一個非葉子節點的索引
          let index = this.calcParentIndex(this.myArray.getSize() - 1);
    
          // 從最後一個非葉子節點開始遍歷 從後向前遍歷 不停的下沉, 這個就是heapify的過程
          // for (let i = index; i >= 0; i --) { this.siftDown(i);}
          while (0 <= index) this.siftDown(index--);
       }
    
       // 堆中兩個元素的位置進行交換
       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. MyPriorityQueue

    // 自定義優先隊列 PriorityQueue
    class MyPriorityQueue {
       constructor() {
          this.maxHeap = new MyMaxHeap();
       }
    
       // 入隊
       enqueue(element) {
          this.maxHeap.add(element);
       }
    
       // 出隊
       dequeue() {
          return this.maxHeap.extractMax();
       }
    
       // 查看隊首元素
       getFront() {
          return this.maxHeap.findMax();
       }
    
       // 查看隊列中實際元素的個數
       getSize() {
          return this.maxHeap.size();
       }
    
       // 返回隊列是否爲空的判斷值
       isEmpty() {
          return this.maxHeap.isEmpty();
       }
    
       // 擴展: 修改最大堆中的比較算法
       updateCompare(compareMethod) {
          // 傳入參數能夠替換掉原堆中實現的compare方法
          this.maxHeap.compare = compareMethod;
       }
    
       // 擴展: 用一個新元素去替換隊首的元素,同時再次確認優先級別
       replaceFront(element) {
          // 這樣就就可 不須要 出隊入隊操做這麼麻煩了
          return this.maxHeap.replace(element);
       }
    }
    複製代碼

Leetcode 上優先隊列相關的問題

優先隊列的經典問題

  1. 1,000,000個元素中選出前 100 名?
    1. 也就是在 N 個元素中選出前 M 個元素,
    2. 若是 M 等於 1,那麼就是在 N 個元素中就選擇第一名的那個元素,
    3. 只須要遍歷一遍就行了,很是的簡單,
    4. 整個算法的時間複雜度是 O(n)級別的,
    5. 可是 M 若是不等於 1 的話,那麼就有一點棘手,
    6. 最樸素的想法就是對這一百萬個元素進行一下排序,
    7. 對於一百萬這個級別的元素來講,使用高級的排序算法,
    8. 不管是歸併排序也好仍是快速排序也好
    9. 均可以在NlogN的時間裏完成任務,
    10. 總體來講這種時間複雜仍是能夠接受的,
    11. 排序完成後直接取出前一百名元素就行了,很是容易,
  2. 可是問題的關鍵在於有沒有更好的方法,
    1. 在這個問題上,若是使用優先隊列的話,
    2. 那麼就能夠在NlogM這個時間複雜度內解決問題,
    3. 若是這個 M 等於 100 的話,logM 大概是 7 左右。
    4. 若是使用高級的排序算法,
    5. NlogN這個時間複雜度內的話,
    6. 那麼 logN 大概是 20。
    7. 這樣一來它們相差了三倍左右,
    8. 因此 NlogM 是比 NlogN 更好的時間複雜度。
  3. 使用優先隊列,維護當前看到的前 M 個元素
    1. 對於這 100 萬個元素,確定是要從頭至尾掃描一遍,
    2. 在掃描的過程當中,首先將這 N 個元素中的前 M 個元素放入優先隊列中,
    3. 以後每次看到一個新的元素,
    4. 若是這個新的元素比當前優先隊列中最小的元素還要大,
    5. 那麼就把優先隊列中最小的那個元素給扔出去,
    6. 取而代之的換上這個新的元素,用這樣的方式,
    7. 至關於這個優先隊列中一直維護者當前能夠看到的前 M 個元素,
    8. 直到把這 n 個元素全都掃描完,在優先隊列中最終留下來的這 M 個元素,
    9. 就是最終要求的結果。
  4. 實際須要的是一個最小堆 MinHeap,
    1. 要可以很是快速的取出當前看到前 M 個元素中最小的那個元素,
    2. 須要不斷的將當前能夠看到的前 M 大的元素中最小的元素進行替換,
    3. 已經實現了最大堆 MaxHeap,你只須要把這個邏輯改一改,
    4. 把它變成一個最小堆 MinHeap 是很是容易的,
    5. 就是將核心邏輯比較的時候符號進行一個改變。
  5. 實際上解決這個問題並不須要真的使用最小堆 MinHeap,
    1. 依然使用最大堆也是能夠的,這裏面最關鍵的怎麼去定義優先級,
    2. 優先級的大小,沒有誰規定最大的元素優先級就是最高的,
    3. 這個問題的解決方案中,每次都是去取出這個優先隊列中最小的元素,
    4. 那麼就徹底能夠本身去定義,例如元素的值越小,它的優先級越高,
    5. 在這樣的一個定義下,依然可使用底層以最大堆實現的優先隊列了,
    6. 大和小實際上是相對的。

在 leetcode 上的問題

  1. 347.前K個高頻元素

    1. https://leetcode-cn.com/problems/top-k-frequent-elements/
    2. 可使用 系統內置的 Map 來統計頻率,
    3. 而後使用 PriorityQueue 來進行頻率的優先級統計,
    4. 因爲本身實現的自定義優先隊列是以一個最大堆爲底層實現,
    5. 那麼入隊的元素的比較操做須要相反,
    6. 要支持的是,頻次越低優先級就越高,
    7. 那麼噹噹前元素的頻次越低,就讓它在堆的最頂端,
    8. 那麼 compareTo 操做時,返回的值爲正數則會進行向上浮動,
    9. 返回的值若是爲負數則會進行下沉。
  2. 代碼示例

    // 答題
    class Solution {
       // leetcode 347. 前K個高頻元素
       topKFrequent(nums, k) {
          /** * @param {number[]} nums * @param {number} k * @return {number[]} * 原版 */
          var topKFrequent = function(nums, k) {
             let map = new Map();
             // 統計 數組中每個元素出現頻率
             for (const num of nums) {
                if (map.has(num)) map.set(num, map.get(num) + 1);
                else map.set(num, 1);
             }
    
             // 優先隊列:使用的時候指定優先級比較的方式
             let queue = new MyPriorityQueue();
             // 變動優先隊列中的定義優先級的方法
             queue.updateCompare((elementA, elementB) => {
                // 原的比較算法是 值越大 優先級越大
                // 如今改成 值越小 優先級越大
                if (elementA.value < elementB.value) return 1;
                else if (elementA.value > elementB.value) return -1;
                else return 0;
             });
    
             for (const key of map.keys()) {
                if (queue.getSize() < k)
                   queue.enqueue({ key: key, value: map.get(key) });
                else if (map.get(key) > queue.getFront().value) {
                   queue.replaceFront({ key: key, value: map.get(key) });
                   // queue.dequeue();
                   // queue.enqueue({"key": key, "value": map.get(key)});
                }
             }
    
             let result = [];
             for (var i = 0; i < k; i++) {
                result.push(queue.dequeue().key);
             }
             return result;
          };
    
          // 精簡版
          var topKFrequent = function(nums, k) {
             let map = new Map();
             // 統計 數組中每個元素出現頻率
             for (const num of nums) {
                if (map.has(num)) map.set(num, map.get(num) + 1);
                else map.set(num, 1);
             }
    
             // 優先隊列:使用的時候指定優先級比較的方式
             let queue = new MyPriorityQueue();
             // 變動優先隊列中的定義優先級的方法
             queue.updateCompare((keyA, keyB) => {
                // 原的比較算法是 值越大 優先級越大
                // 如今改成 值越小 優先級越大
                if (map.get(keyA) < map.get(keyB)) return 1;
                else if (map.get(keyA) > map.get(keyB)) return -1;
                else return 0;
             });
    
             for (const key of map.keys()) {
                if (queue.getSize() < k) queue.enqueue(key);
                else if (map.get(key) > map.get(queue.getFront())) {
                   queue.replaceFront(key);
                }
             }
    
             let result = [];
             for (var i = 0; i < k; i++) {
                result.push(queue.dequeue());
             }
             return result;
          };
    
          return topKFrequent(nums, k);
       }
    }
    
    // main 函數
    class Main {
       constructor() {
          this.alterLine('leetcode 347. 前K個高頻元素');
          let s = new Solution();
    
          let arr = [
             5,
             -3,
             9,
             1,
             7,
             7,
             9,
             10,
             2,
             2,
             10,
             10,
             3,
             -1,
             3,
             7,
             -9,
             -1,
             3,
             3
          ];
          console.log(arr);
          this.show(arr);
    
          let result = s.topKFrequent(arr, 3);
          console.log(result);
          this.show(result);
       }
    
       // 將內容顯示在頁面上
       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. leetcode 中與堆相關的題目
    1. https://leetcode-cn.com/tag/heap/
  2. 本身實現的堆實際上是二叉堆 Binary Heap
    1. 計算機世界中其實還有各類各樣的堆,
    2. 學會了二叉堆以後,最容易拓展的就是 d 叉堆 d-ary heap 了,
    3. 也就是說,對於每個節點來講,它可能有三個四個甚至更多個孩子,
    4. 也排列成徹底 d 叉樹這種形式,用這樣的方式也能夠構建出一個堆來,
    5. 對於這種堆而言,其實它的層數是更加的低了,
    6. 那麼對它的添加操做刪除操做,
    7. 相應的時間複雜度都變成了 log 以 d 爲底 n 這樣的時間複雜度,
    8. 從這個時間複雜度的角度來說,好像比 log 以 2 爲底 n 這樣的時間複雜度要好,
    9. 但是相應的代價會越高,好比每個節點的 SiftDown 下沉操做時,
    10. 須要考慮的節點數變多了,不只僅是考慮兩個節點了,而是要考慮 d 個節點,
    11. 它們之間就存在了一個制衡的關係。
  3. 本身實現的堆有一個很大的缺點
    1. 只能看到堆首的元素,卻不能看到堆中間的元素,
    2. 實際上在不少應用中是須要看到堆中間的元素,
    3. 甚至須要對堆中間的元素進行必定的修改,
    4. 在這種狀況下相應的就要有一個索引堆這樣的數據結構,
    5. 這種堆除了保持你關注的那個元素以外,還對應了一個索引,
    6. 能夠經過這個索引很是方便的檢索到元素存在堆中的哪一個位置,
    7. 甚至能夠根據索引來修改這個元素,
    8. 事實上索引堆仍是應用很是普遍的一種數據結構,
    9. 不過這種數據結構相對是比較高級的,
    10. 在慕課網《算法與數據結構》中第四章 8-9 節有,
    11. 不管是最小生成樹算法仍是最短路徑算法,
    12. 也就是對於 Prim 算法和 Dijkstra 算法均可以使用索引堆進行優化,
    13. 在真實的面試中,幾乎不會問索引堆的問題。
  4. 在計算機的世界中還有各類奇奇怪怪的堆
    1. 二項堆、斐波那契堆,這些堆更是更高級的數據結構。

廣義隊列

  1. Queue
    1. void enqueue(e)
    2. E dequeue()
    3. E getFront()
    4. int getSize()
    5. boolean isEmpty()
  2. 只要支持這樣的接口或者支持入隊或者出隊操做,它就能夠叫作一個隊列。
    1. 這麼定義一個隊列的話,那麼隊列這個概念就太廣義了,
    2. 如普通隊列(先到先得)、優先隊列(優先級最高的先出隊),
    3. 如此一來,棧其實也能夠理解是一個隊列,
    4. 入棧和出棧操做也是向一個數據結構添加元素和拿出元素,
    5. 因此棧也能夠理解成是一個隊列,
    6. 事實上當你這麼理解的時候,對於不少計算機算法,
    7. 你理解的角度將會產生新的變化,最典型的例子如二分搜索樹,
    8. 實現了一個非遞歸的前序遍歷、層序遍歷,
    9. 這兩個算法基本的邏輯是徹底同樣的,
    10. 區別只在於一個使用的棧一個使用的隊列,
    11. 當你把棧也看作隊列的時候,這兩種方式很是完美的統一了,
    12. 對於這兩種遍歷方式來講,它們的區別在於你使用了怎樣的數據結構,
    13. 而不是具體的邏輯,這個具體的邏輯實際上是一致的。
相關文章
相關標籤/搜索