- 浏览: 118713 次
- 性别:
- 来自: 广州
-
文章分类
最新评论
-
Kevin_jiang2011:
能直接在,代码里面配置吗?
Spring+CXF配置HTTP代理访问Internet -
xbiji:
不能用啊!!!!!!!!!!!!!!!!!!!!!!1
JQuery获取页面高度,页面宽度,窗口高度,窗口宽度 -
ben_liang:
# <http-conf:proxyAuthoriza ...
Spring+CXF配置HTTP代理访问Internet -
navy0168:
package com;import java.io. ...
解压缩
递归计算向非递归计算转换模板-转自http://mingliangfeng.javaeye.com/blog/201084
- 博客分类:
- java
最近由于工作上的需要,研究了一下递归计算向非递归计算的转换问题。理论上而言,所有递归程序都可以用非递归程序来实现;这种理论的基础是递归程序的计算总能用一颗树型结构来表示。递归计算从求树根节点的值开始,树根节点的值依赖一个或多个子节点的值,子节点的值又依赖下一级子节点的值,如此直至树的叶子节点。叶子节点的值能直接计算出来,也就是递归程序的出口。如下图所示,是递归函数f(x) = f(x-1) + f(x-3), f(x) = 10 (x < 3)当x=4时的递归调用树。简单的递归计算,比如尾部递归,可以直接用循环来转换成非递归;对于复杂的递归,比如递归程序中递归调用点有多个,就是树型结构中一个父节点的值会依赖多个子节点的值,这种情形的递归转换成非递归通常需要借助堆栈加循环的方式。
直接用编程语言中的递归函数实现递归时,在时间和空间上都有可能造成浪费。从时间上来讲,如果递归调用点有多个,会由于中间计算结果没有被保存重用而导致大量的重复计算。比如递归函数f(x) = f(x-1) + f(x-3),计算f(x)时,会先计算f(x-1)的值,在计算f(x-1)时会计算出许多子节点的值,这些值在计算f(x-3)时是可以重用的,但是当退出f(x-1)的调用后都被丢弃了,导致在计算f(x-3)时会重复很多计算。空间上,由于每次递归调用都要保留现场,递归变深时,树会迅速膨胀,占用的空间也会激增。
递归程序转换成非递归程序时有固定的模式可循,将这个模式抽象成通用的模板,就有可能实现递归至非递归程序的自动转换。根据前面的分析,将递归计算用树型结构来表达,计算父节点时,会要求计算出其子节点的值。如果用一个堆栈来表示,则计算父节点时,会先将该父节点压入堆栈,待到其依赖的所有子节点的值都计算出时,再将其弹出并计算其值;在计算其子节点时依照相同的原理,直至遇到叶子节点。考虑递归函数f(x) = f(x-1) + f(x-3), f(x) = 10 (x < 3), x 的初始值为4,用如图所示的数据结构表示,往pre方向是堆栈的底部,往next的方向是堆栈的顶部,堆栈指针指向当前正在计算的节点。计算第一个子节点的堆栈情况如下所示:
计算f(4)时会先计算f(3),计算f(3)时会先计算f(2),f(2)=10,是叶子节点,那么f(3)的第一个子节点f(2)计算完成,开始计算f(3)的第二个节点f(0):
f(0)=10,至此f(3)的两个子节点都计算完成,可以求出f(3) = f(2) +f(0) = 20。f(3)求出后,相当于f(4)的第一个子节点被计算出来了,开始计算f(4)的第二个子节点的值f(1):
f(1)=10,那么f(4)的两个子节点都已经被计算出来了,可以直接求f(4) = f(3) + f(1) = 30。由于此时已到堆栈底部,计算完成,弹出f(4)的计算结果,即为最终结果。
以上的示例虽然简单,但足以总结出如下规律:
结算某个节点的值时,有两种情况:
* 1) 该节点为叶子节点,可以直接求出其值;
* 2) 该节点为非叶子节点,需要先计算其依赖的子节点。该节点可能依赖多个子节点,子节点一个一个地计算,当最后一个子节点计算出来后,该节点的值可以顺利求出;
* 3) 在2)中计算子节点时,要么其子节点为叶子节点,进入1),值能直接求出;否则,又进入2)计算下一级子节点。每个节点完成计算时都需要监测自己是不是父节点的最后一个子节点。如果是,就可以直接求父节点的值;如果非,就继续求下一个兄弟节点,即父节点的下一个子节点的值。
最后一条很关键,当每个节点完成计算时,它甚至没有信息可以判断前面哪个节点是其父节点!子节点没有父节点的信息,但父节点通过设置状态可以记录当前是在计算哪个子节点。对于特定的递归程序,每个父节点依赖的子节点的数目是确定的,就是递归调用点的数目;比如f(x) = f(x-1) + f(x-3),递归调用点为2,可以确定每个父节点依赖2个子节点。
基于前面如图所示的堆栈结构,计算父节点时会依次计算其子节点,其子节点会依次紧跟在父节点之后压入堆栈。设每个节点的初始状态为0,计算完成后状态为3;则刚进入递归时树根节点的状态为0,此时把该节点当作父节点,开始计算其第一个子节点,父节点的状态设置成1,表示在计算它的第一个子节点。第一个子节点计算完成后,自身状态为3,回溯检查,发现前面的节点状态为1,可以确定其为父节点,并且仅计算出了第一个子节点的值,还需要计算第二个子节点的值。这时将该父节点的状态设置成2,表示在计算它的第二个子节点。第二个子节点计算完成自身状态变为3后,回溯检查,至第一个子节点,第一个子节点状态为 3,再回溯,至状态为2的父节点,由于该父节点总共只有两个依赖的子节点,所以可以断定该父节点的最后一个子节点已经完成了计算,可以计算该父节点的值了。根据前面的数据结构可知该父节点的两个子节点依次紧跟其后,可以直接依次取出计算父节点的值。当该父节点完成计算后,状态变为3,这个时候就该回溯去寻找该父节点的父节点了。当最后一个父节点找到并完成计算后,递归计算就完成了。如上所示的堆栈中保存的是递归参数的值,在实际程序中,我们还需要空间来保存递归参数对应的计算出来的值,一般一个HashMap就足够了。
经过上面的分析,应该能很容易地将例子中的递归程序转换成非递归程序。我们可以清楚的从代码中看出既定的规律,进而归纳出一个通用的递归向非递归程序转换的模板。
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); }
发表评论
-
Java网站
2010-12-11 02:15 710转自:http://txxm.iteye.com/blog/5 ... -
一些反射常用的工具类
2010-09-17 13:59 1669拷贝属性: 1.org.apache.commons.bean ... -
Java安全管理器
2010-08-17 11:00 1633转:http://yuanyong.iteye.com ... -
验证码
2010-07-30 10:40 1119转自:http://flattop.iteye.com/blo ... -
jenlp110 的 一道面试题
2010-07-28 14:01 883转自:http://www.iteye.com/topic/5 ... -
Web前端开发性能优化
2010-07-28 11:01 958参考资料:http://developer.yahoo.com ... -
比较java写text文件的性能
2010-07-20 11:13 1012转自:http://hi.baidu.com/shmily_s ... -
dom4j读取xml:转http://shaqiang32.javaeye.com/blog/246539
2010-06-25 16:52 883SAXReader reader = new SAXRea ... -
反射-调用有参数和无参数的方法
2010-06-19 13:48 9115package com.cn.service; /** ... -
excel导出2
2010-02-05 12:37 906使用POI生成Excel文件,可以自动调整excel列宽等 ... -
多线程-条件变量: 转自:http://huanyue.javaeye.com/blog/560975
2010-01-14 17:56 688条件变量是Java5线程中很重要的一个概念,顾名思义,条件变量 ... -
多线程交互-障碍器 转自:http://huanyue.javaeye.com/blog/560978
2010-01-14 17:24 790Java5中,添加了障碍器类,为了适应一种新的设计需求,比如一 ... -
线程交互-转自:http://huanyue.javaeye.com/blog/560904
2010-01-14 17:13 897一、线程交互的基础知识 SCJP所要求的线程交互知识点需要 ... -
excel 导出
2010-01-11 15:06 1136public static InputStream Expor ...
相关推荐
卡尔曼滤波器通过递归地更新状态估计,动态地调整权重以平衡系统模型和测量之间的不确定性。 该代码是卡尔曼滤波学习(7)http://t.csdnimg.cn/4ubGO使用C语言实现的模拟计算。假设小车是匀速运动,运动中行程和速度均...
这通常通过递归函数实现。 5. **权限管理**:在Android中,读取外部存储(如SD卡)需要申请`READ_EXTERNAL_STORAGE`权限。从Android 6.0(API级别23)开始,还需要在运行时动态请求权限。 6. **事件监听**:...
海洋FFT 真实的海浪仿真,主要基于,使用OpenGL计算着色器。 在签出演示视频。 为了追求更高的真实性,使用了与原始论文中提到的菲利普斯光谱。 FFT的Stockham公式用于更好地将问题映射到GPU并避免香草Cooley-Tukey...
它支持多种选项和参数,可以实现单个文件的下载、断点续传、递归下载整个目录甚至整个网站,以及通过代理服务器进行下载。下面我们将详细讲解这些功能及其用法。 1. **下载单个文件** 使用`wget`的基本语法是提供...
1. **斐波那契数列**:编写一个函数计算斐波那契数列的第 n 项。 2. **回文字符串判断**:给定一个字符串,判断其是否为回文字符串。 3. **冒泡排序**:实现冒泡排序算法。 4. **数组最大值与最小值**:给定一个整数...
在本文中,我们将深入探讨如何使用Lua编程语言创建一个麻将胡牌表的计算方法。麻将是一种深受人们喜爱的策略游戏,而胡牌是游戏的核心部分。理解胡牌的计算方式对于设计麻将游戏算法至关重要。 首先,我们要明确的...
无回朔的递归下降分析的设计与实现; (1)输入串应是词法分析的输出二元式序列,即某算术表达式“专题 1”的输出结果,输出为输入串是否为该文法定义的算术表达式的判断结果; (2)递归下降分析程序应能发现简单的...
判断青蛙过河leetcode 目录 leetcode 打印不重复元素 √ 4、二维数组中查找目标值(从右上角开始查找)√ ...数据分类(int转16进制) √ 设计模式 util doc 基础扎实全面:编程语言、数据结构、算法等 高质量代
[5.2.1]--402递归的应用:任意进制转换.srt
[5.2.1]--402递归的应用:任意进制转换.mp4
C#窗体应用程序的图形化界面设计以及GDI+绘图的一些基本指示,通过制作各种类型的分形树增强对于递归的理解,在创造分形图形的过程中感受编程的快乐 1.制作不同类型的分形图形(本次演示的是两种不同类型的分形树) 2....
在实现中,我们使用了递归函数merge来实现分治算法,该函数将数组分成两个子数组,然后递归调用自身,直到找到整个数组的最大值与最小值。同时,我们也实现了非递归的方法来实现分治算法,通过比较两个方法的时间...
疫情期间无聊帮朋友做的一个简易程序,博客讲解思路链接为https://www.cnblogs.com/xiao-qi-w/p/13031637.html
动态编程递归无重复每个子问题只能评估一次天真的递归方法由于重复而花费指数时间自上而下的回忆这是递归的深度优先搜索实现缓存重复工作自下而上基于依赖图将递归转换为依赖图它是有向无环图您可以使用拓扑排序对...
10# python3 webDirScan.py -u http://example.com/abc/ --pathlist dict.txt -v# python3 webDirScan.py --urllist /example/urllist.txt --pathlist dict.txt -v --thread 20特征支持多线程扫描支持递归扫描,...
人工温度前效 Esse projeto implentainteligê... 长期学习,LSTM,长期递归神经网络(RNN)在美国的深度学习。 教程: 执行脚本:“ python prever.py” 您必须先完成10个以上的预览,然后再进行8个预览。 示例:
利用 vue 的组件递归,动态的渲染不知道层级的菜单 并 可以通过点击菜单动态加载子菜单 的 vue demo
递归检查 DNS 区域导出 (AXFR) 的脚本。 安装 Debian/Ubuntu 所需的软件包: $ sudo apt-get install php5-cli $ wget ...
D5校验工具,是一种应用程序设计允许用户生成文件的校验和的任何文件或字符串(MD5/SHA哈希)。它也可以是有用的检查一个可执行文件,如果是合法的,换句话说,如果它是...http://blog.sina.com.cn/manction 2012.05.18
- `wget -r -p -k -nH --cut-dirs=3 http://example.com/subdir`:递归下载子目录,并转换链接为本地格式。 **3. 高级使用示例:** - `wget --load-cookies cookies.txt --save-cookies cookies.txt --keep-...