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

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

时间:2024-05-23 12:40:20浏览次数:20  
标签:__ 归并 int 算法 hi time 排序

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

◆ 思想

自然的归并排序是自底向上的。先从第一个元素开始找到一个有序的子范围,然后从紧接着的后面元素开始找到另一个有序的子范围,将这两个子范围归并成一个大的有序子范围。接着找到下一个有序子范围,将它与前面的有序子范围归并。重复以上步骤,直到所有元素都被归并,即得到完整有序的结果。

◆ 实现

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

// merge5.hxx

...

class Merge5
{

    ...
    
    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();
        Array<_T> aux = Array<_T>(N);
        for (int k = 0; k < N; ++k)
            aux[k] = a[k];
        int lo = 0, mi = 0, hi = 1;
        while (hi < N) {
            __merge__(a, lo, mi, hi, aux);            // #1
            mi = hi++;
            while (hi < N-1 && __less__(a[hi], a[hi+1])) ++hi;       // #2
        }
    }
    
    ...

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

    ...

先从 [0,0] 和 [1,1] 这两个有序子范围开始,将两个有序子范围归并(#1)成一个大的有序子范围,然后找到下一个有序子范围(#2),再把这两个有序子范围进行归并(#1)。此处的归并使用了归并排序(四)中的快速归并方案(#3)。重复以上步骤,直到所有元素都被归并,即得到完整有序的结果。将 '<' 改为 '>',即得到逆序的结果(#4)。

◆ 性能

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

时间复杂度 空间复杂度 是否稳定
N^2 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;
    Merge5::sort(a);                     // #2
    double time = timer.elapsed_time();
    assert(Merge5::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 8
    
         N      Time  Ratio
      1024     0.201   4.02
      2048     0.819   4.07
      4096     3.267   3.99
      8192    12.630   3.87
     16384    51.466   4.07
     32768   205.775   4.00
     65536   836.357   4.06
    131072  3312.554   3.96

可以看出,随着数据规模的成倍增长,排序所花费的时间将是上一次规模的 4 倍。将数据反映到以 2 为底数的对数坐标系中,可以得到如下图像,

test_data

O(N^2) 代表了平方级别复杂度下的理论排序时间,该行中的数据是以 Time 行的第一个数据为基数逐一乘 4 后得到的结果(因为做的是倍率实验,所以乘 (2)^2 即 4)。

◆ 最后

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

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

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

相关文章

  • 基于双向堆栈的二叉树双向迭代算法
    前言之前一直在研究avl树的迭代算法。我参考了C++标准库map的实现,发现他们在树节点上使用了parent指针+一个状态标志位的形式,去实现动态迭代。但是我不想用parent指针,因为觉得会增加调整指针的时间还有浪费存储空间。于是,在我的不屑努力下,终于,找到了一种基于堆栈实现的双向迭代......
  • Windows server高危漏洞 - 目标主机使用了不受支持的SSL加密算法
    系统扫描出高危漏洞:目标主机使用了不受支持的SSL加密算法。 修复过程:使用IISCrypto工具,下载地址:NartacSoftware-IISCrypto1.打开工具,点击“BestPractices”后,会自动反选掉一些选项,如下图,根据解决建议,手动反选掉TLS1.0和TLS1.1。然后Apply,重启服务器。 2. 服务......
  • 分布式任务调度内的 MySQL 分页查询优化 等值在前,排序在中间,范围在最后
    分布式任务调度内的MySQL分页查询优化https://mp.weixin.qq.com/s/VhSzxYIRv83T3D3JD4cORg三、优化方案 3.1优化方案确定 当前SQL执行计划以主键进行顺序遍历,是一个范围扫描,有点像在一片很大的居民区按照序号挨家挨户寻找一些特定的人一样,比较简单也比较低效。 既然......
  • 基于BP神经网络的QPSK解调算法matlab性能仿真
    1.算法运行效果图预览  2.算法运行软件版本matlab2022a 3.算法理论概述       QPSK(QuadraturePhaseShiftKeying)是一种常见的数字调制方式,通过载波的四种相位状态来传输两比特信息。在接收端,准确解调出原始数据成为关键任务。传统的方法如相干解调虽有效但......
  • 代码随想录算法训练营第第15天 | 层序遍历10道题 、226.翻转二叉树 、101. 对称二叉树
    层序遍历看完本篇可以一口气刷十道题,试一试,层序遍历并不难,大家可以很快刷了十道题。题目链接/文章讲解/视频讲解:https://programmercarl.com/0102.二叉树的层序遍历.html层序遍历,10道题,一一通过,比较简单226.翻转二叉树(优先掌握递归)这道题目一些做过的同学理解的也不够深......
  • AI水位识别/水位超标算法在水利工程与防洪灾害预警中的应用实践
    以近年来全国地表水资源供水量数据为例,从2020年的4792.3亿立方米到2022年的4994.2亿立方米,供水量呈现出逐年上升的趋势。这样的数据变化,反映了水资源需求的增长,同时也意味着防洪压力的加大。在此背景下,水位识别算法能够实时监测水域水位变化,为防洪决策提供及时、准确的数据支持,从......
  • 并行计算排序
    #include<iostream>#include<vector>#include<chrono>#include<algorithm>#include<cstdint>#include<cstdlib>#include<omp.h>//StructuretoholddatastructData{intid;intage;floatweig......
  • LLM-文心一言:FOR、RBM、FST压缩算法
    FOR、RBM(RoaringBitmap)和FST(FiniteStateTransducer)是三种不同的压缩算法,它们各自具有不同的特点和用途。FOR压缩算法:FOR(FrameOfReference)压缩算法主要用于处理整数序列的压缩。它通过计算序列中相邻元素的差值(增量),并将这些差值存储起来,而不是直接存储原始整数。这样可以显......
  • 逗号分开的字符串,统计个数从高到底排序
    usesWinapi.Windows,Winapi.Messages,System.SysUtils,System.Variants,System.Classes,Vcl.Graphics,System.RegularExpressions, functionCompareStrings(List:TStringList;Index1,Index2:Integer):Integer;beginResult:=StrToInt(List.ValueF......
  • 代码随想录算法训练营第一天|704,34,35(二分查找),27(双指针)
    二分查找1.使用条件:数组,升序,值不唯一。2.时间复杂度O(logn)可分为左闭右闭,左闭右开两种区间类型来求解。左闭右闭:left=0,right=nums.Length-1,while(left<=right),right=middle-1.左闭右开:left=0,right=nums.Length,while(left<right),right=middle.......