您的位置:首页 > 其它

排序算法(六) 堆排序

2017-11-19 16:49 204 查看
一、什么是堆排序?

       堆排序也是一种选择类排序,但它选择元素的方式和简单选择排序大不相同。

       它将待排序数组看成一个完全二叉树,并通过建立大根堆或小根堆的算法使之成为一棵特殊的完全二叉树。为什么说其特殊呢? 用大根堆完全二叉树来说,任何一个结点的值大于等于它的左右子树的根的值。而小根堆完全二叉树恰好相反,任何一个结点的值小于等于它的左右子树的根的值。

       举个栗子,大家就明白了 :



二、而堆排序呢(以大根堆排序为例),就是:

       <1>、先将初始序列建成一个大根堆。

       <2>、抽取大根堆的堆顶与最后一个元素进行交换,此时认为最后一个元素是递增有序的,而前面的所有元素都是无序的。

       <3>、然后再将剩余所有无序的元素再弄成一个大根堆。

       <4>、再将堆顶元素与倒数第二个元素进行交换,此时认为最后两个元素是递增有序的,而前面的所有元素都是无序的。

       <5>、之后再将剩余所有无序的元素弄成一个大根堆......直到剩余无序的元素就剩下最后一个时,堆排序完成,从第一个元素先后遍历就是一个递增序列。

三、上述算法中,在堆顶元素与后面元素交换后,需要将剩余的无序元素调整为一个大根堆,那怎样将一个序列调整为一个大根堆呢?

       <1>、首先将与堆对应的完全二叉树的堆顶元素挪出来另行保存,期待找到合适的位置将其放置进去。该堆顶元素称之为待调整元素。

       <2>、然后,堆顶元素位置空置,从空置结点的左右子树中选一个关键字值比较大的结点,如果该值大于待调整元素,则将该值上移至空置结点中。

       <3>、此时,原来那个关键字较大的子结点相当于一个空置结点,然后从该空置结点的左右子树中选出一个关键字较大的结点,如果该结点值仍大于待调整元素的值,则将该结点上移至空置结点中。

       <4>、重复上面的过程,直到空置结点的左右子树的关键字值都小于待调整元素的值。此时,将待调整记录放入空置结点即可。

 
我们以一个实例来观察一下具体的过程:



四、源代码实现:

/*
**函数功能 : record[k .. m]是一棵以record[k]为根结点的完全二叉树,而且
**           分别以record[2k]和record[2k+1]为根的左右子树为大根堆,调整
**           record[k],使整个序列record[k .. m]满足大根堆的性质
**参数说明 :
**@record : 完全二叉树数组
**@k : 待调整为大根堆的完全二叉树的根的数组下标
**@m : 待调整为大根堆的完全二叉树的最后一个元素的下标
**返回值 : 无
*/
void AdjustHeap(int record[], int k, int m)
{
int i, j;
int finished = 0;  //已完成标志,初始为0
int temp = record[k];  //将根暂时保存

i = k;      //当前空出的位置下标
j = 2 * i;  //当前空出的位置的左右子树根结点值较大的结点的下标,初始为左子树的根

while (j <= m && !finished)
{
//用j表示挑选出来的左右子树根结点值较大的结点的下标
if (j < m && record[j] < record[j + 1])
{
j = j + 1;
}

if (temp >= record[j])
{
//筛选完毕,因为temp比record[j] 和 record[j+1]都大。
//已满足大根堆性质,此时i下标就是放置record[k]的恰当位置
finished = 1;
}
else
{
record[i] = record[j];  //将较大结点元素上移
i = j;
j = 2 * i;

//继续筛选
}
}

//将record[k]填入恰当的位置,调整完成。
record[i] = temp;
}

/*
**函数功能 : 建立初始大根堆
**参数说明 :
**@record : 待排序数组
**@len : 待排序数组元素的最大下标,即数组长度为len+1,
**       但下标0所对应空间不使用,所以len也是数组元素个数。
**返回值 : 无
*/
void CreateBigHeap(int record[], int len)
{
int i;

/*     根据完全二叉树的性质 : length/2 是完全二叉树的最后一个非叶结点,
** 从它开始调用 "调整堆函数(AdjustHeap)",因为叶结点(单元素)可以看做一
** 个已经调整好的大根堆。自底向上逐层把所有子树都调整为大根堆,直到将
** 整个完全二叉树调整为大根堆
*/
for (i = len / 2; i >= 1; i--)
{
AdjustHeap(record, i, len);
}
}

/*
**函数功能 : 堆排序法升序排序待排序列
**参数说明 :
**@record : 待排序列数组(数组元素下标从1开始)
**@len : 待排序数组元素的最大下标,即数组长度为len+1,
**       但下标0所对应空间不使用,所以len也是数组元素个数。
**返回值: 无
*/
void HeapSort(int record[], int len)
{
int i;
CreateBigHeap(record, len);     //建成初始大根堆

//i指的是当前大根堆的堆尾下标, 当堆尾下标为1时,即堆排序结束
for (i = len; i >= 2; i--)
{
//将大根堆的堆顶元素(堆顶下标为1)与堆尾元素互换
int temp = record[1];
record[1] = record[i];
record[i] = temp;

//对 record[1] -- record[i-1]之间的元素进行调整,使之成为大根堆
//此时,i之后的所有节点(包括i本身)是递增有序的。
AdjustHeap(record, 1, i - 1);
}
}
int main(void)
{
//数组元素下标从1开始,为了方便使用完全二叉树的性质,0下标弃之不用。
int record[] = { 0,46,55,13,42,94,17,05,70 };
int len = sizeof(record) / sizeof(record[0]);
int i = 0;

printf("堆排序前: \n");
for (i = 1;i < len;i++)
{
printf("%d  ", record[i]);
}
puts("");

HeapSort(record, len - 1);

printf("堆排序后: \n");
for (i = 1;i < len;i++)
{
printf("%d  ", record[i]);
}
puts("");

return 0;
}

运行截图:

                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: