您的位置:首页 > 其它

经典排序算法之堆排序

2015-08-07 18:01 316 查看
首先介绍堆的定义:堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。由于堆的表象结构是一棵完全二叉树,我们称之为二叉堆。下面分别就是两个大顶堆和小顶堆:



随后我们都是以大顶堆为例来介绍堆排序的,小顶堆也是一样的。

对于大顶堆,我们直到,根结点肯定是整个堆中最大的结点,因此我们如果每次都取出根结点,然后再将堆的剩余元素重新调整成大顶堆,再取根结点,即可以获得一个有序序列。但是这里要考虑到几个问题:

1、怎么存储这个大顶堆?

2、如何初始化一个大顶堆?

3、每次取出根结点后该如何调整成一个新的大顶堆?

大顶堆的存储

从上面的介绍我们可以看出,大顶堆是一个完全二叉树结构,但是,如果使用二叉树来存储这个二叉堆,对于整个过程中将经常出现的遍历会非常浪费时间。因此我们直接选择使用一个数组来存储。但是数组如何才能表示一个二叉堆呢?这就是存储的关键。我们直到,完全二叉树的特点是:假如以层序遍历的顺序对每个结点进行标号(从0开始),则对与编号为i的结点,其父亲结点编号为(i - 1)/2,左孩子结点编号为2
* i + 1,右孩子为2 * i + 2。我们可以使用一个数组来表示一棵二叉堆的层序遍历结果,然后标号对应每个结点在数组中的下标即可。

每次取出根结点后该如何调整成一个新的大顶堆

假设现在有一个大顶堆,如果取出根结点,当然我们总不能让根结点为空来调整呐,有一个一举两得的办法就是,将根结点的元素与堆中最后一个元素互换一下,这样就可以每次都将最大的元素存放在最后的位置了。然后问题就转换成了将根结点“下沉”从而来调整成大顶堆问题了。如图:



将末尾元素换到根部之后,然后开始“下沉”,下沉的过程中与两个孩子结点互相比较,取最大者互换,若根结点本身最大则无需互换了。图中将20换到根部,然后20、70、80中80最大,20与80互换,以此类推,一直"下沉"到合适的位置。

将这个“下沉”的过程量化:假设需要“下沉”的元素下标为i,则i处元素将与两个孩子2*i+1和2*i+2处的元素比较,取较大者交换。“下沉”的终止条件是:左孩子2*i+1已经超出数据下标界限n-1;下沉过程的代码如下:

void ElemDown(int *array, int index, int arrayCount)
{
while(2 * index + 1 < arrayCount - 1)
{
if(2 * index + 2 < arrayCount - 1)  //右孩子也存在
{
if(array[index] >= array[2 * index + 1] && array[index] >= array[2 * index + 2])  //比两个孩子都大
break;
int bigIndex = array[2 * index + 1] >= array[2 * index + 2]?2 * index + 1:2 * index + 2;  //取较大孩子下标
int temp = array[bigIndex];  //互换元素
array[bigIndex] = array[index];
array[index] = temp;
index = bigIndex;  //继续下沉
}
else  //只存在左孩子,则左孩子肯定是叶子结点
{
if(array[index] < array[2 * index + 1])  //比左孩子小则互换
{
int temp = array[index];
array[index] = array[2 * index + 1];
array[2 * index + 1] = temp;
}
break;  //都是要结束了
}
}
}


初始化一个大顶堆

现在有一个乱序的序列,在排序的第一步我们要做的就是将此数组调整成一个大顶堆。以下面的例子为例:



