前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >图论入门——从基础概念到NetworkX

图论入门——从基础概念到NetworkX

作者头像
曼亚灿
发布2023-12-13 15:17:12
5410
发布2023-12-13 15:17:12
举报
文章被收录于专栏:亚灿网志亚灿网志

入门图论及NetworkX的使用.

介绍

(Graph)是一种表示对象之间关系的抽象数据结构。图由节点(Vertex)和边(Edge)组成,节点表示对象,边表示对象之间的关系。图可以用于建模各种实际问题,如社交网络、交通网络、电力网络等。

NetworkX是一个用Python编写的库,专门用于创建、操作和研究复杂网络的结构、动态和功能。它提供了简单易用的接口来处理图论和网络结构。NetworkX适用于处理大型网络结构,并提供了许多内置的图算法,如路径寻找、图的构建和修改、节点属性操作等。

  • NetworkX官方文档(网站):https://networkx.org/
  • 使用pip安装:pip install networkx并回车。

基本概念

无向图(Undirected Graph)

代码语言:javascript
复制
import networkx as nx

# 创建一个无向图
G = nx.Graph()

# 添加节点
G.add_node(1)
G.add_nodes_from([2, 3])

# 添加边
G.add_edge(1, 2)
G.add_edges_from([(2, 3), (1, 3)])

# 查看图的节点和边
print("图的节点: ", G.nodes(), "; 图的边: ", G.edges(), '.')

# 可视化
nx.draw(G, node_size=500, with_labels=True)
控制台输出结果 - 无向图
控制台输出结果 - 无向图

控制台输出结果 - 无向图

有向图(Directed Graph)

有向图的创建方式很简单,只需要把上面无向图的对象:

代码语言:javascript
复制
# 创建一个无向图
G = nx.Graph()

换成:

代码语言:javascript
复制
# 创建一个有向图
G = nx.DiGraph()

即可。

控制台输出结果 - 有向图
控制台输出结果 - 有向图

控制台输出结果 - 有向图

有权图(Directed Graph)

创建有权图时需要添加权重信息,且可视化的代码略有不同:

代码语言:javascript
复制
import networkx as nx
import matplotlib.pyplot as plt

# 创建一个带权重的无向图
G = nx.Graph()

# 添加带权重的边
G.add_edges_from([
    (2, 3, {'diameter': 1.0,'length': 12.0}),
    (1, 3, {'diameter': 2.0,'length': 10.0}),
    (2, 1, {'diameter': 3.0,'length': 8.0})
])

# 提取权重信息
edge_weights = nx.get_edge_attributes(G, 'length')

# 可视化图
pos = nx.spring_layout(G)  # 选择布局算法,这里使用弹簧布局
nx.draw(G, pos, with_labels=True, font_weight='bold', node_size=700, node_color='skyblue', font_color='black')

# 绘制权重标签
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_weights)

# 显示图形
plt.show()

# 查看图的节点和边
print("图的节点: ", G.nodes(), "; 图的边: ", G.edges(data=True), '.')
控制台输出结果 - 有权图
控制台输出结果 - 有权图

控制台输出结果 - 有权图

邻接矩阵

邻接矩阵(Adjacency Matrix): 邻接矩阵是一个二维矩阵,其中的行和列分别对应图中的节点。矩阵的元素表示节点之间是否存在边。对于无向图,邻接矩阵是对称的;对于有向图,邻接矩阵不一定对称。

代码语言:javascript
复制
# 获取邻接矩阵(默认是稀疏矩阵格式)
adj_matrix = nx.adjacency_matrix(G)

# 将稀疏矩阵转换为密集矩阵(如果需要)
dense_adj_matrix = adj_matrix.todense()

请注意,如果你的图是有向图,你可以使用 nx.adjacency_matrix(G, directed=True) 来获取有向图的邻接矩阵。如果你想要自定义矩阵的表示方式,你可以使用 toarray() 方法将稀疏矩阵转换为 NumPy 数组。

度、平均度、度分布与度矩阵

下面的演示均以:

