二叉树的4种遍历-递归+迭代

二叉树的前序遍历

前序遍历:root left-child right-child

题源:Leetcode 144

递归写法

//Preorder
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {//参数为二叉树的根结点
        //如果结点非空
        if(root){
            ret.push_back(root->val);//访问根结点
            preorderTraversal(root->left);//遍历左子树
            preorderTraversal(root->right);//遍历右子树
        }
        return ret;
    }
private:
    vector<int> ret;//返回值  
};

迭代写法

首先,初步了解一下迭代是什么概念:

迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。

理解例子:

迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行:

⑴ 选一个方程的近似根,赋给变量x0;

⑵ 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0;

⑶ 当x0与x1的差的绝对值还大于指定的精度要求时,重复步骤⑵的计算1

对题目的迭代分析:

递归本质上和栈上差不多,本题的迭代方式实际上就是在模拟递归的过程。使用stack来模拟系统栈

stack来存放结点指针,出栈表示访问数据,非空则入栈。先序序列中先左后右访问,所以入栈时先右后左

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;//<stack>头文件
        //先判断根结点是否为空
        if (root == NULL)
            return ret;
        s.push(root);//root非空
        
        while (!s.empty()) {//迭代结束(成功)条件
            TreeNode* get = s.top();
            //出栈模拟访问该结点
            ret.push_back(get->val);
            s.pop();

            //再判断左右子树的压栈
            if (get->right)
                s.push(get->right);
            if (get->left)
                s.push(get->left);
        }
        return ret;
    }
private:
    vector<int> ret;//返回值
};

前两种时间空间复杂度均为O(n),不过使用递归比较容易有堆栈溢出的风险

二叉树的中序遍历

前序遍历:left-child root right-child

题源:Leetcode 94

递归写法

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        if(root){
            inorderTraversal(root->left);
            ret.push_back(root->val);
            inorderTraversal(root->right);
        }
        return ret;
    }
private:
    vector<int> ret;
};

迭代写法

基于栈的遍历算法

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        TreeNode* p = root;     
        while (p || !s.empty()) {
            //利用第二个循环来进行入栈操作
            while (p) {
                s.push(p);
                p = p->left;//一直压入左子树
            }
            p = s.top();
            ret.push_back(p->val);//访问
            s.pop();
            p = p->right;//遍历右子树
        }
        return ret;
    }
private:
    vector<int> ret;//返回
};

二叉树的后序遍历

前序遍历:left-child right-child root

题源:Leetcode 145

递归写法

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        if(root){
            postorderTraversal(root->left);
            postorderTraversal(root->right);
            ret.push_back(root->val);
        }
        return ret;
    }
private:
    vector<int> ret;
};

迭代写法

二叉树的后序遍历迭代算法会比较复杂,一个结点的出栈与否跟其是否有右子树有关。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        TreeNode* p = root;
        TreeNode* last = NULL;//记录前一个访问的结点
        while (p || !s.empty()) {
            while (p) {
                s.push(p);
                p = p->left;
            }
            
            p = s.top();
            //若右节点已经访问过或者没有右节点 则输出该节点值
            if (!p->right || p->right == last) {
                s.pop();
                ret.push_back(p->val);
                last = p;
                p = NULL;
            }
            else {
                p = p->right;
                last = NULL;//可有可无
            }
        }
        return ret;
    }
private:
    vector<int> ret;
};

转换写法

前序遍历的非递归比后序遍历更容易理解一些

前序遍历:根 左 右

后序遍历:左 右 跟

改写前序遍历为:根 右 左 再进行逆序,即为后序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;//<stack>头文件
        //先判断根结点是否为空
        if (root == NULL)
            return ret;
        s.push(root);//root非空
        
        while (!s.empty()) {//迭代结束(成功)条件
            TreeNode* get = s.top();
            //出栈模拟访问该结点
            ret.push_back(get->val);
            s.pop();

            //不同于先序,先压左
            if (get->left)
                s.push(get->right);
            if (get->right)
                s.push(get->left);
        }
        ret.reverse(ret.size());//vector的逆序需要额外开销
        return ret;
    }
private:
    vector<int> ret;//返回值
};

vector的逆序开销比较大,可以选择其他的实现形式比如链表等。

莫里斯Morris遍历

其实是一种动态的二叉树线索化,省去了栈的空间,空间复杂度为O(1),但实际应用并不广,先埋个坑在这里。

层次遍历

层次遍历就是逐层遍历树的结构,相当于广度优先搜索,通常使用队列来做广度优先搜索。

题源:Leetcode 102

本题的主要关键是还要梳理出层次来,相较于以前写过的层次遍历输出

//直接层次遍历输出,无梳理层次,非本题题解
void function(TreeNode* root){
    TreeNode* p=root;
    queue<TreeNode*> Q;
    if(p)
        Q.push(p);
    
    while(!Q.empty()){
        p=Q.front();
        cout<<Q->data<<endl;
        Q.pop();
        if(Q->left)
            Q.push(Q->left);
        if(Q->right)
            Q.push(Q->right);
    }
}

而本题要求按照层次梳理,需要了解每一层的结点个数

迭代写法

广度遍历的思想,时间空间复杂度均为O(n)

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ret;//返回
        vector<int> p;//临时一维vector
        queue<TreeNode*> Q;//队列
        TreeNode* T = root;
        
        if (T == NULL)
            return ret;

        Q.push(T);
        while (!Q.empty()) {
            int size = Q.size();//本层次的结点数目
            p.clear();//重置临时vector
            //固定数目的同层次结点pop
            for (int i = 0; i < size; i++) {
                T = Q.front();
                p.push_back(T->val);
                Q.pop();
                if (T->left)
                    Q.push(T->left);
                if (T->right)
                    Q.push(T->right);
            }
            ret.push_back(p);//一次循环结束,意味着一次层次遍历结束
        }       
        return ret;
    }
};

递归写法

先埋个坑


  1. https://baike.baidu.com/item/%E8%BF%AD%E4%BB%A3/8415523?fr=aladdin 百度百科-迭代

猜你喜欢

转载自www.cnblogs.com/tlam/p/12331313.html