用Python海龟画一棵会“生长”的树:从科赫雪花到分形二叉树的保姆级教程

看着屏幕上由代码生成的树木逐渐“生长”出来,仿佛能感受到数字世界里的生命力。这种将数学规律转化为视觉艺术的过程,正是编程与创意结合的迷人之处。今天,我们就用Python的turtle模块,从最基础的分形概念出发,一步步创造属于你自己的数字森林。

1. 准备工作:理解分形与递归

分形是大自然的几何语言。仔细观察一片雪花、一棵树或一段海岸线,你会发现它们的局部与整体有着惊人的相似性——这就是分形的核心特征:自相似性。数学家芒德布罗在20世纪70年代系统性地提出了分形理论,为我们理解这些复杂形态提供了工具。

递归则是实现分形的编程利器。简单来说,递归就是“自己调用自己”的过程。想象一下俄罗斯套娃:每个套娃内部都包含一个更小的、结构相同的套娃。在编程中,递归函数通过不断调用自身来解决问题,直到满足某个终止条件。

分形与递归的经典组合:

  • 科赫雪花:通过无限细分三角形边来模拟雪花
  • 分形树:通过重复分支模式模拟植物生长
  • 曼德勃罗集:通过简单公式迭代产生无限复杂的图案

2. 搭建基础:科赫雪花的绘制

让我们从经典的科赫雪花开始热身。这个看似复杂的图形,其实只需要几行递归代码就能实现。

2.1 科赫曲线的构建原理

科赫曲线的生成遵循一个简单的规则:

  1. 画一条直线(0阶)
  2. 将线段三等分,用等边三角形的两边替换中间段(1阶)
  3. 对每个新线段重复步骤2(n阶)
import turtle

def koch_curve(length, depth):
    if depth == 0:
        turtle.forward(length)
    else:
        koch_curve(length/3, depth-1)
        turtle.left(60)
        koch_curve(length/3, depth-1)
        turtle.right(120)
        koch_curve(length/3, depth-1)
        turtle.left(60)
        koch_curve(length/3, depth-1)

2.2 从曲线到雪花

将三条科赫曲线以等边三角形的方式组合,就得到了科赫雪花:

def draw_koch_snowflake():
    turtle.speed(0)
    turtle.penup()
    turtle.goto(-150, 90)
    turtle.pendown()
    
    for _ in range(3):
        koch_curve(300, 4)
        turtle.right(120)
    
    turtle.hideturtle()
    turtle.done()

运行这段代码,你会看到一个精致的雪花图案逐渐在屏幕上展开。尝试调整depth参数(比如从2到5),观察不同迭代次数下图形的变化。

3. 进阶挑战:分形二叉树的实现

掌握了科赫雪花后,我们来创造更有生命感的图形——会“生长”的树。

3.1 基础分形树

最基本的二叉树遵循这样的递归规则:

  1. 画主干(向前移动)
  2. 向右转一定角度,画右侧分支(缩短长度)
  3. 向左转两倍角度,画左侧分支(缩短长度)
  4. 重复直到分支长度小于阈值
def draw_tree(branch_len, angle):
    if branch_len > 5:
        turtle.forward(branch_len)
        turtle.right(angle)
        draw_tree(branch_len * 0.7, angle)
        turtle.left(angle * 2)
        draw_tree(branch_len * 0.7, angle)
        turtle.right(angle)
        turtle.backward(branch_len)

3.2 让树更逼真

基础版本虽然正确,但看起来有些机械。我们可以通过以下改进增加真实感:

树干粗细变化:

def draw_tree(branch_len, angle, pen_size):
    turtle.pensize(pen_size)
    if branch_len > 5:
        turtle.forward(branch_len)
        turtle.right(angle)
        draw_tree(branch_len * 0.7, angle, pen_size * 0.8)
        turtle.left(angle * 2)
        draw_tree(branch_len * 0.7, angle, pen_size * 0.8)
        turtle.right(angle)
        turtle.backward(branch_len)

添加树叶效果:

def draw_tree(branch_len, angle, pen_size):
    turtle.pensize(pen_size)
    if branch_len > 5:
        # 树干和树枝为棕色
        turtle.pencolor("brown")
        turtle.forward(branch_len)
        
        # 右分支
        turtle.right(angle)
        draw_tree(branch_len * 0.7, angle, pen_size * 0.8)
        
        # 左分支
        turtle.left(angle * 2)
        draw_tree(branch_len * 0.7, angle, pen_size * 0.8)
        
        # 小分支画成绿色(树叶)
        if branch_len < 15:
            turtle.pencolor("green")
            turtle.pensize(3)
        
        turtle.right(angle)
        turtle.backward(branch_len)

4. 注入生命力:随机性与自然感

真正的树木不会完全对称。通过引入随机性,我们可以让数字树更加自然。

4.1 添加随机扰动

import random

def draw_tree(branch_len, angle, pen_size):
    turtle.pensize(pen_size)
    if branch_len > 5:
        # 添加长度随机变化(±10%)
        actual_len = branch_len * random.uniform(0.9, 1.1)
        turtle.forward(actual_len)
        
        # 添加角度随机变化(±30%)
        right_angle = angle * random.uniform(0.7, 1.3)
        turtle.right(right_angle)
        draw_tree(branch_len * 0.7, angle, pen_size * 0.8)
        
        left_angle = angle * random.uniform(0.7, 1.3) * 2
        turtle.left(left_angle)
        draw_tree(branch_len * 0.7, angle, pen_size * 0.8)
        
        turtle.right(right_angle)
        turtle.backward(actual_len)

4.2 季节变化效果

通过修改颜色,我们可以模拟不同季节的树木:

def set_seasonal_color(branch_len, season):
    if branch_len < 15:  # 树叶
        if season == "spring":
            turtle.pencolor("#7CFC00")  # 嫩绿
        elif season == "summer":
            turtle.pencolor("#228B22")  # 深绿
        elif season == "autumn":
            turtle.pencolor(random.choice(["#FFA500", "#FF4500", "#FFD700"]))  # 橙/红/金
        else:  # winter
            turtle.pencolor("#A9A9A9")  # 灰色
    else:  # 树干
        turtle.pencolor("#8B4513")  # 棕色

5. 创意扩展:打造你的数字森林

掌握了基本技术后,让我们发挥创意,创造更丰富的视觉效果。

5.1 多棵树组合

def draw_forest():
    turtle.speed(0)
    turtle.left(90)
    turtle.penup()
    
    positions = [(-200, -100), (0, -100), (200, -100)]
    sizes = [80, 120, 100]
    
    for pos, size in zip(positions, sizes):
        turtle.goto(pos)
        turtle.pendown()
        draw_tree(size, 20, size/15)
        turtle.penup()
    
    turtle.hideturtle()
    turtle.done()

5.2 添加环境元素

def draw_environment():
    # 画地面
    turtle.pencolor("green")
    turtle.penup()
    turtle.goto(-400, -100)
    turtle.pendown()
    turtle.begin_fill()
    for _ in range(2):
        turtle.forward(800)
        turtle.right(90)
        turtle.forward(50)
        turtle.right(90)
    turtle.end_fill()
    
    # 画太阳
    turtle.penup()
    turtle.goto(300, 200)
    turtle.pendown()
    turtle.pencolor("yellow")
    turtle.fillcolor("yellow")
    turtle.begin_fill()
    turtle.circle(50)
    turtle.end_fill()

5.3 交互式树木生长

让用户控制树木的生长过程:

def interactive_tree():
    depth = turtle.numinput("分形树", "请输入递归深度(3-7):", default=5, minval=3, maxval=7)
    angle = turtle.numinput("分形树", "请输入分支角度(15-45):", default=25, minval=15, maxval=45)
    
    turtle.speed(0)
    turtle.left(90)
    turtle.penup()
    turtle.backward(200)
    turtle.pendown()
    
    draw_tree(150, angle, 10)
    turtle.done()

在探索这些代码时,我常常被递归的魔力所震撼——简单的规则通过不断自我引用,竟能创造出如此复杂的自然形态。记得第一次成功运行分形树代码时,那种看着数字枝条在屏幕上“生长”的喜悦至今难忘。编程不仅是解决问题的工具,更可以成为表达创意的画布。

更多推荐