求逆序对问题的实现

         这是C++算法基础-基础算法专栏的第四篇文章,专栏详情请见此处


引入

Q:什么是逆序对呢?

A:对于数组a,如果i<ja_{i}>a_{j},通俗地说,就是大的数排在小的数前,则其称为一个逆序对。

        在一个数组中,计算逆序对数量的问题,暴力枚举做法时间复杂度达到了O(n^{2}),而我们可以通过归并排序的思路将此问题的时间复杂度降到O(nlog_{2}n)

        下面我们就来讲求逆序对问题的实现。

定义

        求逆序对问题一般使用高效的归并排序的思路完成。

过程

        求逆序对问题是在归并排序的同时进行操作的。共分为三个过程:

  1. 将数列划分为两部分;
  2. 递归到两个子序列中分别进行排序;
  3. 合并两个子序列,在合并的同时进行计算逆序对的数量,将合并后的数组存入原数组中。

        第一、二步和第三步中的合并过程与归并排序的步骤大致相同,详情可见归并排序的实现

Q:为什么要说是大致相同?它们有什么不同点吗?

A:因为要计算逆序对数量,所以在代码实现中,我们的函数带有返回值,所以你可以在代码中看到,在递归到两个子序列中分别进行排序时,我们用变量res计算了两个子序列的返回值的和。

        第三步中,将一个数组被划分为两个部分时,对于一个逆序对\left ( x,y \right ),一共有三种出现的可能性:

  1. x,y都在左半部分
  2. x,y都在右半部分
  3. x在左半部分,y在右半部分

        但在归并排序的递归过程中,其实第1、2种情况其实可以通过计算第3种情况来处理,也就是说,只用计算第3种情况即可

Q:为什么只用计算第3种情况即可?

A:因为在之前的递归中,数组左右两半部分已经排序,已经不存在逆序对了,之前的逆序对已经被递归处理了

        在合并过程中,还是维护两个指针ij,对于数组右半部分内的数q\left [ j \right ],如果可以在数组左半部分中找到最小的数q\left [ i \right ]使得q\left [ i \right ]>q\left [ j \right ],则说明数组左半部分q\left [ i \right ]之后的数(也包括q\left [ i \right ])都可以与q\left [ j \right ]组成逆序对,共有mid-i+1个。

Q:为什么数组左半部分在q\left [ i \right ]之后的数(也包括q\left [ i \right ])都可以与q\left [ j \right ]组成逆序对?

A:因为数组之前已经在递归时排好序了,q\left [ i \right ]后的数都比q\left [ i \right ]大,既然q\left [ i \right ]> q\left [ j \right ],那么比q\left [ i \right ]大的数也要比q\left [ j \right ]大。

        这一操作可以在合并时,也就是在判断q\left [ i \right ]q\left [ j \right ]大小并将其中一个数值存进临时数组中的过程中一起进行

性质

        时间复杂度

         求逆序对问题的时间复杂度为O\left (nlog_{2}n \right )

代码

        下面给出求逆序对问题的实现代码:

int merge_sort(int q[],int l,int r){
	if(l>=r)
		return 0;
	int mid=l+r>>1;
	int res=merge_sort(q,l,mid)+merge_sort(q,mid+1,r);
	int k=0,i=l,j=mid+1;
	while(i<=mid&&j<=r){
		if(q[i]<=q[j])
			tmp[k++]=q[i++];
		else{
			res+=mid-i+1;
			tmp[k++]=q[j++];
		}
	}
	while(i<=mid)
		tmp[k++]=q[i++];
	while(j<=r)
		tmp[k++]=q[j++];
	for(i=l,j=0;i<=r;i++,j++)
		q[i]=tmp[j];
	return res;
}

上一篇-归并排序的实现    C++算法基础专栏文章    下一篇-整数二分查找的实现


每周六更新一篇文章,内容一般是自己总结的经验或是在其他网站上整理的优质内容

点个赞,关注一下呗~

  • 47
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值