美文网首页
归并排序的应用——求解逆序数

归并排序的应用——求解逆序数

作者: 徒步青云 | 来源:发表于2018-07-23 10:11 被阅读67次

问题描述:
输入:无序数组
输出:数组中存在的逆序数个数
分析:本题目是在不改变原数组的情况下进行问题求解,可采用朴素算法(暴力求解)和归并算法(分治思想)进行求解。

朴素算法:

int inversions(T arr[],int n)
{
        //略
}

采用分治思想进行求解

分治算法思想在于分而治之,按照:分解->计算->合并这个流程进行计算,而计算这个步骤,又可将其进行递归求解,即再分解->再计算->再合并,通过将复杂问题分解成简单问题,再合并计算,能够使算法复杂度降低到nlogn大小。其算法主体部分如下:

template<typename T>
int __inversions(T arr[],int l, int r){
    if(l+1 >= r)//递归退出条件
        return 0;

    //分治
    int mid = (l+r)/2;
    int li = __inversions(arr, l, mid,type);
    int ri = __inversions(arr, mid, r,type);

    return li+ri+__merge(arr,l,mid,r);//合并
}

//求解函数
template<typename T>
int inversions(T arr[],int n)
{
    // SortTestHelper::copyIntArray函数,是对原有数组进行copy
    //防止在归并过程中,对原数组组成破坏
    T* temp = SortTestHelper::copyIntArray(arr, n);
    int result = __inversions(temp, 0, n);
    delete[] temp;
    return result;
}

其中函数__merge(arr,l,mid,r)为核心算法部分:
算法1:
每次归并过程中,先将左右子数组进行排序,然后再求解逆序数

#include<algorithm>

template<typename T>
int __merge(T arr[], int l, int mid, int r){
    int a_len = mid - l;
    int b_len = r - mid;
    T* a = SortTestHelper::copyIntArray(&arr[l], a_len);
    T* b = SortTestHelper::copyIntArray(&arr[mid], b_len);
    sort(a,a+a_len);
    sort(b,b+b_len);

    int num = 0;

    if(a[0] > b[b_len - 1]){
        num = a_len * b_len;
    }else if(a[a_len - 1] <= b[0]){
        num = 0;
    }else {
        int i=0, j=0;
        while(i < a_len && j < b_len){
            if(a[i] > b[j]){
                num += (a_len-i);
                j++;
            }else{
                i++;
            }
        }
    }

    delete[] a;
    delete[] b;
    return num;
}

算法2:
将原数组逐渐归并,使用插入排序进行计算逆序数,由于每次合并过程中,左右子数组已经有序,故省去了重复排序的过程。

template<typename T>
int __merge(T arr[], int l, int mid, int r){
    //对于数组arr,其[l,mid)和[mid,r)内的子数组已有序
    //然后分情况讨论

    ///情况1:[l,r)范围内数组都有序
    if(arr[mid-1]<=arr[mid])
        return 0;

    ///情况2:[l,mid)全部大于[mid,r)
    if(arr[l]>arr[r-1]){
        __inv(arr,l,mid,r);
        return (mid-l)*(r-mid);
    }

    ///情况3:在[l,r)范围中,由于两个子数组[l,mid)和[mid,r)分别有序,故可采用插入排序思想进行计算合并的逆序数
    int num = 0;
    for(int i=mid; i<r; i++){
        T e = arr[i];
        int j;
        for(j=i; j>l && arr[j-1]>e; j--){
            arr[j]=arr[j-1];
            num++;
        }
        arr[j]=e;
    }

    return num;
}

算法3:
用空间效率换来时间效率,采用归并进行求解。


