二叉树的遍历-Recursive/Iterative/Morris

本文介绍了二叉树的前序、中序、后序遍历,分别用递归、迭代和Morris方法实现。递归方法空间复杂度为O(n),迭代方法使用栈,空间复杂度为O(n/2),而Morris方法实现空间复杂度为O(1)。详细代码和解释帮助理解各种遍历方式。

二叉树是常见的数据结构,二叉树相关的算法题目也是非常常见的。下面总结以下二叉树的前序/中序/后序遍历方法,分别用递归(O(n) Space),迭代(O(n) Space),Morris(O(1) Space)方法实现。

二叉树遍历的时间复杂度都是O(n),不同方法的区别主要是在空间复杂度上。


Recursive Traversal

递归是一种非常直观的方法,也是最容易实现的方法。递归的方法的空间复杂度为O(n)。
节点的定义如下:

// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
}

Preorder Traversal

void preorderTraversal(TreeNode* root, vector<int>& nums) {
    if(!root) return;
    nums.push_back(root->val);
    preorderTraversal(root->left);
    preorderTraversal(root->right);
}

Inorder Traversal

void inorderTraversal(TreeNode* root, vector<int>& nums) {
    if(!root) return;   
    inorderTraversal(root->left, nums);
    nums.push_back(root->val);
    inorderTraversal(root->right, nums);
}   

Postorder Traversal

void postorderTraversal(TreeNode* root, vector<int>& nums) {
    if(!root) return;   
    postorderTraversal(root->left, nums);
    postorderTraversal(root->right, nums);
    nums.push_back(root->val);
}

Iterative Traversal

迭代方法需要使用stack来保存遍历路径上待遍历的节点,从根节点到叶节点,最多保存n/2个节点。空间复杂度为O(n)。

Preorder Traversal

void preorderTraversal(TreeNode* root, vector<int>& nums) {
    vector<int> nums;
    vector<int> nums;
    stack<TreeNode* > st; // pointers in stack are valid

    while (root || !st.empty()) {
        if (!root) {
            root = st.top();
            st.pop();
        }            
        nums.push_back(root->val);
        // if valid right child, push into stack 
        if (root->right) st.push(root->right);

        // go to the left child
        root = root->left;
    }       
    return nums;        
}

Inorder Traversal

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> nums;
    stack<TreeNode* > st; // pointers in stack are valid

    while (root || !st.empty()) {
        if (root) {
            // push root into stack, then go left
            st.push(root);
            root = root->left;
        } else { 
            // for nodes in stack, only visit its right
            root = st.top();
            st.pop();
            nums.push_back(root->val);
            root = root->right;
        }
    }
    return nums;
} 

Postorder Traversal

后序遍历可以看作是和前序遍历是左右对称的,从根节点开始,先遍历右子树,再遍历左子树,只不过为了得到后序遍历的输出,我们需要遍历结果逆序输出。可以比较前序遍历的代码,逻辑完全是一样的,就是左右子树访问顺序交换了,完全是对称的。

vector<int> postorderTraversal(TreeNode* root) {
    vector<int> nums;
    stack<TreeNode* > stnode;

    while (root || !stnode.empty()) {
        if (!root) {
            root = stnode.top();
            stnode.pop();
        }
        nums.push_back(root->val);
        if (root->left) stnode.push(root->left);
        root = root->right;
    }

    return vector<int>(nums.rbegin(), nums.rend()) 
} 

Morris Traversal

Morris遍历方法不需要栈来保存待访问的节点,而是通过利用节点本身的指针来保存待访问节点的指针,并在访问过程中恢复节点。实现了O(1)空间复杂度。

Preorder Traversal

图片摘自 Reference4
这里写图片描述

vector<int> preorderTraversal(TreeNode* root) {
    // morris traversal
    vector<int> nums;
    TreeNode* cur = nullptr;

    while (root) {
        if (root->left) {
            cur = root->left;
            // find the predecessor of root node
            while (cur->right && cur->right != root) {
                cur = cur->right;
            }

            // has visited this root node
            if (cur->right == root) {
                cur->right = nullptr;
                root = root->right;
            } else {
                nums.push_back(root->val);
                cur->right = root;
                root = root->left;
            }
        } else {
            nums.push_back(root->val);   
            root = root->right;
        }
    }
    return nums;
} 

Inorder Traversal

中序和前序代码基本一样,唯一不同的在输出节点值的顺序不同。

图片摘自 Reference4
这里写图片描述

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> nums;
        TreeNode* cur = nullptr;

        while (root) {
            if (root->left) {
                cur = root->left;
                while (cur->right && cur->right != root) {
                    cur = cur->right;
                }

                if (cur->right == root) {
                    nums.push_back(root->val);
                    cur->right = nullptr;
                    root = root->right;
                } else {
                    cur->right = root;
                    root = root->left;
                }               
            } else {
                nums.push_back(root->val);
                root = root->right;
            }
        }

        return nums;
    } 
} 

Postorder Traversal

之前我们在讲迭代方法时,说过了后序遍历其实可以看作是和前序遍历左右对称的,此处,我们同样可以利用这个性质,基于前序遍历的算法,可以很快得到后序遍历的结果。我们只需要将前序遍历中所有的左孩子和右孩子进行交换就可以了。

图片摘自 Reference4
这里写图片描述


vector<int> postorderTraversal(TreeNode* root) {
    vector<int> nums;
    TreeNode* cur = nullptr;

    while (root) {
        if (root->right) {
            cur = root->right;
            while (cur->left && cur->left != root) {
                cur = cur->left;
            }

            if (cur->left == root) {
                cur->left = nullptr;
                root = root->left;
            } else {
                nums.push_back(root->val);
                cur->left = root;
                root = root->right;
            }

        } else {
            nums.push_back(root->val);
            root = root->left;
        }
    }
    return vector<int>(nums.rbegin(), nums.rend());
} 

Reference

1. https://leetcode.com/problems/binary-tree-preorder-traversal/description/
2. https://leetcode.com/problems/binary-tree-inorder-traversal/description/
3. https://leetcode.com/problems/binary-tree-postorder-traversal/description/
4. http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值