归并排序与快速排序深度对比:从原理到实战选型指南
1. 项目概述从“排序”这个日常操作说起我们每天都在和排序打交道无论是整理书架上的书、给手机里的照片按时间排列还是查看电商网站上按价格从低到高的商品列表。在计算机的世界里排序更是无处不在它是数据处理、信息检索、数据库索引等几乎所有计算任务的基石。今天我们不聊那些简单的排序而是聚焦于两个在算法世界里堪称“中流砥柱”的经典算法归并排序和快速排序。很多朋友在面试或者学习数据结构时都会被问到它们的区别但往往得到的答案只是“一个稳定一个不稳定”、“一个时间复杂度是O(n log n)另一个平均也是O(n log n)”。这远远不够。作为一名写过无数遍排序、调优过各种场景下排序性能的开发者我想和你深入聊聊这两个算法到底“骨子里”有什么不同以及在实际项目中我为什么会在这个场景选归并在另一个场景毫不犹豫地用快排。简单来说归并排序和快速排序都采用了“分治”的思想即把一个大问题分解成小问题来解决。但它们的“分”法和“治”法以及背后的哲学截然不同。理解这些区别不仅能帮你通过技术面试更能让你在面对海量数据排序、内存受限系统、要求稳定输出的业务时做出最合适的技术选型。这篇文章我会结合我这些年踩过的坑和总结的经验把这两个算法的核心差异掰开揉碎了讲清楚从原理、实现到应用场景给你一份可以直接“抄作业”的深度对比指南。2. 核心思想与哲学分野两种不同的“分治”之道虽然都顶着“分治算法”的头衔但归并排序和快速排序从第一步开始就走上了不同的道路。这种根本性的差异决定了它们后续所有的特性。2.1 归并排序先分后治结果导向的“老实人”归并排序的哲学非常直接甚至有点“笨拙”它不在乎怎么分只在乎最后怎么合。它的核心步骤可以概括为“分割”与“合并”。分割递归地将当前数组一分为二直到每个子数组只剩下一个元素一个元素自然是有序的。这个过程就像把一本书拆成章章拆成节节拆成句直到拆成单个的字。关键在于这个“拆”的过程是机械的、与数据内容无关的永远从中间下标mid (left right) / 2处切开。合并这是归并排序的灵魂。当拆到最小单元后开始回溯合并。合并两个有序小数组生成一个新的有序数组。这个过程需要额外的临时空间通常是一个和原数组等大的辅助数组用来存放合并过程中的中间结果。合并时我们用两个指针分别指向两个待合并数组的头部比较指针所指的元素将较小的那个放入辅助数组然后移动相应的指针直到某个数组被耗尽再将另一个数组的剩余部分全部追加进去。注意这个“合并”操作是稳定的。稳定是指如果原数组中存在两个相等的元素合并后它们的相对前后顺序不会改变。这是归并排序一个非常重要的特性在某些业务场景下是硬性要求。你可以把归并排序想象成一个非常严谨的归档员。他接到一堆乱序文件数组他的策略是先把所有文件随机分成两堆递归分割然后再分直到每堆只剩一份文件。然后他开始反向工作将两份文件排序合并成一个有序的小堆再将两个有序的小堆合并成更大的有序堆最终将所有文件整理成一摞完全有序的。整个过程他需要一张额外的空桌子辅助空间来摆放正在整理中的文件。2.2 快速排序先治后分过程导向的“投机者”快速排序的哲学则激进得多它希望在“分”的过程中就完成大部分排序工作让“合”变得微不足道。它的核心是“分区”操作。分区在数组中选取一个元素作为“基准”pivot。然后重新排列数组使得所有比基准值小的元素都移到基准的左边所有比基准值大的元素都移到基准的右边。操作结束后基准值就处于它最终应该在的位置上。这个操作是原地的通常通过交换元素来完成不需要像归并那样额外的线性空间。递归分区操作完成后基准值的位置已经确定。我们接着递归地对基准左侧的子数组和右侧的子数组进行同样的快速排序。快速排序的关键在于“分区”函数的设计和“基准值”的选择。一个高效的分区函数能在O(n)时间内完成操作而一个好的基准值选择策略如随机选择、三数取中能极大避免最坏情况的发生。把快速排序想象成一个高效的仓库管理员。他面对一堆杂乱无章的箱子数组快速扫一眼随机挑出一个箱子作为标杆pivot。然后他以这个标杆箱子的尺寸为标准手脚麻利地把所有比它小的箱子扔到左边区域比它大的扔到右边区域。这个过程中他就在原地腾挪箱子不需要额外空地。完成后标杆箱子的位置就固定了。接着他对左边和右边两堆箱子分别重复这个过程。他的目标是在每一次“分区”时都让一个元素找到它的最终归宿。根本区别归并排序的“分”是简单、无脑的二分真正的排序工作发生在“合”的阶段。快速排序的“分”即分区本身就是一次强有力的排序过程其“合”是隐式的递归返回后左右子数组和基准值自然就构成了有序整体。一个重在“合并”一个重在“分割”。3. 核心细节与特性深度对比理解了根本思想我们来从各个维度进行硬核对比。这些细节是面试常考点更是工程选择的依据。3.1 时间复杂度平均相似最坏天差地别这是最常被提及也最容易被误解的一点。归并排序无论输入数据是已经有序、完全逆序还是完全随机它的时间复杂度稳定为O(n log n)。因为它的分割是固定的每次对半分合并两个长度为n/2的数组总是需要 O(n) 的时间而递归的深度是 log n。所以它的性能非常可预测。快速排序平均情况时间复杂度也是O(n log n)。在随机数据或基准值选取得当的情况下递归树比较平衡性能与归并排序在同一个量级通常常数因子更小所以实际更快。最坏情况时间复杂度会退化到O(n²)。什么时候会发生当每次分区选取的基准值都是当前子数组的最大值或最小值时分区极度不平衡递归树退化成一条链。例如对一个已经有序的数组如果总是选择第一个或最后一个元素作为基准就会导致这种最坏情况。实操心得在实际项目中除非有绝对把握数据分布良好否则永远不要使用朴素快排如总是选第一个元素作pivot。务必采用随机化快排随机选择pivot或更高级的策略如三数取中法。在标准库的实现中如C的std::sort Java的Arrays.sort对对象排序通常都是高度优化的快速排序变种如内省排序IntroSort它们会监控递归深度在可能退化为O(n²)时切换到堆排序等保底算法。3.2 空间复杂度原地与非原地的对决归并排序不是原地排序。它需要额外的O(n)空间来作为辅助数组用于合并操作。这是它最大的缺点之一在内存极其受限的环境如嵌入式系统、某些内核操作中可能成为瓶颈。虽然有“原地归并”的研究但实现复杂且通常性能不如传统归并实践中很少用。快速排序是原地排序。它的分区操作通过元素交换在原始数组内完成递归调用仅需要O(log n)的栈空间用于保存递归调用的上下文。在空间效率上快排优势明显。3.3 稳定性业务需求的关键考量归并排序稳定排序。只要在合并两个有序子数组时遇到相等元素优先取前一个子数组的元素就能保证稳定性。这对于需要多关键字排序的场景至关重要。例如先按姓名拼音排序再按年龄排序第二次排序时不能打乱第一次排序中同姓名者的顺序。快速排序不稳定排序。在分区过程中元素的交换是跳跃式的很可能会打乱相等元素的原始相对顺序。例如对一个包含多个相同值的数组进行快排最终这些相同值的内部顺序是不可预测的。3.4 缓存局部性现代计算机架构下的隐形战场这是一个容易被忽略但极其重要的性能因素。现代CPU有多级缓存访问连续内存地址缓存友好的速度远快于随机访问。快速排序通常具有更好的缓存局部性。在分区过程中它顺序地遍历数组与头尾指针进行交换访问的内存地址相对连续。归并排序在合并阶段需要频繁地在原始数组和辅助数组之间来回拷贝数据。尤其是当数据量巨大无法完全放入高速缓存时这种跨数组的访问模式可能导致更多的缓存未命中影响性能。不过对于链表数据结构归并排序是天然缓存友好的因为链表元素本身就不连续而快排对链表性能很差。3.5 实现复杂度与并行化潜力实现复杂度归并排序的递归逻辑非常清晰、对称实现起来不容易出错。快速排序的核心——分区函数——有多种写法如Lomuto分区、Hoare分区边界条件需要小心处理实现不当容易导致死循环或错误。并行化归并排序具有天然的并行性。“分”的阶段可以很容易地将大任务拆分成独立的小任务分发给多个线程或处理器执行“合”的阶段虽然需要同步但也可以并行合并多个已排序的片段。MapReduce这类大数据处理框架的思想就与归并排序高度契合。快速排序并行化相对复杂。虽然分区后的左右子数组可以独立排序但分区操作本身是一个需要全局扫描和交换的过程难以并行。不过在递归的每一层可以并行处理不同的子数组。为了更直观我将核心差异总结成下表特性维度归并排序快速排序平均时间复杂度O(n log n)O(n log n)最坏时间复杂度O(n log n)O(n²)空间复杂度O(n) (非原地)O(log n) (原地)稳定性稳定不稳定缓存局部性一般数组较好实现难度较简单分区函数需注意边界数据敏感性不敏感性能恒定敏感依赖pivot选择并行化友好度高中4. 应用场景与选型实战什么时候用谁理论对比之后我们来点实在的在真实项目中如何选择我根据多年的经验总结出以下几个决策点。4.1 优先选择归并排序的场景需要稳定排序时这是归并排序的“杀手锏”。当你的业务逻辑依赖于排序的稳定性时没有商量余地必须选择稳定排序算法。归并排序是常见的O(n log n)稳定排序算法之一另一个是冒泡、插入这些O(n²)的不适用于大数据量。处理链表数据结构时链表不支持随机访问而快排的分区操作严重依赖下标随机访问。归并排序对于链表来说简直是绝配因为链表的合并操作可以在O(1)的额外空间内完成只需修改节点指针且非常高效。许多编程语言中链表排序的内部实现就是归并排序。数据量巨大且需要外部排序时当数据大到无法全部加载进内存时必须使用外部排序。归并排序是外部排序的基石算法。其思想是将大文件分割成多个能装入内存的小块每块在内存中用内排如快排排好序写回磁盘形成多个有序的“归并段”然后多路归并这些有序段最终得到全局有序文件。这个过程完美契合了归并“先分块排序再合并”的理念。对性能 predictability可预测性要求极高时在一些实时系统或对响应时间有严格上限的场景你不能接受算法偶尔退化到O(n²)。归并排序O(n log n)的稳定表现提供了这种确定性。4.2 优先选择快速排序的场景对通用内存内排序追求极致速度时对于保存在内存中的数组或向量快速排序在平均情况下通常是所有比较排序算法中最快的。其常数因子小且缓存友好。这就是为什么C STL的std::sort、C的qsort、以及许多脚本语言默认排序的内部实现都基于快速排序的优化变种。内存空间受限时原地排序的特性让快排在嵌入式设备、内核开发等内存紧张的环境中大放异彩。O(log n)的栈空间开销远比O(n)的辅助数组开销更容易接受。数据是随机分布时如果数据没有明显的规律如完全有序或逆序随机化快排能很好地避免最坏情况发挥其平均性能的优势。4.3 一个综合案例数据库的ORDER BY考虑一个数据库查询SELECT * FROM users ORDER BY age DESC, name ASC;。数据库需要先按年龄降序排年龄相同的再按姓名升序排。稳定性需求为了保证第二次排序按姓名不破坏第一次排序按年龄的结果数据库必须使用稳定排序算法或者使用一种能一次性处理多列排序的算法如基于比较的排序中传入一个比较两者年龄和姓名的复合比较函数。如果底层用的是不稳定排序那么年龄相同的用户的姓名顺序将是随机的这不符合SQL语义。因此许多数据库在对可能包含重复值的列进行排序时会倾向于使用稳定算法或经过特殊处理的不稳定算法来模拟稳定行为。外部排序可能如果users表非常大无法在内存中完成排序数据库就会启动外部归并排序。它会利用临时磁盘空间进行多轮归并。内存排序阶段在外部排序的每个内部“块”排序阶段或者在数据量能完全装入内存时数据库优化器可能会选择更快的快速排序来对单个块进行排序。所以在一个复杂的系统中归并和快排常常是协同工作的各司其职。5. 常见问题与实战避坑指南在实际编码和面试中总会遇到一些典型问题。这里我分享一些踩过的坑和应对技巧。5.1 如何避免快速排序的最坏情况这是快排面试必问题。关键在于优化基准值pivot的选择策略。随机化每次分区时随机从当前子数组中选取一个元素作为pivot。这是最简单有效的方法能将最坏情况出现的概率降到极低从算法期望上保证了O(n log n)。三数取中法取当前子数组的头、尾、中间三个元素将这三个元素的中位数作为pivot。这种方法能有效避免在数组已有序或接近有序时的性能退化。切换到插入排序当递归到子数组规模很小比如长度小于10时快速排序的递归开销可能比排序本身还大。此时直接改用插入排序。因为插入排序在小规模数据上非常高效且是稳定排序。这也是很多工业级排序算法的优化策略。示例三数取中法的代码片段C风格int medianOfThree(vectorint arr, int left, int right) { int mid left (right - left) / 2; // 对arr[left], arr[mid], arr[right]进行排序取中间值 if (arr[left] arr[mid]) swap(arr[left], arr[mid]); if (arr[left] arr[right]) swap(arr[left], arr[right]); if (arr[mid] arr[right]) swap(arr[mid], arr[right]); // 现在arr[mid]是中位数将其与arr[right-1]交换作为pivot swap(arr[mid], arr[right-1]); return arr[right-1]; // 返回pivot值 }5.2 归并排序的递归与非递归实现递归实现直观但有栈溢出风险深度log n通常风险不大。非递归迭代实现是理解其分治过程的好方法。递归实现自上而下代码简洁。迭代实现自下而上。首先认为每个元素是长度为1的有序子数组然后两两合并成长度为2的有序子数组再两两合并成长度为4的以此类推直到整个数组合并完成。迭代实现避免了递归调用但控制循环的边界条件需要格外小心。5.3 面对“为什么Java的Arrays.sort()对对象用归并对基本类型用快排”这类问题这是一个经典的面试题它完美结合了二者的特性。对对象Object排序使用的是TimSort一种归并排序和插入排序的混合优化算法。TimSort是稳定的。因为对象排序通常用于集合类开发者可能依赖排序的稳定性例如在一个已经按姓名排序的列表中再按部门排序希望同部门内姓名顺序保持不变。稳定性是API契约的一部分。对基本类型int, double等排序使用的是双轴快速排序Dual-Pivot QuickSort的变种。基本类型的值没有“身份”只有大小稳定性没有意义两个相等的整数5交换位置不影响任何逻辑。因此可以采用更快的、但不稳定的快速排序变种来追求极致性能。5.4 调试与验证技巧自己实现排序算法时如何验证正确性小数据量测试用边界case测试如空数组、单元素数组、已排序数组、逆序数组、全等数组。大数据量随机测试生成大量随机数用自己实现的排序函数排序后与语言标准库的排序结果逐元素对比。稳定性测试创建一个结构体数组包含主键和次键。先按次键排序再按主键排序。如果算法稳定那么对于主键相同的元素其次键的顺序应保持第一次排序后的顺序。用这个可以直观验证归并排序的稳定性以及快排的不稳定性。性能粗略分析对于10万、100万量级的随机整数计时对比自己实现的算法和标准库算法。注意自己实现的版本通常远慢于高度优化的标准库实现这个对比主要是为了验证时间复杂度增长趋势是否合理例如数据量增大10倍时间是否大致增加10倍多一点符合n log n的增长。理解归并排序和快速排序的区别远不止于记住一张对比表。它关乎你在设计系统时对性能、内存、稳定性和可预测性的权衡。下次当你需要排序时不妨先问自己几个问题数据有多大在内存还是磁盘需要稳定吗数据结构是数组还是链表回答完这些问题该选谁心里自然就有数了。

相关新闻

多模态大模型夜盲症揭秘:NightSight基准实测与低光视觉鲁棒性提升

多模态大模型夜盲症揭秘:NightSight基准实测与低光视觉鲁棒性提升

1. 项目概述:当AI在黑夜中“失明” 最近在ICLR 2026上看到一篇很有意思的工作,核心议题直指当前多模态大模型的一个“阿喀琉斯之踵”:夜盲。没错,就是字面意思。我们平时用GPT-4V、Gemini、Claude这些模型处理图片时,感…

2026/10/13 23:19:11 阅读更多 →
聊了6位毕业3年的学长,普通人怎么选EMBA

聊了6位毕业3年的学长,普通人怎么选EMBA

对处于职业上升关键期的企业中层、高层管理者而言,选择适配的EMBA项目,核心是算清时间、资金投入与长期职业回报的账,香港科技大学EMBA中英双语课程是不少把跨境发展、科技赛道布局作为目标的高管会优先纳入考量的选项。近期和6位毕业满3年、…

2026/10/13 19:37:08 阅读更多 →
Codex桌宠开发实战:从环境配置到智能交互的完整指南

Codex桌宠开发实战:从环境配置到智能交互的完整指南

你有没有试过在写代码的时候,希望有个虚拟伙伴在旁边?不是那种冷冰冰的代码提示工具,而是真正有互动感、能给你反馈的桌面伴侣。最近,一个叫“codex桌宠”的项目开始在开发者圈子里流传,特别是它结合了热门游戏《鸣潮》…

2026/10/9 7:13:50 阅读更多 →

最新新闻

程序员如何培养“设计师的眼睛”:从经典包豪斯栅格到日常 UI 找茬训练法

程序员如何培养“设计师的眼睛”:从经典包豪斯栅格到日常 UI 找茬训练法

在前端产研团队中,我们经常能听到这样的争论: 资深 UI 设计师走查页面时,皱着眉头说:“这个图标和旁边的文字不对劲,往上偏了 2 像素;这块卡片的投影太脏了;两个区块之间的呼吸感完全断了。”坐…

2026/10/13 23:18:21 阅读更多 →
SpringBoot 2.3+ MySQL中hibernate_sequence报错解决方案

SpringBoot 2.3+ MySQL中hibernate_sequence报错解决方案

1. 项目概述:为什么这个标题值得深挖?SpringBoot 2.3.0 是一个承前启后的关键版本——它正式移除了对 javax.* 包的默认依赖,全面转向 jakarta.* 命名空间;同时 Hibernate 5.4.17 成为默认 JPA 实现,而该版本对数据库序…

2026/10/13 23:18:21 阅读更多 →
深入理解5g<五十六>UM的接收过程

深入理解5g<五十六>UM的接收过程

5G RLC UM(Unacknowledged Mode,非确认模式)接收端的主要职责是**按序重组并向上层递交RLC SDU**,同时通过窗口机制和定时器来管理乱序、分段和丢失的PDU。其过程严格遵循3GPP TS 38.322规范。### 📦 核心状态变量与重…

2026/10/13 23:18:21 阅读更多 →
WBCE损失重写:动态加权、梯度聚焦与标签可信度建模

WBCE损失重写:动态加权、梯度聚焦与标签可信度建模

1. 项目概述:WBCE损失重写不是调参,而是对分类任务底层逻辑的重新校准“WBCE损失重写”这个标题乍看像一个技术微调动作,但实际它指向的是深度学习分类任务中一个被长期低估、却直接影响模型泛化能力与业务落地效果的核心环节——加权二元交叉…

2026/10/13 23:18:21 阅读更多 →
Python文本预处理与词频可视化:从原始文本到词云图实战

Python文本预处理与词频可视化:从原始文本到词云图实战

1. 从原始文本到可视化:为什么预处理决定了词频图的成败很多人拿到一段文本,第一反应就是直接丢进词频统计工具里跑,结果出来的图要么是一堆"的""了""是"占满画面,要么是各种标点符号和乱码混在一起…

2026/10/13 23:18:21 阅读更多 →
免费帝国插件内容流水线搭建:采集、伪原创、发布与推送全流程避坑指南

免费帝国插件内容流水线搭建:采集、伪原创、发布与推送全流程避坑指南

简介:这是一款面向站长与SEO从业者的帝国CMS配套插件,聚焦关键词采集、文章采集、伪原创改写、自动发布与搜索引擎推送等环节,帮助希望提升站点收录与排名、又缺乏持续内容产出精力的用户搭建半自动化的优化流程。资源以zip压缩包形式提供&am…

2026/10/13 23:17:20 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →