首页 > 编程语言 >【算法】归并排序算法

【算法】归并排序算法

时间:2023-09-24 22:24:06浏览次数:75  
标签:归并 -- 算法 nbsp arr1 排序 arr2

归并排序



归并排序的思想

归并排序运用了典型的分治策略,是一种稳定的排序算法,其时间复杂度为 \(O(nlogn)\) ,空间复杂度为 \(O(n)\)。
分治的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。分治策略一般适用于解决一个问题的多个实例,而且这些实例之间有许多共通的特征。很多递归算法都包含着分治的思想。为了解决一个给定的问题,算法一次或多次递归地调用其自身以解决紧密相关的若干个子问题[1]。而对于归并操作,就是指将两个已经排序好的子序列合并成一个有序序列的过程[2]
分治模式在每一层递归上都有三个步骤[1:1]
1. 分解(Divide)原问题为若干子问题,这些子问题的形式与原问题相同,只是规模更小;
2. 解决(Conquer)步骤递归地求解出子问题,如果子问题的规模足够小,则停止递归,直接求解;
3. 合并(Combine)步骤将子问题的结果合并成原问题的解。

归并排序完全遵守分治模式,其具体的步骤如下[1:2]
1. 分解:将待排序的 \(n\) 个元素分成各包含 \(n/2\) 个元素的子序列;
2. 解决:使用归并排序递归地排序两个子序列;
3. 合并:合并两个已排序的子序列以得到排序结果。

归并排序的过程如下图所示:

flowchart TD arr1_01[9] arr1_02[6] arr1_03[5] arr1_04[8] arr1_05[7] arr1_06[4] arr1_07[3] arr1_08[0] arr1_09[1] arr1_10[2] arr2_01["6#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;9"] arr2_02["5#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;8"] arr2_03["4#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;7"] arr2_04["0#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;3"] arr2_05["1#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;2"] arr3_01["5#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;6#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;8#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;9"] arr3_02["0#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;3#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;4#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;7"] arr3_03["1#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;2"] arr4_01["0#nbsp;#nbsp;#nbsp;#nbsp;3#nbsp;#nbsp;#nbsp;#nbsp;4#nbsp;#nbsp;#nbsp;#nbsp;5#nbsp;#nbsp;#nbsp;#nbsp;6#nbsp;#nbsp;#nbsp;#nbsp;7#nbsp;#nbsp;#nbsp;#nbsp;8#nbsp;#nbsp;#nbsp;#nbsp;9"] arr4_02["1#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;2"] arr5_01["0#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;1#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;2#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;3#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;4#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;5#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;6#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;7#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;8#nbsp;#nbsp;#nbsp;#nbsp;#nbsp;9"] arr1_01 --> arr2_01 arr1_02 --> arr2_01 --> arr3_01 arr1_03 --> arr2_02 arr1_04 --> arr2_02 --> arr3_01 --> arr4_01 arr1_05 --> arr2_03 arr1_06 --> arr2_03 --> arr3_02 arr1_07 --> arr2_04 arr1_08 --> arr2_04 --> arr3_02 --> arr4_01 --> arr5_01 arr1_09 --> arr2_05 arr1_10 --> arr2_05 --> arr3_03 --> arr4_02 --> arr5_01

推荐这篇文章的示意图,非常直观:排序——归并排序(Merge sort)

归并排序的实现

2.1 《算法导论》中的实现

[1:3]

MERGE(A, p, q, r)
    n1 = q - p + 1
    n2 = r - q
    let L[1..n1 + 1] and R[1..n2 + 1] be new arrays
    for i = 1 to n1
        L[i] = A[p + i - 1]
    for j = 1 to n2
        R[j] = A[q + j]
    L[n1 + 1] = ∞
    R[n2 + 1] = ∞
    i = 1
    j = 1
    for k = p to r
        if L[i] <= R[j]
            A[k] = L[i]
            i = i + 1
        else A[k] = R[j]
            j = j + 1

MERGE-SORT(A, p, r)
    if p < r
        q = ⌊(p + r) / 2⌋
        MERGE-SORT(A, p, q)
        MERGE-SORT(A, q + 1, r)
        MERGE(A, p, q, r)

2.2 《数据结构(第2版)》中的实现

[2:1]

/* L=右边起始位置,R=右边起始位置,RightEnded=右边终点位置 */
void Merge(ElementType A[], ElementType TmpA[], int L, int R, int RightEnd)
{
    /* 将有序的A[L]~A[R-1]和A[R]~A[RightEnd]归并成一个有序序列 */
    int LeftEnd, NumElements, Tmp;
    int i;

    LeftEnd = R - 1; /* 左边终点位置 */
    Tmp = L;         /* 有序序列的起始位置 */
    NumElements = RightEnd - L + 1;

    while (L <= LeftEnd && R <= RightEnd)
    {
        if (A[L] <= A[R])
            TmpA[Tmp++] = A[L++];   /* 将左边元素复制到TmpA */
        else
            TmpA[Tmp++] = A[R++];   /* 将右边元素复制到TmpA */
    }

    while (L <= LeftEnd)
        TmpA[Tmp++] = A[L++];    /* 直接复制左边剩下的 */
    while (R <= RightEnd)
        TmpA[Tmp++] = A[R++];   /* 直接复制右边剩下的 */

    for (i = 0; i < NumElements; i++, RightEnd--)
        A[RightEnd] = TmpA[RightEnd];   /* 将有序的TmpA[]复制回A[] */
}

