目录
[1. 图](#1. 图)
[1.1 python实现:邻接表、邻接矩阵](#1.1 python实现:邻接表、邻接矩阵)
[1.2 应用场景](#1.2 应用场景)
[2. BFS & DFS](#2. BFS & DFS)
[2.1 广度优先搜索BFS](#2.1 广度优先搜索BFS)
[2.2 深度优先搜索DFS](#2.2 深度优先搜索DFS)
[3.1 图的连通性问题](#3.1 图的连通性问题)
[(1)841 钥匙和房间](#(1)841 钥匙和房间)
[(2)547 省份数量](#(2)547 省份数量)
[(3)1466 重新规划路线](#(3)1466 重新规划路线)
[(4)200 岛屿数量](#(4)200 岛屿数量)
[3.2 BFS解决二维矩阵中的问题](#3.2 BFS解决二维矩阵中的问题)
[(1)1926 迷宫中离入口最近的出口](#(1)1926 迷宫中离入口最近的出口)
[(2)994 腐烂的橘子](#(2)994 腐烂的橘子)
[3.3 带权图](#3.3 带权图)
[(1)399 除法求值](#(1)399 除法求值)
[3.4 拓扑排序](#3.4 拓扑排序)
[(1)207 课程表](#(1)207 课程表)
1. 图
1.1 python实现:邻接表、邻接矩阵
(1)邻接表 (常用:节省空间,适合稀疏图)
python
graph = {
'A': ['B', 'C'], # 无向图邻居
'B': ['A', 'C', 'D'],
'C': ['A', 'B'],
'D': ['B']
}
""" 带权图的邻接表 """
weighted_graph = {
'A': {'B': 2, 'C': 4},
'B': {'C': 1, 'D': 7}
}
(2)邻接矩阵 (适合稠密图)
python
matrix = [
[0, 1, 1, 0], # A的邻居:B、C
[1, 0, 1, 1], # B的邻居:A、C、D
[1, 1, 0, 0], # C的邻居:A、B
[0, 1, 0, 0] # D的邻居:B
]
1.2 应用场景
场景 | 问题类型 | 实现思路 |
---|---|---|
路径规划 | 最短路径 | Dijkstra(非负权)、Bellman-Ford(负权) |
社交网络 | 好友推荐 | BFS查找K度好友,社区检测(DFS连通分量) |
任务调度 | 依赖排序 | 拓扑排序(有向无环图) |
网络分析 | 广播消息 | BFS模拟消息扩散 |
AI寻路 | 状态转移 | DFS回溯(如迷宫) |
2. BFS & DFS
2.1 广度优先搜索BFS
(1)过程: 逐层遍历,用队列实现。
(2)应用场景:最短路径(无权图)、层级遍历、拓扑排序(Kahn算法)、扩散传播等
python
from collections import deque
def bfs(graph, start):
visited = set([start])
queue = deque([start])
while queue:
vertex = queue.popleft()
for neighbor in graph[vertex]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return visited # 返回所有可达节点
2.2 深度优先搜索DFS
(1)过程 :递归深入路径,用栈实现。
(2)应用场景:路径存在性检测(如迷宫)、拓扑排序(有向无环图)、连通分量统计、环检测(递归栈)、回溯问题等
python
""" 递归版 """
def dfs_recursive(graph, start, visited=None):
if visited is None:
visited = set()
visited.add(start)
for neighbor in graph[start]:
if neighbor not in visited:
dfs_recursive(graph, neighbor, visited)
return visited
""" 迭代版(栈实现)"""
def dfs_iterative(graph, start):
visited = set()
stack = [start]
while stack:
vertex = stack.pop()
if vertex not in visited:
visited.add(vertex)
stack.extend(reversed(graph[vertex])) # 保持原顺序
return visited
3.Leetcode
3.1 图的连通性问题
(1)841 钥匙和房间
有 n
个房间,房间按从 0
到 n - 1
编号。最初,除 0
号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而,你不能在没有获得钥匙的时候进入锁住的房间。当你进入一个房间,你可能会在里面找到一套 不同的钥匙 ,每把钥匙上都有对应的房间号,即表示钥匙可以打开的房间。你可以拿上所有钥匙去解锁其他房间。给你一个数组 rooms
其中 rooms[i]
是你进入 i
号房间可以获得的钥匙集合。如果能进入 所有 房间返回 true
,否则返回 false
。
BFS方案:
python
from collections import deque
class Solution(object):
def canVisitAllRooms(self, rooms):
"""
:type rooms: List[List[int]]
:rtype: bool
"""
n=len(rooms)
visited=[False]*n
## 从0号房开始BFS
quene = deque([0])
visited[0]=True
while quene:
cur_room = quene.popleft() ##当前房间号
for key in rooms[cur_room]: ## 遍历当前房间内的钥匙
if not visited[key]:
visited[key]=True
quene.append(key)
return all(visited)
DFS方案:
python
n=len(rooms)
visited=[False]*n
def dfs(room):
visited[room]=True ## 能开当前room,visited=true
for key in rooms[room]: ## 看当前room中有哪些key
if not visited[key]: ## 如果key对应的房间没被开过,去开当前key对应的房间
dfs(key)
dfs(0) ## 从0号房开始DFS
return all(visited)
(2)547 省份数量
省份的定义是一组直接或间接相连的城市(即一个连通分量)→ 求无向图中连通分量的个数
BFS方案:
python
n=len(isConnected)
visited=[False]*n
count=0
for i in range(n):
if not visited[i]: ## 找到未标记省份
count+=1
quene=deque([i]) ## 找省份内的其他城市(找整个连通分量)
while quene:
city=quene.popleft()
for neighbor in range(n):
if isConnected[city][neighbor]==1 and not visited[neighbor]:
visited[neighbor]=True
quene.append(neighbor)
return count
DFS方案:
python
class Solution(object):
def findCircleNum(self, isConnected):
"""
:type isConnected: List[List[int]]
:rtype: int
"""
n = len(isConnected)
visited = [False]*n
count = 0
def dfs(city):
visited[city]=True
## 遍历该城市的所有邻居
for neighbor in range(n):
## 如果是邻居且未被访问过,继续递归,直到找到这一条完整的连通分量
if isConnected[city][neighbor]==1 and not visited[neighbor]:
visited[neighbor]=True
dfs(neighbor)
for i in range(n):
if not visited[i]:
count+=1 ## 找到未标记的省份
dfs(i) ## 找到该省份内的所有城市
return count
(3)1466 重新规划路线
重新规划路线方向,使每个城市都可以访问城市 0 。返回需要变更方向的最小路线数。

1. 构建图结构:创建一个无向图,包含所有节点及其连接关系
2. 方向判断:在遍历过程中,对每条边:
-- 如果原始方向是从父节点指向当前节点,说明方向正确
-- 如果原始方向是从当前节点指向父节点,说明方向需要反转
BFS方案:
python
from collections import deque
class Solution(object):
def minReorder(self, n, connections):
"""
:type n: int
:type connections: List[List[int]]
:rtype: int
"""
graph = [[] for _ in range(n)]
edges = set() ## 存放边的方向
for i, j in connections:
graph[i].append(j)
graph[j].append(i)
edges.add((i,j)) ## 需要翻转的原始有向边(i→j)
count=0
visited=[False]*n
visited[0]=True
quene=deque([0])
while quene:
i=quene.popleft()
for j in graph[i]: ## j是当前节点的邻居
if not visited[j]:
visited[j]=True
quene.append(j)
if (i,j) in edges:
count+=1
return count
DFS方案:
python
class Solution(object):
def minReorder(self, n, connections):
"""
:type n: int
:type connections: List[List[int]]
:rtype: int
"""
graph = [[] for _ in range(n)]
for i, j in connections:
graph[i].append((j, True)) ## i→j:背离0节点的方向,需要翻转
graph[j].append((i, False)) ## j→i:无需翻转
print(graph)
visited=[False]*n
visited[0]=True
self.count=0
def dfs(node):
for neighbor, need_flip in graph[node]:
if not visited[neighbor]:
visited[neighbor]=True
if need_flip:
self.count+=1
dfs(neighbor)
dfs(0)
return self.count
(4)200 岛屿数量
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
python
from collections import deque
class Solution(object):
def numIslands(self, grid):
"""
:type grid: List[List[str]]
:rtype: int
"""
m, n= len(grid), len(grid[0]) ## m-行,n-列
directions=[(0,1), (0,-1), (1,0), (-1,0)]
visited = [[False]*n for _ in range(m)]
island=0
for i in range(m):
for j in range(n):
if grid[i][j]=='1': ## 发现未访问的陆地
island+=1
grid[i][j]='0' ## 标记为水 → 相当于已访问
## 找和当前陆地连接的其他陆地(找连通分量,看能形成多大的岛)
quene=deque()
quene.append((i,j))
while quene:
row, col = quene.popleft()
for dr,dc in directions: ## 往四个方向延伸
cur_row, cur_col = dr+row, dc+col
## 检查新位置是否是有效的陆地
if 0<=cur_row<m and 0<=cur_col<n and grid[cur_row][cur_col]=='1':
grid[cur_row][cur_col]='0'
quene.append((cur_row,cur_col))
return island
3.2 BFS解决二维矩阵中的问题
(1)1926 迷宫中离入口最近的出口

1. 初始化:从入口点开始 BFS
2. 遍历方向:每次可移动上、下、左、右四个方向
3. 边界条件:不能进入墙("+")、不能超出迷宫边界、已访问过的点不再访问
4. 终止条件:到达迷宫边界上的空格子(且不是入口点)
python
from collections import deque
class Solution(object):
def nearestExit(self, maze, entrance):
"""
:type maze: List[List[str]]
:type entrance: List[int]
:rtype: int
"""
m=len(maze) ## 行
n=len(maze[0]) ## 列
directions=[(-1,0),(1,0),(0,-1),(0,1)] ## 下一步要尝试的方向(上下左右)
visited=[[False]*n for _ in range(m)] ## 创建m*n的visited存储访问标记
steps=0
quene=deque()
start_row, start_col = entrance[0], entrance[1] ## 初始位置
visited[start_row][start_col]=True
quene.append((start_row, start_col, steps))
while quene:
row, col, steps = quene.popleft()
""" 判断是否到达出口(非起点的边界处)"""
is_boundary = (row==0 or row==m-1 or col==0 or col==n-1)
is_start = (row==start_row and col==start_col)
""" 若找到出口 """
if is_boundary and not is_start:
return steps
""" 没找到出口,继续向上下左右四个方向分别查找 """
for dr,dc in directions:
cur_row, cur_col = dr+row, dc+col ## 下一步的位置
""" 下一步的位置是否在范围内 """
if 0<=cur_row<m and 0<=cur_col<n:
""" 若是范围内还没走过的地方,看能不能走 """
if not visited[cur_row][cur_col] and maze[cur_row][cur_col]=='.':
visited[cur_row][cur_col]=True
quene.append(((cur_row, cur_col, steps+1)))
return -1
(2)994 腐烂的橘子
每分钟,腐烂的橘子周围 4 个方向上相邻的新鲜橘子都会腐烂。返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1
。

**1. 时间统计:**该按层处理,同一层的橘子同时腐烂(同1分钟)
**2. 访问标记:**不需要独立visited数组,可直接用grid值判断(grid=2 → visited=True)
python
from collections import deque
class Solution(object):
def orangesRotting(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
m, n = len(grid), len(grid[0]) ## 行,列
visited=[[False]*n for _ in range(m)]
directions=[(0,1), (0,-1), (1,0), (-1,0)]
minutes=-1 ## 初始为-1,第一次循环变为0
fresh_nums=0 ## 新鲜橘子数
quene=deque()
for i in range(m):
for j in range(n):
if(grid[i][j]==2): ## 腐烂橘子入队(可能不止一个)
quene.append((i,j))
elif(grid[i][j]==1): ## 统计新鲜橘子数目
fresh_nums += 1
""" 本身就没有新鲜的橘子,直接返回0 """
if fresh_nums==0:
return 0
while quene:
""" 当前有size个腐烂橘子,同时发力腐烂新鲜的 """
size = len(quene)
for _ in range(size):
row, col= quene.popleft()
for dr,dc in directions:
cur_row, cur_col = dr+row, dc+col
if 0<=cur_row<m and 0<=cur_col<n:
if grid[cur_row][cur_col]==1:
grid[cur_row][cur_col]=2
quene.append((cur_row,cur_col))
fresh_nums -= 1
""" 四个方向同时被腐烂 → 走完四个方向时间才会增加 """
minutes += 1
return minutes if fresh_nums==0 else -1
3.3 带权图
(1)399 除法求值
本质上是带权有向图路径查找。
实现方案:
构建图:使用字典,key为节点,value为另一个字典(邻居和对应的权重)。
对于每个查询,在图中搜索路径,并计算乘积。
如果路径不存在或者节点不在图中,返回-1.0。
BFS方案:
python
from collections import deque
from collections import defaultdict
class Solution(object):
def calcEquation(self, equations, values, queries):
"""
:type equations: List[List[str]]
:type values: List[float]
:type queries: List[List[str]]
:rtype: List[float]
"""
""" step1:构建带权有向图 """
graph = defaultdict(dict)
node = set()
for (a,b),val in zip(equations, values):
graph[a][b]=val
graph[b][a]=1.0 / val
node.add(a)
node.add(b)
""" step2:处理每个查询 """
result=[]
for i,j in queries:
""" 情况1-未定义查询变量 """
if i not in node or j not in node:
result.append(-1.0)
""" 跳过当前查询的后续处理,继续下一个查询 """
continue
""" 情况2-相同查询变量 """
if i==j:
result.append(1.0)
continue
""" 情况3-需要按路径查找的查询变量 """
quene=deque() ## BFS队列:(当前节点, 累积乘积)
visited=set() ## 记录已访问节点
quene.append((i,1.0))
visited.add(i)
found=False ## 标记是否找到路径
while quene and not found: ## 有节点待处理且未找到路径
cur_node, cur_product = quene.popleft()
for neighbor, value in graph[cur_node].items():
""" 情况3-1:找到目标节点对应的路径 """
if neighbor==j:
result.append(cur_product*value)
found=True
""" 跳出当前邻居循环,不需要再找其他邻居了 """
break
""" 情况3-2:还没找到目标节点,但还有邻居没访问"""
if neighbor not in visited:
visited.add(neighbor)
quene.append((neighbor,cur_product*value))
""" 情况4:遍历完还没找到路径 """
if not found:
result.append(-1.0)
return result
DFS方案:
python
from collections import deque
from collections import defaultdict
class Solution(object):
def calcEquation(self, equations, values, queries):
"""
:type equations: List[List[str]]
:type values: List[float]
:type queries: List[List[str]]
:rtype: List[float]
"""
graph = defaultdict(dict)
nodes = set()
for (a,b),val in zip(equations, values):
graph[a][b]=val
graph[b][a]=1.0/val
nodes.add(a)
nodes.add(b)
def dfs(i, j, product, visited):
""" 递归终止条件:找到目标节点,返回乘积和 """
if i==j:
return product
""" 若还没找到目标节点 """
visited.add(i) ## # 标记当前节点已访问
for neighbor,value in graph[i].items():
if neighbor not in visited: ## 跳过已访问节点
# 递归搜索:累积路径乘积
result = dfs(neighbor,j,product*value,visited)
# 如果在当前路径中找到解,直接返回结果
if result != -1.0:
return result
""" 没找到目标路径 """
return -1.0
result=[]
for i,j in queries:
if i not in nodes or j not in nodes:
result.append(-1.0)
elif i==j:
result.append(1.0)
else:
visited=set() ## 每次查询都要创建新的visited集合
result.append(dfs(i,j,1.0,visited))
return result
3.4 拓扑排序
(1)207 课程表
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。例如,先修课程对 [0, 1]
表示:想要学习课程 0
,你需要先完成课程 1
。请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
核心是判断有向图 是否存在环。给定课程的先修关系(prerequisites),构建一个有向图:
节点:每门课程 (0 到 numCourses-1)
边 :
prerequisites[i] = [a, b]
表示b → a
(先修 b 才能学 a)需要检测这个有向图是否是 有向无环图(DAG)。
如果可以完成所有课程学习,则图无环;如果存在环则无法完成。
BFS方案:Kahn算法,根据节点的入度判断
1. 初始化:
(1)构建 邻接表 表示图(2)维护 节点入度数组(前驱课程数)
2. 入队入度为零节点:不需要先修课程即可学习的课程入队
3. BFS处理:
(1)每次出队一门课程,标记为已学,并将该课程所有后续课程的入度减1
(2)若某课程入度为零,则入队
4. 结果判断:
成功学习的课程数等于总课程数 → 可行,否则存在循环依赖 → 不可行
python
from collections import deque, defaultdict
class Solution(object):
def canFinish(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: bool
"""
""" step1:构建有向图(邻接表形式)+计算节点入度 """
graph = defaultdict(list)
indegree = [0] * numCourses
for course, pre_course in prerequisites:
graph[pre_course].append(course) # 边:pre_course → course
indegree[course] += 1 # course入度+1(course先修课+1)
""" step2:初始化队列,入度为0的course入队 """
quene=deque()
for i in range(numCourses):
if indegree[i]==0:
quene.append(i)
""" step3:BFS处理 """
visited = 0 ## 标记已学习的课程数
while quene:
cur_course=quene.popleft() ## 当前可学的课程
visited+=1
for neighbor in graph[cur_course]:
indegree[neighbor]-=1 ## 当前课程的后置课程入度-1
if indegree[neighbor]==0:
quene.append(neighbor) ## 入度为0就能开始学了
return visited == numCourses
DFS方案:检测是否有环
1. 状态标记 :
0=未访问
,1=访问中
,2=已访问
2. DFS递归:
(1)进入节点时标记为"访问中"
(2)递归处理所有邻居
(3)若遇到"访问中"节点 → 发现环
(4)若所有邻居无环,回溯标记"已访问"
3. 全局检测:
为每个未访问节点启动DFS,任一环则返回不可行
python
""" step1:构建图 """
graph = defaultdict(list)
for course, pre_course in prerequisites:
graph[pre_course].append(course)
""" step2:DFS检测环
0: 未访问(还未进行DFS)
1: 访问中(当前DFS路径中正在访问该节点)
2: 已访问(该节点的DFS已经完成,没有发现环,是安全节点)
"""
visited = [0]*numCourses
def find_circle(course):
""" 递归终止条件 """
if visited[course]==1: return True ## 已在当前DFS路径中 → 发现环!
if visited[course]==2: return False ## 已经是安全节点 → 无需重复检查
visited[course]=1 ## 标记当前节点为"访问中"(1)
""" 递归检测所有后续课程 """
for neighbor in graph[course]:
if find_circle(neighbor):
return True
""" 回溯标记:将当前节点设为"安全节点"(2) """
visited[course] = 2
return False
for i in range(numCourses):
if visited[i]==0: ## 每次只需要处理还没访问过的节点
if find_circle(i):
return False
return True