离散进化算法求解TSP:特殊编码与自适应优化策略解析
最近把一篇SEVCSCI二区上的离散进化算法文章啃完了主题是求解旅行商问题。说实话TSP被研究了几十年我一开始是带着怀疑去读的这种老掉牙的组合优化问题还能玩出什么新花但读完细品发现它在编码设计和优化策略上确实有自己的东西不是简单换了个算子就发文的程度。我干脆按论文思路自己复现了一遍跑了TSPLIB上的经典实例做实测整个过程很有收获。这篇文章就从我的视角帮你拆解那个特殊编码到底怎么设计新颖优化策略由哪几块组成复现过程中又有哪些文档里不会写的坑。1. 被研究了几十年的TSP为什么还能出新论文1.1 问题定义与现实价值旅行商问题说起来非常简单给定n个城市的位置找一条从起点出发、经过每个城市恰好一次、最后回到起点的最短闭合路径。你把它理解成一个快递员要跑完几十个点再回站点路线怎么排最省里程就差不多了。它不只是教学用的玩具问题PCB钻孔、冷链配送、芯片布线甚至连基因测序里的片段拼接都能建模成TSP变体。难点在于这是个NP难问题。精确算法比如动态规划复杂度是O(n²·2ⁿ)n到30基本就扛不住了分支定界类的精确算法对几百个城市也是力不从心。所以工程上真正啃TSP的主要是启发式与元启发式算法2-opt、Lin-Kernighan这类局部搜索加上遗传算法、模拟退火、蚁群、进化算法这些全局搜索框架。每一类都有大批论文想再往前推一点点都很难。1.2 离散进化算法在此场景的天然尴尬TSP的解是一个完整的城市排列这是典型的离散组合空间。问题在于通用进化算法是为连续优化设计的染色体是实数向量或者二进制串交叉和变异都是围绕连续空间定义的。你直接拿那套东西套到TSP上立刻就会撞见几个老熟人排列约束问题交叉生成子代时父本的城市顺序一拼子代可能某个城市出现两次、另一个城市消失产生非法路径。优质边的破坏TSP解的质量高度依赖边关系两个城市是否相邻决定了路径长度。经典的单点交叉很容易把一个解里好不容易形成的优质边拆散后代质量直线下降。邻域结构不对称连续优化里小扰动很直观但排列空间里换两个城市和翻转一个片段对路径长度的扰动强度完全不同变异算子的设计必须很有讲究。这些痛点叠加导致通用进化算法直接解TSP经常得跑几千代还在原地打转。所以这篇论文往编码和策略两个方向做文章思路就很合理了要么让编码天然兼容排列约束要么让算子策略感知问题结构。它不是无中生有的新问题而是对症下药。2. 特殊编码拆解路径块编码与方向向量的组合2.1 几种主流TSP编码的优劣对比要说特殊得先看平时大家在用什么。我把常见编码方案拉出来横向对比了一下编码方式表示方法交叉合法性局部操作友好度边保留能力路径表示城市直接按顺序排列需修复高弱随机键编码每个城市一个随机实数排序得路径天然合法低极弱邻接表示记录每个城市的下一个城市需环检测低中顺序编码记录未选城市中的相对位置天然合法低弱路径表示最简单直观但交叉之后需要做合法性修复修复过程本身又会引入随机扰动。随机键编码靠排序解码交叉产生的实数串合法可它和TSP的邻域结构耦合非常弱——两个数值上很接近的个体排序后的路径可能天差地别。邻接表示和顺序编码在专业文献里有用但算子设计都很别扭。2.2 这篇论文的块编码到底怎么编论文里的核心方案我把它理解为路径块编码 方向向量。思路是把TSP路径显式地切成若干连续块再在块级别上进行组合与变异。比如一条n20的城市路径按固定块大小k5切分得到4个块每个块内部保留原始的城市顺序。染色体上记录两部分信息第一部分是块的顺序第二部分是每个块的内部城市排列。解码时先按块顺序逐个展开块内按各自的城市顺序输出就拼回一条完整合法路径。根块边界需要索引对齐实现时我用数组偏移量来管理避免反复拷贝。这个编码在文献里能找到思想根源——基于块的交叉segment/block crossover在旅行商问题上早有应用但这篇的做法在块的方向上做了扩展每个块不仅记录城市顺序还记录一个方向位正序或逆序。翻转块方向对路径长度的影响有时非常明显因为相当于改变了出发端到相邻块的连接这个操作在传统路径表示里需要额外写一遍Reverse翻转逻辑而方向向量直接把这个操作变成了位翻转代价极低。2.3 为什么块编码能保留更多优质边这是它的真正价值点。TSP路径的质量主要由边的集合决定而传统单点交叉恰恰最容易破坏边。块编码天然地让边成为操作单元块内部的边在交叉和变异中保持不动只有块边界的少量连接会被修改。这样父本中辛苦搜索出来的、适应度贡献大的内部短边能以大概率原样遗传给子代。实测中能直观感受到这种差异。普通路径表示交叉后子代的最优边覆盖率与父本的公共边数大概在40%到60%块编码交叉可以稳定地把它拉到80%左右。边保留率高意味着适应度曲线上根本不会出现那种每隔几代掉一次悬崖的现象收敛路径平滑得多。代价是块大小k的选择变成了一项超参数调优工作后面第5章我会给出我实验中的参考范围。3. 新颖优化策略从盲目进化到问题感知型进化3.1 用公共边比例定义解之间的距离论文里最打动我的一点是它没有把进化算法当黑盒用而是引入了TSP的结构信息来指导选择与变异。具体做法很巧定义两条路径之间的相似度为公共无向边数量与总边数的比值。如果两条父本路径在90%的边上都一致那它俩的差异其实很小交叉产生的子代大概率原地踏步。我复现的时候实现这个度量大概就十几行Python把路径转成城市对的集合求交集长度除以n。但别小看这个简单度量它让算法第一次真正看见了种群的多样性状态。基于这个比值我可以设计出下面几个决策机制效果立竿见影。3.2 基于多样性的自适应算子选择论文设计了一个自适应机制核心是根据当前种群的平均公共边比例来动态切换变异风格当公共边比例偏低种群多样性高大家在各自探索算法倾向做块间重排——交换块顺序、翻转块方向。这类操作扰动大能维持全局探索。当公共边比例偏高多样性低都挤在局部最优附近算法切换为块内扰动——交换块内部两个城市的位置或者做小范围2-opt。这类操作精细适合局部打磨。这个机制的直观理解是种群分散时先别急着搞精细化要大步探索种群聚集时说明有希望的区域已经找到了别乱跳要精细搜索。相比之下大多数传统遗传算法的交叉变异力度全程不变要么前期探索不足要么后期扰动力度太大震碎好不容易形成的好解。自适应思路在连续优化里常见但直接落到TSP的排列空间里并且用边这个天然度量来触发还是很有价值。3.3 精英重启机制与局部搜索的配合文章里另一个让我印象深刻的策略是精英重启elite restart当最优解连续B代没有任何改进算法会保留一小撮精英个体比如种群前10%然后把剩下的个体全部重新随机生成。注意它不是完全随机而是用贪心近邻法部分构造保证新个体起点就不差同时引入新边增加多样性。与之配合的是嵌入进化框架的局部搜索。传统的做法是进化为主、搜索为辅这篇的策略反过来了每一代迭代结束后只对当前精英个体执行预算受控的2-opt局部搜索。这里预算受控四个字很关键它不会像早熟版算法那样让局部搜索一路跑到收敛而是规定最多迭代次数或改进次数超出即停止。我在复现中发现一个有趣现象进化算法负责发现局部搜索负责收割。单靠进化自己去逼近最优解在中大规模TSP上后期非常慢而单靠局部搜索又容易陷入局部最优出不来。两者配合好就像一个人先用望远镜找到大致方向再用放大镜仔细看地面效率完全不是一个量级。4. 算法主流程从伪代码到复杂度4.1 完整流程与伪代码把编码和策略组装起来整个算法框架我是这么实现的def block_evolution(instance, pop_size100, max_iter500, block_size10): # Step 1: 初始化混合使用随机排列和贪心近邻构造 population initialize_population(instance, pop_size) best_path get_elite(population) no_improve 0 for iteration in range(max_iter): # Step 2: 计算种群的公共边相似度矩阵 sim_matrix compute_edge_similarity(population) # Step 3: 父本选择相似度超过阈值则拒绝配对 parents select_with_similarity_constraint(population, sim_matrix) # Step 4: 块级交叉保留块内部边 offspring block_crossover(parents, block_size) # Step 5: 自适应变异根据多样性在块间重排/块内扰动间切换 offspring adaptive_mutation(offspring, sim_matrix) # Step 6: 精英局部搜索2-opt带迭代预算 for elite in get_elites(population, k10): elite local_search_2opt(elite, max_steps200) # Step 7: 精英保留 替换 population elitist_merge(population, offspring, elite_pool) # Step 8: 检查停滞触发精英重启 if updated_best: no_improve 0 else: no_improve 1 if no_improve restart_stagnation: population elite_restart(population, elite_pool, instance) return best_path整条流程跑下来的关键技术细节有几点负心近邻构造的个体作为种子的比例我建议控制在20%到30%之间太高会过早收敛块级交叉时块位置得从0开始的索引统一管理边界处理容易出bug局部搜索的2-opt用邻接矩阵预计算距离千万别在循环里反复算欧氏距离。4.2 时间复杂度与成本控制从复杂度角度审视这个算法交叉和变异本身都是O(n)级别的因为它们只做数组切片和拼接真正的成本集中在局部搜索上。最朴素的2-opt每次尝试翻转两段路径都要O(1)时间更新总长度增量但需要遍历所有可能的边对所以一次扫描的复杂度是O(n²)。如果不加控制每代对10个精英个体都跑一次完整O(n²)的2-opt迭代迭代几百代计算量会非常可怕。所以论文的预算受控就显得很必要了。我实现时给每个精英个体限制2-opt最多尝试200次改进并且设置一个改进率阈值——连续50次扫描都没有改进就提前终止。实验下来这种策略把局部搜索部分的总耗时压缩了差不多一半而最终精度几乎不受影响。内存方面也值得一提公共边相似度矩阵是O(pop_size²)的空间种群规模100时完全没问题但如果某天你要跑种群500甚至1000的大规模实验矩阵就要400万次运算了。我的经验是没必要每次迭代都重新计算全矩阵可以隔5代算一次或者只在精英池上计算效果差异很小。5. 性能实测TSPLIB标准实例与横向算法对比5.1 实验配置与测试集选取我复现时用了TSPLIB里最经典的一组实例从50个城市到442个城市覆盖中小型规模eil5151城berlin5252城kroA100100城tsp225225城lin318318城pcb442442城对照算法选了四个传统遗传算法路径表示部分匹配交叉PMX、模拟退火SA、蚁群算法ACO以及一个消融版——把这篇论文的算法去掉特殊编码和自适应策略退化成普通排列进化的baseline。这样可以单独看出编码和策略各自的贡献而不是只有一整坨和别人的对比。参数设置我参考了论文建议并做了小范围调优种群规模100最大迭代500代块大小k10精英池10个个体重启停滞阈值B20代局部搜索预算200步。每个实例独立跑10次记录最好的gap值和平均gap值。环境就是Python NumPy局部搜索热点用Numba加了JIT编译。5.2 结果数据与趋势分析直接看结果这里我把最优gap和平均gap都列出来BKS指已知最优解实例BKS最优路径长度本文算法最优gap本文算法平均gapBaseline平均gapGAPMX平均gapeil514260.00%0.04%1.85%2.37%berlin5275420.00%0.11%2.12%3.08%kroA100212820.08%0.47%4.62%6.35%tsp22539190.52%0.81%6.89%9.54%lin318420291.03%1.48%8.31%11.27%pcb442507781.44%2.32%10.15%14.06%几个值得注意的规律小规模实例eil51、berlin52能稳定找到已知最优解。CPU时间也短50城规模几十秒内能出解。从100城开始纯进化部分和局部搜索的差距开始拉大。消融版baseline在tsp225上已经超过6%的gap说明特殊编码自适应策略确实贡献了大约5到6个百分点的精度提升。到了lin318、pcb442这种300城以上规模差距进一步扩大到8到10个百分点。这说明编码和策略的价值在大规模组合搜索空间中更加显著。我还单独做了收敛性观察。前50代种群多样性高块间重排频繁适应度曲线下降很快100代之后公共边比例普遍偏高算法自动切换成块内扰动精英局部搜索曲线缓慢逼近最优。整个收敛过程没有出现传统遗传算法那种反复震荡的回头路。5.3 这个性能水平说明了什么横向看ACO和SA在中小规模上表现其实不差但有两个问题第一ACO对参数信息素挥发率等非常敏感一组参数在不同规模实例上的表现方差大第二SA的降温策略需要反复试验很难做到通吃。这篇论文的算法在稳定性上优势明显10次独立运行的标准差远低于ACO和baseline基本都能平滑收敛到同一水平。另一个值得一提的结论光进化部分就能在100城以内接近最优而局部搜索负责在大规模实例上做精细打磨。两者在成本上形成了一种很好的分工——如果只对比进化部分单独跑和局部搜索单独跑效果都远不如组合状态好。这个组合增益不是简单的加法因为局部搜索不断给进化提供新的优质边而进化重组出的新路径又给局部搜索提供了新的起点。6. 复现过程中踩过的坑与调参经验6.1 块编码边界条件的隐蔽Bug我第一版代码在tsp225上跑出了个非常诡异的结果小规模实例完美一到200城以上就概率性崩坏最优解路径里甚至出现城市缺失。排查了大半天发现问题出在块边界索引上。块编码在实现里通常以块起点数组加块内偏移来表示但块起点可能指向原路径的中间位置翻转方向时块内偏移必须跟着反向计算。这个bug特别隐蔽因为小规模实例上随机初始化通常能恰好掩盖索引错位一旦城市多了进位错误累积到一定程度才爆发。排查建议写一个解码后的校验函数断言解码结果一定是一个0到n-1的完整排列每次交叉变异后都调用一次。我知道这个断言会带来一点性能开销但在调试期它能帮你省下十倍的时间。6.2 局部搜索的过犹不及与早熟陷阱我一开始被论文里的局部搜索效果惊艳到于是贪心地加大了力度——把每个精英的2-opt预算从200步提到2000步。结果适得其反算法在tsp225上迅速收敛到一个次优解之后无论怎么跑都跳不出来。原因很清晰局部搜索太强把种群内几乎所有个体都拉到了同一个局部最优盆地公共边比例瞬间冲到95%以上多样性崩盘后面的搜索完全失去意义。解决办法是给局部搜索加上改进感知的终止条件如果连续若干步没有实际改进就提前停止而不是机械地跑满预算。我还做了个更保守的版本每隔20代关闭一次局部搜索让进化算子单独跑几代人为地把公共边比例降下来效果也很明显。本质上这是在探索与开采之间做周期性的呼吸比固定配比更稳健。6.3 几个让整个算法更稳的小细节初始种群里贪心近邻个体占比我推荐20%到30%。太高会过早收敛太低则前期适应度曲线爬升慢。用KNN变体构造初始解也能显著提升下限质量。父本相似度阈值别设成固定值。种群多样性高的时候阈值可以放宽让远亲个体交配多样性低的时候阈值收紧强制引入差异。动态阈值比固定阈值在pcb442上能多出接近1个百分点的提升。精英重启不要全部随机生成至少保留一批从当前最优解附近做小扰动得到的个体。经验显示这样既补充了多样性又不会让适应度排名倒退太多。欧氏距离计算要预计算成矩阵放进内存。别在局部搜索的热点循环里反复调用sqrt那个开销会被放大成算法的主瓶颈。配合Numba或者Cython把2-opt热点编译一下lin318上的运行时间能从几十秒降到十几秒。我个人的最终体会是这种感知问题结构的进化算法设计思路比单纯堆更强的局部搜索或者调更大的种群有意义得多。公共边相似度这个度量给了算法一双眼睛让它知道什么时候该大步探索、什么时候该小步打磨。后续如果你也想改进其他组合优化算法值得先问一句你的搜索空间里有没有类似的天然不变量能用作进化策略的反馈信号如果有大概率也能设计出一套类似的闭环策略。

