首页 > 编程语言 >常见的排序算法——堆排序(二)

常见的排序算法——堆排序(二)

时间:2024-07-20 11:07:35浏览次数:8  
标签:__ ... log int 堆排序 算法 time 排序

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

◆ 思想

堆的下沉操作中用到了昂贵的数据交换操作,此改动参考无交换的插入排序的思想,实现了减少数据交换的下沉操作。

先将要下沉的元素存放在临时空间中,再将下降过程中遇到的所有元素逐层上移,待找到下沉元素的目标位置后将其归位。这样做就能减少数据交换。

◆ 实现

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

// heap2.hxx

...

class Heap2
{

    ...
    
    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    sort(Array<_T> & a)
    {
        int N = a.size();
        for (int k = N/2; k >= 1; --k)
            __sink__(a, k, N);
        while (N > 1) {
            __exch__(a, 1, N);
            --N;
            __sink__(a, 1, N);
        }
    }
    
    ...
    
    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    __sink__(Array<_T> & a, int k, int n)
    {
        _T t = a[k-1];          // #1
        while (2*k <= n) {
            int j = 2*k;
            if (j < n && __less__(a[j-1], a[j])) ++j;
            if (!__less__(t, a[j-1])) break;
            a[k-1] = a[j-1];                    // #2
            k = j;
        }
        a[k-1] = t;         // #3
    }

    ...
    
    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-1];
        a[i-1] = a[j-1];
        a[j-1] = t;
    }

    ...

先将要下沉的元素存放在临时空间中(#1),再将下降过程中遇到的所有元素逐层上移(#2),待找到下沉元素的目标位置后将其归位(#3)。

◆ 性能

时间复杂度 空间复杂度 是否稳定
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();    // #1
    Stopwatch timer;
    Heap2::sort(a);                     // #2
    double time = timer.elapsed_time();
    assert(Heap2::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.007   2.33
      2048     0.016   2.29
      4096     0.035   2.19
      8192     0.077   2.20
     16384     0.168   2.18
     32768     0.364   2.17
     65536     0.790   2.17
    131072     1.712   2.17
    262144     3.700   2.16
    524288     7.967   2.15
   1048576    17.077   2.14
   2097152    36.424   2.13
   4194304    77.559   2.13
   8388608   164.999   2.13
  16777216   351.880   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/18312351

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

标签:__,...,log,int,堆排序,算法,time,排序
From: https://www.cnblogs.com/green-cnblogs/p/18312351

相关文章

  • 「代码随想录算法训练营」第十六天 | 二叉树 part6
    530.二叉搜索树的最小绝对差题目链接:https://leetcode.cn/problems/minimum-absolute-difference-in-bst/题目难度:简单文章讲解:https://programmercarl.com/0530.二叉搜索树的最小绝对差.html视频讲解:https://www.bilibili.com/video/BV1DD4y11779题目状态:通过思路:将二......
  • Flood Fill(洪水算法)通俗讲解
    ......
  • 快速排序quicksort
    #include<iostream>usingnamespacestd;intpartition(inta[],intlow,inthigh){ intpivot=a[low]; while(low<high) { while(low<high&&a[high]>=pivot)//先从high开始 high--; a[low]=a[high]; while(low<high......
  • 二路归并排序
    #include<iostream>usingnamespacestd;voidmerge(inta[],intlow,intmid,inthigh){ intn1=mid-low+1; intn2=high-mid; int*L=(int*)malloc(sizeof(int)*n1); int*R=(int*)malloc(sizeof(int)*n2); inti,j,k; for(i=0,k=low......
  • 插入排序 insertsort
    #include<iostream>usingnamespacestd;voidinsertsort(inta[],intn){ inti,j,key; for(i=1;i<n;i++) { if(a[i]<a[i-1]) { key=a[i]; for(j=i-1;j>=0&&a[j]>key;j--) a[j+1]=a[j]; a[j......
  • 数据结构_排序
    目录一、排序二、插入排序2.1直接插入排序2.2希尔排序三、选择排序3.1直接选择排序3.2堆排序四、交换排序4.1冒泡排序4.2快速排序五、归并排序六、排序算法分析总结一、排序排序:就是使一串记录序列,按照其中某个或某些关键字的大小,进行递增或递减的排列......
  • 算法第十一天:leetcode707.设计链表
    一、设计链表的题目描述与链接  707.设计链表的链接如下表所示,您可直接复制下面网址进入力扣学习,在观看下面的内容之前一定要先做一遍哦,这样才能印象深刻!https://leetcode.cn/problems/design-linked-list/https://leetcode.cn/problems/design-linked-list/题目描述:你......
  • 循环位移(2024“钉耙编程”中国大学生算法设计超级联赛(1))
    #include<bits/stdc++.h>#defineendl'\n'usingll=longlong;typedefunsignedlonglongull;usingnamespacestd;voidGordenGhost();constintN=4e6+7;constintP=131;ullp[N],an[N],bn[N];intn;voidinit(stringa,stringb){......
  • SciTech-BigDataAIML-Algorithm: Github的Hello 算法项目
    先记录一下,好不好再读:https://github.com/krahets/hello-algo关于本书本项目旨在打造一本开源免费、新手友好的数据结构与算法入门教程。全书采用动画图解,内容清晰易懂、学习曲线平滑,引导初学者探索数据结构与算法的知识地图。源代码可一键运行,帮助读者在练习中提升编程技能......
  • 2024“钉耙编程”中国大学生算法设计超级联赛(1)
    Preface唐完了的一集,被前中期题花式腐乳以至于中期一度被同校队伍打出\(n+3\)最后4h时堪堪写完8个题,最后1h冲刺众数那个题然后写一半方法假了直接下班当然还有个难绷的是传送那个题和我今年给新生拉的数据结构专题的一个题几乎一样,把代码拉来改下输入输入就过了,12min抢......