论坛首页 综合技术论坛

函数式编程疑问

浏览 3579 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-08-04  
FP
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
	<script language="JavaScript" type="text/JavaScript">
	function oldRecursion(n){
		var j=1;
		for(var i=n;i>0;i--){
			j*=i;
		}
		return j;
	}
	function recursion(n){
		return n==1?1:n*recursion(n-1);
	}
	function tailRecursion(n, a){
	    a = a||1;
	    return n==1?a:tailRecursion(n-1, a*n);
	}
	
	
//	-----------------------------------------------------------------------------------------
	var start=new Date();
	for(var i=0;i<100000;i++){
		r1=oldRecursion(3);
	}
	var end=new Date();
	document.write(""+(end-start)+"<br/>");
//	-----------------------------------------------------------------------------------------
	var start=new Date();
	for(var i=0;i<100000;i++){
		r2=recursion(3);
	}
	var end=new Date();
	document.write(""+(end-start)+"<br/>");
//	-----------------------------------------------------------------------------------------
	var start=new Date();
	for(var i=0;i<100000;i++){
		r3=tailRecursion(3);
	}
	var end=new Date();
	document.write(""+(end-start)+"<br/>");
	document.write(r1+" "+r2+" "+r3);
//	-----------------------------------------------------------------------------------------
	</script>
</html>


我在jstang看到过一个帖子“阶乘(factorial)&尾递归(Tail Recursion)http://jstang.5d6d.com/thread-1750-1-1.html
里面讲“效率上和循环迭代、阶乘改进算法相当甚至稍胜出(ie6,firefox2,safari3),普通递归的效率最为底下,且需要深入堆栈”
但是测试表明 尾递归效率最低啊
结果是:
  • 循环:516
  • 递归:1000
  • 尾递归:1046

js语言精髓与编程实践里面也说啦“然而不幸的是。目前已知的javascript 的解释环境中并不支持这种特性(尾递归)。因此,我们在这里讨论函数室式时,可以说“能够通过函数递归来消灭循环语句”,但在不支持尾递归(及其优化技术)的javascript中,这种实现将以大量栈和内存消耗为代价”

那到底尾递归该如何理解呢?
我的测试正确的吗?
   发表时间:2008-08-07  
换一个支持尾递归的测试
0 请登录后投票
   发表时间:2008-08-07  
我已经不是第一次看到自称程序员连尾递归都不知道的人了
真的,很好奇
这些人们上大学的时候都干嘛呢?
怎么学会编程的?必定有些什么我等资质愚钝者所不解的秘诀吧?
0 请登录后投票
   发表时间:2008-08-07  
引用
尾递归是针对传统的递归算法而言的, 传统的递归算法在很多时候被视为洪水猛兽. 它的名声狼籍, 好像永远和低效联系在一起.
尾递归就是从最后开始计算, 每递归一次就算出相应的结果, 也就是说, 函数调用出现在调用者函数的尾部, 因为是尾部, 所以根本没有必要去保存任何局部变量. 直接让被调用的函数返回时越过调用者, 返回到调用者的调用者去.

以下是具体实例:

线性递归:
long Rescuvie(long n) {      
  return(n == 1) ? 1 : n * Rescuvie(n - 1);      
} 
    

尾递归:
long TailRescuvie(long n, long a) {      
  return(n == 1) ? a : TailRescuvie(n - 1, a * n);        
}      

long TailRescuvie(long n) {//封装用的      
  return(n == 0) ? 1 : TailRescuvie(n, 1);   
}  
   

当n = 5时
对于线性递归, 他的递归过程如下:
Rescuvie(5)  
{5 * Rescuvie(4)}
{5 * {4 * Rescuvie(3)}}
{5 * {4 * {3 * Rescuvie(2)}}}
{5 * {4 * {3 * {2 * Rescuvie(1)}}}}
{5 * {4 * {3 * {2 * 1}}}}
{5 * {4 * {3 * 2}}}
{5 * {4 * 6}}
{5 * 24}
120


对于尾递归, 他的递归过程如下:
TailRescuvie(5)
TailRescuvie(5, 1)
TailRescuvie(4, 5)
TailRescuvie(3, 20)
TailRescuvie(2, 60)
TailRescuvie(1, 120)
120


很容易看出, 普通的线性递归比尾递归更加消耗资源, 在实现上说, 每次重复的过程
调用都使得调用链条不断加长. 系统不得不使用栈进行数据保存和恢复.而尾递归就
不存在这样的问题, 因为他的状态完全由n和a保存.