代码语言:javascript
复制
G.add_edges_from([  # Fig. 7
    (0, 1), (0, 2), (0, 7),
    (1, 0), (1, 2), (1, 3), (1, 4), (1, 7),
    (2, 0), (2, 1), (2, 3),
    (3, 1), (3, 2), (3, 4), (3, 5), (3, 7),
    (4, 1), (4, 3), (4, 5), (4, 6), (4, 7),
    (5, 3), (5, 4), (5, 6),
    (6, 4), (6, 5),
    (7, 0), (7, 1), (7, 3), (7, 4)
])
演示图结构
演示图结构

演示图结构

为示例。

度(Degree)的定义:

  • 对于无向图 G,节点 i 的度 \text{degree}(i) 是与节点 i 相连的边的数量。
  • 对于有向图 G,节点 i 的入度 \text{in-degree}(i) 是指向节点 i 的边的数量,出度 \text{out-degree}(i) 是从节点 i 出发的边的数量。

NetworkX求度:

代码语言:javascript
复制
# 获取节点的度
degrees = dict(G.degree())

print("节点的度:", degrees)

节点的度: {0: 3, 1: 5, 2: 3, 7: 4, 3: 5, 4: 5, 5: 3, 6: 2}

平均度(Average Degree)是图中所有节点的度的平均值。

  • 对于无向图 G,平均度 \langle k \rangle 可以通过所有节点的度之和除以节点数得到。
  • 对于有向图 G,同样可以计算平均入度和平均出度。
代码语言:javascript
复制
# 计算平均度
average_degree = sum(dict(G.degree()).values()) / len(G)

print("平均度:", average_degree)

平均度: 3.75

度分布(Degree Distribution)描述了图中节点的度的分布情况,即度为 k 的节点有多少个。度分布是图结构的一个重要特征,它可以帮助我们了解网络中节点的连接模式。

代码语言:javascript
复制
# 获取度分布
degree_distribution = nx.degree_histogram(G)

# 绘制度分布直方图
plt.bar(range(len(degree_distribution)), degree_distribution, tick_label=range(len(degree_distribution)))
plt.xlabel("Degree")
plt.ylabel("Frequency")
plt.title("Degree Distribution")
plt.show()

度矩阵(Degree Matrix)是一个表示图中节点度信息的对角矩阵。对于一个无向图,度矩阵的定义如下:

  1. 对于无向图 G,其度矩阵 D 是一个 n \times n 的矩阵,其中 n 是图中的节点数。
  2. D 的对角线元素 D_{ii} 表示节点 i 的度,即与节点 i 相连的边的数量。
代码语言:javascript
复制
# 获取度矩阵
np.diag(list(dict(G.degree()).values()))
控制台输出结果 - 度矩阵
控制台输出结果 - 度矩阵

控制台输出结果 - 度矩阵

拉普拉斯矩阵

拉普拉斯矩阵(Laplacian Matrix)有多种定义方式,其中最常见的计算方法是使用度矩阵减去邻接矩阵。假设无向图 G 有 n 个节点,其邻接矩阵为 A,度矩阵为 D。标准拉普拉斯矩阵 L 的计算如下:

  1. 计算度矩阵 D 的对角线元素,即每个节点的度: D_{ii} = \sum_{j} A_{ij}
  2. 计算拉普拉斯矩阵 L: L = D - A
代码语言:javascript
复制
# 获取邻接矩阵和度矩阵
adj_matrix = nx.adjacency_matrix(G).toarray()
degree_matrix = np.diag(list(dict(G.degree()).values()))

# 计算标准拉普拉斯矩阵
laplacian_matrix = degree_matrix - adj_matrix

请注意,对于有向图,可以使用 nx.directed_laplacian_matrix 函数来计算有向图的拉普拉斯矩阵。同样,还有对称归一化拉普拉斯矩阵和随机游走拉普拉斯矩阵等不同定义方式。

路径和距离

在图论中,路径和距离是描述图中节点之间连接关系和位置关系的重要概念。

  • 路径(Path):在图中,路径是指图中的一系列节点,其中任意相邻两个节点之间都有边相连。路径的长度是指路径上边的数量。如果路径中的所有节点都是不同的,则路径是简单路径。
  • 距离(Distance):在图中,两个节点之间的距离是指连接这两个节点的最短路径的长度。如果两个节点之间没有路径相连,则它们之间的距离通常被定义为无穷大

