应用归并排序解决——求逆序对问题

前不久了解了归并排序,那么归并排序有没有什么特殊的应用呢?当然,我们可以运用归并排序来计算一个数组中有多少个逆序对数,然而,求逆序对数好像有更简单的办法,没错,如果一个大小为n的数组,我们可以用两个循环去比较数组中任意的两个值,然后累加出逆序对的数量,这样的算法虽然思路简单,但是解决数组数量较小的问题还是可以的,如果数组数量非常的大,那么作为一个O(n^2)复杂度的算法,计算机算出逆序对将要花费大量的时间,这个时候,我们就可以借助归并算法来解决这个问题。


首先,让我们来复习一下归并算法的图解:


归并算法不停的把一个较大的数组去分解为小的数组,分到不能够在分(达到出口条件)则停止分组,然后第二步在进行排序的操作,我们只需要在后面加一个第三步,顺便计算逆序对的数量,随着递归的不停调用,逆序对数量慢慢累加,最后返回出来的结果就是所求。

对于上图,归并算法在第二步的时候,因为1小于2,所以arr[0]被赋值为1,之后我们可以清楚的发现,因为递归返回的数组是已经完全排好序的,所以1不仅比2小,而且比2后面的所有数字(3,6,8)都要小,因此我们的逆序对数只要在原来的基础上在加上一个4即可,其他的步骤以此类推,以下是具体的源码实现:



// 计算逆序数对的结果以long long返回
// 对于一个大小为N的数组, 其最大的逆序数对个数为 N*(N-1)/2, 非常容易产生整型溢出

// merge函数求出在arr[l...mid]和arr[mid+1...r]有序的基础上, arr[l...r]的逆序数对个数
long long __merge( int arr[], int l, int mid, int r){

    int *aux = new int[r-l+1];
    for( int i = l ; i <= r ; i ++ )
        aux[i-l] = arr[i];

    // 初始化逆序数对个数 res = 0
    long long res = 0;
    // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
    int j = l, k = mid + 1;
    for( int i = l ; i <= r ; i ++ ){
        if( j > mid ){ // 如果左半部分元素已经全部处理完毕
            arr[i] = aux[k-l];
            k ++;
        }
        else if( k > r ){ // 如果右半部分元素已经全部处理完毕
            arr[i] = aux[j-l];
            j ++;
        }
        else if( aux[j-l] <= aux[k-l] ){ // 左半部分所指元素 <= 右半部分所指元素
            arr[i] = aux[j-l];
            j ++;
        }
        else{ // 右半部分所指元素 < 左半部分所指元素
            arr[i] = aux[k-l];
            k ++;
            // 此时, 因为右半部分k所指的元素小
            // 这个元素和左半部分的所有未处理的元素都构成了逆序数对
            // 左半部分此时未处理的元素个数为 mid - j + 1
            res += (long long)(mid - j + 1);
        }
    }

    delete[] aux;

    return res;
}

// 求arr[l..r]范围的逆序数对个数
// 思考: 归并排序的优化可否用于求逆序数对的算法? :)
long long __inversionCount(int arr[], int l, int r){

    if( l >= r )
        return 0;

    int mid = l + (r-l)/2;

    // 求出 arr[l...mid] 范围的逆序数
    long long res1 = __inversionCount( arr, l, mid);
    // 求出 arr[mid+1...r] 范围的逆序数
    long long res2 = __inversionCount( arr, mid+1, r);

    return res1 + res2 + __merge( arr, l, mid, r);
}

// 递归求arr的逆序数对个数
long long inversionCount(int arr[], int n){

    return __inversionCount(arr, 0, n-1);
}

我定义了一个只有5个数的数组进行测试,以下为测试结果:

int main()
{
    int arr[5]={10,9,8,7,6};
    int result=inverstion(arr,0,4);
    cout<<result;
    cout<<endl;
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值