【问题标题】:Understanding the Order of Execution of Recursive Functions了解递归函数的执行顺序
【发布时间】:2019-10-10 21:41:48
【问题描述】:
template <typename T>
T sum(stack<T>& s){
    if (s.empty()){
        return 0;
    } else {
        T first = s.top();
        s.pop();
        T total = sum(s)+first;
        s.push(first);
        return total;
        }
}

上面的代码旨在递归地对任何给定类型 T 堆栈的元素求和,唯一的条件是堆栈的完整性必须在函数结束时恢复。意思是,只要函数终止时它处于与传递之前相同的状态,我就可以对堆栈进行更改以对元素求和。

正如您将观察到的,给定的代码可以正常工作,但是我不了解递归调用和返回语句的控制流或执行顺序。当我看到这段代码时,我了解元素是如何求和的,但是我不明白对“s.push(first)”的调用如何将 all 元素添加回堆栈。我很难理解为什么它不会只推送堆栈的最后一个元素然后返回总数。

我目前对其工作原理的理解是不完整的,可能存在缺陷,如下所示:因为每个 return 语句都返回到最近的调用者,所以当递归达到基本情况并终止时,return 语句将按原路返回递归调用堆栈,直到它到达原始调用者,因此在每次移动时执行“s.push()”备份堆栈。

让我感到困惑的是堆栈为空后的执行顺序,我认为这是由于缺乏对函数递归备份调用堆栈的方式的理解。如果有人可以列出执行顺序并解释递归与递归调用下的操作一起工作的方式,我将不胜感激。 谢谢!

【问题讨论】:

  • 您的“当前理解” 对我来说似乎是正确的。为什么你认为它有缺陷?
  • 调试器结合调用堆栈窗口对于分析递归函数非常有用。您是否有带有调试工具(如 Visual Studio)的 IDE?
  • 非数字 T(例如 std::string、std::bits 或某些用户定义的类型)可以相加,但不会导致返回值 0。返回type 必须仍然是 T 类型。

标签: c++ recursion


【解决方案1】:

您的总体理解是正确的。您只是缺少连接最后的点。

要记住的关键点是当函数返回时,它会返回到调用它的地方。递归函数在这个基本方面没有什么不同。递归函数调用的工作方式完全相同。

如果您标记每个递归调用,这将有助于理解。我们将递归函数的初始调用称为“A”。当递归函数以递归方式调用自身时,调用递归函数“B”的调用。然后它再次调用,那是“C”。其次是“D”,以此类推。

要理解的关键点是,当一个函数返回时,它会返回到它被调用的地方。所以,“D”返回“C”,又返回“B”,又返回“A”。

现在看看你的递归函数。当栈中剩下一个值时,我们称它为“D”,它从栈中移除“D”值并递归调用“E”,发现栈为空。

所以它返回到“D”,将“D”值推回堆栈,堆栈现在又多了一个值。然后它返回到“C”,它将“C”值推回堆栈,现在堆栈上的两个原始值,最后一个,以相同的顺序。

在这种方式下,函数调用以与原始调用顺序相反的顺序展开,将堆栈恢复到原来的样子。

【讨论】:

  • 以相反的顺序展开是一种很好的看待它的方法,通常在诸如此类的问题中点是断开的。他可以感谢他的函数只进行 1 次递归调用...
【解决方案2】:

你的函数看起来像这样:

if (s.empty()){
        return 0;
} else {
        T first = s.top();
        s.pop();
        T total = sum(s)+first;
        s.push(first);
        return total;
}

为了看看它是如何工作的,让我们假设这实际上是一个宏,并将函数扩展为通常会执行的内容:

if (s.empty()){
        return 0;
} else {
        T first = s.top();
        s.pop();
        T total = if (s.empty()){
                return 0;
            } else {
                T first = s.top();
                s.pop();
                T total = sum(s)+first;
                s.push(first);
                return total;
        }+first;
        s.push(first);
        return total;
}

这当然只是一个例子。因为它不是一个宏,所以这不是真正发生的事情。这只是为了说明。

但是,关键是每次调用函数时都会执行函数中的代码,类似于第二个代码 sn-p。因此,最终发生的是最里面的函数推入堆栈,然后调用函数推入堆栈,等等。直到所有内容都被推回堆栈。因此,即使有一次调用压栈,它仍然会在每次函数执行时执行。

