目录
写在前面的话
小伙伴们大家好啊!今天依旧小菜鸡库森,为大家带来的是有关归并排序的内容,好的,那么我们废话不多说,直接进入主题。
一,归并思想
那么首先,对于归并,从其字面意思来说,其实就是将两个事物合二为一。
其次,我们则需要这两个需要排序的事物是有序的,如果不是有序的,那么是不能进行归并排序的,因为这是归并的前提条件。
二,归并排序递归实现
2.1思想实现
好的,那么首先,我们的思路是依旧是“大事化小”,采用“分治”的思路实现。
如下图所示(升序为例):
我们既然想要归并,那么首先我们需要两个数组,然后比较他们中的数据,将较小的数放入一个新数组,然后依次比较存入,最后将剩余的那个数组的数直接拷贝到新数组即可。这样就完成了一次归并排序。(如果对于归并思想不是很理解的同学,可以移步 up 主的另一篇文章哦!牛客网第100题:有序序列合并_菜还不承认的库森的博客-CSDN博客目录思路代码实现本文将为大家带来一篇有关有序序列合并的文章。主要内容是:两个有序序列,将两个序列合并后,输出一个有序序列。在此我们规定:1.两个序列中的每个值范围均为:【0~100】2.两个序列的长度均为:【0~50】在开始合并之前:我们通过键盘输入两个升序序列,如图所示:思路首先,该两个序列均为有序序列,所以我们的思路如下:第一步:将两个序列的第一个元素进行比较,假设某一序列的首元素比较大,则将该值打印,然后将序列首元素的位置向后移动一位,指向第二个元素,此.https://blog.csdn.net/qq_52777887/article/details/122809339?spm=1001.2014.3001.5502
但是我们做上一步的前提是:这两组数必须是有序的,如果不是有序的,那么比较之后插入的元素依旧可能是乱序的。
所以对于归并排序而言,最主要问题的就是如何将这两组数变成有序的两组数。
2.2排序实现
那么这里首先我们用最熟悉的递归实现。如下图所示:
采用“分治”的思想,我们将一组数无限化小,直到每个数组中只剩余一个元素(这里的判断条件一定是头指针 begin 不再小于尾指针 end ),此时我们可以保证这两个数组一定是有序的,所以接下来我们只需要对其进行归并到新数组 tmp 中,然后再将其拷贝到原数组(这里一定不能忘记考回原数组,如果不考回,那么原数组的元素依旧没有发生变化)。
然后再层层归并拷贝,最后两个数组归并拷贝到一起之后,便完成了我们的归并排序。
2.3代码实现
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
{
return;
}
int keyi = (begin + end) / 2;
_MergeSort(a, begin, keyi ,tmp);
_MergeSort(a, keyi + 1, end, tmp);
//归并,从 tmp 数组拷贝到 a
int begin1 = begin, end1 = keyi;
int begin2 = keyi + 1, end2 = end;
int i = begin;
while (begin1<=end1 && begin2 <= end2)
{
if (a[begin1] > a[begin2])
{
tmp[i++] = a[begin2++];
}
else
{
tmp[i++] = a[begin1++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
for (int j = begin; j <= end; j++)
{
a[j] = tmp[j];
}
}
void MergrSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
assert(tmp);
_MergeSort(a, 0, n - 1, tmp);
//释放
free(tmp);
tmp = NULL;
}
三,归并排序非递归实现
3.1思路实现(小区间优化)
好的,那么上面我们通过递归思路实现了归并排序,接下来我们通过非递归实现一下。
如下图所示:
这里我们的思路是,首先将一组数通过 gap 划分为 n 个数组(实际上并没有实际的数组,只是我们将其认为是数组),然后对其进行比较归并。
第一次当 gap 为 1 的时候,我们将距离为 1 的两个数组(其实就是两个元素为 1 的数)进行归并,得到了拥有两个元素的一个有序数组,然后通过循环将后面的元素全部用同样的方式归并。然后就得到了如上图所示蓝色表示的元素排布。
同时我们在将这一步骤之后的数组拷贝回到原数组。在进行接下来的操作(这里的意思和上递归的是一样的)。
接着每次将 gap 的值增加 2 倍,直到 gap 的值不小于 n 结束归并。
这个过程我们将其称为小区间优化。
3.2边界值处理
那么因为在归并的时候,这里我们需要将每两个小区间进行归并,所以这里仍旧涉及到四个指针的问题,那么接下来我们对四个指针的越界问题逐一修正。
1.首先我们考虑到的是因为 begin1 的值是循环的次数 i,所以不需要考虑其是否越界。
2.其次就是如果 end1 越界,说明在这个归并中,只剩一个数 begin1 了,此时它就是有序的,而且这组数说明也有序了。所以我们不进行归并,直接退出循环。
3.对于begin2越界也是同样的,不需要做任何处理,因为前面的数组本来就是有序的,如果begin2所在的数组不为空,那么我们才需要归并,如果不是,那么就不需要做任何处理了。
4.那么当 end2 越界时,如下所示,我们需要作出调整:
边界值处理代码展示:
3.2代码实现
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
assert(tmp);
int gap = 1;
//外层循环,控制gap的值,gap每次增加二倍
while (gap < n)
{
//n是数组元素个数
for (int i = 0; i < n; i += 2 * gap)
{
//归并 [i,i+gap-1] [i+gab,i+2*gap-1]
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//处理边界值
//如果是 end1 越界或者 begin2 越界,直接退出即可,不需要归并
if (end1 >= n || begin2 >= n)
{
break;
}
//如果是 end2 越界。需要归并
if (end2 >= n)
{
end2 = n - 1;
}
int index = i;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = a[begin2++];
}
//小区间优化拷贝回数组a
for (int j = i; j <= end2; j++)
{
a[j] = tmp[j];
}
}
gap *= 2;
}
//释放
free(tmp);
tmp = NULL;
}
好的,那么对于归并排序的问题到这里就结束啦,如有问题,还请留言评论哦!