【八大排序(七)】归并排序初级篇-递归版

简介: 【八大排序(七)】归并排序初级篇-递归版

💓博主CSDN主页:杭电码农-NEO💓


⏩专栏分类:八大排序专栏


🚚代码仓库:NEO的学习日记🚚


🌹关注我🫵带你学习排序知识

  🔝🔝


1. 前言

归并排序算法是采用
分治法的一个经典案例
它和数据结构中的二叉树有异曲同工之妙
我们将从如何合并两个有序数组
到如何递归自身达到有序两个方面
给大家介绍归并排序的递归版本

准备好,大家上车开启归并之旅

(注意:本章排序都按升序讲解)


2. 归并排序基本思路

我们先创建一个无序数组:

int a[]={10,6,7,1,3,9,4,2};

基本思路:

  1. 拆分过程:
  • 要使数组整体有序就要将
  • 左半部分和右半部分变为有序
  • 后进行单次归并排序
  • 将数组拆分为两个部分A和B
  • 要使A数组有序就要将
  • A也拆分为两个部分
  • 使左/右半部分都有序
  • 一直拆分直到数组只有一个元素

画图理解:

  1. 合并过程:
  • 从左往右将6,10合并为有序
  • 将7,1合并为有序后6,10,7,1再合并
  • 数组的左半部分有序后.走右边
  • 将3,9合并为有序后将4,2合并为有序
  • 再将3.9.4.2合并为有序
  • 至此左右子区间都有序
  • 再将左右子区间归并为有序
  • 使数组整体有序

画图理解:

这里给大家放出一个动图
帮助大家理解这个过程:

image.png

归并排序


3. 对合并两个有序数组的思考

我们由易到难,定义两个有序数组:

int a[]={1,2,3};
int b[]={2,5,6};

方法:

  • 定义两个指针A和B
  • 分别指向两个数组的第一个元素
  • 定义一个数组C接收这两个数组的数据
  • A和B指向的值谁小,谁就放在C中第一个位置
  • 然后对应的指针(A或B)往后走一步
  • 直到走完其中一个数组后停下来

画图理解:

像这样往后一直走
这里我给出力扣平台的动图视频
帮助大家理解:

image.png

合并两个有序数组


4. 合并两个有序数组代码实现

我们刚刚说明了
合并两个有序数组
在原数组中不好操作
所以我们定义一个临时数组tmp
来接收排序好的顺序,最后再拷贝回原数组

while (begin1 <= end1 && begin2 <= end2)//begin指针指向两个有序数组的第一个元素.end为数组有效范围
  {
    if (a[begin1] < a[begin2])//谁小谁就先进tmp数组
    {
      tmp[i++] = a[begin1++];
    }
    else
    {
      tmp[i++] = a[begin2++];
    }
  }
  while (begin1 <= end1)//当其中一个数组走完后.将另外一个数组所有内容直接放进tmp数组
  {
    tmp[i++] = a[begin1++];
  }
  while (begin2 <= end2)//数组1先走完就将数组2剩下全部内容放进去
  {
    tmp[i++] = a[begin2++];
  }
  //将tmp数组的内容拷贝回a数组
  for (int j = left; j <= right; j++)
  {
    a[j] = tmp[j];
  }
}

5. 归并排序递归版代码实现

由于每次都需要二分数组
而且需要为临时数组tmp开辟空间
在原函数上直接递归就不太方便
所以我们设计一个主函数和一个递归函数
方便我们编写代码

主函数:

//归并排序
void MergeSort(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);//为临时数组tmp开辟空间
  if (tmp == NULL)
  {
    printf("动态开辟失败");
    exit(-1);
  }
  _MergeSort(a, 0, n - 1, tmp);//_MergeSort为递归函数.传参进行递归过程
  free(tmp);
  tmp = NULL;
}

递归函数:

//归并排序的子程序
void _MergeSort(int* a, int left, int right, int* tmp)
{
  if (left >= right)
  {
    return;
  }
  int mid = (left + right) / 2;
  _MergeSort(a, left, mid, tmp);//进了函数一直递归,直到数组元素为1个后开始归并排序
  _MergeSort(a, mid + 1, right, tmp);//先递归左边再递归右边
  int begin1 = left;
  int end1 = mid;
  int begin2 = mid + 1;
  int end2 = right;
  int i = left;
  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++];
  }
  //将tmp数组的内容拷贝回a数组
  for (int j = left; j <= right; j++)
  {
    a[j] = tmp[j];
  }
}

6. 总结思考以及拓展

算法效率思考:

我们按最坏的情况来计算

  • 归并排序会将数组不断二分
    一共分为 log2n 这么多层
  • 而第一次二分的数组要走n/2个元素
    二分完有两个数组也就是走n个元素
  • 以此类推,第二次二分完的数组
    有四个,每个数组需要走n/4个元素
    第二层也就要走n个元素
  • 可以推算出每层要走n个元素

一共log~2~n层,每层遍历n个元素

时间复杂度为: O(N*log2N)


拓展:

归并排序最坏情况下

时间复杂度为: N * (log2N)+1-N ∈ O(Nlog2N)
归并排序最好情况下
时间复杂度为: (N * log2N)/2 ∈ O(Nlog2N)

详细推导过程可以参考:归并算法分析

既然归并有递归版本
那么肯定就有非递归版本
还是那句话:
正在与别人拉开差距的地方
往往就是研究得更加深入的地方


🔎 下期预告:归并排序非递归版 🔍

相关文章
|
9月前
|
机器学习/深度学习 算法 搜索推荐
重点算法排序之堆排序(下篇)
我们已经讲述了快速排序和归并排序,快速排序和归并排序详解文章链接:重点算法排序之快速排序、归并排序(上篇),我们本篇文章来详细讲述以下堆排序。堆排序的主要内容有:最大堆(大顶堆)、最小堆(小顶堆)、通过孩子找父亲、通过父亲找孩子、向下调整算法建堆。下面我会给大家一一介绍。
35 0
|
5天前
|
存储 搜索推荐
【七大排序】堆排序详解
【七大排序】堆排序详解
11 3
|
2月前
|
算法 搜索推荐
【六大排序详解】中篇 :选择排序 与 堆排序
选择排序可以用扑克牌理解,眼睛看一遍所有牌,选择最小的放在最左边。然后略过刚才排完的那张,继续进行至扑克牌有序。这样一次一次的挑选,思路很顺畅。总结为: 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
25 6
|
2月前
|
算法
【六大排序详解】终篇 :冒泡排序 与 快速排序
冒泡排序如同泡泡上升一样,逐个逐个向上冒,一个接一个的冒上去。两两比较,较大者(较小者)向后挪动。全部遍历一遍即可完成排序
26 2
|
2月前
|
搜索推荐 测试技术
【六大排序详解】开篇 :插入排序 与 希尔排序
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 排序存在稳定性,稳定性是评估排序的重要标准。
23 1
|
2月前
|
人工智能 搜索推荐
【hoare基础版】快速排序算法(1)
【hoare基础版】快速排序算法(1)
52 0
|
2月前
|
存储 搜索推荐 算法
拒绝水文!八大排序(四)【适合初学者】归并排序和计数排序
拒绝水文!八大排序(四)【适合初学者】归并排序和计数排序
|
2月前
|
搜索推荐 算法
拒绝水文!八大排序(二)【适合初学者】冒泡排序和选择排序
拒绝水文!八大排序(二)【适合初学者】冒泡排序和选择排序
|
2月前
|
搜索推荐 算法
拒绝水文!八大排序(一)【适合初学者】直接插入排序和希尔排序
拒绝水文!八大排序(一)【适合初学者】直接插入排序和希尔排序
|
8月前
|
算法 搜索推荐
八大排序--------(五)堆排序
八大排序--------(五)堆排序
20 0