go语言|数据结构:二叉树(1)创建与遍历方法

简介: go语言|数据结构:二叉树(1)创建与遍历方法

树 Tree


树是有限结点组成一个具有层次关系的集合。


image.png


开始写代码前,先复习一遍基本概念:


名词术语


结点 Node


也有写作“节点”,组成树的集合中的“元素”。


根结点 Root

没有前驱的结点叫做根结点


结点的度 Node degree

一个结点含有子树的个数


树的度 Tree degree

所有结点的度最大的那一个叫做树的度


叶子结点 Leaf

度为0的结点


结点的层次 Level

根为第一层,根的子节点为第二层,依次往下推


深度 Depth

树的总层数,就是高度树的高度Height


二叉树 Binary Tree

一种特殊的树,是结点的一个有限集合,且所有结点最多有2个子结点,即度只能是0,1,2。



满二叉树 Full Binary Tree

除叶子结点外,每一层上的所有结点都有两个子结点二叉树。它的每一个层次的结点数目都达到了最大值。

结点总数M 和 总层数N 满足: M = 2^N - 1

每层结点数m 和 层数n 满足: m = 2^(n-1)


完全二叉树 Complete Binary Tree

二叉树的叶节点只出现在最下层和次下层,并且最下层的结点都集中在该层的最左边。

aa9b205d2a424e1081a1b629fc5a1d6f.png


性质


(1) 在二叉树中,第i层的结点总数不超过2^(i-1);


(2) 深度为h的二叉树最多有2^h-1个结点(h>=1),最少有h个结点;


(3) 对于任意一棵二叉树,其所有的叶结点数为:N0,而度为2的所有结点的数量为:N2,则:N0=N2+1,请观察上图;


(4) 具有n个结点的完全二叉树的深度为int(log2n)+1


(5)有N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:

若I为结点编号则 如果I<>1,则其父结点的编号为I/2;

如果2*I<=N,则其左儿子(即左子树的根结点)的编号为2*I;若2*I>N,则无左儿子;

如果2*I+1<=N,则其右儿子的结点编号为2*I+1;若2*I+1>N,则无右儿子。


(6) 给定N个节点,能构成h(N)种不同的二叉树。h(N)为卡特兰数的第N项。h(n)=C(n,2*n)/(n+1)。




二叉树的创建


结点

数据域 Data,使用 interface{} 好处是可以添加任意类型的数据结点

左、右子树结点指针 Lchild, Rchild


type btNode struct {
  Data   interface{}
  Lchild *btNode
  Rchild *btNode
}


二叉树

1. type biTree struct {
2.  root *btNode
3. }


创建与访问

package main
import "fmt"
type btNode struct {
  Data   interface{}
  Lchild *btNode
  Rchild *btNode
}
type biTree struct {
  root *btNode
}
func main() {
  var tree *biTree
  var node1, node2, node3, node4 *btNode
  tree = new(biTree)
  node1 = &btNode{Data: 1}
  node2 = &btNode{Data: 2}
  node3 = &btNode{Data: 3}
  node4 = &btNode{Data: 4}
  tree.root = node1
  node1.Lchild = node2
  node1.Rchild = node3
  node2.Lchild = node4
  fmt.Println(tree.root.Data)
  fmt.Println(tree.root.Lchild.Data)
  fmt.Println(tree.root.Rchild.Data)
  fmt.Println(tree.root.Lchild.Lchild.Data)
}
/*
1
2
3
4
*/


用数组批量创建结点

