广度优先搜索(BFS,Breadth First Search) 的一个常见应用是找出从根结点到目标结点的最短路径,其实现用到了队列。下面用一个例子来讲明BFS的原理,在下图中,咱们BFS 来找出根结点 A 和目标结点 G 之间的最短路径。java
图3:BFS例子 node
Q
,将根节点入队:A
A
出队,将与A
相邻的节点入队,此时队列为BCD
B
出队,将与B
相邻的节点入队,此时队列为CDE
C
出队,将与C
相邻的节点入队,此时队列为DEF
(节点E
已经被遍历过,不须要再入队)D
出队,将与D
相邻的节点入队,此时队列为EFG
E
出队,下面没有节点F
出队,下面是G
,已遍历过,为目标值,遍历结束BFS代码模板:python
def BFS(graph, start, end): queue = [] queue.append([start]) visited.add(start) # 对于图来讲,要标记节点已经被访问过,防止重复访问 while queue: node = queue.pop() # 出队 visited.add(node) # 标记访问 process(node) # 对节点进行一些处理 nodes = generate_related_nodes(node) # 获得当前节点的后继节点,而且没有被访问过 queue.push(nodes) # 其余处理部分 ...
与 BFS 相似,深度优先搜索(DFS,Depth-First-Search) 也可用于查找从根结点到目标结点的路径。下面经过一个例子,用DFS 找出从根结点 A 到目标结点 G 的路径。web
图3:BFS例子 算法
在上面的例子中,咱们从根结点 A
开始。首先,咱们选择结点 B
的路径,并进行回溯,直到咱们到达结点 E
,咱们没法更进一步深刻。而后咱们回溯到A
并选择第二条路径到结点 C
。从 C
开始,咱们尝试第一条路径到 E
可是 E
已被访问过。因此咱们回到 C
并尝试从另外一条路径到 F
。最后,咱们找到了 G
。虽然咱们成功找出了路径 A-> C-> F-> G
,但这不是从 A 到 G 的最短路径。数组
DFS代码模板:app
# 递归写法 visited = set() def DFS(node, visited): visited.add(node) # process current node here ... for next_node in node.children(): if next_node not in visited: DFS(next_node, visited)
此题为leetcode的第200题。有两个思路:svg
Flood fill 算法的含义:是从一个区域中提取若干个连通的点与其余相邻区域区分开(或分别染成不一样颜色)的经典 算法。由于其思路相似洪水从一个区域扩散到全部能到达的区域而得名。在 GNU Go 和 扫雷 中,Flood Fill算法被用来计算须要被清除的区域。函数
对于这类问题,其实就是从一个点开始,遍历与这个起点连着的格子,遍历过的格子就标上“被访问过”的记号,找到与之相连的全部格子后,就是发现了一个岛屿。而这个遍历的过程能够用广度优先或深度优先实现,具体讲解能够参考这里,动画很明白,这里只贴上代码:动画
# 广度优先搜索 from typing import List from collections import deque class Solution: # x-1,y # x,y-1 x, y x,y+1 # x+1,y # 方向数组,它表示了相对于当前位置的 4 个方向的横、纵坐标的偏移量,这是一个常见的技巧 directions = [(-1, 0), (0, -1), (1, 0), (0, 1)] def numIslands(self, grid: List[List[str]]) -> int: m = len(grid) if m == 0: return 0 n = len(grid[0]) marked = [[False for _ in range(n)] for _ in range(m)] count = 0 # 从第 1 行、第 1 格开始,对每一格尝试进行一次 DFS 操做 for i in range(m): for j in range(n): # 只要是陆地,且没有被访问过的,就能够使用 BFS 发现与之相连的陆地,并进行标记 if not marked[i][j] and grid[i][j] == '1': # count 能够理解为连通份量,你能够在广度优先遍历完成之后,再计数, # 即这行代码放在【位置 1】也是能够的 count += 1 queue = deque() queue.append((i, j)) # 注意:这里要标记上已经访问过 marked[i][j] = True while queue: cur_x, cur_y = queue.popleft() # 获得 4 个方向的坐标 for direction in self.directions: new_i = cur_x + direction[0] new_j = cur_y + direction[1] # 若是不越界、没有被访问过、而且还要是陆地,就入队 if 0 <= new_i < m and 0 <= new_j < n and not marked[new_i][new_j] and grid[new_i][new_j] == '1': queue.append((new_i, new_j)) # 入队后立刻要标记为“被访问过” marked[new_i][new_j] = True #【位置 1】 return count
# 深度优先搜索 from typing import List class Solution: # x-1,y # x,y-1 x,y x,y+1 # x+1,y # 方向数组,它表示了相对于当前位置的 4 个方向的横、纵坐标的偏移量,这是一个常见的技巧 directions = [(-1, 0), (0, -1), (1, 0), (0, 1)] def numIslands(self, grid: List[List[str]]) -> int: m = len(grid) if m == 0: return 0 n = len(grid[0]) marked = [[False for _ in range(n)] for _ in range(m)] count = 0 # 从第 1 行、第 1 格开始,对每一格尝试进行一次 DFS 操做 for i in range(m): for j in range(n): # 只要是陆地,且没有被访问过的,就能够使用 DFS 发现与之相连的陆地,并进行标记 if not marked[i][j] and grid[i][j] == '1': # 连通份量计数 count += 1 # DFS搜索 self.__dfs(grid, i, j, m, n, marked) return count # 递归进行深度优先搜索 def __dfs(self, grid, i, j, m, n, marked): marked[i][j] = True for direction in self.directions: new_i = i + direction[0] new_j = j + direction[1] if 0 <= new_i < m and 0 <= new_j < n and not marked[new_i][new_j] and grid[new_i][new_j] == '1': self.__dfs(grid, new_i, new_j, m, n, marked)
此题为leetcode第104题
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
深度优先解法:
# 深度优先搜索 class Solution: def maxDepth(self, root: TreeNode) -> int: # 递归,深度优先搜索 if root is None: return 0 else: left_height = self.maxDepth(root.left) # 左子树高度 right_height = self.maxDepth(root.right) # 右子树高度 return max(left_height, right_height) + 1
广度优先解法:
# 迭代,广度优先搜索 from collections import deque class Solution: def maxDepth(self, root: TreeNode) -> int: if root is None: return 0 depth = 0 q = deque() q.append((1, root)) while q: curr_depth, node = q.pop() depth = max(depth, curr_depth) if node.left: q.append((curr_depth + 1, node.left)) if node.right: q.append((curr_depth + 1, node.right)) return depth
此题为leetcode第111题
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
深度优先解法:
# 递归,深度优先搜索 class Solution: def minDepth(self, root: TreeNode) -> int: # 递归,深度优先搜索 if root is None: return 0 if root.left is None: # 若左子树为空,root的深度为1+右子树深度 return 1 + self.minDepth(root.right) if root.right is None: # 若右子树为空,root的深刻为1+左子树深度 return 1 + self.minDepth(root.left) left_height = self.minDepth(root.left) # 左子树高度 right_height = self.minDepth(root.right) # 右子树高度 return min(left_height, right_height) + 1
广度优先解法:
# 迭代,广度优先搜索 from collections import deque class Solution: def minDepth(self, root: TreeNode) -> int: # 迭代,广度优先搜索 if root is None: return 0 q = deque() q.append((1, root)) depth = 1 while q: curr_depth, node = q.popleft() # 若是node为叶子节点,则此时深度为最小深度 if node.left is None and node.right is None: return curr_depth if node.left: q.append((curr_depth + 1, node.left)) if node.right: q.append((curr_depth + 1, node.right))
此题为leetcode第22题
数字 n 表明生成括号的对数,请你设计一个函数,用于可以生成全部可能的而且 有效的 括号组合。
# 深度优先搜索加剪枝 class Solution: def generateParenthesis(self, n: int) -> List[str]: self.res = [] self._gen('', 0, 0, n) return self.res def _gen(self, result, left, right, n): # left为左括号用了几个,right为右括号用了几个 if left == n and right == n: self.res.append(result) return # 若是用的左括号小于n个,能够加左括号 if left < n: self._gen(result+'(', left+1, right, n) # 只有在用的右括号小于用的左括号时,才能够加右括号 if right < left: self._gen(result+')', left, right+1, n)
此题为leetcode第51题
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,而且使皇后彼此之间不能相互攻击。给定一个整数 n,返回全部不一样的 n 皇后问题的解决方案。每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别表明了皇后和空位。
class Solution: def solveNQueens(self, n: int) -> List[List[str]]: if n < 1: return [] self.res = [] # 记录最终结果 self.cols, self.pie, self.na = set(), set(), set() # 标记横竖、斜角方向 self.DFS(n, 0, []) return [['.' * j + 'Q' + '.' * (n - j - 1) for j in col] for col in self.res] def DFS(self, n, row, curr_state): # curr_state表明每行的哪些列能够放置皇后,是一种可能的解决方案 # 若是递归能够走到最后一层,说明curr_state是一种合法的解决方案,将其放入self.res里 if row >= n: self.res.append(curr_state) return # 遍历列 for col in range(n): # 若是当前位置上,能够被横竖、斜角方向上的皇后攻击到,则continue if col in self.cols or row + col in self.pie or row - col in self.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)
此题为leetcode第37题
编写一个程序,经过已填充的空格来解决数独问题。下面给出最朴素的DFS:
class Solution: def solveSudoku(self, board: List[List[str]]) -> None: """ Do not return anything, modify board in-place instead. """ if board is None or len(board) == 0: return self.solve(board) def solve(self, board): for i in range(len(board)): for j in range(len(board)): # 循环遍历,找到空位置 if board[i][j] == '.': # 从1-9遍历 for k in range(1, 10): c = str(k) if self.isValid(board, i, j, c): # 判断c是否能够放在(i, j)上 # 若是在(i, j)上放数字c可行的话,先将c放上去 board[i][j] = c # 继续递归,看放上c后,后面的是否可行 if self.solve(board): return True # 放上c后能够解决整个数独则返回True else: board[i][j] = '.' # 不可行的话将c清空 return False # 1-9遍历完后还不行就返回False return True # 能够遍历完整个board就返回True def isValid(self, board, row, col, c): for i in range(9): if board[i][col] != '.' and board[i][col] == c: return False if board[row][i] != '.' and board[row][i] == c: return False if board[3 * (row // 3) + i // 3][3 * (col // 3) + i % 3] != '.' and board[3 * (row // 3) + i // 3][3 * (col // 3) + i % 3] == c: return False return True