Algorithm O(1)确定节点是否是多路树中另一个节点的后代的算法?

Algorithm O(1)确定节点是否是多路树中另一个节点的后代的算法?,algorithm,tree,trie,descendant,multiway-tree,Algorithm,Tree,Trie,Descendant,Multiway Tree,想象一下下面的树: A / \ B C / \ \ D E F 我正在寻找一种方法来查询例如F是否是a的后代(注意:F不需要是a的直接后代),在这种特殊情况下,这是正确的。只有有限数量的潜在父节点需要针对更大的潜在子节点池进行测试 测试节点是否是潜在父池中节点的后代时,需要针对所有潜在父节点进行测试 这是a提出的: 将多路树转换为trie,即为上述树中的每个节点分配以下前缀: A = 1 B = 11 C = 12 D = 111 E = 112

想象一下下面的树:

    A
   / \
  B   C
 / \   \
D   E   F
我正在寻找一种方法来查询例如F是否是a的后代(注意:F不需要是a的直接后代),在这种特殊情况下,这是正确的。只有有限数量的潜在父节点需要针对更大的潜在子节点池进行测试

测试节点是否是潜在父池中节点的后代时,需要针对所有潜在父节点进行测试

这是a提出的:

  • 将多路树转换为trie,即为上述树中的每个节点分配以下前缀:

     A = 1
     B = 11
     C = 12
     D = 111
     E = 112
     F = 121
    
  • 然后,为每个可能的前缀大小保留一个位数组,并添加要测试的父节点,即,如果将C添加到潜在的父节点池中,则执行以下操作:

      1    2    3  <- Prefix length
    
    *[1]  [1]  ...
     [2] *[2]  ...
     [3]  [3]  ...
     [4]  [4]  ...
     ...  ...
    
    是的,F是C的后代

这个测试似乎是最坏的情况O(n),其中n=最大前缀长度=最大树深度,因此它的最坏情况正好等于向上移动树并比较节点的明显方式。但是,如果被测试节点位于树的底部附近,而潜在的父节点位于树的顶部,则执行效果会更好。结合这两种算法可以缓解两种最坏情况。然而,内存开销是一个问题


还有别的方法吗?非常感谢任何指点

我不知道这是否适合您的问题,但在数据库中存储层次结构的一种方法是使用quick“从此节点和向下提供一切”功能存储“路径”

例如,对于如下所示的树:

    +-- b
    |
a --+       +-- d
    |       |
    +-- c --+
            |
            +-- e
假设上面树中的字母是每行的“id”,则按如下方式存储行:

id    path
a     a
b     a*b
c     a*c
d     a*c*d
e     a*c*e
要查找特定节点的所有后代,您需要在路径列上执行“STARTSWITH”查询,即路径以
a*c*
开头的所有节点

要确定某个特定节点是否是另一个节点的后代,可以查看最长路径是否以最短路径开始

例如:

  • e是a的后代,因为a*c*e以a开头
  • d是c的后代,因为
    a*c*d
    a*c

这在您的实例中有用吗?

对于M-way树,而不是位数组,为什么不在每个节点上存储二进制“trie id”(每级使用M位)?对于您的示例(假设M==2):
A=0b01,B=0b0101,C=0b1001,…

然后您可以在O(1)中进行测试:

如果您有一个返回最高有效位集位置的fast函数,则可以将存储压缩到每级ceil(lg2(M))位:

mask = (1<<( FindMSB(parent->id)+1) ) -1;
retunr (child->id&mask == parent->id);
mask=(1id&mask==parent->id);

遍历任何树都需要“树的深度”步骤。因此,如果您保持平衡的树结构,那么可以证明,您的查找操作将需要O(logn)操作。据我所知,你的树看起来很特别,你不能以一种平衡的方式保持它,对吗?所以O(n)是可能的。但无论如何,在创建树的过程中,这是不好的,因此您可能会在使用查找之前死亡

根据与插入操作相比,您需要执行查找操作的频率,您可以决定在插入操作期间付费,以维护额外的数据结构。如果你真的需要摊销O(1),我建议使用散列法。在每次插入操作中,您都会将节点的所有父节点放入哈希表中。根据您的描述,这可能是给定插入中的O(n)项。如果您不插入,这听起来很糟糕(朝向O(n^2)),但实际上您的树不能降级到如此糟糕的程度,因此您可能会得到O(n log n)的摊销总hastable大小。(实际上,logn部分取决于树的退化程度。如果您希望它的退化程度最大,请不要这样做。)


因此,您将在每次插入时支付大约O(logn),并获得查找的哈希表效率O(1)。在预顺序遍历中,每一组子体都是连续的。以你为例,

A B D E C F
+---------+ A
  +---+ B
    + D
      + E
        +-+ C
          + F
如果可以进行预处理,则只需为每个节点编号并计算后代间隔


如果您不能预处理,那么a将为更新和查询提供O(log n)性能。

您的输入树总是静态的吗?如果是这样,那么您可以使用最低共同祖先算法在O(1)时间内用O(n)时间/空间结构回答is后代问题。LCA查询提供两个节点,并询问哪个节点是树中的最低节点,其子树包含这两个节点。然后,您可以使用单个LCA查询回答IsDescendent查询,如果LCA(a,B)==a或LCA(a,B)==B,则其中一个是另一个的后代


这将在不同的代码复杂度/效率级别上对问题和一些解决方案进行彻底的讨论

只需使用两个辅助数组,就可以在固定时间内回答形式为“节点A是节点B的后代吗?”的查询

通过访问深度优先顺序对树进行预处理,并将每个节点的访问开始和结束时间存储在两个数组Start[]和End[]中

因此,假设End[u]和Start[u]分别是节点u的访问的结束时间和开始时间

那么节点u是节点v的后代当且仅当:


开始[v]看看选择非常有效,但更新速度太慢

这正是我的建议,仅针对数据库,据我所知:)在本机实现中,StartWith将是缓慢的部分-对于最大前缀长度为n的m个潜在父节点,我认为这将是O(nm),然而,位阵列使其成为O(n),以牺牲指数内存增长为代价:(路径长度可能是O(log(n)
mask = (1<<( FindMSB(parent->id)+1) ) -1;
retunr (child->id&mask == parent->id);
A B D E C F
+---------+ A
  +---+ B
    + D
      + E
        +-+ C
          + F