线段树 - 给定范围的求和

2025年3月17日 | 阅读 7 分钟

让我们考虑以下问题来理解线段树。

我们有一个数组 arr[0... n-1]。我们应该能够:

  • 查找索引 l 到 r 之间元素的和,其中 0 <= l <= r <= n-1
  • 将数组中指定元素的值更改为新值 x。我们需要执行 arr[i] = x,其中 0 <= i <= n-1。

使用嵌套循环计算范围和

从 l 到 r 循环并计算给定范围内元素的和是一个直接的解决方案。只需输入 arr[i] = x 即可更改值。虽然第一个操作需要 O(n) 时间,但第二个操作只需要 O(1) 时间。

使用前缀和计算范围总和

另一种方法是创建一个新数组,并将从开头到 i 的总和存储在第 i 个索引处。现在更新操作需要 O(n) 时间,而给定范围的总和可以在 O(1) 时间内计算出来。如果查询操作很多而更新很少,这种方法效果很好。

使用线段树计算范围总和

最有效的方法是使用线段树;我们可以使用线段树在 O(log(N)) 时间内完成这两个操作。

线段树表示

  • 输入数组中的元素是叶节点。
  • 每个内部节点都以某种方式显示了叶节点的合并方式。对于某些问题,可能存在不同的合并方式。对于此问题,合并是指汇总一个节点下的叶节点。
  • 线段树表示为树的数组表示。每个索引为 i 的节点的左子节点在索引 (2*i+1) 处,右子节点在索引 (2*i+2) 处,父节点在索引 (i - 1) / 2) 处。
Segment Tree - Sum of given Range

从给定数组构建线段树

我们首先检查段 arr[0,... n-1]。每次我们将当前段分成两半(假设它还没有成为长度为 1 的段),对两半执行相同的操作,然后将每个此类段的结果存储在适当的节点中。

除了最后一层,创建的线段树的每一层都将完全填充。此外,由于我们总是将每个段在每一层分成两部分,因此该树将是一个完全二叉树。由于创建的树始终是一个具有 n 个叶子的完全二叉树,因此总会有 n-1 个内部节点。因此,总共有 2*n - 1 个节点。

给定数组的线段树有多高?

线段树的高度将为 log2N。由于树必须使用数组表示,并且必须保留父节点和子节点索引之间的关系,因此为线段树分配的内存量将为 (2 * 2log2n-1)。

搜索范围之和

线段树构建完成后如何计算和。计算元素和的算法如下:

在上述实现中,我们需要解决三种情况。

  • 遍历树时,如果当前节点的范围不属于指定范围,则该节点的值未添加到答案中。
  • 如果给定范围与节点的范围部分重叠,则根据重叠情况向左或向右移动。
  • 如果给定范围完全重叠,则将该范围添加到答案中。

更新值

更新可以像树构建和查询操作一样递归地执行。已向我们提供了过时的索引。令 diff 表示新值。从线段树的根开始,我们将 diff 添加到其范围内包含指定索引的每个节点。如果节点不包含指定索引在其范围内,我们不修改它。

C++ 代码

上述策略的应用如下所示:

输出

Sum of values in given range = 15
Updated sum of values in given range = 22
  • 时间复杂度: O(N*log(N))
  • 辅助空间:O(N)