当前位置:   article > 正文

Python - 深夜数据结构与算法之 BFS & DFS_python的bfs\dfs算法

python的bfs\dfs算法

目录

一.引言

二.BFS 与 DFS 简介

1.Search 搜索

2.DFS 深度优先

3.BFS 广度优先

4.DFS & BFS

三.经典算法实战

1.Generate-Parentheses [22]

2.Level-Order-Travesal [102]

3.Word-Ladder [127]

4.Num-Of-Islands [200]

5.Min-Gene-Mutation [433]

6.Largest-Values [515]

7.Mine-Clearance [529]

四.总结


一.引言

BFS 广度优先搜索与 DFS 深度优先搜索是树以及网格等相关题目最常见的方法之一,其通过不同的遍历方式对分叉结构进行遍历,对于树、图而言,其可以是二叉、多叉,其可以是上下左右,下面让我们深入了解下 BFS 与 DFS。

二.BFS 与 DFS 简介

1.Search 搜索

不论是 BFS 还是 DFS,它们都是数据结构节点的访问方式,为了效率足够高,我们希望每个节点访问且仅访问一次,因而衍生出了 DFS - Depth First Search 深度优先搜索与 BFS - Breadth First Search 广度优先搜索两种遍历方式。除此之外还有按照节点优先级访问的算法,不过需要特定的场景才会使用。 

2.DFS 深度优先

深度优先搜索,我们前面提到的二叉树的前中后序遍历都是深度优先搜索的一种,对于二叉树而言,其深度搜索的路径是向 Left 左子树和 Right 右子树递进,多叉树的话则是其 Children 节点集合,对于图而言,则是其他相邻的点 V,其通式可以参照下面的写法。

递归写法

非递归写法

  树的遍历

图的遍历 

3.BFS 广度优先

广度优先与深度优先不同,其从每一层下探访问节点,就好比是一滴水波纹逐渐向下扩散,然后扩散到每一层的节点上。

树的遍历 

代码写法

这里的队列其实就是我们常用到的列表。注意这里的 queue pop 是先进先出的,大家要和上面的 stack.pop 区分开。

4.DFS & BFS

我们可以根据上面的图再理解下 DFS 与 BFS 的遍历方式,如果都是用 List 实现的话,BFS 是 Deque 先进先出,DFS 则是 Stack 后进先出。

三.经典算法实战

1.Generate-Parentheses [22]

括号生成: https://leetcode.cn/problems/generate-parentheses/description/

 题目分析

这里可以看做是一颗二叉树,其每个节点可以分叉为 '(' 和 ')',我们需要遍历生成情况,并判断当前生成是否有效,再向下遍历。有效的条件有两个,一个是左括号一定是开头的第一个字符,另一个是左括号要和右括号数量一致。

 广度优先

  1. class Solution(object):
  2. def generateParenthesis(self, n):
  3. """
  4. :type n: int
  5. :rtype: List[str]
  6. """
  7. res = []
  8. def dfs(cur, left, right):
  9. # 终止条件
  10. if left == n and right == n:
  11. res.append(cur)
  12. # 左括号要在前面
  13. if left < n:
  14. bfs(cur + "(", left + 1, right)
  15. # 右括号要比左括号小
  16. if right < left:
  17. bfs(cur + ")", left, right + 1)
  18. dfs("", 0, 0)
  19. return res

 DFS 遍历的模版,直接一层一层下去找,直到满足 2n 的条件。

 栈实现

  1. class Solution(object):
  2. def generateParenthesis(self, n):
  3. """
  4. :type n: int
  5. :rtype: List[str]
  6. """
  7. res = []
  8. queue = [("", 0, 0)]
  9. while queue:
  10. cur, left, right = queue.pop(0)
  11. if left == right == n:
  12. res.append(cur)
  13. if left < n:
  14. queue.append((cur + "(", left + 1, right))
  15. if right < left:
  16. queue.append((cur + ")", left, right + 1))
  17. return res

