树
- 线性表:1:1
- 树:1:N
- 根节点:只有后继,没有前驱
- 子结点: 既有前驱,又有后继
- 叶子结点:只有前驱,没有后继
树的定义:
- 互不相交的有限结点集合
- 只有一个根结点
树的特性:
- 只有一个前驱,可以有多个后继
- 树中可以有树(子树)
- 树可以为空,结点0
相关概念:
- 结点的度:
- 结点的直接后继个数
- 树的度:
- 先求每个结点分支数, 这些数中取max, 为“树”的度。
- 树的深度(高度):
- 树的层数。
二叉树:
- 每个结点最多两颗子树,即结点的度,不能大于 2 。可以0、1。
- 左子树、右子树 不能颠倒。(有序)
满二叉树:
- 每个结点都有 左子结点、右子结点 的 二叉树。【要求会画】
完全二叉树:
- 除最后一层外,每一层的结点数都达到最大值。(左子、右子都不缺)
- “满二叉树” 是“完全二叉树”的特例!
二叉树数据存储结构:
将一颗多叉树转化为一颗二叉树——左子右兄法:
- 兄弟连:将同层所有兄弟连成一条线 (2 兄弟的也连)
- 右子断:将所有右儿子,与父亲的边断掉。
- 45°转:将刚才连成的那条线,顺时针旋转 45°
【结论】:“兄弟”被线连起来的,都在它们的大哥右子树中,且根结点一定没有右儿子,因为根没有兄弟。
转化之前:
转化之后:
二叉树操作:
方法列表:
- 创建二叉树 Create()
- 二叉树的遍历:大前提:先左后右
- 先序遍历:根、左、右 PreOrder()
- 中序遍历:左、根、右 MideOrder()
- 后序遍历:左、右、根 PostOrder()
- 获取二叉树深度(高度):TreeHeight()
- 获取二叉树叶子结点个数:LeafNum()
- 二叉树的数据查找 Search()
- 销毁二叉树 Destroy()
- 二叉树的翻转 Reverse()
- 二叉树的拷贝 Copy()
创建二叉树 Create()
- 依据 图 创建二叉树的 各个结点。 并且初始化
- 根据图,组织结点和结点之间的层次关系。
// 创建二叉树 -- 根据图
func (node *BinTreeNode) Create() {
// 创建二叉树子节点
node1 := BinTreeNode{1, nil, nil}
node2 := BinTreeNode{2, nil, nil}
node3 := BinTreeNode{3, nil, nil}
node4 := BinTreeNode{4, nil, nil}
node5 := BinTreeNode{5, nil, nil}
node6 := BinTreeNode{6, nil, nil}
node7 := BinTreeNode{7, nil, nil}
node.Data = 0
node.Lchild = &node1
node.Rchild = &node2
node1.Lchild = &node3
node1.Rchild = &node4
node2.Lchild = &node5
node2.Rchild = &node6
node3.Lchild = &node7
}
遍历二叉树的方法
先序遍历
- DLR:先中,再左, 再右。
- 先中: 打印数据
- 再左: 左子树递归调用本函数
- 再右: 右子树递归调用本函数
- 先打印 根结点, 判断左子树是否二叉树,如果是,递归打印该二叉树的 根, 再判断左子树是否是二叉树。。。。。一直到 左子树已经是一个叶子结点。逐层返回。
// 打印二叉树 -- 先序遍历:DLR : 先中,再左,再右
func (node *BinTreeNode) PreOrder() {
if node == nil { // 递归出口
return
}
// 先中,先打印Data数据
fmt.Print(node.Data, " ")
// 再左,左子树递归调用本函数
node.Lchild.PreOrder()
// 再右,右子树递归调用本函数
node.Rchild.PreOrder()
}
中序遍历
- LDR: 先左, 再中,再右
- 先左: 左子树递归调用本函数
- 再中: 打印数据
- 再右:右子树递归调用本函数
- 先判断左子树是否可以递归进入, 如果可以递归进入,递归到叶子结点,打印左子树的叶子结点。打印中间数据,再打印右子树。
// 打印二叉树 -- 中序遍历:LDR : 先左,再中,再右
func (node *BinTreeNode) MidOrder() {
if node == nil { // 递归出口
return
}
// 先左, 左子树递归调用本数据
node.Lchild.MidOrder()
// 再中, 打印Data数据
fmt.Print(node.Data, " ")
// 再右, 右子树递归调用本函数
node.Rchild.MidOrder()
}
后序遍历
- LRD:先左, 再右, 再中
- 先左: 左子树递归调用本函数
- 再右:右子树递归调用本函数
- 再中:打印数据
- 先判断左子树是否可以递归进入, 递归到叶子结点,打印,判断右子树是否可以递归进入, 递归到叶子结点打印,最后再打印中间数据。逐层向上返回。
// 打印二叉树 -- 后序遍历:LRD: 先左,再右,再中
func (node *BinTreeNode) PostOrder() {
if node == nil {
return
}
// 先左, 左子树递归调用本函数
node.Lchild.PostOrder()
// 再右, 右子树递归调用本函数
node.Rchild.PostOrder()
// 再中, 打印data数据
fmt.Print(node.Data, " ")
}
求取二叉树高度TreeHeight()
- 容错。 递归出口,不能返回 -1.
- 左子树、右子树各自递归进入。保存各自的返回值
- 比较 左子树的返回值和 右子树的返回值。最终返回比较结果的大值。
// 获取二叉树的树高(深度)
func (node *BinTreeNode) TreeHeight() int {
if node == nil { // 递归出口
return 0 // 不能返回 -1
}
// 左子树,递归进入
lh := node.Lchild.TreeHeight()
// 右子树,递归进入
rh := node.Rchild.TreeHeight()
// 累加,比较左子树和右子树 高度.
if lh > rh {
lh++
return lh
} else {
rh++
return rh
}
}
获取二叉树的叶子结点个数 LeafNum()
-
使用指针传参
- 容错
- 判断结点的左子树 和 右子树同时为 nil 表示找到叶子结点。将 指针参数 ++
- 左子树递归进入
- 右子树递归进入
// 获取二叉树的叶子结点数 --- 指针传参 func (node *BinTreeNode) LeafNum(num *int) { if node == nil { return } // 判断是否为叶子结点. 左子树和右子树均为 nil if node.Lchild == nil && node.Rchild == nil { (*num)++ } // 左子树/右子树 各自递归进入 node.Lchild.LeafNum(num) node.Rchild.LeafNum(num) }
-
使用全局变量
- 容错
- 判断结点的左子树 和 右子树同时为 nil 表示找到叶子结点。将 全局变量 ++
- 左子树递归进入
- 右子树递归进入
// 获取二叉树的叶子结点数 --- 全局变量 var num2 = 0 func (node *BinTreeNode) LeafNum2() { if node == nil { return } if node.Lchild == nil && node.Rchild == nil { num2++ } // 左子树/右子树 各自递归进入 node.Lchild.LeafNum2() node.Rchild.LeafNum2() }
查找二叉树数据
- 容错
- 比较node.Data 和传递的数据值 类型、数值是否一致。
- reflect.TypeOf()
- reflect.DeepEqual()
- 左子树递归进入
- 右子树递归进入
var bl = false
func (node *BinTreeNode) Search(Data interface{}) {
if node == nil {
return
}
// 比较数据值是否相同,同时数据类型一致
if reflect.TypeOf(node.Data) == reflect.TypeOf(Data) && reflect.DeepEqual(node.Data, Data) {
fmt.Println("找到数据:", node.Data)
bl = true
return
}
// 左子树和右子树各自递归进入
node.Lchild.Search(Data)
node.Rchild.Search(Data)
}
二叉树翻转
- 容错
- 利用多重赋值,交互左右子树
- 左子树递归进入
- 右子树递归进入
// 二叉树翻转
func (node *BinTreeNode) Reverse() {
if node == nil {
return
}
// 左子树和右子树交换. 依赖 Go语言特有的 多重赋值
node.Lchild, node.Rchild = node.Rchild, node.Lchild
node.Lchild.Reverse()
node.Rchild.Reverse()
}
二叉树拷贝Copy()
- 容错
- 左子树递归调用copy, 保存返回值,作为左子结点数据
- 右子树递归调用copy, 保存返回值,作为右子结点数据
- 创建新结点, 初始化
- Data 域 使用当前结点 的 data赋值。 node.Data
- lChild 域 使用 上左子递归返回值 赋值。
- RChild 域 使用上 右子递归返回值 赋值。
- 返回 新结点(新树的根结点。)
// 拷贝二叉树
func (node *BinTreeNode) Copy() *BinTreeNode {
if node == nil {
return nil
}
// 左子树递归进入
lChild := node.Lchild.Copy()
// 右子树递归进入
rChild := node.Rchild.Copy()
// 创建新结点, 赋值
newNode := new(BinTreeNode)
newNode.Data = node.Data
newNode.Lchild = lChild
newNode.Rchild = rChild
return newNode
}
的根结点。)
// 拷贝二叉树
func (node *BinTreeNode) Copy() *BinTreeNode {
if node == nil {
return nil
}
// 左子树递归进入
lChild := node.Lchild.Copy()
// 右子树递归进入
rChild := node.Rchild.Copy()
// 创建新结点, 赋值
newNode := new(BinTreeNode)
newNode.Data = node.Data
newNode.Lchild = lChild
newNode.Rchild = rChild
return newNode
}