数据结构零基础线段树笔记1
Posted karshey
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构零基础线段树笔记1相关的知识,希望对你有一定的参考价值。
是自学的笔记。
引用和参考:
关于线段树
B站视频:【数据结构】线段树(Segment Tree) 灯神讲得好!!
引入
假设我们现在有一个非常大的数组,我们要反复地做两个操作:
- 求某个区间的和。O(n)
- 修改数组中某个值。O(1)
如果要做很多次,时间复杂度会很高。
我们怎么想办法降低求某个区间的和的复杂度呢?
我们建立一个和原数组a一样大的数组s,s是a的前缀和。
如果想求[L,R]这个区间的和,只需要求S[R]-S[L-1]
即可。 O(1)
但是如果想要更新数组的值,又变复杂了。O(n)
这两种方法的时间复杂度都高。
于是,我们引入了线段树,它可以把这两个操作的时间复杂度平均一下,把求区间和、更新数值的时间复杂度都落在O(nlogn)上,从而增加算法的效率。
线段树的性质和作用
1、线段树的每个节点都代表一个区间。
2、线段树具有唯一的根节点,代表的区间是整个统计范围,如[1,N]。
3、线段树的每个叶节点都代表一个长度为1的元区间[x,x]。
4、对于每个内部节点[l,r],它的左子结点是[l,mid],右子节点是[mid+1,r],其中mid=(l+r)/2(向下取整)。
接下来我们根据数组来建线段树。
怎么用呢?
如果我们想求[2,5]区间的和,根据上图,则是求[2,2]+[3,5]=5+27=32;
如果我们想把a[4]改为6,根据上图,则值为9,16,27,36的值都要改,即都减3.
变成这样:
如何保存线段树
我们用数组来保存线段树。
如图,若已知根节点,我们求左右子节点的方法如下:
left=2*node+1;
right=2*node+2;
代码
建树build
//参数分别是:原数组,线段数组,当前节点,数组左、右范围
void build(int a[],int tree[],int node,int start,int end)
//递归边界:遇到叶子节点时 即[x,x]
if(start==end)
tree[node]=a[start];
//当前tree结点值就是a数组的值 因为是叶子节点
else
int mid=(start+end)/2;
//求左右节点的下标
int left=2*node+1;
int right=2*node+2;
//递归地建左子树
build(a,tree,left,start,mid);
//递归地建右子树
build(a,tree,right,mid+1,end);
//当前结点的值等于左右子节点的值之和
tree[node]=tree[left]+tree[right];
让我们看看树长成什么样:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+10;
int a[]=1,3,5,7,9,11;
int size=6;//存数组大小
int tree[N]=0;
//参数分别是:原数组,线段数组,当前节点,数组左、右范围
void build(int a[],int tree[],int node,int start,int end)
//递归边界:遇到叶子节点时 即[x,x]
if(start==end)
tree[node]=a[start];
//当前tree结点值就是a数组的值 因为是叶子节点
else
int mid=(start+end)/2;
//求左右节点的下标
int left=2*node+1;
int right=2*node+2;
//递归地建左子树
build(a,tree,left,start,mid);
//递归地建右子树
build(a,tree,right,mid+1,end);
//当前结点的值等于左右子节点的值之和
tree[node]=tree[left]+tree[right];
int main()
build(a,tree,0,0,5);
for(int i=0;i<=14;i++)
printf("tree[%d] = %d\\n",i,tree[i]);
return 0;
输出:
tree[0] = 36
tree[1] = 9
tree[2] = 27
tree[3] = 4
tree[4] = 5
tree[5] = 16
tree[6] = 11
tree[7] = 1
tree[8] = 3
tree[9] = 0
tree[10] = 0
tree[11] = 7
tree[12] = 9
tree[13] = 0
tree[14] = 0
嗯,对比一下上面的图,确实是这样。
更新数值update-单点更新
//参数分别是:原数组a,线段树数组tree,当前结点,建树左、右范围,要改原数组的下标x,要改为的值
void update(int a[],int tree[],int node,int start,int end,int x,int val)
//找到a[x],修改值
if(start==end)
//改了原数组和线段树的叶子节点
a[x]=val;
tree[node]=val;
else
int mid=(start+end)/2;
int left=2*node+1;
int right=2*node+2;
//要修改的值在左子树:递归地更新
if(x>=start&&x<=mid)
update(a,tree,left,start,mid,x,val);
else //在右子树
update(a,tree,right,mid+1,end,x,val);
//因为更新了叶子节点,所以也要更新其祖先结点
tree[node]=tree[left]+tree[right];
让我们看看更新完的树长成什么样:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+10;
int a[]=1,3,5,7,9,11;
int size=6;//存数组大小
int tree[N]=0;
//参数分别是:原数组,线段数组,当前节点,数组左、右范围
void build(int a[],int tree[],int node,int start,int end)
//递归边界:遇到叶子节点时 即[x,x]
if(start==end)
tree[node]=a[start];
//当前tree结点值就是a数组的值 因为是叶子节点
else
int mid=(start+end)/2;
//求左右节点的下标
int left=2*node+1;
int right=2*node+2;
//递归地建左子树
build(a,tree,left,start,mid);
//递归地建右子树
build(a,tree,right,mid+1,end);
//当前结点的值等于左右子节点的值之和
tree[node]=tree[left]+tree[right];
//参数分别是:原数组a,线段树数组tree,当前结点,建树左、右范围,要改原数组的下标x,要改为的值
void update(int a[],int tree[],int node,int start,int end,int x,int val)
//找到a[x],修改值
if(start==end)
//改了原数组和线段树的叶子节点
a[x]=val;
tree[node]=val;
else
int mid=(start+end)/2;
int left=2*node+1;
int right=2*node+2;
//要修改的值在左子树:递归地更新
if(x>=start&&x<=mid)
update(a,tree,left,start,mid,x,val);
else //在右子树
update(a,tree,right,mid+1,end,x,val);
//因为更新了叶子节点,所以也要更新其祖先结点
tree[node]=tree[left]+tree[right];
int main()
build(a,tree,0,0,5);
update(a,tree,0,0,5,4,6);//想要把a[4]=6
for(int i=0;i<=14;i++)
printf("tree[%d] = %d\\n",i,tree[i]);
return 0;
输出:
tree[0] = 33
tree[1] = 9
tree[2] = 24
tree[3] = 4
tree[4] = 5
tree[5] = 13
tree[6] = 11
tree[7] = 1
tree[8] = 3
tree[9] = 0
tree[10] = 0
tree[11] = 7
tree[12] = 6
tree[13] = 0
tree[14] = 0
对比一下图,嗯,确实是这样的。
求区间和query
操作步骤:
- 向下依次寻找包含在目标区间中的区间,并累加
- 与建树的函数相比,query函数增加了两个参数L,R,即求a的区间[L,R]的和
假如我们想求:a[2]+a[3]+…a[5],即区间[2,5]=32
query模板代码:
//参数:原数组a、线段树数组tree、当前节点、树的区间范围,要求的区间范围
int query(int a[],int tree[],int node,int start,int end,int L,int R)
//若目标区间与当前区间没有重叠,结束递归返回0
//这样 start end L R
if(start>R||end<L)
return 0;
//目标区间包含当时区间,返回节点值
// L start end R
else if(L<=start&&end<=R)
return tree[node];
else
int mid=(start+end)/2;
int left=2*node+1;
int right=2*node+2;
//递归地计算左边区间的值
int sum_left=query(a,tree,left,start,mid,L,R);
//递归地计算右边区间的值
int sum_right=query(a,tree,right,mid+1,end,L,R);
//相加就是答案
return sum_left+sum_right;
总代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+10;
int a[]=1,3,5,7,9,11;
int size=6;//存数组大小
int tree[N]=0;
//参数分别是:原数组,线段数组,当前节点,数组左、右范围
void build(int a[],int tree[],int node,int start,int end)
//递归边界:遇到叶子节点时 即[x,x]
if(start==end)
tree[node]=a[start];
//当前tree结点值就是a数组的值 因为是叶子节点
else
int mid=(start+end)/2;
//求左右节点的下标
int left=2*node+1;
int right=2*node+2;
//递归地建左子树
build(a,tree,left,start,mid);
//递归地建右子树
build(a,tree,right,mid+1,end);
//当前结点的值等于左右子节点的值之和
tree[node]=tree[left]+tree[right];
//参数分别是:原数组a,线段树数组tree,当前结点,建树左、右范围,要改原数组的下标x,要改为的值
void update(int a[],int tree[],int node,int start,int end,int x,int val)
//找到a[x],修改值
if(start==end)
//改了原数组和线段树的叶子节点
a[x]=val;
tree[node]=val;
else
int mid=(start+end)/2;
int left=2*node+1;
int right=2*node+2;
//要修改的值在左子树:递归地更新
if(x>=start&&x<=mid)
update(a,tree,left,start,mid,x,val);
else //在右子树
update(a,tree,right,mid+1,end,x,val);
//因为更新了叶子节点,所以也要更新其祖先结点
tree[node]=tree[left]+tree[right];
//参数:原数组a、线段树数组tree、当前节点、树的区间范围,要求的区间范围
int query(int a[],int tree[],int node,int start,int end,int L,int R)
//若目标区间与当前区间没有重叠,结束递归返回0
//这样 start end L R
if(start>R||end<L)
return 0;
//目标区间包含当时区间,返回节点值
// L start end R
else if(L<=start&&end<=R)
return tree[node];
else
int mid=(start+end)/2;
int left=2*node+1;
int right=2*node+2;
//递归地计算左边区间的值
int sum_left=query(a,tree,left,start,mid,L,R);
//递归地计算右边区间的值
int sum_right=query(a,tree,right,mid+1,end,L,R);
//相加就是答案
return sum_left+sum_right;
int main()
build(a,tree,0,0,5);
//update(a,tree,0,0,5,4,6);//想要把a[4]=6
cout<<query(a,tree,0,0,5,2,5);
return 0;
输出:
32
嗯,确实是这样。
完整模板:build、update、query
//参数分别是:原数组,线段数组,当前节点,数组左、右范围
void build(int a[]ACM学习笔记:线段树