Мой предыдущий ответ касается конкретно прямого вопроса о том, есть ли хороший способ проверить, является ли одно ребро избыточным.
Похоже, вам действительно нужен способ эффективного удаления всех избыточных ребер. Это означает, что вам нужен способ делать их все сразу. Это другой вопрос, но вот ответ на него. Я не верю, что в networkx есть что-то для этого встроенное, но найти работоспособный алгоритм несложно.
Идея состоит в том, что, поскольку это группа DAG, некоторые узлы не имеют внешних ребер. Начните с них и обработайте их. После того, как все они обработаны, у подмножества их родителей нет дочерних элементов, которые не были обработаны. Пройдите через этих родителей. Повторение. На каждом этапе набор необработанных узлов представляет собой DAG, и мы обрабатываем «конечные узлы» этого DAG. Гарантированно завершится (если исходная сеть конечна).
В реализации всякий раз, когда мы обрабатываем узел, мы сначала проверяем, является ли какой-либо дочерний элемент также косвенным потомком. Если да, удалите край. Если нет, оставьте это. Когда все дочерние элементы обработаны, мы обновляем информацию для его родителей, добавляя всех его потомков к набору косвенных потомков для родителя. Если все дочерние элементы родительского элемента обработаны, мы добавляем его в наш список для следующей итерации.
import networkx as nx
from collections import defaultdict
def remove_redundant_edges(G):
processed_child_count = defaultdict(int) #when all of a nodes children are processed, we'll add it to nodes_to_process
descendants = defaultdict(set) #all descendants of a node (including children)
out_degree = {node:G.out_degree(node) for node in G.nodes_iter()}
nodes_to_process = [node for node in G.nodes_iter() if out_degree[node]==0] #initially it's all nodes without children
while nodes_to_process:
next_nodes = []
for node in nodes_to_process:
'''when we enter this loop, the descendants of a node are known, except for direct children.'''
for child in G.neighbors(node):
if child in descendants[node]: #if the child is already an indirect descendant, delete the edge
G.remove_edge(node,child)
else: #otherwise add it to the descendants
descendants[node].add(child)
for predecessor in G.predecessors(node): #update all parents' indirect descendants
descendants[predecessor].update(descendants[node])
processed_child_count[predecessor]+=1 #we have processed one more child of this parent
if processed_child_count[predecessor] == out_degree[predecessor]: #if all children processed, add to list for next iteration.
next_nodes.append(predecessor)
nodes_to_process=next_nodes
Чтобы проверить это:
G=nx.DiGraph()
G.add_nodes_from(['termsequence', 'maximumdegree', 'emptymultigraph', 'minimum', 'multiset', 'walk', 'nonemptymultigraph', 'euleriantrail', 'nonnullmultigraph', 'cycle', 'loop', 'abwalk', 'endvertices', 'simplegraph', 'vertex', 'multipletrails', 'edge', 'set', 'stroll', 'union', 'trailcondition', 'nullmultigraph', 'trivialmultigraph', 'sequence', 'multiplepaths', 'path', 'degreevertex', 'onedgesonvertices', 'nontrivialmultigraph', 'adjacentedges', 'adjacentvertices', 'simpleedge', 'maximum', 'multipleloops', 'length', 'circuit', 'class', 'euleriangraph', 'incident', 'minimumdegree', 'orderedpair', 'unique', 'closedwalk', 'multipleedges', 'pathcondition', 'multigraph', 'trail'])
G.add_edges_from([('termsequence', 'endvertices'), ('emptymultigraph', 'nonemptymultigraph'), ('minimum', 'minimumdegree'), ('multiset', 'trailcondition'), ('multiset', 'pathcondition'), ('multiset', 'multigraph'), ('walk', 'length'), ('walk', 'closedwalk'), ('walk', 'abwalk'), ('walk', 'trail'), ('walk', 'endvertices'), ('euleriantrail', 'euleriangraph'), ('loop', 'simplegraph'), ('loop', 'degreevertex'), ('loop', 'simpleedge'), ('loop', 'multipleloops'), ('endvertices', 'abwalk'), ('vertex', 'adjacentvertices'), ('vertex', 'onedgesonvertices'), ('vertex', 'walk'), ('vertex', 'adjacentedges'), ('vertex', 'multipleedges'), ('vertex', 'edge'), ('vertex', 'multipleloops'), ('vertex', 'degreevertex'), ('vertex', 'incident'), ('edge', 'adjacentvertices'), ('edge', 'onedgesonvertices'), ('edge', 'multipleedges'), ('edge', 'simpleedge'), ('edge', 'adjacentedges'), ('edge', 'loop'), ('edge', 'trailcondition'), ('edge', 'pathcondition'), ('edge', 'walk'), ('edge', 'incident'), ('set', 'onedgesonvertices'), ('set', 'edge'), ('union', 'multiplepaths'), ('union', 'multipletrails'), ('trailcondition', 'trail'), ('nullmultigraph', 'nonnullmultigraph'), ('sequence', 'walk'), ('sequence', 'endvertices'), ('path', 'cycle'), ('path', 'multiplepaths'), ('degreevertex', 'maximumdegree'), ('degreevertex', 'minimumdegree'), ('onedgesonvertices', 'multigraph'), ('maximum', 'maximumdegree'), ('circuit', 'euleriangraph'), ('class', 'multiplepaths'), ('class', 'multipletrails'), ('incident', 'adjacentedges'), ('incident', 'degreevertex'), ('incident', 'onedgesonvertices'), ('orderedpair', 'multigraph'), ('closedwalk', 'circuit'), ('closedwalk', 'cycle'), ('closedwalk', 'stroll'), ('pathcondition', 'path'), ('multigraph', 'euleriangraph'), ('multigraph', 'nullmultigraph'), ('multigraph', 'trivialmultigraph'), ('multigraph', 'nontrivialmultigraph'), ('multigraph', 'emptymultigraph'), ('multigraph', 'euleriantrail'), ('multigraph', 'simplegraph'), ('trail', 'path'), ('trail', 'circuit'), ('trail', 'multipletrails')])
print G.size()
>71
print G.order()
>47
descendants = {} #for testing below
for node in G.nodes():
descendants[node] = nx.descendants(G,node)
remove_redundant_edges(G) #this removes the edges
print G.size() #lots of edges gone
>56
print G.order() #no nodes changed.
>47
newdescendants = {} #for comparison with above
for node in G.nodes():
newdescendants[node] = nx.descendants(G,node)
for node in G.nodes():
if descendants[node] != newdescendants[node]:
print 'descendants changed!!' #all nodes have the same descendants
for child in G.neighbors(node):
if len(list(nx.all_simple_paths(G,node, child)))>1:
print 'bad edge' #no alternate path exists from a node to its child.
Это будет эффективно: он должен обработать каждый узел в начале, чтобы увидеть, является ли он «конечным» узлом. Затем он обрабатывает каждое ребро, достигающее их, и проверяет, были ли обработаны все дочерние элементы этого родителя. Затем он смотрит на этих родителей и повторяет.
Таким образом, он будет обрабатывать каждое ребро один раз (включая просмотр родительского элемента), и каждая вершина будет обработана один раз в начале, а затем обработана один раз.
person
Joel
schedule
26.08.2015
O(V**3)
доO(V**2)
, поэтому для полностью подключенного графа с 4000 узлов время выполнения сокращается примерно с 90 секунд (с помощью кода Джоэла или моего базового алгоритма) до примерно 5 секунд. . - person Patrick Maupin   schedule 30.08.2015