[数据结构复习]关于我对二叉搜索树的理解

100 阅读4分钟

前言

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

参考链接:东哥带你刷二叉搜索树(基操篇) :: labuladong的算法小抄 (gitee.io)

题目

题目一

链接:98. 验证二叉搜索树 - 力扣(LeetCode)

题解:

题目要让我们判断,这个树是不是一个搜索树

那么我们可以回忆下,二叉搜索树的性质

  • 二叉搜索树,左小右大,中序遍历,应该是单调增
  • 当前节点,一定小于右子树的所有节点

但是我们在二叉树遍历的时候,我们只能检查他的左右两个孩子,是不是满足,左小右大原则,但是无法对所有的右边孩子都进行判断

这时候我们就需要引入两个变量maxmin

如果他是一个合格的搜索树

那么对于当前节点而言

  • 左子树的所有节点值应该都是小于当前节点值
  • 右子树的所有节点值应该都是大于当前节点值

所以我们需要重新写一个函数,然后传入三个参数

代码:

/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     val: number
 *     left: TreeNode | null
 *     right: TreeNode | null
 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.left = (left===undefined ? null : left)
 *         this.right = (right===undefined ? null : right)
 *     }
 * }
 */

function isValidBST(root: TreeNode | null): boolean {
    // 首先必须明确的是,搜索树保证,左小右大
    // 但是,搜索树其实保证,左边树的所有节点都要比右边树的所有节点小
    // if(root === null){
    //     return true
    // }
    // if(root.left && root.left.val > root.val){
    //     return false
    // }
    // if(root.right && root.right.val < root.val){
    //     return false
    // }
    // return isValidBST(root.left) && isValidBST(root.right)
    return IsValidBST(root,null,null)
};

// 因为我们没法将当前节点和后面的右边子树连接起来
// max.val > root.val > min.val

const IsValidBST = (root:TreeNode,min:TreeNode,max:TreeNode)=>{
    // 
    if(root === null){
        return true
    }
    // 当min不为空,说明,右子树
    if(min && min.val >= root.val){
        return false
    }
    // 当max不为空,说明,有最大值,左子树
    if(max && max.val <= root.val){
        return false
    }
    // 对于当前节点来说
    // 左子树的所有节点,应该是要小于这个当前节点的,所以当前节点就是MAX
    // 对于右子树来说,我当前的节点,应该是比所有的右边的节点都要小的,所以,我们将他设置为最小值
    return IsValidBST(root.left,min,root)&& IsValidBST(root.right,root,max)
}

题目二(查)

链接:700. 二叉搜索树中的搜索 - 力扣(LeetCode)

题解:

  1. 正如上面介绍的,我们知道,BST的特性是左小右大
  2. 那么我们可以利用这一特性,进行类似二分搜索的方式
  3. 假如root.val < val,那么就说明,这个值太小了,就去右子树上面寻找
  4. 假如root.val > val,那么就说明,这个值偏大,那么就去左子树上寻找

代码:

/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     val: number
 *     left: TreeNode | null
 *     right: TreeNode | null
 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.left = (left===undefined ? null : left)
 *         this.right = (right===undefined ? null : right)
 *     }
 * }
 */

function searchBST(root: TreeNode | null, val: number): TreeNode | null {
// 找到根节点等于val的值,然后返回
// 首先,必须知道,对于二叉搜索树,他的中序遍历一定是单调增的
// 所以我们只要在中序遍历的时候加上二分搜索就可以得到答案
    if(root === null){
        return null;
    }
    if(root.val <val){
        // 这个值比较大,那么就要去他的右子树上寻找  
        return searchBST(root.right,val)
    }
    if(root.val> val){
        return searchBST(root.left,val)
    }
    if(root.val === val){
        return root
    }
};

但是在第一次操作的时候,我进行了一次判断

if(root.val <val){
        // 这个值比较大,那么就要去他的右子树上寻找 
    if(root.right)
        return searchBST(root.right,val)
}

但是这样的结果,就是假如没有root.val === val的值的时候,他就会出现undefined

