不會編程的程序員不用懂遞歸

不會編程的程序員不用懂遞歸

文 | 太陽雪html

來源:Python 技術「ID: pythonall」python

不會編程的程序員不用懂遞歸

編程之因此魅力無比,其中一個重要的緣由是,一次寫入,屢次運行,你可能和我同樣想象過這樣的場景:半躺在椅子上,優雅的點擊一個按鈕,新的宇宙啓動了……程序員

不要以爲這是個幻想,也別隻將其停留在幻想之中,這個幻想說明了一個問題,程序能夠幫助咱們完成更多的事情,而起始點無比的簡單,今天咱們一塊兒聊聊遞歸的事情,也許它就是啓動宇宙的那個小小按鈕算法

關於遞歸

一種計算過程,若是其中每一步都要用到前一步或前幾步的結果,稱爲遞歸的。用遞歸過程定義的函數,稱爲遞歸函數,例如連加、連乘及階乘等。凡是遞歸的函數,都是可計算的,便可行的。編程

古典遞歸函數,是一種定義在天然數集合上的函數,它的未知值每每要經過有限次運算迴歸到已知值來求出,故稱爲「遞歸」。它是古典遞歸函數論的研究對象編程語言

遞歸的應用場景

遞歸做爲一種算法思想,應用的場景不少,例如著名的漢諾塔問題,即有三個柱子,最左邊的上面從小到大串着必定數量的盤子,如今須要將它們移動到最右邊的柱子上,原則是移動過程當中,以及最終的結果,必須保存小盤子在大盤子上面ide

不會編程的程序員不用懂遞歸
漢諾塔函數

還有斐波那契數列,即除了第一個和第二個數爲1,其他的數都是前兩個數之和,例如 1,1,2,3,5,8……,還有階乘算法,即,正整數列中,個數爲 n 的數相乘,等等舉不勝舉工具

在計算機算法中遞歸更經常使用,例如二分法查找,快速排序,樹的遍歷,即對於執行規模不肯定,但能拆解爲簡單步驟的算法,遞歸幾乎都有用武之地,不但能夠解決問題,並且語義也更清晰優化

平常應用中,遞歸也很廣泛,例如遍歷文件夾查找文件,處理 XML 文件,再例如 應收帳款和收款的匹配,還有最近我 在 TMS 項目遇到的處理箱單層層包裹的關係問題,等等舉不勝舉

遞歸與其說是一種編程語言的能力,不如說是一種分析和思考問題的能力,從問題中發現規律,將大問題轉換爲小問題,知道能夠簡單計算爲止,或者正向思考,前面步驟是後面步驟的計算條件,不斷的計算前面的結果,最終獲得預訂步驟的結果。

遞歸的特色

既然遞歸如此好,有沒有限制呢?固然有,任何事物都具備兩面性,解決問題時要揚長避短

優勢

  1. 讓代碼更簡潔,邏輯更清晰,代碼量更少
  2. 爲算法設計和代碼調試提供便利

注意事項

  1. 必須有終止條件,不然將會陷入死循環
  2. 遞歸規模須要有控制,不然會形成程序堆棧溢出,具體受制於操做系統環境,python 默認爲 1000 層
  3. 構造方法中不能使用遞歸
  4. 遞歸能夠簡化程序邏輯,但不能提升運行效率,遞歸的基礎是經過在內存中建立堆棧完成的,若是不加優化,處理過程是非並不是的
  5. 遞歸算法的時間複雜度爲 O(n3),即運算時間會以規模的3次冪速度增加,能夠這麼感覺下,假如漢諾塔中,盤子有64層,用如今最早進的計算,所花的時間將遠遠超過宇宙的年齡

實踐

終於到了實踐部分了,爲您的耐心點贊,咱們用遞歸算法畫一棵分形樹

分形樹就是一棵樹的局部形狀和總體形狀類似,天然界中很常見,例如一片葉子上的形狀,和整棵樹的形狀類似,即整棵樹由樣式相同的無數個從小到大的形狀構成

不會編程的程序員不用懂遞歸
天然分形樹

那麼如何用程序畫出一個分形樹呢?能夠先畫最小的,而後畫次大的,最後組成整個形狀,就是今天聊的 遞歸算法

Turtle 繪圖庫

既然要畫,就須要安裝一個畫庫工具,Python 中有各式各樣的繪圖包,咱們選用 Turtle 庫,庫如其名,能夠將 Turtle 想象成一個小烏龜,逐漸在海灘上移動,畫出一個圖形來,經過將簡單圖形組合起來,能夠輕鬆地繪製出精美的形狀和圖案
安裝

> pip install turtle

概念

  • 畫布: 就是 turtle 爲咱們展開用於繪圖區域,能夠設置它的大小和初始位置
  • 畫筆: 就是用來繪製線條的筆頭,能夠設置粗細,顏色,方向等屬性
  • 繪圖命令: 用來控制畫筆朝向、移動、落筆、起筆等動做的,還有命令是用於作全局設置的,例如清除畫布,撤銷前一個動做等

示例

有了這些概念,就能夠經過代碼來設置畫布,控制畫筆繪製圖形了,代碼很簡單:

from turtle import Turtle

myTurtle = Turtle()
myTurtle.forward(100)
  • 先引入構造方法 Turtle
  • 建立一個 Turtle 實例,會初始化一個畫筆,而且將畫筆放置在畫布中心位置,方向朝右
  • 調用向前移動方法 forward 畫一條長度爲 100 個像素的直線

繪製分形樹

有了繪製工具,就解決了技術問題,如今能夠專心思考算法了

問題設定

爲了簡便,選擇最簡單的二叉分形樹來畫,最基本形狀是一個二叉樹,由樹幹和左右兩個枝叉組成,左右樹杈於樹幹的夾角設置爲 20 度,像一個 Y 字

問題分析

  • 常規思惟:先畫根樹幹,而後分別畫兩個樹杈,每一個樹杈又是一棵樹,接着在各自的樹幹上再畫出兩個樹杈,如此循環往復,直到畫出最小樹的兩個樹杈爲止

  • 遞歸思惟:在畫根樹幹後,假如先畫右側分枝,畫完後,這個分枝若是不是最小樹的枝,就須要爲其畫右分支,如此往復,直到畫出了最小樹的右側分枝,此時就能夠畫最小樹的左側分枝了,當畫完左側分枝後,就畫完整了一顆最小樹,因而能夠畫比最小數大一級的樹的左側分枝了,如此往復,直到繪製完整棵樹

是否是有點繞?下面的圖片展現了部分繪製過程,可能更直觀:

不會編程的程序員不用懂遞歸

實現

直接上代碼:

from turtle import Turtle

# 分形樹遞歸方法
def fractalTree(branchLen, t):
    if branchLen > 0:
        t.forward(branchLen)
        t.right(20)
        fractalTree(branchLen - 15, t)
        t.left(40)
        fractalTree(branchLen - 15, t)
        t.right(20)
        t.backward(branchLen)

# 初始化畫布和畫筆
t = Turtle()  # 建立畫布實例
t.screen.delay(3)  # 設置繪製顯示延遲,以便觀察過程
t.pensize(2)  # 設置畫筆粗細爲 2 個像素
t.color('green')  # 設置畫筆顏色爲 綠色
t.left(90)  # 向左旋轉 90 度,即讓畫筆朝上
t.up()  # 擡起畫筆,即在移動時不會繪製
t.backward(300)  # 相對畫筆朝向,向後退 300 個像素
t.down()  # 落下畫筆,準備繪製

# 繪製分形樹,根樹幹長度爲 105 像素
fractalTree(105, t)

# 獲取畫布窗口,並設置關閉條件爲點擊窗口,以便在執行完成後保留繪圖窗口
t.getscreen().exitonclick()

上面代碼分爲三部分,Turtle 引入、分形樹方法定義,以及分形樹繪製

Turtle 引入沒必要解釋,分形樹繪製部分有詳細註釋,也沒必要多說,主要分析下分形樹方法 fractalTree:

  • 首先設置退出條件,即當要繪製的樹幹長度小於等於 0 時,結束繪製過程,再也不繼續繪製
  • 若是繪製的樹幹長度大於 0,先向前繪製出樹幹,注意,畫筆方向已經被設置爲向上
  • 而後將畫筆向右調整 20 度,準備繪製右側樹枝
  • 右側樹枝應該比樹幹短一下,這裏設置爲短 15 個像素(不一樣的數值畫出的樹略有不一樣),因爲不知道當前樹是否爲最小樹,因此將畫右側樹枝的任務,交給分形樹方法,同時設定了根樹幹長度,即,當前樹幹長度減去 15 像素
  • 若是右側樹枝畫完了,向左轉 40 度,即 20 度將畫筆調整爲向上,再轉 20 度,就爲畫左側樹枝的角度
  • 同畫右側樹枝同樣,將當前樹幹長度減 15 像素,做爲左側樹杈長度
  • 若是左側樹枝畫完後,將畫筆向右調整 20 度,即與樹幹方向一致,而後回退到樹幹起點

總體上看,過程就是,畫樹幹,而後畫右側分枝,再畫左側分枝,最後回到起點,不過在畫兩側分枝時,咱們用了遞歸,讓畫樹的過程獲得重複利用,若是順利,就會看到上面展現的繪製過程,趕忙試試吧

花絮

看似上面過程很順利,其實否則,我在編寫代碼時,不斷的陷入思惟的漏洞,要麼是設置錯了畫筆方向,要麼是畫筆沒有回退到位,等等,烏龍無數(汗!),看下翻車記錄:

不會編程的程序員不用懂遞歸
失敗案例1

再來一個
不會編程的程序員不用懂遞歸
失敗案例2

不過也挺好看的,不是嗎

總結

今天經過畫一顆分形樹,瞭解了一下遞歸的概念和應用,總體感受遞歸代碼短小,但思考過程比正常編程過程要複雜的多,不過這就是編程的魅力,更確切的說是數學的魅力,用簡單的符號語言,描繪紛繁複雜的世界…… 最重要的是,Python 支持遞歸 示例代碼中,不只有分形樹方法及翻車代碼,還有個彩蛋,歡迎把玩

參考

相關文章
相關標籤/搜索