1. 程式人生 > >面試中很值得聊的二叉樹遍歷方法——Morris遍歷

面試中很值得聊的二叉樹遍歷方法——Morris遍歷

Morris遍歷

通過利用空閒指標的方式,來節省空間。時間複雜度O(N),額外空間複雜度O(1)。普通的非遞迴和遞迴方法的額外空間和樹的高度有關,遞迴的過程涉及到系統壓棧,非遞迴需要自己申請棧空間,都具有O(N)的額外空間複雜度。

Morris遍歷的原則:

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

 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的先序遍歷,如果一個節點可以到達兩次則列印第一次,如果只能到達一次直接列印。

 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的中序遍歷,如果一個節點可以到達兩次則列印第二次,如果只能到達一次直接列印。

 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)逆序右邊界(等同於單鏈表的逆序)

 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為頭節點的右邊界。

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遍歷中按時機列印。

 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遍歷的應用

如何判斷一棵樹是否是搜尋二叉樹?

中序遍歷升序就是搜尋二叉樹。

 1     public static boolean isBST(TreeNode head){
 2         if(head == null) return true;
 3         TreeNode cur = head;
 4         TreeNode mostRight = null;
 5         int preValue = Integer.MIN_VALUE;//上一次得到的值
 6         while(cur != null){
 7             mostRight = cur.left;
 8             if(mostRight != null){
 9                 while(mostRight.right != null && mostRight != cur){
10                     mostRight = mostRight.right;
11                 }
12                 if(mostRight.right == null){
13                     mostRight.right = cur;
14                     cur = cur.left;
15                     continue;
16                 }else {
17                     mostRight.right = null;
18                 }
19             }
20             //中序遍歷的操作時機在這裡 所以在這裡進行判斷
21             if(cur.val <= preValue){//沒有遞增
22                 return false;
23             }
24             preValue = cur.val;
25             cur = cur.right;
26         }
27         return true;
28     }

總結

樹型DP問題的套路:定義一個類收集樹的資訊,定義一個遞迴函式,遞迴地收集左子樹的資訊和右子樹的資訊,再整合得到以當前節點為根的樹的資訊。

什麼時候用樹型DP什麼時候用Morris遍歷?

當必須得到左樹的資訊和右樹的資訊後,再在當前節點整合二者資訊後做出判斷則用樹型DP是最優解。

當不需要整合左樹和右樹資訊的時候,可以用樹型DP,但是Morris是最優解。

&n