【数据结构】排序算法(四):归并排序、计数排序与基数排序——突破 O(n log n) 的底层密码
目录一、 归并排序 (Merge Sort)1.1 算法思想1.2 代码实现1.3 运行推演1.4 复杂度分析二、 计数排序 (Counting Sort)2.1 算法思想2.2 具体步骤推演2.3 复杂度分析三、 基数排序 (Radix Sort)3.1 算法思想3.2 LSD 基数排序步骤推演以十进制为例3.3 复杂度分析前言 在之前的三篇文章中我们拆解了插入、交换、选择三大类共八种基于比较的排序算法。它们共同遵循一条铁律任何基于比较的排序时间复杂度下界为 O(n log n)。然而现实世界中存在某些数据凭借其内在结构可以绕过这个理论极限直接冲击线性时间。本文将聚焦三种特殊的排序算法归并排序比较类中的分治典范稳定且能处理海量数据、计数排序与基数排序非比较类双雄在特定场景下可达 O(n)。我们将从算法思想、代码实现、单步推演到复杂度分析逐一揭穿它们的高效秘密。一、 归并排序 (Merge Sort)1.1 算法思想归并排序是“分而治之”思想的教科书级演绎。其核心分为两步分 (Divide)将当前序列从中间位置一分为二并递归地对左右子序列继续切分直到每个子序列只剩下一个元素天然有序。治 (Merge)将两个已经有序的子序列合并成一个更大的有序序列。关键在于“合并”想象桌面上有两叠正面朝上的扑克牌每叠都已经从小到大排好。你每次只能看到最上面的一张比较两张牌将较小的那张抽出放入结果队列重复直到某一叠空掉再将另一叠剩余的所有牌直接接在末尾。归并排序的合并过程正是用双指针在辅助数组上完成这个“二路归一”的动作。1.2 代码实现以下为归并排序的核心 C 语言实现包括合并函数Merge与递归控制函数MergeSort。代码中使用全局变量n表示数组长度并动态分配了与原始数组等长的辅助数组b。// n为全局变量 static int n; n sizeof(a) / sizeof(DataType);DataType*b(DataType*)malloc((n1)*sizeof(DataType));voidMerge(DataType a[],intlow,intmid,inthigh){intilow,jmid1,k;for(klow;khigh;k)b[k]a[k];ki;while(imidjhigh){if(b[i]b[j])a[k]b[j];elsea[k]b[i];}while(imid)a[k]b[i];while(jhigh)a[k]b[j];}voidMergeSort(DataType a[],intlow,inthigh){if(lowhigh){intmidlow(high-low)/2;MergeSort(a,low,mid);MergeSort(a,mid1,high);Merge(a,low,mid,high);}}代码深度解析辅助数组b由于合并两个有序段时不能原地完成直接覆盖会丢失数据所以需要一块等长的临时空间。Merge的第一步是将待合并区间[low, high]整体拷贝到b之后在b上做双指针比较结果直接写回原数组a。双指针合并指针i扫描左半段[low, mid]指针j扫描右半段[mid1, high]。while循环每次挑选b[i]和b[j]中较小者放入a[k]同时推进对应指针。注意条件if (b[i] b[j])是“大于时才选右”当元素相等时会走else分支放入左元素这是保证归并排序稳定性的关键。收尾退出主循环后要么左半段有剩余要么右半段有剩余直接用两个while将剩余元素依次填入a。由于剩余部分本身已经有序直接追加即可。递归控制MergeSort采用标准的二分递归mid low (high - low) / 2的写法可防止(low high)溢出。递归到底层low high后开始回溯合并属于典型的后序遍历模式。1.3 运行推演以初始数组4 6 3 2 8 0 1 10 5 4为例完整跟踪归并排序的合并过程。以下为程序输出的合并日志清晰展示了长度由小到大的归并片段如何最终覆盖整个数组初始数组:46328011054合并[0~0]和[1~1]后:46328011054合并[0~1]和[2~2]后:34628011054合并[3~3]和[4~4]后:34628011054合并[5~5]和[6~6]后:23468011054合并[8~8]和[9~9]后:23468011045合并[0~2]和[3~4]后:23468011054合并[5~6]和[7~7]后:23468011054合并[5~7]和[8~9]后:23468014510合并[0~4]和[5~9]后:0123445681001234456810步骤详解最底层合并数组被逐层拆分最先合并的是相邻的单个元素对如[0~0]的4与[1~1]的6合并后依然为4 6。注意[8~8]和[9~9]5和4合并后变为4 5局部有序。中间层合并长度为 2 的有序段再合并成长度为 4 的段。例如[0~2](3 4 6) 与[3~4](2 8) 合并得到2 3 4 6 8。右半区也同步进行类似操作。顶层收束最终[0~4]的2 3 4 6 8与[5~9]的0 1 4 5 10合并完整有序序列诞生。整个过程如同一棵倒置的二叉树叶子长出有序片段根结点汇成最终结果。1.4 复杂度分析时间复杂度任何情况下均为 O(n log n)。归并排序的递归深度为 log n每一层需要对所有 n 个元素进行一次合并总操作次数严格稳定。无论数据有序与否分割与合并的动作都不会减少。这种“不挑食”的特性使它特别适合外部排序。空间复杂度O(n)。辅助数组b占据了与原始数组同等规模的空间。递归调用栈的深度为 O(log n)可忽略不计。因此归并排序不是原地排序算法内存开销是其唯一明显的短板。稳定性稳定。在Merge函数中当b[i] b[j]时我们会优先放置左半段的元素即b[i]这保证了相等元素的原始相对顺序不会在合并过程中被破坏。二、 计数排序 (Counting Sort)2.1 算法思想计数排序彻底抛弃了元素之间的两两比较。它假设输入数据是落在某个已知范围[min, max]内的整数或者可以映射为整数的对象核心思想是统计每个可能的值出现了多少次然后按照值的顺序依次“复制”出相应数量的元素直接构建有序序列。想象一场按年龄分组的排队你已知所有人的年龄都在 0~120 岁之间于是可以提前准备好 121 个空桶每遇到一个人就往对应年龄的桶里投一枚代币。投完后从 0 岁到 120 岁逐个清点桶里的代币数每清点一个年龄就让该数量的该年龄的人出列最终队伍必然按年龄有序。计数排序正是这样的非比较排序。2.2 具体步骤推演设原始数组A [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]已知所有元素取值范围在 1~9 之间或更宽但确定为 0~9。执行过程如下确定范围找到最小值min 1最大值max 9则计数数组长度为max - min 1 9。初始化计数数组开辟长度为 9 的数组count并全部初始化为 0。统计频次遍历A对每个元素x执行count[x - min]。遍历后值 1出现 2 次值 2出现 1 次值 3出现 2 次值 4出现 1 次值 5出现 2 次值 6出现 1 次值 9出现 1 次 其余为 0累加计数为稳定排序做准备对count数组进行前缀和操作count[i] count[i-1]。累加后count表示每个值在最终有序数组中的“最后一个位置的下一位置”值 1→2值 2→3值 3→5值 4→6值 5→8值 6→9值 9→10。反向填充输出数组开辟与A等长的输出数组output。为确保稳定性从A的末尾向前遍历取A[9] 3count[3-1]为 5将 3 放入output[5-1] output[4]count[3-1]减 1 变为 4。取A[8] 5count[5-1]为 8放入output[7]计数减为 7。依此类推最终output [1,1,2,3,3,4,5,5,6,9]。回写原数组如果需要原地效果将output拷贝回A。2.3 复杂度分析时间复杂度O(n k)其中 k 是取值范围的宽度max-min1。统计、累加、反向填充均只需常数趟线性扫描没有元素间的比较。当 k 与 n 处于同一数量级或更小时计数排序接近 O(n)但若 k 极大例如取值范围遍布整个 32 位整数复杂度将退化到不可接受。空间复杂度O(n k)。需要计数数组大小 k和输出数组大小 n。原地计数排序存在但会破坏稳定性且实现复杂。稳定性稳定前提是上述反向填充的实现方式。正向填充也能正确排序但会破坏稳定性。局限性仅适用于整数或可离散映射的数据要求数据范围不能过大否则空间和时间都不可控。三、 基数排序 (Radix Sort)3.1 算法思想基数排序同样不是基于比较它利用数字的基数表示如十进制、二进制来排序。核心思想是从最低有效位LSD到最高有效位MSD依次对每一位进行稳定排序。由于稳定排序的性质高位排序不会打乱低位已经排好的顺序最终实现全局有序。LSD 基数排序的过程可以类比为一副按花色和点数分类的扑克牌先按点数低位分成 13 摞然后把各摞按顺序收集起来再按花色高位分成 4 摞收集后整副牌就变成按花色优先、点数有序的序列。用于每一位的稳定排序通常采用计数排序或桶排序因为每一位的取值空间很小十进制为 0~9二进制为 0~1。3.2 LSD 基数排序步骤推演以十进制为例待排序数组[329, 457, 657, 839, 436, 720, 355]。所有数字均为三位十进制整数位数不足可高位补零统一视之。第一趟按个位数排序使用计数排序等稳定排序个位9,7,7,9,6,0,5排序后数组变为[720, 355, 436, 457, 657, 329, 839]个位 0,5,6,7,7,9,9第二趟按十位数排序基于上一步的结果十位2,5,3,5,5,2,3排序后[720, 329, 436, 839, 355, 457, 657]十位 2,2,3,3,5,5,5第三趟按百位数排序百位7,3,4,8,3,4,6排序后[329, 355, 436, 457, 657, 720, 839]百位 3,3,4,4,6,7,8最终得到完全有序的数组。注意每一步都必须使用稳定排序否则低位的有序性会被高位排序破坏。3.3 复杂度分析时间复杂度O(d × (n k))其中 d 是最大数字的位数k 是每位可能的取值基数如十进制 k10。若 k 相对 n 为常数通常如此且 d 也是常数或近似 O(1)则基数排序可达到 O(n) 的线性时间。实际中在 d 很大时可以通过扩大基数如以 65536 为基来减少趟数这也是一种常见的工程优化。空间复杂度O(n k)主要是每趟计数排序所需的计数数组和输出数组。稳定性稳定LSB 基数排序因为每一趟稳定排序确保了全局顺序的正确构建。适用范围适用于整数、定长字符串等可分解为“位”的数据。对于浮点数可通过 IEEE 754 的位表示进行基数排序但需注意符号位和阶码的处理。与比较排序的关系基数排序能够突破 O(n log n) 下界正是因为它没有使用元素之间的比较而是利用了值的位级结构信息。这也意味着它对数据的假设更强应用范围自然受限。

