跳转至

6、逆序对问题

​ 排列的逆序数是指在一个排列中,若两个元素的先后顺序与排序后它们的顺序相反,则称这一对元素为一个逆序。排列的逆序数就是这个排列中逆序对的个数。

求排列的逆序数有多种方法,包含暴力枚举、归并排序、使用树状数组(BIT)、线段树(Segment Tree)等方法。

1、暴力枚举

使用暴力法求解逆序对的C++代码示例:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

int countInversePairs(int arr[], int n) {
    int count = 0;
    for (int i = 0; i < n-1; i++) {
        for (int j = i+1; j < n; j++) {
            if (arr[i] > arr[j]) {
                count++;
            }
        }
    }
    return count;
}

int main() {
    int arr[] = {2, 4, 1, 3, 5};
    int n = sizeof(arr) / sizeof(arr[0]);

    int inversePairs = countInversePairs(arr, n);
    cout << "Number of inverse pairs: " << inversePairs << endl;

    return 0;
}

​ 这段代码中,countInversePairs函数使用嵌套循环遍历数组,对于每对元素 (arr[i], arr[j]),如果arr[i] > arr[j],则逆序对的计数增加。然后,在main函数中,创建一个示例数组,使用countInversePairs函数计算逆序对的数量,并将结果打印出来。

2、归并排序

通过归并排序来统计逆序对的个数。具体步骤如下:

  1. 将排列分成左右两个部分;
  2. 递归地求解左右两部分的逆序数;
  3. 合并左右两部分,并统计由左部分中的元素和右部分中的元素构成的逆序对;
  4. 返回左右两部分逆序对的个数和合并后的逆序对个数之和。

下面是使用C++代码实现求排列的逆序数的函数:

C++
 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <vector>

using namespace std;

// 归并函数
int merge(vector<int>& nums, int left, int mid, int right) {
    vector<int> temp(right - left + 1);
    int i = left; // 左序列指针
    int j = mid + 1; // 右序列指针
    int k = 0; // 临时数组指针
    int count = 0; // 记录逆序对个数

    while (i <= mid && j <= right) {
        if (nums[i] <= nums[j]) {
            temp[k++] = nums[i++];
        } else {
            // 如果左序列元素大于右序列元素
            // 则左序列后面的元素都大于右序列当前元素
            // 因此逆序对个数要加上左序列剩余元素的个数
            count += mid - i + 1;
            temp[k++] = nums[j++];
        }
    }

    // 将左序列剩余元素添加到临时数组中
    while (i <= mid) {
        temp[k++] = nums[i++];
    }

    // 将右序列剩余元素添加到临时数组中
    while (j <= right) {
        temp[k++] = nums[j++];
    }

    // 将临时数组中的元素复制回原数组
    for (int m = 0; m < temp.size(); m++) {
        nums[left + m] = temp[m];
    }

    return count;
}

// 归并排序函数
int mergeSort(vector<int>& nums, int left, int right) {
    int count = 0;
    if (left < right) {
        int mid = (left + right) / 2;
        // 分治递归
        count += mergeSort(nums, left, mid);
        count += mergeSort(nums, mid + 1, right);
        // 合并子序列并计算逆序对个数
        count += merge(nums, left, mid, right);
    }
    return count;
}

// 统计逆序对个数函数
int countInversePairs(vector<int>& nums) {
    int n = nums.size();
    return mergeSort(nums, 0, n - 1);
}

int main() {
    vector<int> nums = {7, 5, 6, 4};
    int inversePairs = countInversePairs(nums);
    cout << "逆序对个数为:" << inversePairs << endl;

    return 0;
}

​ 通过调用 countInversePairs() 函数,可以统计给定数组中的逆序对个数。以上是一个简单的示例,你可以根据需要进行修改和扩展。希望对你有帮助!如果你有任何问题,请随时问我。

3、树状数组(BIT)

使用树状数组求解逆序对的C++代码示例:

C++
 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
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <vector>

using namespace std;

// 树状数组的更新操作,将指定位置的值加上给定的增量
void update(vector<int>& tree, int index, int val) {
    while (index < tree.size()) {
        tree[index] += val;
        index += index & -index; // 更新下一个需要加上增量的位置
    }
}

