[英]How to traverse cyclic directed graphs with modified DFS algorithm
OVERVIEW 概述
I'm trying to figure out how to traverse directed cyclic graphs using some sort of DFS iterative algorithm. 我试图找出如何使用某种DFS迭代算法遍历有向循环图 。 Here's a little mcve version of what I currently got implemented (it doesn't deal with cycles):
这是我目前实现的一个小版本(它不涉及周期):
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)
The above code is testing a couple of cases, the first would be some sort of representation of the below graph: 上面的代码测试了几个案例,第一个是下图的某种表示:
The second one is the simplest case of one graph containing one "infinite" loop {a->b, b->a}
第二个是最简单的情况,一个图包含一个“无限”循环
{a->b, b->a}
REQUIREMENTS 要求
Repeat
where you can indicate how many iterations to loop around the cycle Repeat
的特殊节点,您可以在其中指示循环循环的循环次数 QUESTION 题
Could anyone provide some sort of solution which knows how to traverse infinite/finite cycles? 任何人都可以提供某种知道如何遍历无限/有限循环的解决方案吗?
REFERENCES 参考
If question is not clear yet at this point, you can read this more about this problem on this article , the whole idea will be using the traversal algorithm to implement a similar tool like the shown in that article. 如果问题还不清楚在这一点上,你可以阅读更多有关这对这个问题的文章 ,整个构思将使用遍历算法来实现这样的文章中所示的类似的工具。
Here's a screenshot showing up the whole power of this type of data structure I want to figure out how to traverse&run: 这是一个屏幕截图,显示了这种数据结构的全部功能我想弄清楚如何遍历和运行:
Before I start, Run the code on CodeSkulptor! 在开始之前, 在CodeSkulptor上运行代码! I also hope that the comments elaborate what I have done enough.
我也希望这些评论能够详细说明我所做的工作。 If you need more explanation, look at my explanation of the recursive approach below the code.
如果您需要更多解释,请查看我对代码下面的递归方法的解释。
# 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)
The input and (well formatted and indented) output can be found here . 输入和(格式良好和缩进)输出可以在这里找到。 If you want to see how I formatted the output, please refer to the code, which can also be found on CodeSkulptor .
如果您想查看我如何格式化输出,请参阅代码,该代码也可以在CodeSkulptor上找到 。
Right, on to the explanation. 对,解释。 The easier to understand but much more inefficient recursive solution, which I'll use to help explain, follows:
更容易理解但更低效的递归解决方案,我将用它来帮助解释,如下:
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
start
event of the node. start
事件。 end
event and return. end
事件并返回。 graph[node][::-1]
) in the recursive version to maintain the same order (right to left) of traversal of neighbors as in the iterative version. graph[node][::-1]
)来维持邻居遍历的相同顺序(从右到左),如在迭代版本中那样。
middle
event of the current node ( not the neighbor) if the second (or greater) neighbor is being processed. middle
事件。 MAX_THRESHOLD
times (for pseudo-infinite cycles) and b) the above calculated repeat count times. MAX_THRESHOLD
次(对于伪无限循环)和b)上述计算的重复计数次数来完成可访问性检查。 end
event and clear its visits in the hashtable. end
事件并清除其在哈希表中的访问。 This is done so that if some other node calls it again, it does not fail the visitability check and/or execute for less than the required number of times. As per comment66244567 - reducing the graph to a tree by ignoring links to visited nodes and performing a breadth-first search, as this would produce a more natural-looking (and likely more balanced) tree: 根据评论66244567 - 通过忽略到访问节点的链接并执行广度优先搜索将图形缩减为树,因为这将生成更自然(并且可能更平衡)的树:
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
With your graph and def process(node): print node
, this produces: 使用图形和
def process(node): print node
,这会产生:
In [24]: traverse(cyclic_graph,Sequence1,process)
Sequence1
MtxPushPop1
Rotate1
Sequence2
Repeat1
MtxPushPop2
Translate
Rotate2
Rotate3
Scale
Repeat2
Mesh
The other BFS algorithm, iterative deepening DFS (uses less memory at the cost of speed) isn't going to win you anything in this case: since you have to store references to visited nodes, you already consume O(n) memory. 另一个BFS算法, 迭代加深DFS (以速度为代价使用更少的内存)在这种情况下不会赢得任何东西:因为你必须存储对被访问节点的引用,你已经消耗了O(n)内存。 Neither you need to produce intermediate results (but you can anyway - eg
yield
something after processing a level). 你不需要产生中间结果(但无论如何你都可以 - 例如在处理一个级别后
yield
一些东西)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.