分享

递归算法向非递归算法转换

 kittywei 2012-03-31

递归算法非递归算法转换

递归算法实际上是一种分而治之的方法,它把复杂问题分解为简单问题来求解。对于某些复杂问题(例如hanio塔问题),递归算法是一种自然且合乎逻辑的解决问题的方式,但是递归算法的执行效率通常比较差。因此,在求解某些问题时,常采用递归算法来分析问题,用非递归算法来求解问题;另外,有些程序设计语言不支持递归,这就需要把递归算法转换为非递归算法。

    将递归算法转换为非递归算法有两种方法,一种是直接求值,不需要回溯;另一种是不能直接求值,需要回溯。前者使用一些变量保存中间结果,称为直接转换法;后者使用栈保存中间结果,称为间接转换法,下面分别讨论这两种方法。

1. 直接转换法

直接转换法通常用来消除尾递归和单向递归,将递归结构用循环结构来替代。

尾递归是指在递归算法中,递归调用语句只有一个,而且是处在算法的最后。例如求阶乘的递归算法:

long fact(int n)

{

  if (n==0) return 1;

  else return n*fact(n-1);

}

当递归调用返回时,是返回到上一层递归调用的下一条语句,而这个返回位置正好是算法的结束处,所以,不必利用栈来保存返回信息。对于尾递归形式的递归算法,可以利用循环结构来替代。例如求阶乘的递归算法可以写成如下循环结构的非递归算法:

long fact(int n)

{

  int s=0;

  for (int i=1; i<=n;i++)

  s=s*i; //s保存中间结果

  return s;

}

单向递归是指递归算法中虽然有多处递归调用语句,但各递归调用语句的参数之间没有关系,并且这些递归调用语句都处在递归算法的最后。显然,尾递归是单向递归的特例。例如求斐波那契数列的递归算法如下:

int f(int n)

{

  if (n= =1 | | n= =0) return 1;

  else return f(n-1)+f(n-2);

}

对于单向递归,可以设置一些变量保存中间结构,将递归结构用循环结构来替代。例如求斐波那契数列的算法中用s1s2保存中间的计算结果,非递归函数如下:

int f(int n)

{

  int i, s;

  int s1=1, s2=1;

  for (i=3; i<=n; ++i)

        {

         s=s1+s2;

         s2=s1; // 保存f(n-2)的值

         s1=s; //保存f(n-1)的值

  }

  return s;

}

2. 间接转换法

该方法使用栈保存中间结果,一般需根据递归函数在执行过程中栈的变化得到。其一般过程如下:

将初始状态s0进栈

while (栈不为空)

{

  退栈,将栈顶元素赋给s;

  if (s是要找的结果) 返回;

  else

        {

      寻找到s的相关状态s1;

      s1进栈

  }

}

间接转换法在数据结构中有较多实例,如二叉树遍历算法的非递归实现、图的深度优先遍历算法的非递归实现等等。

使用非递归方式实现递归问题的算法程序,不仅可以节省存储空间,而且可以极大地提高算法程序的执行效率。本文将递归问题分成简单递归问题和复杂递归问题;简单递归问题的非递归实现采用递推技术加以求解,复杂递归问题则根据问题求解的特点采用两类非递归实现算法,使用栈加以实现。

递归算法的优缺点:
○1优点:结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此它为设计算法、调试程序带来很大方便。
○2缺点:递归算法的运行效率较低,无论是耗费的计算时间还是占用的存储空间都比非递归算法要多。

边界条件与递归方程是递归函数的二个要素

应用分治法的两个前提是问题的可分性和解的可归并性

以比较为基础的排序算法的最坏倩况时间复杂性下界为0(n?log2n)。

回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T。

舍伍德算法设计的基本思想:
设A是一个确定性算法,当它的输入实例为x时所需的计算时间记为tA(x)。设Xn是算法A的输入规模为n的实例的全体,则当问题的输入规模为n时,算法A所需的平均时间为

这显然不能排除存在x∈Xn使得                  的可能性。希望获得一个随机化算法B,使得对问题的输入规模为n的每一个实例均有

