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

时间:2015-09-13 07:00:29

标签: algorithm graph-theory greedy

我想用图表来模拟以下谜题。

  

酒保给你三个   尺寸分别为1000ml,700ml和400ml的玻璃杯。 700毫升和400毫升的眼镜开始   满满的啤酒,但1000毫升的玻璃杯最初是空的。如果你赢了,你可以获得无限量的免费啤酒   以下游戏:   游戏规则:你可以将啤酒从一个杯子倒入另一个杯子,只在源头停止   玻璃是空的或目的地玻璃已满。如果有一系列的倾泻离开,你就赢了   在700毫升或400毫升玻璃杯中精确200毫升。

我有点不确定如何在图表中翻译此问题。我的想法是,眼镜将由加权的无向图中的节点表示,其中边缘表示玻璃u可以倒入玻璃v,而另一种方式是相同的,因此散步将是一系列倾向,将导致正确的解决方案。

然而,这种具有三个单节点和无向边的方法对于Dijkstra的算法或其他贪婪算法来说并不是很有效,而我将用它来解决问题。将pourings的排列建模为图表更合适吗?

1 个答案:

答案 0 :(得分:0)

如果我们想用图表对这个问题进行建模,每个节点应该代表啤酒量对眼镜的可能分配。假设我们用这样的对象代表每个玻璃:

{ volume: <current volume>, max: <maximum volume> }

然后起始节点是三个这样的对象的列表:

[ { 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;
}

从起始节点开始,我们尝试从每个玻璃杯浇注到每个其他玻璃杯。这些行动中的每一个都导致新的啤酒量分配。我们检查每一个以查看我们是否达到了200的目标量。如果没有,我们将分配推送到队列中。

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

为了能够重建最短路径,我们将每个节点的前任存储在字典中。我们可以使用相同的字典来确保我们不会多次探索节点。

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

&#13;
&#13;
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;
}

function glassesToKey(glasses) {
  return JSON.stringify(glasses);
}

function keyToGlasses(key) {
  return JSON.parse(key);
}

function print(s) {
  s = s || '';
  document.write(s + '<br />');
}

function displayKey(key) {
  var glasses = keyToGlasses(key);
      parts = glasses.map(function (glass) {
        return glass.volume + '/' + glass.max;
      });
  print('volumes: ' + parts.join(', '));
}

var startGlasses = [ { volume: 0, max: 1000 },
                     { volume: 700, max: 700 },
                     { volume: 400, max: 400 } ];

var startKey = glassesToKey(startGlasses);

function solve(targetVolume) {
  var actions = {},
      queue = [ startKey ],
      tail = 0;
  while (tail < queue.length) {
    var key = queue[tail++];                          // Pop from tail.
    for (var i = 0; i < startGlasses.length; ++i) {   // Pick source.
      for (var j = 0; j < startGlasses.length; ++j) { // Pick target.
        if (i != j) {
          var glasses = keyToGlasses(key);
          pour(i, j, glasses);
          var nextKey = glassesToKey(glasses);
          if (actions[nextKey] !== undefined) {
            continue;
          }
          actions[nextKey] = { key: key, source: i, target: j };
          for (var k = 1; k < glasses.length; ++k) {
            if (glasses[k].volume === targetVolume) { // Are we done?
              var path = [ actions[nextKey] ];
              while (key != startKey) {               // Backtrack.
                var action = actions[key];
                path.push(action);
                key = action.key;
              }
              path.reverse();
              path.forEach(function (action) {        // Display path.
                displayKey(action.key);
                print('pour from glass ' + (action.source + 1) +
                      ' to glass ' + (action.target + 1));
                print();
              });
              displayKey(nextKey);
              return;
            }
            queue.push(nextKey);
          }
        }
      }
    }
  }
}

solve(200);
&#13;
body {
  font-family: monospace;
}
&#13;
&#13;
&#13;