如何用 JS 實現二叉堆

前言

二叉樹(Binary Tree)是一種樹形結構,它的特色是每一個節點最多隻有兩個分支節點,一棵二叉樹一般由根節點、分支節點、葉子節點組成,以下圖所示。每一個分支節點也經常被稱做爲一棵子樹,而二叉堆是一種特殊的樹,它屬於徹底二叉樹。api

二叉樹與二叉堆的關係

在平常工做中會遇到不少數組的操做,好比排序等。那麼理解二叉堆的實現對之後的開發效率會有所提高,下面就簡單介紹一下什麼是二叉樹,什麼是二叉堆。數組

二叉樹特徵

  • 根節點:二叉樹最頂層的節點
  • 分支節點:除了根節點之外且擁有葉子節點
  • 葉子節點:除了自身,沒有其餘子節點

在二叉樹中,咱們經常還會用父節點和子節點來描述,好比上圖中左側節點 2 爲 6 和 3 的父節點,反之 6 和 3 是 2 子節點。函數

二叉樹分類

二叉樹分爲滿二叉樹(full binary tree)和徹底二叉樹(complete binary tree)。ui

  • 滿二叉樹:一棵深度爲 k 且有 2 ^ k - 1個節點的二叉樹稱爲滿二叉樹
  • 徹底二叉樹:徹底二叉樹是指最後一層左邊是滿的,右邊可能滿也可能不滿,而後其他層都是滿的二叉樹稱爲徹底二叉樹(滿二叉樹也是一種徹底二叉樹)

二叉樹結構

從圖中咱們能夠看出二叉樹是從上到下依次排列下來,可想而知能夠用一個數組來表示二叉樹的結構,從下標 index( 0 - 8 ) 從上到下依次排列。this

  • 二叉樹左側節點表達式 index * 2 + 1。例如:以根節點爲例求左側節點,根節點的下標爲0,則左側節點的序數是1 ,對應數組中的值爲1
  • 二叉樹右側節點表達式 index * 2 + 2。例如:以根節點爲例求右側節點,根節點的下標爲0,則右側節點的序數是2 ,對應數組中的值爲 8
  • 二叉樹葉子節點表達式 序數 >= floor( N / 2 )都是葉子節點(N是數組的長度)。例如:floor( 9 / 2 ) = 4 ,則從下標 4 開始的值都爲葉子節點

二叉堆特徵

二叉堆是一個徹底二叉樹,父節點與子節點要保持固定的序關係,而且每一個節點的左子樹和右子樹都是一個二叉堆。spa

從上圖能夠看出blog

  • 圖一:每一個父節點大於子節點或等於子節點,知足二叉堆的性質
  • 圖二:其中有一個父節點小於子節點則不知足二叉堆性質

二叉堆分類

二叉堆根據排序不一樣,能夠分爲最大堆和最小堆排序

  • 最大堆:根節點的鍵值是全部堆節點鍵值中最大者,且每一個父節點的值都比子節點的值大
  • 最小堆:根節點的鍵值是全部堆節點鍵值中最小者,且每一個父節點的值都比子節點的值小

如何實現二叉堆

經過上面的講述想必你們對二叉堆有了必定的理解,那麼接下來就是如何實現。以最大堆爲例,首先要初始化數組而後經過交換位置造成最大堆。遞歸

初始化二叉堆

從上面描述,咱們能夠知道二叉堆其實就是一個數組,那麼初始化就很是簡單了。隊列

  • 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;

http://www.ssnd.com.cn 化妝品OEM代加工

示例

相信經過上面的講述你們對最大堆的實現已經有了必定的理解,咱們能夠利用這個來進行排序。

  • 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]

 

總結

文章中主要講述了二叉樹、二叉堆的概念,而後經過代碼實現二叉堆。咱們能夠經過二叉堆來作排序和優先級隊列等。

相關文章
相關標籤/搜索