Algorithm 如何将三个玻璃浇注拼图建模为图形

Algorithm 如何将三个玻璃浇注拼图建模为图形,algorithm,graph-theory,greedy,Algorithm,Graph Theory,Greedy,我想用一个图表来模拟下面的谜题 酒吧招待给你三个 尺寸分别为1000ml、700ml和400ml的玻璃。700毫升和400毫升玻璃杯开始 我们喝完了啤酒,但1000毫升的杯子一开始是空的。如果你赢了,你可以得到无限量的免费啤酒 以下游戏: 游戏规则:你可以不停地把啤酒从一个杯子倒进另一个杯子里,只有当啤酒的来源被打破时才停止 玻璃杯为空或目标玻璃杯已满。如果有一系列的倾盆大雨离开,你就赢了 在700毫升或400毫升的玻璃杯中精确加入200毫升 我有点不确定如何将这个问题转化为图表。我的想法是,玻

我想用一个图表来模拟下面的谜题

酒吧招待给你三个 尺寸分别为1000ml、700ml和400ml的玻璃。700毫升和400毫升玻璃杯开始 我们喝完了啤酒,但1000毫升的杯子一开始是空的。如果你赢了,你可以得到无限量的免费啤酒 以下游戏: 游戏规则:你可以不停地把啤酒从一个杯子倒进另一个杯子里,只有当啤酒的来源被打破时才停止 玻璃杯为空或目标玻璃杯已满。如果有一系列的倾盆大雨离开,你就赢了 在700毫升或400毫升的玻璃杯中精确加入200毫升

我有点不确定如何将这个问题转化为图表。我的想法是,玻璃将由加权无向图中的节点表示,其中边表示玻璃
u
可以倒入玻璃
v
中,而另一种方式是相同的,因此,行走将是一系列倒入,从而得出正确的解


然而,这种有三个单节点和无向边的方法对于Dijkstra算法或其他贪婪算法并不太有效,我将使用这些算法来解决这个问题。将浇注的排列建模为图形是否更合适?

您应该将整个状态存储为顶点。我的意思是,每个玻璃中的值都是状态的一个组成部分,因此状态是
glassesCount
数字的数组。例如,初始状态为
(700400,0)

之后,您应该将初始状态添加到队列并运行BFS是可应用的,因为每条边的权重相等=1。权重是相等的,因为权重是每个状态之间的倾倒次数,显然=1,因为我们只从队列中的每个状态生成可到达的状态

您也可以使用DFS,但BFS返回最短的浇注序列,因为BFS为1加权图提供最短路径。如果您对最短浇注顺序不感兴趣,但对任何解决方案感兴趣,DFS都可以。我将描述BFS,因为它与DFS具有相同的复杂性,并返回更好(更短)的解决方案

在BFS的每一个状态中,你必须通过从所有成对组合中倾注来生成所有可能的新状态。此外,你应该检查浇注的可能性

对于3个眼镜,每个州有3*(3-1)=6个可能的分支,但我实现了更通用的解决方案,允许您将我的代码用于N个眼镜

public class Solution{


    static HashSet<State> usedStates = new HashSet<State>();
    static HashMap<State,State> prev = new HashMap<State, State>();
    static ArrayDeque<State> queue = new ArrayDeque<State>();
    static short[] limits = new short[]{700,400,1000};

        public static void main(String[] args){

            State initialState = new  State(new Short[]{700,400,0});
            usedStates.add(initialState);
            queue.add(initialState);
            prev.put(initialState,null);
            boolean solutionFound = false;


            while(!queue.isEmpty()){
                State curState = queue.poll();
                if(curState.isWinning()){
                    printSolution(curState);
                    solutionFound = true;
                    break; //stop BFS even if queue is not empty because solution already found
                }
                // go to all possible states

               for(int i=0;i<curState.getGlasses().length;i++)
                   for(int j=0;j<curState.getGlasses().length;j++) {
                       if (i != j) { //pouring from i-th glass to j-th glass, can't pour to itself
                           short glassI = curState.getGlasses()[i];
                           short glassJ = curState.getGlasses()[j];
                           short possibleToPour = (short)(limits[j]-glassJ);
                           short amountToPour;
                           if(glassI<possibleToPour) amountToPour = glassI; //pour total i-th glass
                           else amountToPour = possibleToPour; //pour i-th glass partially
                           if(glassI!=0){ //prepare new state
                               Short[] newGlasses = Arrays.copyOf(curState.getGlasses(), curState.getGlasses().length);
                               newGlasses[i] = (short)(glassI-amountToPour);
                               newGlasses[j]  = (short)(newGlasses[j]+amountToPour);
                               State newState = new State(newGlasses);
                               if(!usedStates.contains(newState)){ // if new state not handled before mark it as used and add to queue for future handling
                                   usedStates.add(newState);
                                   prev.put(newState, curState);
                                   queue.add(newState);
                               }
                           }
                       }
                   }
            }
            if(!solutionFound) System.out.println("Solution does not exist");

    }

    private static void printSolution(State curState) {
        System.out.println("below is 'reversed' solution. In order to get solution from initial state read states from the end");
        while(curState!=null){
            System.out.println("("+curState.getGlasses()[0]+","+curState.getGlasses()[1]+","+curState.getGlasses()[2]+")");
            curState = prev.get(curState);
        }

    }

    static class State{

        private Short[] glasses;

        public State(Short[] glasses){
           this.glasses = glasses;
        }

