计算逆序数:在归并和快排两种排序过程中求得逆序数的方法比较

时间:2022-07-23 11:03:43
归并排序是将数列a[l,h]分成两半a[l,mid]a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。
在合并的过程中(设
l<=i<=midmid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,
在前半部分中比
a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i
因此,可以在归并排序中的合并过程中计算逆序数。
源代码(C语言)
#include<stdio.h>
#include<stdlib.h>

_int64 inversionCount=0;
void merge_and_count(int *a,int p,int q,int r){//从零开始计数
    int i,j,t;
    i=p;
    j=q+1;
    t=0;
    int *templist = (int *)malloc(sizeof(int) * (r-p+1));
    while(i<=q && j<=r){
        if(a[i]<a[j]){
            templist[t++] = a[i];
            i++;
        }
        else{
            templist[t++] = a[j];
            inversionCount+=(q+1-i);
            j++;
        }
    }
    while(i<=q){
        templist[t++] = a[i++];
    }
    while(j<=r){
        templist[t++] = a[j++];
    }
    t=0;
    for(int k=p;k<=r;k++)
        a[k] = templist[t++];
}

void merge_sort(int *a,int p,int r){
    if(p<r){
        int q=(p+r-1)/2;
        merge_sort(a,p,q);
        merge_sort(a,q+1,r);
        merge_and_count(a,p,q,r);
    }
}
 

Quick_sort算法来数逆序数(这个多说一些)

 
    

该算法主体与quick_sort相似,给定一个序列,先找到一个分割点splitter,然后quick_sort关于splitter节点两半部的交换过程之间查找一个相对逆序数,包括三部分:左半部的相对逆序数,右半部的相对逆序数,左右两边的相对逆序数。分成两半之后,依次递归,得出结果。以{2 7 8 3 6 9 4 5 10}为例,令splitter=(left+right)/2,分为两部分S- 和 S+:

左半部(从右往左splitter-->left)

如果data[i]>data[splitter],数出位于该点右边小于或等于data[splitter]的元素的个数,将该计数(如图加粗画底线部分计数)加入逆序数计数InversionCount,并把该元素加入S+,如果data[i]<data[splitter],操作与quick_sort相似。

2

7

8

3

6(splitter)

9

4

5

10

 

例如data[i] = 8 > data[splitter] = 6 ; 黑体部分计数为2,因此遍历到8InversionCount += 2.

右半部(从左往右splitter-->right)

如果data[i] < data[splitter],数出位于该点左边小于或等于data[splitter]的元素的个数,将该计数(如图加粗画底线部分计数)加入逆序数计数InversionCount,并把该元素加入S-,如果data[i] > data[splitter],操作与quick_sort相似。

2

7

8

3

6(splitter)

9

4

5

10

 

例如,data[i] = 5 < data[splitter] = 6黑体部分计数为2,因此遍历到5InversionCount += 2.

交叉部分:

数出splitter左半部比data[splitter]大的元素个数lr,数出splitter右半部比data[splitter]小的元素的个数rl,然后InversionCount +=lr * rl。例子中InversionCount += 2 * 2.

2

7

8

3

6(splitter)

9

4

5

10

程序代码(C语言)
#include<stdio.h>
#include<stdlib.h>

_int64 InversionCount=0;
void merge_count(int *data,int & splitter,int left,int right){
    if(left > splitter || splitter > right)
        return;
    int pivot= data[splitter];
    int *tempLL,*tempLR,*tempRL,*tempRR;
    int ll=0,lr=0,rl=0,rr=0;
    int i;
    bool flag1=false,flag2=false;

    //左边求相对逆序数,遍历顺序从splitter往左
    if(left < splitter){
        flag1 = true;
        tempLL = (int *)malloc(sizeof(int) * (splitter - left));
        tempLR = (int *)malloc(sizeof(int) * (splitter - left));
        for(i=splitter - 1; i>=left; i--)
            if(data[i] < pivot)
                tempLL[ll++] = data[i]; //ll为left部分目前比splitter小但离splitter最近的点的个数
            else{
                tempLR[lr++] = data[i];
                InversionCount += ll+1; //ll+1 为该点对于left部分比splitter小但处于该点右边的点的逆序数
            }
    }
    //右边求相对逆序数,遍历顺序从splitter往右
    if(right>splitter){
        flag2 =true;
        tempRL = (int *)malloc(sizeof(int) * (right - splitter));
        tempRR = (int *)malloc(sizeof(int) * (right - splitter));
        for(i=splitter+1; i<=right; i++)
            if(data[i] > pivot)
                tempRR[rr++] = data[i]; //rr为right部分目前比splitter大但离splitter最近的点的个数
            else{
                tempRL[rl++] = data[i];
                InversionCount += rr+1; //rr+1为该点对于right部分比splitter大但处于该点左边的点的逆序数
            }
    }
    //加上left和right之间的相对逆序数
    InversionCount += lr * rl;

    //将新的序列拷贝到原数组,注意拷贝的顺序
    int k=left;
    for(i=ll-1; i>=0; i--)
        data[k++] = tempLL[i];
    for(i=0; i<rl; i++)
        data[k++] = tempRL[i];
    
    splitter  = k; 
    data[k++] = pivot;
    for(i=lr-1; i>=0; i--)
        data[k++] = tempLR[i];
    for(i=0; i<rr; i++)
        data[k++] = tempRR[i];

    if(flag1)
        delete tempLL,tempLR;
    if(flag2)
        delete tempRL,tempRR;
}

void quicksort_count(int *data,int left,int right){

    if(left>=right)
        return;
    int splitter = left;
    merge_count(data, splitter, left, right);
    quicksort_count(data,left,splitter-1);
    quicksort_count(data,splitter+1,right);
}

所有基于比较的排序算法中,快速排序的平均效率是最高的(尽管它性能不太稳定),以上两种求逆序数算法是对归并排序和快速排序算法的稍稍改进,
以下是对十个包含100000个各不相同随机自然数文件的实验结果:
计算逆序数:在归并和快排两种排序过程中求得逆序数的方法比较

可以看出快排求逆序数的算法效率更好一些。