【八大排序】归并排序 | 计数排序 + 图文详解!!

news2025/1/16 5:09:24

在这里插入图片描述

📷 江池俊: 个人主页
🔥个人专栏: ✅数据结构冒险记 ✅C语言进阶之路
🌅 有航道的人,再渺小也不会迷途。


在这里插入图片描述

文章目录

    • 一、归并排序
      • 1.1 基本思想 + 动图演示
      • 2.2 递归版本代码实现 + 算法步骤
      • 2.3 非递归版本代码实现 + 算法步骤
      • 2.4 归并排序的特性总结
    • 二、计数排序
      • 2.1 基本思想
      • 2.2 动图演示
      • 2.3 算法步骤
      • 2.4 代码实现
      • 2.5 计数排序特性总结
    • 三、排序算法复杂度及稳定性分析

在这里插入图片描述

一、归并排序

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

1.1 基本思想 + 动图演示

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

在这里插入图片描述

在这里插入图片描述

2.2 递归版本代码实现 + 算法步骤

归并排序的基本思想是分治思想,它包括以下三个步骤:

  1. 分解(Divide):将含有n个元素的序列分成两个各自包含大约n/2个元素的子序列。(当数组分解成一个时即可认为其有序)
  2. 解决(Conquer):递归地对这两个子序列进行归并排序。
  3. 合并(Combine):将两个排序好的子序列合并成一个最终的排序序列。

归并排序通过不断地将大问题分解成小问题来解决,即把大的数组拆分成若干个小的数组,然后逐一合并这些有序的小数组来得到最终排序好的整体数组。这种算法非常适用于链表等数据结构,在处理大规模数据时尤其高效。

// 归并排序递归函数
void _MergeSort(int* a, int begin, int end, int* temp)
{
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;
	// [begin, mid] [mid+1, end]
	_MergeSort(a, begin, mid, temp);
	_MergeSort(a, mid+1, end, temp);

	// ... 归并 [begin,mid] [mid+1,end]
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			temp[i++] = a[begin1++];
		}
		else
		{
			temp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		temp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		temp[i++] = a[begin2++];
	}

	// 拷贝回原数组
	memcpy(a + begin, temp + begin, sizeof(int) * (end - begin + 1));
}

// 归并排序
void MergeSort(int* a, int n)
{
	// 申请一个与原数组同样大小的空间
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc fail");
		return;
	}

	_MergeSort(a, 0, n - 1, temp);

	free(temp);
}

【递归展开图】:
在这里插入图片描述

现在我们来分析一下以上代码:
这段代码是归并排序(Merge Sort)的实现。归并排序是一种分治算法,它将一个数组分成两半,对每一半进行排序,然后将两个有序的部分合并成一个有序的数组。以下是这段代码的算法思想和步骤分析:

  1. 递归划分

    • _MergeSort函数中,首先检查基准条件,即如果begin大于或等于end,则数组已经完全有序,所以直接返回。
    • 然后,计算中间索引mid,将数组分成两个子数组:[begin, mid][mid+1, end]
    • 对这两个子数组递归地进行归并排序。
  2. 合并

    • 在递归调用返回后,两个子数组都是有序的。然后,将这两个有序的子数组合并成一个有序的数组。
    • 合并操作通过双指针技术完成。指针begin1begin2分别指向两个子数组的开始位置,而指针end1end2分别指向两个子数组的结束位置。
    • 开始时,从两个子数组中取最小的元素,放到临时数组temp中,直到其中一个子数组被完全取完。
    • 然后,将剩余的子数组的所有元素复制到临时数组中。
  3. 拷贝回原数组

    • 最后,使用memcpy函数将临时数组中的元素复制回原数组。这一步是必要的,因为临时数组是在堆上分配的,而原数组是在栈上。
  4. 主函数

    • MergeSort函数是归并排序的入口点。它首先在堆上为原数组分配一个同样大小的临时数组。如果分配失败(即malloc返回NULL),则输出错误信息并返回。
    • 然后,调用递归函数_MergeSort对原数组进行排序。
    • 最后,释放临时数组以防止内存泄漏。
  5. 稳定性

    • 归并排序是稳定的排序算法,这意味着相等的元素在排序后保持其原始顺序。这是因为归并排序在合并两个子数组时,总是选择较小的元素,而不改变其相对顺序。
  6. 时间复杂度

    • 归并排序的时间复杂度为O(nlogn),其中n是数组的大小。这是因为每次递归调用都会将问题规模减半(logn),并且需要进行n次这样的递归调用(n)
  7. 空间复杂度

    • 归并排序的空间复杂度为O(n),因为需要一个与原数组同样大小的临时数组来存储合并过程中的中间结果。

