LeetCode-二叉树遍历-94中序+144前序+145后序-(递归/迭代/Morris)

中序遍历:

递归:

class Solution {
public:
    void midOrder(TreeNode* root,vector&ans){
        if(!root) return;
        midOrder(root->left,ans);
        ans.push_back(root->val);
        midOrder(root->right,ans);
    }
    vector inorderTraversal(TreeNode* root) {
        vectorans;
        midOrder(root,ans);
        return ans;
    }
};

迭代:

中序和前序的写法很相似,只是变换了一下推入数组的语句的位置。

class Solution {
public:
    vector inorderTraversal(TreeNode* root) {
        vectorans;
        stackS;
        // 栈不为空或者指针不为空时说明没遍历完
        while(!S.empty() || root!=nullptr){
            while(root != nullptr){
                // 把左子树推入栈内
                S.push(root);
                root = root->left;
            }
            // 左子树遍历完,回退到栈顶,弹出栈顶
            root = S.top();
            ans.push_back(root->val);
            S.pop();
            // 遍历右子树
            root = root->right;
        }
        return ans;
    }
};

要注意的是,C++中的stack的pop()方法返回值是void,所以如果想提取栈顶元素,只能用top方法。

Morris: 

这种方法精妙在它不利用递归,也不用栈来维护。它的空间复杂度就为O(1)。核心思想是利用树里大量的空指针。整个过程就相当于:设当前遍历到的节点为x,找到其左子树的最右边的节点(也就是相当于x中序遍历的前驱节点),并令其的右孩子指向x,则我们无需用栈去维护整个路径了,在左子树遍历完毕以后就可以通过这个自设的指针走回到x,接下来就继续访问右子树即可。一句话概括就是:将所有右孩子为空的节点的右儿子指向其后继节点。

对于当前根节点x,中序遍历整体算法就是:

1.x若无左孩子,直接输出x的值到数组,再访问其右孩子,x=x->right

2.x若有左孩子,找到x在中序遍历的前驱节点(也就是x左子树最右的节点),设为pre。

        1)若pre的右孩子为空,将右孩子指向x,x=x->left

        2)若pre的右孩子不为空,说明已进行过操作1),则此时pre的右孩子为x,说明x的左子树遍历完毕,x=x->right,输出x的值,并令pre的右孩子置空【相当于访问完节点后会还原树

class Solution {
public:
    vector inorderTraversal(TreeNode* root) {
        vectorans;
        TreeNode *pre = nullptr;
        // 循环结束条件是指针为空
        while(root != nullptr){
            // 当根节点左孩子为空时,直接输出根节点,并遍历右子树
            if(root->left == nullptr){
                ans.emplace_back(root->val);
                root = root->right;
            }else{
                // 根节点左孩子不为空时
                pre = root->left;
                // 找根节点的前驱节点,就是左子树的最右边节点
                while(pre->right != nullptr && pre->right != root){
                    pre = pre->right;
                }
                // pre为空时,使其右孩子指向根节点,同时根节点继续向左遍历
                if(pre->right == nullptr){
                    pre->right = root;
                    root = root->left;
                }else {
                    //pre不为空,说明左子树已经遍历过了,输出当前节点的值,并遍历右子树
                    ans.emplace_back(root->val);
                    root = root->right;
                    pre->right = nullptr;
                }
            }
        }
        return ans;
    }
};

 前序遍历:

递归:

class Solution {
public:
    void frontOrder(TreeNode* root,vector&ans){
        if(!root) return;
        ans.push_back(root->val);
        frontOrder(root->left,ans);
        frontOrder(root->right,ans);
    }
    vector preorderTraversal(TreeNode* root) {
        vectorans;
        frontOrder(root,ans);
        return ans;
    }
};

迭代:

递归隐式地维护了一个栈,而迭代只不过是显式地将这个栈模拟出来。

class Solution {
public:
    vector preorderTraversal(TreeNode* root) {
        vectorans;
        stackS;
        // 栈不为空或者指针不为空时说明没遍历完
        while(!S.empty() || root != nullptr){
            while(root != nullptr){
                // 把左子树推入栈内,区别在于一读到根节点就输出结果
                ans.push_back(root->val);
                S.push(root);
                root = root->left;
            }
            // 空指针退栈
            root = S.top();
            S.pop();
            // 遍历右子树
            root = root->right;
        }
        return ans;
    }
};

Morris:

程序跟中序遍历的Morris写法是基本一致的,有差异的地方仅仅在于emplace_back的一条语句位置发生了变化。变化在于:我们知道当root有左孩子时,我们才开始找root的中序遍历的前驱节点prev,并有以下判断情况:1.当prev的右孩子为空时,说明左子树未开始遍历 2.prev的右孩子不为空时,说明左子树已经遍历完了,此时的root是回溯之后的根节点【因为遍历到左子树倒数第二个最左边的节点y时,prev是其左儿子z,并使其指回了y,遍历到左子树最后一个节点z时,此时z没有左孩子了,他会直接开始访问右孩子,也就是之前prev指回的y】。

