一道有趣的笔试题( 水杯量水问题 )

来源:互联网 发布:网络写手的收入 编辑:程序博客网 时间:2024/04/29 09:26

  若干年前,刚进入编程行业不久,得到一家公司的笔试机会.其中一题印象深刻:给两个空量杯,一个5ml,一个3ml,加水后,要通过量杯互倒,得到4ml水,求算法.

  之前做过一个人机对战的游戏,接触到决策树.看到这道题时,第一感觉应该也是可以用树形数据结构来解决的.虽然它不属于博弈类的问题。

  最初考虑,以量杯作为树的结点,在树中找出一条路径( 分支 ),使得路径终结点的当前水量为4ml,那么问题就得到解决。但仔细考虑,发现这思路有些问题,比如:5ml量杯的水倒入3ml量杯后,再向5ml量杯中倒水,在这棵树就无法表示。

  再仔细分析一下问题,所求算法的输出是一个操作流程。操作流程的每一步都是一个操作。也就是说,在这个问题中,要提练的数据是"操作",而不是"量杯".

  这个问题想清楚后,就好办了.用树是对的,只是结点应该是操作而不是量杯.

  所有的操作总共有六种:

        5->3     表5号量杯倒入3号量杯

  3->5     表3号量杯倒入5号量杯

  5->E     表将5号量杯的水倒空

  3->E     表将3号量杯的水倒空

  F->5     表将5号量杯倒满

  F->3     表将3号量杯倒满


  在这棵树中,每一个结点,理论上有以上六个操作作为子结点.求解的过程就是在这棵树中找到一条路径.使得路径终结点处,5号量杯中的水为4ml.

  再深入考虑:

1. 由于一开始量杯为空,所以只能将F->5或F->3作为决策树的根结点.

2. 理论上,两个量杯互倒的操作可以无限进行下去( 比如把 5->3和3->5的操作作为对方的子结点), 因此用深度优先,极有可能导致无限搜索;另外要得到4ml水这一结果,可以有多种路径,而所求的往往要求是最优解,所以在树上的搜索,只能采用广度优先的方法.

3. 在量杯互倒的操作中,有一些隐含的约束条件,有助于更快地得到解,这些隐含的约束条件,讨论如下:

 父结点为 5->3 时,子结点不能为3->5.  ( 同样 3->5 亦然.)

 父结点为 5==E 时( 5号量杯为空 ),子结点不能为 5->3 或 5->E.   ( 同样 3==E 亦然.)

    父结点为 5==F 时( 5号量杯为满 ),子结点不能为 F->5 或 3->5.    ( 同样 3==F 亦然.)

4. 在树的每一个操作后( 每一个结点 ),应该标记两个量杯的当前状态( E或F ).并且每一个操作后,记录5号量杯当前值.

5. 树搜索算法,采用递归函数.当5号量杯当前值为4时,说明操作成功,返回true.回溯法,递归函数需要返回值.


 至此,问题得到解决.


总结以上的思考过程可知:

 在面对实际问题时,有时如何提练数据,或者说把什么作为待处理的数据,成为解决问题的关键.一般先明确输出结果,然后,从要求解的结果,或者说输出结果倒过来分析,会比较有效.

 而数据结构,也完全是根据如何方便要解决的问题,根据算法的需要来选择的.在学习数据结构时,为了方便理解,待处理数据在逻辑结构上本身就与所要讲解的数据结构相匹配.而在使用时,数据结构有时并不用来反映数据直观的客观逻辑结构,而是为了更好的求解问题,为了满足算法需要的一种选择。( 当然也可以说,这样选择出来的数据结构,反映了数据间的另一种关系.)

    再比如,对一列数据的排序操作,其输入与输出的数据都是线性逻辑结构。对于选择排序和插入排序来说,通过构建数组( 线性逻辑结构 )就可以实现;而构建二叉树也可以实现对无序数列的排序操作。( 其实,本文讨论的量杯倒水问题,输出结果也是一个线性逻辑结构。)

    也就是说,输入输出无法决定运算中究竟要选择哪种数据结构;数据自身客观的逻辑结构,也无法左右运算中要选择哪种数据结构;只有算法本身的需要才能决定要选择哪种数据结构。


另外:

        人机对弈的问题,一般会把棋盘上当前局面作为一个结点。与此类似,如果把每一次倒水结束后,两个杯子中所盛水量的状态作为一个结点,应该也可以求得问题的解。按这种方法得到的算法又会是怎样的呢?