☰
最近邻耦合网络Matlab仿真:邻接矩阵生成、聚类系数与WS小世界模型验证
2026/10/28 13:48:41 网站建设 项目流程

简介:这是围绕复杂网络最近邻耦合网络(NN coupled network)编写的MATLAB建模程序,面向学习复杂网络基础模型的高校学生、科研人员以及相关算法开发爱好者。程序核心实现了最近邻耦合网络的构建逻辑,可根据预设节点数与邻居数生成规则环状拓扑,为后续研究网络同步、渗流、鲁棒性及小世界特性提供基础模型。资源包内共1个文件,为MATLAB的m源文件,压缩包整体仅982B,体量非常轻便,便于直接阅读源码理解规则连接矩阵的组织方式。目前已有650人学习浏览,适合初入复杂网络领域、希望结合代码掌握典型网络模型构造方法的读者。通过该程序可快速生成最近邻耦合网络的邻接矩阵,并在MATLAB中可视化观察环形耦合结构;在此基础上还可自行扩展参数,考察不同连接密度、节点规模对网络动力学行为的影响,作为课程设计或研究预热脚本十分实用。

1. 最近邻耦合网络:为什么复杂网络仿真总从这份代码开始

做复杂网络课程作业或者论文复现的时候,十次里有八次会碰到最近邻耦合网络。它是环状规则网络的代表:N个节点围成一圈,每个节点只连最近的K个邻居。结构看着简单,但它既是WS小世界模型的起点,也是验证聚类系数、平均路径长度这些基础指标的默认基准。这份NN_coupled_net.m程序就是干这个用的——输入节点数和邻居数,直接给你邻接矩阵和拓扑图,省去手工造矩阵的麻烦。适合刚接触复杂网络的本科生、准备复现小世界实验的研究生,以及做规则网络对照组的仿真工程师。下文把理论定义、代码逻辑、特性验证和踩坑点一次性说透。

2. 拓扑定义与模型定位:规则环结构为什么值得单独研究

2.1 数学定义:N个节点、K个邻居和环形距离

最近邻耦合网络的定义就一句话:N个节点分布在环上,每个节点与距离最近的K个节点相连。K取偶数最常见,因为这样左右对称,每个节点恰好有K个邻居。比如N=20、K=4时,节点5的四个邻居是4、6、3、7,左右各两个,非常规整。

邻接矩阵的构造规则是:节点i和节点j之间有边,当且仅当它们在环上的距离不大于K/2。这里的距离不是普通数轴距离,而是环形距离,要取两个方向的最小值:

dist(i,j) = min(|i-j|, N-|i-j|)

为什么必须用环形距离?普通直线距离只在节点下标相近时才有值,但环上节点1和节点N是相邻的。如果忽略了这一点,网络就从环退化成了一条链,拓扑性质完全不同。

参数含义典型取值
N节点总数20 ~ 1000
K邻居数,需为偶数4 ~ 20
E边数 = N × K / 2由N和K决定
平均度每个节点的平均度数等于K

边数为什么是N×K/2?每条边贡献两个端点,所有节点度数之和是N×K,除以2才是边数。这个公式在验证程序输出时非常有用,跑完代码先数一下非零元素,对不上就说明矩阵构造有问题。

2.2 模型定位:WS小世界模型就是从这张环开始重连的

Watts和Strogatz在1998年提出的WS小世界模型,起点正是最近邻耦合网络。操作是:从规则环出发,以概率p随机重连每条边。p=0就是完全规则的最近邻耦合网络,p=1时接近随机网络,而p取很小的中间值时,网络会出现“平均路径短但聚类系数高”的小世界特征。

正因为这个承上启下的位置,最近邻耦合网络成了复杂网络研究里绕不开的基线模型。它的特性非常鲜明:聚类系数高,因为每个节点的邻居在环上形成一个连续区间,区间内部互相连接的比例很高;平均路径长度大,因为信息只能沿着环一步步传,N=100时平均要走几十步。

