跳转至

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 函数计算逆序对的数量,并将结果打印出来。


0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn
0594codes.cn