二叉搜索树的后序遍历序列验证

本文详细介绍了如何验证一个给定的后序遍历序列是否符合二叉搜索树的特性,包括两种解法:递归和单调栈。递归法通过寻找左右子树的边界并递归检查每个子树;单调栈法利用栈的单调性快速判断。这两种方法的时间复杂度和空间复杂度各有不同,但都能有效解决问题。
摘要由CSDN通过智能技术生成

二叉搜索树的后续遍历序列验证

这道题有点抽象,主要是如果没有对二叉搜索树的遍历验证有了解会比较难想象,我也在这里卡了很久。

本质就是找到根节点,判断右子树的所有节点(根节点和所有子节点)都大于根节点,左子树都小于。

原题

image-20221124170956220

二叉搜索树概念

二叉搜索树,又称二叉排序树,若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索树

二叉搜索树的特性:

如下图,就是一颗二叉搜索树,有如下性质:

  • 任意节点的左子树的所有子节点(非空)的数值都小于它的根节点,同理右子树的所有节点的数值(非空)都大于根节点。

  • 任意节点的左右子树也是二叉搜索树

  • 有序性,中序遍历的结果是升序的

    下图的二叉查找树的中序遍历(左根右)为:[1,2,3,4,5,6,7,8,9,10], 可以看出是从小到大有序的

image-20221124171812863

题目解法(java实现)

1. 递归

因为题目给的是后序遍历后的数组,同时我们知道了二叉搜索树的性质,可以知道这个数组是部分乱序的,后续遍历的顺序是左右根,所有二叉搜索树的根节点在数组的最后一个,我们先从这个入手,想要判断这个二叉搜索树是否合法,我们就要判断根节点的左子树的全部节点的值是否都小于根节点的值,右子树的全部节点的值是否都大于根节点;因此,我们先用一个for循环,找到左子树和右子树在数组里的分界线

如上图的例子后序遍历数组是:[1,3,2,5,6,4,8,10,9,7],左右子树分割为[ 1,3,2,5,6,4 | 8,10,9 | 7 ] ,可以发现左子树都比最后一个数组节点小,直到找到比根节点大的值,就能确定左右子树在数组的边界

然后再遍历右子树,判断是否都大于根节点,如果出现小于根节点的可以直接判定,这棵树不是二叉搜索树

/**
* @param: seq  后序遍历的数组
* @param: l-r 判断这颗树或子树在数组里面的范围
**/
private boolean isValid(int[] seq, int l, int r){
    int root = seq[r];  // 根节点
    int i;  // 记录左右子树在数组的边界下标
    // 找出左子树和右子树的边界下标
    for(i = l; i < r; i++) {
        if(seq[i] > root)
            break;
    }

    // 直接判断右子树是否符合条件(都大于根节点)
    for(int j = i; j < r; j++) {
        if(seq[j] < root)
            return false;
    }
}

理解了上面的代码后,就可以上手递归了,不就是多次重复上面的操作,不断分割左右子树,传入不同的下标范围嘛

剩下一个问题就是递归的终止条件是啥?当我们传 isValid(int[] seq, int l, int r) 我们通过 i 来分割左右子树

return isValid(seq, l, i-1) && isValid(seq, i, r-1);

那么当分割到只剩下一个节点的时候,即 l = i-1或 i = r-1 时,下次递归我们得到结果就 l <= r ,并且中途没有检测出和二叉搜索树矛盾的地方(即右子树出现大于根节点的情况return false),我们就直接返回true,完整代码如下

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length == 0) {
            return false;
        }
        return isValid(sequence, 0, sequence.length-1);
    }

    private boolean isValid(int[] seq, int l, int r){
        if(l >= r)   // 递归出口
            return true;

        int root = seq[r];
        int i;
        // 找出左子树和右子树的边界下标
        for(i = l; i < r; i++) {
            if(seq[i] > root)
                break;
        }

        // 直接判断右子树是否符合条件(都大于根节点)
        for(int j = i; j < r; j++) {
            if(seq[j] < root)
                return false;
        }
        // 也可以倒过来,先遍历右子树,再根据左子树判断
        return isValid(seq, l, i-1) && isValid(seq, i, r-1);

    }
}

算法时间复杂度:O(n2) 空间复杂度:O(n)

2. 单调栈

单调栈会比递归更难理解一些,但是原理是有相似的,只不过使用了栈来辅助

EFE7953D46B926AFF99D8C8A97A56E1E

过程如上:乍一看肯定很懵,我们细细分析这个过程:

  1. 数组逆序遍历(即后序遍历的相反顺序:根右左),由于是单点栈,所以一旦出现逆序(即本来是从栈底到栈顶是升序,出现一个比栈顶小的数),就弹出栈的所有比要加入栈的节点的值大的节点,从而保证单调性,我们发现这个时候,如果这棵树是二叉搜索树的话,弹出的最后一个比要入栈的节点大的元素,就是这颗树或子树的根节点,将其记录
  2. 因为一旦出现逆序的现象,就说明后面的数都是当前这个根节点的左子树(右子树已经确定,因为前面大于根节点的值被判定为右子树),而二叉搜索树的左子树是小于根节点的,因此,判断不是二叉搜索树的条件就是,一旦根节点 小于 下面要遍历的左子树的节点,我们就return false;

具体代码如下:

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length == 0)
            return false;
        Stack<Integer> stack = new Stack<>();
        int root = Integer.MAX_VALUE;   // 保证第一次没有根节点的情况下,不会直接返回false
        for(int i = sequence.length-1; i >= 0; i--) {
            // 如果左子树中的节点出现大于根节点的,不符合要求,直接返回false
            if(sequence[i] > root)
                return false;
            // 找到左子树的开始节点,pop弹出右子树,记录最后一个节点为根节点,方便后面判断左子树中的节点是否都小于根节点
            while(!stack.isEmpty() && stack.peek() > sequence[i]) {
                root = stack.pop();
            }
            stack.add(sequence[i]);
        }
        return true;

    }
}

算法时间复杂度:O(n) 空间复杂度:O(n)


希望以上能帮助到你理解这道题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

剽悍兔八哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值