1.BFS和DFS的定义与实现方式
1.1 深度优先搜索(DFS)
基本概念:DFS 是一种用于遍历或搜索图或树的算法。它从起始节点开始,沿着一条路径尽可能深地探索下去,直到无法继续或者达到目标节点,然后回溯到上一个节点,继续探索其他路径。
实现方式:
通常使用递归或者栈(Stack)来实现。在递归实现中,函数不断调用自身来深入探索下一个节点;在使用栈的实现中,将节点压入栈中,每次从栈顶取出节点进行处理,并将其未访问的邻接节点压入栈中。
例如,对于一个图结构 graph = {"A": ["B", "C"], "B": ["D"], "C": ["E"], "D": [], "E": []}
,从节点 "A"
开始 DFS 搜索,首先访问 "A"
,然后选择 "B"
进行深入探索,访问 "B"
后再访问 "D"
,当 "D"
没有未访问的邻接节点时,回溯到 "B"
,由于 "B"
的邻接节点都已访问,再回溯到 "A"
,接着选择 "C"
进行深入探索,以此类推。
1.2 广度优先搜索(BFS)
基本概念:BFS 也是一种用于遍历或搜索图或树的算法。它从起始节点开始,首先访问起始节点的所有邻接节点,然后再依次访问这些邻接节点的邻接节点,以此类推,一层一层地向外扩展,直到找到目标节点或者遍历完整个图或树。
实现方式:
通常使用队列(Queue)来实现。将起始节点放入队列中,每次从队列头部取出一个节点进行处理,并将其未访问的邻接节点依次放入队列尾部。
例如,对于图结构 graph = {"A": ["B", "C"], "B": ["D"], "C": ["E"], "D": [], "E": []}
,从节点 "A"
开始 BFS 搜索,首先将 "A"
放入队列,然后取出 "A"
并访问它,将 "A"
的邻接节点 "B"
和 "C"
放入队列;接着从队列中取出 "B"
并访问它,将 "B"
的邻接节点 "D"
放入队列;再取出 "C"
并访问它,将 "C"
的邻接节点 "E"
放入队列,依此类推。
2.基本代码实现
将上图转化为graph邻接表
python">graph={ "A":["B","C"],"B":["A","C","D"],"C":["A","B","D","E"],"D":["B","C","E","F"],"E":["C","D"],"F":["D"]}
2.1 DFS实现对上图的寻找
python">graph={ "A":["B","C"],"B":["A","C","D"],"C":["A","B","D","E"],"D":["B","C","E","F"],"E":["C","D"],"F":["D"]}
def bfs(graph,s):stack=[]seen=set()#建立一个空集合,用于后续检查元素是否已经存在于栈中stack.append(s)seen.add(s)while len(stack)>0:key=stack.pop()nodes=graph[key]for i in nodes:if i not in seen:stack.append(i)seen.add(i)# 如果不在集合中,向栈中添加,并在集合中标记print(key)
bfs(graph,"A")# A
# C
# E
# D
# F
# B
区别:queue.append()统一在右侧添加元素,DFS是利用pop()删除最右端的元素,而BFS是利用pop(0) 删除最左端的元素
2.2 BFS 实现对上图的寻找
python">graph={ "A":["B","C"],"B":["A","C","D"],"C":["A","B","D","E"],"D":["B","C","E","F"],"E":["C","D"],"F":["D"]}
def bfs(graph,s):queue=[]seen=set()queue.append(s)seen.add(s)while len(queue)>0:key=queue.pop(0)nodes=graph[key]for i in nodes:if i not in seen:queue.append(i)seen.add(i)print(key)
bfs(graph,"A")
# A
# B
# C
# D
# E
# F
3.BFS和DFS的区别,优缺点
3.1 区别
- 搜索方式
- DFS:从起始节点开始,沿着一条路径尽可能深地探索下去,直到无法继续或达到目标节点,然后回溯到上一个未完全探索的节点,继续探索其他路径,就像走迷宫时一直沿着一条路走到死胡同再回头。
- BFS:从起始节点开始,先访问其所有相邻节点,然后再依次访问这些相邻节点的相邻节点,一层一层地向外扩展,如同水波一样向四周扩散。
- 数据结构
- DFS:通常使用递归或栈来实现。递归实现时,系统会自动使用栈来保存函数调用的上下文信息;也可以手动使用栈来存储待访问的节点。
- BFS:使用队列来实现,先将起始节点放入队列,然后每次从队列中取出一个节点,访问其相邻节点并将未访问过的相邻节点放入队列。
- 遍历顺序
- DFS:遍历顺序是深度优先的,会先深入探索某一条路径上的节点,然后再回溯到其他分支。
- BFS:遍历顺序是广度优先的,按照距离起始节点的远近依次访问节点,先访问距离近的节点,再访问距离远的节点。
3.2 优缺点
- DFS
- 优点:实现相对简单,对于某些问题,如寻找图中的连通分量、拓扑排序等,DFS 的递归性质使其代码简洁明了。在一些情况下,如果目标节点位于较深的层次,DFS 可能会更快地找到目标,因为它会优先沿着一条路径深入探索。
- 缺点:可能会陷入无限循环,特别是在处理有环的图时,需要额外的机制来标记已访问的节点以避免重复访问。而且它不一定能找到最短路径,因为它是深度优先探索,可能会先找到一条较长的路径到达目标节点。
- BFS
- 优点:一定能找到从起始节点到目标节点的最短路径(如果存在),因为它是按照层次依次访问节点的。在处理无权图或边权都相等的图时,BFS 是寻找最短路径的理想算法。
- 缺点:需要更多的空间来存储队列中的节点,特别是在图的规模较大时,可能会占用大量的内存。而且对于某些问题,其实现可能相对复杂一些,因为需要维护队列和处理节点的访问顺序。
4.DFS经典例题
4.1 矩阵最长递减路径问题
题意:计算每一个格子,由大递减到小所占的格子数
python">#矩阵最长递减路径
lis=[[1,1,3],[2,3,4],[1,0,5]]
m,n=3,3
def dfs(i,j,val):if i<0 or i==m: #行出界return 0if j<0 or j==n: #列出界return 0if lis[i][j]>=val: #递增,不符合题意return 0if (i,j) in dp:return dp[(i,j)]res=1res=max(res,1+dfs(i-1,j,lis[i][j])) #向上res=max(res,1+dfs(i+1,j,lis[i][j])) #向下res=max(res,1+dfs(i,j-1,lis[i][j])) #向左res=max(res,1+dfs(i,j+1,lis[i][j])) #向右dp[(i,j)]=res #以键值对的形式保存到集合中return res
dp={}
for i in range(m):for j in range(n):res=dfs(i,j,100)print(res,end=' ')print( )
print(dp)
print(f'最短路径格子数为{max(dp.values())}')
#将取出全部键值对中的值,并输出最大值#输出结果
1 1 2
3 4 5
2 1 6
{(0, 0): 1, (0, 1): 1, (0, 2): 2, (2, 1): 1,(2, 0): 2, (1, 0): 3, (1, 1): 4, (1, 2): 5, (2, 2): 6}
最长路径格子数为6
下图为从递减路径最长的一条,即从右下角的单元格出发的遍历流程图。黑色笔标注的数字代表单元格内的数字,红色笔标注的数字为走过的格子数(画图潦草,请各位读者见谅)
4.2 海域污染问题
python">N,M=list(map(int,input().split(',')))
lis=[]
n=N
while n>0:a=list(map(int,input()))lis.append(a)n-=1
def dfs(x,y):if x<0 or x==N: # 行出界return 0if y<0 or y==M: # 列出界return 0if lis[x][y]==0: # 坐标值为0return 0lis[x][y]=0 #将遍历到的不是0的数都变成0dfs(x-1,y) # 向上dfs(x+1,y) # 向下dfs(x,y-1) # 向左dfs(x,y+1) # 向右
count=0
for i in range(N):for j in range(M):if lis[i][j]==1:dfs(i,j)count+=1
print(count)
输入
4,5
11001
10001
00100
01100
输出
3
4.3 最大的油田问题
python">lis=[[1,1,0,0,1],[0,0,0,1,0],[0,0,0,1,1],[1,1,0,1,1]]
hang=len(lis)
lie=len(lis[0])
ans=0
def dfs(x,y):if x>=0 and x<hang and y>=0 and y<lie and lis[x][y]==1:lis[x][y]=0return 1+dfs(x-1,y)+dfs(x+1,y)+dfs(x,y-1)+dfs(x,y+1)else:return 0
for i in range(hang):for j in range(lie):area=dfs(i,j)if area>ans:ans=area
print(ans)
# 5
5.BFS经典例题
5.1 蓝桥杯算法模板题--走迷宫
python">from collections import deque
N, M = 5, 5
a = [[1, 0, 1, 1, 0][1, 1, 0, 1, 1],[0, 1, 0, 1, 1],[1, 1, 1, 1, 1],[1, 0, 0, 0, 1]
]
n, m, c, d = 1, 1, 5, 5
# 因为矩阵下标从 0 开始,将目标坐标转换为 0 索引
c -= 1
d -= 1
n-=1
m-=1dx = [0, 1, 0, -1] # 可以在行方向上移动的四个方向:右、下、左、上
dy = [1, 0, -1, 0] # 可以在列方向上移动的四个方向:右、下、左、上# 标记矩阵,用于记录节点是否已经访问过
visited = [[False] * M for _ in range(N)]def bfs(x, y):# 创建队列,队列元素为 (x, y, steps)queue = deque([(x, y, 0)])visited[x][y] = Truewhile queue:cur_x, cur_y, steps = queue.popleft()# 到达目标点if cur_x == c and cur_y == d:return stepsfor i in range(4): # 在四个方向上尝试移动new_x = cur_x + dx[i]new_y = cur_y + dy[i]# 检查新坐标是否合法且未访问过if 0 <= new_x < N and 0 <= new_y < M and a[new_x][new_y] == 1 and not visited[new_x][new_y]:visited[new_x][new_y] = Truequeue.append((new_x, new_y, steps + 1))return -1 # 如果不满足上述情况返回-1print(bfs(n,m))
标准提交代码:
python">from collections import dequeN,M=list(map(int,input().split()))
a=[list(map(int,input().split())) for _ in range(N)]
n,m,c,d=list(map(int,input().split()))
c -= 1
d -= 1
dx = [0, 1, 0, -1]
dy = [1, 0, -1, 0] visited = [[False] * M for _ in range(N)]
def bfs(x, y):queue = deque([(x, y, 0)])visited[x][y] = Truewhile queue:cur_x, cur_y, steps = queue.popleft() if cur_x == c and cur_y == d:return stepsfor i in range(4): new_x = cur_x + dx[i]new_y = cur_y + dy[i] if 0 <= new_x < N and 0 <= new_y < M and a[new_x][new_y] == 1 and not visited[new_x][new_y]:visited[new_x][new_y] = Truequeue.append((new_x, new_y, steps + 1))return -1
print(bfs(n-1,m-1))
5.2 艰难旅程
python">from collections import dequedef bfs(val,st_row,st_col):# val为二维列表,st_row,st_col为起始坐标row=len(val)col=len(val[0])arrived=[[False for j in range(row)] for i in range(col)]# 二维列表存储地图上的每个方格是否到达过,初始标记为Flasemoverow=[0,1,0,-1]movecol=[1,0,-1,0]# 定义四个方向,以实现向上下左右移动ans=1queue=deque([(st_row,st_col)])arrived[st_row-1][st_col-1]=True # 因为python是0索引,坐标值减1# 将起始坐标放入队列中,记录此点已到达,标记为Truewhile queue:# queue不为空的时候x,y=queue.popleft()# 从左端删除并返回位置坐标for i in range(4):newrow=x+moverow[i]newcol=y+movecol[i]# 向四个方向移动if newrow>row or newrow<=0 or newcol>col or newcol<=0:continue# 出界if not arrived[newrow-1][newcol-1] and val[newrow-1][newcol-1]!=val[x-1][y-1]:# 满足条件,未被标记,单元格内值不同queue.append((newrow,newcol))arrived[newrow-1][newcol-1]=True# 添加至队列并标记以走过ans+=1return ans
val= [[1, 0, 1, 1],[1, 0, 1, 0],[0, 1, 0, 1],[0, 0, 1, 1],
]
result = bfs(val,3,3)
print(result)# 11