【八大排序】归并排序 | 计数排序 + 图文详解!!
文章目录
- 一、归并排序
- 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 递归版本代码实现 + 算法步骤
归并排序的基本思想是分治思想,它包括以下三个步骤:
- 分解(Divide):将含有n个元素的序列分成两个各自包含大约n/2个元素的子序列。(当数组分解成一个时即可认为其有序)
- 解决(Conquer):递归地对这两个子序列进行归并排序。
- 合并(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)的实现。归并排序是一种分治算法,它将一个数组分成两半,对每一半进行排序,然后将两个有序的部分合并成一个有序的数组。以下是这段代码的算法思想和步骤分析:
-
递归划分:
- 在
_MergeSort
函数中,首先检查基准条件,即如果begin
大于或等于end
,则数组已经完全有序,所以直接返回。 - 然后,计算中间索引
mid
,将数组分成两个子数组:[begin, mid]
和[mid+1, end]
。 - 对这两个子数组递归地进行归并排序。
- 在
-
合并:
- 在递归调用返回后,两个子数组都是有序的。然后,将这两个有序的子数组合并成一个有序的数组。
- 合并操作通过双指针技术完成。指针
begin1
和begin2
分别指向两个子数组的开始位置,而指针end1
和end2
分别指向两个子数组的结束位置。 - 开始时,从两个子数组中取最小的元素,放到临时数组
temp
中,直到其中一个子数组被完全取完。 - 然后,将剩余的子数组的所有元素复制到临时数组中。
-
拷贝回原数组:
- 最后,使用
memcpy
函数将临时数组中的元素复制回原数组。这一步是必要的,因为临时数组是在堆上分配的,而原数组是在栈上。
- 最后,使用
-
主函数:
MergeSort
函数是归并排序的入口点。它首先在堆上为原数组分配一个同样大小的临时数组。如果分配失败(即malloc
返回NULL),则输出错误信息并返回。- 然后,调用递归函数
_MergeSort
对原数组进行排序。 - 最后,释放临时数组以防止内存泄漏。
-
稳定性:
- 归并排序是稳定的排序算法,这意味着相等的元素在排序后保持其原始顺序。这是因为归并排序在合并两个子数组时,总是选择较小的元素,而不改变其相对顺序。
-
时间复杂度:
- 归并排序的时间复杂度为
O(nlogn)
,其中n
是数组的大小。这是因为每次递归调用都会将问题规模减半(logn)
,并且需要进行n
次这样的递归调用(n)
。
- 归并排序的时间复杂度为
-
空间复杂度:
- 归并排序的空间复杂度为
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);
}
对于上述代码我们接着来分析一下它的算法步骤:
【算法步骤】:
-
初始化:
- 定义一个临时数组
temp
,其大小为输入数组a
的大小。 - 初始化一个变量
gap
为1,它表示每次归并时每组数据的个数。
- 定义一个临时数组
-
归并循环:
- 当
gap
小于输入数组的长度n
时,进入循环。 - 在每次循环中,将数组分为两个子数组(每个子数组的大小为
gap
),并对这两个子数组进行归并。
- 当
-
子数组归并:
- 定义两个子数组的起始和结束索引:
begin1
到end1
和begin2
到end2
。 - 处理边界情况:如果其中一个子数组超出数组范围,则退出循环。
- 使用一个临时数组
temp
来存储归并的结果。 - 使用一个双指针方法(类似于两个指针比较和交换)来将两个子数组合并为一个有序数组。
- 定义两个子数组的起始和结束索引:
-
拷贝回原数组:
- 使用
memcpy
函数将临时数组中的数据拷贝回原数组。这一步是为了在归并过程中更新原数组。
- 使用
-
扩大gap:
- 在每次循环结束时,将
gap
乘以2,以便在下一次循环中处理更大的子数组。
- 在每次循环结束时,将
-
释放内存:
- 归并完成后,释放临时数组
temp
的内存。
- 归并完成后,释放临时数组
2.4 归并排序的特性总结
- 归并的缺点在于需要
O(N)
的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。 - 时间复杂度:
O(N*logN)
- 空间复杂度:
O(N)
- 稳定性:稳定
二、计数排序
2.1 基本思想
思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:
- 统计相同元素出现次数
- 根据统计的结果将序列回收到原来的序列中
2.2 动图演示
2.3 算法步骤
这段代码是实现计数排序算法的C语言代码。以下是该代码的算法步骤和思想分析:
算法步骤:
- 找出数组中的最小值和最大值:这是计数排序的一个重要步骤,因为算法需要对数组中的每个元素进行计数,所以需要知道元素的可能范围。
- 计算范围:根据最小值和最大值计算出元素的可能范围。
- 计数:遍历输入数组,对每个元素在其可能的范围内进行计数。
- 构建输出数组:根据计数结果,将每个元素放到它在输出数组中的正确位置。
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 计数排序特性总结
- 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。数排序适用于整数且范围较小的情况。对于范围较大的整数或小数,需要更复杂的排序算法。
- 时间复杂度:
O(MAX(N,范围))
,由于算法只涉及到一次遍历输入数组和一次遍历计数数组,所以时间复杂度为O(MAX(N,范围))
。 - 空间复杂度:
O(范围)
,由于需要创建一个与范围大小相等的计数数组,所以空间复杂度为O(范围)
。 - 稳定性:稳定(相等的元素在排序后保持其原始顺序)
三、排序算法复杂度及稳定性分析
相关文章:

【八大排序】归并排序 | 计数排序 + 图文详解!!
📷 江池俊: 个人主页 🔥个人专栏: ✅数据结构冒险记 ✅C语言进阶之路 🌅 有航道的人,再渺小也不会迷途。 文章目录 一、归并排序1.1 基本思想 动图演示2.2 递归版本代码实现 算法步骤2.3 非递归版本代…...

Netty应用(三) 之 NIO开发使用 网络编程 多路复用
目录 重要:logback日志的引入以及整合步骤 5.NIO的开发使用 5.1 文件操作 5.1.1 读取文件内容 5.1.2 写入文件内容 5.1.3 文件的复制 5.2 网络编程 5.2.1 accept,read阻塞的NIO编程 5.2.2 把accept,read设置成非阻塞的NIO编程 5.2.3…...
融资项目——配置redis
一、 在maven中导入相关依赖。在springboot框架中,我们使用spring data redis <!-- spring boot redis缓存引入 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifa…...

npm修改镜像源
背景:切换npm镜像源是经常遇到的事,下面记录下具体操作命令 1. 打开终端运行"npm config get registry"命令来查看当前配置的镜像源 npm config get registry2. 修改成淘宝镜像源"https://registry.npmjs.org/" npm config set re…...
K8S系列文章之 [基于 Alpine 使用 kubeadm 搭建 k8s]
先部署基础环境,然后根据官方文档 K8s - Alpine Linux,进行操作。 将官方文档整理为脚本 整理脚本时,有部分调整 #!/bin/shset -x # 添加源,安装时已经配置 #cat >> /etc/apk/repositories <<"EOF" #htt…...

