首页 > 编程语言 >常见的排序算法——快速排序(三)

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

时间:2024-06-12 15:23:12浏览次数:29  
标签:__ 常见 log 元素 切分 算法 gt 排序

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

◆ 思想

“在有大量重复元素的情况下,快速排序的递归性会使元素全部重复的子数组经常出现。这就有很大的改进潜力,将当前实现的线性对数级的性能提高到线性级别。”(引《算法(第4版)》

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

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

Dijkstra 的解法思想是,维护一个指针 lt ,使得从待排序范围开始位置到 lt-1 位置的所有元素都小于切分元素;维护一个指针 gt ,使得从 gt+1 到待排序范围结束位置的所有元素都大于切分元素;维护一个指针 i ,使得从 lt 到 i-1 中的所有元素都等于切分元素,从 i 到 gt 的所有元素都还未确定与切分元素的大小关系。如下所示,

*-----------------------------------------------------------------*
|    < 切分元素   |   = 切分元素   |    未确定    |     > 切分元素     |
*-----------------------------------------------------------------*
                 ^               ^            ^      
                 lt              i           gt

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

  • 如果 i 所指的元素小于切分元素,将其与 lt 所指的元素交换,将 lt 和 i 都加 1;
  • 如果 i 所指的元素大于切分元素,将其与 gt 所指的元素交换,将 gt 减 1;
  • 如果 i 所指的元素等于于切分元素,将 i 加 1。

这样就逐步缩小了未确定的范围,保证了遍历当前范围结束。然后递归处理从待排序范围开始位置到 lt-1 位置的所有元素,以及从 gt+1 到待排序范围结束位置的所有元素。

◆ 实现

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

// quick3.hxx

...

class Quick3
{

    ...
    
    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    sort(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
    >
    static
    void
    __sort__(Array<_T> & a, int lo, int hi)
    {
        if (hi <= lo) return;
        int lt = lo, i = lo+1, gt = hi;
        _T v = a[lo];
        while (i <= gt) {                             // #1
            int cmp = a[i].compare_to(v);
            if      (cmp < 0) __exch__(a, lt++, i++);    // #2
            else if (cmp > 0) __exch__(a, i, gt--);      // #3
            else              i++;                       // #4
        }
        __sort__(a, lo, lt-1);             // #5
        __sort__(a, gt+1, hi);
    }

    ...

    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    bool
    __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
    >
    static
    void
    __exch__(Array<_T> & a, int i, int j)
    {
        _T t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    
    ...

使用指针 i 遍历待排序范围,直到无未确定大小关系的元素为止(#1)。如果 i 所指的元素小于切分元素,将其与 lt 所指的元素交换,将 lt 和 i 都加 1(#2);如果 i 所指的元素大于切分元素,将其与 gt 所指的元素交换,将 gt 减 1(#3);如果 i 所指的元素等于于切分元素,将 i 加 1(#4)。然后递归处理从待排序范围开始位置到 lt-1 位置的所有元素,以及从 gt+1 到待排序范围结束位置的所有元素(#5)。

◆ 性能

时间复杂度 空间复杂度 是否稳定
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();    // #1
    Stopwatch timer;
    Quick3::sort(a);                     // #2
    double time = timer.elapsed_time();
    assert(Quick3::is_sorted(a));            // #3
    return time;
}

...

test(char * argv[])
{
    int T = std::stoi(argv[1]);          // #4
    double 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) {            // #5
        double time = time_trial(N);
        Std_Out::printf("%10d%10.3f%7.2f\n", N, time, time/prev);   // #6
        prev = 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 15

         N      Time  Ratio
      1024     0.011   2.75
      2048     0.027   2.45
      4096     0.053   1.96
      8192     0.123   2.32
     16384     0.247   2.01
     32768     0.580   2.35
     65536     1.138   1.96
    131072     2.489   2.19
    262144     5.077   2.04
    524288    10.794   2.13
   1048576    23.094   2.14
   2097152    47.949   2.08
   4194304    99.554   2.08
   8388608   209.597   2.11
  16777216   451.508   2.15

可以看出,随着数据规模的成倍增长,排序所花费的时间将是上一次规模的 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/18243804

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

标签:__,常见,log,元素,切分,算法,gt,排序
From: https://www.cnblogs.com/green-cnblogs/p/18243804

相关文章

  • 细胞计数算法 —— 软件与平台实现
    细胞计数算法(自动识别细胞并统计存活)——软件与平台实现结果展示项目介绍项目背景算法详解结果展示原图识别效果图项目介绍项目分为软件和平台双形态。软件可发送邮件至[email protected]处获取,平台可经由网址http://60.204.154.12访问使用。项目以霍......
  • BIOS 编辑和修改的需求;编辑和修改 BIOS 的工具。以下是几款常见的同类工具
    AwardBIOSEditor是一种用于编辑和修改基于AwardBIOS(BasicInput/OutputSystem)的工具。BIOS是计算机主板上的一个固件,它在计算机启动过程中扮演关键角色,负责硬件初始化以及操作系统启动前的准备工作。特点与功能:BIOS修改: AwardBIOSEditor允许用户查看和修改BIOS......
  • 算法01 递推算法及相关问题详解
    目录递推的概念训练:斐波那契数列解析参考代码训练:上台阶参考代码训练:信封解析参考代码递推的概念递推是一种处理问题的重要方法。递推通过对问题的分析,找到问题相邻项之间的关系(递推式),从起点出发(首项或者末项)然后使用循环不断地迭代,得到最后需要的结果。训练:斐波......
  • 常见的WAF攻击类型
    SQL注入、跨站脚本攻击、网页木马上传、命令/代码注入、文件包含、敏感文件访问、第三方应用漏洞攻击、CC攻击、恶意爬虫扫描、跨站请求伪造等攻击,保护Web服务安全稳定。个人觉得:waf攻击的防御具有事后性,除非一些已经成熟的攻击类型和防御办法可以通过预制规则的方式建立起来。......
  • c语言实现密码学算法应用
    一实验目的   1、掌握对称密钥密码体制的基本概念;   2、掌握对称密钥密码体制DES加密/解密的工作原理;   3、掌握非对称密码算法RSA加密/解密的基本原;   4、通过用DES和RSA算法对实际的数据进行加密/解密运算深刻理解加密算法原理。二实验内容   根据给......
  • 十种排序方法
    目录1.冒泡排序(BubbleSort)代码实现2.选择排序(SelectionSort)代码实现3.插入排序(InsertionSort)4.希尔排序(ShellSort)代码实现5.快速排序(QuickSort)代码实现6.归并排序(MergeSort)代码实现7.堆排序(HeapSort)代码实现8.计数排序(CountingSort)代码实现9.桶排序(BucketSor......
  • 【代码+详解】算法题 : 金银岛
    ❗❗❗必看:下列题我全部都使用Java语言写的,并且均可以提交成功,获得Accepted结果的.如果代码和详解看了之后,对答案有任何疑问,都可以在评论区提出来,我都会一个一个回答.❗❗❗感谢大家的支持,如果喜欢我的博客,关注点赞收藏评论一波,非常感谢!!!文章目录......
  • 常见的数据寻址方式
    寄存器间接寻址:想象一下,你有一个信使(寄存器),他知道你想要的东西(操作数)放在哪里。你告诉信使去拿,但他得先看看地址本(寄存器里的地址信息)才知道东西具体在哪。这种方式挺快的,但信使可能要跑几趟,因为他得先查地址本,再去拿东西。相对寻址:这就像是你告诉信使,从当前位置(程序计数器......
  • 【近邻算法】近邻算法详解——深入理解K-近邻(KNN)
    目录1引言2算法基础2.1核心原理2.2算法步骤3关键参数与优化3.1K值选择3.2距离度量4优缺点分析4.1优点4.2缺点5改进策略6应用案例深度解析:K-近邻算法在客户细分中的应用6.1引言6.2数据准备与预处理6.3特征选择与编码6.4K-近邻算法应用6.5模型......
  • 一种改进盲解卷积算法在旋转机械故障诊断中的应用(MATLAB)
    滚动轴承故障形成后,故障区与其他零部件表面接触将产生循环平稳的瞬态脉冲。由于受到系统传递函数、轴转频和环境噪声的干扰,故障脉冲特征受到大幅衰减,在测得信号中表现十分微弱甚至完全不可见。盲解卷积算法通过搜索一个最优的有限脉冲响应滤波器来降低信号传输路径、轴转频和环......