ドアを 2 回使用せずに部屋のセットを通るルートを見つける必要がある、古典的な「ケーニヒスベルクの 7 つの橋」パズルの変形であるパズルがいくつかあります。
...そして、ここでわかるように、解決策
があるわずかに変更されたパズルです。
この種の問題を解決するためのプログラムによるアプローチに興味があります。部屋とドアの特定の構成に解決策がないことを判断する方法はいくつかありますが、解決するために訪問するドアのリストを計算することに興味があります。パズル。問題を表示する 1 つの方法は、その構成をグラフに変換し、ハミルトニアンを解くことです。ただし、この種の問題は、「Uターン」が禁止されているという制約があるため、洗練されていないロジックに取り組む必要があります。
問題を示すために、数分で解決策をハックしました。これは、「部屋」をグループに入れる力ずくのソリューションであり、同じ部屋の 1 つの「ドア」から別の「ドア」に移動できないという不変条件が追加されています (U ターンを行う必要があるため)。
次の「トリック」に頼らない、この問題を表現するためのより良い抽象化が必要だと思います。
パスがその部屋から来たばかりのときに、有効な選択肢として同じ部屋のドアを削除するための追加のロジックがあります。
入力ルームの構成と同型ではないグラフを生成します。
U ターンの制約を満たさないすべての構成をフィルタリングします。(#1 のバリアント)
この種の問題に取り組んでいる既存の文献はありますか? もしそうなら、その結論は何ですか? ルームの問題は、最もよく知られているグラフ アルゴリズムで採用されている方法と根本的に矛盾しているため、この特別なロジックが必要になるのでしょうか? グラフへの変換以外のより良い解決策がある場合は、それについても聞きたいです。
動作する既存のコードは次のとおりです。グループは最初の問題を表し、コメントアウトされたグループは後者の問題を表します。
// I renamed "groups" to rooms to make the code more clear.
var rooms = {
1: ['A','B','C','D'],
//1: ['A','B','C','D','P'],
2: ['E', 'D', 'F', 'G'],
3: ['F','I','J','H'],
//3: ['F','I','P','J', 'H'],
4: ['I', 'M', 'N', 'O'],
5: ['C','J','M','L','K'],
OUTER: ['A', 'B', 'E', 'G', 'H', 'O', 'N', 'L', 'K']
}
class Graph {
constructor(rooms) {
// This is a map of a door letter to the rooms (rooms) that it belongs to.
this.roomKey = {};
// The total number of doors
this.totalNodes = 0;
this.rooms = rooms;
// This is only used to produce the number of rooms, but remains in case
// I need to adapt the algorithm for the classical approach.
this.vertices = {};
for (var key in rooms) {
this.addRoom(key, rooms[key]);
}
}
addRoom(roomName, elements) {
for (var from of elements) {
if (!this.roomKey[from]) {
// initialize
this.roomKey[from] = [roomName]
} else {
this.roomKey[from].push(roomName)
}
for (var to of elements) {
// it doesn't make sense to add a vertex to yourself
if (from === to) continue
// otherwise add the vertex
this.addDoor(from, to)
}
}
}
addDoor(name, edge) {
// initialize if empty
if (!this.vertices[name]) {
this.vertices[name] = []
this.totalNodes++
}
if (this.vertices[name].indexOf(edge) != -1) {
console.log(`${name} already has this edge: ${edge}`)
} else {
this.vertices[name] = this.vertices[name].concat(edge)
}
}
hamiltonian(current, prevRoom, used) {
// Find the rooms that this connects to
var kpossible = this.roomKey[current]
// Find the rooms that connect to this door, but filter those that are
// in the room we just came from, this is the hacky part.
var possibleRoom = kpossible.find((room) => room !== prevRoom)
// Produce all possible rooms, but if we've already been to a room, remove it.
var possibleDoors = this.rooms[possibleRoom].filter((elt) => used.indexOf(elt) == -1)
if (used.length == this.totalNodes) {
console.log("success!", used)
return;
}
// No more possible rooms, this path is no good.
if (!possibleDoors || possibleDoors.length === 0)
return;
for(var door of possibleDoors) {
this.hamiltonian(door, possibleRoom, used.concat(door))
}
}
}