这两个特征正好和随机网络形成对照——随机网络聚类低、路径短。做真实网络分析时,如果没有规则网络和随机网络两个极端作参照,真实网络的小世界性质根本没有可比性。这就是这份程序的第一个价值:它是整个复杂网络实验对照组的基础设施。

2.3 实际使用场景:不只是教学演示

很多人觉得规则网络太简单,实际仿真里没什么用。我见过做无线传感网拓扑仿真的人,把节点布置成环形结构,用最近邻耦合网络做最保守的通信模型;也见过做电力系统分析的人,拿环网结构作为对照拓扑。在这些场景里,规则网络代表的是“最有序、最稳定、信息传输最慢”的极端情况。

另外,复杂网络课程里最经典的一个实验就是“p从0到1扫描,观察聚类系数和平均路径长度的变化曲线”,这个实验的第一步就是先生成一张p=0的最近邻耦合网络。如果没有一份靠谱的生成代码,后面的随机重连实验、小世界效应验证都无从谈起。

3. 把 NN_coupled_net.m 跑起来:参数设置、矩阵生成与画图实操

3.1 运行环境与文件说明

这份程序是纯MATLAB脚本,核心文件就一个:NN_coupled_net.m,不需要额外工具箱,基础矩阵运算和画图功能就够了。我习惯用R2016a以上版本跑,主要是后面的graph对象和distances函数在旧版本里不可用,但生成邻接矩阵这部分在R2014a也能跑。

运行方式有两条路:

% 方式1:直接运行,脚本内部使用默认参数 NN_coupled_net % 方式2:在命令行提前定义参数,让脚本读取工作区变量 N = 30; K = 4; NN_coupled_net

方式1适合先看效果,脚本内部有一组默认的N和K,通常取20和4;方式2适合做参数扫描,脚本会检测工作区里已有的N和K变量。这个设计在复现实验时很省事,扫一组K值不用反复改文件,直接在命令行改变量就行。

3.2 核心逻辑:邻接矩阵生成的三步走

拿到的程序里最关键的一段是邻接矩阵构造逻辑。和我看过的大多数实现一样,它基本是三步:构造坐标差矩阵、换算环形距离、阈值比较生成邻接矩阵。下面这版是向量化实现的典型写法,也是这份脚本里最核心逻辑的还原:

function A = nn_coupled_net_matrix(N, K) % 输入: % N - 节点数量, 正整数 % K - 邻居数量, 必须是偶数 % 输出: % A - N x N 逻辑邻接矩阵, A(i,j)=1表示节点i和节点j之间有边 % 第0步: 参数校验 if mod(K, 2) ~= 0 error('K 必须为偶数, 请检查输入参数'); end half = K / 2; % 第1步: 构造直线距离矩阵 idx = (1:N)'; dist = abs(idx - idx'); % 第2步: 换成环形距离 distCirc = min(dist, N - dist); % 第3步: 阈值判断, 同时排除自环 A = (distCirc > 0) & (distCirc <= half); end

逻辑拆开讲:

idx是N×1列向量,idx - idx'得到一个N×N矩阵,第(i,j)个元素就是i和j的下标差。abs取绝对值后得到直线距离。比如N=20时,节点1和节点19的直线距离是18,但min(18, 20-18)=2,说明它们在环上其实只隔了两个位置,是近邻。distCirc > 0排除自环,distCirc <= half才是真正的“最近K个邻居”。mod(K,2)~=0的校验放在最前面,省得后面算出个不对称网络还不知道哪出了问题。

程序跑完后,工作区里会有A、N、K三个变量,A就是邻接矩阵。拿到A先做两个快速检查:sum(A)得到每个节点的度,应该全等于K;nnz(A)/2得到边数,应该等于N×K/2。这两个检查我每次都做,几乎能过滤掉90%的矩阵构造错误。

3.3 拓扑可视化:环形布局与gplot画图

邻接矩阵只是数据,拓扑长什么样还得画出来。脚本里用的环形布局,把N个节点等间距放在单位圆上:

% 环形布局坐标 theta = 2 * pi * (0:N-1) / N; x = cos(theta); y = sin(theta); % 用 gplot 画邻接矩阵对应的图 figure; gplot(A, [x(:), y(:)], '-o'); axis equal; title(['最近邻耦合网络, N=', num2str(N), ', K=', num2str(K)]); xlabel('x'); ylabel('y');

这段代码里,gplot是MATLAB专门画邻接矩阵图的老牌函数:第一个参数是邻接矩阵,第二个是N×2的坐标矩阵,第三个是线型和标记样式。环形布局就是为了让环状结构一眼能看出来。axis equal保证横纵轴比例一致,不然圆会被压扁成椭圆,拓扑的美感就没了。

参数调整方面有两点经验:节点数超过50时,把'-o'换成'-',不然圆点挤成一团;想突出某个特定节点,先gplot整体图,再用plot在对应坐标画一个红色大圆点标记。画完之后用鼠标拖拽旋转视角是没用的,gplot画的是二维静态图,想看动态三维效果得用graph对象配plot(G,'Layout','circle'),但那是另一种画法了。

4. 特性验证:聚类系数与平均路径长度的计算和参数扫描

4.1 聚类系数:规则网络的“高聚类”到底有多高

聚类系数衡量的是“朋友的朋友也是朋友”的程度。节点i的聚类系数定义为它邻居之间实际存在的边数,除以邻居之间可能存在的最大边数:

C_i = 2 × E_i / (k_i × (k_i - 1))

其中k_i是节点i的度,E_i是邻居之间实际连接的边数。整个网络的聚类系数就是所有节点C_i的平均值。

验证代码按常见写法补一段函数:

function C = clustering_coeff(A) % 计算无向无权网络的聚类系数 % 输入: A - 邻接矩阵(逻辑型或0/1矩阵) % 输出: C - 网络平均聚类系数, 范围[0,1] A = double(A); % 转数值矩阵方便运算 n = size(A, 1); Csum = 0; for i = 1:n nb = find(A(i, :)); % 节点i的邻居下标 k = length(nb); % 度数 if k < 2 continue; % 度小于2时聚类系数约定为0 end subA = A(nb, nb); % 抽取邻居子图 Ei = sum(subA(:)) / 2; % 邻居间实际边数, 每条边被数两次 Csum = Csum + 2 * Ei / (k * (k - 1)); end C = Csum / n; end

说明几个关键点:find(A(i,:))拿到全部邻居下标,A(nb,nb)抽取的是邻居之间的连边关系,sum(subA(:))/2是因为子矩阵里每条边出现两次。k<2时直接跳过,因为分母k×(k-1)为0,数学上未定义,约定取0。

理论值可以提前算好:最近邻耦合网络的聚类系数公式是C = 3(K-2) / (4(K-1))。K=4时代进去是3×2/(4×3)=0.5,程序跑出来应该非常接近这个值。如果差很多,八成是邻接矩阵构造出了问题,优先检查环形距离那一行。

4.2 平均路径长度:N增大时路径的线性增长

平均路径长度是所有节点对之间最短路径长度的平均值。最近邻耦合网络没有小世界性,平均路径长度随N线性增长,近似公式L ≈ N/(2K)。这意味着网络越大,信息传递要走的步数越多,而且是指数级慢——这就是规则网络“慢扩散”的典型特征。

用graph对象计算:

% 基于graph对象计算平均路径长度 G = graph(A); % 由邻接矩阵构造图对象 d = distances(G); % 返回N x N最短路径矩阵 L = sum(d(:)) / (N * (N - 1)); % 除以节点对数, 排除自身

distances是R2016b以后版本提供的函数,返回矩阵对角线为0,sum(d(:))已经包含这些0,除数是N×(N-1)就自然排除了自身到自身的距离。老版本里也有graphallshortestpaths,但那是工具箱函数,新代码建议直接用graph+distances。

实测一组数据:N=100、K=4时,L大约在12.5左右,和N/(2K)=100/8=12.5基本吻合。N=1000、K=4时,L在250附近。路径和N成正比增长,这个线性关系就是规则网络区别于小世界网络的核心证据。

