常见的排序算法——快速排序(四)

news/2024/10/1 12:11:30

本文记述了 J.Bently 和 D.Mcllroy 的快速三向切分快速排序的基本思想和一份参考实现代码,并在说明了算法的性能后用随机数据进行了验证。

◆ 思想

对比快速排序、快速排序(二)和快速排序(三)可以发现,对于随机数据而言,E.W.Dijkstra 的三向切分快速排序的性能要慢于标准快速排序以及改进版本,“因为在数组中重复元素不多的普通情况下它比标准的二分法使用了很多次交换。 J.Bently 和 D.Mcllroy 找到一个聪明的方法解决了这个问题,使得三向切分快速排序比归并排序和其它排序方法在包括重复元素很多的实际应用中更快。...... J.Bently 和 D.Mcllroy 用将重复元素放置于子数组两端的方式实现一个信息量最优的排序算法。”(引《算法(第4版)》)

三向切分快速排序是将待排序范围排序为小于、等于和大于切分元素的三部分。如下所示,

*-----------------------------------------------------------------*
|     < 切分元素       |       = 切分元素      |       > 切分元素     |
*-----------------------------------------------------------------*

J.Bently 和 D.Mcllroy 的解法思想是,维护两个指针 p 和 q ,使得从待排序范围开始位置(lo)到 p-1 、q+1 到待排序范围结束位置(hi)的所有元素都等于切分元素;维护另外两个指针 i 和 j ,使得从 p 到 i-1 的所有元素都小于切分元素,从 j+1 到 q 的所有元素都大于切分元素。从 i 到 j 的所有元素都还未确定与切分元素的大小关系。如下所示,

*----------------------------------------------------------------------------------*
|    = 切分元素   |    < 切分元素   |     未确定    |    > 切分元素    |    = 切分元素   |
*----------------------------------------------------------------------------------*^                ^               ^            ^                   ^              ^lo               p               i            j                   q             hi

使用指针 i 从左到右、指针 j 从右到左遍历待排序范围,

  • 如果 i 所指的元素大于切分元素,停止向右扫描;
  • 如果 i 所指的元素等于切分元素,将其与 p 所指的元素交换,将 p 和 i 都加 1。
  • 如果 i 所指的元素小于切分元素,将 i 加 1;
  • 如果 j 所指的元素小于切分元素,停止向左扫描;
  • 如果 j 所指的元素等于切分元素,将其与 q 所指的元素交换,将 q 和 j 都减 1。
  • 如果 j 所指的元素大于切分元素,将 j 减 1;

这样就逐步缩小了未确定的范围,保证了遍历当前范围结束。然后将与切分元素相等的所有元素都放在中间,小于切分元素的放在当前范围左端,大于切分元素的放在当前范围右端。最后递归处理从待排序范围开始位置(lo)到 j 的所有元素,以及从 i 到待排序范围结束位置(hi)的所有元素。

◆ 实现

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

