原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11406176.html

  尝试一下用Java实现二叉搜索树/二叉查找树,记录自己的学习历程。

  1. 首先先来设计实现一下节点Node。

  一个二叉树的节点需要以下几个元素:

    key 关键字

    value 节点的值(key也可以代替value)

    parent 父节点

    leftChildren 左儿子节点

    rightChildren 右儿子节点

  那就开始吧!

  

/**
* 节点
*/
class Node{
private int key;
private String value;
private Node parent;
private Node leftChildren;
private Node rightChildren; public Node(){} public Node(int key, String value){
this.key = key;
this.value = value;
} public int getKey() {
return key;
} public void setKey(int key) {
this.key = key;
} public Node getParent() {
return parent;
} public void setParent(Node parent) {
this.parent = parent;
} public Node getLeftChildren() {
return leftChildren;
} public void setLeftChildren(Node leftChildren) {
this.leftChildren = leftChildren;
} public Node getRightChildren() {
return rightChildren;
} public void setRightChildren(Node rightChildren) {
this.rightChildren = rightChildren;
} public String getValue() {
return value;
} public void setValue(String value) {
this.value = value;
}
}

  2. 接下来就是树的实现了。

  一个二叉树会有什么操作呢?

    find(int key) 根据key查找对应的节点

    insert(int key, String value) 插入节点

    preOrder(Node root) 前序遍历

    midOrder(Node root) 中序遍历

    backOrder(Node root) 后序遍历

    delete(int key) 根据key删除节点

  好了,需要的东西都知道了,开始实现吧!先新建一个BinarySearchTree.java文件,把其他属性和方法写上。

  

/**
* 二叉查找树/二叉搜索树
*/
public class BinarySearchTree {
private Node root;
/**
* 遍历结果集合
*/
List<Node> orderResult = new ArrayList<>(); public BinarySearchTree(Node root){
this.root = root;
} public BinarySearchTree(){} public Node getRoot(){
return this.root;
}
}

  一步步往里面加入上面列出的方法并且一一实现吧!

  ️find(int key)

    /**
* 查找key
* @param key
* @return
*/
public Node find(int key){
/**
* 从根节点开始找
*/
Node currentNode = root;
/**
* 判断根节点是否符合要求
*/
while (currentNode != null && key != currentNode.getKey()){
if (key < currentNode.getKey()){
//to left
currentNode = currentNode.getLeftChildren();
}else {
//to right
currentNode = currentNode.getRightChildren();
}
} return currentNode;
}

  首先把root节点设置为当前节点,当当前节点不为null而且当前节点的key和传入的key不相等时,我们继续循环,判断这个key的node节点是在当前节点的左子树还是右子树,并且继续把左子树或者右子树设为当前节点继续执行,直到当前节点是null或者当前节点的key值等于传入的key值,返回当前节点,查找结束。

  

  ️insert(int key, String value)

    /**
* 插入
*/
public void insert(int key, String value){
Node newNode = new Node(key, value);
if (null == root){
this.root = newNode;
return ;
}
/**
* 当前搜索到的树
*/
Node currentNode = root;
Node parentNode = root;
/**
* 默认左子树
*/
boolean isLeftChild = true;
while (null != currentNode){
parentNode = currentNode;
if (key < parentNode.getKey()){
//父节点左侧
currentNode = parentNode.getLeftChildren();
isLeftChild = true;
}else {
//父节点右侧
currentNode = parentNode.getRightChildren();
isLeftChild = false;
}
} /**
* 循环结束之后的parentNode就是最终需要插入子节点的节点,根据isLeftChild判断插入左儿子还是右儿子
*/
Node childNode = new Node(key, value);
if (isLeftChild){
parentNode.setLeftChildren(childNode);
childNode.setParent(parentNode);
}else {
parentNode.setRightChildren(childNode);
childNode.setParent(parentNode);
}
return ;
}

  二叉查找树的插入是将比父节点的值插入到左子树里面,大的放右子树去找到位置插入。

  首先,我们new一个节点,看看root节点存不存在,不存在就设置new的节点为root节点,插入结束。

  第二种情况,有root节点,继续往下执行,将root节点赋给currentNode变量和parentNode变量,后面采用cn和pn缩写;

  我们就默许先从左儿子节点开始比较,key小于pn的key,把cn的左儿子节点赋给cn,继续循环,每次循环开始都把cn保存为pn起来,这样的话,直到cn为null,我们可以轻松获取到上一次操作的cn,也就是当前的pn。

  循环结束后,开始插入新节点:

  根据isLeftChild判断插入左边还是右边,插入的时候记得给父节点设置儿子,给儿子设置父节点,方便我们后续进行删除操作。

  

  ️遍历,有前中后三个遍历方法,记住前中后是对父节点来说的就很好办了!

    * 前序遍历就是对每个树遍历的时候把父节点拎到最前面;

    * 中序遍历就是对每个树遍历的时候把父节点拎到最中间;

    * 后序遍历就是对每个树遍历的时候把父节点拎到最后面。

  代码很简单,递归思想解决。不明白的话可以自己执行debug一遍就深刻理解了!100遍的解释不如自己debug一次!

  

    /**
* 前序遍历
* @param root
* @return
*/
public List<Node> preOrder(Node root){
if (null == root){
return null;
}
orderResult.add(root);
preOrder(root.getLeftChildren());
preOrder(root.getRightChildren());
return orderResult;
} /**
* 中序遍历
* @param root
* @return
*/
public List<Node> midOrder(Node root){
if (null == root){
return null;
}
midOrder(root.getLeftChildren());
orderResult.add(root);
midOrder(root.getRightChildren()); return orderResult;
} /**
* 后序遍历
* @param root
* @return
*/
public List<Node> backOrder(Node root){
if (null == root){
return null;
}
backOrder(root.getLeftChildren());
backOrder(root.getRightChildren());
orderResult.add(root); return orderResult;
}

  ️删除,最难的部分来了,这部分是最麻烦的,不过一步一步来,很多麻烦事都可以解决的,不用害怕。

  先贴删除方法和涉及到的方法代码:

  

    /**
* 删除节点(有三种情况)
* 1.待删除节点没有子节点
* 2.待删除节点只有一个子节点
* 3.待删除节点有两个子节点
* @param key
* @return
*/
public boolean delete(int key){
/**
* 被查找到的节点
*/
Node findNode = find(key); if (null == findNode){
return false;
}
/**
* 待删除节点没有儿子节点
*/
if (null == findNode.getLeftChildren() && null == findNode.getRightChildren()){
if (null == findNode.getParent()){
root = null;
return true;
}else {
/**
* 是否和父节点的左儿子相同
*/
if (isParentLeftChildren(findNode, key)){
findNode.getParent().setLeftChildren(null);
return true;
}else{
/**
* 右儿子
*/
findNode.getParent().setRightChildren(null);
return true;
}
}
} /**
* 待删除节点左儿子存在,右儿子为空
*/
if (null == findNode.getRightChildren() && null != findNode.getLeftChildren()){
if (isParentLeftChildren(findNode, key)){
findNode.getParent().setLeftChildren(findNode.getLeftChildren());
          findNode.getLeftChildren().setParent(findNode.getParent());
findNode = null;
return true;
}else{
findNode.getParent().setRightChildren(findNode.getLeftChildren());
          findNode.getLeftChildren().setParent(findNode.getParent());
findNode = null;
return true;
}
}
/**
* 待删除节点右儿子存在,左儿子为空
*/
else if (null == findNode.getLeftChildren() && null != findNode.getRightChildren()){
if (isParentLeftChildren(findNode, key)){
findNode.getParent().setLeftChildren(findNode.getRightChildren());
findNode.getRightChildren().setParent(findNode.getParent());
findNode = null;
return true;
}else{
findNode.getParent().setRightChildren(findNode.getRightChildren());
findNode.getRightChildren().setParent(findNode.getParent());
findNode = null;
return true;
}
}
/**
* 待删除节点左右儿子都存在
*/
else if (null != findNode.getLeftChildren() && null != findNode.getRightChildren()){
if (isParentLeftChildren(findNode, key)){
/**
* 设置一系列的引用
*/
Node succesor = findSuccessorNode(findNode);
/**
* 判断后继者是否就是当前节点的左儿子,如果是,不需要再重复设置自己引用自己,会引发内存泄漏。
*/
if (succesor != findNode.getLeftChildren()){
findNode.getLeftChildren().setParent(succesor);
succesor.setLeftChildren(findNode.getLeftChildren());
findNode.getRightChildren().setParent(succesor);
succesor.setRightChildren(findNode.getRightChildren());
findNode.getParent().setLeftChildren(succesor);
succesor.setParent(findNode.getParent());
findNode = null; return true;
}else {
findNode.getRightChildren().setParent(succesor);
succesor.setRightChildren(findNode.getRightChildren());
findNode.getParent().setLeftChildren(succesor);
succesor.setParent(findNode.getParent());
findNode = null; return true;
} }else{
Node succesor = findSuccessorNode(findNode);
/**
* 判断后继者是否就是当前节点的左儿子,如果是,不需要再重复设置自己引用自己,会引发内存泄漏。
*/
if (succesor != findNode.getLeftChildren()){
findNode.getLeftChildren().setParent(succesor);
succesor.setLeftChildren(findNode.getLeftChildren());
findNode.getRightChildren().setParent(succesor);
succesor.setRightChildren(findNode.getRightChildren());
findNode.getParent().setRightChildren(succesor);
succesor.setParent(findNode.getParent());
findNode = null; return true;
}else {
findNode.getRightChildren().setParent(succesor);
succesor.setRightChildren(findNode.getRightChildren());
findNode.getParent().setRightChildren(succesor);
succesor.setParent(findNode.getParent());
findNode = null; return true;
}
}
} return false;
} /**
* 查询该节点是否父节点的左节点
* @param findNode
* @param key
*/
private boolean isParentLeftChildren(Node findNode,int key){
/**
* 是否和父节点的左儿子相同
*/
if (null != findNode.getParent().getLeftChildren() && findNode.getParent().getLeftChildren().getKey() == key){
return true;
}else{
/**
* 是否和父节点的右儿子相同
*/
return false;
}
} /**
* 查找待删除节点的后继节点,一般是右儿子的左子树的最小值
* @param node
* @return
*/
private Node findSuccessorNode(Node node){
/**
* 保存父节点
*/
Node parentNode = node;
/**
* 当前节点
*/
Node currentNode = node;
while (null != currentNode){
parentNode = currentNode;
currentNode = currentNode.getLeftChildren();
}
/**
* 判断父节点的右儿子是否为空,不为空的话需要把右儿子提到该父节点的父节点(没有重复打,就是两个父节点)的左儿子
*/
if (null == parentNode.getRightChildren()){
return parentNode;
}else {
parentNode.getParent().setLeftChildren(parentNode.getRightChildren());
       parentNode.getRightChildren().setParent(parentNode.getParent());
return parentNode;
}
}

  至此!代码全部贴完!分析完删除部分,再把我的main测试方法代码贴出来,删除方法可能不是很完整,思想大致如此,第一次不是很缜密,下次改进!

  *删除分三种情况:

    1. 待删除节点没有儿子节点

    2.待删除节点只有一个儿子节点

    3.待删除节点有两个儿子节点

  需要分别针对这三种情况处理!

  虽然代码都写了注释,不过还是自己再重新理一遍吧!开干!(看自己写的代码真是觉得要多烂有多烂,头疼)

  情况1 :待删除节点没有儿子节点

    首先看看待删除节点有没有父节点,没有父节点那就是root节点,设置root为null就行了。

    然后看看待删除节点是不是和父节点的左儿子相同,是的话将父节点的左儿子设为null,不是的话将右儿子设为null就行了。

    情况1结束。

  情况2 :待删除节点只有一个儿子节点,但是还不知道是左儿子还是右儿子。

    1) 左儿子节点存在

      先isParentLeftChildren()看看待删除节点是它父节点的左儿子还是右儿子,如果是左儿子,将待删除节点的左儿子节点设置为待删除节点父节点的左儿子,反之亦然。

    2)右儿子节点存在

      先isParentLeftChildren()看看待删除节点是它父节点的左儿子还是右儿子,如果是左儿子,将待删除节点的左儿子节点设置为待删除节点父节点的右儿子,反之亦然。

  情况3 :待删除节点右两个儿子(这种情况略微复杂)

    先isParentLeftChildren()看看待删除节点是它父节点的左儿子还是右儿子,如果是左儿子,继续

      因为待删除节点的两个儿子都存在,说明要从两个子树中挑一个节点来做待删除节点的继承者!

        查找继承者用findSuccessorNode()方法;首先查找到左子树的最左侧的最小的儿子,判断这个最小的儿子是不是有右儿子,没有右儿子的情况下直接返回这个最小的儿子,

        如果这个最小的儿子有右儿子,需要把这个最小的儿子的右儿子设置为这个最小的儿子的父节点的左儿子,再返回这个最小的儿子作为继承者。

      继承者找到了。这里再判断一下继承者是不是就是当前节点的左儿子,是的话就不必重复设置继承者的父节点为待删除节点的左儿子也就是继承者,不是的话,设置引用。

      因为这里前面isParentLeftChildren()找到的是左儿子,所以设置待删除节点的父节点的左儿子为继承者,待删除节点的右儿子设置为继承者的右儿子。

    反之,亦然。

   下面的一大段分析看不明白就结合代码来看,代码里面的注释也有很多的,文章写到这里就结束了!

   结束

Java实现二叉搜索树的更多相关文章

  1. Java实现二叉搜索树的添加,前序、后序、中序及层序遍历,求树的节点数,求树的最大值、最小值,查找等操作

    什么也不说了,直接上代码. 首先是节点类,大家都懂得 /** * 二叉树的节点类 * * @author HeYufan * * @param <T> */ class Node<T ...

  2. Java创建二叉搜索树,实现搜索,插入,删除操作

    Java实现的二叉搜索树,并实现对该树的搜索,插入,删除操作(合并删除,复制删除) 首先我们要有一个编码的思路,大致如下: 1.查找:根据二叉搜索树的数据特点,我们可以根据节点的值得比较来实现查找,查 ...

  3. Java数据结构——二叉搜索树

    定义二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若 ...

  4. Java实现二叉搜索树及相关操作

    package com.tree; import com.tree.BitNode; /** * * 二叉搜索树:一个节点的左子节点的关键字小于这个节点.右子节点的关键字大于或等于这个父节点 * * ...

  5. Java实现二叉搜索树的插入、删除

    前置知识 二叉树的结构 public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() { } TreeNode( ...

  6. Java对二叉搜索树进行插入、查找、遍历、最大值和最小值的操作

    1.首先,须要一个节点对象的类.这些对象包括数据.数据代表存储的内容,并且还有指向节点的两个子节点的引用 class Node { public int iData; public double dD ...

  7. 二叉搜索树Java实现(查找、插入、删除、遍历)

    由于最近想要阅读下 JDK1.8 中 HashMap 的具体实现,但是由于 HashMap 的实现中用到了红黑树,所以我觉得有必要先复习下红黑树的相关知识,所以写下这篇随笔备忘,有不对的地方请指出- ...

  8. 【算法与数据结构】二叉搜索树的Java实现

    为了更加深入了解二叉搜索树,博主自己用Java写了个二叉搜索树,有兴趣的同学可以一起探讨探讨. 首先,二叉搜索树是啥?它有什么用呢? 二叉搜索树, 也称二叉排序树,它的每个节点的数据结构为1个父节点指 ...

  9. Java与算法之(13) - 二叉搜索树

    查找是指在一批记录中找出满足指定条件的某一记录的过程,例如在数组{ 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 }中查找数字15,实现代码很简单 ...

随机推荐

  1. Flask_generate_password_hash的加盐哈希加密算法与check_password_hash的校验

    密码加密简介 密码存储的主要形式: 明文存储:肉眼就可以识别,没有任何安全性. 加密存储:通过一定的变换形式,使得密码原文不易被识别. 密码加密的几类方式: 明文转码加密算法:BASE64, 7BIT ...

  2. Struts-S2-045漏洞利用

    最近也是在看Struts2的漏洞,这里与大家共同探讨一下,本次我复现的是s2-045这个编号的漏洞 漏洞介绍 Apache Struts 2被曝存在远程命令执行漏洞,漏洞编号S2-045,CVE编号C ...

  3. 树莓派和荔枝派,局域网socket 通信

    在虚拟机上面实现了socket 之间的通信之后,突发奇想,想要实现树莓派和 荔枝派zero之间的通信. 1.直接将虚拟机下面的程序复制过来,重新编译并且运行.发现是没有办法进行通信的.客户端一直报错: ...

  4. 【Java常用类】BigInteger

    BigInteger Integer类作为int的包装类,能存储的最大整型值为2^31-1,Long类也是有限的, 最大为2 ^63-1.如果要表示再大的整数,不管是基本数据类型还是他们的包装类 都无 ...

  5. [Eclipse插件] 编辑插件IndentGuide

    IndentGuide插件 将可配置的缩进指导线添加到Eclipse文本编辑器 如下图

  6. 个人作业2-6.4-Python爬取顶会信息

    1.个人作业2 数据爬取阶段 import requestsfrom lxml import etreeimport pymysqldef getdata(url): # 请求CVPR主页 page_ ...

  7. VictoriaMetrics:使用vmctl来实现vm-storage向victoria-metrics-prod(单机版)迁移数据

    前一篇提到了,vm-storage的备份数据,无法被victoria-metrics-prod(单机版)读取. 继续翻文档发现vmctl可以实现这个效果: 1.启动vm-restore恢复数据 vmr ...

  8. AOP-底层原理(JDK动态代理实现)

    AOP(JDK动态代理) 1,使用JDK动态代理,使用Proxy类里面的方法创建代理对象 (1)调用 newProxyInstance 方法 方法有三个参数 第一参数,类加载器 第二参数,增强方法所在 ...

  9. vue中清除路由缓存

    beforeRouteLeave (to, from, next) { if (to.name === 'pageA') { /* pageA是需要跳转的路由 */ // console.log('返 ...

  10. mysql加强(4)~多表查询

    mysql加强(4)~多表查询:笛卡尔积.消除笛卡尔积操作(等值.非等值连接),内连接(隐式连接.显示连接).外连接.自连接 一.笛卡尔积 1.什么是笛卡尔积: 数学上,有两个集合A={a,b},B= ...