動畫演示|二叉樹de深度優先搜尋DFS
原理
深度優先搜尋(DFS)遵循這樣一條原則:總是沿著節點的一條邊,一路走到黑,然後返回到出發節點,再繼續下一條邊,如果找到目標節點,則返回,如果找不到,就會遍歷完全部節點。由於二叉樹只有兩條邊,所以DFS對二叉樹來說,就是先把左子樹遍歷完,再遍歷右子樹,等同於 先序遍歷
。
下面用動畫演示了查詢5的過程:

DFS動畫演示
原理很簡單清晰,不過俗話說得好:
細節是魔鬼
下面來看看如何用Swfit實現DFS,給出一些程式設計中需要注意的問題。
DFS的遞迴實現
由於遞迴很符合演算法邏輯,首先來看看遞迴的實現,先定義二叉樹的節點:
public class TreeNode { public var val: Int //節點的值 public var left: TreeNode? //左節點 public var right: TreeNode? //右節點 public init(_ val: Int) { self.val = val self.left = nil self.right = nil } }
完整的遞迴實現:
func dfsTree(_ root: TreeNode?, _ dst: Int) -> TreeNode? { if (root == nil) { return nil } if (root?.val == dst) { return root } var dstNode = self.dfsTree(root?.left, dst) if (dstNode == nil) { dstNode = self.dfsTree(root?.right, dst) } return dstNode }
由於二叉樹本身就是遞迴定義的,所以用遞迴呼叫顯得很自然,只要先處理left,再處理right即可。
DFS的非遞迴實現
由於大部分遞迴的效率較低,嘗試把遞迴展開為迴圈,這時有兩個問題需要考慮:
- 由於當左分支訪問完成後,要回到節點獲取右分支,需要記錄訪問過的節點,這裡用Array作為棧來儲存。
- 節點有三種狀態:
未訪問
、已訪問
、分支都已訪問完成
。
如動畫中演示的, 藍色
對應 未訪問
,即樹的初始狀態; 黃色
代表節點 已訪問
,已放入棧中;灰色對應 左右分支都已訪問
,又該如何儲存呢?為儲存左右分支的訪問狀態,這裡連同節點一起用元組儲存在棧中:
(node:TreeNode, isCheckLeft:Bool, isCheckRight:Bool)
完整的非遞迴實現如下:
func loopDfsTree(_ root: TreeNode?, _ dst: Int) -> TreeNode? { if (root == nil) { return nil } var checkNodes = Array<(node:TreeNode, isCheckLeft:Bool, isCheckRight:Bool)>() checkNodes.append((root!, false, false)) while checkNodes.last != nil { var nodeInfo = (checkNodes.popLast())! let dstNode = nodeInfo.node if (dstNode.val == dst) { return dstNode } if (dstNode.left != nil && nodeInfo.isCheckLeft == false) { nodeInfo.isCheckLeft = true checkNodes.append(nodeInfo) checkNodes.append(((dstNode.left)!, false, false)) } else if (dstNode.right != nil && nodeInfo.isCheckRight == false) { nodeInfo.isCheckRight = true checkNodes.append(nodeInfo) checkNodes.append(((dstNode.right)!, false, false)) } else { } } return nil }
思考題
對比DFS的遞迴實現和非遞迴實現,為什麼遞迴實現不需要儲存節點的狀態資訊?歡迎你的留言,相信經過深度思考的知識,才會歷久彌新。