// quick4.hxx...class Quick4
{...template<class _T,class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type>staticvoidsort(Array<_T> & a){Std_Random::shuffle(a);__sort__(a, 0, a.size()-1);}...template<class _T,class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type>staticvoid__sort__(Array<_T> & a, int lo, int hi){if (hi <= lo) return;_T v = a[lo];int i = lo+1, j = hi, p = lo+1, q = hi;         // #1while (true) {while (i <= j) {int cmp = a[i].compare_to(v);               // #2if (cmp  > 0) break;if (cmp == 0) __exch__(a, p++, i);++i;}while (i < j) {int cmp = a[j].compare_to(v);               // #3if (cmp  < 0) break;if (cmp == 0) __exch__(a, q--, j);--j;}if (i >= j) break;                              // #4__exch__(a, i++, j--);}j = i-1;for (int k = lo; k < p; ++k) __exch__(a, k, j--);       // #5for (int k = hi; k > q; --k) __exch__(a, k, i++);__sort__(a, lo, j);                                     // #6__sort__(a, i, hi);}...template<class _T,class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type>staticbool__less__(_T const& v, _T const& w){return v.compare_to(w) < 0;}...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];a[i] = a[j];a[j] = t;}...

使用指针 i 从左到右、指针 j 从右到左遍历待排序范围(#1)。比较 i 所指的元素与切分元素的大小关系(#2),大于则停止扫描;等于则将其与 p 所指的元素交换,将 p 和 i 都加 1;小于则将 i 加 1。比较 j 所指的元素与切分元素的大小关系(#3),小于则停止向左扫描;等于则将其与 q 所指的元素交换,将 q 和 j 都减 1;大于则将 j 减 1。当扫描指针相遇后,当前待排序范围的排序过程结束(#4)。然后将与切分元素相等的所有元素都放在中间,小于切分元素的放在当前范围左端,大于切分元素的放在当前范围右端(#5)。最后递归处理从待排序范围开始位置(lo)到 j 的所有元素,以及从 i 到待排序范围结束位置(hi)的所有元素(#6)。

◆ 性能

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

◆ 验证

测试代码采用《算法(第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;Quick4::sort(a);                     // #2double time = timer.elapsed_time();assert(Quick4::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.006   2.002048     0.014   2.334096     0.029   2.078192     0.061   2.1016384     0.127   2.0832768     0.271   2.1365536     0.564   2.08131072     1.196   2.12262144     2.487   2.08524288     5.128   2.061048576    11.086   2.162097152    22.259   2.014194304    46.541   2.098388608    95.625   2.0516777216   200.314   2.09

可以看出,随着数据规模的成倍增长,排序所花费的时间将是上一次规模的 2.0? 倍,且在不断波动地变小。将数据反映到以 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/18252468 。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

揭秘10亿+高并发应用如何实现高效稳定的开发和运维

揭秘10亿+高并发应用如何实现高效稳定的开发和运维,无论你是云原生技术的新手,还是正在寻求优化方案的资深开发者,都能在此找到答案。本文分享自华为云社区《DTSE Tech Talk | 第60期:构筑云原生时代的应用稳定性》,作者: 华为云社区精选。 本期直播主题是《构筑云原生时…

导航栏-棱角社区

网站的地址:[~]#棱角 ::Edge.Forum* (ywhack.com) 可以看到集成了很多方便的工具 第一个是各种产品默认密码查询的工具 第二个是信息收集的各种在线工具,并且做好了分类,想要查询什么信息很快可以查到用什么工具,这里面的工具很多,同类型的可以选择一两个使用,然后添加到…

OSI七层网络模型和TCP/IP四(五)层模型对比理解

转载自:https://www.cnblogs.com/wzh2010/p/18133011

时间设置不合适会导致matlab波形异常

w1=2*pi*60; w2=2*pi*10; t=0:0.01:0.2;plot(t,sin(w1*t)); hold plot(t,sin(w2*t));t=0:0.001:0.2;>> plot(t,sin(w1*t)); hold >> plot(t,sin(w2*t));时间间隔为0.01时的图:时间间隔为0.001时的图:虽然依旧不是很精确,但是基本算是有形了吧,和采样率应该是一…

如何使用JavaScript实现在线Excel附件的上传与下载?

前言 在本地使用Excel时,经常会有需要在Excel中添加一些附件文件的需求,例如在Excel中附带一些Word,CAD图等等。同样的,类比到Web端,现在很多人用的在线Excel是否也可以像本地一样实现附件文件的操作呢?答案是肯定的,不过和本地不同的是,Web端不会直接打开附件,而是使…

LeetCode 2055. Plates Between Candles

原题链接在这里:https://leetcode.com/problems/plates-between-candles/description/ 题目: There is a long table with a line of plates and candles arranged on top of it. You are given a 0-indexed string s consisting of characters * and | only, where a * repr…

案例分享-丢失的请求头

拍摄于富平中华郡背景 今天组内一个小哥找我协助看一个问题,现象是他开放了一个Api给第三方调用,需要在http中传递一个名字为access_token的头,但是发布到测试环境以后却怎么也获取不到这个头,本地调试是没有问题的,希望协助看看。 排查 http传递头还会出问题,这都是很成…

Mac常用sh文件

压缩多个文件夹到一个ZIP #!/bin/bash# 定义目标目录 target_dir="/Users/yuqiu/****/" output_dir="$target_dir/ZIPDIR"# 获取当前日期 current_date=$(date +"%Y-%m-%d")# 定义压缩文件名 zip_filename="XX_${current_date}.zip"# …