數據結構和算法(Golang實現)(17)常見數據結構-樹

樹是一種比較高級的基礎數據結構,由n個有限節點組成的具備層次關係的集合。算法

樹的定義:segmentfault

  1. 有節點間的層次關係,分爲父節點和子節點。
  2. 有惟一一個根節點,該根節點沒有父節點。
  3. 除了根節點,每一個節點有且只有一個父節點。
  4. 每個節點自己以及它的後代也是一棵樹,是一個遞歸的結構。
  5. 沒有後代的節點稱爲葉子節點,沒有節點的樹稱爲空樹。

二叉樹:每一個節點最多隻有兩個兒子節點的樹。數組

滿二叉樹:葉子節點與葉子節點之間的高度差爲0的二叉樹,即整顆樹是滿的,樹呈滿三角形結構。在國外的定義,非葉子節點兒子都是滿的樹就是滿二叉樹。咱們以國內爲準。安全

徹底二叉樹:徹底二叉樹是由滿二叉樹而引出來的,設二叉樹的深度爲k,除第k層外,其餘各層的節點數都達到最大值,且第k層全部的節點都連續集中在最左邊。數據結構

樹根據兒子節點的多寡,有二叉樹,三叉樹,四叉樹等,咱們這裏主要介紹二叉樹。併發

1、二叉樹的數學特徵

  1. 高度爲h≥0的二叉樹至少有h+1個結點,好比最不平衡的二叉樹就是退化的線性鏈表結構,全部的節點都只有左兒子節點,或者全部的節點都只有右兒子節點。
  2. 高度爲h≥0的二叉樹至多有2^h+1個節點,好比這顆樹是滿二叉樹。
  3. 含有n≥1個結點的二叉樹的高度至多爲n-1,由1退化的線性鏈表能夠反推。
  4. 含有n≥1個結點的二叉樹的高度至少爲logn,由2滿二叉樹能夠反推。
  5. 在二叉樹的第i層,至多有2^(i-1)個節點,好比該層是滿的。

2、二叉樹的實現

二叉樹可使用鏈表來實現。以下:數據結構和算法

// 二叉樹
type TreeNode struct {
    Data  string    // 節點用來存放數據
    Left  *TreeNode // 左子樹
    Right *TreeNode // 右字樹
}

固然,數組也能夠用來表示二叉樹,通常用來表示徹底二叉樹。函數

對於一顆有n個節點的徹底二叉樹,從上到下,從左到右進行序號編號,對於任一個節點,編號i=0表示樹根節點,編號i的節點的左右兒子節點編號分別爲:2i+1,2i+2,父親節點編號爲:i/2,整除操做去掉小數spa

如圖是一顆徹底二叉樹,數組的表示:code

咱們通常使用二叉樹來實現查找的功能,因此樹節點結構體裏存放數據的Data字段。

3、遍歷二叉樹

構建一顆樹後,咱們但願遍歷它,有四種遍歷方法:

  1. 先序遍歷:先訪問根節點,再訪問左子樹,最後訪問右子樹。
  2. 後序遍歷:先訪問左子樹,再訪問右子樹,最後訪問根節點。
  3. 中序遍歷:先訪問左子樹,再訪問根節點,最後訪問右子樹。
  4. 層次遍歷:每一層從左到右訪問每個節點。

先序,後序和中序遍歷較簡單,代碼以下:

package main

import (
    "fmt"
)

// 二叉樹
type TreeNode struct {
    Data  string    // 節點用來存放數據
    Left  *TreeNode // 左子樹
    Right *TreeNode // 右字樹
}

// 先序遍歷
func PreOrder(tree *TreeNode) {
    if tree == nil {
        return
    }

    // 先打印根節點
    fmt.Print(tree.Data, " ")
    // 再打印左子樹
    PreOrder(tree.Left)
    // 再打印右字樹
    PreOrder(tree.Right)
}

// 中序遍歷
func MidOrder(tree *TreeNode) {
    if tree == nil {
        return
    }

    // 先打印左子樹
    MidOrder(tree.Left)
    // 再打印根節點
    fmt.Print(tree.Data, " ")
    // 再打印右字樹
    MidOrder(tree.Right)
}

// 後序遍歷
func PostOrder(tree *TreeNode) {
    if tree == nil {
        return
    }

    // 先打印左子樹
    MidOrder(tree.Left)
    // 再打印右字樹
    MidOrder(tree.Right)
    // 再打印根節點
    fmt.Print(tree.Data, " ")
}

func main() {
    t := &TreeNode{Data: "A"}
    t.Left = &TreeNode{Data: "B"}
    t.Right = &TreeNode{Data: "C"}
    t.Left.Left = &TreeNode{Data: "D"}
    t.Left.Right = &TreeNode{Data: "E"}
    t.Right.Left = &TreeNode{Data: "F"}

    fmt.Println("先序排序:")
    PreOrder(t)
    fmt.Println("\n中序排序:")
    MidOrder(t)
    fmt.Println("\n後序排序")
    PostOrder(t)
}

表示將如下結構的樹進行遍歷:

結果以下:

先序排序:
A B D E C F 
中序排序:
D B E A F C 
後序排序
D B E F C A

層次遍歷較複雜,用到一種名叫廣度遍歷的方法,須要使用輔助的先進先出的隊列。

  1. 先將樹的根節點放入隊列。
  2. 從隊列裏面remove出節點,先打印節點值,若是該節點有左子樹節點,左子樹入棧,若是有右子樹節點,右子樹入棧。
  3. 重複2,直到隊列裏面沒有元素。

核心邏輯以下:

func LayerOrder(treeNode *TreeNode) {
    if treeNode == nil {
        return
    }

    // 新建隊列
    queue := new(LinkQueue)
    // 根節點先入隊
    queue.Add(treeNode)
    for queue.size > 0 {
        // 不斷出隊列
        element := queue.Remove()

        // 先打印節點值
        fmt.Print(element.Data, " ")

        // 左子樹非空,入隊列
        if element.Left != nil {
            queue.Add(element.Left)
        }

        // 右子樹非空,入隊列
        if element.Right != nil {
            queue.Add(element.Right)
        }
    }
}

完整代碼:

package main

import (
    "fmt"
    "sync"
)

// 二叉樹
type TreeNode struct {
    Data  string    // 節點用來存放數據
    Left  *TreeNode // 左子樹
    Right *TreeNode // 右字樹
}

func LayerOrder(treeNode *TreeNode) {
    if treeNode == nil {
        return
    }

    // 新建隊列
    queue := new(LinkQueue)

    // 根節點先入隊
    queue.Add(treeNode)
    for queue.size > 0 {
        // 不斷出隊列
        element := queue.Remove()

        // 先打印節點值
        fmt.Print(element.Data, " ")

        // 左子樹非空,入隊列
        if element.Left != nil {
            queue.Add(element.Left)
        }

        // 右子樹非空,入隊列
        if element.Right != nil {
            queue.Add(element.Right)
        }
    }
}

// 鏈表節點
type LinkNode struct {
    Next  *LinkNode
    Value *TreeNode
}

// 鏈表隊列,先進先出
type LinkQueue struct {
    root *LinkNode  // 鏈表起點
    size int        // 隊列的元素數量
    lock sync.Mutex // 爲了併發安全使用的鎖
}

// 入隊
func (queue *LinkQueue) Add(v *TreeNode) {
    queue.lock.Lock()
    defer queue.lock.Unlock()

    // 若是棧頂爲空,那麼增長節點
    if queue.root == nil {
        queue.root = new(LinkNode)
        queue.root.Value = v
    } else {
        // 不然新元素插入鏈表的末尾
        // 新節點
        newNode := new(LinkNode)
        newNode.Value = v

        // 一直遍歷到鏈表尾部
        nowNode := queue.root
        for nowNode.Next != nil {
            nowNode = nowNode.Next
        }

        // 新節點放在鏈表尾部
        nowNode.Next = newNode
    }

    // 隊中元素數量+1
    queue.size = queue.size + 1
}

// 出隊
func (queue *LinkQueue) Remove() *TreeNode {
    queue.lock.Lock()
    defer queue.lock.Unlock()

    // 隊中元素已空
    if queue.size == 0 {
        panic("over limit")
    }

    // 頂部元素要出隊
    topNode := queue.root
    v := topNode.Value

    // 將頂部元素的後繼連接鏈上
    queue.root = topNode.Next

    // 隊中元素數量-1
    queue.size = queue.size - 1

    return v
}

// 隊列中元素數量
func (queue *LinkQueue) Size() int {
    return queue.size
}

func main() {
    t := &TreeNode{Data: "A"}
    t.Left = &TreeNode{Data: "B"}
    t.Right = &TreeNode{Data: "C"}
    t.Left.Left = &TreeNode{Data: "D"}
    t.Left.Right = &TreeNode{Data: "E"}
    t.Right.Left = &TreeNode{Data: "F"}

    fmt.Println("\n層次排序")
    LayerOrder(t)
}

輸出:

層次排序
A B C D E F

系列文章入口

我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook

相關文章
相關標籤/搜索