贪心算法-泊松分酒问题
2017-02-20 17:50
148 查看
讲这道题纯粹就是比较好玩,就记录一下.泊松分酒是很著名的一道题,讲的是假设某人有12品脱的啤酒一瓶,想从中倒出六品脱,但是恰巧身边没有6品脱的容器,仅有一个8品脱和一个5品脱的容器,怎样倒才能将啤酒分为两个6品脱呢?
代码:
这个解法找到的其实是最优解,至于为什么呢,其实利用set的方法十分巧妙,结果集set里随着一次次的分酒一次次地扩增,当第一次出现含有两个6的状态的时候,再往前追溯,步骤是最少的!因为这个我们想要的状态是第一次出现.
假如我们每次都打印出all集合,可以知道,当第一次找到含有两个6状态的时候程序并没有结束,因为还没有找到所有的状态.
而后面的状态再进行分酒时,仍有可能产生两个6的状态,但是想要加入set集合的时候就行不通了,所以此程序只输出最早加入的那一个解,并且是最优的.
当然这种算法并不能输出所有的解,如果要得到所有的解,我们可以采用以下算法,这种算法借鉴了图的深度搜索(DFS)以及回溯的技巧,需要注意的是,和8皇后问题一样,需要回溯的时机有两个,出错的时候和找到某一组解的时候.
代码:
显然,按照深度搜索并不能有效地找到最优解.上面两种算法都是比较巧的,我也比较喜欢.
如果要同时找到所有解和最优解,用图的广度搜索(BFS)会很方便,这也是网上采用的最多的,代码到处都有,就不写了.
代码:
import java.util.LinkedList; import java.util.Set; public class Oil { static class Status{ static int[] full={12,8,5};//满的状态 int[] bottle=new int[3];//瓶子的状态 Status from;//从哪个状态来的 public Status(int a,int b,int c){ bottle[0]=a; bottle[1]=b; bottle[2]=c; } //获取某种状态开始下一步的所有的状态 public Set opreation(){ Set res=new HashSet(); //开始倒酒 for(int i=0;i<bottle.length;i++){ for(int j=0;j<bottle.length;j++){ if(i==j) continue; //不倒自己 if(bottle[i]==0) continue;//自己是空的 不倒 if(bottle[j]==full[j]) continue;//对方是满的 不倒 Status t=new Status(bottle[0], bottle[1], bottle[2]); t.from=this;//从自己这个状态开始变化 //真的开始倒酒了 t.bottle[j]+=t.bottle[i]; t.bottle[i]=0; if(t.bottle[j]>full[j]){//装不下了 t.bottle[i]=t.bottle[j]-full[j];//满的倒回去 t.bottle[j]=full[j]; } res.add(t); } } return res; } //是否含有某种状态 public boolean has2(int x){ int index=0; if (bottle[0]==x) index++; if (bottle[1]==x) index++; if (bottle[2]==x) index++; return index==2?true:false; } public Status getFrom() { return from; } public String toString(){ return "<" + bottle[0] + "," + bottle[1] + "," + bottle[2] + ">"; } public int hashCode() { return 100; } public boolean equals(Object obj) { Status x=(Status)obj; return bottle[0]==x.bottle[0]&&bottle[1]==x.bottle[1]&&bottle[2]==x.bottle[2]; } } public static void main(String[] args) { Set<Status> all=new HashSet<Status>();//存放所有结果状态 all.add(new Status(12, 0, 0)); for(;;){ Set newset=new HashSet(); for(Status x:all){//所有上一种状态产生所有下一种状态 Set t = x.opreation(); newset.addAll(t); } if(all.containsAll(newset)) break;//出口 all.addAll(newset); } LinkedList<Status> list=new LinkedList<Status>();//存放有6的一溜 for(Status k:all){ if(k.has2(6)){ while(k!=null){ list.push(k); k=k.getFrom();//从终止状态开始往上追溯 } } } //输出 while(!list.isEmpty()){ System.out.println(list.pop()); } } }
这个解法找到的其实是最优解,至于为什么呢,其实利用set的方法十分巧妙,结果集set里随着一次次的分酒一次次地扩增,当第一次出现含有两个6的状态的时候,再往前追溯,步骤是最少的!因为这个我们想要的状态是第一次出现.
假如我们每次都打印出all集合,可以知道,当第一次找到含有两个6状态的时候程序并没有结束,因为还没有找到所有的状态.
而后面的状态再进行分酒时,仍有可能产生两个6的状态,但是想要加入set集合的时候就行不通了,所以此程序只输出最早加入的那一个解,并且是最优的.
当然这种算法并不能输出所有的解,如果要得到所有的解,我们可以采用以下算法,这种算法借鉴了图的深度搜索(DFS)以及回溯的技巧,需要注意的是,和8皇后问题一样,需要回溯的时机有两个,出错的时候和找到某一组解的时候.
代码:
import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class Oil { int[] full = new int[3]; //满状态 容量 int[] bottle = new int[3]; //瓶子的状态 int target = 0; //目标 List<int[]> res = new ArrayList<int[]>();//存放结果 public void opreation(int[] bottle) { for(int i=0;i<3;i++) { for(int j=1;j<3;j++){//每个瓶子都不往自己倒 总共6种可能性 int[] temp = bottle.clone();//每次循环都创建临时数组 int to=(i+j)%3;//(i+j)%3 是除每种i瓶子外其他两个瓶子的序号,即要倒的目标 if(temp[i]==0) continue;//自己是空的 不倒 if(temp[to]==full[to]) continue;//对方是满的 不倒 //开始倒酒 temp[to]+=temp[i]; temp[i]=0; if(temp[to]>full[to]){//装不下了 temp[i]=temp[to]-full[to];//满出来的部分倒回去 temp[to]=full[to]; } if(had(temp)) continue;//检测是否已经存在相同状态,防止重复 res.add(temp);//添加到结果链表 if(has2(temp)) return;//如果找到有两个想要的状态的结果就返回 opreation(temp);//继续下一次分酒 res.remove(res.size()-1); //回溯 仔细体会 } } } //是否以及含有状态 private boolean had(int[] bottlex) { for(int[] e:res) if(e[0]==bottlex[0]&&e[1]==bottlex[1]&&e[2]==bottlex[2]) return true; return false; } //检测找到结果 private boolean has2(int[] bottle) { int index=0; for(int i=0;i<bottle.length;i++) if(bottle[i]==target) index++; if(index==2){ show(res);//输出 res.remove(res.size()-1);//回溯 return true; } return false; } //打印 private void show(List<int[]> res) { for(int[] e:res) { System.out.println(e[0] + "," + e[1] + "," + e[2]); } System.out.println(); } public static void main(String[] args) { Oil o = new Oil(); Scanner scanner = new Scanner(System.in); String s =""; if(scanner.hasNext()) { s = scanner.nextLine(); } String[] data = s.split(","); int[] d = new int[data.length]; for(int i=0;i<data.length;i++){ d[i] = Integer.parseInt(data[i]); } o.full = new int[]{d[0],d[1],d[2]}; o.bottle = new int[]{d[3],d[4],d[5]}; o.target = d[6]; o.res.add(new int[]{d[3],d[4],d[5]});//添加初始状态 o.opreation(o.bottle); } }
显然,按照深度搜索并不能有效地找到最优解.上面两种算法都是比较巧的,我也比较喜欢.
如果要同时找到所有解和最优解,用图的广度搜索(BFS)会很方便,这也是网上采用的最多的,代码到处都有,就不写了.
相关文章推荐
- 贪心算法——找纸币问题
- 高效算法——D 贪心,区间覆盖问题
- 贪心算法——找零钱问题
- 贪心算法——区间找点问题
- 贪心算法——区间找点问题
- 贪心算法——区间找点问题
- 贪心算法——NY 14 会场安排问题
- 贪心算法之背包问题
- 贪心算法基础之独木舟问题 51nod 贪心教程
- 贪心算法之活动选择问题
- 贪心算法处理背包问题
- 贪心算法——最优装载问题
- 贪心算法之背包问题
- 贪心算法—删数问题
- 会场安排问题之贪心算法
- UOJ#9 浅谈在线仙人球嵌套动态网络路径剖分优化的分支定界贪心剪枝启发式迭代加深人工智能搜索决策算法解决问题
- 贪心算法——字典序最小问题
- 贪心算法求背包问题
- 贪心算法--活动安排问题
- 贪心算法--畜栏保留问题(poj3190 )