高级排序算法(5)——归并排序理解及其代码

本文深入探讨了归并排序的分治思想,重点在于自底向上的迭代归并算法。通过将数据不断分割成小段,直至单个元素,然后逐步归并,确保排序稳定性。在近乎有序的数组中,可以优化归并过程,甚至使用插入排序提高效率。文章接下来会详细讲解这种迭代方法的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

归并排序理解

归并排序采用的分治的思想,所谓分治思想即分而治之,讲一个整体分为若干个子模块,然后对每个子模块进行处理,合并起来之后便对整体完成了处理;我们这里采用的是二路归并排序算法;首先对一组数据进行分割,一般我们会进行对半分,即mid = length /2;作为中间点,紧接着同样对每个分割好的模块再次进行按如上的方式分割,直至最后会被分成单个一组或者两个一组的数组,然后依次向上进行归并排序,重点的算法就在归并这里;如下图所示:

进行向上归并的算法是是将两组数据 复制出来一份,然后给定三个索引 i ,j ,k ;i指向数组左半部分的首元素,j 指向右半部分的首元素,k指向 原来数组的首元素;

 

代码如下:

// 将arr[l...mid]和arr[mid+1...r]两部分进行归并
template<typename  T>
void __merge(T arr[], int l, int mid, int r){
    
    //* VS不支持动态长度数组, 即不能使用 T aux[r-l+1]的方式申请aux的空间
    //* 使用VS的同学, 请使用new的方式申请aux空间
    //* 使用new申请空间, 不要忘了在__merge函数的最后, delete掉申请的空间:)
    T aux[r-l+1];  //申请新的一个数组空间
    //T *aux = new T[r-l+1];
    
    for( int i = l ; i <= r; i ++ )
        aux[i-l] = arr[i]; //将原来数组中的数值复制到新开辟的数组中
    
    // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
    int i = l; //i指向左半部分的起始索引位置l
    int j = mid+1; //j指向右半部分起始索引位置mid+1
    for( int k = l ; k <= r; k ++ ){
        
        if( i > mid ){  // 如果左半部分元素已经全部处理完毕
            arr[k] = aux[j-l]; j ++;
        }
        else if( j > r ){  // 如果右半部分元素已经全部处理完毕
            arr[k] = aux[i-l]; i ++;
        }
        else if( aux[i-l] < aux[j-l] ) {  // 左半部分所指元素 < 右半部分所指元素
            arr[k] = aux[i-l]; i ++;
        }
        else{  // 左半部分所指元素 >= 右半部分所指元素
            arr[k] = aux[j-l]; j ++;
        }
    }
    
    //delete[] aux;
}

// 递归使用归并排序,对arr[l...r]的范围进行排序
template<typename T>
void __mergeSort(T arr[], int l, int r){
    
    //if( l >= r )
        //return;
    if(r-l+1 <= 15)//优化点2: 由于近乎有序的数组对使用归并排序并不是 那么理想设置一定的阈值 数组长度小于一定的数值时 可以采用 插入排序 
    {
        InsertionSort02(arr,l,r);
        return ;
    }
    
    int mid = (l+r)/2;
    __mergeSort(arr, l, mid);
    __mergeSort(arr, mid+1, r);
    if(arr[mid] > arr[mid+1])  //优化点1:如果左区间最后一个元素数值 小于 右区间首元素说明数组已经有序 不需要归并
        __merge(arr, l, mid, r);
}

template<typename T>
void mergeSort(T arr[], int n){
    
    __mergeSort( arr , 0 , n-1 );
}
// 比较InsertionSort和MergeSort两种排序算法的性能效率
// 整体而言, MergeSort的性能最优, 对于近乎有序的数组的特殊情况, 见测试2的详细注释
int main(int argc, const char * argv[]) {
    // insert code here...
    int n = 50000;
    //cout<<"test for random array size = "<<n<<" [0 ,"<<n<<"]"<<endl;
    
    /*
     在数据量非常大的时候 归并排序的时间短 插入排序的时间长一些
     在近乎有序的数组中排序 插入排序的优势较为明显 
     */
    //int *arr1  = SortTestHelper::generateRandArray(n, 0, n);
    //int *arr2 = SortTestHelper::copyArray(arr1, n);
    
    
    
    cout<<"test for generateNearlyOrderedArray random array size = "<<n<<" [0 ,"<<n<<"]"<<endl;
    int swapTime = 800; //交换次数
    int* arr1 = SortTestHelper::generateNearlyOrderedArray(n, swapTime);
    //int *arr1  = SortTestHelper::generateRandArray(n, 0, n);
    int* arr2 = SortTestHelper::copyArray(arr1, n);
    int* arr3 = SortTestHelper::copyArray(arr1, n);
    
    SortTestHelper::testSort("MergeSort", mergeSort, arr1, n);
    SortTestHelper::testSort("InsertionSort",InsertionSort1, arr2, n);
    SortTestHelper::testSort("MergeSortBU", mergeSortBU, arr3, n);
    
    
   // SortTestHelper::printArray(arr1, n);

    delete[] arr1;
    delete[] arr2;
    delete[] arr3;
    
    return 0;
}

最差时间复杂度:O(logn)  在近乎有序的数组排序的时候 会退化为 该复杂度
平均时间复杂度:O(nlogn)
最差空间复杂度:O(n)
稳定性:稳定

(1)可优化点1:在进行两组数组merge的时候,如果左区间的尾元素小于左区间的首元素,这时候两组数组是有序的,因此可以不需进行归并操作

(2)再利用近乎有序的数组进行归并排序的时候,想象下左右区间的数组都是有序的,因此如果在进行归并排序的话会造成时间的浪费,因此可以设置下,在近乎有序的数组中可以采用插入排序的算法进行优化,插入排序算法有明显的优势。

上述部分阐述的是自顶向下的递归的方式的归并排序,因此下面将阐述自底向上的迭代的方式的归并算法

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值