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

目录

  • 一、概念、性质
  • 二、二叉搜索树的实现
    • 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  intleft  *TSNoderight *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 = newNodereturn}//根结点不为空 判断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.pswp.cn/news/906060.shtml

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

相关文章

自动化:批量文件重命名

自动化&#xff1a;批量文件重命名 1、前言 2、效果图 3、源码 一、前言 今天来分享一款好玩的自动化脚&#xff1a;批量文件重命名 有时候呢&#xff0c;你的文件被下载下来文件名都是乱七八糟毫无规律&#xff0c;但是当时你下载的时候没办法重名或者你又不想另存为重新重…

VueUse/Core:提升Vue开发效率的实用工具库

文章目录 引言什么是VueUse/Core&#xff1f;为什么选择VueUse/Core&#xff1f;核心功能详解1. 状态管理2. 元素操作3. 实用工具函数4. 浏览器API封装5. 传感器相关 实战示例&#xff1a;构建一个拖拽上传组件性能优化技巧与原生实现对比常见问题解答总结 引言 在现代前端开发…

stm32 ADC单通道转换

stm32c8t6仅有12位分辨率 1、单次转换 非扫描 1、初始化 void Ad_Init() {RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//配置ADCCLK时钟分频,ADC的输入时钟不得超过14MHzRCC_ADCCLKConfig(RCC_PCLK2_Div6);G…

2KW压缩机驱动参考设计【SCH篇】

实物展示&#xff1a; ACDC: VAC和VAC-为交流电压检测&#xff1a; 1.C33 C34作为Y电容走线宽度要求&#xff1a; Y电容一般用于L/N到地之间&#xff08;L-PE 或 N-PE&#xff09;&#xff0c;主要作用是抑制共模干扰。其走线的电流非常小&#xff0c;推荐使用 ≥ 1mm 宽的走…

python05——循环结构

1、while循环 n0 #初始条件 while n<5: #判断print(hello python) #要重复执行的代码print(n) #注意同级代码缩进相同n1 #计数器结果&#xff1a; hello python 0 hello python 1 hello python 2 hello python 3 hello python 4 hello python 5 #求阶乘和 sum0 n1 whil…

LINUX编译、运行、测试lowcoder_CN

参考 二者没有太大差异。 LINUX编译、运行、测试lowcoder-CSDN博客 下载 git clone https://github.com/mousheng/lowcoder_CN 或 git clone https://gitcode.com/gh_mirrors/lo/lowcoder_CNcd lowcoder_CN三个模块 node-service api-service client 每个模块都有自己的…

Python 基础之函数命名

几个问题 使用描述性蛇形命名法&#xff08;snake_case&#xff09;Python函数名应使用什么大小写格式&#xff1f;为什么函数名要具有描述性&#xff1f;方法的命名规范是什么&#xff1f;函数、变量和类的命名有何区别&#xff1f; Python函数的命名有一些不可违背的硬性规…

redis 命令大全整理

http://doc.redisfans.com/ 原网址 Redis 命令分类 Key(键) Key(键)命令 exists/del/keys/type/scanobject/move/dump/migratettl/pttl/persist/expireat/pexpireat/expire/pexpirerename/renamenxsort/randomkey/restoreexists 语法:exists key [key ...] 检查一个或多…

React中useDeferredValue与useTransition终极对比。

文章目录 前言一、核心差异对比二、代码示例对比1. useDeferredValue&#xff1a;延迟搜索结果更新2. useTransition&#xff1a;延迟路由切换 三、应用场景总结四、注意事项五、原理剖析1. 核心机制对比2. 关键差异3. 代码实现原理 总结 前言 在React的并发模式下&#xff0c…

高并发内存池|定长内存池的设计

二、定长内存池的设计 设计一个定长的内存池&#xff0c;这个内存池的定长在于&#xff0c;当剩余空间使用完毕后&#xff0c;总是开辟相同长度的新空间来使用。我们会使用到一个指针来切割划分大空间为小空间。大空间是内存池向系统申请的内存大小&#xff0c;而小空间是程序…

微信小程序 自定义图片分享-绘制数据图片以及信息文字

一 、需求 从数据库中读取头像&#xff0c;姓名电话等信息&#xff0c;当分享给女朋友时&#xff0c;每个信息不一样 二、实现方案 1、先将数据库中需要的头像姓名信息读取出来加载到data 数据项中 data:{firstName:, // 姓名img:, // 头像shareImage:,// 存储临时图片 } 2…

从零开始理解Jetty:轻量级Java服务器的入门指南

目录 一、Jetty是什么&#xff1f;先看一个生活比喻 二、5分钟快速入门&#xff1a;搭建你的第一个Jetty服务 步骤1&#xff1a;Maven依赖配置 步骤2&#xff1a;编写简易Servlet&#xff08;厨房厨师&#xff09; 步骤3&#xff1a;组装服务器&#xff08;餐厅开业准备&am…

深入浅出IIC协议 - 从总线原理到FPGA实战开发 -- 第一篇:I2C总线协议深度解剖

第一篇&#xff1a;I2C总线协议深度解剖 副标题 : 两根线如何征服千亿设备&#xff1f;详解硬件工程师必须掌握的通信奥义 1. 为什么I2C仍是嵌入式经典&#xff1f; 1.1 总线拓扑的哲学 拓扑对比图 SPI需4线N片选 vs I2C仅2线级联 UART点对点 vs I2C多主从架构 成本控制实…

MySQL 索引优化以及慢查询优化

在数据库性能优化中&#xff0c;索引优化和慢查询优化是两个关键环节。合理使用索引可以显著提高查询效率&#xff0c;而识别和优化慢查询则能提升整体数据库性能。本文将详细介绍MySQL索引优化和慢查询优化的方法和最佳实践。 一、MySQL 索引优化 1.1 索引的基本概念 索引是…

vue使用Pinia实现不同页面共享token

文章目录 一、概述二、使用步骤安装pinia在vue应用实例中使用pinia在src/stores/token.js中定义store在组件中使用store登录成功后&#xff0c;将token保存pinia中向后端API发起请求时&#xff0c;携带从pinia中获取的token 三、参考资料 一、概述 Pinia是Vue的专属状态管理库…

通俗版解释CPU、核心、进程、线程、协程的定义及关系

通俗版解释&#xff08;比喻法&#xff09; 1. CPU 和核心 CPU 一个工厂&#xff08;负责干活的总部&#xff09;。核心 工厂里的车间&#xff08;比如工厂有4个车间&#xff0c;就能同时处理4个任务&#xff09;。 2. 进程 进程 一家独立运营的公司&#xff08;比如一家…

用 VS Code / PyCharm 编写你的第一个 Python 程序

用ChatGPT做软件测试 编写你的第一个 Python 程序——不只是“Hello, World”&#xff0c;而是构建认知、习惯与未来的起点 “第一行代码&#xff0c;是一个开发者认知世界的方式。” 编程的入门&#xff0c;不只是运行一个字符串输出&#xff0c;更是开始用计算机思维来理解、…

amd架构主机构建arm架构kkfileview

修改本机使用镜像仓库地址 vim /etc/docker/daemon.json {“experimental”: true, “registry-mirrors”: [ “https://docker.m.daocloud.io”, “https://docker.1panel.live”, “http://mirrors.ustc.edu.cn/”, “http://mirror.azure.cn/”, “https://docker.hpcloud.c…

[Linux] vim及gcc工具

目录 一、vim 1.vim的模式 2.vim的命令集 (1):命令模式 (2):底行模式 3.vim配置 二、gcc 1.gcc格式及选项 2.工作布置 三、自动化构建工具makefile 1.基本使用方法 2.配置文件解析 3.拓展 在linux操作系统的常用工具中&#xff0c;常用vim来进行程序的编写&#xff1b…

数据库3——视图及安全性

视图及安全性 学习内容学习感受 学习内容 一、实验目的与要求&#xff1a; 1、设计用户子模式 2、根据实际需要创建用户角色及用户&#xff0c;并授权 3、针对不同级别的用户定义不同的视图&#xff0c;以保证系统的安全性 二、实验内容&#xff1a; 1、 先创建四类用户角色&…