3分钟火速手写一个二叉查找树,搞快点。
文章目录
- 定义
- 实现
- 完整代码
- 测试
- 问题
因为二叉查找树的原理和实现比较简单,所以我们简单了解一下最基本的概念,就直接开始写代码。
定义
二叉查找树(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分钟火速手写一个二叉查找树,搞快点。相关推荐
- python手写一个迭代器_搞清楚 Python 的迭代器、可迭代对象、生成器
很多伙伴对 Python 的迭代器.可迭代对象.生成器这几个概念有点搞不清楚,我来说说我的理解,希望对需要的朋友有所帮助. 1 迭代器协议 迭代器协议是核心,搞懂了这个,上面的几个概念也就很好理解了. ...
- 三分钟手写一个迷你jQuery,附源码
诚然,不管前端技术怎么发展,重心都不会变,就是 操作DOM + 获取数据. 下面的代码演示了如何快速手写一个简单的jQuery: <!DOCTYPE html> <html lang ...
- ds查找—二叉树平衡因子_面试官让我手写一个平衡二叉树,我当时就笑了
平衡二叉树对于初学者一直是一个比较复杂的知识点,因为其里面涉及到了大量的旋转操作.把大量的同学都给转晕了.这篇文章最主要的特点就是通过动画的形式演示.确保大家都能看懂.最后是手写一个平衡二叉树. 一. ...
- vue 使用fs_模仿vue-cli,手写一个脚手架
vue-cli 在vue的开发的过程中,经常会使用到vue-cli脚手架工具去生成一个项目.在终端运行命令vue create hello-world后,就会有许多自动的脚本运行. 为什么会这样运行呢 ...
- 从 0 开始手写一个 Spring MVC 框架,向高手进阶
转载自 从 0 开始手写一个 Spring MVC 框架,向高手进阶 Spring框架对于Java后端程序员来说再熟悉不过了,以前只知道它用的反射实现的,但了解之后才知道有很多巧妙的设计在里面.如 ...
- 自己手写一个Spring MVC框架
想要了解Spring MVC框架的原理,探究框架是如何设计的,不错的学习方式是阅读源码,然后自己手写一个框架.本文带领大家简化的手写一个Spring MVC框架. Spring框架对于Java后端程序 ...
- 来 给朕手写一个OOM异常的栗子
2.4 实战 OutOfMemoryError 异常 面试官:项目中你有没有遇到过Java虚拟机方面的问题,做过虚拟机调优嘛,来,给朕手写一个堆内存溢出的Demo 注:IDEA内存映像分析工具: ht ...
- 自己手写一个 VB 的 DateAdd 函数(VB/C 双语言版本)
可能有些朋友觉得这是多余的事情,既然 VB 有了 DateAdd 函数来进行日期运算,干嘛还要自己手写一个? 其实这一点也不多余,因为有些时候我们的开发环境不一定就在 VB 里,而那些个开发环境不一定 ...
- 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理
动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 在解 ...
最新文章
- 计算机网络课程优秀备考PPT之第五章网络层(五)
- Android移动开发之【Android实战项目】textview实现文字逐字显示效果
- 操作系统实验——简易FAT16文件系统的实现
- python数据挖掘学习笔记】十.Pandas、Matplotlib、PCA绘图实用代码补充
- 米饭里面加点它,4大病症一周见效,一定要告诉家里做饭的人~
- cwntos使用不了php,centos系统不能使用yum命令怎么解决
- android之uniapp调用weex的module
- 【交通标志识别】基于matlab GUI矩匹配算法路标识别【含Matlab源码 1175期】
- linux下order by 报出ORDER BY clause is not in SELECT list
- SQL语句 常用语句
- c语言ide 字体大小,配置最小c语言IDE
- 【死磕DDD】聊聊领域建模方法论
- acwing-2240. 餐饮(最大流+拆点)
- MySQL二进制日志文件格式
- 【Java成王之路】EE进阶第十篇 MyBatis查询数据库
- Google与百度、搜狗合作,共同推进移动网络发展
- 蓝屏代码0x00000074
- 电脑插入耳机无声音,显示AMD HDMI OUTPUT未插入,但是外放有声音故障解决方案
- 如何使用闲置的云服务器搭建一个属于自己的私人云网盘(可道云kodbox)
- 金额平均分配算法 python实现
热门文章
- 最全Cisco 3560交换机使用手册
- CiteSpace学习笔记(三)——数据预处理
- 100集华为HCIE安全培训视频教材整理 | 安全策略
- (C语言)猜数字游戏,猜中负数,直接游戏结束,一次猜中,bingo,1~3次,Lucky you!......
- 如何解决ios横竖屏切换布局错乱的问题
- Android 消息总线汇总(一)
- java sql数字溢出_nested exception is java.sql.SQLException: 数字溢出的问题解决
- Javascript中的Number
- 【程序设计】程序结构与流程图
- 中职计算机应用,中职计算机应用论文