python递归函数公式_Python递归函数

函数执行流程

要明白递归首先需要明白函数的执行流程。

比如下面的这段程序。

def foo1(b, b1=3):print("foo1 called", b, b1)deffoo2(c):

foo3(c)print("foo2 called", c)deffoo3(d):print("foo3 called", d)defmain():print("main called")

foo1(100, 101)

foo2(200)print("main ending")

main()

结果为:

main called

foo1 called100 101foo3 called200foo2 called200main ending

上面的代码执行流程为:

首先在全局帧中生成foo1、foo2、foo3、main函数对象。

然后main函数调用。

main函数中查找内建函数print压栈,然后将常量字符串压栈,然后调用函数,弹出栈顶。

main函数中全局查找函数foo1压栈,然后将常量100,101压栈,调用函数foo1,创建栈帧。然后字符串和变量b,b1压栈,调用函数,弹出栈顶,返回值。

main函数中全局查找函数foo2函数压栈,然后将常量200压栈,调用foo2,创建栈帧。foo3函数压栈,变量c引用压栈,调用foo3,创建栈帧。foo3完成print函数调用后返回。foo2恢复调用,执行print后,返回值。main中foo2调用结束弹出栈顶,main继续执行print函数调用,弹出栈顶,main函数返回。

递归

所谓的递归也就是函数直接或者间接调用自身。需要注意的是递归一定要有边界条件(不然就就是死循环),递归前进段、递归返回段,当边界条件不满足的时候,递归前进,而当递归条件满足的时候,递归返回。

递归相比于循环,只是让解决方案更加清晰,并没有性能上的优势。在有些情况下,如果使用循环,程序的性能可能会更高,而如果使用递归的话,程序可能更容易理解。

比如要生成一个斐波拉契数列,1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ... ߧ如果设f(n)为该数列的第n项,那么可以推导出这样的公式:f(n)=f(n-1)+f(n-2).

使用循环,生成斐波拉契数列,代码为:

pre =0

cur= 1#第一个

print(pre,cur,end=" ")

n= 4

for i in range(n-1):

pre,cur= cur,pre+curprint(cur,end = " ")

结果为:

01 1 2 3将它抽象出函数:deffib(n):

pre=0

cur= 1

print(cur,end=" ")for i in range(n-1):

pre,cur= cur,pre+curprint(cur,end = " ")

fib(10)

结果为:1 1 2 3 5 8 13 21 34 55

而如果使用递归的话,代码可以很简洁:

#第n项的数字为:

deffib(n):if n<2:return 1

else:return fib(n-1)+fib(n-2)

fib(10)

上面的代码可以简化为:deffib(n):return 1 if n<2 else fib(n-1)+fib(n-2)

fib(10)

而生成数列的代码为:

deffib(n):return 1 if n <2 else fib(n-1) + fib(n-2)print(fib(10))for i in range(11):print(fib(i), end=' ')

结果为:89

1 1 2 3 5 8 13 21 34 55 89

由上面的例子可以看到,递归一定要有退出条件,而递归调用一定会执行到这个退出条件,如果没有退出条件的递归调用,就是无限调用,会陷入无限循环,而且递归调用的深度不宜过深,Python对递归调用的深度做了限制(1000)以保护解释器,超过递归深度的限制,会抛出recursionerror错误,同时可以使用sys.getrecursionlimil()设置递归的深度。

递归的性能

循环稍微复杂一些,但是只要不是死循环,就可以多次迭代算出结果,递归函数的代码极简易懂,但是只能获取到最外层的函数调用,内部的递归结果都是中间结果,而且给定一个N都要进行近2N次递归,深度越深,效率就会越低,为了获取斐波拉契数列需要再在外面套一个n的循环,效率就更低了。如果递归复杂,函数会反复的压栈,这样栈的内存很快就会溢出。所以会有递归深度限制。如下面的代码:

importdatetime

start=datetime.datetime.now()

pre=0

cur= 1 #No1

print(pre, cur, end=' ')

n= 35

for i in range(n-1):

pre, cur= cur, pre +curprint(cur, end=' ')

delta= (datetime.datetime.now() -start).total_seconds()print(delta)

结果为:

01 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 0.001

importdatetime

n= 35start=datetime.datetime.now()deffib(n):return 1 if n < 2 else fib(n-1) + fib(n-2)for i inrange(n):print(fib(i), end=' ')

delta= (datetime.datetime.now() -start).total_seconds()print(delta)

结果为:1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 9.371536

上面的例子可以看到for循环,只需要0.01秒就可以很快算出35个斐波拉契数列,而递归的话则需要9秒多。效率特别低,这个时候有什么可以改进的吗?

pre =0

cur= 1#第一个

print(pre,cur,end=" ")def fib(n,pre = 0,cur = 1):

pre,cur= cur,pre+curprint(cur,end=" ")if n ==2:returnfib(n-1,pre,cur)

fib(10)

结果为:

01 1 2 3 5 8 13 21 34 55

上面的这个递归函数就和循环的思想很类似了,参数n是边界条件,用n来计数,上一次的计算结果直接用作函数的实参,这样的话,效率更高,和循环相比较的话,性能更近。

