首先,从 pdf 文件显示在图像上的算法不是解决 Hamilton 路径问题,而是解决迷宫生成问题,因为最终路径有多个分支。
要查找迷宫生成算法,请参阅:
https://en.wikipedia.org/wiki/Maze_generation_algorithm
下面是一个在 N*M 2D 网格上生成哈密顿路径的简单算法:
-
设一个 NM 格为(例如,45):
O-O-O-O-O
| | | | |
O-O-O-O-O
| | | | |
O-O-O-O-O
| | | | |
O-O-O-O-O
-
让我们从东/北角开始,创建一个简单的向西和向东的锯齿形:
O-O-O-O-O
|
O-O-O-O-O
|
O-O-O-O-O
|
O-O-O-O-O
现在我们有一条哈密顿路径。
-
让我们搜索两条胶合边,一条在另一条前面。它们是循环的开始和结束:
O-O-O-O-O
|
O-OXO-O-O
|
O-OXO-O-O
|
O-O-O-O-O
-
确保环内至少有一条边粘在环外的一条边上,否则转至第 3 步:
O-O-O-O-O
|
O-OXO-O-O
|
O-OXOxO-O
|
O-O-OxO-O
-
捷径循环:
O-O-O-O-O
|
O-O O-O-O
| | |
O-O OxO-O
|
O-O-OxO-O
-
通过另外两个胶合边缘重新连接环:
O-O-O-O-O
|
O-O O-O-O
| | |
O-O O O-O
| | |
O-O-O O-O
-
如果哈密顿路径不够随机,请转到步骤 3。
只有开始和结束不会移动。要随机化结束或开始,您可以用另一种算法替换初始之字形:
- 从四个角中选择一个
- 搜索所有未访问的邻居
- 如果没有邻居,则填充地图,否则进入步骤4
- 仅将具有空白或已访问单元格的邻居保留在左侧或右侧(换句话说,沿非访问区域边界行走的邻居)
- 选择其中一个邻居,访问它并转到第 2 步
结果可能如下所示:
O-O-O-O-O
|
O-O-O-O O
| | |
O O-O O O
| | | |
O-O-O O-O
使用此算法,起点保持在拐角处,但终点可以在任何地方。要随机化开始和结束,您可以应用一种算法,您可以在开始或结束时根据需要进行多次迭代。让我们开始吧:
- 找到起点:
|
v
O-O-O-O-O
|
O-O-O-O O
| | |
哦哦哦哦哦
| | | |
O-O-O O-O
- 找到一个不直接连接到起点的邻居(您总是会在 2D 网格中找到一个):
O-O-O-O-O
|
->O-O-O-O O
| | |
哦哦哦哦哦
| | | |
O-O-O O-O
- 从起点(分别从终点)查找您到达它的位置:
O-O-O-O-O
|
OXO-O-O
| | |
哦哦哦哦哦
| | | |
O-O-O O-O
- 剪切此链接并在此点和起点之间创建一个链接:
O-O-O-O-O
| |
哦哦哦哦哦
| | |
哦哦哦哦哦
| | | |
O-O-O O-O
开始移动了两个单元格。起点和终点就像棋盘格一样,只能在同色的箱子上移动。
现在你的路径是完全随机的。
这是 Python 中的整个算法。你可以在这里运行它:
http://www.compileonline.com/execute_python3_online.php
结果存储在一个数组 (self.gameGrid) 中,该数组被记录两次(带有箭头以及节点和线条)。前两条粘合边称为排列,第二条称为交集。
import random
import enum
class From(enum.Enum):
NOWHERE = 1
NORTH = 2
EAST = 3
SOUTH = 4
WEST = 5
class Hamiltonian:
def __init__(self, width: int, height: int, start: tuple = (0, 0)):
self.arcs = {From.NORTH: (0, -1), From.SOUTH: (0, 1), From.EAST: (1, 0), From.WEST: (-1, 0)}
self.width = width
self.height = height
self.start = start
self.grid = {(i, j): self._zig_zag(i, j) for i in range(width) for j in range(height)}
self.grid[start] = From.NOWHERE
self.curr_loop = []
def generate(self, count: int = 100):
for i in range(count):
sp = self._split_grid()
self._modify_path(sp)
tu = self._mend_grid(sp)
self._modify_path(tu)
def _modify_path(self, spl):
pt_a, pt_b = spl
pta, ptb = self.grid[pt_a], self.grid[pt_b]
orientation = pta
if orientation in [From.NORTH, From.SOUTH]:
if pt_a[0] < pt_b[0]:
pta, ptb = From.EAST, From.WEST
else:
pta, ptb = From.WEST, From.EAST
else:
if pt_a[1] < pt_b[1]:
pta, ptb = From.SOUTH, From.NORTH
else:
pta, ptb = From.NORTH, From.SOUTH
self.grid[pt_a] = pta
self.grid[pt_b] = ptb
def _move(self, pt) -> [tuple, None]:
if pt in self.grid and self.grid[pt] != From.NOWHERE:
(x, y), (dx, dy) = pt, self.arcs[self.grid[pt]]
if (x + dx, y + dy) in self.grid:
return x + dx, y + dy
return None
def _set_loop(self, start, stop):
self.curr_loop = []
point = start
while point and len(self.curr_loop) <= len(self.grid) and point != stop and self.grid[point] != From.NOWHERE:
point = self._move(point)
self.curr_loop.append(point)
return point == stop
def _split_grid(self) -> tuple:
candidates = []
for pt, dx in self.grid.items():
x, y = pt
if dx == From.NORTH:
cx = (x+1, y - 1)
if cx in self.grid and self.grid[cx] == From.SOUTH:
candidates.append((pt, cx))
elif dx == From.SOUTH:
cx = (x+1, y + 1)
if cx in self.grid and self.grid[cx] == From.NORTH:
candidates.append((pt, cx))
elif dx == From.EAST:
cx = (x + 1, y + 1)
if cx in self.grid and self.grid[cx] == From.WEST:
candidates.append((pt, cx))
elif dx == From.WEST:
cx = (x - 1, y + 1)
if cx in self.grid and self.grid[cx] == From.EAST:
candidates.append((pt, cx))
if len(candidates) > 0:
start, end = random.choice(candidates)
if self._set_loop(start, end):
return start, end
elif not self._set_loop(end, start):
raise Exception('Cannot split. Loop failed.')
return end, start
def _mend_grid(self, sp):
candidates = []
for pt, dx in self.grid.items():
(x, y), lx = pt, pt in self.curr_loop
if dx == From.NORTH:
cx = (x+1, y - 1)
rx = cx in self.curr_loop
if cx in self.grid and self.grid[cx] == From.SOUTH and rx != lx:
candidates.append((pt, cx))
elif dx == From.SOUTH:
cx = (x+1, y + 1)
rx = cx in self.curr_loop
if cx in self.grid and self.grid[cx] == From.NORTH and rx != lx:
candidates.append((pt, cx))
elif dx == From.EAST:
cx = (x + 1, y + 1)
rx = cx in self.curr_loop
if cx in self.grid and self.grid[cx] == From.WEST and rx != lx:
candidates.append((pt, cx))
elif dx == From.WEST:
cx = (x - 1, y + 1)
rx = cx in self.curr_loop
if cx in self.grid and self.grid[cx] == From.EAST and rx != lx:
candidates.append((pt, cx))
a, b = sp
if (a, b) in candidates:
candidates.remove((a, b))
elif (b, a) in candidates:
candidates.remove((b, a))
if len(candidates) > 0:
return random.choice(candidates)
else:
return sp
def _zig_zag(self, x: int, y: int) -> From:
even = y % 2 == 0
if (x == 0 and even) or (x == self.width - 1 and not even):
return From.NORTH
return From.WEST if even else From.EAST
def print_path(self):
result_str = ''
for y in range(self.height):
for x in range(self.width):
if (self.grid[x, y] == From.NORTH) or ((y > 0) and (self.grid[x, y - 1] == From.SOUTH)):
result_str = result_str + ' |'
else:
result_str = result_str + ' '
result_str = result_str + ' \n'
for x in range(self.width):
if (self.grid[x, y] == From.WEST) or ((x > 0) and (self.grid[x - 1, y] == From.EAST)):
result_str = result_str + '-O'
else:
result_str = result_str + ' O'
result_str = result_str + ' \n'
print(result_str)
if __name__ == '__main__':
h = Hamiltonian(5, 5)
h.generate(500)
h.print_path()