BFS专题 | 广度优先搜索

Posted Li的白日呓语

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BFS专题 | 广度优先搜索相关的知识,希望对你有一定的参考价值。

BFS专题

进行BFS(广度优先搜索)时,可以想象存在一个图,进行遍历的顺序是将某一节点加入队列之后,下次访问此节点时将此节点的邻居加入队列。BFS适用于树的层级遍历、寻找图(矩阵)中末两节点之间的距离(或者保存路径)。

树上的BFS

leetcode102,树的层级遍历:

For example:
Given binary tree [3,9,20,null,null,15,7],

3
/ \
9 20
/ \
15 7
return its level order traversal as:

[
[3],
[9,20],
[15,7]
]

偷懒一下,使用python的队列,最基础的BFS。

"""
Definition of TreeNode:
"""
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None

from queue import Queue

class Solution:
"""
@param root: A Tree
@return: Level order a list of lists of integer
"""
def levelOrder(self, root):
if root is None:
return []

q = Queue()
q.put(root)

result = []

while not q.empty(): # 注意结束条件
size = q.qsize()
level = [] # level初始化
for i in range(size):
node = q.get()
level.append(node.val)
if node.left:
q.put(node.left)
if node.right:
q.put(node.right)
print(level)
result.append(level)
return result

图上的BFS

无向图通过节点、邻接节点表示,同一节点可能多次进入队列

leetcode261 GraphValidTree

图是否是树? 图的遍历,判断图中是否有环,没有环则是树。

from queue import Queue
class Solution:
"""
@param n: An integer
@param edges: a list of undirected edges
@return: true if it's a valid tree, or false
"""
def validTree(self, n, edges):
# 图是树,无环就可以
if len(edges) != n - 1:
return False
# 定义一个字典,每个节点保存它的邻居
dict_graph = self.initial_graph(n, edges)
# 定义一个集合hash,遍历每个节点,如果邻居neighbor在不在集合中,则在集合中添加,否则跳过
hash_set = set()

# 初始化节点,从0开始
q = Queue()
q.put(0)
hash_set.add(0) # 0已经访问到

# bfs搜索
while not q.empty():
node = q.get()
for neighbor in dict_graph[node]:
if neighbor in hash_set:
continue
hash_set.add(neighbor)
q.put(neighbor)

return True if len(hash_set) == n else False

def initial_graph(self, n, edges):
dict_graph = {}

for i in range(n):
dict_graph[i] = []

num_e = len(edges)
for i in range(num_e):
u = edges[i][0]
v = edges[i][1]
dict_graph[u].append(v)
dict_graph[v].append(u)

return dict_graph

leetcode133 CloneGraph

图的深复制。 先复制节点,再对每个节点复制其邻接点

"""
Definition for a undirected graph node
class UndirectedGraphNode:
def __init__(self, x):
self.label = x
self.neighbors = []
"""

from queue import Queue
class Solution:
"""
@param: node: A undirected graph node
@return: A undirected graph node
"""
def cloneGraph(self, node):
if node is None:
return None

# print(node.label)
# for neighbor in node.neighbors:
# print(neighbor.label)
# 使用bfs得到所有的节点
nodes = self.getNodes(node)

map_node = {}
# 对于每个节点进行复制
for node_iter in nodes:
map_node[node_iter] = UndirectedGraphNode(node_iter.label)

# 对于每个节点的邻接节点进行复制
for node_iter in nodes:
new_node = map_node[node_iter]
for neighbor in node_iter.neighbors:
# 复制neighbor
# 由于自己可以连成环,所以这里不能重新定义节点neighbor
# new_neighbor = UndirectedGraphNode(neighbor.label)
# neighbor由map得到,保证每次访问还是原来的节点,只是把它加在了new_node的相邻中而已
new_neighbor = map_node.get(neighbor)
new_node.neighbors.append(new_neighbor)

# 返回字典中初始node对于复制的节点
return map_node.get(node)

def getNodes(self, node):
q = Queue()
node_set = set()

q.put(node)
node_set.add(node)
while not q.empty():
node_iter = q.get()
for neighbor in node_iter.neighbors:
if neighbor not in node_set:
q.put(neighbor)
node_set.add(neighbor)

return node_set

TopologicalSorting

