9 个景点、每天 8 小时、总门票 200 元,贪心排进 6 个,总用时 22.0 小时、 门票 130 元、总评分 52.1。 三天各剩 1.0/1.0/0.0 小时,剩下三个最短也要 2.5 小时,一个都塞不进去。
上一节做单目标推荐,这一节加上约束。旅游助手是「带约束的组合优化」,比推荐难一档。
一、从推荐变成排程,多了什么
推荐是「挑最好的几个」,排程是「挑一组,还要排得下」。 多出来的东西有三个:容量约束(每天只有 8 小时)、预算约束(总共 200 元)、 以及不可分割(一个景点要么去要么不去,不能去半个)。 这三条一加,问题就从「排序」变成「装箱」——NP 难问题。景点一多,穷举所有组合就不现实了。 Demo 用的是最朴素的贪心:按「评分除以耗时」排序,从高到低往每天里塞。 简单、快、能解释,但不保证最优。
二、贪心的结果
9 个景点塞进 6 个:第 1 天故宫加国家博物馆(7.0 小时、60 元),第 2 天颐和园加南锣鼓巷(7.0 小时、 30 元),第 3 天长城加什刹海(8.0 小时、40 元)。 总用时 22.0 小时,门票 130 元,总评分 52.1。 落选的是天坛(2.5 小时)、798 艺术区(3.5 小时)、香山(4.5 小时)。 为什么塞不进去?三天各剩 1.0、1.0、0.0 小时,而最短的天坛也要 2.5 小时。空隙太碎,装不下东西。
三、碎片问题:贪心的固有缺陷
这就是贪心的典型失败模式。 它每一步都做局部最优选择,结果把时间切成一堆小空隙,后面的大件一个都放不进去。 如果换成「先塞耗时的」(长作业优先),第 1 天塞长城加香山(10.5 小时)就超了,但也可能排进 7 个。 真实排程系统不这么干。它们用整数规划(保证最优)或者启发式搜索(模拟退火、遗传算法,近似最优)。 还有一点这个 Demo 没算:地点之间的距离。故宫到长城要一个多小时车程,这部分既占时间又花钱。 加上之后,问题复杂度又上一个台阶。
四、「AI 助手」的真实结构
市面上那些「AI 旅游助手」,拆开看基本都是三段: 1. 理解需求:从自然语言里抽出天数、预算、偏好、同行人(比如带小孩)。这一段用大模型。 2. 求解:把抽出来的条件喂给排程算法(贪心或整数规划)。这一段不用大模型。 3. 组织语言:把结果写成一段人话,再补充注意事项。这一段又回到大模型。 中间那一段是关键:约束求解交给算法,不要交给大模型。 让大模型直接排,它可能排出一个「一天去三个相距 50 公里的景点」的方案,而且说得头头是道。 这个分工在别的场景也成立:算数、排期、对账这类有硬约束的活,交给代码;表达和抽取,交给模型。
五、动手 Demo:三天行程:带时间和预算约束的贪心排程
给 9 个景点各标耗时、门票、评分、类型;按「评分除以耗时」从高到低排序; 逐天装填,同时检查当天 8 小时和总预算 200 元两个约束; 最后打出每天安排、总用时、总门票、总评分、落选名单和三天剩余时间。
order = sorted(spots, key=lambda s: -(s[3] / s[1]))
for d in range(days):
t, cost, got = 0.0, 0.0, []
for s in left:
if s[5]: # 已经排过
continue
if t + s[1] <= hours_per_day and cost + s[2] <= budget:
s[5] = True
t += s[1]
cost += s[2]
got.append(s)
实跑输出:
--------------------------------------------------------------------
三天行程:带时间和预算约束的贪心排程
--------------------------------------------------------------------
候选景点 9 个
约束 每天 8.0 小时,三天总门票不超过 200 元
排序依据 评分 ÷ 耗时(每小时能换多少分)
日子 安排 占用小时 门票
---------------------------------------------
第 1 天 故宫、国家博物馆 7.0 60
第 2 天 颐和园、南锣鼓巷 7.0 30
第 3 天 长城八达岭、什刹海 8.0 40
排进去几个 6 / 9
总用时 22.0 小时
总门票 130 元
总评分 52.1
落选的 天坛、798 艺术区、香山
三天各剩 1.0、1.0、0.0 小时。剩下三个景点最短的也要 2.5 小时,一个都塞不进去。
这就是贪心的「碎片问题」:每次只挑当前最划算的,
排到最后会剩一堆装不下东西的小空隙。
真实排程还要加地点之间的距离和时间窗,那就不是贪心能做的了,
得用整数规划或者启发式搜索。
三天安排:故宫加国博、颐和园加南锣鼓巷、长城加什刹海。每天两个景点,节奏不算赶。 总评分 52.1 是六个景点评分之和(9.5+8.8+9.0+7.6+9.2+8.0)。 少了三个景点,评分自然低——但这是约束下的结果。 剩余时间 1.0/1.0/0.0 是关键数字:空隙太碎,剩下三个景点最短的 2.5 小时,一个都塞不进。 门票只用了 130 元,预算 200 还剩 70——说明这个例子里预算不是瓶颈,时间才是。
30 秒实验
- 把每天时长从 8 小时改成 10 小时,看能多排进几个。
- 把预算从 200 改成 100,看哪些景点被挤掉。
- 想一想:为什么「让大模型直接排行程」容易排出不可能的方案?
常见误区
- 「贪心能排满」——本例三天剩 1、1、0 小时,碎得装不下东西。
- 「预算越多排得越多」——本例预算还剩 70,瓶颈是时间。
- 「大模型什么都能排」——约束求解要交给算法,模型只负责理解和表达。
自测三问(点开对答案)
1. 排程比推荐多出哪三类约束? —— 容量约束、预算约束、以及景点不可分割(要么去要么不去)。 2. 本例为什么还有三个景点没排进去? —— 三天剩余时间只有 1.0、1.0、0.0 小时,最短的景点也要 2.5 小时。 3. AI 旅游助手的三段结构是什么? —— 模型理解需求 → 算法求解排程 → 模型组织语言。行程排完,下一个实例回到办公场景:把一张纸质表格变成电子表。
- ☐ (本节完,下一节 5.2.5 实例5:使用AI识别中文表格)