相关新闻

Oracle自定义加解密函数实战:从等保合规到脱敏展示

Oracle自定义加解密函数实战:从等保合规到脱敏展示

简介:面向 Oracle 数据库管理员与开发人员的一套自定义加密解密函数方案,基于 DES 加密标准对敏感字段进行加密存储与智能脱敏处理,既保护数据隐私,又不影响后续数据分析与挖掘的准确性,可覆盖账户信息、身份证、密码、…

2026/10/2 21:57:03 阅读更多 →
SQL GROUP BY原理与实战:避开HAVING和ONLY_FULL_GROUP_BY的坑

SQL GROUP BY原理与实战:避开HAVING和ONLY_FULL_GROUP_BY的坑

前两天帮同事排查一个线上报表问题,SQL长这样: SELECT province, COUNT(*) FROM orders GROUP BY province;听着像是最基础的分组统计,结果导出来的数据怎么都不对——省份对不上、订单总数差了好几万。查了半天,发现他把GROUP …

2026/10/2 21:57:03 阅读更多 →
OpenClaw实战:从环境部署到Skills技能,打造个人AI自动化工作流

OpenClaw实战:从环境部署到Skills技能,打造个人AI自动化工作流

1. OpenClaw 到底是什么,凭什么能替你干活早上八点五十一分,邮箱弹出一封只有一句话的邮件:“下班前把方案给我”。我打开终端,敲了一行命令:openclaw run "把上周六次会议纪要去重,提取待办&#xff…

