前言:在有些情况下,我们更倾向于使用递归函数,如排序(合并排序)或树相关操作的算法(heapify/ heapify)。但是,递归函数在某些环境中,如在Visual C ++中,如果调用层次过深,可能会出现如堆栈溢出。许多专业的开发人员可能已经知道如何更改递归功能用迭代函数或使用栈(堆栈)和while循环解决(模拟递归的过程)堆栈溢出问题。
如果您使用的是递归函数,因为你不控制调用堆栈和堆栈大小的限制,当递归调用的深度过深stackoverflow/heap-corruption可能随时发生。如果是换成迭代函数,这个过程还是比较复杂的。但是,为了做到这一点,这里介绍一个十步法将递归程序变换成迭代形式的程序。从而取代使用递归函数的栈,以避免堆栈溢出。下面依次介绍这十条规则。
规则一:1.定义一个结构体,这里取名为:Snapshot.这个结构体主要是用来保存递归程序中每一步用到的数据。
2.该结构体Snapshot应该包含如下信息:
a.递归函数调用过程变化的参数,当该参数为引用的形式,不需要包含在Snapshot结构体中。如下面的函数:void SomeFunc(int n, int &retVal);
参数n须要包括在里面,参数retVal不须要保存。
b.过程保存变量"Stage",通常是一个int型变量来标志当前的阶段。具体看【规则六】
c.调用递归函数返回的局部变量(在二分递归和嵌套递归中会出现)。
//规则一的例子:递归函数 int SomeFunc(int n, int &retIdx) { ... if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
// 转换成迭代形式 int SomeFuncLoop(int n, int &retIdx) { // (规则一) struct SnapShotStruct { int n; // - 输入参数 int test; // - 用来保存递归函数返回值的局部变量 // // - retIdx 参数是引用,这里忽略 int stage; // - 递归函数调用后,需要到哪个阶段(规则六 // }}
规则二:1.在迭代函数的顶部定义一个局部变量,用来保存递归函数的返回值。
a.在迭代函数中,该变量是用来保存递归函数中每一次递归调用的返回值
b.如果该递归函数返回值类型为Void, 则可以不定义该局部变量。
c.如果有默认的返回值,则用默认的返回值对该局部变量进行初始化。
// 规则二的例子: int SomeFunc(int n, int &retIdx) { ... if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
// 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 used // 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) }; // (Second rule) int retVal = 0; // initialize with default returning value ... // (Second rule) return retVal; }
规则三:创建一个包含Snapshot结构的栈容器。
// Recursive Function "Third rule" example // 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 used // 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) }; // (Second rule) int retVal = 0; // initialize with default returning value // (Third rule) stack<SnapShotStruct> snapshotStack; ... // (Second rule) return retVal; }
规则四:1.创建一个Snapshot实例,并对里面的参数进行赋初值,然后把它加入到栈容器中。
// Recursive Function "Fourth rule" example // 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 used // 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) }; // (Second rule) int retVal = 0; // initialize with default returning value // (Third rule) stack<SnapShotStruct> snapshotStack; // (Fourth rule) SnapShotStruct currentSnapshot; 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); ... // (Second rule) return retVal; }
规则五:在函数里面建立一个while循环,当栈容器不为空的时候循环,在循环里面弹出一个Snapshot
// Recursive Function "Fifth rule" example // 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 used // 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) }; // (Second rule) int retVal = 0; // initialize with default returning value // (Third rule) stack<SnapShotStruct> snapshotStack; // (Fourth rule) SnapShotStruct currentSnapshot; 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(); ... } // (Second rule) return retVal; }
规则六: 1.当递归函数里面仅有一处自身调用,则将该过程分为两个阶段;第一阶段是在自身调用之前要处理的事情,第二阶段是在自身调用完之后需要处理的事情。
2.当递归函数有两处自身调用,则需要将该过程分为三个阶段;Stage 1-->递归调用-->(从第一次递归调用返回)Stage 2(第一阶段递归调用)---》(第二次递归调用返回)Stage 3
3.当递归函数有三处自身调用,则需要将该过程分为四个阶段。以此类推。
// Recursive Function "Sixth rule" example int SomeFunc(int n, int &retIdx) { ... if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
/ 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 used // 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) }; // (Second rule) int retVal = 0; // initialize with default returning value // (Third rule) stack<SnapShotStruct> snapshotStack; // (Fourth rule) SnapShotStruct currentSnapshot; 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: ... // before ( SomeFunc(n-1, retIdx); ) break; case 1: ... // after ( SomeFunc(n-1, retIdx); ) break; } } // (Second rule) return retVal; }
规则七:根据Snapshot结构体stage的值来分别处理各个过程。
// Recursive Function "Seventh rule" example int SomeFunc(int n, int &retIdx) { ... if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
// 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 used // 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) }; // (Second rule) int retVal = 0; // initialize with default returning value // (Third rule) stack<SnapShotStruct> snapshotStack; // (Fourth rule) SnapShotStruct currentSnapshot; 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: // (Seventh rule) if( currentSnapshot.n>0 ) { ... } ... break; case 1: // (Seventh rule) currentSnapshot.test = retVal; currentSnapshot.test--; ... break; } } // (Second rule) return retVal; }
规则八:1.如果递归函数中有返回值,则在迭代函数中要用一个局部变量来保存循环迭代的结果。如retVal变量
2.当循环退出时,该局部变量的值就是递归函数最后的结果
// Recursive Function "Eighth rule" example int SomeFunc(int n, int &retIdx) { ... if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
// 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 used // 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) }; // (Second rule) int retVal = 0; // initialize with default returning value // (Third rule) stack<SnapShotStruct> snapshotStack; // (Fourth rule) SnapShotStruct currentSnapshot; 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: // (Seventh rule) if( currentSnapshot.n>0 ) { ... } ... // (Eighth rule) retVal = 0 ; ... break; case 1: // (Seventh rule) currentSnapshot.test = retVal; currentSnapshot.test--; ... // (Eighth rule) retVal = test; ... break; } } // (Second rule) return retVal; }
规则九:1.在递归函数中,如果有return关键字,在循环中要把它变成continue关键字。
a.如果在递归函数中返回一个值,则用一个局部变量保存该值(如果规则八的retVal),然后再Continue。
b.规则九一般是可选的,主要是为了避免逻辑错误。
// Recursive Function "Ninth rule" example int SomeFunc(int n, int &retIdx) { ... if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
// 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 used // 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) }; // (Second rule) int retVal = 0; // initialize with default returning value // (Third rule) stack<SnapShotStruct> snapshotStack; // (Fourth rule) SnapShotStruct currentSnapshot; 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: // (Seventh rule) if( currentSnapshot.n>0 ) { ... } ... // (Eighth rule) retVal = 0 ; // (Ninth rule) continue; break; case 1: // (Seventh rule) currentSnapshot.test = retVal; currentSnapshot.test--; ... // (Eighth rule) retVal = test; // (Ninth rule) continue; break; } } // (Second rule) return retVal; }
规则十:为了变换递归函数中的递归调用,在迭代函数中新建一个"Snapshot" 对象,并根据递归调用时候的参数来初始 化"Snapshot" 对象的参数。然后加入到容器栈中,加上 Continue. 如果在递归调用之后还有处理语句,更改当
前"Snapshot" 对象的 stage变量的值到相关阶段,在加入新"Snapshot" 对象到容器栈之前,先把当前更新的"Snapshot"对象压入容器栈。
// Recursive Function "Tenth rule" example int SomeFunc(int n, int &retIdx) { ... if(n>0) { int test = SomeFunc(n-1, retIdx); test--; ... return test; } ... return 0; }
// 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 used // 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) }; // (Second rule) int retVal = 0; // initialize with default returning value // (Third rule) stack<SnapShotStruct> snapshotStack; // (Fourth rule) SnapShotStruct currentSnapshot; 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: // (Seventh rule) if( currentSnapshot.n>0 ) { // (Tenth rule) currentSnapshot.stage = 1; // - current snapshot need to process after // returning from the recursive call snapshotStack.push(currentSnapshot); // - this MUST pushed into stack before // 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); continue; } ... // (Eighth rule) retVal = 0 ; // (Ninth rule) continue; break; case 1: // (Seventh rule) currentSnapshot.test = retVal; currentSnapshot.test--; ... // (Eighth rule) retVal = test; // (Ninth rule) continue; break; } } // (Second rule) return retVal; }
一个详细的例子:
int Fact(long n) { if(0>n) return -1; if(0 == n) return 1; else { return ( n* Fact(n-1)); } } int FactLoop(long n) { // (First rule) struct SnapShotStruct // this can be declared as local structure // since it will be only used within this function. { long inputN; // parameter that changes // no local variable int stage; // the stage variable to track where the snapshot has taken } ; // (Second rule) int returnVal; // the return value at the point // (Third rule) stack<SnapShotStruct> snapshotStack; // (Fourth rule) SnapShotStruct currentSnapshot; currentSnapshot.inputN=n; currentSnapshot.stage=0; // as initial stage snapshotStack.push(currentSnapshot); // (Fifth rule) while(!snapshotStack.empty()) { currentSnapshot=snapshotStack.top(); snapshotStack.pop(); // (Sixth rule) switch(currentSnapshot.stage) { // (Seventh rule) case 0: if(0>currentSnapshot.inputN) { // (Eighth rule && Ninth rule) returnVal = -1; continue; } if(0 == currentSnapshot.inputN) { // (Eighth rule && Ninth rule) returnVal = 1; continue; } else { // (Tenth rule) // return ( n* Fact(n-1)); this is actually two steps // (first calling itself, and second with the value returns multiply with current n value.) // this is where we need make a snapshot. currentSnapshot.stage=1; // current snapshot is done processing and // only waiting for result of calling itself, // so becomes stage "1" snapshotStack.push(currentSnapshot); // Create a new snapshot for calling itself SnapShotStruct newSnapshot; newSnapshot.inputN= currentSnapshot.inputN -1 ; // give parameter as parameter given // when calling itself. newSnapshot.stage = 0 ; // since it will start from the begining of // the function, give the initial stage snapshotStack.push(newSnapshot); continue; } break; // (Seventh rule) case 1: // (Eighth rule) returnVal = currentSnapshot.inputN * returnVal; // (Ninth rule) continue; break; } } // (Second rule) return returnVal; }