题目要求
问题描述:三只牛三只虎过河,船最多只能容纳两只动物,且船在往返途中不能为空。在任一岸边,若牛的数量少于虎的数量,则牛就会被老虎吃掉。为了使动物全部过河且使无损失,请制定合理的渡河方案。
解决方案
这也是一个经典的渡河问题了,由此衍化出的版本有商人仆人(随从)过河,农夫妖怪过河,传教士野人过河...除了角色有变化,内容本质上是一样的。
假设原来的动物和船都在A岸,现在想渡河到对面的B岸。考虑牛虎数量和船的位置,可以将本题中的所有可能出现的情形描述为静态属性和动态属性。静态属性就是船停靠在A岸或者B岸时,A岸牛、虎的数量(A岸数目一定时,B岸也一定,所以只需考虑一边就行),动态属性就是船在运行中时,A岸或者B岸牛、虎的数量。
进一步考虑,只要知道了相邻的两个静态属性,也就知道了发生在其间的动态属性。比如开始船在A岸,A岸有牛、虎各三只,下一个状态为船在B岸,A岸有牛虎各两只,那它们之间的动态属性一定是船由A到B,且运送了一牛一虎过去(不考虑重复的状况)。所以在这里,我们只需要确定每一步对应的静态属性,在编程中,将其描述为状态。对于岸边的牛或者虎的数量,只有0到3这四个取值,对于船的停靠位置,只有A岸和B岸两种情形,这样一来,就有了4*4*2=32种状态。在这32种状态中,有些状态会引起牛吃虎,这必须被排除掉。
到这里,制定渡河方案的问题就转换为在这32个状态中探寻合理“状态路径”的问题。我们从初始状态——A岸牛虎各三只,船在A岸这个状态出发,不断判断遇到的下一个状态是否合理。如果下一个状态合理,就将其加入到“状态路径”当中,并留下访问标记(防止重复添加,形成环路),否则,跳过此状态。在不断向前探寻的过程中,如果遇到一个标记为已访问的状态,说明该状态已加入路径,需要探寻下一种可能。如果遇到了结束状态——A岸牛虎为零,船在B岸,则说明找到了一条完整的“状态路径”,这时需要打印这条路径,并清除当前状态的访问标记,且退出上一个状态,继续寻找下一种可能性。
在具体编程中,可以利用栈来存储初始状态到结束状态的所有状态,目的是为了在遇到不符合题意的状态时,可以原路返回,便于回溯。由于在推进过程中,每个状态都是基于对岸边牛虎数量和船的停靠位置的判断,所以,考虑用递归会更容易。但值得注意的是,这里的递归并不像斐波那契数列那样不断衍生出先决条件,而是更类似于“尾递归”,边递归,边推进,即递归的外壳,迭代的内心。
源码示例
结果展示
小结
一个经典的过河问题,本质就是在各状态之间寻求符合题意的状态路径,在有多条路径的情形下,需要使用栈保存之前的各个状态,以便在遇到不符合条件的状态时可以回溯。这种回溯思想在迷宫寻路问题和八皇后问题中都有体现。可以说是一母同胞,属于同一类型的问题。