数据结构--二叉搜索树

article/2025/8/19 5:24:50

二叉搜索树

  • 一丶概念以及特点
  • 二丶相关操作
    • 定义TreeMap类
    • put()操作--插入节点
    • get()操作--得到key对应的value值
    • getOrDefault()操作
    • containsKey()操作--检查key是否存在
    • containsValue()操作--检查value是否存在
    • remove()操作--删除操作
      • 思路
        • (1)叶子结点
        • (2)左子树为空
        • (3)右子树为空
        • (4)左右子树都在
      • 具体代码

一丶概念以及特点

什么是二叉搜索树呢?

二叉搜索树就是二叉树的基础上对其进行一定规则的限制。即就是:二叉搜索树中任一节点的左子树的值都小于当前节点的值,右子树的值都大于当前节点的值。

也就是说:如果任一结点的左子树非空,则左子树的所有结点的值都小于根结点的值;如果任一结点的右子树非空,则右子树的所有结点的值都大于根结点的值。

这意味着什么呢?

意味着在中序遍历的情况下,二叉搜索树是一个单调递增的数组。

二丶相关操作

那么二叉搜索树这里,我们自己定义实现一个。其中每个节点的值我们用TreeMap的节点来表示。那么在这之前,我们是不是要先定义一个Map的接口,然后以此来实现其内部的部分属性。

public interface Map<K extends Comparable<K>,V> {//Entry<K, V> 是Map内部实现的用来存放<key, value>键值对映射关系的内部类public static class Entry<K,V>{K key;V value;//Key的获取K getKey(){return key;}//Value的获取V getValue(){return value;}//设置Value的方法V setValue(V value){V oldValue = this.value;this.value = value;return oldValue;}}//Map其中的一些内置方法public V put(K key, V value);public V get(K key);public V getOrDefault(K key, V value);public V remove(K key);public boolean containsKey(K key);public boolean containsValue(V value);public int size();
}

我们定义了Map接口之后,接下来我们就准备开始着手准备实现二叉搜索树。

定义TreeMap类

我们定义TreeMap类并且实现自己定义的Map接口后再初始化,这样我们就有了代码的初始版本,然后接下里的工作就是对一些主要的方法进行讲解。

public class TreeMap<K extends Comparable<K>,V> implements Map<K,V>{//定义TreeNode节点public static class TreeNode<K,V>{TreeNode left;TreeNode right;Map.Entry<K,V> kv;public TreeNode(K key,V value){kv = new Entry<>();kv.key = key;kv.value = value;}}//接着定义根节点TreeNode<K,V> root;int size = 0;//以下全部都是需要我们自己实现的方法@Overridepublic V put(K key, V value) {return null;}@Overridepublic V get(K key) {return null;}@Overridepublic V getOrDefault(K key, V value) {return null;}@Overridepublic V remove(K key) {return null;}@Overridepublic boolean containsKey(K key) {return false;}@Overridepublic boolean containsValue(V value) {return false;}@Overridepublic int size() {return 0;}
}

put()操作–插入节点

如果我们要插入一个节点,那么我们首先要根据key的值来确定对应的位置。
就比如下面这个二叉搜索树。
在这里插入图片描述
如果说,此时我们要插入节点的key值为10,那么我们该怎么办呢?
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
那么注意了,如果说我要插入的节点和已经有的节点的key值相同怎么办呢?
k e y 值 相 同 , 那 么 v a l u e 值 会 被 覆 盖 \color{red}{key值相同,那么value值会被覆盖} keyvalue

大致原理说清了,接下来给出代码实现:

@Overridepublic V put(K key, V value) {//这种是节点为null的情况if(root == null){root = new TreeNode<>(key,value);size++;return value;}//接下来对插入位置进行查找,如果下标的节点已经存在就覆盖,不存在的话就要用parent进行来进行标志插入TreeNode<K,V> cur = root;TreeNode<K,V> parent = null;while(cur != null){parent = cur;int fac = key.compareTo(cur.kv.key);//下标进行比较if(fac > 0) {cur = cur.right;}else if(fac < 0){cur = cur.left;}else{V oldValue = cur.kv.value;cur.kv.value = value;return oldValue;}}//要插入节点在树中不存在所以不存在覆盖这种情况,就有了以下对左右子树插入情况的判断int res = key.compareTo(parent.kv.key);TreeNode<K,V> fac = new TreeNode<>(key,value);if(res > 0){parent.right = fac;}else{parent.left = fac;}size++;return value;}

get()操作–得到key对应的value值

首先,如果要知道一个节点存不存在我们就要遍历这些节点了,但不是全部遍历,要根据其性质来。
get操作和put操作原理是一样的,但是呢:

get操作就单单是查找对应节点,如果找到了就返回true,没找到就是false。put操作也是查找对应节点,如果找到了对应节点就覆盖,没找到就插入就行。

@Overridepublic V get(K key) {TreeNode<K,V> cur = root;while(cur != null){int val = key.compareTo(cur.kv.key);if(val > 0){cur = cur.right;}else if(val < 0){cur = cur.left;}else{return cur.kv.value;}}return null;}

getOrDefault()操作

这个操作还是挺容易实现的,毕竟你只需要得到Key对应的value值是不是null,如果是null就返回你设置的值,不为null就返回其本身的值。其实也就是引用一下上面的get()方法。

@Overridepublic V getOrDefault(K key, V value) {//先定义本身的value值V node  = get(key);//如果为null就返回你传入的value值,不为null就返回本身的value值return node == null ? value : node;}

containsKey()操作–检查key是否存在

这个操作也就是对上面的操作加以引用和延伸。我们自己实现的话还是对put()这个方法加以改写。
这个方法的主要作用就是查看Key是否存在

public boolean containsKey(K key) {TreeNode<K,V> cur = root;while(cur != null) {int fac = key.compareTo(cur.kv.key);if(fac == 0){return true;}else if(fac > 0){cur = cur.right;}else{cur = cur.left;}}return false;}

containsValue()操作–检查value是否存在

想要看value值存不存在,那么就需要遍历整个二叉树。

	@Overridepublic boolean containsValue(V value) {return containsValue(value,root);}public boolean containsValue(V value,TreeNode<K,V> root) {TreeNode<K,V> cur = root;if(cur == null){return false;}if(cur.kv.value == value){return false;}return containsValue(value,root.left) || containsValue(value,root.right);}
}

这里为了尽量接近原有代码,不破坏其完整性,所以对方法进行了重载。

remove()操作–删除操作

首先如果说要删除一个节点,那我们肯定是要分情况讨论的。因为不可能一上去就直接乱删。那具体的删除情况根据子节点的不同可以分为以下几种。

1.当前节点是叶子结点
2.当前节点左子树为空
3.当前节点右子树为空
4.当前节点左右子树都在

先罗列出具体的分类,然后一个一个情况来进行讨论。
在 这 里 我 们 用 c u r 来 寻 找 并 且 标 记 要 删 除 的 节 点 , 用 p a r e n t 来 标 \color{red}{在这里我们用cur来寻找并且标记要删除的节点,用parent来标} curparent
记 其 走 过 的 上 一 个 节 点 。 \color{red}{记其走过的上一个节点。}

思路

(1)叶子结点

如果是叶子结点,那么我们归并到情况2进行解决。
为什么可以这样做?那么继续往下走就好啦。

(2)左子树为空

如果说当前节点左子树为空,那么就证明只有右子树,这个时候就需要对当前节点所在位置进行判断。

1.该节点可能是根节点
2.该节点可能是其双亲节点的左子树
3.该节点可能是其双亲节点的右子树

对于上面的三种情况,我们按照如下方式来解决:

1.如果说当前节点为根节点,那么就让root指向它的右子树
2.如果说当前节点为其双亲节点的左子树,那么就是parent.left = cur.right
3.如果说当前节点为其双亲节点的右子树,那么就是parent.right = cur.right

所以如果说当前节点为叶子结点,那么这个时候它不论是其双亲节点的左子树还是右子树,最后它的位置肯定会置为空。
所以左子树为空的情况就直接解决了叶子结点为空的这种情况。

(3)右子树为空

如果说当前节点右子树为空,那么处理方法和左子树为空时候一样,要先清楚当前节点的位置。

1.要删除节点为根节点
2.该节点可能是其双亲节点的左子树
3.该节点可能是其双亲节点的右子树

那么对于以上的情况,我们怎样处理呢?

1.如果是根节点,那么就让root指向当前节点的左子树
2.如果是双亲节点的左子树,那么parent.left = cur.left
3.如果是双亲节点的右子树,那么parent.right = cur.left

(4)左右子树都在

那么如果说一个节点的左右子树都在,那么我们就要从这个节点开始找,找什么呢?

1.可以找左子树当中的最大值。
2.也可以找右子树当中的最小值。

找到之后怎么办呢?用找到的节点和待删除的节点进行交换。接下里就删除我们待删除的节点就好了。这里我们一般情况下是找右子树当中的最小值,也就是右子树当中最左侧的节点。

具体代码

@Overridepublic V remove(K key) {TreeNode<K,V> cur = root;TreeNode<K,V> parent = null;while(cur != null){int res = key.compareTo(cur.kv.key);if(res > 0){parent = cur;cur = cur.right;}else if(res < 0){parent = cur;cur = cur.left;}else{break;}}if(cur == null){return null;}V val = cur.kv.value;//那接下来待删除节点已经找到,开始进行删除if(cur.left == null){//左子树为空的情况if(parent == null){//如果是根节点root = cur.right;}else{//不是根节点if(cur == parent.left){parent.left = cur.right;}else{parent.right = cur.right;}}cur.right = null;}else if(cur.right == null){//右子树为空if(parent == null){root = cur.left;}else{if(parent == parent.left){parent.left = cur.left;}else{parent.left = cur.left;}}cur.left = null;}else{//左右子树都不为空TreeNode<K,V> fac = cur.right;parent = cur;while(fac.left != null){parent = fac;fac = fac.left;}cur.kv = fac.kv;//把右子树最左侧节点覆盖待删除节点if(parent.left == fac){parent.left = fac.right;}else{parent.right = fac.right;}}return val;}

http://chatgpt.dhexx.cn/article/0mIdTuEX.shtml

相关文章

Java数据结构--树2

Java数据结构--树 一、平衡树1.1 2-3 查找树1.1.1 2-3查找树的定义1.1.2 查找1.1.3 插入1.1.3.1 向2-结点中插入新键1.1.3.2 向一棵只含有一个3-结点的树中插入新键1.1.3.3 向一个父结点为2-结点的3-结点中插入新键1.3.1.4 向一个父结点为3-结点的3-结点中插入新键1.3.1.5 分解…

数据结构之多路查找树

多路查找树 一、2-3树1.1 查找1.2 2-3树的插入实现1.3 2-3树的删除节点 二、2-3-4树三、总结 二叉排序树简单的实现在多数情况能够达到预期的查找效率&#xff0c;但是每个节点只能存储一个元素和只能有两个孩子&#xff0c;使得在大量数据下会造成二叉排序树的深度特别大&…

【数据结构 7】二叉查找树及其Java实现

【数据结构 1】顺序表及其Java实现 【数据结构 2】单向链表及其Java实现 【数据结构 3】双向链表及其Java实现 【数据结构 4】栈及其Java实现 【数据结构 5】队列及其Java实现 【数据结构 6】符号表及其Java实现&#xff08;使用链表实现&#xff09; 【数据结构 7】二叉查找树…

C++从入门到精通(第十篇) :二叉搜索树

二叉搜索树 一&#xff1a;二叉搜索树概念二&#xff1a; 二叉搜索树实现节点的定义二叉搜索树实现 三&#xff1a;二叉搜索树的应用四&#xff1a;二叉树有关面试题ps 很多小伙伴为了刷题发愁 今天为大家推荐一款刷题神奇哦&#xff1a;刷题面试神器牛客 各大互联网大厂面试真…

数据结构与算法之树(二)二叉查找树

数据结构与算法之树 数据结构与算法之树&#xff08;一&#xff09;二叉树概念及遍历方式&#xff08;图文并茂&#xff09; 数据结构与算法之树&#xff08;二&#xff09;二叉查找树 数据结构与算法之树&#xff08;三&#xff09;AVL树 数据结构与算法之树&#xff08;四…

【算法修炼】二叉搜索树

学习自&#xff1a;https://labuladong.gitee.io/algo/2/19/26/ 二叉搜索树 一、BST的中序遍历230、二叉搜索树中第k小的元素&#xff08;中等&#xff09;1038、从二叉搜索树到更大和树&#xff08;中等&#xff09; 二、判断BST的合法性※98、验证二叉搜索树&#xff08;中等…

数据结构(C语言)-树

树 一、树1、树的定义2、树的基本术语3、树结构和线性结构的比较 二、二叉树1、二叉树的定义2、二叉树的形态与树的形态3、二叉树的性质4 、二叉树的存储结构5、遍历二叉树6、二叉树的其他操作7、线索二叉树 三、树与二叉树的转换1、树转换成二叉树2、二叉树变树 四、哈夫曼树1…

【数据结构与算法】程序内功篇六--树

程序内功篇六--树 一、树1、树的含义2、树的特点(选看)3、树的逻辑结构 二、二叉树1、二叉树的含义2、二叉树性质3、二叉树-顺序存储4、二叉树-链式存储5、二叉树的遍历6、二叉树创建与遍历C程序的实现&#xff08;1&#xff09;二叉树的创建&#xff08;2&#xff09;前序遍历…

数据结构---与树相关的知识

与树有关的一系列知识: 树,二叉树,满二叉树,完全二叉树,文章还没完,我会后序补充的 一: 树(了解就行)1.1 概念1.2 一些与树相关的重要概念1.3 树的表示形式 二: 二叉树(非常重要,重点掌握)2.1 概念2.2 两种特殊的二叉树2.2.1 满二叉树2.2.2 完全二叉树 2.3 二叉树的性质2.4 二叉…

Java数据结构--树1

Java数据结构--树 一、二叉树入门1.1 树的基本定义1.2 树的相关术语1.3 二叉树的基本定义1.4 二叉查找树的创建1.4.1 二叉树的结点类1.4.2 二叉查找树API设计1.4.3 二叉查找树实现1.4.4 二叉查找树其他便捷方法1.4.4.1 查找二叉树中最小的键1.4.4.2 查找二叉树中最大的键 1.5 二…

树 一

文章目录 1.查找二分查找判定树 2. 树(Tree)2.1 树的术语2.2树的表示&#xff1a;儿子兄弟表示法 3. 二叉树&#xff08;Binary Tree&#xff09;3.1 特殊结构二叉树3.2 二叉树的性质3.3 二叉树的存储3.4二叉树的遍历 分层次组织管理上更有效地操作。 1.查找 静态查找&#xf…

树一:邂逅入门篇

一、树的概念 树是一种典型的非线性结构&#xff0c;是表达有层次特性的图结构的一种方法。 1.1 基本术语 术语描述空树当n0 时称为空树。根结点根结点是一个没有双亲结点的结点。一棵树最多一个。例如&#xff1a;A边结点之间的连线叶子结点没有孩子结点的结点。例如&#x…

树一:定义及存储

树的定义&#xff1a; 树是一种非线性的数据结构。树是由 n (n > 0) 个结点组成的有序集合。 如果 n 为0&#xff0c;称为空树&#xff1b;如果 n > 0, 则&#xff1a; 有一个结点称为根结点&#xff08;root&#xff09;&#xff0c;它有直接后继&#xff0c;但没有直接…

mysql 创建索引规则

1、表的主键、外键必须有索引&#xff1b; 2、数据量超过300的表应该有索引&#xff1b; 3、经常与其他表进行连接的表&#xff0c;在连接字段上应该建立索引&#xff1b; 4、经常出现在Where子句中的字段&#xff0c;特别是大表的字段&#xff0c;应该建立索引&#xff1b; 5、…

mysql分区表之三:MySQL分区建索引,唯一索引

介绍 mysql分区后每个分区成了独立的文件&#xff0c;虽然从逻辑上还是一张表其实已经分成了多张独立的表&#xff0c;从“information_schema.INNODB_SYS_TABLES”系统表可以看到每个分区都存在独立的TABLE_ID,由于Innodb数据和索引都是保存在".ibd"文件当中&#…

分享mysql创建索引的3种方法

大家应该都知道索引的建立对于MySQL数据库的高效运行是很重要的,索引可以大大提升MySQL的检索速度,下面这篇文章主要给大家介绍了关于mysql创建索引的3种方法,需要的朋友可以参考下 1、使用CREATE INDEX创建&#xff0c;语法如下&#xff1a; 1 CREATE INDEX indexName ON tab…

js 监听浏览器刷新还是关闭事件

// $(window).bind(beforeunload,function(){return 您输入的内容尚未保存&#xff0c;确定离开此页面吗&#xff1f;;}); // window.onbeforeunload function() { return "确定离开此页面吗&#xff1f;"; }; // function myFunction() {return "自定…

浏览器刷新和页面手动为什么不一样?

Fiddler(2):AutoResponse修改返回结果_mb5fed6ec4336ce的技术博客_51CTO博客Fiddler(2):AutoResponse修改返回结果&#xff0c;前言怎么修改接口的返回数据呢步骤1.抓包&#xff0c;找到要拦截的请求&#xff0c;然后在AutoResponder中AddRule&#xff1a;2.在RuleEditor中的第…

vue监听浏览器刷新和关闭事件,并在页面关闭/刷新前发送请求

vue监听浏览器刷新和关闭事件&#xff0c;并在页面关闭/刷新前发送请求 1.需求背景&#xff1a;2.需求分析&#xff1a;3.实现方式&#xff1a;4.实现方式解析&#xff1a;1&#xff09;浏览器页面事件基础2&#xff09;在mounted监听beforeunload和unload事件 5.存在的问题/风…

浏览器刷新和关闭时显示提示信息

vue 刷新和关闭浏览器时显示提示信息 使用onbeforeunload事件 mounted() {window.onbeforeunload e > {e e || window.eventif (e) {e.returnValue 关闭提示}return 关闭提示}} }, beforeDestroy() {window.onbeforeunload null },如果是所有页面都需要页面销毁显示提…