二叉树的递归遍历与迭代遍历(图示)

这篇具有很好参考价值的文章主要介绍了二叉树的递归遍历与迭代遍历(图示)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本文将讲述二叉树递归与迭代的相关知识。

🕺作者: 迷茫的启明星
专栏:【数据结构从0到1】
相关文章:《数据结构二叉树的链式存储讲解及前中后序遍历和层次遍历》

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

持续更新中~

1. 二叉树的递归遍历(一入递归深似海,从此offer是路人)

首先我们要知道递归的三要素是什么?

  1. 确定递归函数的参数和返回值

    我们看到一个递归确定它在递归过程中需要处理哪些参数就需要加上那些,并且还要注意返回值是否合理

  2. 确定终止条件

    我们需要判断递归什么时候停止,以及思考怎么停止

  3. 确定递归的逻辑

    递归过程中的操作往往是重复的或者说是相似的,只是改变了一些参数

以二叉树前序遍历为例:

  1. 确定递归函数参数及返回值

    参数里需要返回遍历情况的数组、树的节点

    void traval(TreeNode* cur,vector<int> &result)
    
  2. 确定终止条件

    前序遍历上面时候停止呢?

    在前序遍历的时候的顺序是”中左右“,也就是先访问当前节点,再访问左节点,左节点访问完了,直到遇到没有孩子的节点才返回,访问上一个节点的右节点,再访问左节点,左节点访问完了,直到遇到没有孩子的节点才返回,也就是说返回条件是节点为nullptr时。

     if(cur==nullptr)return;
    
  3. 确定递归逻辑

    上面已经阐述了递归的逻辑,也就是先访问当前节点,再访问左节点和右节点。

    result.push_back(cur->val);
    traval(cur->left,result);
    traval(cur->right,result);
    

由此我们已经完成了二叉树前序遍历的分析工作

那么代码如下,中序遍历和后序遍历逻辑同样如此就再啰嗦。

1.1 前序遍历

class Solution {
public:
    void traval(TreeNode* cur,vector<int> &result)
    {
        if(cur==nullptr)return;
        result.push_back(cur->val);
        traval(cur->left,result);
        traval(cur->right,result);
    }

    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traval(root,result);
        return result;
    }
};

1.2 中序遍历

class Solution {
public:

    void traval(TreeNode*cur,vector<int>& result)
    {
        if(cur==nullptr)return;
        traval(cur->left,result);
        result.push_back(cur->val);
        traval(cur->right,result);

    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        traval(root,result);
        return result;
    }
};

1.3 后序遍历

class Solution {
public:
    void traval(TreeNode*cur,vector<int> &result)
    {
        if(cur==nullptr)return;
        traval(cur->left,result);
        traval(cur->right,result);
        result.push_back(cur->val);
    }
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        traval(root,result);
        return result;
    }
};

2.二叉树的迭代遍历(听说递归能做的,栈也能做?)

为什么可以用栈来实现递归呢?

其实编译器在递归的时候本质就是在调用栈,当一个函数里面嵌套一个函数,在调用的时候,只有里面的函数都返回完了,这个函数才返回,也就是后进先出原则。

2.1 前序遍历

那么前序遍历的迭代是怎么一回事呢?

假如说有个二叉树是这样的:

二叉树的递归遍历与迭代遍历(图示)

它的迭代过程应该是这样的:

二叉树的递归遍历与迭代遍历(图示)

二叉树的递归遍历与迭代遍历(图示)

二叉树的递归遍历与迭代遍历(图示)

那么代码应该怎么表述呢?

  1. 首先应该建立一个存储顺序的数组,一个以树节点为元素的栈
  2. 然后需要判断树是否是空树,是则直接返回无元素数组
  3. 需要建立一个循环,但在循环开始前需要把树的根节点push进去
  4. 然后需要判断循环的停止条件是什么?根据上面图示可知只要栈不为空,循环就不会停止,于是循环的条件就是栈不为空
  5. 在循环里面需要一个变量来存储pop掉的节点
  6. 为了保持”中 左 右“的顺序就需要先让右节点先进,然后左节点进,以此保证左节点先弹出

代码如下:

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

        }
        
        return result;
    }
};