2026/10/2 21:56:02 阅读更多 →

最新新闻

论文结构像一团乱麻?导师力荐这几个一键生成论文工具

论文结构像一团乱麻?导师力荐这几个一键生成论文工具

写论文总感觉结构混乱、逻辑不清,选题难、大纲难、初稿更难——这是很多学生的真实写照。其实,只要用对 AI 工具、走对写作流程,就能事半功倍。多位资深教授在教学中发现,合理利用智能工具能显著提升论文效率与质量,尤…

2026/10/2 22:32:48 阅读更多 →
AI工程化实战:从零搭建完整机器学习流水线的关键与坑

AI工程化实战:从零搭建完整机器学习流水线的关键与坑

1. 为什么值得从零搭建一套AI工程链路先说说我自己的经历。我做了几年后端开发,转岗到AI工程方向之后接到的第一个正经任务,不是训练某个模型,而是把别人在Notebook里跑通的算法变成一条能上线、能迭代的工程链路。当时我面前有两条路&#x…

2026/10/2 22:32:48 阅读更多 →
超越Python:六大接班语言横向对比与迁移实操路径

超越Python:六大接班语言横向对比与迁移实操路径

这两年我被问得最多的一个问题,不是“Python怎么学”,而是“Python学完/学腻了/遇到瓶颈了,下一步该学什么”。如果你用“编程语言推荐”“python入门”这些热词去搜,能看到一大堆排行榜和口水战,但真正能帮你做决定的…