4.3 参数扫描:固定N扫描K,观察两个关键指标

只算一组参数看不出规律。我一般会固定N=50,把K从2扫到10,观察聚类系数和平均路径长度的变化,顺便检验程序的正确性:

N = 50; Ks = [2 4 6 8 10]; results = zeros(length(Ks), 2); for t = 1:length(Ks) K = Ks(t); A = nn_coupled_net_matrix(N, K); results(t, 1) = clustering_coeff(A); G = graph(A); d = distances(G); results(t, 2) = sum(d(:)) / (N * (N - 1)); end % 打印结果对比 fprintf('K\t聚类系数\t平均路径长度\n'); for t = 1:length(Ks) fprintf('%d\t%.4f\t\t%.4f\n', Ks(t), results(t,1), results(t,2)); end

预期的输出大致是:

K聚类系数(程序)平均路径长度(实测)聚类系数(理论)
20约12.500
40.500约6.250.500
60.600约4.170.600
80.643约3.130.643
100.667约2.500.667

K=2时聚类系数为0,这是个有意思的边界:每个节点只连左右两个邻居,邻居之间没有任何边,所以聚类是0。K增大后,每个节点的邻居在环上形成连续区间,区间内部互相连接的比例随之升高,聚类系数稳定提升。平均路径长度则不断下降,因为边多了,绕路步数自然少了。

这个扫描结果如果和理论值对不上,先查邻接矩阵是否正确,再查聚类系数函数里subA矩阵抽取有没有问题。两步都没错的话,结果基本就是对的。

5. 避坑指南:矩阵公式、奇偶性与可视化的四类典型翻车

5.1 K=3导致的不对称邻居结构

现象:把K设成3,运行后sum(A)得到的度数一会儿是3一会儿是4,网络左右不对称,节点分布看起来歪的。

原因:最近邻耦合网络要求每个节点左右各连K/2个邻居,K为奇数时这个对称规则被打破,按“距离不大于half”的阈值实现会算出一边连了一个、另一边连了两个的情况,度数自然不均匀。

解决:约定K只取偶数。或者在程序入口加校验,NN_coupled_net.m里应当有mod(K,2)~=0就报错的检查。如果实验设计里必须用奇数K,那就明确一侧多连一条边的规则,并在文档里写清楚,不要依赖距离阈值去碰运气。

5.2 环形距离公式方向搞反,环变成了链

现象:生成的邻接矩阵只有对角线附近有值,A(1,N)和A(N,1)都是0,环在1号和N号节点之间断开了。

原因:环形距离算成了abs(i-j),忘了跟N-|i-j|取最小值。还有一种写法是(i-j+N)取模,结果在边界处也是错的,等于把环切开了一条缝。这个错误在N较小时尤其隐蔽,因为大多数节点对不受影响,只有跨越边界的那些出问题。

解决:验证方法非常直接——检查A(1,N)和A(N,1)是否为1。我习惯构造N=5、K=2的手工小网络,画一个五边形,再对照邻接矩阵逐行比对,几秒钟就能发现问题出在距离公式上。

5.3 N大了以后gplot画成一团黑

现象:N=200时图上一团黑,环状结构完全看不见,只有黑压压一片。

原因:节点和边太多,gplot默认的线宽和点大小叠加在一起,视觉上完全覆盖。程序本身没有错,是可视化参数的问题。

解决:大网络换一种画法,先画节点再画边:

plot(x, y, 'k.', 'MarkerSize', 3); % 先画节点, 小圆点 hold on; [ei, ej] = find(triu(A)); % 取上三角避免重复线 line([x(ei); x(ej)], [y(ei); y(ej)], ... 'Color', [0.6 0.6 0.6], 'LineWidth', 0.3);

triu取上三角是为了避免每条边画两次,line用矩阵参数批量画线比for循环快一个数量级。颜色调浅、线宽调细,N=200的网络也能看出结构。N更大的话,直接放弃画图,改用度分布直方图和邻接矩阵的spy图来分析。

