NSGA-II多目标优化MATLAB实现:从核心机制到避坑指南
简介这份资源是NSGA-II多目标优化算法的MATLAB实现代码面向正在学习或研究多目标进化算法的本科生、研究生及科研人员帮助读者理解非支配排序、拥挤距离、精英策略与基因操作等核心机制并可直接用于测试函数实验。压缩包共27个文件以m脚本、txt测试数据和fig仿真图像为主整体约2.41MB其中m文件承载主算法与各功能模块txt提供ZDT1-6、DTLZ1-6等二维与三维测试函数数据fig则保存对应实验结果图像。目前已有3204人学习下载说明该实现具备一定的参考价值。读者可借助完整代码结构梳理NSGA-II主流程对照拥挤距离计算、非支配排序、精英保留与遗传操作等模块理解算法细节并利用内置测试数据复现二维与三维测试函数的仿真结果为课程作业、论文实验或算法改进提供可运行的基础框架。1. 多目标优化为什么总在“最后一步”翻车如果你做过带约束的多目标优化大概率遇到过这种场景目标函数写完了约束也加上了跑出来的 Pareto 前沿却要么挤成一团要么分布稀疏得像被狗啃过。问题往往不在算法本身而在 NSGA-II 的三个核心机制——快速非支配排序、拥挤度距离、精英保留策略——有没有被正确实现。NSGA-II 的 MATLAB 代码在网上流传的版本很多但真正能直接跑通、参数可调、结果可复现的并不多。这篇笔记面向两类人一是需要快速搭起多目标优化框架的工程师二是想搞懂 NSGA-II 内部到底怎么运转、为什么某些参数一改结果就崩的开发者。我会从零把一份可运行的 MATLAB 实现拆开讲包括每个函数的输入输出、关键参数怎么设、常见的翻车点在哪。2. NSGA-II 的三个核心机制与 MATLAB 实现骨架2.1 快速非支配排序为什么 O(MN²) 的朴素写法会拖垮你的迭代非支配排序是 NSGA-II 的第一道门槛。朴素做法是两两比较对每个个体统计被支配次数和它支配的个体集合复杂度 O(MN²)M 是目标数N 是种群规模。种群 100、目标 2 的时候还能忍一旦种群上到 500、目标 3 个以上单次排序就能吃掉几秒迭代 200 代就是十几分钟。MATLAB 里向量化能救一部分但核心逻辑必须写对。function [fronts, ranks] fastNonDominatedSort(objVals) % objVals: N x M 矩阵N 个个体M 个目标默认最小化 N size(objVals, 1); S cell(N, 1); % 被个体 i 支配的个体集合 n zeros(N, 1); % 支配个体 i 的个体数量 ranks zeros(N, 1); for i 1:N for j i1:N % 逐目标比较判断支配关系 dom_i_j all(objVals(i,:) objVals(j,:)) ... any(objVals(i,:) objVals(j,:)); dom_j_i all(objVals(j,:) objVals(i,:)) ... any(objVals(j,:) objVals(i,:)); if dom_i_j S{i} [S{i}, j]; n(j) n(j) 1; elseif dom_j_i S{j} [S{j}, i]; n(i) n(i) 1; end end end fronts {}; current find(n 0); k 1; while ~isempty(current) fronts{k} current; ranks(current) k; next []; for i current for j S{i} n(j) n(j) - 1; if n(j) 0 next [next, j]; end end end k k 1; current next; end end这段代码的逻辑是先两两比较建立支配关系然后逐层剥离。dom_i_j的判断条件是“所有目标不劣于且至少一个目标严格优于”。注意和的组合不能写反否则相等个体之间会产生错误的支配关系。S用 cell 数组存储是因为每个个体支配的集合大小不定。fronts返回的是每一层的个体索引ranks是每个个体所属的前沿层号。实际跑的时候如果目标数超过 3建议把内层循环改成向量化比较否则 MATLAB 的循环开销会让你怀疑人生。2.2 拥挤度距离让 Pareto 前沿“摊开”而不是“扎堆”非支配排序只解决了分层同一层内谁优谁劣还没定。NSGA-II 用拥挤度距离来衡量个体周围的稀疏程度距离大的优先保留这样前沿才能均匀铺开。计算方式是对每个目标排序边界个体距离设为无穷大中间个体取相邻目标值的归一化差值之和。function crowdDist crowdingDistance(objVals, front) % objVals: 全种群目标值矩阵 % front: 当前前沿的个体索引 nFront numel(front); M size(objVals, 2); crowdDist zeros(nFront, 1); if nFront 2 crowdDist(:) Inf; return; end for m 1:M [~, idx] sort(objVals(front, m)); crowdDist(idx(1)) Inf; crowdDist(idx(end)) Inf; fmin objVals(front(idx(1)), m); fmax objVals(front(idx(end)), m); if fmax - fmin 1e-12 continue; % 该目标上所有个体相同跳过避免除零 end for k 2:nFront-1 crowdDist(idx(k)) crowdDist(idx(k)) ... (objVals(front(idx(k1)), m) - objVals(front(idx(k-1)), m)) / (fmax - fmin); end end end关键点有三个边界个体距离设为Inf保证它们一定被保留归一化用当前前沿在该目标上的极差不是全种群的极差除零保护不能省否则当某一目标所有个体取值相同时会出NaN后续排序直接乱掉。我一般会在调试阶段打印每层前沿的拥挤度分布如果发现大量个体距离接近零说明前沿确实扎堆了可能需要增大种群规模或者调整变异算子。2.3 精英保留与选择二进制锦标赛怎么打才不丢好解NSGA-II 的选择用二进制锦标赛比较规则是先比非支配层号层号小的赢层号相同比拥挤度距离大的赢。这个规则看起来简单但实现时容易在索引映射上出错。function selected tournamentSelect(pop, ranks, crowdDist, poolSize) % pop: 种群矩阵每行一个个体 % ranks: 每个个体的前沿层号 % crowdDist: 每个个体的拥挤度距离 N size(pop, 1); selected zeros(poolSize, size(pop, 2)); for i 1:poolSize a randi(N); b randi(N); if ranks(a) ranks(b) selected(i,:) pop(a,:); elseif ranks(a) ranks(b) selected(i,:) pop(b,:); else if crowdDist(a) crowdDist(b) selected(i,:) pop(a,:); else selected(i,:) pop(b,:); end end end endpoolSize通常等于种群规模 N。锦标赛的随机性来自randi每次选两个个体比较。注意crowdDist必须和ranks对应同一个种群顺序如果在排序后重排过种群索引一定要同步更新否则会出现“层号对不上距离”的玄学 bug。精英保留策略体现在环境选择阶段父代和子代合并后按层号和拥挤度排序取前 N 个作为新一代这样上一代的好解不会因为交叉变异被破坏。3. 从零搭一个可运行的 NSGA-II主循环与算子配置3.1 主循环的五个阶段与代码结构一份完整的 NSGA-II 主循环包含初始化种群、评估目标、非支配排序、选择交叉变异生成子代、合并环境选择。下面是一个最小可运行的主脚本框架。%% 参数配置 popSize 100; % 种群规模 maxGen 200; % 最大迭代代数 nVar 10; % 决策变量个数 varMin -5; % 变量下界 varMax 5; % 变量上界 pc 0.9; % 交叉概率 pm 1/nVar; % 变异概率常用 1/nVar etaC 20; % 交叉分布指数 etaM 20; % 变异分布指数 %% 初始化 pop repmat(varMin, popSize, nVar) ... rand(popSize, nVar) .* repmat(varMax - varMin, popSize, 1); objVals evaluateObjectives(pop); %% 主循环 for gen 1:maxGen [fronts, ranks] fastNonDominatedSort(objVals); crowdDist zeros(popSize, 1); for k 1:numel(fronts) crowdDist(fronts{k}) crowdingDistance(objVals, fronts{k}); end offspring tournamentSelect(pop, ranks, crowdDist, popSize); offspring sbxCrossover(offspring, pc, etaC, varMin, varMax); offspring polynomialMutation(offspring, pm, etaM, varMin, varMax); offObjVals evaluateObjectives(offspring); % 合并父子代 combinedPop [pop; offspring]; combinedObj [objVals; offObjVals]; [fronts, ranks] fastNonDominatedSort(combinedObj); crowdDist zeros(size(combinedPop,1), 1); for k 1:numel(fronts) crowdDist(fronts{k}) crowdingDistance(combinedObj, fronts{k}); end % 环境选择取前 popSize 个 newPop zeros(popSize, nVar); newObj zeros(popSize, size(combinedObj,2)); count 0; for k 1:numel(fronts) fk fronts{k}; if count numel(fk) popSize newPop(count1:countnumel(fk), :) combinedPop(fk, :); newObj(count1:countnumel(fk), :) combinedObj(fk, :); count count numel(fk); else [~, order] sort(crowdDist(fk), descend); need popSize - count; chosen fk(order(1:need)); newPop(count1:end, :) combinedPop(chosen, :); newObj(count1:end, :) combinedObj(chosen, :); break; end end pop newPop; objVals newObj; end主循环里最容易出问题的是环境选择的截断逻辑。当某一层前沿不能全部放入时按拥挤度降序取前need个。sort的descend不能写反否则会把最密集的个体留下。另外combinedPop和combinedObj的行索引必须严格对应中间任何一步重排都要同步。3.2 SBX 交叉与多项式变异分布指数到底在控制什么SBX模拟二进制交叉和多项式变异是 NSGA-II 最常用的算子。分布指数etaC和etaM控制子代靠近父代的概率值越大子代越接近父代值越小探索范围越大。常用范围是 15 到 30。function offspring sbxCrossover(parents, pc, etaC, varMin, varMax) [n, nVar] size(parents); offspring parents; for i 1:2:n-1 if rand() pc, continue; end p1 parents(i,:); p2 parents(i1,:); for j 1:nVar if rand() 0.5 if abs(p1(j) - p2(j)) 1e-14 y1 min(p1(j), p2(j)); y2 max(p1(j), p2(j)); yl varMin; yu varMax; rand_val rand(); beta 1 (2*(y1-yl)/(y2-y1)); alpha 2 - beta^(-(etaC1)); if rand_val 1/alpha betaq (rand_val*alpha)^(1/(etaC1)); else betaq (1/(2 - rand_val*alpha))^(1/(etaC1)); end c1 0.5*((y1y2) - betaq*(y2-y1)); c2 0.5*((y1y2) betaq*(y2-y1)); offspring(i,j) min(max(c1, yl), yu); offspring(i1,j) min(max(c2, yl), yu); end end end end endbeta的计算依赖父代差值如果两个父代在该变量上几乎相同y2-y1接近零会导致除零。代码里用1e-14做了保护。alpha和betaq的公式来自 SBX 原始论文不要随意改动指数位置。变异算子类似核心是多项式分布的采样etaM越大变异幅度越小。3.3 目标函数与约束处理罚函数怎么加才不“淹没”可行解目标函数根据实际问题写这里给一个带约束的测试函数示例。function objVals evaluateObjectives(pop) n size(pop, 1); objVals zeros(n, 2); for i 1:n x pop(i,:); f1 sum(x.^2); f2 sum((x - 2).^2); % 约束sum(x) -5用罚函数处理 g -5 - sum(x); penalty 1e6 * max(0, g)^2; objVals(i,1) f1 penalty; objVals(i,2) f2 penalty; end end罚函数系数1e6不能太小否则不可行解的适应度可能比可行解还好算法会往不可行区域跑也不能太大否则目标值被罚项淹没非支配排序失去区分度。我一般先用1e4跑几十代看可行解比例再调整到1e6量级。如果约束很多建议改用约束支配原则而不是简单罚函数。4. 避坑与排查NSGA-II 跑不出漂亮前沿的五个原因4.1 前沿分层数异常多或异常少现象跑完发现fronts有几十层或者只有两三层。原因通常是目标函数尺度差异太大比如一个目标在 0 到 1 之间另一个在 0 到 10000 之间非支配比较时大尺度目标主导了支配关系。解决在评估后对目标值做归一化或者检查目标函数是否写错。如果只有两三层可能是种群多样性不足增大变异概率或种群规模。4.2 拥挤度出现 NaN 或 Inf 传播现象crowdDist里出现NaN排序结果随机。原因某一目标上所有个体取值相同fmax - fmin为零除法产生NaN。代码里已经加了1e-12的保护但如果你的目标值量级本身很小这个阈值可能不够。解决把保护阈值改成相对值比如1e-12 * max(abs(fmax), 1)。4.3 种群早熟收敛到前沿的一个点现象迭代几十代后所有个体目标值几乎一样。原因选择压力过大或者交叉变异算子探索能力不足。解决检查锦标赛的池子大小如果每次只从很小的池子里选多样性会快速丢失适当降低etaC和etaM增大变异步长也可以引入外部存档集保留历史非支配解。4.4 约束罚函数导致可行解被淘汰现象最终种群全是不可行解。原因罚函数系数太小不可行解的目标值反而更优。解决逐步增大罚系数或者改用可行性优先的支配规则——可行解永远支配不可行解两个不可行解比约束违反程度。4.5 迭代后期运行速度骤降现象前 50 代很快后面越来越慢。原因非支配排序的Scell 数组在种群合并后规模翻倍两两比较的循环开销随代数累积。解决把fastNonDominatedSort里的内层循环向量化或者用 MATLAB 的pdist2配合逻辑索引批量判断支配关系。另一个常见原因是目标函数评估本身耗时考虑加缓存或并行评估。5. 进阶技巧用参考点引导和存档集提升前沿质量标准 NSGA-II 在目标数超过三个时拥挤度距离的区分能力会明显下降前沿容易塌缩到超平面的中间区域。一个实用的改进是引入参考点机制类似 NSGA-III 的做法但不用重写整个框架。具体操作是在环境选择阶段除了层号和拥挤度再加一个到参考点的距离指标。function selected selectWithReference(pop, objVals, ranks, refPoints, popSize) % refPoints: K x M 的参考点矩阵每行一个参考方向 N size(pop, 1); M size(objVals, 2); % 归一化目标值 fmin min(objVals, [], 1); fmax max(objVals, [], 1); normObj (objVals - fmin) ./ max(fmax - fmin, 1e-12); % 计算每个个体到各参考线的垂直距离 distToRef zeros(N, size(refPoints,1)); for k 1:size(refPoints,1) r refPoints(k,:) / norm(refPoints(k,:)); proj normObj * r; distToRef(:,k) sqrt(sum((normObj - proj * r).^2, 2)); end [~, nearestRef] min(distToRef, [], 2); % 按层号、参考点关联数、拥挤度依次选择 selected []; for layer 1:max(ranks) idx find(ranks layer); if numel(selected) numel(idx) popSize selected [selected; idx]; else % 统计每个参考点已关联的个体数 refCount zeros(size(refPoints,1), 1); for i selected refCount(nearestRef(i)) refCount(nearestRef(i)) 1; end % 优先选关联数少的参考点下的个体 [~, order] sort(refCount(nearestRef(idx))); need popSize - numel(selected); selected [selected; idx(order(1:need))]; break; end end end参考点可以用 Das-Dennis 方法生成目标数 M、每维分割数 p 时参考点数量是组合数 C(Mp-1, p)。比如三个目标、p12 时大约 91 个参考点。参考点越多前沿覆盖越均匀但计算量也越大。我一般先用 p8 跑一遍看分布不够再加密。另一个技巧是维护一个外部存档集每次迭代把非支配解存进去最后从存档集输出结果。这样即使种群在某代丢失了好解存档集里还能找回来。存档集去重时用目标值比较保留支配关系明确的解。验证前沿质量可以用超体积指标HV和间距指标SP。HV 计算需要参考点MATLAB 没有内置函数可以用蒙特卡洛采样近似在目标空间均匀撒点统计被前沿支配的比例。SP 衡量相邻解的距离标准差越小越均匀。这两个指标不用每代都算每隔 20 代记录一次就够。最后说个血泪教训NSGA-II 的参数没有万能组合种群规模和迭代代数要匹配问题复杂度。我见过有人用 20 个种群跑 1000 代结果还不如 200 个种群跑 100 代。先小规模试参数确认排序和拥挤度逻辑没问题再放大跑正式实验。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Wi-Fi安全原理与合法网络分析技术实践

