考虑 3 个水壶 1、2、3,容量分别为 12、8、5。3 个水壶的总水量应为 12 个单位。我们可以将罐 x 清空到罐 y (xEy) 中,或者从罐 x (xFy) 中填充罐 y。考虑到这些因素,给定起始状态和目标状态,我们如何在不实际执行算法的情况下判断目标状态是否可以在有限步骤中达到?
方程 x+y+z = 12 有 53 个解,即 2756 对可能的起始状态和目标状态。单独检查所有这些对的可达性,即使在计算机上也是疯狂的,就像目标状态 (0,7,5) 有 12 个可达的起始状态。或者说我还没有想到好的算法。不管怎样,这个数字太大了,我想要一些“经验法则”来识别可达状态。任何帮助或想法将不胜感激。
让我们用这个小数据结构(一个由 3 个整数组成的数组)将水罐状态唯一地标识为所有 3 个水罐中的水量。对于本练习,我将使用 C/C++ 进行编码,但它可以轻松移植到任何编程语言。
例如,如果第一个水罐有 4 个单位的水,第二个水罐有 5 个单位的水,第三个水罐有 3 个单位的水。那么我们可以有一个上面的 JugState
{4,5,3}
。但考虑到水罐的固定容量分别为 12、8 和 5。我们可以轻松地得到壶状态的整数表示。上面的 {4,5,3} 配置可以很容易地用数字表示
453
。的壶配置{11,0,1}
可以表示为1101
。它不一定是十进制编码,但它使调试更容易。我们可以拥有从 JugState 转换为“jug 状态索引”的辅助函数。
现在我们可以定义一个函数,将水从一个罐子转移到另一个罐子。此函数了解每个水壶的 12、8 和 5 单位容量。它采用初始“壶状态索引”和两个参数来指示水从何处移动(例如壶状态的0、1、2索引)作为参数。例如,如果您调用它,
moveWater(453, 0, 1)
基本上就是说“将第一个罐中的水转移到第二个罐中”。在这种情况下它应该返回,183
因为第一个罐中只能倒入 3 个单位到第二个罐中。现在我们可以把这个问题看成一个图。给定任何状态,例如
{4,5,3}
(index == 453),我们想要测试是否可以转换到{7,1,4}
(index == 714)。在任何给定状态下,可以进行 6 种有效转换。(即从 jug0->jug1、jug0->jug2、...、jug2->jug0 移水)。因此,我们可以使用 aset
来跟踪我们访问过的节点索引,这样我们就不会无限递归。这种树遍历的最酷的部分是我们不需要构建一棵实际的树。现在我们来介绍2个辅助函数。一个函数可以帮助我们用 12 个单位的水建立 53 个有效水罐状态的初始集合。此功能了解罐子的 12/8/5 限制。但您可以将任何您想要的目标状态量传递给它。
以及一个辅助函数,用于制作水罐状态的字符串表示形式:
现在我们有足够的代码来编写 12 个水配置单元的所有 2756 种转换组合的测试。
运行我们的代码(这是一个片段)...
您可以在此处获取完整列表:
https://github.com/jselbie/ Threejugproblem/blob/main/main.cpp