前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试中很值得聊的二叉树遍历方法——Morris遍历

面试中很值得聊的二叉树遍历方法——Morris遍历

原创
作者头像
不会飞的小鸟
修改2020-05-27 14:29:30
1.1K0
修改2020-05-27 14:29:30
举报
文章被收录于专栏:只为你下只为你下

通过利用空闲指针的方式,来节省空间。时间复杂度O(N),额外空间复杂度O(1)。普通的非递归和递归方法的额外空间和树的高度有关,递归的过程涉及到系统压栈,非递归需要自己申请栈空间,都具有O(N)的额外空间复杂度。

Morri遍历的原则:

1. 假设当前节点为cur,

2. 如果cur没有左孩子,cur向右移动,cur = cur.right

3. 如果cur有左孩子,找到左子树上最右的节点mostRight

  3.1 如果mostRight.right == null,令mostRight.right = cur,cur向左移动,cur = cur.left

  3.2 如果mostRight.right == cur,令mostRight.right = null,cur向右移动,cur = cur.right

4. 如果cur == null 停止遍历

Morris:1242513637

代码语言:javascript
复制
 1     public static void morris(TreeNode head){
 2         if(head == null) return;
 3         TreeNode cur = head;
 4         TreeNode mostRight = null;
 5         while(cur != null){//cur为空遍历停止【4】
 6             mostRight = cur.left;//是cur左子树上最右的节点
 7             if(mostRight != null){//有左子树【3】
 8                 while(mostRight.right != null && mostRight != cur){//mostRight!=cur不加就会绕环循环
 9                     mostRight = mostRight.right;//找最右节点【3】
10                 }
11                 if(mostRight.right == null){//第一次来到cur【3.1】
12                     mostRight.right = cur;
13                     cur = cur.left;
14                     continue;//执行循环
15                 }else {//mostRight.right = cur第二次来到cur【3.2】
16                     mostRight.right = null;
17                 }
18             }
19             cur = cur.right;//没有左子树【2】
20 
21         }
22     }
复制代码
复制代码

所有节点遍历左子树右边界的时间总代价O(N)

基于Morris的先中后序遍历

如果cur有左子树一定能遍历2次,没有左子树只能遍历一次。

先序遍历

Morris:1242513637

Morris先序:1245367

基于Morris的先序遍历,如果一个节点可以到达两次则打印第一次,如果只能到达一次直接打印。

复制代码
复制代码

代码语言:javascript
复制
 1     public static void morrisPre(TreeNode head){
 2         if(head == null) return;
 3         TreeNode cur = head;
 4         TreeNode mostRight = null;
 5         while(cur != null){//有左子树
 6             mostRight = cur.left;
 7             if(mostRight != null){
 8                 while(mostRight.right != null && mostRight != cur){
 9                     mostRight = mostRight.right;
10                 }
11                 if(mostRight.right == null){//第一次来到左子树
12                     System.out.println(cur.val);//打印
13                     mostRight.right = cur;
14                     cur = cur.left;
15                     continue;
16                 }else {
17                     mostRight.right = null;
18                 }
19             }else{
20                 System.out.println(cur.val);//没有左子树 只会遍历一次
21             }
22             cur = cur.right;
23         }
24     }
复制代码
复制代码

中序遍历

Morris:1242513637

Morris中序:4251637

基于Morris的中序遍历,如果一个节点可以到达两次则打印第二次,如果只能到达一次直接打印。

复制代码
复制代码

代码语言:javascript
复制
 1     public static void morrisIn(TreeNode head) {
 2         if(head == null) return;
 3         TreeNode cur = head;
 4         TreeNode mostRight = null;
 5         while(cur != null){
 6             mostRight = cur.left;
 7             if(mostRight != null){
 8                 while(mostRight.right != null && mostRight != cur){
 9                     mostRight = mostRight.right;
10                 }
11                 if(mostRight.right == null){
12                     mostRight.right = cur;
13                     cur = cur.left;
14                     continue;
15                 }else {
16                     mostRight.right = null;
17                 }
18             }
19             //没有左树跳过if直接打印,有左树第二次来到cur退出循环打印
20             System.out.println(cur.val);
21             cur = cur.right;
22         }
23     }

后序遍历

Morris:1242513637

Morris先序:4526731

基于Morris的后序遍历,如果一个节点可以到达两次则第二次到达时逆序打印左树的右边界,单独逆序打印整棵树的右边界。

(1)逆序右边界(等同于单链表的逆序)

复制代码
复制代码

代码语言:javascript
复制
 1       public static TreeNode reverseEdge(TreeNode from) {
 2         TreeNode pre = null;
 3         TreeNode next = null;
 4         while(from != null){
 5             next = from.right;
 6             from.right = pre;
 7             pre = from;
 8             from = next;
 9         }
10         return pre;
11     }
复制代码
复制代码

(2)逆序打印以head为头节点的右边界。

复制代码
复制代码

代码语言:javascript
复制
1     public static void printRightEdge(TreeNode head) {
2         TreeNode tail = reverseEdge(head);//逆转右边界
3         TreeNode cur = tail;
4         while(cur != null){
5             System.out.println(cur.val + " ");
6             cur = cur.right;
7         }
8         reverseEdge(tail);//逆转回去 恢复原树
9     }
复制代码
复制代码

(3)在Morris遍历中按时机打印。

复制代码
复制代码

代码语言:javascript
复制
 1     public static void morrisPost(TreeNode head){
 2         if(head == null) return;
 3         TreeNode cur = head;
 4         TreeNode mostRight = null;
 5         while(cur != null){
 6             mostRight = cur.left;
 7             if(mostRight != null){
 8                 while(mostRight.right != null && mostRight != cur){
 9                     mostRight = mostRight.right;
10                 }
11                 if(mostRight.right == null){
12                     mostRight.right = cur;
13                     cur = cur.left;
14                     continue;
15                 }else {//第二次达到 逆序打印左子树的右边界
16                     mostRight.right = null;
17                     printRightEdge(cur.left);
18                 }
19             }
20             cur = cur.right;
21         }
22         //最后退出循环之后,单独打印整棵树的右边界
23         printRightEdge(head);
24     }

Morris遍历的应用

如何判断一棵树是否是搜索二叉树?

中序遍历升序就是搜索二叉树。

复制代码
复制代码

代码语言:javascript
复制
 1     public static boolean isBST(TreeNode head){
 2         if(head == null) return true;
 3         TreeNode cur = head;
 4         TreeNode mostRight = null;
 5         int preValue www.lanboylpt.cn= Integer.MIN_VALUE;//上一次得到的值
 6         while(cur !=www.anxinzc5.cn   null){
 7             mostRight = cur.left;
 8             if(mostRight !www.qiaoheibpt.com= null){
 9                 while(mostRight.right != null && mostRight != cur){
10                     mostRight =www.yixingylzc.cn mostRight.right;
11                 }
12                 if(mostRight.right == null){
13                     mostRight.right = cur;
14                     cur = cur.left;
15                     continue;
16                 }else {www.changanylzc.com
17                     mostRight.right = null;
18                 }
19             }
20             //中序遍历的操作时机在这里 所以在这里进行判断
21             if(cur.val <= preValue){www.qianshenyule.com //没有递增
22                 return false;
23             }
24             preValue www.yipin2zc.com= cur.val;
25             cur = cur.right;
26         }
27         return true;
28     }
复制代码
复制代码

总结

树型DP问题的套路:定义一个类收集树的信息,定义一个递归函数,递归地收集左子树的信息和右子树的信息,再整合得到以当前节点为根的树的信息。

什么时候用树型DP什么时候用Morris遍历?

当必须得到左树的信息和右树的信息后,再在当前节点整合二者信息后做出判断则用树型DP是最优解。

当不需要整合左树和右树信息的时候,可以用树型DP,但是Morris是最优解。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基于Morris的先中后序遍历
    • 先序遍历
      • 中序遍历
        • 后序遍历
        • Morris遍历的应用
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档