想象一下以下的树:
A
/ \
B C
/ \ \
D E F
我正在寻找一种方法来查询例如F是否是A的后代(注意:F不需要是F的直接后代),在此特定情况确实如此。只需要针对较大的潜在后代节点池测试有限数量的潜在父节点。
更新:在测试节点是否是潜在父池中节点的后代时,需要针对所有潜在父节点对其进行测试。
这是一个想法:
将多路树转换为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] ...
... ...
当测试节点是否是潜在父节点的后代时,请使用其trie前缀,查找第一个“前缀数组”中的第一个字符(参见上文),如果存在,则查找第二个前缀第二个“前缀数组”中的字符,依此类推,即测试F导致:
F = 1 2 1
*[1] [1] ...
[2] *[2] ...
[3] [3] ...
[4] [4] ...
... ...
所以是的,F,是C的后代。
此测试似乎是最坏情况O(n),其中n =最大前缀长度=最大树深度,因此其最坏情况恰好等于上升树和比较节点的显而易见的方式。但是,如果测试节点靠近树的底部并且潜在的父节点位于顶部某处,则执行得更好。结合两种算法可以减轻最坏情况。但是,内存开销是一个问题。
还有其他办法吗?任何指针都非常感谢!
答案 0 :(得分:5)
您的输入树是否始终是静态的?如果是这样,那么您可以使用最低公共祖先算法在O(1)时间内使用O(n)时间/空间构造回答后代问题。 LCA查询被给予两个节点并询问哪个是树的最低节点,其子树包含两个节点。然后,您可以使用单个LCA查询回答IsDescendent查询,如果LCA(A,B)== A或LCA(A,B)== B,则一个是另一个的后代。
这个Topcoder algorithm tuorial对代码复杂性/效率的各种级别的问题和一些解决方案进行了全面的讨论。
答案 1 :(得分:3)
我不知道这是否适合您的问题,但是在数据库中存储层次结构的一种方法是快速“从这个节点向下提供所有内容”,这些功能就是存储“路径”。
例如,对于看起来像这样的树:
+-- 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*
要确定特定节点是否是另一个节点的后代,您将看到最长路径是否以最短路径开始。
例如:
a*c*e
以a
a*c*d
以a*c
这对您的实例有用吗?
答案 2 :(得分:2)
遍历任何树都需要“树深度”步骤。因此,如果您保持平衡树结构,则可以证明您需要 O(log n)操作来执行查找操作。根据我的理解,你的树看起来很特别,你无法以平衡的方式保持它,对吧?所以 O(n)是可能的。但是在树的创建过程中这很糟糕,所以在你使用查找之前你可能会死...
与插入相比,根据您需要查找操作的频率,您可以决定在插入期间付费以维护额外数据结构体。如果您确实需要摊销 O(1),我建议进行哈希处理。在每次插入操作中,您将节点的所有父项放入哈希表中。根据您的描述,这可能是给定插入上的 O(n)项目。如果您执行 n 插入,这听起来很糟糕(朝 O(n ^ 2)),但实际上您的树无法降级 不好,所以你可能会得到一个摊销的总体不稳定大小 O(n log n)。 (实际上, log n 部分取决于树的降阶程度。如果您认为它最大程度地降级,请不要这样做。)
因此,您需要在每个插入上支付 O(log n),并获得哈希表效率 O(1)强>查找强>
答案 3 :(得分:1)
对于M路树而不是位数组,为什么不将每个节点存储二进制“trie id”(每个级别使用M位)?对于您的示例(假设M == 2):A=0b01, B=0b0101, C=0b1001, ...
然后你可以在O(1)中进行测试:
bool IsParent(node* child, node* parent)
{
return ((child->id & parent->id) == parent->id)
}
如果你有一个快速FindMSB()函数返回最高位集的位置,你可以将存储压缩到每层的ceil(lg2(M))位:
mask = (1<<( FindMSB(parent->id)+1) ) -1;
retunr (child->id&mask == parent->id);
答案 4 :(得分:1)
在预先遍历中,每组后代都是连续的。对于你的例子,
A B D E C F
+---------+ A
+---+ B
+ D
+ E
+-+ C
+ F
如果您可以预处理,那么您需要做的就是为每个节点编号并计算后代间隔。
如果您无法预处理,则link/cut tree为更新和查询提供O(log n)性能。
答案 5 :(得分:0)
您可以回答表格的查询&#34;节点A是节点B的后代吗?&#34;在恒定的时间内,只需使用两个辅助阵列。
通过以深度优先顺序访问来预处理树,并且对于每个节点A,在两个数组Start []和End []中存储其访问的开始和结束时间。
所以,让我们说End [u]和Start [u]分别是节点u访问的结束和开始时间。
当且仅当:
时,节点u是节点v的后代开始[v]&lt; =开始[u]和结束[u]&lt; =结束[v]。
你完成了,检查这个条件只需要在数组Start和End中进行两次查找
答案 6 :(得分:0)
看看Nested set model选择非常有效,但更新速度太慢
答案 7 :(得分:0)
就其价值而言,您在这里所要求的相当于测试一个类是否是类层次结构中另一个类的子类型,而在像 CPython 这样的实现中,这只是完成了老式的“迭代父类”寻找父母”的方式。