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

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

前言:

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

 

相关文章
|
安全 Java 编译器
kotlin面试题
kotlin面试题
1101 1
|
存储 人工智能 算法
聚类的k值确定之轮廓系数
聚类的k值确定之轮廓系数
4273 0
|
25天前
|
缓存 编译器 C++
Keil MDK常见报错与解决方案详细笔记
本文系统梳理Keil MDK开发中常见错误,涵盖编译、链接、下载调试、路径配置、警告处理等八大类问题,含典型错误码(如#5、L6218E)、根因分析及实操解决方案,助力嵌入式开发者高效排错。(239字)
621 3
|
机器学习/深度学习 算法 关系型数据库
强化学习:动态规划求解最优状态价值函数——手把手教你入门强化学习(四)
本文介绍了基于模型的强化学习算法,重点讲解动态规划(DP)。动态规划通过分解问题为子问题求解状态价值函数,利用贝尔曼期望方程迭代更新。其核心性质包括最优子结构和重叠子问题,适用于已知转移概率和奖励的MDP场景。文章回顾了前期强化学习基础,并展望了后续内容如蒙特卡罗法。适合初学者系统了解强化学习算法原理与应用。
530 7
|
消息中间件 存储 Java
MQ线上消息乱序问题处理及场景详解
【11月更文挑战第22天】在现代分布式系统中,消息队列(MQ)作为核心组件,承担着异步处理、削峰填谷和系统解耦的重任。
879 1
|
机器人 PHP
QQ云端机器人登录系统php源码
QQ云端机器人登录系统php源码
1210 4
|
编译器
区分LR(0),SLR(1),LR(1)和LALR(1)
区分LR(0),SLR(1),LR(1)和LALR(1)
2357 1
|
存储 算法 C++
【C++数据结构——图】图的邻接矩阵和邻接表的存储(头歌实践教学平台习题)【合集】
本任务要求编写程序实现图的邻接矩阵和邻接表的存储。需掌握带权有向图、图的邻接矩阵及邻接表的概念。邻接矩阵用于表示顶点间的连接关系,邻接表则通过链表结构存储图信息。测试输入为图的顶点数、边数及邻接矩阵,预期输出为Prim算法求解结果。通关代码提供了完整的C++实现,包括输入、构建和打印邻接矩阵与邻接表的功能。
655 10
|
小程序 安全 搜索推荐
【微信小程序开发实战项目】——个人中心页面的制作
本文介绍了如何设计和实现一个网上花店的微信小程序,包括个人中心、我的订单和我的地址等功能模块。个人中心让用户能够查看订单历史、管理地址和与客服互动。代码示例展示了`own.wxml`、`own.wxss`和`own.js`文件,用于构建个人中心界面,包括用户信息、订单链接、收藏、地址、客服和版本信息。我的订单部分展示了订单详情,包括商品图片、名称、销量、价格和订单状态,用户可以查看和管理订单。我的地址功能允许用户输入和编辑收货信息,包括联系人、性别、电话、城市和详细地址。每个功能模块都附有相应的WXML和WXSS代码,以及简洁的样式设计。
1128 0
【微信小程序开发实战项目】——个人中心页面的制作
CubeMXST32 FreeRTOS 信号量
CubeMXST32 FreeRTOS 信号量
564 11

热门文章

最新文章