相关新闻

如何快速重置Cursor免费试用:3步解决请求限制的完整指南

如何快速重置Cursor免费试用:3步解决请求限制的完整指南

如何快速重置Cursor免费试用:3步解决请求限制的完整指南 【免费下载链接】go-cursor-help 解决Cursor在免费订阅期间出现以下提示的问题: Your request has been blocked as our system has detected suspicious activity / Youve reached your trial request limit…

2026/6/30 7:03:29阅读更多 →
【2024最新版】ChatGPT API接入避雷图谱:v1/chat/completions接口的12个隐性坑位与官方文档未标注的兼容性断点

【2024最新版】ChatGPT API接入避雷图谱:v1/chat/completions接口的12个隐性坑位与官方文档未标注的兼容性断点

更多请点击: https://codechina.net 第一章:ChatGPT API 接入指南 接入 ChatGPT API 是构建智能对话能力的基础环节,需完成身份认证、请求构造与响应解析三个核心步骤。OpenAI 官方提供 RESTful 接口,支持多种编程语言调用&#…

2026/6/30 6:58:28阅读更多 →
MSP430 DMA控制器配置详解:从原理到实战应用

MSP430 DMA控制器配置详解:从原理到实战应用

1. 项目概述:为什么MSP430的DMA是嵌入式开发的“效率倍增器”?在嵌入式项目里,尤其是那些对实时性和功耗有严苛要求的应用,比如用电池供电的传感器节点或者需要连续采集数据的便携设备,CPU的时间就是最宝贵的资源。你肯…

