Morris遍历
一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1)通过利用原树中大量空闲指针的方式,达到节省空间的目的
如果要遍历的树不能修改不可以用Morris遍历,但是Morris遍历完毕后会将树修改成原来的样子
Morris遍历解决了最本质的遍历问题,所以很多问题的最优解都以它为最优解
Morris遍历细节
假设来到当前节点cur,开始时cur来到头节点位置
- 如果cur没有左孩子,cur向右移动(cur = cur.right)
- 如果cur有左孩子,找到左子树上最右的节点mostRight:
- 如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)
- 如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)
- cur为空时遍历停止
一个节点如果有左数,一定会回到此节点两次,没有左数的节点只能到达一次
public static void morris (Node head) {if (head == null) {return;}Node cur = head;Node mostRight = null;while (cur != null) { // 过流程mostRight = cur.left; // mostRight是cur左孩子if (mostRight != null) { // 有左子树while (mostRight.right != null && mostRight.right != cur) {mostRight = mostRight.right;}// mostRight变成了cur左子树上,最右的节点if (mostRight.right == null) { // 这是第一次来到curmostRight.right = cur;cur = cur.left;continue;} else { // mostRight.right == curmostRight.right = null;}}cur = cur.right;}}
public class MorrisTraversal {public static class Node {public int value;Node left;Node right;public Node(int data) {this.value = data;}}// 中序遍历// 节点只来到一次的直接打印// 节点来到两次的,第二次打印public static void morrisIn(Node head) {if (head == null) {return;}Node cur1 = head;Node mostRight = null;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 + " ");cur1 = cur1.right;}System.out.println();}// 先序遍历// 只来到当前节点一次,直接打印// 来到当前节点两次,第一次打印public static void morrisPre(Node head) {if (head == null) {return;}Node cur1 = head;Node mostRight = null;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;System.out.print(cur1.value + " ");cur1 = cur1.left;continue;} else {mostRight.right = null;}} else {System.out.print(cur1.value + " ");}cur1 = cur1.right;}System.out.println();}// 后序遍历的 做法:对于那些能第二次到达自己的节点,// 逆序遍历其节点的左子树 的最右边界。 然后 最后补上整棵树的最右边界逆序。public static void morrisPos(Node head) {if (head == null) {return;}Node cur1 = head;Node mostRight = null;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;printEdge(cur1.left);}}cur1 = cur1.right;}printEdge(head);System.out.println();}// 以X为头的树,逆序打印这棵树的右边界public static void printEdge(Node head) {Node tail = reverseEdge(head);Node cur = tail;while (cur != null) {System.out.print(cur.value + " ");cur = cur.right;}reverseEdge(tail);}public static Node reverseEdge(Node from) {Node pre = null;Node next = null;while (from != null) {next = from.right;from.right = pre;pre = from;from = next;}return pre;}// for test -- print treepublic static void printTree(Node head) {System.out.println("Binary Tree:");printInOrder(head, 0, "H", 17);System.out.println();}public static void printInOrder(Node head, int height, String to, int len) {if (head == null) {return;}printInOrder(head.right, height + 1, "v", len);String val = to + head.value + to;int lenM = val.length();int lenL = (len - lenM) / 2;int lenR = len - lenM - lenL;val = getSpace(lenL) + val + getSpace(lenR);System.out.println(getSpace(height * len) + val);printInOrder(head.left, height + 1, "^", len);}public static String getSpace(int num) {String space = " ";StringBuffer buf = new StringBuffer("");for (int i = 0; i < num; i++) {buf.append(space);}return buf.toString();}public static void main(String[] args) {Node head = new Node(4);head.left = new Node(2);head.right = new Node(6);head.left.left = new Node(1);head.left.right = new Node(3);head.right.left = new Node(5);head.right.right = new Node(7);printTree(head);morrisIn(head);morrisPre(head);morrisPos(head);printTree(head);}}
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遍历的使用区别:
如果发现某一个方法发现,必须来到某一个节点做第三次强整合(要完左树信息和要完右树信息,然后做强整合)则树型dp套路是最优解
- 如果不需要第三次强整合,则Morris遍历是最优解
