本文主要是介绍左神算法:遍历二叉树的神级方法(Morris遍历 / 线索二叉树),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本题来自左神《程序员代码面试指南》“遍历二叉树的神级方法”题目。
题目
给定一棵二叉树的头节点 head,完成二叉树的先序、中序和后序遍历。如果二叉树的节点数为N,则要求时间复杂度为O(N)
,额外空间复杂度为O(1)
。
题解
之前的题目已经剖析过如何用递归和非递归的方法实现遍历二叉树,但是很不幸,之前所有的方法虽然常用,但都无法做到额外空间复杂度为O(1)。这是因为遍历二叉树的递归方法实际使用了函数栈,非递归的方法使用了申请的栈,两者的额外空间都与树的高度相关,所以空间复杂度为O(h),h为二叉树的高度。
如果完全不用栈结构,能完成三种遍历吗?答案是可以。方法是使用二叉树节点中大量指向null 的指针,本题实际上就是大名鼎鼎的Morris 遍历,由Joseph Morris 于1979年发明。
首先来看普通的递归和非递归解法,其实都使用了栈结构,在处理完二叉树某个节点后可以回到上层去。为什么从下层回到上层会如此之难?因为二叉树的结构如此,每个节点都有指向孩子节点的指针,所以从上层到下层容易,但是没有指向父节点的指针,所以从下层到上层需要用栈结构辅助完成。
Morris 遍历的实质就是避免用栈结构,而是让下层到上层有指针,具体是通过让底层节点指向null 的空闲指针指回上层的某个节点,从而完成下层到上层的移动。我们知道,二叉树上的很多节点都有大量的空闲指针,比如,某些节点没有右孩子节点,那么这个节点的right 指针就指向null,我们称为空闲状态,Morris 遍历正是利用了这些空闲指针。
我们先不管先序、中序、后序的概念,先看看 Morris 遍历的过程 。
假设当前节点为cur,初始时cur 就是整棵树的头节点,根据以下标准让cur 移动:
- 如果cur 为null,则过程停止,否则继续下面的过程。
- 如果cur 没有左子树,则让cur 向右移动,即令cur = cur.right。
- 如果cur 有左子树,则找到cur 左子树上最右的节点,记为mostRight。
- 如果mostRight 的right 指针指向 null,则令mostRight.right = cur,也就是让mostRight
的right 指针指向当前节点,然后让cur 向左移动,即令cur = cur.left。 - 如果mostRight 的right 指针指向 cur,则令mostRight.right = null,也就是让mostRight
的 right 指针指向 null(目的是让二叉树恢复原状),然后让cur 向右移动,即令cur = cur.right。
- 如果mostRight 的right 指针指向 null,则令mostRight.right = cur,也就是让mostRight
举个例子:假设一棵二叉树如图 3-9 所示
cur 依次到达的节点为:4、2、1、2、3、4、6、5、6、7,我们将这个序列叫Morris 序。
可以看出,在一棵二叉树中,对于有左子树的节点都可以到达两次,对于没有左子树的节点都只会到达一次。
对于任何一个能够到达两次的节点Y,是 如何知道此时的 cur 是第一次来到 Y 还是第二次来到 Y 呢?如果Y 的左子树上的最右节点的指针(mostRight.right)是指向 null的,那么此时 cur 就是第一次到达Y;如果 mostRight.right 是指向 Y 的,那么此时cur 就是第二次到达 Y。这就是 Morris 遍历和 Morris 序的实质。
请读者先理解Morris 遍历和Morris 序,因为可以根据Morris 序进一步加工出先序、中序和后序.
package chapter_3_binarytreeproblem;public class Problem_05_MorrisTraversal {public static class Node {public int value;Node left;Node right;public Node(int data) {this.value = data;}}/*** Morris序遍历*/public static void morris(Node head) {if (head == null) {return;}Node cur = head;Node mostRight = null;while (cur != null) {mostRight = cur.left;if (mostRight != null) { // 如果当前cur有左子树// 找到cur左子树上最右的节点while (mostRight.right != null && mostRight.right != cur) {mostRight = mostRight.right;}// 从上面的while里出来后,mostRight就是cur左子树上最右的节点if (mostRight.right == null) { // 如果mostRight.right是指向null的mostRight.right = cur; // 让其指向curcur = cur.left; // cur向左移动continue; // 回到最外层的while,继续判断cur的情况} else { // 如果mostRight.right是指向cur的mostRight.right = null; // 让其指向null}}// cur如果没有左子树,cur向右移动// 或者cur左子树上最右节点的右指针是指向cur的,cur向右移动cur = cur.right;}}/*** 中序遍历*/public static void morrisIn(Node head) {if (head == null) {return;}Node cur = head;Node mostRight = null;while (cur != null) {mostRight = cur.left;if (mostRight != null) { // 如果当前cur有左子树while (mostRight.right != null && mostRight.right != cur) {mostRight = mostRight.right;}if (mostRight.right == null) { // 如果mostRight.right是指向null的mostRight.right = cur;cur = cur.left;continue;} else { // 如果mostRight.right是指向cur的mostRight.right = null;}}// cur没有左子树,或者cur左子树上最右节点的右指针是指向cur的System.out.print(cur.value + " ");cur = cur.right;}System.out.println();}/*** 前序遍历*/public static void morrisPre(Node head) {if (head == null) {return;}Node cur = head;Node mostRight = null;while (cur != null) {mostRight = cur.left;if (mostRight != null) { // 如果当前cur有左子树while (mostRight.right != null && mostRight.right != cur) {mostRight = mostRight.right;}if (mostRight.right == null) { // 如果mostRight.right是指向null的,即第一次到达mostRight.right = cur;System.out.print(cur.value + " ");cur = cur.left;continue;} else { // 如果mostRight.right是指向cur的,第二次到达mostRight.right = null;}} else {System.out.print(cur.value + " ");}cur = cur.right;}System.out.println();}/*** 后序遍历*/public static void morrisPos(Node head) {if (head == null) {return;}Node cur = head;Node mostRight = null;while (cur != null) {mostRight = cur.left;if (mostRight != null) {while (mostRight.right != null && mostRight.right != cur) {mostRight = mostRight.right;}if (mostRight.right == null) {mostRight.right = cur;cur = cur.left;continue;} else {mostRight.right = null;printEdge(cur.left);}}cur = cur.right;}printEdge(head);System.out.println();}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遍历 / 线索二叉树)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!