一. 目的以及背景知识
进行堆排序练习,掌握堆的构建与输出。
堆定义:
对于序列A[1,N],对于任意i都有A[i]>=A[2i],A[i]>=A[2*i+1],则该序列称为大顶堆或者最大堆。反之则成为最小堆。
堆筛选或堆调整:
从最后一个叶子结点开始筛选出该节点的兄弟结点与父节点中最大或最小的元素与父节点交换。
建堆算法证明:
第一步:i=N/2时,i+1,..N都是叶节点,是最简单的最大堆的根。
第二步:进入循环处理,堆算法维护了i+1,..N都是最大堆的根节点或最大堆的性质。i递减进行下一次循环。
第三部:i=0时,计算终止,每个结点都是符合最大堆性质的节点。算法证明完毕。
二. 源码
建堆源码,最大堆与最小堆:
//创建一个小顶堆。Ri<=R2i(左孩子),Ri<=R2i+1(右孩子)
void BuidingSmallHeap(T testArray[], int nSize){
//建立小顶堆从最后一个非叶子结点开始上溯调整
for (i = nSize / 2-1; i >= 0; i--){
SmallHeapAdjust(testArray, i, nSzie);
}
}
//调整小顶堆
void SmallHeapAdjust(T testArray[],int nParent, int nSize){
T key = testArray[nParent];//记录待调整的父节点作为哨兵
for (int j = 2 * nParent+1; j < nSize; j = 2 * j+1){
//左孩子大于右孩子,则选取右孩子作为和父节点交换的结点
if (j<nSize-1 && testArray[j]>testArray[j+1]){
j++;//记录最小结点位置
}
//选取的最小结点大于顶级父节点,则退出调整过程
if (key<=testArray[j]){
break;
}
testArray[nParent] = testArray[j];//将较大的子节点放在父节点位置
nParent = j;//交换的子节点作为父节点进行下一次的堆调整
}
testArray[nParent] = key;//将最后调整的结点位置放置哨兵
}
//创建一个大顶堆。Ri>=R2i(左孩子),Ri>=R2i+1(右孩子)
void BuidingBigHeap(T testArray[], int nSize){
//建立大顶堆从最后一个非叶子结点开始上溯调整
for (int i = nSize / 2-1; i >= 0; i--){
BigHeapAdjust(testArray, i, nSize);
}
}
//调整大顶堆
void BigHeapAdjust(T testArray[], int nParent, int nSize){
T key = testArray[nParent];//记录待调整的父节点作为哨兵
for (int j = 2 * nParent + 1; j < nSize; j = 2 * j + 1){
//左孩子小于右孩子,则选取右孩子作为和父节点交换的结点
if (j+1<nSize && testArray[j]<testArray[j+1]){
j++;//记录最小结点位置
}
//选取的最大结点小于于顶级父节点,则退出调整过程
if (key >= testArray[j]){
break;
}
testArray[nParent] = testArray[j];//将较大的子节点放在父节点位置
nParent = j;//交换的子节点作为父节点进行下一次的堆调整
}
testArray[nParent] = key;//将最后调整的结点位置放置哨兵
}
调用代码:
int main()
{
using namespace Test;
using namespace MyAlgorithm;
using namespace std;
LogInfo<double> log = LogInfo<double>();
Sort<double> sortTest = Sort<double>();
log.ShowState("测试堆排序");
log.ShowLine();
log.ShowState("测试正序数组========================");
log.ShowLine();
TestDataCreator<double, N_MAX> testArray = TestDataCreator<double, N_MAX>(PositiveArray);
sortTest.HeapSort(testArray.GetArray(), testArray.GetSize());
log.ShowState("测试降序数组========================");
log.ShowLine();
testArray = TestDataCreator<double, N_MAX>(NegativeArray);
sortTest.HeapSort(testArray.GetArray(), testArray.GetSize());
log.ShowState("测试随机数组========================");
log.ShowLine();
testArray = TestDataCreator<double, N_MAX>(RandomArray);
sortTest.HeapSort(testArray.GetArray(), testArray.GetSize());
int nWait;
cin >> nWait;
return 0;
}
输出截图:
三. 遇到问题
1、最大堆最小堆从数学定义上很清晰,是怎么一种状态很模糊。
2、在实际编程中下标从0开始,而在定义中下标从1开始,这点在进行父节点子节点位置计算产生了问题。
3、优先创建了最小堆,由于平时都是升序排序,最小堆在升序输出上不具有适用性。
4、想办法进行最小堆到最大堆的转换思考,最后发现还不如直接建造最大堆。
5、建堆与输出都涉及堆调整,分散在多个过程,没有跟踪比较与移动次数。
四. 经验总结
1、最大堆可以实现升序排序,最小堆实现降序排序。
2、下标从0开始,则左孩子为2i-1,右孩子为2i。最后一个可能为非叶子结点为length/2-1。
3、从1开始,则用二进制编码根节点一位,第二层节点二位,第N级节点N位。对于K位二进制编码,第一位1表示根结点,第二位为0表示根结点的左孩子,为1表示右孩子,给定一个结点下标,即刻转换为二进制码从而得到从根结点到该结点的路径。
4、堆排序算法肯定是一种不稳定的算法。
5、包含N个元素的高度为log2(n),高度为h的堆最多有N/2^(h+1)个结点。不用结点的建堆过程与高度有关,在一个高度为h上的结点进行维护,则时间复杂度而O(h),总的时间复杂度就为Sum(h=0…log2(n))(n/2^(h+1))*O(h)=O(n*sum(h=0…log2(n))(h/2^h)=O(n*2)=O(n),因此在线性时间复杂度内可以将序列建成堆。
6、每次调整堆的时候,时间复杂度为高度即log2(N),N-1,排序输出时N-1调整堆,可以看作堆排序的时间复杂度为N*Log2(N)。最好最坏情况也是如此。
7、插入方式建堆的复杂度为Nlog2(N),和输出是反向的。高度K,从根到叶的筛选,至多比较2*(K-1)次,移动K-1次。
五. 后续计划
进行交换排序练习。
六. 参考资料
1、算法导论
2、八大算法排序
http://blog.youkuaiyun.com/wangyaninglm/article/details/44805715
3、建大顶堆和小顶堆及堆排序算法
http://blog.youkuaiyun.com/startwithdp/article/details/8800010
4、堆排序 Heap Sort
http://www.cnblogs.com/mengdd/archive/2012/11/30/2796845.html
本文详细介绍了堆排序的概念、建堆算法、源码实现及应用案例,并通过代码演示了如何利用堆排序对不同类型的数组进行排序。同时,文章还探讨了在实际编程中遇到的问题、解决方案及经验总结。
2333

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



