堆
堆是什么?堆是一种特殊的完全二叉树。
所有父结点都比子结点要小的完全二叉树我们称为最小堆。反之,如果所有父结点都比子结点要大,这样的完全二叉树称为最大堆。
【堆的创建】
把n个元素建立一个堆,首先我们可以将这n个结点以自顶向下、从左到右的方式从1到n编码,即以层序遍历的顺序编码,这样就可以把这n个结点转换成为一棵完全二叉树。紧接着从最后一个非叶结点(结点编号为n/2)开始到根结点(结点编号为1),逐个扫描所有的结点,根据需要将当前结点向下调整,直到以当前结点为根结点的子树符合堆的特性。
#include <stdio.h>
#define maxn 105
int h[maxn]; //用来存放堆
int n; //堆的大小
//交换堆中的两个元素的值
void swap(int x,int y)
{
int t=h[x];
h[x]=h[y];
h[y]=t;
}
//向下调整成最小堆
void siftdown(int pos)
{
int t,flag=0; //flag用来标记是否需要继续向下调整
while(!flag)
{
int t=pos; //用t记录父结点和左右儿子中值较小的结点编号
if(pos*2<=n&&h[t]>h[pos*2]) t=pos*2;
if(pos*2+1<=n&&h[t]>h[pos*2+1]) t=pos*2+1;
//如果最小的结点不是父结点
if(t!=pos)
{
swap(t,pos);
pos=t;
}
else flag=1;
}
}
//建堆
void create()
{
//从最后一个非叶结点到第1个结点依次进行向下调整
for(int i=n/2;i>=1;i--)
siftdown(i);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&h[i]);
create();
for(int i=1;i<=n;i++) printf(i==n?"%d\n":"%d ",h[i]);
return 0;
}
【堆的插入与删除】
若最小(大)堆要进行删除最小(大)数并返回最小(大)数的操作,我们只需要删掉堆顶元素即最小(大)数,将最后一个数放在堆顶,通过比较与左右儿子的大小向下调整即可恢复为最小(大)堆。
最小堆为例
//向下调整成最小堆
void siftdown(int pos)
{
int t,flag=0; //flag用来标记是否需要继续向下调整
while(!flag)
{
int t=pos; //用t记录父结点和左右儿子中值较小的结点编号
if(pos*2<=n&&h[t]>h[pos*2]) t=pos*2;
if(pos*2+1<=n&&h[t]>h[pos*2+1]) t=pos*2+1;
//如果最小的结点不是父结点
if(t!=pos)
{
swap(t,pos);
pos=t;
}
else flag=1;
}
}
//返回并删除最大的元素
int deletemax()
{
int t=h[1];
h[1]=h[n]; //将堆的最后一个点赋值到堆顶
n--; //堆的元素减少1
siftdown(1); //向下调整
return t; //返回最大值
}
若最小(大)堆要进行删除最小(大)数并插入一个新的数的操作,我们只需要删掉堆顶元素即最小(大)数,将新的数放在堆顶,通过比较与左右儿子的大小向下调整即可恢复为最小(大)堆。
若最小(大)堆只需进行插入一个新的数,我们只需要把新的数放在末尾,通过与父亲的比较向上调整即可恢复最小(大)堆。
下面以最小堆为例。
//向上调整成最小堆
void siftup(int pos)
{
int flag=0; //用来标记是否需要继续向上调整
if(pos==1) return; //如果是堆顶,就不需要调整了
//不在堆顶 并且 当前结点pos的值比父结点小的时候继续向上调整
while(pos!=1&&!flag)
{
//判断是否比父结点的小
if(h[pos]<h[pos/2]) swap(pos,pos/2);
else flag=1;
pos=pos/2; //更新编号pos为它父结点的编号
}
}
【堆排序(Heap Sort)】
堆排序在寻找最小值(或最大值)的过程中使用了堆这种数据结构,提高了效率。堆是一个近似二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
那么我们要如何实现堆排序呢?下面以升序排序为例讲解。
算法一:
将需要排序的序列建堆,调整成最小堆。
每次删除并输出堆顶结点的值即最小值,再将最后一个数放置于堆顶结点的位置,最小堆的大小-1,向下调整成最小堆。重复上述步骤直到堆为空。
算法二:
将需要排序的序列建堆,调整成最大堆。
每次把堆顶结点的值即最大值与最后一个结点交换位置,最大堆的大小-1,向下调整成最大堆。重复上述步骤直到结束。
最后层序遍历输出堆排序后的堆,即为升序序列。
下面为算法二的代码,以升序堆排序为例:
#include <stdio.h>
#define maxn 105
int h[maxn];
int n;
//交换函数
void swap(int x,int y)
{
int t=h[x];
h[x]=h[y];
h[y]=t;
}
//向下比较调整成最大堆
void siftdown(int pos,int num)
{
int t,flag=0; //flag用来标记是否需要继续向下调整
while(!flag)
{
int t=pos; //用t记录父结点和左右儿子中值较大的结点编号
if(pos*2<=num&&h[t]<h[pos*2]) t=pos*2;
if(pos*2+1<=num&&h[t]<h[pos*2+1]) t=pos*2+1;
//如果最大的结点不是父结点
if(t!=pos)
{
swap(t,pos);
pos=t;
}
else flag=1;
}
}
void create()
{
//从最后一个非叶结点到第1个结点依次进行向下调整
for(int i=n/2;i>=1;i--)
siftdown(i,n);
}
//堆排序(升序)
void heapSort()
{
create(); //建堆
int num=n;
for(int i=n;i>1;i--)
{
swap(1,i); //交换最大值与最后一个数
num--;
siftdown(1,num); //前num个数调整成最大堆
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&h[i]);
heapSort();
for(int i=1;i<=n;i++) printf(i==n?"%d\n":"%d ",h[i]);
return 0;
}
堆排序是不稳定的排序。最差时间复杂度为O(nlogn),平均时间复杂度为O(nlogn)。
2124

被折叠的 条评论
为什么被折叠?



