想象你是一座陌生城市的游客,手里只有一张地铁线路图。你站在“市中心站”,想去“科技园站”。如果按照线路序号一条条找,可能会绕远路;如果你只想知道“哪些站点可以不用换乘到达”,又是一个问法;如果你想知道“从市中心出发,哪站是交通枢纽中的枢纽”,那又是另一个问题。这些不同的问法,本质上都指向同一个动作——在这张由“站点”和“线路”组成的网络上移动。这个动作,就是图遍历。 图数据库的世界里,数据被存储为“节点”(比如一个人、一个商品)和“边”(比如“关注”、“购买”)。图遍历算法,就是在这张网络上系统化“走一遍”的方法论。它决定了你如何从一个节点出发,找到朋友的朋友、发现商品之间的关联、甚至算出整个网络中谁最重要。这篇文章将用一张“城市地铁图”作为贯穿始终的比喻,带你理解 BFS、DFS、Dijkstra 最短路径和 PageRank 中心性这四大遍历思想,并看看它们在宽列存储和大规模分布式环境下面临的挑战。
掌握图遍历算法,意味着你不仅会“存”数据,更懂得如何“用”数据——这是从事社交推荐、知识图谱构建、反欺诈分析等 AI 应用场景的核心基本功,也是区分初级 CRUD 工程师与高级数据架构师的关键分水岭。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
图的存储结构
拿到一张地铁图,你第一件事是看懂它怎么画的——站点用圆点表示,线路用线段连接。在计算机里,这张图也需要一种“画法”,也就是存储结构。最直观的方式有两种:邻接表和邻接矩阵。
邻接表就像你手机里的通讯录。每个联系人(节点)的名字后面,跟着一串他认识的人(邻居节点)。要查“张三认识谁”,直接翻到张三那一页就行,非常高效。邻接矩阵则像一张班级座位表——行和列都是全班同学的名字,如果第 i 行第 j 列打了个勾,就说明 i 认识 j。这张表查起来极快(看一眼行列交叉处就知道有没有关系),但如果班级有 1000 人,这张表就要有 100 万个格子,大多数格子还空着,浪费空间。
宽列数据库(如 HBase、Cassandra)天然适合存储邻接表——每一行以节点 ID 作为行键,列族里存储所有邻居节点。当图数据规模达到千万级节点时,邻接矩阵所需的存储空间是天文数字,而邻接表只存储真实存在的关系,空间效率高出几个数量级。
1. 节点表示:每个节点(地铁站)在宽列存储中对应一行数据。
2. 邻居列表:节点的所有出边(从该站出发能到达的站)存储在同一个列族中。
3. 空间复杂度对比:邻接矩阵需要 \(V^2\) 个格子,邻接表只需要 \(V+E\) 个存储单元。
回到地铁图:现实中的地铁网络每条线路只连接相邻几站,没有哪条线会从“市中心站”直接连到所有其他站。这就是“稀疏图”——邻接表正是为这种场景量身定做的通讯录。而宽列存储把“通讯录”按行组织,让遍历算法在读取某个节点的全部邻居时,只需一次行读取,效率远高于传统关系型数据库的多次 JOIN 操作。
邻接表: {A: [B, C], B: [A, C, D], C: [A, B, E], ...}邻接矩阵: A B C D A [0, 1, 1, 0] B [1, 0, 1, 1] C [1, 1, 0, 1] D [0, 1, 1, 0]
广度优先搜索
想象你把一块石头扔进平静的湖面,涟漪从落水点开始,一圈一圈向外扩散。第一圈覆盖“一步就能到达”的区域,第二圈覆盖“两步才能到达”的区域。广度优先搜索(BFS)就是这种“水波式”的遍历:从起点出发,先访问所有直接邻居,再依次访问这些邻居的邻居,层层推进。
在计算机里,BFS 靠一个“队列”来维持遍历顺序。队列就像地铁站的候车通道——先进入的人先上车,先进先出(FIFO)。算法将起点放入队列,然后循环:从队首取出一个节点,访问它,把它的所有未访问邻居排到队尾。这个过程保证了你总是先处理“距离起点更近”的节点,再处理更远的节点。
回到湖面的涟漪——每一圈水波都代表了“从落水点出发,走 k 步能到达的所有位置”。BFS 在社交网络中的典型应用是“我认识的人里,谁和某个陌生人只隔了 3 层关系”?LinkedIn 的“二度人脉”推荐、Facebook 的“可能认识的人”,底层都是 BFS 在奔跑。它找到的路径,在“边的数量最少”这个意义下一定是最短的。
- 初始化将起点 s 的距离设为 0,表示“从起点到起点需要 0 步”,放入队列。
- 取出节点从队列头部取出一个节点 u,这就是当前“水波”到达的位置。
- 扩展邻居遍历 u 的所有邻居 v,如果 v 还没被访问过(距离还是无穷大),说明这是第一次“碰到”它。
- 入队等待将 v 放入队尾,等待后续处理它的邻居。
深度优先搜索
有一种探险家的遍历方式:选一条路,一路走到黑,撞了南墙才回头。深度优先搜索(DFS)就是这样的策略——它不满足于浅尝辄止,而是沿着一条路径不断深入,直到无法继续,然后回溯到最近的分岔口,换一条路继续深入。
DFS 的实现依赖“栈”结构——后进先出(LIFO),就像一叠盘子,你总是先拿走最上面那个。递归是 DFS 最自然的表达形式:进入一个节点,递归地访问它的第一个未访问邻居,等那一整条分支走完,再回到上一层继续。也可以用显式栈模拟递归,避免深度过大时栈溢出。
地铁比喻的另一种玩法:DFS 就像一个强迫症游客,拿到地图后坚持“每条支线都要走到底再回来”,虽然走的路径可能绕远,但能完整地探索每个角落。DFS 在工程中的典型应用是检测图中是否存在环(比如检测课程先修关系是否成死循环)、拓扑排序(安排课程学习顺序)、以及判断两个节点是否连通(迷宫求解)。它不需要维护距离信息,空间开销通常比 BFS 更小。
- 标记已访问进入节点 u,立刻标记为已访问,防止后续重复进入。
- 遍历邻居依次检查 u 的每个邻居 v。
- 递归深入如果 v 没被访问过,就递归地对 v 执行同样的过程——这就是“一路走到黑”。
Dijkstra算法
地铁图上的线路长度各不相同,从 A 站到 B 站可能只要 2 分钟,到 C 站却要 15 分钟。如果只关心“经过几个站”,BFS 就够了;但现实中的“路径最短”往往指的是“总耗时最少”。Dijkstra 算法就是为这种带权重的图设计的寻路算法。
Dijkstra 的核心思想是“贪心”——每次都从当前已知的所有候选路径中,选择总距离最短的那一条进行扩展。算法维护一个“已确定最短距离”的集合,和一张“当前已知最短距离”的表。每次从未确定的节点中挑出距离最小的那个,把它标记为确定,然后更新它所有邻居的距离——如果经过这个节点到达邻居比之前的路径更短,就更新邻居的距离。
把你上班的通勤路线想成一次 Dijkstra:地铁每段的行驶时间是边的权重,换乘等待时间也是权重。Google Maps 的路线规划、Uber 的派单调度、物流公司的配送路径优化,背后都有 Dijkstra 或其变体(如 A* 算法)在运行。它和 BFS 的区别在于——BFS 假设每条路一样长,而 Dijkstra 尊重每条路的“真实代价”。
- 初始化距离起点 s 的距离为 0,其他所有节点距离为无穷大。
- 选择当前最近节点在尚未确定最短距离的节点中,选出 dist 最小的那个 u。这是贪心策略的关键动作。
- 标记确定将 u 加入已确定集合 S。一旦确定,dist[u] 就是真正的全局最短距离,不会被后续更新。
- 松弛操作检查 u 的每个邻居 v,如果通过 u 到 v 的路径比当前记录的更短,就更新。
PageRank中心性
前面几种算法回答的是“怎么走”的问题,而 PageRank 回答的是“谁最重要”的问题。想象一个社交网络里,判断一个人是否重要,光看粉丝数不够——如果你有 1000 个粉丝但都是僵尸号,和一个有 100 个粉丝但都是行业大 V 的人相比,后者在网络上更有影响力。PageRank 正是捕捉这种“被重要的人关注,所以你也很重要”的递归逻辑。
PageRank 的数学模型来自“随机游走”:假设一个用户随机点击链接,从当前网页跳到下一个网页。经过足够多次跳转后,用户停留在每个网页上的概率,就是该网页的 PageRank 值。这个值越高,说明网页被访问的概率越大,也就越“重要”。计算方式是迭代的:每一轮,每个节点把自己当前的权重平均分给所有出边邻居,同时接收所有入边邻居分来的权重。
回到城市的比喻:PageRank 是在回答“哪些地铁站是真正的交通枢纽”。一个站即使连接线路不多,但如果它是核心换乘站(像北京西直门),它的重要性会通过“被其他重要站点连接”而放大。Google 当年靠 PageRank 在搜索质量上碾压对手,今天它被广泛应用于社交网络的影响力分析(找到意见领袖)、反欺诈(发现异常密集的资金往来节点)、以及知识图谱中的实体重要性排序。
- 权重传播每个节点 v 将自己的 PageRank 值平均分给它的所有出边邻居。
- 累加入链节点 u 的 PageRank 值等于所有指向它的节点(入链邻居)分配的权重之和。
- 阻尼因子d(通常取 0.85)模拟用户“继续点击”的概率,(1-d) 表示用户随机跳到任意新页面的概率。
分布式遍历挑战
当图数据增长到十亿节点、百亿边时,单台机器的内存已经无法容纳整张图。怎么办?把图切碎,分布到几十台甚至上千台机器上。但“切碎”本身带来一个棘手的问题:原本相邻的节点,现在住在不同的机器上。
数据分片主要有两种策略:按节点 ID 哈希分片(把 A 的邻居 B 分到另一台机器),或者按社区/子图分片(尽量把联系紧密的节点放在同一台机器)。前者负载均衡,但遍历时需要频繁跨机器通信——每访问一个邻居,如果它不在本地,就要发一次网络请求。后者能减少通信,但可能造成某些机器负载过高。
这就像你要画一张全国地铁图——但每个城市的地图分别由不同的人保管。要回答“从北京到上海坐地铁怎么走”,你得不断打电话向上海那边的同事确认“下一站在你那儿吗?”每打一次电话就有一秒延迟,路线越长、涉及城市越多,电话费越贵。这正是 GraphLab、Pregel 等分布式图计算框架试图解决的问题:通过批量同步(BSP 模型)和减少通信轮次来摊薄开销,让“打电话”的次数尽量少。
- 本地计算优先每台机器先遍历存储在本地的那部分节点和边。
- 跨机消息传递当遍历到一条边的另一端不在本机时,需要将“当前状态”打包发送到对方机器。
- 迭代收敛代价像 PageRank 这样的迭代算法,每轮全局迭代都需要所有机器同步一次,等待最慢的那台。
AI 应用场景
社交推荐系统
阶段:特征工程与召回
解决的问题:传统协同过滤只能基于用户-物品二部图做浅层匹配,无法捕捉“朋友的朋友喜欢什么”这类多跳关系。BFS 遍历能在毫秒级找到用户二度、三度网络内的兴趣热点,显著提升召回质量。
具体案例:LinkedIn 的“可能认识的人”功能。当用户注册后,系统以该用户为起点执行 BFS,遍历到二度人脉(朋友的朋友),再按共同好友数排序推荐。整个过程需要在分布式图存储上每秒执行数十万次 BFS 查询。
金融反欺诈检测
阶段:图特征提取与异常检测
解决的问题:欺诈团伙往往通过复杂的转账网络洗钱,单笔交易看起来正常,但整个网络的结构异常。通过 PageRank 中心性计算,可以识别出资金网络中“异常重要”的节点——它们可能是资金归集账户。
具体案例:某银行构建了包含 5 亿节点、20 亿边的大规模转账图谱。每天运行一次 PageRank 迭代计算,标记中心性显著高于正常水平的账户,再结合规则引擎人工审核。该系统将团伙欺诈的召回率提升了 40%。
知识图谱问答
阶段:知识检索与推理路径生成
解决的问题:用户提问“乔布斯创办了哪家公司”,知识图谱中需要从实体“乔布斯”出发,沿着“创办”关系边遍历到目标实体。Dijkstra 算法可以找到语义上最相关的关系路径,避免被无关关系干扰。
具体案例:搜索引擎的知识卡片功能。当用户搜索一个实体时,系统在知识图谱上执行带权重的图遍历——关系越紧密(权重越小)的边优先被遍历,最终返回最相关的实体和关系路径作为答案。
Python 演示
import heapqfrom collections import deque class GraphTraversal: """图遍历算法演示: BFS, DFS, Dijkstra""" def __init__(self): # 邻接表存储: {node: [(neighbor, weight), ...]} self.graph = { 'A': [('B', 4), ('C', 2)], 'B': [('A', 4), ('C', 1), ('D', 5)], 'C': [('A', 2), ('B', 1), ('D', 8), ('E', 10)], 'D': [('B', 5), ('C', 8), ('E', 2)], 'E': [('C', 10), ('D', 2)] } def bfs(self, start): """广度优先搜索: 水波式层层扩散, 计算最短跳数""" dist = {node: float('inf') for node in self.graph} dist[start] = 0 queue = deque([start]) while queue: node = queue.popleft() for neighbor, _ in self.graph[node]: if dist[neighbor] == float('inf'): dist[neighbor] = dist[node] + 1 queue.append(neighbor) return dist def dfs(self, start, visited=None): """深度优先搜索: 一条路走到黑, 递归实现""" if visited is None: visited = set() visited.add(start) for neighbor, _ in self.graph[start]: if neighbor not in visited: self.dfs(neighbor, visited) return visited def dijkstra(self, start): """Dijkstra最短路径: 贪心策略, 优先队列实现""" dist = {node: float('inf') for node in self.graph} dist[start] = 0 pq = [(0, start)] while pq: cur_dist, node = heapq.heappop(pq) if cur_dist > dist[node]: continue for neighbor, weight in self.graph[node]: new_dist = cur_dist + weight if new_dist < dist[neighbor]: dist[neighbor] = new_dist heapq.heappush(pq, (new_dist, neighbor)) return dist if __name__ == '__main__': gt = GraphTraversal() print('BFS from A:', gt.bfs('A')) print('DFS from A:', gt.dfs('A')) print('Dijkstra from A:', gt.dijkstra('A'))
self.graph = {'A': [('B', 4), ...]}用字典实现邻接表,键是节点名,值是 (邻居,权重) 列表。queue = deque([start])BFS 使用双端队列作为先进先出的“候车通道”。dist[neighbor] = dist[node] + 1BFS 中每经过一条边,跳数加 1,第一次到达即最短跳数。visited.add(start)DFS 递归前先标记已访问,防止死循环。heapq.heappop(pq)Dijkstra 用优先队列每次取出当前距离最小的节点,这是贪心策略的体现。if new_dist < dist[neighbor]松弛操作:发现更短路径就更新记录。
常见误区
误区“BFS 和 DFS 都是找最短路径的,只是遍历顺序不同而已。”很多初学者以为两种搜索方法只是“顺序偏好”,结果完全一样。
正解只有无权图(所有边权重都视为 1)中,BFS 才能保证找到最少边数的路径。DFS 找到的只是“某一条可行路径”,完全可能绕远路。类比地铁:BFS 是“一圈圈扩散”,第一圈碰到目标就一定是最短换乘次数;DFS 是“一路走到黑”,走错支线就得折返,路径长度没保障。对于有权图,BFS 直接失效,必须用 Dijkstra。
为何常见:“最短路径”这个概念在无权图里容易让人忽略“边的权重”,而初学者接触的示例图大多是无权图,BFS 恰好总能找到最短路径,形成错误泛化。
误区“邻接矩阵查找两个节点是否相连只需要 O(1) 时间,所以比邻接表更高效,应该优先使用。”
正解邻接矩阵的 O(1) 查询是在“浪费大量存储空间”的前提下达成的。对于 V=1000 万的社交网络,邻接矩阵需要 10^14 个格子(约 100TB),而邻接表只存储真实存在的边(假设每人 200 个好友,约 20 亿条边,约 16GB)。宽列数据库和大多数图数据库都采用邻接表思想存储。类比地铁图:你要一张“本站直接连接的站点”清单,查通讯录(邻接表)比查一本包含所有站点两两关系的巨型表格(邻接矩阵)现实得多。
为何常见:算法教科书喜欢用邻接矩阵讲解图论,因为数学表达简洁,导致初学者误以为它是工程首选。
误区“PageRank 就是把指向某个网页的链接数数一遍,链接越多排名越高。”
正解PageRank 的核心创新在于“加权投票”——被重要节点指向的权重更高。一个被 10 个大网站指向的页面,排名远高于被 1000 个垃圾站指向的页面。就像社交影响力:你有 1000 个粉丝但全是机器人,和一个有 100 个粉丝但都是行业领袖的人,后者在网络上更有话语权。统计入链数量相当于只数粉丝数,PageRank 则考虑了“粉丝的质量”。
为何常见:PageRank 名字里的“Rank“容易让人只想到排序,而忽略其背后的随机游走数学模型中”权重传递”的精髓。
误区“分布式遍历就是把 BFS 代码放在集群上跑,用 MapReduce 处理就行,没什么大不了的。”
正解分布式图遍历面临的核心挑战是“数据局部性”——相邻节点可能存储在不同机器上,每跨一次机器就是一次毫秒级网络通信,比本地内存访问慢百万倍。简单的 MapReduce 模型需要多轮 MR 作业,每轮都要全量读写磁盘,效率极低。Pregel、GraphLab 等框架采用"Think Like a Vertex"的 BSP 模型,让每个顶点在自己的机器上执行计算,通过批量消息传递减少通信轮次。就像全国地铁调度:每个城市(机器)在自己的地盘内调度没问题,但跨城市协调需要统一的通信协议和高昂的电话费。
为何常见:初学者习惯把分布式想象成“多台电脑一起算”,忽略了分布式系统中最本质的网络通信瓶颈。
误区“求最短路径用 Dijkstra 就对了,它是唯一且最优的算法。”
正解Dijkstra 适用于“单源 + 正权边”的场景。如果只需要求两个特定点之间的路径,A* 算法用启发式函数引导搜索方向,通常更快;如果图中有负权边,需要 Bellman-Ford;如果要求所有点对之间的最短路径,Floyd-Warshall 更直接。类比导航:Dijkstra 是“没看地图硬算所有路线的总距离”,A* 是“朝着目标方向优先找路”——后者在目标明确时效率高得多。
为何常见:Dijkstra 在算法课上的存在感太强,“解决最短路径”这个印象被过度简化,忽略了算法的适用边界。
面试题
Q1. BFS 和 DFS 在无权图中都能完成图遍历,如果要找从起点到终点的最少边数路径,应该选择哪种算法?
- A.BFS,因为它逐层扩散,第一次到达目标即走最少边数← 正确答案
- B.DFS,因为它沿路径深入,能快速找到一条完整路径
- C.两种都可以,结果相同
- D.两种都不行,必须用 Dijkstra 算法
答案:A。BFS 按“距离起点的跳数”逐层扩展,水波式扩散保证第一次碰到目标节点时走的边数一定最少。DFS 沿一条路走到底,找到的路径完全可能绕远路。Dijkstra 是处理“带权重边”的算法,在无权图中退化为 BFS 的逻辑,但没有必要额外使用。
Q2. 邻接表和邻接矩阵相比,最大的优势是什么?
- A.查询两个节点是否相邻更快
- B.在稀疏图上存储空间效率更高← 正确答案
- C.遍历所有邻居时无需额外操作
- D.占用内存大小与节点数无关
答案:B。邻接表只存储真实存在的边,空间复杂度为 O(V+E);邻接矩阵固定需要 V² 个格子。在真实图(社交网络、地铁图)中 E 远小于 V²,邻接表空间优势巨大。邻接矩阵查询“两点是否相邻”确实只要 O(1),但代价是海量空间浪费。宽列数据库如 HBase 正是采用类似邻接表的存储方式来支持大规模图数据。
Q3. Dijkstra 算法为什么要求图中不能有负权边?
- A.负权边会导致算法内存溢出
- B.负权边会破坏贪心策略的正确性,已确定最短距离的节点可能被更短的路径再次更新← 正确答案
- C.负权边会导致图不再是连通图
- D.负权边会导致 BFS 队列死循环
答案:B。Dijkstra 的贪心假设是“已确定最短距离的节点不会再被更新”。一旦有负权边,可能出现“先确定的路径更长,后来经过负权边反而更短”的情况——比如 A→B 权重 10,A→C 权重 1,C→B 权重 -5,则通过 C 到 B 只要 -4。这个假设崩塌后,整个算法的正确性基础就不存在了。处理负权边需要 Bellman-Ford 算法。
Q4. PageRank 计算中,阻尼因子 d 通常取 0.85,它的含义是什么?
- A.用户有 85% 的概率点击当前网页上的链接继续浏览,15% 的概率跳到任意新页面← 正确答案
- B.用户有 85% 的概率关闭浏览器,15% 的概率继续浏览
- C.网页有 85% 的概率被搜索引擎收录
- D.网页有 15% 的概率被黑客攻击
答案:A。阻尼因子模拟真实用户的浏览行为——大多数时候用户会顺着当前页面的链接继续跳转(对应 d=0.85),但也有可能直接输入新网址或从书签进入任意页面(对应 1-d=0.15)。这个“随机跳转项”保证了 PageRank 迭代的收敛性,避免某些没有任何出链的“黑洞页面”把权重全部吸收。
Q5. 在分布式图计算中,以下哪些是面临的真实挑战?
- A.数据分片后,相邻节点可能存储在不同机器上,遍历时需要跨机通信← 正确答案
- B.迭代算法每轮都需要全局同步,速度受制于最慢的机器← 正确答案
- C.单机内存无法容纳整个大图的必然性与数据访问局部性的矛盾← 正确答案
- D.分布式系统无法实现 BFS 算法
答案:ABC。A 是数据局部性问题——哈希分片可能导致一条边的两端在不同机器上,每次跨机访问都是毫秒级网络延迟。B 是 BSP 模型的同步屏障代价,PageRank 每轮迭代都要所有机器完成同步才能进入下一轮。C 是分布式图计算的根本原因和核心矛盾。D 显然是错的——分布式系统完全能实现 BFS,只是需要精心设计消息传递和状态管理。
Q6. 在无权图中,BFS 计算出的距离 dist[v] 的含义是什么?
- A.从起点到 v 的总路径长度(边权累加)
- B.从起点到 v 经过的最少边数← 正确答案
- C.从起点到 v 经过的最大边数
- D.从起点到 v 经过的节点总数减 1 的相反数
答案:B。BFS 的核心性质是“逐层扩散”,第一次到达节点 v 时经过的边数一定是最少的。dist[v] 被定义为从起点到 v 的最少跳数。在无权图中每条边视为等权重,所以“最少边数”就是“最短路径”的度量。类比地铁换乘:dist[v]=3 意味着最少坐 3 段地铁能到达 v 站。
Q7. 关于 PageRank 的迭代计算,以下说法正确的是哪些?
- A.每个节点将自己的 PageRank 值平均分给所有出边邻居← 正确答案
- B.节点的入链来自重要节点的投票权重更高,形成“重要性传递”← 正确答案
- C.经过足够多轮迭代后,各节点 PageRank 值收敛到稳定分布← 正确答案
- D.PageRank 值只与入链数量有关,与出链数量无关
答案:ABC。A 是 PageRank 的权重传播规则——出边越多,每条边分到的权重越少。B 是核心创新——权重的“质量”会通过入链节点的 PR 值传递。C 是数学保证——在连通图上迭代必然收敛。D 错误:出链数量直接影响该节点的权重大小。类比口碑传播:你认识 10 个朋友和认识 100 个朋友,向每个人推荐某个产品时的影响力浓度是不同的。
Q8. 哪些数据结构分别支撑 BFS 和 DFS 的遍历顺序?
- A.BFS 用栈,DFS 用队列
- B.BFS 用队列,DFS 用栈← 正确答案
- C.BFS 和 DFS 都用栈
- D.BFS 和 DFS 都用队列
答案:B。BFS 需要先进先出的队列,保证先访问的节点的邻居也先被访问,实现水波式扩散。DFS 需要后进先出的栈(或递归调用栈),保证沿着一条路径深入到底再回溯。类比地铁站:BFS 是“候车通道”先来先走;DFS 是“一叠盘子”后来的反而先拿走。
Q9. 宽列数据库(如 HBase)天然适合存储哪种图结构?
- A.邻接矩阵,因为列族可以映射为矩阵的列
- B.邻接表,因为每一行可以存储一个节点的所有邻居← 正确答案
- C.边列表,因为每一行只存储一条边
- D.邻接矩阵和邻接表都完全不适合
答案:B。宽列数据库以行键为索引,行键就是节点 ID,列族中的每一列可以存一个邻居节点(列名=邻居 ID,值=边权重)。读取一行就能拿到某个节点的全部邻居,这与邻接表的“通讯录”结构完美匹配。这种设计让 BFS 等遍历算法在读取节点邻居时只需要一次行读取操作,效率远高于关系型数据库的多次 JOIN。
Q10. 已知从 A 到 B 的最短路径经过节点序列 A→C→D→B,总权重为 10。以下哪种情况一定不会出现?
- A.存在路径 A→E→B,总权重为 15
- B.A 到 C 的最短距离是 3,C 到 D 的最短距离是 4,D 到 B 的最短距离是 3
- C.A 到 C 的最短距离是 5,但 A→C→D→B 仍是 A 到 B 的最短路径
- D.存在路径 A→F→B,总权重为 8← 正确答案
答案:D。Dijkstra 算法的核心性质是“最短路径的子路径也是最短路径”——如果 A→C→D→B 是 A 到 B 的最短路径(权重 10),那么 A→C 这一段也必须是 A 到 C 的最短路径。若存在总权重 8 的路径 A→F→B,则 A 到 B 的最短距离至少是 8,与题目已知的 10 矛盾(除非边权为负,但 Dijkstra 不允许)。D 选项中总权重 8 < 10,直接违反“已知最短为 10”的前提。