在非循环图(E,V)中,找到总计为0的路径

时间:2013-11-13 21:07:55

标签: algorithm

给出非循环图(树)(E,V)。查找所有路径(路径是相邻节点的序列),路径上的节点总和为0.

蛮力方法是生成所有节点对,对于每对节点检查节点的值是否总和为0.这需要O(N ^ 3)时间和O(N)空间复杂度。请建议更快的解决方案。

2 个答案:

答案 0 :(得分:3)

您可以在O(n ^ 2)时间内通过对每个选择的起始节点运行深度优先搜索来执行此操作。

深度优先搜索计算每个节点距根起始节点的距离。只要距离为0,就会找到路径。

有n个节点,每个DFS占用O(n),因此总运行时间为O(n ^ 2)。

一般来说很难做得更好,因为如果所有节点的权重都为0,那么你需要输出O(n ^ 2)个答案。

Python代码

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)空间中通过将树分成更小的部分来完成。

  1. 在图表的中心选择一个节点(O(n)找到它,虽然我怀疑选择一个随机节点在实践中会以与quicksort运行良好相同的方式运行良好)
  2. 通过为每个邻居运行DFS并将字距映射距离存储到具有该距离的节点数来查找包含此节点的所有零重量路径。比较字典允许我们找到零重量的路径。为O(n)
  3. 现在针对每个孩子的子图重复此算法
  4. 对于二叉树,我们将有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)。