【问题标题】:Creating a tree without duplicates创建没有重复的树
【发布时间】:2018-03-13 05:43:40
【问题描述】:

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

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

[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

【问题讨论】:

  • 在您的情况下,if value not in values: 语句似乎始终评估为 True
  • 是的,它叫做puzzle8游戏。
  • 这里有一件事:因为你总是可以从 A 移动到 B,反之亦然,所以在树中将 A 置于 B 之下的两者之间不存在任何排序,也不存在将 B 置于 A 之下的任何排序。相反,您所拥有的是一个图,其中顶点是状态,边是滑动一个图块的原子操作。

标签: python python-3.x recursion tree sliding-tile-puzzle


【解决方案1】:

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

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) 作为容器。第一次迭代后的状态是相同的,都是给定节点的直接子节点。但是,它会检查这些孩子并将他们的孩子添加到容器中,但只有在检查完所有孩子后才会开始检查孙子。

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

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

【讨论】:

  • 感谢您的详细见解,您介意帮助我如何实现这一点来搜索父节点而不是子节点吗?或者检查树中从根到最后一片叶子的每个项目的最佳方法是什么? (不好意思问太多了,我基本上都是从Python开始的)
【解决方案2】:

除了超出最大递归深度之外,我认为您的实现还可能会生成无限循环。由于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
"""

【讨论】:

  • 嗨,很抱歉这么晚才回来...您介意解释一下我如何获取每个孩子的孩子状态并将其添加到树中,同时保持整个树没有任何重复吗?由于您当前的解决方案仅显示初始状态的子级。提前致谢
  • @JahirFiquitiva 确定:states_to_nodes 是一个字典,将一个状态(一个状态是我们存储在 36 位中的 9 个数字的排列)映射到它的孩子。 while 循环检查状态是否已经存储,if not maybe_new_state in states_to_nodes:,并且仅在状态尚未映射时才添加子项。这导致能够查找每个州的孩子。然而,在这个数据结构中,子节点可以指向他们的祖先。
猜你喜欢
  • 2011-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-19
  • 1970-01-01
相关资源
最近更新 更多