贪心算法-泊松分酒问题

来源:互联网 发布:音频剪辑 for mac 编辑:程序博客网 时间:2024/06/05 17:06

讲这道题纯粹就是比较好玩,就记录一下.泊松分酒是很著名的一道题,讲的是假设某人有12品脱的啤酒一瓶,想从中倒出六品脱,但是恰巧身边没有6品脱的容器,仅有一个8品脱和一个5品脱的容器,怎样倒才能将啤酒分为两个6品脱呢?

代码:

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)会很方便,这也是网上采用的最多的,代码到处都有,就不写了.

1 0