拓扑排序,面试高频,必考的经典。
拓扑排序定义:按照某种规则将这个图的顶点取出来。
规则:

  • A在B前面,则不存在B在A前面的路径。(不能成环!!!!)
  • 保证所有指向它的下个节点在被指节点前面(例如A—>B—>C那么A一定在B前面,B一定在C前面) 拓扑排序可以不唯一。
"""
Definition for a Directed graph node
class DirectedGraphNode:
def __init__(self, x):
self.label = x
self.neighbors = []
"""

from queue import Queue

class Solution:
"""
@param: graph: A list of Directed graph node
@return: Any topological order for the given graph.
"""
def topSort(self, graph):
# 获取每个点的入度
indegrees = self.countIndgree(graph)

# 将入度为0的节点添加到result
order = []
q = Queue()

for node in graph:
if indegrees[node] == 0:
q.put(node)
order.append(node)

# 将入度为0的点添加到result后,所有由此节点出发到达的点的入度减一
while not q.empty():
node_iter = q.get()
for neighbor in node_iter.neighbors:
indegrees[neighbor] -= 1
if indegrees[neighbor] == 0:
order.append(neighbor)
q.put(neighbor)

return order

def countIndgree(self, graph):
# bfs
indegrees = {}

for node in graph:
indegrees[node] = 0

for node in graph:
for neighbor in node.neighbors:
indegrees[neighbor] += 1

return indegrees

leetcode207 CourseSchedule

课程选修(经典),给定一些课程之间的依赖关系,可以理解为有向图之间的指向。问是否可以选课成功,成功的定义是存在一种选课顺序不互相依赖,即构建的图中没有环。

import collections

class Solution:
def canFinish(self, numCourses, prerequisites):

graph = {i:[] for i in range(numCourses)}
in_degree = [0 for i in range(numCourses)]

for i,j in prerequisites:
graph[j].append(i)
in_degree[i] += 1

# 找到入度为0的节点
queue = collections.deque([i for i in range(numCourses) if in_degree[i] == 0])
count = 0

# bfs
while queue:
node = queue.popleft()
count += 1
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)

return count == numCourses

矩阵中的BFS

leetcode598 ZombieInMatrix

僵尸吃人

Description:
Given a 2D grid, each cell is either a wall 2, a zombie 1 or people 0 (the number zero, one, two).
Zombies can turn the nearest people(up/down/left/right) into zombies every day, but can not through wall.
How long will it take to turn all people into zombies?Return -1 if can not turn all people into zombies.

Example:
Input:
[[0,1,2,0,0],
[1,0,0,2,1],
[0,1,0,0,0]]

Output: 2

记得去年有道产品经理变程序员的题,和这题一模一样。变种的bfs,直到所有的1变成2,返回bfs深度steps。

import collections
class Solution:
"""
@param grid: a 2D grid
@return: integer, the days need to trun all people into zombies
"""
def daysNeed(self, grid):
n = len(grid)
if n == 0:
return 0
m = len(grid[0])
if m == 0:
return 0

q = collections.deque()

people = 0
for i in range(n):
for j in range(m):
if grid[i][j] == 1:
q.append((i, j))
if grid[i][j] == 0:
people += 1

deltaX = [0, 1, 0, -1]
deltaY = [1, 0, -1, 0]
days = 0
# bfs,但是之中还要层序遍历
while q:
level_size = len(q)

for iter_num in range(level_size):
x, y = q.popleft()

for i in range(4):
new_x = x + deltaX[i]
new_y = y + deltaY[i]

if not self.canTurn(grid, new_x, new_y):
continue

q.append((new_x, new_y))
grid[new_x][new_y] = 1
people -= 1

days += 1
if people == 0:
return days

return -1

def canTurn(self, grid, x, y):
n = len(grid)
m = len(grid[0])
if (x < n) and (x >= 0) and (y < m) and (y >= 0) and grid[x][y] == 0:
return True
else:
return False

Lintcode611 KnightShortestPath

骑士跳步

Given a knight in a chessboard (a binary matrix with 0 as empty and 1 as barrier) with a source position, 
find the shortest path to a destination position,
return the length of the route.
knight,走“日”字.
import collections
"""
Definition for a point
"""
class Point:
def __init__(self, x = 0, y = 0):
self.x = x
self.y = y

