日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

递归函数python有什么特点_Python中的递归

發布時間:2023/11/30 python 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 递归函数python有什么特点_Python中的递归 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在前面的講解中,函數的調用通常發生在彼此不同的函數之間。其實,函數還有一種特殊的調用方式,那就是自己調用自己,這種方式稱為函數遞歸調用。

遞歸,在程序設計中也是一個常用的技巧,甚至是一種思維方式,非常值得我們掌握。

4.3.1 感性認識遞推

在講解“遞歸”這個抽象概念之前,讓我們來重溫一下昔日往事。小時候,當我們在纏著長輩講故事時,長輩們可能就用下面的故事來“忽悠”我們:從前有座山,山里有座廟,廟里有個老和尚,正在給小和尚講故事!故事是什么呢?從前有座山,山里有座廟,廟里有個老和尚正在給小和尚講故事!故事是什么呢……

除非講故事的人自己停下來不講了,不然這個故事可以“無限”講下去,原因就是“故事”嵌套的“故事”就是“故事”本身,這就是語言上“遞歸”的例子。

但是,由于這個故事并沒有一個終止的條件,因此,它實際上是陷入了一種有頭無尾的死循環,因此并不符合程序設計領域中定義的“遞歸”。在程序設計領域,遞歸是指函數(或方法)直接或間接調用自身的一種操作,如圖4-4所示。遞歸調用的好處在于,它能夠大大減少代碼量,將原本復雜的問題簡化成一個簡單的基礎操作來完成。在編寫程序的過程中,“遞歸調用”是一個非常實用的技巧。

圖4-4 遞歸示意圖

從圖4-4中可以看出,函數不論是直接調用自身,還是間接調用自身,都是一種無終止的過程。

在程序設計中,顯然不能出現這種無終止的調用。因此,在編寫遞歸算法時,讀者要特別注意,所有遞歸一定要有終止條件,這又被稱作遞歸出口。

如果一個遞歸函數缺少遞歸出口,執行時就會陷入死循環。遞歸出口通常可用if語句來設置,在滿足某種條件時不再繼續,調用某個值,結束遞歸。

谷歌公司有世界上最聰明的程序員。他們不光聰明,還很有自己的“冷幽默”,別出心裁。比如說,假設你不懂得什么是“遞歸”,不妨去谷歌搜索一下這個關鍵詞。

然后你會發現,除了給出必要的搜索結果,谷歌還給出了一個提示語“您是不是要找:遞歸”,如圖4-5所示。

圖4-5 谷歌程序員的“冷幽默”

咋一看,你可能會覺得,這谷歌搜索是不是有問題啊?我的確、明明、絲毫無誤地查詢的就是“遞歸”,還提示什么啊?其實,這正是谷歌搜索引擎背后程序員們的“冷幽默”所在:如果你點擊了那個提示“遞歸”,搜索引擎將再次搜索“遞歸”——相當于自己調用自己——這不正是遞歸的精髓嗎?

或許你懂了,會心一笑,但可能還會疑惑:這也不對啊,所有的遞歸都有終止條件,如果我們一直點擊這個提示詞“遞歸”,查詢豈不是會無限循環下去?

放心,你一定不會一直點擊下去。因為這個遞歸的出口正是,查的人終于懂得什么是遞歸而不再查詢。而你就是那個懂得的人。

4.3.2 思維與遞歸思維

遞歸(recurse)在計算機領域被廣泛應用,它不僅是一種計算方法,更是一種思維方式。科技作家吳軍博士認為:遞歸思維是人與計算機思維最大的差別之一。著名計算機科學家彼得·多伊奇(L. Peter Deutsch)甚至認為,To iterate is human, to recurse divine(迭代是人,遞歸是神)。

對于計算機從業者來說,想成為頂級人才,在做計算機相關工作時,必須具有遞歸思維。對于普通人來講,這種思維方式也很有啟發。因此,不論從哪個角度,遞歸思維都值得我們培養和掌握。

人的常規思維被稱為遞推(iterate)思維。在中文里,“遞推”和“遞歸”只有一字之差,但在英文世界里,它們的差別可大了去了,可謂“差之毫厘,謬以千里”。

我們先來說說遞推。比如小時候我們學習數數,從1、2、3一直數到100,就是典型的遞推。類似地,我們在學習過程中循序漸進,如水到而渠成,出發點都是正向的,由易到難,由小到大,由局部到整體。

遞推是人類本能的正向思維,于我們而言,可謂熟稔于心。而“遞歸”則有一定的反常識。

下面我們以計算一個整數的階乘為例來說明兩種思維的差別。如果用人類常用遞推方式計算一個整數的階乘,比如5!=1×2×3×4×5,那么做法是從小到大一個數一個數接連相乘。如果計算10的階乘(10!),過程也是類似的,即從1乘到10。

