线段树(Segment Tree)
一、创建线段树
//创建线段树
void buildtree(int arr[],int tree[],int node,int start,int end){
//递归到树的叶子结点、结点值为单个数组元素
if(start==end)
//修改tree数组结点值
tree[node]=arr[start];
else{
//区间分段点
int mid=(start+end)/2;
//左右结点下标(层序,根结点下标为0)
int left_node=node*2+1;
int right_node=node*2+2;
//创建左右子树
buildtree(arr,tree,left_node,start,mid);
buildtree(arr,tree,right_node,mid+1,end);
//修改根结点的值(根结点=左结点+右结点)
tree[node]=tree[left_node]+tree[right_node];
}
}
二、修改arr[]元素,更新tree[]元素值
//修改数组元素,arr[idx]=val
void update(int arr[],int tree[],int node,int start,int end,int idx,int val){
//找到待修改结点arr[idx],从叶子结点往上依次修改tree数组元素
if(start==end){
arr[idx]=val;
tree[node]=val;
}
else
{
int mid=(start+end)/2;
int left_node=node*2+1;
int right_node=node*2+2;
//寻找待修改元素的下标idx所在的区间
//idx在左区间
if(idx>=start&&idx<=mid)
update(arr,tree,left_node,start,mid,idx,val);
else
//idx在右区间
update(arr,tree,right_node,mid+1,end,idx,val);
tree[node]=tree[left_node]+tree[right_node];
}
}
三、求区间和
需要访问的区间[L-R]和[start-end]没有重叠的部分,直接return 0;([L-R]在[start-end]左、右;两种情况)
//查询数组区间和
int query(int arr[],int tree[],int node,int start,int end,int L,int R){
// printf("%d %d\n",start,end);
//需要访问的区间[L-R]和[start-end]没有重叠的部分
if(start>R||L>end)
return 0;
//[start-end]在[L,R]之内,当前结点即为[start-end]区间和,
//没必要递归至叶结点,直接返回结点值
if(L<=start&&end<=R)
return tree[node];
if(start==end)
return tree[node];
//查询区间和左右子区间均有重叠部分
else
{
int mid=(start+end)/2;
int left_node=node*2+1;
int right_node=node*2+2;
//将[start-end]分成左右、区间,求左右区间和
int sum_left=query(arr,tree,left_node,start,mid,L,R);
int sum_right=query(arr,tree,right_node,mid+1,end,L,R);
return sum_left+sum_right;
}
}
四、时间复杂度降为O(logn)
五、完整代码
#include<bits/stdc++.h>
using namespace std;
const int max_len=1000;
int tree[max_len];
//创建线段树
void buildtree(int arr[],int tree[],int node,int start,int end){
//递归到树的叶子结点、结点值为单个数组元素
if(start==end)
//修改tree数组结点值
tree[node]=arr[start];
else{
//区间分段点
int mid=(start+end)/2;
//左右结点下标(层序,根结点下标为0)
int left_node=node*2+1;
int right_node=node*2+2;
//创建左右子树
buildtree(arr,tree,left_node,start,mid);
buildtree(arr,tree,right_node,mid+1,end);
//修改根结点的值(根结点=左结点+右结点)
tree[node]=tree[left_node]+tree[right_node];
}
}
//修改数组元素,arr[idx]=val
void update(int arr[],int tree[],int node,int start,int end,int idx,int val){
//找到待修改结点arr[idx],从叶子结点往上依次修改tree数组元素
if(start==end){
arr[idx]=val;
tree[node]=val;
}
else
{
int mid=(start+end)/2;
int left_node=node*2+1;
int right_node=node*2+2;
//寻找待修改元素的下标idx所在的区间
//idx在左区间
if(idx>=start&&idx<=mid)
update(arr,tree,left_node,start,mid,idx,val);
else
//idx在右区间
update(arr,tree,right_node,mid+1,end,idx,val);
tree[node]=tree[left_node]+tree[right_node];
}
}
//查询区间和
int query(int arr[],int tree[],int node,int start,int end,int L,int R){
// printf("%d %d\n",start,end);
//需要访问的区间[L-R]和[start-end]没有重叠的部分
if(start>R||L>end)
return 0;
//[start-end]在[L,R]之内,当前结点即为[start-end]区间和,
//没必要递归至叶结点,直接返回结点值
if(L<=start&&end<=R)
return tree[node];
if(start==end)
return tree[node];
//查询区间和左右子区间均有重叠部分
else
{
int mid=(start+end)/2;
int left_node=node*2+1;
int right_node=node*2+2;
//将[start-end]分成左右、区间,求左右区间和
int sum_left=query(arr,tree,left_node,start,mid,L,R);
int sum_right=query(arr,tree,right_node,mid+1,end,L,R);
return sum_left+sum_right;
}
}
int main()
{
int n;
cin>>n;
int arr[n];
for(int i=0;i<n;i++)
cin>>arr[i];
buildtree(arr,tree,0,0,n-1);
//创建线段树
for(int i=0;i<15;i++)//n=6
printf("tree[%d]=%d\n",i,tree[i]);
cout<<endl;
//修改数组元素
update(arr,tree,0,0,n-1,4,6);
for(int i=0;i<15;i++)//n=6
printf("tree[%d]=%d\n",i,tree[i]);
cout<<endl;
//查询区间和
int add=query(arr,tree,0,0,n-1,2,5);//区间[2-5]的元素和:arr[2]+...+arr[5]
cout<<add<<endl;
return 0;
}
//在B站上面看到的一个讲得非常棒的视频,直接搬了过来。。