1 定义
二叉查找树(Binary Search Tree)的特征:
- 若左子树不空,则左子树上所有节点的值均小于或等于它的根节点的值;
- 若右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值;
- 左、右子树也分别为二叉查找树。
2 查找
将要查找的数据与根节点的值进行比较,如果相等就返回,如果小于就到左子树中递归查找,如果大于就到右子树中递归查找。
3 添加
在二叉查找树中进行插入操作时只需找到待插入的父节点,将数据插入即可。
4 删除
- 在待删除的节点没有子节点时,直接删除该节点,即在其父节点中将其对应的子节点置空即可。
- 在待删除的节点只有一个子节点时,使用子节点替换当前节点,然后删除该节点即可。
- 在待删除的节点有两个子节点时,首先查找该节点的替换节点(两种做法:左子树中的最大节点、右子树中的最小节点),然后替换待删除的节点为替换节点,最后删除替换节点。
5 存在的问题
倘若二叉查找树多次在同一侧插入节点,将会导致性能变差,几乎变成线性查找。
6 实现
定义二叉查找树节点:
/**
* 二叉查找树节点
*
* @author wgm
* @since 2021/4/25
*/
public class BinarySearchTreeNode {
private Integer data;
private BinarySearchTreeNode leftChild;
private BinarySearchTreeNode rightChild;
public BinarySearchTreeNode(Integer data) {
this.data = data;
}
public BinarySearchTreeNode setLeftChild(int data) {
BinarySearchTreeNode node = new BinarySearchTreeNode(data);
this.setLeftChild(node);
return node;
}
public BinarySearchTreeNode setRightChild(int data) {
BinarySearchTreeNode node = new BinarySearchTreeNode(data);
this.setRightChild(node);
return node;
}
@Override
public String toString() {
return data.toString();
}
public Integer getData() {
return data;
}
public void setData(Integer data) {
this.data = data;
}
public BinarySearchTreeNode getLeftChild() {
return leftChild;
}
public void setLeftChild(BinarySearchTreeNode leftChild) {
this.leftChild = leftChild;
}
public BinarySearchTreeNode getRightChild() {
return rightChild;
}
public void setRightChild(BinarySearchTreeNode rightChild) {
this.rightChild = rightChild;
}
}
定义二叉查找树:
import java.util.LinkedList;
/**
* 二叉查找树
*
* @author wgm
* @since 2021/4/25
*/
public class BinarySearchTree {
private BinarySearchTreeNode root;
public BinarySearchTree(BinarySearchTreeNode root) {
this.root = root;
}
/**
* 建树
*
* @param ints
* @return
*/
public static BinarySearchTree buildTree(int[] ints) {
if (ints == null || ints.length == 0) {
return null;
}
BinarySearchTree tree = new BinarySearchTree(new BinarySearchTreeNode(ints[0]));
for (int i = 1; i < ints.length; i++) {
tree.insert(ints[i]);
}
return tree;
}
/**
* 查找节点是否存在
*
* @param data
* @return
*/
public boolean exist(int data) {
if (this.root == null) {
return false;
}
BinarySearchTreeNode visit = root;
while (visit != null) {
if (data < visit.getData()) {
visit = visit.getLeftChild();
} else if (data > visit.getData()) {
visit = visit.getRightChild();
} else {
return true;
}
}
return false;
}
/**
* 添加节点
*
* @param data
*/
public void insert(int data) {
if (this.root == null) {
return;
}
BinarySearchTreeNode preVisit = root;
BinarySearchTreeNode visit = root;
while (visit != null) {
preVisit = visit;
if (data <= visit.getData()) {
visit = visit.getLeftChild();
} else {
visit = visit.getRightChild();
}
}
if (data <= preVisit.getData()) {
preVisit.setLeftChild(data);
} else {
preVisit.setRightChild(data);
}
}
/**
* 删除节点
*
* @param data
*/
public void delete(int data) {
if (this.root == null) {
return;
}
BinarySearchTreeNode preVisit = root;
BinarySearchTreeNode visit = root;
while (visit != null) {
if (data < visit.getData()) {
preVisit = visit;
visit = visit.getLeftChild();
} else if (data > visit.getData()) {
preVisit = visit;
visit = visit.getRightChild();
} else {
delete(preVisit, visit);
break;
}
}
}
private void delete(BinarySearchTreeNode preVisit, BinarySearchTreeNode visit) {
BinarySearchTreeNode leftChild = visit.getLeftChild();
BinarySearchTreeNode rightChild = visit.getRightChild();
if (leftChild == null && rightChild == null) { // 待删除节点为叶子节点
if (visit.getData() < preVisit.getData()) { // 待删除节点为父节点的左节点
preVisit.setLeftChild(null);
} else if (visit.getData() > preVisit.getData()) { // 待删除节点为父节点的右节点
preVisit.setRightChild(null);
} else { // 待删除节点为根节点
root = null;
}
} else if (leftChild != null && rightChild == null) { // 待删除节点只有左节点
if (visit.getData() < preVisit.getData()) { // 待删除节点为父节点的左节点
preVisit.setLeftChild(leftChild);
} else if (visit.getData() > preVisit.getData()) { // 待删除节点为父节点的右节点
preVisit.setRightChild(leftChild);
} else { // 待删除节点为根节点
root = leftChild;
}
} else if (leftChild == null && rightChild != null) { // 待删除节点只有右节点
if (visit.getData() < preVisit.getData()) { // 待删除节点为父节点的左节点
preVisit.setLeftChild(rightChild);
} else if (visit.getData() > preVisit.getData()) { // 待删除节点为父节点的右节点
preVisit.setRightChild(rightChild);
} else { // 待删除节点为根节点
root = rightChild;
}
} else { // 待删除节点有左、右节点
BinarySearchTreeNode ppre = leftChild;
BinarySearchTreeNode p = leftChild;
while (p.getRightChild() != null) {
ppre = p;
p = p.getRightChild();
}
ppre.setRightChild(null);
if (visit.getLeftChild() != p) {
p.setLeftChild(visit.getLeftChild());
}
p.setRightChild(visit.getRightChild());
visit.setLeftChild(null);
visit.setRightChild(null);
if (visit.getData() < preVisit.getData()) { // 待删除节点为父节点的左节点
preVisit.setLeftChild(p);
} else if (visit.getData() > preVisit.getData()) { // 待删除节点为父节点的右节点
preVisit.setRightChild(p);
} else { // 待删除节点为根节点
root = p;
}
}
}
/**
* 层序遍历(队列)
*/
public void level() {
if (this.root == null) {
return;
}
BinarySearchTreeNode visit = this.root;
LinkedList<BinarySearchTreeNode> queue = new LinkedList<>();
queue.offer(visit);
while (!queue.isEmpty()) {
visit = queue.poll();
System.out.print(visit + " ");
BinarySearchTreeNode leftChild = visit.getLeftChild();
if (leftChild != null) {
queue.offer(leftChild);
}
BinarySearchTreeNode rightChild = visit.getRightChild();
if (rightChild != null) {
queue.offer(rightChild);
}
}
System.out.println();
}
@Override
public String toString() {
return "BinarySearchTree{" +
"root=" + root +
'}';
}
public BinarySearchTreeNode getRoot() {
return root;
}
public void setRoot(BinarySearchTreeNode root) {
this.root = root;
}
}
编写测试类:
import java.util.Arrays;
/**
* 二叉查找树测试
*
* @author wgm
* @since 2021/4/25
*/
public class BinarySearchTreeTest {
public static void main(String[] args) {
int[] ints = new int[] {10, 4, 5, 2, 12, 14, 8};
BinarySearchTree tree = BinarySearchTree.buildTree(ints);
tree.level();
System.out.println("节点11是否存在:" + tree.exist(11));
System.out.println("节点12是否存在:" + tree.exist(12));
System.out.println("\r\n循环添加元素");
for (int i = 0; i < ints.length; i++) {
if (i == 0) {
tree = BinarySearchTree.buildTree(Arrays.copyOfRange(ints, 0, 1));
tree.level();
} else {
tree.insert(ints[i]);
tree.level();
}
}
System.out.println("\r\n循环删除元素");
for (int i = 0; i < ints.length; i++) {
tree.level();
tree.delete(ints[i]);
}
}
}
7 测试
D:\program\Java\jdk1.8.0_241\bin\java.exe -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:59009,suspend=y,server=n "-javaagent:C:\Users\GMWANG~1\AppData\Local\Temp\captureAgent172jars\debugger-agent.jar" -Dfile.encoding=UTF-8 -classpath "D:\program\Java\jdk1.8.0_241\jre\lib\charsets.jar;D:\program\Java\jdk1.8.0_241\jre\lib\deploy.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\access-bridge-64.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\cldrdata.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\dnsns.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\jaccess.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\jfxrt.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\localedata.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\nashorn.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\sunec.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\sunjce_provider.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\sunmscapi.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\sunpkcs11.jar;D:\program\Java\jdk1.8.0_241\jre\lib\ext\zipfs.jar;D:\program\Java\jdk1.8.0_241\jre\lib\javaws.jar;D:\program\Java\jdk1.8.0_241\jre\lib\jce.jar;D:\program\Java\jdk1.8.0_241\jre\lib\jfr.jar;D:\program\Java\jdk1.8.0_241\jre\lib\jfxswt.jar;D:\program\Java\jdk1.8.0_241\jre\lib\jsse.jar;D:\program\Java\jdk1.8.0_241\jre\lib\management-agent.jar;D:\program\Java\jdk1.8.0_241\jre\lib\plugin.jar;D:\program\Java\jdk1.8.0_241\jre\lib\resources.jar;D:\program\Java\jdk1.8.0_241\jre\lib\rt.jar;D:\project\untitled\out\production\untitled;D:\program\JetBrains\IntelliJ IDEA 2020.1\lib\idea_rt.jar" BinarySearchTreeTest
Connected to the target VM, address: '127.0.0.1:59009', transport: 'socket'
10 4 12 2 5 14 8
节点11是否存在:false
节点12是否存在:true
循环添加元素
10
10 4
10 4 5
10 4 2 5
10 4 12 2 5
10 4 12 2 5 14
10 4 12 2 5 14 8
循环删除元素
10 4 12 2 5 14 8
8 4 12 2 5 14
8 2 12 5 14
8 2 12 14
8 12 14
8 14
8
Disconnected from the target VM, address: '127.0.0.1:59009', transport: 'socket'
Process finished with exit code 0