拉斯维加斯( Las Vegas )算法的基本思想:
设p(x)是对输入x调用拉斯维加斯算法获得问题的一个解的概率。一个正确的拉斯维加斯算法应该对所有输入x均有p(x)>0。
设t(x)是算法obstinate找到具体实例x的一个解所需的平均时间 ,s(x)和e(x)分别是算法对于具体实例x求解成功或求解失败所需的平均时间,则有:
解此方程可得:


蒙特卡罗(Monte Carlo)算法的基本思想:
设p是一个实数,且1/2<p<1。如果一个蒙特卡罗算法对于问题的任一实例得到正确解的概率不小于p,则称该蒙特卡罗算法是p正确的,且称p-1/2是该算法的优势。
如果对于同一实例,蒙特卡罗算法不会给出2个不同的正确解答,则称该蒙特卡罗算法是一致的。

线性规划基本定理:如果线性规划问题有最优解,则必有一基本可行最优解。
单纯形算法的特点是:
(1)只对约束条件的若干组合进行测试,测试的每一步都使目标函数的值增加;
(2)一般经过不大于m或n次迭代就可求得最优解。

单纯形算法的基本思想就是从一个基本可行解出发,进行一系列的基本可行解的变换。每次变换将一个非基本变量与一个基本变量互调位置,且保持当前的线性规划问题是一个与原问题完全等价的标准线性规划问题。

图灵机由以下几部分组成:一条无限长的带(有无穷个格子)、一个读写头、一个有限状态控制器以及一个程序。

NPC形式化定义:
定义1:语言L是NP完全的当且仅当(1) L【NP;(2)对于所有L’【NP有L’ ~pL。
    如果有一个语言L满足上述性质(2),但不一定满足性质(1),则称该语言是NP难的。
所有NP完全语言构成的语言类称为NP完全语言类,就是NPC。

定理1 设L是NP完全的,则(1)L?P当且仅当P=NP;(2)若 L ?p L1,且 L1? NP,则L1是NP完全的。

团问题:
     任给图G和整数k.试判定图G中是否存在具有k个顶点的团?
     1)团问题?NP。显然,验证G的一个子图是否成团只需多项式时间即可。
     2)SAT?团问题。
     任给表达式f.构造一个相应的图G如下:
①图G的每个顶点对应于f中的每个文字(多次出现的重复计算)。
②若G中两个顶点其原对应的文字不相互补且不出现于同一于句中,则将其连线。
    设f有n个子句,则f可满足当且仅当f对应的图G中有n个顶点的团。
    这是因为:
(a)若f可满足,即有某种赋值使得f取值为真,它等价于使得每个ci中都至少有一个文字为真,这n个文字(每个ci(1<i<n)中一个)对应的图G中的n个顶点就构成一个团。
(b)若图G中有一n个顶点的团,则取给出使得这n个顶点对应的文字都为真的赋值,则f的取值为真(这由图G的定义易证)。
显见,上述构造图G的方法是多项式界的,因此SAT   团问题。
由(a)、(b)有,团问题?NPC。证毕。

单源最短路径问题:
 
void shortestpaths(v)
  {
MinHeap H[1000];
 //定义最小堆
MinHeapNode<type> E;
E.i=v;
E.length=0;
Dist[v]=0;
//搜索问题界空间
while(true)
{  for(j=1;j<=n;j++)
if((c[E.i][j]<inf)&&
    (E.length+c[E.i][j]<dist[j]))
  {dist[j]=E.length+c[E.i][j];
    prev[j]=E.i;
   //加入活结点优先队列
MinHeapNode <type> N;
N.i=j;
 N.length=dist[j];
 H.Insert(N);
}
   //取下一个扩展结点
   try { H.DeleteMin(E); }
  //优先队列为空
  catch (OutOfBounds) {break;}
}
}

 

(1)数值随机化算法: ?求解数值问题,得到近似解
(2)Monte Carlo算法:? 问题准确性,但却无法确定解正确性
(3)Las Vegas算法:?获得正确解,但存在找不到解的可能性
(4)Sherwood算法:?保证能获得正确解

