当前位置: 首页 > news >正文

二叉搜索树、二叉排序树(查找、插入和删除)——Java版本

1. 概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
  • 不允许存在相同的结点

如:一个int a [] = {5,3,4,1,7,8,2,6,0,9}; 数组组成二叉排序树

定义二叉搜索树的结构:

static class TreeNode { public int val; public TreeNode left; public TreeNode right; public TreeNode(int val) { this.val = val; } } public TreeNode root;

2. 操作 - 查找

/* 搜索 搜索的时间复杂度最好:O(logN) 搜索的时间复杂度最坏:O(N) */ public boolean search(int key){ TreeNode cur = root; while(cur != null){ //找到key,返回true if(cur.val == key) { return true; } //到右树寻找 else if(cur.val < key){ cur = cur.right; } //到左树寻找 else{ cur = cur.left; } } //没有找到值为key的结点 return false; }

3. 操作 - 插入

(1) 如果树为空树,即根 == null,直接插入

(2)如果树不是空树,按照查找逻辑确定插入位置,插入新结点

/* 插入:都是插入到叶子结点 插入的时间复杂度最好:O(logN) 插入的时间复杂度最坏:O(N) */ public void insert(int val) throws Exception { TreeNode newNode = new TreeNode(val); if (root == null) { root = newNode; return; } TreeNode cur = root; TreeNode parent = null; while (cur != null) { parent = cur; //判断是否有重复元素进入 if (cur.val == val) { throw new Exception("有重复元素进入!"); } //如果要插入的结点的值,大于当前结点,就应该在cur的右子树 if (cur.val < val) { cur = cur.right; } //如果要插入的结点的值,小于当前结点,就应该在cur的左子树 else if (cur.val > val) { cur = cur.left; } } //如果要插入的结点小于父节点,就应该接在父节点的左子树 if (parent.val > val) { parent.left = newNode; } //如果要插入的结点大于父节点,就应该接在父节点的右子树 else { parent.right = newNode; } }

搜索和插入的运行结果:

4. 操作 - 删除 ⭐⭐⭐⭐⭐

分情况讨论,如下所示:

设待删除结点为 cur, 待删除结点的双亲结点为 parent

(1)cur.left == null

① cur 是 root,则root = cur.right

② cur 不是 root,cur 是 parent.left,则parent.left = cur.right

③ cur 不是 root,cur 是 parent.right,则parent.right = cur.right

(2)cur.right == null

① cur 是 root,则root = cur.left

② cur 不是 root,cur 是 parent.left,则parent.left = cur.left

③ cur 不是 root,cur 是 parent.right,则parent.right = cur.left

(3)cur.left != null && cur.right != null

需要使用替换法进行删除,即在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被 删除节点中,再来处理该结点的删除问题。

这部分的代码为(有bug):

//3.cur两边的子树都不为空 else{ TreeNode t = cur.left; TreeNode tp = null; while(t.right != null){ tp = t; t = t.right; } cur.val = t.val; //这个就变成了删除t指向的结点,且t结点的右子树为空 tp.right = t.left; }

原来的图:

第一步进行分析和改进:

第二步进行分析和改进:

树类的方法:

//删除结点的操作 public void remove(int val) { TreeNode parent = null; TreeNode cur = root; while (cur != null) { //去右子树寻找 if (cur.val < val) { parent = cur; cur = cur.right; } else if (cur.val > val) { parent = cur; cur = cur.left; } //找到了 else { removeNode(cur, parent); return; } } } private void removeNode(TreeNode cur, TreeNode parent) { //1.cur的左子树为空 if (cur.left == null) { //(1)cur 是 root,则 root = cur.right if (cur == root) { root = cur.right; } //(2)cur 不是 root,cur 是 parent.left,则 parent.left = cur.right else if (cur == parent.left) { parent.left = cur.right; } //(3)cur 不是 root,cur 是 parent.right,则 parent.right = cur.right else { parent.right = cur.right; } } //2.cur的右子树为空 else if (cur.right == null) { //(1)cur 是 root,则 root = cur.left if (cur == root) { root = cur.left; } //(2)cur 不是 root,cur 是 parent.left,则 parent.left = cur.left else if (cur == parent.left) { parent.left = cur.right; } //(3)cur 不是 root,cur 是 parent.right,则 parent.right = cur.left else { parent.right = cur.right; } } //3.cur两边的子树都不为空 else { TreeNode t = cur.left; TreeNode tp = cur; while (t.right != null) { tp = t; t = t.right; } cur.val = t.val; if (cur == tp) { tp.left = t.left; } else { //这个就变成了删除t指向的结点,且t结点的右子树为空 tp.right = t.left; } } }

测试类:

public class Test { public static void main(String[] args) throws Exception { BinarySearchTree binarySearchTree = new BinarySearchTree(); binarySearchTree.insert(5); binarySearchTree.insert(3); binarySearchTree.insert(7); binarySearchTree.insert(1); binarySearchTree.insert(4); binarySearchTree.insert(6); binarySearchTree.insert(8); binarySearchTree.insert(0); binarySearchTree.insert(9); binarySearchTree.remove(3); } }

5. 二叉搜索树的完整代码

public class BinarySearchTree { static class TreeNode { public int val; public TreeNode left; public TreeNode right; public TreeNode(int val) { this.val = val; } } public TreeNode root; /* 搜索 搜索的时间复杂度最好:O(logN) 搜索的时间复杂度最坏:O(N) */ public boolean search(int key) { TreeNode cur = root; while (cur != null) { //找到key,返回true if (cur.val == key) { return true; } //到右树寻找 else if (cur.val < key) { cur = cur.right; } //到左树寻找 else { cur = cur.left; } } //没有找到值为key的结点 return false; } /* 插入:都是插入到叶子结点 插入的时间复杂度最好:O(logN) 插入的时间复杂度最坏:O(N) */ public void insert(int val) throws Exception { TreeNode newNode = new TreeNode(val); if (root == null) { root = newNode; return; } TreeNode cur = root; TreeNode parent = null; while (cur != null) { parent = cur; //判断是否有重复元素进入 if (cur.val == val) { throw new Exception("有重复元素进入!"); } //如果要插入的结点的值,大于当前结点,就应该在cur的右子树 if (cur.val < val) { cur = cur.right; } //如果要插入的结点的值,小于当前结点,就应该在cur的左子树 else if (cur.val > val) { cur = cur.left; } } //如果要插入的结点小于父节点,就应该接在父节点的左子树 if (parent.val > val) { parent.left = newNode; } //如果要插入的结点大于父节点,就应该接在父节点的右子树 else { parent.right = newNode; } } //删除结点的操作 public void remove(int val) { TreeNode parent = null; TreeNode cur = root; while (cur != null) { //去右子树寻找 if (cur.val < val) { parent = cur; cur = cur.right; } else if (cur.val > val) { parent = cur; cur = cur.left; } //找到了 else { removeNode(cur, parent); return; } } } private void removeNode(TreeNode cur, TreeNode parent) { //1.cur的左子树为空 if (cur.left == null) { //(1)cur 是 root,则 root = cur.right if (cur == root) { root = cur.right; } //(2)cur 不是 root,cur 是 parent.left,则 parent.left = cur.right else if (cur == parent.left) { parent.left = cur.right; } //(3)cur 不是 root,cur 是 parent.right,则 parent.right = cur.right else { parent.right = cur.right; } } //2.cur的右子树为空 else if (cur.right == null) { //(1)cur 是 root,则 root = cur.left if (cur == root) { root = cur.left; } //(2)cur 不是 root,cur 是 parent.left,则 parent.left = cur.left else if (cur == parent.left) { parent.left = cur.right; } //(3)cur 不是 root,cur 是 parent.right,则 parent.right = cur.left else { parent.right = cur.right; } } //3.cur两边的子树都不为空 else { TreeNode t = cur.left; TreeNode tp = cur; while (t.right != null) { tp = t; t = t.right; } cur.val = t.val; if (cur == tp) { tp.left = t.left; } else { //这个就变成了删除t指向的结点,且t结点的右子树为空 tp.right = t.left; } } } }

6. 性能分析

插入和删除操作都必须先查找查找效率代表了二叉搜索树中各个操作的性能。 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度 的函数,即结点越深,则比较次数越多。 但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

  • 最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:
  • 最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,都可以,是二叉搜索树的性能最佳?

答:可以,这就涉及到后面的AVL树和红黑树了,后期的文章会继续讨论AVL树和红黑树。

7. 和Java类集的关系

TreeMap 和 TreeSet 即 Java 中利用搜索树实现的 Map 和 Set;实际上用的是红黑树,而红黑树是一棵近似平衡的二叉搜索树,即在二叉搜索树的基础之上 +颜色以及红黑树性质验证,关于红黑树的内容后序再进行讲解。

http://www.jsqmd.com/news/637573/

相关文章:

  • STM32G474的ADC实战避坑:从CubeMX配置到代码调试,手把手教你精准采集3.3V电压
  • 一丹一世界FLUX.1图像生成服务:支持移动端触控的7861 WebUI部署全流程
  • Java-二叉排序树
  • 如何部署TinyRecursiveModels:生产环境中的7个关键步骤与最佳实践
  • 别再死记硬背Bagging了!用狼人杀和Python代码,5分钟搞懂随机森林的‘投票’精髓
  • Datadog 发布 OpenTelemetry Go 自动插桩工具
  • 如何优化AutoTrain Advanced多模态模型部署:模型拆分与推理加速完整指南
  • 终极指南:Open Images边界框标注技术详解——600+对象类别的精确定位方案
  • 2026届必备的五大AI学术网站解析与推荐
  • 告别环境冲突!用Anaconda在PyCharm里为PyTorch项目创建独立的CUDA环境(保姆级图文)
  • Rust模块系统深度解析
  • 别再只用AES-ECB了!手把手教你用Python复现CTF经典攻击,从密文块反推HTTP请求
  • 如何解决宝塔面板7.x升级到8.x后部分插件不兼容报错_在插件商店重装受影响插件以适配新Python环境
  • Google Earth Engine(GEE)——沿海国家高程数据库(CoNED)
  • 【IET出版】第十一届信息科学、计算机技术与交通运输国际学术会议(ISCTT 2026)
  • 7个步骤!用sakura.css打造极简优雅的Markdown文档网站
  • 高效计算汉明权重的VP-SWAR算法解析与优化实践
  • 【C++类和对象(中)】—— 我与C++的不解之缘(四)
  • PanNet+: Enhancing Spectral and Spatial Preservation in Deep Learning for Pan-Sharpening
  • 直击知网5.0新规!读懂知网报告配合DeepSeek两步降论文AI(附三款降AI工具测评)
  • 如何使用AspNetCore.Diagnostics.HealthChecks实现Azure DevOps发布门控:保障应用部署质量的终极指南
  • 终极指南:如何使用node-opencv实现高效光流算法与运动跟踪
  • 终极指南:DefectDojo API v2开发实战 — 构建定制化安全解决方案
  • 如何使用EasyMocap实现精准人体关键点检测与3D运动捕捉:从2D到3D的完整指南
  • Python装饰器(Decorators)深度解析
  • vLLM-v0.17.1惊艳效果:AWQ量化后Llama3-8B显存占用降至11GB
  • 交期延误?轻流 AI 无代码给出新解法
  • 终极ZCF多语言支持指南:一键实现中英文双语配置与无缝国际化体验
  • 【零成本降AI】别盲目改论文!基于知网报告的DeepSeek降AI实操(附神级提示词)
  • 2025届毕业生推荐的AI科研方案推荐