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是最優解。