void MSort(ElementType A[], ElementType TmpA[], int L, int RightEnd)
{
    /* 核心递归排序函数 */
    int Center;

    if (L < RightEnd)
    {
        Center = (L + RightEnd) / 2;
        MSort(A, TmpA, L, Center);      /* 递归解决左边 */
        MSort(A, TmpA, Center + 1, RightEnd);   /* 递归解决右边 */
        Merge(A, TmpA, L, Center + 1, RightEnd);   /* 合并两段有序序列 */
    }
}

void MergeSort(ElementType A[], int N)
{
    /* 归并排序 */
    ElementType *TmpA;
    TmpA = (ElementType *)malloc(N * sizeof(ElementType));

    if (TmpA != NULL)
    {
        MSort(A, TmpA, 0, N - 1);
        free(TmpA);
    }
    else
        printf("空间不足");
}

2.3 数据结构(C语言版)》中的实现

[3]

void Merge(RcdType SR[], RcdType TR[], int i, int m, int n)
{
    /* 将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n] */
    int j, k, l;
    for (j = m + 1, k = i; i <= m && j <= n; k++)
    {
        if (SR[i].key <= SR[j].key)
            TR[k] = SR[i++];
        else
            TR[k] = SR[j++];
    }
    if (i <= m)
    {
        for (l = 0; l <= m - i; l++)
            TR[k + l] = SR[i + l];  /* 将剩余的SR[i..m]复制到TR */
    }
    if (j <= n)
    {
        for (l = 0; l <= n - j; l++)
            TR[k + l] = SR[j + l];  /* 将剩余的SR[j..n]复制到TR */
    }
}

void MSort(RcdType SR[], RcdType TR1[], int s, int t)
{
    /* 将SR[s..t]归并排序为TR1[s..t] */
    int m;
    RcdType TR2[MAXSIZE + 1];
    if (s == t)
        TR1[s] = SR[s];
    else
    {
        m = (s + t) / 2;    /* 将SR[s..t]平分为SR[s..m]和SR[m+1..t] */
        MSort(SR, TR2, s, m);   /* 递归地将SR[s..m]归并为有序的TR2[s..m] */
        MSort(SR, TR2, m + 1, t);   /* 递归地将SR[m+1..t]归并为有序的TR2[m+1..t] */
        Merge(TR2, TR1, s, m, t);   /* 将TR2[s..m]和TR2[m+1..t]归并到TR1[s..t] */
    }
}

void MergeSort(SqList *L)
{
    /* 对顺序表L作归并排序 */
    MSort(L.r, L.r, 1, L.length);
}

2.4 个人常用的模版

上面提到的几个实现都是为了讲解方便,按顺序写出来的,但是在算法比赛中的时候,我更倾向简洁一点的代码。模板来自《AcWing 787. 归并排序的证明与边界分析》[4]。我自己做了注释,方便理解。

void mergeSort(int a[], int l, int r)
{
    //递归的终止情况
    //当l >= r时,说明数组只有一个元素,不需要再分解
    if(l >= r) return;

    //第一步:分成子问题
    int mid = l + r >> 1;

    //第二步:递归处理子问题
    //这里于快速排序不同,快排是先递归,再合并
    //由于每次都现递归,所以等于将数组划分为一个元素一组
    mergeSort(a, l, mid );
    mergeSort(a, mid + 1, r);

    //第三步:合并子问题
    //这里的合并子问题是指将两个有序的数组合并成一个有序的数组
    //由于每次划分都是从中间划分,所以左右两边的数组都是有序的
    //这里的关键就是处理边界问题
    //左边数组的边界为[l, mid], 右边数组的边界为[mid + 1, r]
    //所以合并的时候,需要从左边数组的第一个元素开始,和右边数组的第一个元素开始比较
    int k = 0,          //临时数组的下标
        i = l,          //左边数组的下标
        j = mid + 1,    //右边数组的下标
        tmp[r - l + 1]; //临时数组
    //这里的边界条件是i <= mid && j <= r,而不是i < mid && j < r 
    //因为当i == mid时,左边数组只有一个元素,而且这个元素是最后一个元素
    //我们需要让合并的两个数组里的其中一个数组的元素全部放入临时数组
    while(i <= mid && j <= r)  
        if(a[i] <= a[j])
            tmp[k++] = a[i++];  //左边数组的元素小于右边数组的元素,将左边数组的元素放入临时数组
        else 
            tmp[k++] = a[j++];  //右边数组的元素小于左边数组的元素,将右边数组的元素放入临时数组
    //将剩余的元素放入临时数组
    while(i <= mid) tmp[k++] = a[i++];
    while(j <= r) tmp[k++] = a[j++];

    for(k = 0, i = l; i <= r; k++, i++) 
        a[i] = tmp[k];  //将临时数组的元素放回原数组
}

