三杯水问题 算法分析、设计与实现(Java)

来源:互联网 发布:linux服务器哪里的好 编辑:程序博客网 时间:2024/06/05 17:33

问题描述:有标注A、B、C的三个杯子,A、B、C杯的最大容量分别为8L、5L、3L,现有A杯中有水8L,请通过算法获取4L+4L水。

 

策略:

(1)      选择实现的语言(这里以java为例)

(2)      如果不知道算法该怎设计,可以先将必要的实体、边界找出,并抽象成类。(缩小所需考虑的内容)

(3)      按“已知算法->递归->分治->贪心->回溯法->分支限界法->动态规划->算法设计”来分析问题,判断是否符合某种算法。一般来说,符合某种算法的问题所需的内么容都是差不多的,例如:符合动态规划,可能就要考虑通式、矩阵、O(n^2)

(4)      按相应的算法分析步骤分析算法并初步实现

(5)      优化

(6)      专门针对时间复杂度、空间复杂度进行优化(如:降低空间复杂度,可将递归改为栈。在本篇中将直接以优化后的算法为准进行分析,即利用栈)

 

目录

0,分析输入输出...1

1,分析算法...2

2,分析实体,及其属性、行为...2

3,实现实体...4

4,分析操作...6

5,实现操作...6

6,测试...9

 

 

 

0,分析输入输出

(1)   输入:最大容量、初始容量、及目标容量,如:

85 3

80 0

44 0

(2)输出:输出A、B、C三个杯中水量的变化,如:

[8,0,0]

[3,5,0]

[0,5,3]

……

[4,4,0]

 

 

1,分析算法

每一次倒水都是一次数值的切换:[x1,y1,z1]->[x2,y2,z2]

上述的输出中没有重复的操作,终态与前一个状态刚好是一次倒水操作,这种操作可以看成是一个方法pour。由于倒水并不总会得到结果,所以要不断地尝试不同的倒水方法(从A杯倒到B杯、从A杯倒到C杯等)。

 

关键词:方法、状态、不断尝试。

按文章开篇的策略可知,这里面符合条件的有回溯法、分支限界法、动态规划。

 

紧接着,可以由输出的方式可知,只需输出一种结果就可以了,没有计算最优值、构造最优值的必要,所以可以考虑使用回溯法。(然后改为使用栈)

2,分析实体,及其属性、行为

(1)初步分析:数据只有三个[x,y,z],每一次倒水可以视为一次状态的改变[x1,y1,z1]è[x2,y2,z2],故将状态抽象为类Cup:

属性:A、B、C三个杯中的水量

行为:倒水动作pour、显示水量display

