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