2.3 非递归版本代码实现 + 算法步骤

// 归并排序(非递归)
void MergeSortNonR(int* a, int n)
{
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1; // 通过gap来控制归并的两个区间的大小,表示的是这两个区间的大小
	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;
			// [begin1, end1] [begin2, end2] 归并

			// 边界处理
			if (end1 >= n || begin2 >= n)
			{
				break;
			}

			if (end2 >= n)
			{
				end2 = n - 1;
			}

			// 归并
			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					temp[j++] = a[begin1++];
				}
				else
				{
					temp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				temp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				temp[j++] = a[begin2++];
			}

			// 拷贝回原数组(边归并边拷贝) --- 因为最后可能有一个区间不需要归并,所以这一个区间的元素是不需要改变的,即不需要拷贝回去,若一次性拷贝回原数组,会使这个区间的元素全部变为随机值
			memcpy(a + i, temp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}

	free(temp);
}

对于上述代码我们接着来分析一下它的算法步骤:

【算法步骤】:

  1. 初始化

    • 定义一个临时数组temp,其大小为输入数组a的大小。
    • 初始化一个变量gap为1,它表示每次归并时每组数据的个数。
  2. 归并循环

    • gap小于输入数组的长度n时,进入循环。
    • 在每次循环中,将数组分为两个子数组(每个子数组的大小为gap),并对这两个子数组进行归并。
  3. 子数组归并

    • 定义两个子数组的起始和结束索引:begin1end1begin2end2
    • 处理边界情况:如果其中一个子数组超出数组范围,则退出循环。
    • 使用一个临时数组temp来存储归并的结果。
    • 使用一个双指针方法(类似于两个指针比较和交换)来将两个子数组合并为一个有序数组。
  4. 拷贝回原数组

    • 使用memcpy函数将临时数组中的数据拷贝回原数组。这一步是为了在归并过程中更新原数组。
  5. 扩大gap

    • 在每次循环结束时,将gap乘以2,以便在下一次循环中处理更大的子数组。
  6. 释放内存

    • 归并完成后,释放临时数组temp的内存。

2.4 归并排序的特性总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

二、计数排序

2.1 基本思想

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中

在这里插入图片描述

2.2 动图演示

在这里插入图片描述

2.3 算法步骤

这段代码是实现计数排序算法的C语言代码。以下是该代码的算法步骤和思想分析:

算法步骤:

  1. 找出数组中的最小值和最大值:这是计数排序的一个重要步骤,因为算法需要对数组中的每个元素进行计数,所以需要知道元素的可能范围。
  2. 计算范围:根据最小值和最大值计算出元素的可能范围。
  3. 计数:遍历输入数组,对每个元素在其可能的范围内进行计数。
  4. 构建输出数组:根据计数结果,将每个元素放到它在输出数组中的正确位置。

2.4 代码实现

// 计数排序 
// 时间复杂度:O(N+range) 空间复杂度:O(range) 
void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];
	for (int i = 1; i < n; i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}

		if (a[i] > max)
		{
			max = a[i];
		}
	}

	int range = max - min + 1;
	int* count = (int*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		perror("calloc fail");
		return;
	}

	// 统计次数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}

	// 排序
	int i = 0;
	for (int j = 0; j < range; j++)
	{
		while (count[j]--)
		{
			a[i++] = j + min;
		}
	}
}

