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这个操做,以及堆的三个性质。

后续

下一篇应该会介绍二叉搜索树。欢迎你们指出文章的错误,若是有什么写做建议也能够提出。我会持续的去写关于前端的一些技术文章,若是你们喜欢的话能够关注一和点个赞,你的赞是我写做的动力。
顺便再提一下,我在等第一个粉丝哈哈

如下我的公众号,欢迎你们关注,用户量达到必定的量,我会推出一些前端教学视频
图片描述

相关文章
相关标签/搜索