欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Python如何實(shí)現(xiàn)尾遞歸優(yōu)化

 更新時(shí)間:2023年06月01日 00:56:26   作者:misakar  
尾遞歸是函數(shù)返回最后一個(gè)操作是遞歸調(diào)用,則該函數(shù)是尾遞歸。本文將介紹Python是如何實(shí)現(xiàn)尾遞歸優(yōu)化的,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

尾遞歸是一種特殊的遞歸形式,它在遞歸調(diào)用時(shí)不會(huì)產(chǎn)生新的棧幀,從而避免了棧溢出的問題。Python并沒有對(duì)尾遞歸進(jìn)行優(yōu)化,但我們可以通過一些技巧來實(shí)現(xiàn)遞歸優(yōu)化。本文將詳細(xì)介紹Python如何實(shí)現(xiàn)尾遞歸優(yōu)化,并提供兩個(gè)示例來說明它的用法。

什么是尾遞歸

在介紹如何實(shí)現(xiàn)尾遞歸優(yōu)化之前,我們先來了解一下什么是尾遞歸。

遞歸是指遞歸函數(shù)在調(diào)用自身之后,不再有其他操作,直接返回結(jié)果。這種形式的遞歸可以被優(yōu)化為迭代形式,從而避免了棧溢出的問題。

例如,下面是一個(gè)階乘函數(shù)的遞歸實(shí)現(xiàn):

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

這個(gè)函數(shù)不是尾遞歸,因?yàn)樵谶f歸調(diào)用之后還有其他操作(乘法)。如果我們將其改寫為尾遞歸形式,可以得到下代碼:

def factorial, acc=1):
    if n == 0:
        return acc
    else:
        return factorial(n-1, acc*n)

在這個(gè)函數(shù)中,我們引入了一個(gè)額外的參數(shù)acc,用于保存中間結(jié)果。在遞歸調(diào)用時(shí),我們將中結(jié)果乘以當(dāng)前的n,并將結(jié)果傳遞給下一次遞歸調(diào)用。當(dāng)遞歸到n=0時(shí),我們直接返回中間結(jié)果``,從而避免了棧溢出的問題。

如何實(shí)現(xiàn)尾遞歸優(yōu)化

Python并沒有對(duì)尾遞歸進(jìn)行優(yōu)化,但我們可以通過一些技巧來實(shí)現(xiàn)尾遞歸優(yōu)化。具體來,我們可以使用循環(huán)、函數(shù)參數(shù)等方式來避免遞歸調(diào)用產(chǎn)生新的棧幀。

使用循環(huán)

使用循環(huán)是一種常見的實(shí)現(xiàn)尾遞歸優(yōu)化的方式。例如,下面是一個(gè)使用循環(huán)實(shí)現(xiàn)階乘函數(shù)的代碼:

def factorial(n):
    acc = 1
    while n > 0:
        acc *= n
        n -= 1
    return acc

在這個(gè)函數(shù)中,我們使用循環(huán)來計(jì)算階乘,避免了遞歸調(diào)用產(chǎn)生新的棧幀。

使用函數(shù)參數(shù)

使用函數(shù)參數(shù)也是一種實(shí)現(xiàn)尾遞歸優(yōu)化的方式。例如,下面是一個(gè)使用函數(shù)參數(shù)實(shí)現(xiàn)階乘函數(shù)的代碼:

def factorial(n, acc=1):
    if n == 0:
        return acc
    else:
        return factorial(n-1, acc*n)

在這個(gè)函數(shù)中,我們引入了一個(gè)額外的參數(shù)acc,用于保存中間結(jié)果。在遞歸調(diào)用時(shí),我們將中間結(jié)果乘以當(dāng)前的參數(shù)n,并將結(jié)果傳遞給下次遞歸調(diào)用。當(dāng)遞歸到n=0時(shí),我們直接返回中間結(jié)果acc從而避免了棧溢出的問題。

示例1:使用循環(huán)實(shí)現(xiàn)斐波那契數(shù)列

下面是一個(gè)使用循環(huán)實(shí)現(xiàn)斐波那契數(shù)列的示例:

def fibonacci(n):
    if n == 0:
        return 0
 elif n == 1:
        return 1
    else:
        a, b = 0, 1
        for i in range(n-1):
            a, b = b, a+b
        return b

在這個(gè)函數(shù)中,我們使用循環(huán)來計(jì)算斐波那契數(shù)列的第n項(xiàng),避免了遞歸調(diào)用產(chǎn)生新的棧幀。

示例2:使用函數(shù)參數(shù)實(shí)現(xiàn)尾遞歸優(yōu)化

下面是一個(gè)使用函數(shù)參數(shù)實(shí)現(xiàn)尾遞歸優(yōu)化的階乘函數(shù)的示例:

def factorial(n, acc=1):
    if n == 0:
        return acc
 else:
        return factorial(n-1, acc*n)

在這個(gè)函數(shù)中,我們引入了一個(gè)額外的參數(shù)acc,用于保存中間結(jié)果。在遞歸調(diào)用時(shí),我們將中間結(jié)果乘以當(dāng)前的參數(shù)n,并將結(jié)果傳遞給下一次遞調(diào)用。當(dāng)遞歸到=0時(shí),我們直接返回中間結(jié)果acc,從而避免了棧溢出的問題。

一般遞歸與尾遞歸

一般遞歸

def normal_recursion(n):
    if n == 1:
        return 1
    else:
        return n + normal_recursion(n-1)

執(zhí)行:

normal_recursion(5)
5 + normal_recursion(4)
5 + 4 + normal_recursion(3)
5 + 4 + 3 + normal_recursion(2)
5 + 4 + 3 + 2 + normal_recursion(1)
5 + 4 + 3 + 3
5 + 4 + 6
5 + 10
15

可以看到, 一般遞歸, 每一級(jí)遞歸都產(chǎn)生了新的局部變量, 必須創(chuàng)建新的調(diào)用棧, 隨著遞歸深度的增加, 創(chuàng)建的棧越來越多, 造成爆棧?

尾遞歸

尾遞歸基于函數(shù)的尾調(diào)用, 每一級(jí)調(diào)用直接返回遞歸函數(shù)更新調(diào)用棧, 沒有新局部變量的產(chǎn)生, 類似迭代的實(shí)現(xiàn):

def tail_recursion(n, total=0):
    if n == 0:
        return total
    else:
        return tail_recursion(n-1, total+n)

執(zhí)行:

tail_recursion(5, 0)
tail_recursion(4, 5)
tail_recursion(3, 9)
tail_recursion(2, 12)
tail_recursion(1, 14)
tail_recursion(0, 15)
15

可以看到, 尾遞歸每一級(jí)遞歸函數(shù)的調(diào)用變成"線性"的形式. 這時(shí), 我們可以思考, 雖然尾遞歸調(diào)用也會(huì)創(chuàng)建新的棧, 但是我們可以優(yōu)化使得尾遞歸的每一級(jí)調(diào)用共用一個(gè)棧!, 如此便可解決爆棧和遞歸深度限制的問題!

C中尾遞歸的優(yōu)化

gcc使用-O2參數(shù)開啟尾遞歸優(yōu)化:

int tail_recursion(int n, int total) {
    if (n == 0) {
        return total;
    }
    else {
        return tail_recursion(n-1, total+n);
    }
}

int main(void) {
    int total = 0, n = 4;
    tail_recursion(n, total);
    return 0;
}

反匯編

$ gcc -S tail_recursion.c -o normal_recursion.S
$ gcc -S -O2 tail_recursion.c -o tail_recursion.S gcc開啟尾遞歸優(yōu)化

對(duì)比反匯編代碼如下(AT&T語法, 左圖為優(yōu)化后)

優(yōu)化

