1. 程式人生 > >遞歸的邏輯(4)——遞歸與分形

遞歸的邏輯(4)——遞歸與分形

必須 公眾號 lin main 繼續 -s 斜線 循環 驚人的

  《最強大腦》第四季的一期節目中,挑戰者余彬晶挑戰的項目是“分形之美”。這是一個數學推理項目,章子怡女神和不懂球的胖子都一臉迷茫。

技術分享圖片

分形的概念

  分形(Fractal)一詞,是曼德布羅特創造出來的,其原意具有不規則、支離破碎等意義,分形幾何學是一門以非規則幾何形態為研究對象的幾何學。由於不規則現象在自然界是普遍存在的,因此分形幾何又稱為描述大自然的幾何學。

  分形通常被定義為“一個粗糙或零碎的幾何形狀,可以分成數個部分,且每一部分都(至少近似地)是整體縮小後的形狀,即具有自相似的性質。”

  分形圖無處不在,綿延的海岸線,從遠距離觀察,其形狀是極不規則的,但是從近距離觀察,其局部形狀又和整體形態相似;一顆參天大樹,它的每一片葉子和枝幹,都和主枝顯現出了高度的相似性;一片雪花的每片花瓣都在放大後都顯出了原圖案驚人的相似性。

技術分享圖片

  既然分形圖的每一部分都是整體的縮小,那麽很自然地會聯想到自身調用自身的程序。我們嘗試用遞歸去繪制一顆分形樹,下圖是繪制的目標:

技術分享圖片

極坐標系下的向量旋轉

  雖然繪制的目標是二維圖形,但是比起刻度尺來,樹的坐標關系要復雜的多,繼續在直角坐標系下處理就顯得有點笨拙了,此時不妨試試極坐標。

  極坐標用向量的長度和角度來表示坐標中的點,一個典型的極坐標如下:

技術分享圖片

  r是向量的長度,φ是向量與x軸逆時針方向的夾角,點t可以用(r,φ)來表示。可以看出,極坐標系仍未脫離原來的直角坐標系,僅僅是將直角坐標系上的點換了一種表示法,如果將點t 轉換成直角坐標系的表示法,那麽:

技術分享圖片

  這也是極坐標到直角坐標的轉換公式。

  接下來將向量逆時針旋轉θ,得到新的t’點:

技術分享圖片

  t’點的坐標:

技術分享圖片

  樹的主幹垂直於x軸,簡單起見,可以讓它附著在y軸上,這相當於φ等於90°:

技術分享圖片

  接下來,把一個稍短的向量r’向左旋轉θ角得到點B,向右旋轉-θ角得到點C:

技術分享圖片

  如此一來可以得到B和C的坐標:

技術分享圖片

  作為樹的枝幹,還需要將兩個新向量上移,讓它們以A為起點:

技術分享圖片

  現在得到了樹的兩個枝幹以及枝幹端點的坐標:

技術分享圖片

  繼續按照上述方法進行下去將會得到更多的枝幹:

技術分享圖片

編寫代碼

  知道原理後就可以編寫相關代碼:

 1 class Fractal:
 2     def __init__(self, fai, theta, depth):
3 ‘‘‘ 4 分形圖的基礎形狀 5 Attributes: 6 fai: 向量的初始角度 7 theta: 向量每次逆時針旋轉的角 8 depth: 樹的深度, 當depth == 0時停止分形 9 ‘‘‘ 10 self.fai = fai 11 self.theta = theta 12 self.depth = depth 13 14 def draw(self, ax): 15 def draw_line(x1, y1, fai, depth, ax): 16 if depth == 0: 17 return 18 # 由於np.cos和np.sin使用的參數是弧度,所以需要先把角度轉換成弧度 19 radian = np.radians(fai) 20 # 旋轉後的坐標 21 x2 = x1 + np.cos(radian) * depth 22 y2 = y1 + np.sin(radian) * depth 23 ax.plot([x1, x2], [y1, y2], color=g) 24 draw_line(x2, y2, fai + self.theta, depth - 1, ax) 25 draw_line(x2, y2, fai - self.theta, depth - 1, ax) 26 draw_line(0, 0, self.fai, self.depth, ax) 27 28 if __name__ == __main__: 29 fig, ax = plt.subplots() 30 plt.axis(off) 31 plt.axis(equal) 32 fractal = Fractal(90, 30, 8) 33 fractal.draw(ax) 34 plt.show()

  我們用數的深度表示向量的長度,每一層枝幹的長度都比上一層少1,直到深度是0為止。每一層枝幹都是由上一層枝幹的終點坐標、偏斜角和枝幹長度決定的。fai的初始值是90°,用draw_line(0, 0, 90, depth)來畫樹的主幹,這樣才能保證主幹是附著在y軸上,主幹的終點坐標:

技術分享圖片

弧度和角度的轉換公式是1rad=180°/π,π是無限不循環小散,因此python中這個轉換是不精確的。如果直接運行np.cos(np.radians(90)),不會得到0,而是得到6.123233995736766e-17,這是一個相當小的數,可以當作0處理。

  運行結果如下:

技術分享圖片

  可以通過改變初始深度來觀察樹的分型過程:

if __name__ == __main__:
    # 觀察分形過程
    fig = plt.figure()
    for i in range(1, 9):
        ax = fig.add_subplot(3, 3, i)
        fractal = Fractal(90, 30, i)
        plt.axis(off)
        plt.axis(equal)
        plt.title(depth= + str(i))
        fractal.draw(ax)
    plt.show()

技術分享圖片

  在繪制depth =1的分形樹時,代碼中plt.axis(‘equal‘)這句話是必須的,它將使x軸和y軸的定標系數相同,即單位長度相同。如果將其註釋掉,由於角度和弧度間的不精確轉換,將得到一條斜線:

技術分享圖片

  斜線是根據(0,0)和(6e-17, 1)繪制的。在添上plt.axis(‘equal‘)後,這個微小的斜率就可以忽略不計了:

技術分享圖片

  通過改變旋轉角,可以得到一些有趣的分形:

技術分享圖片

  


   作者:我是8位的

  出處:http://www.cnblogs.com/bigmonkey

  本文以學習、研究和分享為主,如需轉載,請聯系本人,標明作者和出處,非商業用途!

  掃描二維碼關註公眾號“我是8位的”

技術分享圖片

遞歸的邏輯(4)——遞歸與分形