逆序对
发布于2021-01-19
上次编辑2021-04-20
Introduction
在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
即如果 \(i \lt j\) 且 \(A[i] \gt A[j]\),\(A[i]\) 和 \(A[j]\) 构成一个逆序对。
如何计算数组中逆序对的总数?
算法
下面用 \(T(l,r)\) 表示子数组 \(A[l,r]\) 内的逆序对数量。
归并排序
将 \(A[l,r]\) 划分为 \(A[l,m]\) 和 \(A[m+1,r]\) 两部分。
如果我们已经得到了 \(T(l,m)\) 和 \(T(m+1,r)\) 的值,那么
\[T(l,r)=T(l,m)+T(m+1,r)+C\]
\(C\) 表示子数组 \(A[l,m]\) 和 \(A[m+1,r]\) 之间 形成的逆序对数量,即逆序对中的第一个数取自子数组 \(A[l,m]\),第二个数取自子数组 \(A[m+1,r]\)。
如果要计算所有这样的逆序对,需要使用双重循环来遍历两个子数组的数,这样导致的时间复杂度为 \(\mathcal{O}(n^2)\)。
而如果两个子数组内的数据是有序的话,便可以在线性时间内得到结果。
| int count_inversions(vector<int> &A, int left, int mid, int right) {
// 计算有序数组 A[left, mid] 与 A[mid+1, right] 之间的逆序数
int cnt = 0;
for (int i = left, j = mid + 1; i <= mid; ++i) {
while (j <= right && A[i] > A[j]) ++j; // 如果 A[i] > A[j] 则 A[i+1,mid] 都大于 A[j]
cnt += j - (mid + 1);
}
return cnt;
}
|
所以要求解整个数组的逆序对数量,而且需要子数组是有序的,可以在 归并排序 的过程中增加上述步骤来实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | void merge(vector<int> &A, vector<int> &help, int left, int mid, int right) {
int i = left, j = mid + 1, k = left;
while (i <= mid) {
if (j > right || A[i] <= A[j]) help[k++] = A[i++];
else help[k++] = A[j++];
}
while (j <= right) help[k++] = A[j++];
copy(help.begin() + left, help.begin() + right + 1, A.begin() + left);
}
void merge_sort(vector<int> &A, vector<int> &help, int left, int right) {
if (left >= right) return ;
int mid = left + ((right - left) >> 1);
merge_sort(A, help, left, mid), merge_sort(A, help, mid + 1, right);
merge(A, help, left, mid, right);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | void merge(vector<int> &A, vector<int> &help, int left, int mid, int right) {
int i = left, j = mid + 1, k = left;
while (i <= mid) {
if (j > right || A[i] <= A[j]) help[k++] = A[i++];
else help[k++] = A[j++];
}
while (j <= right) help[k++] = A[j++];
copy(help.begin() + left, help.begin() + right + 1, A.begin() + left);
}
int merge_sort(vector<int> &A, vector<int> &help, int left, int right) {
if (left >= right) return 0;
int mid = left + ((right - left) >> 1);
int cnt = merge_sort(A, help, left, mid) + merge_sort(A, help, mid + 1, right);
cnt += count_inversions(A, left, mid, right);
merge(A, help, left, mid, right);
return cnt;
}
|
完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 | long long merge_sort(vector<int> &A, vector<int> &help, int left, int right) {
if (left >= right) {
return 0;
}
int mid = left + ((right - left) >> 1);
long long cnt = merge_sort(A, help, left, mid) + merge_sort(A, help, mid + 1, right);
// 计算逆序对数量
for (int i = left, j = mid + 1; i <= mid; ++i) {
while (j <= right && A[i] > A[j]) {
++j;
}
cnt += j - (mid + 1);
}
int i = left, j = mid + 1, k = left;
// 合并两个有序数组
while (i <= mid || j <= right) {
if (i <= mid && (j > right || A[i] <= A[j])) {
help[k++] = A[i++];
} else {
help[k++] = A[j++];
}
}
copy(help.begin() + left, help.begin() + right + 1, A.begin() + left);
return cnt;
}
|
二叉搜索树
树状数组
线段树
例题