python扫雷|python扫雷 广度优先_【原创教程】数据结构与算法(5)——广度与深度优先搜索...

_原题是:【原创教程】数据结构与算法(5)——广度与深度优先搜索
一、广度优先搜索
广度优先搜索(BFS,Breadth First Search)的一个常见应用是找出从根结点到目标结点的最短路径,其实现用到了队列。下面用一个例子来说明BFS的原理,在下图中,我们BFS 来找出根结点 A 和目标结点 G 之间的最短路径。
python扫雷|python扫雷 广度优先_【原创教程】数据结构与算法(5)——广度与深度优先搜索...
文章图片

图3:BFS例子
首先初始化一个队列 Q ,将根节点入队: A
A 出队,将与 A 相邻的节点入队,此时队列为 BCD
B 出队,将与 B 相邻的节点入队,此时队列为 CDE
C 出队,将与 C 相邻的节点入队,此时队列为 DEF (节点 E 已经被遍历过,不需要再入队)
D 出队,将与 D 相邻的节点入队,此时队列为 EFG
E 出队,下面没有节点
F 出队,下面是 G ,已遍历过,为目标值,遍历结束
BFS代码模板:
defBFS(graph, start, end):
queue = []
queue.append([start])
visited.add(start) # 对于图来说,要标记节点已经被访问过,防止重复访问
whilequeue:
node = queue.pop # 出队
visited.add(node) # 标记访问
process(node) # 对节点进行一些处理
nodes = generate_related_nodes(node) # 得到当前节点的后继节点,并且没有被访问过
queue.push(nodes)
# 其他处理部分
... 二、深度优先搜索
与 BFS 类似, 深度优先搜索(DFS,Depth-First-Search)也可用于查找从根结点到目标结点的路径。下面通过一个例子,用DFS 找出从根结点 A 到目标结点 G 的路径。
python扫雷|python扫雷 广度优先_【原创教程】数据结构与算法(5)——广度与深度优先搜索...
文章图片

