Python 如何用改进的DFS算法遍历循环有向图

Python 如何用改进的DFS算法遍历循环有向图,python,algorithm,python-2.7,depth-first-search,demoscene,Python,Algorithm,Python 2.7,Depth First Search,Demoscene,概述 我试图找出如何使用某种DFS迭代算法遍历有向循环图。下面是我目前实现的一个小型mcve版本(它不涉及周期): 类节点(对象): 定义初始化(self,name): self.name=名称 def启动(自): 打印“{}\u start.”格式(自) def中级(自我): 打印“{}\u middle”。格式(自) def端(自身): 打印“{}\u end.”格式(自) 定义(自我): 返回“{0}”。格式(self.name) 类节点重复(节点): 定义初始化(self、name、nu

概述

我试图找出如何使用某种DFS迭代算法遍历有向循环图。下面是我目前实现的一个小型mcve版本(它不涉及周期):

类节点(对象):
定义初始化(self,name):
self.name=名称
def启动(自):
打印“{}\u start.”格式(自)
def中级(自我):
打印“{}\u middle”。格式(自)
def端(自身):
打印“{}\u end.”格式(自)
定义(自我):
返回“{0}”。格式(self.name)
类节点重复(节点):
定义初始化(self、name、num\u repeats=1):
超级(NodeRepeat,self)。\uuuu init\uuuu(名称)
self.num\u repeats=num\u repeats
def dfs(图表,开始):
“”“使用带有反向子节点的DFS从开始节点遍历图形”“”
访问={}
堆栈=[(开始“”)]
堆栈时:
#要转换dfs->bfs
#a)将堆栈重命名为队列
#b)pop变为pop(0)
节点,父节点=stack.pop()
如果父项为“无”:
如果访问[节点]<3:
node.end()
已访问[节点]=3
未访问的elif节点:
如果已访问。获取(父项)=2:
parent.middle()
elif已访问。获取(父项)==1:
到访[家长]=2
node.start()
已访问[节点]=1
stack.append((节点,无))
#也许你想要一个不同的顺序,如果是这样的话,不要使用相反的顺序
childs=reversed(graph.get(节点,[]))
对于儿童中的儿童:
如果未探访儿童:
stack.append((子节点、节点))
如果名称=“\uuuuu main\uuuuuuuu”:
Sequence1=节点('Sequence1')
MtxPushPop1=节点('MtxPushPop1')
Rotate1=节点('Rotate1')
Repeat1=NodeRepeat('Repeat1',num_repeats=2)
Sequence2=节点('Sequence2')
MtxPushPop2=节点('MtxPushPop2')
Translate=节点('Translate')
Rotate2=节点('Rotate2')
Rotate3=节点('Rotate3')
比例=节点(“比例”)
Repeat2=NodeRepeat('Repeat2',num_repeats=3)
网格=节点(“网格”)
循环图={
Sequence1:[MtxPushPop1,Rotate1],
MTXPUSHPP1:[Sequence2],
旋转1:[重复1],
Sequence2:[MTXPUSHPP2,翻译],
Repeat1:[Sequence1],
MtxPushPop2:[Rotate2],
翻译:[旋转3],
旋转2:[比例],
旋转3:[重复2],
比例:[网格],
重复2:[序列2]
}
dfs(循环图,序列1)
打印'-'*80
a=节点('a')
b=节点('b')
dfs({
a:[b],
b:[a]
},a)
上面的代码正在测试两种情况,第一种是下图的某种表示:

第二个是一个图包含一个“无限”循环
{a->b,b->a}

要求

  • 不会存在“无限循环”这样的东西,比如说,当发现一个“无限循环”时,会有一个最大阈值(全局变量)来指示何时停止围绕这些“伪无限循环”循环
  • 所有图形节点都能够创建循环,但将存在一个名为
    Repeat
    的特殊节点,您可以在其中指示循环的迭代次数
  • 上面我发布的mcve是遍历算法的迭代版本,不知道如何处理循环图。理想情况下,解决方案也是迭代的,但如果存在更好的递归解决方案,那就太好了
  • 我们在这里讨论的数据结构不应该被称为“有向无环图”,因为在这种情况下,每个节点都有其子节点的顺序,而在图中节点连接没有顺序
  • 任何东西都可以连接到编辑器中的任何东西。您将能够执行任何块组合,唯一的限制是执行计数器,如果您进行了Neverening循环或太多迭代,它将溢出
  • 与上面的代码片段类似,该算法将保留start/middle/after节点的方法执行
问题

有谁能提供某种知道如何遍历无限/有限循环的解决方案吗

参考资料

如果这个问题现在还不清楚,您可以阅读更多关于这个问题的文章,整个想法将是使用遍历算法来实现类似于那篇文章中所示的工具

下面是一个屏幕截图,展示了这种数据结构的全部功能,我想了解如何遍历和运行:

在我开始之前,我也希望这些评论能详细说明我所做的工作。如果您需要更多的解释,请看我在代码下面对递归方法的解释

# If you don't want global variables, remove the indentation procedures
indent = -1

MAX_THRESHOLD = 10
INF = 1 << 63

def whitespace():
    global indent
    return '|  ' * (indent)

class Node:
    def __init__(self, name, num_repeats=INF):
        self.name = name
        self.num_repeats = num_repeats

    def start(self):
        global indent
        if self.name.find('Sequence') != -1:
            print whitespace()
            indent += 1
        print whitespace() + '%s_start' % self.name

    def middle(self):
        print whitespace() + '%s_middle' % self.name

    def end(self):
        global indent
        print whitespace() + '%s_end' % self.name
        if self.name.find('Sequence') != -1:
            indent -= 1
            print whitespace()

def dfs(graph, start):
    visits = {}
    frontier = [] # The stack that keeps track of nodes to visit

    # Whenever we "visit" a node, increase its visit count
    frontier.append((start, start.num_repeats))
    visits[start] = visits.get(start, 0) + 1

    while frontier:
        # parent_repeat_count usually contains vertex.repeat_count
        # But, it may contain a higher value if a repeat node is its ancestor
        vertex, parent_repeat_count = frontier.pop()

        # Special case which signifies the end
        if parent_repeat_count == -1:
            vertex.end()
            # We're done with this vertex, clear visits so that 
            # if any other node calls us, we're still able to be called
            visits[vertex] = 0
            continue

        # Special case which signifies the middle
        if parent_repeat_count == -2:
            vertex.middle()
            continue  

        # Send the start message
        vertex.start()

        # Add the node's end state to the stack first
        # So that it is executed last
        frontier.append((vertex, -1))

        # No more children, continue
        # Because of the above line, the end method will
        # still be executed
        if vertex not in graph:
            continue

        ## Uncomment the following line if you want to go left to right neighbor
        #### graph[vertex].reverse()

        for i, neighbor in enumerate(graph[vertex]):
            # The repeat count should propagate amongst neighbors
            # That is if the parent had a higher repeat count, use that instead
            repeat_count = max(1, parent_repeat_count)
            if neighbor.num_repeats != INF:
                repeat_count = neighbor.num_repeats

            # We've gone through at least one neighbor node
            # Append this vertex's middle state to the stack
            if i >= 1:
                frontier.append((vertex, -2))

            # If we've not visited the neighbor more times than we have to, visit it
            if visits.get(neighbor, 0) < MAX_THRESHOLD and visits.get(neighbor, 0) < repeat_count:
                frontier.append((neighbor, repeat_count))
                visits[neighbor] = visits.get(neighbor, 0) + 1

def dfs_rec(graph, node, parent_repeat_count=INF, visits={}):
    visits[node] = visits.get(node, 0) + 1

    node.start()

    if node not in graph:
        node.end()
        return

    for i, neighbor in enumerate(graph[node][::-1]):
        repeat_count = max(1, parent_repeat_count)
        if neighbor.num_repeats != INF:
            repeat_count = neighbor.num_repeats

        if i >= 1:
            node.middle()

        if visits.get(neighbor, 0) < MAX_THRESHOLD and visits.get(neighbor, 0) < repeat_count:
            dfs_rec(graph, neighbor, repeat_count, visits)

    node.end()  
    visits[node] = 0

Sequence1 = Node('Sequence1')
MtxPushPop1 = Node('MtxPushPop1')
Rotate1 = Node('Rotate1')
Repeat1 = Node('Repeat1', 2)

Sequence2 = Node('Sequence2')
MtxPushPop2 = Node('MtxPushPop2')
Translate = Node('Translate')
Rotate2 = Node('Rotate2')
Rotate3 = Node('Rotate3')
Scale = Node('Scale')
Repeat2 = Node('Repeat2', 3)
Mesh = Node('Mesh')

cyclic_graph = {
        Sequence1: [MtxPushPop1, Rotate1],
        MtxPushPop1: [Sequence2],
        Rotate1: [Repeat1],
        Sequence2: [MtxPushPop2, Translate],
        Repeat1: [Sequence1],
        MtxPushPop2: [Rotate2],
        Translate: [Rotate3],
        Rotate2: [Scale],
        Rotate3: [Repeat2],
        Scale: [Mesh],
        Repeat2: [Sequence2]
    }

dfs(cyclic_graph, Sequence1)

print '-'*40

dfs_rec(cyclic_graph, Sequence1)

print '-'*40

dfs({Sequence1: [Translate], Translate: [Sequence1]}, Sequence1)

print '-'*40

dfs_rec({Sequence1: [Translate], Translate: [Sequence1]}, Sequence1)
#如果不需要全局变量,请删除缩进过程
缩进=-1
最大阈值=10
INF=1=1:
node.middle()
如果访问.get(邻居,0)# If you don't want global variables, remove the indentation procedures
indent = -1

MAX_THRESHOLD = 10
INF = 1 << 63

def whitespace():
    global indent
    return '|  ' * (indent)

class Node:
    def __init__(self, name, num_repeats=INF):
        self.name = name
        self.num_repeats = num_repeats

    def start(self):
        global indent
        if self.name.find('Sequence') != -1:
            print whitespace()
            indent += 1
        print whitespace() + '%s_start' % self.name

    def middle(self):
        print whitespace() + '%s_middle' % self.name

    def end(self):
        global indent
        print whitespace() + '%s_end' % self.name
        if self.name.find('Sequence') != -1:
            indent -= 1
            print whitespace()

def dfs(graph, start):
    visits = {}
    frontier = [] # The stack that keeps track of nodes to visit

    # Whenever we "visit" a node, increase its visit count
    frontier.append((start, start.num_repeats))
    visits[start] = visits.get(start, 0) + 1

    while frontier:
        # parent_repeat_count usually contains vertex.repeat_count
        # But, it may contain a higher value if a repeat node is its ancestor
        vertex, parent_repeat_count = frontier.pop()

        # Special case which signifies the end
        if parent_repeat_count == -1:
            vertex.end()
            # We're done with this vertex, clear visits so that 
            # if any other node calls us, we're still able to be called
            visits[vertex] = 0
            continue

        # Special case which signifies the middle
        if parent_repeat_count == -2:
            vertex.middle()
            continue  

        # Send the start message
        vertex.start()

        # Add the node's end state to the stack first
        # So that it is executed last
        frontier.append((vertex, -1))

        # No more children, continue
        # Because of the above line, the end method will
        # still be executed
        if vertex not in graph:
            continue

        ## Uncomment the following line if you want to go left to right neighbor
        #### graph[vertex].reverse()

        for i, neighbor in enumerate(graph[vertex]):
            # The repeat count should propagate amongst neighbors
            # That is if the parent had a higher repeat count, use that instead
            repeat_count = max(1, parent_repeat_count)
            if neighbor.num_repeats != INF:
                repeat_count = neighbor.num_repeats

            # We've gone through at least one neighbor node
            # Append this vertex's middle state to the stack
            if i >= 1:
                frontier.append((vertex, -2))

            # If we've not visited the neighbor more times than we have to, visit it
            if visits.get(neighbor, 0) < MAX_THRESHOLD and visits.get(neighbor, 0) < repeat_count:
                frontier.append((neighbor, repeat_count))
                visits[neighbor] = visits.get(neighbor, 0) + 1

def dfs_rec(graph, node, parent_repeat_count=INF, visits={}):
    visits[node] = visits.get(node, 0) + 1

    node.start()

    if node not in graph:
        node.end()
        return

    for i, neighbor in enumerate(graph[node][::-1]):
        repeat_count = max(1, parent_repeat_count)
        if neighbor.num_repeats != INF:
            repeat_count = neighbor.num_repeats

        if i >= 1:
            node.middle()

        if visits.get(neighbor, 0) < MAX_THRESHOLD and visits.get(neighbor, 0) < repeat_count:
            dfs_rec(graph, neighbor, repeat_count, visits)

    node.end()  
    visits[node] = 0

Sequence1 = Node('Sequence1')
MtxPushPop1 = Node('MtxPushPop1')
Rotate1 = Node('Rotate1')
Repeat1 = Node('Repeat1', 2)

Sequence2 = Node('Sequence2')
MtxPushPop2 = Node('MtxPushPop2')
Translate = Node('Translate')
Rotate2 = Node('Rotate2')
Rotate3 = Node('Rotate3')
Scale = Node('Scale')
Repeat2 = Node('Repeat2', 3)
Mesh = Node('Mesh')

cyclic_graph = {
        Sequence1: [MtxPushPop1, Rotate1],
        MtxPushPop1: [Sequence2],
        Rotate1: [Repeat1],
        Sequence2: [MtxPushPop2, Translate],
        Repeat1: [Sequence1],
        MtxPushPop2: [Rotate2],
        Translate: [Rotate3],
        Rotate2: [Scale],
        Rotate3: [Repeat2],
        Scale: [Mesh],
        Repeat2: [Sequence2]
    }

dfs(cyclic_graph, Sequence1)

print '-'*40

dfs_rec(cyclic_graph, Sequence1)

print '-'*40

dfs({Sequence1: [Translate], Translate: [Sequence1]}, Sequence1)

print '-'*40

dfs_rec({Sequence1: [Translate], Translate: [Sequence1]}, Sequence1)
def dfs_rec(graph, node, parent_repeat_count=INF, visits={}):
    visits[node] = visits.get(node, 0) + 1

    node.start()

    if node not in graph:
        node.end()
        return

    for i, neighbor in enumerate(graph[node][::-1]):
        repeat_count = max(1, parent_repeat_count)
        if neighbor.num_repeats != INF:
            repeat_count = neighbor.num_repeats

        if i >= 1:
            node.middle()

        if visits.get(neighbor, 0) < MAX_THRESHOLD and visits.get(neighbor, 0) < repeat_count:
            dfs_rec(graph, neighbor, repeat_count, visits)

    node.end()  
    visits[node] = 0
def traverse(graph,node,process):
    seen={node}
    current_level=[node]
    while current_level:
        next_level=[]
        for node in current_level:
            process(node)
            for child in (link for link in graph.get(node,[]) if link not in seen):
                next_level.append(child)
                seen.add(child)
        current_level=next_level
In [24]: traverse(cyclic_graph,Sequence1,process)
Sequence1
MtxPushPop1
Rotate1
Sequence2
Repeat1
MtxPushPop2
Translate
Rotate2
Rotate3
Scale
Repeat2
Mesh