【问题标题】:Fenwick tree vs Segment treeFenwick 树与段树
【发布时间】:2020-10-04 01:14:09
【问题描述】:

我需要在数组的某个范围内计算总和,因此我遇到了 Segment Tree 和 Fenwick Tree,我注意到这两个树都以相同的渐近运行时间进行查询和更新。我做了更多的研究,这两种数据结构似乎以相同的速度做所有事情。两者都有线性内存使用(Segment Tree 使用两倍)。

除了运行时间/内存和实现方面的恒定因素之外,还有什么理由让我选择其中一个?

我正在寻找一个客观的答案,比如某个操作比另一个操作更快,或者某个限制可能有另一个限制。

我看到了另外 2 个关于此的 StackOverflow 问题,但答案只是描述了这两种数据结构,而不是解释什么时候一个可能比另一个更好。

【问题讨论】:

    标签: algorithm tree segment-tree fenwick-tree


    【解决方案1】:

    read this on Quora。希望对您有用。

    1. 段树可以做一些事情,但 BIT 不能:BIT 本质上适用于累积量。当需要区间 [i..j] 的累积量时,它是 [1...j] 和 [1...i-1] 的累积量之间的差。这仅是因为加法具有逆运算。如果操作是不可逆的(例如 max),则不能这样做。另一方面,线段树上的每个区间都可以作为不相交区间的并集找到,不需要逆运算
    2. BIT 只需要段树一半的内存:如果您有自虐的内存限制,您几乎无法使用 BIT
    3. 虽然 BIT 和段树操作都是 O(log(n)),但段树操作有一个更大的常数因子:这对于大多数情况来说应该无关紧要。但是再一次,如果您有受虐的时间限制,您可能希望从分段树切换到 BIT。如果 BIT/Segment 树是多维的,则常数因子可能会成为更大的问题。

    【讨论】:

    • 那么,渐近(即忽略常数因素),Segment Tree 严格来说优于 Fenwick Tree? Fenwick 树可以渐近地做比分段树更快的事情吗?
    【解决方案2】:

    我在cp-Algorithm 上发现了一些可能对你有帮助的东西。

    分段树 -

    • 在 O(logN) 内回答每个查询
    • 在 O(N) 中完成预处理
    • 优点:时间复杂度好。
    • 缺点:与其他数据结构相比,代码量更大。

    芬威克树 -

    • 在 O(logN) 内回答每个查询

    • 预处理在 O(NlogN) 中完成

    • 优点:代码最短,时间复杂度好

    • 缺点:Fenwick 树只能用于 L=1 的查询,所以它是 不适用于很多问题。

    【讨论】:

    • Fenwick 树可以在 O(N) 中构建。不是在每个条目上调用更新,而是遍历每个元素并将数组的值更新为直接父级。我们也可以通过计算 R 和计算 L 的反函数来使用 Fenwick 的树来计算 L != 1。也就是说,如果求和是函数,那么你找到 R 和 L 并从 R 中减去 L 的结果来得到答案。跨度>
    【解决方案3】:

    评论 Harsh Hitesh Shah 的回答: 反对使用 Fenwick 树的最后一点,一般来说是不成立的。 反例证明: 假设我们有一个用于前缀和的 Fenwick 树,函数 query(x) 返回从第一个索引 1 到包括索引 x 的前缀和。 如果我们想计算某个区间 [L, R] 的总和,其中 1

    【讨论】:

      【解决方案4】:

      一些附加信息:

      • 段树可以存储也可以隐式存储(就像堆一样),这将占用2n空间
      • Fenwick 树是一种在线数据结构,这意味着您可以将元素添加到末尾,就像数组一样,它仍然可以工作。默认情况下,段树没有此属性。如果您隐式存储它们,您可以通过将分段树的大小加倍(就像摊销的O(1) 数组)在摊销的O(log(n)) 中实现追加和前置操作。您需要研究段树在内存中的样子,然后相应地放置新空间(您不能只将所有额外空间放在一端)。请记住,由于段树已经占用了 2n 空间,因此每次将数组加倍时,您现在都会使用 4n 空间
      • Fenwick 树速度更快,非常易于实现。渐近界是等价的,但最基本的查询和更新代码几乎是无分支的、非递归的,并且使用的操作很少。段树版本的制作速度几乎一样快,但这确实需要额外的努力。值得庆幸的是,这仅在非常大的输入中很重要,因为隐式存储段树具有出色的空间局部性,与存储指针相比,这给了它一个很好的提升
      • Fenwick 树无法计算log(n) 中的逆查询(据我所知);也就是说,如果我们存储部分和,我想知道索引i 计算为部分和s,这将采用log(n)^2。这个过程在log(n) 中对于段树来说是微不足道的
      • 分段树可以执行许多其他查询,其中许多查询在 Fenwick 树上是不可能的。当然,您需要为这种额外的灵活性付出2n 的存储成本

      编辑:您可以log(n) 中计算此查询!这是我的实现:

      def find(self, s):
          b = 1
          while b < len(bit):
              b <<= 1
          b >>= 1
          index = 0
          cur = 0
          while b > 0:
              if bit[index + b] + cur <= s:
                  index += b
                  cur += bit[index]
              b >>= 1
          return (index, cur)
      

      这将返回最接近目标部分和的索引和总和(将始终为

      好的段树文章:https://cp-algorithms.com/data_structures/segment_tree.html

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多