二叉搜索树(关于BST新思维)

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有一个非空子节点

扫描二维码关注公众号,回复: 12684235 查看本文章

在这里插入图片描述
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

猜你喜欢

转载自blog.csdn.net/qq_44861675/article/details/114263904
今日推荐