How to Eliminate Recursive Summary with Stack

Posted by luckybob on Sat, 22 Jan 2022 18:10:55 +0100

Reference article: How to replace recursive functions using stack and while-loop to avoid the stack-overflow.

Links: How to replace recursive functions using stack and while-loop to avoid the stack-overflow - CodeProject

The paper is a foreign literature in full English. Describes some methods of eliminating recursion with stack, after reading, Qui Ji wrote a summary of ~~

First, why use stacks to eliminate recursion? On the one hand, for input with a large amount of data, it can prevent overflow; On the other hand, this approach is closer to nature, and we may be able to write a question easily and recursively, but I think it's also an ability to really write non-recursively.

Of course, the article also mentioned at the end that recursive function is a very clear and concise way of writing. So when writing code, you might want to write recursion first, and eliminate it if necessary.

Returning to the truth and eliminating recursion with stacks is essentially a simulation process. The article makes this process clear, so let me summarize ten rules:

1. Create a structure with three variables: n//input test//local variable stage//state (execution phase)

2. Create a return value variable and initialize the return value to indicate the role of the return function in recursion. (void type can be ignored)

3. Create a stack.

4. Initialize the data and push it onto the stack.

5. Create a loop if the stack is not empty. The structure that will be executed pops up the stack after each loop is executed.

6. This is important because the stages are divided into the call phase and the execution phase after the call returns because the two stages execute different programs. If there are two calls, three states are required. In summary, recursive calls can be divided into different states, since the call is a new block of code.

7. Execute different states according to stages. (Trash.)

8. If a recursive function has a return value, it needs to be stored.

9. Return keywords to write continue in non-recursive functions (as appropriate)

10. Last but not least, the entire algorithm. The step is: If you are performing a recursive function, first change the state of the structure and push it into the stack. Then create a new structure, initialize the data, and push it into the stack.

I would like to explain with my understanding that in a recursive function, if you request a call again, this call is not completed, and the following statements will not be executed until the called function has been executed. At this point in non-recursion, it can be understood that the state has changed, which is why the state has to be pushed onto the stack: the later state will be executed later.

Template code:

int SomeFunc(int n, int &retIdx) //Recursive function
{
   ...
   if(n>0)
   {
      int test = SomeFunc(n-1, retIdx);
      test--;
      ...
      return test;
   }
   ...
   return 0;
}
C++
// Conversion to Iterative Function
int SomeFuncLoop(int n, int &retIdx)
{
     // (First rule)
    struct SnapShotStruct {
       int n;        // - parameter input
       int test;     // - local variable that will be local variable within the user function
                     //     after returning from the function call
                     // - retIdx can be ignored since it is a reference.
       int stage;    // - Since there is process needed to be done 
                     //     after recursive call. (Sixth rule) execution phase
    };
    // (Second rule)
    int retVal = 0;  // initialize with default returning value return value variable
    // (Third rule)
    stack<SnapShotStruct> snapshotStack;
    // (Fourth rule)
    SnapShotStruct currentSnapshot; //Initialization
    currentSnapshot.n= n;          // set the value as parameter value
    currentSnapshot.test=0;        // set the value as default value
    currentSnapshot.stage=0;       // set the value as initial stage
    snapshotStack.push(currentSnapshot);
    // (Fifth rule)
    while(!snapshotStack.empty())
    {
       currentSnapshot=snapshotStack.top();
       snapshotStack.pop();
       // (Sixth rule)
       switch( currentSnapshot.stage)
       {
       case 0: //Call Function Phase
          // (Seventh rule)
          if( currentSnapshot.n>0 )
          {
             // (Tenth rule)
             currentSnapshot.stage = 1;            // - current snapshot need to process after change state
                                                   //     returning from the recursive call
             snapshotStack.push(currentSnapshot);  // - This MUST pushed into stack beforeThis call has not finished yet
                                                   //     new snapshot!
             // Create a new snapshot for calling itself
             SnapShotStruct newSnapshot;
             newSnapshot.n= currentSnapshot.n-1;   // - give parameter as parameter given
                                                   //     when calling itself
                                                   //     ( SomeFunc(n-1, retIdx) )
             newSnapshot.test=0;                   // - set the value as initial value
             newSnapshot.stage=0;                  // - since it will start from the 
                                                   //     beginning of the function, 
                                                   //     give the initial stage
             snapshotStack.push(newSnapshot); //Next call pushed onto the stack
             continue;
          }
          ...
          // (Eighth rule)
          retVal = 0 ;
          
          // (Ninth rule)
          continue;
          break; 
       case 1: 
          // (Seventh rule)
          currentSnapshot.test = retVal;
          currentSnapshot.test--;
          ...
          // (Eighth rule)
          retVal = currentSnapshot.test;
          // (Ninth rule)
          continue;
          break;
       }
    }
    // (Second rule)
    return retVal;
} 

