LeetCode 144.二叉树的前序遍历


简单递归,迭代算法,莫里斯算法

简单递归

/*
 * @lc app=leetcode.cn id=144 lang=cpp
 *
 * [144] 二叉树的前序遍历
 */

#include <iostream>
#include <vector>
//  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) {}
};

using namespace std;
// @lc code=start

class Solution
{
public:
    vector<int> preorderTraversal(TreeNode *root)
    {
        if (root == nullptr)
        {
            vector<int> empty;
            return empty;
        }
        vector<int> result{root->val};
        vector<int> leftResult = preorderTraversal(root->left);
        if (!leftResult.empty())
        {
            result.insert(result.end(), leftResult.begin(), leftResult.end());
        }
        vector<int> rightResul = preorderTraversal(root->right);
        if (!rightResul.empty())
        {
            result.insert(result.end(), rightResul.begin(), rightResul.end());
        }
        return result;
    }
};
// @lc code=end

注意这里合并两个vector的方法:

result.insert(result.end(), //place you want to insert at
              leftResult.begin(), 
              //An overload version with two iterator of vector
              leftResult.end());

时间复杂度:O ( n ) ,其中 n 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次。 空间复杂度:O ( n ) 。空间复杂度取决于递归的栈深度,而栈深度在二叉树为一条链的情况下会达到 O ( n ) 的级别

迭代

迭代使用的是栈,其依据是:反正对于每个子树,都是先根,再左,再右,那就把字数压进栈里,对于栈顶的元素,输出根,再把栈顶的元素左右子树分别压入栈。因为先输出左边,后进先出(FILO)的栈需要先压入右子树,再压入左子树。

vector<int> preorderTraversal(TreeNode *root)
    {
        vector<int> result;
        if (root == nullptr)
        {
            return result;
        }
        stack<TreeNode*> theStack;
        theStack.push(root);
        while (!theStack.empty())
        {
            TreeNode* pointer = theStack.top();
            result.push_back(pointer->val);
            theStack.pop();
            if (pointer->right != nullptr)
            {
                theStack.push(pointer->right);
            }
            if (pointer->left != nullptr)
            {
                theStack.push(pointer->left);
            }
        }
       return result; 
    }

得益于大量使用STL提供的高质量算法,这个答案击败了LeetCode100%的C++提交,但还是内存占用太大了,因为涉及到栈的维护,有没有什么办法,可以更加节省内存呢?

“还能不能再给力一点?”

莫里斯算法

def preorderTraversal(self, root: TreeNode) -> List[int]:
        self.result = []
        current = root  # 当前节点设置为根节点
        while current:
            if not current.left:  # 前序遍历是中左右的顺序,当没有左子树时,直接输出值,并转到右子树上
                self.result.append(current.val)
                current = current.right
            else:  # 当存在左子树时,找到根节点的中序前驱节点,也就是左子树的最右的叶子
                pre = current.left
                while pre.right and pre.right!=current:  # 判断左子树的最右叶子的右子树(本来是空内存)是否为空
                    pre = pre.right
                if not pre.right:  
                	# 当右子树是空,说明该根节点是第一次被访问
                	# 按照前序 中左右,根节点第一次被访问时就应该输出
                	#
                	# 前序和中序遍历不一样的地方在于,中序遍历是左中右,是从中到左到中的时候才将根节点输出
                	# 所以中序遍历是在第二次访问节点是输出
                	#
                	# 第一次访问根节点时,将中序前驱节点的右子树空内存指向根节点
                	# 然后按照中左右的顺序,访问了根节点就需要左子树,将当前节点转向左子树,
                    self.result.append(current.val)
                    pre.right = current
                    current = current.left
                else:
                	# 此时中序前驱节点的右子树的本来是空的内存已经指向了根节点
                	# 说明当前的根节点,已经是从左子树访问完了又回到了根节点,第二次访问根节点了
                	# 此时就需要将中序前序节点的右子树的内存值为空,回到最初树的形状
                	# 然后当前节点转向右子树
                    pre.right = None
                    current = current.right
        return self.result