BST(二叉排序树)
从BST的概述,BST的创建思想,BST的代码实现与分析,BST的图解展开阐述
若有疏漏,欢迎指出
博主空间
https://blog.youkuaiyun.com/JOElib?type=blogAVL树
https://joelib.blog.youkuaiyun.com/article/details/124072495?spm=1001.2014.3001.5502多叉树和图
https://joelib.blog.youkuaiyun.com/article/details/124042140?spm=1001.2014.3001.5502
目录
BST的概述🐼
BST的定义:🦁
- BST要求任何的非叶子节点的左子节点的权值要求比当前节点的权值要小,且右子节点的权值要比当前节点的权值要大
- 如果父节点的权值和子节点的权值相等,可以任意放在左子节点或者是右子节点,博主代码实现默认放在右子节点
BST的样子:🦁
BST的创建思想🐼
BST的创建🦁
- 用待添加的节点与当前节点进行比较,如果比当前节点大,则判断左子树
- 若右子树为空,则将新的节点直接挂上去,否则向右子树递归添加
- 如果比当前节点小,则判断左子树
- 若左子树为空,则将新的节点直接挂上去,否则向左子树递归添加
BST节点的删除🦁
1.叶子节点的删除😀
- 找到待删除的节点target
- 找到待删除节点的父节点parent
- 判断待删除节点是父节点的左子节点还是右子节点
- 置空
2.非叶子节点且有一棵子树的删除😀
- 找到待删除的节点target
- 找到待删除节点的父节点parent
- 判断待删除节点是父节点的左子节点还是右子节点
- 判断待删除节点有左子树还是右子树
- 根据情况赋值
3.非叶子节点且有两颗子树的删除😀
- 找到待删除的节点target
- 在待删除节点的右子树中找一个最小的节点(或在待删除节点的左子树中找一最大的节点)
- 将找到的节点的值赋值给待删除节点
- 将该节点删除
代码实现与分析🐼
节点类:😶🌫️
1.创建一个节点类🐻
public class Node {
// 节点对应的权
public int value;
// 左子节点
public Node left;
// 右子节点
public Node right;
// 构造方法
public Node(int value) {
this.value = value;
}
// 重写的toString()方法.便于输出
public String toString() {
return value + "";
}
}
代码分析:🐨
- 代码所示
2.创建一个添加节点的方法🐻
// node是待添加的节点
public void add(Node node) {
if (node == null) {
return;
}
if (node.value >= this.value) {
if (right != null) {
right.add(node);
}else {
right = node;
}
}else {
if (left != null) {
left.add(node);
}else {
left = node;
}
}
}
代码分析:🐨
- 首先,我们先要校验待添加节点是否为空,若为空,则直接结束方法
- 根据BST的创建思想得出来以下的代码
3.删除节点的前置操作 😶🌫️
3.1创建一个找到待删除节点的方法🐻
// value是待删除节点的权值
public Node searchTarget(int value) {
if (this.value == value) {
return this;
}else if (value >= this.value) {
return this.right != null?right.searchTarget(value):null;
}else {
return this.left != null?left.searchTarget(value):null;
}
}
代码分析:🐨
- 首先,我们先比较当前节点的权值是否等于待删除节点的权值
- 若等于,说明当前节点是待删除的节点,返回当前节点
- 若不等于,则走一下步骤
- 判断当前节点的权值与待删除节点的权值的大小
- 若待删除节点的权值大于当前节点的权值,说明,待删除节点一定在右子树上(BST特征),否则一定在左子树上
- 再判断右子树(左子树)是否为空,若为空说明找不到待删除的节点,返回null
- 若右子树不为空,则往右子(左子树)树递归查找
- 注意:这里运用到了三目操作符,原理如下:
- 先判断this.right != null这个条件,这个条件若为true,则执行right.searchTarget(value)这个表达式,否则执行null
- 三目操作符中,一个表达式的执行,该表达式的结果作为整个三目操作符表达式的结果
3.2找到待删除节点的父节点🐻
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) {
return this.right != null?right.searchParent(value):null;
}else {
return this.left != null?left.searchParent(value):null;
}
}
代码分析:🐨
- 当前方法与找到待删除节点的方法基本一致,需要改变的是以下节点
- 因为找待删除节点的父节点,所以判断当前节点的左子节点或右子节点是否为待删除的节点
- 若是,则之间返回当前节点,当前节点就是我们想要的父节点,否则递归查找
- 注意:在访问左子节点和右子节点的时候一定要判断其是否为空,否则会空指针异常
3.3创建一个中序遍历的方法(好验证是否写错)🐻
public void infixPrint() {
if (this.left != null) {
left.infixPrint();
}
System.out.println(this);
if (this.right != null) {
right.infixPrint();
}
}
代码分析:🐨
- 略:如有需要,可以翻翻博主之前的博客,里面有详细的解释
BST类:😶🌫️
4.创建一个BST类🐻
public class BinarySortTree {
// root是根节点属性
public Node root;
// 利用JVM默认提供的无参构造即可满足要求
}
代码分析:🐨
- 如代码所示
5.创造对应的表层方法去调用底层方法🐻
public Node searchTarget(int value) {
if (root == null) {
return null;
}else {
return root.searchTarget(value);
}
}
public Node searchParent(int value) {
if (root == null) {
return null;
}else {
return root.searchParent(value);
}
}
public void infixPrint() {
if (root != null) {
root.infixPrint();
}
}
public void add(Node node) {
if (root == null) {
root = node;
return;
}
root.add(node);
}
代码分析:🐨
- 分析上述代码得,都要校验以下根节点是否为空,若为空,这些方法都没有意义,直接结束返回即可
6.删除节点的第三个前置方法(获取左子树最大值)🐻
// node为当前节点(即左子树的根节点)
public int searchLeftMax(Node node) {
var cur = node;
while (cur.right != null) {
cur = cur.right;
}
var res = cur.value;
del(cur.value);
return res;
}
代码分析:🐨
- 将当前节点放入辅助引用cur
- 根据BST树的特征,右子节点的值一定比当前节点的值大,所以我们一直往右子树去找即可,即得 出了对应的while循环及其循环体
- 注意:我们要找到最大节点的父节点,否则无法删除,故cur.right != null
- 当while循环结束后,找到了最大的节点的父节点,将其右子节点的值保存下来,放入res
- 调用del方法,删除该节点(接下来解释),再将值返回即可
7.删除方法(根本)的讲述🐻
// value 待删除节点的权值
public void del(int value) {
if (root == null) {
1 return;
}
2 Node targetDel = searchTarget(value);
3 if (root.left == null && root.right == null && root == targetDel) {
root = null;
return;
}
4 Node parent = searchParent(value);
5 if (targetDel.right == null && targetDel.left == null) {
6 if (parent.left != null && parent.left == targetDel) {
parent.left = null;
7 }else if(parent.right != null && parent.right == targetDel) {
parent.right = null;
}
8 }else if (targetDel.left != null && targetDel.right != null) {
targetDel.value = searchLeftMax(targetDel.left);
9 }else {
10 if (parent != null) {
11 if (parent.left != null && parent.left == targetDel) {
12 if (targetDel.left != null) {
parent.left = targetDel.left;
13 }else {
parent.left = targetDel.right;
}
14 }else if (parent.right != null && parent.right == targetDel) {
15 if (targetDel.left != null) {
parent.right = targetDel.left;
16 }else {
parent.right = targetDel.right;
}
}
17 }else {
18 if (targetDel.right != null) {
root = root.right;
19 }else {
root = root.left;
}
}
}
}
代码分析:🐨
- 注意:由于代码太长,我们分模块展开阐述,注意代码中的标号
- 1.先校验根节点是否为空,若为空,则直接结束方法
- 以下为创建思想的步骤
- 2.找到待删除节点
- 3.(特殊情况)我们先判断以下是否只有根节点,若只有根节点且根节点是我们想要删除的节点,则将root置为null并结束方法
- 原因:若只有根节点的时候,我们是无法得出其父节点(即根节点没有父节点),当此刻只有根节点时,执行以下方法会导致空指针异常(在5发生)
- 4.找到当前节点的父节点
- 5.判断,若当前节点的左子节点和右子节点都为空,说明是叶子节点
- 判断该待删除节点是父节点的左子节点还是右子节点(对应着6,7),判断出来后直接将其置空即可
- 8.判断,若当前节点的左子节点和右子节点都不为空,说明是非叶子节点且有两棵子树
- 将当前节点的权值修改成调用searchLeftMax方法的返回值,就可以完成思路
- 9.否则,只剩下最后一种情况,即待删除节点只有一颗子树的情况
- 10.因为有可能删除根节点,而根节点又没有父节点,所以我们先要排除影响,有这个if
- 11.当父节点的左子节点不为空(有可能为空,不加可能会空指针异常),且父节点的左子节点是待删除节点时
- 12.再判断待删除节点的左子树是否为空,若不为空,则执行parent.left = target.left
- 13.否则待删除节点有右子树,则执行parent.right = target.left
- 注意(进入里面判断的前提是必须有一棵子树)
- 14,15,16同理得
- 17.因为进入了9说明至少有一棵子树
- 18.当根节点的右子树不为空时,删除后右子树的根节点就是新的根节点,所以root.right = root
- 19.同理得
- 注意:为什么8里面也不进行等效操作,因为8不涉及父节点的操作,所以没有必要
删除操作的图解🐼
完整代码🐼
package datastructure.chapter04.tree.binarysorttree;
/**
* Created with IntelliJ IDEA.
* Description:
* User: asus
* Date: 2022-04-01
* Time: 19:46
*/
public class Node {
public int value;
public Node left;
public Node right;
public Node(int value) {
this.value = value;
}
public String toString() {
return value + "";
}
public void add(Node node) {
if (node == null) {
return;
}
if (node.value >= this.value) {
if (right != null) {
right.add(node);
}else {
right = node;
}
}else {
if (left != null) {
left.add(node);
}else {
left = node;
}
}
}
public Node searchTarget(int value) {
if (this.value == value) {
return this;
}else if (value >= this.value) {
return this.right != null?right.searchTarget(value):null;
}else {
return this.left != null?left.searchTarget(value):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) {
return this.right != null?right.searchParent(value):null;
}else {
return this.left != null?left.searchParent(value):null;
}
}
public void infixPrint() {
if (this.left != null) {
left.infixPrint();
}
System.out.println(this);
if (this.right != null) {
right.infixPrint();
}
}
}
package datastructure.chapter04.tree.binarysorttree;
/**
* Created with IntelliJ IDEA.
* Description:
* User: asus
* Date: 2022-04-01
* Time: 19:46
*/
public class BinarySortTree {
public Node root;
public void add(Node node) {
if (root == null) {
root = node;
return;
}
root.add(node);
}
public Node searchTarget(int value) {
if (root == null) {
return null;
}else {
return root.searchTarget(value);
}
}
public Node searchParent(int value) {
if (root == null) {
return null;
}else {
return root.searchParent(value);
}
}
public void del(int value) {
if (root == null) {
return;
}
Node targetDel = searchTarget(value);
if (root.left == null && root.right == null && root == targetDel) {
root = null;
return;
}
Node parent = searchParent(value);
if (targetDel.right == null && targetDel.left == null) {
if (parent.left != null && parent.left == targetDel) {
parent.left = null;
}else if(parent.right != null && parent.right == targetDel) {
parent.right = null;
}
}else if (targetDel.left != null && targetDel.right != null) {
targetDel.value = searchLeftMax(targetDel.left);
}else {
if (parent != null) {
if (parent.left != null && parent.left == targetDel) {
if (targetDel.left != null) {
parent.left = targetDel.left;
}else {
parent.left = targetDel.right;
}
}else if (parent.right != null && parent.right == targetDel) {
if (targetDel.left != null) {
parent.right = targetDel.left;
}else {
parent.right = targetDel.right;
}
}
}else {
if (targetDel.right != null) {
root = root.right;
}else {
root = root.left;
}
}
}
}
public int searchLeftMax(Node node) {
var cur = node;
while (cur.right != null) {
cur = cur.right;
}
var res = cur.value;
del(cur.value);
return res;
}
public void infixPrint() {
if (root != null) {
root.infixPrint();
}
}
}
结论🐼
BST树创建起来还是很简单的,主要是删除操作具有一定的难度,涉及到的思想和步骤有点多,我来总结以下几点
1.删除操作的三大情况
2.每种情况的算法步骤
3.特殊情况的考虑
4.BST的创建
🚇最后一篇:堆排序