需要清楚地解释范围更新和范围查询二进制索引树

时间:2015-01-10 11:25:24

标签: algorithm data-structures fenwick-tree

我已经完成了Range更新的几个教程 - 二进制索引树的范围查询。我无法理解他们中的任何一个。我不了解建造另一棵树的必要性。

有人可以用简单的英语向我解释一下吗?

3 个答案:

答案 0 :(得分:7)

尝试以更直观的方式解释(我理解的方式)。我将它分为四个步骤:

假设更新介于A和B之间,并且查询是任何索引的前缀查询< = X

第一个范围更新/点查询树(T1)

第一个是简单的范围更新/点查询树。当您使用V将A更新为B时,实际上您将V添加到位置A,因此任何前缀查询X> = A都会受其影响。然后你从B + 1删除V,所以任何查询X> = B + 1都没有看到V添加到A.这里没有惊喜。

对范围更新/点树

的前缀查询

T1.sum(X)是对X处第一棵树的点查询。我们乐观地假设在X之前每个元素等于X处的值。这就是为什么我们做T1.sum(X)*X。显然这不是正确的,这就是我们:

的原因

使用修改后的范围更新/点查询树来修复结果(T2)

更新范围时,我们还会更新第二个树,以告知我们必须修复第一个T1.sum(X)*X查询。此更新包括从任何查询X> = A中删除(A-1)*V。然后我们为X> = B添加B*V。我们做后者是因为对第一棵树的查询不会为X> = B + 1(因为T1.add(B+1, -V))返回V,所以我们需要以某种方式告诉我有一个区域{{ 1}}对于任何查询X> = B + 1。我们已从A中删除(B-A+1)*V,我们只需要将(A-1)*V添加回B + 1.

将它们全部包装

B*V

答案 1 :(得分:2)

让我试着解释一下。

  1. 为什么我们需要第二棵树?我无法回答这个问题。严格地说,我无法证明只使用一个二进制索引树来解决这个问题是不可能的(我从未在任何地方见过这样的证据)。

  2. 怎么能想出这个方法?再说一次,我不知道。我不是这个算法的发明者。所以我不知道为什么它看起来完全像这样。我唯一要解释的是这种方法的工作原理和方法。

  3. 为了更好地理解这个算法,我们首先应该忘记二进制索引树本身是如何工作的。我们将其视为一个支持两个操作的黑盒子:更新一个元素并在O(log n)时间内执行范围求和查询。我们只想使用一个或多个这样的“黑盒子”来构建一个可以有效执行范围更新和查询的数据结构。

  4. 我们将维护两个二进制索引树:T1T2。我将使用以下表示法:T.add(pos, delta)pos位置delta执行点更新,T.get(pos)执行点数更新[0 ... pos]。我声称如果更新功能如下所示:

    void update(left, right, delta)
        T1.add(left, delta)
        T1.add(right + 1, -delta);
        T2.add(left, delta * (left - 1))
        T2.add(right + 1, -delta * right);
    

    并以这种方式回答范围查询(对于前缀[0 ... pos]):

    int getSum(pos)
        return T1.sum(pos) * pos - T2.sum(pos)
    

    然后结果总是正确的。

  5. 为证明其正确性,我将证明以下陈述:每次更新都会相应地更改答案(它通过归纳为所有操作提供证明,因为最初所有操作都填充零并且正确性很明显)。假设我们有left, right, DELTA更新,现在我们正在执行pos查询(即0 ... pos sum)。让我们考虑3种情况:
     i)pos < L。此更新不会影响此查询。答案是正确的(由于归纳假设)  ii)L <= pos <= R。此更新将添加DELTA * pos - (left - 1) * pos。这意味着DELTA被添加pos - L + 1次。这正是应该如何。因此,这种情况也得到了正确处理 iii)pos > R。此更新将添加0 + DELTA * right - DELTA * (left - 1)。也就是说,DELTA恰好添加right - left + 1次。这也是正确的。

    我们刚刚展示了诱导步骤的正确性。因此,这个算法是正确的。

  6. 我只展示了如何回答[0, pos]和查询。但现在回答[left, right]查询很简单:它只是getSum(right) - getSum(left - 1)

  7. 就是这样。我已经证明这个算法是正确的。现在让我们尝试对其进行编码并查看它是否有效(它只是一个草图,因此代码质量可能不是很好):

    #include <bits/stdc++.h>
    
    using namespace std;
    
    // Binary index tree.
    struct BIT {
      vector<int> f;
    
      BIT(int n = 0) {
        f.assign(n, 0);
      }
    
      int get(int at) {
        int res = 0;
        for (; at >= 0; at = (at & (at + 1)) - 1)
          res += f[at];
        return res;
      }
    
      void upd(int at, int delta) {
        for (; at < f.size(); at = (at | (at + 1)))
          f[at] += delta;
      }
    };
    
    // A tree for range updates and queries.
    struct Tree {
      BIT f1;
      BIT f2;
    
      Tree(int n = 0): f1(n + 1), f2(n + 1) {}
    
      void upd(int low, int high, int delta) {
        f1.upd(low, delta);
        f1.upd(high + 1, -delta);
        f2.upd(low, delta * (low - 1));
        f2.upd(high + 1, -delta * high);
      }
    
      int get(int pos) {
        return f1.get(pos) * pos - f2.get(pos);
      }
    
      int get(int low, int high) {
        return get(high) - (low == 0 ? 0 : get(low - 1));
      }
    };
    
    // A naive implementation.
    struct DummyTree {
      vector<int> a;
    
      DummyTree(int n = 0): a(n) {}
    
      void upd(int low, int high, int delta) {
        for (int i = low; i <= high; i++)
          a[i] += delta;
      }
    
      int get(int low, int high) {
        int res = 0;
        for (int i = low; i <= high; i++)
          res += a[i];
        return res;
      }
    };
    
    int main() {
      ios_base::sync_with_stdio(0);
      int n = 100;
      Tree t1(n);
      DummyTree t2(n);
      for (int i = 0; i < 10000; i++) {
        int l = rand() % n;
        int r = rand() % n;
        int v = rand() % 10;
        if (l > r)
          swap(l, r);
        t1.upd(l, r, v);
        t2.upd(l, r, v);
        for (int low = 0; low < n; low++)
          for (int high = low; high < n; high++)
        assert(t1.get(low, high) == t2.get(low, high));
      }
      return 0;
    }
    

    哦,是的。我忘记了时间复杂度分析。但这里很简单:我们对二进制索引树进行一定数量的查询,因此每个查询都是O(log n)

答案 2 :(得分:1)

我花了很多天时间来了解范围更新,在这里用例子写了简单的解释: https://github.com/manoharreddyporeddy/AdvancedDataStructuresAndAlgorithms/blob/master/BIT_fenwick_tree_explanation.md