讲这道题纯粹就是比较好玩,就记录一下.泊松分酒是很著名的一道题,讲的是假设某人有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)会很方便,这也是网上采用的最多的,代码到处都有,就不写了.

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