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

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

◆ 思想

归并排序归并排序(二)归并排序(三)归并排序(四)中记述的归并排序,都是把待排序范围分成两个部分分别排序的。而多向归并排序是把待排序范围分为 K 个部分,把它们分别排序然后进行归并。

◆ 实现

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

// merge6.hxx

...

class Merge6
{

    ...
    
    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    sort(int K, Array<_T> & a)                     // #1
    {
        Array<_T> aux = Array<_T>(a.size());
        __sort__(K, 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__(int K, Array<_T> & a, int lo, int hi, Array<_T> & aux)
    {
        if (hi <= lo) return;
        int N = hi - lo + 1;
        Array<int> s(K), e(K);  // start and end indices for subarrays
        int sz = int(std::ceil(double(N) / K));
        for (int i = 0; i < K; ++i) {                                   // #2
            s[i] = lo + i * sz;
            e[i] = (s[i] + sz - 1 < hi) ? s[i] + sz-1 : hi;
        }
        for (int i = 0; i < K; ++i)                         // #3
            __sort__(K, a, s[i], e[i], aux);
        __merge__(K, a, s, e, aux);                         // #4
    }
    
    ...

    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    __merge__(int K, Array<_T> & a, Array<int> & s, Array<int> & e, Array<_T> & aux)
    {
        for (int i = s[0]; i <= e[K-1]; ++i)                 // #5
            aux[i] = a[i];
        for (int i = s[0], min; i <= e[K-1]; ++i) {
            min = s[0];
            for (int k = 0; k < K; ++k)
                if (s[k] <= e[k] && __less__(aux[s[k]], aux[min]))
                    min = s[k], ++s[k];
            a[i] = aux[min];                      // #6
        }
    }

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

    ...

向数 K 作为参数被传递给排序算法(#1)。在排序前,先计算每个子范围的开始和结束位置(#2),把它们分别排序(#3),然后把各个排序的结果归并起来(#4)。在归并前,先利用辅助空间临时存放 K 个子范围的元素(#5),再逐一找到 K 个子范围中的当前最小元素并将其放回到排序范围内(#6),直至 K 个子范围都归并完。将 '<' 改为 '>',即得到逆序的结果(#7)。

◆ 性能

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

◆ 验证

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

// test.cpp
    
...

time_trial(int K, int N)
{
    Array<Double> a(N);
    for (int i = 0; i < N; ++i) a[i] = Std_Random::random();    // #1
    Stopwatch timer;
    Merge6::sort(K, a);                     // #2
    double time = timer.elapsed_time();
    assert(Merge6::is_sorted(a));            // #3
    return time;
}

...

test(char * argv[])
{
    int T = std::stoi(argv[1]);          // #4
    Std_Out::printf("%10s%10s%10s%10s%10s%10s%10s%10s%10s%10s%10s\n", "N", "K = 2", "K = 3", "K = 4", "K = 5", "K = 6", "K = 7", "K = 8", "K = 9", "K = 10", "K = 11");
    for (int i = 0, N = 1024; i < T; ++i, N += N) {            // #5
        double time2  = time_trial(2,  N);
        double time3  = time_trial(3,  N);
        double time4  = time_trial(4,  N);
        double time5  = time_trial(5,  N);
        double time6  = time_trial(6,  N);
        double time7  = time_trial(7,  N);                 // #6
        double time8  = time_trial(8,  N);
        double time9  = time_trial(9,  N);
        double time10 = time_trial(10, N);
        double time11 = time_trial(11, N);
        Std_Out::printf("%10d%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f\n", N, time2, time3, time4, time5, time6, time7, time8, time9, time10, time11);
    }
}

...

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

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

$ g++ -std=c++11 test.cpp std_out.cpp std_random.cpp stopwatch.cpp type_wrappers.cpp

         N     K = 2     K = 3     K = 4     K = 5     K = 6     K = 7     K = 8     K = 9    K = 10    K = 11
      1024     0.028     0.023     0.018     0.022     0.018     0.021     0.025     0.025     0.026     0.020
      2048     0.060     0.043     0.047     0.040     0.050     0.039     0.044     0.049     0.052     0.059
      4096     0.128     0.104     0.082     0.097     0.089     0.107     0.085     0.093     0.099     0.107
      8192     0.272     0.232     0.214     0.198     0.219     0.195     0.240     0.252     0.198     0.213
     16384     0.575     0.428     0.380     0.458     0.423     0.381     0.426     0.484     0.547     0.571
     32768     1.208     0.956     0.951     0.898     0.814     1.037     0.833     0.932     1.009     1.114
     65536     2.541     2.146     1.707     1.693     2.019     1.881     2.267     2.419     1.984     2.146
    131072     5.323     4.072     4.192     4.207     4.025     4.567     4.145     4.614     5.127     4.289
    262144    11.132     9.178     7.587     7.649     7.590     8.870     8.134     8.823     9.945    11.181
    524288    23.245    17.141    18.181    18.439    18.972    17.017    20.447    17.565    18.952    20.757
   1048576    48.281    39.075    33.000    35.410    34.756    40.479    38.386    44.446    46.330    40.997
   2097152   100.685    84.436    78.072    80.730    82.781    80.011    74.419    82.705    90.530   101.835
   4194304   208.772   158.872   142.965   157.102   159.215   152.687   186.003   160.461   178.676   196.547
   8388608   433.488   352.724   338.606   303.316   309.389   364.163   350.672   398.539   352.995   387.393
  16777216   898.020   734.325   618.355   693.583   738.794   709.554   684.961   755.231   861.721   764.983

在给定的 N 下,将 K = 2, 3, ..., 11 对应的时间按照从短到长的顺序排名,得到下表中黑粗线上边的排名数据。然后在针对某个 K 下的所有排名,计算平均数、中位数和众数,得到下表中黑粗线下边的结果。

test_data

根据这些结果可知,对于随机数据而言,当 K = 4 时多向归并表现最佳。

◆ 最后

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

查看性能对比,了解此算法与其它排序算法的相似性和差异性。

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

posted @ 2024-05-25 09:14  green-cnblogs  阅读(4)  评论(0编辑  收藏  举报