图3:BFS例子
在上面的例子中,我们从根结点 A 开始。首先,我们选择结点 B 的路径,并进行回溯,直到我们到达结点 E ,我们无法更进一步深入。然后我们回溯到 A 并选择第二条路径到结点 C 。从 C 开始,我们尝试第一条路径到 E 但是 E 已被访问过。所以我们回到 C 并尝试从另一条路径到 F 。最后,我们找到了 G 。虽然我们成功找出了路径 A-> C-> F-> G ,但这不是从 A 到 G 的最短路径。
DFS代码模板:
# 递归写法
visited = set
defDFS(node, visited):
visited.add(node)
# process current node here
...
fornext_node innode.children:
ifnext_node notinvisited:
DFS(next_node, visited) 三、例题(1)岛屿数量问题
此题为leetcode的第200题(https://leetcode-cn.com/problems/number-of-islands/)。有两个思路:
Flood fill 算法: 深度优先搜索或广度优先搜索
并查集
Flood fill 算法的含义:是从一个区域中提取若干个连通的点与其他相邻区域区分开(或分别染成不同颜色)的经典 算法。因为其思路类似洪水从一个区域扩散到所有能到达的区域而得名。在 GNU Go 和 扫雷 中,Flood Fill算法被用来计算需要被清除的区域。
对于这类问题,其实就是从一个点开始,遍历与这个起点连着的格子,遍历过的格子就标上“被访问过”的记号,找到与之相连的所有格子后,就是发现了一个岛屿。而这个遍历的过程可以用广度优先或深度优先实现,具体讲解可以参考:https://leetcode-cn.com/problems/number-of-islands/solution/dfs-bfs-bing-cha-ji-python-dai-ma-java-dai-ma-by-l/,动画很明白,这里只贴上代码:
# 广度优先搜索
fromtyping importList
fromcollections importdeque
classSolution:
# x-1,y
# x,y-1 x, y x,y+1
# x+1,y
# 方向数组,它表示了相对于当前位置的 4 个方向的横、纵坐标的偏移量,这是一个常见的技巧
directions = [( -1, 0), ( 0, -1), ( 1, 0), ( 0, 1)]
defnumIslands(self, grid: List[List[str]])-> int:
m = len(grid)
ifm == 0:
return0
n = len(grid[ 0])
marked = [[ Falsefor_ inrange(n)] for_ inrange(m)]
count = 0
# 从第 1 行、第 1 格开始,对每一格尝试进行一次 DFS 操作
fori inrange(m):
forj inrange(n):
# 只要是陆地,且没有被访问过的,就可以使用 BFS 发现与之相连的陆地,并进行标记
ifnotmarked[i][j] andgrid[i][j] == '1':
# count 可以理解为连通分量,你可以在广度优先遍历完成以后,再计数,
# 即这行代码放在【位置 1】也是可以的
count += 1
queue = deque
queue.append((i, j))
# 注意:这里要标记上已经访问过
marked[i][j] = True
whilequeue:
cur_x, cur_y = queue.popleft
# 得到 4 个方向的坐标
fordirection inself.directions:
new_i = cur_x + direction[ 0]
new_j = cur_y + direction[ 1]
# 如果不越界、没有被访问过、并且还要是陆地,就入队
if0<= new_i < m and0<= new_j < n andnotmarked[new_i][new_j] andgrid[new_i][new_j] == '1':
queue.append((new_i, new_j))
# 入队后马上要标记为“被访问过”
marked[new_i][new_j] = True
#【位置 1】
returncount # 深度优先搜索
fromtyping importList
classSolution:
# x-1,y
# x,y-1 x,y x,y+1
# x+1,y
# 方向数组,它表示了相对于当前位置的 4 个方向的横、纵坐标的偏移量,这是一个常见的技巧
directions = [( -1, 0), ( 0, -1), ( 1, 0), ( 0, 1)]
defnumIslands(self, grid: List[List[str]])-> int:
m = len(grid)
ifm == 0:
return0
n = len(grid[ 0])
marked = [[ Falsefor_ inrange(n)] for_ inrange(m)]
count = 0
# 从第 1 行、第 1 格开始,对每一格尝试进行一次 DFS 操作
fori inrange(m):
forj inrange(n):
# 只要是陆地,且没有被访问过的,就可以使用 DFS 发现与之相连的陆地,并进行标记
ifnotmarked[i][j] andgrid[i][j] == '1':
# 连通分量计数
count += 1
# DFS搜索
self.__dfs(grid, i, j, m, n, marked)
returncount
# 递归进行深度优先搜索
def__dfs(self, grid, i, j, m, n, marked):
marked[i][j] = True
fordirection inself.directions:
new_i = i + direction[ 0]
new_j = j + direction[ 1]
if0<= new_i < m and0<= new_j < n andnotmarked[new_i][new_j] andgrid[new_i][new_j] == '1':
self.__dfs(grid, new_i, new_j, m, n, marked) (2)二叉树的最大深度
此题为leetcode第104题(https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/)
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
深度优先解法:
# 深度优先搜索
classSolution:
defmaxDepth(self, root: TreeNode)-> int:
# 递归,深度优先搜索
ifroot isNone:
return0
else:
left_height = self.maxDepth(root.left) # 左子树高度
right_height = self.maxDepth(root.right) # 右子树高度
returnmax(left_height, right_height) + 1
广度优先解法:
# 迭代,广度优先搜索
fromcollections importdeque
classSolution:
defmaxDepth(self, root: TreeNode)-> int:
ifroot isNone:
return0
depth = 0
q = deque
q.append(( 1, root))
whileq:
curr_depth, node = q.pop
depth = max(depth, curr_depth)
ifnode.left:
q.append((curr_depth + 1, node.left))
ifnode.right:
q.append((curr_depth + 1, node.right))
returndepth (3)二叉树的最小深度
此题为leetcode第111题(https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/)
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
深度优先解法:
# 递归,深度优先搜索
classSolution:
defminDepth(self, root: TreeNode)-> int:
# 递归,深度优先搜索
ifroot isNone:
return0
ifroot.left isNone: # 若左子树为空,root的深度为1+右子树深度
return1+ self.minDepth(root.right)
ifroot.right isNone: # 若右子树为空,root的深入为1+左子树深度
return1+ self.minDepth(root.left)
left_height = self.minDepth(root.left) # 左子树高度
right_height = self.minDepth(root.right) # 右子树高度
returnmin(left_height, right_height) + 1
广度优先解法:
# 迭代,广度优先搜索
fromcollections importdeque
classSolution:
defminDepth(self, root: TreeNode)-> int:
# 迭代,广度优先搜索
ifroot isNone:
return0
q = deque
q.append(( 1, root))
depth = 1
whileq:
curr_depth, node = q.popleft
# 如果node为叶子节点,则此时深度为最小深度
ifnode.left isNoneandnode.right isNone:
returncurr_depth
ifnode.left:
q.append((curr_depth + 1, node.left))
ifnode.right:
q.append((curr_depth + 1, node.right)) (4)括号生成
此题为leetcode第22题(https://leetcode-cn.com/problems/generate-parentheses/)
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
# 深度优先搜索加剪枝
classSolution:
defgenerateParenthesis(self, n: int)-> List[str]:
self.res = []
self._gen( '', 0, 0, n)
returnself.res
def_gen(self, result, left, right, n):
# left为左括号用了几个,right为右括号用了几个
ifleft == n andright == n:
self.res.append(result)
return
# 如果用的左括号小于n个,可以加左括号
ifleft < n:
self._gen(result+ '(', left+ 1, right, n)
# 只有在用的右括号小于用的左括号时,才可以加右括号
ifright < left:
self._gen(result+ ')', left, right+ 1, n) (5)N皇后问题
此题为leetcode第51题(https://leetcode-cn.com/problems/n-queens/submissions/)
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
python扫雷|python扫雷 广度优先_【原创教程】数据结构与算法(5)——广度与深度优先搜索...
文章图片

classSolution:
defsolveNQueens(self, n: int)-> List[List[str]]:
ifn < 1:
return[]
self.res = [] # 记录最终结果
self.cols, self.pie, self.na = set, set, set # 标记横竖、斜角方向
self.DFS(n, 0, [])
return[[ '.'* j + 'Q'+ '.'* (n - j - 1) forj incol] forcol inself.res]
defDFS(self, n, row, curr_state):# curr_state代表每行的哪些列可以放置皇后,是一种可能的解决方案
# 如果递归可以走到最后一层,说明curr_state是一种合法的解决方案,将其放入self.res里
ifrow >= n:
self.res.append(curr_state)
return
# 遍历列
forcol inrange(n):
# 如果当前位置上,可以被横竖、斜角方向上的皇后攻击到,则continue
ifcol inself.cols orrow + col inself.pie orrow - col inself.na:
continue
# 如果当前位置不会被攻击到,则标记横竖、斜角方向
self.cols.add(col)
self.pie.add(row + col)
self.na.add(row - col)
# 递归,row+1进入下一行,curr_state+[col]保存一种可能的状态
self.DFS(n, row + 1, curr_state + [col])
# 上面的递归出来后,清除横竖、斜角方向的标记
# 因为我们是在(n, col)位置上尝试放置皇后看是否可行
self.cols.remove(col)
self.pie.remove(row + col)
self.na.remove(row - col) (6)解数独
此题为leetcode第37题(https://leetcode-cn.com/problems/sudoku-solver/submissions/)
编写一个程序,通过已填充的空格来解决数独问题。下面给出最朴素的DFS:
classSolution:
defsolveSudoku(self, board: List[List[str]])-> None:
"""
Do not return anything, modify board in-place instead.
"""
ifboard isNoneorlen(board) == 0:
return
self.solve(board)
defsolve(self, board):
fori inrange(len(board)):
forj inrange(len(board)):
# 循环遍历,找到空位置
ifboard[i][j] == '.':
# 从1-9遍历
fork inrange( 1, 10):
c = str(k)
ifself.isValid(board, i, j, c): # 判断c是否可以放在(i, j)上
# 如果在(i, j)上放数字c可行的话,先将c放上去
board[i][j] = c
# 继续递归,看放上c后,后面的是否可行
ifself.solve(board):
returnTrue# 放上c后可以解决整个数独则返回True
else:
board[i][j] = '.'# 不可行的话将c清空
returnFalse# 1-9遍历完后还不行就返回False
returnTrue# 可以遍历完整个board就返回True
defisValid(self, board, row, col, c):
fori inrange( 9):
ifboard[i][col] != '.'andboard[i][col] == c:
returnFalse
ifboard[row][i] != '.'andboard[row][i] == c:
returnFalse
ifboard[ 3* (row // 3) + i // 3][ 3* (col // 3) + i % 3] != '.'andboard[ 3* (row // 3) + i // 3][ 3* (col // 3) + i % 3] == c:
returnFalse
returnTrueCopy
(全文完)
作者简介:
李旭东,中科院在读研究生,研究方向为深度学习、工业故障诊断,喜欢读书,希望同各位小伙伴一起交流学习!
作者微信号:wuan3076
个人网站:lixudong.ink
邮箱:lixudong16@mails.ucas.edu.cn
粉丝福利:
关注公众号“ 辰语学习笔记”,在公众号对话框回复关键词“知识手册”,免费获取《机器学习基础知识手册》!
注:这篇文章是公众号粉丝投稿的原创文章,想要 投稿请联系小编微信:makersky1688返回搜狐,查看更多
【python扫雷|python扫雷 广度优先_【原创教程】数据结构与算法(5)——广度与深度优先搜索...】责任编辑:

    推荐阅读