從turtle海龜動畫 學習 Python - 高中彈性課程系列 8 碎形 (分形 fractal) 二叉樹

46 篇文章 1 订阅
32 篇文章 1 订阅

Goal: 藉由有趣的「海龜繪圖」學會基礎的 Python 程式設計
本篇介紹以 Python 海龜繪圖實作 1. 二叉樹碎形 2. Koch 雪花碎形, 最後討論遞迴 recursive 與 疊代 iterated (迴圈 for loop) 是否可以互相轉換.

“Talk is cheap. Show me the code.”
― Linus Torvalds

老子第41章
上德若谷
大白若辱
大方無隅
大器晚成
大音希聲
大象無形
道隱無名

拳打千遍, 身法自然

“There’s no shortage of remarkable ideas, what’s missing is the will to execute them.” – Seth Godin
「很棒的點子永遠不會匱乏,然而缺少的是執行點子的意志力。」—賽斯.高汀

本系列文章之連結

  • 從turtle海龜動畫學習Python-高中彈性課程1 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 2 安裝 Python, 線上執行 Python link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 3 烏龜繪圖 所需之Python基礎 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 4 烏龜開始畫圖 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 5 用函數封裝重複性指令-呼叫函數令烏龜畫正 n 邊形 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 6 畫多重旋轉圓,螺旋正方形 link

  • 從turtle海龜動畫 學習 Python - 7 遞歸 recursive - 高中彈性課程系列 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 8 碎形 (分形 fractal) link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 8.1 碎形 L-system link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 9 Python 物件導向介紹 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 9.1 Python 物件導向的練習 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 10 藝術畫 自定義海龜形狀 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 10.1 藝術畫 python繪製天然雪花結晶 https://blog.csdn.net/m0_47985483/article/details/122262036 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 10.2 藝術畫 Python 製作生成式藝術 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.1 氣泡排序 - 用 turtle 呈現演算法之執行動作 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.2 maze 迷宮 - 用 turtle 呈現演算法之執行動作 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.3 連分數演算法與轉轉相除法- 用 turtle 呈現演算法之執行動作 link

  • 從turtle海龜動畫 學習 Python - 高中彈性課程系列 11.4 最短路徑 Dijkstra- 用 turtle 呈現演算法之執行動作 link



(原 part2 之碎形部分)

7 Fractal tree 碎形樹- recursive 之應用

觀摩 YouTube上講解以 JavaScript 語言模擬"碎形樹" 的影片
Ref: https://www.youtube.com/watch?v=0jjeOYMjmDU
link

Ex: 你可以用 Python 的 turtle module 重現一次
“The Coding Train” 系列中, 模擬"碎形樹" 的動畫嗎?
Coding Challenge #14: Fractal Trees - Recursive

Python turtle 的 demo 也有:
turtleDemo_forest_2_IDLE
較單純沒有隨機的樹
Python_demo_tree

本篇作者模仿 “The Coding Train” 的用色呈現,
tree1_angle_headingLocal_20201011.py
背景色設為黑色
turtle.bgcolor('black')
T.pencolor('white')
codes 可以看下面 7.2
tree1_angle_headingLocal_20201011.py
把背景色修改一下
在这里插入图片描述

7.1 最簡單 2 叉碎形樹, 樹枝夾角為 45 度

因為demo 的源碼較不好讀,
以下我們試著用自己的較單純的設計方法去實現簡單2叉碎形樹的繪製,

以下是粗略的結構

# By Prof. P-J Lai MATH NKNU 20201007
# 以下是沒有隨機的版本


import turtle
T = Turtle()

def tree1(pos, length, angle, r):
    if length < 0.5:
        return
    
    T.goto(pos)
    T.lt(angle)
    T.fd(length)
    
    tree1(pos?, length*r, angle=angle-45)
    tree1(pos?, length*r, angle=angle+45)

    return

以下補上codes 細節
如果自己試著寫, 會發現相當困難, 牽涉到遞迴的執行, 是層層往深處呼叫, 直到最底層, 才獲得答案, 才一層一層往回給出前一層之解答,
所以必須先記錄往下疊代前, 當下的位置與角度 ( 此處如果你沒有自己試著寫, 並試著跑第一層第二層看看執行結果, 找出錯誤的原因, 嘗試錯誤, 你會不知道這段話的意義!)

