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

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

前言:

本篇讲归并排序的应用——求解无序序列中逆序对的数目。如果需要了解归并排序本身,请跳转至我的另一篇文章归并排序图文详解(一篇讲透归并排序)-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;
}

 

相关文章
|
7月前
|
人工智能 算法 测试技术
【动态规划】【二分查找】C++算法 466 统计重复个数
【动态规划】【二分查找】C++算法 466 统计重复个数
第k小的数(2种快排解法、1种堆排解法)
第k小的数(2种快排解法、1种堆排解法)
67 0
|
2月前
|
搜索推荐 算法
插入排序算法的平均时间复杂度解析
【10月更文挑战第12天】 插入排序是一种简单直观的排序算法,通过不断将未排序元素插入到已排序部分的合适位置来完成排序。其平均时间复杂度为$O(n^2)$,适用于小规模或部分有序的数据。尽管效率不高,但在特定场景下仍具优势。
|
人工智能 算法
【归并排序】归并排序/逆序对的数量
【归并排序】归并排序/逆序对的数量
|
7月前
|
算法 机器人 测试技术
二分查找|双指针:LeetCode:2398.预算内的最多机器人数目
二分查找|双指针:LeetCode:2398.预算内的最多机器人数目
|
7月前
|
存储 算法 索引
算法题解-汇总区间
算法题解-汇总区间
|
7月前
剑指 Offer 40:最小的k个数(快速排序)
剑指 Offer 40:最小的k个数(快速排序)
42 0
算法:分治思想处理快排递归以及快速选择/最小K个数问题
算法:分治思想处理快排递归以及快速选择/最小K个数问题
|
算法 搜索推荐