如何用Warnsdorff的规则改善骑士之旅?

时间:2011-12-06 15:44:07

标签: algorithm graph graph-theory chess

我知道有几个类似的线程,但我甚至没有在SO之外找到解决方案。 这是我的问题: 我为Knight's Tour问题http://en.wikipedia.org/wiki/Knight%27s_tour实施了Warnsdorff的算法,但在某些情况下它没有提供解决方案。在某些地方,我读到它可以通过一些改动更好地工作,但是没有人指定那些改变。有人知道解决方案吗?我知道其他算法,但它们要复杂得多。

即使对于8x8棋盘,它有时也无法提供良好的解决方案。我认为阅读我的代码没有意义,因为它是一个经典的Warnsdorff:检查可能的移动,并在下一步中选择移动最少的移动。

4 个答案:

答案 0 :(得分:6)

这是我发现的:

请注意,这仍然不是一个明确的答案,我不是图论专家,所以这些只是观察。

我称之为经典的Warnsdorff启发式“W”。 http://mirran.web.surftown.se/knight/bWarnsd.htm(缓存:http://web.archive.org/web/20120213164632/http://mirran.web.surftown.se/knight/bWarnsd.htm)的改进将被称为“W +”。 https://github.com/douglassquirrel/warnsdorff/blob/master/5_Squirrel96.pdf?raw=true的改进将是“W2”。 水平字段的数量为“x”,垂直字段为“y”。

所以,这是我的观察。

简短版本:

W很简单,但很多时候它无法提供解决方案。这首先触发了这个问题。 W +也很简单,并且有很大改进,特别是在大型电路板上。 W2实现起来要复杂得多,与W +相比,它似乎没有给出更好的结果。所以我投票支持W +。无论如何,这是我将使用的变种。

长版:

<强> w ^

<强>的优点: 与其他Knights Tour算法相比,简洁。 与W +相比,它并没有真正的优势。 与W2相比,它更容易实现。

<强>缺点: 有很多情况下有解决方案,但W无法提供解决方案 它往往会搞乱更大的板子(50 +)

<强> W +

<强>的优点: 与其他Knights Tour算法相比,简洁。 与W相比:它可以在更多情况下提供解决方案,它几乎不比W复杂。 与W2相比,它更容易实现,W +也适用于非方形板。例如10x20。

<强>缺点: 与W相比,它没有缺点。 与其他骑士旅行算法相比,这个可能会在某些情况下卡住。对W +来说最难的是小板,如5x5,6x6,7x7,9x9等。如Wiki所述,当x和y均匀时,它会出现板卡问题。另一方面,当x和y是偶数但大于9时,似乎W +设法找到解决方案。 与W2相比,我没有遇到任何不利因素。

<强> W2

<强>的优点: 与W相比,它在更多情况下提供了解决方案,特别是对于大型电路板。 与W +相比,我没有注意到优势。

<强>缺点: 实施与W和W +相比。

<强>结论:

我认为W +实际上是最可接受的。不要忘记它并不完美。我不得不说,我的实施不允许真正的大板。我测试了W +高达90x90(8100个节点),它仍然提供了解决方案。虽然,由于时间有限,我没有做过大量的测试。我希望这有助于以前遇到过这个问题的人。 因为这不是一个明确的答案,我暂时不接受它,希望有人能够给出一个完整的答案。 很抱歉长时间阅读。

答案 1 :(得分:5)

W + 的链接不再存在,使得已接受的答案无法使用。

Warnsdorff算法的改进是:

Arnd Roth的主张: 罗斯认为Warnsdorff的启发式问题存在于随机的抢七规则中。 这个命题是通过选择距离董事会中心最大欧氏距离的继任者来打破关系。

  

这里的问题是当不止一个继任者共享相同距离时会发生什么。
Arnd Roth声称这一改进首先   在一块428行的电路板上发生故障并且所有故障的发生率都低于1%   少于2000行的电路板。

Ira Pohl的主张: 为了打破这种联系,波尔决定第二次申请沃恩斯多夫的统治。为了达到这个目的,我们必须得到所有未被访问的邻居,被绑定的继承者的度数之和,并选择总和最小的平方。

  

这里的问题之一是,无论我们应用Warnsdorff规则多少次,我们都会产生平局(两者相邻)   如果我们从角落广场开始。此外,如果我们申请   然后渐渐地,Warnsdorff的启发式次数很多次   这等于详尽的搜索。

