一、定义
堆:实际上是一颗 完全二叉树 ,但是它还满足父结点大于(或小于)子结点特性。父结点大于子结点称为最大堆(或大顶堆,array[i]>=array[2i+1] && array[i]>=array[2i+2],i从0开始),父结点小于子结点称为最小堆(或小顶堆,array[i]<=array[2i+1] && array[i]<=array[2i+2] ,i从0开始)源码地址 GitHub
源码位置
注意:本文全部已最大堆为例,之后不再说明,最小堆与最大堆条件完全相反,不再详细介绍
二、调整
这里先简单介绍一下,稍后详细演示调整过程
向下调整:从父结点、左孩子、右孩子三个结点中选择最大的与父结点进行交换,交换后可能造成孩子结点不满满足堆的性质,因此每次交换后需要从新对交换后的孩子结点进行调整
//交换
void swap(int *x, int *y){
*x ^= *y;
*y ^= *x;
*x ^= *y;
}
//向下调整
//非递归实现
//array是待调整的堆数组,i是待调整的数组元素的位置,nlength是数组的长度
//本函数功能是:根据数组array构建大根堆
void heapDownAdjust(int array[],int i,int nLength)
{
int nChild;
//for(;2*i+1 < nLength;i=nChild)
while(2*i+1 < nLength)
{
//子结点的位置=2*(父结点位置)+1
nChild=2*i+1;//左孩子
//得到子结点中较大的结点
if(nChild<nLength-1 && array[nChild+1]>array[nChild])
++nChild;
//如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点
if(array[i]<array[nChild])
{
swap(array+i,array+nChild);
}
else
break; //否则退出循环
i=nChild;
}
}
//递归实现
void heapDownRecursiveAdjust(int array[],int i,int nLength)
{
int nChild;
if (2*i+1 < nLength) {
//子结点的位置=2*(父结点位置)+1
nChild=2*i+1;//左孩子
//得到子结点中较大的结点
if(nChild<nLength-1 && array[nChild+1]>array[nChild])
++nChild;
if(array[i]<array[nChild]){
swap(array+i,array+nChild);
heapDownRecursiveAdjust(array, nChild, nLength);
}
}
}
向上调整:当前结点与其父结点进行比较,如果比父结点大进行交换,否则退出,依次比较上移直到根结点
//向上调整
//非递归实现
void heapUpAdjust(int *array, int index, int nLength){
int i = index;//子节点
int j = (i-1)/2;//父结点
int temp = array[i];
while(i>0){
if(temp <= array[j])
break;
else
{
array[i] = array[j];//比较换高明
i = j;
//记录父结点
j = (j-1)/2;
}
}
array[i] = temp;
}
//递归实现
void heapUpRecursiveAdjust(int array[], int index, int nLength){
int i = index;
int j = (i-1)/2;
if(i>0){
if(array[i] <= array[j])
return;
else
{
swap(array+i, array+j);
heapUpRecursiveAdjust(array, j, nLength);
}
}
}
三、创建
- 创建堆就是从最后一个非叶子结点到根结点不断进行调整的过程
举例说明:给定数组 array[6]={4,5,6,9,8,7} ,创建最大堆
1.构建完全二叉树:
1.构建完全二叉树
2.调整结点6(6与7交换)
2.调整结点6
3.调整结点5(5与9交换)
调整结点5
4.调整结点4(4与9交换,交换后由于子节点不满足性质,4与8交换)
调整结点4
此时最大堆以构建完毕!
//创建堆
void createHeap(int *array,int length)
{
int i;
//调整序列的前半部分元素,调整完之后第一个元素是序列的最大的元素
//length/2-1是最后一个非叶节点,此处"/"为整除
for(i=length/2-1;i>=0;--i)
heapDownAdjust(array,i,length);
//heapDownRecursiveAdjust(array,0,i);
}
四、插入
- 把新数据放在数组最后,然后再进行向上调整
以我们创建的最大堆为例
插入结点10:
1.将结点10插入最后
将结点10插入最后
2.子结点10与父结点7交换
子结点10与父结点7交换
3.子结点10与父结点9交换
子结点10与父结点9交换
交换到根结点完成!!!
//插入元素
int insertElement(int *array, int length, int element){
if (length) {
//放入元素,这里注意数组长度要大于length+1
array[length]=element;
++length;
//向上调整
heapUpAdjust(array, length-1, length);
//heapUpRecursiveAdjust(array, length-1, length);
return length;
}
return -1;
}
五、删除(根据堆得性质,只能删除根结点)
- 将根结点与最后一个结点交换后,再对根结点向下调整
以我们创建的最大堆为例
删除过程:
1.将根结点9删除,最后的结点6移至根结点(排序时将这两个结点交换)
根结点9删除,结点6根结点
2.由于根结点6不满足堆的性质,调整根结点
调整根结点
//删除堆元素(堆只能删除根元素)
int deleteElement(int *array, int length){
if (length) {
//根结点与最后一个结点交换
swap(array,array+length-1);
//向下交换
heapDownAdjust(array,0,length-1);
return length-1;
}
return 0;
}
六、排序
将根结点与最后一个结点进行交换,再对根结点进行向下调整,最后的元素向前移动一个位子继续做上述过程,直到根结点
以上边创建的堆为例,我们来进行排序
已创建好的最大堆
排序过程:
1.将根结点9与最后一个结点6进行交换
9与6交换(然后调整6)
2.将根结点8与最后一个结点4进行交换
8与4交换(然后调整4)
3.将根结点7与最后一个结点5进行交换
7与5交换(然后调整5)
4.将根结点6与最后一个结点4进行交换
6与4交换(然后调整4)
5.将根结点5与最后一个结点4进行交换
5与4交换
6.已剩最后一个结点,排序完成 {4,5,6,7,8,9}
排序完成
//堆排序
void heapSort(int *array,int length)
{
int i;
//从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
for(i=length-1;i>0;--i)
{
//把第一个元素和当前的最后一个元素交换,
//保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
swap(array,array+i);
//不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
heapDownAdjust(array,0,i);
//heapDownRecursiveAdjust(array,0,i);
}
}
七、测试
int array[] = {12, 14, 54, 5, 6, 3, 9, 8, 47, 89, -1};
for (int i=0; i< 10; i++)
{
printf("%d ", array[i]);
}
printf("\n");
printf("创建堆:");
createHeap(array,10);
for (int i=0; i < 10; i++)
{
printf("%d ", array[i]);
}
printf("\n");
printf("删除元素:");
int length = deleteElement(array,10);
for (int i=0; i < length; i++)
{
printf("%d ", array[i]);
}
printf("\n");
printf("插入元素:");
length = insertElement(array,length,99);
for (int i=0; i < length; i++)
{
printf("%d ", array[i]);
}
printf("\n");
printf("堆排序:");
heapSort(array, length);
for (int i=0; i < length; i++)
{
printf("%d ", array[i]);
}
printf("\n");
关于堆先介绍到这里,如有疑问或是错误之处,欢迎留言!