📜  bfs graph leetcode - 任意(1)

📅  最后修改于: 2023-12-03 15:13:39.158000             🧑  作者: Mango

BFS与图算法

BFS(广度优先搜索)是一种常见的图算法,用于在图中搜索特定的节点或遍历整个图。其思路是从起点开始将所有邻居节点加入到队列中,并依次遍历队首元素的邻居节点,直到找到目标节点或遍历完整个图。

常见应用

广度优先搜索常见的应用包括:

  • 求解迷宫问题
  • 寻找最短路径
  • 检测环路
  • 拓扑排序等

在 Leetcode 上,很多题目涉及到了 BFS 算法,如:

    1. Word Ladder
    1. 01 Matrix
    1. Course Schedule
    1. Surrounded Regions
图的表达

在使用 BFS 算法之前,需要首先了解图的表达方式。主要有两种方式:邻接矩阵和邻接表。

邻接矩阵

邻接矩阵是指用二维矩阵来表示图的一种方式。其中,每个数组元素表示一个节点之间的边,值为1表示存在边,值为0表示没有边。

举个例子,对于如下的无向图,其邻接矩阵如下所示:

    1  2  3  4  5
  --------------
1 | 0  1  1  0  0
2 | 1  0  1  1  0
3 | 1  1  0  0  1
4 | 0  1  0  0  1
5 | 0  0  1  1  0
邻接表

邻接表是指用链表来表示图的一种方式。其中,每个节点连一条链表来表示它的邻居节点。

同样以上图为例,其邻接表如下所示:

1 -> 2 -> 3
2 -> 1 -> 3 -> 4
3 -> 1 -> 2 -> 5
4 -> 2 -> 5
5 -> 3 -> 4
Leetcode 刷题
127. Word Ladder

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换规则如下:

  • 每次转换只能改变一个字母。
  • 转换过程中的单词必须都存在于字典中。

这是一道经典的 BFS 题目。具体思路如下:

  1. 将 beginWord 加入到队列中,并将其从字典中删除。
  2. 当队列不为空时,遍历队列中的所有单词,并遍历其所有可能的下一个单词(只改变一个字母)。如果下一个单词等于 endWord,则返回当前的步数(到 beginWord 的步数加 1)。
  3. 将队列中所有的单词在字典中删除,并将其下一个单词加入队列中。同时,记录下当前的步数。
  4. 如果队列中没有遇到 endWord,则返回 0。

代码片段如下:

from collections import deque

def ladderLength(beginWord: str, endWord: str, wordList: List[str]) -> int:
    wordList = set(wordList)
    if endWord not in wordList:
        return 0
    
    queue = deque([(beginWord, 1)])
    while queue:
        word, step = queue.popleft()
        if word == endWord:
            return step
        
        for i in range(len(word)):
            for c in "abcdefghijklmnopqrstuvwxyz":
                next_word = word[:i] + c + word[i+1:]
                if next_word in wordList:
                    wordList.remove(next_word)
                    queue.append((next_word, step+1))
                    
    return 0
542. 01 Matrix

给定一个由 0 和 1 组成的矩阵,求解每个元素到最近的 0 的距离。

这是一道比较典型的 BFS 题目,需要用到多次 BFS 遍历。具体思路如下:

  1. 首先遍历全局,将所有为0的节点加入到队列中,并将它们的值置0。
  2. 从队列中取出一个节点,遍历其周围的四个节点。如果该节点的新值小于原值,则将其加入到队列中,并更新其值。
  3. 重复以上两个步骤,直到队列为空。

代码片段如下:

from collections import deque

def updateMatrix(matrix):
    queue = deque()
    row, col = len(matrix), len(matrix[0])
    # 将所有为0的节点加入到队列中,并将其的值置0
    for i in range(row):
        for j in range(col):
            if matrix[i][j] == 0:
                queue.append((i, j))
            else:
                matrix[i][j] = float('inf')
                
    # BFS遍历
    directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
    while queue:
        x, y = queue.popleft()
        for dx, dy in directions:
            new_x, new_y = x + dx, y + dy
            if 0 <= new_x < row and 0 <= new_y < col:
                # 如果该节点的新值小于原值,则将其加入到队列中,并更新其值
                if matrix[new_x][new_y] > matrix[x][y] + 1:
                    matrix[new_x][new_y] = matrix[x][y] + 1
                    queue.append((new_x, new_y))
                    
    return matrix
207. Course Schedule

给定一个包含 n 个节点的有向图,判断这个图是否可以完成所有课程的学习。其中,图的节点表示课程,每个结点包含一个课程编号。有些课程之间存在先决条件,例如:想要学习课程0,需要先完成课程1,才能学习课程0。

这是一道比较典型的拓扑排序题目,可以使用 BFS 进行求解。具体思路如下:

  1. 首先将每个节点的入度保存在数组 in_degrees 中,并将所有入度为0的节点加入到队列中。
  2. 当队列不为空时,取出队首节点,并将其出边的邻居节点的入度减1。如果减完之后,某节点入度变为0,则将其加入到队列中。
  3. 重复以上两个步骤,直到队列为空。如果遍历完整个图后,发现还有节点的入度不为0,则说明有环路,返回 False;否则返回 True。

代码片段如下:

from collections import deque
from typing import List

def canFinish(numCourses: int, prerequisites: List[List[int]]) -> bool:
    # 计算每个节点的入度,并将所有入度为0的节点加入到队列中
    in_degrees = [0] * numCourses
    graph = [[] for _ in range(numCourses)]
    for course, pre in prerequisites:
        in_degrees[course] += 1
        graph[pre].append(course)
    
    queue = deque([i for i in range(numCourses) if in_degrees[i] == 0])
    while queue:
        node = queue.popleft()
        for nei in graph[node]:
            in_degrees[nei] -= 1
            if in_degrees[nei] == 0:
                queue.append(nei)
                
    return sum(in_degrees) == 0
130. Surrounded Regions

给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。

将所有的 'O' 转换为 'X',但是不可把 'X' 转换为 'O'。

感谢 @jianchao-li 添加此题并创建所有测试用例。

这是一道比较典型的 BFS 题目。具体思路如下:

  1. 从四周的边界开始进行 BFS 遍历,将所有遍历到的 'O' 标记为 'B'。
  2. 遍历整个矩阵,将所有未被标记为 'B' 的 'O' 替换为 'X',将所有标记为 'B' 的 'B' 替换为 'O'。

代码片段如下:

from collections import deque

def solve(board: List[List[str]]) -> None:
    if not board:
        return 
   
    # 从四周开始进行 BFS 遍历,标记所有遍历到的 O
    m, n = len(board), len(board[0])
    queue = deque()
    for i in range(m):
        for j in range(n):
            if board[i][j] == 'O' and (i in {0, m-1} or j in {0, n-1}):
                queue.append((i, j))
                board[i][j] = 'B'
    while queue:
        i, j = queue.popleft()
        for dx, dy in [(1, 0), (0, 1), (-1, 0), (0, -1)]:
            x, y = i + dx, j + dy
            if 0 <= x < m and 0 <= y < n and board[x][y] == 'O':
                board[x][y] = 'B'
                queue.append((x, y))
   
    # 扫描整个矩阵,将所有未被标记为 B 的 O 替换为 X,将所有标记为 B 的 B 替换为 O 
    for i in range(m):
        for j in range(n):
            if board[i][j] == 'B':
                board[i][j] = 'O'
            else:
                board[i][j] = 'X'
参考资料