python實(shí)現(xiàn)N階乘的算法

圖片發(fā)自簡(jiǎn)書App

最近有一位師弟問(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)題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容