上回学习了最简单也是最直接的插入排序。插入排序在小数据量时是很高效的,但是遇到大数据时,便显得无力了,今天来介绍归并排序,在大数据排序时,时间短,但同时它的空间使用率就显得高了。
第二章 算法基础
2.归并排序
归并算法就是利用递归的特性,循环调用,递归求解。因采用分治算法的思想,对于大数据的排序处理效率很高。
归并排序主要分为三个步骤:
分解:把需要排序的数列分解为n/2个元素的两个子序列。
解决:继续调用归并排序将两个子序列分别排序,直到分解为最后一个元素,即一个元素一定是排列好的。
合并:把排列好的各个子序列合并起来,形成最终的排序。
下面就是解决如何合并两个排列好的子序列的问题。合并两个子序列,用和之前一样的扑克牌的例子,其思想是:假设桌面上有两堆牌,面都是朝上的,每次从两个牌堆中最上面的两张牌中选取最小的,直到一堆牌选完为止,之后把另一堆牌直接放于排完的后面即可。为了代码的连续性与实现简单性,在每个牌堆中的最后加上一个无穷大的值(INF),没有会比无穷大的牌还大。《算法导论》中叫做哨兵牌。我觉得这是个很好的举措,简化了代码的实现过程。下面我先给出根据书中伪代码,以C实现的合并程序。
/************************************************************************
* 函数功能:合并两个排列好的数组 *
* 参数入口:Data为数组,p为第一个数组起点,q为终点,r为第二个数组终点 *
* 返回值: 无返回值 *
************************************************************************/
void Merge(int *Data, int p, int q, int r)
{
int n1=q-p+1; //两个数组的长度
int n2=r-q;
int* L=(int*)malloc(sizeof(int)*(n1+1)); //申请两个长度为n1,n2的数组。
int* R=(int*)malloc(sizeof(int)*(n2+1));
for(int i=0;i<n1;i++) //将要合并的数组分别存入L,R中,并存入结束标志。
L[i]=Data[p+i];
for(int i=0;i<n2;i++)
R[i]=Data[q+1+i];
L[n1]=INF; //结束标志。
R[n2]=INF;
for(int i=0,j=0,k=p;k<=r;k++) //每次比较较小的值,将较小的值放于数组中。
{ //等到一组没有后,结束值为INF,永远最大,因此,相当于直接将剩余的2值直接放于数组中。
if(L[i]<=R[j])
{
Data[k]=L[i];
i++;
}
else
{
Data[k]=R[j];
j++;
}
}
free(L); //释放空间。
free(R);
}
下面就是完成分解程序,将一个数列,不断的分解为小数列,直到最后一个数列只有一个元素,即排列好的,之后调用合并程序,即可完成排序。
下面给出分解程序:
/*************************************************
* 函数功能:归并排序 *
* 参数入口:Data为数组,p为起点,r为终点 *
* 返回值: 无返回值 *
**************************************************/
void Merge_sort(int *Data, int p, int r)
{
if(p<r)
{
int q=(p+r)/2;
Merge_sort(Data,p,q);
Merge_sort(Data,q+1,r);
Merge(Data,p,q,r);
}
}
分解程序中,不停的调用其本身,这样就可以分解到最后一个元素。在不断合并。下面给出整个实验程序:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ARRAY_LENGTH(array,len) {len=sizeof(array)/sizeof(array[0]);} //宏定义获取数组长度的函数。
#define LENGTH 1000
#define INF 1000000
/************************************************************************
* 函数功能:合并两个排列好的数组 *
* 参数入口:Data为数组,p为第一个数组起点,q为终点,r为第二个数组终点 *
* 返回值: 无返回值 *
************************************************************************/
void Merge(int *Data, int p, int q, int r)
{
int n1=q-p+1; //两个数组的长度
int n2=r-q;
int* L=(int*)malloc(sizeof(int)*(n1+1)); //申请两个长度为n1,n2的数组。
int* R=(int*)malloc(sizeof(int)*(n2+1));
for(int i=0;i<n1;i++) //将要合并的数组分别存入L,R中,并存入结束标志。
L[i]=Data[p+i];
for(int i=0;i<n2;i++)
R[i]=Data[q+1+i];
L[n1]=INF; //结束标志。
R[n2]=INF;
for(int i=0,j=0,k=p;k<=r;k++) //每次比较较小的值,将较小的值放于数组中。
{ //等到一组没有后,结束值为INF,永远最大,因此,相当于直接将剩余的2值直接放于数组中。
if(L[i]<=R[j])
{
Data[k]=L[i];
i++;
}
else
{
Data[k]=R[j];
j++;
}
}
free(L); //释放空间。
free(R);
}
/*************************************************
* 函数功能:归并排序 *
* 参数入口:Data为数组,p为起点,r为终点 *
* 返回值: 无返回值 *
**************************************************/
void Merge_sort(int *Data, int p, int r)
{
if(p<r)
{
int q=(p+r)/2;
Merge_sort(Data,p,q);
Merge_sort(Data,q+1,r);
Merge(Data,p,q,r);
}
}
/*************************测试main函数******************************/
int main(void)
{
int* Data=(int*)malloc(sizeof(int)*LENGTH);
srand((unsigned)time(NULL));
for(int i=0;i<LENGTH;i++) //产生随机数
{
Data[i]=rand()%1000;
}
for(int i=0;i<LENGTH;i++)
{
if(i%10==0)
printf("\n");
printf("%d ",Data[i]);
}
Merge_sort(Data,0,LENGTH-1);
for(int i=0;i<LENGTH;i++)
{
if(i%10==0)
printf("\n");
printf("%d ",Data[i]);
}
return 0;
}
归并算法的时间复杂度为O(nlgn),空间复杂度为O(n)。当数据量很大时,需要的堆栈深度要很大,因此,归并算法也有其局限性。今天就学习到这里吧。