BFS 的核心思想應該不難理解的,就是把一些問題抽象成圖,從一個點開始,向四周開始擴散。一般來說,我們寫 BFS 算法都是用「隊列」這種數據結構,每次將一個節點周圍的所有節點加入隊列。
BFS 相對 DFS 的最主要的區別是:BFS 找到的路徑一定是最短的,但代價就是空間復雜度比 DFS 大很多
問題的本質就是讓你在一幅「圖」中找到從起點start
到終點target
的最近距離,這個例子聽起來很枯燥,但是 BFS 算法問題其實都是在干這個事兒。
最短路徑問題用BFS實現
分析思路:
首先明確下起點start和終點target是什么,怎么判斷到達了終點?
顯然起點就是root根節點,終點就是最靠近根節點的那個葉子節點,葉子節點就是兩個子節點都是null的節點
if not tmp.left and not tmp.right:
// 到達終點
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def minDepth(self, root: TreeNode) -> int: if not root: return 0 q = [root] count = 1 # root 本身就是一層,count初始化為 1(count是深度) while q: for i in range(len(q)): # 遍歷當前層的所有節點 tmp = q.pop(0) if not tmp.left and not tmp.right: # 判斷是否到達終點,發現葉子節點,返回當前層數(深度) return count # 隊列中不放空節點 if tmp.left: q.append(tmp.left) if tmp.right: q.append(tmp.right) # 進入下一層,深度加1 count += 1 return count
q = [root]打印出來的結果:
[TreeNode{val: 3, left: TreeNode{val: 9, left: None, right: None}, right: TreeNode{val: 20, left: TreeNode{val: 15, left: None, right: None}, right: TreeNode{val: 7, left: None, right: None}}}]
q初始的值
1、為什么 BFS 可以找到最短距離,DFS 不行嗎?
首先,你看 BFS 的邏輯,depth
每增加一次,隊列中的所有節點都向前邁一步,這保證了第一次到達終點的時候,走的步數是最少的。
DFS 不能找最短路徑嗎?其實也是可以的,但是時間復雜度相對高很多。
你想啊,DFS 實際上是靠遞歸的堆棧記錄走過的路徑,你要找到最短路徑,肯定得把二叉樹中所有樹杈都探索完才能對比出最短的路徑有多長對不對?
而 BFS 借助隊列做到一次一步「齊頭並進」,是可以在不遍歷完整棵樹的條件下找到最短距離的。
形象點說,DFS 是線,BFS 是面;DFS 是單打獨斗,BFS 是集體行動。這個應該比較容易理解吧。
2、既然 BFS 那么好,為啥 DFS 還要存在?
BFS 可以找到最短距離,但是空間復雜度高,而 DFS 的空間復雜度較低。
還是拿剛才我們處理二叉樹問題的例子,假設給你的這個二叉樹是滿二叉樹,節點總數為N
,對於 DFS 算法來說,空間復雜度無非就是遞歸堆棧,最壞情況下頂多就是樹的高度,也就是O(logN)
。
但是你想想 BFS 算法,隊列中每次都會儲存着二叉樹一層的節點,這樣的話最壞情況下空間復雜度應該是樹的最底層節點的數量,也就是N/2
,用 Big O 表示的話也就是O(N)
。
由此觀之,BFS 還是有代價的,一般來說在找最短路徑的時候使用 BFS,其他時候還是 DFS 使用得多一些(主要是遞歸代碼好寫)。
思路:用BFS解決這道題目,關鍵在於將題目轉變成BFS的解題模式
BFS一般步驟:先確定一個搜索范圍、起始點、標記和目標,然后寫出相鄰關系函數。先將起始點出列,馬上與它相鄰點入列,並標記為已訪問,如此循環,直到列表為空。這樣就能一直搜索,直到找到目標點。
題目特征:
搜索范圍:0000至9999這一千個節點
起始節點:0000
相鄰關系:4個位置每次只有一個能+1或者-1(0-1則為9)
目標節點:target
額外條件:節點不會在deadends中
class Solution: def openLock(self, deadends: List[str], target: str) -> int: # 生成器 def neighbors(node): for i in range(0, 4): x = int(node[i]) for d in (-1, 1): y = (x+d) % 10 yield node[:i] + str(y) + node[i+1:] import collections queue = collections.deque([('0000', 0)]) dead = set(deadends) seen = {'0000'} # 集合 while queue: node, depth = queue.popleft() if node == target: return depth if node in dead: continue else: # neighbors(node) 生成器對象 for nei in neighbors(node): if nei not in seen: seen.add(nei) queue.append((nei, depth+1)) return -1
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def isSymmetric(self, root: TreeNode) -> bool: import collections queue = collections.deque() queue.append((root, root)) while queue: left, right = queue.popleft() if not left and not right: continue if not left or not right: return False if left.val != right.val: return False queue.append((left.left, right.right)) queue.append((left.right, right.left)) return True