1.二叉搜索树
1.1 什么是二叉搜索树?
二叉搜索树又叫二叉排序树,二叉搜索树是一种二叉树的数据结构,其中每个节点的左子节点的值都小于该节点的值,右子节点的值都大于该节点的值,它的左右子树也分别为二叉搜索树,同时节点之间不能重复。
二叉搜索树是用来干嘛的呢?
- 快速查找:由于二叉搜索树的特性,可以通过比较查找值与当前节点的大小关系,快速地定位到目标节点,从而实现快速查找。
- 排序:二叉搜索树的中序遍历结果是有序的,可以通过中序遍历的方式对二叉搜索树进行排序操作。
- 快速插入和删除:在二叉搜索树中插入或删除一个节点时,只需要沿着树的路径找到对应的位置即可,操作时间复杂度为 。
- 作为其他数据结构的基础:许多高级的数据结构,如平衡二叉搜索树、B树等都是基于二叉搜索树的思想设计的,因此二叉搜索树是许多数据结构的基础。
1.2 二叉搜索树的查找操作
二叉搜索树的查找操作通常包含以下步骤:
- 从根节点开始查找。
- 如果当前节点为空,则目标值不存在于树中,返回 null 或者抛出异常。
- 如果当前节点的值等于目标值,则返回该节点。
- 如果目标值小于当前节点的值,则在左子树中继续查找,否则在右子树中查找。
- 重复步骤 2-4,直到找到目标节点或者当前节点为空。
public class MySearchTree {
//节点
private static class Node{
int val;
Node left;
Node right;
public Node(int val){
this.val = val;
}
}
//根节点
private Node root = null;
//查找指定的元素的节点
public Node search(int val){
Node cur = root;
while(cur != null){
if(cur.val < val){
cur = cur.right;
}else if(cur.val > val){
cur = cur.left;
}else{
return cur;
}
}
//没有该节点
return null;
}
}
它的时间复杂度为,如果是如下形式的搜索树,那么时间复杂度就是。
1.3 二叉搜索树的插入操作
二叉搜索树的插入步骤如下:
- 从根节点开始,比较目标值和当前节点的值大小关系,如果目标值小于当前节点的值,则向左子树查找;如果目标值大于当前节点的值,则向右子树查找;如果目标值等于当前节点的值,则说明目标节点已经存在于树中,无需再次插入。
- 重复步骤 1 直到找到最终的空节点。
- 在空节点处创建一个新节点,将目标值存储在该节点中。
- 将新节点插入到二叉搜索树中。
public boolean insert(int val){
//特判,根节点为空直接插入
if(root == null){
root = new Node(val);
return true;
}
Node cur = root;
Node parent = null; // 记录cur的父节点
while(cur != null){
parent = cur;
if(cur.val < val){
cur = cur.right;
} else if(cur.val > val){
cur = cur.left;
} else {
//当树中有 val 的时候
return false;
}
}
//插入
Node newNode = new Node(val);
//判断插入到左子树还是右子树
if(val < parent.val){
parent.left = newNode;
}else {
parent.right = newNode;
}
return true;
}
1.4 二叉搜索树的删除操作
二叉搜索树的删除操作的步骤可以分为以下几步:
-
查找目标节点:从根节点开始,比较目标值和当前节点的值大小关系,如果目标值小于当前节点的值,则向左子树查找;如果目标值大于当前节点的值,则向右子树查找;如果目标值等于当前节点的值,则说明找到了目标节点。如果目标节点不存在于树中,则删除操作结束,不需要进行任何操作。
-
分情况讨论:设待删除结点为
cur,待删除结点的双亲结点为parent。-
cur.left = null 的时候
- cur 是 root,则 root = cur.right
- cur 不是 root,cur 是 parent.left,则 parent.left = cur.right
- cur 不是 root,cur 是 parent.right,则 parent.right = cur.right
-
cur.right = null 的时候
- cur 是 root,则 root = cur.left
- cur 不是 root,cur 是 parent.left,则 parent.left = cur.left
- cur 不是 root,cur 是 parent.right,则 parent.right = cur.left
-
cur.left != null && cur.right != null 的时候
- 需要使用替换法进行删除,即在它的右子树中寻找关键码最小的节点(最左边的节点),用它的值填补到被删除节点中,再来处理该节点的删除问题
-
- 返回删除后的根节点。
//删除操作
public Node remove(int val){
//先找到待删除的节点
Node parent = null; //记录 cur 的父节点
Node cur = root;
while (cur != null){
if(cur.val == val) {
//进行删除操作
removeNode(parent,cur);
return root;
} else if(val < cur.val){
parent = cur;
cur = cur.left;
} else {
parent = cur;
cur = cur.right;
}
}
return null;
}
private void removeNode(Node parent,Node cur){
//三种情况
if(cur.left == null){ //cur 没有左树
if(cur == root){
root = cur.right;
}else if(cur == parent.left){
parent.left = cur.right;
}else if(cur == parent.right){
parent.right = cur.right;
}
} else if(cur.right == null){ //cur 没有右树
if(cur == root){
root = cur.left;
}else if(cur == parent.left){
parent.left = cur.left;
}else if(cur == parent.right){
parent.right = cur.left;
}
} else { //cur 有左右子树的情况下删除【重要】
Node target = cur.right; //代表右子树最小的节点
Node targetParent = cur; //代表最小的节点的父节点
while (target.left != null){
//根据搜索树的性质,最小的节点在最左边
targetParent = target;
target = target.left;
}
cur.val = target.val;//复制
if(target == targetParent.left){
targetParent.left = target.right;
}else{ //当 cur 的右树只有一个节点的时候,也就是 target == cur.right
targetParent.right = target.right;
}
}
}