【问题标题】:Count nodes bigger then root in each subtree of a given binary tree in O(n log n)在 O(n log n) 中计算给定二叉树的每个子树中大于根的节点
【发布时间】:2016-01-23 22:52:39
【问题描述】:

我们得到一个带有n节点的树,其形式是指向其根节点的指针,其中每个节点都包含一个指向其父节点、左子节点和右子节点的指针,以及一个整数键。对于每个节点v,我想添加附加字段v.bigger,其中应包含键大于v.key 的节点数,这些节点位于以v 为根的子树中。将这样的字段添加到树的所有节点总共需要 O(n log n) 时间。

我正在寻找任何可以让我解决此问题的提示。我尝试了几种启发式方法-例如,在考虑以自下而上的方式解决此问题时,对于固定节点 vv.leftv.right 可以为 v 提供某种集合(平衡 BST?)操作bigger(x),对于给定的x,它在对数时间内返回大于x 的元素数量。问题是,我们需要在O(log n) 中合并这些集合,所以这似乎是不可行的,因为我不知道任何支持快速合并的有序集合,如数据结构。

我还考虑了自上而下的方法 - 当且仅当 u 位于到根和 u<v 的简单路径上时,节点 v 为某些节点 u 添加一个 u.bigger。所以v 可以以某种方式更新所有这些u,但我想不出任何合理的方式来做到这一点......

那么,思考这个问题的正确方法是什么?

【问题讨论】:

    标签: algorithm data-structures tree


    【解决方案1】:

    在给定的树中执行深度优先搜索(从根节点开始)。

    当第一次访问任何节点(来自父节点)时,将其键添加到一些订单统计数据结构(OSDS)。同时向OSDS查询大于当前key的key个数,并用本次查询的否定结果初始化v.bigger

    当最后一次访问任何节点时(来自右子节点),查询 OSDS 中大于当前键的键的数量,并将结果添加到v.bigger

    您可以将此算法应用于任何有根树(不一定是二叉树)。而且它不一定需要父指针(您可以使用 DFS 堆栈代替)。

    对于 OSDS,您可以使用增强型 BST 或 Fenwick 树。对于 Fenwick 树,您需要对给定的树进行预处理,以便压缩键的值:只需将所有键复制到一个数组中,对其进行排序,删除重复项,然后用该数组中的索引替换键。

    【讨论】:

    • 如此简单......我不敢相信我还没有想出那个;)
    【解决方案2】:

    基本思路:

    使用自底向上的方法,每个节点都会从两个儿子那里得到两个子树中值的有序列表,然后找出其中有多少更大。完成后,向上传递组合有序列表。

    详情:

    1. 叶子:
      叶子显然有v.bigger=0。它们上面的节点创建一个包含两项的值列表,更新自身并将自己的值添加到列表中。
    2. 所有其他节点:
      从儿子那里获取两个列表并以有序的方式合并它们。因为它们已经排序,所以这是O(number of nodes in subtree)。在合并期间,您还可以查看有多少节点符合条件并获取该节点的 v.bigger 值。

    为什么是 O(n logn)?

    树中的每个节点都通过其子树中的节点数来计数。这意味着根计算树中的所有节点,根的儿子每个计算(组合)树中的节点数(是的,是的,-1 用于根)等等相同高度的所有节点一起计算较低的节点数。这给我们计算的节点数是number of nodes * height of the tree - 这是O(n logn)

    【讨论】:

    • 在最坏的情况下你的答案不是O(n log n)(每个节点只有一个孩子,除了叶子)。我不会投反对票,因为作者从未澄清他是在寻找最坏情况复杂性还是预期情况复杂性。此外,为了获得预期的案例O(n log n) 复杂性,还有更简单的解决方案。
    • @Haozhun - 我有点不同意。在最坏的情况下,我们所有的孩子都在一个长树枝上(在我的母语中,我们将其称为“鞋带”或“绳子”或“绳子”),每个节点都会从一个儿子那里收到一个空列表和另一个儿子的(长)名单。组合这些列表是O(1)。您还提到还有其他方法吗?请详细说明。
    • 让我们考虑这样的树:T(i) = tree(T(i-1), T(0)), T(0) = tree(nil, nil)。在这种情况下,T(m) 是 m*2+1 个节点的树,深度为 m。
    • @Haozhun - 你是对的 - 这个例子需要花费O(n^2)。那么你说的解决方案是什么?
    【解决方案3】:

    如果我们为每个节点保留一个单独的二叉搜索树 (BST),它由以该节点为根的子树的节点组成。

    对于k 级别的节点v,合并两个子树v.leftv.right,这两个子树都具有O(n/2^(k+1)) 元素是O(n/2^k)。在为这个节点形成 BST 之后,我们可以通过计算 BST 的右(传统上)子树中的元素在O(n/2^(k+1)) 时间找到 v.bigger。总而言之,我们在级别 k 上对单个节点进行了 O(3*n/2^(k+1)) 操作。总共有2^k 多个 k 级节点,因此我们有O(2^k*3*n/2^(k+1)),它被简化为 O(n)(去掉 3/2 常数)。 k 级别的操作。有log(n) 个级别,因此我们总共有O(n*log(n)) 个操作。

    【讨论】:

    • 现在计算在最坏的情况下你必须存储多少个节点。这将是 O(N^2)
    • @SashaMN 我不明白你的意思,你指的是空间复杂度吗?您不必一次维护所有的 BST,一旦形成一个级别,您可以丢弃下面级别(价值较高的级别)的 BST。
    • 虽然我不同意反对意见,但我可以在评论中解释@SashaMN 的含义。您的解决方案假设树是合理平衡的,即树的深度是O(log N),这不一定是真的。 (OP没有说。)没有这个假设,你的解决方案是O(N^2)
    猜你喜欢
    • 1970-01-01
    • 2021-03-07
    • 1970-01-01
    • 2017-08-25
    • 1970-01-01
    • 2021-03-10
    • 1970-01-01
    • 2014-09-07
    • 2020-08-25
    相关资源
    最近更新 更多