一.算法介绍
归并排序是一种基于分治思想,时间复杂度为O(nlgn),空间复杂度为O(n)的排序算法
二.算法思路
首先我们先来看看一个子问题:怎么把两个有序的数组合并为一个有序的数组。答案很简单,我们可以先把两个数组的数据连为一个数组,再用一个排序算法给它排序。那用什么算法呢,冒泡?显然时间复杂度O(n^2)不好。我们可以换种思路在O(n)的时间复杂度内排好序。为什么呢?因为两个数组都是有序的。我们可以同时遍历两个数组,取出元素较小的那个放进目标数组,再将被取出元素的数组下标加一。
const int N=505;
int a[N],b[N],c[2*N];
//n为有序数组a的长度,m为有序数组b的长度,现在将a,b数组合并到c中
void merge(int n,int m){
int i,j,k=0;
for(i=0,j=0;i<n&&j<m;){
if(a[i]<b[j]){
c[k]=a[i];
i++;
}
else{
c[k]=b[j];
j++;
}
k++;
}
// 当a或者b中某一个数组遍历完啦,另外一个数组直接复制到c后面
if(i<n){
while(i<n){
c[k++]=a[i];
i++;
}
}
if(j<m){
while(j<m){
c[k++]=b[j];
j++;
}
}
}
可以看出,这样我们就实现O(n)时间复杂度合并两个有序数组啦。
那么好有了这一个函数,又怎么样来实现O(nlgn)的归并排序呢。这里我们就用分治来思考,我们可以在O(n)时间复杂度将两个有序数组合并为一个有序数组,那我们只要能将一个乱序数组(n个元素)变成两个有序数组(n/2个元素)就好啦。然而,这两个有序数组,在初始也是乱序的,我们只要能将两个乱序数组变成四个有序数组(n/4)就好啦,同理,四个变八个(n/8),八个变十六个,...............直到最后变为n个有序数组(1个元素)。一个元素,它本身就是有序的。
好,我们再来看看每一层的时间复杂度,第一层,两个有序数组合并得到一个有序。这里时间复杂度是O(n/2)+O(n/2) =O(n)。第二层:四个有序数组合并得到两个有序,O(n/4)+O(n/4) =O(n/2),O(n/4)+O(n/4) =O(n/2).最后还是O(n/2)+O(n/2) =O(n),第三层第四层,都一样为O(n)
好,得到了每一层的时间复杂度为O(n),我们再来看看有多少层,从上面的分析可以看出,我们每一层要分割数组元素,直到最后分割为一个数组一个元素停止,所以我们要分割出n个数组。1->2,2->4,4->8,8->16,......我们可以看出要分出n个数组,只需要lgn次。一共就需要有lgn层。这也就是为什么归并排序的时间复杂度为O(nlgn)
好,下面来看它的实现。
#include <bits/stdc++.h>
using namespace std;
const int N=505;
int a[N],b[N];//a为原数组,b为辅助数组
void merge(int l,int mid,int r){
int i=l,j=mid+1,k=l;
for(;i<=mid&&j<=r;){
if(a[i]<a[j]){
b[k]=a[i];
i++;
}
else{
b[k]=a[j];
j++;
}
k++;
}
// 当左边部分或者右边部分遍历完啦,另外一个部分直接复杂到c后面
while(i<=mid){
b[k++]=a[i];
i++;
}
while(j<=r){
b[k++]=a[j];
j++;
}
// 将对应的辅助数组信息写回原数组
for(i=l;i<=r;i++){
a[i]=b[i];
}
}
void mergeSort(int l,int r){
int mid = (l+r)/2;
//作为划分数组的中间点
if(r-l>=1){
mergeSort(l,mid);
mergeSort(mid+1,r);
merge(l,mid,r);
}
}
void solve(){
// "输入数组的长度:";
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
// 左右都是闭区间实现
mergeSort(0,n-1);
for(int i=0;i<n;i++){
cout<<a[i]<<" ";
}
}
signed main(){
// ios::sync_with_stdio(0);
// cin.tie(0);
// cout.tie(0);
// ofstream cout("Eout.txt");
solve();
return 0;
}
三.总结
找到可递推的性质是用递归实现算法,大幅度减少时间复杂度的关键