二叉搜索树的后序遍历序列(判断数组是否满足二叉搜索树的后续遍历)
明确
二叉搜索树定义:左子树中所有节点的值 < 根节点的值;右子树中所有节点的值 > 根节点的值;其左、右子树也分别为二叉搜索树。
所以怎么判断一个数组是一个二叉搜索树的后序遍历?
从定义出发:左子树所有值小于根节点,右子树所有值大于根节点。
方法一,递归
根据二叉搜索树的定义,可以通过递归,判断所有子树的 正确性 (即其后序遍历是否满足二叉搜索树的定义) ,若所有子树都正确,则此序列为二叉搜索树的后序遍历。
递归解析:
终止条件: 当 i≥j ,说明此子树节点数量 ≤ 1 ,无需判别正确性,因此直接返回 true;
递推工作:
划分左右子树:
遍历后序遍历的 [i, j]区间元素,寻找 第一个大于根节点 的节点,索引记为 m 。此时,可划分出左子树区间 [i,m−1] 、右子树区间 [m, j - 1] 、根节点索引 j 。
判断是否为二叉搜索树:
左子树区间 [i,m−1] 内的所有节点都应 << postorder[j] 。而第 1.划分左右子树 步骤已经保证左子树区间的正确性,因此只需要判断右子树区间即可。
右子树区间 [m, j-1] 内的所有节点都应 >> postorder[j] 。实现方式为遍历,当遇到 ≤postorder[j] 的节点则跳出;则可通过 p = j 判断是否为二叉搜索树。
所有子树都需正确才可判定正确,因此使用 与逻辑符 && 连接。
p=j : 判断 此树 是否正确。
recur(i,m−1) : 判断 此树的左子树 是否正确。
recur(m,j−1) : 判断 此树的右子树 是否正确。
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}
boolean recur(int[] postorder, int i, int j) {
if(i >= j) return true;
int p = i;
while(postorder[p] < postorder[j]) p++;
int m = p;
while(postorder[p] > postorder[j]) p++;
return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);
}
}
方法二:辅助单调栈,栈内放单调递增元素
什么是单调栈,什么时候用单调栈
什么是单调栈?
从名字上就听的出来,单调栈中存放的数据应该是有序的,所以单调栈也分为单调递增栈和单调递减栈
单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小,算法思想:
栈内单调递增,每次遇到stack.peek() > postorder[i]时就进行判断输出。
单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大,算法思想:
栈内单调递减,每次遇到stack.peek() < postorder[i]时就进行判断输出。
这道题,为什么可以使用单调栈?
设后序遍历倒序列表为 [rn,rn−1,...,r1],遍历此列表,设索引为 i ,若为 二叉搜索树 ,则有:
当节点值ri>ri+1 时: 节点 ri一定是节点 ri+1的右子节点。
当节点值ri<ri+1时, 节点 ri一定是某节点 root 的左子节点,且 root为节点 ri+1,ri+2...rn中值大于且最接近ri 的节点(∵ root直接连接 左子节点 ri)。
当遍历时遇到递减节点 ri<ri+1,若为二叉搜索树,则对于后序遍历中节点 ri 右边的任意节点,都小于ri 的根节点。
ri的根节点怎么求,就是单调栈,因为是大于ri,而且是最接近ri,所以单调栈就可以使用。
记住一点,二叉搜索树左子树一定小于根节点,右子树一定大于根节点,这告诉我们什么?当我们找到一个根节点,继续遍历数组时,所有节点一定比根节点小。如果存在大于根节点的值,直接返回false即可。
当然我们会更新root的值,因为当前根节点满足之后就要去进行当前节点的左子树或者父节点的左子树判断。
逆序的目的是先找根节点。
所以根据数组逆序遍历可以进行操作,栈内放单调递增元素,相当于右子树判断完毕,遇到<stack.peek()时,开始判断左子树。
初始化: 单调栈 stack ,父节点值 root = +∞ (初始值为正无穷大,可把树的根节点看为此无穷大节点的左孩子);
倒序遍历 postorder :记每个节点为 ri;
判断: 若 ri>root ,说明此后序遍历序列不满足二叉搜索树定义,直接返回 false;(相当于判断当前根节点左子树)
更新父节点 root : 当栈不为空 且 ri<stack.peek()时,循环执行出栈,并将出栈节点赋给 root 。(相当于根节点右子树判断完毕)
入栈: 将当前节点 ri 入栈
class Solution {
public boolean verifyPostorder(int[] postorder) {
Stack<Integer> stack = new Stack<>();
int root = Integer.MAX_VALUE;
for(int i = postorder.length - 1; i >= 0; i--) {
//我们已经找到了根节点,所以判断左子树。
if(postorder[i] > root) return false;
while(!stack.isEmpty() && stack.peek() > postorder[i])
//找根节点,找到后,就开始判断左子树。
root = stack.pop();
//判断右节点
stack.add(postorder[i]);
}
return true;
}
}