归并排序 与 逆序对数量

一、归并排序

 题目:

        给定你一个长度为 n 的整数数列,请你使用归并排序对这个数列按照从小到大进行排序,并将排好序的数列按顺序输出。

输入格式:

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

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

数据范围:

        0 < n < 100000

解题思路:

        如果单纯做出来这道题方法有很多种,只要要求时间复杂度为O(n log n)即可。使用内置函数sort排序也可以AC,但是这道题目要求咱们使用归并排序,那咱们就以这道题为例讲一下归并排序。

        1.归并排序主要是通过将一个数组进行log n次分解,直至每个数组元素个数为1。以[1,5,3,4,6,2]数组为例,第一次分解为[1,5,3] 和 [4,6,2] 。数组元素个数不为1,继续将数组分解,第二次分解[1,5]和[3]和[4,6]和[2]。还有两个数组没有元素个数不为1。继续进行数组分解。第三次分解成[1]和[5]和[3]和[4]和[6]和[2]每个数组元素个数都已经为1。完成了归并排序的第一个环节。

        

        2. 第一步的铺垫已经完成,第二步就是需要把每个数组进行按照从大到小的顺序进行合并,第一次合并之后是两个元素,第二次合并就是二元素个数与一元素个数数组合并,此时用双指针法能够将两个数组元素的从小到大顺序排列出来。还是拿[1,5,3,4,6,2]数组为例,第一步得到的是[1]和[5]和[3]和[4]和[6]和[2],第一次合并为[1,5] 和[3]和[4,6]和[2]。第二次合并为[1,3,5]和[2,4,6]。第三次合并为[1,2,3,4,5,6]。

 

 log n次分解、log n合并为归并排序

动图展示

实现代码 

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int q[N];
int temp[N];
void mergeSort(int q[],int l,int r)
{
    if(l>=r) return;
    int mid=(l+r)/2;
    mergeSort(q,l,mid);
    mergeSort(q,mid+1,r);
    int k=0;
    int i=l;
    int j=mid+1;
    while(i<=mid&&j<=r)
    {
        if(q[i]<=q[j])
        {
            temp[k++]=q[i++];
        }
        else{
            temp[k++]=q[j++];
        }
    }
    while(i<=mid)
    {
        temp[k++]=q[i++];
    }
    while(j<=r)
    {
        temp[k++]=q[j++];
    }
    for(int a=l,b=0;a<=r;a++,b++)
    {
        q[a]=temp[b];
    }
}
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>q[i];
    }
    mergeSort(q,0,n-1);
    for(int i=0;i<n;i++)
    {
        cout<<q[i]<<" ";
    }
}

 二、逆序对数量

题目:

        给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。逆序对的定义如下:对于数列的第 i 个和第 j个元素,如果满足 i<j且 a[i]>a[j],则其为一个逆序对;否则不是。

输入格式

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

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

输出格式

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

数据范围

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

解题思路:

        题目理解起来很容易,最经典的就是O(N*N)做法,只不过本题中数据范围100000,最大时间复杂度为O(n log n)。

        我们可以用上面讲的归并排序,第一步是将每个元素单独分成一个数组,右边的肯定要比左边的下标大,如果左边元素大于右边,即满足逆序对的条件,可以算出逆序对数量,再将所有逆序对的数量加起来即是结果。

代码实现:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int q[N];
int temp[N];
long long mergeSort(int q[],int l,int r)
{
    if(l>=r) return 0;
    int mid=(l+r)/2;

    long long ans=mergeSort(q,l,mid)+mergeSort(q,mid+1,r);
    int k=0;
    int i=l;
    int j=mid+1;
    while(i<=mid&&j<=r)
    {
        if(q[i]<=q[j])
        {
            temp[k++]=q[i++];
        }
        else{
            temp[k++]=q[j++];
            ans+=mid-i+1;
        }
    }
    while(i<=mid)
    {
        temp[k++]=q[i++];
    }
    while(j<=r)
    {
        temp[k++]=q[j++];
    }
    for(int a=l,b=0;a<=r;a++,b++)
    {
        q[a]=temp[b];
    }
    return ans;
}
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>q[i];
    }
   // mergeSort(q,0,n-1);
    /*for(int i=0;i<n;i++)
    {
        cout<<q[i]<<" ";
    }*/
    cout<<mergeSort(q,0,n-1)<<endl;
}