【讨论】:

    【解决方案3】:

    “如果有人能列出执行顺序……”

    总是允许在执行代码中添加(可移除的)cout's。下面说明了一种方法。

    注 1:为简化起见,我删除了模板问题。演示使用 int。

    注意 2:dumpStack 不是递归的。

    注3:m_stck 是类的数据属性,所以不需要从sumStack 传递到sumStack。

    #include <iostream>
    using std::cout, std::endl; // c++17
    
    #include <iomanip>
    using std::setw, std::setfill;
    
    #include <string>
    using std::string, std::to_string;
    
    #include <stack>
    using std::stack;
    
    #ifndef                 DTB_PCKLRT_HH
    #include "../../bag/src/dtb_pclkrt.hh"
    using  DTB::PClk_t;
    #endif
    
    
    
    class StackW_t   // stack wrapper UDT (user defined type)
    {
    private:
       int         m_N;     // max elements
       stack<int>  m_stck;  // default ctor creates an empty stack
    
    public:
       StackW_t(int  N = 10) // simple default size
          {
             m_N = N;          // capture
             assert(m_N > 1);  // check value
             for (int i=0; i<m_N; ++i)
                m_stck.push(N - i);  // simple fill
          }
    
       ~StackW_t() = default; // dtor default deletes each element of m_stck
    
       // recurse level-vvvv
       int sumStack(int rLvl = 1)
          {
             if (m_stck.empty())
             {
                cout << "\n" << setw(2*rLvl) << " " << setw(4) << "<empty>";
                return 0;
             }
             else
             {
                int first = m_stck.top();                      // top element
                m_stck.pop();                                  // remove top element
    
                cout << "\n" << setw(2*rLvl)
                     << " " << setw(4) << first;               // recurse report
    
                // use first value then recurse into smaller stack with next rLvl
                int sum = first  +  sumStack(rLvl+1);
    
                cout << "\n" << setw(2*rLvl)                   // decurse report
                     << " " << setw(3) << "(" << first << ")";
    
                m_stck.push(first);                            // restore element after use
                return sum;
             }
          }
    
       void dumpStack(string lbl, int rLvl = 1)
          {
             stack<int>  l_stck = m_stck; // for simplicity, use copy of
    
             cout << "\n  dumpStack " << lbl << setw(2*rLvl);
             while (!l_stck.empty())
             {
                cout << " " << " " << l_stck.top();
                l_stck.pop();  // remove displayed member
             }
             cout << "\n";
          }
    }; // class StackW_t
    
    
    // Functor 829
    class F829_t // use compiler provided defaults for ctor and dtor
    {
       PClk_t  pclk; // posix clock access
    
    public:
       int operator()(int argc, char* argv[]) { return exec(argc, argv);  }
    
    private:
    
       int exec(int  , char** )
          {
             int retVal = 0;
    
             // create, auto fill with value 1..10
             StackW_t stk;
    
             stk.dumpStack("before"); // invoke display
    
             cout << "\n  stk.sumStack():  ";
    
             uint64_t start_us = pclk.us();
    
             // invoke recursive compute, start at default rLvl 1
             int sum = stk.sumStack();
    
             auto  duration_us = pclk.us() - start_us;
    
             cout << "\n  sum:  " << sum << endl;
    
             stk.dumpStack("after"); // invoke display
    
             cout << "\n  F829_t::exec() duration   "
                  << duration_us << " us    (" <<  __cplusplus  << ")" << std::endl;
             return retVal;
          }
    
    }; // class F829_t
    
    int main(int argc, char* argv[]) { return F829_t()(argc, argv); }
    

    注4:在递归过程中,rLvl增加,所以每一行的值都向右移动

    注5:在decurse过程中,rLvl在函数返回时恢复,因此输出也恢复对齐

    注6:堆栈前后显示堆栈恢复成功

    输出:

      dumpStack before   1  2  3  4  5  6  7  8  9  10
    
      stk.sumStack():  
         1
           2
             3
               4
                 5
                   6
                     7
                       8
                         9
                          10
                          <empty>
                          (10)
                        (9)
                      (8)
                    (7)
                  (6)
                (5)
              (4)
            (3)
          (2)
        (1)
      sum:  55
    
      dumpStack after   1  2  3  4  5  6  7  8  9  10
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-13
      • 2020-03-26
      • 1970-01-01
      • 2019-12-20
      • 1970-01-01
      • 2016-08-20
      • 2016-04-09
      相关资源
      最近更新 更多