跳转至

逆序对#
发布于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)\)

而如果两个子数组内的数据是有序的话,便可以在线性时间内得到结果。

1
2
3
4
5
6
7
8
9
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;
}

二叉搜索树#

树状数组#

线段树#

例题#

返回顶部

在手机上阅读