09-图
💡 核心结论
图的本质
定义:节点(顶点)+ 边的集合,G = (V, E)
分类:有向/无向、有权/无权、连通/非连通
存储:邻接矩阵O(V²)、邻接表O(V+E)
遍历:DFS深度优先、BFS广度优先
应用:社交网络、地图导航、依赖关系
存储方式对比
方式 |
空间 |
查边 |
遍历邻居 |
适用 |
|---|---|---|---|---|
邻接矩阵 |
O(V²) |
O(1) |
O(V) |
稠密图、需频繁查边 |
邻接表 |
O(V+E) |
O(degree) |
O(degree) |
稀疏图、节省空间 |
图的遍历
算法 |
数据结构 |
空间 |
应用 |
|---|---|---|---|
DFS |
栈/递归 |
O(V) |
路径、连通性、拓扑排序 |
BFS |
队列 |
O(V) |
最短路径、层级关系 |
经典算法
最短路径:Dijkstra、Bellman-Ford、Floyd
最小生成树:Prim、Kruskal
拓扑排序:DFS、Kahn算法
强连通分量:Kosaraju、Tarjan
🎯 图的表示
邻接矩阵
# 无向图
graph = [
[0, 1, 1, 0], # 0连接1,2
[1, 0, 0, 1], # 1连接0,3
[1, 0, 0, 1], # 2连接0,3
[0, 1, 1, 0] # 3连接1,2
]
# 有向图
graph = [
[0, 1, 0, 0], # 0→1
[0, 0, 1, 1], # 1→2,3
[0, 0, 0, 1], # 2→3
[0, 0, 0, 0] # 3无出边
]
# 带权图
graph = [
[0, 4, 2, 0],
[4, 0, 0, 5],
[2, 0, 0, 3],
[0, 5, 3, 0]
]
邻接表
# 无向图
graph = {
0: [1, 2],
1: [0, 3],
2: [0, 3],
3: [1, 2]
}
# 有向图
graph = {
0: [1],
1: [2, 3],
2: [3],
3: []
}
# 带权图
graph = {
0: [(1, 4), (2, 2)],
1: [(0, 4), (3, 5)],
2: [(0, 2), (3, 3)],
3: [(1, 5), (2, 3)]
}
🌲 DFS深度优先
递归实现
def dfs(graph, node, visited):
visited.add(node)
print(node)
for neighbor in graph[node]:
if neighbor not in visited:
dfs(graph, neighbor, visited)
应用:检测环
def has_cycle(graph):
visited = set()
rec_stack = set()
def dfs(node):
visited.add(node)
rec_stack.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
if dfs(neighbor):
return True
elif neighbor in rec_stack:
return True # 找到环
rec_stack.remove(node)
return False
for node in graph:
if node not in visited:
if dfs(node):
return True
return False
🌊 BFS广度优先
最短路径
from collections import deque
def shortest_path(graph, start, end):
queue = deque([(start, [start])])
visited = {start}
while queue:
node, path = queue.popleft()
if node == end:
return path
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return None
🛤️ 拓扑排序
Kahn算法(BFS)
def topological_sort(graph):
# 计算入度
in_degree = {node: 0 for node in graph}
for node in graph:
for neighbor in graph[node]:
in_degree[neighbor] += 1
# 入度为0的节点入队
queue = [node for node in graph if in_degree[node] == 0]
result = []
while queue:
node = queue.pop(0)
result.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
return result if len(result) == len(graph) else None
🎯 最短路径算法
Dijkstra算法(单源最短路径)
import heapq
def dijkstra(graph, start):
distances = {node: float('inf') for node in graph}
distances[start] = 0
pq = [(0, start)] # (distance, node)
while pq:
curr_dist, curr_node = heapq.heappop(pq)
if curr_dist > distances[curr_node]:
continue
for neighbor, weight in graph[curr_node]:
distance = curr_dist + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(pq, (distance, neighbor))
return distances
Floyd算法(所有点对最短路径)
def floyd(graph):
n = len(graph)
dist = [[float('inf')] * n for _ in range(n)]
# 初始化
for i in range(n):
dist[i][i] = 0
for j in range(n):
if graph[i][j] != 0:
dist[i][j] = graph[i][j]
# Floyd
for k in range(n):
for i in range(n):
for j in range(n):
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
return dist
🌳 最小生成树
Kruskal算法(并查集)
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
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):
px, py = self.find(x), self.find(y)
if px != py:
self.parent[px] = py
return True
return False
def kruskal(n, edges):
# edges: [(weight, u, v)]
edges.sort() # 按权重排序
uf = UnionFind(n)
mst = []
total_weight = 0
for weight, u, v in edges:
if uf.union(u, v):
mst.append((u, v, weight))
total_weight += weight
return mst, total_weight
📚 LeetCode练习
基础
进阶
💻 完整代码实现
Python 实现
1"""
2图的实现和基本算法
3"""
4
5from collections import deque, defaultdict
6
7
8class Graph:
9 """图的邻接表实现"""
10
11 def __init__(self, directed=False):
12 self.graph = defaultdict(list)
13 self.directed = directed
14
15 def add_edge(self, u, v, weight=1):
16 """添加边"""
17 self.graph[u].append((v, weight))
18 if not self.directed:
19 self.graph[v].append((u, weight))
20
21 def dfs(self, start):
22 """深度优先遍历"""
23 visited = set()
24 result = []
25
26 def dfs_helper(node):
27 visited.add(node)
28 result.append(node)
29 for neighbor, _ in self.graph[node]:
30 if neighbor not in visited:
31 dfs_helper(neighbor)
32
33 dfs_helper(start)
34 return result
35
36 def bfs(self, start):
37 """广度优先遍历"""
38 visited = {start}
39 queue = deque([start])
40 result = []
41
42 while queue:
43 node = queue.popleft()
44 result.append(node)
45
46 for neighbor, _ in self.graph[node]:
47 if neighbor not in visited:
48 visited.add(neighbor)
49 queue.append(neighbor)
50
51 return result
52
53 def has_cycle(self):
54 """检测有向图是否有环"""
55 visited = set()
56 rec_stack = set()
57
58 def dfs(node):
59 visited.add(node)
60 rec_stack.add(node)
61
62 for neighbor, _ in self.graph[node]:
63 if neighbor not in visited:
64 if dfs(neighbor):
65 return True
66 elif neighbor in rec_stack:
67 return True
68
69 rec_stack.remove(node)
70 return False
71
72 for node in self.graph:
73 if node not in visited:
74 if dfs(node):
75 return True
76 return False
77
78 def topological_sort(self):
79 """拓扑排序(Kahn算法)"""
80 in_degree = {node: 0 for node in self.graph}
81 for node in self.graph:
82 for neighbor, _ in self.graph[node]:
83 in_degree[neighbor] += 1
84
85 queue = [node for node in self.graph if in_degree[node] == 0]
86 result = []
87
88 while queue:
89 node = queue.pop(0)
90 result.append(node)
91
92 for neighbor, _ in self.graph[node]:
93 in_degree[neighbor] -= 1
94 if in_degree[neighbor] == 0:
95 queue.append(neighbor)
96
97 return result if len(result) == len(self.graph) else None
98
99
100def dijkstra(graph, start):
101 """Dijkstra最短路径算法"""
102 import heapq
103
104 distances = {node: float('inf') for node in graph}
105 distances[start] = 0
106 pq = [(0, start)]
107
108 while pq:
109 curr_dist, curr_node = heapq.heappop(pq)
110
111 if curr_dist > distances[curr_node]:
112 continue
113
114 for neighbor, weight in graph[curr_node]:
115 distance = curr_dist + weight
116 if distance < distances[neighbor]:
117 distances[neighbor] = distance
118 heapq.heappush(pq, (distance, neighbor))
119
120 return distances
121
122
123def demo():
124 """演示图操作"""
125 print("=== 图演示 ===\n")
126
127 # 创建无向图
128 g = Graph(directed=False)
129 g.add_edge(0, 1)
130 g.add_edge(0, 2)
131 g.add_edge(1, 3)
132 g.add_edge(2, 3)
133
134 print("DFS遍历:", g.dfs(0))
135 print("BFS遍历:", g.bfs(0))
136
137 # 有向图
138 print("\n=== 拓扑排序 ===\n")
139 dag = Graph(directed=True)
140 dag.add_edge(5, 2)
141 dag.add_edge(5, 0)
142 dag.add_edge(4, 0)
143 dag.add_edge(4, 1)
144 dag.add_edge(2, 3)
145 dag.add_edge(3, 1)
146
147 print("拓扑排序:", dag.topological_sort())
148
149 # Dijkstra
150 print("\n=== Dijkstra最短路径 ===\n")
151 graph_dict = {
152 0: [(1, 4), (2, 2)],
153 1: [(2, 1), (3, 5)],
154 2: [(3, 3)],
155 3: []
156 }
157 distances = dijkstra(graph_dict, 0)
158 print("从节点0到各节点的最短距离:", distances)
159
160
161if __name__ == '__main__':
162 demo()
163
C++ 实现
1/**
2 * 图的实现和基本算法
3 */
4
5#include <iostream>
6#include <vector>
7#include <queue>
8#include <unordered_map>
9#include <unordered_set>
10#include <algorithm>
11#include <climits>
12
13using namespace std;
14
15class Graph {
16private:
17 unordered_map<int, vector<pair<int, int>>> graph; // node -> [(neighbor, weight)]
18 bool directed;
19
20public:
21 Graph(bool dir = false) : directed(dir) {}
22
23 void addEdge(int u, int v, int weight = 1) {
24 graph[u].push_back({v, weight});
25 if (!directed) {
26 graph[v].push_back({u, weight});
27 }
28 }
29
30 // DFS递归
31 void dfsHelper(int node, unordered_set<int>& visited, vector<int>& result) {
32 visited.insert(node);
33 result.push_back(node);
34
35 for (auto& [neighbor, weight] : graph[node]) {
36 if (!visited.count(neighbor)) {
37 dfsHelper(neighbor, visited, result);
38 }
39 }
40 }
41
42 vector<int> dfs(int start) {
43 unordered_set<int> visited;
44 vector<int> result;
45 dfsHelper(start, visited, result);
46 return result;
47 }
48
49 // BFS
50 vector<int> bfs(int start) {
51 unordered_set<int> visited;
52 queue<int> q;
53 vector<int> result;
54
55 visited.insert(start);
56 q.push(start);
57
58 while (!q.empty()) {
59 int node = q.front();
60 q.pop();
61 result.push_back(node);
62
63 for (auto& [neighbor, weight] : graph[node]) {
64 if (!visited.count(neighbor)) {
65 visited.insert(neighbor);
66 q.push(neighbor);
67 }
68 }
69 }
70
71 return result;
72 }
73
74 // 检测环(有向图)
75 bool hasCycleHelper(int node, unordered_set<int>& visited,
76 unordered_set<int>& recStack) {
77 visited.insert(node);
78 recStack.insert(node);
79
80 for (auto& [neighbor, weight] : graph[node]) {
81 if (!visited.count(neighbor)) {
82 if (hasCycleHelper(neighbor, visited, recStack)) {
83 return true;
84 }
85 } else if (recStack.count(neighbor)) {
86 return true;
87 }
88 }
89
90 recStack.erase(node);
91 return false;
92 }
93
94 bool hasCycle() {
95 unordered_set<int> visited;
96 unordered_set<int> recStack;
97
98 for (auto& [node, edges] : graph) {
99 if (!visited.count(node)) {
100 if (hasCycleHelper(node, visited, recStack)) {
101 return true;
102 }
103 }
104 }
105 return false;
106 }
107
108 // 拓扑排序(Kahn算法)
109 vector<int> topologicalSort() {
110 unordered_map<int, int> inDegree;
111
112 for (auto& [node, edges] : graph) {
113 if (!inDegree.count(node)) {
114 inDegree[node] = 0;
115 }
116 for (auto& [neighbor, weight] : edges) {
117 inDegree[neighbor]++;
118 }
119 }
120
121 queue<int> q;
122 for (auto& [node, degree] : inDegree) {
123 if (degree == 0) {
124 q.push(node);
125 }
126 }
127
128 vector<int> result;
129 while (!q.empty()) {
130 int node = q.front();
131 q.pop();
132 result.push_back(node);
133
134 for (auto& [neighbor, weight] : graph[node]) {
135 inDegree[neighbor]--;
136 if (inDegree[neighbor] == 0) {
137 q.push(neighbor);
138 }
139 }
140 }
141
142 return result.size() == inDegree.size() ? result : vector<int>();
143 }
144};
145
146// Dijkstra最短路径
147unordered_map<int, int> dijkstra(
148 const unordered_map<int, vector<pair<int, int>>>& graph,
149 int start) {
150
151 unordered_map<int, int> distances;
152 for (auto& [node, edges] : graph) {
153 distances[node] = INT_MAX;
154 }
155 distances[start] = 0;
156
157 // 优先队列:(distance, node)
158 priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
159 pq.push({0, start});
160
161 while (!pq.empty()) {
162 auto [currDist, currNode] = pq.top();
163 pq.pop();
164
165 if (currDist > distances[currNode]) {
166 continue;
167 }
168
169 if (graph.count(currNode)) {
170 for (auto& [neighbor, weight] : graph.at(currNode)) {
171 int distance = currDist + weight;
172 if (distance < distances[neighbor]) {
173 distances[neighbor] = distance;
174 pq.push({distance, neighbor});
175 }
176 }
177 }
178 }
179
180 return distances;
181}
182
183int main() {
184 cout << "=== 图算法演示 ===" << endl << endl;
185
186 // 创建无向图
187 Graph g(false);
188 g.addEdge(0, 1);
189 g.addEdge(0, 2);
190 g.addEdge(1, 3);
191 g.addEdge(2, 3);
192
193 cout << "DFS遍历: ";
194 vector<int> dfs_result = g.dfs(0);
195 for (int node : dfs_result) {
196 cout << node << " ";
197 }
198 cout << endl;
199
200 cout << "BFS遍历: ";
201 vector<int> bfs_result = g.bfs(0);
202 for (int node : bfs_result) {
203 cout << node << " ";
204 }
205 cout << endl << endl;
206
207 // 拓扑排序
208 cout << "拓扑排序:" << endl;
209 Graph dag(true);
210 dag.addEdge(5, 2);
211 dag.addEdge(5, 0);
212 dag.addEdge(4, 0);
213 dag.addEdge(4, 1);
214 dag.addEdge(2, 3);
215 dag.addEdge(3, 1);
216
217 vector<int> topo = dag.topologicalSort();
218 cout << " ";
219 for (int node : topo) {
220 cout << node << " ";
221 }
222 cout << endl << endl;
223
224 // Dijkstra
225 cout << "Dijkstra最短路径:" << endl;
226 unordered_map<int, vector<pair<int, int>>> weighted_graph;
227 weighted_graph[0] = {{1, 4}, {2, 2}};
228 weighted_graph[1] = {{2, 1}, {3, 5}};
229 weighted_graph[2] = {{3, 3}};
230 weighted_graph[3] = {};
231
232 auto distances = dijkstra(weighted_graph, 0);
233 for (auto& [node, dist] : distances) {
234 cout << " 到节点" << node << "的距离: " << dist << endl;
235 }
236
237 return 0;
238}