手撕排序算法4:归并排序及其非递归版本(下)

简介: 手撕排序算法4:归并排序及其非递归版本(下)

二.归并排序非递归版本

1.算法剖析

递归改非递归

1.直接改循环(简单)

2.借助数据结构栈模拟递归过程(复杂一点)

这里我们使用循环的方式来做

大家看一下这张图片

2.代码实现

void MergeSortNonR(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  int gap = 1;//每组需要归并的数据个数,控制每组有多少个
  while (gap < n)
  {
    for (int i = 0; i < n; i += 2 * gap)
    //每一轮for循环都是归并这两组数据
    //[i,i+gap-1]        [i+gap,i+2*gap-1]
     // [begin1,end1]       [begin2,end2] 
     //所以说每次for循环之后i都要从第一组的begin1到第二组的begin1,
     //所以要加2*gap
    //归并到一起
    {
      int begin1 = i, end1 = i + gap - 1;
      int begin2 = i + gap, end2 = i + 2 * gap - 1;
      //1.归并过程中右半区间可能就不存在
      if (begin2 >= n)
      {
        break;//此时根本就不用进行归并了
      }
      //2.归并过程中右半区间算多了,修正一下
      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++];
      }
      //拷贝回去
      for (int j = i; j <= end2; j++)
      {
        a[j] = tmp[j];
      }
    }
    gap *= 2;
  }
  free(tmp);
}
难点剖析:1.
    for (int i = 0; i < n; i += 2 * gap)
    每一轮for循环都是归并这两组数据
    [i,i+gap-1]        [i+gap,i+2*gap-1]
     [begin1,end1]       [begin2,end2] 
     所以说每次for循环之后i都要从第一组的begin1到第二组的begin1,
     所以要加2*gap
    归并到一起
    2.
      for (int j = i; j <= end2; j++)
      {
        a[j] = tmp[j];
      }
      必须写j<=end2,不能写j<= i + 2 * gap - 1,因为
      //2.归并过程中右半区间算多了,修正一下
      if (end2 >= n)
      {
        end2 = n - 1;
      }
      出现这种情况时我们将边界修正了一下,到那时如果写为
      j<= i + 2 * gap - 1,那就是写死了,导致越界访问了.
    3.  拷贝回去
      for (int j = i; j <= end2; j++)
      {
        a[j] = tmp[j];
      }
要放在上面那个for循环的内部
否则如果放在上面的那个for循环外面,
像这样的话
      for (int j = 0; j <n; j++)
      {
        a[j] = tmp[j];
      }
最外层while循环的里面的话会发生一下这种情况
因为如果放在外面的话,当循环到达4个gap==4且i==0时
| 10 6 7 1 |3 9 4 2|2
我们发现第一轮for循环时左右区间都完整
但是第二轮for循环时不仅右半区间不存在,而且连左半区间都残缺不全
也就是说这个2根本不会拷进tmp临时数组中
tmp临时数组中对应位置存放的是一个随机值
但是我们却在最后进行拷贝,这样就会使得原数组对应位置的这个2被随机值所覆盖,出现错误
4.总之,归并排序的非递归最难的点在于边界的修正和思想
5.因为右半区间的边界需要去时刻准备修正
所以这个归并排序的非递归版本使用栈的话就会很麻烦

3.归并排序的用途

归并排序也叫外排序,还可以对文件中的数据进行排序

只有归并排序才能解决这个问题

假设10G的数据放到硬盘的文件中,要排序,如何排呢?

可能内存不够,假设只有1G的内存可以使用

10G的文件,切分成为10个1G的文件,并且让10个1G的文件有序

依次读文件,每次读1G到内存中形成一个数组,用快排对其进行排序

再写到一个文件中,再继续下一个1G的数据

然后再用归并排序把每个1G的文件逐步归并为一个10G的文件

以上就是归并排序的剖析,希望能对大家有所帮助

相关文章
|
30天前
|
算法 搜索推荐 Java
算法实战:手写归并排序,让复杂排序变简单!
归并排序是一种基于“分治法”的经典算法,通过递归分割和合并数组,实现O(n log n)的高效排序。本文将通过Java手写代码,详细讲解归并排序的原理及实现,帮助你快速掌握这一实用算法。
37 0
|
1月前
|
算法
【算法】递归、搜索与回溯——汉诺塔
【算法】递归、搜索与回溯——汉诺塔
|
1月前
|
算法 关系型数据库 MySQL
揭秘MySQL中的版本号排序:这个超级算法将颠覆你的排序世界!
【8月更文挑战第8天】在软件开发与数据管理中,正确排序版本号对软件更新及数据分析至关重要。因MySQL默认按字符串排序版本号,可能出现&#39;1.20.0&#39;在&#39;1.10.0&#39;之前的不合理情况。解决办法是将版本号各部分转换为整数后排序。例如,使用`SUBSTRING_INDEX`和`CAST`函数从`software`表的`version`字段提取并转换版本号,再按这些整数排序。这种方法可确保版本号按逻辑正确排序,适用于&#39;major.minor.patch&#39;格式的版本号。对于更复杂格式,需调整处理逻辑。掌握此技巧可有效应对版本号排序需求。
86 3
|
30天前
|
数据采集 搜索推荐 算法
【高手进阶】Java排序算法:从零到精通——揭秘冒泡、快速、归并排序的原理与实战应用,让你的代码效率飙升!
【8月更文挑战第21天】Java排序算法是编程基础的重要部分,在算法设计与分析及实际开发中不可或缺。本文介绍内部排序算法,包括简单的冒泡排序及其逐步优化至高效的快速排序和稳定的归并排序,并提供了每种算法的Java实现示例。此外,还探讨了排序算法在电子商务、搜索引擎和数据分析等领域的广泛应用,帮助读者更好地理解和应用这些算法。
21 0
|
1月前
|
算法
【算法】递归总结:循环与递归的区别?递归与深搜的关系?
【算法】递归总结:循环与递归的区别?递归与深搜的关系?
|
1月前
|
算法
【算法】递归、搜索与回溯——简介
【算法】递归、搜索与回溯——简介
|
2月前
|
算法 Python
python中算法递归错误(Recursion Errors)
【7月更文挑战第18天】
39 1
|
2月前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
【7月更文挑战第12天】归并排序是高效稳定的排序算法,采用分治策略。Python 实现包括递归地分割数组及合并已排序部分。示例代码展示了如何将 `[12, 11, 13, 5, 6]` 分割并归并成有序数组 `[5, 6, 11, 12, 13]`。虽然 $O(n log n)$ 时间复杂度优秀,但需额外空间,适合大规模数据排序。对于小规模数据,可考虑其他算法。**
65 4
|
2月前
|
算法 搜索推荐 C#
|
14天前
|
算法 BI Serverless
基于鱼群算法的散热片形状优化matlab仿真
本研究利用浴盆曲线模拟空隙外形,并通过鱼群算法(FSA)优化浴盆曲线参数,以获得最佳孔隙度值及对应的R值。FSA通过模拟鱼群的聚群、避障和觅食行为,实现高效全局搜索。具体步骤包括初始化鱼群、计算适应度值、更新位置及判断终止条件。最终确定散热片的最佳形状参数。仿真结果显示该方法能显著提高优化效率。相关代码使用MATLAB 2022a实现。