JavaScript的递归之递归与循环示例介绍


Posted in Javascript onAugust 05, 2013

递归与循环

对于不同类型的需要重复计算的问题,循环和递归两种方法各有所长,能给出更直观简单的方案。另一方面,循环和递归的方法可以互相转换。任何一个循环的代码都可以用递归改写,实现相同的功能;反之亦然。在不失去其普遍性的前提下,可以把循环和递归分别用下列伪代码概括。

伪代码格式说明:循环采用while形式;变量不加定义;赋值用:=;条件表达式和执行的语句都写成函数的形式,圆括号内写上相关的值。其他语法方面,尽量接近Javascript的规范。

//pseudo code of a loop 
//while形式 
function loop(arguments){ 
//结果的初始值 
result:=initial_value; while(condition(variable, arguments)){//循环条件,可能只需arguments,也可能为了方便引入循环变量 
//计算结果。参数包括之前的结果、当前循环变量和外部变量 
result:=calculate(result, variable, extern_variables); 
//影响函数的外部环境,即修改外部变量 
changeStatus(result, variable, extern_variables); 
//执行完循环体中的语句后,修改参数或循环变量。 
modify_arguments_variable(arguments, variable); 
} 
//返回结果 
return result; 
}

同样我们给出递归函数的伪代码。
//pseudo code of a recursion 
function recursion(arguments){ 
//以下代码为控制函数重复调用的结构部分。 
//获得再次调用此函数的新的参数,可能为多组arguments值。 
//对应于循环中的condition(variable, arguments)和modify_arguments_variable(arguments, variable)。 
new_arguments:=conditional_get_next(arguments); 
//对新参数的每一组,调用函数自身。 
results:=recursion(new_arguments); //以下的代码为每次调用都运行的功能部分 
//计算结果。涉及到之前的结果、当前循环变量和外部变量。 
//对应于循环中的result:=calculate(result, variable, extern_variables)。 
result:=calculate(arguments, extern_variables); 
result:=combine(result, results); 
//影响函数的外部环境,即修改外部变量 
changeStatus(result, arguments, extern_variables); 
return result; 
}

籍比较两段代码,可以看出循环和递归具有相似的构成,通过改变顺序和适当的变换,任何循环都可以用递归的方式实现。程序简单时,这种转换很容易看出。比如下面这个简单的累计求和函数:
//loop 
function sum(num){ 
var result=1; 
while (num>1){ 
result+=num; 
num--; 
} 
return result; 
}

对应的递归形式:
//recursion 
function sum2(num){ 
if (num>1){ 
return num+sum(num-1); 
}else{ 
return 1; 
} 
}

反之,大部分递归程序也可以直接由循环来实现。下面是求最大公约数的循环形式的函数。
function gcd2(a, b){ 
var temp; 
if (a<b){ 
temp=a; 
a=b; 
b=temp; 
} 
var c=a%b; 
while (c!==0){ 
a=b; 
b=c; 
c=a%b; 
} 
return b; 
}

不过,从递归到循环的转换并不是都这么容易。递归伪代码中的产生再次调用此函数的新参数部分

new_arguments:=conditional_get_next(arguments);

较之循环的对应部分更为灵活。可以按照新产生的参数组数(函数需要的所有参数为一组)将递归分为两类。第一类为参数组数固定,该递归可以转换为循环,比如斐波那契数列和最大公约数的例子;第二类为参数组数不确定——就像在遍历一个图或树的时候那样,每个点有任意个相邻的点——该递归不能直接转换为循环。

因为循环只能做一维的重复,而递归可以遍历二维的结构。比如一棵树中,一个节点既有它的子节点,也有同级的节点,简单的一维循环不能够在两个方向上遍历。

但是如果我们在循环中借助某种数据结构记住有关节点位置的一些信息,第二类递归也可以用循环实现。

我们再通过一个例子来实践上面观察得出的结论。HTML5为Document和Element新定义了一个方法getElementsByClassName(names),返回具有给定class值的所有elements。包括Firefox3在内的一些浏览器已经支持该方法。下面我们先用递归的方法给出一个功能较弱的版本,然后再用循环的方法重写它。

var getElementsByClass={}; //elem为一个HTMLElement 
//name为单个class名 
//返回包含elem下所有class属性包含给定名称的element的数组 
getElementsByClass.recursion1=function (elem, name){ 
var list=[]; 
function getElements(el){ 
if (el.className.split(' ').indexOf(name)>-1){ 
list.push(el); 
} 
for (var i=0, c=el.children; i<c.length; i++){ 
getElements(c[i]); 
} 
} 
getElements(elem); 
return list; 
}

如前所述,在循环中为了记住节点的位置信息,我们需要一个能实现以下方法的数据结构。

push(object) //写入一个对象。

objectpop() //读出最近写入的一个对象,并将其从数据结构中删除。

objectget() //读出最近写入的一个对象,不改变数据结构的内容。

堆栈正是这样一个后进先出的数据结构。Javascript中的Array对象支持前两种方法,我们在为其增加第三个方法即可。

采用循环的版本:

getElementsByClass.loop1 = function(elem, name){ 
//use a js array as the basis of a needed stack 
var stack = []; 
stack.get = function(){ 
return stack[stack.length - 1]; 
} var list = []; 
//the business logic part. put the eligible element to the list. 
function testElem(el){ 
if (el.className.split(' ').indexOf(name) > -1) { 
list.push(el); 
} 
} 
//check the root element 
testElem(elem); 
//initialize the stack 
stack.push({ 
pointer: elem, 
num: 0 
}); 
var parent, num, el; 
while (true) { 
parent = stack.get(); 
el = parent.pointer.children[parent.num]; 
if (el) {//enter a deeper layer of the tree 
testElem(el); 
stack.push({ 
pointer: el, 
num: 0 
}); 
} 
else {//return to the upper layer 
if (stack.pop().pointer === elem) { 
break; 
} 
else { 
stack.get().num += 1; 
} 
} 
} 
return list; 
}

归纳起来。所有循环都可以用递归实现;所有递归都可以用循环实现。采用哪种方法,由具体问题下哪种思路更方便直观和使用者的喜好决定。

效率

性能方面,递归不比循环有优势。除了多次函数调用的开销,在某些情况下,递归还会带来不必要的重复计算。以计算斐波那契数列的递归程序为例。求第n项A(n)时,从第n-2项起,每一项都被重复计算。项数越小,重复的次数越多。令B(i)为第i项被计算的次数,则有

B(i)=1; i=n, n-1

B(i)=B(i+1)+B(i+2); i<n-1

这样,B(i)形成了一个有趣的逆的斐波那契数列。求A(n)时有:

B(i)=A(n+1-i)

换一个角度来看,令C(i)为求A(i)时需要的加法的次数,则有

C(i)=0; i=0, 1

C(i)=1+C(i-1)+C(i-1); i>1

令D(i)=C(i)+1,有

D(i)=1; i=0, 1

D(i)=D(i-1)+D(i-1)

所以D(i)又形成一个斐波那契数列。并可因此得出:

C(n)=A(n+1)-1

而A(n)是以几何级数增长,这种多余的重复在n较大时会变得十分惊人。与之相对应的采用循环的程序,有

B(n)=1; n为任意值

C(n)=0; n=0, 1

C(n)=n-1; n>1

因而当n较大时,前面给出的采用循环的程序会比采用递归的程序快很多。

如上一节中的循环一样,递归中的这个缺陷也是可以弥补的。我们只需要记住已经计算出来的项,求较高项时,就可以直接读取以前的项。这种技术在递归中很普遍,被称为“存储”(memorization)。

下面是采用存储技术的求斐波那契数列的递归算法。

