为什么从范围树查询中获得的子树数为O(log(n))?

时间:2018-11-17 12:56:15

标签: algorithm data-structures tree range-tree

我试图弄清楚这个数据结构,但是我不明白我们怎么能 知道有O(log(n))个子树代表查询的答案吗?

这里是一张图片供说明:

enter image description here

谢谢!

2 个答案:

答案 0 :(得分:0)

如果我们假设上面的内容是purely functional binary tree [wiki],那么在节点不可变的情况下,我们可以对这棵树进行“复制”,以便只有值大于 x的元素在树中 1 并低于 x 2

让我们从一个非常简单的案例开始说明这一点。想象一下,我们根本没有任何界限,而只是可以返回整个树。因此,我们无需构建 new 树,而是返回对树根的引用。因此,只要不对树进行编辑(至少只要不使用子树,就可以),我们就可以在 O(1)中无限制地返回树。

以上情况当然很简单。我们只需对整个树进行“复制”(由于数据是不可变的,因此并不是真正的复制,我们可以只返回树)。因此,让我们着眼于解决一个更复杂的问题:我们要构建一棵包含所有大于阈值 x 1 的元素的树。基本上,我们可以为此定义一个递归算法:

  1. None(或代表空引用或对空树的引用)的剪切版本为None
  2. 如果节点的值小于阈值,则返回右侧子树的“剪切”版本;和
  3. 如果节点的值大于阈值,我们将返回一个具有相同右子树的inode,并且作为左子子节点的是左子子节点的切割版本。

所以在伪代码中它看起来像:

def treelarger(some_node, min):
    if some_tree is None:
        return None
    if some_node.value > min:
        return Node(treelarger(some_node.left, min), some_node.value, some_node.right)
    else:
        return treelarger(some_node.right, min)

该算法因此以树的高度在 O(h)中运行,因为对于每种情况(除了第一种情况),我们递归到一个(不是两个),并且如果我们有一个没有孩子的节点(或者至少在我们需要剪切子树的方向上没有子树)的情况下结束。

因此,我们制作树的完整副本。我们重用旧树中的许多节点。我们仅构造一个新的“表面”,但是大部分“体积”是旧的二叉树的一部分。尽管树本身包含 O(n)个节点,但我们最多只能构建 O(h)个新节点。我们可以优化上述内容,以使给定子树之一的分割版本相同,而不会创建新节点。但这在时间复杂度方面甚至没有多大关系:我们最多生成 O(h)个新节点,并且节点总数小于原始数目,或者相同。 / p>

如果是完整的树,则树的高度 h O(log n)缩放,因此该算法将在 O(登录n)

那么我们如何生成一个元素在两个阈值之间的树?我们可以轻松地将以上内容重写为算法treesmaller,该算法会生成包含所有较小元素的子树:

def treesmaller(some_node, max):
    if some_tree is None:
        return None
    if some_node.value < min:
        return Node(some_node.left, some_node.value, treesmaller(some_node.right, max))
    else:
        return treesmaller(some_node.left, max)

因此大致来说有两个区别:

  1. 我们将条件从some_node.value > min更改为some_node.value < max;和
  2. 如果条件成立,我们对right子级子递归,如果条件不成立,则向左递归。

现在我们从以前的算法得出的结论也是可以应用于该算法的结论,因为它再次仅引入了 O(h)个新节点,并且节点总数只能减少。

尽管我们可以构造同时考虑两个阈值的算法,但是我们可以简单地重用以上算法来构造仅包含范围内元素的子树:我们首先将树传递给{ {1}}函数,然后通过treelarger获得结果(反之亦然)。

由于在这两种算法中,我们都引入了 O(h)新节点,并且树的高度不能增加,因此我们最多构造了 O(2 h),因此是 O(h)个新节点。

鉴于原始树是一棵 complete 树,因此它认为我们创建了 O(log n)新节点。

答案 1 :(得分:0)

考虑搜索范围的两个端点。该搜索将继续进行,直到找到跨越您的间隔的两个叶节点中最低的公共祖先。此时,搜索分支,其中一部分向左移动,而另一部分向右移动。现在,让我们只关注查询的左边分支部分,因为逻辑是相同的,但是对于右边分支却是相反的。

在此搜索中,有助于将每个节点视为不是代表单个点,而是代表一系列点。然后,一般过程如下:

  1. 如果查询范围完全包含此节点表示的范围,请停止在x中搜索并开始搜索此节点的y子树。

  2. 如果查询范围仅在此节点的右侧子树所表示的范围内,请继续在右侧进行x搜索,而不要研究y子树。

  3. 如果查询范围与左子树的范围重叠,则它必须完全包含右子树的范围。因此,请处理右侧子树的y子树,然后递归地探索左侧的x子树。

在所有情况下,我们最多考虑一个y子树,然后递归地继续仅在一个方向上探索x子树。这意味着我们实质上是在x-tree下方找到一条路径,每步最多添加一个y-subtree。由于树的高度为O(log n),因此以这种方式访问​​的y子树的总数为O(log n)。然后,在我们从右上分支的情况下,包括访问的y子树的数量,我们将得到另一个O(log n)子树,以搜索总共O(log n)个子树。

希望这会有所帮助!