归并排序详解

归并排序简介

什么是排序算法

排序算法是算法的基石,许多算法都基于排序算法,比如二分搜索、离散化等。这篇文章将要详细介绍将要介绍排序算法之一——归并排序。

归并排序的性能

归并排序的时间复杂度稳定在\(\mathcal{O}(n \log(n))\),是一种具有稳定性(即相同元素相对位置不变)的排序方法。所以一般来说归并排序优于快速排序。归并排序基于一种叫做“分治”的思想,即分而治之。

归并排序原理

下面我们用1 9 2 7 6 5 8 4这8个数来演示归并排序。

归并排序主要分为两部分,一部分是分,一部分是合。

步骤一:分解


对于每个序列,取mid=(l+r)/2(向下取整), 然后分别对左([l~mid])右([mid+1~r])两段进行排序。
等等!如果 \(l = r\),即只有一个数了,那么这段就不用排了, 直接return

步骤二:合并

这个步骤,我们需要将两个子序列进行合并。注意,被合并的两个子序列一定是有序的。这样才可以进行线性的合并。

我们定义两个“箭头”ij分别指向两个子序列的开头,并且定义一个“储物间”数组t,用来存储排好的数字,就像这样:
pkLNwB4.png
然后我们比较两个箭头指向的数字,看哪个小,就让哪个进入“储物间”,并且把那个箭头向前推进。
比如上面这幅图,\(1 < 4\),所以就变成这样:
pkLN69x.png
接下来,\(2 < 4\),所以让i指向的数进入储物间并向前推进。
pkLNggK.png
接下来继续,注意!\(7 > 4\),所以要让4进入储物间,推进的则是j
pkLNfDe.png
以此类推,请你手动模拟一下后面的部分,如果没有错,模拟完应该是这样的:
pkLNIUA.png
怎么样?自己模拟的对不对?注意如果一个箭头走到了末尾,另一个箭头还要继续走完哦!如果还是不理解可以看一下代码

代码实现

// a是原序列,t是储物间。
int a[100],t[100];
void mergesort(int l,int r){
    if (l == r) return; // 如果只有一个数就不用排了
    int mid = (l + r) / 2; // 取中间点
    mergesort(l,mid); // 排序左半部分
    mergesort(mid + 1,r); // 排序右半部分
    int tot = 0,i = l,j = mid + 1; // 开始合并,tot表示储物间最后一个被占用的位置
    while (i <= mid && j <= r){
        if (a[i] <= a[j]) t[++tot] = a[i++]; // 如果i指向的数小,那么就把它放入储物间
        else t[++tot] = a[j++]; // 否则就让j指向的数进入储物间
    }
    while (i <= mid) t[++tot] = a[i++]; // 把剩下的部分装进储物间
    while (j <= r) t[++tot] = a[j++]; // 同上
    for (int k = 1;k <= tot;++k) a[l + k - 1] = t[k]; // 把储物间放回到原序列
}

拓展应用:求逆序对个数

原理

逆序对,指数列中的两个数\(a_i > a_j\)并且\(i < j\)。利用归并排序我们可以以\(\mathcal{O}(n \log(n))\)的时间复杂度求序列中的逆序对个数。
归并排序在合并过程中,i永远小于j,这时,如果\(a_i > a_j\),说明出现了一组逆序对,而且因为被合并的两个序列是有序的,所以\(a_i\)后面的数也一定大于\(a_j\),因此ans += mid - i + 1,具体代码如下:

代码实现

// a是原序列,t是储物间。
int a[100],t[100],ans = 0;
void mergesort(int l,int r){
    if (l == r) return; // 如果只有一个数就不用排了
    int mid = (l + r) / 2; // 取中间点
    mergesort(l,mid); // 排序左半部分
    mergesort(mid + 1,r); // 排序右半部分
    int tot = 0,i = l,j = mid + 1; // 开始合并,tot表示储物间最后一个被占用的位置
    while (i <= mid && j <= r){
        if (a[i] <= a[j]) t[++tot] = a[i++]; 
        else t[++tot] = a[j++],ans += mid - i + 1; // 如果a[j]更小,那么就是发现了逆序对
    }
    while (i <= mid) t[++tot] = a[i++]; // 把剩下的部分装进储物间
    while (j <= r) t[++tot] = a[j++]; // 同上
    for (int k = 1;k <= tot;++k) a[l + k - 1] = t[k]; // 把储物间放回到原序列
}

作者:doingfx

出处:https://www.cnblogs.com/doingfx/p/18332129

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   doing1024  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示