分段树(Segment Tree)是一种高级数据结构,源于计算机科学领域,特别适用于解决与区间相关的动态查询和更新问题。分段树本质上是一种二叉树结构,但它并不是典型的完全二叉树或满二叉树,而是通过对输入数组进行分段并构建树形结构来实现高效的区间操作。
分段树的基本概念:
定义: 分段树主要应用于一个数组或序列中,用于快速查询和更新指定区间内的某个特定属性,如区间和、区间最大值、区间最小值等。每个节点代表一个区间,并且这个区间是其左右子节点区间的合并。
构造过程:
输入是一组数据,通常是一个数组A[0...n-1]。
将整个数组看作一个区间,构造一个包含2^n - 1个节点的树(因此,对于n个元素的数组,树的高度大致为log_2(n))。
树的每个内部节点覆盖两个子区间,它们分别由该节点的左右子节点表示,而叶子节点恰好对应数组的一个单元格。
节点信息:
每个节点不仅存储它所覆盖区间的边界,还存储了一个摘要信息,例如该区间的和、最大值、最小值或其他根据问题需求计算得到的聚合信息。
操作支持:
查询操作:给定一个查询区间,可以在O(log n)时间内找到该区间内的目标信息,如区间和或区间最大值。
更新操作:当数组中的某个元素值发生改变时,可以在O(log n)时间内重新计算受影响的所有区间信息。
实例:
对于一个数值型数组,如果我们想要支持快速查询任意连续子数组的和,那么分段树的每个节点会存储它所管辖的子数组之和。
如果我们关注区间最大值,则每个节点存储其子区间内的最大值。
分段树的实现细节:
在Java或C++等编程语言中,分段树经常通过一个数组实现,而不是实际的二叉树结构。这是因为虽然逻辑上是二叉树,但在内存中我们可以利用数组的连续性减少指针引用的成本。数组的下标与树的层级和位置之间存在一定的转换规则,以便快速定位到相应区间对应的节点。
Java代码展示如何构建一个分段树来维护数组区间和的问题:
public class SegmentTree { private int[] tree; private int n; // 构造函数,传入原始数组arr,构建分段树 public SegmentTree(int[] arr) { n = arr.length; tree = new int[n * 2]; // 由于每个节点都会存储信息,所以初始化的空间是原数组的两倍 build(arr, 0, 0, n - 1); // 递归构建分段树 } // 递归构建分段树 private void build(int[] arr, int node, int start, int end) { if(start == end) { // 叶子节点,直接赋值 tree[node] = arr[start]; } else { // 内部节点,计算左右子区间和 int mid = start + (end - start) / 2; build(arr, 2 * node + 1, start, mid); build(arr, 2 * node + 2, mid + 1, end); // 合并子区间信息 tree[node] = tree[2 * node + 1] + tree[2 * node + 2]; } } // 区间更新函数,更新区间 [i, j] 的所有元素增加 val public void update(int i, int j, int val) { updateHelper(i, j, val, 0, 0, n - 1); } private void updateHelper(int i, int j, int val, int node, int start, int end) { if (i > end || j < start) return; // 区间不重叠,直接返回 if (start >= i && end <= j) { // 区间完全包含在[i, j]内 tree[node] += (end - start + 1) * val; // 更新节点值 if (start != end) { // 如果不是叶子节点,还需递归更新子节点 updateHelper(i, j, val, 2 * node + 1, start, (start + end) / 2); updateHelper(i, j, val, 2 * node + 2, (start + end) / 2 + 1, end); } } else { // 区间部分重叠 updateHelper(i, j, val, 2 * node + 1, start, (start + end) / 2); updateHelper(i, j, val, 2 * node + 2, (start + end) / 2 + 1, end); tree[node] = tree[2 * node + 1] + tree[2 * node + 2]; // 重新计算节点值 } } // 查询区间 [queryStart, queryEnd] 的和 public int query(int queryStart, int queryEnd) { return queryHelper(queryStart, queryEnd, 0, 0, n - 1); } private int queryHelper(int queryStart, int queryEnd, int node, int start, int end) { if (queryStart > end || queryEnd < start) return 0; // 区间不重叠 if (queryStart <= start && queryEnd >= end) return tree[node]; // 区间完全包含在查询区间内 int mid = start + (end - start) / 2; return queryHelper(queryStart, queryEnd, 2 * node + 1, start, mid) + queryHelper(queryStart, queryEnd, 2 * node + 2, mid + 1, end); } }