🏀🏀🏀来都来了,不妨点个关注!
🎧🎧🎧博客主页:欢迎各位大佬!
写在之前:
介绍二叉搜索树的概念是为了我们之后Map和Set的学习做铺垫。
文章目录
1 . 二叉搜索树的概念
二叉搜索树可能是一颗空树,或者具有以下性质:
若它的左子树不为空,那么它的左子树的所有节点的值都小于根节点的值
若它的右子树不为空,那么它的右子树的所有节点的值都大于根节点的值
它的左右子树也分别为一颗二叉搜索树
下面,我们上图理解:
这里不难发现,我们对它进行中序遍历就可以得到一个有序的序列。
2.实现一棵二叉搜索树
2.1 二叉搜索树的主体框架:
class BinarySearchTree {
static class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
TreeNode root = null;
}
不难看出,其实二叉搜索树的结构和二叉树的结构是一样的。
2.2 查找操作
基于二叉搜索树的性质,我们的查找操作就变得简单了很多,遍历二叉搜索树的时候,当val比根节点小就只需遍历左子树即可,当val比根节点的值大的时候就只需遍历右子树即可。
public TreeNode find(int val) {
TreeNode tmp = root;
while (tmp != null) {
if (tmp.val < val) {
//遍历右子树
tmp = tmp.right;
} else if (tmp.val > val) {
//遍历左子树
tmp = tmp.left;
} else {
return tmp;
}
}
return null;
}
2.3插入操作
由于二叉搜索树的性质,左子树的值均小于根节点,右子树的值均大于根节点,进行插入操作就只需比较插入的val和root.val的大小,小于则在其左子树遍历,大于则在其右子树遍历。需要注意的是,我们插入的值一定是作为树的叶子节点。
代码实现:
public void insert(int val) {
if (root == null) {
root = new TreeNode(val);
return ;
}
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
parent = cur;
if (cur.val < val) {
cur = cur.right;
} else if (cur.val > val) {
cur = cur.left;
} else {
return ;
}
}
TreeNode node = new TreeNode(val);
if (parent.val > val) {
parent.left = node;
} else {
parent.right = node;
}
}
2.4 删除操作(重难点)
为什么说删除操作是个难点呢,下面我们来举个例子来说明:
当我们要删除val=10的这个节点的时候,那谁来替代它的位置呢?8?11?12?是不是很迷惑不知道应该插入哪个值来替代?假设插入了12,12的左子树是8还是11呢?是不是有着一系列的问题,那么,遇事不决,分类讨论。这里我们假设要删除的节点是cur
首先我们先写出删除的主体结构:
public void remove(int val) {
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
if (cur.val == val) {
removeNode(parent,cur);
return ;
} else if (cur.val < val) {
parent = cur;
cur = cur.right;
} else {
parent = cur;
cur = cur.left;
}
}
}
这里我们的主要问题就变成了如何实现removeNode(TreeNode parent,TreeNode cur);这个方法.通过上述的代码我们就可以找到我们要删除的节点的位置以及它的父亲节点parent。分类讨论:
① cur的左子树为空
- 当cur == root时,root = root.right。
- 当cur == parent.left 时,parent.left = cur.right。
- 当cur == parent.right 时,parent.right = cur.right。
这样看可能比较难以理解,下面我们用图来理解:
代码实现:
//cur的左子树为空
if (cur.left == null) {
if (cur == root) {
root = cur.right;
} else if (parent.left == cur){
parent.left = cur.right;
} else {
parent.right = cur.right;
}
}
② cur的右子树为空
- 当cur==root时,root = root.left。
- 当cur==parent.left时,parent.left = cur.left。
- 当cur==parent.right时,parent.right = cur.left。
这里就和上面的左子树为空类似了,我们上图理解:
代码实现:
//cur的右子树为空
else if (cur.right == null) {
if (cur == root) {
root = cur.left;
} else if (parent.left == cur) {
parent.left = cur.left;
} else {
parent.right = cur.left;
}
}
③ cur的左子树和右子树都不为空
这也是最难的情况了,这里需要使用替换法进行删除,即在它的右子树中一直向左找左孩子,直到找到右子树中最小值。将它替换到要删除的地方,再将该节点删除即可。
通过上图我们可以知道,当cur的左右子树都不为空的时候,我们需要在cur的右子树中找到最小值,即一直找左子树,直到target的左孩子为空,即找到了最小值,再进行替换。这也是上图中的一般情况,但有一种特殊情况,就是cur的右子树中只有右子树,没有左子树的情况,此时要替换的最小值即cur的右孩子,此处我们在代码中加个判断即可。
代码实现:
else {
TreeNode target = cur.right;
TreeNode targetParent = cur;
while (target.left != null) {
targetParent = target;
target = target.left;
}
cur.val = target.val;
if (target == targetParent.left) {
targetParent.left = target.right;
} else {
targetParent.right = target.right;
}
}
整体代码实现:
private void removeNode(TreeNode parent, TreeNode cur) {
if (cur.left == null) {
if (cur == root) {
root = cur.right;
} else if (parent.left == cur){
parent.left = cur.right;
} else {
parent.right = cur.right;
}
} else if (cur.right == null) {
if (cur == root) {
root = cur.left;
} else if (parent.left == cur) {
parent.left = cur.left;
} else {
parent.right = cur.left;
}
} else {
TreeNode target = cur.right;
TreeNode targetParent = cur;
while (target.left != null) {
targetParent = target;
target = target.left;
}
cur.val = target.val;
if (target == targetParent.left) {
targetParent.left = target.right;
} else {
targetParent.right = target.right;
}
}
}
3. 二叉搜索树小结
3.1 性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:logN
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2
3.2 和Java类集的关系
TreeMap 和 TreeSet 即 java 中利用搜索树实现的 Map 和 Set;实际上用的是红黑树,而红黑树是一棵近似平衡的
二叉搜索树,即在二叉搜索树的基础之上 + 颜色以及红黑树性质验证,关于红黑树的内容后序再进行讲解。