在生活中,這種做法不僅合情合理,而且渾然天成。事實上,在中學里學的數學歸納法(利用當n成立時的結論,推導n+1)的方法論就是遞推。

為了簡單起見,我們還是用前面求階乘的簡單例子來說明遞歸的原理。計算機是怎么計算階乘的呢?它是倒著來的。比如要算5!,計算機就把它變成5x4!(即5乘以4的階乘)。當然,我們可能會質疑,4!還不知道呢!

但沒有關系,計算機會采用同樣的方法,把4!變成4x3!。至于3!,則用同樣的算法處理。最后做到1!時,計算機知道1!=1(這就是遞歸的終止條件),自此便不再往下擴展了。

接下來,就是倒推回所有的結果。因為由于知道了1!,順水推舟,就知道了2!,然后可知3!、4!和5!從上面描述的遞歸過程可以看出,遞歸的方法論可歸結為兩步:先從上向下層層展開,再從下到上一步步回溯。

4.3.3 遞歸調用的函數

你可能會問,計算機為何要這么算?這么算有何優勢?答案并不復雜,因為利用遞歸可以使算法的邏輯變得非常簡單。因為遞歸過程的每一步用的都是同一個算法,計算機只需要自頂向下不斷重復即可。

具體到階乘的計算,無非就是某個數字n的階乘,變成這個數乘以n-1的階乘。因此,遞歸的法則就兩條:一是自頂而下(從目標直接出發),二是不斷重復。

遞歸的另一個特點在于,它只關心自己的下一層的細節,而并不關心更下層的細節。你可以理解遞歸的簡單,源自它只關注“當下”,把握“小趨勢”,雖然每一步都簡單,但一直追尋下去,也能獲得自己獨特的精彩。

下面我們就以計算階乘為例,分別使用遞推和遞歸方式實現,見【范例4-7】,讀者可體會二者的區別。

【范例4-7】利用遞推和遞歸方式分別計算n!(iterative-recursive.py)。01 #用正向遞推的方式計算階乘

02 def iterative_fact( n ):

03 fact = 1

04 for i in range(1, n + 1):

05 fact *= i

06 return fact

07

08 # 用逆向遞歸的方式計算階乘

09 def recursive_fact( n ):

10 if n <= 1 :

11 return n;

12 return n * recursive_fact(n - 1)

13

14 #調用非遞歸方法計算

15 num = 5

16 result = iterative_fact( num );

17 print("遞推方法:{}!= {}".format(num, result))

18 #調用遞歸方法計算

19 result = recursive_fact(num)

20 print("遞歸方法:{}!= {}".format(num, result))

【運行結果】遞推方法:5!= 120

遞歸方法:5!= 120

【代碼分析】

第02~06行定義了一個遞推計算階乘的函數iterative_fact(),函數內部采用for循環的方式來計算結果。在for循環控制過程中使用了range()函數,由于range的取值區間是左閉右開的,最后一個值取不到,所以在第04行執行了n+1操作。

第09~12行定義一個遞歸函數recursive_fact,采用遞歸的方式計算結果。

第17行和第20行用到了Python的格式化輸出。在Python中,一切皆對象。用雙引號引起來的字符串“遞歸方法:{}!= {}”,實際上是一個str對象。既然是對象,它就會有相應的方法成員,format()就是用于格式化輸出的方法,因此可以通過“對象.方法名”的格式來調用合適的方法。字符串中的花括號{}表示輸出占位符,第1個占位符{}用于輸出format()函數中第1個變量,第2個占位符{}用于輸出format()函數中第2個變量,以此類推。

遞歸函數的優點在于,定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環的方式,但正向遞推(即循環)的邏輯不如逆向遞歸的邏輯清晰。

對于遞推的實現,這里用到了前面章節中講到的for循環語句,以1為基數不斷循環相乘,最終得出階乘的結果。而在遞歸實現的操作中,這里通過對方法本身的壓棧和彈棧的方式,將每一層的結果逐級返回,通過逐步累加求得結果。

recursive_fact(5)的計算過程如下。===> recursive_fact (5)

===> 5 * recursive_fact (4)

===> 5 * (4 * recursive_fact (3))

===> 5 * (4 * (3 * recursive_fact (2)))

===> 5 * (4 * (3 * (2 * recursive_fact (1))))

===> 5 * (4 * (3 * (2 * 1)))

===> 5 * (4 * (3 * 2))

===> 5 * (4 * 6)

===> 5 * 24

===> 120

需要注意的是,雖然遞歸有許多的優點,但缺點也很明顯。那就是,使用遞歸方式需要函數做大量的壓棧和彈棧操作,由于壓棧和彈棧涉及函數執行上下文(context)的現場保存和現場恢復,所以程序的運行速度比不用遞歸實現要慢。

