如何找到从特定顶点开始的退化树中的所有等于路径?

Posted

技术标签:

【中文标题】如何找到从特定顶点开始的退化树中的所有等于路径?【英文标题】:How to find all equals paths in degenerate tree, which start on specific vertex? 【发布时间】:2015-07-27 23:37:56 【问题描述】:

我有一些degenerate tree(它看起来像数组或双向链表)。例如,就是这棵树:

每条边都有一定的权重。我想找到从每个顶点开始的所有相等路径。

换句话说,我想获取所有元组 (v1, v, v2),其中 v1 和 v2 是任意祖先和后代,例如 c(v1, v) = c(v, v2)

让边具有以下权重(这只是示例):

a-b = 3

b-c = 1

c-d = 1

d-e = 1

然后:

    顶点A 没有任何相等的路径(左侧没有顶点)。 顶点B 有一对相等的对。路径B-A 等于路径B-E (3 == 3)。 顶点C有一对相等的对。路径B-C 等于路径C-D (1 == 1)。 顶点D 有一对相等的对。路径C-D 等于路径D-E (1 == 1)。 顶点E没有任何相等的路径(右侧没有顶点)。

我实现了在O(n^2) 中工作的简单算法。但是对我来说太慢了。

【问题讨论】:

如果n 是顶点数,那么它不可能比O(n^2) 更快,因为在最坏的情况下你的边数是n^2 @FalconUA,你的观点确实有道理。看来,我正在寻找一种方法来减少O(n^2) 中的常数。我选择了一些顶点。然后我创建两个set。然后我用部分和填充这些集合,同时从这个顶点迭代到树的开始和树的结束。然后我找到set intersection 并从该顶点获取路径数。然后我对所有其他顶点重复算法。 您是在将问题限制在您提出的类型,还是在寻找通用解决方案?一般的解决方案需要您根据所有其他可能的路径评估图中的每条可能路径,并且在具有循环的图中,这可能会趋于无穷。 @AndyG,实际上,我只想从树中的每个顶点找到相等路径的数量。 @AndyG,我的图表没有任何循环。它是退化树(如示例)。 【参考方案1】:

您在 cmets 中写道,您当前的方法是

看来,我正在寻找一种方法来减少 O(n^2) 中的常数。我选择 一些顶点。然后我创建了两个集合。然后我用 部分总和,同时从这个顶点迭代到树的开始并到 树的完成。然后我找到集合交叉点并获取路径数 从这个顶点。然后我对所有其他顶点重复算法。

有一种更简单,我认为更快的O(n^2) 方法,基于所谓的两个指针方法。

对于每个顶点v,同时进入两个可能的方向。让一个“指针”指向一个顶点 (vl) 向一个方向移动,另一个 (vr) 向另一个方向移动,并尽量保持从 vvl 的距离尽可能接近从 @987654330 的距离@ 到 vr 尽可能。每次这些距离相等时,您就有相等的路径。

for v in vertices
    vl = prev(v)
    vr = next(v)
    while (vl is still inside the tree) 
              and (vr is still inside the tree)
        if dist(v,vl) < dist(v,vr)
            vl = prev(vl)
        else if dist(v,vr) < dist(v,vl)
            vr = next(vr)
        else // dist(v,vr) == dist(v,vl)
            ans = ans + 1
            vl = prev(vl)
            vr = next(vr)

(通过预先计算前缀和,您可以在 O(1) 中找到dist。)

很容易看出,只要你没有零长度的边,就不会错过任何相等的对。

关于更快的解决方案,如果你想列出所有对,那么你不能做得更快,因为在最坏的情况下,对的数量将是 O(n^2)。但是,如果您只需要这些对中的 数量,则可能存在更快的算法。

UPD:我想出了另一种计算数量的算法,如果你的边缘很短,它可能会更快。如果您将链的总长度(所有边权重的总和)表示为L,则算法在O(L log L) 中运行。但是,它在概念上更先进,在编码方面也更先进。

首先是一些理论推理。考虑一些顶点v。让我们有两个数组,ab,不是 C 风格的零索引数组,而是索引从 -LL 的数组。

让我们定义

对于i&gt;0, a[i]=1 iff 到v右边 正好在i 的距离上 是一个顶点,否则a[i]=0i=0a[i]≡a[0]=1 对于i&lt;0a[i]=1 iff 到 leftv 的距离正好在 -i 有一个顶点,否则 a[i]=0

