简介:这份MATLAB代码合集聚焦无线传感器网络(WSN)覆盖优化问题,面向从事通信组网、物联网应用或智能算法研究的师生和工程师。合集集成10种主流智能优化算法,涵盖人工蜂群、人工鱼群、灰狼、粒子群、蝠鲼觅食、蛇优化、麻雀搜索、鲸鱼优化、虚拟力及樽海鞘算法,每种算法都配有独立可运行的覆盖仿真主程序,便于横向对比不同算法的覆盖率与收敛性能,也可作为算法改进的基线代码。资源共41个文件,其中39个为.m脚本文件,覆盖主程序、覆盖率计算、边界处理及适应度评估等模块,另附2篇caj格式参考文献辅助理解原理,整包大小约5.73MB。目前已有209人学习使用,注释详细且下载后可直接运行,适合快速搭建WSN覆盖优化实验、开展算法对比或改进研究。
1. WSN覆盖优化MATLAB代码合集:从覆盖空洞到自动迭代一条龙
在 100×100 的监测区域里随机撒 60 个传感器节点,初始覆盖率只有七成上下,角落和中心散着一片覆盖空洞——这是做无线传感器网络(WSN)覆盖问题的人几乎都会撞上的开场。手工挪节点调整位置太玄学,挪几十个节点能挪到怀疑人生。这份WSN覆盖优化MATLAB代码合集就是把这件事自动化:以虚拟力算法为核心,让节点像带电粒子一样互相排斥、被边界推回,迭代几十轮后自动铺开,覆盖率能从 60%~75% 提到 90% 上下,并配有随机部署、粒子群等对照入口。适合正在做 WSN 部署仿真、课程设计,或需要在部署前先用 MATLAB 验证覆盖效果的人。代码模块边界清晰,改参数、加算法、换区域模型都不算费劲。
2. 先看问题模型:覆盖率怎么算,虚拟力凭什么有效
2.1 三种感知模型与网格化覆盖率计算
WSN覆盖优化在数学上是一个“在有限区域放 N 个节点,让被感知覆盖的面积占比最大”的布局问题。要优化它,第一步得把“被覆盖”这件事量化。常见做法是布尔磁盘模型(Boolean Disk Model):每个节点的感知范围是一个以感知半径 Rs 为半径的圆,区域内任意一点只要落入任意一个圆内,就算被覆盖。更精细的仿真会改用概率感知模型,覆盖概率随距离衰减,节点附近接近 1,在 Rs 边界处降到 0.5 左右,边界外衰减到 0。两种模型各有用途,但作为优化目标函数时,绝大多数人还是会用布尔模型,因为它计算快、梯度特性直观。
这套代码合集里的覆盖率计算用的是网格化离散方式:把区域切成 step × step 的小方格,对每个方格中心点判断它是否落在任意节点圆的半径内。网格越细,覆盖率越接近真实几何值,但循环量也越大。step 取 1 时,100 × 100 的区域要判断一万个点,在 MATLAB 里也就是零点几秒,这个量级完全能接受。核心函数 calCoverage.m 写成这样:
function ratio = calCoverage(nodes, Rs, L, step) % nodes: n x 2 矩阵,每行是一个节点的 (x, y) % Rs: 感知半径;L: 方形区域边长;step: 网格步长 xs = 0:step:L; ys = 0:step:L; covered = 0; total = numel(xs) * numel(ys); for i = 1:numel(xs) for j = 1:numel(ys) p = [xs(i), ys(j)]; if any(vecnorm(p - nodes, 2, 2) <= Rs) covered = covered + 1; % 被任意一个节点覆盖就计数 end end end ratio = covered / total; end逻辑说明:对每个网格点,vecnorm 计算它到所有节点的欧氏距离,any 判断是否有任意一个距离小于等于 Rs。这里用 any 而不是逐节点 break,代码更紧凑,网格点不超过 1 万、节点不超过 200 时,MATLAB 的向量化优势足够覆盖多余判断的开销。参数调整上,Rs 直接决定覆盖率上限:Rs 取 10、区域边长 100 时,60 个节点的感知圆面积理论上限接近 190%,考虑边界裁剪和圆重叠后,实际覆盖率落在 80%~95% 区间,正好适合观察虚拟力迭代的对比效果。Rs 如果取 2,节点几乎覆盖不动,曲线看起来像在噪声里抖动,不适合判断算法好坏。
2.2 虚拟力:互相排斥、边界推回、目标点吸引
虚拟力算法(VFA)的出发点是把这个布局问题改写成力学问题:每个节点当成带电粒子,距离太近就互相排斥,防止扎堆;区域边界对节点施加向内的斥力,防止节点被推出部署区域;如果存在需要重点覆盖的目标区域,还可以给节点加指向目标中心的引力。所有力的合力方向就是节点移动的方向,力的大小决定移动步长。迭代几十次后,节点会从随机扎堆自然铺开成一个相对均匀的覆盖布局。
function F = boundaryRepel(node, L, margin) % 边界斥力:距离边界小于 margin 时,往区域内部推 F = [0 0]; if node(1) < margin F(1) = (margin - node(1)) / margin; elseif node(1) > L - margin F(1) = -(node(1) - (L - margin)) / margin; end if node(2) < margin F(2) = (margin - node(2)) / margin; elseif node(2) > L - margin F(2) = -(node(2) - (L - margin)) / margin; end end逻辑说明:边界斥力的大小与“节点离边界的距离”成反比关系,越靠近边界力越大,方向始终指向区域内部。margin 是触发边界斥力的缓冲带宽度,代码合集默认取 2.0,比感知半径小得多,因此不会干扰区域内部的节点运动。真正的节点间斥力在 vfaUpdate.m 里计算,作用半径是 Rc,力的大小与节点间距成反比,距离越近斥力越大,超过 Rc 后直接归零。这里的参数含义要注意区分:Rs 管“感知”,决定覆盖率怎么算;Rc 管“间距控制”,决定节点之间保持多远的距离。把 Rc 设置成 Rs 的 1.2~1.5 倍是一种常见经验,能让感知圆之间的重叠控制在一个合理范围,既不会大面积重叠浪费节点,也不会留下细长空洞。
2.3 代码合集的算法构成:虚拟力为主,粒子群做横向对照
这套 MATLAB 代码合集不是只含一个虚拟力。解压后通常能看到三个主入口:随机部署基线、虚拟力、粒子群,另外还留有遗传算法切换的接口。这个设计思路比单个算法脚本实用得多:先跑基线看随机部署的覆盖率差到什么程度,再跑虚拟力看迭代后的提升幅度,最后跑粒子群做横向对比,一组数据就能支撑“算法有效”的结论。粒子群(PSO)的实现思路是让每个粒子代表一组 N 个节点的完整坐标,维数等于节点数的两倍(每个节点 x、y 各一维),在解空间里搜索最优布局。PSO 的优点是不容易陷入局部布局,缺点是计算代价高:一个粒子就要做一次全覆盖率网格扫描,200 个粒子迭代 50 代,相当于做了一万次覆盖率计算,在 MATLAB 里要跑几十分钟。所以代码合集的定位很清楚:VFA 做快速迭代和动态调整,PSO 做离线方案验证。
这种“不同算法用不同主文件入口”的组织方式,对二次开发特别友好。你不需要改公共的覆盖率函数,只需要复制一份 main_vfa.m,把它改成 main_hybrid.m,然后在里面加入自己的位置更新逻辑就可以了。这种改造的具体做法留到最后一章讲。
3. 源码拆解:目录结构、主脚本与核心函数逐段读
3.1 解压后有什么:文件树与职责划分
代码合集的目录结构大致如下。不同打包版本里文件名会有些微调,但职责边界基本一致:
wsn-cover-opt/ ├─ main_bf.m # 随机部署基线:只算初始覆盖率,不迭代 ├─ main_vfa.m # 虚拟力主入口:迭代更新节点位置,输出曲线 ├─ main_pso.m # 粒子群主入口:与 VFA 做横向对比 ├─ loadParams.m # 返回参数结构体,所有主脚本共用 ├─ calCoverage.m # 网格化覆盖率计算 ├─ vfaUpdate.m # 虚拟力位置更新,含节点间斥力和边界力 ├─ psoUpdate.m # 粒子群速度/位置更新 ├─ boundaryRepel.m # 边界斥力函数,被 vfaUpdate 调用 ├─ initNodes.m # 随机初始化节点坐标 └─ plotNet.m # 画节点分布图和覆盖率迭代曲线把参数从主脚本里抽出来单独放 loadParams.m,是这个合集做得好的地方。改节点数、感知半径、迭代次数不用去翻循环体,改一行就生效。我拆过的很多老代码把参数散在三个主文件里,改一遍要同步三处,漏一处就出现“两个算法初始条件不一致”的错误对比结果——这在写报告时是致命的,评审一眼就能看出对比实验不严谨。
3.2 主脚本:main_vfa.m 的完整流程
以 main_vfa.m 为例,整体流程是四段:读参数、初始化节点、迭代更新、出图。读参数之后第一件事是固定随机数种子,这是保证可复现的关键一步:
params = loadParams(); % 集中读取所有参数 rng(params.seed); % 固定种子,保证实验可复现 nodes = initNodes(params); % 随机部署 numNodes 个节点 coverage0 = calCoverage(nodes, params.Rs, params.L, params.step); fprintf('初始覆盖率: %.2f%%\n', coverage0 * 100); ratioHistory = zeros(params.maxIter, 1); for iter = 1:params.maxIter nodes = vfaUpdate(nodes, params); nodes = min(max(nodes, 0), params.L); % 位置越界直接拉回区域 ratioHistory(iter) = calCoverage(nodes, params.Rs, params.L, params.step); end plotNet(nodes, params, ratioHistory);逻辑说明:这里的位置越界处理用的是“拉回边界”策略,而不是反射或忽略。拉回边界实现最简单,不会引入额外的方向计算。缺点是节点在边界上会产生轻微的聚集感,但因为 vfaUpdate 里本身有边界斥力,下一轮迭代会把聚集推散,不会累积。seed 默认取固定值,需要做蒙特卡洛多次实验时,在外面套一层循环改变 seed 即可,这样每组实验的初始部署都不一样,但每次跑同一组都能完全复现。
3.3 vfaUpdate.m:合力、步长截断与迭代收敛
vfaUpdate.m 是虚拟力的核心实现。关键点有三个:合力要不要截断、位置更新方向如何确定、平衡距离附近怎么处理。这里看完整的位置更新写法:
function nodes = vfaUpdate(nodes, params) n = size(nodes, 1); maxStep = params.maxStep; % 单次迭代最大位移 for i = 1:n F = [0 0]; for j = 1:n if i == j, continue; end d = norm(nodes(i, :) - nodes(j, :)); if d > 0 && d < params.Rc F = F + (1/d - 1/params.Rc) * ... (nodes(i, :) - nodes(j, :)) / d * params.alpha; end end F = F + boundaryRepel(nodes(i, :), params.L, params.margin); if norm(F) > maxStep F = F / norm(F) * maxStep; end nodes(i, :) = nodes(i, :) + F; end end逻辑说明:节点间斥力公式里的 (1/d - 1/Rc) 是一个归一化设计——距离越近斥力越大,距离接近 Rc 时递减到 0,不会在平衡位置附近产生反复的振荡。params.alpha 是力缩放系数,默认 1.0,改造混合算法时通常要把 alpha 和惯性权重一起调。步长截断本质上是力的归一化:只限制单次移动的最大距离,不改变力的方向,所以不会破坏节点间的相对位置关系。maxStep 建议取值 0.5~1.5,取太大会出现震荡,取太小则收敛速度过慢。
3.4 输出与对比:两张图一个数
代码合集出两样结果:一张是最终节点分布图,一张是覆盖率随迭代次数变化的曲线。节点分布图上会把每个节点的感知圆画出来,方便肉眼定位空洞位置;曲线图是判断收敛和横向对比算法的主要依据。完整跑一遍 main_bf.m 和 main_vfa.m 之后,随机部署的覆盖率可能只有 62%~75%,虚拟力迭代 50 次后普遍能到 85%~95%,提升幅度在 10~25 个百分点之间。这个区间就是判断虚拟力参数是否合理的直观指标,低于 10 个百分点先查初始覆盖率是不是太高;高于 25 个百分点大概率是初始部署过于集中,虚拟力只是把“扎堆”铺开而已。
4. 仿真运行:从解压到出图,一遍跑通并看懂结果
4.1 环境准备与打开方式
运行这套代码不需要额外安装工具箱,纯 MATLAB 基础环境就能跑。这两年常见的版本区间我都验证过,没有遇到函数兼容性问题。要特别注意当前文件夹必须切到解压目录,否则 loadParams 和 calCoverage 会提示“未定义函数或变量”。更省心的做法是在 main 脚本开头加一句 cd(fileparts(mfilename('fullpath'))),自动把工作目录切到脚本所在位置,换机器跑也不会迷路。如果你的 MATLAB 版本较老,vecnorm 可能不存在,把 vecnorm(p - nodes, 2, 2) 换成 sqrt(sum((p - nodes).^2, 2)) 即可,一行的事。
打开 main_vfa.m 之后直接按 F5 运行。第一次跑建议不要急着上 200 节点,先用默认参数跑一遍,确认图形窗口正常弹出、命令行打印出初始覆盖率,再开始调参。这样一旦后续结果不对,你能确定问题出在参数而不是环境上。
4.2 关键参数与调参范围:一张表说清楚
运行体验接近开箱即用:命令行打印初始覆盖率后进入迭代,50 次迭代的覆盖率计算在普通笔记本上也就是几十秒。以下是直接决定仿真效果的一组参数,全部集中在 loadParams.m 里:
| 参数 | 默认值 | 作用 | 调参建议 |
|---|---|---|---|
| numNodes | 60 | 节点数量 | 过少覆盖上不去,过多重叠严重,40~100 比较合适 |
| Rs | 10 | 感知半径 | 调整它让初始覆盖率落在 60%~85% 区间 |
| Rc | 14 | 斥力作用半径 | 与 Rs 保持 1.2~1.5 倍关系,过大节点过于分散 |
| maxIter | 50 | 迭代次数 | 看曲线是否收敛,一般 30~80 足够 |
| step | 1 | 网格粒度 | 精度要求高用 0.5,速度要求高用 2 |
| maxStep | 0.8 | 单步最大位移 | 曲线震荡就调小,收敛慢就调大 |
| alpha | 1.0 | 力的缩放系数 | 混合算法时可降到 0.5 左右 |
| seed | 2024 | 随机种子 | 做蒙特卡洛实验时改成循环变量 |
表格的用途是给你一个改参数前的安全范围。Rs 和 Rc 尽量不要脱离这个比例关系,其它参数可以大胆试。之前我把 Rc 从 14 直接调到 20,结果节点被斥力推得四散,边界处堆了一圈,内部反而出现大片空洞——典型的参数翻车案例,调参一定一次只动一个变量。
4.3 结果怎么看:覆盖率曲线与分布图的三个重点
跑完 main_vfa.m,重点看三件事。第一,初始覆盖率在曲线第一个点是多少。如果低于 50%,说明节点数或 Rs 偏小,迭代后即使提升 20 个百分点也未必好看,建议先回 loadParams.m 调整。第二,曲线形态是否单调上升。VFA 在常规参数下应当前 10 代快速上升、后 20 代缓慢逼近上限;曲线明显震荡时,检查 maxStep 和 Rc。第三,节点分布图上的节点是否均匀散开、有没有成对节点几乎重合。重合节点是随机初始化导致的极小概率事件,一旦出现会白白损失一个节点的感知面积,需要重新跑一次。
想论证虚拟力的有效性,就把 main_bf.m 和 main_vfa.m 先后跑一遍,记录两个初始覆盖率值与最终覆盖率。做课题或写报告时,这个差值就是你论证“虚拟力有效”的核心数据。记住两边用同一个 seed,让初始布局完全一致,否则差异会混入随机噪声,结论站不住。要导图时直接在 figure 窗口菜单里导出,或用 print('-dpng', 'coverage_compare.png') 存成 PNG,论文插图基本够用。
5. 避坑与排查:覆盖率出图反直觉时,先按这套清单查
WSN 覆盖优化仿真里有一类经典的反直觉现象:覆盖率从 68% 优化到 90%,听起来是成功;但换一组随机种子,初始覆盖率变成 85%,迭代完反而掉到 87%,结论就站不住脚了。这类问题的根源大多不在算法本身,而在参数设置或离散计算方式上。下面是我拆这套 MATLAB 代码合集时实际踩过的五类坑,每条按现象、原因、解决三个层级写,排查时对着查就行。
5.1 初始覆盖率就有 90% 以上,迭代后反而下降
现象:跑 main_vfa.m,命令行先打印初始覆盖率,跑完后发现初始值接近满覆盖,最终值反而低了一截,看起来像算法越优化越差。
原因:感知半径 Rs 设得太大。比如 Rs 取 15、区域 100×100,60 个节点的感知圆大面积重叠,初始覆盖率被抬高到 90% 以上。此时区域里已经没有“洞”可填,算法为了均匀化间距,会把边缘节点往内部拉,代价就是损失边界覆盖面积。
解决:把 Rs 调小到 8~10,让初始覆盖率落在 60%~80% 之间。覆盖率优化算法最适合解决“有洞”的场景,初始就接近满覆盖时,折腾节点位置只会降低面积利用率。判断 Rs 是否合适的办法很简单:跑一次 main_bf.m,看随机部署的覆盖率是否在 60%~85% 区间。
5.2 节点全部堆到区域边界,内部大片空洞
现象:分布图上节点沿着四条边界排成一圈,区域中心几乎没有节点,覆盖率曲线显示内部大面积空洞。
原因:边界斥力的方向或幅度出了问题。boundaryRepel 的方向应该始终指向区域内部;如果 margin 设置过大,边界附近一片宽阔地带的节点都会被强推回内部,实际效果是把节点挤向区域中间。另一种常见情况是 Rc 过大,节点间斥力在较大距离上仍有作用,节点被彼此推开直到无路可走,最后堆在边界上。
解决:先确认 boundaryRepel 中力方向的符号没问题,再把 margin 调回 2~3,并把 Rc 控制在 Rs 的 1.2~1.5 倍之间。改完后分布图应当呈现从边界到中心均匀铺开的状态。
5.3 覆盖率曲线来回震荡,40 代还看不到收尾
现象:覆盖率曲线像锯齿一样上下跳动,迭代到最后没有平稳趋势,最终覆盖率的数字也不稳定。
原因:单步位移 maxStep 设得太大。取 2.5 时,节点每轮要么冲过头、要么被边界力或斥力弹回,覆盖率在高值和低值之间往复。另一个原因是 Rc 与 Rs 的比例脱离合理区间,导致节点受力始终无法平衡,平衡位置成了“围着目标点转圈”。
解决:把 maxStep 降到 0.5~1.0,Rc 调回 Rs 的 1.2~1.5 倍。改完后曲线应当呈现“前 10 代快速上升、后 20 代缓慢逼近上限”的形态。
5.4 同一份代码两次跑出的结果不一样
现象:连续两次运行 main_vfa.m,初始覆盖率和最终覆盖率都对不上,每次差几个百分点,无法判断改动是否有效。
原因:MATLAB 的 rand 默认使用全局随机数流,每次运行都会生成新的序列。不固定随机种子,任何实验都无法复现。调试时更坑:你改了一行代码,却分不清效果差异来自代码还是随机性。
解决:在 loadParams 之后立即执行 rng(seed)。同时要留意 initNodes.m 里用的是 rand 还是 randn,两者的随机序列相互独立,混用会导致固定了种子也无法完全复现。需要做蒙特卡洛统计时,把 rng 调用放在外层循环里,每次实验换一个 seed,但每组实验内部保持固定。
5.5 网格粒度改了,覆盖率结果变了近 10 个百分点
现象:把 step 从 1 改成 0.5,同一个节点布局算出的覆盖率明显上升,曲线整体抬高,你以为算法变好了,其实只是测量方式变了。
原因:网格化覆盖率本质是离散化近似。step=1 时每个网格点代表一个 1×1 的小区域,边界上的圆弧容易被漏算;step=0.5 时边界估算更精细,覆盖率数值整体偏大。这不是 bug,是离散化误差。
解决:对比实验、写报告时固定同一个 step,不能一个实验用 1、另一个用 0.5。要判断当前精度是否够细,把 step 减半再跑一次,覆盖率变化小于 1 个百分点说明精度已经稳定。
5.6 另一个常见误区:把覆盖率当成连通性来调参
现象:有人把 Rs 当成通信半径去调,试图用调大感知半径的方式“解决”节点孤立问题,结果覆盖率非常漂亮,报告里的网络平均跳数却一塌糊涂。
原因:覆盖率衡量的是“面积被感知”,连通性衡量的是“数据能否多跳传回汇聚点”,这是两个完全不同的指标。Rs 管感知,Rc 管邻居关系,VFA 里的斥力本质上只是控制间距,不承载通信模型。
解决:如果实验需要同时保证连通,在覆盖率迭代结束后单独写一个连通判据函数,检查每个节点到汇聚点是否存在多跳路径。这种验证方式对课堂教学和报告评审都更站得住脚。
排查这类覆盖率异常问题,我的习惯是先把数据抽出来看,而不是盯着图猜。main_vfa.m 每轮迭代都会把覆盖率写进 ratioHistory,在工作区里双击打开,对比第 1、5、20、50 代的值:正常情况是单调递增且增量递减;如果第 5 代低于第 1 代,再打开分布图看那几轮节点到底发生了什么。先看数据后看图,能少走很多弯路。
6. 二次开发:给虚拟力加惯性项,做成 VFA-PSO 混合优化
6.1 改在哪:把位移增量换成带记忆的速度
VFA 的短板是局部搜索强、全局搜索弱,最终布局很大程度上吃初始部署的运气。想缓解,不需要重写粒子群,只需在 vfaUpdate 外面套一个速度记忆项:每个节点保存上一轮的速度,新速度由历史速度乘以惯性权重 w、加上当前虚拟力 F 乘以系数 alpha 得到,位置再按速度更新。这等价于把粒子群的速度迭代式借用到 VFA 上,w 从 0.9 线性衰减到 0.4,让算法前期偏探索、后期偏精修。先把力计算从 vfaUpdate 里抽出来,改成返回合力矩阵,然后主循环改写成这样:
vNodes = zeros(size(nodes)); % 初始速度为 0 for iter = 1:params.maxIter w = 0.9 - 0.5 * iter / params.maxIter; % 惯性项线性衰减 F = vfaForce(nodes, params); % 复用原有虚拟力计算 vNodes = w * vNodes + params.alpha * F; % 速度记忆迭代 nodes = nodes + vNodes; nodes = min(max(nodes, 0), params.L); % 越界裁剪 ratioHistory(iter) = calCoverage(nodes, params.Rs, params.L, params.step); end逻辑说明:w 越小历史速度影响越弱,后期算法自然退化成原始 VFA 的精修行为。alpha 建议取 0.5 而不是 1.0,因为速度项本身已经放大了位移量,alpha 过大极易震荡。
6.2 验证:同一随机种子、同一组参数、双曲线对比
改完先别急着加新功能,用同一个 seed 和同一组 Rs、Rc、numNodes,分别跑原版 VFA 和混合版,把两条覆盖率曲线叠在一张图上。如果混合版前 10 代上升更快、最终覆盖率高 1~3 个百分点,说明惯性项确实缓解了局部收敛问题;如果曲线几乎重合,把 w 的起点提高到 0.95 再试,一次只动一个参数,不然说不清效果来自哪次改动。
这个对比实验我翻过一次车:同时改了 w 和 alpha,曲线变好了,却归因不到具体参数。从那以后,我每次做算法对比都强制固定 seed、单变量调参,宁可多跑几组,也不让结果变成黑匣子。希望帮到你。做课程设计或报告出图时,这套合集自带的三个入口已经够用;真要接自己的不规则区域或重点覆盖目标,按这个思路改力函数就行。
本文还有配套的精品资源,点击获取