6

我正在尝试创建具有众所周知的滑动拼图的不同可能状态的树

如果你不知道,它是这样的:

[3 5 2]
[4   1]
[8 6 7]

你必须在哪里做到这一点:

[1 2 3]
[4 5 6]
[7 8  ]

基本上,每个状态都会生成新状态,具体取决于如何移动空白空间(向上、向下、向左或向右)

我想要的是创建树,所有状态都将根作为拼图的初始状态,但是当向树添加子(新状态)时,它应该检查该状态是否已经添加到树中的任何位置

你介意帮我实现吗?提前致谢 :)

这是我当前的代码RecursionError: maximum recursion depth exceeded while calling a Python object

节点类:

class Node(object):
    def __init__(self, value, parent=None):
        self.parent = parent
        self.value = value
        self.children = []

    def append(self, obj):
        if obj is not self.parent and obj not in self.children and obj is not None:
            self.children.append(obj)

    def has_children(self):
        return len(self.children) > 0

    def __iter__(self):
        yield self.value
        for v in chain(*map(iter, self.children)):
            yield v

    def find_root(self):
        p = self
        while p.parent is not None:
            p = p.parent
        return p

树生成方法(认为self是拼图状态):

def to_tree(self, parent=None):
        values = []
        if parent is not None:
            for child in parent.find_root():
                values.append(child)

        value = nd.Node(self, parent)

        if value not in values:
            children = [move[1].to_tree(value) for move in self.get_possible_movements()]
            for child in children:
                if child is not None:
                    value.append(child)
            return value
        else:
            return None
4

2 回答 2

2

我将尝试回答您进度的直接障碍:

RecursionError: maximum recursion depth exceeded while calling a Python object

这意味着“活动”函数调用(及其本地状态)的数量超过了限制。您可以尝试提高该限制(我半信半疑这可以在某处进行配置),但还有另一种更通用的技术来解决这个问题。

在伪代码中搜索一棵树(这似乎是你正在做的)看起来像这样:

find(predicate, node):
    if predicate(node):
        return node # found it
    for child in node.children:
        res = find(predicate, child):
        if res:
            return res # found it
    return false # not found

是一个函数,predicate它返回一个布尔值,指示是否找到了搜索的节点,它概括了这个搜索。

这里的问题是,正如你所看到的,由于树的高度,这可能会超过递归限制。避免此限制的另一种方法是不使用递归。与其将临时状态存储在堆栈上,不如为它们构建一个专用容器:

find(predicate, node):
    temp = [node]
    while not temp.empty():
        node = temp.pop()
        if predicate(node):
            return node # found it
        for child in temp.children:
            temp.push(child)
    return false # not found

现在,重要的一点是将调用深度移动到temp容器中。但是,让我们看一个细节,pushandpop调用,因为它们的作用并不完全清楚。如果您想模仿上述递归版本,则必须使用堆栈(LIFO)。此外,您必须以相反的顺序将孩子推入堆栈,但孩子的顺序可能无关紧要。这意味着在第一次迭代之后,您在容器中拥有给定节点的所有直接子节点。在第二次迭代中,一个直接子节点被删除并处理,它添加了该节点的子节点。换句话说,搜索首先进入树的深度,因此称为“深度优先搜索”(DFS)。

这种变化称为“广度优先搜索”(BFS)。在那里,您使用队列 (FIFO) 而不是堆栈 (LIFO) 作为容器。第一次迭代后的状态是相同的,都是给定节点的直接子节点。但是,它会检查这些孩子并将他们的孩子添加到容器中,但只有在检查完所有孩子后才开始检查孙子。

关于这种非递归方法的一句话:如果您将其作为进一步开发的基础,这同时会更加灵活。例如,如果您有多种方式可以到达同一个节点(即,如果它不是树),您可以将已经到达的所有子节点存储在第二个容器中以避免循环。另一种方法是根据孩子与解决方案的距离对他们进行排序,以免遵循不提供好处的路径。

一般来说,递归是一种很少使用的工具。理解它确实很重要,尤其是数学中的递归定义,但在编码中使用它通常是不切实际的。你会发现人们的想法不同,这更多的是一种观点,而不是一个可靠的主张,尽管我可以把一些经验和成功作为支持。

于 2018-03-13T06:33:16.463 回答
1

除了超过最大递归深度之外,我认为您的实现也可能会生成无限循环。由于values列表的范围是针对每个to_tree调用的,因此如果一个状态已经被访问过,就没有中心位置可以查找。这是一个基于堆栈的迭代示例,使用位表示来表示拼图状态,适合 4 * 9 = 36 位整数。例如:

123
456
780

将表示为:

0001 0010 0011
0100 0101 0110
0111 1000 0000

但反向链接在一起:

   0|   8|   7|   6|   5|   4|   3|   2|   1
0000 1000 0111 0110 0101 0100 0011 0010 0001

0b000010000111011001010100001100100001
=> 2271560481

initialState()
=> 2271560481

让我们添加一些函数来创建和显示状态:

from sys import stdout

def showState(state):
  mask = 15

  for i in xrange(9):
    if i % 3 == 0 and i > 0:
      stdout.write("\n")
    stdout.write(str((state & mask) >> 4 * i))
    mask = mask << 4

  stdout.write("\n")


def makeState(arr):
  state = 0

  for i in xrange(9):
    state = state | (arr[i] << 4 * i)

  return state


def initialState():
  return makeState([1,2,3,4,5,6,7,8,0])

现在我们需要找到零的索引:

def findZero(state):
  mask = 15
  i = 0

  while mask & state:
    mask = mask << 4
    i = i + 1

  return i

并将相邻的数字移动到零的单元格:

def move(state, fromIdx, toIdx):
  x = (state & (15 << 4 * fromIdx)) >> 4 * fromIdx
  state = state & (2**36 - 1 ^ (15 << 4 * fromIdx) ^ (15 << 4 * toIdx))
  state = state | (x << 4 * toIdx)

  return state


def moves(idx):
  # 012
  # 345
  # 678
  return [
    [1,3], [0,2,4], [1,5],
    [0,4,6], [1,3,5,7], [2,4,8],
    [3,7], [4,6,8], [5,7]
  ][idx]

让我们添加您正在使用的 Node 类的一个版本:

class Node(object):
  def __init__(self, state, parent=None):
    self.parent = parent
    self.state = state
    self.children = []

  def append(self, obj):
    self.children.append(obj)

设置根和一个全局对象 ,states_to_nodes它将访问状态映射到将该状态作为值保存的节点:

root = Node(initialState())
states_to_nodes = {initialState(): root}

这是一个避免最大递归深度错误的基于堆栈的迭代:

stack = [root]

while stack:
  node = stack.pop()
  zero_index = findZero(node.state)

  for i in moves(zero_index):
    maybe_new_state = move(node.state, i, zero_index)

    if not maybe_new_state in states_to_nodes:
      new_node = Node(maybe_new_state)
      states_to_nodes[maybe_new_state] = new_node
      node.append(new_node)

      stack.append(new_node)

    else:
      node.append(states_to_nodes[maybe_new_state])

输出:

example_state = makeState([5,1,3,8,6,0,2,7,4])

print "Example state:\n"
showState(example_state)

print "\nChildren states:\n"

for child in states_to_nodes[example_state].children:
  showState(child.state)
  print

"""
Example state:

513
860
274

Children states:

510
863
274

513
806
274

513
864
270
"""
于 2018-03-17T14:07:59.970 回答