线段树本质上是一棵二叉搜索树(体现在查找阶段),与普通的二叉树不一样,这个二叉树有点特别:用left和right来表示一个区间,即 [left, right],而其值val,则用来表征这个区间的某些特征,例如:区间最大值,区间最小值,区间和;并且容易知道,当left和right相等时,代表叶结点,其值就是某个数组当前位置的值。
一棵区间为[1, 5]上的线段树
线段树在处理某些问题时是非常方便的,一个经典的问题是:给你一个数组{ai},总共n个值(数组从1到n排列),然后给你一个区间 [l, r],对于每次查询,给出区间 [l, r]的和(或者最大值,最小值)
聪明的小伙伴一定会马上想到用前缀和来处理这个问题,是的,前缀和在预处理阶段为o(n)复杂度,而每次查询只需要o(1),对于这种静态问题是十分方便的,但是,如果我们在上面的问题中加上 “每次随机更新数组中的某个值” 这一条件时,维护前缀和的开销其实是十分巨大的,因为对于每次修改,你不得不对前缀和进行o(n)的更新,如果数据量过大,这种方式是肯定不行的。所以,我们采用线段树这一数据结构来解决这个问题
典型的线段树可以采用如下定义:
struct TreeNode
{
int val;
int left,right;
}tree[4*N];
以下为线段树的三个操作,建树,查询,更新
注:代码以求区间和为例
void build(int root,int l,int r)//建树
{
tree[root].left=l;
tree[root].right=r;
if(l==r)//叶结点
{
tree[root].val=arr[l];
return;
}
int mid=(l+r)/2;
build(2*root,l,mid);//左子树
build(2*root+1,mid+1,r);//右子树
tree[root].val=tree[2*root].val+tree[2*root+1].val;//求和
}
int query(int root,int l,int r)//查询[l,r]的和
{
if(tree[root].left==l&&tree[root].right==r)//正好等于该区间
{
return tree[root].val;
}
int mid=(tree[root].left+tree[root].right)/2;
if(mid>=r)
return query(2*root,l,r);//在左子树查找
else if(mid+1<=l)
return query(2*root+1,l,r);//在右子树查找
return query(2*root,l,mid)+query(2*root+1,mid+1,r);//跨区间查找
}
void update(int root,int pos,int val)//更新pos位置的值为val
{
if(tree[root].left==pos&&tree[root].right==pos)
{
tree[root].val=val;
return;
}
int mid=(tree[root].left+tree[root].right)/2;
if(pos<=mid)
update(2*root,pos,val);
else update(2*root+1,pos,val);
tree[root].val=tree[2*root].val+tree[2*root+1].val;//更新和
}
线段树每次查询和更新操作时,其复杂度为O(logn),这对于我们来说,是较为理想的。
应用问题:
第一行给出n,m,分别为数组元素的个数以及请求个数
第二行给出n个数,
随后m行,每行给出一个操作:opt x y
当opt = 1,更新数组中x位置的值为y
当opt = 2,输出[x, y]的区间和
注:下标从1开始
/* test case
8 4
1 2 3 4 5 6 7 8
1 2 0
2 1 8
1 8 0
2 2 2
*/
/*output
34
0
*/
#include <bits/stdc++.h>
#define N 1024
using namespace std;
struct TreeNode
{
int val;
int left,right;
}tree[4*N];
int arr[N];
void build(int root,int l,int r)
{
tree[root].left=l;
tree[root].right=r;
if(l==r)
{
tree[root].val=arr[l];
return;
}
int mid=(l+r)/2;
build(2*root,l,mid);
build(2*root+1,mid+1,r);
tree[root].val=tree[2*root].val+tree[2*root+1].val;
}
int query(int root,int l,int r)
{
if(tree[root].left==l&&tree[root].right==r)
{
return tree[root].val;
}
int mid=(tree[root].left+tree[root].right)/2;
if(mid>=r)
return query(2*root,l,r);
else if(mid+1<=l)
return query(2*root+1,l,r);
return query(2*root,l,mid)+query(2*root+1,mid+1,r);
}
void update(int root,int pos,int val)
{
if(tree[root].left==pos&&tree[root].right==pos)
{
tree[root].val=val;
return;
}
int mid=(tree[root].left+tree[root].right)/2;
if(pos<=mid)
update(2*root,pos,val);
else update(2*root+1,pos,val);
tree[root].val=tree[2*root].val+tree[2*root+1].val;
}
int main()
{
//freopen("2.txt","r",stdin);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>arr[i];
build(1,1,n);
for(int i=0;i<m;++i)
{
int opt,x,y;
cin>>opt>>x>>y;
if(opt==1)
update(1,x,y);
else
cout<<query(1,x,y)<<endl;
}
return 0;
}
本文详细介绍了一种高效的数据结构——线段树,通过实例演示了如何使用线段树处理区间查询和更新问题,并提供了完整的代码实现。
3169

被折叠的 条评论
为什么被折叠?