        public boolean isWinning() {

            return glasses[0]==200 || glasses[1]==200;
        }

        public Short[] getGlasses(){
            return glasses;
        }

        @Override
        public boolean equals(Object other){
            return Arrays.equals(glasses,((State)other).getGlasses());
        }

        @Override
        public int hashCode(){
            return Arrays.hashCode(glasses);
        }
    }
}
有趣的事实-如果替换,这个问题没有解决方案

在g1或g2中加入200ml

在g1和g2中加入200ml


我的意思是,状态(200700)无法从(700400,0)

中访问。如果我们想用一个图来模拟这个问题,每个节点都应该表示啤酒量可能分配给玻璃杯。假设我们用如下对象表示每个玻璃:

{ volume: <current volume>, max: <maximum volume> }
边表示将一个玻璃杯倒入另一个玻璃杯的动作。要执行此操作,我们选择一个源玻璃和一个目标玻璃,然后计算我们可以从源向目标倒入多少:

function pour(indexA, indexB, glasses) {  // Pour from A to B.
  var a = glasses[indexA],
      b = glasses[indexB],
      delta = Math.min(a.volume, b.max - b.volume);
  a.volume -= delta;
  b.volume += delta;
}
从开始节点开始,我们尝试从每一个杯子倒到另一个杯子。每项操作都会导致啤酒量的新分配。我们检查每一个,看看我们是否达到了目标量200。如果没有,我们将分配推送到队列中

为了找到从起始节点到目标节点的最短路径,我们将新发现的节点推到队列的头部,并将节点从队列的末端弹出。这确保了当我们到达目标节点时,它与队列中的任何其他节点相比,距离起始节点不远

为了使重建最短路径成为可能,我们将每个节点的前身存储在字典中。我们可以使用同一个字典来确保不会多次浏览节点

下面是这种方法的JavaScript实现。单击下面的蓝色按钮运行它

函数倒(indexA,indexB,glasses){//从A倒到B。
var a=玻璃[indexA],
b=玻璃[indexB],
增量=数学最小值(a.体积,b.最大值-b.体积);
a、 体积-=三角洲;
b、 体积+=增量;
}
功能玻璃按键(玻璃){
返回JSON.stringify(眼镜);
}
功能按键眼镜(按键){
返回JSON.parse(key);
}
功能打印{
s=s | |'';
文件。写(s+“
”); } 功能显示键(键){ var玻璃=钥匙玻璃(钥匙); 零件=玻璃。映射(功能(玻璃){ 返回glass.volume+'/'+glass.max; }); 打印('卷:'+parts.join(',')); } var startGlasses=[{volume:0,max:1000}, {卷数:700,最大值:700}, {卷数:400,最大值:400}]; var startKey=玻璃钥匙(STARTGLASES); 函数求解(targetVolume){ var actions={}, 队列=[startKey], 尾=0; while(尾部<队列长度){ var key=queue[tail++];//从tail弹出。 对于(var i=0;i[ { volume: 0, max: 1000 }, { volume: 700, max: 700 }, { volume: 400, max: 400 } ]
function pour(indexA, indexB, glasses) {  // Pour from A to B.
  var a = glasses[indexA],
      b = glasses[indexB],
      delta = Math.min(a.volume, b.max - b.volume);
  a.volume -= delta;
  b.volume += delta;
}
par int:N = 7; % only an alcoholic would try more than 7 moves
var 1..N: n;   % the sequence of states is clearly at least length 1. ie the start state

int:X = 10;    % capacities
int:Y = 7;
int:Z = 4;
int:T = Y + Z;

array[0..N] of var 0..X: x; % the amount of liquid in glass X the biggest
array[0..N] of var 0..Y: y;
array[0..N] of var 0..Z: z;

constraint x[0] = 0; % initial contents
constraint y[0] = 7;
constraint z[0] = 4;

% the total amount of liquid is the same as the initial amount at all times
constraint forall(i in 0..n)(x[i] + y[i] + z[i] = T);
% we get free unlimited beer if any of these glasses contains 2dl
constraint y[n] = 2 \/ z[n] = 2; 

constraint forall(i in 0..n-1)(
  % d is the amount we can pour from one glass to another: 6 ways to do it
  let {var int: d = min(y[i], X-x[i])} in (x[i+1] = x[i] + d /\ y[i+1] = y[i] - d) \/ % y to x
  let {var int: d = min(z[i], X-x[i])} in (x[i+1] = x[i] + d /\ z[i+1] = z[i] - d) \/ % z to x

  let {var int: d = min(x[i], Y-y[i])} in (y[i+1] = y[i] + d /\ x[i+1] = x[i] - d) \/ % x to y
  let {var int: d = min(z[i], Y-y[i])} in (y[i+1] = y[i] + d /\ z[i+1] = z[i] - d) \/ % z to y

  let {var int: d = min(y[i], Z-z[i])} in (z[i+1] = z[i] + d /\ y[i+1] = y[i] - d) \/ % y to z
  let {var int: d = min(x[i], Z-z[i])} in (z[i+1] = z[i] + d /\ x[i+1] = x[i] - d)    % x to z
);

solve minimize n;

output[show(n), "\n\n", show(x), "\n", show(y), "\n", show(z)];
[0, 4, 10, 6, 6, 2, 2]
[7, 7, 1, 1, 5, 5, 7]
[4, 0, 0, 4, 0, 4, 2]