首页 > 编程语言 >常见的排序算法——归并排序(四)

常见的排序算法——归并排序(四)

时间:2024-05-20 11:52:28浏览次数:38  
标签:__ 归并 log int lo 算法 time 排序

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

◆ 思想

本文实现了《算法(第4版)》书中提到的 2 项改进和练习题 2.2.10。

  • 对小规模子数组使用插入排序。因为递归会使小规模问题中方法的调用过于频繁,所以改进对它们的处理方法就能改进整个算法。
  • 测试数组是否已经有序。任意有序的子数组算法的运行时间就变成线性的了。
  • 快速归并。在 merge() 函数中,按降序将待排序数组的后半部分复制到辅助数组中,然后将其归并到待排序数组中。这样就可以去掉内循环中检测某半边是否用尽的代码。

◆ 实现

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

// merge4.hxx

...

class Merge4
{

    ...
    
    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    sort(Array<_T> & a)
    {
        Array<_T> aux = Array<_T>(a.size());
        __sort__(a, 0, a.size()-1, aux);
    }
    
    ...

    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, Array<_T> & aux)
    {
        if (hi - lo <= 15) {                            // #1
            for (int i = lo+1; i <= hi; ++i)
                for (int j = i; j > lo && __less__(a[j], a[j-1]); --j)
                    __exch__(a, j, j-1);
        } else {
            int mi = lo + (hi - lo) / 2;
            __sort__(a, lo, mi, aux);
            __sort__(a, mi+1, hi, aux);
            if (__less__(a[mi], a[mi+1]))               // #2
                for (int i = lo; i <= hi; ++i)
                    aux[i] = a[i];
            else
                __merge__(a, lo, mi, hi, aux);
        }
    }
    
    ...

    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    __merge__(Array<_T> & a, int lo, int mi, int hi, Array<_T> & aux)
    {
        int i = lo, j = hi;
        for (int k = lo; k <= mi; ++k)
            aux[k] = a[k];
        for (int k = mi+1; k <= hi; ++k)               // #3
            aux[k] = a[hi - (k - (mi+1))];
        for (int k = lo; i <= j; ++k)                     // #4
            if (__less__(aux[i], aux[j])) a[k] = aux[i++];
            else                          a[k] = aux[j--];
    }

    ...
    
    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;         // #5
    }

    ...

当待排序的子数组规模较小时,采用插入排序(#1)。测试数组是否已经有序,可确保有序的子数组算法的运行时间变成线性的(#2)。按降序将待排序数组的后半部分复制到辅助数组中(#3),然后将其归并到待排序数组中(#4)。将 '<' 改为 '>',即得到逆序的结果(#5)。

◆ 性能

对 __merge()__ 函数的改进,导致了不稳定的归并。

时间复杂度 空间复杂度 是否稳定
N*log(N) 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;
    Merge4::sort(a);                     // #2
    double time = timer.elapsed_time();
    assert(Merge4::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.008   2.67
      2048     0.018   2.25
      4096     0.040   2.22
      8192     0.087   2.17
     16384     0.187   2.15
     32768     0.401   2.14
     65536     0.855   2.13
    131072     1.815   2.12
    262144     3.837   2.11
    524288     8.097   2.11
   1048576    17.023   2.10
   2097152    35.708   2.10
   4194304    73.782   2.07
   8388608   153.411   2.08
  16777216   319.890   2.09

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

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

标签:__,归并,log,int,lo,算法,time,排序
From: https://www.cnblogs.com/green-cnblogs/p/18201378

相关文章

  • 食物识别系统Python+深度学习人工智能+TensorFlow+卷积神经网络算法模型
    一、介绍食物识别系统。该项目通过构建包含11种常见食物类别(包括'Bread','Dairyproduct','Dessert','Egg','Friedfood','Meat','Noodles-Pasta','Rice','Seafood','Soup','Vegeta......
  • 省选阶段算法学习笔记
    一、高级数据结构计划时间:5.20-5.22这个板块欠的帐不多,但是介于它板子太多了,估计复习起来比较麻烦,就三天。这个可能会提前完成。link二、进阶动态规划计划时间:5.23-5.25这个板块不但总结没写,还忘得有点厉害???所以花三天吧。三、字符串算法计划时间:5.26-5.27这个板块总结写......
  • 国密算法测试
    点击查看代码#!/usr/bin/python#encoding:utf-8importtimeimportbase64fromgmsslimportsm2,sm4importcodecsSM2_PRIVATE_KEY='00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'SM2_PUBLIC_KEY='B9C9A6E04E9C91F7BA880429......
  • Python 实现任意多边形的最大内切圆算法_任意多边形最大内切圆算法
    CSDN搬家失败,手动导出markdown后再导入博客园参考Matlab计算轮廓内切圆初衷是为了求裂缝的最大宽度![[output/attachments/5ecf17abcb54aaa4fb35b00c3f243f32_MD5.png]]直接上代码importrandomimportcv2importmathimportnumpyasnpfromnumpy.maimportcos,......
  • 手写Word2vec算法实现
    1.语料下载:https://dumps.wikimedia.org/zhwiki/latest/zhwiki-latest-pages-articles.xml.bz2【中文维基百科语料】2.语料处理(1)提取数据集的文本下载的数据集无法直接使用,需要提取出文本信息。安装python库:pipinstallnumpypipinstallscipypipinstallgensimp......
  • 二分图的最大匹配(匈牙利算法)代码
    二分图的最大匹配代码#include<bits/stdc++.h>usingnamespacestd;constintN=505,M=100005;inth[N],e[M],ne[M],idx;intmatch[N];boolst[N];intn1,n2,m;voidadd(inta,intb){e[idx]=b;//e[idx]存放的是第idx条边的终点ne[idx]=h......
  • m基于BP译码算法的LDPC编译码matlab误码率仿真,对比不同的码长
    1.算法仿真效果matlab2022a仿真结果如下:   2.算法涉及理论知识概要       低密度奇偶校验码(Low-DensityParity-Check,LDPC)是一种高效的前向纠错码,因其优越的纠错性能和近似香农限的接近程度而广泛应用于现代通信系统中。LDPC码的编译码算法众多,其中BeliefProp......
  • 代码随想录算法训练营第十一天 | 20.有效的括号 1047.删除字符串中的所有相邻 重复项
    20.有效的括号题目链接文章讲解视频讲解思路:遍历字符串,如果栈不为空,则进行匹配   如果匹配则出栈,否则入栈   如果栈为空,直接入栈   遍历结束后栈为空则说明全部匹配,否则没有全部匹配classSolution{public:boolisValid(strings){stack<cha......
  • 寻路算法 Pathfinding
    目录我该使用哪种算法?BreadthFirstSearch(BFS)Dijkstra’sAlgorithmGreedyBestFirstSearchA*Algorithm学UGUI的一般使用方法,然后在画grid,除了画热力图之外,还开始了解用于处理寻路的算法A*寻路算法是图搜索算法,所以我打算不用Unity自带的寻路组件,自己简单的实现一......
  • 代码随想录算法训练营第第11天 | 20. 有效的括号 、1047. 删除字符串中的所有相邻重
    今天的题主要是关于栈的,比较简单,一次性过20.有效的括号讲完了栈实现队列,队列实现栈,接下来就是栈的经典应用了。大家先自己思考一下有哪些不匹配的场景,在看视频我讲的都有哪些场景,落实到代码其实就容易很多了。题目链接/文章讲解/视频讲解:https://programmercarl.com/0020.......