0x01.关于归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。
0x02.递归方式的归并排序
先看代码:
void MergeSort(SqList* L)//单独设立一个函数是为了方便子函数的递归调用
{
MSort(L->r, L->r,1,L->length);
}
//归并排序递归函数
//SR是初始的顺序表,TR1是最终要排序得到的顺序表
//s是要归并的起始点,t是要归并的终点
void MSort(int* SR, int* TR1, int s, int t)
{
int m;
int TR2[MAXSIZE + 1];
if (s == t)//最后分到只有一个,就把这个值给TR1数组
{
TR1[s] = SR[s];
}
else
{
m = (s + t) / 2;//将原来的顺序表平分,m为中间值
MSort(SR, TR2, s, m);//将SR[s]到SR[m]的值归并为有序的序列TR2[s]到TR2[m]
MSort(SR, TR2, m + 1, t);//将SR[m+1]到SR[t]的值归并为有序的序列TR2[m+1]到TR2[t]
Merge(TR2, TR1, s, m, t);//将TR2[s]到TR2[m]的值和TR2[m+1]到TR2[t]的值归并为有序的序列TR1[s]到TR1[t]
}
}
//真正起到归并排序的函数
//将SR[i]到SR[m]的值和SR[m+1]到SR[n]的值归并为有序的序列TR[i]到TR[n]
void Merge(int* SR, int* TR, int i, int m, int n)
{
int j, k, l;
//k用来控制需要得到的有序序列的下标
//i用来控制序列SR[i]到SR[m]的下标
//j用来控制序列SR[m+1]到SR[n]的下标
//每一次循环将会往TR里面放一个值
//循环的条件是两个部分的下标都没有达到它们序列的终点值
//因为两个序列都分别是有序的,所以只需要依次拿出一个最小值进行比较,就可以得到最终的有序序列SR
for (j = m + 1, k = i; i <= m && j < n; k++)
{
if (SR[i] < SR[j])
{
TR[k] = SR[i++];
}
else
{
TR[k] = SR[j++];
}
}
if (i <= m)//如果归并后第一个序列还有剩余的元素,都放到TR末尾
{
for (l = 0; l <= m - i; l++)
{
TR[k + l] = SR[i + l];
}
}
if (j <= n)//如果第二个序列还有剩余的元素,也都放到TR末尾
{
for (l = 0; l <= n - j; l++)
{
TR[k + l] = SR[j + l];
}
}
}
原理理解:
归并排序的原理就是不断的把一个大序列分为两个小序列,然后再分别把小序列排序后组合起来,最终得到一个有序的数组。
第二个函数,递归的函数,所完全的工作就是不断的把序列分小,再分别对小序列排序,一个序列,经过不断的二分,最终一定会得到一个只包含一个元素的序列,我们可以把这个只有一个元素得序列理解成有序序列,然后再对这些有序序列归并,对于一个递归的用途,我们首先要确定在什么时候返回,返回方式有两种,一种是最后分到只有一个元素的序列,或者是执行完Merge函数返回,这两种返回方式,其实都把这个小序列归并成了有序的序列,然后看第一步调用,是先处理序列的左半部分,再处理序列的右半部分,最后合在一起处理,最终得到了一个有序的序列。‘
Merge函数所完成的工作,就是整合两个有序序列,整合有序序列就有一个比较巧妙的地方了,因为都是有序的,所以只要依次拿出最小值来比较,每次把最小的放入有序序列里,最后再把剩下的全部放尾部就完成了整合的工作。
0x03.非递归方式的归并排序
代码如下:
//最终得到的有序序列在TR中
void MergeSort2(SqList* L)
{
int* TR = (int*)malloc(L->length * sizeof(int));//因为长度不是常量,所以必须动态分配
int k = 1;
while (k < L->length)//从1开始,最小序列的长度会由1变为2变为4,这样不断的变成2的倍数
{
MergePass(L->r, TR, k, L->length);//两两整合间距为s的序列
k *= 2;//序列长度加倍
MergePass(TR, L->r, k, L->length);
k *= 2;
}
}
//每次整合SR中间距为s的子序列并两两归并到TR中
void MergePass(int* SR, int* TR, int s, int n)
{
int i = 1;//用来记录每次整合的位置
int j;
while (i <= n - 2 * s + 1)//这个2*s的含义是两两整合的间距,所以n-2*s就是最后一个两两整合的位置,再加1就是下一个位置
{
//将SR中的SR[i]到SR[i+s-1]的值和SR[i+s]到SR[i+2*s-1]的值整合到TR中
//i到i+s-1是相隔s个元素的区间,i+s到i+2*s-1是另一个区间
Merge(SR, TR, i, i + s - 1, i + 2 * s - 1);
i = i + 2 * s;//定位下一个两两整合的位置
}
//n-s+1是最后一个两两整合的另一个位置的下一个,如果上面循环后,i还在这之前,说明是最后两个序列了,最后再整合一次
if (i < n - s + 1)
{
Merge(SR, TR, i, i + s - 1, n);
}
else//i在这个之后,就是只剩下单个的子序列了,直接把后面子序列的值放到TR末尾
{
for (j = i; j <= n; j++)
{
TR[j] = SR[j];
}
}
}
原理理解:
不管递归还是不递归,最终用到的归并排序的思想是一样的,在这个非递归中,最显眼的就是两两整合这个思想,在递归中,由于递归的反复调用,其实每一次调用的返回,都是已经整合过一次了的,也是两两整合,不过没有相同间距这个概念,在非递归的实现中,从首先的每次整合两个,然后再每次整合4个,这样不断的每次整合,最后整合成一个有序序列。
归并排序的总体的时间复杂度为。