2.2 中序遍历

前面我们实现了前序遍历的迭代法,但是中序遍历是否像递归那样只要交换顺序就可以了呢?并非如此

因为刚刚前序遍历时我们有两个操作:

  1. 处理:将元素放进result数组中
  2. 访问:遍历节点

它们在前序遍历时是同时发生的

但是中序遍历不同

中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。

那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

图示如下:

二叉树的递归遍历与迭代遍历(图示)

代码如下:

class Solution {
public:

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode*cur=root;
        while(cur!=nullptr||!st.empty())
        {
            if(cur!=nullptr)
            {
                st.push(cur);
                cur=cur->left;
            }
            else
            {
                cur=st.top();
                result.push_back(cur->val);
                st.pop();
                cur=cur->right;
            }
        }
        return result;
    }
};

2.3 后序遍历

后序遍历相当于前序遍历的反面,就不画图了

只需要了解一下思路即可

  1. 声明一个栈,将根节点加入栈中;
  2. 初始化一个变量pre为null,表示上一个访问的节点;
  3. 若栈不为空,则进行如下操作:
    • 取出栈顶元素top;
    • 如果top没有左右孩子或者前一个访问的节点是其左孩子或右孩子,则访问该节点并将其弹出栈
    • 否则,先将其右孩子入栈,再将左孩子入栈,保证左孩子先弹出。
class Solution {
public:

    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if(root==NULL)return result;
        TreeNode*cur=NULL;
        TreeNode*pre=NULL;
        st.push(root);
        while(!st.empty())
        {
            cur=st.top();
            if((cur->left==NULL&&cur->right==NULL)||
            (pre!=NULL&&(pre==cur->left||pre==cur->right)))
            {
                result.push_back(cur->val);
                st.pop();
                pre=cur;
            }
            else
            {
                if(cur->right!=NULL)st.push(cur->right);
                if(cur->left!=NULL)st.push(cur->left);
            }
        }

        return result;
    }
};

3. 二叉树的统一迭代法

前面所讲述的迭代法,前中后序遍历实现并不相同,因为他们对节点处理顺序和访问顺序不一致而造成的,那么有没有一种办法让他们能够像前面递归的方法那样,只需要改变几行代码就实现前中后迭代的切换呢?有的

既然无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。

那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。

如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法

代码如下:

3.1 中序遍历

代码中的 st.push(NULL); 的作用就是插入一个空指针作为标记,表示当前节点的左子树已经全部被访问过了,但是右子树还没有被访问,同时也表示这个节点已经被访问过一次,但是还没有处理过。当遍历到弹出这个空指针时,就说明当前节点的左子树已经全部被访问完了,可以处理这个节点了,于是就将这个节点弹出栈,将它的值加入到结果集中,并继续遍历它的右子树。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                if (node->right) st.push(node->right);  // 添加右节点(空节点不入栈)

                st.push(node);                          // 添加中节点
                st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。

                if (node->left) st.push(node->left);    // 添加左节点(空节点不入栈)
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.top();    // 重新取出栈中元素
                st.pop();
                result.push_back(node->val); // 加入到结果集
            }
        }
        return result;
    }
};

3.2 前序遍历

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左
                st.push(node);                          // 中
                st.push(NULL);
            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

3.3 后序遍历

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                st.push(node);                          // 中
                st.push(NULL);

                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左

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

你会发现的是,只需要改变两行代码就切换了功能,不过需要把思路捋清楚,忘了就再写一遍。

后记

本篇主要讲述了二叉树的递归遍历与迭代遍历,我们发现递归遍历只要交换代码实现的顺序就可以实现前中后遍历,而迭代法则需要限定不同的条件,这是因为处理顺序和访问顺序不一致而造成的,那么有没有办法统一它们呢?有的。我们实现了二叉树的迭代统一,自此二叉树的前中后序遍历我们就讲完了。

感谢大家支持!!!

respect!

下篇见!二叉树的递归遍历与迭代遍历(图示)文章来源地址https://www.toymoban.com/news/detail-442785.html

