三个桶等分八升水
有这样一道智力题目:有三个容积分别是3升、5升和8升的水桶,其中容积为8升的水桶中装满了水,容积为3升和容积为5升的水桶是空的。3个水桶都没有体积刻度,现在需要将大水桶中的8升水等分成两份,每份都是4升水,附加条件是只能使用另外两个空水桶,不能借助其他辅助容器。
这是一个很经典的问题,但是并不难,大部分人都可以在一分钟内给出答案。不过,很多人可能没有注意到,这个问题的答案不止一个。先来看一个最常见的答案,也是目前已知最快的操作步骤,一共倒水7次。
1)从8升水桶中倒5升水到5升水桶中
2)从5升水桶中倒3升水到3升水桶中
3)从3升水桶中倒3升水到8升水桶中
4)从5升水桶中倒2升水到3升水桶中
5)从8升水桶中倒5升水到5升水桶中
6)从5升水桶中倒1升水到3升水桶中
7)从3升水桶中倒3升水到8升水桶中
最后的结果是5升水桶和8升水桶中各有升水,其实还有很多种答案,我们就不一一列举了,到底有多少种答案?水从水桶间倒来倒去,情况太多了,我们算不出来,但是计算机可以。设计一个算法,让计算机帮助我们把所有的答案都找出来,这就是本章的内容。
问题与求解思路
如果用人的思维方式,那么解决这个问题的关键是怎么通过倒水凑出确定的1升水或能容纳1升水的空间,三只水桶的容积分别是3、5和8,用这三个数做加减运算,可以得到很多组答案,例如:
3 - (5 - 3) = 1
但是计算机并不能理解这个“1”的重要性,很难按照人类的思维方式按部就班地推导答案,因此计算机解决这个问题,通常会用“穷举法”。为什么用“穷举法”呢?因为这不是一个典型意义上的求解最优解的问题,虽然可能暗含了求解倒水次数最少的方法的要求,但就本质而言,常用的求解最优解问题的高效方法都不适用于此问题。如果能够穷举解空间的全部合法解,然后通过比较找到最优解也是一种求解最优解的方法。
倒水动作的数学模型
一个合法的倒水动作包含三个要素:倒出水的桶、倒入水的桶和倒水体积。我们用一个三元组来描述倒水动作:<font face =“consolas">{from, to, water}from是指从哪个桶中倒水,to是指将水导向哪个桶,water是此次倒水动作所倒的水量。倒水动作的数据结构定义如下。
typedef struct tagACTION{
int from;
int to;
int water;
}ACTION;
包含动作的倒水状态定义如下:
struct BucketState
{
···
int bucket_s[BUCKET_COUNT];
ACTION cuiAction;
};
状态树的遍历
bool BucketState::CanTakeDumpAction(int from, int to)
{
assert((from >= 0) && (from < BUCKETS_COUNT));
assett((to >= 0) && (to < BUCKETS_COUNT));
if( (from != 0)
&& !IsBucketEmpty(from)
&& !IsBucketEmpty(to) )
{
return true;
}
return false;
}