DFS、BFS 的几种模版一定要多多练习,手到擒来。

 二刷留念

  1. class Solution(object):
  2. def generateParenthesis(self, n):
  3. """
  4. :type n: int
  5. :rtype: List[str]
  6. """
  7. # 保存结果
  8. result = []
  9. def generate(left, right, cur):
  10. # Stop
  11. if left == n and right == n:
  12. result.append(cur)
  13. if left < n:
  14. generate(left + 1, right, cur + "(")
  15. if right < left:
  16. generate(left, right + 1, cur + ")")
  17. generate(0, 0, "")
  18. return result

使用 DFS,不使用栈实现。 

2.Level-Order-Travesal [102]

二叉树层序遍历: https://leetcode.cn/problems/binary-tree-level-order-traversal

 题目分析

二叉树 BFS 遍历,这里要求我们逐层按列表返回而不是一个列表全部返回,所以我们在 BFS 套模版的基础上,还需要记录其 level 层的位置。

 广度优先

  1. # Definition for a binary tree node.
  2. # class TreeNode(object):
  3. # def __init__(self, val=0, left=None, right=None):
  4. # self.val = val
  5. # self.left = left
  6. # self.right = right
  7. class Solution(object):
  8. def levelOrder(self, root):
  9. """
  10. :type root: TreeNode
  11. :rtype: List[List[int]]
  12. """
  13. if not root:
  14. return []
  15. # 先进先出
  16. queue = [(0, root)]
  17. res = []
  18. while queue:
  19. # 记录对应的层次
  20. index, cur = queue.pop(0)
  21. if len(res) < index + 1:
  22. res.append([])
  23. res[index].append(cur.val)
  24. # 添加层消息与节点
  25. if cur.left:
  26. queue.append((index + 1, cur.left))
  27. if cur.right:
  28. queue.append((index + 1, cur.right))
  29. return res

按层遍历,每层下探时增加 index 找到其在 res 中对应的 list 保存即可。 

 深度优先

  1. # Definition for a binary tree node.
  2. # class TreeNode(object):
  3. # def __init__(self, val=0, left=None, right=None):
  4. # self.val = val
  5. # self.left = left
  6. # self.right = right
  7. class Solution(object):
  8. def levelOrder(self, root):
  9. """
  10. :type root: TreeNode
  11. :rtype: List[List[int]]
  12. """
  13. res = []
  14. def dfs(root, level):
  15. if not root:
  16. return
  17. if len(res) == level:
  18. res.append([])
  19. res[level].append(root.val)
  20. # 通过 level 存储层的信息
  21. if root.left:
  22. dfs(root.left, level + 1)
  23. if root.right:
  24. dfs(root.right, level + 1)
  25. dfs(root, 0)
  26. return res

层序遍历适合本题,但是深度遍历也可以实现,我们在向下探索的过程中可以获取当前的 level,只需将遍历到的结果对应的 level 找到即可。 

 二刷留念

  1. # Definition for a binary tree node.
  2. # class TreeNode(object):
  3. # def __init__(self, val=0, left=None, right=None):
  4. # self.val = val
  5. # self.left = left
  6. # self.right = right
  7. class Solution(object):
  8. def levelOrder(self, root):
  9. """
  10. :type root: TreeNode
  11. :rtype: List[List[int]]
  12. """
  13. if not root:
  14. return []
  15. res = {}
  16. stack = [(root, 0)]
  17. while stack:
  18. node, level = stack.pop(0)
  19. if level not in res:
  20. res[level] = []
  21. res[level].append(node.val)
  22. if node.left:
  23. stack.append((node.left, level + 1))
  24. if node.right:
  25. stack.append((node.right, level + 1))
  26. return res.values()

3.Word-Ladder [127]

单词接龙: https://leetcode.cn/problems/word-ladder/description/

 题目分析