获取图中的所有最短路径和距离:

代码语言:javascript
复制
# 获取所有节点对之间的最短路径和距离
all_shortest_paths = dict(nx.all_pairs_shortest_path(G))
all_shortest_distances = dict(nx.all_pairs_shortest_path_length(G))

# 打印最短路径和距离
all_info = []
for source in all_shortest_paths:
    for target in all_shortest_paths[source]:
        paths = all_shortest_paths[source][target]
        distance = all_shortest_distances[source][target]
        print(f"最短路径从节点 {source} 到节点 {target}: {paths}, 距离: {distance}")

集聚系数、平均集聚系数与全局集聚系数

演示实例:

代码语言:javascript
复制
G = nx.Graph()
G.add_edges_from([(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)])
nx.draw(G, node_size=500, with_labels=True)

在图论中,集聚系数(Clustering Coefficient)是用于度量节点周围邻居之间连接紧密程度的指标。它可以帮助我们了解图中的局部连接性。有三种主要的集聚系数:节点的集聚系数、平均集聚系数和全局集聚系数。

节点的集聚系数是一个节点邻居之间实际存在的边数与可能存在的最大边数之比。对于一个无向图中的节点 i,其集聚系数 C_i 的计算方式如下:

C_i = \frac{2 \times \text{实际存在的边数}}{\text{邻居节点数} \times (\text{邻居节点数} - 1)}

这个值在 [0, 1] 范围内,表示节点的邻居之间连接紧密的程度。

代码语言:javascript
复制
# 计算节点的集聚系数
node_clustering = nx.clustering(G)
print("节点的集聚系数:", node_clustering)

节点的集聚系数: {1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0, 5: 1.0}

以节点2为例,节点2的邻居节点是1、3、4、5。节点1、3、4、5可能存在的最大边数组合为C_4^2 = \frac{4 \times 3}{2 \times 1} = 6.

C_n^k = \frac{n!}{k!(n-k)!}

节点1、3、4、5实际存在的连接情况是:

代码语言:javascript
复制
1: [1, 3], [1, 4], [1, 5]
3: [3, 4], [3, 5]
4: [4, 5]

共有6种。因此,节点2的聚集系数为1.

平均集聚系数是图中所有节点的集聚系数的平均值。对于一个无向图 G,其平均集聚系数 C 的计算方式如下:

C = \frac{1}{n} \sum_{i=1}^{n} C_i

其中,n 是图中的节点数。

代码语言:javascript
复制
# 计算平均集聚系数
average_clustering = nx.average_clustering(G)
print("平均集聚系数:", average_clustering)

全局集聚系数是图中所有闭合三角形的数量与所有可能的闭合三角形的数量的比值。闭合三角形是由三个节点和它们之间的边组成的子图。对于一个无向图 G,其全局集聚系数 C_{\text{global}} 的计算方式如下:

C_{\text{global}} = \frac{\text{3×闭合三元组的数量}}{\text{连接三元组的数量}}

连接三元组(Open Triplet)是图论中的一个概念,它指的是在图中任选三个节点,其中至少有两个节点是相互连接的。这种三元组被称为“开放的”,因为它们不一定构成一个闭合的三角形。连接三元组是用来计算图的集聚系数的一个重要概念。 在具体的定义中,连接三元组通常包含以下两种情况:

  1. 闭合三元组(Closed Triplet):这是图中的三个节点,它们之间的每一对节点都相互连接。换句话说,这三个节点形成了一个闭合的三角形。
  2. 非闭合三元组:这也是图中的三个节点,但它们之间不是每一对节点都相互连接。这意味着虽然其中两个节点之间有边相连,但至少有一对节点之间没有直接的连线,因此不形成闭合的三角形。

在计算图的全局集聚系数时,会考虑图中所有可能的连接三元组。全局集聚系数是闭合三元组数量与连接三元组总数量的比例。这个比例说明了在所有可能形成三角形的节点组合中,有多少实际形成了闭合的三角形。 例如,在社交网络分析中,闭合三元组可能表示一种更强的社会关系,因为如果A认识B,B认识C,且A也认识C,这可能意味着这三个人之间有更紧密的社交联系。而非闭合的三元组则可能表示潜在的、未完全形成的社交联系。

