gpt4 book ai didi

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

转载 作者:IT老高 更新时间:2023-10-28 20:29:17 25 4
gpt4 key购买 nike

概览

我试图弄清楚如何使用某种 DFS 迭代算法遍历 有向循环图。这是我目前实现的一个小 mcve 版本(它不处理循环):

class Node(object):

def __init__(self, name):
self.name = name

def start(self):
print '{}_start'.format(self)

def middle(self):
print '{}_middle'.format(self)

def end(self):
print '{}_end'.format(self)

def __str__(self):
return "{0}".format(self.name)


class NodeRepeat(Node):

def __init__(self, name, num_repeats=1):
super(NodeRepeat, self).__init__(name)
self.num_repeats = num_repeats


def dfs(graph, start):
"""Traverse graph from start node using DFS with reversed childs"""

visited = {}
stack = [(start, "")]
while stack:
# To convert dfs -> bfs
# a) rename stack to queue
# b) pop becomes pop(0)
node, parent = stack.pop()
if parent is None:
if visited[node] < 3:
node.end()
visited[node] = 3
elif node not in visited:
if visited.get(parent) == 2:
parent.middle()
elif visited.get(parent) == 1:
visited[parent] = 2

node.start()
visited[node] = 1

stack.append((node, None))

# Maybe you want a different order, if it's so, don't use reversed
childs = reversed(graph.get(node, []))
for child in childs:
if child not in visited:
stack.append((child, node))


if __name__ == "__main__":
Sequence1 = Node('Sequence1')
MtxPushPop1 = Node('MtxPushPop1')
Rotate1 = Node('Rotate1')
Repeat1 = NodeRepeat('Repeat1', num_repeats=2)

Sequence2 = Node('Sequence2')
MtxPushPop2 = Node('MtxPushPop2')
Translate = Node('Translate')
Rotate2 = Node('Rotate2')
Rotate3 = Node('Rotate3')
Scale = Node('Scale')
Repeat2 = NodeRepeat('Repeat2', num_repeats=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 '-'*80

a = Node('a')
b = Node('b')
dfs({
a : [b],
b : [a]
}, a)

上面的代码正在测试几个案例,第一个是下图的某种表示:

enter image description here

第二种是一个图包含一个“无限”循环的最简单情况{a->b, b->a}

要求

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

问题

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

引用文献

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

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

enter image description here

最佳答案

在我开始之前,Run the code on CodeSkulptor!我也希望评论能详细说明我所做的足够多的事情。如果您需要更多解释,请查看我在代码下方对 recursive 方法的解释。

# 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)

输入和(格式和缩进良好的)输出可以在 here 中找到.如果想看如何我格式化输出,请引用代码,也可以是found on CodeSkulptor .


好的,继续解释。下面是更容易理解但效率更低的递归解决方案,我将使用它来帮助解释:

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
  1. 我们要做的第一件事就是访问节点。我们通过增加字典中节点的访问次数来做到这一点。
  2. 然后我们引发节点的 start 事件。
  3. 我们做一个简单的检查,看看该节点是否是无子节点(叶)。如果是,我们引发 end 事件并返回。
  4. 现在我们已经确定节点有邻居,我们遍历每个邻居。 旁注:我在递归版本中反转邻居列表(通过使用graph[node][::-1])以保持相同的顺序(从右到左)在迭代版本中遍历邻居。
    1. 对于每个邻居,我们首先计算重复次数。重复计数从祖先节点传播(继承),因此使用继承的重复计数除非邻居包含重复计数值。
    2. 如果正在处理第二个(或更大的)邻居,我们会引发当前节点(不是邻居)的 middle 事件。
    3. 如果邻居可以被访问,则邻居被访问。可访问性检查是通过检查邻居被访问的次数是否少于 a) MAX_THRESHOLD 次(对于伪无限循环)和 b) 以上计算的重复计数次数。
  5. 我们现在完成了这个节点;引发 end 事件并清除其在哈希表中的访问。这样做是为了如果其他节点再次调用它,它不会使可访问性检查失败和/或执行的次数少于所需的次数。

关于python - 如何使用改进的 DFS 算法遍历循环有向图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39427638/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com