6

我想问一下是否有人知道在插入新节点期间存储从根节点到多路树的新节点的路径的高效方法。例如,如果我有以下树:

多路树

对于每个节点,我目前在插入过程中存储一个从根节点到节点的路径数组,方法是通过int为相同深度的每个子节点分配一个唯一的 ID:

Root node -> [1]

Depth 1, child 1 of root -> [1, 1]
Depth 1, child 2 of root -> [1, 2]

Depth 2, child 1 of parent 1 -> [1, 1, 1]
Depth 2, child 2 of parent 1 -> [1, 1, 2]
Depth 2, child 3 of parent 1 -> [1, 1, 3]
Depth 2, child 1 of parent 2 -> [1, 2, 4]
Depth 2, child 2 of parent 2 -> [1, 2, 5]

Depth 3, child 1 of parent 3 -> [1, 1, 3, 1]

...

如果我现在从深度 3 的叶节点插入一个新节点1,我将必须为其创建一个新的路径数组,以存储父节点的所有节点1(即[1, 1, 3, 1])加上新的子 ID,该 ID1用于第一个子节点:

Depth 4, child 1 of parent 1 -> [1, 1, 3, 1, 1]

由于我的树在高度上增长了很多(每个深度的孩子数量相对较少,但深度可以很高),该算法的缓慢部分将是这个数组重新创建过程。想象一下深度树1.000.000,如果我从深度节点插入一个新节点1.000.000,我必须为这个新节点创建一个新数组,存储1.000.001父节点的所有 ID 并附加新节点的 ID:

Depth 1.000.001, child 1 of parent x -> [...1 million and one IDs... , 1]

在节点插入期间是否有更有效的方法来存储每个节点上的路径?

我基本上需要这个来确定任何给定的节点是否是树中可能的父节点的子节点,并且由于我将路径存储在每个节点中,我可以通过检查子节点的路径数组轻松做到这一点,如下所示:

// Ex. 1
Is node 4 of depth 2 the parent/ancestor of node 1 of depth 3?

node 1 of depth 3 has the following path array: pathArray = [1, 1, 3, 1]
Its ancestor ID on depth 2 is: pathArray[2] -> 3

3 != 4 and therefore I know that node 4 of depth 2
is not a parent of node 1 of depth 3.

// Ex. 2
Is node 1 of depth 1 the parent/ancestor of node 1 of depth 3?

node 1 of depth 3 has the following path array: pathArray = [1, 1, 3, 1]
Its ancestor ID on depth 1 is: pathArray[1] -> 1

1 == 1 and therefore I know that node 1 of depth 1
is a parent of node 1 of depth 3.

这种查找操作会很快,问题是随着树的深入而创建路径数组。

任何建议,将不胜感激。

感谢您的关注。

4

3 回答 3

2

现在,您的解决方案具有O(1)查找时间、O(h)插入时间和O(n^2)空间复杂性,其中n是节点数,'h是高度,最多为n.

O(log n)您可以通过以下方式实现查找、O((log n)^2)插入和O(n log n)空间的权衡:

让每个节点存储一个指向其每个祖先的跳转指针,距离为 1(其父)、2(祖父)、4(祖父的祖父)、8、16 等,直到到达或通过根。从任何节点到根的最大距离是n,因此对于每个节点,您都存储O(log n)跳转指针。由于您对每个节点都执行此操作,因此总空间复杂度为O(n log n).

如果深度不低于,则回答是否y是 的祖先的查询x是微不足道的。命名节点的深度和。要知道,如果是 的祖先,那么它就是 的'th 祖先。也就是说,如果和,您知道 if是的祖先,那么它是高于 的级别。yxdydxyxdx-dyxdy = 5dx = 17yx17 - 5x

因此,您可以通过递归地在树中向上跳跃最大可能距离来执行查找,x而不会超过目标祖先。例如,如果您从深度 16 开始并希望在深度 6 处找到祖先,那么您对上面 10 层的祖先感兴趣。你不能向上跳 16 级,因为这会超过目标祖先,所以你要跳 8 级。现在你在深度 16-8=8,到目标祖先的剩余距离是 6,是 2。因为有一个指针正好向上两步,你跟着它,你已经到达目标祖先。

每次您在树中向上跟随指针时,您至少会到达目标的一半,因此您可以跟随的最大指针数为O(log n).

当插入一个节点e作为另一个节点的子节点时,您可以通过查找距离为 1、3、7、15 等的 的祖先x来构造e的跳转指针(因为距离所有这些都比现在更远一层)。有这样的搜索。正如我们上面所说,每次查找都需要时间。因此总数为。xexO(log n)O(log n)O((log n)^2)

通过存储一些额外的信息,甚至可以使这个操作变得更快,但我现在不能确切地看到如何。

注意 这个想法实际上是水平祖先问题经典解决方案的一部分。经典解决方案允许按照您及时描述的方式进行查找O(1),同时将整个数据结构的空间保持在O(n). 但是,数据结构是静态的,因此解决方案没有指定如何进行插入。可能有一种方法可以使关卡祖先适应动态场景并获得比我在这里描述的更好的运行时间,但我不确定如何。

于 2019-08-25T12:22:30.973 回答
1

数组的所有值都连续存储在内存中。如果你想保留这个属性,你必须使用它们。或者,如果您对希望通过多个内存位置感到满意,您可以在每个节点中仅存储其直接父节点并跟踪到所需的级别以进行所需的检查。

于 2019-08-25T10:18:21.977 回答
1

将您的节点映射到HashMap<node-id, node>.


现在,当你不得不

确定任何给定节点是否是可能的父节点的子节点,

您可以从 HashMap 中找到该节点在树中的确切位置,然后使用父指针返回树以查看可能的父节点是否位于到根的路径上。

在一个相当平衡的树中,这将是 O(Log n) 运行时间(向上遍历树)和 O(n) 空间(HashMap)。


如果您使用当前的设计来存储从每个节点到根的路径,那么您将有 O(Log n) 运行时间(假设是平衡树)和 O(n * Log n) 空间来存储Log n长度n 个节点中每个节点的路径。

于 2019-08-25T19:33:49.463 回答