引
本文主要介绍二叉搜索树的插入、查找、删除操作是如何实现的~
什么是二叉搜索树?
二叉搜索树又称二叉排序树, 具有以下的性质:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也都要满足二叉搜书树的性质
- 二叉搜索树的中序遍历结果是有序的
如下图就是一颗二叉搜索树:
插入
所谓的 增 其实就是往二叉树中插入一个节点, 问题就在于如何插入, 插入哪里?
基本思路:
首先, 要插入的节点一定是一个叶子节点, 所以先找到叶子节点
找到了叶子节点之后, 要判断是往该节点的左边插入还是右边插入
这里还有一个情况我们需要考虑, 就是如果你要插入的值在当前的二叉搜索树中存在, 那么就判定为插入失败. 因为二叉搜索树是不能存在节点值相同的两个节点的~
代码
public boolean insert(int val) {
TreeNode node = new TreeNode(val);
if (this.root == null) {
this.root = node;
return true;
}
TreeNode cur = root;
// 定义一个 父节点, 记录 cur 的前一个结点
TreeNode parent = null;
while (cur != null) {
if (val < cur.val) {
parent = cur;
cur = cur.left;
} else if (val > cur.val) {
parent = cur;
cur = cur.right;
} else {
return false;
}
}
// 此时的 cur 为空, 说明此时的 parent 是一个叶子节点
if(parent.val < val) {
parent.right = node;
}else {
parent.left = node;
}
return true;
}
查
思路:
- 遍历二叉树, 找到 val 值相同的返回即可
- 要查找的 val 值比当前的节点的 val 值小就往左走, 比当前的节点的 val 值大就往右走
- 遍历完还没有找到则返回 null
代码
public TreeNode search(int val) {
TreeNode cur = root;
while (cur != null) {
if (val < cur.val) {
cur = cur.left;
} else if (val > cur.val) {
cur = cur.right;
} else {
return cur;
}
}
return null;
}
删
删除操作是插入、查找、删除中最繁琐的一个操作, 因为你要保证删除目标节点后也要保证整棵树依然是一颗二叉搜索树. 因此这里我们总体上来看, 可以分为三种情况来考虑~ 假设待删除的结点为 cur, 待删除结点的父结点为 parent
分析:
cur.left == null
这里又可以分为三种情况:
- cur 是 根节点, 则 root == cur.right
- cur 不是 根节点, cur == parent.left, 此时 parent.left = cur.right;
- cur 不是 根节点, cur == parent.right, 此时 parent.right = cur.right;
cur.right == null
此时, 这里也可以分为三种情况:
- cur 是 根节点, 则 root == cur.left
- cur 不是 根节点, cur == parent.left, 此时 parent.left = cur.right;
- cur 不是 根节点, cur == parent.right, 此时 parent.right = cur.right;
cur.left != null && cur.right != null
这种情况就需要使用替换法来进行删除,其实质上,就是将左右都为空的这种情况转换为上面这两种情况
- 在带删除节点的右子树中寻找中序遍历下的节点的 val 值最小的那个节点,把它的值填补到被删除节点
- 或者在它的左子树中寻找中序遍历下的节点的 val 值最大的那个节点,把它的值填补到被删除节点
- 最后将 val 值最大(或者最小)的那个节点删除掉, 及转换称为了 1, 2 情况来处理
代码
public boolean delete(int val) {
TreeNode cur = root;
TreeNode parent = null;
while (cur != null) {
if (val < cur.val) {
parent = cur;
cur = cur.left;
} else if (val > cur.val) {
parent = cur;
cur = cur.right;
} else {
// 找到了要删除的节点, 进行删除操作
deleteNode(cur, parent);
return true;
}
}
return false;
}
private void deleteNode(TreeNode cur, TreeNode parent) {
// 分为三种情况:
// 1.cur.left == null
// 2.cur.right == null
// 3.cur.left != null && cur.right != null
if (cur.left == null) {
// 1.cur.left == null
if (cur == root) {
root = cur.right;
} else if (cur == parent.left) {
parent.left = cur.right;
} else {
parent.right = cur.right;
}
} else if (cur.right == null) {
// 2.cur.right == null
if (cur == root) {
root = cur.left;
} else if (cur == parent.left) {
parent.left = cur.left;
} else {
parent.right = cur.left;
}
} else {
// 3.cur.left != null && cur.right != null
TreeNode targetParent = cur;
// 这里的做法是, 去待删除结点的右树上找最小值, 然后替换
TreeNode target = cur.right;
while (target.left != null) {
// 记录 target 的父节点
targetParent = target;
// 一直往左走就能找到最小值~
target = target.left;
}
// 覆盖 待删除结点 的 val
cur.val = target.val;
// 删除最小值节点
if (target == targetParent.left) {
targetParent.left = target.right;
} else {
targetParent.right = target.right;
}
}
}
时间复杂度分析
最好情况: 此时的二叉搜索树是一个完全二叉树, 其时间复杂度为 O(logN)
最坏情况: 此时的二叉搜索树就退化成为了一颗但分支的树, 其时间复杂度为 O(N)