【Data structure&Algorithm】做题笔记-二叉搜索树

知识储备

二叉搜索树(Binary Search Tree-BST)的定义:
1、对于 BST 的每一个节点 node,左子树节点的值都比 node 的值要小,右子树节点的值都比 node 的值大。
2、对于 BST 的每一个节点 node,它的左侧子树和右侧子树都是 BST。
(这意味着BTS中没有重复值)

重要性质:
BST 的中序遍历结果是有序的(升序)。
如果输入一棵 BST,以下代码可以将 BST 中每个节点的值升序打印出来:

void traverse(TreeNode root) {
    if (root == null) return;
    traverse(root.left);
    // 中序遍历代码位置
    print(root.val);
    traverse(root.right);
}

230-二叉搜索树中第K小的元素

题目
利用“BST 的中序遍历结果是有序的(升序)”的特性

    public int kthSmallest(TreeNode root, int k) {
        traverse(root,k);
        return result;
    }
    int result;
    int rank=0;//当前节点的排名
    //利用二叉搜索树中序遍历的特性:中序遍历的节点值为升序
    public void traverse(TreeNode root,int k){
        if(root == null){
            return;
        }

        traverse(root.left,k);
        /**中序遍历的位置**/
        rank++;
        if(rank == k){
            result = root.val;
            return;
        }
        traverse(root.right,k);
    }

538-BST 转化累加树

题目
还是利用“BST 的中序遍历结果是有序的(升序)”的特性,但是要把这个特性转换一下,中序遍历是先遍历左子树、再遍历右子树,在两次遍历中间访问节点值,如果先遍历右子树、再遍历左子树的话,遍历结果就应该是降序了,用额外的数据结构做下累加就可以了。

    public TreeNode convertBST(TreeNode root) {
        traverse(root);
        return root;
    }

    int sum = 0;
    void traverse(TreeNode root){
        if(root == null){
            return;
        }
        traverse(root.right);
        sum += root.val;
        root.val = sum;
        traverse(root.left);
    }

98-验证二叉搜索树

题目
BTS的定义是“root的整个左子树都要小于root.val,整个右子树都要大于root.val”,而不是“root的左节点小于root.val,右节点大于root.val”,所以每次递归只比较root和左右节点是不充分的,这一点要注意。

当前节点root,所能触及到的其他节点只有root.left和root.right两个,怎么能触及到其他节点呢(整个左子树、整个右子树)?

“整个左子树的值都<root.val”,也就是“左子树的最大值<root.val”;
“整个右子树都要大于root.val”,也就是“右子树的最小值>root.val”。
所以当前节点不需要触及到整个左子树、整个右子树,而是只要知道max(左子树),max(右子树)就行了,那么我们在递归函数里增加这两个参数max、min,让这两个参数不断传递下去,max、min记录了目前所能了解的节点的取值范围。

   public boolean isValidBST(TreeNode root) {
        return isValidBST(root,null,null);
    }

    boolean isValidBST(TreeNode root,TreeNode min,TreeNode max){
        if(root == null){
            return true;
        }

        if(min != null && root.val <= min.val){
            return false;
        }

        if(max != null && root.val >= max.val){
            return false;
        }

        return isValidBST(root.left,min,root)&&isValidBST(root.right,root,max);
    }

700-二叉搜索树中的搜索

题目
在普通二叉树中寻找元素:

    public TreeNode searchBST(TreeNode root, int val) {
        if(root == null){
            return null;
        }
        if(root.val == val){
            return root;
        }
        //左子树里找该值
        TreeNode leftSearchResult = searchBST(root.left,val);
        //右子树里找该值
        TreeNode rightSearchResult = searchBST(root.right,val);
        return leftSearchResult != null?leftSearchResult:rightSearchResult;
    }

然而这样完全没有用到二叉搜索树的特点,肯定不是最优解。如果我们利用上其特点,如果当前节点的值>target值,则target节点肯定位于当前节点的左子树上,只搜索左子树就行;果当前节点的值<target值,则target节点肯定位于当前节点的右子树上,只搜索右子树就行。

    public TreeNode searchBST(TreeNode root, int val) {
        if(root == null){
            return null;
        }
        if(root.val == val){
            return root;
        }
        TreeNode leftSearchResult = null;
        TreeNode rightSearchResult = null;
        if(root.val > val){
            //左子树里找该值
            leftSearchResult = searchBST(root.left,val);
        }
        if(root.val < val){
            //右子树里找该值
            rightSearchResult = searchBST(root.right,val);
        }

        return leftSearchResult != null?leftSearchResult:rightSearchResult;
    }

可以提炼出针对BST的遍历框架,所谓遍历,就是一个“寻找特定节点”的过程:

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);
}

701-二叉搜索树中的插入操作

题目
将新节点插入到叶子节点后。

    public TreeNode insertIntoBST(TreeNode root, int val) {
        //返回新节点,以连接到叶子节点
        if(root == null){
            return new TreeNode(val);
        }

        //新节点应在root的左子树
        if(root.val > val){
            root.left = insertIntoBST(root.left,val);
        }
        //新节点应在root的右子树
        if(root.val < val){
            root.right = insertIntoBST(root.right,val);
        }
        return root;//返回当前根节点
    }

450-删除二叉搜索树中的节点

删除某个节点,要先“找”再“删”,这个“找”的过程直接套用之前总结出来的BST遍历框架,所以代码的结构就是:

TreeNode deleteNode(TreeNode root, int key) {
    if (root.val == key) {
        // 找到啦,进行删除
    } else if (root.val > key) {
        // 去左子树找
        root.left = deleteNode(root.left, key);
    } else if (root.val < key) {
        // 去右子树找
        root.right = deleteNode(root.right, key);
    }
    return root;
}

剩下的事情就是“删”,节点位置的不同会导致不同的“删”法,我们来逐个分析每种情况。

情况1:要删除的节点是叶子节点
如图所示,蓝色箭头的方向代表节点值从小到大,删除叶子节点1、3、7都不会导致该BST失去BST特性,所以直接删除就行。
在这里插入图片描述

情况2:要删除的节点只有1个子节点
如图所示,当节点6只有右节点7时,根据BST特性,7比6左边的节点们都大,所以可以将7替换掉6;当节点7只有左节点6时,根据BST特性,6比7左边的节点们都大,所以可以直接将6替换掉7。
牢记BST特性:右子树的节点们>根节点>左子树的节点们
所以对于这种情况,就是用待删节点的子节点替换掉待删节点。
在这里插入图片描述

情况3:要删除的节点有2个子节点
如图所示,要删除的节点2,根据BST特性:节点A的左子树的节点们<节点A<节点A的右子树的节点们,可以推理出:由于节点2的右节点的左节点(节点3)由于位于节点2的右子树上,所以节点3>节点2的左子树的节点们;由于节点3位于节点4的左子树上,所以节点3<节点4。

也就是说,节点1<节点3<节点4,推而广之,如果待删节点为节点A,A的左节点<A的右节点的左节点<A的右节点,所以可以用“A的右节点的左节点”替换掉节点A。

如果A的右节点的左节点不是叶子节点呢?即节点3还有子节点怎么办?我们可以选择删除A的右子树的最靠左的那个节点,也满足:A的左节点<A的右子树的最靠左的那个节点<A的右节点。删除叶子节点比删除有两个子节点的节点更简单。
在这里插入图片描述

    public TreeNode deleteNode(TreeNode root, int key) {
        if(root == null){
            return null;
        }

        if(root.val == key){
            //找到了,进行删除
            //情况1:要删除的节点是叶子节点,直接删除
            if(root.left == null && root.right == null){
                return null;
            }
            //情况2:要删除的节点只有1个子节点,用子节点替代
            if(root.right == null && root.left != null){
                return root.left;
            }
            if(root.left == null && root.right != null){
                return root.right;
            }
            //情况3:要删除的节点有2个子节点,用待删除节点的右子树的最左边的节点替代
            TreeNode mostLeftNode = findMostLeftNode(root.right);
            root.val = mostLeftNode.val;
            //删除掉mostLeftNode
            root.right = deleteNode(root.right,mostLeftNode.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 findMostLeftNode(TreeNode node){
        while(node.left != null){
            node = node.left;
        }
        return node;
    }

返回值可能有点难理解,可以这么理解:递归方法传入root,返回的还是root本身,当root是待删除的值时,返回的是其他节点进行替代。

总结

230-二叉搜索树中第K小的元素 和 538-BST 转化累加树 这两个题目都跟节点值的大小有关系,所以解题思路都用了二叉搜索树的重要特性:BST 的中序遍历结果是有序的(升序)。

针对BST的遍历框架,所谓遍历,就是一个“寻找特定节点”的过程:

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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值