给出非循环图(树)(E,V)。查找所有路径(路径是相邻节点的序列),路径上的节点总和为0.
蛮力方法是生成所有节点对,对于每对节点检查节点的值是否总和为0.这需要O(N ^ 3)时间和O(N)空间复杂度。请建议更快的解决方案。
答案 0 :(得分:3)
您可以在O(n ^ 2)时间内通过对每个选择的起始节点运行深度优先搜索来执行此操作。
深度优先搜索计算每个节点距根起始节点的距离。只要距离为0,就会找到路径。
有n个节点,每个DFS占用O(n),因此总运行时间为O(n ^ 2)。
一般来说很难做得更好,因为如果所有节点的权重都为0,那么你需要输出O(n ^ 2)个答案。
import networkx as nx
G=nx.Graph()
G.add_edge(0,1)
G.add_edge(1,2)
G.add_edge(1,3)
W=[1,0,-1,-1]
def dfs(G,n,dist,parent):
"""Depth first search and yield nodes where sum is 0."""
dist += W[n]
if dist==0:
yield n
for n2 in G[n]:
if n2!=parent:
for e in dfs(G,n2,dist,n):
yield e
for start in G:
for n in dfs(G,start,0,None):
print start,n
请注意,这会为每个路径0-> 2和2-> 0返回2个条目,如果节点的权重为零,则还会返回路径1-> 1。
您可以通过仅在开始时输出答案来删除这些额外案例< Ñ
如果你只是想知道解决方案的数量,你可以在O(nlogn)和O(n)空间中通过将树分成更小的部分来完成。
对于二叉树,我们将有2个大小最多为n / 2的子图,因此第二个阶段将采用大约相同数量的操作,并且类似地每个阶段采用O(n)直到子图包含单个节点。将有O(logn)阶段,因此整体复杂度为O(nlogn)。
对于非二叉树,有更多的子图,但它们也更小,因此每个阶段将像以前一样采用O(n),但我们应该更快地降低,因此非二叉树也应该是O(nlogn) )。 (同样,执行step2稍微复杂一些,但仍可以在O(n)中完成)
答案 1 :(得分:1)
使用Python中的以下代码的O(n)解决方案。由于它是一棵树,我们不需要检查访问过的节点。
from collections import defaultdict
class Node(object):
def __init__(self, id, weight):
self.id = id
self.weight = weight
self.children = []
def __repr__(self):
return '<Node {0}: {1}>'.format(self.id, self.weight)
class Solver(object):
def __init__(self):
self.sums = defaultdict(list)
self.path = []
self.solutions = []
def dfs(self, depth, node, acc):
self.path.append(node)
key = acc + node.weight
for x in self.sums[key]:
self.solutions.append(self.path[x+1:])
self.sums[key].append(depth)
for child in node.children:
self.dfs(depth + 1, child, acc + node.weight)
self.sums[key].pop()
self.path.pop()
def run(self, root):
self.sums[0].append(-1)
self.dfs(0, root, 0)
return self.solutions
nodes = [
Node('A', 5),
Node('B', 1),
Node('C', 2),
Node('D', -3),
Node('E', 1),
Node('F', 2),
Node('G', 0),
Node('H', -8),
]
i = 0
while i < len(nodes) - 1:
nodes[i].children.append(nodes[i+1])
i += 1
s = Solver()
solutions = s.run(nodes[0])
for x in solutions:
print x
输出结果为:
[<Node B: 1>, <Node C: 2>, <Node D: -3>]
[<Node C: 2>, <Node D: -3>, <Node E: 1>]
[<Node D: -3>, <Node E: 1>, <Node F: 2>]
[<Node D: -3>, <Node E: 1>, <Node F: 2>, <Node G: 0>]
[<Node G: 0>]
[<Node A: 5>, <Node B: 1>, <Node C: 2>, <Node D: -3>, <Node E: 1>, <Node F: 2>, <Node G: 0>, <Node H: -8>]
我假设添加,删除和访问密钥的字典方法是O(1)。如果不是,你可以使用一些哈希来平均得到它。
说明:在任何路径(...,n_i,...,n_j,...)中,我们都知道从n_i到n_j的所有权重之和等于n_j.acc - n_(i-1).acc
。所以我们只需要找到两个具有相同累加和的节点。我们使用哈希在O(1)中找到它。
当然,您可以将其调整为仅计算路径数。你只需要总结self.sums的大小(你可以删除self.path)。