package main
import "fmt"
type btNode struct {
  Data   interface{}
  Lchild *btNode
  Rchild *btNode
}
type biTree struct {
  root *btNode
}
func NewTree() *biTree {
  return &biTree{}
}
func newTreeNode(data interface{}) *btNode {
  return &btNode{
    Data:   data,
    Lchild: nil,
    Rchild: nil,
  }
}
func main() {
  list := []interface{}{1, 2, 3, 4, 5, 6}
  node := []*btNode{}
  tree := NewTree()
  for _, data := range list {
    node = append(node, newTreeNode(data))
  }
  tree.root = node[0]
  tree.root.Lchild = node[1]
  tree.root.Rchild = node[2]
  tree.root.Lchild.Lchild = node[3]
  tree.root.Lchild.Rchild = node[4]
  tree.root.Rchild.Lchild = node[5]
  fmt.Println(tree.root.Data)
  fmt.Println(tree.root.Lchild.Data)
  fmt.Println(tree.root.Rchild.Data)
  fmt.Println(tree.root.Lchild.Lchild.Data)
  fmt.Println(tree.root.Lchild.Rchild.Data)
  fmt.Println(tree.root.Rchild.Lchild.Data)
}
/*
1
2
3
4
5
6
*/


追加结点

从上到下逐层且每一层从左到右访问二叉树,遇到首个空结点时就添加一结点。

package main
import "fmt"
type btNode struct {
  Data   interface{}
  Lchild *btNode
  Rchild *btNode
}
type biTree struct {
  root *btNode
}
func NewTree() *biTree {
  return &biTree{}
}
func newTreeNode(data interface{}) *btNode {
  return &btNode{
    Data:   data,
    Lchild: nil,
    Rchild: nil,
  }
}
func (bt *biTree) appendNode(data interface{}) {
  cur := bt.root
  if cur == nil { //空树则创建一个根结点
    bt.root = newTreeNode(data)
    return
  }
  Queue := []*btNode{cur}
  for len(Queue) > 0 {
    cur := Queue[0]
    Queue = Queue[1:]
    if cur.Lchild != nil {
      Queue = append(Queue, cur.Lchild)
    } else {
      cur.Lchild = newTreeNode(data)
      return
    }
    if cur.Rchild != nil {
      Queue = append(Queue, cur.Rchild)
    } else {
      cur.Rchild = newTreeNode(data)
      break
    }
  }
}
func main() {
  tree := &biTree{}
  list := []interface{}{1, 2, 3, 4, 5, 6}
  for _, data := range list {
    tree.appendNode(data)
  }
  fmt.Println(tree.root.Data)
  fmt.Println(tree.root.Lchild.Data)
  fmt.Println(tree.root.Rchild.Data)
  fmt.Println(tree.root.Lchild.Lchild.Data)
  fmt.Println(tree.root.Lchild.Rchild.Data)
  fmt.Println(tree.root.Rchild.Lchild.Data)
  tree.appendNode(0)
  fmt.Println(tree.root.Rchild.Rchild.Data)
}
/*
1
2
3
4
5
6
0
*/


追加结点时的访问方法称为“层序”,层序访问整个二叉树只要把代码中追加结点的语句换成打印所有结点的数据域,即完成了二叉树的“层序遍历”:

package main
import "fmt"
type btNode struct {
  Data   interface{}
  Lchild *btNode
  Rchild *btNode
}
type biTree struct {
  root *btNode
}
func NewTree() *biTree {
  return &biTree{}
}
func newTreeNode(data interface{}) *btNode {
  return &btNode{
    Data:   data,
    Lchild: nil,
    Rchild: nil,
  }
}
func levelorderPrint(bt *biTree) {
  cur := bt.root
  if cur == nil {
    fmt.Print("Empty Binary Tree")
  }
  Queue := []*btNode{cur}
  for len(Queue) > 0 {
    cur := Queue[0]
    Queue = Queue[1:]
    fmt.Print(cur.Data, " ")
    if cur.Lchild != nil {
      Queue = append(Queue, cur.Lchild)
    }
    if cur.Rchild != nil {
      Queue = append(Queue, cur.Rchild)
    }
  }
  fmt.Println()
}
func (bt *biTree) appendNode(data interface{}) {
  cur := bt.root
  if cur == nil {
    bt.root = newTreeNode(data)
    return
  }
  Queue := []*btNode{cur}
  for len(Queue) > 0 {
    cur := Queue[0]
    Queue = Queue[1:]
    if cur.Lchild != nil {
      Queue = append(Queue, cur.Lchild)
    } else {
      cur.Lchild = newTreeNode(data)
      return
    }
    if cur.Rchild != nil {
      Queue = append(Queue, cur.Rchild)
    } else {
      cur.Rchild = newTreeNode(data)
      break
    }
  }
}
func main() {
  tree := &biTree{}
  list := []interface{}{1, 2, 3, 4, 5, 6}
  for _, data := range list {
    tree.appendNode(data)
  }
  levelorderPrint(tree)
  tree.appendNode(0)
  levelorderPrint(tree)
}
/*
1 2 3 4 5 6 
1 2 3 4 5 6 0 
*/



二叉树的遍历


层序遍历

自上而下,自左至右逐层访问树的结点的过程就是层序遍历


20210725085538742.png


上面已写过打印版的层序遍历函数,稍作修改让它变成一个二叉树方法,并返回一个数组。

package main
import "fmt"
type btNode struct {
  Data   interface{}
  Lchild *btNode
  Rchild *btNode
}
type biTree struct {
  root *btNode
}
func (bt *biTree) levelorder() []interface{} {
  var list []interface{}
  cur := bt.root
  if cur == nil {
    return list
  }
  Queue := []*btNode{cur}
  for len(Queue) > 0 {
    cur := Queue[0]
    Queue = Queue[1:]
    list = append(list, cur.Data)
    if cur.Lchild != nil {
      Queue = append(Queue, cur.Lchild)
    }
    if cur.Rchild != nil {
      Queue = append(Queue, cur.Rchild)
    }
  }
  return list
}
func createTree(list []interface{}) *biTree {
  btree := &biTree{}
  if len(list) > 0 {
    btree.root = &btNode{Data: list[0]}
    for _, data := range list[1:] {
      btree.appendNode(data)
    }
  }
  return btree
}
func (bt *biTree) appendNode(data interface{}) {
  cur := bt.root
  if cur == nil {
    bt = &biTree{}
    return
  }
  Queue := []*btNode{cur}
  for len(Queue) > 0 {
    cur := Queue[0]
    Queue = Queue[1:]
    if cur.Lchild != nil {
      Queue = append(Queue, cur.Lchild)
    } else {
      cur.Lchild = &btNode{Data: data}
      return
    }
    if cur.Rchild != nil {
      Queue = append(Queue, cur.Rchild)
    } else {
      cur.Rchild = &btNode{Data: data}
      break
    }
  }
}
func main() {
  list := []interface{}{1, 2, 3, 4, 5, 6}
  tree := createTree(list)
  fmt.Println(tree.levelorder())
  tree.appendNode(0)
  fmt.Println(tree.levelorder())
}
/*
[1 2 3 4 5 6]
[1 2 3 4 5 6 0]
*/


先序遍历

先遍历根节点,再遍历左节点,最后遍历右节点


20210725082352964.png



中序遍历

先遍历左节点,再遍历根节点,最后遍历右节点

20210725083710482.png



后序遍历

先遍历左节点,再遍历右节点,最后遍历根节点


20210725084708331.png


三种遍历的递归打印版:

package main
import "fmt"
type btNode struct {
  Data   interface{}
  Lchild *btNode
  Rchild *btNode
}
type biTree struct {
  root *btNode
}
func preorderPrint(bt *btNode) {
  if bt == nil {
    return
  }
  fmt.Print(bt.Data, " ")
  preorderPrint(bt.Lchild)
  preorderPrint(bt.Rchild)
}
func inorderPrint(bt *btNode) {
  if bt == nil {
    return
  }
  inorderPrint(bt.Lchild)
  fmt.Print(bt.Data, " ")
  inorderPrint(bt.Rchild)
}
func postorderPrint(bt *btNode) {
  if bt != nil { //这样写就不用return语句
    postorderPrint(bt.Lchild)
    postorderPrint(bt.Rchild)
    fmt.Print(bt.Data, " ")
  }
}
func createTree(list []interface{}) *biTree {
  btree := &biTree{}
  if len(list) > 0 {
    btree.root = &btNode{Data: list[0]}
    for _, data := range list[1:] {
      btree.appendNode(data)
    }
  }
  return btree
}
func (bt *biTree) appendNode(data interface{}) {
  cur := bt.root
  if cur == nil {
    bt = &biTree{}
    return
  }
  Queue := []*btNode{cur}
  for len(Queue) > 0 {
    cur := Queue[0]
    Queue = Queue[1:]
    if cur.Lchild != nil {
      Queue = append(Queue, cur.Lchild)
    } else {
      cur.Lchild = &btNode{Data: data}
      return
    }
    if cur.Rchild != nil {
      Queue = append(Queue, cur.Rchild)
    } else {
      cur.Rchild = &btNode{Data: data}
      break
    }
  }
}
func main() {
  list := []interface{}{1, 2, 3, 4, 5, 6, 7, 8}
  tree := createTree(list)
  preorderPrint(tree.root)
  fmt.Println()
  inorderPrint(tree.root)
  fmt.Println()
  postorderPrint(tree.root)
  fmt.Println()
}
/*
1 2 4 8 5 3 6 7 
8 4 2 5 1 6 3 7 
8 4 5 2 6 7 3 1 
*/



三个函数核心代码次序的对比:


   先序:“根左右”

                   fmt.Print(bt.Data, " ")

                   preorderPrint(bt.Lchild)

                   preorderPrint(bt.Rchild)


   中序:“左根右”

                   inorderPrint(bt.Lchild)

                   fmt.Print(bt.Data, " ")

                   inorderPrint(bt.Rchild)


   后序:“左右根”

                   postorderPrint(bt.Lchild)

                   postorderPrint(bt.Rchild)

                   fmt.Print(bt.Data, " ")


递归非打印版:

在打印版的基础上稍作修改,返回一个数组

package main
import "fmt"
type btNode struct {
  Data   interface{}
  Lchild *btNode
  Rchild *btNode
}
type biTree struct {
  root *btNode
}
func (bt *btNode) preorder() []interface{} {
  var res []interface{} //or: res := make([]interface{}, 0)
  if bt != nil {
    res = append(res, bt.Data)
    res = append(res, bt.Lchild.preorder()...)
    res = append(res, bt.Rchild.preorder()...)
  }
  return res
}
func (bt *btNode) inorder() []interface{} {
  var res []interface{}
  if bt != nil {
    res = append(res, bt.Lchild.inorder()...)
    res = append(res, bt.Data)
    res = append(res, bt.Rchild.inorder()...)
  }
  return res
}
func (bt *btNode) postorder() []interface{} {
  var res []interface{}
  if bt != nil {
    res = append(res, bt.Lchild.postorder()...)
    res = append(res, bt.Rchild.postorder()...)
    res = append(res, bt.Data)
  }
  return res
}
func createTree(list []interface{}) *biTree {
  btree := &biTree{}
  if len(list) > 0 {
    btree.root = &btNode{Data: list[0]}
    for _, data := range list[1:] {
      btree.appendNode(data)
    }
  }
  return btree
}
func (bt *biTree) appendNode(data interface{}) {
  cur := bt.root
  if cur == nil {
    bt = &biTree{}
    return
  }
  Queue := []*btNode{cur}
  for len(Queue) > 0 {
    cur := Queue[0]
    Queue = Queue[1:]
    if cur.Lchild != nil {
      Queue = append(Queue, cur.Lchild)
    } else {
      cur.Lchild = &btNode{Data: data}
      return
    }
    if cur.Rchild != nil {
      Queue = append(Queue, cur.Rchild)
    } else {
      cur.Rchild = &btNode{Data: data}
      break
    }
  }
}
func main() {
  list := []interface{}{1, 2, 3, 4, 5, 6, 7, 8}
  tree := createTree(list)
  fmt.Println(tree.root.preorder())
  fmt.Println(tree.root.inorder())
  fmt.Println(tree.root.postorder())
}
/*
[1 2 4 8 5 3 6 7]
[8 4 2 5 1 6 3 7]
[8 4 5 2 6 7 3 1]
*/


非递归方法:

递推法可以写成二叉树的方法,不像递归法要反复调用只能写成结点的方法。

package main
import "fmt"
type btNode struct {
  Data   interface{}
  Lchild *btNode
  Rchild *btNode
}
type biTree struct {
  root *btNode
}
func (bt *biTree) preOrder() []interface{} {
  var res []interface{}
  p := bt.root
  Stack := make([]*btNode, 0)
  for p != nil || len(Stack) > 0 {
    for p != nil {
      res = append(res, p.Data)
      Stack = append(Stack, p)
      p = p.Lchild
    }
    if len(Stack) > 0 {
      p = Stack[len(Stack)-1]
      Stack = Stack[:len(Stack)-1]
      p = p.Rchild
    }
  }
  return res
}
func (bt *biTree) inOrder() []interface{} {
  var res []interface{}
  p := bt.root
  Stack := make([]*btNode, 0)
  for p != nil || len(Stack) > 0 {
    for p != nil {
      Stack = append(Stack, p)
      p = p.Lchild
    }
    if len(Stack) > 0 {
      p = Stack[len(Stack)-1]
      res = append(res, p.Data)
      Stack = Stack[:len(Stack)-1]
      p = p.Rchild
    }
  }
  return res
}
func (bt *biTree) postOrder() []interface{} {
  var res []interface{}
  var cur, pre *btNode
  Stack := make([]*btNode, 0)
  Stack = append(Stack, bt.root)
  for len(Stack) > 0 {
    cur = Stack[len(Stack)-1]
    if cur.Lchild == nil && cur.Rchild == nil ||
      pre != nil && (pre == cur.Lchild || pre == cur.Rchild) {
      res = append(res, cur.Data)
      Stack = Stack[:len(Stack)-1]
      pre = cur
    } else {
      if cur.Rchild != nil {
        Stack = append(Stack, cur.Rchild)
      }
      if cur.Lchild != nil {
        Stack = append(Stack, cur.Lchild)
      }
    }
  }
  return res
}
func createTree(list []interface{}) *biTree {
  btree := &biTree{}
  if len(list) > 0 {
    btree.root = &btNode{Data: list[0]}
    for _, data := range list[1:] {
      btree.appendNode(data)
    }
  }
  return btree
}
func (bt *biTree) appendNode(data interface{}) {
  cur := bt.root
  if cur == nil {
    bt = &biTree{}
    return
  }
  Queue := []*btNode{cur}
  for len(Queue) > 0 {
    cur := Queue[0]
    Queue = Queue[1:]
    if cur.Lchild != nil {
      Queue = append(Queue, cur.Lchild)
    } else {
      cur.Lchild = &btNode{Data: data}
      return
    }
    if cur.Rchild != nil {
      Queue = append(Queue, cur.Rchild)
    } else {
      cur.Rchild = &btNode{Data: data}
      break
    }
  }
}
func main() {
  list := []interface{}{1, 2, 3, 4, 5, 6, 7, 8}
  tree := createTree(list)
  fmt.Println(tree.preOrder())
  fmt.Println(tree.inOrder())
  fmt.Println(tree.postOrder())
}
/*
[1 2 4 8 5 3 6 7]
[8 4 2 5 1 6 3 7]
[8 4 5 2 6 7 3 1]
*/

更多内容请见下集分解......


