1. 二叉树查找指定结点
例题要求
- 编写前序查找, 中序查找和后序查找的方法
- 分别使用三种查找方式, 查找指定的目标结点
- 分析各种查找方式分别比较了多少次才找到目标
1.1. 二叉树查找的分类和思路
1.1.1. 前序查找
- 先判断当前结点的 no 是否与目标相等, 若相等返回当前结点
- 如果不相等
- 先判断当前结点的左子结点是否为空, 非空则递归前序查找
- 再判断当前结点的右子结点是否为空, 非空则递归前序查找
- 如果不相等
- 递归过程中如果匹配正确则输出, 都不匹配则最终返回 null
1.1.2. 中序查找
- 先判断当前结点的左子结点是否为空, 非空则递归中序查找
- 然后判断当前结点的 no 是否与目标相等,
相等就返回, 不相等就进行下一步判断 - 再判断当前结点的右子结点是否为空, 非空则递归中序查找
- 递归过程中如果匹配正确则输出, 都不匹配则最终返回 null
1.1.3. 后序查找
- 先判断当前结点的左子结点是否为空, 非空则递归后续查找
- 再判断当前结点的右子结点是否为空, 非空则递归后续查找
- 再判断当前结点的 no 是否与目标相等, 相等就返回
- 递归过程中如果匹配正确则输出, 都不匹配则最终返回 null
1.2. 二叉树三种查找的代码实现
- 接着使用上一篇的类来进行增添相关方法
1.2.1. 结点类新增三个查找方法
- 每个方法前定义了计数器用来统计比较次数, 若不需要计数器直接删除相关内容即可
//计数器
public static int preCount = 0;
//前序查找方法
public TreeNode preSearch(int target_no){
//创建一个临时变量用以接收目标结点
TreeNode target = null;
//在比较前计数器加一
preCount++;
System.out.printf("the preCount is [%d]\n", preCount);
//先判断当前结点的 no 是否和目标 no 相等
if (this.person_no == target_no){
target = this;
}
//不相等, 则进入下面的判断
else{
//如果当前结点的左子结点不为空, 则递归前序查找
if(this.left_node != null){
target = this.left_node.preSearch(target_no);
}
//如果当前结点的右子结点不为空, 则递归前序查找
//target 非空说明找到目标, 直接跳过即可
if (target == null && this.right_node != null){
target = this.right_node.preSearch(target_no);
}
}
//如果当前结点的左右结点都不符合或者没有左右子结点, 则返回默认值 null
return target;
}
//计数器
public static int infixCount = 0;
//中序查找方法
public TreeNode infixSearch(int target_no){
//创建一个临时变量用以接收目标结点
TreeNode target = null;
//如果当前结点的左子结点不为空, 则递归中序查找
if (this.left_node != null){
target = this.left_node.infixSearch(target_no);
}
//在比较前计数器加一
infixCount++;
System.out.printf("the infixCount is [%d]\n", infixCount);
//判断当前结点的 no 是否和目标 no 相等
//target 非空说明找到目标, 直接跳过即可
if(target == null && this.person_no == target_no){
target = this;
}
//如果当前结点的右子结点不为空, 则递归中序查找
//target 非空说明找到目标, 直接跳过即可
if (target == null && this.right_node != null){
target = this.right_node.infixSearch(target_no);
}
return target;
}
//计数器
public static int postCount = 0;
//后序查找方法
public TreeNode postSearch(int target_no){
//创建一个临时变量用以接收目标结点
TreeNode target = null;
//如果当前结点的左子结点不为空, 则递归中序查找
if (this.left_node != null){
target = this.left_node.postSearch(target_no);
}
//如果当前结点的右子结点不为空, 则递归中序查找
//target 非空说明找到目标, 直接跳过即可
if (target == null && this.right_node != null){
target = this.right_node.postSearch(target_no);
}
//在比较前计数器加一
postCount++;
System.out.printf("the postCount is [%d]\n", postCount);
//判断当前结点的 no 是否和目标 no 相等
//target 非空说明找到目标, 直接跳过即可
if(target == null && this.person_no == target_no){
target = this;
}
return target;
}
1.2.2. 二叉树类新增三个查找调用方法
- 新增三个查找调用方法, 实际只是以根结点调用查找方法
//二叉树前序查找
public void preSearch(int target_no){
if(this.root_node != null){
TreeNode target = this.root_node.preSearch(target_no);
if (target != null){
System.out.printf("The target[no=%d] found which is %s\n", target_no, target.toString());
}
else {
System.out.printf("Can't found the target[no=%d]\n", target_no);
}
}
}
//二叉树中序查找
public void infixSearch(int target_no){
if(this.root_node != null){
TreeNode target = this.root_node.infixSearch(target_no);
if (target != null){
System.out.printf("The target[no=%d] found which is %s\n", target_no, target.toString());
}
else {
System.out.printf("Can't found the target[no=%d]\n", target_no);
}
}
}
//二叉树后序查找
public void postSearch(int target_no){
if(this.root_node != null){
TreeNode target = this.root_node.postSearch(target_no);
if (target != null){
System.out.printf("The target[no=%d] found which is %s\n", target_no, target.toString());
}
else {
System.out.printf("Can't found the target[no=%d]\n", target_no);
}
}
}
1.2.3. 测试类新增
- 本次测试测试结点编号为 3 的结点
//测试前序查找
System.out.println("========test preSearch=======");
testTree.preSearch(3);
//测试中序查找
System.out.println("========test infixSearch=======");
testTree.infixSearch(3);
//测试后续查找
System.out.println("========test postSearch=======");
testTree.postSearch(3);
1.3. 二叉树查找测试
- 用的是如下二叉树
1.3.1. 测试结点 3
-
前序查找的结果
- 一共对比了 5 次
- 一共对比了 5 次
-
中序查找的结果
- 一共对比了 6 次
- 一共对比了 6 次
-
后序查找的结果
- 一共对比了 7 次
- 一共对比了 7 次
1.3.2. 测试结点 4
- 结果是比较次数都一样
2. 二叉树结点的删除
例题要求
- 如果删除的结点是叶子结点, 则删除该结点
- 如果删除的结点是非叶子结点, 则删除该子树
(在二叉树没有规则前, 删除非叶子结点对其子结点的操作
并没太大意义, 因此直接删除子树即可)
2.1. 二叉树删除结点的操作思路
-
先判断根结点是否为空树, 如果只有一个根结点, 则二叉树是空的.
-
因为二叉树是单向的, 所以有如下结论:
-
子结点是不能回溯回去父结点的,
因此就要判断当前结点的子结点是否是待删除结点,
而不能去判断当前结点是否为待删除结点. -
结点删除是父结点断开对子结点的引用,
删除操作必须是要在父结点完成.
-
-
如果当前结点的左子结点不为空且是要删除的结点,
直接将其左子结点置空为 null 即可, 并返回结束递归.
否则进行下一步判断. -
如果当前结点的右子结点不为空且是要删除的结点,
直接将其右子结点置空为 null 即可, 并返回结束递归.
否则进行下一步判断. -
如果步骤 2 和步骤 3 都没有删除结点, 则向左子树进行递归删除
-
如果步骤 4 没有删除结点, 则向右子树进行递归删除
-
如果步骤 5 没有删除结点, 则结点并不存在
2.2. 二叉树结点删除代码实现
- 接着使用上一篇的类来进行增添相关方法
2.2.1. 结点类新增
- 根结点的非空判断交由二叉树类调用时实现
- 结点类主要对根结点后的子结点进行判断
//删除结点方法
public boolean delNode(int target_no){
//如果当前结点的左子结点不为空且是要删除的结点,
//直接将其左子结点置空为 null 即可, 并返回结束递归.
if(this.left_node != null && this.left_node.person_no == target_no){
this.left_node = null;
return true;
}
//如果当前结点的右子结点不为空且是要删除的结点,
//直接将其右子结点置空为 null 即可, 并返回结束递归.
if(this.right_node != null && this.right_node.person_no == target_no){
this.right_node = null;
return true;
}
boolean res = false;
//上述步骤都没有删除结点, 则向左子树进行递归删除
if(this.left_node != null){
res = this.left_node.delNode(target_no);
if(res){
return res;
}
}
//向左递归没有删除结点, 则向右子树进行递归删除
if(this.right_node != null){
res = this.right_node.delNode(target_no);
if(res){
return res;
}
}
//如果都没有删除结点, 则结点并不存在, 删除失败
return false;
}
2.2.2. 二叉树类新增
- 二叉树类调用结点类删除结点时要先对二叉树根结点进行非空判断
//二叉树删除结点
public void delNode(int target_no){
//先判断根结点是否为空树, 如果只有一个根结点, 则二叉树是空的.
if(root_node != null){
//再判断根结点是否为目标删除结点
if(root_node.getPerson_no() == target_no){
//如果是, 则置空当前二叉树即可
root_node = null;
}
else{
boolean res = root_node.delNode(target_no);
if(res){
System.out.printf("deleted the node[no=%d]!\n", target_no);
}
else {
System.out.printf("failed to delete the node[no=%d]!\n", target_no);
}
}
}
else {
System.out.println("the root is null!");
}
}
2.2.3. 测试类新增
- 删除结点前后遍历一次
//测试前序遍历
System.out.println("========test preOrder=======");
testTree.preOrder();
//测试删除结点
System.out.println("========test delNode=======");
testTree.delNode(8);//2
//测试前序遍历
System.out.println("========test preOrder=======");
testTree.preOrder();
2.3. 二叉树删除结点测试结果
- 用的是如下二叉树
2.3.1. 删除结点编号为 2 的非叶子结点
- 显而易见包含结点 B 在内的子树 B - D - E 三个结点都被删除
2.3.2. 删除结点编号为 4 的叶子结点
- 显而易见, 只有结点 D 被删除
2.3.3. 删除结点编号为 8 的不存在结点
- 节点不存在, 则提示删除失败