文章目录

  • 定义
  • 实现
  • 完整代码
  • 测试
  • 问题

因为二叉查找树的原理和实现比较简单,所以我们简单了解一下最基本的概念,就直接开始写代码。

定义

二叉查找树(Binary Search Tree,BST),又叫做二叉排序树、二叉搜索树,是一种对查找和排序都有用的特殊二叉树。

二叉查找树或是空树,或是满足如下三个性质的二叉树:

  • 若其左子树非空,则左子树上所有节点的值都小于根节点的值 若其右子树非空,则右子树上所有节点的值都大于根节点的值 其左右子树都是一棵二叉查找树

  • 二叉查找树的特性:左子树<根<右子树,即二叉查找树的中序遍历是一个递增序列。

实现

树吗,无非查找,新增和删除。

首先定义一下节点的结构:

class BSTreeNode<K, V> {/*** 键值*/private K nodeKey;/*** 值*/private V nodeValue;/*** 左节点引用*/private BSTreeNode<K, V> left;/*** 右节点引用*/private BSTreeNode<K, V> right;/*** 父节点引用*/private BSTreeNode<K, V> parent;}

对于查找,看看谁大谁小,递归慢慢找呗:

 /*** 给定一个根节点,根据Key搜索某一个节点* 时间复杂度和二叉树的层数有关,所以时间复杂度为O(Logn)** @param root 根节点引用* @param key  需要搜索的值* @return 节点*/private BSTreeNode<K, V> indexSearch(BSTreeNode<K, V> root, K key) {//如果当前的根节点为null,返回空节点if (root == null) {return null;}// 如果当前节点key大于待搜索值,那么向左查找,反之,向右查找。if (root.nodeKey.compareTo(key) > 0) {return indexSearch(root.left, key);} else if (root.nodeKey.compareTo(key) < 0) {return indexSearch(root.right, key);} else return root;}

对于新增,先找到在哪增加,然后就加进去呗,引用换一换就好了:

/*** 新增node** @param root    root* @param newNode 新增的节点*/
void insertNode(BSTreeNode<K, V> root, BSTreeNode<K, V> newNode) {// 如果当前根节点为null,将当前根节点引用只想新的node节点if (root == null) {firstRoot = newNode;return;}// 如果当前根节点key大于新增的节点key,则向当前节点的左节点进行操作if (root.nodeKey.compareTo(newNode.nodeKey) > 0) {// 如果当前根节点的左节点为null,则将当前根节点的左节点引用指向新增的节点,// 否则进行递归,直到左节点尽头为止if (root.left == null) {root.left = newNode;newNode.parent = root;} else insertNode(root.left, newNode);} else if (root.nodeKey.compareTo(newNode.nodeKey) < 0) {// 如果当前根节点key小于新增的节点key,则向当前节点的右节点进行操作,// 如果右节点为空,则将当前根节点的右节点引用指向新增的节点,// 否则进行递归,直到右节点尽头为止if (root.right == null) {root.right = newNode;newNode.parent = root;} else insertNode(root.right, newNode);}}

删除可能情况多一些,不过注释写的清清楚楚:

/*** 删除node** @param key key*/private void removeNode(K key) {// 根据key找到节点所在的位置BSTreeNode<K, V> search = indexSearch(firstRoot, key);// 如果找不到,直接返回if (search == null) {return;}// 将要删除的节点的父节点BSTreeNode<K, V> parent = search.parent;// 如果父节点为空,说明没有节点或者只有一个根节点,则将根节点置为空即可// 否则,将分为3种情况://     1. 删除的节点没有左右节点,直接删除即可//     2. 删除的节点有左节点,没有右节点,那么需要将删除节点的父节点的左/右节点指向删除节点的左节点,//        删除节点的左节点的父节点需要指向删除节点的父节点//     3. 删除的节点有右节点,没有左节点,那么需要将删除节点的父节点的左/右节点指向删除节点的右节点,//        删除节点的右节点的父节点需要指向删除节点的父节点//     4. 删除的节点有左右节点,那么需要找到当前节点的中序前序节点或中序后继节点,替换掉当前节点,包括key和value,//        再删掉这个当前节点的中序前序节点或中序后继节点if (parent == null) {firstRoot = null;} else {//     1. 删除的节点没有左右节点,直接删除即可if (search.left == null && search.right == null) {if (parent.left == search) {parent.left = null;} else if (parent.right == search) {parent.right = null;}}//     2. 删除的节点有左节点,没有右节点,那么需要将删除节点的父节点的左/右节点指向删除节点的左节点,//        删除节点的左节点的父节点需要指向删除节点的父节点if (search.right == null) {if (parent.left == search) {parent.left = search.left;search.left.parent = parent;} else if (parent.right == search) {parent.right = search.left;search.left.parent = parent;}}//     3. 删除的节点有右节点,没有左节点,那么需要将删除节点的父节点的左/右节点指向删除节点的右节点,//        删除节点的右节点的父节点需要指向删除节点的父节点if (search.left == null) {if (parent.left == search) {parent.left = search.right;search.right.parent = parent;} else if (parent.right == search) {parent.right = search.right;search.right.parent = parent;}}//     4. 删除的节点有左右节点,那么需要找到当前节点的中序前序节点或中序后继节点,替换掉当前节点,包括key和value,//        再删掉这个当前节点的中序前序节点或中序后继节点if (search.left != null && search.right != null) {BSTreeNode<K, V> left = search.left;while (left.right != null) {left = left.right;}search.nodeKey = left.nodeKey;search.nodeValue = left.nodeValue;removeNode(left.nodeKey);}}}

完整代码


/*** 二叉树** @param <K> K* @param <V> V*/
public class BSTreeMap<K extends Comparable<K>, V> {/*** 原始根节点*/private BSTreeNode<K, V> firstRoot;/*** 根据Key搜索某一个节点** @param key 需要搜索的值* @return 节点值*/public V get(K key) {// 如果节点不存在,则返回空if (indexSearch(firstRoot, key) == null) {return null;}return indexSearch(firstRoot, key).nodeValue;}/*** 存值** @param key   key* @param value value*/public void put(K key, V value) {// 将key和value封装成node对象BSTreeNode<K, V> newNode = new BSTreeNode<K, V>(key, value);insertNode(firstRoot, newNode);}/*** 根据key删除node节点** @param key key*/public void delete(K key) {removeNode(key);}/*** 新增node** @param root    root* @param newNode 新增的节点*/void insertNode(BSTreeNode<K, V> root, BSTreeNode<K, V> newNode) {// 如果当前根节点为null,将当前根节点引用只想新的node节点if (root == null) {firstRoot = newNode;return;}// 如果当前根节点key大于新增的节点key,则向当前节点的左节点进行操作if (root.nodeKey.compareTo(newNode.nodeKey) > 0) {// 如果当前根节点的左节点为null,则将当前根节点的左节点引用指向新增的节点,// 否则进行递归,直到左节点尽头为止if (root.left == null) {root.left = newNode;newNode.parent = root;} else insertNode(root.left, newNode);} else if (root.nodeKey.compareTo(newNode.nodeKey) < 0) {// 如果当前根节点key小于新增的节点key,则向当前节点的右节点进行操作,// 如果右节点为空,则将当前根节点的右节点引用指向新增的节点,// 否则进行递归,直到右节点尽头为止if (root.right == null) {root.right = newNode;newNode.parent = root;} else insertNode(root.right, newNode);}}/*** 删除node** @param key key*/private void removeNode(K key) {// 根据key找到节点所在的位置BSTreeNode<K, V> search = indexSearch(firstRoot, key);// 如果找不到,直接返回if (search == null) {return;}// 将要删除的节点的父节点BSTreeNode<K, V> parent = search.parent;// 如果父节点为空,说明没有节点或者只有一个根节点,则将根节点置为空即可// 否则,将分为3种情况://     1. 删除的节点没有左右节点,直接删除即可//     2. 删除的节点有左节点,没有右节点,那么需要将删除节点的父节点的左/右节点指向删除节点的左节点,//        删除节点的左节点的父节点需要指向删除节点的父节点//     3. 删除的节点有右节点,没有左节点,那么需要将删除节点的父节点的左/右节点指向删除节点的右节点,//        删除节点的右节点的父节点需要指向删除节点的父节点//     4. 删除的节点有左右节点,那么需要找到当前节点的中序前序节点或中序后继节点,替换掉当前节点,包括key和value,//        再删掉这个当前节点的中序前序节点或中序后继节点if (parent == null) {firstRoot = null;} else {//     1. 删除的节点没有左右节点,直接删除即可if (search.left == null && search.right == null) {if (parent.left == search) {parent.left = null;} else if (parent.right == search) {parent.right = null;}}//     2. 删除的节点有左节点,没有右节点,那么需要将删除节点的父节点的左/右节点指向删除节点的左节点,//        删除节点的左节点的父节点需要指向删除节点的父节点if (search.right == null) {if (parent.left == search) {parent.left = search.left;search.left.parent = parent;} else if (parent.right == search) {parent.right = search.left;search.left.parent = parent;}}//     3. 删除的节点有右节点,没有左节点,那么需要将删除节点的父节点的左/右节点指向删除节点的右节点,//        删除节点的右节点的父节点需要指向删除节点的父节点if (search.left == null) {if (parent.left == search) {parent.left = search.right;search.right.parent = parent;} else if (parent.right == search) {parent.right = search.right;search.right.parent = parent;}}//     4. 删除的节点有左右节点,那么需要找到当前节点的中序前序节点或中序后继节点,替换掉当前节点,包括key和value,//        再删掉这个当前节点的中序前序节点或中序后继节点if (search.left != null && search.right != null) {BSTreeNode<K, V> left = search.left;while (left.right != null) {left = left.right;}search.nodeKey = left.nodeKey;search.nodeValue = left.nodeValue;removeNode(left.nodeKey);}}}/*** 给定一个根节点,根据Key搜索某一个节点* 时间复杂度和二叉树的层数有关,所以时间复杂度为O(Logn)** @param root 根节点引用* @param key  需要搜索的值* @return 节点*/private BSTreeNode<K, V> indexSearch(BSTreeNode<K, V> root, K key) {//如果当前的根节点为null,返回空节点if (root == null) {return null;}// 如果当前节点key大于待搜索值,那么向左查找,反之,向右查找。if (root.nodeKey.compareTo(key) > 0) {return indexSearch(root.left, key);} else if (root.nodeKey.compareTo(key) < 0) {return indexSearch(root.right, key);} else return root;}/*** 内部类-node** @param <K> K* @param <V> V*/static class BSTreeNode<K, V> {/*** 键值*/private K nodeKey;/*** 值*/private V nodeValue;/*** 左节点引用*/private BSTreeNode<K, V> left;/*** 右节点引用*/private BSTreeNode<K, V> right;/*** 父节点引用*/private BSTreeNode<K, V> parent;public BSTreeNode(K nodeKey, V nodeValue) {this.nodeKey = nodeKey;this.nodeValue = nodeValue;}@Overridepublic String toString() {return "BSTreeNode{" +"nodeKey=" + nodeKey +'}';}}
}

测试

问题

BST有个最大的问题,就是树的结构不容易平衡,如果我们存入的序列都是有序的,那不成了链表了,链表的时间复杂度肯定就不能达到O(Logn),但是呢,我们让它平衡不就得了。所以啊,红黑树出现了,我们等一个失眠的夜,手写一下红黑树,让失眠的夜不再失眠。

3分钟火速手写一个二叉查找树,搞快点。相关推荐

  1. python手写一个迭代器_搞清楚 Python 的迭代器、可迭代对象、生成器

    很多伙伴对 Python 的迭代器.可迭代对象.生成器这几个概念有点搞不清楚,我来说说我的理解,希望对需要的朋友有所帮助. 1 迭代器协议 迭代器协议是核心,搞懂了这个,上面的几个概念也就很好理解了. ...

  2. 三分钟手写一个迷你jQuery,附源码

    诚然,不管前端技术怎么发展,重心都不会变,就是 操作DOM + 获取数据. 下面的代码演示了如何快速手写一个简单的jQuery: <!DOCTYPE html> <html lang ...

  3. ds查找—二叉树平衡因子_面试官让我手写一个平衡二叉树,我当时就笑了

    平衡二叉树对于初学者一直是一个比较复杂的知识点,因为其里面涉及到了大量的旋转操作.把大量的同学都给转晕了.这篇文章最主要的特点就是通过动画的形式演示.确保大家都能看懂.最后是手写一个平衡二叉树. 一. ...

  4. vue 使用fs_模仿vue-cli,手写一个脚手架

    vue-cli 在vue的开发的过程中,经常会使用到vue-cli脚手架工具去生成一个项目.在终端运行命令vue create hello-world后,就会有许多自动的脚本运行. 为什么会这样运行呢 ...

  5. 从 0 开始手写一个 Spring MVC 框架,向高手进阶

    转载自   从 0 开始手写一个 Spring MVC 框架,向高手进阶 Spring框架对于Java后端程序员来说再熟悉不过了,以前只知道它用的反射实现的,但了解之后才知道有很多巧妙的设计在里面.如 ...

  6. 自己手写一个Spring MVC框架

    想要了解Spring MVC框架的原理,探究框架是如何设计的,不错的学习方式是阅读源码,然后自己手写一个框架.本文带领大家简化的手写一个Spring MVC框架. Spring框架对于Java后端程序 ...

  7. 来 给朕手写一个OOM异常的栗子

    2.4 实战 OutOfMemoryError 异常 面试官:项目中你有没有遇到过Java虚拟机方面的问题,做过虚拟机调优嘛,来,给朕手写一个堆内存溢出的Demo 注:IDEA内存映像分析工具: ht ...

  8. 自己手写一个 VB 的 DateAdd 函数(VB/C 双语言版本)

    可能有些朋友觉得这是多余的事情,既然 VB 有了 DateAdd 函数来进行日期运算,干嘛还要自己手写一个? 其实这一点也不多余,因为有些时候我们的开发环境不一定就在 VB 里,而那些个开发环境不一定 ...

  9. 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理

    动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 在解 ...

最新文章

  1. 计算机网络课程优秀备考PPT之第五章网络层(五)
  2. Android移动开发之【Android实战项目】textview实现文字逐字显示效果
  3. 操作系统实验——简易FAT16文件系统的实现
  4. python数据挖掘学习笔记】十.Pandas、Matplotlib、PCA绘图实用代码补充
  5. 米饭里面加点它,4大病症一周见效,一定要告诉家里做饭的人~
  6. cwntos使用不了php,centos系统不能使用yum命令怎么解决
  7. android之uniapp调用weex的module
  8. 【交通标志识别】基于matlab GUI矩匹配算法路标识别【含Matlab源码 1175期】
  9. linux下order by 报出ORDER BY clause is not in SELECT list
  10. SQL语句 常用语句
  11. c语言ide 字体大小,配置最小c语言IDE
  12. 【死磕DDD】聊聊领域建模方法论
  13. acwing-2240. 餐饮(最大流+拆点)
  14. MySQL二进制日志文件格式
  15. 【Java成王之路】EE进阶第十篇 MyBatis查询数据库
  16. Google与百度、搜狗合作,共同推进移动网络发展
  17. 蓝屏代码0x00000074
  18. 电脑插入耳机无声音,显示AMD HDMI OUTPUT未插入,但是外放有声音故障解决方案
  19. 如何使用闲置的云服务器搭建一个属于自己的私人云网盘(可道云kodbox)
  20. 金额平均分配算法 python实现

热门文章

  1. 最全Cisco 3560交换机使用手册
  2. CiteSpace学习笔记(三)——数据预处理
  3. 100集华为HCIE安全培训视频教材整理 | 安全策略
  4. (C语言)猜数字游戏,猜中负数,直接游戏结束,一次猜中,bingo,1~3次,Lucky you!......
  5. 如何解决ios横竖屏切换布局错乱的问题
  6. Android 消息总线汇总(一)
  7. java sql数字溢出_nested exception is java.sql.SQLException: 数字溢出的问题解决
  8. Javascript中的Number
  9. 【程序设计】程序结构与流程图
  10. 中职计算机应用,中职计算机应用论文