代码随想录Day.14 | 递归遍历、迭代遍历、统一遍历

1. 递归遍历

1. LeetCode链接

144. 二叉树的前序遍历 - 力扣(LeetCode)

145. 二叉树的后序遍历 - 力扣(LeetCode)

94. 二叉树的中序遍历 - 力扣(LeetCode)

2. 题目描述

看题。用递归,前中后序遍历。

3. 解法

递归就太简单了,想好出口。宏观到围观,和谐之美。

/**
 * 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<int> inorderTraversal(TreeNode* root) {
        TreeNode* far = root;
        vector<int> result;
        if (far == NULL) return result;

        // result.push_back(root->val);     // 前序,先push父节点

        vector<int> result_left = inorderTraversal(root->left);
        result.insert(result.end(), result_left.begin(), result_left.end());

        result.push_back(root->val);        // 中序,push完左边的,再push父节点

        vector<int> result_right = inorderTraversal(root->right);
        result.insert(result.end(), result_right.begin(), result_right.end());

        // result.push_back(root->val);     // 后序,push完左边和右边的,最后push父节点


        return result;
    }
};

2. 迭代遍历

1. LeetCode链接

略。

2. 题目描述

禁止递归。

3. 解法

用栈来表示递归。

1. 前序遍历

比较简单,每次取出来栈顶的,就直接push到result中。先压右孩子,再压左孩子。因为每次要先考虑左孩子。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> stc;
        vector<int> result;
        if (root == NULL) return result;
        stc.push(root);
        while (!stc.empty()) {
            TreeNode* cur = stc.top();
            result.push_back(cur->val);
            stc.pop();
            if (cur->right != NULL) stc.push(cur->right);
            if (cur->left != NULL) stc.push(cur->left);
        }
        return result;
    }
};

2. 后序遍历

熟悉后续遍历就知道,它是“左右父”的顺序遍历。用栈的话,明显不如前序遍历简单。但是,比较巧妙的是,后序遍历反过来就是“父右左”的顺序,这和前序遍历“父左右”,事实上异曲同工。(可以举例证实)

于是,后序遍历的迭代法就是,先伪前序遍历“父右左”,然后反转数组即可。

/**
 * 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<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> std;
        vector<int> result;
        if (root == NULL) return result;
        std.push(root);
        // TreeNode* pre = root;
        while (!std.empty()) {
            TreeNode* cur = std.top();
            std.pop();
            if (cur->left != NULL) std.push(cur->left);
            if (cur->right != NULL) std.push(cur->right);
            result.push_back(cur->val);
        }
        reverse(result.begin(), result.end());
        return result;
    }
};

3. 中序遍历

比较难了。从头捋一遍。

        遍历二叉树涉及到两个操作,对于同一个节点,一个是要访问:这个节点是否为NULL,另一个是记录:把其数值存入result数组中。

        对于前序和后序,都可以用:同一个节点访问和记录两个操作同时进行的代码解决。但是中序就不行了,访问某节点时,并不能同时记录该节点。

        进一步说明,在前序和后续遍历中,从栈中pop出来的节点,既要继续访问其子节点(push),又要记录其本身数值(result.push_back)。

        那么,在中序遍历中,从栈中pop出来的节点,是否可以同时访问其子节点并记录其本身数值呢?

        显然不行,有矛盾,left还没被访问,怎么能记录其本身呢?这不符合中序遍历的逻辑。

        故,从栈中pop出来的节点,并不适合访问leat,适合记录然后访问right。

        代码逻辑是,先持续访问left到底(push到栈中),当NULL了,从栈中pop一个,记录,然后访问right(push到栈中)。

        抽象一点描述就是:

1. 横冲直撞left到底。

2. 碰到底(NULL)了,就退回一步(pop),记录并从right拐一步。

3. 循环1~2.

/**
 * 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<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> stc;
        vector<int> result;
        if (root == NULL) return result;
        stc.push(root);
        while (!stc.empty()) {
            TreeNode* cur = stc.top();
            result.push_back(cur->val);
            stc.pop();
            if (cur->right != NULL) stc.push(cur->right);
            if (cur->left != NULL) stc.push(cur->left);
        }
        return result;
    }
};

      

3. 统一遍历

1. LeetCode链接

略。

2. 题目描述

是否有非递归,但统一代码风格的迭代解法?

3. 想法

前中后序遍历,迭代法,最大的不同就是同一个节点的访问和记录错位。即,利用栈来实现遍历时,从栈中pop出来的节点,需要考虑是否要访问该节点的子节点,以及是否要记录该节点的数值。前序,两个操作可以同时进行;中序和后序不能同时。

解决方法是,标记需要记录的节点。可以在需要记录的节点前,压入NULL到栈中。

        循环中,每个栈顶的非空节点,都进行访问,然后根据前中后,考虑把该节点再以什么顺序压入。前,先压右,再压左,最后压自己;中,先右,再自己,后左;后,先自己,再右,后左。重新压入自己时,意味着访问结束,则再压入NULL节点表示只剩访问。

        遇到空节点,则记录空间点底下的那个。

/**
 * 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<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> std;
        if (root == NULL) return result;
        std.push(root);
        while (!std.empty()) {
            TreeNode* cur = std.top();
            if (cur != NULL) {
                std.pop();

                std.push(cur);   ///后
                std.push(NULL);   ///

                if (cur->right != NULL) std.push(cur->right);


                // std.push(cur);   ///中
                // std.push(NULL);   ///

                if (cur->left != NULL) std.push(cur->left);

                // std.push(cur);   ///前
                // std.push(NULL);   ///


            } else {
                std.pop();
                cur = std.top();
                std.pop();
                result.push_back(cur->val);
            }
        }
        return result;
    }
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值