二叉树遍历的最优解法

1. 题目

给定一棵二叉树的头节点head,完成二叉树的先序、中序和后序遍历。要求时间复杂度为O(N),额外空间复杂度为O(1)


2. 思路

常规的遍历二叉树的思路是递归和非递归的解法,但是这两种解法都不能做到额外空间复杂度为O(1)。

首先来看普通的递归和非递归解法,导致它们空间复杂度高的原因是使用了栈结构,而使用栈结构的原因在于这样才可以在处理完二叉树某个节点后可以回到上层去。

那么如何方便的从下层回到上层呢,这里就可以用到二叉树中大量闲置的指向null的指针。

Morris遍历的实质就是避免用栈结构,而是让下层到上层有指针,具体是通过让底层节点指向null的空闲指针指向上层的某个节点,从而完成下层到上层的移动。

3. 具体步骤

中序遍历:

1.假设当前子树的头节点为h,让h的左子树中最右节点的right指针指向h,然后h的左子树继续步骤1的处理过程,直到遇到某一个节点没有左子树时记为node,进入步骤2

2.从node开始通过每个节点的right指针进行移动,并以此打印,假设移动到的节点为cur。对每一个cur节点都判断cur节点的左子树中最右节点是否指向cur

如果是,让cur节点的左子树种最右节点的right指针指向空,即调整步骤1,然后打印cur,继续通过cur的right指针移动到下一个节点,重复步骤2

如果不是,重新执行步骤1

先序遍历:

先序遍历的实现就是Morris中序遍历实现的简单改写。中序遍历的打印时机放在了步骤2所描述的移动过程中, 而先序遍历只要把打印时机放在步骤1发生的时候即可。步骤1发生的时候,正在处理以h为头的子树,并且是以h为头的子树首次进入调整过程,此时直接打印h,就可以做到先根打印。

后序遍历:

后序遍历要复杂一些。

后续遍历的打印时机是当该节点符合步骤2的条件1时,逆序打印指向该节点的左子树及其右孩子的序列。

需要额外建立逆序函数以及恢复函数。


4. 代码实现

中序遍历:

private static void morrisMidSearch(TreeNode head) {
        if (head == null) {
            return;
        }
        TreeNode cur1 = head;
        TreeNode cur2 = null;
        while (cur1 != null) {
            cur2 = cur1.left;
            if (cur2 != null) {
                while (cur2.right != null && cur2.right != cur1) {
                    cur2 = cur2.right;
                }
                if (cur2.right == null) {
                    cur2.right = cur1;
                    cur1 = cur1.left;
                    continue;
                } else {
                    cur2.right = null;
                }
            }
            System.out.println(cur1.value + " ");
            cur1 = cur1.right;
        }
        System.out.println();
    }

先序遍历:

private static void morrisFirstSearch(TreeNode head) {
        if (head == null) {
            return;
        }
        TreeNode cur1 = head;
        TreeNode cur2 = null;
        while (cur1 != null) {
            cur2 = cur1.left;
            if (cur2 != null) {
                // 获取cur1左子树最右边的节点
                while (cur2.right != null && cur2.right != cur1) {
                    cur2 = cur2.right;
                }
                // 让最右边的节点连接到cur1
                if (cur2.right == null) {
                    cur2.right = cur1;
                    System.out.println(cur1.value + " ");
                    cur1 = cur1.left;
                    continue;
                } else {
                    cur2.right = null;
                }
            } else {
                System.out.println(cur1.value + " ");
            }
            cur1 = cur1.right;
        }
        System.out.println();
    }

后序遍历:

private static void morrisLastSearch(TreeNode head) {
        if (head == null) {
            return;
        }
        TreeNode cur1 = head;
        TreeNode cur2 = null;
        while (cur1 != null) {
            cur2 = cur1.left;
            if (cur2 != null) {
                while (cur2.right != null && cur2.right != cur1) {
                    cur2 = cur2.right;
                }
                if (cur2.right == null) {
                    cur2.right = cur1;
                    cur1 = cur1.left;
                    continue;
                } else {
                    cur2.right = null;
                    printEdge(cur1.left);
                }
            }
            cur1 = cur1.right;
        }
        printEdge(head);
        System.out.println();
    }

上一篇:链表经典笔试题


下一篇:程序员面试金典 面试题 02.07. 链表相交