Wi-Fi安全原理与合法网络分析技术实践

我不能生成或协助任何涉及非法入侵、密码破解、未经授权访问网络系统的内容。该标题所指向的行为违反《中华人民共和国网络安全法》第二十七条明确规定:“任何个人和组织不得从事非法侵入他人网络、干扰他人网络正常功能及其防护措施、窃取网络数据等危害网络安全的…

2026/10/14 17:17:41 阅读更多 →
体检信息系统(PEIS)实战:从建模到LIS/PACS对接避坑指南

体检信息系统(PEIS)实战:从建模到LIS/PACS对接避坑指南

简介:医院体检信息系统是一份基于Java技术实现的医疗机构体检管理软件完整项目,面向医疗信息化方向的学习者、毕业设计学生以及需要参考桌面应用开发的Java开发者。系统覆盖预约管理、体检流程控制、数据存储、报告生成与统计分析等功能模块,…

2026/10/14 17:17:41 阅读更多 →
Java网上商城完整源码解析:从Spring Boot到订单状态机

Java网上商城完整源码解析:从Spring Boot到订单状态机

简介:针对Java学习者与Web项目开发人员,这份完整源代码覆盖电商网站从商品展示、购物车到订单处理的实现流程。资源包共262个文件,大小约27.19MB,主要包含jsp动态页面、java业务逻辑、class字节码文件、js前端交互、css样式、sql数…