可以看到, 開啟尾遞歸優(yōu)化前, 使用call調(diào)用函數(shù), 創(chuàng)建了新的調(diào)用棧(LBB0_3); 而開啟尾遞歸優(yōu)化后, 就沒有新的調(diào)用棧生成了, 而是直接pop bp指向的_tail_recursion函數(shù)的地址(pushq %rbp)然后返回, 仍舊用的是同一個(gè)調(diào)用棧!

Python開啟尾遞歸優(yōu)化

cpython本身不支持尾遞歸優(yōu)化, 但是一個(gè)牛人想出的解決辦法:實(shí)現(xiàn)一個(gè) tail_call_optimized 裝飾器

#!/usr/bin/env python2.4
# This program shows off a python decorator(
# which implements tail call optimization. It
# does this by throwing an exception if it is
# it's own grandparent, and catching such
# exceptions to recall the stack.

import sys

class TailRecurseException:
    def __init__(self, args, kwargs):
        self.args = args
        self.kwargs = kwargs

def tail_call_optimized(g):
    """
    This function decorates a function with tail call
    optimization. It does this by throwing an exception
    if it is it's own grandparent, and catching such
    exceptions to fake the tail call optimization.

    This function fails if the decorated
    function recurses in a non-tail context.
    """
    def func(*args, **kwargs):
        f = sys._getframe()
        if f.f_back and f.f_back.f_back \
            and f.f_back.f_back.f_code == f.f_code:
            # 拋出異常
            raise TailRecurseException(args, kwargs)
        else:
            while 1:
                try:
                    return g(*args, **kwargs)
                except TailRecurseException, e:
                    args = e.args
                    kwargs = e.kwargs
    func.__doc__ = g.__doc__
    return func

@tail_call_optimized
def factorial(n, acc=1):
    "calculate a factorial"
    if n == 0:
        return acc
    return factorial(n-1, n*acc)

print factorial(10000)

這里解釋一下sys._getframe()函數(shù):

sys._getframe([depth]):Return a frame object from the call stack.
If optional integer depth is given, return the frame object that many calls below the top of the stack.
If that is deeper than the call stack, ValueEfror is raised. The default for depth is zero,
returning the frame at the top of the call stack.

即返回depth深度調(diào)用的棧幀對(duì)象.

import sys

def get_cur_info():
    print sys._getframe().f_code.co_filename  # 當(dāng)前文件名
    print sys._getframe().f_code.co_name  # 當(dāng)前函數(shù)名
    print sys._getframe().f_lineno # 當(dāng)前行號(hào)
    print sys._getframe().f_back # 調(diào)用者的幀

說一下tail_call_optimized實(shí)現(xiàn)尾遞歸優(yōu)化的原理: 當(dāng)遞歸函數(shù)被該裝飾器修飾后, 遞歸調(diào)用在裝飾器while循環(huán)內(nèi)部進(jìn)行, 每當(dāng)產(chǎn)生新的遞歸調(diào)用棧幀時(shí): f.f_back.f_back.f_code == f.f_code:, 就捕獲當(dāng)前尾調(diào)用函數(shù)的參數(shù), 并拋出異常, 從而銷毀遞歸棧并使用捕獲的參數(shù)手動(dòng)調(diào)用遞歸函數(shù). 所以遞歸的過程中始終只存在一個(gè)棧幀對(duì)象, 達(dá)到優(yōu)化的目的.

為了更清晰的展示開啟尾遞歸優(yōu)化前、后調(diào)用棧的變化和tail_call_optimized裝飾器拋異常退出遞歸調(diào)用棧的作用, 我這里利用pudb調(diào)試工具做了動(dòng)圖:

開啟尾遞歸優(yōu)化前的調(diào)用

開啟尾遞歸優(yōu)化后(tail_call_optimized裝飾器)的調(diào)用

通過pudb右邊欄的stack, 可以很清晰的看到調(diào)用棧的變化.

