Python--科赫雪花--数学原理--turtle绘图

Python--科赫雪花--数学原理--turtle绘图

科赫雪花

科赫雪花于1904年由瑞士数学家黑格尔-冯-科赫提出,是一种分形,即一种不断放大时会重复自己的图形。分形源自递归,递归操作本身就是在重复使用自己定义自己。其图片如下:

递归的基础知识

递归操作的基本步骤

step1:定义一个基线条件,达到条件时,自动结束递归

step2:写一个算法可能用到多次递归

阶层的表达式就是一个经典的递归,

       

f(N)=f(N-1)*N
def factorial(N): if N == 1 return 1 else: return N*factorial(N-1)

循环与递归的选择方面:各有千秋,递归优雅紧凑,循环高效快捷,本节我们将用递归绘制分形。

数学内容补充

垂直向量计算技巧:已知点A(a,b),当点 B为B (-b,a) 时,

A * B = 0

从空间中的一点A沿着向量n出发,到达点B的坐标运算公式:

                                                   

B = A + d \cdot \underset{n}{\rightarrow}

已知线段的两个端点A和B,求其上面任意一点C的坐标公式, 其中a为点A到点C的距离,b为点B到点C的距离

                                                

a*B+b*A/a+b

科赫雪花的相关参数计算

这是科赫雪花的第一个片段,我们一共需要3个相连接的片段。

海龟turtle绘图

绘制三角形--热身
  • t.up() / t.down():海龟绘图的核心操作,抬笔移动不画线,落笔移动才画线,这里用来 “跳” 到指定坐标,避免画多余的线;
  • setpos(x,y):等价于 goto(x,y),让画笔移动到指定坐标,落笔时会画直线;
triangle.py
import turtle def draw_triangle(x1, y1, x2, y2, x3, y3,t): t.up() t.setpos(x1, y1) t.down() t.setpos(x2, y2) t.setpos(x3, y3) t.setpos(x1, y1) t.up() def main(): print('testing turtle graphics...') # 创建海龟对象 t = turtle.Turtle() # 隐藏海龟图标 t.hideturtle() # 调用函数,传入海龟对象和坐标,确保绘制完成后不会关闭tkinter窗口 draw_triangle(t, -100, 0, 0, 173.2, 100, 0) turtle.mainloop() if __name__ == "__main__": main()
绘制科赫雪花--实践

第一步:把原始大线段 (x1,y1)→(x2,y2) 拆成 4 段更小的线段(科赫雪花的规则):

  • 第 1 段:(x1,y1) → p1(三等分第一个点)
  • 第 2 段:p1 → p2(等边三角形的第一条边)
  • 第 3 段:p2 → p3(等边三角形的第二条边)
  • 第 4 段:p3 → (x2,y2)(三等分第二个点)

第二步:对这 4 条小线段重复调用同一个函数(递归),让每条小线段也执行 “拆分成 4 段 / 直接画” 的逻辑 —— 所以形参变成了每条小线段的 “起点 + 终点”。

koch.py
import turtle import math # drawKochSF()计算点的坐标 def drawKochSF(x1, y1, x2, y2, t): d = math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) r = d/3.0 h = r*math.sqrt(3)/2.0 p3 = ((x1 + 2*x2)/3.0, (y1 + 2*y2)/3.0) p1 = ((2*x1 + x2)/3.0, (2*y1 + y2)/3.0) c = (0.5*(x1+x2), 0.5*(y1+y2)) n = ((y1-y2)/d, (x2-x1)/d) p2 = (c[0]+h*n[0], c[1]+h*n[1]) # 递归 if d > 10: # flake #1/片段 drawKochSF(x1, y1, p1[0], p1[1], t) # flake #2/片段 drawKochSF(p1[0], p1[1], p2[0], p2[1], t) # flake #3/片段 drawKochSF(p2[0], p2[1], p3[0], p3[1], t) # flake #4/片段 drawKochSF(p3[0], p3[1], x2, y2, t) else: # draw cone t.up() t.setpos(p1[0], p1[1]) t.down() t.setpos(p2[0], p2[1]) t.setpos(p3[0], p3[1]) # draw sides t.up() t.setpos(x1, y1) t.down() t.setpos(p1[0], p1[1]) t.up() t.setpos(p3[0], p3[1]) t.down() t.setpos(x2, y2) # main() function def main(): print('Drawing the Koch Snowflake...') t = turtle.Turtle() t.hideturtle() # draw try: drawKochSF(-100, 0, 100, 0, t) drawKochSF(0, -173.2, -100, 0, t) drawKochSF(100, 0, 0, -173.2, t) except: print("Exception, exiting.") exit(0) # wait for user to click on screen to exit turtle.Screen().exitonclick() # call main if __name__ == '__main__': main() 