到了这里,关于二叉树的递归遍历与迭代遍历(图示)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 用java实现二叉树的后序遍历(递归和迭代)

    目录 1.题目内容 2.用递归实现后序遍历 2.1解题思路 2.2代码 3.用迭代实现后序遍历 3.1解题思路 3.2代码 给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。 示例 1: 输入:root = [1,null,2,3] 输出:[3,2,1] 示例 2: 输入:root = [] 输出:[] 示例 3: 输入:root = [1] 输出:[1]

    2024年02月08日
    浏览(58)
  • 算法刷题Day14 二叉树的前序、中序、后序遍历(递归、迭代、统一迭代方法)

    二叉树的定义 递归 迭代 普通的遍历(包括前序,中序和后续)迭代方法都需要借助到栈 统一迭代 统一迭代使用标记法,在栈中要处理的结点压入空指针 递归 迭代 中序遍历的迭代方法稍微特殊一点 中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问

    2024年02月15日
    浏览(48)
  • 二叉树的遍历(递归法)

    递归的三要素: ①确定递归函数的参数和返回值 ②确定终止条件 ③确定单层递归的逻辑 以前序遍历为例: 1、确定递归函数的参数和返回值: 参数中需要传入list来存放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,因此递归函数的返回类型就是v

    2024年01月17日
    浏览(73)
  • 【LeetCode】105. 从前序与中序遍历序列构造二叉树,106. 从中序与后序遍历序列构造二叉树,144. 二叉树的前序遍历非递归实现,94. 二叉树的中序遍历非递归实现,145. 二叉树的后序

    给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的 先序遍历 , inorder 是同一棵树的 中序遍历 ,请构造二叉树并返回其根节点。 示例 输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] 输出: [3,9,20,null,null,15,7] 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的

    2024年02月04日
    浏览(37)
  • 二叉树的非递归遍历算法

    二叉树的遍历是指访问二叉树的每个结点,且每个结点仅被访问一次。二叉树的遍历可按二叉树的构成以及访问结点的顺序分为4种方式:先序遍历、中序遍历、后序遍历和层次遍历。请至少给出其中一种遍历方式的非递归算法的思路和代码,并举例演示算法的执行过程。 算

    2023年04月24日
    浏览(41)
  • 二叉树的遍历的递归与非递归算法

    按照一定规律对二叉树的 每个结点进行访问且 仅访问一次 ; 这里的访问:可以是计算二叉树中的结点数据,打印该结点的信息,也可以是对结点进行的任何其它操作! 为什么需要遍历二叉树? 从过遍历可以得到访问结点的顺序序列,遍历操作就是将二叉树的结点按一定的

    2024年04月15日
    浏览(37)
  • 【递归】【后续遍历】【迭代】【队列】Leetcode 101 对称二叉树

    ---------------🎈🎈对称二叉树 题目链接🎈🎈------------------- 时间复杂度O(N) 空间复杂度O(N)

    2024年02月19日
    浏览(40)
  • 二叉树的遍历(先序遍历,中序遍历,后序遍历)递归与非递归算法

    先序遍历:先遍历一颗树的根节点,后遍历左子树,最后遍历右子树     先序遍历序列: 1 - 2 - 4 - 5 - 3 - 6 - 7 分解子问题方法 思路:将一颗二叉树看做两个部分,一个部分是左路节点,另一个部分是左路节点的右子树,先将二叉树的左路节点全部入栈,再依次出栈,出栈的

    2024年02月14日
    浏览(41)
  • 二叉树的最大深度和最小深度(两种方法:递归+迭代)

    二叉树的最大深度:   二叉树的最小深度: 给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明: 叶子节点是指没有子节点的节点。  输入:root = [3,9,20,null,null,15,7] 输出:2  

    2024年02月15日
    浏览(39)
  • 【数据结构】二叉树的遍历递归算法详解

    我们来写一个函数 BuyNode(x)函数 用于创建二叉树结点。 用动态开辟函数 malloc 函数进行动态开辟,并强制转换为 BTNode 型,用变量 node 来去管理开辟的空间。 我们初始化结点,其 val 即为传入的参数x,左右指针 left 和 right 都设为NULL。 我们在主函数中创建上面这样一颗二叉树

    2024年01月20日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包