有许多谜题是经典" 7 Kigs of Konigsberg"拼图,你必须找到一套通过一套房间的路线而不用两次门。
...并且是一个稍微修改过的谜题 有一个解决方案,你可以在这里看到。
我对解决这类问题的程序化方法感兴趣,虽然有很多方法可以确定房间和门的特定配置没有解决方案,但我有兴趣计算门列表参观解决难题。查看问题的一种方法是将其配置转换为图形并求解哈密顿量。然而,这种问题需要加强不优雅的逻辑,因为约束条件是" U-Turns"被禁止。
我在几分钟内修复了一个解决方案以显示问题。这是一个蛮力的解决方案,使'#34;房间"分组,增加不变量,你不能从一个"门"到另一个"门"在同一个房间(因为那将需要掉头)。
我觉得必须有一个更好的抽象来表示这个问题,而不是诉诸于以下"技巧":
当路径刚刚来自那个房间时,有额外的逻辑用于移除同一房间内的门和有效选择。
生成与输入房间配置不同的图表。
过滤所有不满足掉头限制的配置。 (#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))
}
}
}
答案 0 :(得分:4)
如你所说,门只能使用一次。
我会将数据表示为具有以下属性的邻接列表:
Outside
是一个顶点然后你只会跟随每一个边缘。
为了将您的数据结构转换为邻接列表,我将执行以下操作:
这样的东西将根据您已有的数据结构构建邻接列表:
var groups = {
1: ['A','B','C','D','P'],
2: ['E', 'D', 'F', 'G'],
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']
}
var edges = [];
var adjacency_list = [];
// collect all the doors
for (var room in groups) {
doors = groups[room];
for (var door of doors) {
if (edges.indexOf(door) < 0) {
edges.push(door); // mark off this door
}
}
}
// find the connections between the rooms (build the adjacency matrix)
for (var door of edges) {
rooms = [];
// find the two rooms that this door connects
for (var room in groups) {
doors = groups[room];
if (doors.indexOf(door) > 0) {
rooms.push(room);
}
}
// add these as an edge in our adjacency list
if (rooms.length == 2) {
adjacency_list.push(rooms);
}
else {
//TODO: raise an error as the rooms aren't connected properly
}
}
现在,adjacency_list
是一个边缘列表,可用于在房间之间进行遍历。每个门将有一个边缘连接两个房间。如果您穿过边缘(穿过门),则必须将其移除(或标记),这样您就不会再次穿过它(再次通过门)。