
最近有一位師弟問(wèn)我,當(dāng)使用遞歸函數(shù)實(shí)現(xiàn)階乘算法時(shí),隨著計(jì)算深度的增加會(huì)造成Stack溢出。
那我們寫一下這個(gè)例子:
def factorial(n):
if n<0:
return '負(fù)數(shù)不可以階乘'
if n==1 or n==0:
return 1
return n*factorial(n-1)
print(factorial(1000))
返回結(jié)果:
RecursionError: maximum recursion depth exceeded in comparison
出現(xiàn)這個(gè)問(wèn)題的原因是:
在計(jì)算機(jī)中,函數(shù)調(diào)用是通過(guò)棧(stack)這種數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的,每當(dāng)進(jìn)入一個(gè)函數(shù)調(diào)用,棧就會(huì)加一層棧幀,每當(dāng)函數(shù)返回,棧就會(huì)減一層棧幀。由于棧的大小不是無(wú)限的,所以,遞歸調(diào)用的次數(shù)過(guò)多,會(huì)導(dǎo)致棧溢出.
[1]解決遞歸調(diào)用棧溢出的方法是通過(guò)尾遞歸優(yōu)化,事實(shí)上尾遞歸和循環(huán)的效果是一樣的,所以,把循環(huán)看成是一種特殊的尾遞歸函數(shù)也是可以的。
[2]尾遞歸是指,在函數(shù)返回的時(shí)候,調(diào)用自身本身,并且,return語(yǔ)句不能包含表達(dá)式。這樣,編譯器或者解釋器就可以把尾遞歸做優(yōu)化,使遞歸本身無(wú)論調(diào)用多少次,都只占用一個(gè)棧幀,不會(huì)出現(xiàn)棧溢出的情況。
上面的factorail(n)函數(shù)由于return n * factorail(n - 1)引入了乘法表達(dá)式,所以就不是尾遞歸了。要改成尾遞歸方式,需要多一點(diǎn)代碼,主要是要把每一步的乘積傳入到遞歸函數(shù)中:
下面我們通過(guò)尾遞歸來(lái)優(yōu)化這個(gè)方案:
def factorail(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
print(factorial(100))
尾遞歸調(diào)用時(shí),如果做了優(yōu)化,棧不會(huì)增長(zhǎng),因此,無(wú)論多少次調(diào)用也不會(huì)導(dǎo)致棧溢出。
遺憾的是,大多數(shù)編程語(yǔ)言沒(méi)有針對(duì)尾遞歸做優(yōu)化,Python解釋器也沒(méi)有做優(yōu)化,所以,即使把上面的fact(n)函數(shù)改成尾遞歸方式,也會(huì)導(dǎo)致棧溢出。
用for 循環(huán)解決N階乘算法方案:
#!/usr/bin/python3
# Filename : test.py
# author by : byx54192@gmail.com
num=10000
factorial = 1
# 查看數(shù)字是負(fù)數(shù),0 或 正數(shù)
if num < 0:
print("抱歉,負(fù)數(shù)沒(méi)有階乘")
elif num == 0:
print("0 的階乘為 1")
else:
for i in range(1,num + 1):
factorial = factorial*i
print("%d 的階乘為 %d" %(num,factorial))
while 語(yǔ)句實(shí)現(xiàn)階乘算法案例
#!/usr/bin/python3
# Filename : test.py
# author by : byx54192@gmail.com
def fact(num):
result=1
if num < 0:
return("抱歉,負(fù)數(shù)沒(méi)有階乘")
elif num == 0 or num==1:
return(str(num)+" 的階乘為 1")
while num > 1:
tmp=num*(num-1)
result=result*tmp
num -=2
return result
print(fact(555))
小結(jié):
使用遞歸函數(shù)的優(yōu)點(diǎn)是邏輯簡(jiǎn)單清晰,缺點(diǎn)是過(guò)深的調(diào)用會(huì)導(dǎo)致棧溢出。
針對(duì)尾遞歸優(yōu)化的語(yǔ)言可以通過(guò)尾遞歸防止棧溢出。尾遞歸事實(shí)上和循環(huán)是等價(jià)的,沒(méi)有循環(huán)語(yǔ)句的編程語(yǔ)言只能通過(guò)尾遞歸實(shí)現(xiàn)循環(huán)。
Python標(biāo)準(zhǔn)的解釋器沒(méi)有針對(duì)尾遞歸做優(yōu)化,任何遞歸函數(shù)都存在棧溢出的問(wèn)題。