判断一个树是否为二叉查找树

一开始还以为这个问题很简单,平时练习的时候也没有多在意,直到。。。百度二面后一个算法加面以及字节跳动视频面都出现了这个问题,才发现并不是想象中的那么简单。
找了别人的几篇博客看了看,发现别人好像都有过总结。。就有点尴尬,想来想去还是记录下来吧,敲一遍印象更深。。。【我只是代码的搬运工】

———————————————– 我是正文分割线————————————————–

1. 二叉查找树定义?性质?

任意一个节点的值一定大于该节点左子树中的任意一个节点的值,同时满足该节点的值小于其右子树中的任意一个节点的值。

2. 二叉查找树数据结构定义(个人版本)

class TreeNode{
    int data;
    TreeNode left;
    TreeNode right;
    //构造函数 
    ....
}

3. 错误方法

很不好意思的是,这两次面试每次遇到这个问题总是脱口而出:
“递归判断每个节点和其左右节点值的大小关系”
非常遗憾,这种方法是错误的 是错误的 是错误的 是错误的 是错误的,只能判断是否为局部二叉查找树,反例就是这个:
这里写图片描述

先不管是否正确吧,这个方法的思路如下:

public boolean isTree(TreeNode root){
    if(root == null)    return true;
    if(root.left != null && root.left.val > root.val)
        return false;
    if(root.right != null && root.right.val < root.val)
        return false;
    if(!isTree(root.left) || !isTree(root.right))
        return false;
    return true;   
}

4. 使用中序遍历判断二叉查找树(保存到数组中)

这个方法的思路也很简单,根据二叉查找树的性质我们可以知道:如果我们用中序遍历遍历二叉查找树的话,遍历到的每个节点是依次增大的。根据这个思路,我们可以遍历一遍树,将遍历到的值保存到数组中,然后再判断这个数组是否为递增数组就可以了。下面是算法片段:

List<Integer> list=new ArrayLIst<>();
public void isTree(TreeNode root){
    if(root == NULL)  return;
    isTree(root.left);
    list.add(root.val);
    isTree(root.right);
}
public static void main(String[] args){
    //假设给定了树的根节点root
    isTree(root);
    for(int i = 1; i < list.size(); i++)
        if(list.get(i-1) >= list.get(i]))
            return false;
    return true;
}

5. 使用中序遍历判断二叉查找树(保存中序遍历上一个节点值)

每当我说可以用数组保存遍历顺序,然后判断数组是否为递增数组后,面试官总会问“还有没有更简单的方法,使用O(1)的空间复杂度?”
其实面试官的意思就是:只让你保存遍历节点的上一个节点值,不需要另外开辟空间。因为中序遍历是递增的,所以只需要比较上一个节点值和该节点值的大小即可。递归算法思路就是如下:

int last = Integer.MIN_VALUE;
public boolean isTree(TreeNode root){
    if(root == null)    
        return true;
    if(!isTree(root.left))    
        return false;
    if(root.val <= last)    
        return false;
    last=root.val;
    if(!isTree(root.right))    
        return false;
    return true;
}

有的时候面试官还要问能不能用迭代写出来,我就一块写一下吧。迭代算法思路就是使用栈进行存储,先将左子树压入栈,然后依次取出判断。代码如下:

public boolean isBST(TreeNode root) {
    Stack<TreeNode> stack = new Stack<>();
    TreeNode p = root, pre =n ull;
    while(p != null || !stack.isEmpty()){
        while(p != null){
            stack.push(p);
            p = p.left;
        }         
        TreeNode t = stack.pop();
        if(pre != null && t.val <= pre.val)
            return false;
        pre = t;
        p = t.right;        
    }
    return true;
}

6. 使用Morris Traversal方法

这个方法也是从别人那里看到的一种遍历二叉树的方法,不用栈存储而且为空间复杂度为O(1)。思路就是利用叶子节点中的左右空指针指向中序遍历下的前驱节点或后继节点,从而得到判断条件。在一开始last我用的是int last=Ineger.MIN_VALUE 结果在leetcode上跑的时候有个测试用例是 -2147483648,正好是MIN_VALUE,然后就判断错误,于是用了其包装类型。代码如下:

public boolean isValidBST(TreeNode root) {
    if(root == null) 
        return true;
    Integer last = null;
    TreeNode cur=root, pre=null;
    while(cur != null){
        if(cur.left != null){
            pre = cur.left;
            while(pre.right != null && pre.right != cur)
                pre = pre.right;
            if(pre.right == null){
                pre.right = cur;
                cur = cur.left;
            }else{
                pre.right = null;
                if(last != null && cur.val <= last)
                    return false;
                last = cur.val;
                cur = cur.right;
            }                                                   
        }else{
            if(last != null && cur.val <= last)
                return false;
            last = cur.val;
            cur = cur.right;
        }
    }
    return true;
 }

6. 参考文章

当然,还有好多方法没有写出来,比如说这个: https://blog.csdn.net/FlushHip/article/details/70941221 用每个子树的最大最小值和根节点值比较得到结果。
感谢一下大神提供思路以及代码。。。
递归非递归方法
https://blog.csdn.net/qq_30490125/article/details/53135274
Morris Traversal方法
https://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html
Leetcode上题目
https://leetcode-cn.com/problems/validate-binary-search-tree

假设二叉查找树的节点结构体如下: ``` typedef struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; } TreeNode; ``` 那么对于一个二叉查找树,它的中序遍历序列是一个升序的序列。因此,我们可以对输入序列进行如下判断: 1. 如果输入序列为空,那么它一定是一个二叉查找树的中序遍历序列。 2. 如果输入序列不为空,那么我们先取出序列的第一个元素作为根节点的值。 3. 然后,我们依次遍历输入序列中剩余的元素,将它们与根节点的值进行比较: - 如果某个元素小于根节点的值,那么它应该作为左子的一部分,我们递归处理左子的序列。 - 如果某个元素大于根节点的值,那么它应该作为右子的一部分,我们递归处理右子的序列。 - 如果某个元素等于根节点的值,那么它不应该出现在二叉查找树中,因此输入序列不是二叉查找树的中序遍历序列。 具体的代码实现如下: ``` bool isValidBST(int* sequence, int length) { if (sequence == NULL || length == 0) { return true; } int root = sequence[0]; int i = 1; while (i < length && sequence[i] < root) { i++; } int j = i; while (j < length && sequence[j] > root) { j++; } if (j != length) { return false; } bool leftValid = isValidBST(sequence + 1, i - 1); bool rightValid = isValidBST(sequence + i, length - i); return leftValid && rightValid; } ``` 该函数返回一个布尔值,表示输入序列是否二叉查找树的中序遍历序列。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值