归并排序
归并排序是建立在归并操作上的一种排序算法,该算法采用分治的思想(divide and conquer)。就是先将序列进行分裂成一个个子序列,使得每个子序列都是有序的,然后再将所有的子序列整合成一个完整的序列。所以整个算法分为两部分,分裂和合并。分裂比较简单,最容易想到的就是折半分解,直至每个子序列是一个单独的数为止。合并就可能麻烦一点。
合并
假设存在两个已经排列好的子序列
{a1,a2,a3……an}
和
{b1,b2,b3……bm}
。我们逐个比较两个子序列中项目的大小,将关键字较小的项目放入一个临时数组中,然后从对应的子序列删除放入的项目,当某个子序列为空时,就可以将另外一个子序列的全部项目依次放入临时数组中。然后利用临时数组给
{c1,c2,c3……cn+m}
赋值。
void MemeryArray(int* p,int first,int middle,int end,int* tmp)
{
int i=first;//第一个子序列的起始位置
int m=middle;//第一个子序列的终止位置
int j=middle+1;//第二个子序列的起始位置
int n=end;//第二个子序列的终止位置
int k=0;
while(i<=m&&j<=n)
{
if(p[i]<p[j])
tmp[k++]=p[i++];
else
tmp[k++]=p[j++];
}//将较小的项目放入临时数组tmp
while(i<=m)
tmp[k++]=p[i++];
while(j<=n)
tmp[k++]=p[j++];//将剩余的某一子序列全部放入临时数组
for(int i=0;i<k;i++)//利用临时数组对原本的序列进行赋值
p[first+i]=tmp[i];
}
上图可以很直接的说明归并排序的思想,下面是完整的归并排序的code
#include<iostream>
using namespace std;
void MemeryArray(int* p,int first,int middle,int end,int* tmp)
{
int i=first;
int m=middle;
int j=middle+1;
int n=end;
int k=0;
while(i<=m&&j<=n)
{
if(p[i]<p[j])
tmp[k++]=p[i++];
else
tmp[k++]=p[j++];
}
while(i<=m)
tmp[k++]=p[i++];
while(j<=n)
tmp[k++]=p[j++];
for(int i=0;i<k;i++)
p[first+i]=tmp[i];
}//合并操作
void mergeSort(int* p,int first,int end,int* tmp)
{
if(first<end)//递归操作的终止条件就是first>=end
{
int middle=(first+end)/2;
mergeSort(p,first,middle,tmp);//分解
mergeSort(p,middle+1,end,tmp);//分解
MemeryArray(p,first,middle,end,tmp);//合并
}
}
bool MergeSort(int* p,int length)
{
int* q=new int[length];//由于每次调用归并排序都需要一个临时数组,为了避免麻烦,我们直接创建一个较大的临时数组,供所有的归并排序使用。
if(q==NULL)
return false;
mergeSort(p,0,length-1,q);
delete[] q;
return true;
}
int main()
{
int data[5]={5,4,3,2,1};
MergeSort(data,sizeof(data)/sizeof(data[0]));
for(int i=0;i<sizeof(data)/sizeof(data[0]);i++)
cout<<data[i]<<endl;
return 0;
}
从上面可以分析出来,归并排序的总共会递归调用 log2(length) 次,而每次调用的主要时间代价就是合并,从合并函数我们知道,每次的需要赋值 2∗length 次,所以时间复杂度 Ω(nlog(n)) ;空间复杂度,由于需要额外开辟一个临时数组,所以空间复杂度仍然是 Ω(n) ;同时归并排序是一种稳定排序。