一、关于树的BFS
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
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
二、关于图的BFS
neighbor:邻接点。
set
/
seen:判断是否访问
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
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
三、拓扑排序
算法大致流程如下:
统计所有点的入度,并初始化拓扑序列为空。
将所有入度为
0
的点,也就是那些没有任何依赖的点,放到宽度优先搜索的队列中
将队列中的点一个一个的释放出来,放到拓扑序列中,每次释放出某个点 A 的时候,就访问 A 的相邻点(所有A指向的点),并把这些点的入度减去
1
。
如果发现某个点的入度被减去
1
之后变成了
0
,则放入队列中。
直到队列为空时,算法结束
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
参考:https://www.cnblogs.com/bonelee/p/11724346.html
上个比较难的题-单词接龙 II
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:
如果不存在这样的转换序列,返回一个空列表。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: []
解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。
链接:https://leetcode-cn.com/problems/word-ladder-ii
先定义判断可以转换的方法:
def judge_same(str1, str2):
n = 0
for ch1, ch2 in zip(str1, str2):
if ch1 != ch2:
n += 1
if n > 1:
return False
return n == 1
开始尝试字典+判断:
def findLadders(beginWord, endWord, wordList):
if beginWord not in wordList:
wordList.insert(0, beginWord)
if endWord not in wordList:
return []
maps = {}
for i in range(len(wordList)):
for j in range(i + 1, len(wordList)):
if judge_same(wordList[i], wordList[j]):
if maps.get(wordList[i]):
maps.get(wordList[i]).append(wordList[j])
else:
maps[wordList[i]] = [wordList[j]]
if maps.get(wordList[j]):
maps.get(wordList[j]).append(wordList[i])
else:
maps[wordList[j]] = [wordList[i]]
print(maps)
return maps
尝试好几次不过。。。过了29/39,,,大约5秒
接着尝试。。。不用字典的,用BFS,每一次添加节点都搜一遍
def findLadders(beginWord, endWord, wordList):
if endWord not in wordList:
return []
dict = set(wordList)
queue = [(beginWord, [beginWord])]
res = {}
depth = 0
distance = {}
min_len = float('inf')
while queue:
depth += 1
new_queue = []
for cur, path in queue:
if cur == endWord:
if len(path) < min_len:
min_len = len(path)
res[min_len] = [path]
else:
res.get(min_len).append(path)
continue
# 此处方法有多种
for next in get_next_words(cur, dict):
if distance.get(next) and distance.get(next) < depth:
continue
distance[next] = depth
if len(path) + 1 > min_len:
continue
if next not in path:
new_queue.append((next, path + [next]))
queue = new_queue
return res.get(min_len)
方法一:遍历所有wordList
def get_next_words(word, wordList):
words = []
for next_word in wordList:
if next_word != word and judge_same(word, next_word):
words.append(next_word)
return words
用时30多秒。。。还是不过
方法二:固定26个字母替换:
def get_next_words(word, wordList):
words = []
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
next_word = word[:i] + c + word[i + 1:]
if next_word != word and next_word in wordList:
words.append(next_word)
return words
更慢了,,,1分钟跑不完,,,要爆炸了。。。
方法三:用set替换list
再审审题,,,我去,,,这个题变态地方在字典,说是字典给的却是list
于是,有了画龙点睛之笔:word_set= set(wordList)
def get_next_words(word, word_set):
words = []
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
next_word = word[:i] + c + word[i + 1:]
if next_word != word and next_word in word_set:
words.append(next_word)
return words
终于过了,,,其实BFS还有好多的记忆搜索的优化,,不再赘述,,,
全代码如下:
class Solution:
def findLadders(self, beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]:
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: List[List[str]]
"""
def get_next_words(word, word_set, ch_set):
words = []
for i in range(len(word)):
for c in ch_set:
next_word = ''.join((word[:i], c, word[i + 1:]))
if next_word != word and next_word in word_set:
words.append(next_word)
return words
if endWord not in wordList:
return []
word_set = set(wordList) # 优化,减少遍历时间
ch_set = set(''.join(wordList))
queue = [(beginWord, [beginWord])]
res = [] # 存储结果
depth = 0 # 深度,用于去除重复判断
distance = {} # 用于存储节点在哪一层遍历了
while queue:
depth += 1
new_queue = []
for cur, path in queue:
if cur == endWord:
res.append(path)
continue
for next_word in get_next_words(cur, word_set, ch_set):
if distance.get(next_word, float('inf')) < depth:
continue
distance[next_word] = depth
new_queue.append((next_word, path + [next_word]))
if res: # 这一层找到了一定是最短的
break
queue = new_queue
return res
最近又刷了一遍,发现不过了,32个用例超时,因为增加了特殊数据集~_~
双向 BFS介绍
什么是双向 BFS?
双向 BFS(Bidirectional Breadth-First Search)是一种图搜索算法,它从起点和终点同时开始搜索,以求得最短的路径。相比于普通的 BFS 算法,双向 BFS 算法可以减少搜索的节点数,从而提高搜索效率。
在双向 BFS 算法中,我们使用两个队列或集合分别记录从起点和终点出发的可达节点,并在每一轮中交替地从两个队列或集合中选择一个节点进行扩展。如果某个节点在两个方向上都被访问到了,那么说明在这两个节点之间存在一条路径。
双向 BFS 的优势
与普通的 BFS 算法相比,双向 BFS 算法具有以下优势:
搜索的节点数更少。由于双向 BFS 算法从两个方向同时开始搜索,因此它的搜索深度只需要普通 BFS 算法的一半,从而减少了搜索的节点数。
搜索的速度更快。由于双向 BFS 算法从起点和终点同时开始搜索,因此它的搜索速度更快。
能够避免无效搜索。由于双向 BFS 算法是从起点和终点同时开始搜索,因此它可以避免搜索那些无效的节点。
双向 BFS 的应用场景
双向 BFS 算法常用于求解最短路径问题,例如:
- 单词接龙问题
- 迷宫问题
- 网络延迟时间问题
- 火车换乘问题
双向 BFS 的代码实现
Python 代码实现如下:
def two_end_bfs(start, end):
queue1, queue2 = [start], [end]
visited1, visited2 = {start: 0}, {end: 0}
while queue1 and queue2:
cur_node1 = queue1.pop(0)
for next_node in get_next_nodes(cur_node1):
if next_node in visited2:
return visited1[cur_node1] + visited2[next_node] + 1
if next_node not in visited1:
visited1[next_node] = visited1[cur_node1] + 1
queue1.append(next_node)
cur_node2 = queue2.pop(0)
for next_node in get_next_nodes(cur_node2):
if next_node in visited1:
return visited1[next_node] + visited2[cur_node2] + 1
if next_node not in visited2:
visited2[next_node] = visited2[cur_node2] + 1
queue2.append(next_node)
return -1
写了如下代码:
def findLadders(beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: List[List[str]]
"""
def get_next_words(word, dict):
words = set()
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
next_word = word[:i] + c + word[i + 1:]
if next_word != word and next_word in dict:
words.add(next_word)
return words
if endWord not in wordList:
return []
word_set = set(wordList) # 优
word_set.add(beginWord)
queue1 = {beginWord}
queue2 = {endWord}
visited1 = defaultdict(list) # 用于存储节点在哪一层遍历了
visited1[beginWord].append([beginWord])
visited2 = defaultdict(list) # 用于存储节点在哪一层遍历了
visited2[endWord].append([endWord])
reversed = False
res = [] # 存储结果
max_len = float('inf')
depth = 0
while queue1 and queue2:
depth += 1
if len(queue1) > len(queue2):
queue1, queue2 = queue2, queue1
visited1, visited2 = visited2, visited1
beginWord, endWord = endWord, beginWord
reversed = not reversed
new_queue = set()
visited = defaultdict(list)
for cur in queue1:
if cur == endWord:
for path in visited1.get(cur):
if len(path) <= max_len and path not in res:
res.append(path)
max_len = len(path)
continue
for next_word in get_next_words(cur, word_set):
if visited2.get(next_word):
for other_path in visited2.get(next_word):
for path in visited1.get(cur):
if reversed:
new_path = other_path + path
else:
new_path = path + other_path
if len(new_path) <= max_len and new_path not in res:
res.append(new_path)
max_len = len(new_path)
if visited1.get(next_word):
continue
for path in visited1.get(cur):
if reversed:
new_path = [next_word] + path
else:
new_path = path + [next_word]
if new_path not in visited[next_word]:
visited[next_word].append(new_path)
new_queue.add(next_word)
if res:
return res
queue1 = new_queue
word_set -= new_queue
visited1.update(visited)
return res
32用例内存超限,因为存储路径太多了
接着试了BFS+DFS回溯法+邻接表,终于通过!!!
思路:
- 需要记录所有遍历过的单词还有前后关系, 如 adjoin_list= { next_word :[pre1, pre2,...,preN]},其中preN为前序节点。
- 再用一个word_level保存每一层的单词 word_level= {1:[word1 ,word2 ...]}
- BFS遍历的时候,判断每一层单词的关联单词是否在wordList ,并且是否已经被记录过了,如果没有记录过 就把word加入adjoin_list和 word_level ,并且把它的后序单词加入new_queue,直到遇到endWord 这样当我们进行BFS遍历的时候,直到遇到endWord(最后这层需要遍历完,因为可能存在多个单词都能到end的情况)。
- 得到了一个保存每一层有哪些word,和其中哪些word存在上下层的连接关系的list,我们再从endWord,用一个dfs来还原bfs的遍历顺序,得到result。
代码如下:
def findLadders(beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: List[List[str]]
"""
def get_next_words(word, word_set):
words = set()
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
next_word = word[:i] + c + word[i + 1:]
if next_word != word and next_word in word_set:
words.add(next_word)
return words
if endWord not in wordList:
return []
word_set = set(wordList) # 查询提高速度
adjoin_list = defaultdict(set) # 邻接矩阵,存前向节点,不是后向节点的原因是判重很方便
word_level = defaultdict(set) # 记录每一层遍历遇到的单词
word_level[0].add(beginWord)
depth = 0
find_flag = False # 是否找到
queue = {beginWord}
while queue:
new_queue = set()
depth += 1
for cur in queue:
for next_word in get_next_words(cur, word_set):
if next_word == endWord: # 如果遇到终点,则标记True,这一层走完就跳出循环
find_flag = True
if next_word not in adjoin_list:
new_queue.add(next_word)
word_level[depth].add(next_word)
adjoin_list[next_word].add(cur)
if find_flag: # 这一层找到了一定是最短的
break
queue = new_queue
if not find_flag: # 没找到
return []
# dfs回溯路径
def dfs(depth, tmp, next_word):
if depth == -1:
return [tmp]
res = []
for cur_word in word_level[depth]:
if cur_word in adjoin_list[next_word]:
res.extend(dfs(depth - 1, [cur_word] + tmp, cur_word))
return res
return dfs(len(word_level) - 2, [endWord], endWord)