二叉排序树(BinarySortTree)的添加,删除功能实现(java代码)
介绍
二叉排序树:BST(Binary Sort Tree):要求该树的任何一个非叶子结点的左子结点的值小于当前结点的值,右子结点的值大于当前结点的值
注意:若是存在相同值的情况,则将该结点放在当前结点的左子结点或者右子结点都可以
删除结点的思路分析
分为三种情况:
第一种情况:删除的是叶子结点
第二种情况:删除的是只有一颗子树的结点
第三种情况:删除的是有两颗子树的结点
下面代码中会做详细的步骤分析
package com.bingym.temp01.binarysorttree;
public class BinarySortTreeDemo {
/*
* 二叉排序树:BST(Binary Sort Tree):要求该树的任何一个非叶子结点的左子结点的值小于当前结点的值,右子结点的值大于当前结点的值
* 注意:若是存在相同值的情况,则将该结点放在当前结点的左子结点或者右子结点都可以
* 下面进行二叉排序树的创建,结点添加和中序遍历代码的实现
* */
public static void main(String[] args) {
int[] arr = {7,3,10,12,5,1,9,4};
BinarySortTree binarySortTree = new BinarySortTree();
//for循环添加结点
for (int i = 0; i < arr.length; i++) {
binarySortTree.add(new Node(arr[i]));
}
//中序遍历
System.out.println("该二叉排序树的中序遍历为:");
binarySortTree.infixOrder();
//测试删除叶子结点
//binarySortTree.delNode(9);
//System.out.println("二叉树删除叶子结点9后的中序遍历为:");
//binarySortTree.infixOrder();
//测试删除只有一颗子树的结点
//binarySortTree.delNode(5);
//System.out.println("二叉树删除只有一颗子树的结点5后的中序遍历为:");
//binarySortTree.infixOrder();
//测试删除有两颗子树的结点
binarySortTree.delNode(7);
System.out.println("二叉树删除有两颗子树的结点7以后的中序遍历为:");
binarySortTree.infixOrder();
}
}
//定义二叉排序树
class BinarySortTree {
//定义头结点
private Node root;
//构造方法:空参构造
//结点的添加方法
public void add(Node node) {
if (this.root == null) {
//根节点为空,则直接将该结点设置为根节点
this.root =node;
}else {
this.root.add(node);//否则,利用root结点与添加结点的value值进行比较,完成结点的添加工作
}
}
//中序遍历的方法
public void infixOrder() {
if (this.root != null) {
//说明当前树不为空
this.root.infixOrder();
}else {
System.out.println("二叉排序树为空...");
}
}
//查找当前删除结点的方法
public Node search(int value) {
if (this.root == null) {
//说明该树没有结点
return null;
} else {
return this.root.search(value);
}
}
//查找当前删除结点的父结点的方法
public Node searchParent(int value) {
if (this.root == null) {
return null;
}else {
return this.root.searchParent(value);
}
}
//根据传入的结点(以传入的结点为根节点),查找左子树最小结点的值,返回其值,并删除其最小结点
//由于找到的最小结点必然为叶子结点,则可以调用delNode方法进行删除
public int delMinNode(Node node) {
Node tempNode = node;
//通过while循环查找到左子树的最小结点
while (tempNode.left != null) {
tempNode = tempNode.left;
}
//退出while循环,tempNode即为左子树的最小结点
//删除该最小结点
delNode(tempNode.value);
//返回最小结点的值
return tempNode.value;
}
//根据传入的结点(以传入的结点为根节点),查找右子树最大结点的值,返回其值,并删除其最大结点
//由于找到的最大结点必然为叶子结点,则可以调用delNode方法进行删除
public int delMaxNode(Node node) {
Node tempNode = node;
//通过while循环查找到右子树的最小结点
while (tempNode.right != null) {
tempNode = tempNode.right;
}
//退出while循环,tempNode即为右子树的最大结点
//删除该最大结点
delNode(tempNode.value);
//返回最小结点的值
return tempNode.value;
}
//进行删除指定value值的结点的方法
public void delNode(int value) {
if (root == null) { //说明查找的树为空
return;
}else { //否则进行删除操作
//第一步:找到要删除的结点targetNode
Node targetNode = search(value);
//若没有(没找到要删除的结点),则直接返回
if (targetNode == null) {
return;
}
//若该树只有一个结点root,并且root结点即为要删除的结点,则直接将root置空即可
if (root.left == null && root.right == null) {
root = null;
return;
}
//第二步:找到要删除结点的父结点
Node parent = searchParent(value);
//第三步:第一种情况:删除结点为叶子结点
if(targetNode.left == null && targetNode.right == null) {//即targetNode的左子结点和右子结点均为null(叶子结点)
//判断targetNode为parent的左子结点还是右子结点
if (parent.left != null && parent.left.value == value) {
//说明targetNode为parent的左子结点
parent.left = null;
}else if (parent.right != null && parent.right.value == value) {
//说明targetNode为parent的右子结点
parent.right = null;
}
}else if (targetNode.left != null && targetNode.right != null) {//第三步:第三种情况:删除结点为有两颗子树的结点
//删除含有两颗子树的结点的结点
//从targetNode的右子树中找到最小结点的值minValue:其中内部已经删除掉minValue对应的结点
int minValue = delMinNode(targetNode.right);
//将target的value修改为minValue
targetNode.value = minValue;
//第二种方式
/*int maxValue = delMaxNode(targetNode.left);
targetNode.value = maxValue;*/
}else {//第三步:第二种情况:删除结点为只有一颗子树的结点
//判断删除结点存在左子结点还是右子结点
if (targetNode.left != null) { //说明targetNode存在左子结点
if (parent.left.value == value) {//targetNode是parent的左子结点
parent.left = targetNode.left;
}else {//targetNode是parent的右子结点
parent.right = targetNode.left;
}
}else { //说明targetNode存在右子结点
if (parent.left.value == value) {//targetNode是parent的左子结点
parent.left = targetNode.right;
}else {//targetNode是parent的右子结点
parent.right = targetNode.right;
}
}
}
}
}
}
//定义结点类
class Node {
public int value;//结点的值
public Node left;
public Node right;
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//中序遍历的方法
public void infixOrder() {
//左子树递归遍历
if (this.left != null) {
this.left.infixOrder();
}
//输出当前结点
System.out.println(this);
//右子树递归遍历
if (this.right != null) {
this.right.infixOrder();
}
}
//结点的添加方法
public void add(Node node) {
if (node == null) {
//首先判断添加的结点是否为空,若为空,直接返回
return;
}
if (node.value < this.value) {//如果传入结点的value值小于当前根节点的value值
if (this.left == null) {//若当前根节点的左子树为空
this.left = node;
}else {
this.left.add(node);//递归地向左子树添加
}
}else {//如果传入结点的value值大于(或者等于)当前根节点的value值
if (this.right == null) {
this.right = node;
}else {
this.right.add(node);//递归地向右子树添加
}
}
}
//根据value值查找当前要删除的结点
//value:表示查找的结点的value值,return:若找到,则返回,若没找到,则返回null
public Node search(int value) {
//首先判断当前结点是否为要删除的结点
if (this.value == value) {
//说明当前结点即为要删除的结点
return this;
}
if (this.value > value) {//查找的value值小于当前结点的value值,则向左递归查找
if (this.left == null) {
return null;
}
return this.left.search(value);
}else {//查找的value值大于或者等于当前结点的value值(尽可能保证二叉排序树的结点值都不相同),则向右递归查找
if (this.right == null) {
return null;
}
return this.right.search(value);
}
}
//根据value值查找当前要删除结点的父节点
//value:表示查找的结点的value值,return:若找到,则返回,若没找到,则返回null
public Node searchParent(int value) {
//如果当前结点就是所要删除结点的父节点,则直接返回其结点
if ((this.left != null &&this.left.value == value) || (this.right != null && this.right.value ==value)) {
//直接返回当前结点,即为删除结点的父结点
return this;
}else {
if (value < this.value && this.left != null) {
//删除结点值小于当前结点值,并且当前结点左子树不为空,则递归左子树进行查询
return this.left.searchParent(value);
}else if (value >= this.value && this.right != null) {
//删除结点值大于或者等于当前结点值,并且当前结点右子树不为空,则递归右子树进行查询
return this.right.searchParent(value);
}else {
//如果都不满足,则说明找不到该结点的父节点(例如是根节点),则直接返回null
return null;
}
}
}
}
注意:上面的代码存在bug:我们在删除结点的过程,加入我们删除到最后剩下10和1,并且10此时为root结点:
此时我们再删除10这个只含有一个子树的结点时:由于此时10已经成为root,结点,即10的父节点为null,所以我们再delNode(int value)的只含有一颗子树的代码中,需要对targetNode的父节点进行判断,防止出现零指针异常:
//判断删除结点存在左子结点还是右子结点
if (targetNode.left != null) { //说明targetNode存在左子结点
//注意:需要判断此时parent是否为空的情况:有可能通过我们的删除操作最后此时删除的结点变成了root结点
//则此时root的parent结点为空
//这里判断root是否为空:若为空,直接将root指向此时targetNode存在的左子结点
if (parent != null) {
if (parent.left.value == value) {//targetNode是parent的左子结点
parent.left = targetNode.left;
}else {//targetNode是parent的右子结点
parent.right = targetNode.left;
}
}else{
//如果为空,则直接将root指向此时存在的左子结点即可
root = targetNode.left;
}
}else { //说明targetNode存在右子结点
//这里同上:对parent进行判断
if (parent != null) {
if (parent.left.value == value) { //targetNode是parent的左子结点
parent.left = targetNode.right;
}else {//targetNode是parent的右子结点
parent.right = targetNode.right;
}
}else {
//同上:将此时的root结点指向此时存在的右子结点即可
root = targetNode.right;
}
}