二叉排序树的增删查与遍历

关于二叉排序树的相关概念,可以查看上一篇文章——树的概念
本文将使用Go语言代码实现二叉排序树的增删查操作,同时以它为例,实现二叉树的前、中、后序遍历与层次遍历等操作。

一、二叉排序树的结构定义

1. 结构定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 二叉排序树节点结构
**/
type SearchBinTreeNode struct {
    data  int                //数据
    left  *SearchBinTreeNode //左节点
    right *SearchBinTreeNode //右节点
}
 
/**
* 二叉排序树结构
**/
type SearchBinTree struct {
    Root *SearchBinTreeNode //树根节点
}

2. 创建
支持创建空树,也可使用数组创建已初始化数据的二叉排序树,其中Insert()和Insert2()方法分别为插入操作的递归算法与非递归算法,下文将会有介绍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 创建空二叉排序树
**/
func NewSearchBinTree() *SearchBinTree {
    return &SearchBinTree{}
}
 
/**
* 使用切片创建二叉排序树
**/
func MakeSearchBinTree(slice []int) *SearchBinTree {
    sbt := NewSearchBinTree()
    for _, data := range slice {
        sbt.Insert(sbt.Root, data)
        //sbt.Insert2(data)
    }
    return sbt
}

 

二、二叉排序树的增删查操作
1. 插入与查找
插入操作与查找操作都可分别使用递归算法与非递归算法实现,但原理是一样的,都是利用二叉排序树的有序性查找到要插入或查找数据的节点位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* 插入(递归)
**/
func (sbt *SearchBinTree) Insert(root *SearchBinTreeNode, data int) bool {
    if sbt.Root == nil {
        sbt.Root = &SearchBinTreeNode{data: data}
        return true
    }
    if data <= root.data {
        if root.left == nil {
            root.left = &SearchBinTreeNode{data: data}
            return true
        } else {
            return sbt.Insert(root.left, data)
        }
    } else {
        if root.right == nil {
            root.right = &SearchBinTreeNode{data: data}
            return true
        } else {
            return sbt.Insert(root.right, data)
        }
    }
}
 
/**
* 插入(非递归)
**/
func (sbt *SearchBinTree) Insert2(data int) bool {
    ptr := sbt.Root
    var parent *SearchBinTreeNode = nil
    for ptr != nil {
        parent = ptr
        if data <= ptr.data {
            ptr = ptr.left
        } else {
            ptr = ptr.right
        }
    }
    ptr = &SearchBinTreeNode{data: data}
    if parent == nil {
        sbt.Root = ptr
    } else {
        if data <= parent.data {
            parent.left = ptr
        } else {
            parent.right = ptr
        }
    }
    return true
}
 
/**
* 查找(递归)
**/
func (sbt *SearchBinTree) Search(root *SearchBinTreeNode, data int) *SearchBinTreeNode {
    if root == nil || root.data == data {
        return root
    }
    if data < root.data {
        return sbt.Search(root.left, data)
    } else {
        return sbt.Search(root.right, data)
    }
}
 
/**
* 查找(非递归)
**/
func (sbt *SearchBinTree) Search2(data int) *SearchBinTreeNode {
    if sbt.Root == nil || sbt.Root.data == data {
        return sbt.Root
    }
    ptr := sbt.Root
    for ptr != nil {
        if data == ptr.data {
            break
        } else if data < ptr.data {
            ptr = ptr.left
        } else {
            ptr = ptr.right
        }
    }
    return ptr
}

