上一篇文章对递归向非递归转换的原理和过程作了介绍,本篇谈谈具体的代码实现。还是考虑上一篇文章中的递归例子: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框架里面即可:
根据上面的分解,递归至非递归的转换模板就已经很清晰了,可以轻易的用模板技术,比如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); }