图解:求逆序对数量(归并排序的应用)

简介: 图解:求逆序对数量(归并排序的应用)

前言:

本篇讲归并排序的应用——求解无序序列中逆序对的数目。如果需要了解归并排序本身,请跳转至我的另一篇文章归并排序图文详解(一篇讲透归并排序)-CSDN博客

题目

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

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

输入格式

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

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

输出格式

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

数据范围

1≤n≤100000

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

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

题目分析:

1、理解逆序对的含义:对于数组中的两个数,如果下标小的数的数值却大,那么这两个数就是一组逆序对。

2、本题的要求很简单,没有其他可以分析的。我们看到题目第一眼想到的肯定是暴力解法:就是先选择第一个数,然后一一比较后面的数,如果满足逆序对要求则+1.然后选择第二个数再和后面数一一比较,如此反复。这样写的话时间复杂度会在O(n^2)。当数据量一大后运行时间会非常可怕,所以本题肯定要考虑其他的解法。

解法分析:

归并排序思想:

1、回顾一下分离奇偶数的题目,就是给一个无序序列要求实现调整后,序列左边为奇数右边为偶数。本题我们采用的办法就是双指针,一个从左边向右走,一个从右边向左走,如果左指针遇到偶数则停止,右指针遇到奇数则停止。然后两者交换,接下来继续走直到两者相遇。这样子在运行中时刻会保持左指针左边为奇数,右指针右边为偶数的条件。

这个算法的思想和我们快速排序的核心思想是一样的。

这就说明快排和归并的核心思想很多时候能用在其他的算法当中。

2、那么归并的核心思想是什么呢?核心思想就是:采用的是双指针归并法(链表合并本质也是这样)(两两比较并合并)。我们想想这个思路能不能用在这里。

本题深入分析:

1、先上张图:

假如我们将整个数组分为两段。那么根据这两段我们可以将逆序对分为以上三种类型,然后一一进行求解再把每种的个数相加。

逆序对1:两个数全在左边(如图中红色点)

逆序对2:两个数全在右边(如图中绿色点)

逆序对3:一个数在左边区域,另一个数在右边区域(如图中黄色的点)

2、这时再假如逆序对3,这种类似的逆序对数目能够被计算出来。那么对于左边的逆序对1和右边的逆序对2,我们就可以通过递归的方式,将左边、右边再各自划分为两个区域,计算这新的区域中的逆序对3。

3、不停分割计算逆序对3,对于1、2的计算就是通过分割花为逆序对3,以及新的逆序对1、2.直到最后一组逆序对1、2只剩一个元素,则可以知道结果,并且过程中的逆序对3一直可以计算,所以到这里问题就可以化为求解逆序对3,这一个问题。

4、求解逆序对3:让我们想想归并排序的核心思想:合并的方法采用的是双指针归并法(链表合并本质也是这样)(两两比较并合并)。

那么对于本题能不能拿来用呢?假如我们两个有序数组,我们要如何求出逆序对数?下面我通过图来说明:

选择归并原因:

1、看到这里,大家肯定能想到,这双指针,然后一个个比较大小,然后让小的那个后移的操作和归并排序一模一样啊!!所以很自然,我们可以把这两个操作合并在一起。

2、同时,我们的操作的前提正好也是两个数组内部有序。而归并排序正好也满足合并过程中内部有序的条件。

代码分析:

#include<iostream>
using namespace std;
int temp[100001]={0};//这个temp对于每个递归来说必须是一样的。所以在函数外面定义
long ct_inver_merge(int num[],int l,int r){
    if(l>=r) return 0;
    int mid=(l+r)>>1;
    long result=0;
    result=ct_inver_merge(num,l,mid)+ct_inver_merge(num,mid+1,r);
//先求解左边和右边的两个子区间的逆序对数目
    int i=l;
    int j=mid+1;
    int k=0;//这个k对于每个递归来说都从0开始,所以必须在函数内定义
    while(i<=mid&&j<=r){
        if(num[i]<=num[j])temp[k++]=num[i++];
        else{
            temp[k++]=num[j++];
            result+=mid-i+1;
        }
//实现排序的同时计算两个有序数组间逆序对3的数目,并加到前面的result中
    }
    while(i<=mid)temp[k++]=num[i++];
    while(j<=r)temp[k++]=num[j++];
//单纯为了实现归并排序
    for(int i=l,j=0;i<=r;i++,j++){
        num[i]=temp[j];
    }
//归并排序的必要操作。求逆序对3的算法基于就是数组内部有序。
//所以实现归并保持内部有序是必须的
    return result;
}
int main(){
    int n=0;
    int num[100001]={0};
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>num[i];
    }
    cout<<ct_inver_merge(num,0,n-1)<<endl;
}

 

相关文章
|
6月前
|
算法
DAY-7 | 牛客-BM21 寻找旋转数组的最小元素:二分法分治思想真的很可靠
这是一个关于编程题目的摘要:题目是牛客网上的&quot;BM21 旋转数组的最小数字&quot;,要求找到旋转数组中的最小数字。题解介绍了使用二分查找算法来解决此问题,因为其时间复杂度优于暴力搜索的线性时间复杂度。二分查找的核心是通过比较中间元素与右端元素的大小,不断缩小搜索范围,最终找到最小值。代码示例展示了如何实现这个算法。总结中强调了二分查找适用于部分有序数组,并指出了解决这类问题的关键在于理解数组的二段单调性。
42 1
|
人工智能 算法
【归并排序】归并排序/逆序对的数量
【归并排序】归并排序/逆序对的数量
|
6月前
|
搜索推荐
【hoare优化版】快速排序算法 | 三数取中&小区间优化(2)
【hoare优化版】快速排序算法 | 三数取中&小区间优化(2)
74 0
|
6月前
|
算法 机器人 测试技术
二分查找|双指针:LeetCode:2398.预算内的最多机器人数目
二分查找|双指针:LeetCode:2398.预算内的最多机器人数目
|
6月前
|
算法 测试技术 C#
二分查找|差分数组|LeetCode2251:花期内花的数目
二分查找|差分数组|LeetCode2251:花期内花的数目
|
6月前
|
存储 算法 搜索推荐
【算法训练-排序算法 二】【快速排序】数组中的第K个最大元素、最小的K个数
【算法训练-排序算法 二】【快速排序】数组中的第K个最大元素、最小的K个数
62 0
算法:分治思想处理快排递归以及快速选择/最小K个数问题
算法:分治思想处理快排递归以及快速选择/最小K个数问题
|
算法
图解:快速排序之单边循环法
单边循环法是快速排序的算法之一,之前的双边循环法从数列的两边交替遍历元素,虽然更加直观,不过代码实现起来相对复杂。而单边循环法就要简单多了,只需要从数组的一边对元素进行遍历和交换即可。
195 0
图解:快速排序之单边循环法
|
算法
【快速排序】快速排序/第k个数
【快速排序】快速排序/第k个数
|
算法 搜索推荐