LeetCode-图论基础及算法技巧总结

引言

图是一种复杂而强大的数据结构,在计算机科学和现实世界中有着广泛的应用。本文将介绍图的基本概念、表示方法、常见算法以及解决图相关问题的技巧。

1. 图的基本概念

图由顶点(节点)和边组成。根据边的性质,图可以分为:

  • 无向图:边没有方向
  • 有向图:边有特定方向
  • 加权图:边有权重

2. 图的表示方法

2.1 邻接矩阵

使用二维数组表示图中节点之间的连接关系。

1
2
3
4
5
6
# 邻接矩阵表示
graph = [
[0, 1, 0],
[1, 0, 1],
[0, 1, 0]
]

2.2 邻接表

使用字典或列表数组表示每个节点的邻接节点。

1
2
3
4
5
6
# 邻接表表示
graph = {
0: [1],
1: [0, 2],
2: [1]
}

3. 图的遍历

3.1 深度优先搜索(DFS)

1
2
3
4
5
6
7
8
def dfs(graph, start, visited=None):
if visited is None:
visited = set()
visited.add(start)
print(start)
for next in graph[start] - visited:
dfs(graph, next, visited)
return visited

3.2 广度优先搜索(BFS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import deque

def bfs(graph, start):
visited = set()
queue = deque([start])
visited.add(start)

while queue:
vertex = queue.popleft()
print(vertex)
for neighbour in graph[vertex]:
if neighbour not in visited:
visited.add(neighbour)
queue.append(neighbour)

4. 常见图算法

4.1 最短路径算法

Dijkstra算法

用于找到图中一个顶点到其他所有顶点的最短路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import heapq

def dijkstra(graph, start):
distances = {node: float('infinity') for node in graph}
distances[start] = 0
pq = [(0, start)]

while pq:
current_distance, current_node = heapq.heappop(pq)

if current_distance > distances[current_node]:
continue

for neighbor, weight in graph[current_node].items():
distance = current_distance + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(pq, (distance, neighbor))

return distances

Floyd-Warshall算法

用于找到图中所有顶点对之间的最短路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def floyd_warshall(graph):
dist = {node: {node: float('inf') for node in graph} for node in graph}

for node in graph:
dist[node][node] = 0
for neighbor, weight in graph[node].items():
dist[node][neighbor] = weight

for k in graph:
for i in graph:
for j in graph:
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

return dist

4.2 最小生成树算法

Kruskal算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def find(parent, i):
if parent[i] == i:
return i
return find(parent, parent[i])

def union(parent, rank, x, y):
xroot = find(parent, x)
yroot = find(parent, y)
if rank[xroot] < rank[yroot]:
parent[xroot] = yroot
elif rank[xroot] > rank[yroot]:
parent[yroot] = xroot
else:
parent[yroot] = xroot
rank[xroot] += 1

def kruskal(graph):
result = []
i, e = 0, 0
graph = sorted(graph, key=lambda item: item[2])
parent = []
rank = []
for node in range(len(graph)):
parent.append(node)
rank.append(0)
while e < len(graph) - 1:
u, v, w = graph[i]
i = i + 1
x = find(parent, u)
y = find(parent, v)
if x != y:
e = e + 1
result.append([u, v, w])
union(parent, rank, x, y)
return result

4.3 拓扑排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from collections import defaultdict

def topological_sort(graph):
in_degree = {u: 0 for u in graph}
for u in graph:
for v in graph[u]:
in_degree[v] += 1

queue = deque([u for u in in_degree if in_degree[u] == 0])
result = []

while queue:
u = queue.popleft()
result.append(u)
for v in graph[u]:
in_degree[v] -= 1
if in_degree[v] == 0:
queue.append(v)

if len(result) != len(graph):
return None # Graph has a cycle
return result

5. 图的高级主题

5.1 强连通分量

Kosaraju 算法用于找出有向图中的强连通分量。

5.2 二分图

可以使用 DFS 或 BFS 来判断一个图是否为二分图。

5.3 网络流

Ford-Fulkerson 算法用于解决最大流问题。

6. 图论问题解题技巧

  1. 选择合适的图表示: 根据问题特点选择邻接矩阵或邻接表。
  2. 灵活运用 DFS 和 BFS: 很多图问题可以通过 DFS 或 BFS 来解决。
  3. 考虑边的权重: 在加权图中,要考虑边的权重对算法的影响。
  4. 注意图的特殊性质: 如是否为有向图、是否有环等。
  5. 使用辅助数据结构: 如并查集、堆等可以帮助优化算法。
  6. 考虑时间和空间复杂度: 在大规模图中,算法的效率尤为重要。

结语

图论是一个博大精深的领域,本文仅涵盖了其中的基础部分。要真正掌握图论,需要大量的练习和实际应用。希望这篇文章能为你打开图论的大门,激发你进一步探索的兴趣。