注意:个人建议在看堆排序时可以先看看什么是二叉树(如果没学过的话也不影响对堆排序的学习和了解)
堆排序分为两种排列方式,一种是最大堆另一种是最小堆。在最大堆中,它的父结点不小于它的左右孩子结点。最小堆则相反它的父结点不大于它的左右孩子结点。因此在最大堆中,根元素为最大值,而最小堆中,根元素为最小值。
注意:若假设父节点为i 则左节点为i*2 右节点为i*2+1 如下图所示:
这里首先要明确一点,这里的堆并不是指的内存上的堆,而是一种类似于二叉树的排列方式。就以最大堆(即按照从大到小的顺序输出)进行举例:
算法思想:
初始化时将一组无序的数,按照二叉树的方式排列,即按照一个父节点和左右字节点排列,此时的二叉树为无序的二叉树。
首先从树的右下角进行查找,要求是父节点必须比它的左右子节点都要大,如果父节点比子节点小时,将父节点与子节点中大的进行交换,交换后依然变成父节点大于左右字节点,依次进行操作,最终的根节点就为树中的最大数,只要将该数取数就可以了,将剩下的树组成一个新的二叉树。依次重复进行上述操作最后就可以将该组数按照从大到小的顺序输出了。
具体的操作过程可以看下图的过程图
过程图:
一、将一组数初始化为初始二叉树如下图:
二、初始化后的二叉树,从右下端开始将子节点与父节点进行比较,如果父节点小的话将左右子节点中大的节点中的数与父节点进行互换,形成任意父节点大于它的左右子节点的一个二叉树。如一下整理过后的二叉树。
三、将二叉树中的根节点即97取出,剩下的数形成一个新的二叉树(该二叉树可能还是无序的)如下图:
重复上述操作最终的数据就是一个按照从大到小的顺序排列的数。如下图:
原理(以下代码是以最小堆为例):
一、将一个初始化的二叉树开始根节点进行整理上的代码如下(核心代码):
void siftdown(int i)/*传入一个需要向下调整的节点编号i,假
设传入1,即从堆顶开始向下调整*/
{
int t,flag=0;//flag表示是否需要继续向下调整
/*当i节点有儿子(至少有左儿子时)并且需要进行调整时,下列循环就进行*/
while(i*2<=n&&flag==0)
{
//首先判断它与左儿子的大小,并用t记录较小的节点编号
if(h[i]>h[i*2])
t=i*2;
else
t=i;
//如果它有右儿子时,再对右儿子进行比较
if(i*2+1<=n)
{
//如果右儿子的值更小,更新较小的节点编号
if(h[t]>h[i*2+1])
t=i*2+1;
}
//如果发现最小的编号不是原来自己,就说明它的子结点比父节点小
if(t!=i)
{
swap(t,i);//将他们的之进行交换,注意swap函数要自己写
i=t;//更新i为刚才与他交换的儿子节点的编号,便于接下来继续向下调整
}
else
flag=1;//否则说明当前的父节点已经比两个子节点都要小,不需要再进行调整了
}
}
二、堆排序的完整代码如下:
注意:之所以创建堆的函数是从n/2开始的是因为完全二叉树有一个性质:最后一个非叶节点是第n/2个节点
#include<stdio.h>
int h[101];//用来存放堆的数组
int n;//用来储存对中元素的个数,也是堆的大小
//交换函数,用来交换两个元素的值
void swap(int x,int y)
{
int t;
t=h[x];
h[x]=h[y];
h[y]=t;
}
//向下调整函数
void siftdown(int i)/*传入一个需要向下调整的节点编号i,假
设传入1,即从堆顶开始向下调整*/
{
int t,flag=0;//flag表示是否需要继续向下调整
/*当i节点有儿子(至少有左儿子时)并且需要进行调整时,下列循环就进行*/
while(i*2<=n&&flag==0)
{
//首先判断它与左儿子的大小,并用t记录较小的节点编号
if(h[i]>h[i*2])
t=i*2;
else
t=i;
//如果它有右儿子时,再对右儿子进行比较
if(i*2+1<=n)
{
//如果右儿子的值更小,更新较小的节点编号
if(h[t]>h[i*2+1])
t=i*2+1;
}
//如果发现最小的编号不是原来自己,就说明它的子结点比父节点小
if(t!=i)
{
swap(t,i);//将他们的之进行交换,注意swap函数要自己写
i=t;//更新i为刚才与他交换的儿子节点的编号,便于接下来继续向下调整
}
else
flag=1;//否则说明当前的父节点已经比两个子节点都要小,不需要再进行调整了
}
}
//创建堆的函数
void creat()
{
for(int i=n/2;i>=1;i--)
siftdown(i);
}
//取出最大的元素
int deletemax()
{
int t;
t=h[1];//用一个临时变量记录堆顶的值
h[1]=h[n];//将堆的最后一个点赋值到堆顶
n--;//堆的元素减1,即将堆顶元素取出了
siftdown(1);//向下调整
return t;//返回之前记录的堆的顶点的最大值
}
int main()
{
int num;
//输入要排序的数字个数
scanf("%d",&num);
for(int i=1;i<=num;i++)
scanf("%d",&h[i]);
n=num;
//创建堆
creat();
//取出堆顶的数
for(int i=1;i<=num;i++)
printf("%d ",deletemax());
return 0;
}