2. 删除
删除二叉排序树中的某个数据,需要先找到要删除数据的位置,然后分以下4中情况分别采取不同的删除方法:
(1)要删除数据为叶子节点
(2)要删除数据只有左子树
(3)要删除数据只有右子树
(4)要删除数据既有左子树又有右子树
其中前面三种情况都比较简单,第四种比较复杂,可以使用当前要删除节点的左子树中的最大数据(左子树中序遍历排在最右边的数据)来替换当前数据,也可使用右子树中的最小数据(右子树中序遍历排在最左边的数据)来替换当前数据,下面代码使用的是第一种方法。
另外还需注意一些边界情况的处理,例如,当要删除的数据是树根节点的时候;第4中情况中要删除节点的左子树或右子树为叶子节点的时候。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 删除
**/
func (sbt *SearchBinTree) Remove(data int) bool {
    parent, ptr := sbt.Root, sbt.Root
    for ptr != nil { //找到要删除数据的位置
        if data == ptr.data {
            break
        } else if data < ptr.data {
            parent, ptr = ptr, ptr.left
        } else {
            parent, ptr = ptr, ptr.right
        }
    }
    if ptr == nil { //要删除数据不存在
        return false
    }
    if ptr.left == nil && ptr.right == nil { //要删除数据为叶子节点
        if parent == ptr { //要删除数据为根节点
            sbt.Root = nil
        } else if ptr.data < parent.data {
            parent.left = nil
        } else {
            parent.right = nil
        }
    } else if ptr.left != nil && ptr.right == nil { //要删除数据只有左子树
        if parent == ptr {
            sbt.Root = ptr.left
        } else if ptr.data < parent.data {
            parent.left = ptr.left
        } else {
            parent.right = ptr.left
        }
    } else if ptr.left == nil && ptr.right != nil { //要删除数据只有右子树
        if parent == ptr {
            sbt.Root = ptr.right
        } else if ptr.data < parent.data {
            parent.left = ptr.right
        } else {
            parent.right = ptr.right
        }
    } else { //要删除数据左右子树都不为空,找左子树的最大数据替换当前要删除数据
        lparent, lptr := ptr.left, ptr.left
        for lptr.right != nil { //找到左子树中最右边的数据
            lparent, lptr = lptr, lptr.right
        }
        lptr.right = ptr.right
        if lparent != lptr { //若是要删除数据的直接左节点的话,无需赋值
            lptr.left = ptr.left
            lparent.right = nil
        }
        if parent == ptr { //要删除数据为根节点
            sbt.Root = lptr
        } else if ptr.data < parent.data {
            parent.left = lptr
        } else {
            parent.right = lptr
        }
    }
    return true
}

 

三、二叉树的遍历
1. 二叉树前、中、后序遍历递归算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 前序遍历(递归)
**/
func (sbt *SearchBinTree) PreOrder(root *SearchBinTreeNode) {
    if root == nil {
        return
    }
    fmt.Print(root.data, " ")
    sbt.PreOrder(root.left)
    sbt.PreOrder(root.right)
}
/**
* 中序遍历(递归)
**/
func (sbt *SearchBinTree) InOrder(root *SearchBinTreeNode) {
    if root == nil {
        return
    }
    sbt.InOrder(root.left)
    fmt.Print(root.data, " ")
    sbt.InOrder(root.right)
}
/**
* 后序遍历(递归)
**/
func (sbt *SearchBinTree) PostOrder(root *SearchBinTreeNode) {
    if root == nil {
        return
    }
    sbt.PostOrder(root.left)
    sbt.PostOrder(root.right)
    fmt.Print(root.data, " ")
}

2. 二叉树前、中、后序遍历非递归算法
二叉树前、中、后序遍历非递归算法稍微比递归算法复杂一点,需要借助栈结构进行实现,其中较为复杂的为后序遍历,每个节点都需要进出栈两次,并且需要标记数据是第一次出栈还是第二次出栈,所以栈中数据结构需要对二叉树节点结构进行多一层封装,存储进栈标识。以下为使用数组来实现栈的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 数组栈元素结构
**/
type asNode struct {
    sbtNode *SearchBinTreeNode
    flag    int //进栈标识,后序遍历非递归算法需要,1:第一次进栈,2:第二次进栈
}
 
/**
* 数组栈结构
**/
type ArrayStack struct {
    slice []*asNode
    head  int
}
 
/**
* 创建空栈
**/
func NewArrayStack() *ArrayStack {
    return &ArrayStack{
        slice: make([]*asNode, 10),
        head:  -1,
    }
}
 
/**
* 判断栈是否为空
**/
func (as *ArrayStack) IsEmpty() bool {
    return as.head == -1
}
 
/**
* 入栈
**/
func (as *ArrayStack) Push(data *asNode) bool {
    as.head++
    if as.head+1 <= len(as.slice) {
        as.slice[as.head] = data
    } else {
        as.slice = append(as.slice, data)
    }
    return true
}
 
/**
* 出栈
**/
func (as *ArrayStack) Pop() (*asNode, error) {
    if as.head == -1 {
        return nil, errors.New("栈为空")
    }
    data := as.slice[as.head]
    as.head--
    return data, nil
}