2026/6/30 6:58:28阅读更多 →
从ADC值到摄氏度:基于8051与查表法的NTC温度测量实战

从ADC值到摄氏度:基于8051与查表法的NTC温度测量实战

1. NTC温度测量基础原理 NTC热敏电阻是一种温度敏感元件,它的电阻值会随着温度升高而降低,这种特性被称为负温度系数。在实际应用中,我们通常将NTC与一个固定电阻串联,形成一个分压电路。当温度变化时,NTC的阻值改变导…

2026/6/30 8:13:34阅读更多 →
VinXiangQi:基于YOLOv5的中国象棋智能助手实战指南

VinXiangQi:基于YOLOv5的中国象棋智能助手实战指南

VinXiangQi:基于YOLOv5的中国象棋智能助手实战指南 【免费下载链接】VinXiangQi Xiangqi syncing tool based on Yolov5 / 基于Yolov5的中国象棋连线工具 项目地址: https://gitcode.com/gh_mirrors/vi/VinXiangQi VinXiangQi是一款基于深度学习技术YOLOv5的…

2026/6/30 8:13:34阅读更多 →
第二十二篇:咨询方法论——如何帮企业完成DISC成熟度评估与转型

第二十二篇:咨询方法论——如何帮企业完成DISC成熟度评估与转型

