按证据修错,不靠乱改
这一课只练一件事:用按证据修错,而不是靠猜测乱改。顺序是先做,再用或检验假设,最后记下修复原因并重新测试。必会:能把错误缩小到小数据;能写出验证假设的输出;不靠乱改碰运气。最小示例:求前缀和的程序对 n=5 错了,先缩成 n=2 仍错。样例追踪:n=5 输出 9,手算应是 10;缩到 a=[1,2] 仍少 1;分段输出发现 prefix 从 a[1] 开始加。边界是不要在大数据上猜改,也不要改完不回归。建议掌握:写下修复原因。拓展:用暴力程序做只是拓展习惯,现在不当硬门槛。这里不会在浏览器里运行 C++。
任务 1:先选出该怎么修
先确定答案错误时该按什么开始,再确定有了反例后该不该用。点不认识的词可看基础解释。
必会:能说出为什么要先做;能说出为什么不能靠乱改碰运气。这里不会在浏览器里运行 C++,只练习选择和走过程。
程序在 n=100000 上错了,样例却是对的。第一步该做什么?最小复现 · 必会
已经有一组 n=2 的反例,接下来该怎么验证“是下标写错了”?分段输出 · 必会
任务 2:按缩小、分段输出、回归三步走完修错卡
最小示例:求前缀和的程序对 n=5 输出 9,手算应是 10。按顺序点三步:先做,再,最后记下原因并用原样例和反例回归。这里不会在浏览器里运行 C++。
必会:能解释每一步为什么这样做。边界:不要在 n=100000 上猜改;不要改完不回归。
- // 现象:n=5,a=[1,2,3,4,5],输出 9,手算应是 10
- // 缩小:试 a=[1,2],输出 2,手算应是 3,错误仍在
- // 分段输出:打印 i 和 prefix,发现循环从 i=1 开始,漏了 a[0]
- // 回归:记下“少加了 a[0]”,再用原样例和这组反例复测
三步走完了,现在把“分段输出”落成一行代码:在循环里把这一轮的轮次 i 和当前算出来的和 prefix 都打到屏幕上,再和手算对照。下面是只读脚手架,只留一空给你敲键盘;这里不会在浏览器里运行 C++。
- // 现象:n=5,a=[1,1,2,3,3],手算应是 10,程序却输出 9
- int a[5] = {1, 1, 2, 3, 3};
- int prefix = 0; // prefix:加到这一轮为止的和
- int main() {
- for (int i = 1; i <= 4; i += 1) { // 可疑:这一轮该从哪里开始加?
- // 分段输出:把 i 和 prefix 都打出来
- prefix += a[i];
- }
- cout << prefix << "\n"; // 输出 9
- return 0;
- }
cout << i << " " << prefix << "\n"; // 每一轮各占一行,才好和手算对照
任务 3:用断言思路检验,并把差分检查当拓展
必会:修完后要用原样例和最小反例再测一遍。建议掌握:用写下“这里应该等于什么”。拓展:现在只需要认识,不当硬门槛。
必会 / 建议掌握 / 拓展:必会是缩小错误、写出验证假设的输出、改完要回归。建议掌握是记下修复原因并用断言思路。差分检查是拓展,现在不用当硬门槛。
- // 必会:改完循环起点后,再用 n=2 反例和原样例复测
- // 建议掌握:写下“prefix 在 i=0 时应等于 a[0]”
- // 边界:不要连续改三处还不记录,也不要只看最终答案
- // 拓展:拿暴力程序对照同一组输入(现在不当硬门槛)
先选出该用什么思路检验假设:
再选出修完后该怎么对待测试:
任务 4:随机小测,看看你是否真的懂了
每次从本课题库抽出三道不同的选择题。题目与解析已通过校验,答完后看解析,不要只看分数;拿不准就回到对应任务再看一遍。