排序算法总结——归并排序

版权声明:转载请注明出处!谢谢! https://blog.csdn.net/qq_28114615/article/details/86345717

1. 算法原理及步骤

2. 代码实现

3. 复杂度分析

4. 稳定性分析


1. 算法原理及步骤

       归并排序体现的是一种分治+合并的思想,我们知道,数组长度越小,排序越简单,而不管数组有多大,都是由小数组构成的,因此,要想对一个长度为N的数组进行排序,就可以将其进行分割,分割到大小为1的小数组,然后再将每个小数组进行排序再合并,最终合并为有序的原数组,如图所示:

       由图可知,归并排序主要是将原数组拆分成长度为1的小数组中,然后再将这些小数组两两归并再排序,然后再将归并后的数组两两归并再排序.....最终归并为长度为n的数组再对其进行排序即可。那么问题来了:归并最终也要对n的数组排序呀,它的效率体现在哪里呢?

        实际上,在每两个小数组归并的过程中,都是会对其归并结果进行排序的。最小的数组长度就是1,本身就是有序的,两个长度为1的数组再进行合并成长度为2的小数组并对其进行排序,然后再归并、再归并、....再归并为n的数组,可以发现,每次进行归并的两个小数组都是分别有序的,这就是归并排序能体现其效率的关键:相比于对无序数组排序,合并两个有序数组的时间复杂度只需要O(N)。

       因此,归并排序最关键的就是对两个有序数组进行排序了,可以采用双指针的方法,一开始分别指向两个数组头部元素,然后比较二者大小,将较小的作为合并结果的第一个元素,然后将较小数的指针往后移动一步,然后再比较,再将较小的作为合并结果的第二个元素......直到其中某一个指针走到尾部了,那么此时只需要将还没到尾的另一个数组剩下的元素直接按序放入合并数组的后面即可,整个过程如图所示:

     通过以上可以知道,归并排序实际上就是数组分割+合并排序。而其中数组分割是将数组二分为左半区间和右半区间,然后再对左半区间、右半区间进行二分.....直到最终数组长度为1,而合并排序即是对长度为1的数组开始自下而上开始两两归并并排序,最终得到有序的原数组。(以上图片来自于https://www.cnblogs.com/chengxiao/p/6194356.html

       总结可得递归排序的步骤:

       ①找到中点;

       ②根据中点划分左子区间及右子区间,并且分别递归两子区间;

       ③合并递归后的子区间。


2. 代码实现

void mergeTwoSortedParts(vector<int>& nums,int left,int right,int mid)//合并数组中的两个有序子区间,需要指定整个区间范围以及划分出子区间的中点
{
    int i=left;   //指向左区间头
    int j=mid+1;  //指向右区间头
    vector<int>temp;  //临时数组存放合并结果

    while(i<=mid&&j<=right)
    {
        if(nums[i]<nums[j])temp.push_back(nums[i++]);  //如果左区间数较小就放到临时数组中,然后左指针右移
        else temp.push_back(nums[j++]); //如果右区间数较小就放到临时数组中,然后右指针右移
    }

    if(i<=mid)  //如果左区间还没遍历完,直接将左区间剩下的接在临时数组后面
    {
        for(int l=i;l<=mid;l++)temp.push_back(nums[l]);
    }

    if(j<=right) //如果右区间还没遍历完,直接将右区间剩下的接在临时数组后面
    {
        for(int l=j;l<=right;l++)temp.push_back(nums[l]);
    }
    for(auto x:temp)nums[left++]=x;  //此时临时数组已经排好序,将临时数组复制到原数组中即可
    return ;
}
void divArraySort(vector<int>& nums,int left,int right)//分割排序函数,参数为待排序数组及区间
{
    if(right-left==0)return ; //数组分割到长度为1时返回

    int mid=left+(right-left)/2;  //取中点二分

    divArraySort(nums,left,mid); //分割左子区间并排序
    divArraySort(nums,mid+1,right); //分割右子区间并排序
    
    mergeTwoSortedParts(nums,left,right,mid); //对排序好的左子区间和右子区间进行合并排序
    return ;

}
void MergeSort(vector<int>& nums,int len) //归并排序函数,参数为待排序函数及其长度
{
    if(!len)return;
    divArraySort(nums,0,len-1);  //归并排序
    return;
}

3. 复杂度分析

       不难看出,归并排序的过程实际就是将原数组不断二分,到最后子数组长度为1,二分的次数当然就是logN次了,而另一方面,每一次二分都还需要将二分的数组再合并排序,这个排序的过程就是O(N)了,因此整个归并排序不管是最好情况还是最坏情况都一样,时间复杂度就是O(NlogN),当然其中还包括临时数组向原数组赋值的O(N)时间复杂度。在空间复杂度方面,开销主要是由合并排序引起的,合并排序需要一个与原数组大小相同的临时数组来存放合并结果,因此空间复杂度为O(N)

4. 稳定性分析

       由归并过程可知,只会改变两个有大小关系的元素间的位置,对于相等元素是不会改变二者相对位置的,因此归并排序是稳定的。

猜你喜欢

转载自blog.csdn.net/qq_28114615/article/details/86345717