归并排序

Posted engineerzhang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了归并排序相关的知识,希望对你有一定的参考价值。

目录

1.概念

将一个数组排序,可以递归的将它们分成两半分别排序,然后将结果归并起来。体现了分治思想。算法时间复杂度:O(nlgn)

2.自顶向下的归并排序算法(递归实现)

技术分享图片

上图所示为归并数组a[0...15]的调用轨迹图。

2.1初实现

template<typename T>
void mergeSort(T arr[], int n)
{
    __mergeSort(arr, 0, n - 1);
}

//对arr[l...r]进行排序
template<typename T>
void __mergeSort(T arr[], int l, int r)
{
    if (l >= r)
        return;

    int mid = (r + l) / 2;                  //int mid = l+(r-l)/2;
    __mergeSort(arr, l, mid);
    __mergeSort(arr, mid + 1, r);

    __merge(arr, l, mid, r);
}

template<typename T>
void __merge(T arr[], int l, int mid, int r)
{
    T *tmp = new T[r - l + 1];
    for (int i = l; i <= r; i++)
    {
        tmp[i - l] = arr[i];
    }

    int i = l;
    int j = mid + 1;
    for (int k = l; k <= r; k++)
    {
        if (i > mid)
        {
            arr[k] = tmp[j - l];
            j++;
        }
        else if (j > r)
        {
            arr[k] = tmp[i - l];
            i++;
        }
        else if (tmp[i - l] < tmp[j - l])
        {
            arr[k] = tmp[i - l];
            i++;
        }
        else
        {
            arr[k] = tmp[j - l];
            j++;
        }
    }

    delete[] tmp;
}

2.2 优化

技术分享图片

技术分享图片

//对arr[l...r]进行排序
template<typename T>
void __mergeSort(T arr[], int l, int r)
{
    /*if (l >= r)
    {
    return;
    }*/

    //优化二   对于小范围的数据进行插入排序
    if (r - l<15)
    {
        insertionSort(arr, l, r);
        return;
    }

    int mid = (r + l) / 2;                  //int mid = l+(r-l)/2;
    __mergeSort(arr, l, mid);
    __mergeSort(arr, mid + 1, r);

    if (arr[mid] <= arr[mid + 1])           //优化一
    {
        return;
    }
    __merge(arr, l, mid, r);
}

3.自底向上的归并排序(性能更优)

技术分享图片

上图所示为归并数组a[0...15]的调用轨迹图。

3.1初实现

template<typename T>
void mergeSortBU(T arr[], int n)
{
    //归并操作为两两操作
    //sz: 归并时每组中元素的个数
    for ( int sz=1; sz<=n; sz+=sz )
    {
        /*for (int i=0; i<n; i+=sz*2)
        {
            __merge(arr, i, i + sz - 1, i + sz * 2 - 1);
        }*/
        //i:代表数组下标,每次跳跃 (每组中元素的个数)*2组
        //for终止条件:让第二个数组起始元素小于n
        //将arr[i....i+sz-1]和arr[i+sz...i+sz*2-1] 归并
        for ( int i=0; i+sz<n; i+=sz*2 )
        {
            __merge(arr, i, i + sz - 1, min(i + sz * 2 - 1, n));
        }
    }
}

template<typename T>
void __merge(T arr[], int l, int mid, int r)
{
    //
    T *tmp = new T[r - l + 1];
    for (int i = l; i <= r; i++)
    {
        tmp[i - l] = arr[i];
    }

    int i = l;
    int j = mid + 1;
    for (int k = l; k <= r; k++)
    {
        if (i > mid)
        {
            arr[k] = tmp[j - l];
            j++;
        }
        else if (j > r)
        {
            arr[k] = tmp[i - l];
            i++;
        }
        else if (tmp[i - l] < tmp[j - l])
        {
            arr[k] = tmp[i - l];
            i++;
        }
        else
        {
            arr[k] = tmp[j - l];
            j++;
        }
    }

    delete[] tmp;
}

3.2优化

根据2.2节提到的优化,可以得到如下代码。
template <typename T>
void mergeSortBU(T arr[], int n)
{
    //首先,每16个元素为一组进行 插入排序
    for( int i = 0 ; i < n ; i += 16 )
        insertionSort(arr,i,min(i+15,n-1));

    
    for( int sz = 16; sz < n ; sz += sz )
        for( int i = 0 ; i < n - sz ; i += sz+sz )
            // 对于arr[mid] <= arr[mid+1]的情况,不进行merge
            if( arr[i+sz-1] > arr[i+sz] )
                __merge(arr, i, i+sz-1, min(i+sz+sz-1,n-1) );

}

应用:求逆序对

归并排序中的__merge()函数,是将arr[l...mid]和arr[mid+1...r]两个有序数组合并,让j指向第一个数组中要归并的元素,让k指向第二个数组中要归并的元素,若当前第一个数组中i指向的元素大于第二个数组中j指向的元素,那么可知第一个数组中arr[j...mid]都可以与arr[k]组成逆序对,逆序对数为mid-j+1。
// 注:计算逆序数对的结果以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;
    // 初始化,j指向左半部分的起始索引位置l;k指向右半部分起始索引位置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);
}

以上是关于归并排序的主要内容,如果未能解决你的问题,请参考以下文章

python代码实现归并排序(Merge Sort )

排序之外部排序

Python代码实现归并排序

Python代码实现归并排序

算法排序02——归并排序介绍及其在分治算法思想上与快排的区别(含归并代码)

排序算法之归并排序