美文网首页
二叉树三种遍历的递归和非递归实现&层次遍历实现(C++)

二叉树三种遍历的递归和非递归实现&层次遍历实现(C++)

作者: 快乐的二叉树 | 来源:发表于2020-03-06 19:01 被阅读0次

    对于二叉树的三种遍历方式(先序、中序、后序),用递归和非递归(栈)的方式实现,对于后序遍历用队列实现。

    四种遍历方式的概念

    二叉树

    先序遍历(中->左->右):1->2->4->5->3->6->7
    中序遍历(左->中->右):4->2->5->1->6->3->7
    后序遍历(左->右->中):4->5->2->6->7->3->1
    层序遍历(从上到下,从左到右):1->2->3->4->5->6->7

    树结构体

    struct TreeNode{
        int val;
        TreeNode *left;
        TreeNode *right;
        TreeNode(int x): val(x), left(NULL), right(NULL)  {}
    };
    

    先序遍历

    递归

    递归调用先序函数,将输出值的语句放在遍历子节点之前。

    void PreOrder(TreeNode *root){
        if (root){
            cout<<root->val<<endl;  //对节点值的操作
            PreOrder(root->left);
            PreOrder(root->right);
        }
        return;
    }
    
    非递归1

    用栈来模拟递归函数的调用,先将根节点压栈,当栈不空的时候,访问并弹出栈顶元素,再将其右节点、左节点按顺序压栈,这样栈的输出就变成左节点、右节点,重复访问并弹出栈顶元素,直到栈空。栈中存储的都是将要遍历的节点,已遍历的节点都被弹出。

    void PreOrder1(TreeNode *root){
        if (!root) return;
        stack<TreeNode*> s;
        s.push(root);
        TreeNode *temp;
        while(!s.empty()){
            temp=s.top();
            cout<<temp->val<<endl;  //对节点值的操作
            s.pop();
            if (temp->right) {    //将右节点压栈
                s.push(temp->right);
            }
            if (temp->left) {     //将左节点压栈
                s.push(temp->left);
            }
        }
        return;
    }
    
    非递归2

    主要不同点在于,在将每个节点压栈的同时输出节点的值,这样向左搜索到最左节点,最左节点出栈,栈顶元素为其父节点,再将右节点压栈。这样能确保每个根节点都被访问过,只需要再访问它的左右节点。

    void PreOrder2(TreeNode *root){
        if (!root) return;
        stack<TreeNode*> s;
        TreeNode *temp=root;
        while(temp || !s.empty()){
            if (temp){        //节点存在则压栈,同时操作其值,再遍历其左节点(往左搜索)
                s.push(temp);
                cout<<temp<<endl;
                temp=temp->left;
            }else{      //节点不存在(找到最左),则弹出栈顶元素,遍历它的右节点
                temp=s.top();
                s.pop();
                temp=temp->right;
            }
        }
    }
    

    中序遍历

    递归

    递归调用中序函数,将输出值的语句放在遍历左右节点之间。

    void InOrder(TreeNode *root){
        if (root){
            InOrder(root->left);
            cout<<root->val<<endl;  //对节点值的操作
            InOrder(root->right);
        }
        return;
    }
    
    非递归

    同样用栈来做,先将左节点都找到并入栈,找到最左节点后,操作它的值,弹出栈顶元素,再遍历它根节点的右节点。这样能确保每个节点的左节点都被访问过,只需要再访问它的根节点和右兄弟节点就好了。和先序遍历相比,只是把输出节点值的语句放到了出栈的时候。

    void InOrder(TreeNode *root){
        if (!root) return;
        stack<TreeNode*> s;
        TreeNode *temp=root;
        while(temp || !s.empty()){
            if (temp){        //节点存在则压栈,再遍历其左节点(往左搜索)
                s.push(temp);
                temp=temp->left;
            }else{        //节点不存在则(找到最左),操作根节点的值,再遍历其右节点(
                temp=s.top();
                cout<<temp->val<<endl;
                s.pop();
                temp=temp->right;
            }
        }
        return;
    }
    

    后序遍历

    递归

    递归调用后序函数,将输出值的语句放在遍历左右节点之后。

    void PostOrder(TreeNode *root){
        if (root){
            PostOrder(root->left);
            PostOrder(root->right);
            cout<<root->val<<endl;  //对节点值的操作
        }
        return;
    }
    
    非递归1

    从根节点开始,先入栈,再将其右节点、左节点入栈,注意入栈后就将其对应节点的指针设为空,相当于标记为左右节点都遍历过,只需要最后遍历这个根节点。这样出栈的顺序就变成左右中,而且把每个节点的父子关系都拆散了!

    void PostOrder1 (TreeNode *root){
        if (!root) return;
        stack<TreeNode*> s;
        s.push(root);
        TreeNode *temp;
        while(!s.empty()){
            temp=s.top();
            if (!temp->left && !temp->right){   //栈顶元素是“叶子节点”,说明左右都访问过,或者说明它是真的叶子节点
                cout<<temp->val;
                s.pop();
            }else{           //如果节点的左右有节点,就把它们入栈,再拆散关系
                if (temp->right){
                    s.push(temp->right);
                    temp->right=nullptr;
                }
                if (temp->left){
                    s.push(temp->left);
                    temp->left=nullptr;
                }
            }
        }
        return;
    }
    
    非递归2

    这种方法比较取巧,后序是左右中,先得到中右左的遍历结果,再将这个结果翻转,就得到了正确的后序。代码在先序非递归遍历上做了小的改动,把左右节点的顺序调换一下。

    void PostOrder2(TreeNode *root){
        if (!root) return;
        vector<int> res;
        stack<TreeNode*> s;
        TreeNode *temp=root;
        while(temp || !s.empty()){
            if (temp){
                s.push(temp);
                res.push_back(temp->val);
                temp=temp->right;
            }else{
                temp=s.top();
                s.pop();
                temp=temp->left;
            }
        }
        reverse(res.begin(), res.end());    //将中右左变成左右中
    }
    

    层序遍历

    不需要分层,连续输出

    层序遍历需要借助队列来实现,当不需要区别遍历到的节点属于那个层时,用一个队列操作就够了。先把根节点入队,当队中有元素时,反复进行一个操作:将队前元素输出,将其左右节点分别入队(如果左右节点不为空的话),直到清空队列。

    void LevelOrder1(TreeNode *root){
        if (!root) return;
        queue<TreeNode*> q;
        q.push(root);
        TreeNode *temp;
        while(!q.empty()){
            temp=q.front();
            cout<<temp->val<<endl;   //操作这个数
            q.pop();
            if (temp->left){
                q.push(temp->left);
            }
            if (temp->right){
                q.push(temp->right);
            }
        }
        return;
    }
    
    需要按层的顺序输出

    按层的顺序输出时,可以用两个队列来实现,分别存储当前层和下一层的所有节点。先将根节点存入第一个队列,再将第一个队列中元素全部取出,取出的同时把它们的左右节点放入第二个队列;再去处理第二个队列,同样将所有元素取出,它们的左右节点放入第一个队列,这样来回倒,直到最后一层遍历结束,两个队全部为空时退出循环。

    void LevelOrder2(TreeNode* root){
        if (!root) return;
        queue<TreeNode*> q1, q2;
        vector<vector<int>> res;   //用二维数组保存遍历结果
        TreeNode *temp;
        vector<int> levelout;   //层遍历数组
        q1.push(root);
        while(!q1.empty() || !q2.empty()){
            while(!q1.empty()){     //把当前层所有节点取出来,把它们的左右节点放到另一个队列里
                temp=q1.front();
                levelout.push_back(temp->val);
                q1.pop();
                if (temp->left) q2.push(temp->left);
                if (temp->right) q2.push(temp->right);
            }
            res.push_back(levelout);
            levelout.clear();     //当前层遍历结束,清空层遍历数组
            while(!q2.empty()){     //一样的操作,
                temp=q2.front();
                res[level].push_back(temp->val);
                q2.pop();
                if (temp->left) q1.push(temp->left);
                if (temp->right) q1.push(temp->right);
            }
            res.push_back(levelout);
            levelout.clear();     //当前层遍历结束,清空层遍历数组
        }
        return;
    }
    

    上述遍历也可以用一个队列实现,在将根节点入队之后,再将一个空指针nullptr也入队,标志着一层遍历的结束。当出队元素为空指针时,说明当前层已经访问结束,也就说明当前层的左右节点都入队完毕,也就是下一层的节点全部在队里,这时再将一个空指针入队,标记下一层结束的位置。

    void LevelOrder3(TreeNode* root){
        if (!root) return;
        queue<TreeNode*> q;
        vector<vector<int>> res;   //用二维数组保存遍历结果
        TreeNode *temp;
        vector<int> levelout;   //层遍历数组
        q1.push(root);
        q1.push(nullptr);   //第一层结束
        while(!q1.empty()){
            temp=q.front();
            q.pop();
            if (temp){
                levelout.push_back(temp->val);
                if (temp->left) q2.push(temp->left);
                if (temp->right) q2.push(temp->right);
            }else{
                if (!q.empty()){
                    q.push(nullptr);      //下一层入队结束
                }
                res.push_back(levelout);
                levelout.clear();     //当前层遍历结束,清空层遍历数组
            }
        }
        return;
    }
    

    参考

    1、https://www.jianshu.com/p/9f148caf2535
    2、https://blog.csdn.net/weixin_40457801/article/details/90349144

    相关文章

      网友评论

          本文标题:二叉树三种遍历的递归和非递归实现&层次遍历实现(C++)

          本文链接:https://www.haomeiwen.com/subject/qpqirhtx.html