js數據結構-二叉樹(二叉堆)

二叉樹

二叉樹(Binary Tree)是一種樹形結構,它的特色是每一個節點最多隻有兩個分支節點,一棵二叉樹一般由根節點,分支節點,葉子節點組成。而每一個分支節點也經常被稱做爲一棵子樹。前端

圖片描述

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

經常使用術語
在二叉樹中,咱們經常還會用父節點和子節點來描述,好比圖中2爲6和3的父節點,反之6和3是2子節點api

二叉樹的三個性質

  1. 在二叉樹的第i層上,至多有2^i-1個節點數組

    • i=1時,只有一個根節點,2^(i-1) = 2^0 = 1
  2. 深度爲k的二叉樹至多有2^k-1個節點函數

    • i=2時,2^k-1 = 2^2 - 1 = 3個節點
  3. 對任何一棵二叉樹T,若是總結點數爲n0,度爲2(子樹數目爲2)的節點數爲n2,則n0=n2+1

樹和二叉樹的三個主要差異

  • 樹的節點個數至少爲1,而二叉樹的節點個數能夠爲0
  • 樹中節點的最大度數(節點數量)沒有限制,而二叉樹的節點的最大度數爲2
  • 樹的節點沒有左右之分,而二叉樹的節點有左右之分

二叉樹分類

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

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

圖片描述

二叉樹的數組表示

用一個數組來表示二叉樹的結構,將一組數組從根節點開始從上到下,從左到右依次填入到一棵徹底二叉樹中,以下圖所示this

圖片描述

經過上圖咱們能夠分析獲得數組表示的徹底二叉樹擁有如下幾個性質:spa

  • 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開始的值都爲葉子節點

二叉堆

二叉堆由一棵徹底二叉樹來表示其結構,用一個數組來表示,但一個二叉堆須要知足以下性質:code

  • 二叉堆的父節點的鍵值老是大於或等於(小於或等於)任何一個子節點的鍵值
  • 當父節點的鍵值大於或等於(小於或等於)它的每個子節點的鍵值時,稱爲最大堆(最小堆)

圖片描述
從上圖能夠看出:視頻

  • 左圖:父節點老是大於或等於其子節點,因此知足了二叉堆的性質,
  • 右圖:分支節點7做爲2和12的父節點並無知足其性質(大於或等於子節點)。

二叉堆的主要操做

  • insert:插入節點
  • delete:刪除節點
  • max-hepify:調整分支節點堆性質
  • rebuildHeap:從新構建整個二叉堆
  • sort:排序

初始化一個二叉堆

從上面簡單的介紹,咱們能夠知道,一個二叉堆的初始化很是的簡單,它就是一個數組blog

  • 初始化一個數組結構
  • 保存數組長度
class Heap{
        constructor(arr){
            this.data = [...arr];
            this.size = this.data.length;
        }
    }

max-heapify最大堆操做

max-heapify是把每個不知足最大堆性質的分支節點進行調整的一個操做。

圖片描述

如上圖:

  1. 調整分支節點2(分支節點2不知足最大堆的性質)

    • 默認該分支節點爲最大值
  2. 將2與左右分支比較,從2,12,5中找出最大值,而後和2交換位置

    • 根據上面所將的二叉堆性質,分別獲得分支節點2的左節點和右節點
    • 比較三個節點,獲得最大值的下標max
    • 若是該節點自己就是最大值,則中止操做
    • 將max節點與父節點進行交換
  3. 重複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這個操做,以及堆的三個性質。

後續

下一篇應該會介紹二叉搜索樹。歡迎你們指出文章的錯誤,若是有什麼寫做建議也能夠提出。我會持續的去寫關於前端的一些技術文章,若是你們喜歡的話能夠關注一和點個贊,你的贊是我寫做的動力。
順便再提一下,我在等第一個粉絲哈哈

如下我的公衆號,歡迎你們關注,用戶量達到必定的量,我會推出一些前端教學視頻
圖片描述

相關文章
相關標籤/搜索