平均集聚系数关注的是每个节点的局部连接性,而全局集聚系数关注的是整个图中的全局连接性。

代码语言:javascript
复制
# 计算全局集聚系数
global_clustering = nx.transitivity(G)
print("全局集聚系数:", global_clustering)

全局集聚系数: 1.0

上面的示例图是一个完全图(任意两个顶点都是相连的),在完全图中,每一组三个节点都会形成一个闭合三角形,所以闭合三元组的数量等于连接三元组的数量。因此,全局集聚系数是 1.0。

实际存在的闭合三角形的数量(闭合三元组的数量):

代码语言:javascript
复制
from itertools import combinations

# 获取图中所有可能的闭合三角形数量
all_triangles_count = 0

for node in G.nodes():  # 遍历所有节点
    neighbors = set(G.neighbors(node))  # 获取该节点的所有邻居节点
    for pair in combinations(neighbors, 2):  # 获取所有邻居节点的两两组合
        if G.has_edge(pair[0], pair[1]):  # 检查这个两两组合之间是否有边(因为它俩都是node的邻居节点,因此它俩肯定与node相连),如果它俩相连,那么就说明node与pair[0]、pair[1]构成了三角形.
            all_triangles_count += 1

上面的代码等同于:

代码语言:javascript
复制
# 获取图中实际存在的闭合三角形数量(也需要除以3)
actual_triangles_count = sum(nx.triangles(G).values()) 

需要注意的是:all_triangles_count需要除以3得到的才是闭合三元组的数量,因为闭合三元组中的三个顶点会在不同的组合中进行重复计算。

连接三元组的数量:

代码语言:javascript
复制
open_triplets_count = 0

for node in G.nodes():
    neighbors_count = len(list(G.neighbors(node)))
    open_triplets_count += (neighbors_count * (neighbors_count - 1)) // 2

# open_triplets_count 现在就是图中连接三元组的总数量

n 个数中随机挑选出 2 个的组合数可以用组合数学中的公式来计算,这个公式是 C(n, 2) 或者 \binom{n}{2} ,可以表示为:

C(n, 2) = \frac{n!}{2!(n-2)!} = \frac{n \times (n-1)}{2}

图的连通性

连通性描述的是图中节点之间是否存在路径相连的性质。一个图是连通的,意味着从图中的任意一个节点到另一个节点都存在路径。

代码语言:javascript
复制
G = nx.Graph()
G.add_nodes_from([i for i in range(1, 8)])
G.add_edges_from([(1, 2), (1, 3), (2, 3), (4, 7), (5, 6), (5, 7), (6, 7)])
nx.draw(G, node_size=500, with_labels=True)

# 查看图的连通性
nx.is_connected(G)

什么叫做图的连通性?在无向图中,如果对于每一对不同的顶点 uv,都存在至少一条由边连接的路径从 uv,则该图是连通的。请注意这个概念并不等同于完全图的概念,完全图的概念是每一对不同的顶点 uv都直接相连,而连通图的每一对不同的顶点 u 都可以达到 v,两者可以不直接相连。

通过创建两个图来展示不同的连通性:

代码语言:javascript
复制
# 创建具有较强连通性的图(低Fiedler值)
G_strong = nx.Graph()
G_strong.add_edges_from([(1, 2), (2, 3), (3, 4), (4, 1), (1, 3), (2, 4)])

# 创建具有较弱连通性的图(高Fiedler值)
G_weak = nx.Graph()
G_weak.add_edges_from([(1, 2), (2, 3), (3, 4)])

# 绘制这两个图
plt.figure(figsize=(12, 6))

# 绘制具有较强连通性的图
plt.subplot(1, 2, 1)
nx.draw(G_strong, with_labels=True, node_color='lightblue', node_size=700)
plt.title("Strongly Connected Graph")

# 绘制具有较弱连通性的图
plt.subplot(1, 2, 2)
nx.draw(G_weak, with_labels=True, node_color='lightgreen', node_size=700)
plt.title("Weakly Connected Graph")