2.5 计数排序特性总结

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。数排序适用于整数且范围较小的情况。对于范围较大的整数或小数,需要更复杂的排序算法。
  2. 时间复杂度:O(MAX(N,范围)),由于算法只涉及到一次遍历输入数组和一次遍历计数数组,所以时间复杂度为O(MAX(N,范围))
  3. 空间复杂度:O(范围),由于需要创建一个与范围大小相等的计数数组,所以空间复杂度为O(范围)
  4. 稳定性:稳定(相等的元素在排序后保持其原始顺序)

三、排序算法复杂度及稳定性分析

在这里插入图片描述
在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1442475.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux应用程序参数传递的深入探索

大家好&#xff0c;今天给大家介绍Linux应用程序参数传递的深入探索&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 在Linux环境中&#xff0c;应用程序的参数传递是一个核心且灵…

【机房预约系统(C++版)】

一、机房预约系统需求 1.1、系统简介 学校现有几个规格不同的机房&#xff0c;由于使用时经常出现“撞车“现象,现开发一套机房预约系统&#xff0c;解决这一问题。 1.2、身份简介 分别有三种身份使用该程序学生代表:申请使用机房教师:审核学生的预约申请管理员:给学生、教…

Flink从入门到实践(三):数据实时采集 - Flink MySQL CDC

文章目录 系列文章索引一、概述1、版本匹配2、导包 二、编码实现1、基本使用2、更多配置3、自定义序列化器4、Flink SQL方式 三、踩坑1、The MySQL server has a timezone offset (0 seconds ahead of UTC) which does not match the configured timezone Asia/Shanghai. 参考资…

tlias智能学习辅助系统-增删改查+参数传递

本篇实现黑马tlias智能学习辅助系统中的部门以及员工管理&#xff0c;主要完成增删改查操作以及一些细节问题&#xff0c;后面会进一步总结登录校验、异常处理以及AOP的内容。 目录 一、环境搭建&#xff1a; 二、部门管理&#xff1a; 1、查询所有部门&#xff1a; contr…

switch 安装 moonlight 串流

准备工作&#xff1a; 1. 破解的switch 2. 能进行串流&#xff0c;支持moonlight 的电脑&#xff08;nvdia shiled 功能 / sunshine 软件&#xff09; 3. 比较好的网络环境&#xff1a;5Ghz频段wifi的稳定链接 --- 1. 去下载 moonlight 的 nro 包 GitHub - rock88/moonlig…

探索ChatGPT-4:智能会话的未来已来

深入了解ChatGPT-4&#xff1a;前沿AI的强大功能 ChatGPT-4是最先进的语言模型之一&#xff0c;由OpenAI开发&#xff0c;它在自然语言理解和生成方面的能力已经达到了新的高度。如今&#xff0c;ChatGPT-4已经被广泛应用于多个领域&#xff0c;从教育到企业&#xff0c;再到技…

Spring Boot 笔记 003 Bean注册

使用Idea导入第三方jar包这种方法是不对的 使用maven命令导入jar包 官网下载maven Maven – Download Apache Maven 解压缩后在命令行输入命令&#xff0c;注意看输出的结果&#xff0c;安装的是不是你本地的maven仓库 mvn install:install-file -Dfile"D:\common-pojo-2…

CTFSHOW命令执行web入门29-54

description: >- 这里就记录一下ctfshow的刷题记录是web入门的命令执行专题里面的题目,他是有分类,并且覆盖也很广泛,所以就通过刷这个来,不过里面有一些脚本的题目发现我自己根本不会笑死。 如果还不怎么知道写题的话,可以去看我的gitbook,当然csdn我也转载了我自己的…

[SAP ABAP] 创建Package

Package被称作包或开发类&#xff0c;能够存储所有SAP系统开发过程中的相关对象&#xff0c;方便进行管理和查询 我们可以通过Package实现其所包含的对象在不同服务器之间进行批量传输(通过请求号传输) 请求号是文件&#xff0c;用于记录所有对象的创建与修改记录 1.创建Packag…

【Jenkins】Jenkins关闭Jenkins关闭、重启

