原创:转载需注明原创地址 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. Zookeeper基础教程(二):Zookeeper安装

    上一篇说了,一个Zookeeper集群一般认为至少需要3个节点,所以我们这里安装需要准备三台虚拟机: # 192.168.209.133 test1 # 192.168.209.134 test2 # ...

  2. golang mongodb 驱动二次封装

    mongodb 官方的go驱动包 go.mongodb.org/mongo-driver 使用起来比较繁琐,最近对其进行了二次封装 github地址:https://github.com/w3liu/ ...

  3. Linux下如何部署FTP服务器

    FTP 是 File Transfer Protocol 的缩写,即文件传输协议,它通过网络在服务器和客户端之间传输文件,现在已经成为一种广泛使用的标准工具 vsftpd 是 very secure ...

  4. IdentityServer4 综合应用实战系列 (一)登录

    这篇文章主要说登录,这里抛开IdentityServer4的各种模式,这里只说登录 我们要分别实现 4中登录方式来说明,  IdentityServer4本地登陆 . Windows账户登录(本地的电 ...

  5. JS 在使用hasOwnProperty()函数时报错

    在使用hasOwnProperty()方法判断对象是否具有某种属性时eslint报下列错误: Do not access Object.prototype method 'hasOwnProperty ...

  6. Centos安装rrdtool的yum源

    由于centos的标准组件中是不带rrdtool的,因此我们需要添加一个dag的yum源,以安装rrdtool. 修改/etc/yum.repos.d/CentOS-Base.repo, #vi /e ...

  7. Go语言系列之自定义实现日志库

    日志库logo gitee地址传送门:https://gitee.com/zhangyafeii/logo 日志库需求分析 1. 支持往不同的地方输出日志 2. 日志分级别 Debug Trace I ...

  8. fis学习

    http://fis.baidu.com/docs/beginning/getting-started.html#md5 还是喜欢时间戳?没问题,FIS也可以满足你的需求,点击这里

  9. 《手把手教你》系列技巧篇(五十六)-java+ selenium自动化测试-下载文件-上篇(详细教程)

    1.简介 前边几篇文章讲解完如何上传文件,既然有上传,那么就可能会有下载文件.因此宏哥就接着讲解和分享一下:自动化测试下载文件.可能有的小伙伴或者童鞋们会觉得这不是很简单吗,还用你介绍和讲解啊,不说就 ...

  10. 毫米转像素dpi

    public static double MillimeterToPixel_X(double length) //length是毫米,1厘米=10毫米 { System.Windows.Forms. ...