try-except 代码块,异常捕获机制,专门用来处理绘制过程中可能出现的错误,避免程序直接崩溃。

  • 递归阈值:线段长度 d > 10 时递归拆分,d ≤ 10 时执行 else 里的绘制逻辑;
  • 雪花由 3 条线段组成:(-100,0)→(100,0)(0,-173.2)→(-100,0)(100,0)→(0,-173.2)
阶段 1:程序启动与初始化
if __name__ == '__main__': main() # 调用主函数
  1. 执行 main() 函数,首先打印提示:Drawing the Koch Snowflake...
  2. 创建海龟画笔对象:t = turtle.Turtle(),隐藏画笔箭头:t.hideturtle()(画笔默认速度,无加速);
  3. 进入 try 代码块,准备依次绘制 3 条科赫边。
阶段 2:绘制第一条边 drawKochSF(-100, 0, 100, 0, t)
步骤 1:计算几何参数(第一次调用)
  • 线段长度 d = √[(-100-100)² + (0-0)²] = 200(>10,触发递归);
  • 三等分长度 r = 200/3 ≈ 66.67,等边三角形高 h = 66.67×√3/2 ≈ 57.74
  • 三等分点:p1 = (2×(-100)+100)/3, (2×0+0)/3 = (-33.33, 0)p3 = (-100+2×100)/3, (0+2×0)/3 = (33.33, 0)
  • 线段中点 c = (0, 0)
  • 法向量 n = (0-0)/200, (100-(-100))/200 = (0, 1)
  • 凸起顶点 p2 = (0 + 57.74×0, 0 + 57.74×1) = (0, 57.74)
步骤 2:递归拆分(d=200>10)

函数不绘制,而是拆成 4 段,依次递归调用自身

  • 调用 1:drawKochSF(-100, 0, -33.33, 0, t) → 处理第一段(起点→p1);
  • 调用 2:drawKochSF(-33.33, 0, 0, 57.74, t) → 处理第二段(p1→p2);
  • 调用 3:drawKochSF(0, 57.74, 33.33, 0, t) → 处理第三段(p2→p3);
  • 调用 4:drawKochSF(33.33, 0, 100, 0, t) → 处理第四段(p3→终点)。
步骤 3:递归深入(直到 d ≤ 10)

以「调用 1:drawKochSF(-100, 0, -33.33, 0, t)」为例,继续拆解:

  • 计算该线段长度 d = √[(-100+33.33)² + 0] ≈ 66.67(>10,继续拆);
  • 计算该段的 p1/p2/p3(比如新 p1≈-77.78,新 p3≈-55.56,新 p2≈-66.67, 19.25);
  • 再次拆成 4 段,递归调用……
  • 这个过程会持续,直到某次计算出的 d ≤ 10(比如拆到第 3 层,d≈7.41),触发 else 分支。
else: # 1. 绘制“凸起”:p1→p2→p3 t.up() t.setpos(p1[0], p1[1]) # 移动到当前短线段的p1 t.down() t.setpos(p2[0], p2[1]) # 画p1→p2 t.setpos(p3[0], p3[1]) # 画p2→p3 # 2. 绘制两端:起点→p1,p3→终点 t.up() t.setpos(x1, y1) # 移动到当前短线段的起点 t.down() t.setpos(p1[0], p1[1]) # 画起点→p1 t.up() t.setpos(p3[0], p3[1]) # 移动到当前短线段的p3 t.down() t.setpos(x2, y2) # 画p3→终点
步骤 5:递归回溯

完成当前短线段的绘制后,函数执行完毕,回到上一层递归调用,继续处理下一段,直到第一层递归的 4 段都处理完,第一条边绘制结束。

阶段 3:绘制第二、第三条边(重复阶段 2)
  1. 调用 drawKochSF(0, -173.2, -100, 0, t)
    • 计算该线段长度≈200(>10),递归拆分→直到 d≤10,执行你的 else 绘制逻辑;
    • 这条边是「(0,-173.2)→(-100,0)」,是倒置等边三角形的左斜边;
  2. 调用 drawKochSF(100, 0, 0, -173.2, t)
    • 同理,递归拆分→绘制,是倒置等边三角形的右斜边。
阶段 4:程序收尾
  1. 3 条边的递归绘制全部完成(无论图形是否正常);
  2. 执行 turtle.Screen().exitonclick():等待你点击画布,程序退出;

若绘制中出现任何错误(比如递归深度超限、坐标计算异常),触发 except 块:打印 Exception, exiting. 并退出程序。

Read more

刷题笔记:力扣第1题-两数之和

刷题笔记:力扣第1题-两数之和

2026.3.5(双层循环、排序+双指针) 1.Note: The returned array must be malloced, assume caller calls free().这句话的意思是“注意:返回的数组必须使用 malloc 分配,假设调用者会调用 free()。”也就是说提交的代码里面只需要分配内存,不需要释放内存。 2.原题目给的函数里面的参数,* nums是数组首地址,numsSize是数组长度,target是所要寻找的加和目标值,* returnSize是返回数组的长度(在本题中固定为2)。 3.由于题目说使用的数组需要自行malloc获取内存,所以必不可少的一句代码便是: int *list = (int *)malloc(2 * sizeof(int)); 当获取完内存之后,list就可以看作是一段数组的首地址,数组长度为2。 4.拿到题目,第一个想到的方法便是枚举法,

By Ne0inhk
《算法题讲解指南:优选算法-双指针》--01移动零,02复写零

《算法题讲解指南:优选算法-双指针》--01移动零,02复写零

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 一、双指针算法介绍   1、对撞指针   2、快慢指针 01、移动零 题目链接: 题目描述: 题目示例: 算法思路: 算法流程: C++ 代码演示: 算法总结: 02、复写零 题目链接: 题目描述: 题目示例: 算法思路: 算法流程: C++代码演示: 算法总结及流程解析: 结束语 一、双指针算法介绍       在正式讲解本次的算法题之前我们先来看看算法中一个非常常用的方法——双指针。双指针有两种形式,一种对撞指针,一种是左右指针。   1、对撞指针

By Ne0inhk

傅里叶变换 | FFT 与 DFT 原理及算法

注:本文为 “傅里叶变换 | FFT 与 DFT” 相关合辑。 英文引文,机翻未校。 中文引文,略作重排。 图片清晰度受引文原图所限。 如有内容异常,请看原文。 Fast Fourier Transform (FFT) 快速傅里叶变换(FFT) In this section we present several methods for computing the DFT efficiently. In view of the importance of the DFT in various digital signal processing applications, such as linear filtering,

By Ne0inhk
单双序列问题——动态规划

单双序列问题——动态规划

文章目录 * 一、最长递增子序列 * 二、等差数列划分II-子序列 * 三、最长公共子序列 * 四、正则表达式匹配 动态规划是解决复杂算法问题的利器,本文将聚焦于单序列与双序列两类经典问题,通过分析最长递增子序列、正则表达式匹配等典型案例,深入剖析动态规划的状态定义与转移方程构建思路。 在阅读该文章时最好对基础的动态规划有所了解,因为在此不会讲解动态规划基础的细节,大家可以通过阅读下文进行学习:基础dp——动态规划多状态dp——动态规划子数组问题——动态规划 单序列问题往往具备两个关键特征,使其特别适合用动态规划求解。 * 问题解决路径需拆解为多个步骤,每个步骤都存在多种选择,最终目标是计算可行解的总数,或是找到满足条件的最优解。 * 问题的输入数据通常呈现为序列形态,比如一维数组、字符串等典型的线性数据结构。 根据题目的特点找出该元素对应的最优解(或解的数目)和前面若干元素(通常是一个或两个)的最优解(或解的数目)的关系,并以此找出相应的状态转移方程。一旦找出了状态转移方程,只要注意避免不必要的重复计算,问题就能迎刃而解。 下面讲解两个适合运用动态规划的单序

By Ne0inhk