Morris遍历

一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1)通过利用原树中大量空闲指针的方式,达到节省空间的目的
如果要遍历的树不能修改不可以用Morris遍历,但是Morris遍历完毕后会将树修改成原来的样子
Morris遍历解决了最本质的遍历问题,所以很多问题的最优解都以它为最优解

Morris遍历细节

假设来到当前节点cur,开始时cur来到头节点位置

  1. 如果cur没有左孩子,cur向右移动(cur = cur.right)
  2. 如果cur有左孩子,找到左子树上最右的节点mostRight:
    1. 如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)
    2. 如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)
  3. cur为空时遍历停止
  4. 一个节点如果有左数,一定会回到此节点两次,没有左数的节点只能到达一次

    1. public static void morris (Node head) {
    2. if (head == null) {
    3. return;
    4. }
    5. Node cur = head;
    6. Node mostRight = null;
    7. while (cur != null) { // 过流程
    8. mostRight = cur.left; // mostRight是cur左孩子
    9. if (mostRight != null) { // 有左子树
    10. while (mostRight.right != null && mostRight.right != cur) {
    11. mostRight = mostRight.right;
    12. }
    13. // mostRight变成了cur左子树上,最右的节点
    14. if (mostRight.right == null) { // 这是第一次来到cur
    15. mostRight.right = cur;
    16. cur = cur.left;
    17. continue;
    18. } else { // mostRight.right == cur
    19. mostRight.right = null;
    20. }
    21. }
    22. cur = cur.right;
    23. }
    24. }
    1. public class MorrisTraversal {
    2. public static class Node {
    3. public int value;
    4. Node left;
    5. Node right;
    6. public Node(int data) {
    7. this.value = data;
    8. }
    9. }
    10. // 中序遍历
    11. // 节点只来到一次的直接打印
    12. // 节点来到两次的,第二次打印
    13. public static void morrisIn(Node head) {
    14. if (head == null) {
    15. return;
    16. }
    17. Node cur1 = head;
    18. Node mostRight = null;
    19. while (cur1 != null) {
    20. mostRight = cur1.left;
    21. if (mostRight != null) {
    22. while (mostRight.right != null && mostRight.right != cur1) {
    23. mostRight = mostRight.right;
    24. }
    25. if (mostRight.right == null) {
    26. mostRight.right = cur1;
    27. cur1 = cur1.left;
    28. continue;
    29. } else {
    30. mostRight.right = null;
    31. }
    32. }
    33. System.out.print(cur1.value + " ");
    34. cur1 = cur1.right;
    35. }
    36. System.out.println();
    37. }
    38. // 先序遍历
    39. // 只来到当前节点一次,直接打印
    40. // 来到当前节点两次,第一次打印
    41. public static void morrisPre(Node head) {
    42. if (head == null) {
    43. return;
    44. }
    45. Node cur1 = head;
    46. Node mostRight = null;
    47. while (cur1 != null) {
    48. mostRight = cur1.left;
    49. if (mostRight != null) {
    50. while (mostRight.right != null && mostRight.right != cur1) {
    51. mostRight = mostRight.right;
    52. }
    53. if (mostRight.right == null) {
    54. mostRight.right = cur1;
    55. System.out.print(cur1.value + " ");
    56. cur1 = cur1.left;
    57. continue;
    58. } else {
    59. mostRight.right = null;
    60. }
    61. } else {
    62. System.out.print(cur1.value + " ");
    63. }
    64. cur1 = cur1.right;
    65. }
    66. System.out.println();
    67. }
    68. // 后序遍历的 做法:对于那些能第二次到达自己的节点,
    69. // 逆序遍历其节点的左子树 的最右边界。 然后 最后补上整棵树的最右边界逆序。
    70. public static void morrisPos(Node head) {
    71. if (head == null) {
    72. return;
    73. }
    74. Node cur1 = head;
    75. Node mostRight = null;
    76. while (cur1 != null) {
    77. mostRight = cur1.left;
    78. if (mostRight != null) {
    79. while (mostRight.right != null && mostRight.right != cur1) {
    80. mostRight = mostRight.right;
    81. }
    82. if (mostRight.right == null) {
    83. mostRight.right = cur1;
    84. cur1 = cur1.left;
    85. continue;
    86. } else {
    87. mostRight.right = null;
    88. printEdge(cur1.left);
    89. }
    90. }
    91. cur1 = cur1.right;
    92. }
    93. printEdge(head);
    94. System.out.println();
    95. }
    96. // 以X为头的树,逆序打印这棵树的右边界
    97. public static void printEdge(Node head) {
    98. Node tail = reverseEdge(head);
    99. Node cur = tail;
    100. while (cur != null) {
    101. System.out.print(cur.value + " ");
    102. cur = cur.right;
    103. }
    104. reverseEdge(tail);
    105. }
    106. public static Node reverseEdge(Node from) {
    107. Node pre = null;
    108. Node next = null;
    109. while (from != null) {
    110. next = from.right;
    111. from.right = pre;
    112. pre = from;
    113. from = next;
    114. }
    115. return pre;
    116. }
    117. // for test -- print tree
    118. public static void printTree(Node head) {
    119. System.out.println("Binary Tree:");
    120. printInOrder(head, 0, "H", 17);
    121. System.out.println();
    122. }
    123. public static void printInOrder(Node head, int height, String to, int len) {
    124. if (head == null) {
    125. return;
    126. }
    127. printInOrder(head.right, height + 1, "v", len);
    128. String val = to + head.value + to;
    129. int lenM = val.length();
    130. int lenL = (len - lenM) / 2;
    131. int lenR = len - lenM - lenL;
    132. val = getSpace(lenL) + val + getSpace(lenR);
    133. System.out.println(getSpace(height * len) + val);
    134. printInOrder(head.left, height + 1, "^", len);
    135. }
    136. public static String getSpace(int num) {
    137. String space = " ";
    138. StringBuffer buf = new StringBuffer("");
    139. for (int i = 0; i < num; i++) {
    140. buf.append(space);
    141. }
    142. return buf.toString();
    143. }
    144. public static void main(String[] args) {
    145. Node head = new Node(4);
    146. head.left = new Node(2);
    147. head.right = new Node(6);
    148. head.left.left = new Node(1);
    149. head.left.right = new Node(3);
    150. head.right.left = new Node(5);
    151. head.right.right = new Node(7);
    152. printTree(head);
    153. morrisIn(head);
    154. morrisPre(head);
    155. morrisPos(head);
    156. printTree(head);
    157. }
    158. }

    Morris遍历判断一棵树是否是搜索二叉树

    public static boolean morrisIn(Node head) {
         if (head == null) {
             return true;
         }
         Node cur1 = head;
         Node mostRight = null;
         int preValue = Integer.MIN_VALUE;
         while (cur1 != null) {
             mostRight = cur1.left;
             if (mostRight != null) {
                 while (mostRight.right != null && mostRight.right != cur1) {
                     mostRight = mostRight.right;
                 }
                 if (mostRight.right == null) {
                     mostRight.right = cur1;
                     cur1 = cur1.left;
                     continue;
                 } else {
                     mostRight.right = null;
                 }
             }
             //System.out.print(cur1.value + " ");
             if (cur.value <= preValue) {
                 return false;
             }
             preValue = cur.value;
             cur1 = cur1.right;
         }
         return true;
     }
    

    树型dp套路和Morris遍历的使用区别:

  5. 如果发现某一个方法发现,必须来到某一个节点做第三次强整合(要完左树信息和要完右树信息,然后做强整合)则树型dp套路是最优解

  6. 如果不需要第三次强整合,则Morris遍历是最优解