template<typename T>
int __merge(T arr[], int l, int mid, int r){
    //对于数组arr,其[l,mid)和[mid,r)内的子数组已有序
    //然后分情况讨论

    ///情况1:[l,r)范围内数组都有序
    if(arr[mid-1]<=arr[mid])
        return 0;

    ///情况2:[l,mid)全部大于[mid,r)
    if(arr[l]>arr[r-1]){
        __inv(arr,l,mid,r);
        return (mid-l)*(r-mid);
    }

    ///情况3:在[l,r)范围中,通过寻找最小数组[l,min),和最大数组[max,r),缩小求解范围
    ///只需求得[min,max)中逆序数即可
    int min=l,max=r;
//    while(arr[min] <= arr[mid])
//        min++;
//    while(arr[max-1] >= arr[mid-1])
//        max--;

    int a_len = mid - min;
    int b_len = max - mid;
    T* a = SortTestHelper::copyIntArray(&arr[min], a_len);
    T* b = SortTestHelper::copyIntArray(&arr[mid], b_len);

    int num=0, i=0, j=0;
    for(int k=min; k<max; k++){
        if(i >= a_len){
            ///这里不需要copy的原因是,当a结束时,b剩余的[j,b_len)的子数组与原arr[k,max)相同
            //copy(b+j,b+b_len,arr+k);
            break;
        }
        else if(j >= b_len)
        {
            copy(a+i, a+a_len, arr+k);
            break;
        }
        else if(a[i] > b[j]){
            arr[k] = b[j];
            num += (a_len-i);
            j++;
        }else{
            arr[k] = a[i];
            i++;
        }
    }

    delete[] a;
    delete[] b;

    return num;
}

其中__inv函数,作用是将左右子数组进行交换,虽然mid是当前操作的子数组中点,但并不疑问着左右子数组长度相等,故需要考虑越界问题。

//将左右子数组进行替换
template<typename T>
void __inv(T arr[], int l, int mid, int r){
    int l_len = mid-l;//左子数组长度
    T* temp =  SortTestHelper::copyIntArray(&arr[l], l_len);
    copy(arr+mid,arr+r,arr+l);
    copy(temp, temp+l_len, arr+(r-l_len));
    delete[] temp;
}

结果

测试数据为10000000个不重复顺序数组,随机交换10000000次。
结果如图:


image.png

相关文章

  • 归并排序的应用——求解逆序数

    问题描述:输入:无序数组输出:数组中存在的逆序数个数分析:本题目是在不改变原数组的情况下进行问题求解,可采用朴素算...

  • Java程序员必看的动图解析面试常见排序算法(下)

    归并排序 归并排序是分治算法的典型应用.所谓归并即是将两个有序的数组归并成一个更大的有序数组. 它有一个主要的缺点...

  • php常用的排序算法与二分法查找

    一 : 归并排序 将两个的有序数列合并成一个有序数列,我们称之为"归并"。归并排序(Merge Sort)就是利用...

  • 归并排序

    归并排序是利用归并的思想对数列进行排序。--将两个有序数列合并成一个有序数列,称之为“归并”,归并包括从上往下和从...

  • 算法 | 归并排序

    归并排序 归并排序算法的核心就是 “归并”,将两个有序的数列合并,形成更大的有序数组。 归并排序的原理 上面说了,...

  • 面试题总结

    算法思维 简述归并排序归并排序是一个使用分治策略来对乱序数组进行排序的算法,使用的思想是将乱序数组不断切割分解成若...

  • 归并排序

    归并排序的基本思路: merge函数将两个有序数列归并到一个有序数列中 根据分治法,利用递归,不断归并有序数列 递...

  • 3.归并排序

    1.什么是归并排序? 归并:将两个有序数组合并成一个更大的有序数组。 归并排序有两个主要操作:递归和合并。要将一个...

  • 排序

    归并排序,N个有序数组的归并排序 无序数组查找中位数 1.1 将前(n+1)/2个元素调整为一个最小堆; 1.2 ...

  • 归并排序

    将两个的有序数列合并成一个有序数列,我们称之为"归并"。 归并排序(Merge Sort)就是利用归并思想对数列进...

网友评论

      本文标题:归并排序的应用——求解逆序数

      本文链接:https://www.haomeiwen.com/subject/bsdlmftx.html