全文目录
- 引言
- 归并排序
- 思路
- 递归实现
- 归排非递归
- 思路
- 实现
- 总结
引言
在本篇文章中,将继续介绍一种排序算法:归并排序。
归并排序运用了归并的思想,即将两个有序数列归并为一个有序数列。在前面的合并两个有序链表时,运用了这种思想:戳我看归并实现合并两个有序链表(方法二)
归并排序
思路
归并排序需要一块与数组大小相同的空间,用于临时存储归并后的数据;
排序时,先将整个数组平分为两份,再分为4份……以此类推,直到不能再平分后(区间只剩一个元素);
向上归并,即将两个区间归并到临时空间中的对应位置;
再将临时数组中对应区间中已经排序好的数据拷回原数组;
一直向上归并,直到排序完成。
(需要注意的是,这里的平分不是同时进行的,在递归中,是一条线一条线进行的。左边递归到头后,返回给上一级,继续递归倒数第二层的右边,右边到底返回到上一级,执行最底层左右区间的归并。归并结束后返回到倒数第三层,然后递归该层的右边……)
递归实现
与快排的递归模式不同,快排是对最大的区间操作完成后,向下递归,对左右区间进行操作,直到排序结束;而归排是先递归到最小区间,再向上归并。所以在实现快排时,每层的操作在前,函数递归在后;而快排是递归在前,每层的操作在后;
函数有4个参数:原数组、区间的左右下标、临时空间。
初始传给函数的参数为整个数组,向下递归,当begin>=end时,return结束递归;
创建midi,记录区间的中间位置,再将左右区间分别递归;
之后实现对区间的操作:
首先用 begin1 、end1 、begin2 、end2记录要归并区间的左右区间的始末下标,并用k记录要归并的区间在临时空间temp中的对应起始位置;
然后将左右区间归并到temp的对应位置;
最后将temp对应空间中的数据拷贝回来:
对于归并的过程,即将两个区间中的数据依次比较,将较小的元素尾插到临时空间中:
void _MergeSort(int* a, int begin, int end, int* temp)
{
if (begin >= end)
{
return;
}
int midi = (begin + end) / 2;
_MergeSort(a, begin, midi, temp);
_MergeSort(a, midi+1, end, temp);
int begin1 = begin, end1 = midi;
int begin2 = midi+1, end2 = end;
int k = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
temp[k++] = a[begin1++];
}
else
{
temp[k++] = a[begin2++];
}
}
while (begin1 <= end1)
{
temp[k++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[k++] = a[begin2++];
}
memcpy(a + begin, temp + begin, sizeof(int)*(end - begin + 1));
}
void MergeSort(int* a, int n) //递归实现
{
int left = 0;
int right = n - 1;
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc");
return;
}
_MergeSort(a, left, right, temp);
free(temp);
}
归排非递归
思路
非递归实现归排时,与快排不同,不用记录上一层的区间,将某个区间归并后,转回原数组即可。所以我们只需要将数组分组,归并每两组的值,每组元素的数量从1开始,每次循环后,每组元素个数乘2,直到大于数组长度循环结束;
每层循环内,需要将这一层中,两个为一组的区间全部归并完成,并将这些数据转回原数组中:
实现
实现非递归时,首先动态开辟一块临时空间temp;
然后,创建gap为每层中每个需要归并区间的元素个数,并初始化为1;
外层while循环控制gap的值,大于等于数组的元素个数时,循环结束;
内层for循环控制每层中要归并的分组(两个元素个数为gap的小区间为一组归并):
左区间的起始位置begin1为i,结束位置end1为begin1+gap-1;右区间的起始位置begin2为end1+1,结束位置end2为begin2+gap-1;k为对应在temp中的起始位置下标;
然后需要判断的是,这组区间是否越界。由于for循环只是控制了i<n,即begin1<n,所以end1、begin2、end2都有可能越界;
当end1或begin2越界时,说明这组(左右)区间已经少了一个区间,不用归并了。当end2越界时,说明一组左右区间是存在的,需要归并,将end2改为数组最后一个元素的下标即可;
然后对左右区间进行归并,归并到temp中的对应位置;
每组(左右)区间归并后,就将数组拷回原数组:
【需要注意的是,由于之前的end2是可能越界的,所以不能直接拷贝2*gap个数据回去,而应该为end2-i+1个数据】
void MergeSortNonR2(int* a, int n) //非递归实现(分别转移)
{
int gap = 1;
int* temp = (int*)malloc(sizeof(int) * n);
if (temp == NULL)
{
perror("malloc");
return;
}
while (gap < n)
{
for (int i = 0; i < n; i += (gap * 2))//每层
{
int begin1 = i, end1 = begin1 + gap - 1;
int begin2 = end1 + 1, end2 = begin2 + gap - 1;
int k = begin1;
//判断是否越界(如果越界,直接break跳过归并)
if (end1 >= n || begin2 >= n)
{
break;
}
else if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
temp[k++] = a[begin1++];
}
else
{
temp[k++] = a[begin2++];
}
}
while (begin1 <= end1)
{
temp[k++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[k++] = a[begin2++];
}
memcpy(a + i, temp + i, (end2 - i + 1) * sizeof(int));//每次归并都转移
}
gap *= 2;//走向下一层
}
}
总结
到此,关于归并排序的内容就已经介绍完了,同时排序算法的讲解也暂告一段落了
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
希望与大家共同进步哦