问题
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;
}
}
The code above is designed to recursively sum the elements of any given stack of type T with the only condition being that the integrity of the stack must be restored at the end of the function. Meaning, I am allowed to make changes to the stack to sum the elements as long as it is in the same state as it was before it was passed when the function terminates.
As you will observe the given code works however I do not understand the control flow or execution sequence of the recursive calls and return statements. When I see this code I understand how the elements are summed, however I do not understand how the call to "s.push(first)" adds all of the elements back on to the stack. I'm having a hard time wrapping my head around why it wouldn't solely push the last element of the stack and then return the total.
My current understanding of why this works is incomplete and likely flawed and is as follows: because each return statement returns to the most recent caller, when the recursion hits the base case and terminates, the return statements will work their way back up the recursive call stack until it gets to the original caller and therefore executing "s.push()" at each movement back up the stack.
What is causing confusion for me is the execution sequence once the stack is empty and I think it is due to a lack of understanding the way the function recurses back up the call stack. If someone could lay out the execution sequence and explain the way recursion works with operations underneath the recursive call that would me much appreciated. Thanks!
回答1:
Your overall understanding is correct. You're only missing connecting the final dots.
The key point to remember is when a function returns, it returns to wherever it was called from. Recursive functions are no different in that fundamental respect. Recursive function calls work exactly the same way.
It will help to understand if you label each recursive call. Let's call the initial invocation of the recursive function "A
". When the recursive function calls itself, recursively, call that invocation of the recursive function "B
". Then it calls again, and that's "C
". Followed by "D
", and so on.
The key point to understand is that when a function returns, it returns to wherever it was called from. So, "D
" returns to "C
", which returns to "B
", and it returns to "A
".
Now look at your recursive function. When the stack had one value left, let's call it "D
", it removes the "D
" value from the stack and makes the recursive call "E
", which discovers that the stack is empty.
So it returns to "D
", which pushes the "D
" value back to the stack, which now has one value again. Then it returns to "C
", which pushes the "C
" value back to the stack, which now has the two original, last, values on the stack, in the same order.
In this fashion, the function calls unwind in reverse order from their original calling sequence, restoring the stack to exactly what it was, originally.
回答2:
Your function looks something like this:
if (s.empty()){
return 0;
} else {
T first = s.top();
s.pop();
T total = sum(s)+first;
s.push(first);
return total;
}
To kind of see how this works, let's pretend that this is actually a macro, and expand the function into what would generally get executed:
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;
}
This is of course, just an example. Since it is not a macro,this isn't what really happens. It is just to illustrate.
However, the point is that the code in your function will get executed every time you call the function similarly to the second code snippet. Thus, what ends up happening is that the innermost function pushes to the stack, and then the calling function pushes to the stack, etc.. until everything gets pushed back on to the stack. So, even though there is one call to push on the stack, it will still get executed every time the function executes.
回答3:
"If someone could lay out the execution sequence ... "
It is always allowed to add (removable) cout's to the executing code. The following illustrates one approach.
Note 1: To simplify, I removed template issues. The demo uses int.
Note 2: dumpStack is not recursive.
Note 3: m_stck is data attribute of the class, so it need not be passed from sumStack to 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); }
Note 4: during recurse, rLvl increases, so the value shifts to the right for each line
Note 5: during decurse, rLvl is restored upon function return, thus output is also restored to alignment
Note 6: before and after of stack shows successful restore of stack
Output:
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
来源:https://stackoverflow.com/questions/58253247/understanding-the-order-of-execution-of-recursive-functions