python函数递归年龄_【算法】算法图解笔记_递归

递归是个有意思的概念,正如在前面所说,递归能让算法的可读性大大提高,而且通常要比使用循环结构更能写出准确的算法。这本书形象引入了递归,并没有太深入,所以我进行了一点“添油加醋”。

递归

概念

递归其实就是自己调用自己。可以从多种维度对递归分类,我见过的最常见的分类:

直接递归

自己直接调用自己。如:

--haskell

length' :: [a] -> Int

length' [] = 0

length' (_:xs) = 1 + length' xs

上面定义的length'就是通过直接递调用自身完成列表长度的计算。

间接递归

可以认为只要不是直接调用自己的递归都是间接递归,其表现形式较多,如A->(调用)B,B->(调用)A,如奇偶谓词函数:

--haskell

odd' :: Int -> Bool

odd' 0 = False

odd' n = even' (n - 1)

even' :: Int -> Bool

even' 0 = True

even' n = odd' (n - 1)

也可以有A->B,B->C,... Z->A。这里就不举例子了。

书中的例子

有一个盒子,盒子里套着一个或多个盒子,盒子里的盒子又有盒子,依次类推。而钥匙就在某个盒子里,我们怎么找到钥匙呢。

策略一

伪代码如下:[伪代码是对手头问题的简要描述,看着像代码,但其实更接近自然语言。]

def look_for_key(main_box):

pile = main_box.make_a_pile_to_look_through()

while pile is not empty:

box = pile.grab_a_box()

for item in box:

if item.is_a_box():

pile.append(item)

elif item.is_a_key():

print "found the key!"

如果学习过树的遍历,是不是发现这就是广度优先遍历啊

策略二

伪代码如下:

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!"

对应的就是深度优先遍历啊

这两种方法的作用相同,但第二种方法更清晰。递归只是让解决方案更清晰,并没有性能上的优势。实际上,在有些情况下,使用循环的性能更好。Leigh Caldwell在Stack Overflow上说的一句话:“如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解。如何选择要看什么对你来说更重要。”

基线条件和递归条件

每个递归函数都有两部分:基线条件(base case)和递归条件(recursive case)。递归条件指的是函数调用自己,而基线条件则指的是函数不再调用自己,从而避免形成无限循环。递归条件一定是向基线条件靠拢的,否则,只能无限递归下去。如

#python

def countdown(i):

print i

if i <= 0:

return

else:

countdown(i-1)

上述函数中i的值收敛于0,即达到基线条件,从而不会无限递归下去。

主要讲了与递归相关的一种数据结构--栈(stack)。栈是一种支持FILO(First In Last Out)的数据结构。

为什么要提这种数据结构呢?

调用栈

这是因为现代计算机对函数的实现用到了调用栈(call stack)的栈。调用函数时,计算机将首先为该函数调用分配一块内存。每当你调用函数时,计算机都将函数调用涉及的所有变量的值存储到内存中。

假设A函数调用B函数,此时计算机为两个函数分配了内存,暂且称之为A函数内存和B函数内存,它们的位置关系如下:

----栈顶----

B函数内存

—————

A函数内存

—————

若B函数执行完,计算机就可以回收B函数内存了,即从栈顶弹出B函数内存,此时只有A函数内存了。

----栈顶----

A函数内存

—————

以上操作符合FILO的定义,调用栈是栈的一种具体应用。

那如果调用栈数量太多,会有什么后果呢?

递归调用栈

#python

def fact(x):

if x == 1:

return 1

else:

return x * fact(x-1)

对于较小的正整数,这个程序没有问题;而如果x较大,在fact执行的时会发现内存量会飙升,甚至会出现程序无法正常执行下去。这是因为此时递归调用栈的情况类似:

----栈顶----

fact(1)函数内存

———————

...

———————

fact(n-2)函数内存

———————

fact(n-1)函数内存

———————

fact(n)函数内存

———————

fact(n)依赖fact(n-1),依次类推,导致计算机存储了大量函数调用的信息。这类问题大体有两种解决方式:

重新编写代码,转而使用循环。

使用尾递归。现在已经有很多编译器支持尾递归优化了。

我的公众号地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值