五维度评估模型、四阶段交付流程、三份标准化工具包——DISC咨询从0到1的完整操作手册 一、咨询是DISC落地的“最后一公里” 一位制造企业的CIO读完了DISC架构的系列文章。他认同“数据不动,能力流动”的理念,理解了“51”核心组件——能力注册中心是地…

2026/6/30 8:13:34阅读更多 →
为什么92%的用户Prompt效果不佳?揭秘LLM底层token解析机制与6步精准控制法

为什么92%的用户Prompt效果不佳?揭秘LLM底层token解析机制与6步精准控制法

更多请点击: https://codechina.net 第一章:为什么92%的用户Prompt效果不佳?揭秘LLM底层token解析机制与6步精准控制法 绝大多数用户将Prompt视为自然语言指令,却忽视了大语言模型实际处理的是离散token序列——而非语义完整的句…

2026/6/30 8:13:34阅读更多 →
DAC5681Z时钟配置与SPI寄存器调试实战指南

DAC5681Z时钟配置与SPI寄存器调试实战指南

1. 项目概述与核心价值在射频直采、宽带信号合成或者任意波形生成这类对信号纯度要求极高的应用里,时钟就像是整个系统的“心跳”。这颗“心跳”哪怕有一丁点不规律,最终输出的模拟信号频谱上就会多出许多不该有的“杂音”,专业术语叫杂散或相…

2026/6/30 8:13:34阅读更多 →
本地文件包含漏洞深度解析:从原理到实战修复

本地文件包含漏洞深度解析:从原理到实战修复

1. 项目概述:从一次“意外”访问说起那天下午,我正在排查一个内部管理系统的异常日志,系统管理员反馈说某个页面的访问日志里出现了奇怪的路径参数。我顺着日志里的请求路径尝试访问,本来应该显示用户配置文件的页面,却…

2026/6/30 8:08:34阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月,Boris Cherny 公开宣布自己卸载了 IDE。一时间,Vibe Coding 成了全行业最热的话题。6个月后,当我们回过头来拉一份真实账本,发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/6/30 4:03:30阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言:审计结束三个月了,审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间,内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中,审计…

2026/6/30 4:36:27阅读更多 →
为什么你需要Destiny 2 Solo Enabler:技术原理与实战指南

为什么你需要Destiny 2 Solo Enabler:技术原理与实战指南

为什么你需要Destiny 2 Solo Enabler:技术原理与实战指南 【免费下载链接】Destiny-2-Solo-Enabler Repo containing the C# and XAML code for the D2SE program. Included is also the dependency for the program, and image asset. 项目地址: https://gitcode…

2026/6/30 0:02:58阅读更多 →
第六章:PowerPoint 2010 核心功能与实战应用 —— 从入门到精通

第六章:PowerPoint 2010 核心功能与实战应用 —— 从入门到精通

1. PowerPoint 2010基础操作全攻略 刚接触PowerPoint 2010时,很多人会被它复杂的界面吓到。其实只要掌握几个核心区域,就能快速上手。我最开始用PPT时,经常找不到功能按钮在哪,后来发现主要操作都集中在顶部功能区。 工作窗口主要…

2026/6/30 0:02:58阅读更多 →
XGBoost超参数实战:从理论到调优策略

XGBoost超参数实战:从理论到调优策略

1. XGBoost超参数基础认知 第一次接触XGBoost时,我被它那密密麻麻的参数列表吓到了。这感觉就像面对一架波音747的驾驶舱——每个按钮都可能有神奇的效果,但按错了就可能坠机。经过多年实战,我发现其实掌握十几个核心参数就能解决90%的问题。…

2026/6/30 0:02:59阅读更多 →