层序遍历的方法,类似前面的基因突变,这里可以理解为连续突变且每次可以突变 26 个字母。

 广度优先

  1. class Solution(object):
  2. def ladderLength(self, beginWord, endWord, wordList):
  3. """
  4. :type beginWord: str
  5. :type endWord: str
  6. :type wordList: List[str]
  7. :rtype: int
  8. """
  9. # 去重使用
  10. valid_word = set(wordList)
  11. if endWord not in wordList or len(wordList) == 0:
  12. return 0
  13. queue = [(beginWord, 1)]
  14. while queue:
  15. new_queue = []
  16. for word, step in queue:
  17. # 停止条件
  18. if word == endWord:
  19. return step
  20. # 处理当前元素
  21. for i in range(len(word)):
  22. # 突变每一个位置
  23. for char in 'abcdefghijklmnopqrstuvwxyz':
  24. # 去重优化
  25. if char != word[i]:
  26. new_word = word[:i] + char + word[i + 1:]
  27. if new_word in valid_word:
  28. valid_word.remove(new_word)
  29. new_queue.append((new_word, step + 1))
  30. queue = new_queue
  31. return 0

这里 for char in 'abcd...' 可以使用 orc + chr 转换:

  1. for char in range(ord('a'), ord('z') + 1):
  2. print(chr(char))

 双向 BFS

  1. class Solution(object):
  2. def ladderLength(self, beginWord, endWord, wordList):
  3. """
  4. :type beginWord: str
  5. :type endWord: str
  6. :type wordList: List[str]
  7. :rtype: int
  8. """
  9. # 去重使用
  10. valid_word = set(wordList)
  11. if endWord not in wordList or len(wordList) == 0:
  12. return 0
  13. # 双向 BFS
  14. begin, end, step = {beginWord}, {endWord}, 1
  15. while begin and end:
  16. if len(begin) > len(end):
  17. begin, end = end, begin
  18. # 存储下一层
  19. next_level = set()
  20. for word in begin:
  21. for i in range(len(word)):
  22. # a-z
  23. for x in 'abcdefghijklmnopqrstuvwxyz':
  24. # 节省无必要的替换
  25. if x != word[i]:
  26. new_word = word[:i] + x + word[i + 1:]
  27. if new_word in end:
  28. return step + 1
  29. if new_word in valid_word:
  30. next_level.add(new_word)
  31. valid_word.remove(new_word)
  32. begin = next_level
  33. step += 1
  34. return 0

为了加快搜素速度我们改写为双向 BFS,Begin-A-B-C-D-End 我们拆分为 Begin -> B,End -> B,两边同时检索,最终输出总的 step。这里我们尝试了 ord 的写法,但是时间复杂度没有上面的直接。

4.Num-Of-Islands [200]

岛屿数量: https://leetcode.cn/problems/number-of-islands/description/

 题目分析

对于每个点 row col,其都有上下左右 4 个分叉点,我们可以作为四叉树遍历,通过 generate_related_nodes 方法生成其不同分叉,并判断是否可以联通 [row,col] == 1,最后遍历所有 gird 的点,看可以走通多少次返回 count 即可。

 深度优先

  1. class Solution(object):
  2. def numIslands(self, grid):
  3. """
  4. :type grid: List[List[str]]
  5. :rtype: int
  6. """
  7. # 异常情况
  8. if not grid:
  9. return 0
  10. # 记录岛屿数量
  11. numLand = 0
  12. # 遍历并标记
  13. for row in range(len(grid)):
  14. for col in range(len(grid[0])):
  15. if grid[row][col] == "1":
  16. self.dfs(row, col, grid)
  17. numLand += 1
  18. return numLand
  19. # 在网格索引范围内
  20. def inArea(self, row, col, grid):
  21. return 0 <= row < len(grid) and 0 <= col < len(grid[0])
  22. def dfs(self, row, col, grid):
  23. # 判断节点合法性
  24. if not self.inArea(row, col, grid) or grid[row][col] != "1":
  25. return
  26. grid[row][col] = "2"
  27. self.dfs(row + 1, col, grid)
  28. self.dfs(row, col + 1, grid)
  29. self.dfs(row - 1, col, grid)
  30. self.dfs(row, col - 1, grid)

继续 dfs 的条件是在 grid 内且值为 "1"。 

5.Min-Gene-Mutation [433]

