Skip to content

1.5 建模

学习目标

学完这一节,你能:

  • 把一句模糊需求改写成完整的建模卡
  • 区分硬约束和目标,避免混淆
  • 为输入输出选择精确的表示方式
  • 用小例子验证模型是否对应现实
  • 发现隐式约束,避免模型遗漏

本节定位

1.2 六问诊断法给出了分析问题的框架:理解问题、建立基线、检索知识、利用结构、选择范式、承认边界。

这一节聚焦第一问的具体技能:如何把"我到底在解决什么"从一句模糊需求变成可审查的建模卡。

如果你还没有读过 1.2,建议先了解六问法的整体框架,再来看这一节的具体操作。


建模不是给问题贴算法名

建模,是把现实任务翻译成可计算的对象、规则和评价方式。

现实任务通常长这样:

帮班级订午餐,别超预算,尽量让大家满意。

这句话还不能直接写代码。因为它没有说清楚:

  • 有多少人?
  • 有哪些套餐?
  • 每个套餐多少钱?
  • 有没有素食、过敏、库存限制?
  • "满意"怎么衡量?
  • 如果两个方案都满足要求,哪个更好?

如果你直接让工具写代码,它可能会把问题理解成"选评分最高的套餐",也可能理解成"选最便宜的套餐"。这两个方向都可能写出能运行的代码,但都未必解决真实问题。

建模要先回答:

什么样的答案算合法?在合法答案里,什么样的答案更好?

这是本节的核心。


建模闭环:从现实任务提炼规格,再用小例子和边界约定回到现实中校验
建模闭环:从现实任务提炼规格,再用小例子和边界约定回到现实中校验

建模卡的核心结构

建模卡不需要固定格式,但通常包含以下几项:

  • 输出:要返回什么?(判断、数值、列表、安排...)
  • 输入:已知数据有哪些?格式、单位、范围?
  • 硬约束:哪些条件必须满足?
  • 目标:在合法答案里,哪个更好?
  • 规模:输入大概多大?
  • 边界:特殊情况怎么处理?

下面逐项展开。


先确定输出

很多建模错误来自没有先确定输出。

同一句需求,输出不同,问题就不同。

帮我安排会议。

它可能至少有四种含义:

输出实际问题
true / false给定一组会议,判断是否存在时间冲突
一个时间段为一个新会议找一个可用时间
一整张安排表把多场会议分配到不同时间和房间
一个冲突列表找出哪些会议之间冲突,供人工调整

这些问题共享一些数据,却不是同一个问题。

所以建模时先问:

我到底要输出什么?

输出一旦变了,输入、约束、目标和复杂度都会跟着变。


再定义输入

输入不是自然语言描述,而是算法真正能读取的数据。

以会议问题为例,清晰输入可以写成:

text
输入:
- meetings:会议列表
- 每个会议包含 start、end、room
- start 和 end 使用分钟数表示,例如 9:30 写成 570
- start < end

这比"一些会议时间"清楚得多。

注意三件事:

  1. 单位:分钟、秒、元、人数、公里,必须明确。
  2. 范围:最多多少条记录?数值最大是多少?
  3. 合法性:如果 start >= end,是拒绝输入,还是自动修正?

很多 bug 看起来是实现错误,实际上是输入没有定义清楚。


硬约束和目标

建模时最重要的区分之一是:

哪些条件必须满足?哪些只是越好越好?

必须满足的条件叫硬约束。例如:

  • 总价格不能超过 900 元
  • 每个人必须有一份午餐
  • 素食同学必须拿到素食套餐
  • 不能选择库存为 0 的套餐

越好越好的标准叫目标。例如:

  • 总满意度尽量高
  • 总价格尽量低
  • 套餐种类尽量少,方便发放
  • 剩余预算尽量多

把目标误写成硬约束,会让问题变得过度严格;把硬约束误写成目标,会让算法产出看似高分但不可接受的方案。

例如:"尽量便宜"通常是目标;"不能超过预算"是硬约束。一个超过预算但便宜程度排名很高的方案,仍然不能接受。


例子:班级订午餐

现在把开头的模糊需求改成建模卡。

text
任务:
- 为 30 名学生订午餐。

输入:
- 套餐列表 foods
- 每个套餐包含 price、stock、is_vegetarian、score
- price 是单价,单位为元
- stock 是最多可订份数
- score 是学生问卷得到的满意度估计,范围 1 到 5

输出:
- 每个套餐订多少份

