使用递归能够更天然地解决一些问题。例如,像斐波那契数列:数列中的每一个数字都是数列中前两个数字的和。凡是须要您构建或遍历树状数据结构的问题基本均可以经过递归来解决,锻炼本身强大的递归思惟,你会发现解决这类问题十分容易。javascript
在本文中,我将列举两个案例,让大家了解递归函数是如何工做的。java
函数的递归就是在函数中调用自身,看一个简单的例子:数组
function doA(n) {
...
doA(n-1);
}
复制代码
为了理解递归在理论上是如何工做的,咱们先举一个与代码无关的例子。想象一下,你是一家公司的话务员。因为这是一家业务繁忙的公司,你的座机链接多条线路,所以你能够同时处理多个电话。每条线路对应接收器上的一个按钮,当有来电时,该按钮将闪烁。今天当你到达公司开始工做时,发现有四条线路对应的按钮正在闪烁,因此你须要接听全部这些电话。数据结构
你接通线路一,并告诉他“请稍等”,而后你接通线路二,并告诉他“请稍等”,接着,你接通线路三,也告知他“请稍等”,最后,你接通线路四,并与其通话。当你结束了与线路四的通话以后,你回过头来接通线路三,当你结束了与线路三的通话以后,你接通线路二,结束以后,你再接通线路一,当与线路一的这位客户结束通话后,你终于能够放下电话了。函数
这个例子中的每一通电话就像某函数中的一个递归调用。当你接到一个电话且不能当即处理时,这个电话将被搁置;当你有一个不须要当即触发的函数调用时,它将停留在调用栈上。当你能够接听一个电话时,这个线路会被接通;当你的代码可以触发一个函数调用时,它会从调用栈中弹出。在你看到以后的代码案例有些发懵时,请回想一下这个比喻。测试
每一个递归函数都须要一个终止条件,从而使其不会无休止地循环下去。然而,仅仅加一个终止条件,是不足以免其无限循环的。该函数必须一步一步地接近终止条件。在递归步骤中,问题会逐步简化为更小的问题。spa
假设有一个函数:从1加到n。例如,当n = 4,它实现的就是“1 + 2 + 3 + 4”。code
首先,咱们须要寻找终止条件。这一步能够认为是找到那个不经过递归就直接结束该问题的条件。当n等于0时,无法再拆分了,因此咱们的递归在到达0时中止。对象
在每一步中,你将从当前数字减去1。什么是递归条件?就是用减小的数字调用函数sum
。递归
function sum(num){
if (num === 0) {
return 0;
} else {
return num + sum(--num)
}
}
sum(4); //10
复制代码
每一步过程以下:
这是查看函数如何处理每一个调用的另外一种方式:
sum(4)
4 + sum(3)
4 + ( 3 + sum(2) )
4 + ( 3 + ( 2 + sum(1) ))
4 + ( 3 + ( 2 + ( 1 + sum(0) )))
4 + ( 3 + ( 2 + ( 1 + 0 ) ))
4 + ( 3 + ( 2 + 1 ) )
4 + ( 3 + 3 )
4 + 6
10
复制代码
咱们能够发现,递归条件中的参数不断改变,并逐渐接近并最终符合终止条件。在上面的案例中,咱们在递归条件中的每一步都将参数减1,最后在终止条件中测试参数是否等于0。
multiply(2,4)
将返回8,写出multiply(2,4)
的每一步发生的状况。数组的递归和数字的递归类似,相似于数字的递减,咱们在每一步递减数组中的元素个数,直到得到一个空数组。
考虑使用数组做为求和函数的参数,并返回数组中全部元素的总和。求和函数以下:
function sum(arr) {
var len = arr.length;
if (len == 0) {
return 0;
} else {
return arr[0] + sum(arr.slice(1));
}
}
复制代码
若是数组长度等于0,则返回0,arr[0]
表示数组的第一位,arr.slice(1)
表示从第一位开始截取arr数组,并返回截取以后的数组。例如var arr = [1,2,3,4];
,arr[0]
为1,arr.slice(1)
为[2,3,4]
。当咱们执行sum([1,2,3,4])
时,都发生了一些什么?
sum([1,2,3,4])
1 + sum([2,3,4])
1 + ( 2 + sum([3,4]) )
1 + ( 2 + ( 3 + sum([4]) ))
1 + ( 2 + ( 3 + ( 4 + sum([]) )))
1 + ( 2 + ( 3 + ( 4 + 0 ) ))
1 + ( 2 + ( 3 + 4 ) )
1 + ( 2 + 7 )
1 + 9
10
复制代码
每一次执行都检查数组是否为空,不然,对元素数量逐渐递减的该数组执行递归。
length()
函数,数组做为参数,返回数组长度(不可使用Javascript Array对象内置的length属性)。例如:length(['a', 'b', 'c', 'd'])
,并写出每一步发生的事情。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它一般把一个大型复杂的问题层层转化为一个与原问题类似的规模较小的问题来求解,递归策略只需少许的程序就可描述出解题过程所须要的屡次重复计算,大大地减小了程序的代码量。
本文只列举两个小案例,只为说明递归是怎么回事,上述两个案例的公式都是变量+函数的形式,固然也有不少函数+函数的形式的案例,例如文章开头提到的著名的斐波那契数列,代码以下:
function func( n ) {
if (n == 0 || n == 1) {
return 1;
}
return func(n-1) + func(n-2);
}
复制代码
下面来讲一下使用递归的步骤及优缺点。
可见递归的缺点仍是很明显的,在实际开发中,在可控的状况下,合理使用。
感谢您的阅读!