此外,大量的堆棧操作消耗的內存資源要比非遞歸調用多。而且,過深的遞歸調用還可能會導致堆棧溢出。如果操作不慎,還容易出現死循環。因此讀者編寫代碼過程中需要多加注意,一定要設置遞歸操作的終止條件。

思考與練習:一道關于遞歸的面試題(谷歌公司)

(1)有這么一個游戲:有兩個人,第一個人先從1和2中挑一個數字,第二個人可以在對方的基礎上選擇加1或者加2,然后又輪到第一個人,他也可以選擇加1或者加2,之后再把選擇權交給對方,就這樣雙方交替地選擇加1或者加2,誰先加到20,誰就贏了。對于這個游戲,你用什么策略保證一定能贏?

【案例分析】

如果用正向的遞推思維(比如說窮舉法),并不容易想清楚,而且還容易漏掉合理的解。但如果用逆向的遞歸思維,問題的解就非常容易推導出來。我們先從結果出發,如果要想搶到20,就需要搶到17,因為搶到了17,無論對方是加1還是加2,你都可以加到20。而要想搶到17,就要搶到14,以此類推,就必須搶到11、8、5和2。

圖4-6 計算一下共有多少種上樓梯的方法

因此對于這道題,只要第一個人搶到了2,他就贏定了。這是因為,無論對方選擇加1還是加2,他都可以讓這一輪兩個人加起來的數值等于5。同樣的道理,在當前和為5的基礎上,無論對方選擇加1或加2,他都能讓和向著8進發。以此類推,整個過程都被他牢牢控制,最終的數列之和,毫無懸念地被他鎖定在20。

當然谷歌的面試題并非這么簡單,如果你答對第一道題,那么緊接著就會有下一道題。

(2)按照上述方法,在不考慮誰輸誰贏的情況下,從一開始(以1或2為起點)加到20,有多少種不同的遞加過程?比如1,4,7,10,12,15,18,20算一種;2,5,8,11,14,17,20又是一種。那么一共會有多少種這樣的過程呢?

【案例分析】

這道題顯然并不簡單,通過正向的窮舉法很難完備遍歷。解這道題的技巧還是要使用遞歸。我們假定數到20有F(20)種不同的路徑,那么到達20這個數字,前一步只有兩個可能的情況,即從18直接跳到20,或者從19數到20。

由于從18跳到20和從19到20是不同的,因此達到20的路徑數量,其實就是達到18的路徑數量,加上達到19的路徑數量,也就是說,F(20)=F(18)+F(19)。類似地,F(19)=F(18)+F(17)。這就是遞推公式。

最后,F(1)只有一個可能,就是1,F(2)有兩個可能,要么直接跳到2,要么從1達到2。知道了F(1)=1和F(2)=2,就可以知道F(3)。知道F(3),就可以知道F(4),因為F(4)= F(3)+ F(2),以此類推,一直到F(20)即可。

聰慧如你,你一定看出來了,這就是著名的斐波那契數列,如果我們認為F(0)也等于1,那么這個數列就長成這樣:1(F(0)),1,2,3,5,8,13,21,……這個數列幾乎按照幾何級數的速度增長,到了F(20),就已經是10946了(可利用前面的【范例3-13】來測試)。因此,僅僅靠正向的窮舉法,基本上是不可能把所有情況都列舉出來的。

上述面試題來自于曾在就職于谷歌公司的吳軍博士。吳軍博士在分析這道面試題時指出,在數學和計算機上,等價性原則是一個非常重要的原則。很多問題的表象看起來紛繁復雜,但抽絲剝繭之后,其本質是等價的。

比如說,如果一個樓梯有20階,你每次可以爬一階歇一會,也可以兩階歇一會,爬到20階一共有多少種歇息法?這個問題的解,其實和“誰先搶到20”是一樣的,也是一個斐波那契數列。

除了前面講解的技巧,本章涉及的一些思維方式也值得讀者注意。從某種程度上來看,遞歸思維是一種以結果為導向,反向追尋,直到追尋到原點(遞歸的終止條件)的思維方式,一旦原點問題得以解決,其后的問題都會迎刃而解。

你看看,這是不是和埃隆·馬斯克(Elon Musk)等人常說的“第一性原理”思想有著類似之處呢?

本文部分節選自《Python極簡講義:一本書入門數據分析與機器學習》(張玉宏)【摘要 書評 試讀】- 京東圖書?item.jd.com

(張玉宏著,電子工業出版社,2020年5月出版)。更多理論推導及實戰環節,請參閱該書。

總結

以上是生活随笔為你收集整理的递归函数python有什么特点_Python中的递归的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。