// 树状数组的查询操作,计算从1到指定位置的累加和
int query(vector<int>& tree, int index) {
    int sum = 0;
    while (index > 0) {
        sum += tree[index];
        index -= index & -index; // 更新下一个需要查询的位置
    }
    return sum;
}

// 使用树状数组统计逆序对的个数
int countInversions(vector<int>& nums) {
    int n = nums.size();
    vector<int> tree(n + 1, 0); // 初始化树状数组
    int inversions = 0;

    for (int i = n - 1; i >= 0; i--) {
        inversions += query(tree, nums[i] - 1); // 统计小于当前数的数的个数
        update(tree, nums[i], 1); // 更新树状数组
    }

    return inversions;
}

int main() {
    vector<int> nums = {5, 2, 6, 1};
    int inversions = countInversions(nums);
    cout << "逆序对的个数为:" << inversions << endl;
    return 0;
}

​ 这段代码使用树状数组实现了统计逆序对的功能。首先定义了树状数组的更新和查询函数,分别用于更新树状数组的值和计算前缀和。然后在 countInversions 函数中,从数组的末尾开始遍历,使用树状数组来统计小于当前数的数的个数,并对树状数组进行更新。最后返回得到的逆序对个数。

​ 在 main 函数中,我们定义了一个示例数组 [5, 2, 6, 1],并调用 countInversions 函数来统计逆序对的个数。最后输出结果。

4、线段树(Segment Tree)

使用线段树来求解逆序对的C++代码示例:

C++
  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
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
#include <iostream>
#include <vector>
using namespace std;

// 线段树节点
class Node {
public:
    int start;
    int end;
    int count;
    Node* left;
    Node* right;

    Node(int start, int end) {
        this->start = start;
        this->end = end;
        this->count = 0;
        this->left = nullptr;
        this->right = nullptr;
    }
};

// 更新线段树中的节点
void update(Node* root, int index) {
    if (root == nullptr)
        return;

    if (root->start == root->end) {
        root->count++;
        return;
    }

    int mid = (root->start + root->end) / 2;
    if (index <= mid) {
        if (root->left == nullptr)
            root->left = new Node(root->start, mid);
        update(root->left, index);
    }
    else {
        if (root->right == nullptr)
            root->right = new Node(mid + 1, root->end);
        update(root->right, index);
    }

    // 更新当前节点的逆序对数量
    root->count++;
    if (root->left != nullptr)
        root->count += root->left->count;
    if (root->right != nullptr)
        root->count += root->right->count;
}

// 查询线段树中小于等于index的逆序对数量
int query(Node* root, int index) {
    if (root == nullptr || index < root->start)
        return 0;

    if (index >= root->end)
        return root->count;

    int mid = (root->start + root->end) / 2;
    int result = query(root->left, index);
    if (index > mid && root->right != nullptr)
        result += query(root->right, index);

    return result;
}

int countInversePairs(vector<int>& nums) {
    int n = nums.size();
    int count = 0;

    if (n < 2)
        return count;

    int maxNum = INT_MIN;
    int minNum = INT_MAX;
    for (int i = 0; i < n; i++) {
        maxNum = max(maxNum, nums[i]);
        minNum = min(minNum, nums[i]);
    }

    Node* root = new Node(minNum, maxNum);

    for (int i = n-1; i >= 0; i--) {
        count += query(root, nums[i] - 1);
        update(root, nums[i]);
    }

    return count;
}

int main() {
    vector<int> nums = {2, 4, 1, 3, 5};

    int inversePairs = countInversePairs(nums);
    cout << "Number of inverse pairs: " << inversePairs << endl;

    return 0;
}

​ 这段代码中,首先定义了一个线段树节点类 Node,表示线段树的节点,包含了节点的起始位置、结束位置、逆序对数量、左子节点和右子节点。然后,在 update 函数中,通过递归的方式构建线段树,并在更新节点时记录逆序对的数量。在 query 函数中,通过递归的方式查询小于等于给定位置的逆序对数量。

​ 在 countInversePairs 函数中,先找到数组中的最大值和最小值,然后创建根节点,并依次遍历数组中的元素,在遍历的过程中更新线段树和计算逆序对的数量。

​ 最后,在 main 函数中,创建一个示例数组,使用 countInversePairs 函数计算逆序对的数量,并将结果打印出来。