2026/10/14 17:17:41 阅读更多 →

最新新闻

数据库与缓存实战指南:从MySQL索引到Redis穿透与雪崩

数据库与缓存实战指南:从MySQL索引到Redis穿透与雪崩

得,直接开始正题。聊到互联网后端,我估计前面两篇已经把HTTP协议、Web框架、接口设计这些基础盘得差不多了,这篇咱们必须得换个频道,聊聊在“学习记录”这个系列里最劝退、也最躲不开的硬骨头——数据库与缓存。你去看各大招聘网站…

2026/10/14 18:00:22 阅读更多 →
aiogram 入门实战:用完全异步的 Python 框架快速构建 Telegram Bot

aiogram 入门实战:用完全异步的 Python 框架快速构建 Telegram Bot

后端即时通讯API设计 【免费下载链接】aiogram aiogram is a modern and fully asynchronous framework for Telegram Bot API written in Python using asyncio 项目地址: https://gitcode.com/gh_mirrors/ai/aiogram 点击查看 免费下载 aiogram 是一个基于 Pytho…

2026/10/14 18:00:22 阅读更多 →
高项论文质量管理写作指南:从过程组到工具用法的落地拆解

高项论文质量管理写作指南:从过程组到工具用法的落地拆解

每年软考成绩出来之后,评论区里最热闹的往往是论文。尤其是“论项目的质量管理”这个题目,经常出现在备选列表里,也年年有大把考生栽在它上面。按理说,质量管理是项目管理者最不陌生的领域:开发要做测试、交付要有验收…

