作为警告,我在python中仍然缺乏经验
我正在尝试使用networkx库执行有向图的传递减少。我已经找到了算法,但是我在实现它时遇到了麻烦。经过快速搜索,我在其他堆栈交换问题中找到了与我类似的算法,但没有演示如何对算法进行实际编码。
这是我的想法:
For X in Nodes
For Y in Nodes
For z in Nodes
if (x,y) != (y,z) and (x,y) != (x,z)
if edges xy and yz are in Graph
delete xz
这是我在python中表达这一点的尝试:
G = graph
N = G.Nodes()
for x in N:
for y in N:
for z in N:
if (x,y) != (y,z) and (x,y) != (x,z):
if (x,y) and (y,z) in G:
G.remove_edge(x,z)
我认为我没有正确地调用网络中每个边缘的排列,并且正在考虑尝试使用itertools。即使我有各种可能的排列,我也不知道如何用这些信息实现算法。
任何帮助都会很精彩。谢谢!
答案 0 :(得分:3)
如果我正确地阅读了tred
实用程序的源代码(它计算了dot fromat中图形的传递减少作为graphviz实用程序的一部分),那么它使用的算法如下:遍历所有顶点对于图形和每个顶点,在每个子节点上执行DFS。对于每个遍历的顶点,从原始父顶点到该顶点移除任何边。
我使用networkx实现了这个算法,如下所示:
g = nx.read_graphml("input.dot")
for n1 in g.nodes_iter():
if g.has_edge(n1, n1):
g.remove_edge(n1, n1)
for n2 in g.successors(n1):
for n3 in g.successors(n2):
for n4 in nx.dfs_preorder_nodes(g, n3):
if g.has_edge(n1, n4):
g.remove_edge(n1, n4)
nx.write_graphml(g, "output.dot")
嵌套级别表明了一种可怕的复杂性,但它实际上做的是已经执行了DFS的前两个步骤,这将在稍后执行。这样做可以避免在DFS部分中检查当前节点是否是父节点的直接后继节点(因为networkx的DFS结果包括遍历从中开始的顶点)。因此,下面的替代公式将更好地显示此算法的复杂性,但由于在最内部循环中进行额外检查,因此也稍微慢了一点:
g = nx.read_graphml("input.dot")
for n1 in g.nodes_iter():
c = set(g.successors(n1))
for n2 in nx.dfs_preorder_nodes(g, n1):
if n2 in c:
continue
if g.has_edge(n1, n2):
g.remove_edge(n1, n2)
nx.write_graphml(g, "output.dot")
操作has_edge
和remove_edge
使用Python词典,因此平均O(1)
。 DFS的最坏情况时间复杂度为O(E)
,其中E
是图中边的数量。由于DFS执行N
次(N
是顶点数),因此上述算法的时间复杂度为O(NE)
。
另一个有趣的观察是,上面的Python代码如何比graphviz套件中的tred
实用程序快几个数量级。我不知道为什么会这样。下面是非循环图上运行时间与482个顶点和9656个边的比较:
我还尝试了一个带有14956个顶点和190993个边缘的图形上的另一个基准测试,但是当上面的Python代码在11分钟内完成时,tred
在编写本文时运行2小时后仍然运行。
编辑:7天3小时后,tred
仍然在该图表上搅拌。我现在放弃了。
答案 1 :(得分:2)
以下似乎有效,至少对于我提供的示例数据。如果你有一个特定的案例,那么它就没有用。
import random
import pprint
class Graph:
nodes = []
edges = []
removed_edges = []
def remove_edge(self,x,y):
e = (x,y)
try:
self.edges.remove(e)
print("Removed edge %s" % str(e))
self.removed_edges.append(e)
except:
print("Attempted to remove edge %s, but it wasn't there" % str(e))
def Nodes(self):
return self.nodes
# Sample data
def __init__(self):
self.nodes = [1,2,3,4,5]
self.edges = [
(1,2),
(1,3),
(1,4),
(1,5),
(2,4),
(3,4),
(3,5),
(4,5),
]
G = Graph()
N = G.Nodes()
for x in N:
for y in N:
for z in N:
#print("(%d,%d,%d)" % (x,y,z))
if (x,y) != (y,z) and (x,y) != (x,z):
if (x,y) in G.edges and (y,z) in G.edges:
G.remove_edge(x,z)
print("Removed edges:")
pprint.pprint(G.removed_edges)
print("Remaining edges:")
pprint.pprint(G.edges)
输出:
Removed edge (1, 4)
Attempted to remove edge (1, 4), but it wasn't there
Removed edge (1, 5)
Attempted to remove edge (2, 5), but it wasn't there
Removed edge (3, 5)
Removed edges:
[(1, 4), (1, 5), (3, 5)]
Remaining edges:
[(1, 2), (1, 3), (2, 4), (3, 4), (4, 5)]
答案 2 :(得分:0)
if (x,y) and (y,z) in G:
这需要写成:
if (x,y) in G and (y,z) in G:
答案 3 :(得分:0)
与此同时,在networkx
中内置了传递减少。
import networkx as nx
G = nx.DiGraph()
G.add_edges_from([(0,1),(0,2),(0,3),(0,4),(1,2),(1,4),(1,4),(3,4)])
H = nx.transitive_reduction(G)
print(H.edges)
# [(0, 1), (0, 3), (1, 2), (1, 4), (3, 4)]