5.4 大N全矩阵存储导致内存溢出

现象:N=10000时程序直接卡死,或者报“内存不足”。

原因:N×N的双精度稠密矩阵是10000×10000×8字节,约800MB,如果同时保留graph对象,内存直接翻倍。MATLAB默认工作区根本扛不住。

解决:改用稀疏矩阵存储。

% 稀疏法构造邻接矩阵, 只存非零边 A = sparse(N, N); % 按层填充: 第1层是距离为1的邻居, 第2层是距离为2的 for layer = 1:half offset = layer; % 环上距离为offset的节点对 idx1 = [(1:N-offset), (N-offset+1:N)]; idx2 = [(1+offset:N), (1:offset)]; A(sub2ind([N N], idx1, idx2)) = 1; A(sub2ind([N N], idx2, idx1)) = 1; end

空间复杂度从O(N²)降到O(E),E=N×K/2,对于N=10000、K=4的网络,存边数只有20000条,内存占用从800MB降到几百KB。不过坦白说,课程作业里N=10000的场景极少见,先把稠密矩阵方案跑通,再根据实际需要去优化。上来就写稀疏版本,只会给自己增加调试难度。

6. 进阶验证:一行随机重连把规则网络变成WS小世界

WS小世界模型的核心改动就是随机重连。把最近邻耦合网络的每条边以概率p打乱重连,就得到了小世界网络。代码量不大,但有几个细节必须处理对:

function A_ws = ws_rewire(A, p) % 输入: % A - 最近邻耦合网络邻接矩阵(逻辑型) % p - 重连概率, 0到1之间 % 输出: % A_ws - 重连后的WS网络邻接矩阵 A_ws = double(A); n = size(A, 1); for i = 1:n for j = (i+1):n if A(i, j) == 1 && rand() < p % 候选目标: 节点i当前的非邻居, 排除自己 candidates = find(A_ws(i, :) == 0); candidates(candidates == i) = []; if isempty(candidates) continue; end newj = candidates(randi(length(candidates))); % 删除旧边, 添加新边 A_ws(i, j) = 0; A_ws(j, i) = 0; A_ws(i, newj) = 1; A_ws(newj, i) = 1; end end end A_ws = logical(A_ws); end

重连逻辑有几个容易踩的地方:候选节点必须从当前非邻居里选,否则会产生重边;排除自己是为了避免自环;删除旧边和添加新边要同时更新对称位置。我见过有人的实现只改了A(i,j)没改A(j,i),结果邻接矩阵上三角和下三角不一致,聚类系数怎么算都不对。

验证小世界效应的方法很经典:固定N=200、K=4,扫描p从0到0.5,观察归一化后的聚类系数和平均路径长度:

N = 200; K = 4; pList = [0 0.001 0.01 0.05 0.2 0.5]; A0 = nn_coupled_net_matrix(N, K); C0 = clustering_coeff(A0); G0 = graph(A0); L0 = sum(distances(G0), 'all') / (N * (N - 1)); for t = 1:length(pList) A = ws_rewire(A0, pList(t)); C = clustering_coeff(A); G = graph(A); L = sum(distances(G), 'all') / (N * (N - 1)); fprintf('p=%.3f, C/C0=%.3f, L/L0=%.3f\n', ... pList(t), C/C0, L/L0); end

正常结果应该是:p=0时两个比值都等于1;p=0.001时L/L0就明显下降,而C/C0几乎不动;p=0.01时L/L0可能已经降到0.2左右,C/C0还保持在0.9以上。这就是“小世界”的典型特征——路径长度大幅缩短,聚类结构基本保持。这个实验做出来,才算真正把最近邻耦合网络到小世界网络这条路走通了。

从那以后,我每次拿到一份网络模型程序,都会强制走一遍“小参数验证、邻接矩阵对称性检查、聚类系数和平均路径长度跟理论值核对”这三步,再进下一步实验。这套流程帮我省掉了无数个半夜调bug的时光,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询