三杯水问题 算法分析、设计与实现(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]
- 三杯水问题 算法分析、设计与实现(Java)
- 第K大数 算法分析、设计与实现(Java)
- 棋盘覆盖 算法分析、设计与实现(Java)
- 汉诺塔问题的算法分析与实现(Java)
- 算法设计与分析--N皇后问题实现程…
- 汽车加油问题 java版 算法设计与分析
- java实现算法设计与分析-最大间隙
- [Java算法分析与设计]顺序循环队列的实现
- [Java算法分析与设计]链式队列的实现
- 算法设计与分析——N后问题(C++实现)
- 【算法分析与设计】快速求幂算法的分析及java实现
- 算法分析与设计基础(1)汉诺塔问题
- 区间相交问题(贪心)-算法设计与分析
- 汽车加油问题(贪心)-算法设计与分析
- 符号三角形问题(回溯)-算法设计与分析
- 学生选择问题(回溯)-算法设计与分析
- 块交换问题算法设计与分析
- 统计数字问题[算法设计与分析]
- 在word中轻松将mathtype公式转换成latex
- 工厂三姐妹
- 网站静态化--谈谈网站静态化
- 在mysql中创建索引,提升获取数据库数据效率
- Spark Sort Based Shuffle内存分析
- 三杯水问题 算法分析、设计与实现(Java)
- jsonp
- 超分辨率技术与插值,图像恢复,图形渲染技术的区别
- Spark UI (基于Yarn) 分析与定制
- [leetcode] 31. Next Permutation 解题报告
- nyoj--55--懒省事的小明(STL优先队列)
- Python 入门建议
- Spark Tungsten in-heap / off-heap 内存管理机制
- JSTL标签,EL表达式,OGNL表达式,struts2标签 汇总