亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

什么是尾遞歸

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

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

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

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

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

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

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

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

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

使用循環(huán)

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

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

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

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

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

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

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

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

下面是一個使用循環(huán)實現(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

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

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

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

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

在這個函數(shù)中,我們引入了一個額外的參數(shù)acc,用于保存中間結果。在遞歸調(diào)用時,我們將中間結果乘以當前的參數(shù)n,并將結果傳遞給下一次遞調(diào)用。當遞歸到=0時,我們直接返回中間結果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

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

尾遞歸

尾遞歸基于函數(shù)的尾調(diào)用, 每一級調(diào)用直接返回遞歸函數(shù)更新調(diào)用棧, 沒有新局部變量的產(chǎn)生, 類似迭代的實現(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

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

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)化

對比反匯編代碼如下(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)然后返回, 仍舊用的是同一個調(diào)用棧!

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

cpython本身不支持尾遞歸優(yōu)化, 但是一個牛人想出的解決辦法:實現(xiàn)一個 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)用的棧幀對象.

import sys

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

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

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

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

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

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

因為實現(xiàn)了尾遞歸優(yōu)化, 所以factorial(10000)都不害怕遞歸深度限制報錯啦!

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

相關文章

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

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

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

    在keras中獲取某一層上的feature map實例

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

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

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

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

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

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

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

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

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

    Python爬蟲之解析HTML頁面詳解

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

    Python的pygame安裝教程詳解

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

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

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

    Python項目管理Git常用命令詳圖講解

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

最新評論