因?yàn)閷?shí)現(xiàn)了尾遞歸優(yōu)化, 所以factorial(10000)都不害怕遞歸深度限制報(bào)錯(cuò)啦!

到此這篇關(guān)于詳解Python如何實(shí)現(xiàn)尾遞歸優(yōu)化的文章就介紹到這了,更多相關(guān)Python尾遞歸優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Python利用OpenCV和skimage實(shí)現(xiàn)圖像邊緣檢測

    Python利用OpenCV和skimage實(shí)現(xiàn)圖像邊緣檢測

    提取圖片的邊緣信息是底層數(shù)字圖像處理的基本任務(wù)之一。本文將通過OpenCV和skimage的?Canny?算法實(shí)現(xiàn)圖像邊緣檢測,感興趣的可以了解一下
    2022-12-12
  • 在keras中獲取某一層上的feature map實(shí)例

    在keras中獲取某一層上的feature map實(shí)例

    今天小編就為大家分享一篇在keras中獲取某一層上的feature map實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-01-01
  • python操作ie登陸土豆網(wǎng)的方法

    python操作ie登陸土豆網(wǎng)的方法

    這篇文章主要介紹了python操作ie登陸土豆網(wǎng)的方法,涉及Python操作頁面元素的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-05-05
  • 利用python對(duì)Excel中的特定數(shù)據(jù)提取并寫入新表的方法

    利用python對(duì)Excel中的特定數(shù)據(jù)提取并寫入新表的方法

    今天小編就為大家分享一篇利用python對(duì)Excel中的特定數(shù)據(jù)提取并寫入新表的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-06-06
  • python 輪詢執(zhí)行某函數(shù)的2種方式

    python 輪詢執(zhí)行某函數(shù)的2種方式

    這篇文章主要介紹了python 輪詢執(zhí)行某函數(shù)的2種方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-05-05
  • Python pandas 的索引方式 data.loc[],data[][]示例詳解

    Python pandas 的索引方式 data.loc[],data[][]示例詳解

    這篇文章主要介紹了Python pandas 的索引方式 data.loc[], data[][]的相關(guān)資料,其中data.loc[index,column]使用.loc[ ]第一個(gè)參數(shù)是行索引,第二個(gè)參數(shù)是列索引,本文結(jié)合實(shí)例代碼講解的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • Python爬蟲之解析HTML頁面詳解

    Python爬蟲之解析HTML頁面詳解

    本文介紹了Python中用于解析HTML頁面的重要工具之一——BeautifulSoup庫,詳細(xì)講解了BeautifulSoup庫的基本使用方法、標(biāo)簽選擇器、CSS選擇器、正則表達(dá)式、遍歷文檔樹等內(nèi)容,并結(jié)合實(shí)例代碼展示了BeautifulSoup庫的應(yīng)用場景
    2023-04-04
  • Python的pygame安裝教程詳解

    Python的pygame安裝教程詳解

    Pygame是跨平臺(tái)Pyth,Pygame 作者是 Pete Shinners, 協(xié)議為 GNU Lesser General Public License。這篇文章主要介紹了Python的pygame安裝教程,需要的朋友可以參考下
    2020-02-02
  • python?pandas處理excel表格數(shù)據(jù)的常用方法總結(jié)

    python?pandas處理excel表格數(shù)據(jù)的常用方法總結(jié)

    在計(jì)算機(jī)編程中,pandas是Python編程語言的用于數(shù)據(jù)操縱和分析的軟件庫,下面這篇文章主要給大家介紹了關(guān)于python?pandas處理excel表格數(shù)據(jù)的常用方法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • Python項(xiàng)目管理Git常用命令詳圖講解

    Python項(xiàng)目管理Git常用命令詳圖講解

    這篇文章主要介紹了Python項(xiàng)目管理Git常用命令詳圖講解,文中附含詳細(xì)的圖片講解,建議收藏閱讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-09-09

最新評(píng)論