简介:本资源为鸽群优化算法(PIO)的MATLAB实现与教学配套包,面向智能优化、计算智能方向的本科生、研究生及工程技术人员,用于解决非线性、多模态函数优化问题,如超参数调优、结构参数寻优等实际工程场景。压缩包共8个文件(5个核心m脚本、2个说明文本、1张运行结果图),总大小39KB,结构精炼:main.m为主控入口,PIO.m封装核心迭代逻辑,initialization.m与func_plot.m分别负责种群初始化与可视化,Get_Functions_details.m集成多种经典测试函数,配合txt文档提供清晰使用指引。已有902人学习下载,资源代码规范、注释完整,支持快速复现算法流程、调试参数影响,并可作为改进基础(如融合混沌策略或边界处理机制)。运行结果图直观展示收敛曲线与最优解分布,便于理解PIO的全局探索与局部开发平衡特性。 前几天有个做路径规划的朋友跑来问我,说他在找一种比粒子群更“冷门”但效果不差的优化算法做论文对比实验,问我有没有推荐。我直接把这个鸽群优化算法的Matlab实现丢给他了。他跑完benchmark之后专门跑回来跟我感慨:这东西收敛速度是真的快,尤其在前中期,比经典PSO猛多了。今天就把这个项目的源码、原理和调试经验完整拆一遍。
鸽群优化算法(Pigeon-Inspired Optimization,简称PIO)是2014年前后提出的一类新型群体智能算法,灵感来自鸽子归巢时的导航机制。它最大的特点是分两个阶段建模:前期靠磁场和太阳方向做“地图罗盘导航”,后期切换到“地标导航”模式,利用熟悉的地标逐步逼近终点。这种两阶段切换的思路在优化算法里非常少见,也是它区别于PSO、GA等传统算法的核心差异点。这套Matlab源码就是完整的PIO算法实现,包含主程序、目标函数、参数配置和结果可视化,适合算法研究者、毕业论文党、以及做工程优化但想用新鲜算法的朋友直接拿去改造。
1. 项目概述与整体设计思路
1.1 这套源码解决了什么问题
先说结论:这套源码解决的是“在没有商业工具箱的情况下,快速跑通一个PIO完整流程”的问题。Matlab自带的Global Optimization Toolbox里有GA、PSO、模拟退火,但没有PIO。如果你在论文里想用PIO做实验对比,要么自己从零写,要么去GitHub扒一份不一定能跑的代码。这套源码的价值就在于:打开即跑、结构清晰、注释完整,把PIO的两阶段算子全部实现出来了,而且目标函数可以随意替换。
源码本身遵循的是标准的PIO训练流程:先初始化一群随机鸽子,然后经过地图罗盘算子迭代Nc1次,再切换到地标算子迭代Nc2次,最后输出全局最优解。整个流程在benchmark函数上的表现,比我自己之前用Python写的一版要稳定不少,可能是因为Matlab在矩阵运算上的效率优势,尤其在种群规模到500以上的时候很明显。
1.2 为什么是PIO而不是更主流的PSO或GA
很多第一次接触PIO的人都会问一句话:PSO已经很成熟了,为什么还要用PIO?我当时的想法和你一样,直到我做了几组对比实验才理解区别。
PSO的核心机制是“个体向自身历史最优和全局最优学习”,它没有阶段切换的概念,整个搜索过程用的是一套更新规则。而PIO的两阶段设计本质上是在做“广搜转精搜”:第一阶段的地图罗盘算子带着较大的随机扰动做全局探索,第二阶段的地标算子通过淘汰弱势鸽子、向种群中心聚集来加速收敛。换句话说,PIO把“探索”和“开发”两个阶段在时间上做了显式分离,而不是像PSO那样靠惯性权重去隐式平衡。
这个区别带来的直接好处是:在处理某些多峰函数时,PIO不容易在早期就陷入某个局部最优的引力圈,因为它前期的随机性更大,而且种群会不断更新位置更新速度;到了后期,地标算子的淘汰机制又能快速收缩搜索范围,收敛精度往往比同等参数下的PSO更高。当然,它也有自己的短板,比如高维问题上后期多样性不足,这个后面我会专门讲。
1.3 源码整体架构与文件说明
拿到这套源码后,第一件事是把文件结构和调用关系理清楚。整套代码的模块划分非常清晰:
| 文件/模块 | 功能说明 | 关键变量 |
|---|---|---|
| 主程序脚本 | 参数初始化、调用PIO主循环、记录收敛曲线 | Nc1、Nc2、Np、D、R |
| 目标函数文件 | 定义待优化的函数,可替换为任意benchmark或工程问题 | Fobj |
| 位置更新核心 | 实现地图罗盘算子和地标算子的位置速度更新 | Xpbest、Xgbest、Lcenter |
| 绘图模块 | 绘制收敛曲线和最终结果 | Convergence_curve |
这里要特别提醒:这套代码对变量命名比较讲究,Np是种群大小(Number of Pigeons),D是问题维度(Dimension),Nc1和Nc2分别是两个阶段的迭代次数,R是地图罗盘算子中的衰减系数。拿到代码后先把这些变量对应清楚,否则改参数的时候非常容易改错地方。
2. 核心原理解析:鸽子是怎么“找到家”的
2.1 归巢行为背后的生物学机制
要理解PIO,必须先理解鸽子为什么能千里归巢。科学家很早就发现,鸽子在距离目的地较远时,主要依赖地球磁场和太阳位置来建立方位感,这个过程叫“地图罗盘”机制。简单讲,鸽子体内有一种叫隐花色素(Cryptochrome)的蛋白质,能感知地磁场的强度和方向,从而在大脑中形成一张“磁场地图”。同时,太阳的方位和高度角可以帮助鸽子校准方向。
但当鸽子飞到离家比较近的位置时,导航策略会发生变化。这时候鸽子不再依赖磁场,而是切换成“地标导航”模式:依靠对地形、建筑物等熟悉地标的视觉识别来精确定位。这就像你开车去一个陌生的城市,先靠导航走高速,进入市区后就要靠熟悉的商店、路口来找路。
Duan Haibin和Qiao Peixin在2014年提出PIO时,就是把这个归巢过程抽象成了两个数学模型:一个是地图罗盘算子(Map and Compass Operator),一个是地标算子(Landmark Operator)。前者负责全局大范围搜索,后者负责后期精确收敛。这种生物学机制到数学模型的转化,是整个算法的灵魂。
2.2 地图罗盘算子:第一阶段的全域搜索
地图罗盘算子的数学形式如下:
% PIO 第一阶段:地图罗盘算子(更新速度和位置) for t = 1:Nc1 for i = 1:Np % 核心更新公式:速度衰减 + 向全局最优学习 V(i, :) = V(i, :) * exp(-R * t) + rand * (Xgbest - X(i, :)); X(i, :) = X(i, :) + V(i, :); end end这短短几行代码里包含了一个非常关键的机制:速度的指数衰减项exp(-R * t)。R是衰减系数,t是当前迭代次数,这个衰减项会让鸽子在第一阶段前期的移动步长很大,随着迭代次数增加,步长逐渐缩小。换句话说,算法前期的探索范围大、随机性强,越往后越趋向于在当前好的位置附近细化搜索。
同时,rand * (Xgbest - X(i, :))这一项的作用是让每只鸽子都被全局最优位置吸引。这里的rand是0到1之间的随机数,它保证每只鸽子的更新带有随机扰动,避免所有个体完全一致地飞向同一个位置,从而保留一定的种群多样性。
比较关键的一点是:地图罗盘算子只是更新速度和位置,并没有做任何淘汰操作。所有鸽子都会存活到第二阶段,这保证了全局搜索阶段的信息不会丢失。
2.3 地标算子:第二阶段的精英收敛
当地图罗盘阶段的迭代次数达到Nc1后,算法切换到地标算子。这个算子的设计灵感来自鸽子归巢后半段对熟悉地标的利用。数学实现上做了这样的简化:每次迭代时,按照适应度值对鸽子排序,淘汰掉适应度较差的一半。剩余的鸽子计算出一个“中心位置”,所有鸽子再向这个中心位置靠拢。
% PIO 第二阶段:地标算子(淘汰机制 + 向中心聚集) for t = 1:Nc2 % 根据适应度排序,保留适应度较好的一半 [~, order] = sort(Fitness); X = X(order, :); % 种群数量减半(每次迭代淘汰一半) Np = ceil(Np / 2); % 计算剩余鸽子的中心位置 Lcenter = sum(X(1:Np, :)) / Np; % 所有鸽子向中心位置移动 for i = 1:Np X(i, :) = X(i, :) + rand * (Lcenter - X(i, :)); end end这个算子的妙处在于:通过淘汰劣解,种群快速向优秀区域收缩;通过计算中心位置,种群以群体智慧而非单个个体为向导。这种机制让PIO在第二阶段拥有极强的局部开发能力,收敛速度非常快。
但这里也埋了一个隐患:如果第一阶段没有找到真正靠近全局最优的区域,第二阶段的强制收缩会把种群拉向局部中心,导致早熟收敛。这就是为什么Nc1和Nc2的比例设置至关重要。
2.4 两阶段切换的完整流程
把两个算子串起来,整套代码的执行流程如下:
- 初始化种群:随机生成Np只鸽子的位置和速度,维度为D。
- 计算初始适应度,找到全局最优位置Xgbest。
- 执行地图罗盘算子Nc1次:更新速度和位置,每次迭代都重新计算适应度并更新Xgbest。
- 切换到地标算子,执行Nc2次:每次迭代淘汰一半鸽子,计算中心位置,更新剩余鸽子位置。
- 输出最终Xgbest作为最优解,绘制收敛曲线。
我在实际跑代码时经常拿这个流程和粒子群对比,一个很直观的感受是:PIO的收敛曲线在阶段切换的瞬间会出现一个明显的“台阶”拐点。第一阶段结束时的收敛值可能还比较高,但进入地标算子后,一两轮迭代之内就会急剧下降。这个现象第一次看到会觉得挺神奇,其实就是淘汰机制带来的集中搜索效应。
3. Matlab源码实现的工程化细节
3.1 参数配置与初始化策略
这套源码的初始化部分写得比较规整,拿到手后只需要改开头几行参数就能跑通。我的建议是照着下面这套配置开始:
%% 参数配置 Np = 30; % 种群规模 D = 30; % 维度(根据目标函数调整) Nc1 = 30; % 地图罗盘迭代次数 Nc2 = 20; % 地标迭代次数 R = 0.7; % 地图罗盘衰减系数 Xmin = -100; % 搜索空间下界 Xmax = 100; % 搜索空间上界 %% 初始化种群 X = Xmin + rand(Np, D) * (Xmax - Xmin); % 均匀随机初始化位置 V = zeros(Np, D); % 初始速度一般设为0这里有一个容易踩的坑:初始化时一定要确认搜索空间的上下界和目标函数的要求匹配。比如Rastrigin函数常用范围是[-5.12, 5.12],如果你用[-100, 100]去初始化,前期的搜索范围太大,浪费大量迭代次数在无效区域。虽然算法最后还是能收敛,但会显著影响收敛精度和稳定性。
初始速度设为0是一个稳妥的选择。有些改进版的PIO会用随机速度初始化,但我实测下来,初始速度对最终结果的影响不大,反而可能出现第一轮迭代就飞出边界的情况。所以除非你有明确的改进需求,否则零初速加上边界裁剪就够用了。
3.2 目标函数的替换与接口设计
源码中目标函数的封装方式非常友好,它是一个独立的函数文件,你只需要修改函数内部的计算逻辑,不需要动主循环的代码。标准的接口长这样:
function fitness = fitnessFunc(x) % 示例:Sphere函数 fitness = sum(x .^ 2, 2); end注意这里我特意用了sum(x .^ 2, 2)而不是sum(x .^ 2),原因在于Matlab的sum函数默认按列求和,如果你传入的是整个种群矩阵而不是单个个体,按列求和会得到1行D列的结果,维度对不上,后续计算适应度排序时就会报错。
我个人建议把目标函数设计成支持向量化输入的形式:输入是整个种群的位置矩阵,输出是每个个体对应的适应度列向量。这样做的好处是主循环里一次调用就能算完全部个体的适应度,不需要再套一层for循环,整体运行速度提升很明显。尤其是处理大规模种群或者高维问题时,这种向量化的写法和循环写法在性能上能差出好几倍。
3.3 边界处理与速度限制
边界处理是群体智能算法落地时躲不开的一个话题。PIO在更新位置后,可能会出现部分鸽子飞出搜索空间的情况。如果不做任何处理,这些越界个体的适应度会非常差,而且会污染全局最优的更新逻辑。
源码中的处理方式是位置裁剪加适应度惩罚。位置裁剪很简单,就是X = max(Xmin, min(Xmax, X)),把越界的坐标强行拉回边界。适应度惩罚则是对越界的个体给予一个非常大的惩罚值,让它们不至于被当作最优解保留下来。这两种方式本质上是两个思路:裁剪改变的是搜索轨迹,惩罚保留的是个体位置但降低其竞争力。
我在实际使用中更多采用一种混合策略:对速度做限制(防止飞出太远),对位置做裁剪(保证所有个体始终在合法范围内)。相比之下,纯惩罚机制的问题在于,惩罚值设置不合理时会影响种群整体的适应度分布,导致排序逻辑失真。而裁剪不会引入额外的超参数,更稳健。
3.4 收敛曲线的绘制与数据记录
源码附带的绘图模块会记录每次迭代的全局最优适应度值,最终绘制收敛曲线。这部分我喜欢额外做一点扩展:除了保存Convergence_curve,还把每一代的平均适应度也记录下来。原因很简单:全局最优只能反映算法最终能找到多好的点,而平均适应度能反映整个种群的收敛状态和多样性水平。
举个例子,如果你看到全局最优曲线在下降,但平均适应度曲线迟迟不下落,说明种群多样性还很高,算法可能还有探索潜力;如果两条曲线都快速贴到同一个值,说明种群已经高度集中,再迭代也基本不会有太大改善了。这套分析方法在对比不同参数组合的表现时特别有用。
4. 关键参数调优与避坑指南
4.1 参数敏感度分析:哪些参数最关键
很多同学拿到PIO代码后,第一件事就是调Np和迭代次数。但我跑了大量实验之后发现,PIO中最敏感的参数其实是Nc1和Nc2的比例,以及衰减系数R。这两个参数直接决定了两个阶段的平衡。
先讲R。R控制的是速度指数的衰减速度,取值范围一般在0到1之间。R越小,速度衰减越慢,前期的探索步长保持得越久,全局搜索越充分。R太大则相反,速度很快归零,鸽群过早进入“爬行”状态,容易早熟。实测下来,R取值在0.5到0.9之间比较合理。如果你做的是高维问题,建议往小了调,比如0.5左右;如果是低维问题,0.8的效果往往更好。
再讲Nc1:Nc2的比例。我常用的策略是Nc1占总迭代次数的60%到70%,Nc2占30%到40%。这个比例背后的逻辑是:全局探索需要足够长的周期才能扫到全局最优附近的区域,而局部收敛本身是很快的,不需要太多迭代就能集中到局部最优点上。如果你把大部分迭代都给了第二阶段,就容易出现“还没找到好区域就急着收网”的尴尬局面。
4.2 种群规模怎么定:不是越大越好
种群规模Np是个让人纠结的参数。直观上觉得越大越好,但实际上PIO在地标阶段会每次淘汰一半个体,初始种群越大,淘汰后保留的个体数仍然很多,中心位置的计算会更稳定,但计算代价也更高。
我给一个经验范围:处理常见的benchmark函数,Np取20到50就足够了。比如二维或十维问题,30只鸽子已经能稳定收敛;30维的CEC测试函数,40到50只也完全够用。当你想追求极致的稳定性(比如跑30次实验不出现一次异常差的收敛结果),可以把Np拉到100,但此时运行时间会成倍增加,收益并不明显。
另外提一个容易被忽略的点:种群规模最好设置成2的幂次方的倍数。因为地标算子每轮淘汰一半,Np如果不是2的若干次方,经过连续几轮减半后会出现ceil(Np/2)这种非整数处理,虽然代码里用了ceil保证不出错,但淘汰节奏会不均匀。我自己习惯设置Np = 32或Np = 64,这样经过两轮淘汰后分别是16、8,节奏非常干净。
4.3 阶段切换的判别点要不要优化
标准PIO是固定按迭代次数切换阶段的,这相当于一个开环控制。但你在实际使用中可能会发现一个情况:第一阶段还没收敛得好,就强行切换到了地标阶段,导致后续没有足够多样性做精细搜索。
我自己的做法是在标准PIO基础上加一个简单的动态切换策略:当连续多轮全局最优的变化量小于某个阈值时,提前从地图罗盘算子切到地标算子。这意味着如果第一阶段已经收敛到瓶颈期,没必要硬等剩余的迭代次数浪费算力。实测下来这种提前切换策略在Rastrigin和Ackley这类多峰函数上效果不错,能节省约20%的迭代次数且不损失精度。
如果你不想改代码结构,也可以用更朴素的方案:直接人为调大Nc1,给第一阶段更多预算。这个方法粗暴但有效,特别是在不确定最优区域位置的时候,多花点迭代在全局搜索上不会有坏处。
4.4 多次运行取统计结果的必要性
群体智能算法本质上属于随机优化方法,单次运行的结果说明不了任何问题。就算你把参数调得再好,单次运行可能因为随机种子的关系陷入局部最优。我在写论文做对比实验时,从来不会拿单次运行的结果去下结论。
标准做法是每种参数组合独立运行至少30次,记录每次的最优值、平均值、标准差和最差值。平均值反映算法的整体寻优能力,标准差反映算法的稳定性,最差值和最优值的差距反映了算法的鲁棒性。这套统计体系是论文里算法对比实验的基本功,也是你判断参数好坏的科学依据。
真实验证:我跑过一组对比,固定R=0.7,Np=32,分别在Nc1:Nc2为20:5、15:10、10:15的情况下跑同一组Rastrigin函数,前者的平均值比最后一组好了差不多一个数量级。这就是阶段比例重要性的直观证据。
5. 常见问题与调试经验
5.1 收敛曲线呈直线,算法“没反应”
拿到代码第一次运行时,最常见的情况不是报错,而是收敛曲线一开始就平了,甚至完全不下降。这种情况大概率出在适应度计算这个环节。
我之前帮人排查过一个案例:他把目标函数写成了fitness = sum(x.^2),结果因为x是矩阵而不是列向量,sum默认按列求和,返回的适应度变成一个1×D的行向量,导致后续排序、取最优全部出错,算法虽然在跑但一直在用错误的数据做引导。
排查思路是:在任何迭代开始前,先手动计算一次全部个体的适应度,打印出来检查维度。对Np×D的种群矩阵,适应度必须是Np×1的列向量。这一步检查做完,80%的“没反应”问题都能解决。
5.2 总是收敛到同一个局部最优解
如果算法每次运行到最后都落在差不多同一个结果上,而且这个结果明显不是全局最优,那基本可以判断是“探索能力不足”导致的早熟收敛。常见原因有两个:一是R值太大,速度衰减过快,前期搜索范围不够;二是Nc1太短,还没充分探索就切到了地标阶段。
解决办法按优先级排列:先把R降到0.5试试,再把Nc1:Nc2的比例调成7:3,最后才是增大Np。不要一上来就盲目增大种群规模,那是性价比最低的方案。
5.3 高维问题上表现急剧变差
我踩过比较深的一个坑是:把PIO直接在100维的测试函数上和PSO对比,结果被PSO按在地上摩擦。这不是PIO这个算法不行,而是我没做高维适配。
高维问题的核心难点是搜索空间呈指数级增长,PIO第一阶段有限的迭代次数根本没法覆盖足够大的范围。这时候需要做的调整是:显著增大R对速度的保持时间,把R调小到0.3左右;同时适当增加Nc1,保证前期的全局搜索足够充分。我实测在50维的Griewank函数上,调优后的PIO能比默认参数的PIO提升约40%的精度,所以参数适配的影响真的非常大。
5.4 对比实验时结果的方差太大
如果每次运行结果忽好忽坏,标准差大得离谱,问题可能出在边界裁剪策略上。当搜索空间很大而初始种群规模不够大时,初始个体可能分布在离全局最优很远的位置,导致每次运行的结果严重依赖随机初始种子的位置。
解决办法有两种思路:一是用“混沌映射”或“Sobol序列”替代纯随机初始化,让初始种群在搜索空间里分布更加均匀;二是增加运行次数,用统计结果而非单次结果去评价算法。我倾向两种都做,前者提升算法本身的质量,后者保证论文实验的可信度。
5.5 Matlab版本兼容性问题
这套源码使用的是Matlab基础语法,不依赖任何工具箱,所以在R2016b到R2024a这些常见版本上都能直接运行。唯一需要注意的是,如果你的Matlab版本较老(R2014a之前),rand和sort函数的调用接口基本没变,问题不大。
但如果你的代码里用到了randperm或sortrows这类函数,不同版本的排序稳定性有细微差异,在实际优化结果上可能产生微小波动。遇到这种情况,手动固定随机种子(rng(0))就能保证结果可复现。
6. 应用场景与改进扩展方向
6.1 现成的应用方向梳理
从论文发表和工程实践来看,PIO目前主要应用在这些方向:
| 应用领域 | 具体问题 | 为什么适合PIO |
|---|---|---|
| 无人机路径规划 | 三维空间航线寻优、避障路径生成 | 两阶段结构适合“全局粗规划+局部精修” |
| 工程结构优化 | 桁架重量最小化、形状优化 | 前期探索能力强,能处理复杂非线性约束 |
| 图像处理 | 图像分割阈值寻优、特征选择 | 可在高维特征空间有效筛选 |
| 能源系统 | 微电网调度、风电功率预测参数优化 | 收敛速度快,适合在线优化场景 |
| 机器学习 | SVM参数寻优、神经网络权重初始化 | 需要一个高效的全局搜索过程 |
在这些场景里,PIO最讨巧的地方是它天然适配“先粗后细”的问题结构,尤其是路径规划这种需求:先在大地图上找到一条大致可通的路线,再在细节上优化平滑度。这种两阶段需求跟PIO的算子设计几乎是一一对应的。
6.2 改进方向一:把递减策略引进来
原始PIO的地标阶段是用固定的淘汰比例和固定的随机扰动,不用太多花活也能有不错的收敛效果。但如果你想复现一些高水平论文里的改进效果,可以考虑加入线性递减的扰动幅度:迭代前期扰动大,后期扰动小。这样能在不增加额外复杂度的前提下,让第二阶段的局部搜索更加精细。
这在代码里只是一个很小的改动:把rand * (Lcenter - X(i,:))中的rand替换成rand * (1 - t / Nc2)之类的递减因子。但收敛精度上的提升是可以感知的。如果你在写论文,建议把这个改进写进对比实验,能讲出一个完整的改进故事。
6.3 改进方向二:混合策略与双层框架
更高阶的玩法是把PIO和其他算法做混合。常见的混合方式有三种:
- PIO + 局部搜索:第二阶段结束后,对最终解做一次模式搜索或Nelder-Mead局部精修。适用于对收敛精度要求高的工程问题。
- PIO + PSO:用PIO跑前期的全局搜索,把最终种群作为PSO的初始种群,利用PSO的全局最优学习机制做后期精修。这种组合在CEC测试集上表现非常稳定。
- PIO + 差分进化:在第一阶段引入DE的变异算子增强种群多样性,缓解PIO早期容易早熟的弱点。
混合策略的关键思想是扬长避短:PIO的优势是阶段清晰、全局搜索能力强,短板是后期的精细开发能力一般。那就让擅长局部开发的算法来补位。这种组合思路才是算法应用的正确打开方式,而不是想着一个算法打遍天下无敌手。
7. 实操总结:如何把这份代码用起来
跑完一遍代码之后,我的体会是这套PIO实现的工程完成度确实够用,但离“开箱即用”还有一步之遥。所谓的一步,是指你需要真正理解两个算子的切换逻辑和参数联动关系,而不是只改个目标函数就交差。
具体来说,我建议你拿到代码后按下面这条路径走一遍:
第一步,用默认参数跑Sphere和Rastrigin两个函数,对照论文里的标准结果确认代码没有逻辑错误。第二步,分别改变R、Nc1、Np,各跑30次,画出收敛曲线对比图,亲手感受每个参数的影响方向。第三步,把你自己的工程问题抽象成目标函数,先做归一化处理,再调整搜索范围,最后用调好参数的PIO跑出结果。第四步,如果要做算法对比,至少跑30次,把平均值、标准差、最优值、最差值全部列进表格里。
最后再说一个调试小技巧:在代码里临时加一行disp([t, Xgbest_fitness])在循环中打印当前迭代次数和最优适应度,能让你非常直观地看到阶段切换点的行为变化。这个习惯曾经帮我节省了大量盲调参数的时间。如果你在跑PIO的过程中发现什么有意思的现象,或者踩了什么坑,也欢迎回来交流,算法这种东西,真的是跑多了才有感觉。
本文还有配套的精品资源,点击获取