面試中很值得聊的二叉樹遍歷方法——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