掌握了二叉树不掌握线段树真的有点可惜的,线段树本身并不难,代码也容易理解。这玩意发明出来就是为了提高效率的,例如:你现在要在数组中计算任意一段区间 Li-------Lj 之间的和,如果采用数组遍历,那么时间复杂度肯定是O(n),当然你说你可以前缀处理,可以让时间复杂度达到o(1),问题是你前缀处理的时间加起来中和考虑一波,你还是难以逃脱o(n)的魔掌,并且如果数组中有一个节点的值改变了,你不得将前缀再处理一遍??
所以,线段树运营而生,线段树就是为了来解决这种问题的,线段树的本质是一棵平衡二叉树,一般需要会如下三种操作:1.建树,2.查询。3.改变原数组中某一元素的值以后更新这棵线段树
线段树无论是查找,还是计算某一区间的和,其时间复杂度可以降低到o(log n)
我们要将数组a这些元素建议一棵线段树。首先我们要明白一点,根节点是左右节点的和
如图所示,根节点表示区间内所有的和,左子树代表0–mid,之间的和,右子树代表mid+1—end之间的和,其中mid=(L+R)/2
首先是建树操作:
我们需要node,指向线段树的当前节点,start–end表示原数组中的范围
那么node2表示其左子树,node2+1表示右子树
然后对左右子树进行递归,然后此时node指向线段树的节点的值就是node指向线段树节点的左右子树根节点之和,递归的出口就是当start==end,当范围缩小到一个点的时候,start=end,此时node所指的节点就等于a[start],a[end]这个元素
void built_tree(int node, int start, int end) //建树
{
if(start==end) //递归出口
{
tree[node]=a[start];
return ;
}
int left=node*2;//左边
int right=node*2+1; //右边
int mid=(start+end)/2;
built_tree(left,start,mid);
built_tree(right,mid+1,end);
tree[node]=tree[left]+tree[right];
}
紧接着是更新操作,将原数组中下标是div所对应的值修改为X
同样的,我们需要一个node来指向树的当前的某个节点,start—end表示原数组中的范围,我们需要他来确定我们的div在那块位置,所哟我们将mid=(left+right)/2,如果是左半球就往左边递归,如果是右半球就往右边递归,递归完成以后一定要记得,将node所指节点的值给更新了,因为其发生了改变,递归的出口很显然,当start==end,范围只有一个元素时就是出口,修改原数组中的值,并将tree[]数组中所对应的值tree[node]给修改了即tree[node]=x
oid update_node(int node,int start,int end,int x,int div)
{
//修改a[div]里面的值
if(start==end)
{
a[div]=x;
tree[node]=x;
return ;
}
int left=node*2;
int right=node*2+1;
int mid=(start+end)/2;
if(div>=start&&div<=mid)
{
update_node(left,start,mid,x,div);
}
else
{
update_node(right,mid+1,end,x,div);
}
tree[node]=tree[left]+tree[right];//改完左边改完右边需要更新父节点的值
}
最难的部分应该就是query,这个查询某一段区间上的值~~
首先,我们计算的区间范围是L–R,这一区间的值,所以自然少不了start,end,这两个变量,因为我们求的也是从a[L]—a[R]之间的和,node指向当前线段树某一节点的指针当然也不能少,我们计算某一区间的值是分区来算的,就比如上面的那个图,计算的范围在左半球,右半球没有范围,那么只需要加上左半球的结果即可,反之,如果计算的范围全在右半球,左半球没有计算的范围,那么只需要加到右半球接即可。,如果左右都有一点范围,就要两边分别计算机后相加,将两边的结果加起来即可,就比如上图中的如果是要2–4之间的值,左右两边都有一些范围,结束条件如上图的右边部分,第一个是不在区间范围直接范围0即可,或者start–end之间的范围在L–R之内则可以直接返回tree[node]
int query(int node,int start,int end,int L,int R) //求L--R之间的值
{
if(end<L||start>R)//不在区间范围内
{
return 0;
}
else if(start>=L&&end<=R)
{
return tree[node];
}
int mid=(start+end)/2;
int left=node*2;
int right=node*2+1;
int left_sum=query(left,start,mid,L,R);
int right_sum=query(right,mid+1,end,L,R);
return left_sum+right_sum;
}
完整代码~~·
#include <bits/stdc++.h>
#define Max 666
int a[6]={2,3,4,6,1,5};
int tree[24]={0};
using namespace std;
void built_tree(int node, int start, int end);
void update_node(int node,int start,int end,int x,int div);
int query(int node,int start,int end,int L,int R); //求L--R之间的值
int main()
{
int size=6;
built_tree(1,0,size-1);
for(int i=1;i<=20;i++)
{
cout<<tree[i]<<" ";
}
cout<<endl;
cout<<query(1,0,size-1,2,4)<<endl;
return 0;
}
//node表示tree的当前下标,start表示a数组开始下标,end表示a数组的结束下标
void built_tree(int node, int start, int end) //建树
{
if(start==end) //递归出口
{
tree[node]=a[start];
return ;
}
int left=node*2;//左边
int right=node*2+1; //右边
int mid=(start+end)/2;
built_tree(left,start,mid);
built_tree(right,mid+1,end);
tree[node]=tree[left]+tree[right];
}
//如果某一个叶子节点的值被改变了,那就需要更新node所对应的值
void update_node(int node,int start,int end,int x,int div)
{
//修改a[div]里面的值
if(start==end)
{
a[div]=x;
tree[node]=x;
return ;
}
int left=node*2;
int right=node*2+1;
int mid=(start+end)/2;
if(div>=start&&div<=mid)
{
update_node(left,start,mid,x,div);
}
else
{
update_node(right,mid+1,end,x,div);
}
tree[node]=tree[left]+tree[right];//改完左边改完右边需要更新父节点的值
}
int query(int node,int start,int end,int L,int R) //求L--R之间的值
{
if(end<L||start>R)//不在区间范围内
{
return 0;
}
else if(start>=L&&end<=R)
{
return tree[node];
}
int mid=(start+end)/2;
int left=node*2;
int right=node*2+1;
int left_sum=query(left,start,mid,L,R);
int right_sum=query(right,mid+1,end,L,R);
return left_sum+right_sum;
}