创建没有重复的树

时间:2018-03-13 05:43:41

标签: python python-3.x recursion tree 8-puzzle

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

如果你不了解它,那就是:

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

你必须这样做:

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

基本上,每个州都会产生新的状态,具体取决于空白区域的移动方式(向上,向下,向左或向右)

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

你介意帮我实现吗?在此先感谢:)

这是我当前的代码,它会抛出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

2 个答案:

答案 0 :(得分: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容器。但是,让我们看一下详细信息pushpop来电,因为它们并不完全清楚它们的作用。如果你想模仿上面的递归版本,你将不得不使用堆栈(LIFO)。另外,你必须以相反的顺序将孩子们推到堆叠上,但孩子们的顺序可能无关紧要。这意味着在第一次迭代之后,您将拥有容器中给定节点的所有直接子节点。在第二次迭代中,删除并处理一个直接子节点,这将添加该节点的子节点。换句话说,搜索首先进入树的深度,因此称为“深度优先搜索”(DFS)。

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

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

通常,递归是一种很少使用的工具。理解它确实很重要,特别是数学中的递归定义,但在编码中使用它通常是不切实际的。你会发现那些有不同看法的人,这更像是一种观点,而不是一个可靠的主张,尽管我可以在其背后提供一些经验和成功来支持它。

答案 1 :(得分: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
"""