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

💓博主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)

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

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


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

相关推荐
不怕犯错,就怕不做12 分钟前
linux 如何查看自己的帐号密码及samba的帐号和密码
linux·运维·服务器
地下核武17 分钟前
Ubuntu 24.04 在线安装 Qt 6.10.2 后 Qt Creator 无法启动问题记录与解决
linux·qt·ubuntu
张3231 小时前
Linux 启动过程
linux·运维
三万棵雪松1 小时前
【Linux 物联网网关主控系统-Linux主控部分(二)】
linux·嵌入式linux
chinesegf1 小时前
ubuntu建虚拟环境制作docker容器
linux·ubuntu·docker
Stack Overflow?Tan901 小时前
标注软件labelImg在linux下鼠标滚轮闪退解决办法
linux·labelimg
李彦亮老师(本人)1 小时前
Rocky Linux 9.x 新特性详解
linux·运维·服务器·centos·rocky linux
NiKick1 小时前
在Linux系统上使用nmcli命令配置各种网络(有线、无线、vlan、vxlan、路由、网桥等)
linux·服务器·网络
biubiubiu07062 小时前
Python 环境安装与 Linux 控制入门
linux·开发语言·python
扛枪的书生4 小时前
包管理器用法速查
linux