溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript中二叉樹/二叉堆是什么

發(fā)布時間:2020-07-09 09:55:12 來源:億速云 閱讀:217 作者:Leah 欄目:web開發(fā)

JavaScript中二叉樹/二叉堆是什么?針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

二叉樹

二叉樹(Binary Tree)是一種樹形結(jié)構(gòu),它的特點是每個節(jié)點最多只有兩個分支節(jié)點,一棵二叉樹通常由根節(jié)點,分支節(jié)點,葉子節(jié)點組成。而每個分支節(jié)點也常常被稱作為一棵子樹。

JavaScript中二叉樹/二叉堆是什么

  • 根節(jié)點:二叉樹最頂層的節(jié)點

  • 分支節(jié)點:除了根節(jié)點以外且擁有葉子節(jié)點

  • 葉子節(jié)點:除了自身,沒有其他子節(jié)點

常用術(shù)語

在二叉樹中,我們常常還會用父節(jié)點和子節(jié)點來描述,比如圖中2為6和3的父節(jié)點,反之6和3是2子節(jié)點

二叉樹的三個性質(zhì)

  1. 在二叉樹的第i層上,至多有2^i-1個節(jié)點

    • i=1時,只有一個根節(jié)點,2^(i-1) = 2^0 = 1

  2. 深度為k的二叉樹至多有2^k-1個節(jié)點

    • i=2時,2^k-1 = 2^2 - 1 = 3個節(jié)點

  3. 對任何一棵二叉樹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é)點的二叉樹稱為滿二叉樹

  • 完全二叉樹:完全二叉樹是指最后一層左邊是滿的,右邊可能滿也可能不滿,然后其余層都是滿的二叉樹稱為完全二叉樹(滿二叉樹也是一種完全二叉樹)

JavaScript中二叉樹/二叉堆是什么

二叉樹的數(shù)組表示

用一個數(shù)組來表示二叉樹的結(jié)構(gòu),將一組數(shù)組從根節(jié)點開始從上到下,從左到右依次填入到一棵完全二叉樹中,如下圖所示

JavaScript中二叉樹/二叉堆是什么

通過上圖我們可以分析得到數(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é)點的鍵值時,稱為最大堆(最小堆)

    JavaScript中二叉樹/二叉堆是什么

從上圖可以看出:

  • 左圖:父節(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)整的一個操作。

JavaScript中二叉樹/二叉堆是什么

如上圖:

  1. 調(diào)整分支節(jié)點2(分支節(jié)點2不滿足最大堆的性質(zhì))

    • 默認(rèn)該分支節(jié)點為最大值

  2. 將2與左右分支比較,從2,12,5中找出最大值,然后和2交換位置

    • 根據(jù)上面所將的二叉堆性質(zhì),分別得到分支節(jié)點2的左節(jié)點和右節(jié)點

    • 比較三個節(jié)點,得到最大值的下標(biāo)max

    • 如果該節(jié)點本身就是最大值,則停止操作

    • 將max節(jié)點與父節(jié)點進(jìn)行交換

  3. 重復(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操作

JavaScript中二叉樹/二叉堆是什么

具體步驟:

  • 找到所有分支節(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);
        }
    }

最大堆排序

JavaScript中二叉樹/二叉堆是什么

最大堆的排序,如上圖所示:

  • 交換首尾位置

  • 將最后個元素從堆中拿出,相當(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ì)。

關(guān)于JavaScript中二叉樹/二叉堆是什么問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI