DS进阶:二叉搜索树

2024-04-29 20:36
文章标签 进阶 搜索 二叉 ds

本文主要是介绍DS进阶:二叉搜索树,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、概念

二、搜索二叉树相关操作

1.查找

2.插入

3.删除(难点)

第一类:

第二类:

第三类:

三、性能分析


一、概念

二叉搜索树,又称二叉排序树,它或者是一颗空树,也是具有一下特征:

1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

2.若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

因此,二叉搜索树中没有重复的元素。

3.它的左右子树也分别为二叉搜索树

其次,二叉搜索树进行中序遍历会得到一个升序的序列。

如图:

二、搜索二叉树相关操作

搜索二叉树的基本结构和普通二叉树没有任何区别:

        

1.查找

对于给定的元素int val

在搜索二叉树root(先假设root是已经创建好的二叉搜索树)中进行查找:

    //方法也很简单,只要依照二叉搜索树右子树永远大于双亲节点,左子树永远小于双亲节点的性质即可public TreeNode findNode(int val){if(root==null)return null;//如果树是空的,就无法寻找了TreeNode cur=root;//cur用来遍历整棵树while(cur!=null){if(cur.val<val){//往右边走cur=cur.right;}else if(cur.val>val){//往左边走cur=cur.left;}else{//cur.val==val就是要找的节点return cur;}}return null;//在这颗树中没有找到,直接返回空}

2.插入

二叉搜索树的插入有点不同,想要在二叉搜索树中插入一个元素:

那么必须在叶子节点下面插入,才能够保证结构特点不发生改变。

以下面这颗二叉搜索树为例:

如果要插入一个val=10的结点:

如果要插入一个val=-1的结点:

上述的插入,都是在叶子节点操作的,且最后插入完成之后,仍然是二叉搜索树。

至于如何找对叶子结点,请看下面代码以及注释:

 public boolean insertNode(int val) {//插入成功返回真,否则假TreeNode newNode = new TreeNode(val);//先创建好一个结点if (root == null) {root = newNode;//首次插入return true;//直接返回}TreeNode cur = root;//用来遍历树,找到合适的叶子结点TreeNode parent = null;//用来记录cur的双亲结点//插入的结点必须也要满足二叉搜索树的性质,所以如果val大于根结点的值,就往右树走,反之往左树走while (cur != null) {parent = cur;if (cur.val < val) {cur = cur.right;} else if (cur.val > val) {cur = cur.left;} else {//cur.val==valreturn false;//这种情况是不允许的,因为二叉搜索树,每个元素各不相同}}//出循环之后,cur=null parent就是正确的叶子结点//还是依照二叉搜索树的性质,来判断位置if(parent.val<val){parent.right=newNode;}else{//parent.val>valparent.left=newNode;}//注意下面这种写法是不行的,因为cur已经等于空了,如果parent左右都空,那么就可能出现插入位置错误
/*        if (cur == parent.right) {parent.right = newNode;} else {//cur==parent.leftparent.left = newNode;}*/return true;}

3.删除(难点)

设待删除的结点为cur

待删除的结点的双亲结点为parent

想要删除某一个结点,而又不破坏二叉搜索树的结构,依据不同的情况,分为三大类:

第一类:cur.left==null(cur.right可以是空,也可以不是空)

第二类:cur.right==null(cur.left可以是空,也可以不是空)

注意:前两类有一个重叠情况,就是cur.left和cur.right都是空的情况,不过都不影响,往下看就知道了

第三类:cur.right!=null&&cur.left!=null

第一类:

cur.left==null(cur.right可以是空,也可以不是空)

删除val=7的结点:

在第一类中,cur.left肯定是空的,要安全删除,接可以直接这样:

这样不就大功告成了吗?

我们再回头看看这个val=8的结点,如果这个结点是空,即原先的条件是cur.left==null&&cur.right==null是不是也可以用这个逻辑,显然也是可以的嘛,parent.right=null呗。

所以前文说即使前两类有所重叠但是其实没有影响。

第二类:

cur.right==null(cur.left可以是空,也可以不是空)

第二类和第一类,不说一摸一样,完全相同肯定是有的  ƪ(˘⌣˘)ʃ

删除val=6的结点:

因为是第二类,所以cur.right肯定是空嘛

parent.right=cur.left就可以咯,就是第一类的左右颠倒:

显然,val=6的结点可有可无,反正我的parent.right指向的就是你,管你是啥呢。

第三类:

cur.right!=null&&cur.left!=null

第三类相对来讲有点特殊,不过他转来转去,用的还是是前面两类的方法

此话怎讲?

活动一下僵硬的嘎吱窝,俺娓娓道来:

删除val=7 的结点:

第一步:

