一、相关概念
-
二叉排序树:BST: (Binary Sort(Search) Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
-
示意图:
二、基本应用
2.1 需求分析
给你一个数列 (7, 3, 10, 12, 5, 1, 9),要求能够高效的完成对数据的查询和添加,解决方案如下:
- 使用未排序数组:直接在数组尾添加速度快(优点),但查找速度慢( 缺点)。
- 使用排序数组:可以使用二分查找,查找速度快(优点),为了保证数组有序,在添加新数据时找到插入位置后,后面的数据需整体移动,速度慢(缺点)。
- 使用链式存储-链表:添加数据速度比数组快,不需要数据整体移动(优点),不管链表是否有序,查找速度都慢(缺点)。
- 使用二叉排序树。
2.2 代码示例
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] arr = {7, 3, 10, 12, 5, 1, 9, 2};
// 创建二叉排序树。
BinarySortTree binarySortTree = new BinarySortTree();
// 循环的添加节点到二叉排序树。
for (int i : arr) {
binarySortTree.add(new Node(i));
}
// 中序遍历二叉排序树。
System.out.println("中序遍历二叉排序树~");
binarySortTree.infixOrder();
// 1, 3, 5, 7, 9, 10, 12
// 测试删除节点。
for (int id : arr) {
binarySortTree.del(id);
}
System.out.println("删除节点后:");
binarySortTree.infixOrder();
// binary tree is null.
}
}
/**
* 二叉排序树。
*/
class BinarySortTree {
private Node root;
public Node getRoot() {
return root;
}
public void infixOrder() {
if (null == root) {
System.out.println("binary tree is null.");
} else {
this.root.infixOrder();
}
}
public void add(Node node) {
if (null == root) {
this.root = node;
} else {
this.root.add(node);
}
}
public Node searchParent(int id) {
if (null == root) {
return null;
} else {
return this.root.searchParent(id);
}
}
public Node search(int id) {
if (null == root) {
return null;
} else {
return this.root.search(id);
}
}
/**
* 删除节点。
*
* @param id id
*/
public void del(int id) {
if (null == root) {
throw new NullPointerException("binary tree is null.");
} else {
// [第一步]:找到需要删除的节点。
Node target = search(id);
// 没找到,则退出方法调用。
if (null == target) {
return;
}
// 若这颗二叉排序树只有一个节点,赋值 null 后就退出方法调用。
boolean isOneNode = noChildNode(this.root);
if (isOneNode) {
this.root = null;
return;
}
// [第二步]:找到待删除节点的父节点。
Node parent = searchParent(id);
boolean isLeafNode = noChildNode(target);
boolean hasTwoTrees = hasTwoTrees(target);
// [第三步]:根据不同情况执行对应的删除操作。
// 情况一:删除叶子节点。
if (isLeafNode) {
delLeafNode(id, parent);
// 情况二:删除有两颗子树的节点。
} else if (hasTwoTrees) {
int minId = delTreeMin(target.getRight());
target.setId(minId);
// 情况三:删除只有一颗子树的节点。
} else {
delJustOneTreeNode(parent, target, id);
}
}
}
/**
* 删除只有一颗子树的节点。
*
* @param parent 父节点
* @param target 待删除的目标节点
* @param id id
*/
private void delJustOneTreeNode(Node parent, Node target, int id) {
if (null != target.getLeft()) {
delCurrentNode(parent, target.getLeft(), id);
} else {
delCurrentNode(parent, target.getRight(), id);
}
}
/**
* 删除当前节点。
*
* @param parent 父节点
* @param node 待删除的目标节点
* @param id id
*/
private void delCurrentNode(Node parent, Node node, int id) {
if (parent != null) {
// 如果目标节点是父节点的左子节点。
if (parent.getLeft().getId() == id) {
parent.setLeft(node);
} else {
// 目标节点是父节点的右子节点。
parent.setRight(node);
}
} else {
root = node;
}
}
/**
* 删除当前树的最小节点
*
* @param node 节点
* @return int id
*/
private int delTreeMin(Node node) {
Node target = node;
// 向左进行查找。
while (null != target.getLeft()) {
target = target.getLeft();
}
// 删除最小节点。
del(target.getId());
return target.getId();
}
/**
* 判断当前节点是没有子节点的。
*
* @param node 节点
* @return boolean
*/
private boolean noChildNode(Node node) {
return null == node.getLeft() && null == node.getRight();
}
/**
* 判断当前节点是有两颗树的。
*
* @param node 节点
* @return boolean
*/
private boolean hasTwoTrees(Node node) {
return null != node.getLeft() && null != node.getRight();
}
/**
* 删除叶子节点。
*
* @param id id
* @param parent 父节点
*/
private void delLeafNode(int id, Node parent) {
//判断目标节点是父节点的左子节点,还是右子节点。
if (null != parent.getLeft() && parent.getLeft().getId() == id) {
parent.setLeft(null);
} else if (null != parent.getRight() && parent.getRight().getId() == id) {
parent.setRight(null);
}
}
}
/**
* 节点。
*/
class Node {
private int id;
private Node left;
private Node right;
public Node(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
@Override
public String toString() {
return "Node id=[" + id + "]";
}
/**
* 查找要删除的节点。
*
* @param id id
* @return {@link Node}
*/
public Node search(int id) {
// 找到了。
if (id == this.getId()) {
return this;
// 向左递归查找。
} else if (id < this.getId()) {
if (null == this.getLeft()) {
return null;
}
return this.getLeft().search(id);
// 否则,向右递归查找。
} else {
if (null == this.getRight()) {
return null;
}
return this.getRight().search(id);
}
}
/**
* 查找要删除节点的父节点。
*
* @param id id
* @return {@link Node}
*/
public Node searchParent(int id) {
if (
// 如果当前节点就是要删除的节点的父节点,就进行返回。
(null != this.getLeft() && this.getLeft().getId() == id) ||
(null != this.getRight() && this.getRight().getId() == id)
) {
return this;
} else {
// 如果查找的值小于当前节点的值, 并且当前节点的左子节点不为空。
if (null != this.getLeft() && id < this.getId()) {
// 向左子树递归查找。
return this.getLeft().searchParent(id);
} else if (null != this.getRight() && id >= this.getId()) {
// 向右子树递归查找。
return this.getRight().searchParent(id);
} else {
// 没有找到父节点。
return null;
}
}
}
/**
* 添加节点。
*
* @param node 节点
*/
public void add(Node node) {
if (null == node) {
throw new NullPointerException();
}
// 通过当前节点值判断与根节点的关系。
if (this.getId() > node.getId()) {
if (null == this.getLeft()) {
// 左子节点赋值。
this.setLeft(node);
} else {
// 左子树递归。
this.getLeft().add(node);
}
} else {
if (null == this.getRight()) {
// 右子节点赋值。
this.setRight(node);
} else {
// 右子树递归。
this.getRight().add(node);
}
}
}
/**
* 中序遍历。
*/
public void infixOrder() {
if (null != this.getLeft()) {
this.getLeft().infixOrder();
}
System.out.println(this);
if (null != this.getRight()) {
this.getRight().infixOrder();
}
}
}
三、结束语
“-------怕什么真理无穷,进一寸有一寸的欢喜。”
微信公众号搜索:饺子泡牛奶。