JVM相关-JVM模型、垃圾回收、JVM调优
一、JVM模型 JVM内部体型划分 JVM的内部体系结构分为三部分,分别是:类加载器(ClassLoader)子系统、运行时数据区(内存)和执行引擎 1、类加载器 概念 每个JVM都有一个类加载器子系统(class l…...

提升图像分割精度:学习UNet++算法
文章目录 一、UNet 算法简介1.1 什么是 UNet 算法1.2 UNet 的优缺点1.3 UNet 在图像分割领域的应用 二、准备工作2.1 Python 环境配置2.2 相关库的安装 三、数据处理3.1 数据的获取与预处理3.2 数据的可视化与分析 四、网络结构4.1 UNet 的网络结构4.2 UNet 各层的作用 五、训练…...

排序算法---冒泡排序
原创不易,转载请注明出处。欢迎点赞收藏~ 冒泡排序是一种简单的排序算法,其原理是重复地比较相邻的两个元素,并将顺序不正确的元素进行交换,使得每次遍历都能将一个最大(或最小)的元素放到末尾。通过多次遍…...

基于数据挖掘的微博事件分析与可视化大屏分析系统
设计原理,是指一个系统的设计由来,其将需求合理拆解成功能,抽象的描述系统的模块,以模块下的功能。功能模块化后,变成可组合、可拆解的单元,在设计时,会将所有信息分解存储在各个表中࿰…...

数学建模-灰色预测最强讲义 GM(1,1)原理及Python实现
目录 一、GM(1,1)模型预测原理 二、GM(1,1)模型预测步骤 2.1 数据的检验与处理 2.2 建立模型 2.3 检验预测值 三、案例 灰色预测应用场景:时间序列预测 灰色预测的主要特点是模型使用的…...

智慧自助餐饮系统(SpringBoot+MP+Vue+微信小程序+JNI+ncnn+YOLOX-Nano)
一、项目简介 本项目是配合智慧自助餐厅下的一套综合系统,该系统分为安卓端、微信小程序用户端以及后台管理系统。安卓端利用图像识别技术进行识别多种不同菜品,识别成功后安卓端显示该订单菜品以及价格并且生成进入小程序的二维码,用户扫描…...

零基础学编程从入门到精通,系统化的编程视频教程上线,中文编程开发语言工具构件之缩放控制面板构件用法
一、前言 零基础学编程从入门到精通,系统化的编程视频教程上线,中文编程开发语言工具构件之缩放控制面板构件用法 编程入门视频教程链接 https://edu.csdn.net/course/detail/39036 编程工具及实例源码文件下载可以点击最下方官网卡片——软件下载—…...

【MySQL进阶之路】MySQL 中表空间和数据区的概念以及预读机制
欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送! 在我后台回复 「资料」 可领取编程高频电子书! 在我后台回复「面试」可领取硬核面试笔记! 文章导读地址…...

JVM 性能调优 - 常用的垃圾回收器(6)
垃圾收集器 在 JVM(Java虚拟机)中,垃圾收集器(Garbage Collector)是负责自动管理内存的组件。它的主要任务是在程序运行过程中,自动回收不再使用的对象所占用的内存空间,以便为新的对象提供足够的内存。 JVM中的垃圾收集器使用不同的算法和策略来实现垃圾收集过程,以…...

【java】Hibernate访问数据库
一、Hibernate访问数据库案例 Hibernate 是一个在 Java 社区广泛使用的对象关系映射(ORM)工具。它简化了 Java 应用程序中数据库操作的复杂性,并提供了一个框架,用于将对象模型数据映射到传统的关系型数据库。下面是一个简单的使…...
从零开始手写mmo游戏从框架到爆炸(八)— byte数组传输
导航:从零开始手写mmo游戏从框架到爆炸(零)—— 导航-CSDN博客 Netty帧解码器 Netty中,提供了几个重要的可以直接使用的帧解码器。 LineBasedFrameDecoder 行分割帧解码器。适用场景:每个上层数据包,使…...

Elasticsearch:BM25 及 使用 Elasticsearch 和 LangChain 的自查询检索器
本工作簿演示了 Elasticsearch 的自查询检索器将非结构化查询转换为结构化查询的示例,我们将其用于 BM25 示例。 在这个例子中: 我们将摄取 LangChain 之外的电影样本数据集自定义 ElasticsearchStore 中的检索策略以仅使用 BM25使用自查询检索将问题转…...
uniapp的api用法大全
页面生命周期API uniApp中的页面生命周期API可以帮助开发者在页面的不同生命周期中执行相应的操作。常用的页面生命周期API包括:onLoad、onShow、onReady、onHide、onUnload等。其中,onLoad在页面加载时触发,onShow在页面显示时触发…...
笔记——asp.net core 中的 REST
REST(reprentational state transfer,表层状态转移) REST原则:提倡按照HTTP的语义使用HTTP。 如果一个系统符合REST原则,我们就说这个系统是Restful风格的。 在RPC风格的Web API系统中,我们把服务端的代码…...

排序算法---堆排序
原创不易,转载请注明出处。欢迎点赞收藏~ 堆排序(Heap Sort)是一种基于二叉堆数据结构的排序算法。它将待排序的元素构建成一个最大堆(或最小堆),然后逐步将堆顶元素与堆的最后一个元素交换位置,…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...