了解递归函数的执行顺序

时间:2019-10-06 00:03:22

标签: c++ recursion

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的给定堆栈的元素,唯一的条件是必须在函数末尾恢复堆栈的完整性。意思是,只要函数处于与函数终止时通过之前相同的状态,就可以对堆栈进行更改以对元素求和。

正如您将看到的那样,给定的代码有效,但是我不理解递归调用和return语句的控制流程或执行顺序。当我看到这段代码时,我了解了元素的求和方式,但是我不理解对“ s.push(first)”的调用如何将元素的 all 添加回堆栈中。我很难理解为什么它不会只推栈的最后一个元素然后返回总数。

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

给我造成混乱的是一旦堆栈为空后的执行顺序,我认为这是由于缺乏对函数递归返回调用堆栈的方式的了解所致。如果有人可以安排执行顺序并解释递归在递归调用下的操作方式,我将不胜感激。 谢谢!

3 个答案:

答案 0 :(得分:2)

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

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

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

要理解的关键点是,当函数返回时,它将返回到调用它的位置。因此,“ D返回到“ C”,返回到“ B”,然后返回到“ A”。

现在看看您的递归函数。当堆栈中剩下一个值时,我们将其称为“ D”,它将从堆栈中删除“ D”值,并进行递归调用“ E”,这将发现堆栈为空。

因此它返回到“ D”,这会将“ D”值推回堆栈,该堆栈现在又具有一个值。然后返回“ C”,这将“ C”的值推回到堆栈中,该堆栈现在以相同的顺序在堆栈上具有两个原始的最后一个值。

以这种方式,该函数以与原始调用顺序相反的顺序调用unwind,将堆栈恢复到原来的状态。

答案 1 :(得分:0)

您的函数如下所示:

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;
}

这当然只是一个例子。由于它不是宏,所以这不是真正的情况。只是为了说明。

但是,关键是每次调用该函数时,函数中的代码都会与第二个代码片段类似地执行。因此,最终发生的是,最里面的函数被压入堆栈,然后调用函数被压入堆栈,依此类推。直到所有东西都被压回到堆栈上。因此,即使有一个调用要压入堆栈,每次执行该函数时仍会执行。

答案 2 :(得分:0)

  

“如果有人可以安排执行顺序……”

始终允许在执行代码中添加(可移动)cout。下面说明了一种方法。

注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:在递归过程中,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