Java数据结构超详细分析二叉搜索树
作者:未见花闻 发布时间:2022-12-01 01:34:20
1.搜索树的概念
二叉搜索树是一种特殊的二叉树,又称二叉查找树,二叉排序树,它有几个特点:
如果左子树存在,则左子树每个结点的值均小于根结点的值。
如果右子树存在,则右子树每个结点的值均大于根结点的值。
中序遍历二叉搜索树,得到的序列是依次递增的。
二叉搜索树的左右子树均为二叉搜索树。
二叉搜索树的结点的值不能发生重复。
2.二叉搜索树的简单实现
我们来简单实现以下搜索树,就不使用泛型了,二叉搜索树基本结构:
public class BinarySearchTree {
static class Node {
public int val;
public Node left;
public Node right;
public Node(int val) {
this.val = val;
}
}
public Node root;
//其他方法
}
2.1查找
二叉搜索树最擅长的就是查找,根据二叉搜索树的定义,左子树的元素比根小,右子树的元素比根大,所以我们只需要根据根结点的值与目标元素的值比较,就能实现查找功能。
根与目标元素相等,表示找到了。
根比目标元素大,去左子树找。
根比目标元素小,去右子树找。
左右子树找不到,那就找不到了。
参考实现代码:
public Node search(int key) {
Node cur = this.root;
while (cur != null) {
//根与目标元素相等,表示找到了。
if (cur.val == key) return cur;
//根比目标元素大,去左子树找。
else if (cur.val > key) cur = cur.left;
//根比目标元素小,去右子树找。
else cur = cur.right;
}
//此时cur = null, 左右子树找不到,那就找不到了。
return cur;
}
2.2插入
需要在二叉搜索树中插入一个元素,首先得找到一个合适的插入位置,如何找呢?其实就是利用搜索树查找的方式,找到一个空位,如何将目标结点插入到这个位置。
根与插入元素相等,插入元素不能与搜索树中的元素相等,插入失败。
根比插入元素大,去左子树找。
根比插入元素小,去右子树找。
找到的结点为空,那这个位置就是我们要找的空位。
由于你找到空位时,无法获取该空位的前一个位置,所以每次查找的时候都需要保存上一次查找的位置。
找到位置后,将目标结点插入到该位置。
参考实现代码:
public boolean insert(int val) {
//结点为空,直接插
if(root == null) {
root = new Node(val);
return true;
}
Node cur = this.root; //当前查找位置
Node parent = null; //查找的上一个位置
while (cur != null) {
parent = cur;
if (val > cur.val) cur = cur.right;
else if (val < cur.val) cur = cur.left;
else return false;
}
//开始插入,找到空位前一个位置,比插入元素小,空位在右边,插入右边
if (val > parent.val) {
parent.right = new Node(val);
} else {
//比插入元素大,空位在左边,插入左边
parent.left = new Node(val);
}
return true;
}
2.3删除
删除是搜索树基本操作中最麻烦的一个操作,需要考虑多种情况。
不妨设需要删除的结点为cur
,cur
的父结点为parent
,搜索树的根结点为root
。首先需要删除结点,那就得找到结点,所以第一步是找结点,思路与查找的思路一模一样。
第二步那就是删除了,删除结点大概有下面几种情况:
情况1:cur.left == null
cur == root,让root = cur.right;
cur != root且parent.left == cur,让parent.left = cur.right;
cur != root且parent.right == cur,让parent.right = cur.right。
情况2:cur.right == null
cur == null,让root = cur.left;
cur != root且parent.left == cur,让parent.left = cur.left;
cur != root且parent.right == cur,让parent.right = cur.left。
情况3:cur.left != null && cur.right != null
方案1:找到cur右子树中最小的元素target
,然后将该元素的值覆盖到cur处(可以理解为交换),此时等价于删除target
处的结点,即该结点的父结点为preTarget
。
因为target
为cur
右子树最小的一个结点,所以target.left == null
,此时preTarget.left == target
,所以删除时按照上面的情况1去进行删除。
但是还有一种特殊情况,那就是cur.right
就是最小结点,此时preTarget==cur
,即preTarget.right == target
,这时删除时要将 preTarget.right = target.right。
方案2:找到cur左子树中最大的元素target
,然后将该元素的值覆盖到cur处(可以理解为交换),此时等价于删除target
处的结点,即该结点的父结点为preTarget
。
因为target
为cur
左子树最大的一个结点,所以target.right == null
,此时preTarget.right == target
,所以删除时按照上面的情况2去进行删除。
但是还有一种特殊情况,那就是cur.left
就是左子树最大结点,此时preTarget==cur
,即preTarget.left == target
,这时删除时要将 preTarget.left = target.left。
参考实现代码:
public void remove(int key) {
Node cur = root;
Node parent = null;
while (cur != null) {
if(cur.val == key) {
//这里开始删除
removeNode(cur,parent);
break;
}else if(cur.val < key) {
parent = cur;
cur = cur.right;
}else {
parent = cur;
cur = cur.left;
}
}
}
removeNode方法(方案1):
public void removeNode(Node cur,Node parent) {
if(cur.left == null) {
if(cur == root) {
root = cur.right;
}else if(cur == parent.left) {
parent.left = cur.right;
}else {
parent.right = cur.right;
}
}else if(cur.right == null) {
if(cur == root) {
root = cur.left;
}else if(cur == parent.left) {
parent.left = cur.left;
}else {
parent.right = cur.left;
}
}else {
Node preTarget = cur ;
Node target = cur.right;
while (target.left != null) {
preTarget = target;
target = target.left;
}
cur.val = target.val;
if (target == preTarget.left) {
preTarget.left = target.right;
} else {
preTarget.right = target.right;
}
}
}
removeNode方法(方案2):
public void removeNode(Node cur,Node parent) {
if(cur.left == null) {
if(cur == root) {
root = cur.right;
}else if(cur == parent.left) {
parent.left = cur.right;
}else {
parent.right = cur.right;
}
}else if(cur.right == null) {
if(cur == root) {
root = cur.left;
}else if(cur == parent.left) {
parent.left = cur.left;
}else {
parent.right = cur.left;
}
}else {
Node preTarget = cur ;
Node target = cur.left;
while (target.right != null) {
preTarget = target;
target = target.right;
}
cur.val = target.val;
if (target == preTarget.left) {
preTarget.left = target.left;
} else {
preTarget.right = target.left;
}
}
}
2.4修改
搜索树的修改可以基于删除和插入,先删除目标元素,然后再插入修改元素。
参考实现代码:
public void set(int key, int val) {
remove(key);
insert(val);
}
3.二叉搜索树的性能
在平衡二叉树的情况下(左右子树高度差不超过1),假设有n个结点,此时时间复杂度为二叉树的高度,即 O ( l o g 2 n ) O(log_2n) O(log2n),但是这只是例行情况,最不理想的情况就是二叉树化为单分支树,时间复杂为 O ( n ) O(n) O(n)。
为了解决这个问题,后面引申出AVL树,红黑树,其中TreeMap与TreeSet的底层就是红黑树。具体红黑树是什么,这里就不多说了。
本文到底了,你学会了吗?
来源:https://weijianhuawen.blog.csdn.net/article/details/123044815
猜你喜欢
- 这篇文章主要介绍了Spring JDK * 实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要
- 可以用于简单的过期订单取消支付、7天自动收货场景中1、Spring Boot整合redis 参考https://www.jb51.net/a
- 让我们来看看这段代码: import java.util.BitSet;import java.util.concurrent.C
- 一、问题最近在做代码重构,代码工程采用了Controller/Service/Dao分层架构,Dao层使用了Mybatis-Plus框架。在
- 本文实例为大家分享了Struts2框架拦截 器实例的示例代码,供大家参考,具体内容如下在看拦截 器的小例子的前我们先来看看sturts2的原
- 在Java移动文件夹及其所有子文件与子文件夹可以有如下的一段简单的方法来说明:public static void moveFolder(S
- mybatis 传入null值解决前端传入两个值,如果其中一个为null时,很多时候我们都很困惑,明明传入的是null,为啥mybatis
- GET请求不支持对象传参问题@GetMapping("/getByParam")String hello(Student
- java 计算同比增长工具类为了数据的严谨性,统一装换为BigDecimal,话不多说,看代码。package com.pig4cloud.
- 一、JDBC简介JDBC是连接java应用程序和数据库之间的桥梁。什么是JDBC?Java语言访问数据库的一种规范,是一套API。JDBC
- java Mybatis存进时间戳封装了一个实体类,里面有个字段 Integer createTime。要利用这个实体类将一个时间戳存进数据
- 字符, 字节与字符串字符与字符串字符串内部包含一个字符数组,String 可以和 char[] 相互转换.字符数组变为字符串:public
- * 的实现使用的模式:代理模式。代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。类似租房的中介。两种 * :(1)jd
- 本文会介绍从一个最基本的java工程,到Web工程,到集成Spring、SpringMVC、Spring
- 6.0的手机对于写入手机需要申请权限的我做了如下处理下面我贴出代码package com.example.admin.sdapplicati
- 资源加载器使用Java,您可以使用当前线程的classLoader并尝试加载文件,但是Spring Framework为您提供了更为优雅的解
- 英文设置加粗可以在xml里面设置: <SPAN style="FONT-SIZE: 18px">androi
- java * 的方法总结AOP的拦截功能是由java中的 * 来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该
- 如下所示:package cn.sunzn.md5;import java.security.MessageDigest;import ja
- 第一步:后端简单建个SpringBoot项目,提供一个 helloWorld接口;版本选用 2.2.6.RELEASEpackage com