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

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

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

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

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

🔝🔝


归并排序

  • [1. 前言](#1. 前言 "#1__15")
  • [2. 归并排序基本思路](#2. 归并排序基本思路 "#2__30")
  • [3. 对合并两个有序数组的思考](#3. 对合并两个有序数组的思考 "#3__77")
  • [4. 合并两个有序数组代码实现](#4. 合并两个有序数组代码实现 "#4__111")
  • [5. 归并排序递归版代码实现](#5. 归并排序递归版代码实现 "#5___149")
  • [6. 总结思考以及拓展](#6. 总结思考以及拓展 "#6__224")

1. 前言

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

准备好,大家上车开启归并之旅
(注意:本章排序都按升序讲解)


2. 归并排序基本思路

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

c 复制代码
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合并为有序
  • 至此左右子区间都有序
  • 再将左右子区间归并为有序
  • 使数组整体有序

画图理解:

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

归并排序


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

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

c 复制代码
int a[]={1,2,3};
int b[]={2,5,6};

方法:

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

画图理解:

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

合并两个有序数组


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

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

c 复制代码
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开辟空间
在原函数上直接递归就不太方便
所以我们设计一个主函数和一个递归函数
方便我们编写代码

主函数:

c 复制代码
//归并排序
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;
}

递归函数:

c 复制代码
//归并排序的子程序
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(N
log2N)

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

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


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

相关推荐
知北游天3 分钟前
Linux:多线程---深入互斥&&浅谈同步
linux·运维·服务器
Gappsong8745 分钟前
【Linux学习】Linux安装并配置Redis
java·linux·运维·网络安全
try2find38 分钟前
移动conda虚拟环境的安装目录
linux·运维·conda
码农101号1 小时前
Linux中容器文件操作和数据卷使用以及目录挂载
linux·运维·服务器
PanZonghui1 小时前
Centos项目部署之Nginx 的安装与卸载
linux·nginx
PanZonghui1 小时前
Centos项目部署之安装数据库MySQL8
linux·后端·mysql
PanZonghui1 小时前
Centos项目部署之运行SpringBoot打包后的jar文件
linux·spring boot
PanZonghui1 小时前
Centos项目部署之Java安装与配置
java·linux
程序员弘羽2 小时前
Linux进程管理:从基础到实战
linux·运维·服务器
PanZonghui2 小时前
Centos项目部署之常用操作命令
linux