二路归并排序
1,递归思想
假设初始列表含有n个记录,则可以看成n个有序子序列,每个有序子序列的长度为1,然后两两归并,得到n/2个长度为2的有序子序列,再两两归并,如此重复下去,直到得到一个长度为n的有序序列为止。
2,递归实现二路归并排序
#define MAXSIZE 10 //定义一个宏,是arr的大小,也是需要开辟数组的大小
//二路归并,将TR2[m..t]和[t+1..n]归并成有序序列
void Merge(int TR1[], int TR2[],int m, int t, int n)
{
int j,k;//j为TR1[]的下标,j为TR2[t+1..n]的下标,而m为TR2[m..t]的下标
//将TR2中记录由小到大归并入TR1中
for(k = m,j = t+1; m <= t && j <= n; ++k)
{
if(TR2[m] < TR2[j])
TR1[K] = TR2[m++];
else
TR1[K] = TR2[j++];
}
//将剩余的TR2[m..t]复制到TR1中
for(; m <= t; ++m,++k)
{ TR1[k] = TR2[m]; }
//将剩余的TR2[t+1..n]复制到TR1中
for(; j <= n; ++j,++k)
{ TR1[k] = TR2[j]; }
}
//该函数将大量数据递归下去,直到数据有序
void MSort(int SR[], int TR1[], int m, int n)
{
int t; //中间值,来划分归并的前半部分和后半部分
int TR2[MAXSIZE];//辅助数组,用来存储排归并排序后的有序子序列
if(m == n)//递归终止条件 m==n时,此趟归并完成
TR1[m] = SR[m];
else
{
t = (m+n) / 2; //将SR分为[m..t]和[t+1..n]
Msort(SR,TR2,m,t);//将SR[m..t]归并排序成有序数组放到TR2[m..t]
Msort(SR,TR2,t+1,n);//将SR[t+1..n]归并排序成有序数组放到TR2[t+1..n]
Merge(TR1,TR2,m,t,n);//最后将TR2中的[m..t]与[t+1..n]合并成有序数组放到TR1,则TR1保存的便是归并排序后的结果。
}
}
//该函数只是封装了一个需要递归的函数
void MergeSort(int *arr, int n)
{
assert(arr != NULL && n > 1);
MSort(arr,arr,0,n-1);
}
3,归并排序递归算法的时间复杂度
时间复杂度为O(nlogn)
一趟排序需要将SR[0]~SR[n-1]中相邻的长度为h的有序子序列进行两两归并,并将结果放入TR1[0]~TR1[n-1]中,这需要将待排序序列扫描一遍,耗费时间为O(n),根据二叉树的性质,整个归并排序需要logn次,所以归并排序的时间复杂度为O(nlogn)。
空间复杂度为O(n+logn),存储交换结果的数组已经递归的栈空间。
归并排序特点:空间比较占内存,但排序效率高且稳定。
归并排序的递归算法,大量的引用的递归,尽管代码比较清晰,但其会消耗时间和空间上的性能,我们追求效率,所以让我们来看看迭代方法。
4,归并排序的迭代实现
#define MAXSIZE 10
//将SR[m..t]和[t+1..n]归并的结果放入TR中
void Merge(int SR, int TR,int m, int t, int n)
{
int j,k;
for(j = t+1,k = m;m <= t && j <= n; ++k)
{
if(SR[m] < SR[j])
TR[k] = SR[m++];
else
TR[k] = SR[j++];
}
for(;m <= t; ++m,++k)
{
TR[k] = SR[m];
}
for(;j <= n; ++j,++k)
{
TR[k] = SR[j];
}
}
//将相邻长度为s的子序列归并到TR中
void MergePass(int SR[], int TR[],int s, int n)
{
int i = 0;
int j;
while(i <= n - 2*s +1)
{
Merge(SR,TR,i,i+s-1,i+2*s-1);//两两归并
i = i + 2 * s;
}
if(i < n-s+1) //归并最后两个序列
{
Merge(SR,TR,i,i+s-1,n);
}
else //归并最后剩下的单个子序列
{
for(j = i; j < n; ++j)
TR[j] = SR[j];
}
}
void NiceMergeSort(int *arr, int n)
{
assert(arr != NULL && n > 1);
int *TR = (int*) malloc(sizeof(int) * MAXSIZE);
int k = 1; //k为归并长度 第一次将相隔为1的数据进行归并
while(k < MAXSIZE-1)
{
//将原来无序的数列两两归并入TR
MergePass(arr,TR,k,MAXSIZE-1);
k *= 2; //归并长度扩大一倍
//将两两归并有序的数列,再次归并入数组arr中
MergePass(TR,arr,k,MAXSIZE-1);
k *= 2;
}
}
5,非递归迭代的复杂度
非递归迭代的方法,避免了递归时深度为logn的栈空间,空间上只申请了临时数组TR,空间复杂度为O(n),避免递归也在时间复杂度上有了一定的提升,因此,我们使用归并排序,应该多使用迭代方法。