class Solution:
"""
@param grid: a chessboard included 0 (false) and 1 (true)
@param source, destination: a point
@return the shortest path
"""
def shortestPath(self, grid, source, destination):
shortest_path = -1
# 判断边界
if grid is None:
return shortest_path
n = len(grid)
if n == 0:
return shortest_path
m = len(gird[0])
if m == 0:
return shortest_path

# 将节点加入队列
q = collections.deque()
q.append(source)

deltaX = [1,1,-1,-1,2,2,-2,-2]
deltaY = [2,-2,2,-2,1,-1,1,-1]
# 节点出队,每次出队搜索8个方向
shortest_path = 0
# 求最短路径长度,层次遍历
while q:
level_size = len(q)
shortest_path += 1

for iter_size in range(level_size):
point = q.popleft()

for i in range(8):
new_x = x + deltaX[i]
new_y = y + deltaY[i]

new_point = Point(new_x, new_y)
if notCrossBoundary(gird, new_x, new_y):
if new_point == destination:
return shortest_path
if grid[new_x][new_y] == 0:
q.append(new_point)
# 访问过标记为1
gird[new_x][new_y] = 1
return -1

# 写一个函数判断是否越界
def notCrossBoundary(self, grid, x, y):
n = len(grid)
m = len(grid[0])

if (x < n) and (x >= 0) and (y < m) and (y >= 0):
return True
else:
return False

Lintcode574 BulidPostOfficeII

建警局

Given a 2D grid, each cell is either a wall 2, 
an house 1 or empty 0 (the number zero, one, two),
find a place to build a post office so that
the sum of the distance from the post office to all the houses is smallest.

Return the smallest sum of distance.
Return -1 if it is not possible.

找到一个0点建立警局,到所有1的距离和最小。

class Solution:
"""
@param grid: a 2D grid
@return: An integer
"""
def shortestDistance(self, grid):
# 将所有房子作为起始点,对所有空地进行搜索,记录下他们到空地的距离,并相加,返回距离最短的点
shortest_path = -1
# 判断边界
if grid is None:
return shortest_path
n = len(grid)
if n == 0:
return shortest_path
m = len(grid[0])
if m == 0:
return shortest_path

houses = []
empty_area = {}
for i in range(n):
for j in range(m):
if grid[i][j] == 1:
houses.append((i,j))
if grid[i][j] == 0:
empty_area[(i,j)] = 0
num_houses = len(houses)

deltaX = [1,0,-1,0]
deltaY = [0,1,0,-1]

count_visit = [[0 for j in range(m)] for i in range(n)]
for house in houses:
q = collections.deque()
q.append(house)

# 标记是否访问过
visited = [[False for j in range(m)] for i in range(n)]
step = 0

while q:
level_size = len(q)
step += 1
for iter_num in range(level_size):
house = q.popleft()
x, y = house

for i in range(4):
new_x = x + deltaX[i]
new_y = y + deltaY[i]

if self.isEmptyArea(grid, new_x, new_y) and not visited[new_x][new_y]:
count_visit[new_x][new_y] += 1
empty_area[(new_x, new_y)] += step
q.append((new_x, new_y))
visited[new_x][new_y] = True

postOffice = None
shortest_path = float("inf")
for key, value in empty_area.items():
x, y = key
if count_visit[x][y] == num_houses:
if value < shortest_path:
shortest_path = value
postOffice = (x, y)

return shortest_path if shortest_path != float("inf") else -1


def isEmptyArea(self, grid, x, y):
n = len(grid)
m = len(grid[0])
if (x >= 0) and (x < n) and (y >= 0) and (y < m) and grid[x][y] == 0:
return True
else:
return False


以上是关于BFS专题 | 广度优先搜索的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode之广度优先搜索(BFS)专题-133. 克隆图(Clone Graph)

Leetcode之广度优先搜索(BFS)专题-994. 腐烂的橘子(Rotting Oranges)

Leetcode之广度优先搜索(BFS)专题-1162. 地图分析(As Far from Land as Possible)

AcWing刷题蓝桥杯专题突破-广度优先搜索-bfs(11)

洛谷刷题蓝桥杯专题突破-广度优先搜索-bfs(16)

广度优先搜索(BFS)