Chap 1 归并排序及其用法

一、归并排序

问题情景:

        给出一组数,对该组数进行排序(由小到大)​​​

思想:

        将给出的这一组数每次分成左右两半部分,再将分开的两个再进行分割,直到分成每个小数组由一个元素构成,此时按照从小到大的顺序依次合并分割的左右两个数组,从而完成整个数组的排序如图所示,在这个过程中需要用到递归:

代码及其注释:

#include<iostream>

using namespace std;

const int N=1e5+10;
//定义一个tem数组,存放归并比较两数组操作之后所归并的新数组
int tem[N];
 
 //l代表数组左边,r代表数组右边
void merge_sort(int a[],int l,int r)
{
    //当数组不可再分的时候,跳出递归
    if(l>=r)return;
    
    //每次取当前数组的中间位置存入mid中
    int mid=(l+r)>>1;
    
    //将分开的两个数组再次进行上述操作,注意左右数组的下标范围
    merge_sort(a,l,mid);  merge_sort(a,mid+1,r);
    
    //i表示当前所分数组,左半部分数组的起始位置,j表示右半部分数组的起始位置
    //k为下标指针,表示每次tem数组存的下表位置
    int k=0;int i=l,j=mid+1;
    
    //合并实现,类似于双指针算法
    //首先保证i、j的范围均合法
    while(i<=mid&&j<=r)
    {
        
        //从左向右依次比较两部分小数组每一个数字的大小,
        //将较小的存入tmp中,接着修改指针位置
        if(a[i]<=a[j])tem[k++]=a[i++];
        else tem[k++]=a[j++];
        
    }
    
    //如果左半边元素有剩余将其拼接到tem之后,右半边同理
    //且,左右两边必然只会有一个部分剩余
    //注意i、j的范围条件
    while(i<=mid)tem[k++]=a[i++];
    while(j<=r)tem[k++]=a[j++];
    
    //将排好序的这一部分数放入a数组中
    //使用i,j两个变量的目的:i规定了放入a数组的位置
    //                       j代表每次tem要放入元素的位置,因为每次递归k都变为0,
    //                       相当于初始化了tem数组,所以需要j表示tem中的元素位置
    for(i=l,j=0;i<=r;i++,j++)a[i]=tem[j];
    
    return;
}
int main()
{
    int n;cin>>n;
    int a[N]={0};
    for(int i=0;i<n;i++)cin>>a[i];
    merge_sort(a,0,n-1);
    for(int i=0;i<n;i++)cout<<a[i]<<' ';
    return 0;
}


二:利用归并排序求逆序数

        思路:在归并排序的基础上,我们知道归并排序每次都把左右两边的数组进行排序,所以我们能保证每次所分割出去的两部分数组一定是按照升序排列的,那么有以下规则:

                

        如图所示,为两部分已经由归并排序排好的两部分数组,如果arr1[i]>arr2[j],那么一定可以得到i之后的所有数都比arr2[j]大,也就是说>=i的所有元素均为arr2[j]的逆序数(arr1为左半部分)

        所以我们只需要在并归排序的基础上稍作调整即可得到逆序数,代码如下:

#include<iostream>

using namespace std;

const int N = 1e5 + 10;
int tem[N];

//记住答案ans要开long long,可以算一下逆序对最多为多少,
//得到的数超过int的范围了
long long ans;

void merge_sort(int a[], int l, int r)
{
    if (l >= r)return;
    int mid = (l + r) >> 1;
    merge_sort(a, l, mid);  merge_sort(a, mid + 1, r);
    int k = 0; int i = l, j = mid + 1;
    while (i <= mid && j <= r)
    {
        if (a[i] <= a[j])tem[k++] = a[i++];
        else
        {
            //当a[i]>a[j]时,i之后,mid之前(包括i和mid)的所有数均为a[j]的逆序数
            tem[k++] = a[j++];
            
            //i到mid之间的元素个数为 mid - i + 1
            ans += mid - i + 1;
        }
    }
    while (i <= mid)tem[k++] = a[i++];
    while (j <= r)tem[k++] = a[j++];
    for (i = l, j = 0; i <= r; i++, j++)a[i] = tem[j];
    return;
}
int main()
{
    int n; cin >> n;
    int a[N] = { 0 };
    for (int i = 0; i < n; i++)cin >> a[i];
    merge_sort(a, 0, n - 1);
    cout << ans << '\n';
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值