原文链接: 使用 Go 语言实现二叉搜索树

二叉树是一种常见并且非常重要的数据结构,在很多项目中都能看到二叉树的身影。

它有很多变种,比如红黑树,常被用作 std::mapstd::set 的底层实现;B 树和 B+ 树,广泛应用于数据库系统中。

本文要介绍的二叉搜索树用的也很多,比如在开源项目 go-zero 中,就被用来做路由管理。

这篇文章也算是一篇前导文章,介绍一些必备知识,下一篇再来介绍具体在 go-zero 中的应用。

二叉搜索树的特点

最重要的就是它的有序性,在二叉搜索树中,每个节点的值都大于其左子树中的所有节点的值,并且小于其右子树中的所有节点的值。

这意味着通过二叉搜索树可以快速实现对数据的查找和插入。

Go 语言实现

本文主要实现了以下几种方法:

  • Insert(t):插入一个节点
  • Search(t):判断节点是否在树中
  • InOrderTraverse():中序遍历
  • PreOrderTraverse():前序遍历
  • PostOrderTraverse():后序遍历
  • Min():返回最小值
  • Max():返回最大值
  • Remove(t):删除一个节点
  • String():打印一个树形结构

下面分别来介绍,首先定义一个节点:

type Node struct {
key int
value Item
left *Node //left
right *Node //right
}

定义树的结构体,其中包含了锁,是线程安全的:

type ItemBinarySearchTree struct {
root *Node
lock sync.RWMutex
}

插入操作:

func (bst *ItemBinarySearchTree) Insert(key int, value Item) {
bst.lock.Lock()
defer bst.lock.Unlock()
n := &Node{key, value, nil, nil}
if bst.root == nil {
bst.root = n
} else {
insertNode(bst.root, n)
}
} // internal function to find the correct place for a node in a tree
func insertNode(node, newNode *Node) {
if newNode.key < node.key {
if node.left == nil {
node.left = newNode
} else {
insertNode(node.left, newNode)
}
} else {
if node.right == nil {
node.right = newNode
} else {
insertNode(node.right, newNode)
}
}
}

在插入时,需要判断插入节点和当前节点的大小关系,保证搜索树的有序性。

中序遍历:

func (bst *ItemBinarySearchTree) InOrderTraverse(f func(Item)) {
bst.lock.RLock()
defer bst.lock.RUnlock()
inOrderTraverse(bst.root, f)
} // internal recursive function to traverse in order
func inOrderTraverse(n *Node, f func(Item)) {
if n != nil {
inOrderTraverse(n.left, f)
f(n.value)
inOrderTraverse(n.right, f)
}
}

前序遍历:

func (bst *ItemBinarySearchTree) PreOrderTraverse(f func(Item)) {
bst.lock.Lock()
defer bst.lock.Unlock()
preOrderTraverse(bst.root, f)
} // internal recursive function to traverse pre order
func preOrderTraverse(n *Node, f func(Item)) {
if n != nil {
f(n.value)
preOrderTraverse(n.left, f)
preOrderTraverse(n.right, f)
}
}

后序遍历:

func (bst *ItemBinarySearchTree) PostOrderTraverse(f func(Item)) {
bst.lock.Lock()
defer bst.lock.Unlock()
postOrderTraverse(bst.root, f)
} // internal recursive function to traverse post order
func postOrderTraverse(n *Node, f func(Item)) {
if n != nil {
postOrderTraverse(n.left, f)
postOrderTraverse(n.right, f)
f(n.value)
}
}

返回最小值:

func (bst *ItemBinarySearchTree) Min() *Item {
bst.lock.RLock()
defer bst.lock.RUnlock()
n := bst.root
if n == nil {
return nil
}
for {
if n.left == nil {
return &n.value
}
n = n.left
}
}

由于树的有序性,想要得到最小值,一直向左查找就可以了。

返回最大值:

func (bst *ItemBinarySearchTree) Max() *Item {
bst.lock.RLock()
defer bst.lock.RUnlock()
n := bst.root
if n == nil {
return nil
}
for {
if n.right == nil {
return &n.value
}
n = n.right
}
}

查找节点是否存在:

func (bst *ItemBinarySearchTree) Search(key int) bool {
bst.lock.RLock()
defer bst.lock.RUnlock()
return search(bst.root, key)
} // internal recursive function to search an item in the tree
func search(n *Node, key int) bool {
if n == nil {
return false
}
if key < n.key {
return search(n.left, key)
}
if key > n.key {
return search(n.right, key)
}
return true
}

删除节点:

func (bst *ItemBinarySearchTree) Remove(key int) {
bst.lock.Lock()
defer bst.lock.Unlock()
remove(bst.root, key)
} // internal recursive function to remove an item
func remove(node *Node, key int) *Node {
if node == nil {
return nil
}
if key < node.key {
node.left = remove(node.left, key)
return node
}
if key > node.key {
node.right = remove(node.right, key)
return node
}
// key == node.key
if node.left == nil && node.right == nil {
node = nil
return nil
}
if node.left == nil {
node = node.right
return node
}
if node.right == nil {
node = node.left
return node
}
leftmostrightside := node.right
for {
//find smallest value on the right side
if leftmostrightside != nil && leftmostrightside.left != nil {
leftmostrightside = leftmostrightside.left
} else {
break
}
}
node.key, node.value = leftmostrightside.key, leftmostrightside.value
node.right = remove(node.right, node.key)
return node
}

删除操作会复杂一些,分三种情况来考虑:

  1. 如果要删除的节点没有子节点,只需要直接将父节点中,指向要删除的节点指针置为 nil 即可
  2. 如果删除的节点只有一个子节点,只需要更新父节点中,指向要删除节点的指针,让它指向删除节点的子节点即可
  3. 如果删除的节点有两个子节点,我们需要找到这个节点右子树中的最小节点,把它替换到要删除的节点上。然后再删除这个最小节点,因为最小节点肯定没有左子节点,所以可以应用第二种情况删除这个最小节点即可

最后是一个打印树形结构的方法,在实际项目中其实并没有实际作用:

func (bst *ItemBinarySearchTree) String() {
bst.lock.Lock()
defer bst.lock.Unlock()
fmt.Println("------------------------------------------------")
stringify(bst.root, 0)
fmt.Println("------------------------------------------------")
} // internal recursive function to print a tree
func stringify(n *Node, level int) {
if n != nil {
format := ""
for i := 0; i < level; i++ {
format += " "
}
format += "---[ "
level++
stringify(n.left, level)
fmt.Printf(format+"%d\n", n.key)
stringify(n.right, level)
}
}

单元测试

下面是一段测试代码:

func fillTree(bst *ItemBinarySearchTree) {
bst.Insert(8, "8")
bst.Insert(4, "4")
bst.Insert(10, "10")
bst.Insert(2, "2")
bst.Insert(6, "6")
bst.Insert(1, "1")
bst.Insert(3, "3")
bst.Insert(5, "5")
bst.Insert(7, "7")
bst.Insert(9, "9")
} func TestInsert(t *testing.T) {
fillTree(&bst)
bst.String() bst.Insert(11, "11")
bst.String()
} // isSameSlice returns true if the 2 slices are identical
func isSameSlice(a, b []string) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
} func TestInOrderTraverse(t *testing.T) {
var result []string
bst.InOrderTraverse(func(i Item) {
result = append(result, fmt.Sprintf("%s", i))
})
if !isSameSlice(result, []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"}) {
t.Errorf("Traversal order incorrect, got %v", result)
}
} func TestPreOrderTraverse(t *testing.T) {
var result []string
bst.PreOrderTraverse(func(i Item) {
result = append(result, fmt.Sprintf("%s", i))
})
if !isSameSlice(result, []string{"8", "4", "2", "1", "3", "6", "5", "7", "10", "9", "11"}) {
t.Errorf("Traversal order incorrect, got %v instead of %v", result, []string{"8", "4", "2", "1", "3", "6", "5", "7", "10", "9", "11"})
}
} func TestPostOrderTraverse(t *testing.T) {
var result []string
bst.PostOrderTraverse(func(i Item) {
result = append(result, fmt.Sprintf("%s", i))
})
if !isSameSlice(result, []string{"1", "3", "2", "5", "7", "6", "4", "9", "11", "10", "8"}) {
t.Errorf("Traversal order incorrect, got %v instead of %v", result, []string{"1", "3", "2", "5", "7", "6", "4", "9", "11", "10", "8"})
}
} func TestMin(t *testing.T) {
if fmt.Sprintf("%s", *bst.Min()) != "1" {
t.Errorf("min should be 1")
}
} func TestMax(t *testing.T) {
if fmt.Sprintf("%s", *bst.Max()) != "11" {
t.Errorf("max should be 11")
}
} func TestSearch(t *testing.T) {
if !bst.Search(1) || !bst.Search(8) || !bst.Search(11) {
t.Errorf("search not working")
}
} func TestRemove(t *testing.T) {
bst.Remove(1)
if fmt.Sprintf("%s", *bst.Min()) != "2" {
t.Errorf("min should be 2")
}
}

上文中的全部源码都是经过测试的,可以直接运行,并且已经上传到了 GitHub,需要的同学可以自取。

以上就是本文的全部内容,如果觉得还不错的话欢迎点赞转发关注,感谢支持。


源码地址:

推荐阅读:

参考文章:

使用 Go 语言实现二叉搜索树的更多相关文章

  1. 二叉搜索树(Binary Search Tree)--C语言描述(转)

    图解二叉搜索树概念 二叉树呢,其实就是链表的一个二维形式,而二叉搜索树,就是一种特殊的二叉树,这种二叉树有个特点:对任意节点而言,左孩子(当然了,存在的话)的值总是小于本身,而右孩子(存在的话)的值总 ...

  2. 小白专场-是否同一颗二叉搜索树-python语言实现

    目录 一.二叉搜索树的相同判断 二.问题引入 三.举例分析 四.方法探讨 4.1 中序遍历 4.2 层序遍历 4.3 先序遍历 4.4 后序遍历 五.总结 六.代码实现 一.二叉搜索树的相同判断 二叉 ...

  3. 小白专场-是否同一颗二叉搜索树-c语言实现

    目录 一.题意理解 二.求解思路 三.搜索树表示 程序框架搭建 3.1 如何建搜索树 3.2 如何判别 3.3 清空树 更新.更全的<数据结构与算法>的更新网站,更有python.go.人 ...

  4. Go语言实现:【剑指offer】二叉搜索树的第k个的结点

    该题目来源于牛客网<剑指offer>专题. 给定一棵二叉搜索树,请找出其中的第k小的结点.例如,(5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4. Go语言实现: ...

  5. Go语言实现:【剑指offer】二叉搜索树与双向链表

    该题目来源于牛客网<剑指offer>专题. 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表.要求不能创建任何新的结点,只能调整树中结点指针的指向. Go语言实现: type T ...

  6. Go语言实现:【剑指offer】二叉搜索树的后序遍历序列

    该题目来源于牛客网<剑指offer>专题. 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. Go ...

  7. 二叉搜索树 C语言实现

    1.二叉搜索树基本概念 二叉搜索树又称二叉排序树,它或者是一棵空树,或者是一棵具有如下特性的非空二叉树: (1)若它的左子树非空,则左子树上所有结点的关键字均小于根结点的关键字: (2)若它的右子树非 ...

  8. 二叉搜索树BST(C语言实现可用)

    1:概述 搜索树是一种可以进行插入,搜索,删除等操作的数据结构,可以用作字典或优先级队列.二叉搜索树是最简单的搜索树.其左子树的键值<=根节点的键值,右子树的键值>=根节点的键值. 如果共 ...

  9. 《数据结构与算法分析——C语言描述》ADT实现(NO.03) : 二叉搜索树/二叉查找树(Binary Search Tree)

    二叉搜索树(Binary Search Tree),又名二叉查找树.二叉排序树,是一种简单的二叉树.它的特点是每一个结点的左(右)子树各结点的元素一定小于(大于)该结点的元素.将该树用于查找时,由于二 ...

  10. 98. 验证二叉搜索树 前序遍历解法以及后续遍历解法(go语言)

    leetcode题目 98. 验证二叉搜索树 前序遍历 最简洁的答案版本,由于先判断的是根节点,所以直接判断当前root的值v,是否满足大于左子树最大,小于右子树最小,然后再遍历左子树,右子树是否是这 ...

随机推荐

  1. Python-HwTestReport的简单使用

    一.工具包下载 https://github.com/hongweifuture/HwTestReport(出自此大神) 二.使用示例(直接上代码) 1.将 HwTestReport.py 导入项目 ...

  2. MQTT.fx的安装和使用

    一.下载和安装 MQTT.fx支持Windows/Linux/Mac,附下载地址:http://www.jensd.de/apps/mqttfx/,下载完成之后双击进行安装. 二.配置使用 打开软件, ...

  3. AutoGPT:有手就会的安装教程

    AutoGPT 是什么 Auto-GPT 是一个实验性开源应用程序,展示了 GPT-4 语言模型的功能.该程序由 GPT-4 驱动,将 LLM 的"思想"链接在一起,以自主实现您设 ...

  4. Linux xfs文件系统stat命令Birth字段为空的原因探究

    在Linux平台找出某个目录下创建时间最早的文件,测试验证脚本结果是否准确的过程中发现一个很有意思的现象,stat命令在一些平台下Birth字段有值,而在一些平台则为空值,如下所示: RHEL 8.7 ...

  5. 2022-07-06:以下go语言代码是否会panic?A:会;B:不会。 package main import “C“ func main() { var ch chan struct

    2022-07-06:以下go语言代码是否会panic?A:会:B:不会. package main import "C" func main() { var ch chan st ...

  6. 百度飞桨(PaddlePaddle) - PP-OCRv3 文字检测识别系统 Paddle Inference 模型推理

    Paddle Inference 模型推理流程 分别介绍文字检测.方向分类器和文字识别3个模型,基于Paddle Inference的推理过程. Paddle Inference 的 Python 离 ...

  7. 【HDU】1559 最大子矩阵 (二维前缀和,动态规划)

    动态规划之二维前缀和 题目 给你一个m×n的整数矩阵,在上面找一个x×y的子矩阵,使子矩阵中所有元素的和最大. 输入 输入数据的第一行为一个正整数T,表示有T组测试数据.每一组测试数据的第一行为四个正 ...

  8. 使用Cordova插件实现两个app之间的相互调用和通讯

    几年前使用Cordova 进行两个app之间的相互调用和通讯:当时也是几经折腾,今天把它整理出来,理一下思路,也方便有同样需求的朋友参考 一.require引入 plugin require(&quo ...

  9. MySQL-DQL

    准备测试表,先跟着执行下面的SQL #1.登录MySQL后 #2.创建test_database数据库,不存在则创建 create database if not exists test_databa ...

  10. OCR -- 文本识别 -- 实践篇

    OCR -- 文本识别 -- 理论篇 本章将详细介绍如何基于PaddleOCR完成CRNN文本识别模型的搭建.训练.评估和预测.数据集采用 icdar 2015,其中训练集有4468张,测试集有207 ...