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)拓扑排序 记住下面的代码
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
69. 二叉树的层次遍历
给出一棵二叉树,返回其节点值的层次遍历(逐层从左往右访问)
Example
样例 1:
输入:{1,2,3}
输出:[[1],[2,3]]
解释:
1
/ \
2 3
它将被序列化为{1,2,3}
层次遍历
样例 2:
输入:{1,#,2,3}
输出:[[1],[2],[3]]
解释:
1
\
2
/
3
它将被序列化为{1,#,2,3}
层次遍历
Challenge
挑战1:只使用一个队列去实现它
挑战2:用BFS算法来做
Notice
- 首个数据为根节点,后面接着是其左儿子和右儿子节点值,"#"表示不存在该子节点。
- 节点数量不超过20。
"""
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
71. 二叉树的锯齿形层次遍历
给出一棵二叉树,返回其节点值的锯齿形层次遍历(先从左往右,下一层再从右往左,层与层之间交替进行)
样例
样例 1:
输入:{1,2,3}
输出:[[1],[3,2]]
解释:
1
/ \
2 3
它将被序列化为 {1,2,3}
样例 2:
输入:{3,9,20,#,#,15,7}
输出:[[3],[20,9],[15,7]]
解释:
3
/ \
9 20
/ \
15 7
它将被序列化为 {3,9,20,#,#,15,7}
"""
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
70. 二叉树的层次遍历 II
给出一棵二叉树,返回其节点值从底向上的层次序遍历(按从叶节点所在层到根节点所在的层遍历,然后逐层从左往右遍历)
样例
例1:
输入:
{1,2,3}
输出:
[[2,3],[1]]
解释:
1
/ \
2 3
它将被序列化为 {1,2,3}
层次遍历
例2:
输入:
{3,9,20,#,#,15,7}
输出:
[[15,7],[9,20],[3]]
解释:
3
/ \
9 20
/ \
15 7
它将被序列化为 {3,9,20,#,#,15,7}
层次遍历
"""
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]
137. 克隆图
克隆一张无向图. 无向图的每个节点包含一个 label
和一个列表 neighbors
. 保证每个节点的 label
互不相同.
你的程序需要返回一个经过深度拷贝的新图. 新图和原图具有同样的结构, 并且对新图的任何改动不会对原图造成任何影响.
样例
样例1
输入:
{1,2,4#2,1,4#4,1,2}
输出:
{1,2,4#2,1,4#4,1,2}
解释:
1------2
\ |
\ |
\ |
\ |
4
说明
关于无向图的表示: http://www.lintcode.com/help/graph/
注意事项
你需要返回与给定节点具有相同 label 的那个节点.
"""
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])
120. 单词接龙
给出两个单词(start和end)和一个字典,找出从start到end的最短转换序列,输出最短序列的长度。
变换规则如下:
- 每次只能改变一个字母。
- 变换过程中的中间单词必须在字典中出现。(起始单词和结束单词不需要出现在字典中)
样例
样例 1:
输入:start = "a",end = "c",dict =["a","b","c"]
输出:2
解释:
"a"->"c"
样例 2:
输入:start ="hit",end = "cog",dict =["hot","dot","dog","lot","log"]
输出:5
解释:
"hit"->"hot"->"dot"->"dog"->"cog"
注意事项
- 如果不存在这样的转换序列,返回 0。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
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
7. 二叉树的序列化和反序列化
设计一个算法,并编写代码来序列化和反序列化二叉树。将树写入一个文件被称为“序列化”,读取文件后重建同样的二叉树被称为“反序列化”。
如何反序列化或序列化二叉树是没有限制的,你只需要确保可以将二叉树序列化为一个字符串,并且可以将字符串反序列化为原来的树结构。
样例
样例 1:
输入:{3,9,20,#,#,15,7}
输出:{3,9,20,#,#,15,7}
解释:
二叉树 {3,9,20,#,#,15,7},表示如下的树结构:
3
/ \
9 20
/ \
15 7
它将被序列化为 {3,9,20,#,#,15,7}
样例 2:
输入:{1,2,3}
输出:{1,2,3}
解释:
二叉树 {1,2,3},表示如下的树结构:
1
/ \
2 3
它将被序列化为 {1,2,3}
我们的数据是进行 BFS 遍历得到的。当你测试结果 Wrong Answer 时,你可以作为输入调试你的代码。
你可以采用其他的方法进行序列化和反序列化。
注意事项
对二进制树进行反序列化或序列化的方式没有限制,LintCode 将您的 serialize
输出作为 deserialize
的输入,它不会检查序列化的结果。
"""
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
433. 岛屿的个数
给一个 01 矩阵,求不同的岛屿的个数。
0 代表海,1 代表岛,如果两个 1 相邻,那么这两个 1 属于同一个岛。我们只考虑上下左右为相邻。
样例
样例 1:
输入:
[
[1,1,0,0,0],
[0,1,0,0,1],
[0,0,0,1,1],
[0,0,0,0,0],
[0,0,0,0,1]
]
输出:
3
样例 2:
输入:
[
[1,1]
]
输出:
1
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]
611. 骑士的最短路线
给定骑士在棋盘上的 初始
位置(一个2进制矩阵 0
表示空 1
表示有障碍物),找到到达 终点
的最短路线,返回路线的长度。如果骑士不能到达则返回 -1
。
样例
例1:
输入:
[[0,0,0],
[0,0,0],
[0,0,0]]
source = [2, 0] destination = [2, 2]
输出: 2
解释:
[2,0]->[0,1]->[2,2]
例2:
输入:
[[0,1,0],
[0,0,1],
[0,0,0]]
source = [2, 0] destination = [2, 2]
输出:-1
说明
如果骑士的位置为 (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]
djkstra最短路问题,其实就是bfs不分层的模板,无非就是将deque修改为heapq而已,heapq中记录了路径距离优先级(下面程序如果不要path则去掉即可):
import heapq def shortest_path(graph, start, end):
queue,seen = [(0, start, [])], {start}
while queue:
cost, v, path = heapq.heappop(queue)
path = path + [v]
if v == end:
return cost, path
for (neighbor_node, c) in graph[v].items():
if neighbor_node not in seen:
seen.add(neighbor_node)
heapq.heappush(queue, (cost + c, neighbor_node, path))
return -1, [] graph = {
'a': {'w': 14, 'x': 7, 'y': 9},
'b': {'w': 9, 'z': 6},
'w': {'a': 14, 'b': 9, 'y': 2},
'x': {'a': 7, 'y': 10, 'z': 10},
'y': {'a': 9, 'w': 2, 'x': 10, 'z': 11},
'z': {'b': 6, 'x': 15, 'y': 11},
}
cost, path = shortest_path(graph, start='a', end='z')
print(cost, path)
拓扑排序定义
在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting)。
- 每个顶点出现且只出现一次;
- 若A在序列中排在B的前面,则在图中不存在从B到A的路径。
也可以定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面。
(来自 Wiki)
实际运用
拓扑排序 Topological Sorting 是一个经典的图论问题。他实际的运用中,拓扑排序可以做如下的一些事情:
- 检测编译时的循环依赖
- 制定有依赖关系的任务的执行顺序
拓扑排序不是一种排序算法
虽然名字里有 Sorting,但是相比起我们熟知的 Bubble Sort, Quick Sort 等算法,Topological Sorting 并不是一种严格意义上的 Sorting Algorithm。
确切的说,一张图的拓扑序列可以有很多个,也可能没有
。拓扑排序只需要找到其中一个
序列,无需找到所有
序列。
入度与出度
在介绍算法之前,我们先介绍图论中的一个基本概念,入度
和出度
,英文为 in-degree & out-degree。
在有向图中,如果存在一条有向边 A-->B,那么我们认为这条边为 A 增加了一个出度,为 B 增加了一个入度。
算法流程
拓扑排序的算法是典型的宽度优先搜索算法,其大致流程如下:
- 统计所有点的入度,并初始化拓扑序列为空。
- 将所有入度为 0 的点,也就是那些没有任何
依赖
的点,放到宽度优先搜索的队列中 - 将队列中的点一个一个的释放出来,放到拓扑序列中,每次释放出某个点 A 的时候,就访问 A 的相邻点(所有A指向的点),并把这些点的入度减去 1。
- 如果发现某个点的入度被减去 1 之后变成了 0,则放入队列中。
- 直到队列为空时,算法结束,
深度优先搜索的拓扑排序
深度优先搜索也可以做拓扑排序,不过因为不容易理解,也并不推荐作为拓扑排序的主流算法。
127. 拓扑排序
给定一个有向图,图节点的拓扑排序定义如下:
- 对于图中的每一条有向边
A -> B
, 在拓扑排序中A一定在B之前. - 拓扑排序中的第一个节点可以是图中的任何一个没有其他节点指向它的节点.
针对给定的有向图找到任意一种拓扑排序的顺序.
Example
Challenge
能否分别用BFS和DFS完成?
Clarification
Notice
你可以假设图中至少存在一种拓扑排序
"""
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. 课程表
现在你总共有 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