高等排序——归并排序

前面学习了初等排序,其时间复杂度一般为O(n^2),当面对庞大的输入数据时,这种排序效率就显得十分低下,在这里,我们运用递归与分治的思想,可以实现更加高效的排序算法

今天我学习的是归并排序

归并排序

我们先引入这样一个情景:

给定两个非严格递增数组a与b,我们要将其合并成为一个非严格递增数组c,该如何进行处理呢?

显然,只需要开两个指针分别指向a和b,利用其有序的特点,将两个指针指向的较小的元素先填入数组c,如此循环,直到将a,b的元素全部填完,就可以得到答案了

在上述的排序方法中,时间复杂度为O(n+m),n、m分别为数组a、b的长度

可见,如果我们在给数组排序的时候,能够将其一半一半地分解,再排序成为两个有序的数组,再进行合并,就可以大大减少时间复杂度

这时我们可以发现,这个分解的方法,不正是我们分治思想的体现吗?

分解的两个子数组,又可以继续分解为他们自身的子数组,如此往复,直到数组不可再分

而这个方法的具体实现,就要依靠函数的递归

这样一来,一个长度为N的数组,大概需要经过log_{2}^{N}次的分解

再加上子数组自身需要O(n)的时间复杂度,我们可以得到归并排序的时间复杂度为O(nlogn),这已经是相当高效的!

当然,我们在归并排序的时候,需要临时占用部分内存空间来保存子数组,这是一种牺牲空间换时间的策略

实现代码如下:

//merge:用于将两个有序子数组合并的函数
//nums:需要排列的数组
//temp:临时储存合并排序的数组,长度与nums相同
//left:指向第一个子数组的第一个元素
//mid:指向第一个子数组的最后一个元素
//right:指向第二个子数组的最后一个元素
void merge(int* nums, int* temp, int left, int mid, int right){
    int i=left;
    int j=mid+1;                  //j指向第二个子数组的第一个元素,用于遍历第二个子数组
    int k=left;                   //k用来指向temp数组中与第一个子数组的第一个元素相同下标的地方,用于后面对应合并数组
    while(i<mid+1 && j<right+1){ //条件防止溢出
        if(nums[i]<nums[j]){
            temp[k++]=nums[i++];  //第一个子数组的元素较小,则将它填入临时数组,然后k,i分别+1
        }
        else{
            temp[k++]=nums[j++];  //同理第二个数组
        }
    }                             //当跳出循环时,两个子数组至少有一个遍历完毕,则我们需要将未遍历完的数组元素全部填入temp
    while(i<mid+1){              //第一个子数组未遍历完时:
        temp[k++]=nums[i++];
    }
    while(j<right+1){             //第二个子数组未遍历完
        temp[k++]=nums[j++];
    }
                                  //此时,我们得到合并完的数组temp
    for(int n=left;n<right+1;n++){
        nums[n]=temp[n];          //将临时数组temp复制到原数组nums中
    }
}
//MergeSort:用于实现归并排序的函数
//nums:同上
//temp:同上
//left:输入数组的左边界(对于一个子数组而言)
//right:输入数组的右边界
void MergeSort(int* nums, int* temp, int left, int right){
    if(left<right){//子数组元素个数大于1个时
        int mid=left+(right-left)/2;  //将其分割,算式是防止int溢出
        MergeSort(nums, temp, left, mid);//对分割出的第一个子数组排序
        MergeSort(nums, temp, mid+1, right);//对第二个排序
        merge(nums, temp, left, mid, right);//合并
    }
}
//使用MergeSort时,记得自己开一个temp数组:
int main(){
    int nums[10]={2,6,18,5,99,3,77,52,36,10};
    int* temp=(int*)malloc(sizeof(int)*10);
    MergeSort(nums,temp,0,9);
    for(int i=0;i<10;i++){
        printf("%d\n",nums[i]);
    }
    return 0;
}

    

        
    
    

归并排序很好地体现了分治与递归的思想,而递归往往会占用较多的栈空间,这里我有一个疑问,能否用迭代的方式写出归并排序的实现代码?

由于归并排序需要新开一个空间为n的数组来临时储存排列好的元素,在面对元素较多的数组时,该算法需要占用较多的内存空间。

事实上,在只有排序的目的的前提下,我们通常使用效率更高(或相同)且占用空间更少的快速排序算法,而归并排序的优势并不在此

归并排序的优势在于在分治的情况下,我们使之产生了局部有序的数组,这是一点十分重要的性质,在后面的题目我会对其进行应用,进行更深一步的学习

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值