找到cur结点右子树中,val值最小的结点,命名为:replace

第二步:

cur.val和replace.val进行互换:

要删除的是val=7这个结点,您看这图眼熟不眼熟。

这不就是第一类中的情况吗?

没错,此时我们成功的把问题,转化为了前面两类的其中之一了。

第三步:

使用第一类中的逻辑,最终实现删除。

注意:

有时候,替换完可能是第一类的情况,也可能是第二类的情况,但是思路都一样的呢。

转化为代码:

    public boolean remove(int val){//首先要找到cur(要删除的结点)和parent(cur的双亲结点)TreeNode cur=root;TreeNode parent=null;while(cur!=null){//一直遍历这颗树去寻找parent=cur;if(cur.val<val){//往右边,找大的cur=cur.right;}else if(cur.val>val){//往左边,找小的cur=cur.left;}else{//cur.val==val//找到了的情况/*下面代码嵌套的比较多,封装一个方法来实现*/_remove(parent,cur);return true;}}//程序到这,说明要删除的结点都没有找到,直接返回假return false;}private void _remove(TreeNode parent,TreeNode cur){if(cur.left==null){//第一类if(parent.left==cur){//要先判断双亲结点哪一个子树是curparent.left=cur.right;}else{//parent.right==curparent.right=cur.right;}}else if(cur.right==null){//第二类if(parent.left==cur)parent.left=cur.left;else parent.right=cur.left;}else{//要删除的结点左右两个子树都不为空------>第三类/*   对于第三类*///第一步:在cur的右子树中找到最小值的结点TreeNode min=cur.right;TreeNode minDad=null;//min的双亲结点while(min.left!=null){//一直遍历cur右子树的左树,因为左子树永远更小minDad=min;min=min.left;}//出了这个循环,叶子结点已经找到了//第二步:叶子节点和要要删除的结点的val进行交换cur.val=min.val;//反正min结点要交换,更改cur结点的val就可以了//第三步:以min结点作为要删除的结点,执行第一类逻辑即可完成if(min==minDad.left){//要先判断双亲结点的那个子结点是要删除的结点minDad.left=min.right;//min.left一定是空的,因为我们找的就是最小的叶子节点}else{//min==minDad.rightminDad.right=min.right;//min.left一定是空的,因为我们找的就是最小的叶子节点}}}

三、性能分析

插入和删除操作都必须先查找,所以查找效率代表了二叉搜索树中各个操作的性能。 

由于二叉搜索树储存元素的特点,查找效率就和这颗二叉树的深度有关系,结点越深,比较次数就越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

显而易见:

最优情况下,二叉搜索树是完全二叉树,比较次数为:logN

最差情况下,二叉搜索树是单分支树,比较次数为:N

为了优化搜索二叉树的效率,由此在二叉搜索树的基础上,诞生了AVL树,和红黑树,等等。这里就不展开了,留在下一篇在介绍吧。


这篇关于DS进阶:二叉搜索树的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/947098

相关文章

C# ComboBox下拉框实现搜索方式

《C#ComboBox下拉框实现搜索方式》文章介绍了如何在加载窗口时实现一个功能,并在ComboBox下拉框中添加键盘事件以实现搜索功能,由于数据不方便公开,作者表示理解并希望得到大家的指教... 目录C# ComboBox下拉框实现搜索步骤一步骤二步骤三总结C# ComboBox下拉框实现搜索步骤一这

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

hdu1240、hdu1253(三维搜索题)

1、从后往前输入,(x,y,z); 2、从下往上输入,(y , z, x); 3、从左往右输入,(z,x,y); hdu1240代码如下: #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#inc

[MySQL表的增删改查-进阶]

🌈个人主页:努力学编程’ ⛅个人推荐: c语言从初阶到进阶 JavaEE详解 数据结构 ⚡学好数据结构,刷题刻不容缓:点击一起刷题 🌙心灵鸡汤:总有人要赢,为什么不能是我呢 💻💻💻数据库约束 🔭🔭🔭约束类型 not null: 指示某列不能存储 NULL 值unique: 保证某列的每行必须有唯一的值default: 规定没有给列赋值时的默认值.primary key:

hdu 4517 floyd+记忆化搜索

题意: 有n(100)个景点,m(1000)条路,时间限制为t(300),起点s,终点e。 访问每个景点需要时间cost_i,每个景点的访问价值为value_i。 点与点之间行走需要花费的时间为g[ i ] [ j ] 。注意点间可能有多条边。 走到一个点时可以选择访问或者不访问,并且当前点的访问价值应该严格大于前一个访问的点。 现在求,从起点出发,到达终点,在时间限制内,能得到的最大

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时,算法停止。 — Choose k successors randomly, biased towards good ones — Close