教程文档示例工程【免费下载链接】website-archiveArchive of the Coding Train website (first version)项目地址https://gitcode.com/gh_mirrors/we/website-archive点击查看免费下载本文基于仓库中 Coding Challenge #77 Recursion 的挑战文档 及其配套源码展开它以 p5.js 在 HTML5 Canvas 上利用递归Recursion绘制分形图案是 Coding Train 系列中讲解「函数调用自身 基线条件」这一核心思想的经典一集。读完本文你将理解递归分形的两个基本要素能够逐行剖析并复现drawCircle递归圆环程序掌握递归深度与调用树的分析方法并顺藤摸瓜看到同一仓库中分形树等更多递归实战案例。挑战档案文档与配套资源该挑战文档的元信息front matter记录了以下关键内容字段值说明video_number77Coding Train 系列第 77 期挑战date2017-10-05发布与存档日期repositoryCC_077_Recursion对应源码目录名web_editorpseBrWaMm在线 p5.js 编辑器版本编号video_idjPsZwrV9ld0配套视频 ID仓库内仅作记录文档正文只有一句核心陈述In this coding challenge, I explore the concept of recursion to create fractal patterns in JavaScript HTML5 canvas with the p5.js library.—— 即用递归在 HTML5 Canvas 上创建分形图案。与之配套的源码就存放在 CodingChallenges/CC_077_Recursion 目录下包含p5.js 版本P5/sketch.js 与 P5/index.htmlProcessing 移植版本Processing/CC_077_Recursion/CC_077_Recursion.pde。文档还关联了「分形树递归」一期的视频内部链接指向 014-fractaltree以及 Benoit Mandelbrot 的著作The Fractal Geometry of Nature作为延伸阅读这提示了本挑战的理论背景递归是生成自相似self-similar分形结构最自然的编程手段。递归的本质函数调用自己在深入代码之前先厘清递归的两个核心要素基线条件base case决定递归何时停止的条件。缺少它函数会无限调用自身最终导致调用栈溢出stack overflow。递归调用recursive call函数在自身内部调用自己通常传入一个「规模更小」的参数使问题逐步逼近基线条件。本挑战的drawCircle正是这两要素的教科书级示范每层递归把圆的直径缩小为上一层的 1/4直到直径不超过 2 像素时停止。自相似的分形结构由此诞生——任意一层圆圈的排列方式都与整体一致。核心实现递归圆环 drawCircle 逐行解析完整的 p5.js 源码位于 P5/sketch.js// Daniel Shiffman // http://codingtra.in // http://patreon.com/codingtrain // Recursion // Edited Video: https://www.youtube.com/watch?vjPsZwrV9ld0 function setup() { createCanvas(600, 600); } function draw() { background(0); stroke(255); noFill(); drawCircle(300, 200, 600); noLoop(); } function drawCircle(x, y, d) { ellipse(x, y, d); if (d 2) { let newD d * 0.25; drawCircle(x newD, y, newD); drawCircle(x - newD, y, newD); //drawCircle(x, y d * 0.5, d * 0.5); } }主流程setup 与 drawcreateCanvas(600, 600)创建 600 × 600 的画布draw()中先background(0)将背景刷成黑色再设置stroke(255)白色描边与noFill()空心圆随后以drawCircle(300, 200, 600)启动递归——圆心在 (300, 200)直径 600关键一行noLoop()递归在draw内一次性完成绘制画面是静态的因此调用noLoop()让draw只执行一次避免每帧重复绘制。这是这类「一帧出图」程序的常用手法与后续章节中交互式分形树每帧重绘形成鲜明对比。递归函数drawCircle(x, y, d)函数签名drawCircle(x, y, d)的三个参数含义如下参数含义本次调用示例x圆心横坐标300y圆心纵坐标200d圆的直径600函数体分三步先绘制当前圆ellipse(x, y, d)画出一个空心圆判断基线条件if (d 2)—— 只有当直径大于 2 像素时才继续递归否则该分支是树叶节点递归调用计算newD d * 0.25然后向左右两侧各派生一个直径为newD的子圆圆心分别偏移到x newD与x - newD。第 3 步的几何意义值得展开子圆直径是父圆的 1/4圆心相对父圆心水平偏移一个子圆直径的长度。因为偏移量newD小于父圆半径d / 2所以所有后代圆都嵌套在父圆内部层层收缩形成「圆内有圆」的递归分形。源码中最后一行//drawCircle(x, y d * 0.5, d * 0.5);是被注释掉的第三个分支——若取消注释会在正下方再派生一个直径为 1/2 的子圆图案会变得更加稠密详见下文「变体实验」。运行与观察配套的 P5/index.html 通过 CDN 引入 p5.js 运行时仓库记录的是 1.4.1 版本再加载sketch.js!DOCTYPE html html head meta charsetUTF-8 meta http-equivX-UA-Compatible contentIEedge meta nameviewport contentwidthdevice-width, initial-scale1 titlerecursion/title script typetext/javascript srchttps://cdn.jsdelivr.net/npm/p51.4.1/lib/p5.min.js/script script typetext/javascript srchttps://cdn.jsdelivr.net/npm/p51.4.1/lib/addons/p5.sound.min.js/script script typetext/javascript srcsketch.js/script /head body /body /html运行方式很简单直接在浏览器中打开该index.html或在本地起一个静态文件服务器例如在P5/目录下执行python3 -m http.server后访问对应端口。页面加载后即一次性渲染出白色空心圆构成的递归分形。注意本挑战没有使用 p5.sound 相关功能HTML 中加载 sound 附加库属于模板遗留可以按需移除。递归深度与调用树分析从源码可以直接推导出递归的规模和形态。设初始直径d 600每层乘系数0.25当直径 ≤ 2 时停止层级 k直径 d 600 × 0.25ᵏ该层圆数量是否继续递归06001是600 211502是237.54是39.3758是42.3437516是2.34375 250.585937532否≤ 2仅绘制不派生因此画面上共绘制1 2 4 8 16 32 63 个圆递归深度约为 6 层。这个数字完全由源码中的参数初始直径 600、缩放系数 0.25、基线阈值 2决定修改任意一个都会改变分形的层数与稠密度。从计算复杂度看这是一棵满二叉树状的递归调用树每层调用数翻倍调用总数与深度呈2^深度的指数关系。这也解释了为什么递归分形必须设置基线条件——没有d 2的约束调用数将无限膨胀直至栈溢出。由于绘制按「先父后子、先左后右」的深度优先顺序执行所以渲染结果中内层的圆总是覆盖在外层圆之上描边互相叠加这也是分形视觉层次感的来源之一。Processing 移植版本对照同一挑战在仓库中还提供了 Processing 版本 Processing/CC_077_Recursion/CC_077_Recursion.pde逻辑与 p5.js 版一一对应// Daniel Shiffman // http://codingtra.in // http://patreon.com/codingtrain // Recursion // Edited Video: https://www.youtube.com/watch?vjPsZwrV9ld0 // Originally written using p5.js void setup() { size(600, 600); } void draw() { background(0); stroke(255); noFill(); drawCircle(300, 200, 600); noLoop(); } void drawCircle(float x, float y, float d) { ellipse(x, y, d, d); if (d 2) { float newD d * 0.25; drawCircle(x newD, y, newD); drawCircle(x - newD, y, newD); //drawCircle(x, y d * 0.5, d * 0.5); } }两个版本只有三处语法层面的差异算法结构完全一致p5.jsProcessing说明createCanvas(600, 600)size(600, 600)画布创建 API 不同ellipse(x, y, d)ellipse(x, y, d, d)p5.js 中直径 d 自动同时作用于宽高Processing 需显式传入两个直径动态类型let静态类型float语言类型系统差异这份「同构双版本」恰好说明递归思想与具体语言无关只要把「绘制当前圆 按条件派生子圆」的规则表达出来无论是 JavaScript 还是 Java 系语言都能得到同样的分形结果。变体实验改造递归参数源码的结构让变体实验变得极其简单以下是几个可立即验证的方向恢复第三个分支取消注释//drawCircle(x, y d * 0.5, d * 0.5);图案会在每个圆的垂直下方增加一支直径为 1/2 的子圆链整体更接近「雪花」式的稠密分形。改变缩放系数把d * 0.25换成d * 0.33或d * 0.5子圆更大、层数更少直径衰减慢更快达到或超过基线阈值图案更疏朗换成更小的系数如d * 0.15则层数更多、图案更细密。调整基线阈值把d 2改为d 50递归提前终止只保留少数几层改为d 1则会增加一层圆数量从 63 增至 127。加入第三个偏移方向将两个子调用扩展为三个例如分别偏移-newD、0、newD形成三叉分形。这些改动都不超过几行却能直观感受到「基线条件与缩放系数共同决定分形复杂度」这一核心规律是理解递归成本与视觉密度的最佳实验台。仓库中的递归应用从圆环到分形树本挑战文档在videos字段中明确关联了「Fractal Trees - Recursive」一期014-fractaltree.md其源码 CC_014_FractalTree/P5/sketch.js 是理解递归分形的第二个经典范例var angle 0; var slider; function setup() { createCanvas(400, 400); slider createSlider(0, TWO_PI, PI / 4, 0.01); } function draw() { background(51); angle slider.value(); stroke(255); translate(200, height); branch(100); } function branch(len) { line(0, 0, 0, -len); translate(0, -len); if (len 4) { push(); rotate(angle); branch(len * 0.67); pop(); push(); rotate(-angle); branch(len * 0.67); pop(); } }对比drawCircle与branch可以提炼出递归分形的通用模板每次调用先绘制一个基本单元圆环画ellipse树画line基线条件圆环是d 2树是len 4枝长小于等于 4 像素时停止递归缩放圆环乘 0.25树枝乘 0.67递归分支数圆环 2 个水平子圆树通过push()/pop()保护坐标系后左右各旋转angle派生出 2 条子枝。分形树版本还展示了递归与状态保存的配合push()记住当前坐标系rotate(angle)旋转后递归pop()恢复坐标系从而让左右两枝从同一个分叉点长出。交互上它通过滑杆实时调节角度配合draw()的每帧重绘让读者直接看到分支角度如何改变树的形态。仓库中更进阶的变体还包括 CC_015_FractalTreeArray/P5/sketch.js用Branch对象数组 p5.Vector计算分支端点鼠标点击逐步长出树枝是「递归生成 数组存储」的替代思路其分支几何在 branch.js 中实现以及 CC_129_Koch_Snowflake/P5/sketch.js科赫雪花采用「每一代由上一代所有线段派生、存入下一代数组」的迭代式生成与直接递归形成对照。这些案例共同说明递归/迭代只是手段分形的本质是用相同的规则反复作用于更小的尺度。社区贡献与延伸阅读挑战文档的contributions字段记录了社区围绕「递归分形」创作的一批作品例如Fractaloid - Interactive Fractal BranchesDiego de la Fuente Curaqueo可交互的分形树枝Sierpinski Cometfanbyprinciple谢尔宾斯基彗星#RecursiveTrainAlca、Recursive RemixJustin Riley对递归主题的再混排Recursive Squares与Recursive Koch CurveDonKarlssonSan递归正方形与科赫曲线Recursion PyramidTrevor Clarke递归金字塔Sierpinski TriangleChris Oliver、Recursion Geometric ShapeHussain Salih Mahdi谢尔宾斯基三角形与递归几何形状。这些作品与源码细节均记录在 077-recursion.md 的 front matter 中可作为继续探索递归分形表现力的灵感来源。理论背景方面文档推荐的 Benoit Mandelbrot《The Fractal Geometry of Nature》是理解分形数学的经典读物仓库内 Coding Challenges 索引 汇总了包括分形树014、科赫雪花129在内的全部挑战条目适合按主题串读。结语从 63 个空心圆开始本挑战用最短的代码讲透了递归分形的全部要点函数调用自身、基线条件防止无限递归、参数逐层收缩产生自相似结构。掌握drawCircle之后无论是转向分形树的分支递归还是把同样的思想用于四叉树分区如 CC_098.1_QuadTree 这类数据结构你都能快速识别出「递归 基线条件」的骨架。递归不是魔法而是一种「把问题缩小一点再交给同一个自己」的工程思维。赞分享教程文档示例工程【免费下载链接】website-archiveArchive of the Coding Train website (first version)项目地址https://gitcode.com/gh_mirrors/we/website-archive点击查看免费下载相关推荐用 p5.js 递归算法绘制分形树Coding Train 第 14 号挑战全解析用 p5.js 递归算法绘制分形树Coding Train 第 14 号挑战全解析 本篇技术指南围绕 Coding TrainCoding Rainbow教程文档示例工程2D 太阳系生成器递归行星轨道系统的 Processing 与 p5.js 实现Coding Challenge 72D 太阳系生成器递归行星轨道系统的 Processing 与 p5.js 实现Coding Challenge 7 本篇文章以 Coding Train教程文档示例工程p5.js 迷宫生成器第 3 部分递归回溯算法的拆墙与访问标记实战解析Coding Challenge 10.3p5.js 迷宫生成器第 3 部分递归回溯算法的拆墙与访问标记实战解析Coding Challenge 10.3 本文聚焦 Coding Train 的经教程文档示例工程上一篇MoviePilot媒体文件同步删除插件优化解析下一篇deepagents 上下文检索评估任务 cb-cloud-70 深度解析多实体比较题的构造、运行与 LLM 判题机制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考