使用 Go 语言实现二叉搜索树

news2025/1/18 21:02:23

原文链接: 使用 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,需要的同学可以自取。

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


源码地址:

  • https://github.com/yongxinz/go-example

推荐阅读:

  • Go 语言 select 都能做什么?
  • Go 语言 context 都能做什么?

参考文章:

  • https://flaviocopes.com/golang-data-structure-binary-search-tree/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/821665.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

刷题笔记 day4

力扣 611 有效三角形的个数 首先需要知道如何判断 三个数是否能构成三角形。 假如 存在三个数 a < b < c&#xff0c;如果要构成三角形&#xff0c;需要满足&#xff1a; ab > c ; a c > b ; b c > a ; 任意两个数大于第三个数就可构成三角形。 其实不难…

F5 LTM 知识点和实验 8-配置和管理高可用性部署

第八章:配置和管理高可用性部署 设备集群(device service clustering) 与许多关键网络和IT基础设施组件一样,BIGIP系统可以部署在高可用性(HA)配置中,以支持持续的应用程序交付,而不会在很长一段时间内中断服务。当前BIG-IP系统的HA功能主要由DSC (Device Service Clust…

实验-路由器配置静态路由

软件&#xff1a;cicso packet tracer 8.0 拓扑图&#xff1a;路由器&#xff1a;Router-PT、连接线&#xff1a;Serial DTE、连接口&#xff1a;Serial口&#xff08;serial是串行口,一般用于连接设备,不能连接电脑&#xff09; 实验步骤&#xff1a; 1、构建拓扑图&#xf…

安全基础 --- 编码(02)+ form表单实现交互

浏览器解析机制和XSS向量编码 <!-- javascript伪协议不能被urlcode编码&#xff0c;但可以被html实体编码:也是js协议的一部分&#xff0c;不能被编码js协议被解码后&#xff0c;URL解析器继续解析链接剩下的部分unicode编码可识别实现解码但符号不能被编码&#xff0c;编码…

技术应用:我有一个存放着在线SQL语句测试的收藏夹想要与你分享

&#x1f525; 技术相关&#xff1a;《技术应用》 ⛺️ I Love you, like a fire! 文章目录 &#x1f4cb; 前言&#x1f9e9; SQL Fiddle&#x1f9e9; DB-Fiddle&#x1f9e9; dbfiddle&#x1f9e9; Live SQL -&#xff08;Oracle&#xff09;&#x1f9e9; Free MySQL Host…

JVM面试题--JVM组成

JVM是什么 Java Virtual Machine Java程序的运行环境&#xff08;java二进制字节码的运行环境&#xff09; 运行流程 什么是程序计数器&#xff1f; 程序计数器&#xff1a;线程私有的&#xff0c;内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。 我们知道ja…

在腾讯云服务器OpenCLoudOS系统中安装nginx(有图详解)

1. 创建安装目录 2. 下载、安装、编译 进入安装目录&#xff1a; cd /app/soft/nginx/ 下载&#xff1a; wget https://nginx.org/download/nginx-1.21.6.tar.gz 解压&#xff1a; tar -zxvf nginx-1.21.6.tar.gz 安装插件&#xff1a; yum -y install pcre-devel 安装…

【编程语言 · C语言 · 学生管理系统】

【编程语言 C语言 学生管理系统】https://mp.weixin.qq.com/s?__bizMzg4NTE5MDAzOA&mid2247491542&idx1&snf9b72a5af62a93bc902c5467056a9343&chksmcfade32ff8da6a3956be7d6a5dceb97de27e25157804abf8a3193272fa8ad68e78640ca33a5c&token1462056111&…

Linux CentOS系统怎么下载软件

Linux CenOS系统想要下载软件可以在Linux内置的应用商店&#xff0c;并通过Yum 包管理器来下载&#xff08;直接使用yum命令下载软件&#xff09; 在Linux系统中&#xff0c;Yum&#xff08;Yellowdog Updater, Modified&#xff09;是用于管理RPM软件包的一个包管理器。 安装…

selenium2(webdriver API)—软件测试

文章目录 1.定位元素的方式1.1id1.2name1.3class name1.4link text1.5partial link name1.6tag name1.7xpath1.8css selector 2.操作测试对象2.1send_keys()2.2click()2.3submit()2.4clear()2.5text 3.等待3.1sleep()3.2implicitly_wait() 4.信息打印4.1打印title4.2打印URL 5.…

深度学习:使用全连接神经网络FCN实现MNIST手写数字识别

1 引言 本项目构建了一个全连接神经网络(FCN)&#xff0c;实现对MINST数据集手写数字的识别&#xff0c;没有借助任何深度学习算法库&#xff0c;从原理上理解手写数字识别的全过程&#xff0c;包括反向传播&#xff0c;梯度下降等。 2 全连接神经网络介绍 2.1 什么是全连接…

android studio 找不到符号类 Canvas 或者 错误: 程序包java.awt不存在

android studio开发提示 解决办法是&#xff1a; import android.graphics.Canvas; import android.graphics.Color; 而不是 //import java.awt.Canvas; //import java.awt.Color;

uniapp android底部弹框

uniapp android底部弹框&#xff0c;带有动画效果 <view class"popup_box"><view class"bottom_more" click"handleClickCancel"><image src"/static/images/rescue/icon_more.png"></image></view><…

华为OD机试真题 JavaScript 实现【小朋友排队】【2023 B卷 100分】,附详细解题思路

目录 一、题目描述二、输入描述三、输出描述四、解题思路五、JavaScript算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试&am…

触发器实现海豚调度失败企业微信自动告警

原理 触发器监控工作流实例表&#xff0c;当工作流实例表中的状态更新后&#xff0c;针对状态为失败的任务进行企业微信告警。 发送企业微信消息函数 # 必须在pg的主机上线安装requests模块 pip install requests # 以postgres用户登陆psql客户端到etl数据库 psql etl -U po…

Modbus TCP转Profinet网关modbus tcp转以太网

大家好&#xff0c;今天我们来聊一聊如何使用捷米特的Profinet转modbusTCP协议转换网关在博图上进行非透传型配置。 1, 首先&#xff0c;我们需要安装捷米特JM-TCP-PN的GSD文件&#xff0c;并根据现场设备情况配置modbusTCP地址。然后&#xff0c;在博图中添加该GSD文件&#x…

Zoho CRM数据存储在哪里?如何保障数据安全?

随着互联网的发展&#xff0c;在线CRM逐渐成为企业管理客户关系&#xff0c;提高销售效率的首选。然而&#xff0c;很多企业对于在线CRM数据的存储方式并不了解&#xff0c;担心会有数据丢失和泄露的风险。那么&#xff0c;CRM数据存储在哪里&#xff1f;安全是否有保障&#x…

【Jquery大事件时间线】jquery实现大事件时间线(时间轴)的滚动切换效果『附完整源码』

文章目录 写在前面涉及知识点页面效果1、搭建框架1.1 模块搭建1.2 内容填充1.3 时间线的切换 2、完整代码2.1 html源码2.2 CSS源码2.3 js源码 3、完整源码包下载3.1百度网盘3.2 123云盘3.3邮箱留言 总结 写在前面 其实这种大事件记录的web页面也是我们常见的&#xff0c;尤其是…

检查 CPU 的上下文切换

一.什么是cpu上下文切换 CPU 上下文切换是操作系统在多任务环境下管理进程的一项关键任务。在现代计算机系统中&#xff0c;有多个进程同时运行&#xff0c;每个进程都需要一定的 CPU 时间来执行其任务。由于 CPU 在某一时刻只能执行一个进程的指令&#xff0c;因此操作系统需…

【【STM32学习-3】】

STM32学习-3 下面是对c语言的稍微复习 这个是我们设置好的文件 以后拖出去用就可以了 这里加入关于指针的感想 关于指针数组和数组指针的想法 常规的东西是int a10; int * p&a; &#xff08;p指向了a元素&#xff0c;意思是p等于a的地址 类型是int*&#xff09;就是 整型指…