所以递归并不一样都是效率低下,但是它有深度限制。

可以对比下三个斐波拉契数列生成函数的效率。如下:

importdatetime#Fib Seq

start =datetime.datetime.now()

pre=0

cur= 1 #No1

print(pre, cur, end=' ')

n= 35

#loop

for i in range(n-1):

pre, cur= cur, pre +curprint(cur, end=' ')

delta= (datetime.datetime.now() -start).total_seconds()print(delta)#Fib Seq

start =datetime.datetime.now()

pre=0

cur= 1 #No1

print(pre, cur, end=' ')#recursion

def fib1(n, pre=0,cur=1):

pre, cur= cur, pre +curprint(cur, end=' ')if n == 2:returnfib1(n-1, pre, cur)

fib1(35)

delta= (datetime.datetime.now() -start).total_seconds()print(delta)

start=datetime.datetime.now()deffib2(n):if n < 2:return 1

return fib2(n-1) + fib2(n-2)for i inrange(n):print(fib2(i), end=' ')

delta= (datetime.datetime.now() -start).total_seconds()print(delta)

前面两个性能相当,而第三个效率特特别低下。写递归应该尽量避免写成第三个这样的。

间接递归

所谓的间接递归,就是通过别的函数间接调用了函数自身,但是,如果构成了循环递归调用就是件非常危险的事情,而且这种情况在代码复杂的情况下, 很还可能发生。所以一定要规范代码,来避免发生这种调用的发生。比如下面这个例子。

deffoo1():

foo2()deffoo2():

foo1()

foo1()

上面的代码就会陷入死循环。应该尽量的避免。

递归总结:

递归是一种很自然的表达,符合逻辑思维。

递归相对来讲运行效率低,每一次调用函数都要开辟栈帧。

递归有深度限制,如果递归的层次太深,函数反复的压栈,栈内存很快就会溢出。

如果是有限次的递归,可以使用递归调用,或者使用循环代码,循环代码稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果。

绝大多数递归,都可以使用循环来实现。

即便递归代码简洁,但是能不用则不用递归。

练习题:

求n的阶乘!

将一个数逆序放入列表中,比如1234--[4,3,2,1]

解决猴子吃桃问题:猴子第一天摘下若干个桃子,马上吃了一半,还不过瘾,又多吃了一个,第二天早上又将剩下的桃子吃掉了一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想吃的时候,只剩下一个桃子,求第一天共摘了多少个桃子。

#求阶乘

def fac(n):#性能最好

if n==1:return 1

else:return n*fac(n-1)

fac(10)

结果为:3628800

def fac1(n,p=1):if n==1:returnp

p*=nprint(p)

fac1(n-1,p)returnp

fac1(10)

结果为:10

90

720

5040

30240

151200

604800

1814400

3628800

10

def fac2(n,p=None):if p isNone:

p= [1]if n == 1:returnp[0]

p[0]*=n#print(p[0])

fac2(n-1,p)returnp[0]

fac2(10)

结果为:3628800

def fac3(n,p=1):

p*=nif n==1:returnpelse:return fac4(n-1,p)

fac3(10)

结果为:3628800

2.将一个数逆序放入列表中

data = str(1234)defrevert(x):if x==-1:return " "

return data[x]+revert(x-1)print(revert(len(data)-1))

结果为:4321

def revert1(n,lst =None):if lst isNone:

lst=[]

lst.append(n%10)if n//10 ==0:returnlstreturn revert1(n//10,lst)

revert1(12345)

结果为:

[5, 4, 3, 2, 1]def revert2(n,lst =None):if lst isNone:

lst=[]

x,y=divmod(n,10)

lst.append(y)if x==0:returnlstreturnrevert2(x,lst)

revert2(12345)

结果为:

[5, 4, 3, 2, 1]

num= 12354

def revert3(num,target=[]):ifnum:

target.append(num[len(num)-1])#target.append(num[-1:])

revert3(num[:len(num)-1])returntargetprint(revert3(str(num)))

结果为:

['4', '5', '3', '2', '1']

猴子吃桃问题:

def peach(day= 9,sum = 1):

sum=2*(sum+1)

day-=1

if day ==0:returnsumreturnpeach(day,sum)print(peach())

结果为:1534

defmonkey(n):if n==1:return 1

return 2*monkey(n-1)+2monkey(10)

结果为:1534

这个猴子吃桃的问题,可以假设猴子摘了x个桃子

d1: x//2-1

d2:d1//2-1

d3:d2//2-1

……

d9:d8//2-1

d10:1

那么:

def peach(day = 10):if day==1:return 1

return (peach(day-1)+1)*2

print(peach())

结果为:1534

应该注意的是,这里必须是10,因为return (peach(day-1)+1)*2立即拿不到结果,必须通过再一次进入函数时判断是不是到了最后一天。也就是当前使用的值是由下一次函数调用得到的,所以要执行10次函数调用。换一种方式表达:

def peach(days = 1):if days==10:return 1

return (peach(days+1)+1)*2

print(peach())

结果为:1534

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值