406 lines
9.3 KiB
Markdown
406 lines
9.3 KiB
Markdown
---
|
||
title: 用go实现二叉树
|
||
tags: 数据结构
|
||
categories: 数据结构, 二叉树
|
||
date: 2022-05-08 20:01:00
|
||
---
|
||
|
||
# 用go实现二叉树
|
||
|
||
> 二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小,
|
||
右节点的值要比父节点的值大。它的高度决定了它的查找效率。在理想的情况下,二叉查找树增删查改的时间复杂度为O(logN)
|
||
(其中N为节点数),最坏的情况下为O(N)。当它的高度为logN+1时,我们就说二叉查找树是平衡的。
|
||
|
||
## 二叉树的构成
|
||
|
||
因为二叉树的节点需要可比较,所以定义了一个接口,包含了比较大小的方法和是否相等的方法
|
||
|
||
```go
|
||
|
||
// 实现该接口则代表可比较
|
||
type compare interface {
|
||
// 如果大于返回true,否则返回false
|
||
compare(data interface{}) bool
|
||
// 判断是否相等
|
||
equals(data interface{}) bool
|
||
}
|
||
|
||
// 二叉树节点
|
||
type BST struct {
|
||
// 节点值
|
||
data compare
|
||
// 左节点
|
||
LeftNode *BST
|
||
// 右节点
|
||
RightNode *BST
|
||
}
|
||
|
||
// 节点值得结构体,实现了compare接口
|
||
type data struct {
|
||
key int
|
||
value interface{}
|
||
}
|
||
|
||
func (d *data) equals(d1 interface{}) bool {
|
||
if d.key == d1.(*data).key {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (d *data) compare(d1 interface{}) bool {
|
||
if d.key > d1.(*data).key {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
```
|
||
|
||
## 二叉树的插入
|
||
|
||
二叉树的插入非常简单,先判断根节点是否为空,为空则直接作为根节点,
|
||
若不为空则找到需要插入位置的父节点,从根节点开始比较,小于则向左子节点靠近,
|
||
大于则靠近右子节点,找到插入位置的父节点后,再进行比较插入就行。
|
||
|
||
```go
|
||
// insert 插入
|
||
func (b *BST) insert(content compare) {
|
||
var parent *BST
|
||
temp := b
|
||
// 先判断根节点是否为空
|
||
if temp.data == nil {
|
||
temp.data = content
|
||
return
|
||
}
|
||
// 循环遍历找到父节点
|
||
for true {
|
||
// 当节点为null时,
|
||
if temp == nil {
|
||
break
|
||
}
|
||
parent = temp
|
||
// 如果当前节点值小于插入的值,则跳向左子节点
|
||
if temp.data.compare(content) {
|
||
temp = temp.LeftNode
|
||
} else {
|
||
temp = temp.RightNode
|
||
}
|
||
}
|
||
// 比较值,进行插入赋值
|
||
if parent.data.compare(content) {
|
||
parent.LeftNode = &BST{
|
||
data: content,
|
||
LeftNode: nil,
|
||
RightNode: nil,
|
||
}
|
||
} else {
|
||
parent.RightNode = &BST{
|
||
data: content,
|
||
LeftNode: nil,
|
||
RightNode: nil,
|
||
}
|
||
}
|
||
}
|
||
|
||
func main() {
|
||
bst := NewBST()
|
||
bst.insert(&data{
|
||
key: 3,
|
||
value: "123",
|
||
})
|
||
|
||
bst.insert(&data{
|
||
key: 2,
|
||
value: "234",
|
||
})
|
||
bst.insert(&data{
|
||
key: 1,
|
||
value: "345",
|
||
})
|
||
bst.insert(&data{
|
||
key: 4,
|
||
value: "456",
|
||
})
|
||
fmt.Println(bst)
|
||
}
|
||
```
|
||
通过goland断点调试查看插入结果
|
||
|
||
## 二叉树节点查询
|
||
|
||
二叉树查询只需要依次比较大小进行判断就能找到需要的值
|
||
```go
|
||
// 查询
|
||
func (b *BST) query(node compare) compare {
|
||
temp := b
|
||
for true {
|
||
// 判断节点是否为空
|
||
if temp == nil {
|
||
return nil
|
||
}
|
||
// 判断节点的值是否匹配
|
||
if temp.data.equals(node) {
|
||
return temp.data
|
||
}
|
||
// 根据节点值得大小移动当前位置
|
||
if temp.data.compare(node) {
|
||
temp = temp.LeftNode
|
||
} else {
|
||
temp = temp.RightNode
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func main() {
|
||
bst := NewBST()
|
||
bst.insert(&data{
|
||
key: 3,
|
||
value: "123",
|
||
})
|
||
|
||
bst.insert(&data{
|
||
key: 2,
|
||
value: "234",
|
||
})
|
||
bst.insert(&data{
|
||
key: 1,
|
||
value: "345",
|
||
})
|
||
bst.insert(&data{
|
||
key: 4,
|
||
value: "456",
|
||
})
|
||
// &{4 456}
|
||
fmt.Println(bst.query(&data{key: 4}))
|
||
}
|
||
```
|
||
|
||
## 二叉树更新
|
||
更新二叉树节点得值和插入类似,依次遍历找到节点更新即可
|
||
|
||
```go
|
||
func (b *BST) update(node compare) error {
|
||
temp := b
|
||
for true {
|
||
if temp == nil {
|
||
return errors.New("not found")
|
||
}
|
||
if temp.data.equals(node) {
|
||
temp.data = node
|
||
return nil
|
||
}
|
||
|
||
if temp.data.compare(node) {
|
||
temp = temp.LeftNode
|
||
} else {
|
||
temp = temp.RightNode
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
```
|
||
|
||
## 节点遍历
|
||
节点遍历分为三种方式,分别为前序遍历,中序遍历,后续遍历
|
||
|
||
+ ### 前序遍历
|
||
前序遍历访问顺序为当前节点,左节点,右节点
|
||
我们通过递归的方式进行遍历
|
||