//recursion with memorization 
function fibonacci4(n){ 
var memory = []; //used to store each calculated item 
function calc(n){ 
var result, p, q; 
if (n < 2) { 
memory[n] = n; 
return n; 
} 
else { 
p = memory[n - 1] ? memory[n - 1] : calc(n - 1); 
q = memory[n - 2] ? memory[n - 2] : calc(n - 2); 
result = p + q; 
memory[n] = result; 
return result; 
} 
} 
return calc(n); 
}
Javascript 相关文章推荐
javascript 表格排序和表头浮动效果(扩展SortTable)
Apr 07 Javascript
jquery 1.3.2 IE8中的一点点的小问题解决方法
Jul 10 Javascript
JavaScript类型转换方法及需要注意的问题小结(挺全面)
Nov 11 Javascript
jquery 之 $().hover(func1, funct2)使用方法
Jun 14 Javascript
Jquery插件分享之气泡形提示控件grumble.js
May 20 Javascript
JQuery拖动表头边框线调整表格列宽效果代码
Sep 10 Javascript
Jquery的基本对象转换和文档加载用法实例
Feb 25 Javascript
微信小程序--onShareAppMessage分享参数用处(页面分享)
Apr 18 Javascript
node中使用es5/6以及支持性与性能对比
Aug 11 Javascript
JS非行间样式获取函数的实例代码
Jun 05 Javascript
浅谈webpack4.x 入门(一篇足矣)
Sep 05 Javascript
JS实现TITLE悬停长久显示效果完整示例
Feb 11 Javascript
php与js的区别是什么
Aug 05 #Javascript
IE下写xml文件的两种方式(fso/saveAs)
Aug 05 #Javascript
js获取input标签的输入值实现代码
Aug 05 #Javascript
js实现页面转发功能示例代码
Aug 05 #Javascript
Jquery:ajax实现翻页无刷新功能代码
Aug 05 #Javascript
jquery js 重置表单 reset()具体实现代码
Aug 05 #Javascript
JS幻灯片可循环播放可平滑旋转带滚动导航(自写)
Aug 05 #Javascript
You might like
蝙蝠侠:侠影之谜
2020/03/04 欧美动漫
通俗易懂的php防注入代码
2010/04/07 PHP
利用PHP实现短域名互转
2013/07/05 PHP
PHP移动文件指针ftell()、fseek()、rewind()函数总结
2014/11/18 PHP
php+Ajax无刷新验证用户名操作实例详解
2019/03/04 PHP
js 针对html DOM元素操作等经验累积
2014/03/11 Javascript
js判断登录与否并确定跳转页面的方法
2015/01/30 Javascript
JavaScript事件类型中焦点、鼠标和滚轮事件详解
2016/01/25 Javascript
JavaScript实现Java中Map容器的方法
2016/10/09 Javascript
Javascript使用function创建类的两种方法(推荐)
2016/11/19 Javascript
JavaScript的变量声明提升问题浅析(Hoisting)
2016/11/30 Javascript
js eval函数使用,js对象和字符串互转实例
2017/03/06 Javascript
JS实现图片预加载之无序预加载功能代码
2017/05/12 Javascript
JavaScript实现的原生态兼容IE6可调可控滚动文字功能详解
2017/09/19 Javascript
详解SPA中前端路由基本原理与实现方式
2018/09/12 Javascript
微信小程序授权登录解决方案的代码实例(含未通过授权解决方案)
2019/05/10 Javascript
JS中的算法与数据结构之列表(List)实例详解
2019/08/16 Javascript
JavaScript中的this原理及6种常见使用场景详解
2020/02/14 Javascript
利用信号如何监控Django模型对象字段值的变化详解
2017/11/27 Python
利用Python对文件夹下图片数据进行批量改名的代码实例
2019/02/21 Python
python 解决flask uwsgi 获取不到全局变量的问题
2019/12/22 Python
使用python的turtle函数绘制一个滑稽表情
2020/02/28 Python
python 将html转换为pdf的几种方法
2020/12/29 Python
使用gunicorn部署django项目的问题
2020/12/30 Python
字中字效果的实现【html5实例】
2016/05/03 HTML / CSS
canvas 绘图时位置偏离的问题解决
2020/09/16 HTML / CSS
单位实习证明怎么写
2014/01/17 职场文书
离婚协议书怎样才有法律效力
2014/10/10 职场文书
2014年工作总结及2015工作计划
2014/12/12 职场文书
教师外出学习心得体会
2016/01/18 职场文书
2016年度农村党员干部主题教育活动总结
2016/04/06 职场文书
周一早安温馨问候祝福语!
2019/07/15 职场文书
七年级作文之英语老师
2019/10/28 职场文书
Python如何使用logging为Flask增加logid
2021/03/30 Python
Redis数据同步之redis shake的实现方法
2022/04/21 Redis
Python如何快速找到多个字典中的公共键(key)
2022/04/29 Python