归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
递归实现归并排序
根据归并排序的思想我们可以采用分而治之,即用递归来解决,把问题不断分割成子问题,分割到不可分割为止。而递归实现归并排序类似于二叉树的后序遍历,我们需要先解决小区间再到大区间;
所以我们的思路如下:我们先求出序列的中间元素下标,然后以中间元素为中心,把序列分为左右区间;接着又对左右区间进行递归分割,分割到不能分割为止就进行排序,排序后就不断递归返回就可以了。
图片分析如下:
代码实现如下:
#include <stdio.h>
#include <stdlib.h>
//递归实现归并排序
void _MergerSort(int* p, int left, int right, int* tmp)
{
//当只有一个元素或者区间不存在就不要递归下去了
if (left >= right)
{
return;
}
//求出中间元素的下标
int midi = (left + right) / 2;
//对左右区间进行分割区间
_MergerSort(p, left, midi, tmp);
_MergerSort(p, midi + 1, right, tmp);
//对左右区间进行归并排序
int begin1 = left, end1 = midi;
int begin2 = midi + 1, end2 = right;
int i = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (p[begin1] < p[begin2])
{
tmp[i++] = p[begin1++];
}
else
{
tmp[i++] = p[begin2++];
}
}
//检查哪个区间有剩余就往新数组里面拷贝
while (begin1 <= end1)
{
tmp[i++] = p[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = p[begin2++];
}
//归并好后,要把归并好的元素拷贝回去原来的数组
//如果不拷贝回去,后面的归并区间就不是有序的,
//无法达到左右区间都有序,所以一定要拷贝回原数组
int j = 0;
for (j = left;j <= right; j++)
{
p[j] = tmp[j];
}
}
//归并排序
void MergerSort(int* p, int n)
{
//先开辟等同原数组大小的新数组
//把原数组的元素往新数组中归并
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
exit(-1);
}
//归并排序子函数
_MergerSort(p, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}
//打印数组函数
void Print(int* p, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d ", p[i]);
}
printf("\n");
}
int main()
{
//三组测试用例
int arr1[] = {
4,1,3,2,9,8,6,5,7 };
int sz1 = sizeof(arr1) / sizeof(arr1[0]);
int arr2[] = {
5,8,44,22,0,5,8,1,3 };
int sz2 = sizeof(arr2) / sizeof(arr2[0]);
int arr3[] = {
2,5,99,3,8,11,33,62 };
int sz3 = sizeof(arr3) / sizeof(arr3[0]);
MergerSort(arr1, sz1);
Print(arr1, sz1);
MergerSort(arr2, sz2);
Print(arr2, sz2);
MergerSort(arr3, sz3);
Print(arr3, sz3);
return 0;
}
代码执行结果:都能排序成功
非递归实现归并排序
归并排序的递归是有缺点的,如果当数据量太大了,函数不断地开辟栈帧很可能会造成栈溢出,所以为了解决这个问题我们还可以用非递归来实现归并排序,不过非归并排序的过程就和归并实现不同了;
非递归实现不用分左右区间,先是2个2个归并,然后是4个4个归并,然后8个8个归并……所以这个过程不难,但是边界是非常难控制的,我们得对边界进行控制在有效的范围内才行,图片分析如下:
代码实现如下:
#include <stdio.h>
#include <stdlib.h>
//非递归实现归并排序
void MergerSortNonR(int* p, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
exit(-1);
}
int gap = 1;
int i = 0;
while (gap < n)
{
//以间隔为gap的两区间进行归并
for (i = 0; i < n; i += 2 * gap)
{
//利用i来产生区间
//[i,i+gap-1] [i+gap,i+2*gap-1]
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
int j = i;
//修改边界,有可能越界
//i不可能越界,控制好end1和end2就可以了
if (end1 >= n)
{
end1 = n - 1;
}
//如果begin2越界,end2必定越界,所以修改end2就可以
if (end2 >= n)
{
end2 = n - 1;
}
//对两区间进行归并
while (begin1 <= end1 && begin2 <= end2)
{
if (p[begin1] < p[begin2])
{
tmp[j++] = p[begin1++];
}
else
{
tmp[j++] = p[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = p[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = p[begin2++];
}
}
//当以gap为间隔归并完后要拷贝回原数组
int j = 0;
for (j = 0; j < n; j++)
{
p[j] = tmp[j];
}
gap = gap * 2;
}
free(tmp);
tmp = NULL;
}
void Print(int* p, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d ", p[i]);
}
printf("\n");
}
int main()
{
//三组测试用例
int arr1[] = {
4,1,3,2,9,8,6,5,7 };
int sz1 = sizeof(arr1) / sizeof(arr1[0]);
int arr2[] = {
5,8,44,22,0,5,8,1,3 };
int sz2 = sizeof(arr2) / sizeof(arr2[0]);
int arr3[] = {
2,5,99,3,8,11,33,62 };
int sz3 = sizeof(arr3) / sizeof(arr3[0]);
printf("非递归归并排序结果如下:\n");
Print(arr1, sz1);
MergerSortNonR(arr1, sz1);
Print(arr1, sz1);
printf("\n");
Print(arr2, sz2);
MergerSortNonR(arr2, sz2);
Print(arr2, sz2);
printf("\n");
Print(arr3, sz3);
MergerSortNonR(arr3, sz3);
Print(arr3, sz3);
printf("\n");
return 0;
}
代码执行如下: