芬威克树简介和示例

举报
码乐 发表于 2025/03/02 08:27:06 2025/03/02
40 0 0
【摘要】 1 简介让 f 是某个 group作(具有恒等元素和逆元素的集合的二进制关联函数)和 A 是长度为 N。表示 f 美元的中缀表示法为 * ;那是 f(x,y) = x*y 对于任意整数 x,y. (由于这是结合的,我们将省略括号以执行 f 当使用中缀表示法时。Fenwick 树是一种数据结构,它:计算 function 的值 ,f在给定范围内 [l, r](即 A_l * A_{l+1} ...

1 简介

让 f 是某个 group作(具有恒等元素和逆元素的集合的二进制关联函数)和 A 是长度为 N。

表示 f 美元的中缀表示法为 * ;
那是 f(x,y) = x*y 对于任意整数 x,y. (由于这是结合的,我们将省略括号以执行 f 当使用中缀表示法时。

Fenwick 树是一种数据结构,它:

计算 function 的值 ,f在给定范围内 [l, r](即 A_l * A_{l+1} * \dots * A_r) 在 O(\log N)$时间

更新 A在 O(\log N)$时间
需要 O(N)memory 所需的内存量相同 A
易于使用和编码,尤其是在多维数组的情况下

Fenwick 树最常见的应用是计算范围的总和。 例如,使用整数集的加法作为组运算,即 
f(x,y) = x + y:二进制运算,

* 是 + 在本例中,因此

		A_l * A_{l+1} * ... * A_r = A_l + A_{l+1} + \dots + A_{r}.

Fenwick 树也称为二进制索引树 (BIT)。 它首次在一篇题为“累积频率表的新数据结构”的论文(Peter M. Fenwick,1994 年)中进行了描述。

  • 简单示例

为简单起见,我们将假设该函数 f 定义为 f(x,y) = x + y在整数上。

假设我们得到一个整数数组, A[0 … N-1]. (请注意,我们使用的是从零开始的索引。
Fenwick 树只是一个数组, T[0 … N-1],其中每个元素等于 A 在某个范围内, [g(i), i]:

T_i = sum_{j = g(i)}^{i}{A_j}

g 是满足  0 <= g(i) <= i.
我们将在接下来的几段中定义 g 。

数据结构被称为树,因为它以树的形式很好地表示了它,尽管我们不需要用节点和边对实际的树进行建模。
我们只需要维护数组 T 处理所有查询。

注意:此处介绍的 Fenwick 树使用从零开始的索引。 许多人使用从 1 开始的索引的 Fenwick 树版本。
因此,您还可以在 implementation 部分找到使用从 1 开始的索引的替代实现。
两个版本在时间和内存复杂性方面是等效的。

现在我们可以为上面提到的两个作编写一些伪代码。 下面,我们得到 的元素之和

下图显示了 Fenwick 树作为 tree 的可能解释。 树的节点显示它们覆盖的范围

3 在一维数组中求和

我们介绍了 Fenwick 树的实现,用于 sum 查询和单个更新。

普通的 Fenwick 树只能回答 
[0, r]使用 ,但是我们也可以回答sum(int r) [l, r]通过计算两个和。 
[0, r]和 [0, l-1]并减去它们。 这是在方法中处理的。sum(int l, int r)

此外,此实现支持两个构造函数。 您可以创建用零初始化的 Fenwick 树,也可以将现有数组转换为 Fenwick 形式

		 struct FenwickTree {
      vector<int> bit;  // binary indexed tree
      int n;

      FenwickTree(int n) {
          this->n = n;
          bit.assign(n, 0);
      }

      FenwickTree(vector<int> const &a) : FenwickTree(a.size()) {
          for (size_t i = 0; i < a.size(); i++)
              add(i, a[i]);
      }

      int sum(int r) {
          int ret = 0;
          for (; r >= 0; r = (r & (r + 1)) - 1)
              ret += bit[r];
          return ret;
      }

      int sum(int l, int r) {
          return sum(r) - sum(l - 1);
      }

      void add(int idx, int delta) {
          for (; idx < n; idx = idx | (idx + 1))
              bit[idx] += delta;
      }
  };

4 在FWK树种新增修改与求和的操作

芬威克用于解决我们在固定大小上有以下类型的多个作的问题。

前缀运算 (Sum, Product, XOR, OR, etc).请注意,range作也可以使用 prefix 来解决。例如,从索引 L 到 R 的范围 sum 是前缀 sum till R(包括减去前缀 sum til L-1)。

更新数组项
两个作的时间复杂度都是 O(Log n)。请注意,我们需要 O(n Log n) 预处理时间和 O(n) 辅助空间。

  type FenwickTree struct {
      tree []int
  }

  func NewFenwickTree(n int) *FenwickTree {
      return &FenwickTree{make([]int, n+1)}
  }

  func (ft *FenwickTree) Update(index, delta int) {
      for i := index + 1; i < len(ft.tree); i += i & -i {
          ft.tree[i] += delta
      }
  }

  func (ft *FenwickTree) Edit(index, delta int) {
      for i := index + 1; i < len(ft.tree); i += i & -i {
          ft.tree[i] = delta
      }
  }

  func (ft *FenwickTree) Query(index int) int {
      sum := 0
      for i := index + 1; i > 0; i -= i & -i {
          j := i
          fmt.Printf("sun & j:%v\n", j&-j)
          j -= j & -j
          fmt.Printf("sun index j:%v\n", j)
          sum += ft.tree[i]
      }
      return sum
  }

  func main() {
      ft := NewFenwickTree(5)
      fmt.Printf("init ft:%#v \n", ft)
      // 示例用法:更新第3个元素的值为2
      ft.Update(2, 2)
      fmt.Printf("update 2 2 ft:%#v \n", ft)

      // 示例用法:更新第4个元素的值 +3
      ft.Update(3, 3)
      fmt.Printf("update 3 3 ft:%#v \n", ft)

      // 示例用法:查询前4个元素的和
      sum := ft.Query(3)
      fmt.Println("前4个元素的和:", sum) // 输出: 5

      // 示例用法:更新第4个元素的值 = 3
      ft.Edit(3, 3)
      fmt.Printf("edit 3 3 ft:%#v \n", ft)

      // 示例用法:查询第4个元素的和
      sum2 := ft.Query(3)
      fmt.Println("前4个元素的和:", sum2) // 输出: 5

      var k int = 0
      for k = 0; k < 10; k++ {

          fmt.Printf("k of: %v  &k: %v, binary:%b\n", k, k&-k, k&-k)
          fmt.Printf("k of: %v ^k: %v, binary:%b\n", k, k^-k, k^-k)
          fmt.Printf("k of: %v |k: %v, binary:%b\n", k, k|-k, k|-k)
          fmt.Printf("k of: %v <<k: %v, binary:%b\n", k, k<<1, k<<1)
          fmt.Printf("k of: %v >>k: %v, binary:%b\n", k, k>>1, k>>1)
          ks := -k
          fmt.Printf("k of: %v  binary:%b. binary -k:%b\n", k, k, ks)
          fmt.Printf("%b\n", *(*[8]byte)(unsafe.Pointer(&ks)))
      }
  }

5 小结

Fenwick 树可以支持以下范围作:

  点更新和范围查询
  范围更新和点查询
  Range Update 和 Range Query
  1. 点更新和范围查询

这只是上面解释的普通 Fenwick 树。

  1. 范围更新和点查询

使用简单的技巧,我们还可以进行反向作:增加范围并查询单个值。

让 Fenwick 树初始化为零。
假设我们想由 x 要增加 [l, r]. 我们对 Fenwick 树进行两个点更新作,它们是 和 add(l, x)add(r+1, -x)

如果我们想获取A[i],我们只需要使用普通 range sum 方法获取前缀 sum。
要了解为什么会这样,我们可以再次关注前面的 increment作。
如果 i < l,则这两个 update作对查询没有影响,我们得到 sum 0. 如果 i in [l, r],那么我们得到答案

x 因为第一次更新作。 如果 i > r,则第二个 update作将取消第一个 update作的效果。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。