后来发现,其实这样做,是完全没有必要的

因为我有一个兜底的if(root){return null}

所以,其实我们没有必要去多判断他是不是满足不为null这么回事

题目三(增)

链接:701. 二叉搜索树中的插入操作 - 力扣(LeetCode)

题解:

  1. 使用二分法,去往里面插入元素
  2. 但是这次不是简单的return就结束了
  3. 而是需要将树的结构进行变化

代码:

/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     val: number
 *     left: TreeNode | null
 *     right: TreeNode | null
 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.left = (left===undefined ? null : left)
 *         this.right = (right===undefined ? null : right)
 *     }
 * }
 */

function insertIntoBST(root: TreeNode | null, val: number): TreeNode | null {
    // 现在需要往二叉搜索树里面插入一个数
    // 依然保证,树的结构成立
    if(root === null){
        return new TreeNode(val)
    }
    // 因为题目说了,不存在,值相同的情况
    // 因为需要将树进行改变,所以,不能简单的return就结束了
    if(root.val < val){
        // 去右子树
        root.right = insertIntoBST(root.right,val)
    }
    if(root.val > val){
        root.left =  insertIntoBST(root.left,val)
    }
    // 最后返回这个新的树
    return root
};

第三题(删)

链接:450. 删除二叉搜索树中的节点 - 力扣(LeetCode)

题解:

  1. 要想删除,第一步肯定是,先找到这个元素

  2. 然后对这个元素进行一些修改

    • 假如他是一个叶子节点,直接断掉就行

    • 假如他没有左或右节点,我们只需将,现有的节点,放到需要删除的地方就行了

      • 也就是直接返回,有的那个子节点就行
    • 但是假如,他的左右子节点都齐全,那么就会复杂一点

    • 因为需要继续满足BST的条件,那么也就意味着,我们删除的那个节点

      • 要么是左子树的最大节点
      • 要么是右子树的最小节点
    • 这里,我们找的是,右子树的最小节点

    • 其实还是比较好理解的,BST而言,最小节点一定在:左子树的叶子节点,那么只需要找到他,然后将他交给根节点,然后删除这个最小节点就可以了

代码:

/**
 * Definition for a binary tree node.
 * class TreeNode {
 *     val: number
 *     left: TreeNode | null
 *     right: TreeNode | null
 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.left = (left===undefined ? null : left)
 *         this.right = (right===undefined ? null : right)
 *     }
 * }
 */

function deleteNode(root:TreeNode|null,key:number):TreeNode | null {
    if(root === null){
        return null
    }
    if(root.val < key){
        root.right =  deleteNode(root.right,key)
    }
    if(root.val > key){
        root.left =  deleteNode(root.left,key)
    }
    if(root.val === key){
        // return root
        // 那么进行删除
        // 假如左或右子树缺少,那么只要将有的那个子树的值放上去就好了
        // 同时也排除了,叶子节点情况
        if(root.left === null){
            return root.right
        }
        if(root.right === null){
            return root.left
        }
        // 假如左右子树都很齐全的情况,那么我就要去找右子树里面最小的那个值放进来,或者左子树里面最大的那个放进来
        if(root.left !== null && root.right !== null){
            // 那么我们就去寻找,右子树里面的最小值
            let minNode = GetMin(root.right);
            // 让当前最小节点作为根节点,并且删除那个小的节点
            root.val = minNode.val
            // 然后删除MinNode
            root.right = deleteNode(root.right,minNode.val)
        }
    }
    return root
}
const GetMin = (root:TreeNode)=>{
    // 对于BST而言,最小的节点,一定是,左子树的那个叶子节点
    while(root.left !== null){
        root = root.left
    }
    return root
}

总结

从这几道基本题看出来,其实在做搜索树的题目前,你需要知道

  • BST的左小右大,这个可以使用二分查找的方法
  • BST的中序遍历是递增的

对树的操作无非遍历 + 访问,遍历就是「找」,访问就是「改」。

对于改而言,其实就要函数返回TreeNode类型,并要对递归调用的返回值进行接受