2020年8月31日 星期一

Graph DFS and BFS 深度優先 廣度優先 Tree Traversal

 今天想要探討一下關於Graph裡面的DFS、BFS、Stack Treversal以及Binary Tree的Preorder、InOrder、PostOrder Traversal。

先看一下DFS的Iterative代碼

def dfs(graph, node):

    parents = {node:None}

    visited = {}

    stack = [node]

    while stack:

        v = stack.pop()

        if not v in visited:

            visited[v] = parents[v]

            for a in reversed(graph[v]):

                stack.append(a)

                parents[a] = v

        print(v, ''.join(stack))

    return visited

DFS代碼最重要的是使用stack並且拿出最後一個node出來,然後檢查確認這個node沒有曾經處理過的話才會被“搜全家”並且放進stack等待處理,換句話說如果這個node曾經被處理過的話下次再遇到的話它就會被忽略掉。那麼什麼時候一個node在stack拿出來的時候就已經被處理過呢?唯一的可能性就是一個node被重複放到stack裡面,第一次拿出來的時候沒有處理過,第二次拿出來的時候就知道已經處理過了。Wikipedia也寫著"DFS has worst case O(|E|) with possible duplication of nodes on the stack"。

舉個例子掌心對著自己的那5隻手指全部互聯的話會產生5角形以及5角星合共10條邊,在第一次iteration處理拇指之後stack裡面就有4根手指,在第二次iteration處理食指之後stack除了原有拇指連著的的中無尾3根手指之外又要添加4根重複的母中無尾手指合共7根手指。stack裡面最長的暫存資料是5435454321或者“尾無中尾無尾無中食母”,剛好是10個資料並且是合共的10條邊。

正因為node可以被多次重複放進stack,所以才能夠過讓stack裡面的node“走得更遠”,並且達到最大的深度。DFS會從第一個分支的頭部開始一邊蔓延一邊儲存在stack裡面直到末端,然後才跑到第二個分支。


再來看一下BFS的Iterative代碼

def bfs(graph, node):

    parents = {node:None}

    visited = {node:None}

    deque = collections.deque([node])

    while deque:

        v = deque.popleft()

        for a in reversed(graph[v]):

            if a not in visited:

                parents[a] = v

                visited[a] = parents[a]

                deque.append(a)

        print(v, ''.join(deque))

    return visited

BFS的代碼特點除了使用queue代替stack去暫存資料之外,它的處理方法是先“搜全家”然後確認沒有被處理過才會被放進暫存queue裡面,而且由於全部node都會確認沒有被處理過才放進去queue裡面,因此queue裡面不可能有重複的node,而且每一個node都只會被放進queue裡面1次而不是多次。


Stack Traversal

BFS與DFS看起來有點相似的,的確它們都是用來搜索整顆樹,但是他們的特點都是有各自的優先的。假設在BFS的演算法裡面的queue換成stack就會變成DFS嗎?不會啊,姑且叫它做stack traversal,雖然兩者都可以搜索整顆樹,但是stack traversal不是深度優先。

深度優先的演算法的重點在於每個node的“全家”會被無條件添加到stack裡面,至於要不要處理就等到他們的從stack拿出來的時候才檢查,越遲檢查的話stack的深度就越長,達到深度優先的效果。

那麼stack traversal為什麼不是深度優先?stack traversal將BFS的queue換成stack,僅此而已。因此BFS的其中一個特質被保留:先檢查node沒有處理過才會放進暫存的stack或者queue,因此接近七點的node的家人一早就被認定已經處理過了,而且這些node只會被放進暫存最多1次而不是多次,因此其後如果發現有backedge的話那些node不會在被放進暫存了,因此深度被中斷了,也因此不是深度優先。


比較DFS與BFS

DFS的精髓在於允許node重複放進暫存,並且等到拿出來的時間才檢查是否曾經處理過,因此暫存的stack增長的很快很深而且有重複。

BFS則是先檢查是否曾經處理過才放進暫存queue的,而且不允許重複放進queue裡面,因此queue裡面的資料就是“沿著起點繞圈”往外擴展,也許將BFS叫做Sprial Search更為生動一些。


Preorder

Preorder使用“上左右”的方法穿越整顆樹,其實就是用DFS的無HashMap版本。不需要HashMap的原因是因為BinaryTree沒有backedge不會存在一個node被處理多次。

class Solution:

    def preorderTraversal(self, root: TreeNode) -> List[int]:

        answer = []

        stack = [root]

        while stack:

            node = stack.pop()

            if node:

                answer.append(node)

                stack.append(node.right)

                stack.append(node.left)

        return answer


Postorder

Postorder使用“左右上”的方法穿越整顆樹,其實也是使用DFS的無HashMap版本。Postorder與Preorder都是從上到下穿越整顆樹的,只是最後結果需要反向一次。

class Solution:

    def postorderTraversal(self, root: TreeNode) -> List[int]:

        answer = []

        stack = [root]

        while stack:

            node = stack.pop()

            if node:

                answer.append(node.val)

                stack.append(node.left)

                stack.append(node.right)

        return reversed(answer)


Inorder

Inorder使用的方法是先靠左DFS,去到盡頭之後將指針往右移動,然後繼續DFS。演算法運行起來像中文字的”久“。

class Solution:

    def inorderTraversal(self, root: TreeNode) -> List[int]:

    # func(left)+[val]+func(right)

        answer = []

        stack = []

        while stack or root:

            if root:

                stack.append(root)

                root = root.left

            else:

                node = stack.pop()

                if node:

                    answer.append(node)

                    root = node.right

        return answer






End

沒有留言:

張貼留言

2023 Promox on Morefine N6000 16GB 512GB

2023 Promox on Morefine N6000 16GB 512GB Software Etcher 100MB (not but can be rufus-4.3.exe 1.4MB) Proxmox VE 7.4 ISO Installer (1st ISO re...