简介:这份MATLAB代码合集面向无线传感器网络覆盖优化方向的课程设计、毕业设计与算法对比实验,围绕传感器节点部署、覆盖率和覆盖效率提升,汇集人工蜂群、人工鱼群、灰狼、粒子群、蝠鲼觅食、蛇优化、麻雀搜索、鲸鱼优化、樽海鞘以及虚拟力等十种常见求解思路。压缩包共41个文件,包括39个m脚本和2个caj文献,整体约5.73MB;代码注释较详细,下载后可运行,便于按算法模块复现实验,并观察不同策略在WSN覆盖问题上的表现。已有209人学习下载,适合需要快速搭建对比基线、理解虚拟力与群智能算法实现细节的学习者。
1. 从一张“覆盖率上不去”的曲线说起:这套MATLAB代码合集能解决什么
WSN覆盖优化最让人抓狂的场景,是传感器网络节点撒下去后覆盖率卡在0.78,MATLAB代码合集里的虚拟力算法跑了几十轮,节点还是在空洞边缘打转。这份WSN覆盖优化MATLAB代码合集围绕传感器网络覆盖优化算法展开,核心是虚拟力算法,同时带覆盖计算、节点部署、迭代曲线和结果可视化,适合做课程设计、算法对照和二次开发。
它不教你从零推导公式,而是把“节点怎么动、覆盖率怎么算、结果怎么画”拆成能跑的脚本。新手可以先把main.m跑起来看覆盖率曲线,熟手可以直接换感知模型、改适应度函数,或者把虚拟力与PSO、GA做对照。下面按工程骨架、虚拟力核心、优化算法对照、排查和二次开发拆开讲。
2. 拆开ZIP看工程骨架:MATLAB入口脚本与WSN参数面板
拿到这种MATLAB代码合集,我一般不会先翻算法文件,而是先看目录里谁被谁调用。WSN覆盖优化项目最怕入口脚本藏在一堆test_*.m里,改了半天参数,结果跑的是另一个函数。合集里常见的文件分工如下,具体命名会变,但功能基本绕不开这几类。
| 文件/目录 | 作用 | 先看什么 |
|---|---|---|
main.m | 总入口,串起初始化、算法迭代、绘图 | 有没有clear; clc; close all,参数从哪里来 |
params.m | 参数面板,节点数、区域大小、感知半径 | 单位是否统一,半径和区域比例是否合理 |
init_network.m | 随机撒点或网格初始化 | 随机种子、边界处理 |
coverage_calc.m | 网格法算覆盖率 | 网格分辨率、布尔覆盖还是概率覆盖 |
vfa_core.m | 虚拟力迭代核心 | 斥力、引力、边界力的系数 |
pso_core.m/ga_core.m | 优化算法对照 | 适应度函数是否与覆盖计算一致 |
plot_coverage.m | 画节点、覆盖圆、空洞 | 图例和坐标范围 |
2.1 入口先认main.m,别急着改算法
main.m的职责不是算覆盖,而是把参数、初始部署、算法迭代和绘图串起来。常见写法是先把参数结构体P传进去,再调用初始化函数,最后在循环里调用算法核心并记录覆盖率。下面这段是我会先检查的入口骨架,变量名按合集习惯改,逻辑不变。
% main.m clear; clc; close all; rng(2024, 'twister'); % 固定随机种子,方便复现 P = params(); % 读取参数面板 P = init_network(P); % 生成初始节点坐标 P.nodes bestCover = zeros(P.maxIter, 1); % 记录每轮覆盖率 for k = 1:P.maxIter P = vfa_core(P, k); % 虚拟力迭代一轮 bestCover(k) = coverage_calc(P); % 网格法计算覆盖率 if mod(k, P.plotEvery) == 0 plot_coverage(P, bestCover, k); drawnow; end end save('result_cover.mat', 'P', 'bestCover');这段代码的关键在调用顺序:先固定随机种子,再初始化网络,然后每轮先更新节点位置、再算覆盖率。参数说明上,P.maxIter是最大迭代轮数,P.plotEvery控制绘图频率,bestCover保存历史覆盖率用于看收敛趋势。很多合集把coverage_calc写在vfa_core内部,改算法时容易漏掉绘图数据,入口里单独记录一层更稳。
注意:如果
main.m里没有rng,每次运行结果都会漂,调参时会把随机波动误判成算法改进。
2.2 参数面板决定覆盖曲线的形状
WSN覆盖优化的结果对参数很敏感。节点数、区域边长、感知半径、通信半径、网格分辨率、虚拟力步长,这六个参数基本决定曲线是稳步上升还是来回震荡。下面是一个常见的params.m写法,数值只作为示例,实际按你的场景改。
function P = params() P.area = [0, 100, 0, 100]; % 部署区域 [xmin xmax ymin ymax] P.N = 40; % 传感器节点数 P.Rs = 12; % 感知半径 P.Rc = 20; % 通信半径,虚拟力邻居搜索用 P.gridStep = 1; % 覆盖计算网格步长 P.maxIter = 200; % 最大迭代轮数 P.step = 0.8; % 位置更新步长 P.wRep = 1.2; % 节点间斥力权重 P.wAtt = 1.0; % 覆盖空洞引力权重 P.wBound = 2.0; % 边界斥力权重 P.plotEvery = 20; % 绘图间隔 endP.area用四元组表示矩形区域,后面所有坐标都落在这个范围里。P.Rs是感知半径,覆盖圆的大小由它决定;P.Rc是通信半径,虚拟力算法通常只让节点和通信范围内的邻居算斥力,否则节点一多计算量会明显上升。P.gridStep是覆盖计算的网格分辨率,取1表示每平方米一个采样点,区域100×100就是一万个点,放大区域后要适当调大,不然跑起来会很慢。P.step是每轮移动比例,太大容易越过空洞,太小收敛慢;P.wRep、P.wAtt、P.wBound分别控制斥力、引力和边界力的强弱,调参时先固定两个、只动一个。
2.3 一次完整数据流:初始化、覆盖计算、迭代、绘图
把数据流画清楚,后面换算法就不会乱。初始化阶段init_network.m负责生成P.nodes,通常是N×2矩阵,第一列x、第二列y。覆盖计算阶段coverage_calc.m在区域里打网格,对每个网格点判断是否被至少一个节点覆盖。覆盖判断常见写法是距离小于等于感知半径,布尔模型如下。
function coverRate = coverage_calc(P) xs = P.area(1):P.gridStep:P.area(2); ys = P.area(3):P.gridStep:P.area(4); [X, Y] = meshgrid(xs, ys); covered = false(size(X)); for i = 1:size(P.nodes, 1) d2 = (X - P.nodes(i,1)).^2 + (Y - P.nodes(i,2)).^2; covered = covered | (d2 <= P.Rs^2); end coverRate = sum(covered(:)) / numel(covered); end这段函数没有复杂技巧,重点是covered用逻辑矩阵累积,最后除以总网格点数。P.nodes(i,1)和P.nodes(i,2)是第i个节点的坐标,P.Rs^2避免开方。实际项目里我会把xs、ys缓存到P里,避免每轮重复生成网格;如果合集里没有缓存,区域一大就能感觉到卡顿。绘图阶段plot_coverage.m通常画三样东西:节点位置、每个节点的感知圆、当前覆盖率标题。看曲线时别只看最终值,前20轮上升快、后50轮几乎不动,说明虚拟力已经进入局部稳定,继续加迭代意义不大。
3. 虚拟力算法核心:节点怎么被“推”进覆盖空洞
虚拟力算法在WSN覆盖优化里的直觉很朴素:节点太挤就互相推开,覆盖空洞附近给一个引力把节点拉过去,碰到边界再弹回来。它不像PSO那样全局搜索,优势是局部调整快、物理意义清楚,缺点是参数一乱就震荡。拆vfa_core.m时我通常分三步看:邻居怎么找、每个力怎么算、位置怎么更新。
3.1 受力模型拆解:斥力、引力与边界回弹
节点i受到的合力可以拆成三部分。节点间斥力只在通信半径Rc内生效,距离越近斥力越大;覆盖空洞引力来自未覆盖网格点,通常取空洞点对节点的反方向,或者取最近空洞点的方向;边界力在节点靠近区域边缘时把它往内推。下面是一个常见的受力计算片段,写在一个循环里,参数名和params.m对应。
function F = force_on_node(P, i, holePts) pi = P.nodes(i, :); F = [0, 0]; % 1) 邻居斥力:只算通信半径内的其他节点 for j = 1:size(P.nodes, 1) if j == i, continue; end dij = pi - P.nodes(j, :); d = norm(dij); if d > 0 && d < P.Rc F = F + P.wRep * (1/d - 1/P.Rc) * (dij / d); end end % 2) 空洞引力:取最近的若干空洞点,避免被远处空洞拖散 if ~isempty(holePts) d2 = sum((holePts - pi).^2, 2); [~, idx] = min(d2); dir = holePts(idx, :) - pi; d = norm(dir); if d > 0 F = F + P.wAtt * (dir / d); end end % 3) 边界斥力:离边界越近,往内推得越狠 margin = 5; if pi(1) - P.area(1) < margin F(1) = F(1) + P.wBound * (margin - (pi(1)-P.area(1))) / margin; end if P.area(2) - pi(1) < margin F(1) = F(1) - P.wBound * (margin - (P.area(2)-pi(1))) / margin; end if pi(2) - P.area(3) < margin F(2) = F(2) + P.wBound * (margin - (pi(2)-P.area(3))) / margin; end if P.area(4) - pi(2) < margin F(2) = F(2) - P.wBound * (margin - (P.area(4)-pi(2))) / margin; end end这段代码里F是二维合力,holePts是当前未覆盖网格点坐标。斥力项(1/d - 1/Rc)在距离趋近0时会很大,实际跑的时候要加一个最小距离保护,比如d = max(d, 0.5),否则两个节点重合会直接把位置弹飞。空洞引力只取最近的一个空洞点,是为了减少远处空洞对局部调整的干扰;如果合集里取所有空洞点求平均,覆盖大的区域时节点会被拉向区域中心,边缘空洞反而补不上。边界力用margin控制触发范围,P.wBound一般比斥力和引力大一点,防止节点被推出区域。
3.2 vfa_core.m逐段实现:从邻居搜索到位置更新
vfa_core.m通常不是只算一个节点,而是遍历所有节点、算合力、更新位置,再做边界截断。下面是一个可运行的骨架,保留核心逻辑,省略了部分绘图代码。
function P = vfa_core(P, iter) holePts = find_holes(P); % 当前未覆盖网格点 newNodes = P.nodes; for i = 1:size(P.nodes, 1) F = force_on_node(P, i, holePts); % 合力归一化,避免步长被大力支配 if norm(F) > 0 F = F / norm(F); end newNodes(i, :) = P.nodes(i, :) + P.step * F; % 边界截断,保证节点不跑出部署区域 newNodes(i, 1) = min(max(newNodes(i, 1), P.area(1)), P.area(2)); newNodes(i, 2) = min(max(newNodes(i, 2), P.area(3)), P.area(4)); end P.nodes = newNodes; P.history{iter} = P.nodes; % 可选:保存轨迹,方便回放 endfind_holes按P.gridStep生成网格,返回未覆盖点。合力归一化是血泪经验:不归一化时,节点重合产生的斥力会远大于空洞引力,位置一步跳很远,覆盖率曲线像心电图。P.step取0.5到1.0之间比较稳,区域大、节点少时可以适当放大,但别超过感知半径的一半。边界截断用min(max(...))简单有效,比事后画图发现节点飞出区域再回来查强。P.history保存轨迹会占内存,迭代200轮、40个节点还好,节点上千时建议只保存覆盖率或每隔10轮存一次。
3.3 终止条件与覆盖率回写:别让迭代空转
虚拟力算法常见终止条件有三种:达到最大迭代轮数、覆盖率连续若干轮变化小于阈值、合力范数小于阈值。合集里如果只写for k = 1:P.maxIter,跑满200轮也能出结果,但调参时看不出什么时候已经稳定。我会在main.m里加一个早停判断,同时把覆盖率回写到P里,方便后续算法对照。
P.cover = bestCover(k); if k > 10 && abs(bestCover(k) - bestCover(k-10)) < 1e-4 fprintf('覆盖率在 %d 轮后基本稳定:%.4f\n', k, bestCover(k)); break; endbestCover是每轮覆盖率,1e-4是稳定阈值,按你的网格精度调。早停不是必须,但对比PSO和虚拟力时,固定迭代轮数更公平;如果要比收敛速度,就记录达到某个覆盖率阈值所需的轮数。虚拟力算法的覆盖率不一定单调上升,节点被推开后可能短暂出现新空洞,所以别用“连续两轮下降就判失败”这种规则,容易误杀。
4. 优化算法对照:从虚拟力到PSO/GA的替换与组合
合集里既然叫“优化算法”,通常不会只有虚拟力一个文件。虚拟力适合局部调整,PSO、GA这类群体智能算法适合全局搜索,两者解决的是同一个覆盖问题,只是更新方式不同。改算法时最怕评价标准不一致:虚拟力用网格覆盖率,PSO用距离和,最后两张图没法比。我的做法是先固定一个fitness_cover.m,所有算法都调它。
4.1 覆盖问题先写成统一的适应度函数
覆盖优化的目标就是最大化覆盖率,约束是节点坐标不越界。把覆盖率计算包成适应度函数,输入是节点坐标矩阵,输出是标量适应度,PSO和GA都能直接接。
function fit = fitness_cover(nodes, P) P.nodes = nodes; fit = coverage_calc(P); % 覆盖率越高,适应度越好 % 可选惩罚项:节点越界或重叠严重时扣分 outX = nodes(:,1) < P.area(1) | nodes(:,1) > P.area(2); outY = nodes(:,2) < P.area(3) | nodes(:,2) > P.area(4); if any(outX | outY) fit = fit - 0.5; end endnodes是N×2矩阵,P里带区域和感知半径。惩罚项不是必须,但PSO更新位置时容易越界,加惩罚比事后截断更能让粒子往可行域靠。coverage_calc内部每次都会重新打网格,粒子数多、迭代轮数大时计算量不小;实际项目里我会把网格坐标预先生成到P里,coverage_calc只做距离判断,速度会好很多。
4.2 PSO替换虚拟力:接口不变,只换更新公式
PSO的粒子位置可以编码成所有节点的坐标展开向量,也可以每个粒子代表一个节点的候选位置。覆盖优化里更常见的是“一个粒子=一套完整部署方案”,维度是2N。下面是一个最小PSO骨架,接上面的适应度函数。
function [gbest, gbestFit] = pso_core(P) dim = 2 * P.N; nParticle = 30; w = 0.7; c1 = 1.5; c2 = 1.5; X = rand(nParticle, dim); X(:,1:2:end) = P.area(1) + X(:,1:2:end) * (P.area(2)-P.area(1)); X(:,2:2:end) = P.area(3) + X(:,2:2:end) * (P.area(4)-P.area(3)); V = zeros(nParticle, dim); pbest = X; pbestFit = -inf(nParticle,1); [gbestFit, idx] = max(pbestFit); gbest = pbest(idx,:); for it = 1:P.maxIter for p = 1:nParticle nodes = reshape(X(p,:), P.N, 2); fit = fitness_cover(nodes, P); if fit > pbestFit(p) pbestFit(p) = fit; pbest(p,:) = X(p,:); end if fit > gbestFit gbestFit = fit; gbest = X(p,:); end end for p = 1:nParticle V(p,:) = w*V(p,:) + c1*rand(1,dim).*(pbest(p,:)-X(p,:)) ... + c2*rand(1,dim).*(gbest-X(p,:)); X(p,:) = X(p,:) + V(p,:); end % 越界截断 X(:,1:2:end) = min(max(X(:,1:2:end), P.area(1)), P.area(2)); X(:,2:2:end) = min(max(X(:,2:2:end), P.area(3)), P.area(4)); end enddim是粒子维度,nParticle是粒子数,w是惯性权重,c1、c2是学习因子。reshape(X(p,:), P.N, 2)把展开向量还原成节点矩阵,顺序是x1,y1,x2,y2。PSO的初始粒子用rand生成后要映射到区域范围,不然粒子一开始就在界外,适应度会被惩罚项压得很低。越界截断放在速度更新之后,和虚拟力的边界处理保持同一套坐标范围。
4.3 混合策略与对比实验:VFA做局部,PSO做全局
纯PSO在覆盖优化里容易早熟,粒子全挤到一个局部最优部署;纯虚拟力又依赖初始撒点,初始节点全在一边时,引力再强也很难把节点拖过整个区域。混合策略常见两种:先用PSO跑几十轮得到粗部署,再用虚拟力做局部空洞修补;或者把虚拟力的斥力项塞进PSO的速度更新里,让粒子之间也保持距离。第二种改动小,但参数更多,调参成本高。
| 算法 | 优势 | 容易翻车的地方 | 适用场景 |
|---|---|---|---|
| 虚拟力VFA | 局部调整快,物理意义清楚 | 参数震荡、节点被推飞 | 初始部署不太差,补空洞 |
| PSO | 全局搜索能力强 | 早熟、越界、维度高时慢 | 节点数中等,求一套部署方案 |
| GA | 组合优化思路直观 | 编码和交叉设计麻烦 | 离散选址或开关控制 |
| VFA+PSO | 全局加局部,覆盖率上限高 | 两套参数要一起调 | 对覆盖率要求高、能接受耗时 |
对比实验要固定三件事:同一初始节点、同一覆盖网格、同一最大评价次数。虚拟力一轮算一次覆盖率,PSO一个粒子算一次,比较“达到0.9覆盖率所需评价次数”比比较“迭代轮数”公平。如果合集里没有统一的评价计数器,我会在fitness_cover里加一个全局计数变量,所有算法共用。
5. 跑通与排查:MATLAB环境、随机种子与边界越界的血泪经验
这类MATLAB代码合集跑不起来,通常不是算法错,而是路径、函数同名、参数单位或随机种子在作怪。WSN覆盖优化的图很直观,覆盖率曲线一出来就能判断有没有跑对,但前提是入口脚本真的跑的是你以为的那个文件。下面按环境、翻车记录和复现实验三块排查。
5.1 环境与路径排查:纯英文目录、函数同名、工具箱依赖
先把压缩包解压到纯英文路径,别放在带中文或空格的目录里。MATLAB对路径中的特殊字符不是每次都报错,有时是load找不到mat文件,有时是函数调用进了旧版本缓存。打开MATLAB后先cd到入口目录,再which main -all看有没有同名文件。合集里如果有多个coverage_calc.m,MATLAB按路径顺序选一个,你以为改了新文件,实际跑的是旧文件,这种翻车最隐蔽。
restoredefaultpath; % 清理用户路径,避免旧函数干扰 addpath(genpath(pwd)); % 只加当前项目目录 which coverage_calc -all % 检查是否有同名函数 ver % 看当前MATLAB版本和工具箱restoredefaultpath会重置路径,适合排查“换了文件夹结果不一样”的问题;addpath(genpath(pwd))把项目子目录都加进来;which -all列出所有同名函数。工具箱依赖方面,虚拟力和覆盖计算通常只用基础MATLAB,绘图可能用到sgtitle、tightlayout这类较新函数,老版本报错就换成title和手动调边距,别为了一个绘图函数卡住主流程。
注意:不要在没有备份的情况下全局
restoredefaultpath后直接保存路径,否则你之前加的其他项目路径会丢。
5.2 五条高频翻车记录:现象、原因、解决
第一条,覆盖率曲线每次不一样。现象是同一套参数跑三次,最终覆盖率能差0.05以上;原因是init_network.m用了rand但没有固定种子;解决是在main.m最前面加rng(2024,'twister'),并把初始节点保存到mat文件,后续对比算法都加载同一份。
第二条,节点飞出部署区域。现象是绘图时节点跑到坐标轴外面,覆盖率反而虚高;原因是虚拟力合力没有归一化,或者边界截断写在位置更新之前;解决是合力先归一化,更新后再用min(max(...))截断,边界力权重别设成0。
第三条,覆盖率算出来一直是1。现象是节点很少但覆盖率显示100%;原因是网格步长太大,比如区域100×100、gridStep=20,只采样25个点,碰巧都被覆盖;解决是gridStep先取1,区域放大后按比例调,至少保证每个感知圆内有几十个采样点。
第四条,迭代到后面覆盖率来回跳。现象是曲线上下震荡,节点位置左右晃;原因是P.step太大,或者斥力和引力权重接近、方向相反;解决是把P.step降到0.3到0.5,先固定wAtt调wRep,再看边界力。
第五条,PSO结果不如虚拟力。现象是PSO跑完覆盖率更低;原因是PSO粒子维度是2N,初始粒子全在区域中心附近,或者适应度函数和虚拟力用的不是同一个coverage_calc;解决是检查粒子初始化映射、统一适应度函数,并给PSO足够的评价次数。
5.3 复现实验:固定种子、统一网格、保存中间结果
调参最怕“这次好像好一点”但说不清是参数变了还是随机性。我的习惯是每次实验保存三样东西:参数结构体P、最终节点坐标、覆盖率历史。文件名带上关键参数,比如N40_Rs12_step08_seed2024.mat,后面回看不用猜。下面这段放在main.m末尾,能省很多后悔药。
result.P = P; result.nodes = P.nodes; result.cover = bestCover; result.seed = 2024; save(sprintf('exp_N%d_Rs%d_step%.2f_seed%d.mat', ... P.N, P.Rs, P.step, result.seed), 'result');sprintf拼文件名时注意参数顺序和格式,%.2f保留两位小数,避免文件名里出现一长串浮点。统一网格的意思是所有算法、所有对比实验都用同一个P.gridStep和同一个区域范围,否则覆盖率数值不可比。中间结果保存后,后面画对比图可以直接load,不用重跑算法。
6. 二次开发落地:改覆盖模型、加能耗约束与验证方法
这套代码合集的二次开发空间主要在评价函数和约束条件。虚拟力、PSO的更新公式可以不动,把coverage_calc换成概率感知模型,或者把能耗、连通性加进适应度,整套流程还能跑。我的建议是先改一个评价函数,跑通三组场景,再动算法参数。
6.1 从布尔覆盖到概率感知:只改一个评价函数
布尔模型只判断距离是否小于感知半径,边缘一刀切。概率感知模型更贴近实际传感器,距离越近覆盖概率越高。改动集中在coverage_calc.m,其他入口和算法不用大改。
function coverRate = coverage_prob(P) xs = P.area(1):P.gridStep:P.area(2); ys = P.area(3):P.gridStep:P.area(4); [X, Y] = meshgrid(xs, ys); prob = zeros(size(X)); for i = 1:size(P.nodes, 1) d = sqrt((X - P.nodes(i,1)).^2 + (Y - P.nodes(i,2)).^2); % 距离小于Rs时按指数衰减,大于Rs时置零 p = exp(-0.5 * (d / P.Rs).^2); p(d > P.Rs) = 0; prob = 1 - (1 - prob) .* (1 - p); % 多节点联合覆盖概率 end coverRate = mean(prob(:) >= 0.5); % 概率过半视为覆盖 endP.Rs在这里是感知半径尺度,exp(-0.5*(d/Rs)^2)是常见的衰减形式,p(d > P.Rs) = 0做硬截断。prob用联合概率累积,多个节点对同一网格点的覆盖概率按1-(1-p1)(1-p2)合成。最后用0.5阈值转成覆盖率,阈值可以按业务改。改完这个函数,虚拟力和PSO都不用动,直接看曲线差异。
6.2 验证改造效果:固定种子跑三组场景
改造后别只跑一组参数。固定种子,至少跑“节点少、节点中等、节点多”三组,观察覆盖率提升和收敛轮数。对比表里放初始覆盖率、最终覆盖率、达到0.9覆盖率所需轮数,比单看最终值更有说服力。如果概率模型下覆盖率下降,先别怀疑算法,检查阈值和衰减尺度是不是把有效覆盖压低了。
我踩过的坑是改完评价函数忘了同步绘图里的覆盖圆,图上还是布尔圆,曲线却按概率算,看起来像算法失效。从那以后我每次改覆盖模型,都强制先跑一遍plot_coverage核对图例和数值是否对得上,再开始调参。如果你也在做WSN覆盖优化,这份MATLAB代码合集适合先跑通main.m,再按自己的场景换评价函数,希望帮到你。
本文还有配套的精品资源,点击获取