目录 一、Jenkins关闭、重启 二、Jenkins服务的启动、停止方法。 一、Jenkins关闭、重启 1.关闭Jenkins 只需要在访问jenkins服务器的网址url地址后加上exit&#xff0c;关闭Jenkins服务。 例如&#xff1a;http://localhost:8081/exit 2.重启Jenkies 只有在Jenkins服务启动…

如何在 Mac 上恢复永久删除的文件:有效方法

您是否错误地从 Mac 中删除了某个文件&#xff0c;并且确信它已经永远消失了&#xff1f;好吧&#xff0c;你可能错了。即使您认为已永久删除计算机上的数据&#xff0c;仍有可能将其恢复。 在本文中&#xff0c;您将了解如何在 Mac 上恢复永久删除的文件&#xff0c;并了解增…

天猫数据分析(天猫数据查询工具):2023年滑雪服市场消费现状及趋势(天猫服饰行业分析报告)

随着全民健身的理念逐渐深入人心&#xff0c;滑雪运动产业迎来了前所未有的发展机遇。相关政策的大力支持&#xff0c;冰雪旅游服务设施的日益完善&#xff0c;都为滑雪运动的普及和推广提供了有力保障。 目前&#xff0c;滑雪行业正在蓬勃发展&#xff0c;而在滑雪运动的热潮…

ctfshow-命令执行(web118-web122)

web118 是一个窗口 查看源码 发现是system($code) 命令执行 经过测试禁用了很多东西 很多很多 $IFS可以 思路就是使用系统变量 构造我需要的poc 这些都是系统的环境变量 这是答案${PATH:~A}${PWD:~A}$IFS????.??? 解释一下 PATH变量输出结尾一般都是n 因为网站默认根目…

54.螺旋矩阵(Java)

题目描述&#xff1a; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 输入&#xff1a; matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a; [1,2,3,6,9,8,7,4,5] 代码实现&#xff1a; import java.util.ArrayLi…

Docker overlay2文件busy,容器不能删除问题解决

文章目录 在删除docker容器的时候报错,说设备正忙通过 docker ps -a 查看有两个状态的dead的容器解决方法&#xff1a;1.查看所有挂载的设备2.截取设备的进程id3.清理进程(kill掉即可) 在删除docker容器的时候报错,说设备正忙 Error response from daemon: Driver overlay2 fai…

从一到无穷大 #23 《流计算系统图解》书评

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言内容总结 引言 春节假期回到家里断然是不会有看纸质书的时间的。造化弄人&#…

python-游戏篇-初级-超级画板

文章目录 开发环境要求运行方法PyCharmVScode 代码main.pytools.py 效果 开发环境要求 本系统的软件开发及运行环境具体如下。 操作系统&#xff1a;Windows 7、Windows 10。Python版本&#xff1a;Python 3.7.1。开发工具&#xff1a;PyCharm 2018。Python内置模块&#xff…

政安晨:示例演绎TensorFlow的官方指南(三){快速使用数据可视化工具TensorBoard}

这篇文章里咱们演绎TensorFLow的数据可视化工具&#xff1a;TensorBoard。 在机器学习中&#xff0c;要改进模型的某些参数&#xff0c;您通常需要对其进行衡量。TensorBoard 是用于提供机器学习工作流期间所需测量和呈现的工具。它使您能够跟踪实验指标&#xff08;例如损失和…

STM32 定时器

目录 TIM 定时器定时中断 定时器外部时钟 PWM驱动LED呼吸灯&#xff08;OC&#xff09; PWM控制舵机 PWMA驱动直流电机 输入捕获模式测频率&#xff08;IC&#xff09; 输入捕获模式测占空比 编码器接口测速(编码器接口) TIM 通用定时器 高级定时器 定时器定时中断 Ti…

材料非线性Matlab有限元编程:初应力法与初应变法

导读:本文主要围绕材料非线性问题的有限元Matlab编程求解进行介绍,重点围绕牛顿-拉普森法(切线刚度法)、初应力法、初应变法等三种非线性迭代方法的算法原理展开讲解,最后利用Matlab对材料非线性问题有限元迭代求解算法进行实现,展示了实现求解的核心代码。这些内容都将收…