摘要:二叉樹二叉樹是一種樹形結(jié)構(gòu),它的特點是每個節(jié)點最多只有兩個分支節(jié)點,一棵二叉樹通常由根節(jié)點,分支節(jié)點,葉子節(jié)點組成。
二叉樹
二叉樹(Binary Tree)是一種樹形結(jié)構(gòu),它的特點是每個節(jié)點最多只有兩個分支節(jié)點,一棵二叉樹通常由根節(jié)點,分支節(jié)點,葉子節(jié)點組成。而每個分支節(jié)點也常常被稱作為一棵子樹。
根節(jié)點:二叉樹最頂層的節(jié)點
分支節(jié)點:除了根節(jié)點以外且擁有葉子節(jié)點
葉子節(jié)點:除了自身,沒有其他子節(jié)點
常用術(shù)語
在二叉樹中,我們常常還會用父節(jié)點和子節(jié)點來描述,比如圖中2為6和3的父節(jié)點,反之6和3是2子節(jié)點
在二叉樹的第i層上,至多有2^i-1個節(jié)點
i=1時,只有一個根節(jié)點,2^(i-1) = 2^0 = 1
深度為k的二叉樹至多有2^k-1個節(jié)點
i=2時,2^k-1 = 2^2 - 1 = 3個節(jié)點
對任何一棵二叉樹T,如果總結(jié)點數(shù)為n0,度為2(子樹數(shù)目為2)的節(jié)點數(shù)為n2,則n0=n2+1
樹和二叉樹的三個主要差別樹的節(jié)點個數(shù)至少為1,而二叉樹的節(jié)點個數(shù)可以為0
樹中節(jié)點的最大度數(shù)(節(jié)點數(shù)量)沒有限制,而二叉樹的節(jié)點的最大度數(shù)為2
樹的節(jié)點沒有左右之分,而二叉樹的節(jié)點有左右之分
二叉樹分類二叉樹分為完全二叉樹(complete binary tree)和滿二叉樹(full binary tree)
滿二叉樹:一棵深度為k且有2^k - 1個節(jié)點的二叉樹稱為滿二叉樹
完全二叉樹:完全二叉樹是指最后一層左邊是滿的,右邊可能滿也可能不滿,然后其余層都是滿的二叉樹稱為完全二叉樹(滿二叉樹也是一種完全二叉樹)
二叉樹的數(shù)組表示用一個數(shù)組來表示二叉樹的結(jié)構(gòu),將一組數(shù)組從根節(jié)點開始從上到下,從左到右依次填入到一棵完全二叉樹中,如下圖所示
通過上圖我們可以分析得到數(shù)組表示的完全二叉樹擁有以下幾個性質(zhì):
left = index * 2 + 1,例如:根節(jié)點的下標(biāo)為0,則左節(jié)點的值為下標(biāo)array[0*2+1]=1
right = index * 2 + 2,例如:根節(jié)點的下標(biāo)為0,則右節(jié)點的值為下標(biāo)array[0*2+2]=2
序數(shù) >= floor(N/2)都是葉子節(jié)點,例如:floor(9/2) = 4,則從下標(biāo)4開始的值都為葉子節(jié)點
二叉堆二叉堆由一棵完全二叉樹來表示其結(jié)構(gòu),用一個數(shù)組來表示,但一個二叉堆需要滿足如下性質(zhì):
二叉堆的父節(jié)點的鍵值總是大于或等于(小于或等于)任何一個子節(jié)點的鍵值
當(dāng)父節(jié)點的鍵值大于或等于(小于或等于)它的每一個子節(jié)點的鍵值時,稱為最大堆(最小堆)
從上圖可以看出:
左圖:父節(jié)點總是大于或等于其子節(jié)點,所以滿足了二叉堆的性質(zhì),
右圖:分支節(jié)點7作為2和12的父節(jié)點并沒有滿足其性質(zhì)(大于或等于子節(jié)點)。
二叉堆的主要操作insert:插入節(jié)點
delete:刪除節(jié)點
max-hepify:調(diào)整分支節(jié)點堆性質(zhì)
rebuildHeap:重新構(gòu)建整個二叉堆
sort:排序
初始化一個二叉堆從上面簡單的介紹,我們可以知道,一個二叉堆的初始化非常的簡單,它就是一個數(shù)組
初始化一個數(shù)組結(jié)構(gòu)
保存數(shù)組長度
class Heap{ constructor(arr){ this.data = [...arr]; this.size = this.data.length; } }max-heapify最大堆操作
max-heapify是把每一個不滿足最大堆性質(zhì)的分支節(jié)點進(jìn)行調(diào)整的一個操作。
如上圖:
調(diào)整分支節(jié)點2(分支節(jié)點2不滿足最大堆的性質(zhì))
默認(rèn)該分支節(jié)點為最大值
將2與左右分支比較,從2,12,5中找出最大值,然后和2交換位置
根據(jù)上面所將的二叉堆性質(zhì),分別得到分支節(jié)點2的左節(jié)點和右節(jié)點
比較三個節(jié)點,得到最大值的下標(biāo)max
如果該節(jié)點本身就是最大值,則停止操作
將max節(jié)點與父節(jié)點進(jìn)行交換
重復(fù)step2的操作,從2,4,7中找出最大值與2做交換
遞歸
maxHeapify(i) { let max = i; if(i >= this.size){ return; } // 當(dāng)前序號的左節(jié)點 const l = i * 2 + 1; // 當(dāng)前需要的右節(jié)點 const r = i * 2 + 2; // 求當(dāng)前節(jié)點與其左右節(jié)點三者中的最大值 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節(jié)點是其本身,則已經(jīng)滿足最大堆性質(zhì),停止操作 if(max === i) { return; } // 父節(jié)點與最大值節(jié)點做交換 const t = this.data[i]; this.data[i] = this.data[max]; this.data[max] = t; // 遞歸向下繼續(xù)執(zhí)行 return this.maxHeapify(max); }重構(gòu)堆
我們可以看到,剛初始化的堆由數(shù)組表示,這個時候它可能并不滿足一個最大堆或最小堆的性質(zhì),這個時候我們可能需要去將整個堆構(gòu)建成我們想要的。
上面我們做了max-heapify操作,而max-heapify只是將某一個分支節(jié)點進(jìn)行調(diào)整,而要將整個堆構(gòu)建成最大堆,則需要將所有的分支節(jié)點都進(jìn)行一次max-heapify操作,如下圖,我們需要依次對12,3,2,15這4個分支節(jié)點進(jìn)行max-hepify操作
具體步驟:
找到所有分支節(jié)點:上面堆的性質(zhì)提到過葉子節(jié)點的序號>=Math.floor(n/2),因此小于Math.floor(n/2)序號的都是我們需要調(diào)整的節(jié)點。
例如途中所示數(shù)組為[15,2,3,12,5,2,8,4,7] => Math.floor(9/2)=4 => index小于4的分別是15,2,3,12(需要調(diào)整的節(jié)點),而5,2,8,4,7為葉子節(jié)點。
將找到的節(jié)點都進(jìn)行maxHeapify操作
rebuildHeap(){ // 葉子節(jié)點 const L = Math.floor(this.size / 2); for(let i = L - 1; i>=0; i--){ this,maxHeapify(i); } }最大堆排序
最大堆的排序,如上圖所示:
交換首尾位置
將最后個元素從堆中拿出,相當(dāng)于堆的size-1
然后在堆根節(jié)點進(jìn)行一次max-heapify操作
重復(fù)以上三個步驟,知道size=0 (這個邊界條件我們在max-heapify函數(shù)里已經(jīng)做了)
sort() { for(let i = this.size - 1; i > 0; i--){ swap(this.data, 0, i); this.size--; this.maxHeapify(0); } }插入和刪除
這個的插入和刪除就相對比較簡單了,就是對一個數(shù)組進(jìn)行插入和刪除的操作
往末尾插入
堆長度+1
判斷插入后是否還是一個最大堆
不是則進(jìn)行重構(gòu)堆
insert(key) { this.data[this.size] = key; this.size++ if (this.isHeap()) { return; } this.rebuildHeap(); }
刪除數(shù)組中的某個元素
堆長度-1
判斷是否是一個堆
不是則重構(gòu)堆
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; } /** * 重構(gòu)堆 */ 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(); } /** * 堆的其他地方都滿足性質(zhì) * 唯獨跟節(jié)點,重構(gòu)堆性質(zhì) * @param {*} i */ maxHeapify(i) { let max = i; if (i >= this.size) { return; } // 求左右節(jié)點中較大的序號 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; } // 如果當(dāng)前節(jié)點最大,已經(jīng)是最大堆 if (max === i) { return; } swap(this.data, i, max); // 遞歸向下繼續(xù)執(zhí)行 return this.maxHeapify(max); } } module.exports = Heap;總結(jié)
堆講到這里就結(jié)束了,堆在二叉樹里相對會比較簡單,常常被用來做排序和優(yōu)先級隊列等。堆中比較核心的還是max-heapify這個操作,以及堆的三個性質(zhì)。
后續(xù)下一篇應(yīng)該會介紹二叉搜索樹。歡迎大家指出文章的錯誤,如果有什么寫作建議也可以提出。我會持續(xù)的去寫關(guān)于前端的一些技術(shù)文章,如果大家喜歡的話可以關(guān)注一和點個贊,你的贊是我寫作的動力。
順便再提一下,我在等第一個粉絲哈哈
以下個人公眾號,歡迎大家關(guān)注,用戶量達(dá)到一定的量,我會推出一些前端教學(xué)視頻
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/100683.html
摘要:二叉堆的有趣之處在于,其邏輯結(jié)構(gòu)上像二叉樹,卻是用非嵌套的列表來實現(xiàn)。二叉堆結(jié)構(gòu)性質(zhì)為了更好地實現(xiàn)堆,我們采用二叉樹。圖完全二叉樹有意思的是我們用單個列表就能實現(xiàn)完全樹。下列所示的代碼是完全二叉堆的實現(xiàn)。 優(yōu)先隊列的二叉堆實現(xiàn) 在前面的章節(jié)里我們學(xué)習(xí)了先進(jìn)先出(FIFO)的數(shù)據(jù)結(jié)構(gòu):隊列(Queue)。隊列有一種變體叫做優(yōu)先隊列(Priority Queue)。優(yōu)先隊列的出隊(Dequ...
摘要:構(gòu)建二叉樹構(gòu)建二叉樹,就是把一個無序的完全二叉樹調(diào)整為二叉堆,本質(zhì)上就是讓所有非葉子節(jié)點一次下沉上浮構(gòu)建最大堆節(jié)點大的上浮,小的下沉構(gòu)建最小堆節(jié)點小的上浮,大的下沉文章什么是二叉堆 什么是二叉堆 二叉堆的本質(zhì)是一種完全二叉樹,它分為兩種類型:最大堆和最小堆 最大堆任何一個父節(jié)點的值,都大于等于它左右孩子的值,最小堆正好與之相反 showImg(https://segmentfault....
摘要:模型優(yōu)先隊列是允許至少下列兩種操作的數(shù)據(jù)結(jié)構(gòu)以及找出返回并刪除優(yōu)先隊列中最小的元素。左式堆也是二叉樹,左式堆和二叉堆的唯一區(qū)別是左式堆不是理想平衡,而實際上趨向于非常不平衡。事實上,沿左式堆的右路徑是該堆中的最短路徑。 6.1 模型 優(yōu)先隊列是允許至少下列兩種操作的數(shù)據(jù)結(jié)構(gòu):insert 以及 deleteMin(找出、返回并刪除優(yōu)先隊列中最小的元素)。 insert 操作等價于 en...
摘要:二叉堆數(shù)據(jù)結(jié)構(gòu)是一種特殊的二叉樹,他能高效快速的找出最大值和最小值,常應(yīng)用于優(yōu)先隊列和著名的堆排序算法中。 二叉堆數(shù)據(jù)結(jié)構(gòu)是一種特殊的二叉樹,他能高效、快速的找出最大值和最小值,常應(yīng)用于優(yōu)先隊列和著名的堆排序算法中。 二叉堆 二叉堆有以下兩個特性: 是一顆完全二叉樹,表示數(shù)的每一層都有左側(cè)和右側(cè)子節(jié)點(除最后一層的葉節(jié)點),并且最后一層的葉節(jié)點盡可能是左側(cè)子節(jié)點 二叉堆不是最小堆就是...
閱讀 1991·2021-09-26 10:19
閱讀 3266·2021-09-24 10:25
閱讀 1654·2019-12-27 11:39
閱讀 1937·2019-08-30 15:43
閱讀 683·2019-08-29 16:08
閱讀 3515·2019-08-29 16:07
閱讀 915·2019-08-26 11:30
閱讀 1279·2019-08-26 10:41