2026/10/2 22:32:48 阅读更多 →
Ubuntu下AX200网卡固件下载失败?iwlwifi驱动安装与排查全指南

Ubuntu下AX200网卡固件下载失败?iwlwifi驱动安装与排查全指南

兄弟们,Ubuntu下AX200网卡固件iwlwifi-cc-46.3cfab8da.0.tgz下载不下来这事儿,我太熟悉了。尤其是刚装完系统,插上USB无线网卡或者笔记本自带AX200/AX210,发现Wi-Fi列表空空如也,然后翻教程去Intel官网手动下载固件&am…

2026/10/2 22:32:48 阅读更多 →
图书馆管理系统业务流程图、数据流程图与ER图绘制指南

图书馆管理系统业务流程图、数据流程图与ER图绘制指南

简介:一份图书馆管理系统开发设计文档,面向正在进行软件工程课程设计、数据库系统实践或毕业设计的计算机相关专业学生。文档以业务流程图、数据流图和ER图为核心,系统梳理了当前传统人工管理存在的检索慢、借还工作量大、统计难等问题&#…

2026/10/2 22:32:48 阅读更多 →
强化学习中的稀疏奖励难题:HER如何让智能体从失败中学习

强化学习中的稀疏奖励难题:HER如何让智能体从失败中学习