This is the general method of simulating recursive functions given in this paper. However, this does not mean that every recursive function needs to be simulated as non-recursive. In fact, in most cases, we can do it recursively. For example, factorials, Fibonacci arrays, and most dynamic programming problems can be written recursively, which can be directly converted to recursion, recursively, without using stacks.

Following that line of thought, let me write the Hannotta problem nonrecursively:

Recursive function form:

#include<iostream>
using namespace std;
void move(int n,char x,char y,char z)
{
	if(n==1) cout<<x<<"->"<<z<<endl;
	else{
		move(n-1,x,z,y);
		cout<<x<<"->"<<z<<endl;
		move(n-1,y,x,z);
	}
}
int main()
{
	int n;
	scanf("%d",&n);
	move(n,'A','B','C');
	return 0;
}

Test run results:

Non-recursive form:

Ideas: A simpler type of simulation, first, the function does not return a value, and does not need to return a value variable. In addition, recursive functions are called twice, so there are three phases.

 

#include<iostream>
#include<stack>
using namespace std;
struct node{
	int n;
	char x;
	char y;
	char z;
	int stage; //state variable
};
void move(int n,char x,char y,char z)
{
	node currentnode;
	currentnode.n=n;
	currentnode.x=x;
	currentnode.y=y;
	currentnode.z=z;
	currentnode.stage=0;  //Initialization of first call
	stack<struct node>S;
	S.push(currentnode);
	while(!S.empty()){
		currentnode=S.top();
		S.pop();
		switch(currentnode.stage){ //Judgement Status
			case 0: //Execute move(n-1,x,z,y);
				if(currentnode.n==1) cout<<currentnode.x<<"->"<<currentnode.z<<"\n";
				else{
					currentnode.stage=1; //Next State
				    S.push(currentnode); //Remember to push on the stack because the call has not yet been executed
				    node newnode;  //New Call
				    newnode.n=currentnode.n-1;
	                newnode.x=currentnode.x;
	                newnode.y=currentnode.z;
	                newnode.z=currentnode.y;
	                newnode.stage=0;
	                S.push(newnode);
				}
	            break;
	        case 1:
	            currentnode.stage=2;
	            S.push(currentnode); //Same as
	            cout<<currentnode.x<<"->"<<currentnode.z<<"\n";
	            break;
	        case 2: //Execute move(n-1,y,x,z);
	        	if(currentnode.n==1) cout<<currentnode.x<<"->"<<currentnode.z<<"\n";
	        	else{
	        		node newnode; //You have reached the last step without pushing the current onto the stack
	        		newnode.n=currentnode.n-1;
	                newnode.x=currentnode.y;
	                newnode.y=currentnode.x;
	                newnode.z=currentnode.z;
	                newnode.stage=0;
	                S.push(newnode);
				}
	            break;
		}
	}
}
int main()
{
	int n;
	scanf("%d",&n);
	move(n,'A','B','C');
	return 0;
}

Test run results:

Readers are interested in experimenting with non-recursive writing and arranging, which is not necessarily the case.

Topics: Algorithm data structure