|
||
该二叉树我们通过前序遍历的顺序为**3 2 4 7 6 9**
|
||
|
||
```go
|
||
// 前序遍历
|
||
func (b *BST) frontItem(slice []compare) []compare {
|
||
if b.data == nil {
|
||
return nil
|
||
}
|
||
slice = append(slice, b.data)
|
||
if b.LeftNode != nil {
|
||
slice = b.LeftNode.frontItem(slice)
|
||
}
|
||
if b.RightNode != nil {
|
||
slice = b.RightNode.frontItem(slice)
|
||
}
|
||
return slice
|
||
}
|
||
```
|
||
|
||
+ ### 中序遍历
|
||
中序遍历访问顺序为当左子节点,当前节点,右子节点
|
||
前面的二叉树我们通过中序遍历的顺序为**2 3 4 7 6 9**
|
||
```go
|
||
// 中序遍历
|
||
func (b *BST) midItem(slice []compare) []compare {
|
||
if b.data == nil {
|
||
return nil
|
||
}
|
||
if b.LeftNode != nil {
|
||
slice = b.LeftNode.frontItem(slice)
|
||
}
|
||
slice = append(slice, b.data)
|
||
if b.RightNode != nil {
|
||
slice = b.RightNode.frontItem(slice)
|
||
}
|
||
return slice
|
||
}
|
||
```
|
||
|
||
+ ### 后序遍历
|
||
后序遍历访问顺序为左节点,右节点,当前节点
|
||
前面二叉树我们通过后续遍历访问顺序为**2 6 9 7 4 3**
|
||
|
||
```go
|
||
// 后序遍历
|
||
func (b *BST) rearItem(slice []compare) []compare {
|
||
if b.data == nil {
|
||
return nil
|
||
}
|
||
if b.LeftNode != nil {
|
||
slice = b.LeftNode.rearItem(slice)
|
||
}
|
||
if b.RightNode != nil {
|
||
slice = b.RightNode.rearItem(slice)
|
||
}
|
||
slice = append(slice, b.data)
|
||
return slice
|
||
}
|
||
```
|
||
|
||
## 二叉树删除
|
||
|
||
二叉树得删除分为三种情况
|
||
|
||
+ 节点左右子节点都为空
|
||
|
||
该情况只需要将该节点从父节点的指向删除即可
|
||
|
||
|
||
+ 节点的子节点有一个为空
|
||
|
||
该情况需要将该节点的唯一子节点根据大小连接向该节点的父节点
|
||
从该二叉树中删除节点6,只需要将节点7的左子节点指向5即可
|
||

