使用Python和DFS算法的递归深度问题

时间:2015-02-22 17:11:21

标签: algorithm python-3.x recursion

DFS算法已经在处理小型测试用例,但是当我用大量样本运行它时会抛出" RuntimeError:超出最大递归深度",所以我包含了sys.setrecursionlimit(10 ** 6)和系统抛出这条消息:" python.exe停止工作"并且PyCharm抛出消息:"处理以退出代码-1073741571(0xC00000FD)"结束。 您可以下载sample

的zip文件

代码:

import sys
import threading

threading.stack_size(67108864)
sys.setrecursionlimit(10 ** 6)


def get_input(filename):
    graph_map = {}

    for line in open(filename, 'r').readlines():
        values = [int(val) for val in line.split()]
        key1 = values.pop(0)
        key2 = values.pop(0)

        if not key1 in graph_map:
            graph_map[key1] = []

        if not key2 in graph_map:
            graph_map[key2] = []

        graph_map[key1].extend([key2])

    return graph_map


def DFS(graph_map, start):
    global visited

    visited[start-1] = True

    for child in graph_map[start]:
        if visited[child-1] is False:
            DFS(graph_map, child)


def DFS_Loop(graph_map):
    global visited

    i = len(graph_map)  # max(graph_map.keys())
    for i in reversed(range(1, i+1)):
        if visited[i-1] is False:
            DFS(graph_map, i)


graph_map = get_input("SCC.txt")
visited = [False]*len(graph_map)  # size of the graph
DFS_Loop(graph_map)

有没有办法在不带走递归的情况下实现这一目标?

提前致谢。

2 个答案:

答案 0 :(得分:3)

Python的一个很酷的事情是迭代器只是一种普通的数据类型。虽然通常使用循环和理解进行迭代,但如果方便的话,没有什么能阻止你手动进行迭代。它可能很方便的一个原因是通过用显式堆栈替换它来避免深度递归。

虽然这确实“消除了递归”,但它并没有使程序复杂化,并且递归结构仍然很明显。 Python根本不会优雅地进行递归,因此这种转换通常很有用。

写作时

for child in graph_map[start]:
    ...

你做的事与以下非常相似:

it = iter(graph_map[start])
try:
  child = next(it)
  ...
except StopIteration:
  pass

[见注1]

iter函数返回集合的迭代器(或其他可迭代对象,例如理解,生成器或范围)。 next方法返回下一个值,并推进迭代器;如果没有下一个值,则会引发StopIteration异常。

DFS函数只是为其参数的每个未访问子项递归调用自身。我们可以使用迭代器堆栈来精确模拟该行为。我们将迭代器推送到迭代器堆栈上,而不是递归调用节点。当堆栈顶部的迭代器终止时,我们将它从堆栈中弹出。

def DFS(graph_map):
  visited = [False] * len(graph_map)
  # By initializing the stack with a range iterator, we do
  # the equivalent of DFS_Loop.
  # In Python2, you should use xrange instead of range
  stack = [iter(range(len(graph_map), 0, -1))]
  while stack:
    try:
      child = next(stack[-1])
      if not visited[child - 1]:
        visited[child - 1] = True
        # Do whatever you want to do in the visit
        stack.append(iter(graph_map[child]))
    except StopIteration:
      stack.pop()

应用于OP中提供的文件中的样本数据的上述函数最多使用62794个堆栈槽。在我的Linux笔记本电脑上,一旦数据被读入,它花了大约3秒钟;我没有准确地计时。

值得注意的是,只需将堆栈更改为队列即可将上述内容更改为进行广度优先搜索。对于深度搜索,首先,堆栈必须是迭代器的堆栈(或等效的,在不那么简单的语言中);对于广度搜索,队列可能是迭代器的队列,但它也可以使用值队列。


注释

  1. 实际的虚拟机使用定义的迭代器协议,该协议在Python2和Python3之间略有不同。在这两个版本中,容器(或其他可迭代的)异议都有一个名为__iter__的成员函数,它返回一个新创建的迭代器对象。迭代器对象在Python2中有一个名为next的方法,在Python3中有一个__next__,它返回当前值并推进迭代器。从2.6开始,您可以使用iternext全局函数来避免担心这些细节,这就是我在这里所做的。

答案 1 :(得分:0)

您需要更改系统堆栈限制。在linux上你可以这样做:

ulimit -s 1000000(对于Windows检查here

然后我运行你的程序,它正常退出。