硬约束:
- 总份数必须等于 30
- 总价格不能超过 900
- 至少 5 份必须是素食套餐
- 每个套餐订购份数不能超过库存
- 每个订购份数都必须是非负整数

目标:
- 在满足硬约束的方案中,让总满意度尽量高
- 如果总满意度相同,选择总价格更低的方案

规模:
- 套餐种类通常不超过 20

边界:
- 如果没有任何合法方案,返回"无法满足约束",而不是随便给一个近似方案

这时,问题已经变得可审查。

如果有人给出一个方案,你可以逐条检查它是否合法。如果有多个合法方案,你也知道应该如何比较。


选择表示方式

建模不仅要定义目标,还要选择怎样表示现实对象。

常见表示方式包括:

现实结构可计算表示适合表达什么
一批对象列表遍历、筛选、统计
时间段[start, end] 区间是否重叠、先后顺序
对象之间的关系点和连接组成的图谁和谁相连、谁依赖谁
候选方案每个候选是否被选择从一组选项中取一部分
分配关系二维表谁分到什么资源
分层结构父子关系表组织层级、目录结构

这里的"图"不是图片,而是一种关系模型:点表示对象,连接表示对象之间的关系。比如学生之间是否同组、任务之间是否有先后依赖,都可以用点和连接表达。

表示方式会影响后面的算法方向。更重要的是,它会影响你能不能把现实约束表达出来。

如果一个约束在你的表示里很难表达,可能不是算法还不够聪明,而是模型选错了。


模型也需要验证

前面一节讲过验证代码正确性。建模也需要验证。

检查模型时,问两个方向:

  1. 模型接受的方案,现实中真的能接受吗?
  2. 现实中可接受的方案,模型会不会错误地排除?

还是午餐例子。

如果模型只要求"总价不超过 900",那么它可能接受 30 份同一种非素食套餐。这个方案满足预算,却不能满足素食约束。模型接受了现实中不能接受的方案。

如果模型要求"每种套餐至少订 1 份",但现实并没有这个要求,那么它可能排除一个本来很好的方案。模型错误地拒绝了现实中可接受的方案。

这两类错误都很常见:

  • 模型太松:产生不合法方案
  • 模型太紧:排除好方案

建模不是一次写完就结束。它需要用小例子、边界情况和反例不断校准。


隐式约束

现实任务里,最危险的约束常常没有写出来。

例如班级订午餐,需求里可能没有写:

  • 有同学过敏,某些食材不能出现
  • 午餐必须在 12:00 前送到
  • 商家单个套餐至少 3 份起订
  • 老师希望种类不要太多,方便发放

这些约束如果不进入模型,代码会给出一个"数学上很好、现实中很糟"的答案。

所以建模时要主动追问:

text
这个结果拿到现实中,会被谁拒绝?
拒绝理由是什么?
有没有没有写出来但必须遵守的规则?
有没有看似越多越好、其实超过某个点就变差的指标?

工具可以帮你列澄清问题,但通常无法自行知道现实中谁会拒绝这个结果。这个判断来自业务、用户、物理世界和责任边界。


给工具之前,先给模型

当你需要工具帮忙实现时,不要只给一句需求:

帮我写一个订午餐的算法。

更好的输入是建模卡:

text
请先审查下面的建模是否完整,不要直接写代码。

任务:为 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 位同学需要素食,尽量让大家满意。

要求写出:

  1. 输入数据有哪些字段?
  2. 输出是什么?
  3. 哪些是硬约束?
  4. 目标是什么?
  5. 至少两个边界情况是什么?

练习 2:同一句需求,不同模型

需求:

帮我安排今天的学习任务。

请给出两种不同建模:

  1. 输出是一张按时间排列的计划表。
  2. 输出是今天应该完成的任务集合。

分别说明两种模型的输入、输出、硬约束和目标有什么不同。


练习 3:找建模错误

有人把午餐问题建模成:

text
目标:选择平均满意度最高的套餐,订 30 份。

请指出这个模型至少漏掉了哪些现实条件。然后给出一个小例子,说明这个模型会产生现实中不能接受的方案。


练习 4:让工具先审查模型

任选一个你熟悉的编程任务,先写建模卡,再写一段提示词,让工具只做三件事:

  1. 找出模型中的歧义。
  2. 找出缺失的边界情况。
  3. 找出硬约束和目标可能混淆的地方。

不要让它先写代码。


课后测验

📝 课后测验

得分:0 / 0

上一节:1.4 复杂度判断

下一节:1.6 综合练习

新时代的算法课程