携手创作,共同成长!这是我参与「掘金日新计划 · 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)。
题目
输入样例:
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;
}
总结
归并排序的思想其实和快速排序的思想有很多相似之处,最重要的其实都运用到了分治思想,并且可以用递归来巧妙地处理分治问题。今天的算法就到这里,我们下次见~