• 【算法32】计算数组中的逆序对


    问题描述

        设 A[1...n] 是一个数组,如果对于 i < j 有 A[i] > A[j], 则 A[i] 和 A[j] 构成一对逆序。给定一个数组,计算数组中逆序对的个数。例如数组 a[] = {1, 4, 3, 2}, 则 {4, 3} {4, 2} {3, 2}是逆序对,返回 3。

    解法一:暴力求解

        两个 for 循环枚举所有的数对,如果是逆序对,则 count++,最终返回 count 即可。时间复杂度 O(n^2),代码如下:

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 using namespace std;
     5 
     6 int CountInversions(const vector<int>& a)
     7 {
     8     int cnt = 0;
     9     for (size_t i = 0; i < a.size(); i++)
    10     {
    11         for (size_t j = i + 1; j < a.size(); j++)
    12         {
    13             if (a[i] > a[j]) cnt++;
    14         }
    15     }
    16     return cnt;
    17 }
    18 
    19 int main()
    20 {
    21     int a[] = {1, 4, 3, 2};
    22     vector<int> v(a, a + 4);
    23     cout << CountInversions(v) << endl;
    24     return 0;
    25 }

    解法二:Divide & Conquer (修改归并排序)

        这个解法在《算法导论》中归并排序那一节的思考题 2-4 中有提到。原题如下:

        设 A[1...n] 是一包含 n 个不同数的数组,如果在 i < j 的情况下,有 A[i] > A[j], 则 (i, j) 称为 A 中的一个逆序 (inversion)。

        (a) 列举出 <2, 3, 8, 6, 1> 的 5 个逆序对。      

        答: <2, 1> <3, 1> <8, 6> <8, 1> <6, 1>

        (b) 如果数组的元素取自集合 {1, 2, 3, ..., n}, 那么怎么样的数组含有最多的逆序? 它包含多少个逆序?

        答:n 个元素最多有 n(n-1) / 2 个数组,在最坏的情况下,所有的数对都是逆序,因而最多有 n(n-1)/2 个逆序。这样的数组为倒序 {n,n-1, ..., 2, 1}

        (c) 插入排序的运行时间与输入数组中逆序对的数量之间有怎么样的关系?说明你的理由。

        答:插入排序的核心过程如下,可以看到内循环的执行实际上是因为 {i, j} 构成了逆序,换句话说,内循环执行完毕后后 {i, j}的逆序被消去,所以插入排序总的循环次数 == 数组中逆序对个数。

    1 for (int i = 1; i < n; ++i)
    2 {
    3     int x = a[i];
    4     for (int j = i - 1; j >= 0 && a[j] > x; --j)
    5     {
    6         a[j + 1] = a[j];
    7     }
    8     a[j + 1] = x;
    9 }

        (d) 给出一个算法,它能用 O(nlogn) 的最坏情况运行时间,确定 n 个元素的任何排列中的逆序对数。(提示:修改归并排序)

        归并排序的基本思想就是 Divide & Conquer: 将数组划分为左右两部分,归并排序左部分,归并排序右部分,然后 Merge 左右两个数组为一个新的数组,从而完成排序。按照这个基本思想,我们也可以运用到计算逆序对中来,假设我们将数组划分左右两部分,并且我告诉你左边部分的逆序对有 inv1 个, 右边部分的逆序对有 inv2 个,如下图所示

          那么剩余的逆序对必然是一个数出现在左边部分,一个数出现在右边部分,并且满足出现左边部分的数 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) 个即可。

    至此整个算法的框架图可以如下表示:

    整个算法的代码如下,时间复杂度当然是 O(nlogn)

     1 #include <iostream>
     2 #include <string>
     3 #include <vector>
     4 using namespace std;
     5 
     6 
     7 typedef long long llong;
     8 
     9 llong MergeAndCount(vector<unsigned int> &a, int left, int mid, int right, vector<unsigned int> &temp);
    10 llong MergeSortAndCount(vector<unsigned int>& a, int left, int right, vector<unsigned int>& temp);
    11 llong CountInversions(vector<unsigned int>& a);
    12 
    13 int main()
    14 {
    15     //freopen("data.in", "r", stdin);
    16 
    17     int N;
    18     cin >> N;
    19 
    20     vector<unsigned int> a(N, 0);
    21     for (int i = 0; i < N; ++i)
    22     {
    23         cin >> a[i];
    24     }
    25 
    26     llong result = CountInversions(a);
    27     cout << result << endl;
    28     return 0;
    29 }
    30 
    31 llong MergeAndCount(vector<unsigned int> &a, int left, int mid, int right, vector<unsigned int> &temp)
    32 {
    33     int i = left;
    34     int j = mid + 1;
    35     int k = left;
    36     llong cnt = 0;
    37 
    38     while (i <= mid && j <= right)
    39     {
    40         if (a[i] <= a[j])
    41         {
    42             temp[k++] = a[i++];
    43         }
    44         else 
    45         {
    46             temp[k++] = a[j++];
    47             cnt += mid - i + 1;    // key step
    48         }
    49     }
    50 
    51     while (i <= mid) temp[k++] = a[i++];
    52     while (j <= right) temp[k++] = a[j++];
    53 
    54     for (i = left; i <= right; ++i)
    55     {
    56         a[i] = temp[i];
    57     }
    58     return cnt;
    59 }
    60 
    61 llong MergeSortAndCount(vector<unsigned int>& a, int left, int right, vector<unsigned int>& temp)
    62 {
    63     // base case
    64     if (left >= right) return 0;
    65 
    66     int mid = (left + right) / 2;
    67     
    68     llong InversionCnt1 = MergeSortAndCount(a, left, mid, temp);
    69     llong InversionCnt2 = MergeSortAndCount(a, mid+1, right, temp);
    70     llong MergeInversionCnt = MergeAndCount(a, left, mid, right, temp);
    71 
    72     return InversionCnt1 + InversionCnt2 + MergeInversionCnt;
    73 }
    74 
    75 llong CountInversions(vector<unsigned int>& a)
    76 {
    77     int n = a.size();
    78     vector<unsigned int> temp(a.begin(), a.end());
    79     llong ans = MergeSortAndCount(a, 0, n-1, temp);
    80     return ans;
    81 }

     参考文献

    [1] 《算法导论》中文第二版, P24,2-4 逆序对。

    [2]  http://blog.csdn.net/qcgrxx/article/details/8005221

    [3]  www.cs.umd.edu/class/fall2009/cmsc451/lectures/Lec08-inversions.pdf

  • 相关阅读:
    MySql的常用命令
    yum命令配置及使用说明和常见问题处理
    oracle12c创建用户和表空间出现的问题
    oracle云部署
    ORA-12154: TNS:could not resolve the connect identifier specified
    Linux之iptables
    Linux之MySQL
    Linux之apache
    oracle查锁表
    cookie 和 HttpSession
  • 原文地址:https://www.cnblogs.com/python27/p/4381953.html
Copyright © 2020-2023  润新知