问题描述:
设A[1…n]是一个包含n个不同数的数组。如果在i < j的情况下,有A[i] > A[j],则(i,j)就称为A中的一个逆序对(inversion),(逆序对的元素是下标,而不是数组里的值)。给出一个算法,它能用Θ(nlgn)的最坏情况运行时间,确定n个元素的任何排列中逆序对的数目。
问题求解:
方法一:
循环从数组中取出一个元素k,然后从k之后的元素中找到比k小的元素个数,最后统计所有的个数即为排列中逆序对的数目。从数组中取元素的次数为n,每次取出一个元素后,需要遍历(n-i)次(i为当前元素的位置),其时间复杂度为
算法实现:
int CountInversions(const vector<int>& a)
{
int cnt = 0;
for (size_t i = 0; i < a.size(); i++)
{
for (size_t j = i + 1; j < a.size(); j++)
{
if (a[i] > a[j]) cnt++;
}
}
return cnt;
}
方法二:插入排序
利用插入排序的方式来解决。
对数组用插入排序来做升序排列时,如果有元素需要移动,则每次移动时都相当于是找到了一个逆序对,因此插入队列中移动元素的次数,也就是排列中逆序对的数量。
但插入排序的时间复杂度为Θ(n^2),不符合题目要求。
方法三:修改归并排序(时间复杂度O(lgn))
归并排序的基本思想就是 Divide & Conquer: 将数组划分为左右两部分,归并排序左部分,归并排序右部分,然后 Merge 左右两个数组为一个新的数组,从而完成排序。
按照这个基本思想,我们也可以运用到计算逆序对中来。假设将数组划分左右两部分:
(1)左边部分的逆序对有 inv1 个
(2)右边部分的逆序对有 inv2 个
(3)剩余的逆序对必然是一个数出现在左边部分,一个数出现在右边部分,并且满足出现左边部分的数 a[i] > a[j]。左右两部分排序与否,不会影响这种情况下的逆序对数,因为左右两部分的排序只是消除了两部分内部的逆序对,而对于 a[i] 来自左边, a[j] 来自右边构成的逆序,各自排序后还是逆序。
接下来就需要在 Merge 的过程中计算 a[i], a[j] 分别来自左右两部分的逆序对数。
如下图所示,如果所有 a[i] 都小于 b[j] 的第一个数,显然是没有逆序的。只有当 a[i] > b[j] 是才会发生逆序,由于我们事先对 a[] 和 b[] 已经排好序,而所以如果发生 a[i] > b[j], 那么所有 a[ii] (ii > i) 也都满足 a[ii] > b[j], 也就是说和 b[j] 构成逆序的数有 {a[i], a[i+1]…a[end]},所以逆序数增加 end- i + 1个, 所以我们每次碰到 a[i] > b[j] 的情况, 逆序对数增加 (end - i + 1) 个即可。
算法的框架图:
伪代码:
The initial call is COUNT-INVERSIONS(A,1,n)
本文详细介绍了如何通过归并排序算法在Θ(nlgn)的时间复杂度内计算数组排列中的逆序对数量。首先解释了逆序对的定义,然后探讨了三种方法,包括直接遍历(时间复杂度O(n^2))、插入排序(同样为O(n^2))以及优化的归并排序方法(O(nlgn))。重点讲述了归并排序在计算逆序对时的思路,即在合并过程中动态统计逆序对数量。
1274

被折叠的 条评论
为什么被折叠?



