用golang实现二叉搜索树(BST)

news2025/5/19 11:51:13

目录

  • 一、概念、性质
  • 二、二叉搜索树的实现
    • 1. 结构
    • 2. 查找
    • 3. 插入
    • 4. 删除
    • 5. 中序遍历
  • 中序前驱/后继结点

一、概念、性质

二叉搜索树(Binary Search Tree),简写BST,又称为二叉查找树

它满足:

  • 空树是一颗二叉搜索树
  • 对于任意结点 node ,它的左右孩子如果不为空 ,满足:
    • 左子树上所有结点的值都小于node的值
    • 右子树上所有结点的值都大于node的值
  • 不存在数值重复的结点

如下图,图(1)是二叉搜索树,图(2)、图(3)不是二叉搜索树
图(2)中不满足 70 < 50
图(3)中不满足 30 < 10

在这里插入图片描述


之所以叫做二叉搜索树,是因为在BST中搜索一个值是很简单的

例如图(1)中要查找40
从根结点出发,40比50小,来到根结点的左孩子30,40比30大,来到30的右孩子40,40等于40,这样就找到了

例如图(1)中要查找10
从根结点出发,10比50小,来到根节点的左孩子30,10比30小,来到30的左孩子20,10比20小,来到20的左孩子null,所以没有10这个结点


在这里插入图片描述

对图(1)进行中序遍历,得到 20 30 40 50 60
发现是一个有序的序列

所以对二叉搜索树进行中序遍历,会得到一个有序的序列

二、二叉搜索树的实现

1. 结构

定义一个二叉搜索树很简单,就和定义一个二叉树的结构一样,需要包含 数据、左右孩子指针

// 定义结点结构
type TSNode struct {
	data  int
	left  *TSNode
	right *TSNode
}

2. 查找

查找一个值target,从根结点开始,递归进行比较。

如果等于根结点,找到返回
如果小于根结点则走到左子树,在左子树中找
如果大于根结点则走到右子树,在右子树中找

// 判断结点是否存在
func (t *TSNode) Search(data int) *TSNode {
	if t == nil {
		return nil
	}
	if t.data == data { //找到了 返回结点
		return t
	}

	if data < t.data { //要找的数据小于根结点 向左找
		return t.left.Search(data)
	} else { //要找的数据大于根结点  向右找
		return t.right.Search(data)
	}
}

3. 插入

向二叉搜索树插入数据data ,分为两种情况

  • 树为空,定义一个新结点储存data,直接 根结点 = 新结点
  • 树不为空,和结点进行比较(和查找数据步骤一样),直到结点为空,将新结点插入

如向图(1)中插入数据35
在这里插入图片描述

  1. 首先和根结点50进行比较,小于50,走到左孩子30
  2. 和30进行比较,大于30,走到30的右孩子40
  3. 和40进行比较,小于40,走到40的左孩子 null,将35 插入40 左孩子

最终得到
在这里插入图片描述

// 插入数据
func (t *TSNode) Insert(data int) {
	newNode := &TSNode{
		data,
		nil,
		nil,
	}

	//如果根结点为空 直接插入
	if t == nil {
		t = newNode
		return
	}

	//根结点不为空 判断
	if data < t.data { //小于根结点数据 向左找
		if t.left == nil { //左孩子为空 直接赋值
			t.left = newNode
		} else {
			t.left.Insert(data) //左孩子不为空 遍历左孩子 找
		}
	} else {
		if t.right == nil {
			t.right = newNode
		} else {
			t.right.Insert(data)
		}
	}
}

4. 删除

删除二叉搜索树的数据分为三种情况

  • 要删除的结点没有左右孩子
  • 要删除的结点没有左孩子或右孩子
  • 要删除的结点既有左孩子也有右孩子

有这样一颗二叉搜索树,下面第一二种拿这个举例子

在这里插入图片描述

  1. 删除的结点没有左右孩子

直接删除结点就可以
例如删除上图中的25,直接删除25得到
在这里插入图片描述

  1. 要删除的结点只有左孩子或只有右孩子

如果只有右孩子,比如删除图中的60
直接让60父结点与60的右孩子连接,将60删除就可以
得到:
在这里插入图片描述
3. 要删除的结点既有左孩子也有右孩子

这里要引入中序后继中序前驱的概念,我把这两个概念放在最后了

blog.csdnimg.cn/direct/e37cc3938fe3426b925afd7c3c2237fd.png)

比如要删除30,找到30的中序后继结点或者中序前驱结点,哪个都可以,就拿中序后继结点举例子

30的中序后继结点是35,将35的值赋给30这个节点,再对30的右子树进行删除中序后继结点的操作就可以了

在这里插入图片描述

使用中序前驱结点一样,将中序前驱节点的值赋给要删除的节点的值,再对要删除的节点的左子树进行删除中序前驱节点的操作


实现删除操作的代码:

首先要找到要删除的结点, if、else if、else 就是在找要删除的结点

// 删除结点
func (t *TSNode) Delete(data int) *TSNode {
	//查找结点 -- 查找到要找的结点,分情况对结点进行删除操作
	if t == nil {
		return nil
	}

	if data < t.data { //要删除的数据小于根结点
		//递归查找左子树
		t.left = t.left.Delete(data)
	} else if data > t.data {
		//递归查找右子树
		t.right = t.right.Delete(data)
	} else { //查找到了要删除的数据
		//此时t是要删除的结点 分情况
		if t.left == nil && t.right == nil { //如果左右孩子都是空 直接删除 返回空
			return nil
		}

		//只有一个结点
		if t.left == nil { //只有一个右结点  父结点和左孩子结点相连
			return t.right
		}
		if t.right == nil { //只有一个左结点  父结点和右孩子结点相连
			return t.left
		}

		//左右孩子结点都存在
		//找到 中序后继结点 -- 右孩子一直向左找
		minNode := t.right.MinNode()
		//用这个结点替换要删除的结点
		t.data = minNode.data
		//删除中序后继结点--因为是查找的右孩子的中序后继,所以调整右子树
		t.right = t.right.Delete(minNode.data)
	}
	return t
}

// 查找中序后继结点  
func (t *TSNode) MinNode() *TSNode {
	if t.left == nil {
		return t
	}
	return t.left.MinNode()
}

5. 中序遍历

使用递归遍历,和二叉树的中序遍历一样
先递归左子树,再打印节点的值,最后递归右子树

// 中序遍历
func (t *TSNode) InOrder() {
	if t == nil {
		return
	}

	t.left.InOrder()
	fmt.Printf("%d ", t.data)
	t.right.InOrder()
}

中序前驱/后继结点

在这里插入图片描述

  • 中序后继结点:在中序遍历中紧跟在某个节点后面的节点
    怎么找中序后继结点呢?
    先走到一个结点Node 的右孩子,再从右孩子不断向左走,直到某个节点的左孩子为空,那这个结点就是Node 的中序后继结点
    比如要找30的中序后继结点,30走到40,40向左走到35,35的左孩子为空,那35就是30的后继结点

  • 中序前驱结点:在中序遍历中在某个结点前一个的结点
    怎么找中序前驱结点呢?
    先走到一个结点Node 的左孩子,再从左孩子不断向右走,直到某个节点的右孩子为空,那这个结点就是Node 的中序前驱结点
    比如要找30的中序前驱结点,30走到左孩子20,20向右走到25,25的右孩子为空,所以25就是30的中序前驱结点

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

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

相关文章

服务器防文件上传手写waf

一、waf的目录结构&#xff0c;根据自己目录情况进行修改 二、创建文件夹以及文件 sudo mkdir -p /www/server/waf-monitor sudo mkdir -p /www/server/waf-monitor/quarantine #创建文件夹 chmod 755 /www/server/waf-monitor #赋权cd /www/server/waf-monitor/touch waf-m…

计算机的基本组成与性能

1. 冯诺依曼体系结构&#xff1a;计算机组成的金字塔 1.1. 计算机的基本硬件组成 1.CPU - 中央处理器&#xff08;Central Processing Unit&#xff09;。 2.内存&#xff08;Memory&#xff09;。 3.主板&#xff08;Motherboard&#xff09;。主板的芯片组&#xff08;Ch…

linux下编写shell脚本一键编译源码

0 前言 进行linux应用层编程时&#xff0c;经常会使用重复的命令对源码进行编译&#xff0c;然后把编译生成的可执行文件拷贝到工作目录&#xff0c;操作非常繁琐且容易出错。本文编写一个简单的shell脚本一键编译源码。 1 linux下编写shell脚本一键编译源码 shell脚本如下&…

【深度学习】#12 计算机视觉

主要参考学习资料&#xff1a; 《动手学深度学习》阿斯顿张 等 著 【动手学深度学习 PyTorch版】哔哩哔哩跟李沐学AI 目录 目标检测锚框交并比&#xff08;IoU&#xff09;锚框标注真实边界框分配偏移量计算损失函数 非极大值抑制预测 多尺度目标检测单发多框检测&#xff08;S…

Baklib赋能企业知识资产AI化升级

AI驱动知识管理革新 在数字化转型浪潮中&#xff0c;企业知识管理的范式正经历AI技术的深度重构。传统知识库受限于静态存储与人工维护&#xff0c;而Baklib通过构建知识中台架构&#xff0c;将多模态数据处理与语义理解引擎深度融合&#xff0c;实现知识资产的动态聚合与智能…

【C++】模板上(泛型编程) —— 函数模板与类模板

文章目录 一、啥是泛型编程二、函数模板2.1、函数模板的概念2.2、函数模板的格式2.3、函数模板的原理2.4、函数模板的实例化2.4.1、隐式实例化&#xff1a;让编译器根据实参推演模板参数的实际类型2.4.2、显示实例化&#xff1a;在函数名后的<>中指定模板参数的实际类型 …

【大模型系列】logprobs(对数概率)参数

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

C语言内存函数与数据在内存中的存储

