堆是一种灵巧,部分有序的数据结构,它适合用来实现优先队列。优先队列是元素的一个集合,其中每个元素都包含一个被称作元素优先级的可排序属性,优先队列支持下面的操作
- 找出一个具有最高优先级的元素(一般是最大或者最小)
- 删除一个具有最高优先级的元素
- 添加一个元素到集合中去
堆的概念
定义:堆可以定义为一根二叉树,树的节点包含键(每一个节点都有一个键值),并且满足以下条件。
- 树的形状要求—这根二叉树是完全二叉树,就是树的每一层都是满的,除了最后一层最右边的元素可能会缺。
- 父母优势要求—堆的特性,每一个节点都要大于或者等于他子女的键。
堆的特性
如何来构造一个堆呢? 有两种做法
- 自底向上堆构造
初始化一颗包含n个节点的完全二叉树,按照给定数的顺序来放置键,然后按照下面的方法对树进行处理,从最后的父母节点开始,直到根为止,依次检查这些节点的键K是否满足父母优势,如果不满足,就把该节点的键K和它子女的最大键进行交换,然后在检查新位置上,K是否满足父母优势,这个过程一直继续到对K的父母优势要求满足为止。对于以当前父母节点为根的子树,在完成它的堆化后,该算法对该节点的直接前驱进行同样的操作,在对树的根完成这种操作以后,该算法就停止了。
c语言实现
void HeapBottomUp(int n)
{
int i,j;
int k;
int v;
int heap;
for(i=n/2;i>=1;i--)//遍历所有的父母节点
{
k=i;
v=h[k]; //存放父母节点的键值
heap=0; //设置标志,表示此节点是否可继续堆化
while(!heap&&2*k<=n) //
{
j=2*k;
if(j<n) //最后一个父母节点存在两个子女
{
if(h[j]<h[j+1])
j=j+1;
}
if(v>=h[j])
heap=1;
else
{
h[k]=h[j];
k=j;
h[k]=v;
}
}
}
}
- 把新的键连续插入预先构造好的堆,来建造一个新堆,也可以叫做自顶向下构造算法。
通过把新的键插入预先构造好的堆,来构造一个新堆,那么我们怎么把一个新的键插入堆中呢?
首先把一个包含键k的结点附加在当前堆的最后一个叶子后面,然后按照下面的方法把k筛选到正确的位置,拿k与它的父母作比较,如果它大于父母,则交换,如果不大于或者达到树根,算法停止,否则交换后继续比较与新父母的大小。
那么我们如何从堆中删除一个元素呢?
这里我们考虑的是删除根中的键,利用下面的算法删除根中的键。
从堆中删除最大的键
- 根的键和堆最后一个键K做交换。
- 堆的规模减一
- 验证根此时的键是否满足父母优势,做法同于自底向上堆构造里的算法
堆排序就是堆的一个很好应用
堆排序
堆排序的步骤大致分为两部
- 构造堆,就是为给定的数组构造一个堆
- 删除最大键,就是对剩下的对进行n-1次根删除操作
最后的结果是按照降序删除了数组的元素,但是对于堆的数组的实现来说,一个正在被删除的元素是位于最后的,所以结果数组将恰好是按照升序排列的原始数组。
堆排序实现代码
#include<stdio.h>
void swap(int * array, int i, int j)
{
int tmp;
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
}
/*最小根堆调整,这里的注释在上面的图片有说明*/
void minheapify(int * array,int heapsize,int currentnode)
{
int i,j;
int k;
int heap;
int parentvalue;
for(i=heapsize/2;i>=1;i--)
{
k=i;
parentvalue=array[k];
heap=0;
while(!heap&&2*k<=heapsize)
{
j=2*k;
if(j<heapsize)
{
if(array[j]>array[j+1])
j++;
}
if(parentvalue<=array[j])
heap=1;
else
{
array[k]=array[j];
k=j;
array[k]=parentvalue;
}
}
}
}
void maxheapify(int * array, int heapsize, int currentnode)
{
int i,j;
int k;
int heap;
int parentvalue;
for(i=heapsize/2;i>=1;i--)
{
k=i;
parentvalue=array[k];
heap=0;
while(!heap&&2*k<=heapsize)
{
j=2*k;
if(j<heapsize)
{
if(array[j]<array[j+1])
j++;
}
if(parentvalue>=array[j])
heap=1;
else
{
array[k]=array[j];
k=j;
array[k]=parentvalue;
}
}
}
}
void minheapcreate(int * array,int heapsize)
{
int i;
for(i=heapsize/2;i>=1;i--)//依次检查所有父母节点是否满足堆的性质
{
minheapify(array,heapsize,i);//调整堆,化堆为最小堆。
}
}
void maxheapcreate(int * array, int heapsize)
{
int i;
for(i = heapsize/2; i >= 1; i--)
{
maxheapify(array, heapsize, i);
}
}
int main()
{
int array[]={0,6,1,14,7,5,8};
//初始化数组,也叫作待排序数组,第一个0不是需要排序的数,只是为了表述排序从下标1到 6的这六个数
int *res=array; //res用作返回值,在array上做修改。
int i,heapsize=sizeof(array)/sizeof(int)-1;//堆使用数组实现,数组的大小为6;
printf("原始数据:\n");
for(i=1;i<=heapsize;i++)
{
printf("%d\t",array[i]);
}
printf("\n");
for(i=heapsize;i>=1;i--)//堆的规模减一(因为已经把最小的数放在数组末尾了),继续创建并调整最小堆。
{
minheapcreate(array,i);//创建最小堆 并且调整最小堆,可知经过最小堆的调整之后,数组的下标1就是最小的数
swap(array,1,i);//将最小的数和数组的最后一个位置交换,然后堆的规模减一。
}
printf("最小堆输出(利用最小堆实现数组降序排列):\n");
for(i=1;i<=heapsize;i++)
printf("%d\t",array[i]);
printf("\n");
//以下是最大堆的处理,思想同于上述最小堆。
for(i=heapsize;i>=1;i--)
{
maxheapcreate(array,i);
swap(array,1,i);
}
printf("最大堆输出(利用最大堆实现数组升序排列):\n");
for(i=1;i<=heapsize;i++)
printf("%d\t",array[i]);
printf("\n");
return 0;
}
运行截图
实现最大堆,最小堆还可以有另外一个技巧,使用函数自身调用。
可以参考下面的优秀博客,写的很清楚。