算法基础:归并排序(超详细)

归并排序

题目1:归并排序

给定你一个长度为 n 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式
输入共两行,第一行包含整数 n。

第二行包含 n 个整数(所有整数均在 1∼109 范围内),表示整个数列。

输出格式
输出共一行,包含 n 个整数,表示排好序的数列。

数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 100010;

int q[N],tmp[N];

int n;

void merge_sort(int q[], int l, int r){
    
    if(l>=r) return;
    
    int mid = l+r >>1;
    
    merge_sort(q,l,mid)merge_sort(q,mid+1,r);
    
    //i和j相当于一个双指针,指向当前需要排序的数组的两个部分。
    int i=l,j=mid+1;
    int k=0;
    while(i<=mid && j<=r){
        if(q[i]<=q[j])  tmp[k++] = q[i++];
        else  tmp[k++] = q[j++];
    }
    while(i<=mid)  tmp[k++] = q[i++];
    while(j<=r)   tmp[k++] = q[j++];
    
    //注意这里的l和r是相对于原始的q数组的位置,而tmp保存的值只是当前指针指向的这两个部分的排好序的值。
    for(int i=l,j=0;i<=r;i++,j++) q[i]=tmp[j];
}

int main(){
    
    int n;
    cin>>n;
    
    for(int i=0;i<n;i++) scanf("%d", &q[i]);
    
    merge_sort(q,0,n-1);
    
    for(int i=0; i<n;i++) printf("%d ",q[i]);
    
    return 0;
    
}


算法思想: 归并排序也是基于分治的思想,但是与快速排序不同的是,归并排序是先分治,再排序(先递归,再比较)。而快速排序是先排序,再分治(先比较,再递归);从总体来说归并排序的计算是自底向上的,而快速排序的计算是自顶向下的。由于归并排序在排序的过程中对数组的划分是固定的(快速排序不是固定的,与选择的基准点有关),所以归并排序的复杂度是固定的。
复杂度分析:归并排序(Merge Sort)是一种典型的分治算法,其时间复杂度和空间复杂度如下:
时间复杂度:最好情况下,归并排序的时间复杂度为O(nlogn);最坏和平均情况下,归并排序的时间复杂度也为O(nlogn)。
空间复杂度:归并排序的空间复杂度为O(n),因为在合并过程中,需要额外的空间存储左右两个子序列的元素。
具体的分析过程如下:
1.归并排序首先将待排序序列均分为两个子序列,然后分别对这两个子序列进行排序,最后将排序好的子序列进行合并。这个过程可以递归进行,直到子序列的长度为1,此时可以认为子序列已经排好序。
2.在最好情况下,即待排序序列已经排好序时,每次递归的两个子序列都是有序的。这时,归并排序的时间复杂度为O(n)。因为无论序列长度为多少,都只需要进行一次合并操作。
3.在最坏和平均情况下,即待排序序列完全无序或部分有序时,每次递归的两个子序列都需要进行排序。这时,归并排序的时间复杂度为O(nlogn)。因为每次递归都需要将序列均分为两个子序列,然后对这两个子序列进行排序。这个过程可以看作是对元素数量的对数级别的递归调用。
在空间复杂度方面,由于归并排序需要额外的空间存储左右两个子序列的元素,因此空间复杂度为O(n)。

题目2:归并排序的应用-逆序对的数量

给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。

输入格式
第一行包含整数 n,表示数列的长度。

第二行包含 n 个整数,表示整个数列。

输出格式
输出一个整数,表示逆序对的个数。

数据范围
1≤n≤100000,
数列中的元素的取值范围 [1,109]。

输入样例:
6
2 3 4 5 6 1
输出样例:
5

//在分治后的每一层合并中顺便求出逆序对数量是这个题想法的由来,归并排序分治我们求的是从小到
//大的顺序,我们所求的逆序对恰好是逆序数量,与归并排序不谋而合。


#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>

using namespace std;

const int N =100000;

int q[N],f[N];

long long  res=0;

long long  merge_sort(int l,int r){

    if(l>=r) return 0;

    int mid=(l+r)>>1;

    res=merge_sort(l,mid)+merge_sort(mid+1,r);

    int k=0,i=l,j=mid+1;
    while(i<=mid&&j<=r){
        if(q[i]<=q[j]) f[k++]=q[i++];
        else{
        //当前i后面的数都比j大,而且每个数组段在排序的过程中都是排好序的(由更小的数组段合并来的),所以逆序数加上mid-i+1。
            res+=mid-i+1;
            f[k++]=q[j++];
        }
    }
    while(i<=mid) f[k++]=q[i++];
    while(j<=r) f[k++]=q[j++];

   for(int i=l,j=0;i<=r;i++,j++) q[i]=f[j];

   return res;
}


int main(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++) scanf("%d",&q[i]);

    merge_sort(0,n-1);

    printf("%lld",res);

    return 0;
}

算法思想:由于递归的过程中是由下向上的合并的过程,且合并的过程中的各个子序列都是排好序的。所以就可以根据这一性质来判断逆序对的数量。具体来说,归并排序是一种稳定的排序算法,即相同元素的位置是不会改变的,当靠前的排好序的子序列的元素比后面的元素要小(默认降序),那么当前子序列中后面的元素也都要比他小,所以逆序的数量res + = mid+1-l.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MingchenS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值