经典排序算法之堆排序
2015-08-07 18:01
316 查看
首先介绍堆的定义:堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。由于堆的表象结构是一棵完全二叉树,我们称之为二叉堆。下面分别就是两个大顶堆和小顶堆:
随后我们都是以大顶堆为例来介绍堆排序的,小顶堆也是一样的。
对于大顶堆,我们直到,根结点肯定是整个堆中最大的结点,因此我们如果每次都取出根结点,然后再将堆的剩余元素重新调整成大顶堆,再取根结点,即可以获得一个有序序列。但是这里要考虑到几个问题:
1、怎么存储这个大顶堆?
2、如何初始化一个大顶堆?
3、每次取出根结点后该如何调整成一个新的大顶堆?
* 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;下沉过程的代码如下:
由于完全二叉树的特性,在层序排列的序列中,叶子结点总是全部排在后面,而所有拥有孩子的父结点总是排在前面。假设结点个数为n,则最后一个结点的下标为n-1,则其父亲结点的下标为n/2 - 1。即整个序列,0到n-1,父亲结点是从0到n/2-1,叶子结点是从n/2到n-1。我们可以从最后一个父结点开始,以该父结点为根结点的子树中我们构造大顶堆,那么可以保证每次选取一个父结点的时候,其左右子树均已经是一个大顶堆了,则可以对该父结点做前面所说的“下沉”操作了。上面的例子,最后一个父结点是30,以30为根结点所在的树(红色标记内的),对30做“下沉”操作。下一个父结点是90,然后对其做“下沉”操作。再下一个父结点是10,此时10的两个左右子树均为大顶堆了,则可以直接对10做“下沉操作”。以此类推,直到整棵树的根结点做完“下沉”为止,此时,我们便将一棵乱序的完全二叉树变成一棵大顶堆了。代码如下:
好了,现在可以对其进行排序了。对于给定的一个乱序数组,第一步将其初始化为一个大顶堆,然后每次将根结点跟最后一个结点互换,然后在对这个新的(除去最后一个元素)的树进行“下沉”操作(因为此时只有根结点是不在原位的,其左右子树都是大顶堆)。最后所得到的就是一个升序的序列了。代码如下:
到此为止,整个堆排序就结束了。
下面是一个完整的程序来测试一下堆排序。
运行结果如下:
随后我们都是以大顶堆为例来介绍堆排序的,小顶堆也是一样的。
对于大顶堆,我们直到,根结点肯定是整个堆中最大的结点,因此我们如果每次都取出根结点,然后再将堆的剩余元素重新调整成大顶堆,再取根结点,即可以获得一个有序序列。但是这里要考虑到几个问题:
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");
}
运行结果如下:
相关文章推荐
- 关于 ArtifactTransferException: Failure to transfer
- MySQL主从配置步骤
- bzoj 3211 分类: bzoj 2015...
- Webbench-http压力测试
- 使用ClickableSpan轻松实现一段文本中的多个点击事件
- poj 2406 Power Strings
- Arcgis for Java(一)在应用程序中添加地图
- bzoj 3211
- swift封装的跑马灯效果
- bzoj 3211 分类: bzoj 2015-08-07 18:00 8人阅读 评论(0) 收藏
- 各公司年资金归集汇总sql
- 关于DB In-Memory Option你必须知道的17个问题
- lvs
- 【MVC框架】——Razor语法
- ls 对应各种目录、文件列出的用法
- java执行linux命令获取MAC和ip地址
- OC学习:OC中实现多继承
- block在页面逻辑处理上的使用,替换代理的功能
- Linux必须会的命令---也是以前记录的,ctrl+z fg 啥的 jobs 比较实用
- 求和求到手软(字符串处理)