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

原标题:【原创教程】数据结构与算法(5)——广度与深度优先搜索

一、广度优先搜索

广度优先搜索(BFS,Breadth First Search)的一个常见应用是找出从根结点到目标结点的最短路径,其实现用到了队列。下面用一个例子来说明BFS的原理,在下图中,我们BFS 来找出根结点 A 和目标结点 G 之间的最短路径。

图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 的路径。

图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’ 和 ‘.’ 分别代表了皇后和空位。

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返回搜狐,查看更多

责任编辑:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值