1.归并排序
归并排序是建立在归并上的一种有效的排序方法,把复杂的问题分解成若干个小文题,是很多复杂问题的 有效解决办法。化繁为简,分而治之。分治法使我们经常会用到的一种方法,归并排序就是采用分治法的一个典型的应用,它是将已经有序的两个区间进行连续归并成一个有效区间,即将两个顺序序列合并为一个顺序序列,我们可以称为二路归并。
这个概念可能比较抽象,我们并不能十分清晰的去理解它的排序过程。再来介绍一下归并排序的详细步骤:
归并排序需要借助辅助数组我们设为:arr3[k];我们设将要归并的两个区间分别为:arr1[i]和arr2[j],第一步:比较arr1[i]和arr2[j]的大小,若arr[i]<=arr[j],则将arr[i]放在arr3中,并让i和k分别+1,如此循环,直到其中一个有序表为空,然后直接将另外一个有序表中剩余的元素直接放在arr3数组里,此时arr3中就是arr1和arr2合并后的有序数组。归并排序的算法我们通常用递归来实现,先把待排序区间中点二分,接着把子左区间排序,再把子右区间排序,最后把左区间和右区间归并为一个有序区间即可完成排序。
来用图直观的感受一下归并排序:
对于归并的过程中借用了辅助数组 arr3;先把arr1和arr2合并的结果放在arr3,再把辅助数组arr3里的有序序列复制到arr数组中,由于比较简单,并未在图中画出。理解的差不多了,接下来我们来实现一下代码。
2.代码实现,递归和非递归版本
void PrintSort(int arr[],size_t size)
{
size_t i=0;
for(; i<size; ++i)
{
printf(" %d ",arr[i]);
}
printf("\n");
}
//递归版本归并排序
void _MergeArr(int arr[],int beg,int middle,int end,int *tmp)
{
int cur1=beg;
int cur2=middle;
int tmp_index=beg;
while(cur1<middle&&cur2<end)
{
if(arr[cur1]<arr[cur2])//找到将要归并两个相邻子区间的较小值放到辅助数组里
{
tmp[tmp_index++]=arr[cur1++];
}
else
{
tmp[tmp_index++]=arr[cur2++];
}
}
while(cur1<middle)//左区间元素未完,将剩余元素放在辅助数组里
{
tmp[tmp_index++]=arr[cur1++];
}
while(cur2<end)//右区间元素未完,将剩余元素放在辅助数组
{
tmp[tmp_index++]=arr[cur2++];
}
//将辅助数组tmp中已经归并好的有序序列复制到原数组arr里
memcpy(arr+beg,tmp+beg,sizeof(int)*(end-beg));
}
void _MergeSort(int arr[],int beg,int end,int *tmp)
{
int middle=beg+(end-beg)/2;
if(arr==NULL||tmp==NULL)
{
return;
}
if(end-beg<=1)
{
return;
}
_MergeSort(arr,beg,middle,tmp);//递归分割左子区间使其有序
_MergeSort(arr,middle,end,tmp);//递归分割右子区间使其有序
//只有左右子区间均有序再进行归并
_MergeArr(arr,beg,middle,end,tmp);
}
void MergeSort(int arr[],size_t size)
{
//开辟size大小辅助存储空间
int *tmp=(int*)malloc(sizeof(int)*size);
if(size<=1)
{
return;
}
_MergeSort(arr,0,size,tmp);//前闭后开区间
free(tmp);
}
void MergeSortByLoop(int arr[],size_t size)
{
size_t gap;
size_t i;
size_t beg;
size_t end;
size_t mid;
int *tmp=(int*)malloc(sizeof(int)*size);//开辟size大小的辅存
if(size<=1)
{
return;
}
gap=1;//初始每组为一个元素
for(; gap<size; gap*=2)//子区间以2*gap的速度进行扩大
{
i=0;
for(; i<size; i+=gap*2)//每次循环归并好相邻子区间
{
beg=i;
mid=i+gap;
end=i+2*gap;
if(mid>size)
{
mid=size;
}
if(end>size)
{
end=size;
}
_MergeArr(arr,beg,mid,end,tmp);//归并相邻区间
}
}
free(tmp);
tmp=NULL;
}
int main()
{
int arr[]={34,56,77,12,545,23,18,67};
size_t size=sizeof(arr)/sizeof(arr[0]);
MergeSort(arr,size);
PrintSort(arr,size);
MergeSortByLoop(arr,size);
PrintSort(arr,size);
system("pause");
return 0;
}
运行结果:
3.算法分析
对于归并排序来说,假设有N个数据,时间复杂度为:O(N*logN);刚好最近在网上看到了一种归并排序时间复杂度的计算方法,这里我简单的证明一下:运行时间设为T(N);当把N分为两半时,那么处理N/2子数组花费时间为:T(N/2);合并子区间花费时间与数据个数N成正比,总消费时间=两个大小为N/2的子数组+归并所花费的时间,所以有:T(N)=2*T(N/2)+N;对于N=1,T(1)=1;
T(1)=1;所以有:T(N)=N*logN;
2.空间复杂度
归并排序是一种用空间换时间的做法。额外开辟了大小为N的辅助数组,空间复杂度为:O(N);
3.稳定性
稳定,因为在进行相邻区间归并选取较小元素的时候,是先将前面的元素放在辅助数组里的。
如图: