Morris 遍历二叉树

Morris Traversal 方法实现前序、中序以及后序遍历二叉树。相比使用栈或者递归(也是通过栈空间)方法,Morris 方法可以在空间复杂度为 O(1),时间复杂度为 O(n) 的条件下实现对二叉树的遍历。

前序遍历

  1. 如果当前节点左孩子 cur->left 为空,输出当前节点 cur 并指向右孩子 cur->right。
  2. 如果当前节点左孩子 cur->left 不为空,那么在当前节点的左子树中找出前驱节点 pre,也就是左子树中最大的点。
    • 如果前驱节点的右孩子 pre->right 为空,那么将右孩子指向当前节点。输出当前节点。当前节点更新为当前节点的左孩子。
    • 如果前驱节点的右孩子 pre->right 不为空,也就是指向当前节点。重新将右孩子设为空。当前节点更新为当前节点的右孩子。
  3. 重复 1、2 直到当前节点为空。
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        TreeNode* cur = root;
        TreeNode* pre = NULL;
        vector<int> result;
        while(cur!=NULL){
            if (cur->left == NULL){
                result.push_back(cur->val);
                cur = cur->right;
            }else{
                pre = cur->left;
                while(pre->right!=NULL && pre->right!=cur){
                    pre = pre->right;
                }
                if (pre->right==NULL){
                    pre->right=cur;
                    result.push_back(cur->val);
                    cur = cur->left;
                }else{
                    pre->right = NULL;
                    cur = cur->right;
                }
            }
        }
        return result;
    }
};

中序遍历

  1. 如果当前节点左孩子 cur->left 为空,输出当前节点 cur 并指向右孩子 cur->right。
  2. 如果当前节点左孩子 cur->left 不为空,那么在当前节点的左子树中找出前驱节点 pre,也就是左子树中最大的点。
    • 如果前驱节点的右孩子 pre->right 为空,那么将右孩子指向当前节点。当前节点更新为当前节点的左孩子。
    • 如果前驱节点的右孩子 pre->right 不为空,也就是指向当前节点。重新将右孩子设为空。输出当前节点。当前节点更新为当前节点的右孩子。
  3. 重复 1、2 直到当前节点为空。
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        TreeNode* cur = root;
        TreeNode* pre = NULL;
        vector<int> result;
        while(cur!=NULL){
            if (cur->left == NULL){
                result.push_back(cur->val);
                cur = cur->right;
            }else{
                pre = cur->left;
                while(pre->right!=NULL && pre->right!=cur){
                    pre = pre->right;
                }
                if (pre->right == NULL){
                    pre->right=cur;
                    cur = cur->left;
                }else{
                    pre->right = NULL;
                    result.push_back(cur->val);
                    cur = cur->right;
                }
            }
        }
        return result;
    }
};

后序遍历

  1. 新增临时节点 dump,并且将 root 设为 dump 的左孩子。
  2. 如果当前节点左孩子 cur->left 为空,输出当前节点 cur 并指向右孩子 cur->right。
  3. 如果当前节点左孩子 cur->left 不为空,那么在当前节点的左子树中找出前驱节点 pre,也就是左子树中最大的点。
    • 如果前驱节点的右孩子 pre->right 为空,那么将右孩子指向当前节点。当前节点更新为当前节点的左孩子。
    • 如果前驱节点的右孩子 pre->right 不为空,也就是指向当前节点。逆序输出当前节点左孩子到前序节点的路径。重新将右孩子设为空。当前节点更新为当前节点的右孩子。
  4. 重复 2、3 直到当前节点为空。

逆序打印路径其实就是逆序打印单链表节点。先将单链表反转,然后依次打印,接下来重新反转到初始状态。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode dump(-1);
        dump.left = root;
        TreeNode* cur = &dump;
        TreeNode* pre = NULL;
        vector<int> result;
        while(cur!=NULL){
            if (cur->left == NULL){
                cur = cur->right;
            }else{
                pre = cur->left;
                while(pre->right!=NULL && pre->right!=cur){
                    pre = pre->right;
                }
                if (pre->right == NULL){
                    pre->right=cur;
                    cur = cur->left;
                }else{
                    printReverse(cur->left, pre, result);
                    pre->right = NULL;
                    cur = cur->right;
                }
            }
        }
        return result;
    }

    void printReverse(TreeNode* from, TreeNode* to, vector<int>& result){
        Reverse(from, to);
        TreeNode* p = to;
        while(true){
            result.push_back(p->val);
            if(p == from){
                break;
            }
            p = p->right;
        }
        Reverse(to, from);
    }

    void Reverse(TreeNode* from, TreeNode* to){
        TreeNode* x = from;
        TreeNode* y = from->right;
        TreeNode* z;
        if (from == to){
            return;
        }
        x->right = NULL;
        while(x != to){
            z = y->right;
            y->right = x;
            x = y;
            y = z;
        }
    }
};

总结

Morris 方法遍历之所以能够在 O(1) 的空间的条件下完成是因为它充分利用到叶子的左右孩子来记录上层关系,从而不需要额外的栈空间来记录顺序关系。通过三种遍历可以看到,其实总体上的代码逻辑没有发生改变,主要是改变了输出结果的时机和方式。

https://www.ouyangsong.com/posts/27490/

原文地址:https://www.cnblogs.com/ouyangsong/p/9348179.html

时间: 2024-10-16 07:59:55

Morris 遍历二叉树的相关文章

【转载】Morris遍历二叉树 &amp; BST(二叉搜索树) Traverse &amp; 空间O(1) 时间O(n)

因为做一道Leetcode的题目(前面博客有:link),需要用Space O(1)空间复杂度来中序遍历树, 看了Discuss,也上网搜了一下,发现空间O(1)可以用 Morris遍历的方法.方法介绍如下: http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html其中,中序遍历和前序遍历比较方便解决: 通常,实现二叉树的前序(preorder).中序(inorder).后序(postorder)遍历有两个常用

Morris遍历遍历二叉树

遍历二叉树的递归方法使用了函数栈,非递归方法使用了申请的栈, 两者的额外空间都与树的高度有关,所以空间复杂度为O(h),h为二叉树的高度. 可以使用二叉树叶子节点中大量指向null的指针实现空间复杂度O(1)的遍历. Morris遍历的实质就是避免使用栈结构,让下层到上层有指针, 具体是通过让底层节点指向null的空闲指针指回上层的某个节点,从而完成下层到上层的移动. 先序中序后序主要基于两个主要步骤,然后输出的位置有所不同,以中序遍历为例. 中序遍历: 1.假设当前子树的头节点为h,让h的左子

遍历二叉树的三种方法

朋友面试遇到一道笔试题:写出递归遍历二叉树的代码(先序.中序.后序遍历都可以)? 首先要知道二叉树是什么,它的数据结构是怎样的? 如何实现这种二叉树?采用匿名内部类的形式实现 class Node{ //节点数据 private T data; //可比较的泛型 //左子树 private Node leftChildTree; //右子树 private Node rightChildTree; public Node(T data){ this.data = data; } } 知道它的数据

算法进阶面试题03——构造数组的MaxTree、最大子矩阵的大小、2017京东环形烽火台问题、介绍Morris遍历并实现前序/中序/后序

接着第二课的内容和带点第三课的内容. (回顾)准备一个栈,从大到小排列,具体参考上一课.... 构造数组的MaxTree [题目] 定义二叉树如下: public class Node{ public int value; public Node left; public Node right; public Node(int data){ this.value=data; } } 一个数组的MaxTree定义如下: ◆ 数组必须没有重复元素 ◆ MaxTree是一颗二叉树,数组的每一个值对应一

JAVA递归、非递归遍历二叉树(转)

原文链接: JAVA递归.非递归遍历二叉树 import java.util.Stack; import java.util.HashMap; public class BinTree { private char date; private BinTree lchild; private BinTree rchild; public BinTree(char c) { date = c; } // 先序遍历递归 public static void preOrder(BinTree t) {

非递归实现遍历二叉树

非递归实现二叉树主要利用queue和stack的特点,对于层次遍历二叉树主要运用queue队头出,队尾插入,先进先出的特点,先将根插入队尾,然后输出队头的元素,同时将队头的左子树和右子树元素插入队尾,依次输出输出队头的元素,同时将队头的左子树和右子树元素插入队尾,直到队列为空. void levelorder() { queue<BinaryTreeNode<T> *>s; if (_root == NULL) return; s.push(_root); while (!s.em

递归遍历二叉树

递归遍历分三种: 1.前序遍历二叉树(二叉树非空) 1.访问根节点 2.前序遍历左子树 3.前序遍历右子树 2.中序遍历二叉树(二叉树非空) 1.中序遍历左子树 2.访问根节点 3.中序遍历右子树 3.后序遍历二叉树(二叉树非空) 1.后序遍历左子树 2.后序遍历右子树 3.访问根节点 三种递归的算法遍历,终止条件是二叉树为空的时候. 记忆的方法呢,前中后,都是以根节点命名的,前序,先访问根节点,中序,根节点在第二,后序,根节点最后进行访问.

TAOCP_2.3.1_遍历二叉树

1. 算法T(以中根序遍历二叉树) 设$T$是指向二叉树的指针:本算法以中根序访问二叉树中的所有节点,并且利用一个辅助栈A. T1. [初始化] 置栈A为空,并置链接变量$P \gets T$. T2. [$P=\bigwedge$?] 如果$P=\bigwedge$,转到步骤T4. T3. [$栈 \Leftarrow P$] (现在P指向要加以遍历的一个非空二叉树.)置 $A \Leftarrow P$:即是,把P的值放入栈A.然后置$P \to LLINK(P)$并返回步骤T2. T4.

数据结构(C实现)------- 遍历二叉树

本文是自己学习所做笔记,欢迎转载,但请注明出处:http://blog.csdn.net/jesson20121020 二叉树是另一中树型结构,它的特点是每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分,其次序不能任意颠倒. 根据二叉树的的递归定义可知,二叉树是由3个基本单元组成,根结点.左子树和右子树,因此,若能依次遍历这三部分,便是遍历了整个二叉树.假如以L.D.R分别表示遍历左子树.访问根结点和遍历右子树,则可能有DLR.LDR.LRD.DRL.RD