第十一章 图论 Part1
任务
797. 所有可能的路径
给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)
graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。
思路
题目所给的图的表示是邻接表,题意就是找到从i到n的所有路径,这里使用dfs。参考之前的回溯章节,区别是当时的各种情况的出发根节点可以认为是虚拟节点,在循环和递归中处理各种情况,而现在的出发节点是实际的图的点,需要提前加入到path中来保证正确。
class Solution: def __init__(self): self.result = [] self.path = [] def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]: self.path.append(0) self.dfs(graph,0,len(graph)-1) return self.result def dfs(self,graph,x,n): # 从x出发到n if x == n: self.result.append(self.path[:]) return for i in graph[x]: self.path.append(i) self.dfs(graph,i,n) self.path.pop()
下面给出输入为 邻接矩阵的算法
class Solution: def __init__(self): self.result = [] self.path = [] def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]: self.path.append(0) self.dfs(graph,0,len(graph)-1) return self.result def dfs(self,graph,x,n): # 从x出发到n if x == n: self.result.append(self.path[:]) return for i in graph[x][i]: if graph[x][i] == 1: self.path.append(i) self.dfs(graph,i,n) self.path.pop()
深度有限搜索(dfs)和广度优先搜索(bfs) 简析
可以认为树中的 先中后序遍历为dfs的特例,层序遍历为bfs的特例。
dfs适合的问题:
- 路径查找:在一些问题中,我们需要找到从起点到终点的一条路径(或所有路径)。DFS 很适合这种问题,因为它可以递归地探索每一条可能的路径,直到找到目标为止。(想想二叉树的根序遍历)
- 连通性问题:DFS 可以用于判断图是否连通,即图中是否存在一条从一个节点到另一个节点的路径。
- 拓扑排序:在处理有向无环图(DAG)时,DFS 可以用于生成拓扑排序
- 回溯算法:之前的回溯算法章节的问题都可以认为是dfs解决的问题
bfs适合的问题
- 最短路径问题:BFS 是在无权图中寻找两点之间最短路径的最优算法。因为 BFS 按层次遍历,所以首次到达目标节点时,一定是通过最短路径到达的。
- 层次遍历:BFS 按层次逐层遍历图或树结构,非常适合需要按照距离或层次关系处理的场景。
- 最小生成树(MST):在一些图算法中,BFS 可以用来生成最小生成树(虽然更常用的是 Prim 或 Kruskal 算法)。
bfs的参考代码(输入为grid)
需要注意这里的bfs与树的层序遍历的区别!
在树的层序遍历中,我们一般处理节点或者说标记遍历过节点是在从队列弹出时,而对于图,一定要是在加入队列时,否则虽然不会出错,但是会浪费时间和空间!
这是因为对于树来说,它加入队列只有一种情况,即其父节点弹出时将其加入队列,而对于更为一般的图,让它加入队列的节点可能有很多,所以通过在 加入队列时就标记访问,我们能保证同一个节点只会被处理一次,避免了重复加入队列。
from collections import deque # 表示四个方向 dir = [(0, 1), (1, 0), (-1, 0), (0, -1)] def bfs(grid, visited, x, y): # 定义队列 que = deque() # 起始节点加入队列 que.append((x, y)) # 只要加入队列,立刻标记为访问过的节点 visited[x][y] = True #记录层级 level = 0 while que: # 从队列取元素 curx, cury = que.popleft() # 当前节点坐标 # 开始向当前节点的四个方向左右上下去遍历 for i in range(4): nextx = curx + dir[i][0] nexty = cury + dir[i][1] # 获取周边四个方向的坐标 # 坐标越界了,直接跳过 if nextx < 0 or nextx >= len(grid) or nexty < 0 or nexty >= len(grid[0]): continue # 如果节点没被访问过 if not visited[nextx][nexty]: # 队列添加该节点为下一轮要遍历的节点 que.append((nextx, nexty)) # 只要加入队列立刻标记,避免重复访问 visited[nextx][nexty] = True #这里可以对新访问的格子做处理 level+=1
dfs的参考代码(输入为grid)
实际外层调用中,需要对每个格子(未访问过)做dfs,这里是遍历整个格子图,之前的所有可能路径是路径问题,只是处理i到n点的所有路径,这是有所区别的。
路径问题的dfs终止条件是终点,而grid问题的dfs终止条件是遇到边界。
def dfs(self,grid,visited,x,y): dir = [(1,0),(0,1),(-1,0),(0,-1)] #逆时针四个方向 #处理当前格子 visited[x][y] = True for i in range(4): nextX = x + dir[i][0] nextY = y + dir[i][1] if nextX < 0 or nextY<0 or nextX>=len(grid) or nextY>=len(grid[0]): continue if visited[nextX][nextY] == False: self.dfs(grid,visited,nextX,nextY)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步