【问题标题】:Algorithm to find a random Hamiltonian path in a grid?在网格中找到随机哈密顿路径的算法?
【发布时间】:2011-09-10 10:57:11
【问题描述】:

我正在寻找一种高效算法,能够在双向 N*M 网格中找到尽可能随机的Hamiltonian path

有谁知道我在哪里可以找到,或者如何构建这样的算法?


我已经找到了一种有效的方法(见下图)。这里的最终结果是哈密顿循环。删除随机边将使其成为哈密顿路径。该算法是有效的,但没有提供足够的随机性。这种方法将始终使路径的起点和终点彼此相邻,而我希望它们位于随机位置。 Space-filling curve http://img593.imageshack.us/img593/8060/sfc.png 图片取自http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.35.3648&rep=rep1&type=pdf

【问题讨论】:

  • 闻起来像家庭作业 - Google 是你的朋友。

标签: algorithm graph-algorithm hamiltonian-cycle


【解决方案1】:

首先,从 pdf 文件显示在图像上的算法不是解决 Hamilton 路径问题,而是解决迷宫生成问题,因为最终路径有多个分支。

要查找迷宫生成算法,请参阅: https://en.wikipedia.org/wiki/Maze_generation_algorithm

下面是一个在 N*M 2D 网格上生成哈密顿路径的简单算法:

  1. 设一个 NM 格为(例如,45):

    O-O-O-O-O | | | | | O-O-O-O-O | | | | | O-O-O-O-O | | | | | O-O-O-O-O

  2. 让我们从东/北角开始,创建一个简单的向西和向东的锯齿形:

    O-O-O-O-O | O-O-O-O-O | O-O-O-O-O |
    O-O-O-O-O

现在我们有一条哈密顿路径。

  1. 让我们搜索两条胶合边,一条在另一条前面。它们是循环的开始和结束:

    O-O-O-O-O |
    O-OXO-O-O | O-OXO-O-O |
    O-O-O-O-O

  2. 确保环内至少有一条边粘在环外的一条边上,否则转至第 3 步:

    O-O-O-O-O |
    O-OXO-O-O | O-OXOxO-O |
    O-O-OxO-O

  3. 捷径循环:

    O-O-O-O-O |
    O-O O-O-O | | | O-O OxO-O |
    O-O-OxO-O

  4. 通过另外两个胶合边缘重新连接环:

    O-O-O-O-O |
    O-O O-O-O | | | O-O O O-O | | |
    O-O-O O-O

  5. 如果哈密顿路径不够随机,请转到步骤 3。

只有开始和结束不会移动。要随机化结束或开始,您可以用另一种算法替换初始之字形:

  1. 从四个角中选择一个
  2. 搜索所有未访问的邻居
  3. 如果没有邻居,则填充地图,否则进入步骤4
  4. 仅将具有空白或已访问单元格的邻居保留在左侧或右侧(换句话说,沿非访问区域边界行走的邻居)
  5. 选择其中一个邻居,访问它并转到第 2 步

结果可能如下所示:

O-O-O-O-O
        |
O-O-O-O O
|     | |
O O-O O O
|   | | |
O-O-O O-O

使用此算法,起点保持在拐角处,但终点可以在任何地方。要随机化开始和结束,您可以应用一种算法,您可以在开始或结束时根据需要进行多次迭代。让我们开始吧:

  1. 找到起点:
| v O-O-O-O-O | O-O-O-O O | | | 哦哦哦哦哦 | | | | O-O-O O-O
  1. 找到一个不直接连接到起点的邻居(您总是会在 2D 网格中找到一个):
O-O-O-O-O | ->O-O-O-O O | | | 哦哦哦哦哦 | | | | O-O-O O-O
  1. 从起点(分别从终点)查找您到达它的位置:
O-O-O-O-O | OXO-O-O | | | 哦哦哦哦哦 | | | | O-O-O O-O
  1. 剪切此链接并在此点和起点之间创建一个链接:
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()

【讨论】:

    【解决方案2】:

    本文介绍了一种方法:

    奥伯多夫,R.;弗格森,A。雅各布森,J.L.; Kondev, J. - Secondary Structures in Long Compact Polymers (arXiv.org)

    该方法大致包括以下内容:从锯齿形图案(网格上的非随机哈密顿路径)开始,并重复对路径应用变换(称为“backbite”)。 backbite 包括将一条边从端点 A 添加到相邻顶点 B 而不是 A 连接到的那个(从而创建一个循环),然后删除从 B 开始的边,而不是刚刚添加的边,并且这会导致循环(除了刚刚添加的循环之外,总是只有一个导致循环)。

    作者添加了一些条件来获得粗略的均匀性(包括估计应用背咬移动的次数)。论文中的详细信息。

    作者还通过经验证明,他们的方法生成相邻端点的概率与均匀随机哈密顿路径中的理论概率大致匹配。

    这里有一个算法在 JavaScript 中的实现:Hamiltonian Path Generator

    【讨论】:

      【解决方案3】:

      足够的随机性是很笼统的, 你应该有一些基准,最著名的欧几里德 TSP 算法有 3/2 近似值 (Christofides algorithm),它使用 MST(就像你提到的算法一样,它是 2 近似值),正如你在 wiki 找到的最佳 PTAS 中所见当前的运行时间取决于 (n log n)^f(c,2) 对于 c > 0(在像您的样本一样的二维空间中),近似为 (1+1/c),以及具有常数因子的 TSP 的最佳近似是3/2 - 1/500 algorithm(最近发现的),但它们都使用逻辑方式,有一些随机用法,但不会导致所有东西都留给随机选择。如果你只想使用随机,你可以使用Random Walk,它更随机,但请参阅Markove Chain 以获得更好的性能和随机性。

      【讨论】:

        【解决方案4】:

        您可以从您提到的方法开始寻找哈密顿路径。要进一步随机化解决方案,您可以按照wiki 中的说明开始旋转边缘。更频繁地执行此操作将使解决方案更加随机。将随机边旋转 N*M 次使算法保持在有效范围内,同时使找到的哈密顿路径更加随机。

        【讨论】:

        • 谁能解释为什么这个被否决的答案被接受?
        猜你喜欢
        • 2010-12-31
        • 1970-01-01
        • 1970-01-01
        • 2017-11-12
        • 2011-07-17
        • 2023-04-10
        • 1970-01-01
        • 2018-08-14
        • 1970-01-01
        相关资源
        最近更新 更多