二叉树之Morris遍历

  • 递归理解以及morris遍历
  • morris遍历改成前序遍历
  • morris遍历改成中序遍历
  • morris遍历改成后续遍历
  • 完整测试代码

递归理解以及morris遍历

  1. static void rec(Node head) {
  2. if (head == null) return;
  3. System.out.print(head.value + " "); //这里打印就是前序遍历
  4. rec(head.left);
  5. System.out.print(head.value + " "); //这里打印就是中序遍历
  6. rec(head.right);
  7. System.out.print(head.value + " "); //这里打印就是后续遍历
  8. }

这是我们用递归写的三种遍历方法,如果是下面的例子,上面程序输出如下:

image.png

程序输出:

  1. 1 2 4 8 8 8 4 4 2 5 5 9 9 9 5 2 1 3 6 10 10 10 6 6 3 7 11 11 11 7 7 3 1
  • 可以看到,每一个结点都会被访问三次,也就是说,访问的过程中,每一个结点都会经过三次;
  • 一个结点如果没有parent指针或者我们在非递归遍历中使用栈,是不能回到它的父节点的;
  • morris就是借助叶子结点的空闲指针帮助我们想办法回到父亲结点,省去了递归的空间,或者栈的空间,达到O(1)空间;

每个结点来到三次:

image.png

下面介绍morris遍历规则(现在和前、中、后序无关,就是morris序):

image.png

流程看下图的举例:

image.png

image.png

按照上面的解释可以完全写出下面的morris遍历过程(没有打印):

  1. static void morris(Node head){
  2. if(head == null)return;
  3. Node cur = head;
  4. Node mostRight = null;//左子树上最右的结点
  5. while(cur != null){
  6. mostRight = cur.left; //cur的第一个左结点
  7. if(mostRight != null){ //如果左子树不为空
  8. while(mostRight.right != null && mostRight.right != cur){ //找到最右边的结点 有两种情况
  9. mostRight = mostRight.right;
  10. }
  11. if(mostRight.right == null){ //第一次来到这个结点 满足第二大条中的第一条
  12. mostRight.right = cur;
  13. cur = cur.left;
  14. continue; // cur = cur.left 直接结束所有的
  15. }else { //第二次来到这个结点 满足第二大条中的第二条
  16. mostRight.right = null;
  17. }
  18. }
  19. cur = cur.right; //包括两种情况的 左子树为空和第二次来到这个结点
  20. }
  21. }

morris遍历改成前序遍历

需要注意几点:

  • 首先可以肯定是第一次来到这个结点的时候打印,所以是当mostRight.right == null(第一次来的时候,打印当前的cur);
  • 其次,如果一个结点没有左子树,相当于只会来到结点一次,就直接打印一遍就可以了;
  • 其实这样的做法等同于在递归的时候把打印的行为放在第一次来到这个结点的时候;
  1. //先序遍历
  2. static void morrisPre(Node head){
  3. if(head == null)return;
  4. Node cur = head;
  5. Node mostRight = null;
  6. while(cur != null){
  7. mostRight = cur.left;
  8. if(mostRight != null){
  9. while(mostRight.right != null && mostRight.right != cur){
  10. mostRight = mostRight.right;
  11. }
  12. if(mostRight.right == null){
  13. mostRight.right = cur;
  14. System.out.print(cur.value + " "); //先序是第一次来到这个结点的时候打印
  15. cur = cur.left;
  16. continue;
  17. }else {
  18. mostRight.right = null;
  19. }
  20. }else { //如果一个结点没有左子树 相当于只会来到这个结点一次 直接打印,然后往右走
  21. System.out.print(cur.value + " ");
  22. }
  23. cur = cur.right;
  24. }
  25. System.out.println();
  26. }

morris遍历改成中序遍历

中序更加简单:

  • 如果一个结点有左子树,那么我打印的时机是我遍历完左子树之后的打印,也就是第二次来到这个结点时候打印,也就是mostRight.right = cur的时候打印;
  • 如果一个结点没有左子树,那么本来也要打印一下,打印完就往右边窜了;
  • 本质就是不论怎么样要处理玩左子树才打印根结点;
  1. //中序遍历
  2. static void morrisIn(Node head){
  3. if(head == null)return;
  4. Node cur = head;
  5. Node mostRight = null;
  6. while(cur != null){
  7. mostRight = cur.left;
  8. if(mostRight != null){
  9. while(mostRight.right != null && mostRight.right != 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. System.out.print(cur.value + " ");//这里包括两种情况 没有左子树和有左子树的第二次来到这里打印
  21. cur = cur.right;
  22. }
  23. System.out.println();
  24. }

morris遍历改成后续遍历

后续遍历比较麻烦: 因为morris只会来到一个结点两次,但是递归会来到一个结点三次:

主要看以下几点:

  • 我们只关心那些有左子树,也就是会到一个结点两次的结点;
  • 逆序打印有左子树结点的左子树的右边界;
  • 最后打印整棵树的右边界的逆序;

image.png

  1. //morris后续
  2. static void morrisPos(Node head){
  3. if(head == null)return;
  4. Node cur = head;
  5. Node mostRight = null;
  6. while(cur != null){
  7. mostRight = cur.left;
  8. if(mostRight != null){
  9. while(mostRight.right != null && mostRight.right != 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. printEdge(cur.left); //打印左子树的右边界
  19. }
  20. }
  21. cur = cur.right;
  22. }
  23. printEdge(head); //最后打印整棵树的右边界
  24. System.out.println();
  25. }
  26. //打印边界
  27. static void printEdge(Node head) {
  28. //先逆序边界
  29. Node tail = reverseEdge(head);
  30. //打印
  31. Node cur = tail;
  32. while(cur != null){
  33. System.out.print(cur.value + " ");
  34. cur = cur.right;
  35. }
  36. //再逆序回来
  37. reverseEdge(tail);
  38. }
  39. //有点类似链表的逆序
  40. static Node reverseEdge(Node cur) {
  41. Node pre = null;
  42. Node next = null;
  43. while(cur != null){
  44. next = cur.right;//先保存下一个
  45. cur.right = pre;
  46. pre = cur;
  47. cur = next;
  48. }
  49. return pre;
  50. }

完整测试代码(例子就是上面的例子):

  1. public class Morris {
  2. static class Node {
  3. public int value;
  4. public Node left;
  5. public Node right;
  6. public Node(int value) {
  7. this.value = value;
  8. }
  9. }
  10. static Node createTree(int[] arr, int i) {
  11. if (i >= arr.length || arr[i] == -1) return null;
  12. Node head = new Node(arr[i]);
  13. head.left = createTree(arr, 2 * i + 1);
  14. head.right = createTree(arr, 2 * i + 2);
  15. return head;
  16. }
  17. static void process(Node head) {
  18. if (head == null) return;
  19. System.out.print(head.value + " ");
  20. process(head.left);
  21. System.out.print(head.value + " ");
  22. process(head.right);
  23. System.out.print(head.value + " ");
  24. }
  25. static void pre(Node head) {
  26. if (head == null) return;
  27. System.out.print(head.value + " ");
  28. pre(head.left);
  29. pre(head.right);
  30. }
  31. static void in(Node head) {
  32. if (head == null) return;
  33. in(head.left);
  34. System.out.print(head.value + " ");
  35. in(head.right);
  36. }
  37. static void pos(Node head) {
  38. if (head == null) return;
  39. pos(head.left);
  40. pos(head.right);
  41. System.out.print(head.value + " ");
  42. }
  43. static void morris(Node head) {
  44. if (head == null) return;
  45. Node cur = head;
  46. Node mostRight = null;//左子树上最右的结点
  47. while (cur != null) {
  48. mostRight = cur.left; //cur的第一个左结点
  49. if (mostRight != null) { //如果左子树不为空
  50. while (mostRight.right != null && mostRight.right != cur) { //找到最右边的结点 有两种情况
  51. mostRight = mostRight.right;
  52. }
  53. if (mostRight.right == null) { //第一次来到这个结点 满足第二大条中的第一条
  54. mostRight.right = cur;
  55. cur = cur.left;
  56. continue; // cur = cur.left 直接结束所有的
  57. } else { //第二次来到这个结点 满足第二大条中的第二条
  58. mostRight.right = null;
  59. }
  60. }
  61. cur = cur.right; //包括两种情况的 左子树为空和第二次来到这个结点
  62. }
  63. }
  64. //先序遍历
  65. static void morrisPre(Node head) {
  66. if (head == null) return;
  67. Node cur = head;
  68. Node mostRight = null;
  69. while (cur != null) {
  70. mostRight = cur.left;
  71. if (mostRight != null) {
  72. while (mostRight.right != null && mostRight.right != cur) {
  73. mostRight = mostRight.right;
  74. }
  75. if (mostRight.right == null) {
  76. mostRight.right = cur;
  77. System.out.print(cur.value + " "); //先序是第一次来到这个结点的时候打印
  78. cur = cur.left;
  79. continue;
  80. } else {
  81. mostRight.right = null;
  82. }
  83. } else { //如果一个结点没有左子树 相当于只会来到这个结点一次 直接打印,然后往右走
  84. System.out.print(cur.value + " ");
  85. }
  86. cur = cur.right;
  87. }
  88. System.out.println();
  89. }
  90. //中序遍历
  91. static void morrisIn(Node head) {
  92. if (head == null) return;
  93. Node cur = head;
  94. Node mostRight = null;
  95. while (cur != null) {
  96. mostRight = cur.left;
  97. if (mostRight != null) {
  98. while (mostRight.right != null && mostRight.right != cur) {
  99. mostRight = mostRight.right;
  100. }
  101. if (mostRight.right == null) {
  102. mostRight.right = cur;
  103. cur = cur.left;
  104. continue;
  105. } else {
  106. mostRight.right = null;
  107. }
  108. }
  109. System.out.print(cur.value + " ");
  110. cur = cur.right;
  111. }
  112. System.out.println();
  113. }
  114. //morris后续
  115. static void morrisPos(Node head) {
  116. if (head == null) return;
  117. Node cur = head;
  118. Node mostRight = null;
  119. while (cur != null) {
  120. mostRight = cur.left;
  121. if (mostRight != null) {
  122. while (mostRight.right != null && mostRight.right != cur) {
  123. mostRight = mostRight.right;
  124. }
  125. if (mostRight.right == null) {
  126. mostRight.right = cur;
  127. cur = cur.left;
  128. continue;
  129. } else { //第二次来的时候
  130. mostRight.right = null;
  131. printEdge(cur.left); //打印左子树的右边界
  132. }
  133. }
  134. cur = cur.right;
  135. }
  136. printEdge(head); //最后打印整棵树的右边界
  137. System.out.println();
  138. }
  139. //打印边界
  140. static void printEdge(Node head) {
  141. //先逆序边界
  142. Node tail = reverseEdge(head);
  143. //打印
  144. Node cur = tail;
  145. while (cur != null) {
  146. System.out.print(cur.value + " ");
  147. cur = cur.right;
  148. }
  149. //再逆序回来
  150. reverseEdge(tail);
  151. }
  152. //有点类似链表的逆序
  153. static Node reverseEdge(Node cur) {
  154. Node pre = null;
  155. Node next = null;
  156. while (cur != null) {
  157. next = cur.right;//先保存下一个
  158. cur.right = pre;
  159. pre = cur;
  160. cur = next;
  161. }
  162. return pre;
  163. }
  164. public static void main(String[] args) {
  165. int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, -1, -1, 9, 10, -1, 11, -1};
  166. Node head = createTree(arr, 0);
  167. System.out.println("------------递归树每个结点回到三次-----------");
  168. process(head);
  169. System.out.println();
  170. System.out.println();
  171. System.out.println("---------------------前序----------------");
  172. pre(head);
  173. System.out.println();
  174. morrisPre(head);
  175. System.out.println();
  176. System.out.println("---------------------中序----------------");
  177. in(head);
  178. System.out.println();
  179. morrisIn(head);
  180. System.out.println();
  181. System.out.println("---------------------后序----------------");
  182. pos(head);
  183. System.out.println();
  184. morrisPos(head);
  185. System.out.println();
  186. }
  187. }

测试结果:
二叉树之Morris遍历 - 图7