您可以在每个节点上保留一个表,记录哪些标记的节点可以从它到达,并在图表中添加或删除节点(或边缘)时保持更新,类似于为网络中的每个节点保留网络路由表.关于您的问题,有一些细节使其比网络路由表更简单:
- 您不想知道从给定节点到标记节点的实际路径,只知道一个(或多个)存在。
- 图形是无环的。
- 它不是一个分布式系统,所以你有完全的控制权(显然......)。
因为您不关心路径并且因为图是非循环的,所以每个节点上的表可以是映射marked_node_id -> count,其中计数是从给定节点到给定标记节点的路径数。添加新节点时,新节点的表将构建为与新节点相邻的所有节点表的联合,其中 count 是总和。此外,所有相邻节点的表从必须通过将新节点的表添加到它们中的每一个来更新新节点,并且必须递归地向上完成相邻的链。当一个节点被删除时,你必须做类似的事情。
基本复杂度分析:查找给定节点的所有标记节点是 O(1) 并且可以通过存储在单个节点上的信息来完成 - 这就是重点。通常,添加和删除一条边(或一个新节点加上它的边)将需要递归更新所有连接节点的表(调用深度高达 100,分支因子高达 100)。通过标记节点的反向泛洪,最初构建表的时间为 O(节点数)。
代码示例:
这是抽象的代码内解决方案,但应该翻译。我正在使用 Python (+GraphViz),因为你没有指定语言,它可能最适合最广泛的受众,并且很容易制作原型。我也将只实现添加/删除节点操作(修改节点可以删除然后添加不同的初始化)并从头开始构建图形,这不太现实,但是您可以通过从标记的节点向后工作很容易地在给定现有图形的情况下构建表。另请注意:
- 除了
adjacent_to 列表之外,以下要求每个节点都拥有/维护一个 adjacent_from 列表,以便我们可以在删除给定节点时递归路径的相邻路径。
- 我假设每个标记的节点都可以从自身到达 - 只是让事情更容易实现。
def main():
''' Build a test graph, then test. '''
graph = Graph()
a = graph.add_node('a', marked=True)
b = graph.add_node('b', marked=True)
c = graph.add_node('c', marked=True)
d = graph.add_node('d', adjacent_to=[a])
e = graph.add_node('e', adjacent_to=[d])
f = graph.add_node('f',adjacent_to=[c])
g = graph.add_node('g', adjacent_to=[d,f])
h = graph.add_node('h', adjacent_to=[e,g])
i = graph.add_node('i')
j = graph.add_node('j', marked=True, adjacent_to=[i])
k = graph.add_node('k', adjacent_to=[j])
l = graph.add_node('l', adjacent_to=[k])
m = graph.add_node('m', adjacent_to=[j])
with open('main0.dot', 'w') as f:
f.write(graph.gviz())
graph.delete_node('f')
with open('main1.dot', 'w') as f:
f.write(graph.gviz())
graph.delete_node('e')
with open('main2.dot', 'w') as f:
f.write(graph.gviz())
graph.delete_node('g')
with open('main3.dot', 'w') as f:
f.write(graph.gviz())
# Run this script to process graphviz files: for i in *.dot; do dot -Tpng $i > "${i%%.dot}.png"; done
class Graph:
''' Container for nodes. '''
def __init__(self):
self.nodes = {}
def add_node(self, id, marked=False, adjacent_to=[]):
assert id not in self.nodes
self.nodes[id] = Node(id, marked, adjacent_to)
return self.nodes[id]
def delete_node(self, id):
assert id in self.nodes
node = self.nodes[id]
self._recursive_subtract_table_on_delete(node, node)
for adjacent_from_node in node.adjacent_from:
adjacent_from_node._remove_adjacent_node(node.id)
del self.nodes[id]
def _recursive_subtract_table_on_delete(self, node, deleted_node):
for adjacent_from_node in node.adjacent_from:
self._recursive_subtract_table_on_delete(adjacent_from_node, deleted_node)
node._delete_reachability_table(deleted_node)
def gviz(self):
return 'strict digraph {
%s}' % ''.join([n._gviz_edges() for n in self.nodes.values()])
class Node:
def __init__(self, id, marked=False, adjacent_to = []):
''' Init node. Note only adjacent_to not adjacent_from node are allowed,
which measn we dno't have to update adjacent_from reachable_marks. '''
self.id = id
self.marked = marked
self.adjacent_to = adjacent_to
self.adjacent_from = []
self.reachable_marks = {}
if marked:
self.reachable_marks[id] = 1
for adjacent_node in adjacent_to:
adjacent_node.adjacent_from.append(self);
self._add_reachability_table(adjacent_node)
def _add_reachability_table(self, node):
''' Add the reachable_marks table from node to self. '''
for (marked_node_id, k) in node.reachable_marks.items():
self.reachable_marks[marked_node_id] = self.reachable_marks[marked_node_id] + 1 if marked_node_id in self.reachable_marks else 1
def _delete_reachability_table(self, node):
''' Delete the reachable_marks table from node from self. '''
for (marked_node_id, k) in node.reachable_marks.items():
self.reachable_marks[marked_node_id] = self.reachable_marks[marked_node_id] - 1 if marked_node_id in self.reachable_marks else 0
self.reachable_marks = {k: v for k,v in self.reachable_marks.items() if v}
def _remove_adjacent_node(self, id):
self.adjacent_to = list(filter(lambda n: n.id != id, self.adjacent_to))
def _gviz_edges(self):
''' Helper to print graphviz edges adjacent to this node. '''
_str = ''
if self.marked:
_str += ' %s[style=filled,fillcolor=blue]
' % (self._gviz_name(),)
else:
_str += self._gviz_name() + '
'
for adjacent_node in self.adjacent_to:
_str += ' %s -> %s
' % (self._gviz_name(), adjacent_node._gviz_name())
return _str;
def _gviz_name(self):
''' Helper to print graphviz name with reachable marks. '''
return '"' + self.id + '(' + ','.join(self.reachable_marks.keys()) + ')"'
if __name__ == '__main__':
main()
结果:
输出图显示了从括号中的每个节点可到达的标记节点。
最初的:
删除节点 f:
删除节点 e:
删除节点 g: