什么是线段树?
当你要面临一个问题需要反复区间修改以及查询区间内的某个结果(如区间和、区间最值),如果用朴素的暴力修改与查询时间复杂度会非常的高,这时利用线段树或者树状数组来维护就可以以O(log)级别的时间复杂度来得到结果,这就是线段树。
如以下用长度为10的数组,来建立线段树,维护区间和,单点修改操作.
用线段树构造的逻辑图如下所示,以数组下标左右两端为长度,进行划分,每次分两段,直至叶子节点(L == R)不能分为止。
由上图可知,每个叶子节点 L == R 对应原数组一个对应值a[L] 或 a[R]。每个节点均对应原数组a区间[L,R],在每个节点处留一个sum值,该sum值就是区间[L,R]的和.
观察上述线段树的逻辑结构,可知是一颗平衡树,是一颗完全二叉树。所以父节点与左右儿子之间的关系可以用一个一维数组表示,当前节点为p,左儿子为p2,右儿子为p2+1。
所以在用线段树维护区间时,需要先建树,在已经建成的树上进行修改与查询
线段树的一般数据结构
struct STree
{
int l, r;// 当前节点的左端点、右端点
int dat, sum;//sum区间和值
}t[SIZE * 4];
int a[SIZE];//原数组
故线段树有三个基本的操作:
1.建树
step1: 先存储当前节点p的左端点值l与右端点值r.
step2: 判断左端点与右端点是否相等 相等 把原数组的对应下标值赋给叶子节点值返回
step3: 用分治的思想从当前左右端点值之和取中点进行划分
step4:递归左儿子节点,区间缩短至[L,mid]
step5:递归右儿子节点,区间缩短至[mid+1,R]
重复上述步骤即可
void build(int p, int l, int r)
{
t[p].l = l, t[p].r = r;
if(l == r){
t[p].sum = a[l];
return;
}
int mid = (l + r) >> 1;
build(p*2,l , mid);
build(p*2+1, mid+1, r);
t[p].sum = t[p*2].sum + t[p*2+1].sum;
}
用法: build(1,1,10);//建立[1,10]区间的线段树
2.单点修改原数组a[x]值
从根节点开始递归,找到左右端点值等于x的值,即找到对应修改x的值在叶子节点的位置,类似搜索树的方式进行探索.
void change(int p,int x,int v)
{
if(t[p].l == t[p].r){
t[p].sum = v;
return;
}
int mid = (t[p].l + t[p].r) >> 1;
if(x <= mid) change(p*2, x, v);
else change(p*2+1, x, v);
t[p].sum = t[p*2].sum + t[p*2+1].sum;
}
使用方法: change(1, x, v);//x为下标值,v为修改值
3.查询区间[L,R]的区间和
有二种情况:
t[p].l t[p].r 为当前树上节点
情况一:当[L,R] 区间 包含了 当前树上的节点所表示的区间和时,直接返回
情况二:不包含时,mid为当前节点区间的中点,L<=mid,满足查询[L,mid]剩余和
再R>mid,查找满足[mid+1,R]剩余和,返回次区间即可.
int ask(int p, int l, int r)
{
if(l >= t[p].l && t[p].r >= r){
return t[p].sum;
}
int mid = (t[p].l + t[p].r) >> 1;
int val = 0;
if(l <= mid) val+=(p*2,l,r);
if(r > mid) val +=(p*2+1,l,r);
return val;
}