什么是图结构?
📖 基本定义
图(Graph)是由顶点(Vertex)和边(Edge)组成的非线性数据结构,表示为 G = (V, E)。
与树不同,图可以有环,任意两个顶点之间可以有边相连,是描述复杂关系的最通用数据结构。
✨ 核心特点
• 顶点之间可以有多条路径
• 可以存在环(循环路径)
• 边可以有方向(有向图)或无方向(无向图)
• 边可以带权重,表示距离、成本等
🎯 图 vs 树
• 树:特殊的图,无环且连通
• 图:可以有环,可以不连通
• 树有 n-1 条边(n个节点)
• 图的边数可以是 0 到 n(n-1)/2
核心术语解析
常见图的类型
无向图 (Undirected Graph)
边没有方向的图
📌 特点
- 边连接两个顶点,没有起点终点之分
- 如果A连接B,则B也连接A
- 顶点的度等于与之相连的边数
💼 应用
- 社交网络的好友关系
- 无向道路网络
- 计算机网络拓扑
有向图 (Directed Graph)
边有方向的图
📌 特点
- 边有起点和终点,用箭头表示
- A→B 不等于 B→A
- 顶点有入度(进入的边)和出度(出去的边)
💼 应用
- 网页链接关系(PageRank)
- 任务依赖关系
- 微博/Twitter关注关系
加权图 (Weighted Graph)
边带有权值的图
📌 特点
- 每条边都有一个数值(权重)
- 权重可表示距离、成本、时间等
- 可以是有向或无向的
💼 应用
- 地图导航(道路距离)
- 航班票价查询
- 网络带宽/延迟
有向无环图 (DAG)
没有环的有向图
📌 特点
- 有向图且不存在环
- 可以进行拓扑排序
- 有明确的依赖层次
💼 应用
- 任务调度系统
- 编译器依赖分析
- Git版本控制
- 区块链结构
完全图 (Complete Graph)
任意两点都直接相连
📌 特点
- n个顶点有 n(n-1)/2 条边
- 任意两点距离为1
- 是最稠密的简单图
🔧 性质
- 每个顶点的度为 n-1
- 包含所有可能的子图
- 常用于算法分析的极端情况
二部图 (Bipartite Graph)
顶点可分两组,边只在组间
📌 特点
- 顶点分为两个互斥的集合
- 边只连接不同集合的顶点
- 没有奇数长度的环
💼 应用
- 任务分配问题
- 推荐系统(用户-商品)
- 匹配问题
图的遍历算法
🎮 遍历可视化演示
广度优先搜索 (BFS)
策略:先访问所有邻居,再访问邻居的邻居
数据结构:队列 (FIFO)
应用:最短路径、层级遍历、社交网络度数
// 广度优先搜索 (BFS) - 使用队列 function bfs(graph, start) { const visited = new Set(); const queue = [start]; const result = []; while (queue.length > 0) { const vertex = queue.shift(); if (!visited.has(vertex)) { visited.add(vertex); result.push(vertex); for (const neighbor of graph[vertex]) { if (!visited.has(neighbor)) { queue.push(neighbor); } } } } return result; } // 深度优先搜索 (DFS) - 使用递归/栈 function dfs(graph, start, visited = new Set()) { const result = []; function traverse(vertex) { if (visited.has(vertex)) return; visited.add(vertex); result.push(vertex); for (const neighbor of graph[vertex]) { traverse(neighbor); } } traverse(start); return result; }
最短路径算法
🔷 Dijkstra算法
适用场景:单源最短路径,非负权重
策略:贪心,每次选择当前距离最小的未访问顶点
时间复杂度:O((V+E)logV) 使用优先队列
应用:地图导航、网络路由
🔶 Bellman-Ford算法
适用场景:单源最短路径,可处理负权边
策略:动态规划,松弛所有边V-1次
时间复杂度:O(VE)
特点:能检测负权环
🔴 Floyd-Warshall算法
适用场景:多源最短路径(所有点对)
策略:动态规划,考虑所有中间节点
时间复杂度:O(V³)
应用:距离矩阵计算、传递闭包
• 单源、无负边 → Dijkstra(最高效)
• 单源、有负边 → Bellman-Ford(能检测负环)
• 所有点对最短路 → Floyd-Warshall(代码简洁)
• 无权图最短路 → BFS(最简单)
// Dijkstra算法 - 使用优先队列优化 function dijkstra(graph, start) { const distances = {}; const visited = new Set(); const pq = [[0, start]]; // [距离, 顶点] // 初始化距离为无穷大 for (const vertex in graph) { distances[vertex] = vertex === start ? 0 : Infinity; } while (pq.length > 0) { // 取出当前最小距离的顶点 pq.sort((a, b) => a[0] - b[0]); const [dist, vertex] = pq.shift(); if (visited.has(vertex)) continue; visited.add(vertex); // 更新邻居的距离 for (const [neighbor, weight] of graph[vertex]) { const newDist = dist + weight; if (newDist < distances[neighbor]) { distances[neighbor] = newDist; pq.push([newDist, neighbor]); } } } return distances; }
交互式图结构演示
动态图可视化
输入边来构建图,支持有向图和无向图。可以执行BFS/DFS遍历并查看结果。拖拽节点可调整布局。
算法复杂度对比
| 算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| BFS | O(V + E) | O(V) | 无权图最短路、层级遍历 |
| DFS | O(V + E) | O(V) | 连通性检测、拓扑排序、路径搜索 |
| Dijkstra | O((V+E)log V) | O(V) | 单源最短路径(非负权) |
| Bellman-Ford | O(VE) | O(V) | 单源最短路径(可负权) |
| Floyd-Warshall | O(V³) | O(V²) | 全源最短路径 |
| Prim (MST) | O((V+E)log V) | O(V) | 最小生成树(稠密图) |
| Kruskal (MST) | O(E log E) | O(V) | 最小生成树(稀疏图) |
| 拓扑排序 | O(V + E) | O(V) | DAG依赖排序 |
* V = 顶点数, E = 边数
实际应用场景
地图导航
城市道路网络建模,计算最短/最快路线
社交网络
好友关系分析、推荐系统、社群发现
网络路由
数据包传输路径选择、负载均衡
依赖管理
包管理器的依赖解析、构建顺序
网页排名
PageRank算法、搜索引擎优化
生物信息
蛋白质相互作用网络、基因调控网络
供应链
物流网络优化、最优配送路径
游戏AI
寻路算法(A*)、状态空间搜索
图的存储结构
📐 邻接矩阵
结构:V×V 的二维数组,matrix[i][j] 表示 i 到 j 的边
优点:
• 判断两点是否相邻:O(1)
• 实现简单直观
缺点:
• 空间 O(V²),稀疏图浪费空间
• 遍历所有边需要 O(V²)
📋 邻接表
结构:每个顶点维护一个邻居列表
优点:
• 空间 O(V+E),适合稀疏图
• 遍历顶点的邻居效率高
缺点:
• 判断两点是否相邻需要 O(度)
• 实现相对复杂
🔗 边集数组
结构:存储所有边的数组,每条边包含起点、终点、权重
优点:
• 适合按边操作的算法
• 如 Kruskal 最小生成树算法
缺点:
• 查找顶点的邻居效率低 O(E)
• 不适合频繁查询邻接关系