算法刷题(4)——归并排序

103 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

今天我们开始进入归并排序算法的学习,首先我们先对归并排序进行一个大致的了解:

什么是归并排序

归并排序是建立在归并操作上的一种有效,稳定的排序算法。

该算法是采用分治法的一个非常典型的应用。其具体的步骤为:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。它的基本思想是并排序,首先把一个数组中的元素,按照某一方法,先拆分了之后,按照一定的顺序各自排列,然后再归并到一起,使得归并后依然是有一定顺序的。

在算法当中,归并排序的思想其实和快速排序有很多相似之处,也会用到递归的思想,通过递归不断地拆分待排序的无序数组,然后在按照从小到大(从大到小)的顺序依次重组,最终得到新的有序数组。

归并排序的流程

  • 把长度为n的输入序列分成两个长度为 n/2和n/2 的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。

具体到以下代码的实现,则是参考以下思路:

1.有数组 a, 左端点 l, 右端点 r

2.确定划分边界 mid

3.递归处理子问题 a[l..mid], a[mid+1..r]

4.合并子问题

(1)主体合并

至少有一个小数组添加到t数组中

(2)收尾

可能存在的剩下的一个小数组的尾部直接添加到t数组中

(3)复制回来

t数组覆盖原数组

时间复杂度分析

将递归的次数分为n次,在合并每一个子区间的过程中n个元素都会被操作一次,所以每一次递归的时间复杂度都是 O(n)。归并排序划分子区间,将子区间划分为只剩1个元素,需要划分 logn 次,共有 logn层,所以归并排序的时间复杂度就是 O(nlogn)。

题目

image.png

输入样例:

5
3 1 2 4 5

输出样例:

1 2 3 4 5

代码实现

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6+10;
int t[N],a[N];//分别为临时存放数据的数组和需要排序的数组

void merge_sort(int *a,int l,int r){
    if(l>=r){ //必须确保l小于r
        return;
    }
    int mid = (l+r)>>1;//把数组对半分,分治思想当中的分
    /*递归划分子区间*/
    merge_sort(a,l,mid);//左半区间
    merge_sort(a,mid+1,r);//右半区间

    int i=l,j=mid+1,cnt=0;//i指针位于左半区间的首位置,j指针位于右半区间的首位置
    /*两个指针同时开始移动*/
    while(i<=mid&&j<=r){
         //左部分的数据比有部分的小,则把小的数据存入临时数组,反之亦然
        if(a[i]<a[j]){ 
            t[cnt++] = a[i++];
        }
        else{
            t[cnt++] = a[j++];
        }
    }
    while(i<=mid){ //左边数组还有剩余
        t[cnt++] = a[i++];
    }
    while(j<=r){ //右边数组还有剩余
        t[cnt++] = a[j++];
    }
    for(int i=l,j=0;i<=r;i++,j++){//把临时数组中的元素重新存入待排序数组,此时临时数组以及排序完毕
        a[i] = t[j];
    }
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
    }
    merge_sort(a,0,n-1);//归并排序
    for(int i=0;i<n;i++){
        printf("%d ",a[i]);
    }
    return 0;
}

总结

归并排序的思想其实和快速排序的思想有很多相似之处,最重要的其实都运用到了分治思想,并且可以用递归来巧妙地处理分治问题。今天的算法就到这里,我们下次见~