前言
在原课程中二叉树删除节点的操作由二叉树类调用节点的删除方法进行递归,因为原返回值类型为void类型
会导致在递归过程中没有合适的方式让方法及时return停止,导致void方法一直执行到底,虽然结果一样但明显是不合适的
以及没有合适的标记让调用者判断是否删除成功(成功可以在删除方法内处理,但失败无法判断)
一、老韩的方法
1.代码
首先是二叉树类的方法,主要进行节点个数的判断,因为对于节点为0以及根节点就是需要删除的节点则没必要调用节点的删除方法
public class BinaryTree {
private TreeNode root;
public static void main(String[] args) {
//创建树
BinaryTree binaryTree = new BinaryTree();
//创建需要的节点
TreeNode treeNode1 = new TreeNode(1);
TreeNode treeNode2 = new TreeNode(2);
TreeNode treeNode3 = new TreeNode(3);
TreeNode root = new TreeNode(4);
//先手动创建二叉树,后续以递归的方式创建二叉树
root.setLeft(treeNode1);
root.setRight(treeNode3);
treeNode1.setRight(treeNode2);
binaryTree.setRoot(root);
binaryTree.deleteTreeNode(5);
binaryTree.preOrder();
}
public void setTreeNode(TreeNode root) {
this.root = root;
}
/**
* 删除节点
*/
public void deleteTreeNode(int num) {
//首先判断是不是空树;以及处理只有一个节点的情况,如果只有一个根节点,则等价于将整棵树置空
if (root == null) {
System.out.println("删除失败,该树是空树");
return;
}
if (root.getNum() == num) {
root = null;
System.out.println("删除成功");
return;
}
boolean flag = root.delete2(num);
if (!flag) {
System.out.println("删除失败,未找到节点");
}
}
public TreeNode getRoot() {
return root;
}
public void setRoot(TreeNode root) {
this.root = root;
}
}
接下来是节点类的删除方法
/**
* 树的节点类
*/
class TreeNode {
private int num;
private TreeNode left;
private TreeNode right;
public TreeNode(int num) { //不需要左右参数,因为可以指向null
this.num = num;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public TreeNode getLeft() {
return left;
}
public void setLeft(TreeNode left) {
this.left = left;
}
public TreeNode getRight() {
return right;
}
public void setRight(TreeNode right) {
this.right = right;
}
@Override
public String toString() {
return "TreeNode{" +
"num=" + num +
'}';
}
public void delete(int num) {
//如果当前节点的左子节点不为空且就是需要删除的节点,则 this.left = null,结束递归
if (left != null && left.num == num) {
left = null;
System.out.println("删除成功");
return;
}
//如果当前节点的右子节点不为空且就是需要删除的节点,则 this.right = null
if (right != null && right.num == num) {
right = null;
System.out.println("删除成功");
return;
}
//如果第二步和第三步没有删除节点,那么我们就需要向左子树(判空)进行递归删除
if (left != null) {
this.left.delete(num);
}
//第四步没有删除节点,就向右子树(判空)进行递归删除
if (right != null) {
this.right.delete(num);
}
System.out.println("走到底");
}
}
2.分析
先画一下我在二叉树类主方法内构造的简单的二叉树的示意图
通过代码可知,删除的逻辑是先看当前节点的左节点,再看右节点,然后左节点递归,接着右节点递归。也就是说,如果删除数字为2的元素,那么按照正常的逻辑,这个方法应该不需要进行右节点的递归就可以完成
- 第一步:匹配4的左节点,不满足,下一步
- 第二步:匹配4的右节点,不满足,下一步
- 第三步:4的左节点递归,此时节点变为1
- 第四步:匹配1的左节点,不满足,下一步
- 第五步:匹配1的右节点,满足,删除,完成
可以看到整个方法是不应该运行到右节点递归的,但上述代码无法完成相应的需求,进行验证,我们在方法后打印一句话
public void delete(int num) {
//如果当前节点的左子节点不为空且就是需要删除的节点,则 this.left = null,结束递归
if (left != null && left.num == num) {
left = null;
System.out.println("删除成功");
return;
}
//如果当前节点的右子节点不为空且就是需要删除的节点,则 this.right = null
if (right != null && right.num == num) {
right = null;
System.out.println("删除成功");
return;
}
//如果第二步和第三步没有删除节点,那么我们就需要向左子树(判空)进行递归删除
if (left != null) {
this.left.delete(num);
}
//第四步没有删除节点,就向右子树(判空)进行递归删除
if (right != null) {
this.right.delete(num);
}
System.out.println("走到底"); //增加一句打印,看看调用时是否没有及时中断方法
}
3.运行结果
可以看到,在节点已经删除的情况下,整个方法继续进行后续的进行以及递归,最终将全部节点查找一遍结束方法
其次因为缺乏标记,无法使调用方法根据返回值打印出删除情况
所以我进行了改进,该该递归方法的返回值改为boolean,通过在方法中获取、判断标记及时地中断遍历的执行,并且调用方可根据返回值判断是否修改成功
二、改进后的方法
1.代码
public boolean deleteImprove(int num) {
boolean flag = false; //设置一个flag位对返回值进行标记
//如果当前节点的左子节点不为空且就是需要删除的节点,则 this.left = null,结束递归
if (left != null && left.num == num) {
left = null;
System.out.println("删除成功");
return true; //删除成功就返回true
}
//如果当前节点的右子节点不为空且就是需要删除的节点,则 this.right = null
if (right != null && right.num == num) {
right = null;
System.out.println("删除成功");
return true; //删除成功就返回true
}
//如果第二步和第三步没有删除节点,那么我们就需要向左子树(判空)进行递归删除
if (left != null) {
flag = this.left.deleteImprove(num); //获取左节点递归的返回值
}
if (flag) { //如果上个递归已经删除掉需要删除的节点,在这里判断return
return true;
}
//第四步没有删除节点,就向右子树(判空)进行递归删除
if (right != null) {
flag = this.right.deleteImprove(num); //获取右节点递归的返回值
}
if (flag) { //如果上个递归已经删除掉需要删除的节点,在这里判断return
return true;
}
System.out.println("走到底");
return false; //如果到头都没删掉,就返回false
}
2.分析
基本上代码已经添加了注释,主要就是获取递归的返回值,及时终止方法的进行;对于调用方法添加以下代码段,对删除失败进行打印
boolean flag = root.deleteImprove(num);
if (!flag) {
System.out.println("删除失败,未找到节点");
}