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

问题描述:有标注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);
 
              Stackstack= new Stack();// 创建栈,将初始顶点入栈
              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]


你可能感兴趣的:(算法)