目录
相关文章
|
6月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
343 2
|
11月前
|
Go C++
Go语言方法与接收者 -《Go语言实战指南》
本文介绍了 Go 语言中方法的相关概念和用法。方法是绑定到特定类型上的函数,包含值接收者和指针接收者两种形式。值接收者不会改变原始数据,而指针接收者可修改原始数据,且在处理大型结构体时性能更优。文章详细对比了方法与普通函数的区别,并说明了选择指针接收者的原因,如修改原始值、提升性能及保持一致性。此外,Go 支持为任意自定义类型定义方法,不仅限于结构体。最后通过表格总结了方法的核心概念和使用场景。
296 34
|
11月前
|
存储 安全 Go
Map的遍历与判断键是否存在-《Go语言实战指南》
本文介绍了 Go 语言中对 `map` 的常见操作,包括遍历所有项和判断键是否存在。通过 `for range` 可以遍历 `map` 的键值对、仅键或仅值(需忽略键)。注意,`map` 遍历顺序是随机的。判断键是否存在时,使用双赋值语法 `value, ok := map[key]`,其中 `ok` 表示键是否存在。直接访问不存在的键会返回类型的零值,可能导致逻辑错误。掌握这些机制可更安全高效地处理键值对数据。
|
11月前
|
Go 开发者 索引
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣33 / 81/ 153/154)(Go语言版)
本文深入探讨了LeetCode中四道关于「搜索旋转排序数组」的经典题目,涵盖了无重复和有重复元素的情况。通过二分查找的变形应用,文章详细解析了每道题的解题思路和Go语言实现代码。关键点包括判断有序区间、处理重复元素以及如何缩小搜索范围。文章还总结了各题的异同,并推荐了类似题目,帮助读者全面掌握二分查找在旋转数组中的应用。无论是初学者还是有经验的开发者,都能从中获得实用的解题技巧和代码实现方法。
451 14
|
算法 Go
【LeetCode 热题100】深入理解二叉树结构变化与路径特性(力扣104 / 226 / 114 / 543)(Go语言版)
本博客深入探讨二叉树的深度计算、结构变换与路径分析,涵盖四道经典题目:104(最大深度)、226(翻转二叉树)、114(展开为链表)和543(二叉树直径)。通过递归与遍历策略(前序、后序等),解析每题的核心思路与实现方法。结合代码示例(Go语言),帮助读者掌握二叉树相关算法的精髓。下一讲将聚焦二叉树构造问题,欢迎持续关注!
339 10
|
存储 算法 数据可视化
【二叉树遍历入门:从中序遍历到层序与右视图】【LeetCode 热题100】94:二叉树的中序遍历、102:二叉树的层序遍历、199:二叉树的右视图(详细解析)(Go语言版)
本文详细解析了二叉树的三种经典遍历方式:中序遍历(94题)、层序遍历(102题)和右视图(199题)。通过递归与迭代实现中序遍历,深入理解深度优先搜索(DFS);借助队列完成层序遍历和右视图,掌握广度优先搜索(BFS)。文章对比DFS与BFS的思维方式,总结不同遍历的应用场景,为后续构造树结构奠定基础。
574 10
|
Go 索引 Perl
【LeetCode 热题100】【二叉树构造题精讲:前序 + 中序建树 & 有序数组构造 BST】(详细解析)(Go语言版)
本文详细解析了二叉树构造的两类经典问题:通过前序与中序遍历重建二叉树(LeetCode 105),以及将有序数组转化为平衡二叉搜索树(BST,LeetCode 108)。文章从核心思路、递归解法到实现细节逐一拆解,强调通过索引控制子树范围以优化性能,并对比两题的不同构造逻辑。最后总结通用构造套路,提供进阶思考方向,帮助彻底掌握二叉树构造类题目。
782 9
|
12月前
|
Go
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣437 / 236 )(Go语言版)
本文深入探讨二叉树中路径与祖先问题,涵盖两道经典题目:LeetCode 437(路径总和 III)和236(最近公共祖先)。对于路径总和 III,文章分析了双递归暴力解法与前缀和优化方法,后者通过哈希表记录路径和,将时间复杂度从O(n²)降至O(n)。在最近公共祖先问题中,采用后序遍历递归查找,利用“自底向上”的思路确定最近公共祖先节点。文中详细解析代码实现与核心要点,帮助读者掌握深度追踪技巧,理解树结构中路径与节点关系的本质。这类问题在面试中高频出现,掌握其解法意义重大。
283 4
|
存储 安全 Go
Go语言中的map数据结构是如何实现的?
Go 语言中的 `map` 是基于哈希表实现的键值对数据结构,支持快速查找、插入和删除操作。其原理涉及哈希函数、桶(Bucket)、动态扩容和哈希冲突处理等关键机制,平均时间复杂度为 O(1)。为了确保线程安全,Go 提供了 `sync.Map` 类型,通过分段锁实现并发访问的安全性。示例代码展示了如何使用自定义结构体和切片模拟 `map` 功能,以及如何使用 `sync.Map` 进行线程安全的操作。
495 9

热门文章

最新文章

下一篇
开通oss服务