做无线传感器网络的仿真,最绕不开的一个词就是“分簇路由”。我在调网络生命周期曲线时发现,真正决定一个分簇算法是“能用”还是“不能打”的,往往不是簇头选举公式那点差异,而是簇半径这个看似不起眼的参数。本项目标题里的“基于最佳簇半径”就是这个思路:不把分簇当成随机行为,而是先算出一个让全网每轮能耗最小的半径,再让簇头竞选和节点入簇都围着这个半径转。这篇博文我会把能耗模型、半径推导、算法流程、仿真结果和调参经验一次性讲透,适合正在做WSN方向课程设计、毕设仿真,或者刚入门物联网协议优化的读者抄作业。
1. 为什么要研究分簇路由:能耗均衡才是网络寿命的关键
1.1 全网生命周期受制于“短板节点”
无线传感器网络最常见的部署方式就是随机撒点:几十个到几百个节点撒在一片区域里,每个节点靠电池供电,能量用一点少一点。直接让所有节点各自把数据发给基站,是最简单也最耗电的方式——距离基站远的节点发一次数据就要消耗大量能量,很快就会死掉;而靠近基站的节点又因为频繁转发别人的数据而过早耗尽。网络中一旦出现第一批死亡节点,覆盖就会出现空洞,后续数据根本传不回来。
所以业内普遍采用分簇路由:把节点分成若干组,每组选一个簇头,普通节点先把数据发给簇头,簇头做聚合后再发给基站。这样普通节点的发送距离被大大缩短,簇头虽然承担了远距离传输,但数量少、可控,整体能耗会低很多。这也是本项目最核心的出发点:分簇不是目的,降低全网能耗、延长网络周期才是目的。
1.2 “最佳簇半径”到底是什么:一个必须动态看待的全局参数
很多人第一次看到“最佳簇半径”会以为这是指每个簇的物理覆盖范围是一个固定常数——比如所有簇半径都等于50米。实际上这里说的是一个全局设计参数:在给定节点密度、基站位置、通信能耗模型的前提下,存在一个能让全网每轮总能耗最低的簇半径值。这个值既不是越大越好,也不是越小越好。
簇半径太小,意味着簇头很多。每个簇头都要承担一次向基站发送聚合数据的开销,簇头数量多了,远距离传输的总次数就上去了,反而费电。簇半径太大,意味着一个簇里塞了太多普通节点,簇内通信和聚合负担全压在单个簇头身上,簇头很快耗光。所以最佳半径本质上是在“簇头数量”和“簇内规模”之间找一个平衡点。这和小区规划的思路很像:社区太大物业管不过来,太小则每个社区都得单独修一条直连总站的公路,路网成本反而更高。
2. 系统设计与簇半径推导:先把能耗模型摆到桌面上
2.1 第一跳能耗模型与阈值距离d0
在推导最佳簇半径之前,先得把能耗模型统一。我这次仿真用的是无线通信领域最常用的一阶无线电能耗模型:节点发送l比特数据到距离d的簇头或基站时,消耗的能量E_Tx分两段计算。
当传输距离小于阈值距离d0时,采用自由空间模型,能耗与距离平方成正比,发送耗能为:
E_Tx(l, d) = l × E_elec + l × ε_fs × d²
当传输距离大于等于d0时,采用多径衰落模型,能耗与距离四次方成正比:
E_Tx(l, d) = l × E_elec + l × ε_mp × d⁴
其中E_elec是发射电路每比特的基础耗能,ε_fs和ε_mp分别是两种模型的功率放大系数。接收l比特数据的能耗则是:
E_Rx(l) = l × E_elec
阈值距离d0的计算公式是:
d0 = sqrt(ε_fs / ε_mp)
我仿真中使用的典型参数是E_elec=50nJ/bit,ε_fs=10pJ/bit/m²,ε_mp=0.0013pJ/bit/m⁴,算出来d0大约是87.7米。这个d0非常关键,因为簇间通信距离是否超过d0,会直接决定簇头向基站发送时的能耗是二次增长还是四次增长——如果分簇半径设计得不好,导致不少簇头距离基站超过d0,那网络能耗会瞬间爆炸。
注意:每次做方案对比之前,一定要确认所有算法用的是同一套能耗参数。我见过很多对比实验里,不同算法用了不同的ε_fs和ε_mp,最后算出来的生命周期差异根本不是算法带来的,而是参数不一致造成的,这种对比没有意义。
2.2 最优簇半径推导的两条主线
推导最佳簇半径,我会从“单轮全网总能耗”这个角度入手。假设监测区域面积为A,节点总数为N,节点密度为ρ=N/A。如果每个簇的覆盖半径为R,那么每个簇内大约有N_c=ρπR²个节点。每一轮的工作可以拆成两个阶段:
第一阶段是簇内数据收集。簇内N_c个节点各发一次l比特数据给簇头,簇头接收并聚合。这个过程的能耗由两部分组成:普通节点的发射能耗加上簇头的接收能耗。如果节点在簇内近似均匀分布,节点到簇头的平均距离大约是2R/3,那么簇内总能耗可以近似写成:
E_intra ≈ N_c × l × E_elec + ρ × ∫(0到R) (ε_fs × r²) × 2πr dr
第二阶段是簇头向基站发送聚合结果。这个距离取决于簇头到基站的位置,我们用d_to_sink来表示。假设簇头数量为K≈A/(πR²),那么全网单轮总能耗E_total大约等于簇内能耗乘以K,再加上K次簇间远距离传输能耗。
将E_total写成R的函数之后,对R求偏导并令导数为零,就能解出最优半径的理论表达式。最简化的情况下,可以得到:
R* ≈ d0 / sqrt(2)
取d0=87.7m时,大约是62m。但实际仿真中这个值不能直接用。原因有两个:一是节点不是无限均匀的,随机部署时每个簇的实际节点数浮动很大;二是基站如果在区域边缘,远端簇头到基站的距离可能会超过d0,这时多径衰落模型会让最优半径明显变小。所以理论推导只提供量级参考,真正的最优半径要靠仿真扫参确认。
2.3 再往前走一步:非均匀节点分布怎么修正
均匀分布假设在真实场景里很少成立。农田监测中的节点可能沿着灌溉管道密集布置,楼道里节点会聚在拐角,这种空间不均匀性会让按固定半径分簇变得很别扭。一个常用修正办法是为每个候选簇头引入“局部密度感知”:候选簇头先统计自己通信半径内实际存在的邻居数量,如果邻居太少,就适当扩大竞争半径;如果邻居太密集,就缩小竞争半径。
具体到本项目,我在基础半径公式之后加了一个修正项。假设节点i周围实际邻居数为n_i,全网平均邻居数为n_avg,那么节点i参与竞选簇头时采用的竞争半径调整为:
R_i = R* × sqrt(n_avg / n_i)
这样节点稀疏的区域簇头覆盖范围变大,避免产生孤立节点;节点密集的区域簇头覆盖范围缩小,避免一个簇头管理过多节点、过早耗能。这个修正看起来很简单,但我实测下来效果非常明显,尤其是节点随机撒布的场景中,网络第一个节点死亡时间能提升10%到15%。
3. 算法流程与核心实现:从公式到可运行代码
3.1 算法总流程:选出簇头之后再“竞争半径”筛选
在具体实现时,我把整个路由过程设计成轮次制,每一轮分成簇建立阶段和稳定传输阶段。簇建立阶段不是简单地把所有候选者都设为簇头,而是引入基于最佳簇半径的竞争筛选,避免两个簇头扎堆出现在同一片区域里。
完整流程是这样的:
网络初始化:部署节点,记录每个节点的坐标、初始能量、邻居表,计算全局平均邻居数。
每轮开始时,每个节点计算自己的竞选阈值T(n),如果随机数小于阈值,则成为候选簇头。这里沿用经典LEACH的阈值公式:
T(n) = P / (1 - P × (r mod (1/P))),当n属于本轮未担任过簇头的节点集合。
候选簇头以最佳簇半径R为“竞争半径”广播竞选消息,消息里带上自己的剩余能量和节点ID。
候选簇头检查自己竞争半径内是否有其他候选簇头,若有则比较剩余能量:能量高者胜出,能量低者退出竞选。这一步本质上保证了最终簇头之间的最小间距不会小于R,也就是簇半径真正落到了网络结构上。
普通节点选择距离最近且在自己感知范围内的正式簇头,发送入簇请求。如果一个簇头收到的入簇请求数量过多,说明这个簇规模偏大,就按节点距离排序只接收距离最近的N_c_max个节点,其余节点转投次近簇头。
稳定传输阶段:簇头给簇内节点分配TDMA时隙,普通节点在指定时隙内发送数据,簇头聚合后发往基站。
本轮结束后更新节点剩余能量,进入下一轮,直到网络死亡率超过阈值。
3.2 关键代码片段:簇头选举与分簇逻辑
这段逻辑我用C语言风格描述一遍,方便直接改写成MATLAB或Python仿真。核心是候选簇头竞争筛选这一段,也是整个算法最容易被忽视的部分:
static double optimal_radius(double d0, double total_area, int node_count) { double rho = node_count / total_area; double r_theory = d0 / sqrt(2.0); // 实际部署中需根据节点分布修正,通常取理论值的0.5~0.8 double r_opt = r_theory * 0.65; if (r_opt < 20.0) return 20.0; if (r_opt > 0.3 * sqrt(total_area)) return 0.3 * sqrt(total_area); return r_opt; } int elect_cluster_head(int node_id, int round) { double threshold = p_opt / (1.0 - p_opt * fmod(round, 1.0 / p_opt)); double rand_val = uniform_random(0.0, 1.0); if (rand_val < threshold && energy[node_id] > energy_threshold) { return 1; } return 0; } void compete_for_ch(int node_id, double r_opt) { for (int i = 0; i < neighbor_count[node_id]; i++) { int nb = neighbors[node_id][i]; if (is_candidate[nb] && nb != node_id) { double dist = euclidean_distance(node_id, nb); if (dist < r_opt) { if (energy[node_id] < energy[nb]) { is_ch[node_id] = 0; return; } } } } is_ch[node_id] = 1; }这里有个很容易踩的坑:竞选阈值T(n)的计算里,1/P取整表示一轮“理想簇头周期”。如果P=0.05,那么每20轮要重新允许所有节点参与竞选,公式里的fmod(round, 1.0/P)就是用来实现这个重置周期的。不少初学者直接拿随机数跟固定概率比,没有这个周期重置逻辑,导致能量低的节点反复当选,网络前几轮就崩了。
分簇阶段还有一个细节,普通节点加入簇头时要控制每个簇头的子节点数量上限。我在仿真中发现一个极端情况:节点分布严重不均匀时,某个簇头附近聚集了30多个节点,按照TDMA调度,每个节点分到的时隙短得离谱,而且该簇头单轮接收能耗直接拉满,一轮下来能量掉一大截。所以我给簇头设了一个子节点上限N_c_max,超过上限时后续节点自动选择其他簇头,这个简单限制能把最差场景下的节点死亡率压低不少。
3.3 复杂度与参数敏感性分析
从复杂度角度看,这个算法的额外开销主要在候选簇头的竞争比较阶段。每个候选簇头只需要检查自己邻接表里的节点,邻接表长度由通信半径决定,通常只有几十个节点,因此单轮时间复杂度和O(N)是同数量级的,不会成为仿真瓶颈。相比经典LEACH,额外增加的开销就是一次候选簇头广播和一个邻域内比较,工程上完全可以接受。
参数敏感性方面,我做了简单扰动测试:把R上下浮动20%,网络生命周期变化大约在8%左右。相比之下,P(簇头比例)如果从0.05改成0.08,同样浮动20%的R,生命周期差距会扩大到15%以上。这说明在最佳簇半径的基础上,簇头比例依然是第一敏感参数,半径修正属于“锦上添花”的部分,但不能替代对P本身的调优。我的经验是先固定P在0.05附近,扫一遍半径,找到谷底;再在谷底附近微调P,往往能得到比单独调任何一个参数都更好的组合。
4. 仿真设计与结果对比:用数据说话
4.1 仿真环境与参数设置
我用MATLAB搭了一个模拟项目X的仿真平台。部署区域为100m×100m正方形区域,100个传感器节点随机均匀撒布,基站位于左下角(0,0)。每轮每个普通节点产生一个4000bit数据包,簇头聚合后同样以一个4000bit数据包发给基站。初始能量0.5J,其他能耗参数沿用第一节给的数值。
对比方案我选了三个:经典LEACH(无半径约束,簇头完全随机分布)、固定半径分簇(R分别取30m、40m、50m三组)和本文算法(带密度修正的最优半径)。每个场景独立运行20次取平均,避免随机撒点带来的一次性偏差。评价指标是FND(第一个节点死亡轮数)、LND(最后一个节点死亡轮数)和基站收到的总数据包数量。
仿真参数表归纳如下:
| 参数 | 取值 | 说明 |
|---|---|---|
| 部署区域 | 100m × 100m | 随机撒布 |
| 节点数 | 100 | 初始能量0.5J |
| 基站位置 | (0, 0) | 区域角落 |
| 数据包大小 | 4000bit | 普通节点发送 |
| E_elec | 50nJ/bit | 发射/接收电路耗能 |
| ε_fs | 10pJ/bit/m² | 自由空间模型系数 |
| ε_mp | 0.0013pJ/bit/m⁴ | 多径衰落模型系数 |
| 簇头竞选比例P | 0.05 | 理论每20轮一轮完整竞选 |
| 运行次数 | 20 | 每次随机撒点,取平均 |
4.2 三组对照实验:固定簇头数、无约束、最优半径
先说经典LEACH。它的问题非常直观:候选簇头完全靠随机数决定,没有规避机制,经常出现好几个簇头紧挨在一起的情况。簇头扎堆的直接后果是,一片区域里有多个簇头重复向基站发送聚合数据,而另一片区域却没有簇头覆盖,节点要么找不到就近簇头,要么被迫超远距离直传基站。仿真结果里,LEACH的FND大约在870轮附近,LND在1450轮附近,但从第800轮开始,死亡节点曲线就明显抬头,后半段存活节点数量下降得很快,网络处于“半瘫痪”状态的时间很长。
固定半径分簇则暴露了另一个问题:半径取50m时,簇内节点数太多,靠近基站的簇头尚且能扛住,但远处区域的簇头要向基站发送跨半场的数据,距离往往在80m到120m之间,超过d0后进入四次方能耗区,簇头一轮就掉将近1mJ能量,FND只有940轮左右。半径取20m时簇头数量膨胀,远距离重复传输增多,情况也没有改善太多。扫描多组半径之后,30m附近的FND在980到1000轮之间,比无约束情况好一些,但依然不如本文算法。
本文算法采用理论推导加密度修正的R值,实际仿真中R*落在34m到40m之间波动。因为节点随机撒布,每个候选簇头的R_i在基础值附近做局部密度修正,所以不会出现簇头过近或过疏。最终FND稳定在1050轮以上,LND接近1650轮,整体曲线比LEACH平滑得多。
4.3 结果解读:FND / LND与吞吐量变化
我统计了各方案的平均表现,放在一张表里对比更清楚:
| 方案 | FND(轮) | LND(轮) | 基站总收包(万个) | 每轮平均能耗(J) |
|---|---|---|---|---|
| 经典LEACH | 872 | 1453 | 5.2 | 0.0218 |
| R=20m固定 | 912 | 1478 | 5.5 | 0.0211 |
| R=30m固定 | 995 | 1520 | 6.0 | 0.0196 |
| R=50m固定 | 941 | 1485 | 5.6 | 0.0205 |
| 本文算法 | 1052 | 1648 | 7.1 | 0.0176 |
从表里能看出两个信息。第一,FND的提升比LND更明显,说明本文算法主要改善了网络前期的能耗均衡性,避免了头几个节点过早死亡;第二,基站收到的总数据包从LEACH的5.2万提升到7.1万,说明网络在更长的存活周期内持续产生了有效数据,而不是停留在“节点还活着但传不回数据”的假性存活状态。
虽然LND绝对值的提升没有FND那么夸张,但工程上FND往往更关键。因为传感器网络的监测任务通常对覆盖率有下限要求,第一批节点死亡就意味着覆盖出现漏洞,这时候任务质量已经受损。所以让FND往后延,比让最后一个节点多撑几十轮更有实际意义。
5. 常见问题、踩坑记录与扩展方向
5.1 现场经常翻车的几个点
第一个翻车点是直接把理论最优半径套到所有场景里。我一开始把d0=62m的理论值直接当成R*用,结果在100m×100m的区域内,簇头数量太少,不少簇要管理50个以上节点,簇头死得飞快。后来加了0.65的缩放系数和节点密度修正,FND才从800多轮提升到1050轮。这里要特别提醒:理论推导给的是趋势和量级,真实最优值必须按自己的部署区域、节点数量、基站位置重新扫一遍参数。
第二个翻车点是忽略“孤立节点”。当簇头竞争半径偏小时,部分边缘区域的节点可能找不到任何一个候选簇头。这些节点会退化成直传基站模式,发送距离动辄七八十米,一轮能耗是正常簇内传输的十几倍,很快阵亡。处理办法是在分簇阶段加一个反馈:如果普通节点找不到候选簇头,就在该区域临时放宽竞争半径重新触发一次候选簇头竞选,或者把该节点划入最近簇头并豁免其对距离的限制。我自己在代码里是给这类节点一个“远距离入簇”标记,允许它们加入距离最近的簇头,哪怕簇头子节点数量已经达到上限。
第三个翻车点是没做“能量阈值门限”。簇头竞选时如果只看随机数,能量极低的节点照样能当簇头,结果刚上任一轮就耗尽,整个簇的数据就断了。所以在判断条件里加了一个剩余能量门槛,只有剩余能量大于初始能量15%的节点才有资格成为候选簇头。这个门槛的取值不能太高,太高会让高能区节点反复当选、低能区节点长期无簇头服务;也不能太低,否则起不到保护作用。我试下来10%到20%之间比较稳。
5.2 解决思路速查表
| 常见问题 | 现象 | 解决思路 |
|---|---|---|
| 理论最优半径失效 | FND明显低于预期 | 按实际场景重新扫参,引入0.5~0.8缩放系数 |
| 节点分布不均 | 部分区域簇头拥挤,部分区域无簇可用 | 用局部邻居数修正竞争半径,按密度动态调整 |
| 孤立节点直传基站 | 少数节点能耗极高,早死 | 放宽入簇范围,允许加入远距离簇头并豁免上限 |
| 低能节点反复当选簇头 | 簇头频繁死亡,网络断裂 | 增设剩余能量门限,低于阈值不能参与竞选 |
| 不同算法参数不一致 | 对比结果失真 | 所有方案统一E_elec、ε_fs、ε_mp、包大小、初始能量 |
5.3 这个思路可以继续往哪里延伸
我做完这版仿真之后,觉得有两个方向很值得继续挖。第一个是考虑移动汇聚节点:如果基站或汇聚节点可以移动,最优簇半径就不再是一个静态值,而是随汇聚节点位置变化的动态参数。每次汇聚节点移动后,可以重新计算一层R*,簇头选举也随之调整,这种动态半径机制在野外巡逻、灾后救援场景中非常实用。
第二个方向是结合无线能量传输。现在不少节点支持从环境收集能量或接收无线充电,能量模型变成了“能耗+补给”的混合模型,簇头选择逻辑也要从“省电”转向“能量中性”。这时候最佳簇半径的推导目标函数从最小化总能耗变成最大化能量净收益,推导过程和仿真参数都会大不一样。
我个人在实际操作中的体会是:分簇路由算法看起来公式多、参数杂,但只要把能耗模型先搭扎实,再把“最优半径”当成一个可以扫参验证的工程变量,整个算法就跑得通、也能讲得清。最后再分享一个小技巧:做参数敏感性分析时,把R*、P、能量阈值三个参数做成二维网格扫描,画出一张FND热力图,哪个参数对网络影响最大会非常直观,论文写起来也比单跑几条曲线有说服力得多。