<think>好的,我现在需要解释归并排序如何用于计算逆序对的原理。首先,我得回忆归并排序的基本步骤,因为逆序对的计算是在这个过程中进行的。归并排序是分治法的典型应用,分为分解、解决合并三个阶段。分解阶段将数组不断二分,直到子数组长度为1;解决阶段递归排序子数组;合并阶段将两个有序子数组合并成一个有序数组。 接下来,用户想知道如何在这个过程中计算逆序对逆序对的定义是数组中前面的元素大于后面的元素的情况。比如数组[3,1,2]中的逆序对有(3,1)(3,2),共2对。 在归并排序的合并过程中,左右两个子数组都是有序的。当合并时,比较左右两个数组的元素,如果左边的当前元素大于右边的当前元素,那么左边数组中该元素及其后面的所有元素都会右边的这个元素形成逆序对。例如,左数组是[2,5,7],右数组是[3,4,6],当比较23时,2小于3,直接放入结果;然后比较53,此时5大于3,说明左数组中5及其后面的元素(5,7)都3形成逆序对,这里贡献了2个逆序对。这一步是关键,需要在合并过程中统计这些情况。 根据引用[3],归并排序过程中计算逆序对的具体思路是在合并时统计跨越左右子数组的逆序对数量。每次发现右子数组的元素小于左子数组的当前元素时,累加左子数组剩余元素的个数作为逆序对数量。这需要在合并函数中添加一个计数器,并在每次交换时更新这个计数器。 需要注意的点是,分解递归处理子数组时,需要分别计算左半部分、右半部分以及合并时的逆序对,并将三者相加。这样才能得到整个数组的逆序对总数。例如,假设左子数组内部有a个逆序对,右子数组内部有b个,合并过程中产生c个,总逆序对数为a+b+c。 可能用户会疑惑为什么归并排序适合计算逆序对。因为归并排序在合并时已经让左右子数组有序,这样在比较时可以高效地统计跨子数组的逆序对,而不需要逐个比较,从而将时间复杂度保持在O(n log n),这比暴力法的O(n²)高效得多。 此外,引用[2]提到归并排序是稳定的,不会改变相等元素的顺序,这对保持逆序对的准确性有帮助。同时,合并过程中通过指针移动的方式,可以确保每个逆序对被正确计数一次,不会有重复或遗漏。 举个例子,假设有一个数组[4,3,2,1],使用归并排序计算逆序对。分解到最底层后,合并时每个元素比较都会产生逆序对。例如,合并[4][3]时,4>3,逆序对+1;合并[3,4][1,2]时,3>1,此时左子数组剩余2个元素(3,4),所以逆序对+2,接着3>2,再+2,4>14>2各+2,总共有1+2+2+2+2=9,但实际上正确总数应该是6。这说明例子可能需要更详细的计算步骤来验证,但大致思路是对的,即每次右元素较小的时候累加左剩余元素的数量。 因此,总结归并排序计算逆序对的原理为:在合并过程中,当右子数组的元素被选中放入结果数组时,统计左子数组剩余元素的个数,这些元素都当前右元素形成逆序对,并将这些数目累加,最终得到总的逆序对数量。</think>### 归并排序计算逆序对的原理 归并排序利用**分治思想****有序数组合并**的特性,在合并阶段高效统计逆序对。具体步骤如下: 1. **分治分解** 将数组递归拆分为左右两个子数组,直到子数组长度为1(天然有序)。 2. **合并统计** 合并两个有序子数组时,若发现**左子数组当前元素 > 右子数组当前元素**,则说明左子数组中该元素及之后的所有元素均右子数组当前元素构成逆序对。此时逆序对数量需增加左子数组剩余元素的个数[^3]。 3. **累加逆序对** 逆序对总数 = **左子数组内部的逆序对** + **右子数组内部的逆序对** + **合并时跨子数组的逆序对**。 --- #### 示例说明 假设数组为 $[7, 5, 2, 6]$,分解后合并过程如下: 1. 左子数组 $[7,5]$ → 拆分为 $[7]$ $[5]$,合并时发现 $7 > 5$,逆序对 $+1$,合并结果为 $[5,7]$。 2. 右子数组 $[2,6]$ → 天然有序,合并后仍为 $[2,6]$。 3. 合并 $[5,7]$ $[2,6]$: - $5 > 2$ → 左子数组剩余2个元素(5,7),逆序对 $+2$。 - $5 < 6$ → 无逆序对。 - $7 > 6$ → 左子数组剩余1个元素(7),逆序对 $+1$。 - 总逆序对 $=1+2+1=4$。 --- #### 代码实现(Python) ```python def merge_sort_count(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 left, cnt_left = merge_sort_count(arr[:mid]) right, cnt_right = merge_sort_count(arr[mid:]) merged, cnt_merge = merge(left, right) return merged, cnt_left + cnt_right + cnt_merge def merge(left, right): merged = [] i = j = cnt = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) cnt += len(left) - i # 统计逆序对 j += 1 merged += left[i:] merged += right[j:] return merged, cnt ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

北方以南ccc

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

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

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

打赏作者

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

抵扣说明:

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

余额充值