逆序对问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tmylzq187/article/details/51820865

1.定义

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

    例如,A=(2, 3,8, 6, 1)的逆序对有(1, 5)、(2, 5)、(3, 4)、(3, 5)、(4, 5)共5个。


2.求解给定数组A的逆序对数目

    (1)算法思想

            通过对归并排序算法(请参考:排序算法(2)——归并排序)的改写,可以得到一个时间复杂度为Θ(nlgn)的算法来计算数组A的逆序对数目。同样采用分治法:

           a.分解:将数组A[1..n]划分为包含n/2个元素的子序列;

           b.解决:递归求解两个子序列的逆序对的数目;

           c.合并:最终的逆序对数目应该为两个子序列的逆序对数目之和再加上合并后的逆序对的数目。


           因此,问题的关键在于求解两个有序序列合并后的逆序对数目。

           参考归并排序算法的Merge操作,设L[1..n1],R[1..n2],对于L中的某一元素L[i]和R中的某一元素R[j],当L[i]>R[j]时,这两个元素在A中的下标构成一个逆序对,则对于i < ii≤ n1,同样有L[ii] < R[j],则L[ii]与R[j]在A中的下标同样构成逆序对。因此,当出现L[i]>R[j]时,针对于R中的元素R[j],L中可与之构成逆序对的元素数目为n1-i+1,即有n1-i+1个逆序对。

           例如: A=(2, 5, 9,11, 3, 4, 8, 10)

                    L=(2, 5, 9, 11)       R=(3, 4, 8, 10)

                    可知,i=2,j=1时,L[2]=5, R[1]=3, 有L[2]>R[1],则可得L[3]>R[1],L[4]>R[1],分别对应A中的逆序对(2, 5),(3, 5)(4, 5)。n1=4, i=3,共n1-i+1=4-2+1=3个逆序对。


          因此,只要在归并排序每次出现L[i]>R[j]的情况时,计算针对该R[j]的逆序对数目,再求和,即可得到两个有序序列构成的数组的逆序对数目。逆序对问题即是对A边进行归并排序边求解逆序对数目的问题,其实递升排序本身就是在不断消除逆序对。

   

    (2)伪代码

             对归并排序的伪代码稍微进行修改便可得到逆序对问题的伪代码,如下:

            INVERSION(A, p, r)

             1 count   0                           
             2 if  p < r
             3     then q ← ⎣(p+r)/2⎦                                    
             4             count count + INVERSION (A, p, q)
             5             count count +INVERSION (A, q+1, r)
             6             count count +MERGEANDCOUNT (A, p, q, r)

             7 return count                          


             MERGEANDCOUNT(A, p, q, r)
             1 count ← 0     
             2 n1 ← q-p+1                                                    //计算左半部分已排序序列的长度
             3 n2 ← r-q                                                        //计算右半部分已排序序列的长度
             4 create arrays L[1..n1+1] and R[1..n2+1]      //新建两个数组临时存储两个已排序序列,长度+1是因为最后有一个标志位
             5 for i ← 1 to n1
             6      do L[i] ← A[p + i-1]                                //copy左半部分已排序序列到L中
             7 for j ← 1 to n2
             8      do R[j] ← A[q + j]                                   //copy右半部分已排序序列到R中
             9 L[n1+1] ← ∞                                                //L、R最后一位设置一个极大值作为标志位                       
            10 R[n2+1] ← ∞
            11 i ← 1
            12 j ← 1
            13 for k ← p to r                                              //进行合并
            14      do if L[i] ≤ R[j]
            15          then A[k] ← L[i]
            16                  i ← i + 1
            17          else A[k] ← R[j]
            18                  j ← j + 1
            19                  count ← count + (n1-i+1)        //计算逆序对数目


    (2)代码实现

int MergeAndCount(int A[],int p,int q,int r)
{
    int i,j,k,count=0;
    int n1=q-p+1;
    int n2=r-q;
    int *L=new int[n1+1]; //开辟临时存储空间
    int *R=new int[n2+1];
    for(i=0;i<n1;i++)
        L[i]=A[i+p];      //数组下标从0开始时,这里为i+p
    for(j=0;j<n2;j++)
        R[j]=A[j+q+1];    //数组下标从0开始时,这里为就j+q+1
    L[n1]=INT_MAX;        //"哨兵"设置为整数的最大值,INT_MAX包含在limits.h头文件中
    R[n2]=INT_MAX;
    i=0;
    j=0;
    for(k=p;k<=r;k++)     //开始合并
    {
        if(L[i]<=R[j])
            A[k]=L[i++];
        else
        {
            A[k]=R[j++];
            count+=n1-i;  //在此计算合并的逆序对数目
        }
    }
    return count;
}

int Inversion(int A[],int p,int r)
{
    int count=0;
    if(p<r)
    {
        int q=(p+r)/2;
        count+=Inversion(A,p,q);
        count+=Inversion(A,q+1,r);
        count+=MergeAndCount(A,p,q,r);
    }
    return count;
}


                   

          


  

展开阅读全文

没有更多推荐了,返回首页