hindsight这个词,直译是“后见之明”,说人话就是事情做完之后回头看,才意识到当时其实应该怎么做。我第一次见到它出现在强化学习领域,是OpenAI那篇Hindsight Experience Replay论文里——这篇文章解决的问题非常朴实却很致命&…

2026/10/2 22:31:46 阅读更多 →

日新闻

从零搭建AI工程化:模型之外的完整闭环

从零搭建AI工程化:模型之外的完整闭环

先搞清楚一件事:从零开始做 AI 工程化,难的从来不是调模型、写提示词,而是把一套原型 Demo 变成长得像是“正经系统”的东西。你手里可能已经有了能跑通的代码,也可能刚读完一些概念,但真到了要把它变成可维护、可观测…

2026/10/2 0:00:20 阅读更多 →
大模型训练显存估计与混合精度训练实战指南

大模型训练显存估计与混合精度训练实战指南

1. 大模型训练显存估计与混合精度训练详解显存不够用,几乎是每个做大模型训练的人都会撞上的第一堵墙。你可能也经历过:模型代码写完了,数据管道跑通了,满心欢喜地按下训练启动脚本,结果几秒钟后终端弹出一行红字——C…

2026/10/2 0:00:20 阅读更多 →
小样本学习数据集选型指南:27个真正可用的高质量数据集

小样本学习数据集选型指南:27个真正可用的高质量数据集

1. 小样本学习的“弹药库”:为什么你总在找数据集,却总找不到真正能用的? 小样本、数据集——这两个词最近半年在我处理的200多个AI项目咨询里,出现频率排进前三。不是模型调不好,不是代码写不对,而是卡在…

2026/10/2 0:00:20 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/10/1 19:40:48 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/10/1 19:41:40 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/10/1 20:05:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/2 10:36:31 阅读更多 →
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/2 5:26:06 阅读更多 →
黑夜航拍船只数据集训练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/2 6:09:11 阅读更多 →