这是C++算法基础-基础算法专栏的第四篇文章,专栏详情请见此处。
引入
Q:什么是逆序对呢?
A:对于数组
,如果
且
,通俗地说,就是大的数排在小的数前,则其称为一个逆序对。
在一个数组中,计算逆序对数量的问题,暴力枚举做法时间复杂度达到了,而我们可以通过归并排序的思路将此问题的时间复杂度降到
。
下面我们就来讲求逆序对问题的实现。
定义
求逆序对问题一般使用高效的归并排序的思路完成。
过程
求逆序对问题是在归并排序的同时进行操作的。共分为三个过程:
- 将数列划分为两部分;
- 递归到两个子序列中分别进行排序;
- 合并两个子序列,在合并的同时进行计算逆序对的数量,将合并后的数组存入原数组中。
第一、二步和第三步中的合并过程与归并排序的步骤大致相同,详情可见归并排序的实现。
Q:为什么要说是大致相同?它们有什么不同点吗?
A:因为要计算逆序对数量,所以在代码实现中,我们的函数带有返回值,所以你可以在代码中看到,在递归到两个子序列中分别进行排序时,我们用变量
计算了两个子序列的返回值的和。
第三步中,将一个数组被划分为两个部分时,对于一个逆序对,一共有三种出现的可能性:
都在左半部分
都在右半部分
在左半部分,
在右半部分
但在归并排序的递归过程中,其实第1、2种情况其实可以通过计算第3种情况来处理,也就是说,只用计算第3种情况即可。
Q:为什么只用计算第3种情况即可?
A:因为在之前的递归中,数组左右两半部分已经排序,已经不存在逆序对了,之前的逆序对已经被递归处理了。
在合并过程中,还是维护两个指针和
,对于数组右半部分内的数
,如果可以在数组左半部分中找到最小的数
使得
,则说明数组左半部分在
之后的数(也包括
)都可以与
组成逆序对,共有
个。
Q:为什么数组左半部分在
之后的数(也包括
)都可以与
组成逆序对?
A:因为数组之前已经在递归时排好序了,
后的数都比
大,既然
,那么比
大的数也要比
大。
这一操作可以在合并时,也就是在判断和
大小并将其中一个数值存进临时数组中的过程中一起进行。
性质
时间复杂度
求逆序对问题的时间复杂度为。
代码
下面给出求逆序对问题的实现代码:
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++算法基础专栏文章 下一篇-整数二分查找的实现
每周六更新一篇文章,内容一般是自己总结的经验或是在其他网站上整理的优质内容
点个赞,关注一下呗~