一、c语言内存函数 1、memcpy函数是一个标准库函数&#xff0c;用于内存复制。功能上是用来将一块内存中的内容复制到另一块内存中。用户需要提供目标地址、源地址以及要复制的字节数。例如结构体之间的复制。 memcpy函数的原型是&#xff1a;void* memcpy&#xff08;void* …

通过MCP让LLM调用系统接口

场景 MCP的出现大大丰富了LLM的功能&#xff0c;对于存量系统&#xff0c;我们希望能让模型调用已有的接口&#xff0c;以最小的成本让AI能够获取系统内部数据。因此我们开发了一个名为http-api-call的MCP Server&#xff0c;来支持模型到内部API的调用 实现方案 使用用标准…

【刚下赛场!】2025年江西省电子专题赛 - 现场制作:简易数控直流电流源原题

一、题目要求 二、赛场注意事项 1、一定要用铜柱将板子升起来&#xff0c;不然我们剪下来的引脚在测试的时候放在桌子上非常容易导致我们的板子短路&#xff08;记得把铜柱卸下来再上交作品&#xff0c;不然会被认为是做标记判0分&#xff09;&#xff1b; 2、发下来器件之后…

材料×工艺×AI:猎板PCB重构汽车电子四层板技术逻辑

一、汽车电子四层板的三大核心挑战 1. 极端环境下的可靠性保障 汽车电子需在-40℃至150℃的剧烈温变、高湿振动等环境中稳定运行。例如&#xff0c;电池管理系统&#xff08;BMS&#xff09;要求PCB在高温下阻抗漂移率低于8%&#xff0c;且镀层需具备抗腐蚀能力。猎板PCB通…

MCP(一)——QuickStart

目录 1. MCP简介2. MCP的优势3. MCP核心4. QuickStart For Server Developers(仅具参考)4.1 MCP核心概念4.2 构建MCP服务器的代码4.2.1 设置MCP服务器实例4.2.2 辅助函数4.2.3 实现工具执行4.2.4 在Cherry-Studio中添加MCP服务器4.2.5 演示4.2.5.1 测试工具get_alerts4.2.5.2 测…

Spring AOP从0到1

Spring有两大核心&#xff1a; 1、IoC 控制反转 2、AOP 面向切面编程 AOP&#xff1a;切面就是指某⼀类特定问题, 所以AOP也可以理解为面向特定⽅法编程. 引入AOP依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spri…

软考IPSEC案例分析

要回忆IPSEC点击这里 题目 5/21 某全国连锁企业的总部和分布在全国各地的30家分公司之间经常需要传输各种内部数据&#xff0c;因此公司决定在总部和各分公司之间建立VPN技术。具体拓扑如下&#xff1a; 配置部分只显示了与总部与分公司1的配置。 根据拓扑完成问题1-问题2。…

C++(23):容器类<vector>

目录 一、核心概念 二、基本语法 1. 头文件 2. 声明与初始化 三、常用操作 四、具体实例 1、size()、front()、back() 2、push_back()、pop_back()、capacity() 3、reserve&#xff08;&#xff09; 一、核心概念 Vectors 包含着一系列连续存储的元素,其行为…

Hugo 安装保姆级教程(搭建个人blog)

Hogo 安装保姆级教程 友链 参考文章&#xff1a; https://blog.csdn.net/xianyun_0355/article/details/140261279 前言 Hugo 是 Go 编写的静态网站生成器&#xff0c;速度快&#xff0c;易用&#xff0c;可配置。作为一款跨平台开源建站系统&#xff0c;当前提供 Windows&…

tomcat查看状态页及调优信息

准备工作 先准备一台已经安装好tomcat的虚拟机&#xff0c;tomcat默认是状态页是默认被禁用的 1.添加授权用户 vim /usr/local/tomcat/conf/tomcat-users.xml22 <role rolename"manager-gui"/>23 <user username"admin" password"tomcat&q…

从坏道扫描到错误修复:HD Tune实战指南

一、硬盘检测的必要性 随着计算机使用时间的增加&#xff0c;机械硬盘和固态硬盘都会出现不同程度的性能衰减。定期进行硬盘健康检查可以&#xff1a;及时发现潜在故障&#xff1b;预防数据丢失风险&#xff1b;掌握存储设备实际状态。 二、HD Tune功能解析 性能测试&#x…

将嵌入映射到 Elasticsearch 字段类型:semantic_text、dense_vector、sparse_vector

作者&#xff1a; Andre Luiz 讨论如何以及何时使用 semantic_text、dense_vector 或 sparse_vector&#xff0c;以及它们与嵌入生成的关系。 通过这个自定进度的 Search AI 实践学习亲自体验向量搜索。你可以开始免费云试用&#xff0c;或者在本地机器上尝试 Elastic。 多年来…

解决uni-app开发中的“TypeError: Cannot read property ‘0‘ of undefined“问题

问题背景 在使用uni-app开发小程序或App时&#xff0c;你可能会遇到这样一个错误: TypeError: Cannot read property 0 of undefinedat uni.promisify.adaptor.js:7这个错误看起来很唬人&#xff0c;但它实际上与uni-app框架中的Promise适配器有关。今天&#xff0c;我们将深…