旅行售货员问题:(优先队列式分支限界法)
 
 Type Travding (int v[])
{   MinHeapNode H(1000);
    Type  Minout[N+1];
    //计算 Minout[i]=顶点 i的最小出边费用
    Type Minsurn=0;//最小出边费用和
for(i=1;i<=n;i++){
      Min=NoEdge;
      for( j=1;j<=n;j++)
        if(a[i][j]!=NoEdge&&(a[i][j]<Min || Min==NoEdge)    Min=a[i][j];
      if(Min==NoEdge) return(NoEdge); //无回路
      MinOut[i]= Min;
      MinSum+=Min;
    }
 //初始化
    MinHeapNode E;
    for(i= 0;i< n;i++)
           E.x[i]= i+ 1;
    E.s=0;
  E.cc=0;
    E.rcost=MinSum;
    Bestc=NoEdge;
  while(E.s<n-1) //非叶结点
if(E.s<n-1){ //当前扩展结点是叶结点的父结点                     
if(a[E.x[n-2][E.x[n-1]]!=NoEdge &&a[E.x[n-2][1]!=NoEdge&&(E.cc+
a[E.x[n-2]][E.x[n-1]]+a[E.x[n-1]][1]<
bestc || bestc==NoEdge){
  //费用更小的回路
    bestc=Ecc+a[E.x[n-2]][E.x[n-1]]
           +a[E.x[n-1]][1];
          E.c=bestc;
          E.lcost=bestc;
          E.s++;
          Insert(H,E);}
      else delete(E.x) ;} //舍弃扩展结点
 else { //产生当前扩展结点的儿子结点
     for( i=E.s+1;i<n;i++=
        if(a[E.x[E.s]][E.x[i]]!=NoEdge) 
  { //可行儿子结点
     Type cc=E.cc+a[E.x[E.s]][E.x[i]];
      Type rcost=E.rcost-MinOut[E.x[E.s]];
       Type b=cc+rcost; //下界
if(b< bestc||bestc== NoEdge )
    { //子树可能含最优解
           for(j= 0; j< n; j++)
            N.x[j]=E.x[j];
       N.x[E.s+1]=E.x[i];
       N.x[i]=E.x[E.s+1];
       N.cc=cc;
       N.s= E.s+1;
N.lcost=b;
       N.rcost=rcost;
       Insert(H,N);
     }
   }
  delete(H,E.x);
}//完成结点扩展
DeleteMin(H,E);} //取下一扩展结点
   if (堆已空)  break; //堆已空
 }
 if(bestc==NoEdge)return( NoEdge);    
//无回路
//将最优解复制到v[l:n]
for(i=0;i<n;i++)
  v[i+ 1]=E.x[i];
while (true){ //释放最小堆中所有结点
     delete(H, E. x);
     DeleteMin(H,E);} //取下一扩展结点
     if (堆已空)  break; //堆已空
   }
  return(bestc);
}
回溯算法解批处理作业调度(解空间:排列树):
 
void Flowshop::Backtrack(int i)
{
   if (i > n) {
       for (int j = 1; j <= n; j++)
         bestx[j] = x[j];
       bestf = f;
       }
   else
      for (int j = i; j <= n; j++) {
         f1+=M[x[j]][1];
         f2[i]=((f2[i-1]>f1)?f2[i-1]:f1)+M[x[j]][2];
         f+=f2[i];
         if (f < bestf) {
            Swap(x[i], x[j]);
            Backtrack(i+1);
            Swap(x[i], x[j]);
            }
         f1- =M[x[j]][1];
         f- =f2[i];
         }
}
所以在最坏的情况下,整个算法的计算时间复杂性为O(n!)
 

回溯算法解0-1背包问题(解空间:子集树):
 
template<class Typew, class Typep>
Typep Knap<Typew, Typep>::Bound(int i)
{// 计算上界
   Typew cleft = c - cw;  // 剩余容量
   Typep b = cp;
   // 以物品单位重量价值递减序装入物品
   while (i <= n && w[i] <= cleft) {
      cleft -= w[i];
      b += p[i];
      i++;
      }
   // 装满背包
   if (i <= n) b += p[i]/w[i] * cleft;
   return b;
}
void  backtrack(i)
 {  if( i>n )
        { bestp=cp;
          return; }
 if(cw+w[i]<=c)//x[i]=1
  { cw+=w[i] ;cp+=p[i];    
     backtrack(i+1);
     cw-=w[i] ;cp-=p[i];  }
 if ( bound(i+1)>bestp )
  backtrack(i+1); //x[i]=0
}
由于上界函数Bound()需要O(n)的时间,在最坏的情况下有O(2n)个右儿子结点需要计算上界函数,所以0-1背包问题的回溯算法Backtrack()所需要的时间是O(n2n)。


 

回溯算法解图的m着色问题:
 
void Color::Backtrack(int t)
{
  if (t>n) {
   sum++;
   for (int i=1; i<=n; i++)
     cout << x[i] << ' ';
   cout << endl;
   }
    else
      for (int i=1;i<=m;i++) {
        x[t]=i;
        if (Ok(t)) Backtrack(t+1);
      }
}
bool Color::Ok(int k)
{// 检查颜色可用性
  for (int j=1;j<=n;j++)
    if ((a[k][j]==1)&&(x[j]==x[k])) return false;
  return true;
}
回溯法总的时间耗费是O(m^n *n)
 

回溯算法解最大团问题(解空间:子集树):
 
void Clique::Backtrack(int i)
{// 计算最大团
   if (i > n) {// 到达叶结点
      for (int j = 1; j <= n; j++) bestx[j] = x[j];
      bestn = cn;   return;}
   // 检查顶点 i 与当前团的连接
   int OK = 1;
   for (int j = 1; j < i; j++)
      if (x[j] && a[i][j] == 0) {
         // i与j不相连
         OK = 0;  break;}
   if (OK) {// 进入左子树
      x[i] = 1;  cn++;
      Backtrack(i+1);
      x[i] = 0; cn--;}
   if (cn + n - i > bestn) {// 进入右子树    x[i] = 0;
      Backtrack(i+1);}
}
解最大团问题的回溯算法Backtrack所需的计算时间为O(n2n)。


 

 回溯法的基本思想是:不断用修改过的判定函数Pi只(x1,x2,…,xi)(亦称为限界函数)去测试正在构造中的n元组的部分向量(x1,x2,…,xn).看其是否可能导致最优解。如果判定(x1,x2,…,xn)不可能导致最优解,那么就不再测试可能要测试的mi+1mi+2...mn个向量。

解符号三角形问题的回溯算法Backtrack所需的计算时间为O(n2n)。 

贪心法解最优装载问题:
template<class Type>
void Loading(int x[],  Type w[], Type c, int n)
{
        int *t = new int [n+1];
        Sort(w, t, n);
        for (int i = 1; i <= n; i++) x[i] = 0;
        for (int i = 1; i <= n && w[t[i]] <= c; i++) {x[t[i]] = 1; c -= w[t[i]];}
}
算法所需的计算时间为 O(nlogn)

算法是指解决问题的一种方法或一个过程。算法是若干指令的有穷序列,满足性质:(1)输入 (2)输出 (3)确定性  (4)有限性:

问题的计算时间下界为?(f(n)),则计算时间复杂性为O(f(n))的算法是最优算法。

1. 什么是动态规划法:将问题分解成多级或许多子问题,然后顺序求解子问题,前一个子问题的解为后一个子问题的求解提供有用的信息。
2. 什么是贪心法:从问题某一初始或推测值出发,一步步的攀登给定目标,尽可能快的去逼近更好的解,当达到某一步不能继续时终止。

3. 什么是分支定界法:对有约束条件的最优化问题所有可行解定向、适当地进行搜索。将可行解空间不断地划分为越来越小的子集(分支),并为每一个子集的解计算一个上界和下界(定界)。
5、什么是NP类问题:
NP={L|L是一个能在多项式时间内被一台NDTM图灵机所接受的语言},其中NDTM是非确定性图灵机。或者可说:NP是所有可在多项式时间内用不确定的算法求解的判定问题的集合。对于NP类,相当于要求验证模块在多项式时间内完成对应NDTM,有非确定性算法。

1. 算法的分类:1)(数值算法 ) 2) 非数值算法
2. 算法的描述:1)自然语言描述 2)(流程图描述) 3)程序语言描述
3. 算法的分析标准:1) 时空观念 2 )(发展观念) 3) 设计观点 4) 交流的观点

