简介这份PDF是针对浙江高中信息技术课程中《算法与程序设计学生活动手册》的参考答案汇编由一线教师整理面向浙江地区高中生及信息技术教师服务算法与程序设计实践环节的自主校对与课堂讲评。文件为单个PDF文档共1个文件大小244KB页面紧凑、便于手机或电脑上直接查看、按实践编号快速定位。目前已有72人学习适合正在使用该活动手册的学生对照练习、查漏补缺也可供教师批改参考。内容覆盖实践一至实践八从环境搭建、变量与输入输出到条件循环结构、数组/列表遍历、函数定义与调用、文件读写再到递归及排序搜索算法均有对应操作提示和相关练习解答能直观呈现解题思路与程序实现结果。借助这份答案学习者可以及时发现问题、修正算法设计更高效地完成高中阶段算法与程序设计的实践任务。1. 一份被压到极简的算法参考答案十三次实践的完整还原拿到这份手册的参考答案时我的第一反应是“省”字。实践一的圆环面积只给了s PI * R1 ^ 2 - PI * R2 ^ 2实践十的冒泡排序只留了三个循环边界实践十三的二分查找连数组状态都没画。真要把这些答案讲给学生听缺的恰恰是中间的推导过程变量声明写在哪循环每一轮数组怎么变ASCII 内码加 10 之后会得到什么字符。把十三个实践按「表达式与函数、分支与循环、字符串处理、排序与查找、随机模拟」五个主题重排后会发现它其实覆盖了 VB 基础语法到经典算法的完整链路。这份还原适合高中信息技术教师、准备学考的学生以及想用少量代码快速回看算法基础的程序员配套的网络技术资源里能查到不少同类讲解但建议先跟着下文把代码跑通一遍。2. VB 表达式与内置函数实践一到三的求值顺序2.1 圆环面积里的常量声明与幂运算优先级实践一第 1 题是圆环面积(s \pi R_1^2 - \pi R_2^2)。手册答案给出两种写法第一种直接用字面量第二种用Const声明常量。后者是教材推荐做法也更容易维护 写法一直接使用字面量程序里出现两次 3.14 s 3.14 * R1 ^ 2 - 3.14 * R2 ^ 2 写法二声明编译期常量修改圆周率精度只改一处 Const PI 3.14 s PI * R1 ^ 2 - PI * R2 ^ 2关键点在于^运算符的优先级。VB 中幂运算优先级高于乘除所以PI * R1 ^ 2会先计算R1 ^ 2再与 PI 相乘不会产生(PI * R1)^2的歧义。这里最常见的错误是学生把内半径和外半径写反圆环面积是外圆减内圆一旦R1 R2结果变成负数程序不会报错但几何意义就错了。调试时可以先在立即窗口用Debug.Print输出中间量确认两个半径变量分别取到了正确的文本框值。与此相关的还有变量声明方式实践一相关练习第 5 小题给了Dim b As Double, x As Double这是 VB 里声明 Double 型局部变量的标准写法。注意 VB 的Dim一行声明多个变量时每个变量都要带类型关键字Dim b, x As Double只会让 x 是 Doubleb 是 Variant这是新手极易踩的坑。声明方式与作用域的对应关系可以按下面这张表整理声明位置关键字示例作用域与说明过程内部DimDim b As Double, x As Double仅当前过程可用模块顶部ConstConst PI 3.14过程内只读编译期确定模块顶部DimDim arr(1 To 100) As Integer整个模块可用数组下标从 1 开始2.2 一元二次方程求根Sqr 的参数必须非负实践二相关练习第 1 题给出了三个填空判别式b^2-4*a*c、求根公式(-bsqr(d))/(2*a)、以及两根相同时的条件d0。Sqr是 VB 的平方根函数要求参数非负否则运行时会弹出“实时错误 5无效的过程调用参数”。补全后的分支代码应该是Dim a As Double, b As Double, c As Double Dim d As Double, x1 As Double, x2 As Double d b ^ 2 - 4 * a * c If d 0 Then MsgBox 方程无实数解 ElseIf d 0 Then x1 (-b Sqr(d)) / (2 * a) d0 时两根相同 Else x1 (-b Sqr(d)) / (2 * a) x2 (-b - Sqr(d)) / (2 * a) End If这里的(2 * a)括号不能省。VB 的/和*优先级相同按从左到右结合如果不加括号(-b Sqr(d)) / 2 * a会被解析成先除以 2 再乘 a结果差了一个平方因子。这类表达式的检查技巧是先看括号层数再看运算符每层括号必须成对出现除号后面的分母整体要放在括号里。学生在抄写手册答案时经常丢掉这层括号程序不报错但值全错恰恰是最难排查的逻辑错误。2.3 嵌套函数与换底公式从最内层括号往外读实践三相关练习第 1 题给了三个表达式第 3 个Log(5) / Log(10)是 VB 里求常用对数的经典写法。VB 的Log函数默认是自然对数 ln(x)没有现成的Log10所以要用换底公式(\log_{10} 5 \frac{\ln 5}{\ln 10})。第 1 个表达式Sqr(Abs(x y) / 2)的求值顺序是先算xy再对结果取绝对值除以 2最后开方嵌套函数必须从内向外读。第 2 个表达式Abs(b - a a) - Abs(-2 * a)可以化简为Abs(b) - Abs(-2*a)其中b-aa的 a 相互抵消这类化简能减少一次运行时计算虽然手册阶段不要求性能但养成先化简后编码的习惯对后续算法题很有帮助。 组合求值观察嵌套顺序与换底写法 Dim x As Double, y As Double Dim r1 As Double, r2 As Double x 4 y -8 r1 Sqr(Abs(x y) / 2) 先算 xy-4取绝对值 4除 2 得 2开方约 1.414 r2 Log(5) / Log(10) 以 10 为底 5 的对数约 0.699 Debug.Print r1, r2程序设计实践里表达式求值错误大多集中在三处幂运算误用为位运算、括号层级错位、整除与浮点除混用。VB 里\是整除/是浮点除法求平均值必须用/而求数组中间下标用\两者混用会导致小数值被截断或类型不匹配。实践三的选择题答案集中在 D、C、B考的就是这些基本函数的返回值边界比如Sqr(0)返回 0Log(1)返回 0Abs(-3.14)返回 3.14这些都是可以直接在立即窗口里验证的。3. 条件分支与循环控制闰年、素数与最大公约数3.1 分段条件的两种写法If 分支与数轴图实践四操作提示第 4 小题给出的是b 20 Then ... Else ... 或 ElseIF b 35 Then这说明手册在让学生补全一个分段条件的判断逻辑。分段问题最稳妥的做法是先在纸上画数轴把 b 的取值区间切成互不重叠的几段再决定分支顺序。如果手册原题是让填b 20之后的分支一种完整结构是 结构一从低区间往高区间依次判断 If b 20 Then fee 0 ElseIf b 35 Then fee 15 Else fee 8 End If 结构二用 And 显式写出区间边界 If b 20 And b 35 Then fee 8 ElseIf b 20 Then fee 0 Else fee 15 End If两种写法等价差别在可读性和扩展性。结构一按区间顺序推进新增档位时只需在中间插入 ElseIf代码更清晰结构二用 And 把边界直接写出来等于把数轴画在代码里适合边界值较少的场景。我一般让学生先用第一种因为 ElseIf 天然保证了互斥不会出现两个分支条件同时成立的情况。这里要特别注意 VB 的And不是短路运算符b 20 And b 35左右两边都会求值如果第二个表达式可能越界抛错需要把先做判断的条件放在左边。3.2 闰年判断布尔函数调用与复合条件实践七操作提示第 4 小题给出了年份和月份的合法性判断year 1 And year 9999 And month 1 And month 12以及闰年判断的两种调用写法leap(year) Or leap(year) True。这说明教材允许两种风格但leap(year)本身就是布尔表达式直接作为条件更简洁。完整的闰年函数和日期分支如下Function leap(ByVal y As Integer) As Boolean leap (y Mod 4 0 And y Mod 100 0) Or (y Mod 400 0) End Function Dim year As Integer, month As Integer, d As Integer 先校验输入合法性再计算当月天数 If year 1 And year 9999 And month 1 And month 12 Then Select Case month Case 1, 3, 5, 7, 8, 10, 12 d 31 Case 4, 6, 9, 11 d 30 Case 2 If leap(year) Then d 29 Else d 28 End Select End If闰年判断的规则容易记混口诀是“四年一闰百年不闰四百年再闰”。代码里两个条件通过And和Or组合注意And优先级高于Or所以(y Mod 4 0 And y Mod 100 0) Or (y Mod 400 0)能按预期解析不用额外嵌套。我在课堂上会让每个学生拿2000、1900、2024、2023四个年份手算一次2000 年能整除 400 是闰年1900 年能整除 100 但不能整除 400 是平年这两个边界值能筛掉绝大多数对规则理解不到位的情况。3.3 更相减损法与素数筛选循环边界的三种选择实践七相关练习第 1 题输出 6是求两个数的最大公约数手册特别注明用的是更相减损法并且提示还可以用欧几里得算法。两种实现对比 更相减损法大数减小数直到两数相等 Dim a As Integer, b As Integer a 42: b 30 Do While a b If a b Then a a - b Else b b - a End If Loop Debug.Print a 输出 6 欧几里得算法取模迭代循环次数远少于相减 a 42: b 30 Do While b 0 t a Mod b a b b t Loop Debug.Print a 输出 6更相减损法在数学课上直观好懂但当两个数差距悬殊时比如求 1 和 10000 的公约数需要相减近万次欧几里得算法用Mod一步把规模降到余数级别效率差别是线性和对数的区别。手册特意保留两种解法目的是让学生体会“同一个算法问题不同策略的运算量差异”。我一般建议把欧几里得作为标准实现因为取模本身就是除法运算硬件层面比循环相减高效得多。实践八的素数判定是另一个循环边界问题。标准写法是Function IsPrime(ByVal n As Integer) As Boolean Dim i As Integer If n 2 Then Exit Function For i 2 To n - 1 If n Mod i 0 Then Exit Function Next i IsPrime True End FunctionFor i 2 To n - 1逐个试除只要有一次余数为 0函数立即退出返回 False。这段代码的时间复杂度是 O(n)但 n 的因子成对出现a * b n中较小的因子必然不超过 (\sqrt{n})所以循环上界可以安全改成Int(Sqr(n))这就是从朴素试除到根号剪枝的经典优化实践八的相关练习里m Mod i 0配合布尔变量p1、p2做复合判断本质上也是在提前排除非素数。三个实践涉及的循环结构对比如下实践循环结构退出条件时间复杂度实践七相关练习 1Do While a b两数相等O(n)最坏是两数之和级实践八相关练习 2For i 2 To n - 1n Mod i 0O(n)优化后 O(√n)实践六相关练习 2For i 1 To ni nO(n)求最小值4. 字符串内码与加密循环Mid、Asc、Chr 的组合用法4.1 逐字符取用的三个基础函数实践五操作提示第 4 题给出了Mid(s, i, 1)和Chr(Asc(a) - 10)实践六操作提示第 2 题又给出了Mid(x, j, 1)和Chr(Asc(b) 10)。两组提示连起来看就是在实现一个“字符内码加 10”的替换式加密Asc取字符的内码加 10 后用Chr还原成新字符Mid负责逐字符遍历。三个函数的配合关系是固定的函数作用示例返回值类型Mid(s, i, 1)截取字符串第 i 个字符Mid(ABC, 2, 1) → BStringAsc(c)字符转内码Asc(A) → 65IntegerChr(n)内码转字符Chr(65) → AString实践五相关练习第 3 题的填空i 1: k d(i)是另一种遍历起点它假设数组第一个元素就是当前最大值循环从第 2 个元素开始比较这和第 3 章的求最小值套路一致。字符串处理的遍历则不同它的循环上界必须用Len(s)动态获取因为字符串长度在运行时才确定。一次典型的取字符循环是Dim s As String, i As Integer, c As String s Zhejiang2024 For i 1 To Len(s) c Mid(s, i, 1) Debug.Print i, Asc(c) 输出位置和字符内码 Next i4.2 内码加 10 的加密与解密循环实践六实现的是每个明文字符内码加 10 得到密文。补全后的加密过程非常短但信息量集中 加密把每个明文字符的内码加 10 Dim plain As String, cipher As String Dim j As Integer, c As String plain Text1.Text For j 1 To Len(plain) c Mid(plain, j, 1) cipher cipher Chr(Asc(c) 10) Next j Text2.Text cipher解密时把10换成-10即可。这段代码有两个边界问题值得在课堂上演示。第一Asc对英文字符返回 ASCII 码但中文字符在 VB6/VBA 里返回的是本地代码页编码不是 Unicode 码点加 10 之后可能落在未定义字符区所以教材只拿可见的英文字符做示例如果换成 VB.NET应该用AscW和ChrW处理 Unicode。第二加密字符内码加 10 后可能越过可打印范围比如z的 ASCII 是 122加 10 变成 132在特定代码页下输出乱码这一点手册没有展开实际教学里可以让学生自己试一组a、m、z的加密结果。实践六相关练习第 3 题还出现了sum sum i和Text2.Text Str(sum)Str函数把数值转成字符串再赋值给文本框VB 里 TextBox 的 Text 属性只接受字符串直接赋数值虽然会自动转换但显式用Str能避免空格和格式上的意外。这个习惯在做输出格式化时尤其重要。4.3 最大子序列和一次扫描完成的最值问题实践五相关练习最后一题的答案是 6手册注释明说是“求最大子序列和”。这个题目在算法和数据结构里属于经典入门问题初级解法是三重循环枚举所有子序列而手册想教的是在线扫描的版本Dim d(1 To 10) As Integer Dim i As Integer, s As Integer, mx As Integer 数组 d 已按要求赋值假设最大连续子序列和为 6 s 0 mx 0 For i 1 To 10 s s d(i) If s 0 Then s 0 If s mx Then mx s Next i Debug.Print mx 输出 6核心是If s 0 Then s 0这一行。任何连续子序列如果当前累加和已经变成负数它作为后续元素的前缀只会拖累总和所以直接从 0 重新累计mx记录的是历史上出现过的最大和和当前累计值s是两个独立概念。数组全是正数时最大子序列和等于总和数组全是负数时这个算法会错误地返回 0这是它唯一需要修补的边界解法是让mx初始值为第一个元素而不是 0。整段代码只扫描数组一遍时间复杂度 O(n)空间 O(1)其思想也是后面动态规划入门课里“状态只依赖前一步结果”的雏形值得让学生把执行过程按 i 的变化手写一遍再对比运行结果。5. 排序与查找从冒泡到二分查找的循环边界5.1 冒泡排序内层上界为什么不断缩小实践十操作提示只给了三个填空外层1 To n - 1、内层n To i 1、交换d(j) d(j - 1)。这是从后向前扫描的冒泡变体补全成更常见的向前扫描版本逻辑是等价的Dim i As Integer, j As Integer, t As Integer Dim d(1 To 6) As Integer 数组赋值略 For i 1 To n - 1 For j 1 To n - i If d(j) d(j 1) Then t d(j) d(j) d(j 1) d(j 1) t End If Next j Next i每轮外层循环执行完未排序区间的最大值就被交换到末尾所以第 i 轮结束后最后 i 个位置已经排好。内层循环的上界因此从 n 逐渐缩小到 n - i这就是答案1 To n - 1、n To i 1的来源。手册相关练习题里给出的序列164 168 172 175 185 178到164 168 172 175 178 185正是最后一轮把 185 和 178 交换后的状态。排序状态变化可以用表格记录轮次遍历范围本轮确定的位置示例状态升序初始——178 168 172 175 185 164第 1 轮1 到 5下标 6168 172 175 178 164 185第 2 轮1 到 4下标 5168 172 175 164 178 185最终——164 168 172 175 178 185最容易被忽视的坑是把内层上界写成 n这样程序不会报错但每一轮都在比较已经就位的元素白白多出近一半运算量。调试时可以在每次外层循环结束加一句Debug.Print Join(d, ,)观察最大值逐步“冒泡”到末尾的过程比直接看运行结果直观得多。冒泡排序的比较次数固定为 n(n-1)/2交换次数则依赖数据的初始有序程度这也是教材先讲冒泡再讲选择排序的原因——两者比较次数相同但交换策略的差异正好引出算法效率的讨论。5.2 选择排序比较次数不变交换次数锐减实践十一操作提示的第 3 题给出了选择排序的核心填空外层1 To n - 1、记录当前最小值位置k i、内层i 1 To n、交换条件min i。补全后的代码为For i 1 To n - 1 k i For j i 1 To n If d(j) d(k) Then k j Next j 若最小值不在当前位置才交换省掉多余的赋值 If k i Then t d(i) d(i) d(k) d(k) t End If Next i冒泡排序在每次比较时都可能做交换最坏情况下要交换 n(n-1)/2 次选择排序每一轮只记录最小值的下标一轮结束才交换一次最多交换 n-1 次。手册相关练习第 4 题的填空就是这组对比数据比较次数恒为 n(n-1)/2升序时交换 0 次无序时最多 n-1 次答案是“少快”。两种排序的对比学习是数据结构与算法入门的标准路径很多人面试时被问“冒泡和选择哪个快”本质就是在问交换开销和稳定性排序算法比较次数最优交换次数最差交换次数稳定性冒泡排序n(n-1)/20n(n-1)/2稳定选择排序n(n-1)/20n-1不稳定选择排序不稳定的原因在于交换可能把相同值的相对顺序打乱比如5a、3、5b、1第一轮把 1 换到最前面时两个 5 的相对位置就变了。手册阶段不需要深究稳定性定义但要让学生知道“比较次数相同不代表性能相同”。归并排序和堆排序这类更快的算法通常安排在后续章节选择排序的价值是建立一个 O(n^2) 排序家族的基线后续学快排时才看得出差距。5.3 线性查找与二分查找循环条件的两种风格实践十二操作提示里的i n、d(i) lSearch、i i 1是线性查找的骨架从下标 1 开始逐个比较直到越界或命中。二分查找则在实践十三核心是(lLeft lRight) \ 2和lSearch mArray(lMidd)这两个分支。两者的循环条件差异直接决定了各自的适用前提 线性查找不要求数组有序最坏遍历 n 次 Function lSearch(d() As Integer, key As Integer) As Integer Dim i As Integer i LBound(d) Do While i UBound(d) And d(i) key i i 1 Loop If i UBound(d) Then lSearch i Else lSearch 0 End Function 二分查找要求数组升序每次排除一半区间 Function bSearch(mArray() As Integer, key As Integer) As Integer Dim lLeft As Integer, lRight As Integer, lMidd As Integer lLeft LBound(mArray) lRight UBound(mArray) Do While lLeft lRight lMidd (lLeft lRight) \ 2 If mArray(lMidd) key Then bSearch lMidd Exit Function ElseIf key mArray(lMidd) Then lLeft lMidd 1 Else lRight lMidd - 1 End If Loop bSearch -1 End Function线性查找的循环条件是“没越界且没找到”同时成立一旦越界说明数组里没有目标值二分查找的循环条件是lLeft lRight区间还有元素就继续折半。两个函数都用了LBound和UBound代替固定 n这样数组下标从 0 开始还是从 1 开始都不影响正确性。二分查找里最容易错的是把lMidd写成(lLeft lRight) / 2浮点除返回带小数的 Double直接赋值给 Integer 型变量会发生四舍五入而不是截断可能越界用\整除既保证下标是整数也符合二分查找“取下整”的语义。实践十三相关练习给出的答案是 8 次对应大约 256 个元素的搜索规模每次比较把区间缩小一半最多需要 (\lceil \log_2(n1) \rceil) 次。76 人落位后真正要提醒学生的点是二分查找之前必须排序否则lMidd位置的元素和 key 的大小比较不携带任何区间信息整个算法立刻失效。这是排序算法和查找算法被放在同一个章节的原因没有前面的有序性就没有后面的对数级复杂度。6. 蒙特卡洛模拟用随机撒点估算 π 值6.1 从公式验算到随机模拟实践九的答案里有两条线索Text3.Text对应的累加公式ai*(i-1)/(2*i-1)和Str(pai*2)这是莱布尼茨级数计算 π 的近似值Text4.Text对应的是a^2b^2、ss1、Str(4*s/n)这是蒙特卡洛模拟。蒙特卡洛的思路是在单位正方形里随机撒点统计落在四分之一圆内的点数比例比例乘以 4 就是 π 的估计值 蒙特卡洛估算 πn 个随机点落在圆内的比例逼近 π/4 Dim n As Long, i As Long, s As Long Dim a As Double, b As Double n 10000 s 0 Randomize For i 1 To n a Rnd 生成 [0,1) 区间随机数 b Rnd If a ^ 2 b ^ 2 1 Then s s 1 End If Next i Text4.Text Str(4 * s / n)判断条件a^2 b^2 1就是原点距离平方小于等于 1落在半径 1 的四分之一圆内。每个随机点独立均匀分布在 1×1 的正方形里圆内面积与正方形面积之比是 π/4所以4 * s / n逼近 π。n 取 10000 时误差约 1%n 取 1000000 时可以到 0.1% 量级学生可以直观看到“次数越多随机模拟越精确”的大数定律现象这也是这个实践放在排序和查找之后的价值它展示了不靠解析公式也能算出数值结果的一种算法思维。6.2 用固定种子复现实验结果随机模拟在教学里有个麻烦每次运行结果都不一样学生无法对照检查自己的程序是否正确。VB 的Rnd函数在没有Randomize时每次程序启动生成的随机序列完全相同这反而适合调试一旦调用了Randomize就按当前时间初始化随机序列结果才真正随机。想复现某次实验数据常见做法是先用一个固定种子重置随机序列 固定随机种子复现同一组实验结果 Rnd -1 Randomize 42Rnd -1用固定值重置随机数生成器后面的Randomize 42再让序列从确定位置开始这样全班同学运行程序得到的是同一组随机点误差也一致便于课堂讨论。实践九相关练习第 1 题里的Int(Rnd * 10 1)生成的是 1 到 10 的随机整数它和Int(Rnd * 10) 1的区别只在边界前者直接加 1后者先取整再加 1两种写法在Rnd 0.1时结果略有差异这也是随机数取区间的一个细节。判断一个随机数公式是否正确可以连续运行 1000 次并统计s/n的收敛趋势比单次数值更有说服力。本文还有配套的精品资源点击获取