1. 概念
二叉排序(查找)树( BST:Binary Sort(Search) Tree ):
对于二叉排序树,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大,如果有相同的值,可以将该值放在左子节点或右子节点。
图示:
2. 二叉排序树的创建与遍历
由二叉排序树的性质可知其中序遍历结果是有序的
代码如下:
public class BinarySortTreeDemo {
public static void main(String[] args) {
//初始化数组
int[] arr = new int[]{9, 3, 45, -2, 7, -23};
BinarySortTree tree = new BinarySortTree();
//循环遍历二叉树,将其作为节点加入二叉树
for (int value : arr) {
tree.add(new BinarySortTree.Node(value));
}
//中序遍历二叉树
tree.midOrder();
}
}
class BinarySortTree {
/**
* 节点内部类
*/
static class Node {
private int value;
private Node left;
private Node right;
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
/**
* 添加节点
* @param node 待添加的节点
*/
public void add(Node node) {
//如果待添加的节点为空,则直接结束方法
if (node == null) {
return;
}
//如果待添加节点的值小于当前节点的值
if (node.value < this.value) {
//如果左子节点为空,则直接添加为左子节点
if (this.left == null) {
this.left = node;
} else {
//否则向左递归比较添加
this.left.add(node);
}
} else {
//如果待添加节点的值大于等于当前的值
//且右子节点为空,则直接添加为右子节点
if (this.right == null) {
this.right = node;
} else {
//否则向右递归比较添加
this.right.add(node);
}
}
}
/**
* 中序遍历
*/
public void midOrder() {
if (this.left != null) {
this.left.midOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.midOrder();
}
}
}
private Node root;
/**
* @param node 添加节点
*/
public void add(Node node) {
if (root == null) {
root = node;
} else {
root.add(node);
}
}
/**
* 中序遍历
*/
public void midOrder() {
if (root == null) {
System.out.println("当前二叉树为空!无法进行中序遍历!");
} else {
root.midOrder();
}
}
}
测试结果:
Node{value=-23}
Node{value=-2}
Node{value=3}
Node{value=7}
Node{value=9}
Node{value=45}
3. 二叉排序树的删除
二叉排序树的删除需要考虑下列三种情况(二叉排序树如下图):
- 删除叶子节点(如:-23,7,45)
- 删除只有一棵子树的节点(如:-2)
- 删除只有两棵子树的节点(如:3,9)
二叉排序树:
3.1 删除叶子节点
思路:
1.找到待删除的叶子节点
2.找到待删除节点的父节点
3.判断待删除的节点是左子节点还是右子节点
4.通过父节点直接删除该节点
3.2 删除只有一棵子树的节点
思路:
1.找到待删除的节点
2.找到待删除节点的父节点
3.判断待删除节点的子节点是左子节点还是右子节点
4.判断待删除的节点是左子节点还是右子节点
1.如果待删除节点的子节点是左子节点
1.如果待删除节点是其父节点的左子节点
则将其子节点作为其父节点的左子节点
2.如果待删除节点是其父节点的右子节点
则将其子节点作为其父节点的右子节点
2.如果待删除节点的子节点是右子节点
1.如果待删除节点是其父节点的左子节点
则将其子节点作为其父节点的左子节点
2.如果待删除节点是其父节点的右子节点
则将其子节点作为其父节点的右子节点
3.3 删除有两棵子树的节点
思路:
1.找到待删除的叶子节点
2.找到待删除节点的父节点
3.从待删除节点的右子树找到值最小的节点(也可以找左子树的最大值)
4.用一个临时变量将最小值保存
5.删除最小值节点
6.将最小值赋给待删除节点
3.4 代码实现
代码如下:
public class BinarySortTreeDemo {
public static void main(String[] args) {
//初始化数组
int[] arr = new int[]{9, 3, 45, -2, 7, -23};
BinarySortTree tree = new BinarySortTree();
//循环遍历二叉树,将其作为节点加入二叉树
for (int value : arr) {
tree.add(new BinarySortTree.Node(value));
}
System.out.println("中序遍历二叉树:");
tree.midOrder();
System.out.println("删除叶子节点:");
tree.delNode(7);
tree.midOrder();
System.out.println("删除只有一棵子树的节点:");
tree.delNode(-2);
tree.midOrder();
System.out.println("删除有两棵子树的节点:");
tree.delNode(9);
tree.midOrder();
}
}
class BinarySortTree {
/**
* 节点内部类
*/
static class Node {
private int value;
private Node left;
private Node right;
public Node() {
}
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
/**
* 查找当前二叉树中是否存在要删除的节点
*
* @param value 要删除的值
* @return 如果在当前二叉树中存在当前元素,则返回节点;否则,返回 null
*/
public Node search(int value) {
if (this.value == value) {
return this;
} else if (value < this.value && this.left != null) {
return this.left.search(value);
} else if (value > this.value && this.right != null) {
return this.right.search(value);
}
return null;
}
/**
* 查找要删除节点的父节点
*
* @param 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 (this.left != null && value < this.value) {
return this.left.searchParent(value);
} else if (this.right != null && value > this.value) {
return this.right.searchParent(value);
}
return null;
}
/**
* 添加节点
*
* @param node 待添加的节点
*/
public void add(Node node) {
//如果待添加的节点为空,则直接结束方法
if (node == null) {
return;
}
//如果待添加节点的值小于当前节点的值
if (node.value < this.value) {
//如果左子节点为空,则直接添加为左子节点
if (this.left == null) {
this.left = node;
} else {
//否则向左递归比较添加
this.left.add(node);
}
} else {
//如果待添加节点的值大于等于当前的值
//且右子节点为空,则直接添加为右子节点
if (this.right == null) {
this.right = node;
} else {
//否则向右递归比较添加
this.right.add(node);
}
}
}
/**
* 中序遍历
*/
public void midOrder() {
if (this.left != null) {
this.left.midOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.midOrder();
}
}
}
private Node root;
/**
* 调用节点内部类中封装的查询节点方法
*
* @param value 要删除的值
* @return 如果在当前二叉树中存在当前元素,则返回节点;否则,返回 null
*/
public Node search(int value) {
if (root == null) {
return null;
} else {
return root.search(value);
}
}
/**
* 调用节点内部类中封装的查询父节点方法
*
* @param value 要删除的值
* @return 如果在当前二叉树中存在当前元素节点的父节点,则返回父节点;否则,返回 null
*/
public Node searchParent(int value) {
if (root == null) {
return null;
} else {
return root.searchParent(value);
}
}
/**
* 删除当前节点右子树最小值节点并返回最小值
*
* @param node 要删除的节点
* @return 待删除节点右子树最小值节点的值
*/
public int delRightTreeMin(Node node) {
Node target = node;
while (target.left != null) {
target = target.left;
}
//调用删除节点方法,只是让其父节点的左/右子节点的引用指向空
//该节点实际仍存在
delNode(target.value);
return target.value;
}
/**
* 删除节点
*
* @param value 待删除的值
*/
public void delNode(int value) {
if (root == null) {
System.out.println("当前二叉树为空!无法进行删除操作!");
} else {
Node targetNode = search(value);
//如果待删除节点为空(即二叉树中没有找到该节点),结束方法
if (targetNode == null) {
return;
}
//如果上一步条件判断没有结束方法,则说明在二叉树中找到了该节点
//此时如果根节点的左右子节点均为空,则该节点就是根节点
//直接将根节点置空,即删除该节点
if (root.left == null && root.right == null) {
root = null;
return;
}
Node parentNode = searchParent(value);
//待删除节点的左右子树均为空,即为叶子节点
if (targetNode.left == null && targetNode.right == null) {
//如果当前节点为其父节点的左/右子节点,则直接通过其父节点删除
if (parentNode.left != null && parentNode.left.value == value) {
parentNode.left = null;
} else if (parentNode.right != null && parentNode.right.value == value) {
parentNode.right = null;
}
//如果待删除节点的左右子树均不为空,即为有两棵子树的节点
} else if (targetNode.left != null && targetNode.right != null) {
//找到并删除其右子树的最小值节点
//并将该节点的值赋给当前节点
targetNode.value = delRightTreeMin(targetNode.right);
//除去上述两种情况,即为有一棵子树的节点
} else {
//判断当前节点的子节点为左子节点还是右子节点
if (targetNode.left != null) {
//如果其父节点为空,则当前节点即为根节点
//直接执行 else,将子节点赋给根节点
if (parentNode != null) {
//判断当前节点是其父节点的左子节点还是右子节点
//然后通过父节点删除
if (parentNode.left == targetNode) {
parentNode.left = targetNode.left;
} else {
parentNode.right = targetNode.left;
}
} else {
root = targetNode.left;
}
} else {
//如果其父节点为空,则当前节点即为根节点
//直接执行 else,将子节点赋给根节点
if (parentNode != null) {
//判断当前节点是其父节点的左子节点还是右子节点
//然后通过父节点删除
if (parentNode.left == targetNode) {
parentNode.left = targetNode.right;
} else {
parentNode.right = targetNode.right;
}
} else {
root = targetNode.right;
}
}
}
}
}
/**
* @param node 添加节点
*/
public void add(Node node) {
if (root == null) {
root = node;
} else {
root.add(node);
}
}
/**
* 中序遍历
*/
public void midOrder() {
if (root == null) {
System.out.println("当前二叉树为空!无法进行中序遍历!");
} else {
root.midOrder();
}
}
}
测试结果:
中序遍历二叉树:
Node{value=-23}
Node{value=-2}
Node{value=3}
Node{value=7}
Node{value=9}
Node{value=45}
删除叶子节点:
Node{value=-23}
Node{value=-2}
Node{value=3}
Node{value=9}
Node{value=45}
删除只有一棵子树的节点:
Node{value=-23}
Node{value=3}
Node{value=9}
Node{value=45}
删除有两棵子树的节点:
Node{value=-23}
Node{value=3}
Node{value=45}