1.5 建模
学习目标
学完这一节,你能:
- 把一句模糊需求改写成完整的建模卡
- 区分硬约束和目标,避免混淆
- 为输入输出选择精确的表示方式
- 用小例子验证模型是否对应现实
- 发现隐式约束,避免模型遗漏
本节定位
1.2 六问诊断法给出了分析问题的框架:理解问题、建立基线、检索知识、利用结构、选择范式、承认边界。
这一节聚焦第一问的具体技能:如何把"我到底在解决什么"从一句模糊需求变成可审查的建模卡。
如果你还没有读过 1.2,建议先了解六问法的整体框架,再来看这一节的具体操作。
建模不是给问题贴算法名
建模,是把现实任务翻译成可计算的对象、规则和评价方式。
现实任务通常长这样:
帮班级订午餐,别超预算,尽量让大家满意。
这句话还不能直接写代码。因为它没有说清楚:
- 有多少人?
- 有哪些套餐?
- 每个套餐多少钱?
- 有没有素食、过敏、库存限制?
- "满意"怎么衡量?
- 如果两个方案都满足要求,哪个更好?
如果你直接让工具写代码,它可能会把问题理解成"选评分最高的套餐",也可能理解成"选最便宜的套餐"。这两个方向都可能写出能运行的代码,但都未必解决真实问题。
建模要先回答:
什么样的答案算合法?在合法答案里,什么样的答案更好?
这是本节的核心。
建模卡的核心结构
建模卡不需要固定格式,但通常包含以下几项:
- 输出:要返回什么?(判断、数值、列表、安排...)
- 输入:已知数据有哪些?格式、单位、范围?
- 硬约束:哪些条件必须满足?
- 目标:在合法答案里,哪个更好?
- 规模:输入大概多大?
- 边界:特殊情况怎么处理?
下面逐项展开。
先确定输出
很多建模错误来自没有先确定输出。
同一句需求,输出不同,问题就不同。
帮我安排会议。
它可能至少有四种含义:
| 输出 | 实际问题 |
|---|---|
true / false | 给定一组会议,判断是否存在时间冲突 |
| 一个时间段 | 为一个新会议找一个可用时间 |
| 一整张安排表 | 把多场会议分配到不同时间和房间 |
| 一个冲突列表 | 找出哪些会议之间冲突,供人工调整 |
这些问题共享一些数据,却不是同一个问题。
所以建模时先问:
我到底要输出什么?
输出一旦变了,输入、约束、目标和复杂度都会跟着变。
再定义输入
输入不是自然语言描述,而是算法真正能读取的数据。
以会议问题为例,清晰输入可以写成:
输入:
- meetings:会议列表
- 每个会议包含 start、end、room
- start 和 end 使用分钟数表示,例如 9:30 写成 570
- start < end这比"一些会议时间"清楚得多。
注意三件事:
- 单位:分钟、秒、元、人数、公里,必须明确。
- 范围:最多多少条记录?数值最大是多少?
- 合法性:如果
start >= end,是拒绝输入,还是自动修正?
很多 bug 看起来是实现错误,实际上是输入没有定义清楚。
硬约束和目标
建模时最重要的区分之一是:
哪些条件必须满足?哪些只是越好越好?
必须满足的条件叫硬约束。例如:
- 总价格不能超过 900 元
- 每个人必须有一份午餐
- 素食同学必须拿到素食套餐
- 不能选择库存为 0 的套餐
越好越好的标准叫目标。例如:
- 总满意度尽量高
- 总价格尽量低
- 套餐种类尽量少,方便发放
- 剩余预算尽量多
把目标误写成硬约束,会让问题变得过度严格;把硬约束误写成目标,会让算法产出看似高分但不可接受的方案。
例如:"尽量便宜"通常是目标;"不能超过预算"是硬约束。一个超过预算但便宜程度排名很高的方案,仍然不能接受。
例子:班级订午餐
现在把开头的模糊需求改成建模卡。
任务:
- 为 30 名学生订午餐。
输入:
- 套餐列表 foods
- 每个套餐包含 price、stock、is_vegetarian、score
- price 是单价,单位为元
- stock 是最多可订份数
- score 是学生问卷得到的满意度估计,范围 1 到 5
输出:
- 每个套餐订多少份
硬约束:
- 总份数必须等于 30
- 总价格不能超过 900
- 至少 5 份必须是素食套餐
- 每个套餐订购份数不能超过库存
- 每个订购份数都必须是非负整数
目标:
- 在满足硬约束的方案中,让总满意度尽量高
- 如果总满意度相同,选择总价格更低的方案
规模:
- 套餐种类通常不超过 20
边界:
- 如果没有任何合法方案,返回"无法满足约束",而不是随便给一个近似方案这时,问题已经变得可审查。
如果有人给出一个方案,你可以逐条检查它是否合法。如果有多个合法方案,你也知道应该如何比较。
选择表示方式
建模不仅要定义目标,还要选择怎样表示现实对象。
常见表示方式包括:
| 现实结构 | 可计算表示 | 适合表达什么 |
|---|---|---|
| 一批对象 | 列表 | 遍历、筛选、统计 |
| 时间段 | [start, end] 区间 | 是否重叠、先后顺序 |
| 对象之间的关系 | 点和连接组成的图 | 谁和谁相连、谁依赖谁 |
| 候选方案 | 每个候选是否被选择 | 从一组选项中取一部分 |
| 分配关系 | 二维表 | 谁分到什么资源 |
| 分层结构 | 父子关系表 | 组织层级、目录结构 |
这里的"图"不是图片,而是一种关系模型:点表示对象,连接表示对象之间的关系。比如学生之间是否同组、任务之间是否有先后依赖,都可以用点和连接表达。
表示方式会影响后面的算法方向。更重要的是,它会影响你能不能把现实约束表达出来。
如果一个约束在你的表示里很难表达,可能不是算法还不够聪明,而是模型选错了。
模型也需要验证
前面一节讲过验证代码正确性。建模也需要验证。
检查模型时,问两个方向:
- 模型接受的方案,现实中真的能接受吗?
- 现实中可接受的方案,模型会不会错误地排除?
还是午餐例子。
如果模型只要求"总价不超过 900",那么它可能接受 30 份同一种非素食套餐。这个方案满足预算,却不能满足素食约束。模型接受了现实中不能接受的方案。
如果模型要求"每种套餐至少订 1 份",但现实并没有这个要求,那么它可能排除一个本来很好的方案。模型错误地拒绝了现实中可接受的方案。
这两类错误都很常见:
- 模型太松:产生不合法方案
- 模型太紧:排除好方案
建模不是一次写完就结束。它需要用小例子、边界情况和反例不断校准。
隐式约束
现实任务里,最危险的约束常常没有写出来。
例如班级订午餐,需求里可能没有写:
- 有同学过敏,某些食材不能出现
- 午餐必须在 12:00 前送到
- 商家单个套餐至少 3 份起订
- 老师希望种类不要太多,方便发放
这些约束如果不进入模型,代码会给出一个"数学上很好、现实中很糟"的答案。
所以建模时要主动追问:
这个结果拿到现实中,会被谁拒绝?
拒绝理由是什么?
有没有没有写出来但必须遵守的规则?
有没有看似越多越好、其实超过某个点就变差的指标?工具可以帮你列澄清问题,但通常无法自行知道现实中谁会拒绝这个结果。这个判断来自业务、用户、物理世界和责任边界。
给工具之前,先给模型
当你需要工具帮忙实现时,不要只给一句需求:
帮我写一个订午餐的算法。
更好的输入是建模卡:
请先审查下面的建模是否完整,不要直接写代码。
任务:为 30 名学生订午餐。
输入:foods,每个元素包含 price、stock、is_vegetarian、score。
输出:每个套餐订多少份。
硬约束:
- 总份数等于 30
- 总价格不超过 900
- 至少 5 份素食
- 不超过库存
目标:
- 先最大化总满意度
- 满意度相同时,总价格更低更好
规模:套餐种类不超过 20。
请指出:
1. 这个模型还有哪些歧义?
2. 哪些边界情况需要确认?
3. 有没有硬约束和目标混淆的地方?这不是固定模板,而是本节最自然的训练动作:先把问题建模成可审查规格,再让工具参与。
常见建模错误
1. 把实现想法当成问题定义
错误写法:
用一个循环找最合适的套餐。
这不是问题定义。它已经跳到实现了。
更好的写法:
在满足预算、库存和素食要求的所有订餐方案中,选择满意度最高的方案。
2. 只优化容易量化的指标
价格容易量化,满意度难量化。于是有人只优化价格。
这会得到便宜但没人愿意吃的方案。
容易计算的目标,不一定是真正重要的目标。
3. 忘记不可违反的边界
预算、库存、过敏、时间限制、合法输入,这些通常不是"尽量满足",而是必须满足。
如果一个方案违反硬约束,它的目标分数再高也不能接受。
4. 没有写平局规则
如果两个方案满意度相同,一个 850 元,一个 899 元,选哪个?
如果不写平局规则,工具可能随便返回一个答案。这个答案不一定错,但不可审查。
5. 为了套某个算法而扭曲问题
有时你会先想到一个熟悉的算法形状,然后强行把现实问题塞进去。
这很危险。正确顺序应该是:先理解现实约束,再选择表示方式和算法方向。
本节要点
- 建模是把现实任务翻译成可计算的对象、规则和评价方式。
- 建模要先确定输出,再定义输入、硬约束、目标、规模和边界。
- 硬约束决定方案是否合法,目标决定合法方案之间哪个更好。
- 表示方式会影响哪些约束容易表达,哪些算法方向自然出现。
- 模型也需要验证:既不能接受现实中不合法的方案,也不能排除现实中可接受的方案。
- 在让工具写代码前,先让它审查你的模型,通常比直接让它实现更可靠。
课堂练习
练习 1:写建模卡
把下面需求改写成建模卡:
班级要订 30 份午餐,预算 900 元以内,至少 5 位同学需要素食,尽量让大家满意。
要求写出:
- 输入数据有哪些字段?
- 输出是什么?
- 哪些是硬约束?
- 目标是什么?
- 至少两个边界情况是什么?
练习 2:同一句需求,不同模型
需求:
帮我安排今天的学习任务。
请给出两种不同建模:
- 输出是一张按时间排列的计划表。
- 输出是今天应该完成的任务集合。
分别说明两种模型的输入、输出、硬约束和目标有什么不同。
练习 3:找建模错误
有人把午餐问题建模成:
目标:选择平均满意度最高的套餐,订 30 份。请指出这个模型至少漏掉了哪些现实条件。然后给出一个小例子,说明这个模型会产生现实中不能接受的方案。
练习 4:让工具先审查模型
任选一个你熟悉的编程任务,先写建模卡,再写一段提示词,让工具只做三件事:
- 找出模型中的歧义。
- 找出缺失的边界情况。
- 找出硬约束和目标可能混淆的地方。
不要让它先写代码。
课后测验
📝 课后测验
上一节:1.4 复杂度判断
下一节:1.6 综合练习