
圖源:庫洛魔法使第一季第四集
經過昨天聖誕夜和雪兔哥在遊樂園激戰後,時間一下子又來到了開學,小櫻已是五年級的學生。
這天才剛開學,天上卻不斷的降雪,根據先前的經驗,一定又是庫洛牌搞得鬼。但是這集雪跟要教的樹沒有關係,所以只能把第一季第四集的樹拿來移花接木當一下封面了。

劇情來到了第二季,各位魔法使們,我們還有幾張「簡單」的資結Golang牌要搜集,大家一起加油吧!
樹(Tree),如同先前教的鏈結串列(Linked list)、堆疊(Stack)和佇列(Queue)一樣,都是一種資料結構。在魔法科學(資訊科學)中樹是倒過來長的,我們通常會這樣子形容一棵樹

先不要管什麼根、莖、葉、花、果實、種子了。
每個圓圈都是一個節點 Node ,而 Node 通常包含了「值」和指向「下一個節點的指標們」,有點類似先前學的鏈結串列(linked list),只是樹的指標不侷限下一個。
今天不會講到那麼複雜,只會討論分支度只有 2 的樹,又稱為「二元樹」

二元樹的每個節點最多只會指向另外 2 個節點
因此可以簡單的用 Go 表示成
type Node struct{
    val interface{}    // 節點值
    left *Node         // 指向左節點
    right *Node        // 指向右節點
}
首先我們先創建一個 package,至於要怎麼創建?請複習:#14 套件 Package (2020) & 套件實戰(math, http, sort) | Golang魔法使
因為這次只要實作簡單的操作,所以這次實作一步做完
./tree/tree.go
package tree
type Node struct{
    Val interface{}
    Right *Node
    Left *Node
}
func NewNode(val interface{}) *Node{
    node := new(Node)
    node.Val = val
    return node
}
func (n *Node) AddLeftNode(l *Node){
    n.Left = l
}
func (n* Node) AddRightNode(r *Node){
    n.Right = r
}

本系列教學採用新版 Golang version >= 1.11 並使用 go mod 初始化為 practice module
實作上圖二元樹
./lesson24.go
package main
import(
    "practice/tree"
)
func main(){
    root   := tree.NewNode(3)
    node7  := tree.NewNode(7)
    node1  := tree.NewNode(1)
    node2  := tree.NewNode(2)
    node6  := tree.NewNode(6)
    node9  := tree.NewNode(9)
    node5  := tree.NewNode(5)
    node11 := tree.NewNode(11)
    node4  := tree.NewNode(4)
    root.AddLeftNode(node7)
    root.AddRightNode(node1)
    node7.AddLeftNode(node2)
    node7.AddRightNode(node6)
    node6.AddLeftNode(node5)
    node6.AddRightNode(node11)
    node1.AddRightNode(node9)
    node9.AddLeftNode(node4)
}
創建一顆單純的二元樹並沒有很複雜,甚至比 Linked-list 還簡單,但是如果想要走訪一棵樹,或著照著某些規則創建都不容易
在走訪一顆樹時有主要三種走訪方式後序、前序、中序

從根節點開始一直可以往左走最後走到 2,因為 2 沒有左節點也沒有右節點所以將其印出

回到前一個節點 7 ,因為還有右節點所以往 6 走,而 6 又有左節點所以往左走,節點 5 沒有左節點也沒有右節點因此印出

回到前一個節點 6,因為還有右節點所以往 11 走,11 沒有左節點也沒有右節點所以印出

回到前一個節點 6,因為 6 沒有左節點也沒有右節點所以印出

回到前一個節點 7,因為 7 沒有左節點也沒有右節點所以印出

回到前一個節點 3,因為 3 還有右子點所以往右走到 1,1 沒有左子點所以往右走到 9 ,9 有左子點所以走到 4,但 4 沒有左節點也沒有右節點所以印出

回到前一個節點 9,因為 9 沒有左節點也沒有右節點所以印出

回到前一個節點 1,因為 1 沒有左節點也沒有右節點所以印出

最後回到根節點 3,印出 3
因為根節點最後印出所以稱為後序走訪,這根樹的後序走訪為
2 5 11 6 7 4 9 1 3
package main
import(
    "practice/tree"
    "fmt"
)
func postorder(n *tree.Node){
    if n != nil{
        // 嘗試往左走
        postorder(n.Left)
        // 嘗試往右走
        postorder(n.Right)
        // 都嘗試過後印出
        fmt.Printf("%d ", n.Val)
    }
}
func main(){
    root   := tree.NewNode(3)
    node7  := tree.NewNode(7)
    node1  := tree.NewNode(1)
    node2  := tree.NewNode(2)
    node6  := tree.NewNode(6)
    node9  := tree.NewNode(9)
    node5  := tree.NewNode(5)
    node11 := tree.NewNode(11)
    node4  := tree.NewNode(4)
    root.AddLeftNode(node7)
    root.AddRightNode(node1)
    node7.AddLeftNode(node2)
    node7.AddRightNode(node6)
    node6.AddLeftNode(node5)
    node6.AddRightNode(node11)
    node1.AddRightNode(node9)
    node9.AddLeftNode(node4)
    postorder(root)
}
執行結果:
2 5 11 6 7 4 9 1 3
而所謂的中序走訪則是先走左子樹,如果沒有左子樹則把自己印出,然後開始走右子樹
func inorder(n *tree.Node){
    if n != nil{
        // 嘗試往左走
        inorder(n.Left)
        // 印出
        fmt.Printf("%d ", n.Val)
        // 嘗試往右走
        inorder(n.Right)
    }
}
前序走訪則是先將自己印出,再開始走左子樹,走完再走右子樹
func preorder(n *tree.Node){
    if n != nil{
        // 印出
        fmt.Printf("%d ", n.Val)
        // 嘗試往左走
        preorder(n.Left)
        // 嘗試往右走
        preorder(n.Right)
    }
}

本集圖片主要來自: