原创:转载需注明原创地址 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. Java高级大一结业认证考试试题 - 云南农业职业技术学院 - 互联网技术学院 - 美和易思校企合作专业

     第1题 .关于XML的文档结构描述错误的是 一个基本的XML文档通常由序言和文档元素两部分组成 XML文档中的序言可以包括XML声明.处理指令和注释 XML文档中的元素以树形结构排列 XML文档的声 ...

  2. Java高级程序设计笔记 • 【第5章 XML解析】

    全部章节   >>>> 本章目录 5.1 XML 文档概述 5.1.1 XML文档结构 5.1.1 XML结构说明 5.1.1 XML文档元素 5.1.2 XML文档语法规范 ...

  3. Ranger-Solr审计日志安装

    使用RangerAdmin安装solr,基于已有的solr环境安装,主要是在solr创建用于存储数据的CoreAdmin. # 1.解压安装 在solr的安装机器上面,获取RangerAdmin并且解 ...

  4. 深入 Laravel 内核之外观模式(门面模式)

    门面模式核心内容: 客户端与子系统的通信通过外观对象进行: 外观对象封装一系列子系统的具体对应方法,对客户端只需暴露一个单一的入口方法: 客户端通过访问外观对象即可调用子系统的基础方法,无需关心子系统 ...

  5. Visual Studio Code常用插件(持续更新)

    1.open in browser:使用快捷键在浏览器中打开html页面 在默认浏览器中打开html页面是Alt+B:在其他浏览器中显示是Shift+Alt+B:也可以右键单击html文件,在弹出的窗 ...

  6. bind 标签

    <select id="finduserbylikename"  parameterType="string"  resultMap="cour ...

  7. 总结 sql 的 并集、交集、差集

    有两个表 ,表a ,表b , create table a { age  int , name varchar(20) } ending=innodb; insert into a values(13 ...

  8. Java 递归 常见24道题目 总结

    1.N个台阶的走法递归[这里设为10个台阶] /** * N个台阶的走法递归 * <p> * 有个楼梯,台阶有10个,每次可以跳上1阶 或者 2阶 ,那么台阶的走法一共有多少种 */ @T ...

  9. [转]webpack配置本地服务器

    亲测,webpack打包vue项目之后生成的dist文件可以部署到 express 服务器上运行. 我的vue项目结构如下: 1. 进入该vue项目目录,打开git bash,执行:npm run b ...

  10. SYCOJ2140祝福短信

    题目-祝福短信 (shiyancang.cn) 1 #include<bits/stdc++.h> 2 using namespace std; 3 map<string,bool& ...