递归 vs 迭代


首先提出两个问题

  • 所有递归都可以改写成循环吗?
  • 改写后会有什么好处?

接下来我们仔细分析下具体的区别

递归转换为迭代的方法

对于递归而言有两种转换方式

一、直接转换法

方法:使用变量保存中间结果

计算阶乘:

long fact(int n) {
  if (n==0) return 1;
  else return n*fact(n-1);
}
long fact(int n) {
  int res = 1;
  for (int i=1; i<=n;i++)
       res *= i;
  return res;
}

可以看到,时间复杂度依旧为 \(\mathcal{O}(n)\),但空间复杂度已经从 \(\mathcal{O}(n) \to \mathcal{O}(1)\) 了,因为原本需要调用 fact(...) \(n\) 次,但现在只需要 \(1\) 次了

二、间接转换法

方法:使用栈保存中间结果

树的先序遍历:

/**
 * 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) {}
 * };
 */
class Solution {
public:
    void travel(TreeNode *root)
    {
        if(root == nullptr)
        {
            return;
        }
        res.push_back(root->val);
        travel(root->left);
        travel(root->right);
    }
    vector<int> preorderTraversal(TreeNode* root) 
    {
        travel(root);
        return res;
    }
private:
    vector<int> res;
};
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans;
        stack<TreeNode*> st;
        TreeNode *p=root;
        while (!st.empty() || p) {
            while (p) {
                ans.emplace_back(p->val);
                st.emplace(p);
                p = p->left;
            }
            p = st.top();
            st.pop();
            p = p->right;
        }
        return ans;
    }
};

但此处的时间和空间复杂度却都没有变化,因为用栈来保存结果,本质就是在模拟函数调用的过程,并没有什么好处,但编写代码的难度却大大提高

所以,回答下开头的问题

所有递归都可以改写成循环吗?

是的,所有递归都使用直接或间接的方式转换

改写后会有什么好处?

使用直接转换可以避免空间上的开销,使用间接转换本质在模拟函数调用没有意义

总结

虽然递归有着很大的空间开销,但很多时候我们是很难用迭代写出我们想要的程序,而且空间上的开销也不是很重要,相比较迭代的自底向上,递归的自顶向下往往更符合人类的逻辑,很多时候做动态规划的题目没有思路的时候,都可以考虑用自顶向下的记忆化搜索来完成


文章作者: Mou shuai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mou shuai !
评论
  目录