持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情
结构问题的最优解
假设我们决定近似值不够好,即,我们想要这个问题的最佳解决方案。这样的解决方案被称为最优的,这并不奇怪,因为我们正在解决一个优化问题。碰巧的是,我们的窃贼面临的问题是一个经典优化问题的实例,称为0/1结构问题。0/1 结构问题可以按如下方式形式化:
·每个项目都由一对表示,<值,重量>。
结构可以容纳总重量不超过w的物品。
长度为 n 的向量 I 表示可用项的集合。
矢量的每个元素都是一个项目。
长度为 n 的向量 V 用于指示每个物品是否被窃贼拿走。如果 V[i] = 1,则取项目 I[i]。如果 V[i] = 0,则不采用项目 I[i]。
找到一个最大化的V
ΣV[i]*[i].受约束
ΣV[]*I[i].重量≤w
让我们看看如果我们尝试以一种直接的方式实现问题的这个公式,会发生什么:
- 枚举所有可能的项目组合。也就是说,生成项目集的所有子集.9°这称为幂集,并在第11章中讨论过。
2.删除所有重量超过允许重量的组合。
- 从其余组合中,选择值最大的任何一个。
这种方法肯定会找到最佳答案。但是,如果原始项目集很大,则需要很长时间才能运行,因为正如我们在第 11.3.6 节中看到的那样,子集的数量随着项目数量的增加而增长得非常快。
图 14-5 包含这种解决 0/1 结构问题的暴力方法的直接实现。它使用 Eigure 14-2、Eigure 14-3、Eigure 14-4 中定义的类和函数,以及图 11-6 中定义的函数gen_powerset。
此实现的复杂性是阶数 θ(n*2“),其中 n 是项的长度。函数gen_powerset返回列表列表项目数。此列表的长度为 2“,其中最长的列表的长度为 n。因此,choose_best中的外部循环将按顺序执行 θ(2“) 次,并且执行内部循环的次数以 n 为界。
可以应用许多小的优化来加速此程序。例如,gen_powerset可能具有标头 def gen_powerset(项、约束、get_val get_weight)
并仅返回满足权重约束的那些组合。或者,choose_best可以在超过权重约束后立即退出内循环。虽然这些类型的优化通常值得做,但它们并不能解决根本问题。choose_best的复杂性仍然是θ(n*2“),其中n是项目的长度,因此choose_best在项目很大时仍然需要很长时间才能运行。
从理论上讲,这个问题是无望的。O/1结构问题在项目数量上本质上是指数级的。然而,从实际意义上讲,这个问题远非毫无希望,正如我们将在第15.2节中讨论的那样。
运行test_best时,它打印
请注意,此解决方案查找的总值高于贪婪算法找到的任何解决方案的项目组合。贪婪算法的本质是在每一步都做出最好的(由某些指标定义)本地选择。它做出本地最佳选择。但是,如本示例所示,一系列局部最优决策并不总是导致全局最优的解决方案。
尽管他们并不总是找到最佳解决方案,但贪婪算法在实践中经常被使用。它们通常比保证找到最佳解决方案的算法更容易实现和运行效率更高。正如伊万·博斯基曾经说过的那样,“我认为贪婪是健康的。你可以很贪婪,但仍然对自己感觉良好。
对于结构问题的变体,称为分数(或连续)结构问题,贪婪算法保证找到最佳解决方案。由于此变体中的项目是无限可分割的,因此尽可能多地获取剩余价值重量比最高的项目总是有意义的。例如,假设我们的窃贼在房子里只发现了三件有价值的东西:一袋金粉,一袋银粉和一袋葡萄干。在这种情况下,按密度划分的贪婪算法将始终找到最佳解决方案。