对这个数组的简单理解如下。拉伸图形并将其沿坐标轴放置,使每条边的长度等于其权重,并且顶点v 位于原点。然后a[i]=1 iff 在坐标i 处有一个顶点。

对于您的示例和选择为v 的顶点“b”:

          a--------b--c--d--e
     --|--|--|--|--|--|--|--|--|-->
      -4 -3 -2 -1  0  1  2  3  4
a: ... 0  1  0  0  1  1  1  1  0 ...  

对于另一个数组,数组b,我们以相对于原点对称的方式定义值,就好像我们颠倒了轴的方向:

对于i&gt;0, b[i]=1 iff 到 vleft 的距离正好在 i 那里 是一个顶点,否则b[i]=0i=0,b[i]≡b[0]=1 对于i&lt;0, b[i]=1 iff 到v右边 距离正好-i 有一个顶点,否则b[i]=0

现在考虑第三个数组c,这样c[i]=a[i]*b[i],星号在这里保留用于普通乘法。显然c[i]=1 iff 长度为abs(i) 到左侧的路径以顶点结束,而长度为abs(i) 的右侧路径以顶点结束。因此,对于i&gt;0c 中具有c[i]=1 的每个位置都对应于您需要的路径。还有负位(c[i]=1i&lt;0),只是反映正位,还有一个位置c[i]=1,即位置i=0

计算c 中所有元素的总和。这个总和将是sum(c)=2P+1,其中P 是您需要的路径总数,v 是它的中心。所以如果你知道sum(c),你可以很容易地确定P

现在让我们更仔细地考虑数组ab,以及当我们更改顶点v 时它们如何变化。让我们将v0 表示为最左边的顶点(树的根),而a0b0 表示该顶点对应的ab 数组。

对于任意顶点v 表示d=dist(v0,v)。然后很容易看出,对于顶点v,数组ab 只是数组a0b0 移动了d

a[i]=a0[i+d]
b[i]=b0[i-d]

如果你还记得那张树沿着坐标轴伸展的图片,那就很明显了。

现在让我们再考虑一个数组S(所有顶点的一个数组),对于每个顶点v,让我们将sum(c)的值放入S[d]元素(d和@ 987654411@ 依赖于v)。

更准确地说,让我们定义数组S,这样对于每个d

S[d] = sum_over_i(a0[i+d]*b0[i-d])

一旦我们知道S 数组,我们就可以遍历顶点,并且对于每个顶点v 获得它的sum(c),就像S[d]d=dist(v,v0),因为对于每个顶点v,我们都有@987654422 @。

但是S 的公式非常简单:S 只是a0b0 序列的convolution。 (公式不完全符合定义,但很容易修改为精确的定义形式。)

所以我们现在需要给定a0b0(我们可以在O(L)时间和空间中计算),计算S数组。之后,我们可以遍历 S 数组并简单地从 S[d]=2P+1 中提取路径数。

上面公式的直接应用是O(L^2)。但是,通过应用快速傅里叶变换算法对两个序列can be calculated in O(L log L) 进行卷积。此外,您可以应用类似的Number theoretic transform(不知道是否有更好的链接)仅处理整数并避免精度问题。

所以算法的大致轮廓就变成了

calculate a0 and b0   // O(L)
calculate S = corrected_convolution(a0, b0)    // O(L log L)
v0 = leftmost vertex (root)
for v in vertices:
    d = dist(v0, v)
    ans = ans + (S[d]-1)/2

(我称之为corrected_convolution,因为S不完全是一个卷积,而是一个非常相似的对象,可以应用类似的算法。此外,你甚至可以定义S'[2*d]=S[d]=sum(a0[i+d]*b0[i-d])=sum(a0[i]*b0[i-2*d]),然后S'是卷积本身。)

【讨论】:

以上是关于如何找到从特定顶点开始的退化树中的所有等于路径?的主要内容,如果未能解决你的问题,请参考以下文章

在Graph中找到仅通过小于或等于一次的特定边的最短路径

图论 - 从顶点 A 开始,经过两个方向的所有路径,并以最短的方式再次到达 A

使用最小生成树查找从 A 到 B 的路径 - C/C++

数据结构——图

树中最长路径的公共段

c语言中如何获取树中所有叶子节点的路径