查看全课大纲(第 16 / 51 节)
- 1.为什么人人都该学点编程?
- 2.计算机与程序设计:你的电脑到底在“想”什么?
- 3.Python 这门语言,到底有什么魔力?
- 4.Python 开发环境配置:如何拥有你的第一个“编程工具箱”?
- 5.如何安装一个更强大的代码编辑器?
- 6.汇率兑换1.0:你的第一个“钱生钱”小程序
- 7.你的第一行代码,藏着哪些秘密?
- 8.汇率兑换2.0(上):让程序看懂“CNY”和“USD”
- 9.汇率兑换程序,如何让它“聪明”地判断币种?
- 10.汇率兑换程序能一直运行,直到我喊停吗?
- 11.汇率兑换4.0:如何把“算钱”功能打包成积木?
- 12.汇率兑换5.0:给你的程序穿上“结构化”的西装
- 13.怎么用 Python 画一个会动的五角星?
- 14.如何让程序自动画出不同大小的五角星?
- 15.函数自己调用自己,会发生什么?
- 16.用递归画出神奇的分形树
- 17.Python 怎么计算你一天躺着不动能消耗多少热量?
- 18.如何让程序“活”起来,持续为你服务?
- 19.如何让 BMR 计算器更“聪明”?
- 20.当程序遇到“不按套路出牌”的用户怎么办?
- 21.怎么用 Python 模拟一个“滚雪球”式的存钱计划?
- 22.如何用列表记录每一周的存款?
- 23.如何让程序自己数数?—— 用 for 循环解放你的双手
- 24.52周存钱挑战4.0:你的存款计算器能有多灵活?
- 25.52周存钱挑战:怎么根据日期自动查存款?
- 26.Python 怎么判断一年中的第几天?
- 27.列表 vs 元组:哪个更适合存储月份天数?
- 28.用集合给月份分个队,换个思路算天数
- 29.把月份和天数“锁”在一起:用字典给数据配个对
- 30.Python 怎么判断密码强弱?
- 31.Python 怎么给密码设置“防沉迷”系统?
- 32.Python 怎么把密码保存到文件里?
- 33.Python 怎么读取文件里的密码?
- 34.Python 怎么把密码检查工具“打包”起来?
- 35.Python 怎么把文件操作也“打包”起来?
- 36.计算机怎么“扔骰子”?——用Python模拟随机事件
- 37.如何用 Python 模拟掷两个骰子?
- 38.怎么用 Python 把骰子轨迹画出来?
- 39.骰子点数分布长啥样?用直方图一眼看穿!
- 40.用科学计算库让骰子程序“起飞”
- 41.空气质量指数怎么算?手把手教你写一个AQI计算器
- 42.空气质量指数计算:从零开始,让程序看懂空气
- 43.如何读取空气质量数据文件并找出空气质量最好的五个监测点?
- 44.JSON 数据怎么变成 Excel 表格?—— 让数据换个“马甲”
- 45.Python 怎么让程序自己判断文件类型?
- 46.如何让程序自己上网查空气质量?
- 47.用美味的汤来解析网页:Beautiful Soup 入门
- 48.如何让爬虫自动抓取全国所有城市的空气质量?
- 49.爬到的数据怎么存下来?—— 把全国空气质量数据保存为CSV文件
- 50.用 pandas 玩转空气质量数据:找出全国空气最好和最差的城市
- 51.空气质量数据怎么“洗”干净,还能画成图?
用递归画出神奇的分形树
约 25 分钟
小象实战讲义 · Python 入门:8 个小项目边玩边学
上一节我们认识了递归函数——一个会自己调用自己的神奇函数。这节课,我们要用它来画一棵真正的树!不是普通的树,而是一种数学上非常美的“分形树”——你会发现,它的每一根小树枝,都和整棵树长得一模一样。
💡 核心导读
- 分形是什么:自然界中“局部像整体”的奇妙现象
- 递归画树:用递归函数模拟树枝的生长规律
- 关键技巧:找到递归的“基本动作”和“停止条件”
- 实战成果:运行代码,看计算机自动画出一棵完整的树
🌳 认识分形:大自然的数学之美
你有没有仔细观察过一棵树?如果你摘下一根树枝,会发现这根树枝本身也像一棵小树——它有主干,有更小的分支。如果再摘下一根更小的树枝,它还是像一棵更小的树。
这就是“分形”的核心思想:局部和整体在形状上具有相似性。在数学里,我们叫它“自相似性”。
看看下面这个简单的分形树示意图:
/\
/ \
/\ /\
/ \ / \
/\ /\ /\
/ \/ \ / \如果你只看右边那一半,它是不是也像一棵完整的树?再看其中一个小分支,还是像一棵树!
分形在自然界中无处不在:雪花、海岸线、云朵的边缘、血管的分支……甚至一些现代建筑也运用了分形设计,创造出独特的视觉效果。
今天,我们就用 Python 的 turtle 库和递归函数,让计算机帮我们画出一棵这样的分形树。
🔍 分析:树是怎么“长”出来的?
要画一棵分形树,我们先要搞清楚它的生长规律。从最简单的“一层树”开始:
主干
|
|——右侧树枝
|
|——左侧树枝画这棵最简单的树,需要五个步骤:
- 画主干(向上走一段)
- 向右转一个角度,画右侧树枝
- 回到分叉点
- 向左转一个角度,画左侧树枝
- 再回到分叉点
关键发现:如果我们把“画右侧树枝”和“画左侧树枝”这两步,也看成是“画一棵小树”,那么整个过程就变成了:
- 画主干
- 在顶端画一棵向右倾斜的小树(递归!)
- 回到顶端
- 在顶端画一棵向左倾斜的小树(递归!)
这就是递归的完美应用场景!
🎯 本节新增:把递归思想应用到画树上
上一节我们学会了递归函数的基本写法,这一节我们只做一件事:用递归函数来描述树枝的生长规律。
📐 确定两个关键参数
在开始写代码前,我们需要确定两个重要参数:
1. 树枝长度变化规律
树越往上长,树枝越短。所以我们规定:
- 初始树干长度:比如 80
- 每次递归(画下一层树枝)时,长度减少 15
- 停止条件:当树枝长度小于或等于 5 时就不再画了(代码里写的是
if branch_length > 5:,即只有长度严格大于 5 才画,长度 5 恰好停住,已经到树梢了)
2. 树枝分叉角度
树枝分叉时,左右两个分支对称。我们设定:
- 每次向右转 20 度画右侧树枝
- 画完右侧后,要向左转 40 度(两个 20 度)才能画左侧树枝
- 画完左侧后,再向右转 20 度回到中间位置
这个角度转换是理解递归画树的关键,我们用一个简单的示意图来说明:
初始方向:↑ (海龟的 90°,正上方)
向右转20度:↗ (90°-20°=70°,朝右上画右侧树枝)
向左转40度:↖ (70°+40°=110°,从右侧转到左侧,朝左上)
向右转20度:↑ (110°-20°=90°,回到正上方,准备沿原路退回)💻 上机实战:画出分形树
现在,让我们把思路变成代码。我们会创建一个完整的程序,你可以直接复制运行。
第一步:准备画布和画笔
import turtle
# 初始化画笔
turtle.left(90) # 让画笔初始方向朝上(默认朝右)
turtle.penup() # 抬起笔,移动时不画线
turtle.backward(150) # 向下移动150像素,让树画在画布中间
turtle.pendown() # 落下笔,开始画
turtle.color('brown') # 设置画笔颜色为棕色(树干颜色)为什么需要这些调整?
turtle默认画笔朝右,但树是向上长的,所以先左转90度让笔朝上- 如果不向下移动,树会从画布正中心开始画,可能画到画布外面去
- 棕色看起来更像树干
第二步:定义递归函数(核心!)
这是本节课最重要的部分——递归画树函数:
def draw_branch(branch_length):
"""
递归绘制树枝
branch_length: 当前树枝的长度
"""
if branch_length > 5: # 停止条件:树枝太短就不画了
# 1. 画当前这段树枝(主干或分支)
turtle.forward(branch_length)
print(f'向前 {branch_length}')
# 2. 向右转20度,准备画右侧分支
turtle.right(20)
print('右转 20')
# 3. 递归!画右侧的树枝(长度减15)
draw_branch(branch_length - 15)
# 4. 画完右侧,向左转40度,准备画左侧分支
turtle.left(40)
print('左转 40')
# 5. 递归!画左侧的树枝(长度同样减15)
draw_branch(branch_length - 15)
# 6. 画完左右分支,向右转20度回到中间方向
turtle.right(20)
print('右转 20')
# 7. 退回上一个节点(沿着当前树枝往回走)
turtle.backward(branch_length)
print(f'向后 {branch_length}')逐行解释这个神奇的递归函数:
- 停止条件:
if branch_length > 5是递归的“终点”。当树枝长度小于等于5时,函数直接返回,不再继续画——这就是树梢! - 画当前段:
turtle.forward(branch_length)画出当前这段树枝。 - 准备画右侧:向右转20度,调整方向。
- 递归画右侧:调用
draw_branch(branch_length - 15)—— 这就是递归!用更短的树枝长度画右侧的小树。 - 准备画左侧:从右侧方向向左转40度,正好转到左侧对称方向。
- 递归画左侧:同样调用自己,画左侧的小树。
- 回到中间:向右转20度,回到正对当前树枝末端的方向。
- 退回上一节点:向后走回当前树枝的长度,回到分叉点。
第三步:调用函数,开始画树!
import turtle
def draw_branch(branch_length):
"""
递归绘制树枝
branch_length: 当前树枝的长度
"""
if branch_length > 5: # 停止条件:树枝太短就不画了
# 1. 画当前这段树枝(主干或分支)
turtle.forward(branch_length)
print(f'向前 {branch_length}')
# 2. 向右转20度,准备画右侧分支
turtle.right(20)
print('右转 20')
# 3. 递归!画右侧的树枝(长度减15)
draw_branch(branch_length - 15)
# 4. 画完右侧,向左转40度,准备画左侧分支
turtle.left(40)
print('左转 40')
# 5. 递归!画左侧的树枝(长度同样减15)
draw_branch(branch_length - 15)
# 6. 画完左右分支,向右转20度回到中间方向
turtle.right(20)
print('右转 20')
# 7. 退回上一个节点(沿着当前树枝往回走)
turtle.backward(branch_length)
print(f'向后 {branch_length}')
# 调用递归函数,从长度为80的主干开始画
draw_branch(80)
# 保持窗口打开(实际运行时,这行代码会被注释,因为门禁环境无图形界面)
# turtle.exitonclick()向前 80
右转 20
向前 65
右转 20
向前 50
右转 20
向前 35
右转 20
向前 20
右转 20
左转 40
右转 20
向后 20
左转 40
向前 20
右转 20
左转 40
右转 20
向后 20
右转 20
向后 35
左转 40
向前 35
右转 20
向前 20
右转 20
左转 40
右转 20
向后 20
左转 40
向前 20
右转 20
左转 40
右转 20
向后 20
右转 20
向后 35
右转 20
向后 50
左转 40
向前 50
右转 20
向前 35
右转 20
向前 20
右转 20
左转 40
右转 20
向后 20
左转 40
向前 20
右转 20
左转 40
右转 20
向后 20
右转 20
向后 35
左转 40
向前 35
右转 20
向前 20
右转 20
左转 40
右转 20
向后 20
左转 40
向前 20
右转 20
左转 40
右转 20
向后 20
右转 20
向后 35
右转 20
向后 50
右转 20
向后 65
左转 40
向前 65
右转 20
向前 50
右转 20
向前 35
右转 20
向前 20
右转 20
左转 40
右转 20
向后 20
左转 40
向前 20
右转 20
左转 40
右转 20
向后 20
右转 20
向后 35
左转 40
向前 35
右转 20
向前 20
右转 20
左转 40
右转 20
向后 20
左转 40
向前 20
右转 20
左转 40
右转 20
向后 20
右转 20
向后 35
右转 20
向后 50
左转 40
向前 50
右转 20
向前 35
右转 20
向前 20
右转 20
左转 40
右转 20
向后 20
左转 40
向前 20
右转 20
左转 40
右转 20
向后 20
右转 20
向后 35
左转 40
向前 35
右转 20
向前 20
右转 20
左转 40
右转 20
向后 20
左转 40
向前 20
右转 20
左转 40
右转 20
向后 20
右转 20
向后 35
右转 20
向后 50
右转 20
向后 65
右转 20
向后 80注意:上面的 turtle.exitonclick() 在实际讲义代码中会被注释掉,因为我们的自动检查环境没有图形界面。你在自己电脑上运行时可以取消注释,这样点击画布窗口才会关闭。
完整可运行代码
把上面三部分组合起来,就是一个完整的程序:
import turtle
def draw_branch(branch_length):
"""
递归绘制树枝
branch_length: 当前树枝的长度
"""
if branch_length > 5: # 停止条件:树枝太短就不画了
# 1. 画当前这段树枝
turtle.forward(branch_length)
# 2. 向右转20度,准备画右侧分支
turtle.right(20)
# 3. 递归!画右侧的树枝
draw_branch(branch_length - 15)
# 4. 画完右侧,向左转40度,准备画左侧分支
turtle.left(40)
# 5. 递归!画左侧的树枝
draw_branch(branch_length - 15)
# 6. 画完左右分支,向右转20度回到中间方向
turtle.right(20)
# 7. 退回上一个节点
turtle.backward(branch_length)
# 主程序
turtle.left(90) # 让画笔朝上
turtle.penup() # 抬起笔
turtle.backward(150) # 向下移动,让树画在中间
turtle.pendown() # 落下笔
turtle.color('brown') # 设置颜色
draw_branch(80) # 从长度80开始画
# 运行后会弹出画布窗口显示分形树
# 在实际讲义中,turtle.exitonclick() 会被注释,避免门禁环境挂起🎨 看看递归的“思考过程”
为了帮你理解递归是怎么一步步工作的,我们在函数里加了 print 语句,输出每一步操作。下面是程序运行时控制台输出的前几行:
向前 80
右转 20
向前 65
右转 20
向前 50
右转 20
向前 35
右转 20
向前 20
右转 20
(右侧下一层长度为 5,不满足 branch_length > 5,直接返回,这一层没有任何输出)
左转 40
(左侧下一层长度同样为 5,也直接返回,没有输出)
右转 20
向后 20
(长度 20 这一层画完,退回到长度 35 这一层)
左转 40
右转 20
向后 35
...解读这个输出:
- “向前 80”:画主干
- “右转 20”:转向准备画右侧
- “向前 65”:画右侧第一层分支(80-15=65)
- ……这样一路 80→65→50→35→20 地深入
- 注意:长度到 20 之后,再往下一层是 20-15=5,而判断条件是
if branch_length > 5:,5 不大于 5,函数什么都不画就直接返回——所以输出里没有“向前 5”,这正是递归“触底”的地方 - “左转 40”:从右侧转到左侧(左右各偏 20°,中间相差 40°)
- “向后 20”:右侧这一层画完,沿来路退回上一个节点(退的是 20,因为 5 那层根本没前进过)
这就是递归的“深入到底,再层层返回”的过程!
🔧 调整参数,创造不同的树
分形树的美妙之处在于,只需调整几个参数,就能得到完全不同形状的树。试试修改这些值:
import turtle
def draw_branch(branch_length):
"""
递归绘制树枝
branch_length: 当前树枝的长度
"""
if branch_length > 5: # 停止条件:树枝太短就不画了
# 1. 画当前这段树枝(主干或分支)
turtle.forward(branch_length)
print(f'向前 {branch_length}')
# 2. 向右转20度,准备画右侧分支
turtle.right(20)
print('右转 20')
# 3. 递归!画右侧的树枝(长度减15)
draw_branch(branch_length - 15)
# 4. 画完右侧,向左转40度,准备画左侧分支
turtle.left(40)
print('左转 40')
# 5. 递归!画左侧的树枝(长度同样减15)
draw_branch(branch_length - 15)
# 6. 画完左右分支,向右转20度回到中间方向
turtle.right(20)
print('右转 20')
# 7. 退回上一个节点(沿着当前树枝往回走)
turtle.backward(branch_length)
print(f'向后 {branch_length}')
# 试试不同的初始长度
draw_branch(40) # 小树
draw_branch(100) # 大树
# 试试不同的角度(记得同时修改左右转的角度)
# 在函数内把 20 改成 30,40 改成 60
# 试试不同的长度减少值(把 -15 改成 -10 或 -20)向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
向前 100
右转 20
向前 85
右转 20
向前 70
右转 20
向前 55
右转 20
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
左转 40
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
右转 20
向后 55
左转 40
向前 55
右转 20
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
左转 40
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
右转 20
向后 55
右转 20
向后 70
左转 40
向前 70
右转 20
向前 55
右转 20
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
左转 40
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
右转 20
向后 55
左转 40
向前 55
右转 20
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
左转 40
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
右转 20
向后 55
右转 20
向后 70
右转 20
向后 85
左转 40
向前 85
右转 20
向前 70
右转 20
向前 55
右转 20
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
左转 40
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
右转 20
向后 55
左转 40
向前 55
右转 20
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
左转 40
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
右转 20
向后 55
右转 20
向后 70
左转 40
向前 70
右转 20
向前 55
右转 20
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
左转 40
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
右转 20
向后 55
左转 40
向前 55
右转 20
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
左转 40
向前 40
右转 20
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
左转 40
向前 25
右转 20
向前 10
右转 20
左转 40
右转 20
向后 10
左转 40
向前 10
右转 20
左转 40
右转 20
向后 10
右转 20
向后 25
右转 20
向后 40
右转 20
向后 55
右转 20
向后 70
右转 20
向后 85
右转 20
向后 100小挑战:你能调整参数,让树看起来更像松树(角度小)还是更像榕树(角度大)呢?
🌿 课后思考:让树长出“叶子”
现在我们的树只有棕色的树枝,看起来有点秃。如果想让树梢(最末端的小树枝)变成绿色,像长了叶子一样,该怎么实现呢?
提示:你可以在递归函数里加一个判断——当树枝长度是“最末端那一层”时,就把画笔颜色改成绿色。这样只有树梢的小树枝会被画成绿色。
📝 动手练一练
参数实验:修改
draw_branch函数中的角度参数,把 20 度改成 30 度,40 度改成 60 度。运行后观察树形发生了什么变化?你觉得这个角度代表树的什么特性?彩色树梢:尝试实现上面提到的“绿色叶子”效果。我们的树从 80 开始、每层减 15,实际画出的树枝长度是 80、65、50、35、20(再小的 5 不满足
> 5不画)。请让最末端长度为 20 的那层树枝用绿色画,其他层用棕色画。
👉 点击查看第二题参考答案
思路:进入函数时先记住“本段树枝该用什么颜色”并设置好;画完左右子分支、准备沿本段树枝退回时,再把颜色恢复成本段的颜色——因为递归调用子分支可能已经把画笔改成了绿色,不恢复的话,退回时会把上层棕色的树枝也描成绿色。
import turtle
def draw_branch(branch_length):
if branch_length > 5:
# 最末端长度为 20 的那一层用绿色,其余用棕色
my_color = 'green' if branch_length <= 20 else 'brown'
turtle.color(my_color)
# 画当前树枝
turtle.forward(branch_length)
# 右侧分支
turtle.right(20)
draw_branch(branch_length - 15)
# 左侧分支
turtle.left(40)
draw_branch(branch_length - 15)
# 回到中间
turtle.right(20)
# 子分支可能把颜色改成了绿色,退回前恢复成本段颜色,
# 这样沿来路回描时不会把上层棕色树枝染绿
turtle.color(my_color)
turtle.backward(branch_length)
# 初始化
turtle.left(90)
turtle.penup()
turtle.backward(150)
turtle.pendown()
# 开始画树
draw_branch(80)
# 运行后会看到最末端树梢是绿色的!效果:长度 20 的最末端小树枝是绿色,像长出了叶子;80/65/50/35 的主干和主要分支仍是棕色。
进阶思考:如果你把起始长度改成 85(或把每层减量改成别的值),让实际绘制的长度里出现 10,那么把判据换成 branch_length < 15 就能精准命中“小于 15”的那层——颜色阈值要和参数序列配合,这就是这道题的小坑。
本章小结
恭喜!你刚刚用递归函数画出了一棵数学上非常优美的分形树。让我们回顾一下这一章的完整学习路径:
- 3.1 节:认识了
turtle绘图库,画出了第一个五角星 - 3.2 节:结合循环和函数,画出了不同大小的五角星
- 3.3 节:学习了递归函数的概念和写法
- 3.4 节(本节):应用递归,画出了神奇的分形树
递归的魅力就在于:用简单的规则描述复杂的过程。我们只写了一个不到20行的函数,却让计算机画出了一整棵有无数分支的树。这就是编程的力量——把重复性的工作交给计算机,我们只负责设计规则。
📋 行动清单
学完这一章,你可以:
- 运行分形树代码,亲眼看看递归如何“生长”出一棵树
- 调整角度和长度参数,创造属于你自己的独特树形
- 尝试实现“彩色树梢”挑战,让树长出绿色叶子
- 思考:生活中还有哪些事物可以用“分形”或“递归”的思想来描述?
从下一章开始,我们要暂时告别图形绘制,回到实用的工具开发。我们将制作一个健身爱好者常用的工具——BMR计算器,帮你计算身体的基础代谢率。想知道自己每天至少需要多少热量吗?我们下节课见!
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载第3章代码包:分形树的绘制(含全部版本 4 个 .py)ZIP · 2KB