您好,登錄后才能下訂單哦!
JavaScript中二叉樹/二叉堆是什么?針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
二叉樹
二叉樹(Binary Tree)是一種樹形結構,它的特點是每個節點最多只有兩個分支節點,一棵二叉樹通常由根節點,分支節點,葉子節點組成。而每個分支節點也常常被稱作為一棵子樹。
根節點:二叉樹最頂層的節點
分支節點:除了根節點以外且擁有葉子節點
葉子節點:除了自身,沒有其他子節點
常用術語
在二叉樹中,我們常常還會用父節點和子節點來描述,比如圖中2為6和3的父節點,反之6和3是2子節點
二叉樹的三個性質
在二叉樹的第i層上,至多有2^i-1個節點
i=1時,只有一個根節點,2^(i-1) = 2^0 = 1
深度為k的二叉樹至多有2^k-1個節點
i=2時,2^k-1 = 2^2 - 1 = 3個節點
對任何一棵二叉樹T,如果總結點數為n0,度為2(子樹數目為2)的節點數為n2,則n0=n2+1
樹的節點個數至少為1,而二叉樹的節點個數可以為0
樹中節點的最大度數(節點數量)沒有限制,而二叉樹的節點的最大度數為2
樹的節點沒有左右之分,而二叉樹的節點有左右之分
二叉樹分為完全二叉樹(complete binary tree)和滿二叉樹(full binary tree)
滿二叉樹:一棵深度為k且有2^k - 1個節點的二叉樹稱為滿二叉樹
完全二叉樹:完全二叉樹是指最后一層左邊是滿的,右邊可能滿也可能不滿,然后其余層都是滿的二叉樹稱為完全二叉樹(滿二叉樹也是一種完全二叉樹)
用一個數組來表示二叉樹的結構,將一組數組從根節點開始從上到下,從左到右依次填入到一棵完全二叉樹中,如下圖所示
通過上圖我們可以分析得到數組表示的完全二叉樹擁有以下幾個性質:
left = index * 2 + 1,例如:根節點的下標為0,則左節點的值為下標array[0*2+1]=1
right = index * 2 + 2,例如:根節點的下標為0,則右節點的值為下標array[0*2+2]=2
序數 >= floor(N/2)都是葉子節點,例如:floor(9/2) = 4,則從下標4開始的值都為葉子節點
二叉堆由一棵完全二叉樹來表示其結構,用一個數組來表示,但一個二叉堆需要滿足如下性質:
二叉堆的父節點的鍵值總是大于或等于(小于或等于)任何一個子節點的鍵值
當父節點的鍵值大于或等于(小于或等于)它的每一個子節點的鍵值時,稱為最大堆(最小堆)
從上圖可以看出:
左圖:父節點總是大于或等于其子節點,所以滿足了二叉堆的性質,
右圖:分支節點7作為2和12的父節點并沒有滿足其性質(大于或等于子節點)。
insert:插入節點
delete:刪除節點
max-hepify:調整分支節點堆性質
rebuildHeap:重新構建整個二叉堆
sort:排序
從上面簡單的介紹,我們可以知道,一個二叉堆的初始化非常的簡單,它就是一個數組
初始化一個數組結構
保存數組長度
class Heap{ constructor(arr){ this.data = [...arr]; this.size = this.data.length; } }
max-heapify是把每一個不滿足最大堆性質的分支節點進行調整的一個操作。
如上圖:
調整分支節點2(分支節點2不滿足最大堆的性質)
默認該分支節點為最大值
將2與左右分支比較,從2,12,5中找出最大值,然后和2交換位置
根據上面所將的二叉堆性質,分別得到分支節點2的左節點和右節點
比較三個節點,得到最大值的下標max
如果該節點本身就是最大值,則停止操作
將max節點與父節點進行交換
重復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); }
我們可以看到,剛初始化的堆由數組表示,這個時候它可能并不滿足一個最大堆或最小堆的性質,這個時候我們可能需要去將整個堆構建成我們想要的。
上面我們做了max-heapify操作,而max-heapify只是將某一個分支節點進行調整,而要將整個堆構建成最大堆,則需要將所有的分支節點都進行一次max-heapify操作,如下圖,我們需要依次對12,3,2,15這4個分支節點進行max-hepify操作
具體步驟:
找到所有分支節點:上面堆的性質提到過葉子節點的序號>=Math.floor(n/2),因此小于Math.floor(n/2)序號的都是我們需要調整的節點。
例如途中所示數組為[15,2,3,12,5,2,8,4,7] => Math.floor(9/2)=4 => index小于4的分別是15,2,3,12(需要調整的節點),而5,2,8,4,7為葉子節點。
將找到的節點都進行maxHeapify操作
rebuildHeap(){ // 葉子節點 const L = Math.floor(this.size / 2); for(let i = L - 1; i>=0; i--){ this,maxHeapify(i); } }
最大堆的排序,如上圖所示:
交換首尾位置
將最后個元素從堆中拿出,相當于堆的size-1
然后在堆根節點進行一次max-heapify操作
重復以上三個步驟,知道size=0 (這個邊界條件我們在max-heapify函數里已經做了)
sort() { for(let i = this.size - 1; i > 0; i--){ swap(this.data, 0, i); this.size--; this.maxHeapify(0); } }
這個的插入和刪除就相對比較簡單了,就是對一個數組進行插入和刪除的操作
往末尾插入
堆長度+1
判斷插入后是否還是一個最大堆
不是則進行重構堆
insert(key) { this.data[this.size] = key; this.size++ if (this.isHeap()) { return; } this.rebuildHeap(); }
刪除數組中的某個元素
堆長度-1
判斷是否是一個堆
不是則重構堆
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; } /** * 重構堆 */ 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;
堆講到這里就結束了,堆在二叉樹里相對會比較簡單,常常被用來做排序和優先級隊列等。堆中比較核心的還是max-heapify這個操作,以及堆的三個性質。
關于JavaScript中二叉樹/二叉堆是什么問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。