# By Prof. P-J Lai MATH NKNU 20201007
# 修改自河西朝雄的 C 程式碼
# 以下是沒有隨機的版本

import turtle
T = turtle.Turtle()
r = 0.7
#T.lt(90)

def tree1(pos, length, angle):
    if length < 10:
        return
    
    T.setheading(angle)
    T.fd(length)
    pos = T.position()
    
    angle1=angle-45
    tree1(pos, length*r, angle1)

    
    angle2=angle+45
    T.penup()
    T.goto(pos)
    T.pendown()
    tree1(pos, length*r, angle2)

    return

tree1((0,0),100, 0)

執行結果
tree1_角度正確版本_goto_setheading
把顏色跟角度調整一下
程式碼改一下

import turtle
T = turtle.Turtle()
r = 0.7
#T.lt(90)
T.shape('turtle')
T.pencolor('red')
T.pensize(3)
T.fillcolor("blue")
,,,,

tree1((0,0),100, 90)

以下為往正北出發, 樹枝夾角=45
tree1_角度正確版本_正上_紅色

7.2 樹枝夾角為 可調整

以下我們將程式碼修改成增加夾角可以調整, 不一定都要是45度,
函數增加一個輸入引數
tree1(pos, length, headingAngle, angle)

結果發現錯誤, 原因在全域變數之問題

以下為往正北出發, 樹枝夾角=30, codes有 bug 之圖

tree1_angle_error_csdn_2_20201011.png

以下為錯誤之版本, 沒有增加局部變數 headingAngle1, headingAngle2
tree1_angle_error_csdn_2_20201011.py

# By Prof. P-J Lai MATH NKNU 20201007
# 修改自河西朝雄的 C 程式碼
# 增加夾角的引數
# 以下是以下為錯誤之版本, 沒有增加局部變數  headingAngle1,  headingAngle2,
# 兩個 分枝使用同一個局部變數 headingAngle
# tree1_angle_error_csdn_2_20201011.py


import turtle
T = turtle.Turtle()
r = 0.7
#T.lt(90)
T.shape('turtle')
T.pencolor('red')
T.pensize(3)
T.fillcolor("blue")


def tree1(pos, length, headingAngle, angle):
    if length < 10:
        return
    
    T.setheading(headingAngle)
    T.fd(length)
    pos = T.position()
    
    #headingAngle=headingAngle-45
    headingAngle=headingAngle-angle
    tree1(pos, length*r, headingAngle, angle)

    
    headingAngle= headingAngle + angle
    #headingAngle2= headingAngle + angle
    T.penup()
    T.goto(pos)
    T.pendown()
    tree1(pos, length*r, headingAngle, angle)

    return

#tree1((0,0),100, 0)
tree1((0,0),100, 90, 30)

修正後:
以下是修訂之後的版本, 增加2個局部變數 headingAngle1, headingAngle2, 會紀錄當下的 heading angle (海龜的前進方向)

以下為往正北出發, 樹枝夾角=30, codes 正確 之圖
tree1_增加夾角_正確_全域變數之問題

tree1_angle_headingLocal_20201011.py

# By Prof. P-J Lai MATH NKNU 20201007
# 修改自河西朝雄的 C 程式碼
# 增加夾角的引數
# 以下是沒有隨機的版本
tree1_angle_headingLocal_csdn_20201011.py

import turtle
T = turtle.Turtle()
r = 0.7
#T.lt(90)
T.shape('turtle')
T.pencolor('red')
T.pensize(3)
T.fillcolor("blue")


def tree1(pos, length, headingAngle, angle):
    if length < 10:
        return

    headingLocal = headingAngle
    T.setheading(headingLocal)
    T.fd(length)
    pos = T.position()
    
    #headingAngle=headingAngle-45
    headingLocal_1=headingLocal - angle
    tree1(pos, length*r, headingLocal_1, angle)

    headingLocal_2= headingLocal + angle
    T.penup()
    T.goto(pos)
    T.pendown()
    tree1(pos, length*r, headingLocal_2, angle)

    return

#tree1((0,0),100, 0)
tree1((0,0),100, 90, 30)


7.3 樹枝夾角改為隨機擾動

樹枝夾角增加隨機擾動, 擾動值範圍 = 夾角的 0 ~ 0.7倍
codes 增加:

if random.randint(0,1)>0:
        randomPerturbation = random.random()*0.7
    else:
        randomPerturbation = -random.random()*0.7
        
headingLocal_1=headingLocal - angle*(1+randomPerturbation) 
    
tree1_random_angle(pos, length*r, headingLocal_1, angle)

tree1_夾角隨機擾動_

tree1_random_angle_20201011.py

# By Prof. P-J Lai MATH NKNU 20201007
# 修改自河西朝雄的 C 程式碼
# 增加夾角的引數
# 以下是樹枝夾角改為隨機擾動的版本


import turtle
import random
T = turtle.Turtle()
r = 0.7
#T.lt(90)
T.shape('turtle')
T.pencolor('red')
T.pensize(3)
T.fillcolor("blue")


def tree1_random_angle(pos, length, headingAngle, angle):
    if length < 10:
        return

    headingLocal = headingAngle
    T.penup()
    T.goto(pos)
    T.pendown()
    T.setheading(headingLocal)
    T.fd(length)
    pos = T.position()
    
    #headingAngle=headingAngle-45

    if random.randint(0,1)>0:
        randomPerturbation = random.random()*0.7
    else:
        randomPerturbation = -random.random()*0.7
        
    headingLocal_1=headingLocal - angle*(1+randomPerturbation) 
    
    tree1_random_angle(pos, length*r, headingLocal_1, angle)

    if random.randint(0,1)>0:
        randomPerturbation = random.random()*0.7
    else:
        randomPerturbation = -random.random()*0.7

    headingLocal_2= headingLocal + angle*(1+randomPerturbation)
    T.penup()
    T.goto(pos)
    T.pendown()
    tree1_random_angle(pos, length*r, headingLocal_2, angle)

    return

#tree1((0,0),100, 0)
tree1_random_angle((0,0),100, 90, 45)

以下是 長 200, 夾角 45 隨機小擾動, 起始位置 = (0, -300)

tree1_random_angle((0,-300),200, 90, 45)

7.4 左右分枝不同的收縮率

以下參考河西朝雄的 ch8, 讓左右是不同的收縮率, 左邊的收縮率是右邊的 0.8 倍, 造成枝葉偏右的型態

>>>tree1_random_angle_diff_length((0,-300),150, 90, 20)

tree1_random_angle_左右不同縮率_右邊較長
tree1_random_angle_diff_length(pos, length, headingAngle, angle)

# By Prof. P-J Lai MATH NKNU 20201007
# 修改自河西朝雄的 C 程式碼
# 增加夾角的引數
# 以下是樹枝夾角改為隨機擾動的版本
# 以下參考河西朝雄的 ch8,  讓左右不同的縮收率, 左邊是右邊的0.8倍, 造成枝葉偏# 右的型態


import turtle
import random
T = turtle.Turtle()
right_ratio = 0.8
left_ratio = 0.64
#T.lt(90)
T.shape('turtle')
T.pencolor('red')
T.pensize(2)
T.fillcolor("blue")
T.speed(0)
turtle.tracer(0,0)


def tree1_random_angle_diff_length(pos, length, headingAngle, angle):
    if length < 10:
        return

    headingLocal = headingAngle
    T.penup()
    T.goto(pos)
    T.pendown()
    T.setheading(headingLocal)
    T.fd(length)
    pos = T.position()
    
    #headingAngle=headingAngle-45

    if random.randint(0,1)>0:
        randomPerturbation = random.random()*0.7
    else:
        randomPerturbation = -random.random()*0.7
        
    headingLocal_1=headingLocal - angle*(1+randomPerturbation) 
    
    tree1_random_angle_diff_length(pos, length*right_ratio, headingLocal_1, angle)

    if random.randint(0,1)>0:
        randomPerturbation = random.random()*0.7
    else:
        randomPerturbation = -random.random()*0.7

    headingLocal_2= headingLocal + angle*(1+randomPerturbation)
    T.penup()
    T.goto(pos)
    T.pendown()
    tree1_random_angle_diff_length(pos, length*left_ratio, headingLocal_2, angle)

    return

tree1_random_angle_diff_length((0,-300),150, 90, 20)

Ex 左右分枝的長度收縮率 隨機擾動

Ex: 之前的 codes, 左右分枝的長度收縮率是在程式裡面直接給定, 例如, right_ratio = 0.8, left_ratio = 0.64,
請同學改成, 左右分枝的長度收縮率有一個隨機擾動的範圍, 並觀察圖形是否更自然更有變化.

8 Von Koch Curve 寇赫雪花碎形曲線- recursive 之應用

修改河西朝雄 8-7 KochRei62.c

遞迴 第 0 層, 就是畫一條直線段

以下為遞迴 1 層, 此形狀英文叫 motif, (動機), 一般碎形(分形) 就是持續用 motif 替換每一個小線段, 畫出 碎形的前幾層, 理論上的碎形, 是重複做無窮次替換而得之圖形.
Sec8_7KochRei62_1層

以下為遞迴 2 層

Sec8_7KochRei62_2層

以下為遞迴 5 層
Sec8_7KochRei62_5層

#2013/11/28  by Prof. P-J Lai MATH NKNU 修改河西朝雄8-7KochRei62之執行

from turtle import *
def koch(turtle, n,leng):   # Koch 遞迴的程序
    if (n==0):
        turtle.fd(leng)
    else: 
        koch(turtle,n-1,leng)
        turtle.lt(60)
        koch(turtle,n-1,leng)
        turtle.lt(-120)
        # self.rt(120 )
        koch(turtle,n-1,leng)
        turtle.lt(60)
        koch(turtle,n-1,leng)
    
def main():
    T=Turtle()
    T.shape('turtle')
    T.color('blue','green')
    T.speed(0)
    #以下三行只是希望將烏龜的起始位置移到(-200,0)的地方
    #以利烏龜出現在視窗較適當的位置, 當length改變時, 或是層數n 改時, 
    #此起始位置還要自己再修改

    T.penup()
    T.goto(-600,-100)
    T.pendown()
    koch(T,5,5)

main()


以下是參考tdemo fractalcurves.py 物件導向,
與 河西朝雄 8-7 KochRei62.c, Dr62_1.c 十字繡之寫法
兩份的寫法,

kochSquare() 是用長城形狀, 參考 Dr62_1.c 十字繡之寫法

# revise from tdemo_fractalcurves.py
# 修改河西朝雄8-7KochRei62,
# kochSquare 是用長城形狀, 參考 Dr62_1.c 十字繡之寫法
# Koch curve 20160520

from turtle import *
from time import sleep, time

class Koch(Pen):

    def koch(self, dist, depth, dir):
            if depth < 1:
                self.fd(dist)
                return
            self.koch(dist / 3, depth - 1, dir)
            self.lt(60 * dir)
            self.koch(dist / 3, depth - 1, dir)
            self.rt(120 * dir)
            self.koch(dist / 3, depth - 1, dir)
            self.lt(60 * dir)
            self.koch(dist / 3, depth - 1, dir)
            
    def kochSquare(self,dist, depth, dir):
        if depth<1:
            self.fd(dist)
            return
        self.kochSquare(dist/3, depth-1,dir)
        self.lt(90*dir)
        self.kochSquare(dist/3,depth-1,dir)
        self.rt(90*dir)
        self.kochSquare(dist/3,depth-1,dir)
        self.rt(90*dir)
        self.kochSquare(dist/3,depth-1,dir)
        self.lt(90*dir)
        self.kochSquare(dist/3,depth-1,dir)
        
def main():
    kochT=Koch()
    kochT.speed(0)
    kochT.pensize(4)
    kochT.pu()
    kochT.setpos(-300,50)
    kochT.pd()
    kochT.size=6
    kochT.shape("turtle")
    kochT.color("blue","red")
    #Screen().delay(100)
    #Screen().tracer(8,25)
    kochT.koch(500,2,1)

    kochT.color("yellow","red")
    kochT.pu()
    kochT.setpos(-300,-100)
    kochT.pd()
    kochT.kochSquare(500,3,1)
    return
    
    

if __name__  == '__main__':
    msg = main()
    print(msg)
    mainloop()

Ex: 1. 將以上改寫成, 每小段之長度有隨機擾動,
2. 將以上改寫成, 每小段之正三角形方向為隨機往外或往內,

9. 遞迴 recursive 與 疊代 iterated (迴圈 for loop) 是否可以互相轉換?

  • 循環 (for, while loop) 效能很高, 但是遞迴 (recursive) 的程式碼結構簡潔一目了然 (假設你已經很懂遞迴的語法).

  • 尾遞迴效能與循環接近

在 5. 那裏我們舉了 2 n 2^n 2n, 累加, 費氏數列, 它們 recursive 寫法與 loop 寫法是可以互相轉換, 但是對一般的狀況, 兩者是否確實等價的?

在網路上查詢, 理論上是可以的, 但是在 知乎的文章:
所有递归都可以改写成循环吗?
https://www.zhihu.com/question/29373492
link
有回答:
栈底不可预见的时候,递归是无法有效地化为循环的。If you maintain your own stack it would be correct. Otherwise recursion can do things loops can’t, like walk a tree.[http://c2.com/cgi/wiki?RecursionVsLoop] link

作者:高木端
链接:https://www.zhihu.com/question/20418254/answer/15081000 link
来源:知乎

較常見的是將遞迴改寫成尾遞迴 tail-recursive, 尾遞迴效能類似 loop, 有許多程式之編譯器 (C, JavaScript等) 會自動把遞迴的程式最佳化為尾遞迴.

  • 以下可以等進階時再細看

可以參考Baidu百科, n ! n! n! 的尾遞迴寫法:

Ref: https://baike.baidu.com/item/%E5%B0%BE%E9%80%92%E5%BD%92#reference-[1]-1439396-wrap link

示例3-2:

/*facttail.c*/
 
#include"facttail.h"
 
/*facttail*/
 
 
int facttail(int n, int a)
{
 
    /*Compute a factorialina tail - recursive manner.*/
     
    if (n < 0)
        return 0;    
    else if (n == 0)
        return 1;    
    else if (n == 1)
        return a;
    else
        return facttail(n - 1, n * a);
 
}

Ex: 將 5 那裏我們舉了 2 n 2^n 2n, 累加, 費氏數列, 用尾遞迴改寫, 並比較尾遞迴, 遞迴, for loop 三種寫法 的效能差異.

  • 以上可以等進階時再細看

References

  • 觀摩 YouTube上講解以 JavaScript 語言模擬"碎形樹" 的影片
    Ref: https://www.youtube.com/watch?v=0jjeOYMjmDU
    link

  • 生成式藝術和演算法創作07 L, https://zhuanlan.zhihu.com/p/50712698 link

  • Michael Hansmeyer:塑造不可思议, https://v.youku.com/v_show/id_XNDc4MDM2NzA4.html link

  • 河西朝雄的 C 程式碼

  • 所有递归都可以改写成循环吗?
    https://www.zhihu.com/question/29373492
    link

  • 從 Python 海龜繪圖初入門到使用遞迴(遞歸) 畫嵌套圖形, 碎形及進階迷宮演算法等之很好的文章: 数据结构与算法(Python版)——(3)通俗易懂的介绍递归(上), https://blog.csdn.net/weixin_45901519/article/details/109456646 link

  • 栈底不可预见的时候,递归是无法有效地化为循环的。If you maintain your own stack it would be correct. Otherwise recursion can do things loops can’t, like walk a tree.[http://c2.com/cgi/wiki?RecursionVsLoop] link

  • 高木端, 链接:https://www.zhihu.com/question/20418254/answer/15081000 link 来源:知乎

  • n ! n! n! 的尾遞迴寫法, https://baike.baidu.com/item/%E5%B0%BE%E9%80%92%E5%BD%92#reference-[1]-1439396-wrap link

  • Python——畫一棵漂亮的樱花樹, https://blog.csdn.net/weixin_43943977/article/details/102691392 link
    非常漂亮的樹!

  • 呈現碎形樹 “Coding Challenge #14: Fractal Trees - Recursive”
    https://www.youtube.com/watch?v=0jjeOYMjmDU&t=8s, link

  • Generative art in Python: Fractal Trees
    https://youtu.be/EICpm9rnPjE link

  • Generative art in Python: Basic Tiling, https://youtu.be/Cm_SzDlQ2cM link

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值