最小基因变化: https://leetcode.cn/problems/minimum-genetic-mutation/

 题目分析

题目给定了基因的 4 种类型 'ACGT',就像是 4 叉树一样,我们每次遍历有 4 个方向可以走,当前层遍历完再向下一个索引继续遍历,总共有 4^^8 种情况,由于有 bank 的限制,我们可以过滤掉多种情况。 

 广度优先

  1. class Solution(object):
  2. def minMutation(self, startGene, endGene, bank):
  3. """
  4. :type startGene: str
  5. :type endGene: str
  6. :type bank: List[str]
  7. :rtype: int
  8. """
  9. # 没有基因库或最终突变不在基因库
  10. if not bank or endGene not in bank:
  11. return -1
  12. queue = []
  13. queue.append((startGene, 0))
  14. # 记录基因是否有效
  15. valid_bank = set(bank)
  16. while queue:
  17. # 获取当前步数
  18. gene, step = queue.pop(0)
  19. # 突变每一个位置
  20. for i in range(len(gene)):
  21. for g in 'ACGT':
  22. # 从第一个位置开始,每个突变一次
  23. if g != gene[i]:
  24. change_gene = gene[:i] + g + gene[i + 1:]
  25. if change_gene == endGene:
  26. return step + 1
  27. elif change_gene in valid_bank:
  28. queue.append((change_gene, step + 1))
  29. valid_bank.remove(change_gene)
  30. return -1

 判断 g != gene[i] 减少相同字符的替换。

6.Largest-Values [515]

每层的最大值: https://leetcode.cn/problems/find-largest-value-in-each-tree-row/

 题目分析

层序遍历,找到每一层的最大值即可,也可以深度遍历,记录即可。

 广度优先

  1. # Definition for a binary tree node.
  2. # class TreeNode(object):
  3. # def __init__(self, val=0, left=None, right=None):
  4. # self.val = val
  5. # self.left = left
  6. # self.right = right
  7. class Solution(object):
  8. def largestValues(self, root):
  9. """
  10. :type root: TreeNode
  11. :rtype: List[int]
  12. """
  13. if not root:
  14. return []
  15. queue = [root]
  16. res = []
  17. while queue:
  18. cur_max = -99999999999
  19. new_queue = []
  20. # 遍历每一层
  21. for i in range(len(queue)):
  22. if queue[i]:
  23. cur_max = max(cur_max, queue[i].val)
  24. if queue[i].left:
  25. new_queue.append(queue[i].left)
  26. if queue[i].right:
  27. new_queue.append(queue[i].right)
  28. res.append(cur_max)
  29. queue = new_queue
  30. return res

空间复杂度主要使用了 queue,其余操作主要在计算 max。 

 深度优先

  1. # Definition for a binary tree node.
  2. # class TreeNode(object):
  3. # def __init__(self, val=0, left=None, right=None):
  4. # self.val = val
  5. # self.left = left
  6. # self.right = right
  7. class Solution(object):
  8. def largestValues(self, root):
  9. """
  10. :type root: TreeNode
  11. :rtype: List[int]
  12. """
  13. res = []
  14. def dfs(root, level):
  15. # 停止条件
  16. if not root:
  17. return
  18. # 当前层逻辑
  19. if len(res) == level:
  20. res.append(-9999999999)
  21. res[level] = max(res[level], root.val)
  22. # 下一层
  23. if root.left:
  24. dfs(root.left, level + 1)
  25. if root.right:
  26. dfs(root.right, level + 1)
  27. dfs(root, 0)
  28. return res

同理,DFS 时可以使用 level 保存层的信息,我们通过 level 与数组建立关系构造 max 即可。

7.Mine-Clearance [529]

扫雷游戏: https://leetcode.cn/problems/minesweeper/description/ 

 题目分析

给定初始二维数组和起点,返回修改后的二维数组。若起点处是雷,即 ‘M’,直接将其修改为 'X',游戏结束;若起点处是空,即 ‘E’,则从起点开始向 8 邻域中的空地搜索,直到到达邻接

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/464428
推荐阅读
相关标签