设计动态规划算法的步骤。
(1)找出最优解的性质,并刻划其结构特征。
(2)递归地定义最优值。
(3)以自底向上的方式计算出最优值。
(4)根据计算最优值时得到的信息,构造最优解。

动态规划法求矩阵连乘问题:
void MatrixChain(int *p,int n,int **m,int **s)
{for (int i = 1; i <= n; i++) m[i][i] = 0;
   for (int r = 2; r <= n; r++)
   for (int i = 1; i <= n - r+1; i++) {
   int j=i+r-1;
   m[i][j] = m[i+1][j]+ p[i-1]*p[i]*p[j];
   s[i][j] = i;
   for (int k = i+1; k < j; k++) {
   int t = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
   if (t < m[i][j]) { m[i][j] = t; s[i][j] = k;}
              }
          }
}
因此算法的计算时间上界为O(n3)。算法所占用的空间显然为O(n2)。

1.简述算法的五个重要的特征。:①有穷性: 一个算法必须保证执行有限步之后结束;②确切性: 算法的每一步骤必须有确切的定义; ③输入:一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定义了初始条件;④输出:一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;⑤可行性: 算法原则上能够精确地运行,而且人们用笔和纸做有限次运算后即可完成。备注: 算法可以没有输入。因为有些算法中包含了输入,如随机产生输入。

