1、BST的定义回顾
二叉查找树(英语:Binary Search Tree),也称为二叉查找树、有序二叉树(ordered binary tree)或排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 任意节点的左、右子树也分别为二叉查找树;
二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低。为O(log n)。二叉查找树是基础性数据结构,用于构建更为抽象的数据结构,如集合、多重集、关联数组等。
二叉查找树的查找过程和次优二叉树类似,通常采取二叉链表作为二叉查找树的存储结构。中序遍历二叉查找树可得到一个关键字的有序序列,一个无序序列可以透过建构一棵二叉查找树变成一个有序序列,建构树的过程即为对无序序列进行查找的过程。每次插入的新的结点都是二叉查找树上新的叶子结点,在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。搜索、插入、删除的复杂度等于树高,期望O(log n),最坏O(n)(数列有序,树退化成线性表)。
虽然二叉查找树的最坏效率是O(n),但它支持动态查询,且有很多改进版的二叉查找树可以使树高为O(\log n),从而将最坏效率降至O(log n),如AVL树、红黑树等。
关于平衡二叉树,参考我之前的博客:平衡二叉树及其算法实现
2、BST的增删改查
查——要查就是需要遍历
步骤:
在二叉查找树b中查找x的过程为:
- 若b是空树,则搜索失败,否则:
- 若x等于b的根节点的数据域之值,则查找成功;否则:
- 若x小于b的根节点的数据域之值,则搜索左子树;否则:
- 查找右子树。
boolean isInBST(TreeNode root, int target) {
if (root == null) return false;
if (root.val == target)
return true;
if (root.val < target)
return isInBST(root.right, target);
if (root.val > target)
return isInBST(root.left, target);
// root 该做的事做完了
}
插入节点——如同查的操作,只不过具体的动作改为了插入一个节点
向一个二叉查找树b中插入一个节点s的算法,过程为:
- 若b是空树,则将s所指节点作为根节点插入,否则:
- 若s->data等于b的根节点的数据域之值,则返回,否则:
- 若s->data小于b的根节点的数据域之值,则把s所指节点插入到左子树中,否则:
- 把s所指节点插入到右子树中。(新插入节点总是叶子节点)
TreeNode insertIntoBST(TreeNode root, int val) {
// 找到空位置插入新节点
if (root == null) return new TreeNode(val);
// if (root.val == val)
// BST 中一般不会插入已存在元素
if (root.val < val)
root.right = insertIntoBST(root.right, val);
if (root.val > val)
root.left = insertIntoBST(root.left, val);
return root;
}
删除——需要先找到该节点然后才能删除
分三种情况:
1、A是叶子节点且没有孩子
2、A有一个非空子节点
3、A有两个非空子节点
从上面三种情况,我们可以得到如下代码:
TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
if (root.val == key) {
// 这两个 if 把情况 1 和 2 都正确处理了
if (root.left == null) return root.right;
if (root.right == null) return root.left;
// 处理情况 3,这里传入它的右子树,为了找到右子树中的最小数
TreeNode minNode = getMin(root.right);
root.val = minNode.val;
root.right = deleteNode(root.right, minNode.val);
} else if (root.val > key) {
root.left = deleteNode(root.left, key);
} else if (root.val < key) {
root.right = deleteNode(root.right, key);
}
return root;
}
TreeNode getMin(TreeNode node) {
// BST 最左边的就是最小的
while (node.left != null) node = node.left;
return node;
}
删除操作就完成了。注意一下,这个删除操作并不完美,因为我们一般不会通过root.val = minNode.val修改节点内部的值来交换节点,而是通过一系列略微复杂的链表操作交换root和minNode两个节点。
因为具体应用中,val域可能会是一个复杂的数据结构,修改起来非常麻烦;而链表操作无非改一改指针,而不会去碰内部数据。
从上面,我们就得到了一个实现的框架:
void BST(TreeNode root, int target) {
if (root.val == target)
// 找到目标,做点什么
if (root.val < target)
BST(root.right, target);
if (root.val > target)
BST(root.left, target);
}
3、BST的合法约束判断
接下来,就是我们如何来判断一颗给定的二叉树,它是一棵二叉搜索树呢?我们知道它的定义是根节点大于它左子树上所有的节点,小于它右子树上所有的节点。那么,我们就可以制定了当前节点它必须大于左子树上的最大值,小于右子树上的最小值。然后递归调用。
boolean isBST(TreeNode root){
if(root==null) return true;
return isBSTByMax_Min(root,null,null);
}
//正确的规则应该是min<root.val<max
boolean isBSTByMax_Min(TreeNode root,TreeNode min,TreeNode max){
if(root==null) return true;
//没有大于左子树的最大值
if(max!=null && root.val>=max.val) return false;
//没有小于右子树的最小值
if(min!=null && root.val<=min.val) return false;
//当前节点满足了
//进而判断其左子树和右子树是否满足,左子树根节点的左子树最大值应该为
return isBSTByMax_Min(root.left,min,root) &&
isBSTByMax_Min(root.right,root,max);
}
这样,我们通过递归函数(min<root.val<max这样的节点就是正确的BST,子问题),将这种约束传给子树的所有结点,从而可以判断是否为BST