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()”将堆栈向上备份。
给我造成混乱的是一旦堆栈为空后的执行顺序,我认为这是由于缺乏对函数递归返回调用堆栈的方式的了解所致。如果有人可以安排执行顺序并解释递归在递归调用下的操作方式,我将不胜感激。 谢谢!
答案 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