2.简答贪心算法的基本元素:①贪心选择性质:所谓贪心选择性质指所求问题的整体最优解可以通过一系列局部最优的选择达到。②最优子结构性质:当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构。


3.简述动态规划算法的基本思想和基本步骤以及动态规划问题的特征。
动态规划的实质是分治思想和解决冗余,因此,动态规划是一种将问题实例分解为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优化问题的算法策略。
按以下几个步骤进行:
①分析最优解的性质,并刻划其结构特征。
②递归地定义最优值。
③以自底向上的方式或自顶向下的记忆化方法(备忘录法)计算出最优值。
④根据计算最优值时得到的信息,构造一个最优解。
动态规划问题的特征:动态规划算法的有效性依赖于问题本身所具有的两个重要性质:最优子结构性质和子问题重叠性质。
1、最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
2、重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。

4.简述回溯算法的基本思想及解题步骤。
回溯法的基本思想:确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。换句话说,这个结点不再是一个活结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。(9分)
运用回溯法解题通常包含以下三个步骤:
(1)针对所给问题,定义问题的解空间;(2分)
(2)确定易于搜索的解空间结构;(2分)
  (3)以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。

5.简述分治算法的基本思想及基本步骤。
分治法的基本思想:对于一个输入规模为 的问题,若该问题容易的解决,则直接解决,否则将其分解为 个规模较小的子问题,这些子问题相互独立且与原问题形式相同,递归求解这些子问题,然后将各个子问题的解合并,得到原问题的解。(9分)
分治法在每一层递归上由以下三个步骤组成:
①划分:将原问题分解为若干规模较小、相互独立、与原问题形式相同的子问题;(2分)
②解决:若子问题规模较小,则直接解决;否则递归求解各个子问题。(2分)
③合并:将各个子问题的解合并为原问题的解。(2分)
6.分支限界法的基本思想:
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一棵有序树,常见的有排列树。在搜索问题的解空间树时,分支限界法与回溯法对当前扩展结点所使用的扩展方式不同。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,那些导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被子加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所求的解或活结点表为空时为止。

7.贪心算法的基本思想如下:从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。当达到某算法中的某一步不能再继续前进时,算法停止,得到问题的一个解。

贪心算法存在问题:1. 不能保证求得的最后解是最佳的;2. 不能用来求最大或最小解问题;3. 只能求满足某些约束条件的可行解的范围。

8.动态规划与分治法的异同:
相同点:动态规划法与分治法类似,它们都是将问题划分为更小的、相似的子问题,并通过求解子问题产生一个全局最优解。
不同点:而分治法中的各个子问题是独立的 (即不包含公共的子子问题),因此一旦递归地求出各子问题的解后,便可自下而上地将子问题的解合并成问题的解。但不同的是,如果各子问题是不是相互独立的,则分治法要做许多不必要的工作,重复地解公共的子问题。

9.分支限界法与回溯法的异同:
 分支限界法类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使用某一目标函数值达到极大或极小的解,即在某种意义下的最优解。因此, 回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T。