plt.show()

可以看出,左边的图具有更高的连通性,其应该具有更高的高Fiedler值,表明要将图分割成孤立的子图,需要切断更多的边。这通常表示图在整体上更加紧密连接,没有明显的弱连接点。右边的图具有更低的连通性,表明图可以通过切断较少的边来分割成不同的部分。这通常发生在图中存在一个或多个"瓶颈"区域,这些区域的边相对较少,是连接大的图区域的桥梁。

G_strong
G_strong

G_strong

G_weak
G_weak

G_weak

通过对两个图结构的拉普拉斯矩阵进行特征值分解发现,左边图结构的Fiedler值为4.0,而右边的为0.586左右。因此,说明左边图结构的连通性更强。

Thinking/ 思考 两点自己的思考:

  1. 对于一个管网系统来说,Fiedler似乎可以反应该管网的冗余性,Fiedler值越大说明该管网中网状结构越多。
  2. 如果对一个管网进行切分,是否可以设计一种算法,来使得切出来的这个分区的Fiedler值最小,这样就保证了“切最少的管道”就可以得到了这个分区。

图的特征值分解

下面的讲解以[STANKOVIC L, DAKOVIC M, SEJDIC E. Introduction to Graph Signal Processing[C]//,2019:3-108. 10.1007/978-3-030-03574-7_1.](https://www.researchgate.net/publication/329350163_Introduction_to_Graph_Signal_Processing)中P19页的Fig. 10(图1,连通图)、P14页的Fig. 7(图2,非连通图)为例。

代码语言:javascript
复制
G_strong = nx.Graph()
G_strong.add_edges_from([
    (0, 1), (0, 2), (0, 7),
    (1, 2), (1, 3), (1, 4), (1, 7),
    (2, 3),
    (3, 4), (3, 5), (3, 7),
    (4, 5), (4, 6), (4, 7),
    (5, 6)
])

G_weak = nx.Graph()
G_weak.add_edges_from([
    (0, 1), (0, 2),
    (1, 2),
    (3, 4), (3, 5), (3, 7),
    (4, 5), (4, 6), (4, 7),
    (5, 6)
])

# 绘制这两个图
plt.figure(figsize=(12, 6))

plt.subplot(1, 2, 1)
nx.draw(G_strong, with_labels=True, node_color='lightblue', node_size=700)
plt.title("Strongly Connected Graph")

plt.subplot(1, 2, 2)
nx.draw(G_weak, with_labels=True, node_color='lightgreen', node_size=700)
plt.title("Weakly Connected Graph")

plt.show()

特征值分解的意义

邻接矩阵更倾向于揭示图的整体连通性,而拉普拉斯矩阵的特征值和特征向量则更多地用于研究图的局部结构特征,如社区结构或者图的连通分量。

为什么拉普拉斯矩阵的第一个特征值总是0?

拉普拉斯矩阵的第一个特征值总是0,其对应的特征向量是一个所有元素都相同的向量。这代表图中所有节点的均匀分布。

拉普拉斯矩阵 L 的第一个特征值总是 0 的原因与拉普拉斯矩阵的定义和图的性质有关。拉普拉斯矩阵 L 定义为度矩阵 D 减去邻接矩阵 A ,即 L = D - A

这里是为什么其第一个特征值总是 0:

  1. 和为零的行(列):在拉普拉斯矩阵中,每一行的和(以及每一列的和,因为 L 是对称的)都是 0。这是因为每一行的非对角元素(即 -A 的部分)与对角线上的元素(即 D 的部分,它是节点的度数)相加抵消。换句话说,对于每个节点 i ,它的度数 D_{ii} 等于与它相连的边的数量,而 -A 中的元素表示与节点 i 相连的邻居节点,因此当你在一行中把这些值加起来时,结果是 0。
  2. 恒等特征向量:由于每行的和为零,这意味着全 1 向量(所有元素都是 1 的向量)是拉普拉斯矩阵的一个特征向量,因为 L \times \textbf{1} = \textbf{0} (其中 \textbf{1} 是全1向量,\textbf{0} 是零向量)。这意味着乘以全 1 向量后,每行的元素相加都得到 0,这与零向量相匹配。
  3. 特征值 0 的意义:特征值 0 对应的特征向量表示图中所有节点的均匀分布,它反映了图的连通性。对于连通图,特征值 0 是唯一的,它的代数重数(特征值的重复次数)等于图的连通分量的数量。因此,对于连通图,特征值 0 的代数重数是 1。如果图不是完全连通的,特征值 0 的代数重数将等于图的连通分量数量。

简而言之,拉普拉斯矩阵的每一行和每一列的和为零这个属性保证了第一个特征值必定是 0。这与图的基本性质密切相关,特别是与图的连通性有关。

其他特征值的意义:

  1. 第二个特征值(Fiedler值)和特征向量
    • 第二个最小的特征值通常被称为Fiedler值,它在图的谱聚类和社区检测中非常重要。Fiedler值的大小可以表示图的连通性:Fiedler值越小,图的连通性越弱。
    • 对应的Fiedler向量可以用来识别图中的社区或集群。通过分析Fiedler向量的组件,可以将图划分为不同的部分,其中每个部分相对内部紧密连接,而与其他部分的连接较少。
  2. 其他特征值和特征向量
    • 更高的特征值和对应的特征向量可以揭示图的更复杂的结构特征,如多层次的社区结构。
    • 在某些情况下,这些特征向量也用于嵌入技术,将图的节点映射到低维空间,以便于可视化和进一步分析。

特征值分解

下面对上面的示例图进行拉普拉斯矩阵的特征值分解。

拉普拉斯矩阵

首先,获取图结构的拉普拉斯矩阵:

代码语言:javascript
复制
# 获取拉普拉斯矩阵
L = nx.laplacian_matrix(G).toarray()
L

$$ \begin{bmatrix} 3 & -1 & -1 & -1 & 0 & 0 & 0 & 0 \\ -1 & 5 & -1 & -1 & -1 & -1 & 0 & 0 \\ -1 & -1 & 3 & 0 & -1 & 0 & 0 & 0 \\ -1 & -1 & 0 & 4 & -1 & -1 & 0 & 0 \\ 0 & -1 & -1 & -1 & 5 & -1 & -1 & 0 \\ 0 & -1 & 0 & -1 & -1 & 5 & -1 & -1 \\ 0 & 0 & 0 & 0 & -1 & -1 & 3 & -1 \\ 0 & 0 & 0 & 0 & 0 & -1 & -1 & 2 \\ \end{bmatrix} VS \begin{bmatrix} 2 & -1 & -1 & 0 & 0 & 0 & 0 & 0 \\ -1 & 2 & -1 & 0 & 0 & 0 & 0 & 0 \\ -1 & -1 & 2 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 3 & -1 & -1 & -1 & 0 \\ 0 & 0 & 0 & -1 & 4 & -1 & -1 & -1 \\ 0 & 0 & 0 & -1 & -1 & 3 & 0 & -1 \\ 0 & 0 & 0 & -1 & -1 & 0 & 2 & 0 \\ 0 & 0 & 0 & 0 & -1 & -1 & 0 & 2 \\ \end{bmatrix} $$

需要注意的是,返回的拉普拉斯矩阵的行列顺序并不与图中的顺序相同,矩阵中的行列数据是按照节点的添加顺序来的。如何查看节点的顺序:

代码语言:javascript
复制
list(G.nodes())
# [0, 1, 2, 7, 3, 4, 5, 6]

对于图1来说,因为节点7添加的早,所以排在节点3之前。也就是说,拉普拉斯矩阵中第4行代表的是第7个元素的连接情况。

特征值
代码语言:javascript
复制
#求矩阵特征值以及特征向量
eig_value, eig_vector = np.linalg.eig(L)
eig_value

特征值及其可视化:

[4.44089210e-16, 1.13357211e+00, 3.05427452e+00, 3.31740297e+00, 4.00000000e+00, 5.67905993e+00, 6.47332881e+00, 6.34236165e+00]
代码语言:javascript
复制
def autolabel(rects):
    for rect in rects:
        height = rect.get_height()
        height_pos = height + .1 if height >= -0.1 else height - .1
        plt.text(
            rect.get_x() + rect.get_width() / 2,
            height_pos,
            '{:.5f}'.format(height),
            size=10,
            family="Times new roman",
            horizontalalignment='center',
            verticalalignment='center'
        )


fig = plt.bar([i for i in range(len(eig_value))], np.sort(eig_value))
autolabel(fig)

plt.title('Eigenvalues')
plt.show()
图1
图1

图1

图2
图2

图2

图1有一个接近零的特征值,表明图是连通的。图2特征值有两个接近于零的值,这与图中的两个连通分量相对应。特征值为0的数量恰好等于图的连通分量的数量。

图2的 Fiedler 值(最小非0值)为1.58,该 Fiedler 值对应的特征向量是:

$$ \begin{bmatrix} 0. \\ 0. \\ 0. \\ -0.4472136 \\ -0.4472136 \\ -0.4472136 \\ -0.4472136 \\ -0.4472136 \\ \end{bmatrix} $$

说明,该 Fiedler 值代表的是3、4、5、6、7节点对应的连通分量的连通性。

总结:图1的连通性更强,因为其特征值中仅有一个为0;图2包含两个连通分量,因为其特征值中包含两个0。图2中3、4、5、6、7节点组成的连通分量的连通性要高于图1整体的连通性。因为图2中3、4、5、6、7节点组成的连通分量的 Fiedler 值为1.58,大于图1整体的连通分量1.13。

特征向量

特征向量(图1):

$$ \begin{bmatrix} 0.35355339 & 0.40809973 & -0.38534069 & -0.32322928 & 0.61237244 & 0.10317586 & -0.24182446 & 0.10660991 \\ 0.35355339 & 0.21821011 & 0.07059821 & -0.06640201 & -0.20412415 & 0.58748394 & 0.64631494 & 0.11603433 \\ 0.35355339 & 0.36261755 & -0.3221172 & 0.67753284 & -0.20412415 & -0.23709192 & -0.00834996 & -0.28766179 \\ 0.35355339 & 0.18086105 & 0.27243317 & -0.5085369 & -0.20412415 & -0.62680632 & 0.20197088 & -0.18470141 \\ 0.35355339 & 0.05048967 & 0.33222523 & 0.17458035 & -0.20412415 & -0.05547633 & -0.37548832 & 0.7388255 \\ 0.35355339 & -0.15837433 & 0.24016423 & -0.13207484 & -0.20412415 & 0.41726191 & -0.52854257 & -0.52883224 \\ 0.35355339 & -0.40809973 & 0.38534069 & 0.32322928 & 0.61237244 & -0.10317586 & 0.24182446 & -0.10660991 \\ 0.35355339 & -0.65380405 & -0.59330365 & -0.14509945 & -0.20412415 & -0.08537128 & 0.06409502 & 0.14633561 \\ \end{bmatrix} $$

将特征值从小到大排序,然后可视化特征向量:

代码语言:javascript
复制
def plot_eigenvectors(value, vector):
    vector = vector[:, np.argsort(value)]
    value = np.sort(value)

    # plt.figure(dpi=100)
    fig, ax_arr = plt.subplots(len(value), 1, sharex='col', sharey='row', figsize=(5, 2 * len(value)))
    fig.subplots_adjust(hspace=0.05, wspace=0.03)

    for i in range(len(value)):
        ax_arr[i].stem([i for i in range(len(value))], vector[:, i])
        ax_arr[i].set_ylabel(f'$u_{{{i}}}(n)$')


plot_eigenvectors(eig_value, eig_vector)
图1
图1

图1

图2
图2

图2

----- END -----

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-12-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • 基本概念
    • 无向图(Undirected Graph)
      • 有向图(Directed Graph)
        • 有权图(Directed Graph)
          • 邻接矩阵
            • 度、平均度、度分布与度矩阵
              • 拉普拉斯矩阵
                • 路径和距离
                  • 集聚系数、平均集聚系数与全局集聚系数
                    • 图的连通性
                    • 图的特征值分解
                      • 特征值分解的意义
                        • 特征值分解
                          • 拉普拉斯矩阵
                          • 特征值
                          • 特征向量
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档