package com.lance.algorithm.model;/** * 倒水杯水状态类:只负责倒水,以及状态的保存 */public class Cup {       // 杯水        /**        * 倒水操作        */       pour() {}        /**        * 显示各水杯水量        */       display() {}}


 

 

(2)进一步分析:杯水的状态的变化前后可视为一种新的初始状态,如:

从[8,0,0]到[4,4,0] 与 从[3,5,0]到[4,4,0]等价。故可以使用递归、或者栈。在此使用栈java.util.Stack(也可以自己写一个)

 

(3)考虑所有的状态切换操作,如:

从A杯往B杯倒,从A杯往C杯倒,……,共有6种,对应6种操作,故可以用常量类Action来保存切换操作标志。

package com.lance.algorithm.constant;/** * 倒水操作类型接口 */public interfaceAction {         /**          * 从A杯往B杯倒          */         public staticfinal intAToB = 0;         /**          * 从A杯往C杯倒          */         public staticfinal intAToC = 1;         /**          * 从B杯往A杯倒          */         public staticfinal intBToA = 2;         /**          * 从B杯往C杯倒          */         public staticfinal intBToC = 3;         /**          * 从C杯往A杯倒          */         public staticfinal intCToA = 4;         /**          * 从C杯往B杯倒          */         public staticfinal intCToB = 5;}


 

(4)假设使用栈,每一次循环都会有一个新的action,action从0开始,所以会有:[8,0,0]=>[3,5,0], [3,5,0]=> [8,0,0]的死循环,故使用三维boolean型标志位isArrive来标识是否已经出现过该情况。若出现过则不再操作,直接返回。(即弹出栈顶元素)

3,实现实体

package com.lance.algorithm.model;import java.util.Arrays;/** * 倒水杯水状态类:只负责倒水,以及状态的保存 */public class Cup{       /**        * 杯状态公共属性:可被所有Cup对象使用        */       private staticint[] max= { 0, 0, 0 }; // 最大容量       public staticboolean[][][] isArrive = newboolean[1][1][1]; // 已达:true已达,false未达        /**        * 杯状态私有属性        */       private int[]cups; // 杯水       private intaction = 0;// 操作类型:参考Action接口        public Cup(int[]cups) {              this.cups =cups;       }        /**        * 倒水操作        *        * @param fromIndex 倒水杯下标        * @param toIndex 被倒水杯下标        * @return返回下一个状态。若不存在,则返回null        */       public Cup pour(intfromIndex, inttoIndex) {              if (fromIndex< 0 || fromIndex > 3 || toIndex < 0 || toIndex> 3) {                     throw newArrayIndexOutOfBoundsException();              }               if (fromIndex== toIndex) {// 倒水杯与被倒水杯相同                     return this;              }               isArrive[cups[0]][cups[1]][cups[2]]= true; //设置已达,并更改状态位              action++;               // 倒水              int[] tempCups= cups.clone();              if (tempCups[fromIndex] + tempCups[toIndex] > max[toIndex]) {                     tempCups[fromIndex]= tempCups[fromIndex]+ tempCups[toIndex]                                   -max[toIndex];                     tempCups[toIndex]= max[toIndex];              }else {                     tempCups[toIndex]= tempCups[fromIndex]+ tempCups[toIndex];                     tempCups[fromIndex]= 0;              }               // 判断是否已遍历              if (isArrive[tempCups[0]][tempCups[1]][tempCups[2]]) { // 已遍历                     return null;              }               return newCup(tempCups);       }        public int[]getCups() {              return cups;       }        public intgetAction() {              return action;       }        public staticvoid setMax(int[] max){              Cup.max = max;       }        public staticvoid setIsArrive(boolean[][][] isArrive){              Cup.isArrive = isArrive;       }        /**        * 显示各水杯水量        */       public voiddisplay() {              System.out.println(Arrays.toString(cups));       }}


 

4,分析操作

①  将初始状态入栈

②  取得栈顶结点cup

③  判断:若该结点是目标结点,则转至⑥;否则转至④

④  根据杯状态cup的action,选择相应的倒水操作。若有新的杯状态返回,则将新状态入栈;若没有新的杯状态则不做操作;若没有对应接口Action中的操作,则将cup出栈。

⑤  若栈为空,则退出循环,否则转至②

⑥  若栈为空,则显示“无结果”;否则,依次显示栈元素

⑦  退出

 

5,实现操作

package com.lance.algorithm.test; import java.util.Scanner;import java.util.Stack;import com.lance.algorithm.constant.Action;import com.lance.algorithm.model.Cup;/** * @description倒水测试类 * @author Lance */public classPourWater {       public staticvoid main(String[] args) {              Scannerscan = newScanner(System.in);               System.out.println("请分别输入A杯、B杯、C杯的最大容量:");              int maxA = scan.nextInt();              int maxB = scan.nextInt();              int maxC = scan.nextInt();               System.out.println("请分别输入A杯、B杯、C杯的初始容量:");              int initA =scan.nextInt();              int initB =scan.nextInt();              int initC =scan.nextInt();               System.out.println("请分别输入A杯、B杯、C杯的目标容量:");              int targetA= scan.nextInt();              int targetB= scan.nextInt();              int targetC= scan.nextInt();               // 判断所输入的信息是否合法              if (!isLegal(maxA,maxB, maxC,initA, initB,initC, targetA,targetB,                            targetC)) {                     scan.close();                     return;              }               int[] cup_max= { maxA, maxB,maxC };// 设置初始条件              int[] init= new int[]{ initA, initB,initC };              int[] target= { targetA, targetB,targetC };              boolean[][][] isArri= new boolean[maxA + 1][maxB+ 1][maxC + 1];              Cup.setMax(cup_max);              Cup.setIsArrive(isArri);               Stack<Cup>stack= new Stack<Cup>();// 创建栈,将初始顶点入栈              Cuptop = newCup(init);              stack.push(top);               while (!stack.isEmpty()) {                     Cupcup = stack.peek();//取出顶部状态                      int[] cups= cup.getCups();                     if (cups[0]== target[0] && cups[1] == target[1]                                   &&cups[2] == target[2]){// 若到达目标状态,退出                            break;                     }                      Cuptemp = null;                     switch (cup.getAction()){// 判断条件:若已经到达终态,则将栈顶出栈;否则执行相应的操作                     case Action.AToB:temp = cup.pour(0,1); break;                     case Action.AToC:temp = cup.pour(0,2); break;                     case Action.BToA:temp = cup.pour(1,0); break;                     case Action.BToC:temp = cup.pour(1,2); break;                     case Action.CToA:temp = cup.pour(2,0); break;                     case Action.CToB:temp = cup.pour(2,1); break;                     default: stack.pop();                     }                      // 若能成功倒水,则将新节点入栈                     if (temp !=null) {                            stack.push(temp);                     }              }               if (stack.isEmpty()) {                     System.out.println("无结果");              }               // 显示操作流程              for (inti = 0; i< stack.size();i++) {                     stack.get(i).display();              }               scan.close();       }        /**        * 检查输入的杯水量是否合法        * @param maxA A杯最大容量        * @param maxB B杯最大容量        * @param maxC C杯最大容量        * @param initA A杯初始容量        * @param initB B杯初始容量        * @param initC C杯初始容量        * @param targetA A杯目标容量        * @param targetB B杯目标容量        * @param targetC C杯目标容量        * @return若合法则返回true,否则返回false        */       public staticboolean isLegal(int maxA, int maxB, int maxC, int initA,                     int initB, int initC, int targetA,int targetB,int targetC){              // 筛选条件              if (maxA< initA || maxB< initB || maxC< initC || maxA< targetA                            ||maxB < targetB|| maxC < targetC){                     System.out.println("初始容量或者目标容量超出最大容量");                     return false;              }               if ((initA+ initB + initC)!= (targetA + targetB+ targetC)) {                     System.out.println("初始杯水量与目标杯水量不匹配");                     return false;              }               if (maxA< 0 || maxB < 0 || maxC < 0 || initA< 0 || initB < 0                            ||initC < 0 || targetA< 0 || targetB < 0 || targetC < 0) {                     System.out.println("杯水量不能小于0");                     return false;              }               if ((initA+ initB + initC)== 0) {                     System.out.println("请输入初始杯水量");                     return false;              }               return true;       }}


 

6,测试

测试①

输入:

85 3

80 0

44 0

 

输出:

[8, 0, 0]

[3, 5, 0]

[0, 5, 3]

[5, 0, 3]

[5, 3, 0]

[2, 3, 3]

[2, 5, 1]

[7, 0, 1]

[7, 1, 0]

[4, 1, 3]

[4, 4, 0]

 

测试②

输入:

106 4

100 0

55 0

 

输出:

无结果

 

测试③

输入:

107 3

100 0

55 0

 

输出:

[10, 0, 0]

[3, 7, 0]

[0, 7, 3]

[7, 0, 3]

[7, 3, 0]

[4, 3, 3]

[4, 6, 0]

[1, 6, 3]

[1, 7, 2]

[8, 0, 2]

[8, 2, 0]

[5, 2, 3]

[5, 5, 0]


0 0