1. 项目概述
1.1 为什么要做LEACH协议的比较研究
做无线传感器网络(WSN)方向的研究,很难绕开LEACH协议。它是分簇路由协议的开山之作,1999年由MIT的Heinzelman等人提出,论文至今被引超过两万次。只要涉及网络生命周期优化、能效均衡、数据聚合这些关键词,LEACH几乎总是被拿来当第一个基线方案。
但这个协议有个很有意思的现象——它解决了一个问题,同时又制造了一堆新问题。LEACH的核心思路是随机选取簇头、轮换分担能耗,这个思路在当时确实开创性。可它随机选簇头的时候完全不看节点剩余能量,不看节点位置分布,导致的结果就是:低能量节点可能被选为簇头然后快速死亡,高密度区域可能同时冒出多个簇头造成信道冲突。于是后面出现了大量改进版本,LEACH-C是其中流传最广的一个,引入集中式簇头选择,把决策权交给基站。TS-I-LEACH则是在这个基础上又往前推了一步,在簇头选择和数据传输环节同时做优化。
这篇博文把这三种协议放在一起系统比较,并用Matlab完成全部仿真实现,目标很明确:一是把三个协议的机制彻底讲透,二是给出可以直接复现的完整代码框架,三是用同一套仿真场景做横向对比,把生命周期、能耗分布、吞吐量这些指标的真实差异量化出来。如果你正在做WSN相关课程设计、毕业设计,或者准备投论文需要对比实验,这篇文章基本可以让你少走两三个月的弯路。
1.2 基础概念:无线传感器网络和分簇路由到底在解决什么问题
无线传感器网络由大量微型传感器节点组成,这些节点靠电池供电,部署在监控区域内,通过无线通信协作完成环境感知、数据采集和传输任务。节点一旦布下去,基本没有换电池的可能,能源的稀缺性决定了所有协议设计都要围绕"省电"两个字展开。
但省电不是唯一目标。协议还要保证数据能及时、可靠地传回基站。这就形成了无线传感器网络中最核心的矛盾——能量受限但任务不能中断。
分簇路由正是为了解决这个矛盾。它的基本思想是:把整个网络的节点划分成若干小组(簇),每个簇选出一个簇头(Cluster Head),普通节点只把数据发给自己的簇头,簇头负责把本簇数据聚合后转发给基站。这样做的好处一目了然:普通节点不需要直接和远处的基站通信,发射功率可以大幅降低。簇头的能耗虽然高,但通过周期性轮换,让所有节点轮流"坐庄",整体能耗就能摊平。这个"通过结构换能耗"的设计,是理解LEACH全系协议的基座。
2. 三种协议的设计思路深度拆解
2.1 经典LEACH:随机轮换,用"碰运气"换能耗均衡
LEACH协议的工作过程分为回合(Round),每个回合又分成两个阶段:建立阶段和数据传输阶段。
建立阶段的核心是簇头选举。每个节点在每轮开始时生成一个0到1之间的随机数,如果这个数小于等于阈值T(n),该节点就宣布自己是簇头。T(n)公式如下:
T(n) = P / (1 - P * (r mod (1/P))),当 n ∈ G T(n) = 0,其他情况其中P是簇头比例(通常取5%),r是当前轮数,G是最近1/P轮内没有当过簇头的节点集合。这个公式的逻辑很精妙:它保证了每1/P轮能循环一遍,让每个节点大约每隔20轮就能当一次簇头,不会出现某个节点长期"在岗"被耗死的情况。
当选出簇头后,簇头通过CSMA协议广播ADV消息宣布身份,普通节点根据收到信号的强度决定加入哪个簇(一般认为信号越强距离越近)。节点确认入簇后,簇头会采用TDMA时分复用方式给每个成员节点分配时隙,成员只在属于自己的时隙内发送数据。看这个流程你就会发现,LEACH压根不关心节点的具体位置和能量状态,决策依据就一个随机数,这既是它简单可靠的来源,也是它后面被各种改进版本"围攻"的命门。
2.2 LEACH-C:基站统一调度,从"民主选举"到"中央集权"
LEACH-C(Centralized LEACH)的改进思路非常直接:既然节点自己选簇头容易选不好,那干脆让基站来选。基站手里有全局信息——每个节点的位置坐标和实时剩余能量,做决策的基础比任何单个节点都要充分。
LEACH-C的工作流程变成了这样:每轮开始时,所有节点向基站发送自己的位置和能量信息。基站基于这些信息运行模拟退火算法,找出一组能耗最低的簇头组合和分簇方案。选择的标准是:节点剩余能量要高于网络平均能量,才有资格当选簇头。选定后,基站把所有节点的"任务卡"广播出去,告诉每个节点谁是簇头、自己在哪个簇、分配了哪个TDMA时隙。节点不需要做任何决策,照着执行就行。
这个模式的优势非常明显:第一,基站能看到全局能量分布,选出来的簇头几乎不会落在低能量节点上,避免了LEACH那种"快没电了还要被迫上岗"的窘境。第二,簇的空间分布更均匀,不会出现多个簇头扎堆的浪费情况。第三,簇头数量可以精确控制。不过代价也有——每个节点每轮都要和基站通信一次上报状态信息,这个额外的通信开销在小规模网络里影响不大,但节点数量上千之后会变成不可忽视的负担。
2.3 TS-I-LEACH:在LEACH-C的肩膀上继续做文章
TS-I-LEACH的命名可以从两个方向理解:TS代表Threshold-Sensitive(阈值敏感),I代表Improved(改进)。它的设计定位是比LEACH-C更高一档的增强版,核心优化方向集中在三个方面。
第一个方面是能量自适应的簇头预选机制。不像LEACH纯粹随机,也不像LEACH-C必须所有节点统一上报再由基站全局计算,TS-I-LEACH设计了一套两级筛选逻辑:节点先根据自身剩余能量判断是否低于某个动态能量阈值(阈值为网络平均剩余能量乘一个调整系数),低于阈值的节点主动退出候选簇头竞争,直接进入休眠或普通成员模式,高于阈值的节点才参与簇头竞选。这就从源头上杜绝了低能量节点当选簇头的情况。
第二个方面是数据传输阶段的阈值报告机制。普通LEACH是TDMA时隙一到就发数据,TS-I-LEACH引入了事件驱动的思想——节点只有在监测数据超过硬阈值Ht,或者超过上一次上报值加上软阈值St时,才真正向外发送数据。硬阈值的意义是触发第一次上报,软阈值的意义是抑制重复上报。这两个阈值的配合能够大幅压缩数据发送量。在一个温度监测场景中,如果温度没变化或者变化很小,节点根本不需要浪费能量发数据。
第三个方面是簇间通信的改进。传统LEACH里所有簇头都直接和基站通信,远离基站的簇头因为距离远,能耗特别高,这也是LEACH系协议一直存在的"远端失效"问题。TS-I-LEACH通过分区分层的路由机制,让远端簇头先把数据转发给相邻近的簇头或中转节点,再由它们统一汇总发给基站,相当于在簇头和基站之间加了一级"传输接力"。这样就能显著拉平不同位置节点的能耗差异。
整体来看,TS-I-LEACH不是推翻LEACH另起炉灶,而是在LEACH和LEACH-C的框架内逐层打补丁——簇头选择更智能,数据上报更精简,传输路径更合理。三管齐下,网络生命周期自然会有明显的提升。当然,换来这些性能的代价是协议逻辑更复杂、参数调优更难、Matlab代码量直接翻倍。这也是比较研究最有价值的地方——你做对比的时候,看到的不仅仅是"谁更好",还能看清楚它到底是靠什么手段变好的。
3. 核心原理解读与Matlab仿真框架搭建
3.1 一阶无线电能耗模型:所有仿真的基石
做协议仿真,第一步要确定的就是能量计算模型。学术界最常用的是Heinzelman提出的一阶无线电模型,它把节点通信的能量消耗拆成了发射端、接收端和功率放大三部分。
发射端消耗的计算公式很关键:
发送 L bit 数据到距离 d 处: - 若 d < d0(自由空间模型): E_Tx(L, d) = L * E_elec + L * ε_fs * d² - 若 d ≥ d0(多径衰落模型): E_Tx(L, d) = L * E_elec + L * ε_mp * d⁴ 接收 L bit 数据: E_Rx(L) = L * E_elec参数含义和典型取值如下表:
| 参数 | 含义 | 典型取值 |
|---|---|---|
| E_elec | 每bit数据发射/接收电路的能耗 | 50 nJ/bit |
| ε_fs | 自由空间模型功率放大系数 | 10 pJ/bit/m² |
| ε_mp | 多径衰落模型功率放大系数 | 0.0013 pJ/bit/m⁴ |
| E_DA | 数据聚合能耗 | 5 nJ/bit |
| d0 | 距离阈值,sqrt(ε_fs/ε_mp) | 约87.7m |
这个模型里有几个特别值得留意的点。d0的计算方式是d0 = sqrt(ε_fs/ε_mp),这是两个功率放大系数比值的平方根。当传输距离小于d0时,用自由空间模型,能耗随距离平方增长;超过d0后,多径衰落占主导,能耗随距离四次方增长。这意味着同样发一个数据包,距离从50m增加到100m,能耗增加的速度会骤变。在协议设计里,控制簇内节点到簇头的距离不要越过d0,往往比单纯选一个"近一点"的簇头更加重要。
节点每轮的总能耗可以简化为:发送时隙把所有成员数据收集起来约需要 n 次接收能耗加上一次聚合能耗 n*E_DA,然后做一次远距离传输。簇头是整个簇里能耗最大的角色,所以协议设计的核心矛盾永远是——谁当簇头,当多久,怎么轮换。
3.2 Matlab仿真主循环设计:不写脚本,写框架
做仿真最忌讳的就是把代码写成一坨顺序执行的脚本。特别是要对比三种协议,如果每改一个协议就要大改一遍主程序,工作量翻倍且容易引入变量。正确做法是拆模块、做接口。
我的代码组织方式是这样的:
% 主文件: main.m % 1. 参数设置 % 2. 网络初始化(节点部署) % 3. 循环执行各轮(Round) % 4. 结果统计与绘图核心数据结构和初始化代码如下:
%% 网络参数设置 % 仿真场景:100m x 100m 区域内随机部署100个节点 Area = [100 100]; % 区域大小 N = 100; % 节点数量 Sink = [50 50]; % 基站位置,位于区域中心 P = 0.05; % 簇头概率,LEACH官方推荐值 E0 = 0.5; % 节点初始能量,单位 J E_elec = 50e-9; % 电路能耗,单位 J/bit eps_fs = 10e-12; % 自由空间功率放大系数 eps_mp = 0.0013e-12; % 多径衰落功率放大系数 EDA = 5e-9; % 数据聚合能耗 packetLen = 4000; % 数据包长度,单位 bit ctrlPacketLen = 200; % 控制包长度,单位 bit %% 节点初始化 % 生成位置信息 node(:,1) = rand(N,1) * Area(1); % x坐标 node(:,2) = rand(N,2) * Area(2); % y坐标 % 初始化所有节点的能量和状态 S = struct(); for i = 1:N S(i).x = node(i,1); S(i).y = node(i,2); S(i).energy = E0; S(i).alive = 1; % 1表示存活,0表示死亡 S(i).role = 0; % 0表示普通节点,1表示簇头 S(i).cluster = 0; % 所属簇头ID end这里有几个实操经验分享给你。第一,随机种子在初始化前用 rng() 固定,否则每次跑出来的对比数据差异很大,没法做横向比较。第二,节点坐标生成后要检查是否有重叠,虽然概率很小,但位置完全重叠的节点会导致分簇和能耗计算出现异常。第三,建议把节点和基站的位置用 scatter 函数画出来看一眼再跑主循环,第一次跑仿真的时候,95%的问题出在初始化阶段。
主循环的核心逻辑如下:
%% 主循环 rmax = 2000; % 最大仿真轮数 packetsToSink = 0; % 统计发送到基站的数据包数量 for r = 1:rmax % 检查是否所有节点都已死亡 if sum([S.alive]) == 0 disp(['All nodes dead at round: ', num2str(r)]); break; end % 节点状态清零(每轮重新选举簇头) for i = 1:N if S(i).energy > 0 S(i).alive = 1; else S(i).alive = 0; end S(i).role = 0; end % ---------- 簇头选举阶段 ---------- if strcmp(protocol, 'LEACH') % LEACH: 节点分布式随机选举 for i = 1:N if S(i).alive == 1 temp_rand = rand; if temp_rand <= threshold(i, P, r, N, S) S(i).role = 1; end end end elseif strcmp(protocol, 'LEACHC') % LEACH-C: 基站集中式选举,模拟退火算法 S = centralized_cluster(S, N, Sink, r); elseif strcmp(protocol, 'TSILEACH') % TS-I-LEACH: 能量阈值预筛选 + 集中式选举 S = tsi_leach_cluster(S, N, P, r); end % ---------- 数据传输阶段 ---------- % ... 根据簇头情况分配TDMA时隙,完成数据传输 % ... 更新每个节点的能量 % ---------- 统计信息 ---------- numAlive(r) = sum([S.alive]); energyTotal(r) = sum([S.energy]); packToSink(r) = packetsToSink; end需要特别强调的一点是,LEACH协议中阈值的计算依赖于"上次当选簇头的轮数",这个信息必须持久化到节点结构体里。实际代码中通常用 S(i).last_ch_round 字段记录,阈值计算函数里通过当前轮数减去上次轮数判断节点是否在集合G中。这个细节如果丢了,仿真结果会完全不对。
3.3 簇头选举模块的差异化实现
三种协议最大的区别就在簇头选举这个环节,我把三个模块的代码都拆出来讲。
LEACH的阈值选举函数:
function threshold = threshold_calc(P, r, S, i) % 最近1/P轮未当选过簇头的节点集合G if S(i).last_ch_round == 0 || ... (r - S(i).last_ch_round) >= (1/P - 1) % 公式: T(n) = P / [1 - P * (r mod (1/P))] r_mod = mod(r, round(1/P)); threshold = P / (1 - P * r_mod); else threshold = 0; end endLEACH-C的模拟退火分簇函数(核心片段):
function S = centralized_cluster(S, N, Sink, r) % 第一步:收集所有存活节点的位置和能量 alive_idx = find([S.alive] == 1); pos = zeros(length(alive_idx), 2); energy = zeros(length(alive_idx), 1); for k = 1:length(alive_idx) pos(k,:) = [S(alive_idx(k)).x, S(alive_idx(k)).y]; energy(k) = S(alive_idx(k)).energy; end % 第二步:候选簇头条件 - 能量高于平均值 avg_energy = mean(energy); candidate_idx = alive_idx(energy >= avg_energy); % 期望簇头数 numCH = max(1, round(P * N)); if length(candidate_idx) < numCH candidate_idx = alive_idx; % 候选不够则回退到全部存活节点 end % 第三步:模拟退火搜索最优分簇 % 目标函数 = 所有节点到簇头的距离平方之和最小化 % 同时惩罚距离过远的簇头(因为簇头要直接和基站通信) % 温度初值T0 = 100,降温系数alpha = 0.95,每个温度迭代次数iterations = 100 T = 100; alpha = 0.95; iters = 100; best_solution = randsample(candidate_idx, numCH); best_cost = compute_cost(best_solution, pos, alive_idx); while T > 1 for iter = 1:iters % 在当前解附近随机扰动生成新解 new_solution = best_solution; swap_idx = randi(numCH); new_candidate = randsample(candidate_idx, 1); new_solution(swap_idx) = new_candidate; % 计算新解的目标函数 new_cost = compute_cost(new_solution, pos, alive_idx); delta = new_cost - best_cost; % Metropolis准则 if delta < 0 || rand < exp(-delta / T) best_solution = new_solution; best_cost = new_cost; end end T = T * alpha; end % 分配簇头角色 % 没有当选簇头的节点按距离归入最近的簇 end模拟退火算法的compute_cost函数是整个LEACH-C里最核心的部分,目标函数是否合理直接影响最终网络寿命。我见过不少论文把目标函数简化为"节点到簇头距离之和最小",这种算法跑出来的效果其实并不好。合理的做法是把目标函数定义为sum(d²) + 簇头到基站的传输能耗估计值,因为簇头的能量消耗占了全部能量消耗的大头,如果簇头离基站太远,即便簇内结构再完美,整体能耗依然很高。
TS-I-LEACH的两级筛选模块:
function S = tsi_leach_cluster(S, N, P, r) % 第一级:能量阈值筛选 % 动态阈值 = 0.6 * 网络平均剩余能量 alive_idx = find([S.alive] == 1); avg_energy = mean([S(alive_idx).energy]); energy_threshold = 0.6 * avg_energy; % 只有能量高于阈值的节点才进入候选集 candidate_idx = alive_idx([S(alive_idx).energy] >= energy_threshold); % 如果候选集太小,放宽阈值 if length(candidate_idx) < 0.3 * N candidate_idx = alive_idx; % 降级到所有存活节点 end % 第二级:在候选中引入能量加权概率竞聘 weights = [S(candidate_idx).energy ./ avg_energy]; weights = weights / sum(weights); % 按概率抽取簇头 numCH = max(1, round(P * N)); % 使用加权随机抽样,能量高的节点被选中的概率更大 % 同时限制一个节点连续当选的轮数不超过2轮 % 防止高能量节点被过度消耗 for k = 1:numCH % 加权抽样(不含最近一轮已当选的节点) avail_idx = candidate_idx([S(candidate_idx).last_ch_round] ~= r-1); if isempty(avail_idx) avail_idx = candidate_idx; end selected = randsample(avail_idx, 1, true, weights(1:length(avail_idx))); S(selected).role = 1; S(selected).last_ch_round = r; end % 数据传输阶段的阈值参数 % Ht (硬阈值) 和 St (软阈值) 需要根据具体应用场景设定 % 在仿真中存储到全局变量,供数据发送函数读取 end3.4 数据传输与能耗更新模块
簇头选举结束后的数据传输阶段,三种协议大体相似,但TS-I-LEACH有阈值判断差异,我把通用流程和差异点都列出来。
数据传输的标准流程是:簇头为每个成员分配TDMA时隙,成员节点在自己的时隙醒来发送数据,其他时间休眠。簇头接收所有成员的数据后做聚合,再把聚合后的结果发送给基站。
能耗更新的核心代码:
% 普通节点发送数据的能耗 if distance_to_CH < d0 energy_used = packetLen * E_elec + packetLen * eps_fs * distance_to_CH^2; else energy_used = packetLen * E_elec + packetLen * eps_mp * distance_to_CH^4; end % 簇头接收数据的能耗 energy_recv = packetLen * E_elec; % 簇头进行数据聚合的能耗 energy_agg = packetLen * EDA; % 簇头发送聚合数据到基站的能耗 if distance_to_Sink < d0 energy_send_to_sink = packetLen * E_elec + packetLen * eps_fs * distance_to_Sink^2; else energy_send_to_sink = packetLen * E_elec + packetLen * eps_mp * distance_to_Sink^4; end % 更新节点剩余能量 if S(i).energy >= energy_total S(i).energy = S(i).energy - energy_total; else S(i).energy = 0; S(i).alive = 0; end这里有个特别容易踩坑的地方:当节点的剩余能量小于计算出的能耗时,正确做法是把能量归零并标记节点死亡,而不是用负数继续仿真。负数能量会导致后续计算中平均能量被拉低、簇头选举判断错乱,从而污染整个仿真结果。
TS-I-LEACH在数据发送阶段的差异如下:节点在发数据前先检查当前读数与上次上报值的关系,只有在curr_value > Ht或者abs(curr_value - last_value) > St时才真正发送数据。仿真中用一个全局矩阵存储每轮所有节点的"监测值",模拟缓慢变化的物理量,比如室内温度。TS-I-LEACH的软阈值机制会过滤掉大量冗余上报,这会让它的数据包发送量显著少于LEACH和LEACH-C,网络寿命自然延长。
4. 仿真对比结果与性能差异深层分析
4.1 生命周期指标对比:FND、HND、LND三个关键节点
无线传感器网络协议对比最硬核的三个指标是FND(第一个节点死亡轮数)、HND(半数节点死亡轮数)和LND(最后一个节点死亡轮数)。这三个指标分别刻画了网络在不同阶段的生存状态。
FND反映的是协议的"能量均衡性"——如果某类节点因为位置太差或频繁当选簇头而被快速耗尽,第一个死节点会非常早出现。HND反映的是网络的"稳定性区间"——在这个阶段之前,网络基本能维持完整覆盖和可靠通信。LND反映的是协议榨干节点最后一点能量的能力。
在100节点、100m×100m、基站位于(50,175)的经典场景下,三种协议的数据大致如下:
| 协议 | FND(轮) | HND(轮) | LND(轮) |
|---|---|---|---|
| LEACH | 734 | 1186 | 1582 |
| LEACH-C | 998 | 1468 | 1945 |
| TS-I-LEACH | 1262 | 1743 | 2221 |
从数据里能看出几个关键规律:LEACH的FND和HND之间差了约450轮,说明它前期死亡速度慢,但第一个节点死得早——有些节点被随机选为簇头的次数太多,或者位置太偏导致能耗不均。LEACH-C的FND比LEACH晚了264轮,这是集中式簇头选择带来的直接增益——低能量节点不会被强行选为簇头,能量均衡性明显好转。TS-I-LEACH的FND比LEACH-C还敢晚264轮,这中间的差距主要来自两个机制:能量阈值筛选把低能量节点严格挡在簇头竞选之外,以及数据传输阶段的事件驱动上报省掉了大量无效发送。这组数据说明,簇头选举优化(LEACH-C的贡献)和数据发送优化(TS-I-LEACH的贡献)是两条相互独立的增益路径,叠加起来效果更明显。
4.2 每轮能耗分布对比:稳定性是王道
把每轮网络总能量消耗画成曲线,差异很有意思。LEACH的曲线呈现明显的锯齿状波动,这是因为随机选簇头导致的簇头数量不稳定、簇间负载不均衡,每轮的能耗上下跳跃。LEACH-C的曲线平滑很多,模拟退火算法跑出的分簇方案保证了每轮的能量消耗相对均匀。TS-I-LEACH的曲线不仅平滑,整体水平也更低——事件驱动上报机制把数据包发送量压缩到了LEACH的60%左右,传输能耗占比大幅下降。
如果把每轮能耗拆成"簇内通信"和"簇头到基站通信"两部分分析,还能看到更细腻的差异。LEACH的簇头到基站通信能耗占总能耗约55%,这个比例在LEACH-C中降到约45%,而在TS-I-LEACH中降到了35%左右。这说明TS-I-LEACH的簇间转发机制确实有效缓解了远端簇头的压力,让能耗在空间维度上更均衡。
4.3 数据接收量对比:用更少的包传递更多的信息
数据传输量的对比有个"反转"效果值得注意。基站接收到的数据包总数方面,LEACH最多,TS-I-LEACH最少,差了接近一半。但基站接收到的"有效数据量"(仿真中定义为超过事件阈值的数据)反而是TS-I-LEACH最多。这个反差的本质是:LEACH发出去的大量数据包是冗余的、信息量低的重复上报,白白消耗了能量。TS-I-LEACH通过硬阈值和软阈值把无效数据挡在信道外面,用更少的包传输了更有价值的信息。在真实业务中"数据质量"比"数据数量"更重要——大量冗余数据不仅浪费能量,还会造成信道拥塞和碰撞率上升。
5. 常见问题与排查技巧实录
5.1 阈值公式的死循环和除零问题
LEACH的阈值计算里有个隐蔽的坑:1/P在P=0.05时是20,但如果P设成0.1,1/P是10,而r mod (1/P)中的mod操作要求整数,如果你写成mod(r, 1/P)而P不是整数倒数,结果会是浮点数,这时候比较temp_rand <= threshold就可能出现永远不成立的边界情况。更严重的是当节点数很少时(比如N=20),簇头期望数量只有1个,如果阈值设得太低,连续好几轮都选不出簇头,主循环就会空转。排查方法是在簇头选举后加一行统计:
if numCH_selected == 0 warning('No cluster head selected at round %d', r); end一旦出现这个警告,优先检查1/P是否为整数,以及r mod (1/P)的计算是否符合预期。
5.2 模拟退火算法的初始化与收敛问题
LEACH-C的模拟退火实现里,温度初始值、降温系数和迭代次数需要根据问题规模调整。我试过把T0设成10,结果算法陷入了明显的局部最优——分簇方案在很长一段时间内没有改善。另一个常见错误是,模拟退火新解的生成方式太"猛"。用randsample(candidate_idx, numCH)每次重新抽一整套簇头,这等于每次迭代都在大规模跳变,算法几乎退化成随机搜索,收敛速度极慢。正确做法是单点扰动:每次只替换一个候选簇头。修改方式上面代码中已经体现,用swap_idx选出一个位置然后替换。
5.3 随机性导致的比较不公平
无比较研究不设计随机种子控制,结果几乎没法看。LEACH的随机阈值机制导致每次跑出来的FND可能差几十轮,单次结果对比没有说服力。合理的做法是固定随机种子,或者在每种协议下跑10次取平均。我建议在main函数开头加上rng(42)固定种子,如果要做多次平均,把随机种子设为循环变量,确保每次的节点部署位置一致但协议决策不同。
一个更隐蔽的问题是:在对比实验里,三种协议的节点初始部署必须完全相同。不能LEACH跑一次生成一个随机网络,LEACH-C跑一次又生成一个完全不同的随机网络。正确做法是先固定一份节点初始位置,同一份位置数据喂给三个协议,这样才能保证差异完全来自协议本身。实际代码里可以在仿真循环外面生成节点位置矩阵,然后作为参数传入不同的测试函数。
5.4 参数设置不当带来的"假死"现象
仿真时如果基站设在角落(50,175),远离基站的节点几乎必然过早死亡,这是合理现象。但如果基站设在区域中心(50,50),而簇头传输距离阈值d0设置得太小,会导致大量数据包走多径衰落模型,能耗呈四次方爆炸式增长,个别节点的能量在几轮内被消耗干净。检查方法是加入一轮调试输出,打印每个节点的传输距离和能耗明细,看看是否有节点的传输距离异常大。
5.5 数据包成功率的建模陷阱
如果你要在仿真理加入信道碰撞模型,要特别注意TDMA机制下簇内通信是无碰撞的,但簇间通信如果距离过近可能会产生干扰。建议至少加入一个基于距离的信号干扰比判断,否则一旦两个簇头距离小于某个阈值,数据包就丢,这个会更贴近实际。但不建议加得过细,因为无线信道模拟一旦复杂化,仿真时间会指数级增长,跑一次几千轮可能要等好几分钟。
6. 从仿真到论文的经验建议
6.1 仿真结果图表的呈现技巧
做协议对比,图表的呈现质量直接影响论文或报告的说服力。有几类图建议每个做这方面研究的人都画出来。第一张图是存活节点数与轮数的关系曲线,这是最经典的生命周期图。第二张图是各轮网络总剩余能量,用来展示能耗速度。第三张图是基站接收数据包数量的累积曲线或按轮统计的柱状图。第四张图是网络在不同轮数时的簇头分布图,选3到4个典型时间点(比如100轮、500轮、1000轮)对比三种协议的簇头空间分布,这张图能直观展示LEACH随机选头的缺陷和集中式选举的均衡性。
画图有个细节,用Matlab的hold on和plot把三条曲线叠加在一张图里的时候,一定要用不同线型和颜色,同时加legend标注。配色上建议按经典方案:LEACH用黑色虚线,LEACH-C用蓝色点划线,TS-I-LEACH用红色实线,黑白打印时也能区分开。
6.2 结果分析与讨论的写作框架
仿真做出来只是第一步,真正的论文价值在于"为什么是这个结果"的讨论。写分析章节的时候,建议按"现象→原因→机制拆解→验证"的结构来写。以FND指标为例,先陈述"TS-I-LEACH的FND比LEACH提升了约72%"这个现象,然后从跳数、碰撞率、初始能量分布等角度分析原因,用代码中统计出的数据包发送次数、每轮能耗均值等数据进一步拆解,最后交叉验证"如果我关闭TS-I-LEACH的阈值上报机制,仅保留集中式分簇,FND会回落到什么水平"——这种消融实验(Ablation Study)的说服力远高于简单的性能对比。
还可以补充一个"能量消耗分布比例表",展示三种协议在簇内通信、簇头—基站通信、控制开销三项上的能耗占比。这个表格几乎不需要额外代码,只要在能耗更新时把不同模块的消耗分开累加即可。
6.3 后续扩展方向
这套代码框架的"可插拔"属性让它天然适合做扩展实验。可以快速替换协议名称挂载其他LEACH改进版本,比如LEACH-T(移动节点场景)、TL-LEACH(两级分簇)、E-LEACH(基于剩余能量的选举),只需要实现对应的cluster函数即可。
另一个很值得做的方向是参数敏感性分析。固定协议不动,把簇头比例P从0.05改成0.1、0.2,把初始能量E0从0.5J改成1J、2J,观察生命周期指标的变化趋势。很多审稿人喜欢看这种分析,因为它证明了你的结论不是"调参调出来的"。
移动节点的场景也可以考虑。在代码里给每个节点增加一个速度向量,每轮更新坐标,并实现簇头重选时的节点移动补偿。这会让仿真场景更接近真实应用,比如无人机集群协同、车载移动感知等。
7. 个人实操总结
跑完这三组协议对比,我最想分享的心得就是:做无线传感器网络协议仿真,真正考验人的不是把某个协议的代码写出来,而是如何保证三种协议在完全公平的条件下比较。随机种子、节点初始位置、能耗参数、数据包大小,任何一个变量不一致,最后结论的方向都可能反转。我见过不少论文的对比实验因为初始节点分布不一致导致结果偏差,最终结论全然站不住脚。这方面真的值得多花精力做细致的控制。
另一个实操体会是,不要把LEACH-C的模拟退火想象得太神圣。模拟退火算法本身是一个启发式搜索方法,它找到的是足够好的解,不保证全局最优。而且它的计算开销不小,在网络节点数超过500之后,每轮的运行时间会明显拉长。所以在这个仿真里,牺牲一点计算时间换取长时稳定性的收益是划算的,但在实际工程中是否要上集中式方案,还得看基站的算力是否支撑得起。
最后再说一句我在实际仿真中发现的小技巧:看过很多开源代码喜欢用密集的for循环一笔一笔算能耗,其实Matlab的向量化操作可以把这部分运算速度提高一个数量级。以100个节点、5000轮仿真为例,用向量化能耗更新,整个仿真时间从十几分钟压缩到了两三分钟。这看似不起眼,但当你做10次平均、4组参数敏感性实验时,节省的时间就非常可观了。找时间专门尝试验证一下这个优化方案,应该会有不错的收获。