【数据结构】第十三站:排序(下)归并排序

简介: 【数据结构】第十三站:排序(下)归并排序


一、归并排序递归法

1.归并排序的基本思想

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

也就是说,先将排序给劈成两半,然后对分别对左边和右边使用归并使得左右都变得有序。然后我们在对左右子数组进行归并即可

2.归并排序的代码实现

void _MergeSort(int* a, int begin, int end, int* tmp)
{
  if (begin >= end)
  {
    return;
  }
  int mid = (begin + end) / 2;
  _MergeSort(a, begin, mid, tmp);
  _MergeSort(a, mid + 1, end, tmp);
  int begin1 = begin, end1 = mid;
  int begin2 = mid + 1, end2 = end;
  int i = begin;
  while (begin1 <= end1 && begin2 <= end2)
  {
    if (a[begin1] < a[begin2])
    {
      tmp[i++] = a[begin1++];
    }
    else
    {
      tmp[i++] = a[begin2++];
    }
  }
  while (begin1 <= end1)
  {
    tmp[i++] = a[begin1++];
  }
  while (begin2 <= end2)
  {
    tmp[i++] = a[begin2++];
  }
  memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并排序
void MergeSort(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail");
    return;
  }
  _MergeSort(a, 0, n - 1, tmp);
  free(tmp);
  return 0;
}

二、归并排序非递归

1.可否使用栈来模拟?

其实是不可以的,因为这里类似于一个后序遍历,而快排之所以可以使用栈来模拟,是因为他是一个先序遍历。

对于快速排序,他是一趟搞定一个,当递归结束而不返回的时候,已经排好了。

而对于归并排序,他是先搞左右两边的。最后还需要获取原来的区间的。显然栈无法实现这样的功能。

2.直接改非递归(简化版)

我们可以这样思考,如果我们利用循环,先将前一个元素认为是一组给归并,然后依次归并后,再将两个元素设置为一组依次归并。让每组元素个数依次乘以2。只要起始值不越界。就可以一直依次拷贝下去。我们可以这样做,但是这种方式存在一个很明显的问题。只可以归并前2的次方的数组。否则将会产生越界的风险

//归并排序非递归
void MergeSortNonR(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail");
    return;
  }
  int gap = 1;
  while (gap < n)
  {
    for (int i = 0; i < n; i += 2 * gap)
    {
      int begin1 = i, end1 = i + gap - 1;
      int begin2 = i + gap, end2 = i + 2 * gap - 1;
      int j = begin1;
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (a[begin1] < a[begin2])
        {
          tmp[j++] = a[begin1++];
        }
        else
        {
          tmp[j++] = a[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[j++] = a[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[j++] = a[begin2++];
      }
    }
    gap *= 2;
    memcpy(a, tmp, sizeof(int) * n);
  }
  free(tmp);
}

3.处理边界之一把梭哈

我们有了上面的代码作为基础,我们发现上面存在的问题是会出现越界。需要注意的是,在上面的代码中,我们并不是跟递归一样。每处理一组拷贝一组,我们是处理一次间距后才进行拷贝,也就是一把梭哈的。这就会出现一些潜在的问题。

为了处理边界问题,我们不妨先将代码的可能出现的越界情况给分析出来

我们一共有九个元素,不难发现,除了begin1之外都有可能产生越界

为了方便我们分析,我们将上面的情况分为三类

1.end1越界,此时我们直接不归并就可以了。但是需要注意的是,我们是一把梭哈的,所以我们还是需要将[begin1,n-1]这段区间给拷贝下去。否则出现问题。为了拷贝给tmp,我们可以这样做,修正end1为n-1,然后将beigin2和end2给搞成不存在的区间

2.end1不越界,但begin2越界。这样的话,我们只需要将begin2和end2所控制的区间给不存在即可。

3.end1,begin2不越界,但end2越界。这样的话,我们就直接将end2给修正为n-2即可

结果如下所示,这样一来我们就成功的修正了边界

代码如下:

//归并排序非递归
void MergeSortNonR(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail");
    return;
  }
  int gap = 1;
  while (gap < n)
  {
    for (int i = 0; i < n; i += 2 * gap)
    {
      int begin1 = i, end1 = i + gap - 1;
      int begin2 = i + gap, end2 = i + 2 * gap - 1;
      //修正边界
      if (end1 > n - 1)
      {
        end1 = n - 1;
        begin2 = n;
        end2 = n - 1;
      }
      else if (begin2 > n - 1)
      {
        begin2 = n;
        end2 = n - 1;
      }
      else if (end2 > n - 1)
      {
        end2 = n - 1;
      }
      //打印边界
      //printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
      int j = begin1;
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (a[begin1] < a[begin2])
        {
          tmp[j++] = a[begin1++];
        }
        else
        {
          tmp[j++] = a[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[j++] = a[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[j++] = a[begin2++];
      }
    }
    gap *= 2;
    //printf("\n");
    memcpy(a, tmp, sizeof(int) * n);
  }
  free(tmp);
}

4.处理边界之归并一次拷贝一次

这样的话,我们就可以简化很多了。我们对于end1越界和begin1越界,我们直接break就可以了。唯一需要注意的是拷贝的元素个数是end2-i+1,因为i是起始位置下标,end2是末位置。

//归并排序非递归
void MergeSortNonR(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail");
    return;
  }
  int gap = 1;
  while (gap < n)
  {
    for (int i = 0; i < n; i += 2 * gap)
    {
      int begin1 = i, end1 = i + gap - 1;
      int begin2 = i + gap, end2 = i + 2 * gap - 1;
      //修正边界
      if (end1 > n - 1 || begin2 > n - 1)
      {
        break;
      }
      else if (end2 > n - 1)
      {
        end2 = n - 1;
      }
      printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
      int j = begin1;
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (a[begin1] < a[begin2])
        {
          tmp[j++] = a[begin1++];
        }
        else
        {
          tmp[j++] = a[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[j++] = a[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[j++] = a[begin2++];
      }
      memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
    }
    gap *= 2;
    printf("\n");
  }
  free(tmp);
}

打印结果为

我们可以发现,对于不符合区间的部分,我们就直接不管这块的区间了


本期内容到此位置

如果对你有帮助的话,不要忘记点赞加收藏哦!!!

相关文章
|
1月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》图、查找、排序专题考点(含解析)
408考研——《数据结构》图,查找和排序专题考点选择题汇总(含解析)。
74 29
|
2月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
2月前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
41 10
|
2月前
|
搜索推荐 算法 数据处理
【C++数据结构——内排序】希尔排序(头歌实践教学平台习题)【合集】
本文介绍了希尔排序算法的实现及相关知识。主要内容包括: - **任务描述**:实现希尔排序算法。 - **相关知识**: - 排序算法基础概念,如稳定性。 - 插入排序的基本思想和步骤。 - 间隔序列(增量序列)的概念及其在希尔排序中的应用。 - 算法的时间复杂度和空间复杂度分析。 - 代码实现技巧,如循环嵌套和索引计算。 - **测试说明**:提供了测试输入和输出示例,帮助验证代码正确性。 - **我的通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了代码运行的测试结果。 通过这些内容,读者可以全面了解希尔排序的原理和实现方法。
62 10
|
2月前
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
50 7
|
5月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
89 1
|
5月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
62 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
5月前
|
存储 搜索推荐 算法
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
46 1
|
5月前
【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序
【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序
|
5月前
|
算法
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序