问题描述:有标注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
(1) 输入:最大容量、初始容量、及目标容量,如:
85 3
80 0
44 0
(2)输出:输出A、B、C三个杯中水量的变化,如:
[8,0,0]
[3,5,0]
[0,5,3]
……
[4,4,0]
每一次倒水都是一次数值的切换:[x1,y1,z1]->[x2,y2,z2]
上述的输出中没有重复的操作,终态与前一个状态刚好是一次倒水操作,这种操作可以看成是一个方法pour。由于倒水并不总会得到结果,所以要不断地尝试不同的倒水方法(从A杯倒到B杯、从A杯倒到C杯等)。
关键词:方法、状态、不断尝试。
按文章开篇的策略可知,这里面符合条件的有回溯法、分支限界法、动态规划。
紧接着,可以由输出的方式可知,只需输出一种结果就可以了,没有计算最优值、构造最优值的必要,所以可以考虑使用回溯法。(然后改为使用栈)
(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来标识是否已经出现过该情况。若出现过则不再操作,直接返回。(即弹出栈顶元素)
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));
}
}
① 将初始状态入栈
② 取得栈顶结点cup
③ 判断:若该结点是目标结点,则转至⑥;否则转至④
④ 根据杯状态cup的action,选择相应的倒水操作。若有新的杯状态返回,则将新状态入栈;若没有新的杯状态则不做操作;若没有对应接口Action中的操作,则将cup出栈。
⑤ 若栈为空,则退出循环,否则转至②
⑥ 若栈为空,则显示“无结果”;否则,依次显示栈元素
⑦ 退出
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;
}
}
测试①
输入:
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]