由于完全二叉树的特性,在层序排列的序列中,叶子结点总是全部排在后面,而所有拥有孩子的父结点总是排在前面。假设结点个数为n,则最后一个结点的下标为n-1,则其父亲结点的下标为n/2 - 1。即整个序列,0到n-1,父亲结点是从0到n/2-1,叶子结点是从n/2到n-1。我们可以从最后一个父结点开始,以该父结点为根结点的子树中我们构造大顶堆,那么可以保证每次选取一个父结点的时候,其左右子树均已经是一个大顶堆了,则可以对该父结点做前面所说的“下沉”操作了。上面的例子,最后一个父结点是30,以30为根结点所在的树(红色标记内的),对30做“下沉”操作。下一个父结点是90,然后对其做“下沉”操作。再下一个父结点是10,此时10的两个左右子树均为大顶堆了,则可以直接对10做“下沉操作”。以此类推,直到整棵树的根结点做完“下沉”为止,此时,我们便将一棵乱序的完全二叉树变成一棵大顶堆了。代码如下:

void MakeBigHeap(int *array, int arrayCount)
{
int fatherNo = arrayCount/2 - 1;  //每颗子树的父结点下标
for(; fatherNo >= 0; --fatherNo)
ElemDown(array, fatherNo, arrayCount);
}


好了,现在可以对其进行排序了。对于给定的一个乱序数组,第一步将其初始化为一个大顶堆,然后每次将根结点跟最后一个结点互换,然后在对这个新的(除去最后一个元素)的树进行“下沉”操作(因为此时只有根结点是不在原位的,其左右子树都是大顶堆)。最后所得到的就是一个升序的序列了。代码如下:

void HeapSort(int array[], int arrayCount)
{
MakeBigHeap(array, arrayCount);  //构造大顶堆
int temp;
for(int newArrayCount = arrayCount; newArrayCount > 0; --newArrayCount)  //去除根结点
{
temp = array[0];
array[0] = array[newArrayCount - 1];
array[newArrayCount - 1] = temp;
ElemDown(array, 0, newArrayCount);
}
}


到此为止,整个堆排序就结束了。

下面是一个完整的程序来测试一下堆排序。

#include <iostream>
using namespace std;

void ElemDown(int *array, int index, int arrayCount) { while(2 * index + 1 < arrayCount - 1) { if(2 * index + 2 < arrayCount - 1) //右孩子也存在 { if(array[index] >= array[2 * index + 1] && array[index] >= array[2 * index + 2]) //比两个孩子都大 break; int bigIndex = array[2 * index + 1] >= array[2 * index + 2]?2 * index + 1:2 * index + 2; //取较大孩子下标 int temp = array[bigIndex]; //互换元素 array[bigIndex] = array[index]; array[index] = temp; index = bigIndex; //继续下沉 } else //只存在左孩子,则左孩子肯定是叶子结点 { if(array[index] < array[2 * index + 1]) //比左孩子小则互换 { int temp = array[index]; array[index] = array[2 * index + 1]; array[2 * index + 1] = temp; } break; //都是要结束了 } } }
void MakeBigHeap(int *array, int arrayCount)
{
int fatherNo = arrayCount/2 - 1;
for(; fatherNo >= 0; --fatherNo)
ElemDown(array, fatherNo, arrayCount);
}

void HeapSort(int array[], int arrayCount)
{
cout<<"before sort: \n";
for(int index = 0; index < arrayCount; ++index)
cout<<array[index]<<" ";

MakeBigHeap(array, arrayCount); //构造大顶堆

cout<<"\nafter make big sort: \n";
for(int index = 0; index < arrayCount; ++index)
cout<<array[index]<<" ";

int temp;
for(int newArrayCount = arrayCount; newArrayCount > 0; --newArrayCount)
{
temp = array[0];
array[0] = array[newArrayCount - 1];
array[newArrayCount - 1] = temp;
ElemDown(array, 0, newArrayCount);
}

cout<<"\nafter sort: \n";
for(int index = 0; index < arrayCount; ++index)
cout<<array[index]<<" ";
}

void main()
{
int array[] = {9, 1, 5, 8, 3, 7, 4, 6, 2};
HeapSort(array, 9);

system("pause");
}


运行结果如下:

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