递归计算向非递归计算转换模板 -- 续

上一篇文章对递归向非递归转换的原理和过程作了介绍,本篇谈谈具体的代码实现。还是考虑上一篇文章中的递归例子:f(x) = f(x-1) + f(x-3), f(x) = 10 (x < 3)。用上文分析出来的规律,其实现如下:

  public static double nonRecursion(double x) {
    double initValue = x;
    final int endFlag = 3; // count of branches plus 1   
    Map<Double, Double> values = new HashMap<Double, Double>();
    StackItem ci = new StackItem(initValue);
    while (ci != null) {
      switch (ci.flag) {
      case 0:
        x = ci.number;
        if (x < 3) { // exit of recursion   
          values.put(x, 10.0);
          ci.flag = endFlag;
        } else {
          ci.flag = ci.flag + 1;
          StackItem sub;
          if (ci.next == null) {
            sub = new StackItem();
            sub.pre = ci;
            ci.next = sub;
          } else {
            sub = ci.next;
          }
          sub.number = x - 1; // branch one   
          if (values.containsKey(sub.number)) {
            sub.flag = endFlag;
          } else {
            sub.flag = 0;
          }
          ci = sub;
        }
        break;
      case 1:
        x = ci.number;
        ci.flag = ci.flag + 1;
        StackItem sub;
        if (ci.next.next == null) {
          sub = new StackItem();
          sub.pre = ci.next;
          ci.next.next = sub;
        } else {
          sub = ci.next.next;
        }
        sub.number = x - 3; // branch two   
        if (values.containsKey(sub.number)) {
          sub.flag = endFlag;
        } else {
          sub.flag = 0;
        }
        ci = sub;
        break;
      case 2: // two sub items are ready, calculating using sub items   
        x = ci.number;
        double f1 = values.get(ci.next.number);
        double f2 = values.get(ci.next.next.number);
        double result = f1 + f2; // recursive body
        values.put(x, result);
        ci.flag = endFlag;
        break;
      case endFlag: // current item is ready, back to previous item           
        ci = ci.pre;
        break;
      }
    }
    return values.get(initValue);
  }

 

其中本地的Map类型的变量values用来存放递归因子和它对应的计算出来的值。堆栈是用双向链表来实现的,双向链表非常方便向前回溯和向后取得父节点的子节点。双向链表的item类型为StackItem,它除了拥有向前和向后的指针外,还包含当前递归因子的值和状态。其结构如下:

public class StackItem {
  
  public double number;
  
  public int flag;
  
  public StackItem pre = null;
  
  public StackItem next = null;
  
  public StackItem() {
    this(0);
  }
  
  public StackItem(double number) {
    this.number = number;
    this.flag = 0;
  }
  
}

 

在本机上运行了一下,由于转换后的非递归程序保存了中间递归因子的计算值,它比直接用java语言实现递归在时间上要快不少。递归参数越大时,其优势越明显。

 

上一篇文章提到要把分析出来的规律总结成一个通用模板,其实从上面的实现代码中可以看出,模板的框架已经浮现出来了。根据递归函数中的递归调用点,可以确定计算完成时的状态值,进而确定循环体中case的个数。其中每个case里面的代码块都具备可以模板化的特点。总体来说,将一个递归函数分解成如下几块,安插至case框架里面即可:

  1. 递归调用出口(exit of recursion):在当前节点状态为0,即初次遍历至该节点时,会检查递归因子的值。如果能直接计算出来,则计算出来并将状态置为完成;否则,状态递增1,开始计算第一个子节点的值;
  2. 递归调用点(branch),包括递归因子的收敛公式,根据其在递归体中出现的顺序安插至case代码体中去;
  3. 递归体(recursive body),当节点的所有子节点都完成计算后,根据递归体计算当前节点的值。代码的位置位于倒数第二个case中。

根据上面的分解,递归至非递归的转换模板就已经很清晰了,可以轻易的用模板技术,比如velocity或freemarker来实现。

 

下面再用上面的规律对一个递归函数进行非递归转换,以验证模板的通用性:

递归函数:f(x) = ( f(x-1) + f(x-3) + x) / f(x-2), f(x) = 10 (x < 3)

附上的代码是对以上函数的递归和非递归的java实现:

  public static double recursion(double x) {
    if (x < 3)
      return 10.0;

    double f1 = recursion(x - 1);
    double f2 = recursion(x - 3);
    double f3 = recursion(x - 2);
    return (f1 + f2 + x) / f3;
  }

  public static double nonRecursion(double x) {
    double initValue = x;
    final int endFlag = 4; // count of branches plus 1
    Map<Double, Double> values = new HashMap<Double, Double>();
    StackItem ci = new StackItem(initValue);
    while (ci != null) {
      switch (ci.flag) {
      case 0:
        x = ci.number;
        if (x < 3) { // exit of recursion
          values.put(x, 10.0);
          ci.flag = endFlag;
        } else {
          ci.flag = ci.flag + 1;
          StackItem sub;
          if (ci.next == null) {
            sub = new StackItem();
            sub.pre = ci;
            ci.next = sub;
          } else {
            sub = ci.next;
          }
          sub.number = x - 1; // branch one
          if (values.containsKey(sub.number)) {
            sub.flag = endFlag;
          } else {
            sub.flag = 0;
          }
          ci = sub;
        }
        break;
      case 1:
        x = ci.number;
        ci.flag = ci.flag + 1;
        StackItem sub1;
        if (ci.next.next == null) {
          sub1 = new StackItem();
          sub1.pre = ci.next;
          ci.next.next = sub1;
        } else {
          sub1 = ci.next.next;
        }
        sub1.number = x - 3; // branch two
        if (values.containsKey(sub1.number)) {
          sub1.flag = endFlag;
        } else {
          sub1.flag = 0;
        }
        ci = sub1;
        break;
      case 2:
        x = ci.number;
        ci.flag = ci.flag + 1;
        StackItem sub2;
        if (ci.next.next.next == null) {
          sub2 = new StackItem();
          sub2.pre = ci.next.next;
          ci.next.next.next = sub2;
        } else {
          sub2 = ci.next.next.next;
        }
        sub2.number = x - 2; // branch three
        if (values.containsKey(sub2.number)) {
          sub2.flag = endFlag;
        } else {
          sub2.flag = 0;
        }
        ci = sub2;
        break;
      case 3: // three sub items are ready, calculating using sub items
        x = ci.number;
        double f1 = values.get(ci.next.number);
        double f2 = values.get(ci.next.next.number);
        double f3 = values.get(ci.next.next.next.number);
        values.put(x, (f1 + f2 + x) / f3); // recursive body
        ci.flag = endFlag;
        break;
      case endFlag: // current item is ready, back to previous item           
        ci = ci.pre;
        break;
      }
    }
    return values.get(initValue);
  }

 

 

你可能感兴趣的:(JavaScript,算法,cache,F#,Ruby)