第三章讲的是递归和栈。
书中对学习递归的人分为三个阵营:恨它的,爱它的,狠了几年后又爱上它的。
我认为我是第三种,恨是因为无法理解,爱上是因为它的优雅。
以前写递归主要问题都是递归出口条件不知道怎么写,什么时候调用自身。学习完这章后也只是大概了解了,具体应用还需要去leetcode磨练。
假设你在祖母的阁楼中翻箱倒柜发现了了一个上锁的神秘手提箱。
-
非递归式找钥匙
除了这个“创建盒子堆”的操作脑补不出来,其他都还好。“只要盒子堆不空”那不就是while循环嘛。
-
递归式找钥匙
来看看书中代码模拟实现-
非递归式
-
递归式
-
def look_for_key(box):
for item in box:
if item.is_a_box();
look_for_key(item) <------递归
elif item.is_a_key():
print "found the key!"
相对而言递归式代码看起来就清晰简洁多了。
下面用一个书本例子切实理解递归,建议手敲运行一下,看一万遍也不如写一遍体会来的深。
用递归编写一个倒计时函数。(没学过递归的,估计都是用for循环实现)
//python 2.7
def countdown(i):
print i
countdown(i-1)
//java
public int countdown(int n){
System.out.println(n);
return countdown(n-1);
}
运行上述代码会发现函数停不下来。(ps:说是这么说,但是内存满了会停下来报错的)
黑窗口用 ctrl+c停止
所以,编写递归函数时,必须告诉它何时停止递归。【PS:写递归函数一定要想出停止递归的条件】
每个递归函数都有两个部分:基线条件和递归条件。
- 基线条件,让递归(或者说是循环)停下来的条件。
- 递归条件,调用函数自身。
//python 2.7
def countdown(i):
print i
if i <= 1: <-----基线条件
return
else
countdown(i-1) <----递归条件
//java
public int countdown(int n){
System.out.println(n);
if(n<=1)
return n; <----你也可以把n改成0,无关紧要,因为打印在上面那句
else
return countdown(n-1);
}
这个就需要多做题锻炼了,练多了就有感觉知道怎么写基线条件了。
接下来,讲栈的概念,并用图像告诉你递归时栈是怎么做的。
栈有两个操作:压入(插入)和弹出(删除并读取)
假设你去野外烧烤,并为此创建了一个待办事项清单。
具体操作:
普通的函数调用(非递归式)栈
//python 2.7
def greet(name):
print "hello," + name + "!"
greet2(name)
print "getting ready to say bye..."
bye()
def greet2(name):
print "how are you," + name + "?"
def bye():
print "ok bey!"
//假设你运行个greet("Tom")
函数递归调用栈
//计算 n! ,例如5!=5*4*3*2*1
def fact(x):
if x==1:
return 1
else
return x * fact(x-1)
//假设运行fact(3)
- 第一次调用,x=3
- 第二次调用,x=2,此时 fact(3) 暂停等待 fact(2) 完成。
- 第三次调用,x=1,此时 fact(3) 在等待 fact(2) 完成,fact(2) 在等待 fact(1) 完成。
PS:每个 fact 函数调用都有自己的 x 变量。在一个函数调用中不能访问另一个的 x 变量。
理解完上面的东西后,我们可以写一个斐波那契数列练练手了。
斐波那契数列公式:f(n) = f(n-1) + f(n-2) ( f(1) = 1,f(2) = 1,n > 2)
public class Demo {
public static void main(String[] args) {
int n = 6;
Demo demo = new Demo();
int res = demo.fibonacci(n);
System.out.println(res);
}
public int fibonacci(int n){
if(n == 1 || n == 2){
return 1; //递归出口,基线条件
}else{
return fibonacci(n-1)+fibonacci(n-2); //递归条件
}
}
}
如果你的程序没完没了的运行,说明你的基线条件没有写对。