邻接矩阵 vs 邻接表:如何为你的图算法选择最佳数据结构(附Python代码示例)
在算法工程师和数据科学家的日常工作中,图结构无处不在。从社交网络的好友关系推荐,到电商平台的商品关联分析,再到知识图谱的实体链接,图都是描述复杂关系最直观的数学模型。然而,当我们真正动手实现一个图算法时,第一个要面对的抉择往往不是算法本身,而是如何存储这个图。这个看似基础的选择,却能在实际运行中带来数倍甚至数百倍的性能差异。
我见过不少项目,初期为了快速验证想法,随手选了一种图表示方式,结果在数据量增长后陷入性能泥潭。有的团队在处理千万级节点的社交网络时,因为使用了邻接矩阵而导致内存爆炸;也有的在实现实时推荐路径搜索时,由于邻接表的遍历效率低下,导致接口响应缓慢。选择合适的数据结构,就像为算法挑选趁手的兵器——用错了,再精妙的剑法也施展不开。
这篇文章,我将结合多年实战经验,深入剖析邻接矩阵和邻接表这两种核心表示方法。我们不仅会讨论它们的内存占用、时间复杂度等理论指标,更会聚焦于实际工程场景下的权衡:什么时候该用哪种结构?如何根据图的特点动态选择?如何在Python中高效实现并集成到现有系统中?我会用真实的代码示例和性能对比数据,帮你建立起一套清晰的决策框架。
1. 理解图的两种核心表示:从数学定义到内存布局
在离散数学的教科书里,图被抽象为顶点集合V和边集合E的组合G=(V, E)。但在计算机的内存中,这个抽象的数学概念需要被具体化为字节的排列。邻接矩阵和邻接表,正是两种最经典、最根本的“翻译”方式。
1.1 邻接矩阵:用二维数组映射顶点关系
邻接矩阵的思想非常直观:既然图描述的是顶点之间的关系,那么用一个二维表格(矩阵)来记录每对顶点之间是否有边相连,再自然不过了。
对于一个有n个顶点的图,我们创建一个n×n的矩阵A。如果顶点i和顶点j之间存在一条边,那么矩阵元素A[i][j]就设为1(或者边的权重);否则设为0。对于无向图,这个矩阵是对称的,因为如果i到j有边,j到i也一定有边。
class AdjacencyMatrixGraph:
def __init__(self, num_vertices, directed=False):
self.num_vertices = num_vertices
self.directed = directed
# 初始化一个全0的n×n矩阵
self.matrix = [[0] * num_vertices for _ in range(num_vertices)]
def add_edge(self, v1, v2, weight=1):
if 0 <= v1 < self.num_vertices and 0 <= v2 < self.num_vertices:
self.matrix[v1][v2] = weight
if not self.directed:
self.matrix[v2][v1] = weight
def has_edge(self, v1, v2):
return self.matrix[v1][v2] != 0
def get_neighbors(self, vertex):
neighbors = []
for i in range(self.num_vertices):
if self.matrix[vertex][i] != 0:
neighbors.append((i, self.matrix[vertex][i]))
return neighbors
邻接矩阵的最大优势在于查询速度。判断任意两个顶点是否相邻,只需要O(1)的时间——直接访问矩阵的对应位置即可。这种随机访问的特性,使得它在某些算法中表现优异。
但它的代价也显而易见:空间复杂度是O(n²)。无论图中有多少条边,都需要分配n×n的存储空间。对于有10000个顶点的图,即使只有100条边,也需要存储1亿个元素的矩阵。在实际项目中,我遇到过团队用邻接矩阵存储用户关系图,结果在用户量达到50万时,服务器内存直接告警。
提示:虽然邻接矩阵的空间效率低,但在图非常稠密(边数接近n²)时,它的空间浪费相对较小。当边的数量超过n²/2时,邻接矩阵甚至可能比邻接表更节省内存。
1.2 邻接表:用链表集合记录连接关系
邻接表采取了完全不同的思路:不为不存在的边分配存储空间。对于每个顶点,我们只记录它实际连接到的邻居顶点。
具体实现上,通常用一个数组(或字典)来存储所有顶点,每个顶点对应一个链表(或列表、集合),链表中存储该顶点的所有邻居信息。对于有权图,还需要存储边的权重。
from collections import defaultdict
class AdjacencyListGraph:
def __init__(self, directed=False):
self.graph = defaultdict(list) # 顶点 -> [(邻居, 权重), ...]
self.directed = directed
self.vertices = set()
def add_vertex(self, vertex):
self.vertices.add(vertex)
if vertex not in self.graph:
self.graph[vertex] = []
def add_edge(self, v1, v2, weight=1):
self.add_vertex(v1)
self.add_vertex(v2)
self.graph[v1].append((v2, weight))
if not self.directed:
self.graph[v2].append((v1, weight))
def has_edge(self, v1, v2):
for neighbor, _ in self.graph.get(v1, []):
if neighbor == v2:
return True
return False
def get_neighbors(self, vertex):
return self.graph.get(vertex, [])
邻接表的空间复杂度是O(V + E),其中V是顶点数,E是边数。对于稀疏图(边数远小于V²),这比邻接矩阵节省了大量内存。在实际的社交网络分析中,大多数用户的直接好友不会超过几百个,用邻接表存储可能只需要邻接矩阵1%甚至更少的内存。
但邻接表也有自己的短板:查询任意两个顶点是否相邻需要O(degree(v))的时间,在最坏情况下可能是O(V)。虽然对于稀疏图这通常很快,但在需要频繁进行此类查询的场景下,性能可能成为瓶颈。
2. 性能深度对比:不只是理论上的O(n)与O(1)
很多教材只给出邻接矩阵和邻接表的时间复杂度对比表格,但在实际工程中,情况要复杂得多。缓存局部性、内存访问模式、数据结构的具体实现方式,都会显著影响实际性能。
2.1 内存占用:不只是空间复杂度那么简单
让我们通过一个具体例子来感受两种结构的内存差异。假设我们有一个社交网络图,包含10万个用户,平均每个用户有150个好友(关注关系)。
使用邻接矩阵存储:
- 需要100,000 × 100,000的矩阵
- 如果每个元素用1字节的布尔值表示,需要约10GB内存
- 如果考虑权重(用4字节浮点数),需要约40GB内存
使用邻接表存储:
- 顶点存储:100,000个顶点,假设每个顶点ID用4字节整数,约0.4MB
- 边存储:总边数 = 100,000 × 150 = 15,000,000条
- 每条边需要存储目标顶点ID(4字节)和可能的权重(4字节),共约120MB
- 加上链表指针等开销,通常不超过200MB
| 存储方式 | 理论空间复杂度 | 示例中的实际内存 | 适用场景 |
|---|---|---|---|
| 邻接矩阵 | O(V²) | 10-40GB | 稠密图,顶点数少,需要频繁查询任意顶点对 |
| 邻接表 | O(V + E) | 200MB | 稀疏图,大规模图,内存受限 |
注意:Python中的列表和字典有额外的内存开销。在实际测量中,一个包含1000个顶点的邻接表图,在Python中可能占用比理论值多2-3倍的内存。对于性能敏感的应用,可以考虑使用array模块或numpy数组来优化。
2.2 操作效率:不同算法下的表现差异
不同的图算法对数据结构有不同的访问模式,这导致邻接矩阵和邻接表的性能差异会随着算法变化。
广度优先搜索(BFS)的实现对比:
# 使用邻接矩阵的BFS
def bfs_matrix(graph, start_vertex):
visited = [False] * graph.num_vertices
queue = [start_vertex]
visited[start_vertex] = True
result = []
while queue:
vertex = queue.pop(0)
result.append(vertex)
# 遍历所有顶点,检查是否为邻居
for i in range(graph.num_vertices):
if graph.matrix[vertex][i] != 0 and not visited[i]:
visited[i] = True
queue.append(i)
return result
# 使用邻接表的BFS
def bfs_list(graph, start_vertex):
visited = set()
queue = [start_vertex]
visited.add(start_vertex)
result = []
while queue:
vertex = queue.pop(0)
result.append(vertex)
# 只遍历实际存在的邻居
for neighbor, _ in graph.graph.get(vertex, []):
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return result
对于BFS,邻接表的时间复杂度是O(V + E),而邻接矩阵是O(V²)。在稀疏图中,E ≈ O(V),所以邻接表是O(V),邻接矩阵是O(V²),性能差异巨大。
但在某些特殊场景下,邻接矩阵可能有优势。比如需要频繁判断“顶点v的所有邻居是否都满足某个条件”,用邻接矩阵可以批量操作:
# 使用邻接矩阵快速检查顶点v的所有邻居是否都被标记
def all_neighbors_marked_matrix(matrix, vertex, marked):
n = len(matrix)
for i in range(n):
if matrix[vertex][i] != 0 and not marked[i]:
return False
return True
# 使用numpy可以进一步向量化加速
import numpy as np
def all_neighbors_marked_numpy(matrix_np, vertex, marked_np):
# matrix_np是numpy数组,marked_np是布尔数组
neighbors = matrix_np[vertex] > 0
return np.all(marked_np[neighbors])
2.3 缓存友好性:现代CPU架构下的隐藏因素
在现代计算机体系结构中,CPU缓存的影响常常比时间复杂度更重要。邻接矩阵的数据在内存中是连续存储的,这具有良好的空间局部性。当程序访问matrix[i][j]时,相邻的matrix[i][j+1]、matrix[i][j+2]等很可能已经在缓存中。
# 邻接矩阵的连续内存访问模式(缓存友好)
def count_edges_matrix(matrix):
n = len(matrix)
count = 0
for i in range(n):
for j in range(n): # 内层循环访问连续内存
if matrix[i][j] != 0:
count += 1
return count
相比之下,邻接表的每个顶点的邻居列表是独立分配的内存块,遍历时需要频繁跳转到不同的内存区域,可能导致缓存未命中。
# 邻接表的不连续内存访问(可能缓存不友好)
def count_edges_list(graph):
count = 0
for vertex in graph.vertices:
count += len(graph.graph[vertex]) # 每次访问不同内存区域
return count
在实际测试中,对于中等规模的稠密图,由于缓存优势,邻接矩阵的遍历速度有时甚至可能超过邻接表,尽管它的时间复杂度更高。
3. 实战场景选择指南:从社交网络到路径规划
理论对比只是基础,真正的决策需要在具体场景下做出。下面我结合几个典型应用场景,分析如何选择最合适的图表示方法。
3.1 场景一:社交网络分析(超大规模稀疏图)
社交网络通常有数亿用户,但平均好友数只有几百个,是典型的稀疏图。在这种场景下,邻接表几乎是唯一的选择。
为什么邻接矩阵不可行? 假设微信有10亿用户,用邻接矩阵需要10¹⁸个元素的矩阵。即使用1位表示是否存在边,也需要125PB的内存,这显然不现实。
邻接表的优化实践: 在实际的社交网络系统中,单纯的邻接表可能还不够。我们需要进一步优化:
class CompressedSocialGraph:
def __init__(self):
# 使用数组存储邻居列表,减少Python对象开销
self.vertex_offset = [] # 每个顶点邻居列表的起始位置
self.neighbors_array = [] # 所有邻居连续存储
self.vertex_map = {} # 用户ID到内部ID的映射
def build_from_edges(self, edges):
"""从边列表构建压缩图"""
# 第一遍:统计每个顶点的度数
degree = defaultdict(int)
for u, v in edges:
degree[u] += 1
degree[v] += 1
# 分配连续内存
total_edges = len(edges) * 2 # 无向图,每条边存两次
self.neighbors_array = [0] * total_edges
# 计算偏移量
self.vertex_offset = [0] * (len(degree) + 1)
current_offset = 0
for i, (vertex, deg) in enumerate(sorted(degree.items())):
self.vertex_map[vertex] = i
self.vertex_offset[i] = current_offset
current_offset += deg
self.vertex_offset[len(degree)] = current_offset
# 第二遍:填充邻居数据
temp_index = self.vertex_offset.copy()
for u, v in edges:
uid = self.vertex_map[u]
vid = self.vertex_map[v]
# 添加u->v
pos = temp_index[uid]
self.neighbors_array[pos] = vid
temp_index[uid] += 1
# 添加v->u
pos = temp_index[vid]
self.neighbors_array[pos] = uid
temp_index[vid] += 1
def get_neighbors(self, vertex):
"""获取顶点的所有邻居"""
if vertex not in self.vertex_map:
return []
vid = self.vertex_map[vertex]
start = self.vertex_offset[vid]
end = self.vertex_offset[vid + 1]
return self.neighbors_array[start:end]
这种压缩存储方式将邻居数据连续存放,既节省内存(减少Python对象开销),又提高了缓存命中率。在真实的社交网络推荐系统中,这种优化可以将内存占用减少60%以上,同时提升遍历速度2-3倍。
3.2 场景二:小规模稠密图与矩阵运算
当图的顶点数较少(比如几百个),但非常稠密时,邻接矩阵的优势就体现出来了。特别是在需要频繁进行矩阵运算的场景下。
案例:语义相似度图 在自然语言处理中,我们经常需要计算词语之间的语义相似度。如果有500个核心术语,我们需要计算每对术语的相似度,形成一个完全连接的加权图。
import numpy as np
class SemanticGraph:
def __init__(self, terms):
self.terms = terms
self.n = len(terms)
self.term_to_idx = {term: i for i, term in enumerate(terms)}
# 初始化相似度矩阵
self.similarity_matrix = np.ones((self.n, self.n))
np.fill_diagonal(self.similarity_matrix, 1.0)
def update_similarity(self, term1, term2, similarity):
i = self.term_to_idx[term1]
j = self.term_to_idx[term2]
self.similarity_matrix[i, j] = similarity
self.similarity_matrix[j, i] = similarity
def find_most_similar(self, term, top_k=5):
"""找到与给定术语最相似的其他术语"""
idx = self.term_to_idx[term]
similarities = self.similarity_matrix[idx]
# 排除自身
similarities[idx] = -np.inf
# 获取top-k
top_indices = np.argsort(similarities)[-top_k:][::-1]
return [(self.terms[i], similarities[i]) for i in top_indices]
def propagate_similarity(self, iterations=3):
"""通过矩阵乘法传播相似度(类似PageRank的思想)"""
# 归一化矩阵
row_sums = self.similarity_matrix.sum(axis=1, keepdims=True)
normalized = self.similarity_matrix / row_sums
# 迭代传播
result = normalized.copy()
for _ in range(iterations - 1):
result = result @ normalized
self.similarity_matrix = result
return self.similarity_matrix
在这个例子中,邻接矩阵(相似度矩阵)不仅存储了图结构,还直接支持矩阵乘法等运算。如果使用邻接表,实现相似度传播算法会复杂得多,性能也更差。
3.3 场景三:动态图与频繁更新
有些图是动态变化的,边会频繁添加或删除。这时,不同数据结构的表现差异很大。
邻接表的动态操作:
class DynamicGraphWithAdjList:
def __init__(self):
self.graph = defaultdict(set) # 使用集合而不是列表,便于快速查找和去重
def add_edge(self, u, v):
self.graph[u].add(v)
self.graph[v].add(u)
def remove_edge(self, u, v):
if u in self.graph and v in self.graph[u]:
self.graph[u].remove(v)
self.graph[v].remove(u)
# 如果顶点变得孤立,可以选择删除
if not self.graph[u]:
del self.graph[u]
if v in self.graph and not self.graph[v]:
del self.graph[v]
return True
def add_vertex(self, v):
if v not in self.graph:
self.graph[v] = set()
def remove_vertex(self, v):
if v in self.graph:
# 先删除所有与v相连的边
neighbors = list(self.graph[v])
for neighbor in neighbors:
self.graph[neighbor].remove(v)
if not self.graph[neighbor]:
del self.graph[neighbor]
# 删除顶点v本身
del self.graph[v]
邻接矩阵的动态操作: 对于邻接矩阵,添加或删除边是O(1)操作,但添加顶点却很昂贵——需要重新分配整个矩阵。如果图需要频繁添加顶点,邻接矩阵可能不合适。
| 操作 | 邻接表(集合实现) | 邻接矩阵 |
|---|---|---|
| 添加边 | O(1)平均 | O(1) |
| 删除边 | O(1)平均 | O(1) |
| 添加顶点 | O(1) | O(V²)(需要重建矩阵) |
| 删除顶点 | O(degree(v)) | O(V²)(需要重建矩阵) |
| 查询边 | O(1)平均 | O(1) |
对于动态图,我通常的决策流程是:
- 如果顶点数固定或变化很少,但边频繁更新 → 考虑邻接矩阵
- 如果顶点和边都频繁变化 → 使用邻接表
- 如果还需要快速边查询 → 使用基于哈希表的邻接表(如上例中的defaultdict(set))
4. 高级技巧与混合策略:超越二选一
在实际工程中,我们不必拘泥于纯粹的邻接矩阵或邻接表。根据具体需求,可以采用混合策略或特殊优化。
4.1 基于度的自适应表示
一个有趣的观察是:图中不同顶点的度数差异可能很大。有些顶点有大量连接(如社交网络中的名人),有些顶点只有少数连接。我们可以为高度数顶点使用邻接表,为低度数顶点使用邻接矩阵的行压缩存储。
class HybridGraph:
def __init__(self, threshold=100):
self.threshold = threshold # 度数阈值
self.high_degree_vertices = {} # 高度数顶点用邻接表
self.low_degree_matrix = {} # 低度数顶点用行压缩
self.vertex_info = {} # 顶点元数据
def add_edge(self, u, v):
# 更新顶点度数
self._update_degree(u)
self._update_degree(v)
# 根据度数选择存储方式
if self.vertex_info[u]['degree'] > self.threshold:
# u是高度数顶点,使用邻接表
if u not in self.high_degree_vertices:
self.high_degree_vertices[u] = set()
self.high_degree_vertices[u].add(v)
else:
# u是低度数顶点,使用行压缩
if u not in self.low_degree_matrix:
self.low_degree_matrix[u] = {}
self.low_degree_matrix[u][v] = True
# 对v做同样处理(无向图)
if self.vertex_info[v]['degree'] > self.threshold:
if v not in self.high_degree_vertices:
self.high_degree_vertices[v] = set()
self.high_degree_vertices[v].add(u)
else:
if v not in self.low_degree_matrix:
self.low_degree_matrix[v] = {}
self.low_degree_matrix[v][u] = True
def get_neighbors(self, vertex):
if vertex in self.high_degree_vertices:
return list(self.high_degree_vertices[vertex])
elif vertex in self.low_degree_matrix:
return list(self.low_degree_matrix[vertex].keys())
return []
def _update_degree(self, vertex):
if vertex not in self.vertex_info:
self.vertex_info[vertex] = {'degree': 0}
self.vertex_info[vertex]['degree'] += 1
# 如果度数超过阈值,需要迁移数据
if self.vertex_info[vertex]['degree'] == self.threshold + 1:
self._migrate_to_high_degree(vertex)
def _migrate_to_high_degree(self, vertex):
"""将顶点从低度数存储迁移到高度数存储"""
if vertex in self.low_degree_matrix:
neighbors = self.low_degree_matrix[vertex]
self.high_degree_vertices[vertex] = set(neighbors.keys())
del self.low_degree_matrix[vertex]
# 更新邻居的引用
for neighbor in neighbors:
if neighbor in self.low_degree_matrix:
# 邻居还在低度数存储中
if vertex in self.low_degree_matrix[neighbor]:
del self.low_degree_matrix[neighbor][vertex]
# 注意:邻居如果已经是高度数顶点,其数据结构中已有这个边
这种混合策略在现实世界的图中特别有效,因为大多数图都遵循幂律分布——少数顶点拥有大量连接,多数顶点只有少量连接。
4.2 基于应用场景的定制优化
不同的图算法对数据结构有不同的访问模式,我们可以针对特定算法优化图的表示。
针对PageRank算法的优化: PageRank需要频繁计算“指向某个顶点的所有顶点”。邻接表存储的是出边,但PageRank需要入边。我们可以存储反向邻接表:
class GraphForPageRank:
def __init__(self):
self.out_edges = defaultdict(list) # 出边邻接表
self.in_edges = defaultdict(list) # 入边邻接表
def add_edge(self, from_vertex, to_vertex):
self.out_edges[from_vertex].append(to_vertex)
self.in_edges[to_vertex].append(from_vertex)
def get_incoming_vertices(self, vertex):
"""获取所有指向该顶点的顶点(PageRank需要)"""
return self.in_edges.get(vertex, [])
def get_outgoing_vertices(self, vertex):
"""获取该顶点指向的所有顶点"""
return self.out_edges.get(vertex, [])
针对社区发现算法的优化: 社区发现算法需要快速判断两个顶点是否在同一个社区(连通分量)。可以使用并查集(Union-Find)配合邻接表:
class GraphWithUnionFind:
def __init__(self):
self.graph = defaultdict(list)
self.parent = {} # 并查集父节点
self.rank = {} # 并查集秩
def add_vertex(self, v):
if v not in self.parent:
self.parent[v] = v
self.rank[v] = 0
def add_edge(self, u, v):
self.add_vertex(u)
self.add_vertex(v)
self.graph[u].append(v)
self.graph[v].append(u)
# 更新并查集
self._union(u, v)
def _find(self, x):
"""查找根节点(路径压缩)"""
if self.parent[x] != x:
self.parent[x] = self._find(self.parent[x])
return self.parent[x]
def _union(self, x, y):
"""合并两个集合"""
root_x = self._find(x)
root_y = self._find(y)
if root_x != root_y:
# 按秩合并
if self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
elif self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1
def are_connected(self, u, v):
"""判断两个顶点是否连通(同一社区)"""
if u not in self.parent or v not in self.parent:
return False
return self._find(u) == self._find(v)
4.3 使用networkx库的实践建议
Python的networkx库是图分析的瑞士军刀,它内部使用了邻接表表示。了解它的实现细节可以帮助我们更好地使用它。
import networkx as nx
import time
# 创建图的不同方式性能对比
def benchmark_graph_creation():
# 方式1:逐步添加边(最慢)
start = time.time()
G1 = nx.Graph()
for i in range(10000):
for j in range(i+1, i+11): # 每个顶点连接10个邻居
if j < 10000:
G1.add_edge(i, j)
print(f"逐步添加边: {time.time() - start:.2f}秒")
# 方式2:批量添加边(较快)
start = time.time()
edges = []
for i in range(10000):
for j in range(i+1, i+11):
if j < 10000:
edges.append((i, j))
G2 = nx.Graph()
G2.add_edges_from(edges)
print(f"批量添加边: {time.time() - start:.2f}秒")
# 方式3:使用生成器(最快)
start = time.time()
def edge_generator():
for i in range(10000):
for j in range(i+1, i+11):
if j < 10000:
yield (i, j)
G3 = nx.Graph()
G3.add_edges_from(edge_generator())
print(f"生成器添加边: {time.time() - start:.2f}秒")
# networkx的内存优化技巧
def optimize_networkx_memory():
"""对于超大规模图,使用nx.Graph()可能内存不足,可以尝试以下优化"""
# 1. 使用更紧凑的数据类型
G = nx.Graph()
# 添加顶点时指定类型(如果顶点是整数)
# networkx默认使用dict存储,对于整数顶点可以转换为连续整数
original_ids = [1000, 2000, 3000, 4000] # 原始ID可能不连续
mapping = {orig: i for i, orig in enumerate(original_ids)} # 映射到0,1,2,3
# 2. 对于只读图,考虑使用nx.from_scipy_sparse_array
import scipy.sparse as sp
# 创建稀疏矩阵
n = 10000
# 模拟稀疏连接:每个顶点随机连接10个其他顶点
rows, cols, data = [], [], []
for i in range(n):
neighbors = np.random.choice(n, size=10, replace=False)
for j in neighbors:
if i != j:
rows.append(i)
cols.append(j)
data.append(1)
sparse_matrix = sp.csr_matrix((data, (rows, cols)), shape=(n, n))
# 从稀疏矩阵创建图(更节省内存)
G_sparse = nx.from_scipy_sparse_array(sparse_matrix)
print(f"稀疏图顶点数: {G_sparse.number_of_nodes()}")
print(f"稀疏图边数: {G_sparse.number_of_edges()}")
return G_sparse
# 将networkx图转换为自定义高效结构
def nx_to_custom(graph):
"""将networkx图转换为更高效的邻接表表示"""
custom_graph = {}
# 对于无向图,确保对称性
for u, v in graph.edges():
if u not in custom_graph:
custom_graph[u] = set()
if v not in custom_graph:
custom_graph[v] = set()
custom_graph[u].add(v)
custom_graph[v].add(u)
# 对于孤立的顶点
for node in graph.nodes():
if node not in custom_graph:
custom_graph[node] = set()
return custom_graph
在实际项目中,我通常这样使用networkx:
- 原型阶段:直接使用networkx,快速验证算法
- 小规模数据:继续使用networkx,利用其丰富的算法库
- 大规模生产环境:将networkx图转换为自定义的高效结构,或直接使用像graph-tool、igraph这样的高性能库
选择图数据结构不是一次性的决定,而是一个需要根据数据特征、访问模式、性能要求和开发资源综合权衡的过程。邻接矩阵和邻接表各有优劣,没有绝对的好坏。关键是要理解它们的本质差异,知道在什么情况下该用什么工具,甚至如何组合使用它们。
290



被折叠的 条评论
为什么被折叠?



