Morris遍历判断二叉树是否为搜索二叉树?
提示:本节来说二叉树的Morris遍历,面试的高超优化技能!
此前学的关于二叉树的概念,先序遍历,中序遍历,后续遍历(这仨统称DFS遍历)和按层的方式遍历(俗称BFS遍历)重要的基础知识:
【1】二叉树,二叉树的归先序遍历,中序遍历,后序遍历,递归和非递归实现
【2】二叉树的宽度优先遍历BFS:按层的遍历方式,请你用队列实现DFS,或者请你用栈实现BFS
【3】求二叉树中,包含的最大二叉搜索子树的头节点是谁,它包含的节点数量是多少
这仨文章,都是重要的基础知识,笔试的时候可以用
面试的时候,除了上面仨,咱们还可以说一下Morris遍历的优化技能
Morris遍历的基础知识,有了这个知识,你才能看懂今天的题目解法
【4】Morris遍历:与二叉树的递归遍历(DFS/BFS)不同,优化空间复杂度为o(1)
【5】二叉树的Morris遍历:先序遍历和中序遍历
题目
Morris遍历判断二叉树是否为搜索二叉树?
一、审题
示例:
所谓搜索二叉树,就是任意节点x
x左树最大值max
x右树最小值min
与x.value的关系满足升序
max<x.value<min
在这里插入图片描述
Morris中序遍历
之前我们用DFS搞过中序遍历:
【1】二叉树,二叉树的归先序遍历,中序遍历,后序遍历,递归和非递归实现
一个规则:
遍历在Morris遍历第1次见cur时打印,就是先序遍历【当然,没有左树就见1次,直接打印】
遍历在Morris遍历第2次见cur时打印,就是中序遍历【当然,没有左树就见1次,直接打印】
跟先序遍历类似,手撕先序遍历代码,直接在Morris遍历上修改
咱们第一次见面不打印,但是也要注意,没左树是就必须打印,由于没左树和二次见面的代码是融合的,所以就直接在融合地方打印就行
//复习Morris遍历——中序遍历
//复习Morris遍历
public static void inMorrisReview(Node head){
if (head == null) return;
//【随时熟悉Morris遍历】**Morris遍历的流程:**
//(0)cur默认最开始指向head,mR默认为null【即cur左树的最右节点】,判断cur是否有左树?
Node cur = head;
Node mR = null;
while (cur != null){
//(1)cur如果**没有左树**,直接去右树cur=cur.right(当然没有右树那停止Morris遍历)
//没左树的,跟(4)联合精简代码
mR = cur.left;//mR走一步先
//(2)cur如果**有左树**,先找到**左树的最右节点mR**
if (mR != null){
while (mR.right != null && mR.right != cur) mR = mR.right;//往右穿
//(3)如果(2)中mR.right=null,则让mR.right=cur,cur=cur.left
if(mR.right == null){
mR.right = cur;
cur = cur.left;
continue;//绕过(4)
}else mR.right = null;//mR.right=cur,则让mR.right=null,然后cur=cur.right
}
//打印cur,没有左树的就打印这1次,有左树的这是第2次见面打印,就是中序遍历
System.out.print(cur.value +" ");//这个else一定加上
//(1)cur如果**没有左树**,直接去右树cur=cur.right(当然没有右树那停止Morris遍历)
//(4)如果(2)中mR.right=cur,则让mR.right=null,然后cur=cur.right
cur = cur.right;//因为(3)那continue,不会来着,否则(1)和(4)都要来
//这里加不加else都行,但是先序打印可能需要加else,1面就要打印
}
}
Morris中序遍历看是否次次升序
正常情况下,中序遍历,将二叉树生成一个数组,然后从头遍历看看i-1位置<i位置,即可,就是搜索二叉树
这样额外空间复杂度o(n)
为了优化空间,o(1)
咱们这样搞,利用Morris遍历中序遍历中
pre记录上一个cur的value
每一次pre不空时,与cur.value对比一下,如果pre>cur.value则不是搜索二叉树
整个遍历完成pre<cur.value的次序一直没变,那就是搜索二叉树
基础代码就是Morris中序遍历的代码,咱们直接改
在第二次见面时判断pre和cur.value,同时将cur.value赋值给pre,当下一个cur的pre
//复习判断二叉树是否为搜索二叉树
//复习Morris遍历——中序遍历
//复习Morris遍历
public static boolean inMorrisJudgeBST(Node head){
if (head == null) return true;
//【随时熟悉Morris遍历】**Morris遍历的流程:**
//(0)cur默认最开始指向head,mR默认为null【即cur左树的最右节点】,判断cur是否有左树?
Node cur = head;
Node mR = null;
Integer pre = null;//最开始不要
while (cur != null){
//(1)cur如果**没有左树**,直接去右树cur=cur.right(当然没有右树那停止Morris遍历)
//没左树的,跟(4)联合精简代码
mR = cur.left;//mR走一步先
//(2)cur如果**有左树**,先找到**左树的最右节点mR**
if (mR != null){
while (mR.right != null && mR.right != cur) mR = mR.right;//往右穿
//(3)如果(2)中mR.right=null,则让mR.right=cur,cur=cur.left
if(mR.right == null){
mR.right = cur;
cur = cur.left;
continue;//绕过(4)
}else mR.right = null;//mR.right=cur,则让mR.right=null,然后cur=cur.right
}
//打印cur,没有左树的就打印这1次,有左树的这是第2次见面打印,就是中序遍历
System.out.print(cur.value +" ");//这个else一定加上
if (pre != null && pre > cur.value) return false;//在中序遍历这搞定BST判断条件
pre = cur.value;
//(1)cur如果**没有左树**,直接去右树cur=cur.right(当然没有右树那停止Morris遍历)
//(4)如果(2)中mR.right=cur,则让mR.right=null,然后cur=cur.right
cur = cur.right;//因为(3)那continue,不会来着,否则(1)和(4)都要来
//这里加不加else都行,但是先序打印可能需要加else,1面就要打印
}
System.out.println();
return true;//整体遍历OK,没违反升序条件就OK
}
本质上就是考Morris的中序遍历而已
public static void test(){
Node head = createTree();
Node head2 = createTree();
System.out.println(isBSTmorris(head));
System.out.println();
System.out.println(inMorrisJudgeBST(head2));
}
public static void main(String[] args) {
test();
}
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int v){
value = v;
}
}
//造树,长啥样呢:
// 4
// 2 6
// 1 3 5 7
public static Node createTree(){
Node head = new Node(4);
Node n2 = new Node(2);
Node n3 = new Node(6);
Node n4 = new Node(1);
Node n5 = new Node(3);
Node n6 = new Node(5);
Node n7 = new Node(7);
head.left = n2;
head.right = n3;
n2.left = n4;
n2.right = n5;
n3.left = n6;
n3.right = n7;
return head;
}
测试结果:
true
1 2 3 4 5 6 7
true
中序遍历就是升序的,OK
总结
提示:重要经验:
1)Morris遍历的强大之处就在节约空间,o(1)空间复杂度搞定中序遍历,而中序遍历的升序关系,就是判断二叉树是否为搜索二叉树的条件。
2)Morris中序遍历,在Morris序的第二次见面时打印,判断pre<cur.value条件,也在此刻赋值cur.value给pre,方便下一次对比。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。