0 请登录后投票
   发表时间:2008-09-16  
楼上各位怎么这样啊?看明白 LZ 想要讨论的问题是什么了吗?
怎么讥笑 LZ 不懂尾递归的有之,引经据典为 LZ 解释尾递归的也有之(LZ 也不容易啊,当了一年多的潜水员,才冒上来发个处女贴,马上就被拍得晕乎乎的,真是世态炎凉啊!呵呵!),稍仔细点看看就应该明白 LZ 想要讨论的是:“为什么我试验下来,尾递归的效率那么低?”

其实仔细想想,尾递归和迭代有多大区别?没多大吧!迭代也要使用堆栈,只不过那上面只放了当前操作数,中间结果放到了变量里,但当没有变量或变量不可变时,尾递归就是最好的选择,它把当前操作数与中间结果都放到了堆栈上(是比迭代多用了点堆栈,但相对的,它没使用变量。),因此,在相同的优化下,我想它们确实应该一样快。

我把 LZ 的代码放到了五个不同的浏览器中(IE7、FF3.01、Safari3.1.2、Opera9.50、Chrome0.2.149.29)执行,结果和楼主的差不多(Chrome 的结果很特别,下面说),这种情况我想是 JS 解释器的实现引起的,LZ 不应该选择 JS 来测试尾递归和迭代的效率。JS 虽然包含许多 FP 的特征,但同时它对命令式的支持也很好,它目标用户(脚本程序员)的头脑也大多是命令式的构成,他们不会也不想用递归来考虑问题,那么实现者当然要把用得最多的“for”好好优化一番,至于递归嘛,有就行了,反正也没人用,最多会用来写几个例子代码,展示一下 JS 的多范式编程能力。当然这只是我的猜测,我并不了解各个浏览器中 Javascript 解释器的具体实现,要完全搞明白也许只能跟踪一下,看看汇编码到底如何。

下面是各浏览器的测试结果:
我计算了 9 的阶乘,而且在尾递归函数中删除了“a = a||1”,直接用(9,1)来调用尾递归函数。所有的浏览器都是打开后拖入测试文档,第一次执行的结果,没有重复执行。

IE
迭代:1297
递归:5985
尾递归:7484
这些值没太大的意义,因为在 IE 的执行过程中,它已经弹出对话框说执行时间太长,问我是否要继续,这样的时间肯定不准了。

FF
迭代:381
递归:596
尾递归:554

Opera
迭代:484
递归:812
尾递归:844

Safari
迭代:312
递归:953
尾递归:985

Chrome
迭代:92
递归:89
尾递归:70
不比不知道,一比吓一跳,Chrome 实在太快了,但三个值非常接近,而且每次都不太一样,一下这个快点,一下那个快点,我不明白为什么会这样!我想加长它的执行时间,用 99 做为参数来调用,这时别的浏览器已经非常慢了,IE 更是等得没耐心,但 Chrome 还有
迭代:445
递归:713
尾递归:514
这样的表现,虽说这时递归版的函数已经溢出,但这个结果实在太让人吃惊了。

还有一个有意思的是,如果我们不让函数返回,也就是删掉每个函数的 return 关键字,这时的结果是:迭代与尾递归没什么变化,但递归版函数所用的时间将大大增加,在所有浏览器中都是这样。
0 请登录后投票
   发表时间:2008-09-16  
上网了解了一番,看来 Javascript 的尾递归与迭代一样快的时代已经到来,看这里:
http://ejohn.org/blog/javascript-performance-rundown/

John Resig 介绍了三个浏览器(Javascript)测试套件:
1、SunSpider(只测试 Javascript 性能,objects, function calls, math, recursion 等,但不包含 rendering or DOM manipulation 。);
2、V8 Benchmark(只测试 Javascript 性能,特别强调递归。);
3、Dromaeo(大量的浏览器特性测试,包括 DOM 与 各种 JS 库。)。

Chrome 中的递归优化得很好,在 V8 Benchmark 测试中大比分领先,与我上面用 LZ 的代码测的情况相符,在 SunSpider 中 Chrome 也只比最新的 TraceMonkey 稍慢,但据说 TraceMonkey 有如此表现还是被递归拖了后腿,它的 recursion tracing 还没准备好,一旦完成,后果将……!哈哈!

LZ 可以去看看 V8 Benchmark 的测试代码。
0 请登录后投票
   发表时间:2008-09-17  
我也认为现在的浏览器对尾递归的优化远远没做到位,未来一定会有改观。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics