BFS(广度优先遍历)图算法总结

BFS(广度优先遍历)图算法总结

首先图算法基本都是用BFS解决的,所以学懂了BFS就会图算法了。

例题一:四周扩散型
leetcode200
leetcode130

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        if not grid or not grid[0]:
            return 0
        row = len(grid)
        col = len(grid[0])
        res = 0
        # dfs 标准逻辑代码,判断条件,更改状态,四周扩散,递归
        def dfs(i,j):
            if 0<=i<row and 0<=j<col and grid[i][j] == '1':
                grid[i][j] = '0'
                for x,y in [(-1,0),(1,0),(0,1),(0,-1)]:
                    dfs(i+x,j+y)
        #遍历矩阵
        for i in range(row):
            for j in range(col):
                if grid[i][j] == '1':
                    dfs(i,j)
                    res +=1
        return res

类似题目:

给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。
找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

class Solution:
    def solve(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        if not board or not board[0]:
            return 0
        row = len(board)
        col = len(board[0])
        def bfs(i,j):
            if 0<=i<row and 0<=j<col and board[i][j] == 'O':
                board[i][j] = 'B'
                for x,y in [(-1,0),(1,0),(0,1),(0,-1)]:
                    bfs(i+x,j+y)
        for i in range(row):
            if board[i][col-1] == 'O':
                bfs(i,col-1)
            if board[i][0] == 'O':
                bfs(i,0)
        for i in range(col):
            if board[0][i] == 'O':
                bfs(0,i)
            if board[row-1][i] == 'O':
                bfs(row-1,i)
        for i in range(row):
            for j in range(col):
                if board[i][j] == 'O':
                    board[i][j] = 'X'
                if board[i][j] == 'B':
                    board[i][j] = 'O'

下面是非简化的标准写法:

class Solution:
    def solve(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        if not board or not board[0]:
            return
        row = len(board)
        col = len(board[0])

        def bfs(i, j):
            from collections import deque
            queue = deque()
            queue.appendleft((i, j))
            while queue:
                i, j = queue.pop()
                if 0 <= i < row and 0 <= j < col and board[i][j] == "O":
                    board[i][j] = "B"
                    for x, y in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
                        queue.appendleft((i + x, j + y))

        for j in range(col):
            # 第一行
            if board[0][j] == "O":
                bfs(0, j)
            # 最后一行
            if board[row - 1][j] == "O":
                bfs(row - 1, j)

        for i in range(row):

            if board[i][0] == "O":
                bfs(i, 0)
            if board[i][col - 1] == "O":
                bfs(i, col - 1)

        for i in range(row):
            for j in range(col):
                if board[i][j] == "O":
                    board[i][j] = "X"
                if board[i][j] == "B":
                    board[i][j] = "O"

其实吧bfs都必须有一个队列来负责存储扩散情况的,但是由于上面例子的情况简单只有四周四个方向,所以在for循环列表里面就包括了,就不另外建立新列表了。下面情况复杂所以得把for循环和列表分别处理。

小改进的一道题:
leetcode542 01矩阵

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
示例 1:
输入:
0 0 0
0 1 0
0 0 0
输出:
0 0 0
0 1 0
0 0 0
示例 2:
输入:
0 0 0
0 1 0
1 1 1
输出:
0 0 0
0 1 0
1 2 1

这道题不能用上面的那种简单的递归bfs,因为我们需要计算走的步数,所以用递归的话,就涉及到步数累加操作,写起来比较麻烦。

from collections import deque
class Solution:
    def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:
        if not matrix or not matrix[0]:
            return []
        n = len(matrix)
        m = len(matrix[0])
        queue = deque()
        #创建一个新矩阵用来储存结果,这样方便计算步数
        res = [[-1]*m for _ in range(n)]
         
        for i in range(n):
            for j in range(m):
            	#反向思维,求0到1的步数
                if matrix[i][j] == 0:
                    res[i][j] = 0 
                    queue.append([i,j])
        while queue:
            i,j = queue.popleft()
            for x,y in [(-1,0),(1,0),(0,1),(0,-1)]:
                new_i,new_j = i+x,j+y
                if 0<=new_i<n and 0<=new_j<m and res[new_i][new_j]==-1:
                    res[new_i][new_j] = res[i][j] +1
                    queue.append([new_i,new_j])
        
        return res

例题二:
leetcode127

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]
输出: 5
解释: 一个最短转换序列是 “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
返回它的长度 5。
示例 2:
输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”]
输出: 0
解释: endWord “cog” 不在字典中,所以无法进行转换。

