数据结构之二叉树

miloyang
0 评论
/ /
825 阅读
/
5774 字
15 2023-09

介绍

二叉树,是一种树形的数据结构,每个节点最多有两棵子树,有左右之分。
如下图:

erchashu2b5

  • 头结点:F就是头节点
  • 子树:从某一个头结点出发,下面所有的节点都需要算上。如C、E就是F的子树,A、D就是C的子树,B就是D子树。但是D和B并不是C的子数,因为漏掉了A。
  • 左子树:就是我左边的所有子树,比如F,就是C/A/D/B全部是F的左子树。C的左子树就是A。
  • 右子树:就是我右边的所有子树,比如F,就是E/H/G/M全部是F的右子树,G的右子树就是空,E的右子树就是G。

遍历,有几种方式。

  • 先序(头左右)

    任何子树的处理顺序,都是先从头节点,再左子树,然后再右子树。
    所以顺序就是 F->C->A->D->B->E->H->G->M

  • 中序(左头右)

    任何子树的处理顺序,都是先从左节点开始,再到头,最后到右子树。
    所以顺序就是 A->C->D->B->F->H->E->G->M

  • 后序(左右头)

    任何子树的处理顺序,都是先从左节点开始,再到右,最后到头节点。
    所以顺序就是 A->D->C->B->E->H->G->M->F

代码

首先创建二叉树

type Node struct {
    value string
    left  *Node
    right *Node
}

func createNode() *Node {
    //F的全部左子树
    nodeB := &Node{
        value: "B",
        left:  nil,
        right: nil,
    }
    nodeD := &Node{
        value: "D",
        left:  nodeB,
        right: nil,
    }
    nodeA := &Node{
        value: "A",
        left:  nil,
        right: nil,
    }
    nodeC := &Node{
        value: "C",
        left:  nodeA,
        right: nodeD,
    }

    // F的全部右子树
    nodeM := &Node{
        value: "M",
        left:  nil,
        right: nil,
    }
    nodeG := &Node{
        value: "G",
        left:  nodeM,
        right: nil,
    }
    nodeH := &Node{
        value: "H",
        left:  nil,
        right: nil,
    }
    nodeE := &Node{
        value: "E",
        left:  nodeH,
        right: nodeG,
    }

    nodeF := &Node{
        value: "F",
        left:  nodeC,
        right: nodeE,
    }
    return nodeF
}

基础的遍历

递归遍历

func main() {
    node := createNode()
    //pre(node)
    middle(node)
    //after(node)

}

// 先序
func pre(root *Node) {
    if root == nil {
        return
    }
    fmt.Println(root.value)
    pre(root.left)
    pre(root.right)
}

// 中序
func middle(root *Node) {
    if root == nil {
        return
    }
    middle(root.left)
    fmt.Println(root.value)
    middle(root.right)
}

// 后续
func after(root *Node) {
    if root == nil {
        return
    }
    after(root.left)
    after(root.right)
    fmt.Println(root.value)
}

有趣的现象

比如一下二叉树: shuzixu0ONDXWEs

我们比如说是按照先序的打印,就是头左右的来看,每个节点都会出现三次的打印以及调用。
如:先来到1节点,左边有,就来到了2节点,接着来到了4节点,然后4节点的左节点为nil,返回4,接着去4的右节点,为nil又返回4。
然后返回到2节点,接着去5节点,5节点左为空,返回5,来到5的右节点,为空返回5。
....以此类推,每个节点都会出现三次。最终走过的路线就是:
1,2,4,4,4,2,5,5,5,2,1,3,6,6,6,3,7,7,7,3,1 每个节点都会到达3次。
这不能说明什么,重要的是下面才有趣呢。
先序遍历:就是每次第一次到达节点的时候,就打印,如:1,2,4,5,3,6,7
中序遍历,就是每次第二次到达节点的时候,就打印,如:4,2,5,1,6,3,7
后续遍历,就是每次第三次到达节点的时候,就打印,如:4,5,2,6,7,3,1

这说明什么,说明每个节点都有机会去左、右、头节点三个去转一圈。所以这就是 递归序 也是在二叉树上面做动态规划的基础。
ok,先到这。

压栈遍历

前序

有这么几个顺序:先把头节点压入栈,然后再pop,pop就打印,如果当前栈顶又右节点,压入栈,如果有左节点,压入栈,也就是先压右,再压左。

func stackPre(head *Node) {
    var stack []*Node
    stack = append(stack, head)
    for len(stack) != 0 {
        top := stack[len(stack)-1]
        fmt.Println(top.value)
        //pop
        stack = stack[:len(stack)-1]
        if top.right != nil {
            stack = append(stack, top.right)
        }
        if top.left != nil {
            stack = append(stack, top.left)
        }
    }
}

后序

一样,也是有顺序的,先把头压入栈,然后再pop的时候,但是不打印,而是重新放入一个新的栈。但是顺序反了,如果有左,压入左,如果有右,压入右。
然后需要重新打出来栈来。

func stackAfter(head *Node) {
    var stack []*Node
    var bakStack []*Node
    stack = append(stack, head)
    for len(stack) != 0 {
        top := stack[len(stack)-1]
        bakStack = append(bakStack, top)
        //pop
        stack = stack[:len(stack)-1]
        if top.left != nil {
            stack = append(stack, top.left)
        }
        if top.right != nil {
            stack = append(stack, top.right)
        }
    }

    for i := len(bakStack) - 1; i >= 0; i-- {
        fmt.Println(bakStack[i].value)
    }
}

中序

这个稍微麻烦点,有两个分支:

  • 整条左边界依次入栈
  • 如果左边界为nil,弹出加打印,然后右数入栈。
func stackMiddle(head *Node) {
    if head == nil {
        return
    }
    var stack []*Node
    for len(stack) != 0 || head != nil {
        if head != nil {
            stack = append(stack, head)
            head = head.left
        } else {
            head = stack[len(stack)-1]
            fmt.Println(head.value)
            // pop
            stack = stack[:len(stack)-1]
            head = head.right
        }
    }
}

按层遍历

实现从上到下,从左至右的遍历,如最上图,遍历结果为: F->C->E->A->D->H->G->B->M
我们可以通过队列的方式来:

  • 先把头结点加入到队列里面去
  • 条件为,如果队列不为空则结束
  • 先把队列的头poll出去,然后打印,也就是弹出就打印。
  • 然后左节点不为空,加进去队列中,右节点不为空,也加进去队列中。
func level(node *Node) {
    var levelArr []*Node
    levelArr = append(levelArr, node)

    for len(levelArr) != 0 {
        current := levelArr[0]
        fmt.Println(current.value)
        levelArr = levelArr[1:]

        if current.left != nil {
            levelArr = append(levelArr, current.left)
        }
        if current.right != nil {
            levelArr = append(levelArr, current.right)
        }
    }
}
人未眠
工作数十年
脚步未曾歇,学习未曾停
乍回首
路程虽丰富,知识未记录
   借此博客,与之共进步