常见的排序算法——堆排序

news/2024/10/6 19:09:06/文章来源:https://www.cnblogs.com/green-cnblogs/p/18289928

本文记述了堆排序的基本思想和一份参考实现代码,并在说明了算法的性能后用随机数据进行了验证。

◆ 思想

J.W.J Williams 提出了堆排序的算法,该算法利用了二叉堆有序的性质,将排序的过程分为先构建堆再排序的两个阶段。

先构建堆。从当前待排序范围一半的位置开始向第一个位置扫描,用下沉操作处理每个位置的元素。每操作一个位置后,以该位置为根节点的子堆就是堆有序的。当扫描完范围的第一个位置后,整个待排序范围就达到了堆有序的状态,即完成了堆的构建。

再排序。将当前堆中的最大元素放到堆底的最后位置,即交换待排序范围的第一个位置的元素和最后一个位置的元素。然后将待排序范围的第一个位置至倒数第二个位置视为新的堆,对新的堆顶元素做下沉操作后,新堆也恢复到堆有序状态。重复以上操作,直到待排序范围只有一个元素为止,排序结束。

◆ 实现

排序代码采用《算法(第4版)》的“排序算法类模板”实现。(代码中涉及的基础类,如 Array,请参考算法文章中涉及的若干基础类的主要API)

// heap.hxx...class Heap
{...template<class _T,class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type>staticvoidsort(Array<_T> & a){int N = a.size();for (int k = N/2; k >= 1; --k)          // #1__sink__(a, k, N);while (N > 1) {__exch__(a, 1, N);              // #2--N;__sink__(a, 1, N);              // #3}}...template<class _T,class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type>staticvoid__sink__(Array<_T> & a, int k, int n){while (2*k <= n) {                      // #4int j = 2*k;if (j < n && __less__(a, j, j+1)) ++j;    // #5if (!__less__(a, k, j)) break;__exch__(a, k, j);k = j;}}...template<class _T,class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type>staticbool__less__(Array<_T> const& a, int i, int j){return a[i-1].compare_to(a[j-1]) < 0;       // #6}template<class _T,class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type>staticvoid__exch__(Array<_T> & a, int i, int j){_T t = a[i-1];a[i-1] = a[j-1];a[j-1] = t;}...

从当前待排序范围一半的位置开始向第一个位置扫描,用下沉操作达到堆有序的状态(#1)。将当前堆中的最大元素放到堆底的最后位置(#2)。然后对新的堆顶元素做下沉操作后,使新堆也恢复到堆有序状态(#3)。该算法用的是二叉堆(#4),所以某个节点与其两个子节点相比较后,决定是否继续下沉(#5)。将 '<' 改为 '>',即得到逆序的结果(#6)。

◆ 性能

时间复杂度 空间复杂度 是否稳定
N*log(N) 1

◆ 验证

测试代码采用《算法(第4版)》的倍率实验方案,用随机数据验证其正确性并获取时间复杂度数据。

// test.cpp...time_trial(int N)
{Array<Double> a(N);for (int i = 0; i < N; ++i) a[i] = Std_Random::random();    // #1Stopwatch timer;Heap::sort(a);                     // #2double time = timer.elapsed_time();assert(Heap::is_sorted(a));            // #3return time;
}...test(char * argv[])
{int T = std::stoi(argv[1]);          // #4double prev = time_trial(512);Std_Out::printf("%10s%10s%7s\n", "N", "Time", "Ratio");for (int i = 0, N = 1024; i < T; ++i, N += N) {            // #5double time = time_trial(N);Std_Out::printf("%10d%10.3f%7.2f\n", N, time, time/prev);   // #6prev = time;}
}...

用 [0,1) 之间的实数初始化待排序数组(#1),打开计时器后执行排序(#2),确保得到正确的排序结果(#3)。整个测试过程要执行 T 次排序(#4)。每次执行排序的数据规模都会翻倍(#5),并以上一次排序的时间为基础计算倍率(#6),

此测试在实验环境一中完成,

$ g++ -std=c++11 test.cpp std_out.cpp std_random.cpp stopwatch.cpp type_wrappers.cpp$ ./a.out 15N      Time  Ratio1024     0.009   2.252048     0.022   2.444096     0.048   2.188192     0.106   2.2116384     0.230   2.1732768     0.500   2.1765536     1.086   2.17131072     2.349   2.16262144     5.064   2.16524288    10.883   2.151048576    23.276   2.142097152    49.570   2.134194304   105.310   2.128388608   223.426   2.1216777216   474.822   2.13

可以看出,随着数据规模的成倍增长,排序所花费的时间将是上一次规模的 2.1? 倍,且在不断地变小。将数据反映到以 2 为底数的对数坐标系中,可以得到如下图像,

test_data

O(N*log(N)) 代表了线性对数级别复杂度下的理论排序时间,该行中的数据是以 Time 行的第一个数据为基数逐一乘 2 + 2/log(N) 后得到的结果(因为做的是倍率实验,所以乘 (2*N*log(2*N)) / (N*log(N)),化简得到 2 + 2/log(N),即乘 2+2/log(1024),2+2/log(2048),2+2/log(4096),... 2+2/log(16777216);因为是二叉堆,所以 log 的底数为 2)。

◆ 最后

完整的代码请参考 [gitee] cnblogs/18289928 。

写作过程中,笔者参考了《算法(第4版)》的堆排序、“排序算法类模板”和倍率实验。致作者 Sedgwick,Wayne 及译者谢路云。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/740782.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vulnhub - JIS-CTF

泡面机vulnhub - JIS-CTF 这个靶场一共有5个flag 信息收集 靶机ip:192.168.157.172 kali ip:192.168.157.161 nmap 192.168.157.0/24 nmap -sT --min-rate 10000 -p- 192.168.157.172 sudo nmap -sT -sV -sC -O -p22,80 192.168.157.172一眼看到robots.txt User-agent: * Dis…

暑假进度表

7.8 个人赛打的还可以,就是F题敲得太慢了,最后差十分钟做出来。 补了一个重要但是原来没注意到的一个知识点 \(01bfs\) ,做了四道相关题,将F题写进了双端队列的内容中,感觉非常不错的一题。

私有云盘-可道云-安装和使用和数据迁移

私有云盘是什么 随着云计算和移动办公大潮的到来,iPad、智能手机等家庭联网设备不断增多,以及搭载小容量SSD笔记本电脑的流行,能够跨平台分享的个人云服务需求不断增长;而今天的个人云服务也已经极大丰富,从2TB的百度网盘到商务人士中流行的Dropbox和Box个人云,不但免费,…

比赛获奖的武林秘籍:04 电子类比赛嵌入式开发快速必看的上手指南

本文主要介绍了电子类比赛中负责嵌入式开发同学的上手比赛的步骤、开发项目的流程和具体需要学习的内容,并结合自身比赛经历给出了相关建议。比赛获奖的武林秘籍:04 电子类比赛嵌入式开发快速必看的上手指南 摘要 本文主要介绍了电子类比赛中负责嵌入式开发同学的上手比赛的步…

线程饥饿问题——b2b - Thread starvation or clock leap detected (housekeeper delta=5h28m19s393ms972......

原因:在方法上配置了 @Async 注解进行异步执行,但是没有在主配置类上配置 @EnableAsync 启动异步执行。 修改前 修改后

SpringBoot项目启动,运行停留在标题处

详情: 原因:yml文件存在问题,比如:在切换生产环境和开发环境的配置文件时,yml名称写错,如下,图,此处多写了一个p。解决办法:修改为正确的配置文件,即可。

git合并代码方法

你合并代码用 merge 还是用 rebase ? macrozheng 2024年07月08日 14:10 江苏 1人听过以下文章来源于古时的风筝 ,作者风筝古时的风筝. 写代码是一种爱好,写文章是一种情怀。mall学习教程官网:macrozheng.com 你们平时合并代码的时候用 merge 还是 rebase? 我问了一圈,发现…

秒杀圣经(2):10Wqps秒杀,16大架构绝招,一文帮你秒变架构师

文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备 免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,…

写入多维表格失败

原因一: 没有获取到正确的table_id 解决办法将多维表格在浏览器打开,url的这部分就是table_id,如下图。由于模板中的设置多维表格模块需要输入网址,所以建议将网址和table_id一起复制

【算法篇】KMP算法,一种高效的字符串匹配算法

我们今天了解一个字符串匹配算法-KMP算法,内容难度相对来说较高,建议先收藏再细品!!! KMP算法我们今天了解一个字符串匹配算法-KMP算法,内容难度相对来说较高,建议先收藏再细品!!!KMP算法的基本概念 KMP算法是一种高效的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.…

飞书集成平台,流程触发器触发,没有运行日志

原因:飞书平台自动关联自建应用时,回调地址生成存在问题。解决办法:将飞书的触发器回调地址的内容,复制到关联的自建应用的事件回调地址中

NOIP2024模拟1

NOIP2024模拟1\(T1\) GHzoj 3752. 分糖果 \(100pts\)设最终答案中有 \(a\) 个小组中的小朋友的糖数 \(\mod 3\) 均等于 \(1\) , \(b\) 个小组中的小朋友的糖数 \(\mod 3\) 互不相等, \(c\) 个小组中的小朋友的糖数 \(\mod 3\) 均等于 \(0\) , \(d\) 个小组中的小朋友的糖数 …