Pohl还建议,如果我们在第二次申请Warnsdorff后未能产生继任者,则通过使用固定的任意顺序来打破关系。他的主张是,这成功地在8 * 8板上产生了解决方案。

通过使用其中一项增强功能,我们可以更好地通过防止可能的锁定来系统地创建解决方案。

答案 2 :(得分:1)

这是Python 2中的一个工作示例,它使用计时器来完成电路板并说明解决方案。我找到最靠近电路板边缘的节点用于断路器,如果两者相同,我只返回第一个节点。这似乎是一个自然的选择,因为如果电路板是空的,靠近边缘的单元应该具有较少的可能移动。似乎运作良好,但正如Panos所提到的, Arnd Roth的主张有1%的失败率。可以轻松修改此代码以使用 Ira Pohl的命题。可以修改访问节点以针对具有最小移动的节点再次运行possible_unvisited_moves。如果是关系,使用第一个应该工作。

class GNode(object):
    """ Used to represent a node in the graph """
    def __init__(self, value, row=None, col=None):
        self.row = row  # allows node to know its loc. in array
        self.col = col
        self.value = value

def setup_graph(n):
    """ Create x by x grid (graph inside an array). """
    graph = []
    for row in range(n):
        graph.append([])
        for col in range(n):
            graph[row].append(GNode(None,row=row, col=col))
    return graph

def possible_moves(graph_node, total):
    """ Find out all possible moves for the current node position. """
    moves = []
    move_patterns = [[-1,-2], [-1, 2], [-2, 1], [-2, -1], [1, -2], [1, 2], [2, 1], [2, -1]]
    for row, col in move_patterns:
        move_row = graph_node.row + row; move_col = graph_node.col + col
        if move_row >= 0 and move_col >= 0 and move_row < total and move_col < total:
            moves.append([move_row, move_col])
    return moves

def possible_unvisited_moves(graph_node, grid):
    """Get all possible moves and weed out the ones that are already visited. 
       visited means they have a value.
    """
    moves = possible_moves(graph_node, len(grid))
    unvisited = []
    for move in moves:
        if grid[move[0]][move[1]].value is None:
            unvisited.append(move)
    return unvisited


def closest_to_edge(pos1, pos2, total):  
    """ Find out which position is closest to the edge of board, and return it. 
        pos are 2 arrays [x,y], total is the board size (length)
    """
    total -= 1
    pos1_min = min(total - pos1[0], pos1[0], pos1[1], total-pos1[1])
    pos2_min = min(total - pos2[0], pos2[0], pos2[1], total-pos2[1])
    if pos2_min < pos1_min: return pos2
    return pos1  # default


def visit_node(graph_node, graph):
    """ Check possible unvisited moves from the pos & find next move to make """
    graph_node.value = "{}{}".format(graph_node.row, graph_node.col)  # visited
    p_moves = possible_unvisited_moves(graph_node, graph)
    next_move = None
    min_no_moves = 9 # highest can be 8 for knights move pattern
    for move in p_moves:
        next_moves = possible_unvisited_moves(graph[move[0]][move[1]], graph)
        if len(next_moves) < min_no_moves:
            next_move = move
            min_no_moves = len(next_moves)
        elif len(next_moves) == min_no_moves:
            min_move = closest_to_edge(next_move, move, len(graph))
            if min_move != next_move:
                next_move = min_move
                min_no_moves = len(next_moves)
    if next_move:
        os.system(clear_screen) 
        print "This Move: [",graph_node.row, ",",  graph_node.col, "]. Next Move: ", next_move, "Min Moves from next: ", min_no_moves
        display_graph(graph)
        import time
        time.sleep(.5)
        return visit_node(graph[next_move[0]][next_move[1]], graph)
    else:
        os.system(clear_screen) 
        display_graph(graph)
        return "Done"

def display_graph(graph):
    print ""
    for row in graph:
        row_vals = []
        for cell in row:
            row_vals.append(cell.value)
        print row_vals

import os
clear_screen = 'cls' if os.name == 'nt' else 'clear'

graph = setup_graph(8)

print visit_node(graph[4][4], graph)

玩得开心玩得开心。 :)

答案 3 :(得分:1)

我认为在应用Warnsdorff规则时最容易被忽视的是正在构建的路径有两个目的。当将两级平局规则应用于路径的两端时,可以获得相当好的结果。并且,作为一个额外的奖励,产生的折返旅行的数量大大增加。