這是第 90 篇不摻水的原創,想獲取更多原創好文,請搜索公衆號關注咱們吧~ 本文首發於政採雲前端博客: 如何用 JS 實現二叉堆
二叉樹(Binary Tree)是一種樹形結構,它的特色是每一個節點最多隻有兩個分支節點,一棵二叉樹一般由根節點、分支節點、葉子節點組成,以下圖所示。每一個分支節點也經常被稱做爲一棵子樹,而二叉堆是一種特殊的樹,它屬於徹底二叉樹。javascript
在平常工做中會遇到不少數組的操做,好比排序等。那麼理解二叉堆的實現對之後的開發效率會有所提高,下面就簡單介紹一下什麼是二叉樹,什麼是二叉堆。前端
在二叉樹中,咱們經常還會用父節點和子節點來描述,好比上圖中左側節點 2 爲 6 和 3 的父節點,反之 6 和 3 是 2 子節點。java
二叉樹分爲滿二叉樹(full binary tree)和徹底二叉樹(complete binary tree)。api
從圖中咱們能夠看出二叉樹是從上到下依次排列下來,可想而知能夠用一個數組來表示二叉樹的結構,從下標 index( 0 - 8 ) 從上到下依次排列。數組
二叉堆是一個徹底二叉樹,父節點與子節點要保持固定的序關係,而且每一個節點的左子樹和右子樹都是一個二叉堆。函數
從上圖能夠看出post
二叉堆根據排序不一樣,能夠分爲最大堆和最小堆性能
經過上面的講述想必你們對二叉堆有了必定的理解,那麼接下來就是如何實現。以最大堆爲例,首先要初始化數組而後經過交換位置造成最大堆。ui
從上面描述,咱們能夠知道二叉堆其實就是一個數組,那麼初始化就很是簡單了。this
class Heap{ constructor(arr){ this.data = [...arr]; this.size = this.data.length; } }
圖一中 2 做爲父節點小於子節點,很顯然不符合最大堆性質。maxHeapify 函數能夠把每一個不符合最大堆性質的節點調換位置,從而知足最大堆性質的數組。
調整步驟:
1.調整分支節點 2 的位置(不知足最大堆性質)
2.獲取父節點 2 的左右節點 ( 12 , 5 ) ,從 ( 2 , 15 , 5 ) 中進行比較
3.找出最大的節點與父節點進行交換,若是該節點自己爲最大節點則中止操做
4.重複 step2 的操做,從 2 , 4 , 7 中找出最大值與 2 作交換(遞歸)
maxHeapify(i) { let max = i; if(i >= this.size){ return; } // 當前序號的左節點 const l = i * 2 + 1; // 當前須要的右節點 const r = i * 2 + 2; // 求當前節點與其左右節點三者中的最大值 if(l < this.size && this.data[l] > this.data[max]){ max = l; } if(r < this.size && this.data[r] > this.data[max]){ max = r; } // 最終max節點是其自己,則已經知足最大堆性質,中止操做 if(max === i) { return; } // 父節點與最大值節點作交換 const t = this.data[i]; this.data[i] = this.data[max]; this.data[max] = t; // 遞歸向下繼續執行 return this.maxHeapify(max); }
咱們能夠看到,初始化是由一個數組組成,如下圖爲例很顯然並不會知足最大堆的性質,上述 maxHeapify 函數只是對某一個節點做出對調,沒法對整個數組進行重構,因此咱們要依次對數組進行遞歸重構。
1.找到全部分支節點 Math.floor( N / 2 )(不包括葉子節點)
2.將找到的子節點進行 maxHeapify 操做
rebuildHeap(){ // 葉子節點 const L = Math.floor(this.size / 2); for(let i = L - 1; i >= 0; i--){ this.maxHeapify(i); } }
1.swap 函數交換首位位置
2.將最後一個從堆中拿出至關於 size - 1
3.執行 maxHeapify 函數進行根節點比較找出最大值進行交換
4.最終 data 會變成一個升序的數組
sort() { for(let i = this.size - 1; i > 0; i--){ swap(this.data, 0, i); this.size--; this.maxHeapify(0); } }
Insert 函數做爲插入節點函數,首先
1.往 data 結尾插入節點
2.由於節點追加,size + 1
3.由於一個父節點擁有 2 個子節點,咱們能夠根據這個性質經過 isHeap 函數獲取第一個葉子節點,能夠經過第一個葉子節點獲取新插入的節點,而後進行 3 個值的對比,找出最大值,判斷插入的節點。若是跟父節點相同則不進行重構(相等知足二叉堆性質),不然進行 rebuildHeap 重構堆
isHeap() { const L = Math.floor(this.size / 2); for (let i = L - 1; i >= 0; i--) { const l = this.data[left(i)] || Number.MIN_SAFE_INTEGER; const r = this.data[right(i)] || Number.MIN_SAFE_INTEGER; const max = Math.max(this.data[i], l, r); if (max !== this.data[i]) { return false; } return true; } } insert(key) { this.data[this.size] = key; this.size++ if (this.isHeap()) { return; } this.rebuildHeap(); }
delete 函數做爲刪除節點,首先
1.刪除傳入index的節點
2.由於節點刪除,size - 1
3.重複上面插入節點的操做
delete(index) { if (index >= this.size) { return; } this.data.splice(index, 1); this.size--; if (this.isHeap()) { return; } this.rebuildHeap(); }
/** * 最大堆 */ function left(i) { return (i * 2) + 1; } function right(i) { return (i * 2) + 2; } function swap(A, i, j) { const t = A[i]; A[i] = A[j]; A[j] = t; } class Heap { constructor(arr) { this.data = [...arr]; this.size = this.data.length; this.rebuildHeap = this.rebuildHeap.bind(this); this.isHeap = this.isHeap.bind(this); this.sort = this.sort.bind(this); this.insert = this.insert.bind(this); this.delete = this.delete.bind(this); this.maxHeapify = this.maxHeapify.bind(this); } /** * 重構堆,造成最大堆 */ rebuildHeap() { const L = Math.floor(this.size / 2); for (let i = L - 1; i >= 0; i--) { this.maxHeapify(i); } } isHeap() { const L = Math.floor(this.size / 2); for (let i = L - 1; i >= 0; i--) { const l = this.data[left(i)] || Number.MIN_SAFE_INTEGER; const r = this.data[right(i)] || Number.MIN_SAFE_INTEGER; const max = Math.max(this.data[i], l, r); if (max !== this.data[i]) { return false; } return true; } } sort() { for (let i = this.size - 1; i > 0; i--) { swap(this.data, 0, i); this.size--; this.maxHeapify(0); } } insert(key) { this.data[this.size++] = key; if (this.isHeap()) { return; } this.rebuildHeap(); } delete(index) { if (index >= this.size) { return; } this.data.splice(index, 1); this.size--; if (this.isHeap()) { return; } this.rebuildHeap(); } /** * 交換父子節點位置,符合最大堆特徵 * @param {*} i */ maxHeapify(i) { let max = i; if (i >= this.size) { return; } // 求左右節點中較大的序號 const l = left(i); const r = right(i); if (l < this.size && this.data[l] > this.data[max]) { max = l; } if (r < this.size && this.data[r] > this.data[max]) { max = r; } // 若是當前節點最大,已是最大堆 if (max === i) { return; } swap(this.data, i, max); // 遞歸向下繼續執行 return this.maxHeapify(max); } } module.exports = Heap;
相信經過上面的講述你們對最大堆的實現已經有了必定的理解,咱們能夠利用這個來進行排序。
const arr = [15, 12, 8, 2, 5, 2, 3, 4, 7]; const fun = new Heap(arr); fun.rebuildHeap(); // 造成最大堆的結構 fun.sort();// 經過排序,生成一個升序的數組 console.log(fun.data) // [2, 2, 3, 4, 5, 7, 8, 12, 15]
文章中主要講述了二叉樹、二叉堆的概念,而後經過代碼實現二叉堆。咱們能夠經過二叉堆來作排序和優先級隊列等。
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com