完全二叉树
若设二叉树的深度为h,除第h层外,其它各层 (1 ~ h-1) 的结点数都达到最大个数,第h层的所有节点都集中在左边的若干位置,这就是完全二叉树
二叉树和二叉堆
- 二叉树一般采用
链表
的形式实现 - 二叉堆一般采用
数组
的形式实现
二叉堆
- 具有完全二叉树的特性
- 二叉堆的根节点称为堆顶
- 堆中任何一个父节点的值都 >=/<= 它的左右孩子节点
最大堆:父节点的值 >= 左右孩子节点的值
最小堆
以小根堆举例
构建二叉堆:
- 即一个有 n 个节点的 无序的完全二叉树,把它构建成二叉堆
- 非叶子节点–从下往上-依次下沉。从下面的非叶子节点开始,让所有非叶子节点依次下沉(从下往上开始)
插入节点:
具有完全二叉树的特性,插入一个节点的时候,需要保证节点插入后,仍然是一颗完全二叉树,然后在进行调整,使它满足二叉堆的另一个特性
- 插入。把新节点插入到完全二叉树的最后一个位置
- 调整–上浮。把新插入的节点与它的父节点进行比较,如果新节点小于父节点的值,则让新节点上浮,即和父节点交换位置
删除节点(一般删除根节点):
具有完全二叉树的特性,删除一个几点的时候,要马上恢复它具有完全二叉树特性
- 删除。把根节点删除
- 最后一个元素顶替删除的根节点为止。用二叉堆最后一个元素顶替上来
- 调整----顶替后的根节点下沉。把顶替上来的根节点和它的左右孩子比较,如果根节点大于子节点,则让根节点下沉,和子节点交换位置
- 如何选取子节点?因为是最小堆,要保证父节点比子节点小,因此选择左右节点中小的元素进行交换
举个例子:
数组形式:1 2 3 5 4 6 7 8 9
二叉堆形式:
1
2 3
5 4 6 7
8 9
此处 n 是数组下标
节点位置
:n
左孩子位置
:2 * n + 1
右孩子位置
:2 * n + 2
举例:
节点[2]下标:1
节点[5]下标:3 = 2 * 1 + 1
节点[4]下标:4 = 2 * 1 + 2
看一个代码更加了解一下:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
// 下沉:即把当前节点和孩子节点进行比较
void DownAdjust(int * a , int parent , int indexSize)
{
int temp = a[parent];
// 左孩子节点先赋值
int child = 2 * parent + 1; // 找到左右节点中的最小值
// 下沉要一直下沉,直到叶子节点
while(child < indexSize)
{
// 如果存在右节点,找出左右节点中最小的那个进行交换
if ((child + 1 < indexSize) && (a[child] > a[child + 1]))
{
child = child + 1;
}
// 当父节点比子节点都小或者相等时,下沉结束
if(temp <= a[child])
{
break;
}
// 单向赋值(为啥不是)
a[parent] = a[child];
parent = child;
child = 2 * parent + 1;
}
a[parent] = temp;
}
// 上浮:对插入的节点执行上浮操作 indexSize插入前最后一个元素的下标
void UpAdjust(int * a , int indexSize)
{
// 插入的节点的下标
int chlid = indexSize + 1;
int temp = a[chlid];
int parent = (chlid - 1)/2; // 为什么要 (child - 1)/2,可以去试试,插入的节点是左节点或者右节点通过-1/2计算出来的值都一样
while(chlid > 0 && temp < a[parent]) // 即浮到最顶上了
{
// 单向赋值。不用每次都交换,因为你发现交换后还得给temp重新赋值,还是之前那个值,直接用temp值去比较就行了
a[chlid] = a[parent];
chlid = parent;
parent = (chlid - 1)/2;
}
a[chlid] = temp;
}
// 构建二叉堆
void BuildHeap(int * arr , int indexSize)
{
for(int i = (indexSize - 2)/2 ; i >= 0 ;--i)
{
DownAdjust(arr , i , indexSize);
}
}
void PrintArr(int * a , int size)
{
for(int i = 0 ; i < 10; ++i)
{
printf("%d " , a[i]);
}
printf("\n");
}
int main()
{
int a[10] = {10 , 12, 7 , 8 , 6 ,3 ,5 , 2 ,1 , 9};
PrintArr(a , 10);
// 本例都是以小根堆为例子
// 构建二叉堆:即从下往上对非叶子节点进行下沉,每次都沉到底/或父节点比左右子节点都小为止
BuildHeap(a , 9);
PrintArr(a , 10);
return 0;
}
输出结果:
10 12 7 8 6 3 5 2 1 9
1 2 3 8 6 7 5 12 10 9