from typing import List
from collections import deque

class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        word_set = set(wordList)
        if len(word_set) == 0 or endWord not in word_set:
            return 0

        if beginWord in word_set:
            word_set.remove(beginWord)
        #储存队列
        queue = deque()
        queue.append(beginWord)
        #防止重复队列
        visited = set(beginWord)
        visited.add(beginWord)

        step = 1
        while queue:
            current_size = len(queue)
            #queue中可能有多个与上一次迭代的单词相差一个编辑距离的单词
            #不要直接用len
            for i in range(current_size):
                word = queue.popleft()

                word_list = list(word)
                for j in range(len(word_list)):
                    origin_char = word_list[j]
                    #扩散寻找
                    for k in range(26):
                        word_list[j] = chr(ord('a') + k)
                        next_word = ''.join(word_list)
                        if next_word in word_set:
                            #判断是否到达终点
                            if next_word == endWord:
                                return step + 1
                            if next_word not in visited:
                                queue.append(next_word)
                                word_set.remove(next_word)
                    #跟回溯一样返回原状态,因为还要与单词下一位判断
                    word_list[j] = origin_char
            step += 1
        return 0

例题三:(拓扑排序)
leetcode210

现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
示例 1:
输入: 2, [[1,0]]
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
示例 2:
输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

想要看懂这道题,应该先了解一下拓扑排序和图的相关知识下面是简单讲解链接
简单讲解

from collections import deque
class Solution:
    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
    	#入度表
        indegrees = [0 for _ in range(numCourses)]
        #指向关系,也叫邻接表
        adjacency = [[] for _ in range(numCourses)]
        #入度为零的队列
        queue = deque()
        #结果队列
        res = []
        # Get the indegree and adjacency of every course.
        #遍历关系,建立入度表和邻接表
        for cur, pre in prerequisites:
            indegrees[cur] += 1
            adjacency[pre].append(cur)
        # Get all the courses with the indegree of 0.
        #获得所有起始入度为0的数
        for i in range(len(indegrees)):
            if not indegrees[i]: 
                queue.append(i)
        # BFS TopSort.
        #求解关系,查看所给是否为一有向无环图
        while queue:
            pre = queue.popleft()
            res.append(pre)
            #队列出一个元素则总数减一,
            #若numCourses为0则说明最后所有元素入度为0
            numCourses -= 1
            #入度表相关元素减一
            for cur in adjacency[pre]:
                indegrees[cur] -= 1
                #若入度表中有元素值为0,则进入queue
                if not indegrees[cur]: 
                    queue.append(cur)
        return res if not numCourses else []

例题四:岛屿的周长
leetcode 463

给定一个包含 0 和 1 的二维网格地图,其中 1 表示陆地 0 表示水域。
网格中的格子水平和垂直方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
示例 :
输入:
[[0,1,0,0],
[1,1,1,0],
[0,1,0,0],
[1,1,0,0]]
输出: 16

class Solution:
    def islandPerimeter(self, grid: List[List[int]]) -> int:
        row = len(grid)
        col = len(grid[0])
        res = 0
        for i in range(row):
            for j in range(col):
                if grid[i][j] == 1:
                    for x,y in [(-1,0),(1,0),(0,1),(0,-1)]:
                        new_row,new_col = i+x,j+y
                        #因为每次都只在岛屿的位置移动一步,所以不满足条件边数加1即可
                        if not (0<=new_row<row and 0<=new_col<col) or grid[new_row][new_col]==0:
                            res +=1
        return res
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值