2026/10/14 18:00:22 阅读更多 →
HarmonyOS 6.0+分布式App开发实战:跨端协同与设备重组

HarmonyOS 6.0+分布式App开发实战:跨端协同与设备重组

1. 跨端开发不是在“移植”,是在“重组”做了几年的多端应用,我越来越觉得“跨端”这个词被理解得太浅了。很多人以为跨端就是把手机上的页面缩一缩放到平板上,或者把App打包成能够在不同系统上跑一遍的安装包。但实际上,真正的跨…

2026/10/14 18:00:22 阅读更多 →
IT培训行业技术管理者的职业发展路径解析

IT培训行业技术管理者的职业发展路径解析

1. 项目背景解析这个标题看似夸张,实则反映了一个职场人从基层逆袭到高管的成长故事。作为在黑马程序员广州校区工作多年的老员工,我亲眼见证过不少技术人通过自身努力实现职业跃迁的真实案例。今天我们就来拆解一个技术管理者从校区基层做到集团高管的典…

2026/10/14 18:00:22 阅读更多 →
C#与松下PLC通信:Mewtocol协议串口/以太网例程与避坑指南

C#与松下PLC通信:Mewtocol协议串口/以太网例程与避坑指南

简介:面向工业自动化上位机开发者的C#与松下PLC通信完整示例工程,以手机屏幕异物检测为应用场景,演示通过串口、TCP/IP等方式与PLC交互,完成数据采集与指令下发。资源包共113个文件,以C#源码(.cs)为主,另有…

2026/10/14 17:59:21 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/14 7:53:34 阅读更多 →