简介这份资源面向无线传感器网络课程学习、仿真实验与毕业设计人群提供基于Matlab与Python双语言实现的完整仿真代码与数据。内容覆盖RSSI测距多边定位、五种网络拓扑结构生成、CSMA/CA五种退避策略对比、粒子群算法优化节点覆盖率以及改进LEACH路由协议与经典LEACH的仿真比较适合具备一定编程基础、需要复现或拓展WSN实验的读者。压缩包共85个文件约1.46MB以png结果图、py与m源码、md说明文档为主另含mat数据、npy数组与xml配置便于直接运行与二次开发。目前已有602人学习下载。各实验模块均配有独立说明读者可据此快速搭建仿真环境、对比算法性能、分析拓扑与覆盖效果并借助现成脚本与数据完成实验报告或课题验证。1. 无线传感器网络仿真的五种拓扑到底差在哪做无线传感器网络WSN仿真的人绕不开一个前置问题节点怎么摆。很多人一上来就调路由协议、改 MAC 层参数结果曲线怎么都不对最后发现是拓扑结构选错了。基于 Matlab 和 Python 实现无线传感器网络仿真、生成五种网络拓扑结构本质上解决的就是这件事——把随机拓扑、网格拓扑、环形拓扑、星型拓扑、分簇拓扑这五种常见结构用代码生成出来输出节点坐标、邻接关系和可视化图再喂给后续的能耗、覆盖、连通性仿真。这套东西适合两类人一类是刚进实验室、要快速搭一个可复现仿真环境的研究生另一类是做协议对比、需要固定拓扑做消融实验的工程师。五种拓扑不是随便凑数它们分别对应不同的物理部署场景和协议假设选错了后面全是玄学。下面我按「先讲清每种拓扑的适用边界再给可抄的生成代码最后说踩过的坑」这个顺序展开Matlab 和 Python 两套实现都会给到。2. 五种拓扑的生成逻辑与适用场景拆解2.1 随机拓扑最像真实部署也最容易翻车随机拓扑是 WSN 仿真的默认起点。常见做法是在一个 100m×100m 或 200m×200m 的矩形区域内用均匀分布撒 N 个节点。它的价值在于模拟无规划投放比如飞机撒播、灾区临时布设。但随机拓扑有个隐蔽问题节点可能重叠也可能出现孤立节点导致连通性直接崩掉。Matlab 里生成随机拓扑的核心就三行但边界处理要自己加% 随机拓扑生成N个节点区域L×L N 100; L 100; rng(42); % 固定随机种子保证可复现 x L * rand(1, N); y L * rand(1, N); % 去重叠任意两节点距离小于d_min则重新撒该点 d_min 2; for i 1:N for j i1:N while norm([x(i)-x(j), y(i)-y(j)]) d_min x(i) L * rand(); y(i) L * rand(); end end end scatter(x, y, 30, filled); axis([0 L 0 L]); grid on;逻辑说明rng(42)是后悔药不固定种子每次跑出来的拓扑都不一样实验没法对比。d_min是节点最小间距物理上对应节点体积仿真上避免距离矩阵出现零导致除零。参数上N 一般取 50 到 200L 取 100 到 500节点密度 N/L² 决定了平均度密度低于 0.005 时连通性大概率不满足。Python 版本用 numpy 更顺手import numpy as np import matplotlib.pyplot as plt N, L, d_min 100, 100, 2 rng np.random.default_rng(42) pts rng.uniform(0, L, size(N, 2)) # 简单去重叠迭代重撒 for i in range(N): for j in range(i1, N): while np.linalg.norm(pts[i]-pts[j]) d_min: pts[i] rng.uniform(0, L, size2) plt.scatter(pts[:,0], pts[:,1], s30) plt.xlim(0, L); plt.ylim(0, L); plt.grid(True) plt.show()参数说明default_rng(42)是 numpy 新式随机接口比老的np.random.seed更干净。uniform(0, L, size(N,2))一次生成坐标矩阵比循环快一个量级。去重叠的双层循环在 N200 时还能接受N 再大就要换网格哈希否则 O(N²) 会拖慢。2.2 网格拓扑可控性最强但别死板照搬网格拓扑把节点按行列等距排列适合规则部署场景比如农田监测、厂房设备监控。它的最大好处是连通性和覆盖可以解析计算做协议对比时变量最少。常见做法是 sqrt(N)×sqrt(N) 的方阵间距 d 由区域边长除以行列数决定。% 网格拓扑rows×cols间距d rows 10; cols 10; d 10; [X, Y] meshgrid(0:d:(cols-1)*d, 0:d:(rows-1)*d); x X(:); y Y(:); % 可选加轻微扰动避免完全对称导致的路径碰撞 jitter 0.5; x x jitter * (rand(size(x)) - 0.5); y y jitter * (rand(size(y)) - 0.5); plot(x, y, o); axis equal; grid on;逻辑说明meshgrid生成规则网格(:)拉平成行向量。加jitter是我一般会做的完全对称的网格在基于地理路由的协议里会出现大量等长路径仿真结果过于理想。扰动幅度取间距的 5% 以内太大就破坏网格特性了。Python 对应实现rows, cols, d 10, 10, 10 xs np.arange(cols) * d ys np.arange(rows) * d X, Y np.meshgrid(xs, ys) pts np.column_stack([X.ravel(), Y.ravel()]) jitter 0.5 pts pts rng.uniform(-jitter, jitter, pts.shape) plt.scatter(pts[:,0], pts[:,1], s30) plt.axis(equal); plt.grid(True)参数说明d要大于通信半径的 1/√2 才能保证对角邻居也连通否则网格会退化成多个孤立簇。jitter用均匀分布而不是高斯避免出现离群点。2.3 环形与星型两种极端各有各的坑环形拓扑节点排成一个圆适合管道监测、边界巡逻这类线性场景。星型拓扑所有节点连到一个中心节点适合单跳汇聚的小规模网络。这两种结构简单但边界条件容易写错。环形生成的关键是半径和角度% 环形拓扑N个节点均匀分布在半径R的圆上 N 50; R 50; theta linspace(0, 2*pi, N1); theta(end) []; x R * cos(theta); y R * sin(theta); % 邻接每个节点只与前后两个邻居相连 adj zeros(N); for i 1:N adj(i, mod(i, N)1) 1; adj(mod(i, N)1, i) 1; end逻辑说明linspace(0, 2*pi, N1)再去掉最后一个点保证首尾不重合。mod(i, N)1是环形索引的标准写法注意 Matlab 索引从 1 开始Python 从 0 开始移植时这里最容易翻车。星型拓扑更简单但要注意中心节点的位置N, R 30, 50 theta np.linspace(0, 2*np.pi, N, endpointFalse) leaf np.column_stack([R*np.cos(theta), R*np.sin(theta)]) center np.array([[0, 0]]) pts np.vstack([center, leaf]) # 邻接矩阵中心与所有叶子相连 adj np.zeros((N1, N1)) adj[0, 1:] 1 adj[1:, 0] 1参数说明endpointFalse避免首尾重叠这是 numpy 里生成环形坐标的常见写法。星型的中心节点能耗是瓶颈仿真时要把它的初始能量设高一些否则跑不了几轮就死了。2.4 分簇拓扑最贴近实际协议也最考验参数分簇拓扑是 LEACH 这类协议的标配。常见做法是用 K-means 把节点聚成若干簇每簇选一个簇头。它的难点不在生成而在簇头数量和簇内距离的平衡。% 分簇拓扑先随机撒点再用kmeans聚成K簇 N 100; L 100; K 5; x L * rand(1, N); y L * rand(1, N); [idx, C] kmeans([x y], K, Replicates, 5); % 每簇选离质心最近的节点作为簇头 heads zeros(K, 2); for k 1:K members find(idx k); [~, mi] min(sum(([x(members) y(members)] - C(k,:)).^2, 2)); heads(k,:) [x(members(mi)) y(members(mi))]; end逻辑说明kmeans的Replicates参数设 5 是为了避免陷入局部最优默认只跑一次结果可能很差。簇头选离质心最近的节点而不是直接用语质心因为质心位置可能没有实际节点。参数上K 一般取 sqrt(N) 左右N100 时 K5 到 10 比较合理K 太大簇内通信省了但簇头开销上去了。Python 用 scipy 或 sklearn 都可以from sklearn.cluster import KMeans N, L, K 100, 100, 5 pts rng.uniform(0, L, size(N, 2)) km KMeans(n_clustersK, n_init10, random_state42).fit(pts) labels km.labels_ centers km.cluster_centers_ heads [] for k in range(K): members pts[labels k] d np.linalg.norm(members - centers[k], axis1) heads.append(members[np.argmin(d)]) heads np.array(heads)参数说明n_init10对应 Matlab 的Replicates显式指定比默认值更稳。random_state42保证可复现。簇头数量 K 的选择可以用肘部法但仿真里一般直接按经验给。3. 用 Matlab 和 Python 跑通拓扑生成的最小工程3.1 统一的数据结构节点表加邻接矩阵五种拓扑生成方式不同但输出要统一否则后续仿真没法复用。我一般会定义两个核心结构节点表nodesN×2 的坐标矩阵和邻接矩阵adjN×N 的 0/1 矩阵。邻接矩阵的生成依赖通信半径不是拓扑本身决定的但要在生成拓扑后立刻算出来。% 统一输出nodes坐标 adj邻接矩阵 function [nodes, adj] build_topology(type, N, L, R) switch type case random nodes L * rand(N, 2); case grid side ceil(sqrt(N)); [X, Y] meshgrid(linspace(0, L, side)); nodes [X(:), Y(:)]; nodes nodes(1:N, :); case ring theta linspace(0, 2*pi, N1); theta(end) []; nodes [L/2*cos(theta)L/2, L/2*sin(theta)L/2]; case star theta linspace(0, 2*pi, N); nodes [[L/2, L/2]; [L/2*cos(theta)L/2, L/2*sin(theta)L/2]]; case cluster nodes L * rand(N, 2); end % 基于通信半径R生成邻接矩阵 D pdist2(nodes, nodes); adj (D R) (D 0); end逻辑说明pdist2算距离矩阵(D R) (D 0)同时排除自身和超距节点。参数上R 的取值直接决定平均度R 太小拓扑会碎成多个连通分量R 太大就退化成全连接。一般让平均度落在 6 到 10 之间比较合理可以用mean(sum(adj))检查。Python 版本from scipy.spatial.distance import cdist def build_topology(kind, N, L, R, seed42): rng np.random.default_rng(seed) if kind random: nodes rng.uniform(0, L, size(N, 2)) elif kind grid: side int(np.ceil(np.sqrt(N))) xs np.linspace(0, L, side) X, Y np.meshgrid(xs, xs) nodes np.column_stack([X.ravel(), Y.ravel()])[:N] elif kind ring: theta np.linspace(0, 2*np.pi, N, endpointFalse) nodes np.column_stack([L/2*np.cos(theta)L/2, L/2*np.sin(theta)L/2]) elif kind star: theta np.linspace(0, 2*np.pi, N, endpointFalse) leaf np.column_stack([L/2*np.cos(theta)L/2, L/2*np.sin(theta)L/2]) nodes np.vstack([[L/2, L/2], leaf]) elif kind cluster: nodes rng.uniform(0, L, size(N, 2)) D cdist(nodes, nodes) adj (D R) (D 0) return nodes, adj参数说明cdist比手写双层循环快很多N500 时差距明显。endpointFalse在环形和星型里都要加否则首尾节点会重叠。[:N]是网格拓扑的截断因为side²可能大于 N。3.2 可视化一张图看出拓扑是否合理生成完拓扑第一件事是画图检查。我一般会画三张子图节点分布、邻接关系、度分布直方图。度分布能快速暴露问题比如随机拓扑出现大量度为 0 的孤立节点或者网格拓扑度分布过于集中。% 可视化节点 边 度分布 figure; subplot(1,3,1); scatter(nodes(:,1), nodes(:,2), 20, filled); axis equal; title(节点分布); subplot(1,3,2); gplot(adj, nodes, -); hold on; scatter(nodes(:,1), nodes(:,2), 10, filled); axis equal; title(邻接关系); subplot(1,3,3); histogram(sum(adj), 0:max(sum(adj))); title(度分布);逻辑说明gplot是 Matlab 画邻接图的老牌函数输入邻接矩阵和坐标即可。sum(adj)按列求和得到每个节点的度。度分布直方图的 bin 用0:max保证每个整数度一个柱子不然默认分箱会糊在一起。Python 用 networkx 更直观import networkx as nx G nx.from_numpy_array(adj) pos {i: nodes[i] for i in range(len(nodes))} nx.draw(G, pos, node_size20, with_labelsFalse)参数说明from_numpy_array把邻接矩阵转成图对象注意它默认生成无向图如果邻接矩阵不对称要先处理。pos用真实坐标而不是布局算法这样拓扑的空间结构才看得出来。3.3 批量生成与保存一次跑完五种拓扑做对比实验时五种拓扑要能批量生成并保存成统一格式。我一般存成.mat或.npz包含nodes、adj、type三个字段。% 批量生成五种拓扑并保存 types {random, grid, ring, star, cluster}; N 100; L 100; R 25; for i 1:length(types) [nodes, adj] build_topology(types{i}, N, L, R); save(sprintf(topo_%s.mat, types{i}), nodes, adj); end逻辑说明sprintf拼文件名避免手动改。参数上R25 在 L100 的区域里平均度大概在 8 左右是个比较稳的起点。如果跑出来度分布太偏先调 R 再调 N。Python 对应for kind in [random, grid, ring, star, cluster]: nodes, adj build_topology(kind, 100, 100, 25) np.savez(ftopo_{kind}.npz, nodesnodes, adjadj)参数说明np.savez不压缩np.savez_compressed压缩但慢数据量小的时候无所谓。文件名带类型后续加载时不用记。4. 拓扑生成里最容易踩的五个坑4.1 随机种子没固定实验没法复现现象同一份代码跑两次连通性曲线差很多审稿人问为什么结果不一致。原因rand或np.random默认用系统时间做种子。解决在生成拓扑前显式设种子Matlab 用rng(42)Python 用default_rng(42)。注意rng要放在所有随机操作之前中途设没用。4.2 网格拓扑的间距小于通信半径的 1/√2现象网格拓扑跑出来连通分量不止一个对角节点连不上。原因网格间距 d 太大对角距离 d√2 超过通信半径 R。解决保证 d ≤ R/√2或者把邻接矩阵的生成改成考虑对角连接。我一般直接让 d R/√2 再留 10% 余量。4.3 环形拓扑首尾节点重叠现象环形拓扑画出来少一个节点或者第一个和最后一个节点坐标一样。原因linspace(0, 2*pi, N)包含终点首尾重合。解决用linspace(0, 2*pi, N1)再去掉最后一个或者 Python 里用endpointFalse。这个坑在 Matlab 和 Python 之间移植时特别常见。4.4 分簇拓扑的簇头选在空位置现象簇头坐标不在任何实际节点上后续仿真找不到对应节点。原因直接用语质心当簇头。解决选簇内离质心最近的实际节点代码里就是argmin那一步。另外 K-means 的Replicates或n_init要设大一点不然簇划分本身就不稳。4.5 邻接矩阵把自身算成邻居现象度分布里每个节点度都至少是 1孤立节点检测不出来。原因距离矩阵对角线为 0D R把自身也算进去了。解决加D 0条件或者生成后把对角线置零。这个坑在随机拓扑里最隐蔽因为节点密集时看不出来。5. 从拓扑到仿真一个可验证的连通性检查技巧生成完拓扑别急着跑路由协议先做连通性检查。这一步能省掉后面大量无效实验。我一般用广度优先搜索BFS从第一个节点出发看能覆盖多少节点覆盖率低于 90% 就说明拓扑有问题先调 R 或 N。% 连通性检查BFS从节点1出发 function ratio connectivity_ratio(adj) N size(adj, 1); visited false(1, N); queue 1; visited(1) true; while ~isempty(queue) u queue(1); queue(1) []; neighbors find(adj(u, :) ~visited); visited(neighbors) true; queue [queue, neighbors]; end ratio sum(visited) / N; end逻辑说明find(adj(u,:) ~visited)一次找出所有未访问邻居比逐个判断快。queue用数组模拟N 不大时够用N 上千就换真正的队列结构。参数上连通性比率低于 0.9 就要警惕低于 0.7 基本没法用。Python 版本用 collections.dequefrom collections import deque def connectivity_ratio(adj): N adj.shape[0] visited np.zeros(N, dtypebool) visited[0] True q deque([0]) while q: u q.popleft() for v in np.where(adj[u] ~visited)[0]: visited[v] True q.append(v) return visited.sum() / N参数说明deque的popleft是 O(1)比 list 的pop(0)快。np.where返回索引数组直接迭代。这个检查跑一次只要几毫秒但能挡掉大部分低级错误。五种拓扑里随机拓扑的连通性最不稳定网格和环形最稳星型取决于中心节点是否在通信半径内覆盖所有叶子分簇拓扑要看簇头之间是否连通。我一般会把这五种拓扑的连通性比率打出来对比作为实验的第一张表。最后一个习惯每次生成拓扑后把nodes、adj、R、N、L和随机种子一起存进日志文件。后面结果不对时能直接定位是哪次生成的拓扑有问题不用重新跑一遍。这个习惯帮我省过很多次返工希望帮到你。本文还有配套的精品资源点击获取