BFS模板,记住这5个:
(1)针对树的BFS
1.1 无需分层遍历
from collections import deque
def levelOrderTree(root):
if not root:
return
q = deque([root])
while q:
head = q.popleft()
do something with this head node...
if head.left:
q.append(head.left)
if head.right:
q.append(head.right)
return xxx
1.2 需要分层遍历
def levelOrderTree(root):
if not root:
return
q = [root]
while q:
new_q = []
for node in q: # 和上面代码相比 差异就在这里 和 deque
do something with this layer nodes...
if node.left:
new_q.append(node.left)
if node.right:
new_q.append(node.right)
q = new_q
return xxx
(2)针对图的BFS
2.1 无需分层遍历
from collections import deque
def bfs_graph(root):
if not root:
return
queue = deque([root])
seen = set([root])
while queue:
head = queue.popleft()
do something with this head...
for neighbor in head.neighbors:
if neighbor not in seen: # 和tree的区别无非就是多了一个是否访问过的判断
seen.add(neighbor)
queue.append(neighbor)
return xxx
上述代码中:
neighbor 表示从某个点 head 出发,可以走到的下一层的节点。
set/seen 存储已经访问过的节点(已经丢到 queue 里去过的节点)
queue 存储等待被拓展到下一层的节点
set/seen 与 queue 是一对好基友,无时无刻都一起出现,往 queue 里新增一个节点,就要同时丢到 set 里。
需要分层遍历的宽度搜先搜索
2.2 需分层遍历【较为少见,可以先不看】
def bfs_graph(root):
if not root:
return []
q = [root]
seen = set([root])
while q:
new_q = []
for node in q:
do something with this layer nodes...
for neighbor in node.neighbors:
if neighbor not in seen: # 和tree的区别无非就是多了一个是否访问过的判断
seen.add(neighbor)
new_q.append(neighbor)
q = new_q
return xxx
(3)拓扑排序 ==》仅仅针对有向无环图,!!!如果是有环的图,则生成的排序仅仅能够完成无环的点,有环路的点因为入度始终无法为0,所以不会加入到排序结果!==》体会精髓!!!
更新代码模板:20230208 果然还是比我的简洁
q = [u for u, d in inDeg.items() if d == 0]
for u in q:
for v in g[u]:
inDeg[v] -= 1
if inDeg[v] == 0:
q.append(v)
return q
之前的模板
记住下面的代码
class Solution:
"""
@param graph: A list of Directed graph node
@return: Any topological order for the given graph.
"""
def topSort(self, graph):
node_to_indegree = self.get_indegree(graph)
# bfs
order = []
start_nodes = [n for n in graph if node_to_indegree[n] == 0]
queue = collections.deque(start_nodes)
while queue:
node = queue.popleft()
order.append(node)
for neighbor in node.neighbors:
node_to_indegree[neighbor] -= 1
if node_to_indegree[neighbor] == 0:
queue.append(neighbor)
return order
def get_indegree(self, graph):
node_to_indegree = {x: 0 for x in graph}
for node in graph:
for neighbor in node.neighbors:
node_to_indegree[neighbor] += 1
return node_to_indegree
算法流程
拓扑排序的算法是典型的宽度优先搜索算法,其大致流程如下:
统计所有点的入度,并初始化拓扑序列为空。
将所有入度为 0 的点,也就是那些没有任何依赖的点,放到宽度优先搜索的队列中
将队列中的点一个一个的释放出来,放到拓扑序列中,每次释放出某个点 A 的时候,就访问 A 的相邻点(所有A指向的点),并把这些点的入度减去 1。
如果发现某个点的入度被减去 1 之后变成了 0,则放入队列中。
直到队列为空时,算法结束
一些实际案例:
https://www.cnblogs.com/bonelee/p/11724346.html
外加一个djstra最短路算法:
下面示例求解最短的飞行航线
from heapq import heappush, heappop
def dijkstra(graph, from_node, to_node):
q = [(0, from_node, [])]
seen = {from_node}
while q:
cost, node, path = heappop(q)
path = path + [node]
if node == to_node:
return cost, path
for adj_node, c in graph.get(node, {}).items():
if adj_node not in seen:
seen.add(adj_node)
heappush(q, (cost + c, adj_node, path))
return -1, []
air_lines = {"1": {"2": 2000, "3": 2000, "4": 4000, "5": 4500}, "2": {"5": 1000}, "3": {"4": 1000}, "4": {"5": 500}}
print(dijkstra(air_lines, "1", "4"))
print(dijkstra(air_lines, "1", "5"))
print(dijkstra(air_lines, "4", "5"))
print(dijkstra(air_lines, "5", "4"))
print(dijkstra(air_lines, "1", "1"))
print(dijkstra(air_lines, "10", "10"))
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
class Solution:
"""
@param root: A Tree
@return: Level order a list of lists of integer
"""
def levelOrder(self, root):
# write your code here
if not root:
return []
q = [root]
result = []
while q:
children = []
q2 = []
for node in q:
children.append(node.val)
if node.left:
q2.append(node.left)
if node.right:
q2.append(node.right)
result.append(children)
q = q2
return result
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
class Solution:
"""
@param root: A Tree
@return: A list of lists of integer include the zigzag level order traversal of its nodes' values.
"""
def zigzagLevelOrder(self, root):
# write your code here
if not root:
return []
q = [root]
result = []
layer = 0
while q:
q2 = []
values = []
for node in q:
values.append(node.val)
if node.left:
q2.append(node.left)
if node.right:
q2.append(node.right)
if layer % 2 == 0:
result.append(values)
else:
result.append(values[::-1])
q = q2
layer += 1
return result
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
class Solution:
"""
@param root: A tree
@return: buttom-up level order a list of lists of integer
"""
def levelOrderBottom(self, root):
# write your code here
# write your code here
if not root:
return []
result = []
q = [root]
while q:
new_q = []
values = []
for node in q:
values.append(node.val)
if node.left:
new_q.append(node.left)
if node.right:
new_q.append(node.right)
result.append(values)
q = new_q
return result[::-1]
"""
Definition for a undirected graph node
class UndirectedGraphNode:
def __init__(self, x):
self.label = x
self.neighbors = []
"""
from collections import deque
class Solution:
"""
@param: node: A undirected graph node
@return: A undirected graph node
"""
def cloneGraph(self, root):
# write your code here
if not root:
return None
"""
使用宽度优先搜索 BFS 的版本。
第一步:找到所有独一的点
第二步:复制所有的点,将映射关系存起来
第三步:找到所有的边,复制每一条边
"""
nodes = self.get_unique_nodes(root)
mapping_nodes = self.copy_nodes(nodes)
self.copy_edges(nodes, mapping_nodes)
return mapping_nodes[root]
def get_unique_nodes(self, root):
q, seen = deque([root]), set([root])
while q:
node = q.popleft()
for neighbor_node in node.neighbors:
if neighbor_node not in seen:
seen.add(neighbor_node)
q.append(neighbor_node)
return seen
def copy_nodes(self, nodes):
return {node: UndirectedGraphNode(node.label) for node in nodes}
def copy_edges(self, nodes, mapping_nodes):
for node in nodes:
copied_node = mapping_nodes[node]
for neighbor in node.neighbors:
copied_node.neighbors.append(mapping_nodes[neighbor])
class Solution:
"""
@param: start: a string
@param: end: a string
@param: dict: a set of string
@return: An integer
"""
def ladderLength(self, start, end, dict):
dict.add(end)
q = [start]
visited = set([start])
distance = 0
while q:
distance += 1
new_q = []
for word in q:
if word == end:
return distance
for next_word in self.get_next_words(word):
if next_word not in dict or next_word in visited:
continue
new_q.append(next_word)
visited.add(next_word)
q = new_q
return 0
# O(26 * L^2)
# L is the length of word
def get_next_words(self, word):
words = []
for i in range(len(word)):
left, right = word[:i], word[i + 1:]
for char in 'abcdefghijklmnopqrstuvwxyz':
if word[i] == char:
continue
words.append(left + char + right)
return words
我用dijstra算法求解如下:优势是可以找到最短的路径!!!
from typing import (
Set,
)
import heapq
class Solution:
"""
@param start: a string
@param end: a string
@param dict: a set of string
@return: An integer
"""
def ladder_length(self, start: str, end: str, wdict: Set[str]) -> int:
# write your code here
q = [(1, start, [start])]
seen = {start}
wdict.add(end)
while q:
cost, virtex, path = heapq.heappop(q)
if virtex == end:
print(path)
return cost
neibors = self.get_neibors(virtex, wdict)
for neibor in neibors:
if neibor not in seen:
seen.add(neibor)
heapq.heappush(q, (cost + 1, neibor, path+[neibor]))
def get_neibors(self, word, words_set):
words = []
for i in range(len(word)):
left, right = word[:i], word[i + 1:]
for char in 'abcdefghijklmnopqrstuvwxyz':
if word[i] == char:
continue
word2 = left + char + right
if word2 in words_set:
words.append(word2)
return words
class Solution:
def removeInvalidParentheses(self, s: str) -> List[str]:
def isValid(s:str)->bool:
cnt = 0
for c in s:
if c == "(": cnt += 1
elif c == ")": cnt -= 1
if cnt < 0: return False
return cnt == 0
# BFS
level = {s} # 用set避免重复
while True:
valid = list(filter(isValid, level)) # 所有合法字符都筛选出来
if valid: return valid # 如果当前valid是非空的,说明已经有合法的产生了
# 下一层level
next_level = set()
for item in level:
for i in range(len(item)):
if item[i] in "()": # 如果item[i]这个char是个括号就删了,如果不是括号就留着
next_level.add(item[:i]+item[i+1:])
level = next_level
"""
Definition of TreeNode:
class TreeNode:
def __init__(self, val):
self.val = val
self.left, self.right = None, None
"""
from collections import deque
class Solution:
"""
@param root: An object of TreeNode, denote the root of the binary tree.
This method will be invoked first, you should design your own algorithm
to serialize a binary tree which denote by a root node to a string which
can be easily deserialized by your own "deserialize" method later.
"""
def serialize(self, root):
# write your code here
if not root:
return []
q = deque([root])
result = []
while q:
head = q.popleft()
if head:
result.append(str(head.val))
q.append(head.left)
q.append(head.right)
else:
result.append('#')
while result and result[-1] == '#':
result.pop()
return result
"""
@param data: A string serialized by your serialize method.
This method will be invoked second, the argument data is what exactly
you serialized at method "serialize", that means the data is not given by
system, it's given by your own serialize method. So the format of data is
designed by yourself, and deserialize it here as you serialize it in
"serialize" method.
"""
def deserialize(self, data):
# write your code here
if not data:
return None
root = TreeNode(data[0])
q = deque([root])
i = 1
while q and i < len(data):
head = q.popleft()
if data[i] != '#':
head.left = TreeNode(data[i])
q.append(head.left)
i += 1
if data[i] != '#':
head.right = TreeNode(data[i])
q.append(head.right)
i += 1
return root
from collections import deque
class Solution:
"""
@param grid: a boolean 2D matrix
@return: an integer
"""
def numIslands(self, grid):
if not grid or not grid[0]:
return 0
islands = 0
visited = set()
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] and (i, j) not in visited:
self.bfs(grid, i, j, visited)
islands += 1
return islands
def bfs(self, grid, x, y, visited):
queue = deque([(x, y)])
visited.add((x, y))
while queue:
x, y = queue.popleft()
for delta_x, delta_y in [(1, 0), (0, -1), (-1, 0), (0, 1)]:
next_x = x + delta_x
next_y = y + delta_y
if not self.is_valid(grid, next_x, next_y, visited):
continue
queue.append((next_x, next_y))
visited.add((next_x, next_y))
def is_valid(self, grid, x, y, visited):
n, m = len(grid), len(grid[0])
if not (0 <= x < n and 0 <= y < m):
return False
if (x, y) in visited:
return False
return grid[x][y]
(x + 1, y + 2)
(x + 1, y - 2)
(x - 1, y + 2)
(x - 1, y - 2)
(x + 2, y + 1)
(x + 2, y - 1)
(x - 2, y + 1)
(x - 2, y - 1)
注意事项
起点跟终点必定为空.
骑士不能碰到障碍物.
路径长度指骑士走的步数.
"""
Definition for a point.
class Point:
def __init__(self, a=0, b=0):
self.x = a
self.y = b
"""
DIRECTIONS = [
(-2, -1), (-2, 1), (-1, 2), (1, 2),
(2, 1), (2, -1), (1, -2), (-1, -2),
]
class Solution:
"""
@param grid: a chessboard included 0 (false) and 1 (true)
@param source: a point
@param destination: a point
@return: the shortest path
"""
def shortestPath(self, grid, source, destination):
queue = collections.deque([(source.x, source.y)])
distance = {(source.x, source.y): 0}
while queue:
x, y = queue.popleft()
if (x, y) == (destination.x, destination.y):
return distance[(x, y)]
for dx, dy in DIRECTIONS:
next_x, next_y = x + dx, y + dy
if (next_x, next_y) in distance:
continue
if not self.is_valid(next_x, next_y, grid):
continue
distance[(next_x, next_y)] = distance[(x, y)] + 1
queue.append((next_x, next_y))
return -1
def is_valid(self, x, y, grid):
n, m = len(grid), len(grid[0])
if x < 0 or x >= n or y < 0 or y >= m:
return False
return not grid[x][y]
使用dijstra解决:
from typing import (
List,
)
from lintcode import (
Point,
)
import heapq
"""
Definition for a point:
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
"""
DIR = [(1, 2), (1, -2), (-1, 2), (-1, -2), (2, 1), (2, -1), (-2, 1), (-2, -1)]
class Solution:
"""
@param grid: a chessboard included 0 (false) and 1 (true)
@param source: a point
@param destination: a point
@return: the shortest path
"""
def shortest_path(self, grid: List[List[bool]], source: Point, destination: Point) -> int:
# write your code here
r, c = len(grid), len(grid[0])
root = (source.x, source.y)
q = [(0, root, [root])]
seen = {root}
while q:
cost, v, path = heapq.heappop(q)
if v == (destination.x, destination.y):
print(path)
return cost
x, y = v[0], v[1]
for dx, dy in DIR:
x2, y2 = x + dx, y + dy
if 0 <= x2 < r and 0 <= y2 < c:
if grid[x2][y2] == 0 and (x2, y2) not in seen:
heapq.heappush(q, (cost+1, (x2, y2), path+[(x2, y2)]))
seen.add((x2, y2))
return -1
"""
Definition for a Directed graph node
class DirectedGraphNode:
def __init__(self, x):
self.label = x
self.neighbors = []
"""
class Solution:
"""
@param graph: A list of Directed graph node
@return: Any topological order for the given graph.
"""
def topSort(self, graph):
node_to_indegree = self.get_indegree(graph)
# bfs
order = []
start_nodes = [n for n in graph if node_to_indegree[n] == 0]
queue = collections.deque(start_nodes)
while queue:
node = queue.popleft()
order.append(node)
for neighbor in node.neighbors:
node_to_indegree[neighbor] -= 1
if node_to_indegree[neighbor] == 0:
queue.append(neighbor)
return order
def get_indegree(self, graph):
node_to_indegree = {x: 0 for x in graph}
for node in graph:
for neighbor in node.neighbors:
node_to_indegree[neighbor] += 1
return node_to_indegree
615. 课程表
中文
English
现在你总共有 n 门课需要选,记为 0 到 n - 1.
一些课程在修之前需要先修另外的一些课程,比如要学习课程 0 你需要先学习课程 1 ,表示为[0,1]
给定n门课以及他们的先决条件,判断是否可能完成所有课程?
样例
例1:
输入: n = 2, prerequisites = [[1,0]]
输出: true
例2:
输入: n = 2, prerequisites = [[1,0],[0,1]]
输出: false
from collections import deque
class Solution:
# @param {int} numCourses a total of n courses
# @param {int[][]} prerequisites a list of prerequisite pairs
# @return {boolean} true if can finish all courses or false
def canFinish(self, numCourses, prerequisites):
# Write your code here
edges = {i: [] for i in range(numCourses)}
degrees = [0 for i in range(numCourses)]
for i, j in prerequisites:
edges[j].append(i)
degrees[i] += 1
queue, count = deque([]), 0
for i in range(numCourses):
if degrees[i] == 0:
queue.append(i)
while queue:
node = queue.popleft()
count += 1
for x in edges[node]:
degrees[x] -= 1
if degrees[x] == 0:
queue.append(x)
return count == numCourses
上面的写法比较简洁,另外我写的(20220605):
from typing import (
List,
)
class Solution:
"""
@param num_courses: a total of n courses
@param prerequisites: a list of prerequisite pairs
@return: true if can finish all courses or false
"""
def can_finish(self, num_courses: int, prerequisites: List[List[int]]) -> bool:
# write your code here
graph = self.build_graph(prerequisites, num_courses)
degree = self.calc_indegree(num_courses, graph)
q = collections.deque(self.get_head_nodes(degree))
orders = self.top_sort(q, degree, graph)
return len(orders) == num_courses
def top_sort(self, q, degree, graph):
ans = []
while q:
node = q.popleft()
ans.append(node)
for neibor in graph[node]:
degree[neibor] -= 1
if degree[neibor] == 0:
q.append(neibor)
return ans
def get_head_nodes(self, degree):
return [node for node in degree if degree[node] == 0]
def build_graph(self, arr, n):
ans = {i: set() for i in range(n)}
for i, j in arr:
ans[j].add(i)
return ans
def calc_indegree(self, n, graph):
indegree = {x: 0 for x in range(n)}
for node in graph:
for neighbor in graph[node]:
indegree[neighbor] += 1
return indegree
Dijkstra’s Algorithm
单源最短路径Dijkstra算法:只能处理正权边,****可以有环****
Given a weighted graph and a starting (source) vertex in the graph, Dijkstra’s algorithm is used to find the shortest distance from the source node to all the other nodes in the graph.
As a result of the running Dijkstra’s algorithm on a graph, we obtain the shortest path tree (SPT) with the source vertex as root.
In Dijkstra’s algorithm, we maintain two sets or lists. One contains the vertices that are a part of the shortest-path tree (SPT) and the other contains vertices that are being evaluated to be included in SPT. Hence for every iteration, we find a vertex from the second list that has the shortest path.
The pseudocode for the Dijkstra’s shortest path algorithm is given below.
Pseudocode
Given below is the pseudocode for this algorithm. ===>下面这个伪代码就非常能够说明思路!!!如何去寻找最短的路径,使用的是previous一个hash表记录!
procedure dijkstra(G, S)
G-> graph; S->starting vertex
begin
foreach vertex V in G //initialization; initial path set to infinite
path[V] <- infinite
previous[V] <- NULL
If V != S, add V to Priority Queue PQueue
path [S] <- 0
whilePQueue IS NOT EMPTY
U <- Extract MIN from PQueue
foreach unvisited adjacent_node V of U
tempDistance <- path [U] + edge_weight(U, V)
iftempDistance < path [V]
path [V] <- tempDistance
previous[V] <- U
returnpath[], previous[]
end
Let’s now take a sample graph and illustrate the Dijkstra’s shortest path algorithm.
Initially, the SPT (Shortest Path Tree) set is set to infinity.
Let’s start with vertex 0. So to begin with we put the vertex 0 in sptSet.
sptSet = {0, INF, INF, INF, INF, INF}.
Next with vertex 0 in sptSet, we will explore its neighbors. Vertices 1 and 2 are two adjacent nodes of 0 with distance 2 and 1 respectively.
In the above figure, we have also updated each adjacent vertex (1 and 2) with their respective distance from source vertex 0. Now we see that vertex 2 has a minimum distance. So next we add vertex 2 to the sptSet. Also, we explore the neighbors of vertex 2.
Now we look for the vertex with minimum distance and those that are not there in spt. We pick vertex 1 with distance 2.
As we see in the above figure, out of all the adjacent nodes of 2, 0, and 1 are already in sptSet so we ignore them. Out of the adjacent nodes 5 and 3, 5 have the least cost. So we add it to the sptSet and explore its adjacent nodes.
In the above figure, we see that except for nodes 3 and 4, all the other nodes are in sptSet. Out of 3 and 4, node 3 has the least cost. So we put it in sptSet.
As shown above, now we have only one vertex left i.e. 4 and its distance from the root node is 16. Finally, we put it in sptSet to get the final sptSet = {0, 2, 1, 5, 3, 4} that gives us the distance of each vertex from the source node 0.