SegmentTree 线段树

线段树

给定nn个数字s=[x0,x2,,xn1]s = [x_{0}, x_{2}, \dots, x_{n-1}],求出[p,q)[p, q)这个区间上的所有数字之和

i=pqxi\sum_{i=p}{q} x_i

其中0pq<n0 \leq p \leq q \lt n。该区间可以修改任意元素的值。

O(1)O(1)的时间复杂度内可以修改某个位置上的值,但需要O(n)O(n)的时间复杂度遍历数组累加求出[p,q)[p, q)区间上所有元素之和。而线段树的修改、求和的时间复杂度都为O(log2n)O(log_2 n)

线段树是一种二叉树,它将数组s=[x0,x1,,xn1]s = [x_{0}, x_{1}, \dots, x_{n-1}]划分区间,线段树中的每个节点会有一个区间[p,q)[p,q),表示数组上范围s[p,q)s[p,q)上被关注的内容,例如该区间上所有元素的和、最大/最小元素的值等。本问题关注区间s[p,q)s[p,q)上的元素之和。

节点[p,q)[p,q)代表数组s[p,q)s[p,q)上所有元素之和。左子树为s[p,p+q2)s[p, \frac{p + q}{2})上元素之和,右子树为s[p+q2,q)s[\frac{p + q}{2}, q)上元素之和。线段树的叶子节点是长度为11的区域[p,p+1)[p,p+1)。数组s=[0,1,2,3,4,5]s = [0, 1, 2, 3, 4, 5]的线段树如图所示:

构造操作:从根节点开始,递归的将节点[p,q)[p,q)拆分为[p,p+q2)[p, \frac{p+q}{2})[p+q2,q)[ \frac{p+q}{2},q),则显然i=pq1si=i=pp+q2si+i=p+q2q1si\sum_{i=p}^{q-1} s_{i} = \sum_{i=p}^{\frac{p+q}{2}} s_{i} + \sum_{i = \frac{p+q}{2}}^{q-1} s_{i},即父节点的值等于左右孩子节点值的和,我们将区域s[p,q)s[p,q)上所有元素之和记录在线段树的节点[p,q)[p,q)上。像这样递归的分解左右孩子,直到叶子节点为止。构造操作的时间复杂度为O(n)O(n)

更新操作:修改区间ss中任意一个值s[i]s[i]需要修改从叶子节点[i,i+1)[i, i+1)到根节点[p,q)[p,q)这一分支上的所有节点,因为它们上所存储的信息都会受到影响。更新操作的时间复杂度为O(log2n)O(log_2⁡n)

查询操作:查询区间s[i,j)s[i,j)上所有元素之和,需要递归的从根节点向下匹配。对于节点[p,q)[p, q)有以下四种情况:

(1)(1)[p,q)=[i,j)[p,q) = [i,j)则该节点上的值即为所求,算法结束;

(2)(2)jp+q2j \leq \frac{p+q}{2}说明[i,j)[i,j)只属于其左孩子;

(3)(3)ip+q2i \geq \frac{p+q}{2}说明[i,j)[i,j)只属于其右孩子;

(4)(4)i<p+q2,j>p+q2i \lt \frac{p+q}{2}, j \gt \frac{p+q}{2}说明[i,j)[i, j)中一部分属于左孩子,另一部分属于右孩子,这时将[i,j)[i,j)拆分为两部分[i,p+q2)[i, \frac{p+q}{2})[p+q2,j)[\frac{p+q}{2}, j),分别匹配自己所属的区域;

实际编码时用数组tt来表示二叉树(也可以真的写一个二叉树),节点ii的左孩子为2i+12i+1,右孩子为2i+22i+2t[0]t[0]为二叉树根节点,代表s[0,n)s[0,n)区域上所有元素之和,左孩子t[1]t[1]表示s[0,n2)s[0, \frac{n}{2})区域之和,右孩子t[2]t[2]表示s[n2,n)s[\frac{n}{2}, n)区域之和,以此类推。

源码

SegmentTree.h

SegmentTree.cpp

测试

SegmentTreeTest.cpp

Last updated