二叉排序树的增删查与遍历
关于二叉排序树的相关概念,可以查看上一篇文章——树的概念。
本文将使用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) } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 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)