10.证明装载问题具有贪心选择性质.
证明:设集装箱已依其重量从小到大排序, (x1,x2, …,xn)是最优装载问题的一个最优解.又设1.当k=1时, (x1,x2, …,xn)是一个满足贪心选择性质的最优解.
2.当k>1时,取y1=1;yk=0;yi=xi,1<I≤N,I≠K
因此,(y1,y2, …,yn)是所给最优装载问题的一个可行解
另一方面,由
(y1,y2, …,yn)是一个满足贪心选择性质的最优解
所以,最优装载问题具有贪心选择性质

 
Hanoi塔问题:
void hanoi(int n, int a, int b, int c)
   {
       if (n > 0)
       {
          hanoi(n-1, a, c, b);
          move(a,b);
          hanoi(n-1, c, b, a);
       }
   }
 
6.3 递归算法到非递归算法的转换
      递归算法有两个基本特性:一是递归算法是一种分而治之的、把复杂问题分解为简单问题的求解问题方法,对求解某些复杂问题,递归算法分析问题的方法是十分有效的;二是递归算法的时间/空间效率通常比较差
      因此,对求解某些问题时,我们希望用递归算法分析问题,用非递归算法具体求解问题。这就需要把递归算法转换为非递归算法。

 
把递归算法转化为非递归算法有如下三种基本方法:
      (1)通过分析,跳过分解过程,直接用循环结构的算法实现求值过程
      (2)自己用模拟系统的运行时栈,通过分析只保存必须保存的信息,从而用非递归算法替代递归算法。
      (3)利用保存参数,由于栈的后进先出特性吻合递归算法的执行过程,因而可以用非递归算法替代递归算法。
6.3.1 利用循环跳过分解过程从而消除递归
       采用循环结构消除递归。求解Fibonacci数列的算法如下:
   int Fib(int n)
   {    int i,f1,f2,f3;
         if (n==1 || n==2)  return(n);
         f1=1;f2=2;
         for (i=3;i<=n;i++)
         {    f3=f1+f2;
               f1=f2;f2=f3;
         }
         return(f3);
   }
 
6.3.2  模拟系统的运行时栈消除递归
        下面讨论直接使用栈保存中间结果,从而将递归算法转化为非递归算法的过程。
 
仍以例6.1的递归算法进行讨论,其递归模型有一个递归出口和一个递归体两个式子,分别称为(1)式和(2)式。设计一个栈,其结构如下:
   struct
   { int vn;   /*保存n*/
int vf;   /*保存fun1(n)*/
int tag;  /*标识是否求出fun1(n),1:未求出,0:已求出*/
   } St[MaxSize]; /*定义栈*/
 
 
计算fun1(5)之值的过程如下:
(5,*,1)进栈;
while (栈不空)
{    if (栈顶元素未计算出栈顶元素的vf值即St[top].tag==1)
     {     if (栈顶元素满足(1))
    求出对应的St[top].vf,并置St[top].tag=0;
                表示已求出对应的函数值;
else  /*栈顶元素满足(2)*/
    (St[top].vn-1,*,1)进栈;
    }
    else if (栈顶元素已计算出栈顶元素的vf值即St[top].tag==0)
          显然栈顶元素由次栈顶元素通过(2)式分解得到的,回过来
          由栈顶元素的vf值计算出次栈顶元素的vf值并退栈;
    if  (栈中只有一个已求出vf值的元素)
        退出循环;
}
St[0].vf即为所求的fun1(n);
 
对应的非递归fun1算法如下:
  int fun1(int n)
  {     top++;       /*初值进栈*/
        St[top].vn=n; St[top].tag=1;
        while (top>-1)       /*栈不空时循环*/
        {           if (St[top].tag==1)    /*未计算出栈顶元素的vf*/
        {     if (St[top].vn==0) /*(1)*/
  {     St[top].vf=1;
        St[top].tag=0;
  }
  else /*(2)*/
  {    top++;
        St[top].vn=St[top-1].vn-1; St[top].tag=1;
  }
        }
 
else if (St[top].tag==0)      /*已计算出vf*/
            {    St[top-1].vf=St[top-1].vn*St[top].vf;  /*(2)*/
      St[top-1].tag=0;
      top--;
              }
/*栈中只有一个已求出vf的元素时退出循环*/
                        if (top==0 && St[top].tag==0)
    break;
}
return(St[top].vf);
    }

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多