有了栈以后,就可以使用它来实现二叉树前、中、后序遍历非递归算法了。其中前序遍历有两种实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* 前序遍历(非递归)
**/
func (sbt *SearchBinTree) PreOrder2() {
    //方法一
    if sbt.Root == nil {
        return
    }
    as := NewArrayStack()
    ptr := sbt.Root
    for !as.IsEmpty() || ptr != nil {
        for ptr != nil {
            fmt.Print(ptr.data, " ")
            nod := &asNode{sbtNode: ptr}
            as.Push(nod)
            ptr = ptr.left
        }
        if !as.IsEmpty() {
            nod, _ := as.Pop()
            ptr = nod.sbtNode.right
        }
    }
    //方法二
    /*
        if sbt.Root == nil {
            return
        }
        as := NewArrayStack()
        nod := &asNode{sbtNode: sbt.Root}
        as.Push(nod)
        for !as.IsEmpty() {
            nod, _ := as.Pop()
            ptr := nod.sbtNode
            fmt.Print(ptr.data, " ")
            if ptr.right != nil {
                nod := &asNode{sbtNode: ptr.right}
                as.Push(nod)
            }
            if ptr.left != nil {
                nod := &asNode{sbtNode: ptr.left}
                as.Push(nod)
            }
        }
    */
}
/**
* 中序遍历(非递归)
**/
func (sbt *SearchBinTree) InOrder2(root *SearchBinTreeNode) {
    if sbt.Root == nil {
        return
    }
    as := NewArrayStack()
    ptr := sbt.Root
    for !as.IsEmpty() || ptr != nil {
        for ptr != nil {
            nod := &asNode{sbtNode: ptr}
            as.Push(nod)
            ptr = ptr.left
        }
        if !as.IsEmpty() {
            nod, _ := as.Pop()
            ptr = nod.sbtNode
            fmt.Print(ptr.data, " ")
            ptr = ptr.right
        }
    }
}
/**
* 后序遍历(非递归)
**/
func (sbt *SearchBinTree) PostOrder2() {
    if sbt.Root == nil {
        return
    }
    as := NewArrayStack()
    ptr := sbt.Root
    for !as.IsEmpty() || ptr != nil {
        for ptr != nil {
            nod := &asNode{sbtNode: ptr, flag: 1}
            as.Push(nod)
            ptr = ptr.left
        }
        if !as.IsEmpty() {
            nod, _ := as.Pop()
            if nod.flag == 1 {
                nod.flag = 2
                as.Push(nod)
                ptr = nod.sbtNode.right
            } else {
                fmt.Print(nod.sbtNode.data, " ")
                ptr = nil
            }
        }
    }
}

3. 二叉树的层次遍历
层次遍历需要借助队列结构进行实现。
队列实现代码(使用链表实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* 链表队列节点结构
**/
type node struct {
    data *SearchBinTreeNode
    next *node
}
 
/**
* 链表队列结构
**/
type ListQueue struct {
    head *node
    tail *node
}
 
/**
* 创建空队列
**/
func NewListQueue() *ListQueue {
    nod := &node{next: nil}
    return &ListQueue{
        head: nod,
        tail: nod,
    }
}
 
/**
* 判断队列是否为空
**/
func (lq *ListQueue) IsEmpty() bool {
    return lq.head == lq.tail
}
 
/**
* 入列
**/
func (lq *ListQueue) Push(data *SearchBinTreeNode) bool {
    nod := node{data: data, next: nil}
    lq.tail.next = &nod
    lq.tail = &nod
    return true
}
 
/**
* 出列
**/
func (lq *ListQueue) Pop() (*SearchBinTreeNode, error) {
    if lq.head.next == nil {
        return nil, errors.New("队列为空")
    }
    data := lq.head.next.data
    lq.head.next = lq.head.next.next
    if lq.head.next == nil {
        lq.tail = lq.head
    }
    return data, nil
}

层次遍历代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 层次遍历
**/
func (sbt *SearchBinTree) LevelOrder() {
    if sbt.Root == nil {
        return
    }
    lq := NewListQueue()
    lq.Push(sbt.Root)
    for !lq.IsEmpty() {
        sbtNode, _ := lq.Pop()
        fmt.Print(sbtNode.data, " ")
        if sbtNode.left != nil {
            lq.Push(sbtNode.left)
        }
        if sbtNode.right != nil {
            lq.Push(sbtNode.right)
        }
    }
}
posted @   疯一样的狼人  阅读(309)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示