|
||
|
||
|
||
+ 节点的左右子节点都存在
|
||
|
||
该情况需要进行节点交换,需要先获取到当前节点的右子节点的中序遍历的第一个节点,然后交换两个节点的值,如果该右子节点的前序遍历
|
||
首节点存在右子节点,则继续获取并交换,知道将该节点交换到叶子节点。
|
||
|
||

|
||
|
||
若将该二叉树的节点7删除,先需要获取右子节点的中序遍历守节点,因为节点9不存在左子节点,所以该节点就为9,我们就将节点9与节点7
|
||
进行交换,因为仍不为叶子节点,所以继续获取,将节点11与节点7交换,最终交换结果为:
|
||

|
||
我们直接将该叶子节点删除即可
|
||
|
||
```go
|
||
// 删除节点
|
||
func (b *BST) delete(node compare) error {
|
||
temp := b
|
||
if temp == nil {
|
||
return errors.New("root")
|
||
}
|
||
isRoot := false
|
||
var parent *BST
|
||
// 获取要删除节点的位置和父节点位置
|
||
for true {
|
||
// 判断节点是否为根节点
|
||
if b.data.equals(node) {
|
||
isRoot = true
|
||
break
|
||
}
|
||
// 判断节点的值的大小
|
||
if temp.data.compare(node) {
|
||
if temp.LeftNode == nil {
|
||
return errors.New("not found")
|
||
}
|
||
if temp.LeftNode.data.equals(node) {
|
||
parent = temp
|
||
temp = temp.LeftNode
|
||
break
|
||
} else {
|
||
temp = temp.LeftNode
|
||
}
|
||
} else {
|
||
if temp.RightNode == nil {
|
||
return errors.New("not found")
|
||
}
|
||
if temp.RightNode.data.equals(node) {
|
||
parent = temp
|
||
temp = temp.RightNode
|
||
break
|
||
} else {
|
||
temp = temp.RightNode
|
||
}
|
||
}
|
||
}
|
||
|
||
// case1: 删除节点即无左节点也无右节点
|
||
if temp.LeftNode == nil && temp.RightNode == nil {
|
||
if parent.data.compare(node) {
|
||
parent.LeftNode = nil
|
||
} else {
|
||
parent.RightNode = nil
|
||
}
|
||
return nil
|
||
|
||
// case2: 删除节点有左节点无右节点
|
||
} else if temp.LeftNode != nil && temp.RightNode == nil {
|
||
|
||
if isRoot {
|
||
b.data = temp.LeftNode.data
|
||
b.LeftNode = temp.LeftNode.LeftNode
|
||
b.RightNode = temp.LeftNode.RightNode
|
||
return nil
|
||
}
|
||
|
||
if parent.LeftNode == temp {
|
||
parent.LeftNode = temp.LeftNode
|
||
} else {
|
||
parent.RightNode = temp.LeftNode
|
||
}
|
||
return nil
|
||
|
||
// case3: 删除节点有右节点无左节点
|
||
} else if temp.LeftNode == nil && temp.RightNode != nil {
|
||
if isRoot {
|
||
b.data = temp.RightNode.data
|
||
b.LeftNode = temp.RightNode.LeftNode
|
||
b.RightNode = temp.RightNode.RightNode
|
||
return nil
|
||
}
|
||
if parent.LeftNode == temp {
|
||
parent.LeftNode = temp.RightNode
|
||
} else {
|
||
parent.RightNode = temp.RightNode
|
||
}
|
||
return nil
|
||
|
||
// case3: 删除节点有右节点有左节点
|
||
} else {
|
||
for temp.RightNode != nil {
|
||
firstMidNode := temp.RightNode.firstMidNode()
|
||
firstMidNode.data, temp.data = temp.data, firstMidNode.data
|
||
temp = firstMidNode
|
||
}
|
||
return b.delete(node)
|
||
}
|
||
}
|
||
|
||
// 获取中序遍历第一个节点
|
||
func (b *BST) firstMidNode() *BST {
|
||
temp := b
|
||
for true {
|
||
if temp.LeftNode == nil {
|
||
return temp
|
||
}
|
||
temp = temp.LeftNode
|
||
}
|
||
return nil
|
||
}
|
||
```
|
||
|
||
## 个人博客 https://shhy.xyz |