所以想要完成前序遍历,只需要在第一次访问该节点的时候就输出即可,也就是上述的判断情况1.而中序遍历则需要在遍历完左子树并回溯到根节点的时候才能输出,也就是上述判断情况2.

class Solution {
public:
    vector preorderTraversal(TreeNode* root) {
        vectorans;
        TreeNode *prev = nullptr;
        while(root!=nullptr){
            if(root->left != nullptr){
                prev = root->left;
                while(prev->right!=nullptr && prev->right!=root){
                    prev = prev->right;
                }
                if(prev->right == nullptr){
                    //说明此时还未遍历左子树,直接输出根节点,并开始遍历左子树
                    ans.emplace_back(root->val);
                    prev->right = root;
                    root = root->left;
                }else{
                    //此时左子树已经遍历过了,回到了根节点,直接置空prev,不用再输出一遍根节点了
                    // 直接访问右子树
                    prev->right = nullptr;
                    root = root->right;
                }
            }else{
                // 没有左子树的时候直接输出根节点并访问右子树
                ans.emplace_back(root->val);
                root = root->right;
            } 
        }
        return ans;
    }
};

后序遍历:

递归:

class Solution {
public:
    void lastOrder(TreeNode* root,vector&ans){
        if(!root) return;
        lastOrder(root->left,ans);
        lastOrder(root->right,ans);
        ans.push_back(root->val);
    }
    vector postorderTraversal(TreeNode* root) {
        vectorans;
        lastOrder(root,ans);
        return ans;
    }
};

迭代:

方法一

是在非递归前序遍历的基础上通过一点修改,变成根右左。然后再利用STL中的reverse翻转数组得到左右根,从而实现迭代后序遍历。

class Solution {
public:
    vector postorderTraversal(TreeNode* root) {
        vectorans;
        stackS;
        // 栈不为空或者指针不为空时说明没遍历完
        while(!S.empty() || root != nullptr){
            while(root != nullptr){
                // 把右子树推入栈内,一读到根节点就输出结果 变成根右左
                ans.push_back(root->val);
                S.push(root);
                root = root->right;
            }
            // 空指针退栈
            root = S.top();
            S.pop();
            // 遍历左子树
            root = root->left;
        }
        reverse(ans.begin(),ans.end());
        return ans;
    }
};

方法二:

传统的栈遍历方法。跟中序非递归遍历的区别在于,中序遍历时是确定左子树遍历完了,然后就可以访问根结点,接着遍历右子树。但是后序遍历在遍历完左子树时,要确定右子树是否遍历过了或者是否为空,当右子树为空或者遍历完了才能访问根节点。因此我们需要定义一个TreeNode型指针来记录历史访问记录,判断回溯到父节点时,上一个访问的节点是否为右子树。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector postorderTraversal(TreeNode* root) {
        vectorans;
        stackS;
        TreeNode *prev = nullptr;
        // 栈不为空或者指针不为空时说明没遍历完
        while(!S.empty() || root != nullptr){
            while(root != nullptr){
                S.push(root);
                root = root->left;
            }
            // 左子树遍历完,回溯到根节点
            root = S.top();
            // 如果右子树为空,或者已经遍历过右子树,则直接访问根节点
            if(root->right == nullptr || prev == root->right){
                S.pop();
                ans.push_back(root->val);
                // 更新历史记录
                prev = root;
                //根节点已被访问,记得置空根指针
                root = nullptr;
            }else{
                // 否则继续遍历右子树
                root = root->right;
            }
        }
        return ans;
    }
};

Mirros:

class Solution {
public:
    void addPath(vector &vec, TreeNode *node) {
        int count = 0;
        while (node != nullptr) {
            ++count;
            vec.emplace_back(node->val);
            node = node->right;
        }
        reverse(vec.end() - count, vec.end());
    }

    vector postorderTraversal(TreeNode *root) {
        vector res;
        if (root == nullptr) {
            return res;
        }

        TreeNode *p1 = root, *p2 = nullptr;

        while (p1 != nullptr) {
            p2 = p1->left;
            if (p2 != nullptr) {
                while (p2->right != nullptr && p2->right != p1) {
                    p2 = p2->right;
                }
                if (p2->right == nullptr) {
                    p2->right = p1;
                    p1 = p1->left;
                    continue;
                } else {
                    p2->right = nullptr;
                    addPath(res, p1->left);
                }
            }
            p1 = p1->right;
        }
        addPath(res, root);
        return res;
    }
};

 

你可能感兴趣的:(LeetCode,算法,c++)