1. 从“鸽巢”到“抽屉”一个数学原理的趣味化呈现如果你是一位数学老师或者是一位想给孩子做数学启蒙的家长你肯定遇到过这样的难题如何把一个听起来有点抽象、甚至有点枯燥的数学原理讲得让孩子眼睛发亮并且能真正理解、记住、甚至会用鸽巢原理或者说抽屉原理就是这样一个典型的“拦路虎”。它的表述很简单“把多于n个的物体放到n个抽屉里则至少有一个抽屉里的东西不少于2个。” 听起来像一句废话对吧但就是这个简单的原理能解决从日常生活中的“生日悖论”到计算机科学中的哈希碰撞等一系列有趣而深刻的问题。传统的数学课堂里我们可能用画图、举例比如“13个人中至少有2个人同一个月生日”来讲解。但今天我想分享一个更酷、更直观、更能让孩子动手参与的方法用Scratch编程来“看见”并“验证”鸽巢原理。这不是一个简单的动画演示而是一个完整的、可交互的探究项目。通过这个项目孩子不仅能理解原理本身还能亲身体验“数学家”是如何通过实验、观察、归纳来发现和验证规律的。这比单纯记住一个结论要有价值得多。这个项目适合所有对数学和编程感兴趣的学习者无论你是老师想丰富课堂家长想亲子共学还是学生想挑战自己。我们将从最基础的“放鸽子”模拟开始一步步深入到概率统计的层面最终你会得到一个可以自己调整参数、反复实验的“鸽巢原理实验室”。下面就让我们打开Scratch开始这次奇妙的数学与编程之旅。2. 项目核心设计如何用积木块“搭建”一个数学原理用Scratch实现鸽巢原理核心目标不是“证明”它——因为它本身是一个不证自明的公理——而是“可视化”它的必然性并探究在随机实验中的统计表现。因此我们的项目设计需要分为几个层次首先是基础的确定性模拟然后是随机实验模拟最后是数据收集与分析。2.1 场景与角色规划为了让项目生动我们需要设计好舞台和演员。舞台背景我选择了一个简洁的、网格化的背景或者自己画一个。上面清晰地划分出几个区域代表“鸽巢”或“抽屉”。例如我们可以画5个并排的矩形框代表5个巢穴。角色设计鸽子角色这是我们的“物体”。我们可以用一个简单的圆形精灵来代表或者用一个更有趣的造型比如一个小鸟图案。关键点是我们需要创建多个鸽子角色的克隆体。例如我们要验证“6只鸽子飞进5个巢至少有一个巢有2只鸽子”那么我们就需要6个鸽子克隆体。巢穴角色通常不需要单独的角色用背景上的图形表示即可。但为了交互和数据显示我们可以为每个巢穴创建一个透明的“感应区域”角色一个矩形用它来检测内部鸽子的数量并显示。控制角色一个按钮或者标志用于开始实验、重置等。我们可以用Scratch自带的按钮造型或者自己画一个。变量设计这是项目的“大脑”。巢穴数量一个变量比如叫巢数。我们可以让它可调节来验证不同条件下的原理。鸽子数量另一个变量比如叫鸽数。它必须大于巢数。巢穴鸽子计数列表这是一个列表非常重要。列表的长度等于巢数列表中的每一项对应一个巢穴里当前的鸽子数量。例如巢穴计数[1]就代表1号巢穴里的鸽子数。实验次数与至少一双次数用于做随机统计实验。实验次数记录我们总共随机分配了多少回至少一双次数记录其中有多少次出现了“至少一个巢有2只以上鸽子”的情况。最后可以用至少一双次数/实验次数来计算频率它会无限接近概率1100%。2.2 核心算法逻辑拆解整个项目的运行逻辑可以用下面这个流程图来理解我们用文字描述初始化设置巢数5鸽数6。清空巢穴计数列表并为其添加5个0表示每个巢初始为空。分配鸽子确定性版本让第1只鸽子随机选择一个1到5之间的数假设是3。那么就将巢穴计数[3]的值增加1同时让鸽子克隆体移动到3号巢穴的位置。让第2只鸽子重复这个过程。…直到第6只鸽子完成选择。关键检查在所有鸽子分配完成后遍历巢穴计数列表。如果发现有任何一项的值大于等于2就说明“至少有一个巢有2只或以上鸽子”。由于65根据鸽巢原理这个结果必然发生。我们的程序要能高亮显示这个些巢穴。分配鸽子随机统计版本这是更精彩的环节。我们做一个循环比如重复1000次实验。每次实验开始时都要将巢穴计数列表全部重置为0。然后模拟鸽数只鸽子依次进行随机选择就像上面步骤2一样。一次实验完成后检查列表。如果出现“至少一双”的情况就将至少一双次数增加1。1000次循环结束后计算并显示频率。你会发现这个频率几乎就是100%。随着实验次数增加它会越来越稳定在100%。这从统计角度强有力地印证了鸽巢原理的必然性——不是“可能”而是“每次都会发生”。这个设计巧妙地将“必然性”的数学原理转化为“可观察、可重复、可统计”的编程实验。孩子通过调整鸽数和巢数比如7只鸽子进5个巢8只进5个巢能直观看到结论依然成立从而加深理解。3. 分步实现从零搭建你的“鸽巢模拟器”现在我们进入具体的Scratch积木搭建环节。我会详细解释每一组积木的作用以及为什么这样设计。3.1 初始化舞台与变量首先我们设置好舞台和全局变量。创建变量和列表在“变量”模块中点击“建立一个变量”创建巢数、鸽数、实验次数、至少一双次数。点击“建立一个列表”创建巢穴计数。为了界面整洁我通常将巢数和鸽数设置为滑杆显示这样在运行中可以直接拖动调整。右键点击舞台上这些变量的显示框选择“滑杆”并设置一个合理的范围比如巢数1-10鸽数1-15。编写初始化脚本 我们需要一个当绿旗被点击时运行的脚本来初始化一切。通常我会把这个脚本放在舞台或者一个控制角色上。当绿旗被点击 将 [巢数 v] 设为 [5] // 初始默认值 将 [鸽数 v] 设为 [6] 将 [实验次数 v] 设为 [0] 将 [至少一双次数 v] 设为 [0] 删除 (巢穴计数 v) 的全部项目 // 清空旧列表 重复执行 (巢数) 次 // 根据当前巢数初始化列表 将 [0] 加入 (巢穴计数 v) 结束 广播 [初始化完成 v] 并等待 // 通知其他角色准备开始为什么这样做清空列表和根据巢数动态填充0保证了程序的灵活性。之后我们改变巢数滑杆重新点击绿旗列表长度会自动调整。3.2 创建并控制鸽子克隆体鸽子角色的代码是核心之一它负责克隆自己并在克隆后执行“找巢”的行为。鸽子本体代码负责克隆当接收到 [初始化完成 v] 隐藏 // 本体隐藏只显示克隆体 重复执行 (鸽数) 次 创建克隆体 [自己 v] 结束鸽子克隆体代码核心行为 这是最关键的逻辑。当克隆体启动时它需要显示自己。随机选择一个巢穴编号。更新该巢穴的计数列表。移动到对应巢穴的位置。当作为克隆体启动时 显示 将 [我的巢编号 v] 设为 (在 (1) 到 (巢数) 间随机选一个数) // 使用一个私有变量记录自己的选择 将 [巢穴计数 v] 的第 (我的巢编号) 项替换为 ((巢穴计数 v) 的第 (我的巢编号) 项) (1)) // 更新列表 在 (1) 秒内滑行到 x: (巢穴X坐标 v) y: (巢穴Y坐标 v) // 移动到对应巢穴。坐标需要预先计算或设定。这里有个非常重要的细节列表的并发修改问题。如果多个克隆体在同一瞬间对于计算机而言去修改列表的同一项可能会导致计数错误。Scratch虽然简单但也存在类似“竞态条件”的隐患。更稳妥的做法是让克隆体在修改列表前稍作等待或者使用“广播并等待”来同步。一个简单实用的技巧是当作为克隆体启动时 显示 将 [我的巢编号 v] 设为 (在 (1) 到 (巢数) 间随机选一个数) 等待 (0.1) 秒 // 加入一个极短的等待让克隆体的执行在时间上错开一点点 将 [巢穴计数 v] 的第 (我的巢编号) 项替换为 ((巢穴计数 v) 的第 (我的巢编号) 项) (1)) ...这个等待0.1秒在视觉效果上几乎察觉不到但能有效避免计数错误是处理简单并发的一个小窍门。3.3 设计巢穴的视觉反馈我们需要让“至少有一双”的巢穴看起来与众不同。我们可以通过一个独立的“巢穴高亮”角色或者通过背景切换来实现。这里介绍用角色实现的方法。创建一个“高亮框”角色画一个颜色鲜艳如红色、轮廓较粗的矩形框大小刚好能罩住一个巢穴。初始状态为隐藏。编写检查与高亮脚本 在所有鸽子都就位后可以通过等待一个具体时间或者用“广播并等待”确保所有克隆体完成移动进行检查。当接收到 [检查结果 v] // 在所有鸽子分配完成后广播此消息 将 [i v] 设为 [1] // 使用一个计数器变量i 重复执行 (巢数) 次 如果 (巢穴计数 v) 的第 (i) 项 [1] 那么 将 [高亮框 v] 的x坐标设为 (巢穴X坐标 v) // 移动到第i个巢穴的位置 将 [高亮框 v] 的y坐标设为 (巢穴Y坐标 v) 显示 [高亮框 v] 说 (连接 [巢穴 ] (连接 (i) [ 里有 ] (连接 ((巢穴计数 v) 的第 (i) 项) [ 只鸽子]))) (2) 秒 停止 [这个脚本 v] // 找到一个就停止或者不停止以显示所有符合条件的巢穴 结束 将 [i v] 改变 [1] 结束 说 [所有巢穴都最多只有1只鸽子。] (2) 秒 // 理论上只要鸽数巢数就不会执行到这里3.4 实现批量随机实验与统计这是项目的升华部分让孩子理解“概率为1”的概念。创建控制按钮画一个“开始实验”的按钮角色。编写批量实验脚本当角色被点击 // “开始实验”按钮 将 [实验次数 v] 设为 [0] 将 [至少一双次数 v] 设为 [0] 重复执行 (1000) 次 // 进行1000次实验 将 [实验次数 v] 增加 [1] // 1. 重置巢穴计数列表 删除 (巢穴计数 v) 的全部项目 重复执行 (巢数) 次 将 [0] 加入 (巢穴计数 v) 结束 // 2. 模拟一次随机分配不使用克隆体动画只进行逻辑计算以提高速度 将 [j v] 设为 [1] 重复执行 (鸽数) 次 将 [随机巢 v] 设为 (在 (1) 到 (巢数) 间随机选一个数) 将 [巢穴计数 v] 的第 (随机巢 v) 项替换为 ((巢穴计数 v) 的第 (随机巢 v) 项) (1)) 将 [j v] 改变 [1] 结束 // 3. 检查本次实验是否出现“至少一双” 将 [k v] 设为 [1] 将 [本次有双 v] 设为 [0] // 一个临时变量0代表无1代表有 重复执行 (巢数) 次 如果 (巢穴计数 v) 的第 (k) 项 [1] 那么 将 [本次有双 v] 设为 [1] 结束 将 [k v] 改变 [1] 结束 // 4. 根据检查结果更新“至少一双次数” 如果 (本次有双) [1] 那么 将 [至少一双次数 v] 增加 [1] 结束 结束 // 5. 显示统计结果 说 (连接 [实验次数] (连接 (实验次数) (连接 [至少一双次数] (连接 (至少一双次数) (连接 [频率] (连接 ((至少一双次数) / (实验次数)) [。])))))) (5) 秒关键点解释这个脚本没有使用克隆体和动画纯粹进行数字计算因此运行速度极快能瞬间完成上千次实验。这向孩子展示了计算机模拟的强大力量。结果显示的频率会非常接近1100%。你可以尝试将鸽数设置为小于等于巢数比如5只鸽子进5个巢再运行实验会发现频率不再是100%从而对比理解原理成立的条件。4. 教学延展与项目变式不止于原理验证一个优秀的教学项目不应该止步于功能实现。鸽巢原理本身很简单但围绕它可以展开丰富的教学活动和思维拓展。4.1 探究式问题设计在项目完成后可以向学生提出一系列问题引导他们进行探究基础验证“如果鸽子数量正好等于巢穴数量会出现‘至少一双’的情况吗用程序试试看。” 答案不一定只有当分配方式导致时才会出现随机实验下概率小于1。定量深入“鸽巢原理有一个更强形式如果把 q1 q2 ... qn - n 1 个物体放入 n 个抽屉那么要么第一个抽屉至少有 q1 个物体要么第二个抽屉至少有 q2 个物体...要么第 n 个抽屉至少有 qn 个物体。我们能修改程序来验证这个更复杂的结论吗” 这可以作为一个高阶挑战引导学生修改列表检查和判断逻辑。联系生活“我们班有30个人。为什么几乎可以肯定至少有两个人的生日在同一天一年最多366天。用我们的程序模拟一下巢数设为366鸽数设为30看看‘至少一双’的频率是不是很高” 这就是著名的“生日悖论”的简化版结果会出乎很多孩子的意料。极限思考“如果让实验次数变得非常大比如10万次频率会有什么变化它最终会稳定在一个值上吗这个值和我们理论推导的概率有什么关系” 这引入了“大数定律”的直观感受。4.2 项目优化与美化建议为了让项目更完善、更像一个真正的“产品”可以从以下方面优化动态巢穴布局不要固定死巢穴的坐标。可以用Scratch的计算功能根据巢数动态计算每个巢穴的x坐标让它们均匀分布在舞台上。这样调整巢数滑杆时布局会自动变化。更丰富的可视化不要只用一个红框高亮。可以让拥挤的巢穴鸽子数1抖动、变色、或者变大。给每只鸽子加上编号这样能更清楚地看到是哪几只鸽子挤进了同一个巢。实时数据显示在舞台角落创建一个信息面板实时显示每个巢穴当前的鸽子数量以及“是否已出现至少一双”的状态。这比最后才检查更直观。错误处理与用户提示如果用户设置的鸽数小于等于巢数在运行确定性模拟时程序可以给出提示“当前鸽子数未超过巢穴数不一定有巢穴包含两只以上鸽子哦。” 提升用户体验。制作两种模式在舞台上做两个按钮“单次演示模式”带动画和“批量实验模式”纯计算快速出统计结果。让用户自由选择。4.3 从Scratch到数学思维的跨越这个项目的终极目标不是学会一堆Scratch积木而是培养一种“计算思维”和“实验数学”的素养。建模思想孩子学会了如何将一个数学问题鸽巢原理转化为计算机可以理解和处理的数据模型变量、列表和过程模型初始化、随机分配、检查判断。算法思维他们设计并实现了一套明确的步骤算法来解决“验证原理”和“统计频率”这两个问题。模拟与验证他们亲身体验了对于某些数学结论除了逻辑证明还可以通过大量重复的计算机模拟来获得强有力的佐证。这是一种现代科学和工程中非常重要的研究方法。调试与迭代在搭建过程中计数出错、克隆体位置不对、逻辑顺序混乱等问题一定会出现。解决这些问题的过程就是最真实的“调试”训练锻炼了耐心和逻辑排查能力。通过这样一个从趣味情境入手到动手实现再到深入探究和优化的完整项目鸽巢原理就不再是书本上一句冰冷的定理而是一个孩子自己“发现”和“验证”过的、活生生的知识。这正是编程与数学结合的魅力所在——让思维可见让过程可玩让知识生根。