归并排序代码上的分析

若要深刻理解归并排序,必须首先理解归并排序的思想,以及它的排序过程。然后思考如何将这个过程转化为代码。

我们再来看一下前文提到的归并排序的过程:
1. 分解:将待排序的 \(n\) 个元素分成各包含 \(n/2\) 个元素的子序列;
2. 解决:使用归并排序递归地排序两个子序列;
3. 合并:合并两个已排序的子序列以得到排序结果。

//TODO



参考与注释:


  1. 《算法导论》 Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein 机械工业出版社 ↩︎ ↩︎ ↩︎ ↩︎

  2. 《数据结构(第2版)》 陈越 高等教育出版社 ↩︎ ↩︎

  3. 《数据结构(C语言版)》 严蔚敏, 吴伟民 清华大学出版社 ↩︎

  4. 《AcWing 787. 归并排序的证明与边界分析 》 ↩︎

标签:归并,--,算法,nbsp,arr1,排序,arr2
From: https://www.cnblogs.com/BryceAi/p/17726811.html

相关文章

  • R语言使用Metropolis-Hastings采样算法自适应贝叶斯估计与可视化|附代码数据
    原文链接:http://tecdat.cn/?p=19889原文出处:拓端数据部落公众号 最近我们被客户要求撰写关于Metropolis-Hastings采样的研究报告,包括一些图形和统计输出。如果您可以写出模型的似然函数,则 Metropolis-Hastings算法可以负责其余部分(即MCMC)。我写了r代码来简化对任意模型的后......
  • 基于方向编码的模板匹配算法matlab仿真
    1.算法运行效果图预览  2.算法运行软件版本MATLAB2022a 3.算法理论概述       模板匹配是一种常见的计算机视觉方法,用于在一幅图像中寻找指定的模板。它在目标检测、图像识别、物体跟踪等领域中有广泛的应用。基于方向编码的模板匹配算法是一种改进的模板......
  • 【算法】循环不变式
    循环不变式一、数学归纳法因为循环不变式的定义与数学归纳法类似,所以我们先来看看数学归纳法。我们首先从高中开始回忆起,有关于数列的数学归纳法。一般的,证明一个与正整数\(n\)有关的命题,可以分为以下两个步骤[1]:1.归纳奠基:证明当\(n=n_0(n_0\inN^*)\)时,命题成立。2......
  • PostgreSQL排序字段不唯一导致分页查询结果出现重复数据
    背景pg单字段排序,排序字段中可能会出现重复,这样就会导致我们在进行分页查询时会出现一些和预期不相符的现象,如这个数据在第一页出现了,然后再第二页又出现。复现步骤createtabletest_select_order_page_duplicate(idvarchar(36)notnullconstrainttest_select_order_pa......
  • 排序算法
    排序算法哪些是稳定的排序算法,哪些是不稳定的稳定的:直接插入排序:最坏情况是逆序,时间复杂度是O(N^2^),最好情况是插入的都是顺序,时间复杂度O(N),空间复杂度O(1)冒泡排序:时间复杂度O(N^2^),空间复杂度O(1)计数排序:时间复杂度O(N+Range),空间复杂度O(range)不稳定:希尔排序:时间复杂......
  • 最近公共祖先 Tarjan算法
    P3379【模板】最近公共祖先(LCA)利用并查集点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=5e5+10;vector<int>g[N];vector<pair<int,int>>query[N];intans[N],vis[N];intf[N];intfind(intx){ returnf[x]==x?x:f[x]......
  • 实现一致性哈希算法
    背景一致性哈希主要用于分布式系统解决数据存储与访问的负载问题,极大的提高了可用性与扩展性。分布式系统往往是把数据分布到不同的节点,这些节点可以动态的加入或离开集群,这样就需要考虑一些问题,如果按照传统的hash算法进行数据分布,动态扩缩节点就需要对数据进行rehash,数据量大或......
  • 一致性哈希算法实现(java)
    代码基本实现未完待续........... publicstaticvoidmain(String[]args){​TreeMap<Integer,String>hashNodes=newTreeMap<>();hashNodes.put(1,"1.1.1.1");hashNodes.put(4,"1.1.1.2");hashNodes.put......
  • 最近公共祖先 倍增算法
    P3379【模板】最近公共祖先(LCA)点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=5e5+10;vector<int>g[N];intdep[N],fa[N][22];voiddfs(intu,intfather){ dep[u]=dep[father]+1; fa[u][0]=father;//2的i次方的点 for(inti=......
  • 文心一言 VS 讯飞星火 VS chatgpt (99)-- 算法导论9.3 5题
    五、用go语言,假设你已经有了一个最坏情况下是线性时间的用于求解中位数的“黑箱”子程序。设计一个能在线性时间内解决任意顺序统计量的选择问题算法。文心一言:为了在线性时间内解决任意顺序统计量的选择问题,我们可以使用一个基于快速选择算法的方法。快速选择算法是基于快速排序的......