线段树
给定n个数字s=[x0,x2,…,xn−1],求出[p,q)这个区间上的所有数字之和
i=p∑qxi 其中0≤p≤q<n。该区间可以修改任意元素的值。
在O(1)的时间复杂度内可以修改某个位置上的值,但需要O(n)的时间复杂度遍历数组累加求出[p,q)区间上所有元素之和。而线段树的修改、求和的时间复杂度都为O(log2n)。
线段树是一种二叉树,它将数组s=[x0,x1,…,xn−1]划分区间,线段树中的每个节点会有一个区间[p,q),表示数组上范围s[p,q)上被关注的内容,例如该区间上所有元素的和、最大/最小元素的值等。本问题关注区间s[p,q)上的元素之和。
节点[p,q)代表数组s[p,q)上所有元素之和。左子树为s[p,2p+q)上元素之和,右子树为s[2p+q,q)上元素之和。线段树的叶子节点是长度为1的区域[p,p+1)。数组s=[0,1,2,3,4,5]的线段树如图所示:
构造操作:从根节点开始,递归的将节点[p,q)拆分为[p,2p+q)和[2p+q,q),则显然∑i=pq−1si=∑i=p2p+qsi+∑i=2p+qq−1si,即父节点的值等于左右孩子节点值的和,我们将区域s[p,q)上所有元素之和记录在线段树的节点[p,q)上。像这样递归的分解左右孩子,直到叶子节点为止。构造操作的时间复杂度为O(n)。
更新操作:修改区间s中任意一个值s[i]需要修改从叶子节点[i,i+1)到根节点[p,q)这一分支上的所有节点,因为它们上所存储的信息都会受到影响。更新操作的时间复杂度为O(log2n)。
查询操作:查询区间s[i,j)上所有元素之和,需要递归的从根节点向下匹配。对于节点[p,q)有以下四种情况:
(1) 若[p,q)=[i,j)则该节点上的值即为所求,算法结束;
(2) 若j≤2p+q说明[i,j)只属于其左孩子;
(3) 若i≥2p+q说明[i,j)只属于其右孩子;
(4) 若i<2p+q,j>2p+q说明[i,j)中一部分属于左孩子,另一部分属于右孩子,这时将[i,j)拆分为两部分[i,2p+q)和[2p+q,j),分别匹配自己所属的区域;
实际编码时用数组t来表示二叉树(也可以真的写一个二叉树),节点i的左孩子为2i+1,右孩子为2i+2。t[0]为二叉树根节点,代表s[0,n)区域上所有元素之和,左孩子t[1]表示s[0,2n)区域之和,右孩子t[2]表示s[2n,n)区域之和,以此类推。
源码
SegmentTree.h
SegmentTree.cpp
测试
SegmentTreeTest.cpp