简介:围绕多通信半径与跳距加权优化的DV-Hop改进算法,这份资料面向无线传感器网络(WSN)研究者、课程设计学生与算法开发者,针对传统DV-Hop在复杂通信环境下跳距假设恒定、误差易累积的问题,提供可运行的Matlab仿真实现与定位精度改进思路。压缩包共5个文件,约66KB,包含1个m主程序、1个md算法说明、1个txt代码说明以及2个fig结果图,分别用于复现实验流程、理解实现细节和查看网络节点分布与定位误差表现。已有787人学习下载,适合需要对照代码分析多通信半径设置、跳距加权策略与平均定位误差的读者。通过阅读说明文档并结合仿真图,可较快把握算法结构、参数调优方向及误差来源,并据此复现对比实验、调整参数,为环境监测、灾害应急等WSN定位场景的研究与改进提供参考。
1. 多通信半径 DV-HOP 改进包先别改公式:一张误差账本决定先动哪
把压缩包解到独立目录,很多人第一件事是打开 HopSize 那几行改权重,跑完发现 RMSE 反而涨了。这套基于多通信半径与跳距加权优化的 DV-HOP 改进算法,主线并不神秘:先把 WSN 里单半径布尔邻居关系拆成多级跳数,再对锚节点平均跳距做加权修正,最后用加权最小二乘解未知节点坐标。它适合正在做 WSN 定位仿真、课程设计或算法对照的从业者,尤其适合手里已有原始 DV-HOP 基线、想看清“多通信半径到底改在哪一步”的人。先别急着动公式,先把误差账本拆开。
2. 拆开 DV-HOP 的三步账:跳数、平均跳距与最小二乘的误差放大点
2.1 三步定位流程与误差放大点
DV-HOP 的经典流程只有三步,但每一步都会把误差往下传。第一步,锚节点向邻居广播自身位置,网络里每个节点记录到各锚节点的最小跳数;第二步,每个锚节点利用其他锚节点的真实距离和跳数,算出一个平均每跳距离 HopSize;第三步,未知节点用自己到锚节点的跳数乘 HopSize 得到估计距离,再用多边定位或最小二乘解坐标。
单半径 DV-HOP 的第一个误差源是跳数量化。通信半径 R 内的邻居都记 1 跳,R 外记不可达,跳数矩阵里只有整数。两个节点实际相距 0.2R 和 0.9R,在跳数上没区别,到了乘 HopSize 时误差就被放大。第二个误差源是 HopSize 的全局平均。锚节点之间的路径往往不是直线,绕路越多,距离/跳数越偏小;网络各向异性明显时,一个锚节点算出的 HopSize 只对它附近有效,直接给全网未知节点用就会飘。第三个误差源是最小二乘对异常锚节点敏感。未知节点只要混进一个跳数异常或 HopSize 异常的锚节点,线性方程组的最小二乘解就会被拽偏。
多通信半径改的是第一步:把“1 跳”拆成 1/3、2/3、1 这样的分级代价,让近邻和远邻不再同权。跳距加权改的是第二步和第三步:锚节点 HopSize 不再简单平均,未知节点也不再拿一个全局 HopSize 一刀切。两处改动叠在一起,才叫“多通信半径与跳距加权优化”。只改一处,误差曲线经常不降,这不是算法玄学,而是误差账本没对齐。
2.2 资源包入口与运行顺序
解压后先找主脚本入口,常见命名是main.m、run_dvhop.m或demo_dvhop.m,下面挂拓扑生成、跳数计算、HopSize 计算、定位解算和误差评估几个函数。不要一上来逐个函数读,先按运行顺序拉一条线:固定随机种子生成节点坐标,划出锚节点集合,算真实距离矩阵,建邻居关系,跑原始 DV-HOP 基线,再跑改进算法,最后用同一套未知节点和同一套误差指标对比。
读包时重点看四个变量:节点坐标矩阵P、锚节点索引anchor、跳数矩阵H、锚节点平均跳距HopSize。P的维度决定后面距离矩阵能不能对上;anchor是索引还是逻辑向量,直接影响P(anchor,:)的写法;H是整数跳数还是分级跳数,决定 HopSize 的量纲;HopSize是标量还是向量,决定未知节点定位时是统一乘还是按锚节点加权。血泪经验是先把这四个变量的size和前几行打印出来,再动代码,否则后面报错都找不到是哪一层维度错了。
运行顺序建议固定成“基线一次、改进一次、只改一个参数一次”。同一张拓扑图下跑三遍,第一遍原始 DV-HOP,第二遍只换多通信半径跳数矩阵,第三遍再加跳距加权。每遍都把 RMSE、定位成功率、锚节点 HopSize 分布存下来。这样后面误差变大时,能立刻判断是跳数分级的问题,还是加权方向的问题。
2.3 先跑基线:把原始 DV-HOP 误差曲线留作对照
下面这段是基线脚本的核心骨架,放在主脚本同级就能跑。它不依赖资源包里的具体函数名,目的是先把原始 DV-HOP 的跳数、HopSize、最小二乘定位和 RMSE 跑通,后面改进版才有对照。
% baseline_dvhop.m rng(7); % 固定种子,先保证拓扑可复现 N = 100; % 节点总数 A = 20; % 锚节点数,取前 A 个节点 R = 30; % 最大通信半径 area = 100; % 方形部署区域边长 P = rand(N,2) * area; % 节点坐标 anchor = 1:A; % 锚节点索引 D = squareform(pdist(P)); % 真实距离,仅仿真评估用 Adj = D <= R & D > 0; % 布尔圆盘邻居关系 % 建初始跳数矩阵,再跑 Floyd 最短路 H = inf(N); for i = 1:N for j = 1:N if Adj(i,j) H(i,j) = 1; end end H(i,i) = 0; end for k = 1:N H = min(H, H(:,k) + H(k,:)); end % 锚节点平均跳距:距离和除以跳数和 HopSize = zeros(1,A); for i = 1:A dsum = 0; hsum = 0; for j = 1:A if i ~= j && isfinite(H(anchor(i),anchor(j))) && H(anchor(i),anchor(j)) > 0 dsum = dsum + D(anchor(i),anchor(j)); hsum = hsum + H(anchor(i),anchor(j)); end end HopSize(i) = dsum / max(hsum, eps); % 防止锚节点对不可达时除零 end % 未知节点定位:简单平均 HopSize + 线性最小二乘 est = nan(N,2); for u = A+1:N idx = find(isfinite(H(u,anchor)) & H(u,anchor) > 0); if numel(idx) < 3 continue; % 少于 3 个锚节点直接跳过 end hs = mean(HopSize(idx)); % 基线做法:统一平均 d = H(u,anchor(idx)) * hs; % 估计距离 x1 = P(anchor(idx(1)),:); Hmat = []; bvec = []; for m = 2:numel(idx) xi = P(anchor(idx(m)),:); Hmat(m-1,:) = 2 * (xi - x1); bvec(m-1) = d(1)^2 - d(m)^2 + norm(xi)^2 - norm(x1)^2; end est(u,:) = (Hmat \ bvec')'; end % 只统计成功解出的未知节点 valid = A+1:N; ok = all(isfinite(est(valid,:)),2); rmse = sqrt(mean(sum((est(valid(ok),:) - P(valid(ok),:)).^2,2))); fprintf('baseline RMSE = %.3f, 成功定位 %d/%d\n', rmse, sum(ok), numel(valid));这段代码的逻辑很直白:D只用于仿真评估和锚节点 HopSize 计算,实际定位时未知节点不能拿真实距离;H用 Floyd 求最小跳数,保证任意锚节点对之间取的是最短路径;HopSize按锚节点逐个算,基线定位时对可用锚节点取平均;最小二乘那几行是把非线性距离方程线性化,Hmat是系数矩阵,bvec是常数项。参数上,N、A、R、area四个量决定网络密度,R=30、area=100时平均邻居数不算低,适合先跑通;如果资源包默认参数不同,先保持原参数跑一次,不要混着改。
3. 多通信半径落地:把 1 跳拆成 3 级邻居表并写进跳数矩阵
3.1 多通信半径的选型理由与门限设计
单半径 DV-HOP 的邻居表是布尔的:在 R 内就是 1,在 R 外就是不可达。多通信半径把 R 分成几档,比如[R/3, 2R/3, R],节点落在第一档记 1/3 跳,第二档记 2/3 跳,第三档记 1 跳。这样做的目的不是把跳数变成物理距离,而是让跳数矩阵保留“近邻比远邻更近”的信息,减少整数跳数带来的量化误差。
门限设计不能拍脑袋。级数太少,退化成单半径;级数太多,最短路径会偏向走小半径多跳,跳数总和不增反降,而且 Floyd 的计算量和噪声都会上来。我一般先用 3 级,门限按通信半径等分,跑通后再试 4 级或 2 级做对照。锚节点稀疏时,级数多反而容易让某些锚节点对之间只剩一条绕路,HopSize 偏差更大。资源包如果已经给出半径集合,先按原集合跑;要改,就固定拓扑只改ratio,看 RMSE 和 HopSize 方差是不是同时下降。
还要注意一点:多通信半径不是把通信半径改小再广播。节点物理通信半径仍是 R,分级只是邻居发现后的代价划分。把这个概念写进注释,后面排查时才不会把“分级跳数”误当成“多轮广播”。
3.2 邻居发现与分级跳数矩阵实现
下面这个函数把坐标矩阵、最大半径和门限比例吃进去,输出分级跳数矩阵。它替代第 2 章的布尔邻接矩阵,后面 HopSize 和定位流程不用大改,只把H换掉。
function H = build_multirange_hops(P, R, ratio) % P : N x 2 节点坐标 % R : 最大通信半径 % ratio : 递增门限比例,例如 [1/3 2/3 1] N = size(P,1); D = squareform(pdist(P)); H = inf(N); for i = 1:N H(i,i) = 0; for j = 1:N if i == j continue; end level = find(D(i,j) <= R * ratio, 1, 'first'); if ~isempty(level) H(i,j) = level / numel(ratio); % 1/3、2/3、1 跳 end end end % 分级跳数也要跑最短路,否则绕路路径会污染 HopSize for k = 1:N H = min(H, H(:,k) + H(k,:)); end end代码里level取的是节点 j 落在第几档门限,level / numel(ratio)把档位归一化成跳数代价。ratio必须递增,否则find第一次命中会落在错误的档位。Floyd 那三层不能省,因为分级之后两点之间可能通过多跳更便宜,直接拿原始邻居代价当最终跳数会让锚节点 HopSize 偏大。参数上,R要和拓扑生成时的通信半径一致;ratio的长度决定级数,3 级用[1/3 2/3 1],2 级用[0.5 1],4 级可以试[0.25 0.5 0.75 1]。改完先打印H(anchor,anchor)的前几行,看锚节点之间跳数是不是非整数且没有 Inf 混进来。
3.3 半径集合与网络密度的参数表
多通信半径的效果跟网络密度强相关,参数不能只看算法名。下面这张表是我做对照时常用的扫描范围,具体数值按资源包默认拓扑调整,不要直接照搬。
| 参数 | 建议范围 | 作用 | 改动风险 |
|---|---|---|---|
N | 80~200 | 节点总数 | 太小定位样本少,RMSE 波动大 |
A | 15~30 | 锚节点数 | 少于 3 个无法定位,少于 10 个 HopSize 不稳 |
R | 20~40 | 最大通信半径 | 太小出现孤立节点,太大跳数区分度下降 |
ratio | 3 级或 4 级 | 分级门限 | 级数过多增加绕路和计算量 |
area | 80~120 | 部署区域 | 与 R 共同决定平均邻居数 |
| 随机种子 | 固定一组 | 复现拓扑 | 只换种子不换参数,结果差异可能很大 |
扫描时一次只动一个参数。先固定N=100, A=20, area=100,让R从 20 扫到 40,看连通率和 RMSE 的拐点;再固定R,让ratio在 2 级、3 级、4 级之间换。每次记录锚节点 HopSize 的均值和方差,如果方差明显变大,说明分级跳数把某些锚节点对的路径拉歪了,这时候加跳距加权才有意义。参数表不是拿来背的,是拿来排除“改了但没改对”的。
4. 跳距加权优化:锚节点 HopSize 修正与未知节点加权最小二乘
4.1 平均跳距的偏差从哪来
锚节点 HopSize 的定义是“到其他锚节点的真实距离和 / 跳数和”。问题在于跳数和距离不是线性关系:两个锚节点之间隔 5 跳,路径可能弯了三次,真实距离远小于 5 倍单跳距离;另一个锚节点对只隔 2 跳,路径接近直线,距离/跳数就更接近真实每跳距离。把所有锚节点对一视同仁做平均,等于把绕路路径和直线路径混在一起。
加权修正的思路是给“跳数少、路径直”的锚节点对更高权重,给“跳数多、绕路多”的锚节点对更低权重。常见做法是用1/(h+1)做权重,h是锚节点对之间的分级跳数。跳数越小,权重越大;加 1 是防止h=0时除零。未知节点定位时也别拿一个全局 HopSize,按自己到各锚节点的跳数做加权平均:跳数少的锚节点 HopSize 更可信,权重更高。
这里有个容易翻车的点:权重方向别反。有人把h直接当权重,结果跳数越大的锚节点对越占主导,HopSize 被绕路路径拉偏,RMSE 不降反升。判断方向很简单,打印加权前后的 HopSize 向量,如果加权后数值整体比基线更远离真实每跳距离,权重就是反了。
4.2 锚节点跳距加权修正代码
下面函数在分级跳数矩阵H和真实距离矩阵D上算加权 HopSize。D只在锚节点之间用,未知节点定位阶段不参与。
function HopSize = weighted_hopsize(P, anchor, H, D) % anchor : 锚节点索引 % H : 分级跳数矩阵 % D : 真实距离矩阵,仅锚节点间使用 A = numel(anchor); HopSize = zeros(1,A); for i = 1:A ii = anchor(i); dsum = 0; wsum = 0; for j = 1:A jj = anchor(j); if i == j || ~isfinite(H(ii,jj)) || H(ii,jj) <= 0 continue; end wij = 1 / (H(ii,jj) + 1); % 跳数少,权重高 dsum = dsum + wij * D(ii,jj); wsum = wsum + wij * H(ii,jj); end HopSize(i) = dsum / max(wsum, eps); % 加权平均每跳距离 end endwij是锚节点对权重,dsum累加加权距离,wsum累加加权跳数,两者相除得到该锚节点的 HopSize。max(wsum, eps)是后悔药,防止某个锚节点与其他锚节点全不可达时出现 NaN。参数上,anchor传索引向量,不要传逻辑向量;H必须是跑完 Floyd 的分级跳数;D必须和P的节点顺序一致。跑完打印HopSize,如果出现 0 或 Inf,先回去查锚节点连通性,不要继续往下定位。
4.3 未知节点定位与权重矩阵
未知节点定位分两步:先按跳数加权得到自己的 HopSize,再把估计距离塞进加权最小二乘。权重矩阵用对角阵,对角线元素还是1/(h+1),跳数少的锚节点方程权重高。
est = nan(N,2); for u = A+1:N idx = find(isfinite(H(u,anchor)) & H(u,anchor) > 0); if numel(idx) < 3 continue; end w = 1 ./ (H(u,anchor(idx)) + 1); % 未知节点到锚节点的权重 hs_u = sum(w .* HopSize(idx)) / sum(w); % 加权平均 HopSize d = H(u,anchor(idx)) * hs_u; % 估计距离 x1 = P(anchor(idx(1)),:); Hmat = []; bvec = []; for m = 2:numel(idx) xi = P(anchor(idx(m)),:); Hmat(m-1,:) = 2 * (xi - x1); bvec(m-1) = d(1)^2 - d(m)^2 + norm(xi)^2 - norm(x1)^2; end W = diag(w(2:end)); % 与方程对应的权重 est(u,:) = (Hmat' * W * Hmat) \ (Hmat' * W * bvec'); endw是未知节点到各锚节点的权重,hs_u是加权 HopSize,d是估计距离。线性化那几行和第 2 章基线一致,区别在最后用W做加权最小二乘。W的维度要和Hmat的行数对上,这里取w(2:end)是因为第一个锚节点被当作参考点,不进入差分方程。参数上,如果某个未知节点可用锚节点刚好 3 个,W是 2×2,仍能解;少于 3 个直接跳过,不要硬解。跑完对比基线 RMSE,如果改进版只降了一点点,先看HopSize加权前后差异,再看W是不是把异常锚节点的权重压下去了。
5. 避坑与排查:五条把改进算法跑翻车的现场记录
5.1 现象:改进后误差反而变大
现象很直接:同一张拓扑图,原始 DV-HOP 的 RMSE 是 12.4,换多通信半径加跳距加权后变成 15.1。原因通常不是算法方向错,而是跳数量纲和 HopSize 没对齐。分级跳数把 1 跳拆成 1/3、2/3、1,锚节点 HopSize 的分母变小,算出来的每跳距离变大;未知节点再用分级跳数乘这个 HopSize,距离被放大。另一种原因是权重方向反了,跳数大的锚节点对占了主导。
解决分三步:先固定拓扑,只换H,HopSize 仍用基线平均,看 RMSE 变化;再把 HopSize 换成加权版,定位仍用简单平均,看变化;最后才上加权最小二乘。每一步都打印mean(HopSize)和std(HopSize),如果加权后标准差明显变大而 RMSE 没降,说明权重把个别锚节点放大了。必要时把1/(h+1)改成1/(h.^2+1),压一压远跳锚节点的影响。
5.2 现象:跳数矩阵出现孤立节点
现象是H某一行全是 Inf,定位阶段该未知节点直接被跳过,定位成功率从 95% 掉到 70%。原因多半是通信半径相对区域太小,或者随机种子把节点甩到了边角。多通信半径不会制造孤立节点,但分级门限会让“刚好在 R 边缘”的邻居关系变得更脆。
解决先跑连通性检查,把最大连通分量找出来,只在这个分量里评估定位误差,不要拿全网节点数当分母。下面这段排查脚本放在跳数矩阵生成之后。
% check_topology.m isolated = sum(~isfinite(H),2) == N-1; fprintf('孤立节点数: %d\n', sum(isolated)); comp = conncomp(graph(isfinite(H) & H > 0)); % 需要 Bioinformatics Toolbox 时可换手写 BFS fprintf('连通分量数: %d, 最大分量节点数: %d\n', max(comp), sum(comp==mode(comp))); bad = find(any(~isfinite(H(anchor,anchor)),2)); fprintf('与其他锚节点不连通的锚节点: %s\n', mat2str(bad));如果孤立节点集中在边角,调大R或缩小area再跑;如果锚节点之间不连通,减少锚节点数量或换种子,不要硬用不可达锚节点算 HopSize。
5.3 现象:锚节点跳距出现 NaN
现象是HopSize里混进 NaN,后面最小二乘解全变 NaN,RMSE 变成 NaN。原因通常是锚节点对之间不可达,hsum为 0,除法没保护;或者H里有 Inf 参与了累加。分级跳数之后,如果门限比例写错,比如ratio不是递增,find可能返回空,邻居关系缺失,也会把不可达放大。
解决是在 HopSize 循环里加isfinite和>0判断,分母用max(hsum, eps),并且在定位前筛掉HopSize<=0或非有限的锚节点。不要用HopSize(isnan(HopSize))=0这种掩耳盗铃的写法,0 会直接进最小二乘。更稳妥的做法是打印每个锚节点参与计算的锚节点对数量,少于 2 对就标记为低可信,定位时降权或剔除。
5.4 现象:换随机种子结果差异巨大
现象是种子 7 跑出 RMSE 11.8,种子 8 跑出 16.3,看起来像算法时好时坏。原因不是算法玄学,而是锚节点位置和网络连通性对随机拓扑太敏感,尤其A=15、N=80时,某个锚节点落在边角就会把 HopSize 拉偏。单次实验的 RMSE 不能拿来证明改进有效。
解决是固定一组种子,比如 1 到 30,每个种子下同时跑基线和改进版,报告均值、标准差和 CDF。对比时看同一 seed 的差值,不要拿改进版 seed 7 去比基线 seed 8。如果时间有限,至少跑 10 个种子,并且把锚节点索引固定成随机抽取而不是固定前 A 个,避免锚节点全挤在左上角。
5.5 现象:多半径参数扫描越扫越慢
现象是ratio从 3 级扫到 5 级,单次运行从 2 秒涨到 20 秒,Floyd 成了瓶颈。原因是分级跳数矩阵每换一次门限就要重建一次N×N距离矩阵和 Floyd 最短路,N=200时三重循环很吃力。更糟的是在循环里反复pdist,距离矩阵根本没变。
解决是把拓扑生成和距离矩阵缓存到循环外,只重建H;N大于 200 时用稀疏图最短路,别硬跑 Floyd。参数扫描按“先粗后细”走:先扫R的 3 个值,再扫ratio的 2 级、3 级,最后才扫种子。每次扫描前清空无关变量,保存中间HopSize和 RMSE,别等扫完再回头找哪组参数跑崩了。
6. 验收与进阶:用 RMSE、CDF 与参数扫描把改进幅度钉死
6.1 三个验收指标
只看平均 RMSE 不够,锚节点稀疏时一条长尾就能把结论带偏。我一般同时看三个量:RMSE 看整体误差,CDF 看 80% 分位误差,定位成功率看有多少未知节点根本没解出来。CDF 的写法很简单,把每个未知节点的定位误差排序,画累计概率;如果改进版均值降了但 90% 分位没降,说明它只优化了容易定位的节点,边角节点仍然翻车。
| 指标 | 计算对象 | 用途 |
|---|---|---|
| RMSE | 成功定位的未知节点 | 看整体误差水平 |
| CDF 80% 分位 | 每个未知节点误差 | 看长尾是否被压住 |
| 定位成功率 | 可用锚节点≥3 的未知节点 | 看改进是否牺牲了覆盖率 |
6.2 参数扫描的最小脚本
扫描时把ratio做成元胞数组,拓扑和距离矩阵只生成一次,循环里只换分级门限。下面这段是骨架,run_locator换成资源包里的定位主流程即可。
ratios = {[1/3 2/3 1], [0.25 0.5 0.75 1], [0.5 1]}; rmse = zeros(1,numel(ratios)); for q = 1:numel(ratios) H = build_multirange_hops(P, R, ratios{q}); HopSize = weighted_hopsize(P, anchor, H, D); est = run_locator(P, anchor, H, HopSize); % 资源包定位函数 rmse(q) = eval_rmse(est, P, A+1:N); % 统一误差评估 end disp(table((1:numel(ratios))', rmse', 'VariableNames', {'case','RMSE'}));6.3 一个具体技巧:先筛连通锚节点再加权
锚节点稀疏时,先别急着加权,先把“到当前未知节点连通的锚节点”筛出来。未知节点到某锚节点跳数为 Inf,这个锚节点就不该进 HopSize 加权,也不该进最小二乘。我的习惯是每个未知节点先算可用锚节点集合,少于 3 个直接标记失败,再在可用集合里做1/(h+1)加权。这样 CDF 的长尾会短一截,定位成功率也不会被不可达锚节点拖下去。
有次我没筛连通锚节点,改进版 RMSE 看着降了,CDF 尾巴却拖到 40 米开外,后来每次跑完都先看连通率和可用锚节点数,再谈加权。希望帮到你。
本文还有配套的精品资源,点击获取