JavaScript 递归
递归最典型的就是两个,
1. 一个是阶乘,
2. 一个是斐波那契数列
四、写一个函数,实现n的阶乘
第一种方法用循环
第二钟方法用递归
1、循环
function mul(n){ var num = 1; for(var i = 1; i <= n; i ++) { num *= i; } return num; } console.log(mul(5)); // 120
从1到自身的数乘一遍(累乘)
num *= i
1 x 1 = 1
1 x 2 = 2
2 x 3 = 6
6 x 4 = 24
24 x 5 = 120
2、递归
阶乘的规律?
n阶乘的规律绝对不是从n乘到1,这是阶乘的现象,
n阶乘的规律是 n! = n * (n - 1)! ,n的阶乘 等于 n乘以n减1的阶乘,然后再求n减1的阶乘,n减1的阶乘等于n减1乘以n减2的阶乘,一直求到1结止,这才是它抽象的规律
函数就是为了抽象的规则,用函数把这个规则写出来
function mul(n){ return n * mul(n - 1); // n乘以n减1的阶乘,把这个结果返回 } mul(5);
理解一下 mul(5)
mul(5) => 5 * mul(4) 5乘以4的阶乘的计算结果,要等到函数调用mul(5-1=4)的计算出结果乘5,mul(4)的结果又要等下面mul(3)的结果
mul(4) => 4 * mul(3) 4阶的乘又调用函数mul(4-1=3)等到3的阶乘结果,接下来3的阶乘的结果,又要等2的阶乘的结果
mul(3) => 3 * mul(2) 3阶的乘的结果,要等2阶乘的结果
mul(2) => 2 * mul(1) 2阶乘的结果,要等1阶乘的结果
1的阶乘结果等0的阶乘,再等-1的一直往下等……
一直往下等算不到头,无限死循环下去,这个函数还是差最后一步找到它的"出口",什么时候让无限死循环终止返回具体的数呢?
找到已知的东西,5的阶乘算不出来,4的阶乘算不出来,0的阶乘是已知的,1的阶乘也是已知的一样的,1的阶乘就可以,也就是说当n是1的时候不用去计算了,返回结果就是1,已知1的阶乘结果就是1
判断n等于1的时候 return 1; 就可以了,有了这句之后这个函数就发生质变了,原来还无限死循环,现在这个函数就有了一个出口
function mul(n){ if(n == 1){ return 1; } return n * mul(n - 1); } mul(5); // 120 /** * mul(5) ==> 5 * mul(4) * mul(4) ==> 4 * mul(3) * mul(3) ==> 3 * mul(2) * mul(2) ==> 2 * 1 * * mul(5) ==> 5 * mul(4) * mul(4) ==> 4 * mul(3) * mul(3) ==> 3 * 2 * mul(2) ==> 2 * 1 * * mul(5) ==> 5 * mul(4) * mul(4) ==> 4 * 6 * mul(3) ==> 3 * 2 * mul(2) ==> 2 * 1 * mul(5) ==> 5 * 24 * mul(4) ==> 4 * 6 * mul(3) ==> 3 * 2 * mul(2) ==> 2 * 1 * * mul(5) ==> 5 * 24 = 120 * mul(4) ==> 4 * 6 * mul(3) ==> 3 * 2 * mul(2) ==> 2 * 1 */
想知道5的阶乘的结果,必须先知道4的阶乘的结果,想知道4阶乘的结果必须知道3阶乘的结果,想知道3的结果必须知道2的结果,想知道2的结果必须知道1的结果,现在1的结果已经知道是1不需要计算了
mul(5) => 5 * mul(4)=> 5 x 24=120 mul(4)的结果变成24 ,这样一层一层往上反结果,最后5的阶乘的结果120就求出来了
mul(4) => 4 * mul(3) => 4 x 6 = 24 mul(3)变成6,计算完mul(4)的结果24
mul(3) => 3 * mul(2) => 3 x 2 = 6 mul(2)的结果2直接返回到这,mul(3)的结果就可求了
mul(2) => 2 * mul(1) => 2 x 1 = 2 现在已知1的阶乘mul(1)结果是1,到1这立马换成数,然后进行计算,计算完后是mul(2)的结果2
这个整体的过程叫用"递归"的方法来解决问题
递归主要关注两点
1. 先说一下递归的好处就是特别符合人为的想法,我们的思维过程是我想求n的阶乘,最直观的找到它的规律是"n的阶乘等于n乘以n-1的阶乘",
然而我们把这条把这条规律 n * (n - 1) 写到程序里面了,递归就是更好找规律这是第一点。
找规律,递归就是return公式,没有return就不会循环计算
2. 第二点找出口,递归没有出口就会无限死循环,必须用一个已知的数当做递归的出口,最后不用公式去计算,用一个实质性的数返回结果
程序有一个缺陷,求不了0的阶乘,把0的阶乘加上,0的阶乘是已知就是1
function mul(n){ if(n == 1 || n == 0){ return 1; } return n * mul(n - 1); } mul(0); // 1
递归只有一点好处,它能让代码变的更加简洁,除此以外没有任何好处,特别复杂的程序不能用递归它特别慢
比如求10的阶乘 mul(10) ,想知道10的阶乘要等9的阶乘算完,9的阶乘要等8的阶乘算完,一层一层的等,
等到最后1的阶乘算完之后往回反结果,1的阶乘知道了,2的阶乘马上知道了,然后3的阶乘知道了,最后才知道是10的阶乘,10的阶乘最先执行,10阶乘最后被执行完,所以递归慢,空间复杂度非常高。
但是递归的好处是,代码写完后少了不少工作量,下面的“斐波那契数列”就知道什么是真正的少了。
五、写一个函数,实现斐波那契数列
斐波那契数列正常用循环也能写出来,但是用递归就很容易找到抽象的概念了,对这种很容易找到抽象规律的,用递归是最方便的。
斐波那契数列的规律是 第n位 等于 n-1位 加上 n-2位的和 ,而且是有出口的"1或2",有规律有出口就大胆的用递归吧。
function fb(n){ if(n == 1 || n == 2){ return 1; } return fb(n - 1) + fb(n - 2); // 第n位等于 n-1位 加上 n-2位的和 } console.log(fb(5)); // 5 /** * fb(5) ==> fb(4) + fb(3); * fb(4) ==> fb(3) + fb(2); fb(3) ==> fb(2) + fb(1); * fb(3) ==> fb(2) + fb(1); * fb(5) ==> fb(4) + fb(3); * fb(4) ==> fb(3) + fb(2); fb(3) ==> fb(2) + fb(1); * fb(3) ==> 1 + 1; * fb(5) ==> fb(4) + fb(3); * fb(4) ==> fb(3) + fb(2); fb(3) ==> 1 + 1; * fb(3) ==> 1 + 1; * fb(5) ==> fb(4) + fb(3); * fb(4) ==> 2 + 1; fb(3) ==> 1 + 1; * fb(3) ==> 1 + 1; * fb(5) ==> 3 + 2; * fb(4) ==> 2 + 1; fb(3) ==> 1 + 1; * fb(3) ==> 1 + 1; */
输入的数对应输出的斐波那契数
输入对应的数: 1 2 3 4 5 6 7 8 9 10 11...
斐波那契数列: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89...
六、自己做的一个练习,用递归的方法依次打印 10 - 1
依次打印10 ~ 1,规律是n - 1,出口是n等于1
function dg(n){ if(n == 1){ console.log(n); return ; }else{ console.log(n); } return dg(n - 1); } dg(10);
把上面的代码优化一下,出口是n等于0
function mul(n){ if(n == 0){ //console.log(n); return ; } console.log(n); return mul(n - 1); } mul(10);
不写作业也是可以的,但总要练点什么东西,要么练练技术,要么练练心里素质