1. 简介
二叉树(Binary Tree)是一种非线性的数据结构(线性数据结构:数组,链表,栈,队列)。 首先什么样的结构算是树,树是一个无连通的图。其中二叉树是树中的特例,其一个顶点的度不大于3。二叉搜索树是二叉树的特例,要求一个节点的左子节点的值小于其值,右子节点的值大于其值。
可以看出节点之间有相互关系,一个节点的上一层节点叫父亲节点,属于同一个父亲节点的另一个节点叫兄弟节点,其下一层节点叫子节点。把没有父亲节点的节点叫根节点,把没有任何子节点的节点叫叶子节点。
2. 二叉搜索树的特性
虽然二叉搜索树在极端环境下会退化成一个近似链表的结构,时间复杂度为O(N),但左右平均情况下的时间复杂度为O(logN)。所以为了维持这个平衡关系,还衍生出了一个叫红黑树的数据结构。
如果我们要实现这样的一个二叉搜索树,具体过程是什么样子呢。我来详细讲述一下。
2.1 搜索
根据搜索的值进行左右递归,如果正好遇到要搜索的值返回true,搜索到叶子节点也没有遇到则返回false。
public boolean search(int value) {
return search(root, value);
}
public boolean search(Node node, int value) {
if (node == null) return false;
if (node.value > value) return search(node.leftNode, value);
else if (node.value < value) return search(node.rightNode, value);
return true; // 相同的情况返回true
}
2.2 插入
插入操作是根据插入值的大小在二叉搜索树中遍历,当遍历到叶子节点时停止,并根据大小插入到左或者右子节点中。
public boolean insert(int value) {
return insert(root, value);
}
public boolean insert(Node node, int value) {
if (value == node.value) return false;
if (node.value > value) {
if (node.leftNode != null) {
insert(node.leftNode, value);
} else {
Node newNode = new Node(value);
node.leftNode = newNode;
}
} else {
if (node.rightNode != null) {
insert(node.rightNode, value);
} else {
Node newNode = new Node(value);
node.rightNode = newNode;
}
}
return true;
}
2.3 查找最小值,最大值
二叉搜索树中的最小值即是二叉搜索树中的最左节点。所以一直递归左节点直到遇到叶子节点,最大值也是同理,递归到最右边的叶子节点即可。
public Node minValue() {
return minValue(root);
}
public Node minValue(Node node) {
// 查找二叉查找树中的最小值,即最左边的节点
if (node == null) return null;
if (node.leftNode == null) {
return node;
}
return minValue(node.leftNode);
}
public Node maxValue() {
return maxValue(root);
}
public Node maxValue(Node node) {
// 查找二叉查找树中的最大值,即最右边的节点
if (node == null) return null;
if (node.rightNode == null) {
return node;
}
return maxValue(node.rightNode);
}
2.4 遍历
二叉搜索树的遍历根据遍历顺序的不同分为前序遍历,中序遍历,后序遍历。 前序遍历: 根节点, 左节点, 右节点 中序遍历: 左节点, 根节点, 右节点 后序遍历: 左节点, 右节点, 根节点
public void preOrderSearch() {
preOrderSearch(root);
}
public void preOrderSearch(Node node) {
// 前序遍历, 遍历顺序:根节点,左节点,右节点
System.out.println(node.value);
if (node.leftNode != null) {
preOrderSearch(node.leftNode);
}
if (node.rightNode != null) {
preOrderSearch(node.rightNode);
}
}
public void inOrderSearch() {
inOrderSearch(root);
}
public void inOrderSearch(Node node) {
// 中序遍历, 遍历顺序:左节点,根节点,右节点
if (node.leftNode != null) {
inOrderSearch(node.leftNode);
}
System.out.println(node.value);
if (node.rightNode != null) {
inOrderSearch(node.rightNode);
}
}
public void postOrderSearch() {
postOrderSearch(root);
}
public void postOrderSearch(Node node) {
// 后序遍历, 遍历顺序:左节点,右节点,根节点
if (node.leftNode != null) {
inOrderSearch(node.leftNode);
}
if (node.rightNode != null) {
inOrderSearch(node.rightNode);
}
System.out.println(node.value);
}
2.5 删除
为什么最后说删除呢。因为删除最难最麻烦,需要分情况讨论。一共有三种情况。
- 如果要删除的节点没有没有左右子节点,也就是要删除的节点是叶子节点 -> 则直接把要删除的节点设为null。
- 如果要删除的节点只有一个左节点或者右节点 -> 把子节点替换到要删除的节点上面。
- 如果要删除的节点左右子节点都存在 -> 把要删除的节点的右子树的最小值,即右子树的最左节点替换到要删除节点上,并删除那个最小节点。 说的有点复杂,直接看代码:
public boolean delete(int value) {
return delete(null, root, value);
}
public boolean delete(Node parent, Node node, int value) {
// 删除节点的操作根据要删除节点的位置的不同分为三种情况
// 要删除节点没有任何子节点: 直接删除该节点
// 要删除节点有一个子节点: 把子节点顶替到要删除节点的位置
// 要删除节点有两个子节点: 把右子树中最左节点替换到要删除节点的位置
if (node == null) return false;
if (node.value != value) {
if (node.value > value) {
return delete(node, node.leftNode, value);
} else {
return delete(node, node.rightNode, value);
}
} else {
if (node.leftNode == null && node.rightNode == null) {
if(node==root){
// 如果删除节点是根节点,且没有左右子节点,则直接把root设为null
root = null;
return true;
}
boolean isParentLeftNode = parent.value > node.value;
// 没有左右子节点
if (isParentLeftNode) {
parent.leftNode = null;
} else {
parent.rightNode = null;
}
node = null;
} else if (node.leftNode != null && node.rightNode != null) {
// 有左右两个节点
// 查找右子树中的最左节点,进行替换
Node minNode = node.rightNode;
Node minNodeParent = null;
if(minNode.leftNode==null){
// 如果要删除节点的右节点没有左子树,则把右节点直接替换到要删除的节点上
node.value = node.rightNode.value;
node.rightNode = null;
}else{
// 如果要删除节点的右节点有左子树,则找其最小值
while(minNode.leftNode !=null){
minNodeParent = minNode;
minNode = minNode.leftNode;
}
// 把刚才查找到的值赋值到要删除的节点上
node.value = minNode.value;
// 删除刚才查找的节点
minNode = null;
minNodeParent.leftNode = null;
}
} else {
boolean isParentLeftNode = parent.value > node.value;
// 有左右节点中的其中一个
if (node.leftNode != null) {
// 存在左节点
if (isParentLeftNode) {
parent.leftNode = node.leftNode;
} else {
parent.rightNode = node.leftNode;
}
} else {
// 存在右节点
if (isParentLeftNode) {
parent.leftNode = node.rightNode;
} else {
parent.rightNode = node.rightNode;
}
}
node = null;
}
}
return true;
}
github: github.com/HyejeanMOON…