二叉树
二叉树(Binary Tree)是一种树形构造,它的特点是每一个节点最多只要两个分支节点,一棵二叉树一般由根节点,分支节点,恭弘=叶 恭弘子节点构成。而每一个分支节点也经常被称作为一棵子树。
- 根节点:二叉树最顶层的节点
- 分支节点:除了根节点之外且具有恭弘=叶 恭弘子节点
- 恭弘=叶 恭弘子节点:除了自身,没有其他子节点
经常使用术语
在二叉树中,我们经常还会用父节点和子节点来形貌,比方图中2为6和3的父节点,反之6和3是2子节点
二叉树的三个性子
在二叉树的第i层上,最多有2^i-1个节点
- i=1时,只要一个根节点,2^(i-1) = 2^0 = 1
深度为k的二叉树最多有2^k-1个节点
- i=2时,2^k-1 = 2^2 – 1 = 3个节点
- 对任何一棵二叉树T,假如总结点数为n0,度为2(子树数目为2)的节点数为n2,则n0=n2+1
树和二叉树的三个重要差异
- 树的节点个数最少为1,而二叉树的节点个数可认为0
- 树中节点的最大度数(节点数目)没有限定,而二叉树的节点的最大度数为2
- 树的节点没有摆布之分,而二叉树的节点有摆布之分
二叉树分类
二叉树分为完全二叉树(complete binary tree)和满二叉树(full binary tree)
- 满二叉树:一棵深度为k且有2^k – 1个节点的二叉树称为满二叉树
- 完全二叉树:完全二叉树是指末了一层左侧是满的,右侧能够满也能够不满,然后其他层都是满的二叉树称为完全二叉树(满二叉树也是一种完全二叉树)
二叉树的数组示意
用一个数组来示意二叉树的构造,将一组数组从根节点最先从上到下,从左到右顺次填入到一棵完全二叉树中,以下图所示
经由过程上图我们能够剖析获得数组示意的完全二叉树具有以下几个性子:
- 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最先的值都为恭弘=叶 恭弘子节点
二叉堆
二叉堆由一棵完全二叉树来示意其构造,用一个数组来示意,但一个二叉堆须要满足以下性子:
- 二叉堆的父节点的键值老是大于或即是(小于或即是)任何一个子节点的键值
- 当父节点的键值大于或即是(小于或即是)它的每一个子节点的键值时,称为最大堆(最小堆)
从上图能够看出:
- 左图:父节点老是大于或即是其子节点,所以满足了二叉堆的性子,
- 右图:分支节点7作为2和12的父节点并没有满足其性子(大于或即是子节点)。
二叉堆的重要操纵
- insert:插进去节点
- delete:删除节点
- max-hepify:调解分支节点堆性子
- rebuildHeap:从新构建全部二叉堆
- sort:排序
初始化一个二叉堆
从上面简朴的引见,我们能够晓得,一个二叉堆的初始化异常的简朴,它就是一个数组
- 初始化一个数组构造
- 保留数组长度
class Heap{
constructor(arr){
this.data = [...arr];
this.size = this.data.length;
}
}
max-heapify最大堆操纵
max-heapify是把每一个不满足最大堆性子的分支节点举行调解的一个操纵。
如上图:
调解分支节点2(分支节点2不满足最大堆的性子)
- 默许该分支节点为最大值
将2与摆布分支比较,从2,12,5中找出最大值,然后和2交流位置
- 依据上面所将的二叉堆性子,离别获得分支节点2的左节点和右节点
- 比较三个节点,获得最大值的下标max
- 假如该节点自身就是最大值,则住手操纵
- 将max节点与父节点举行交流
反复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这个操纵,以及堆的三个性子。
后续
下一篇应该会引见二叉搜刮树。迎接人人指出文章的毛病,假如有什么写作发起也能够提出。我会延续的去写关于前端的一些技术文章,假如人人喜好的话能够关注一和点个赞,你的赞是我写作的动力。
趁便再提一下,我在等第一个粉丝哈哈
以下个人民众号,迎接人人关注,用户量到达肯定的量,我会推出一些前端教授教养视频