线段树

来源:互联网 发布:外贸数据的计价方式有 编辑:程序博客网 时间:2024/06/07 14:44

本质:

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。

定义:

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。
线段树至少支持下列操作:
Insert(t,x):将包含在区间 int 的元素 x 插入到树t中;
Delete(t,x):从线段树 t 中删除元素 x;
Search(t,x):返回一个指向树 t 中元素 x 的指针。

基本结构:

线段树是建立在线段的基础上,每个结点都代表了一条线段[a,b]。长度为1的线段称为元线段。非元线段都有两个子结点,左结点代表的线段为[a,(a + b) / 2],右结点代表的线段为[((a + b) / 2)+1,b]。
长度范围为[1,L] 的一棵线段树的深度为log (L) + 1。这个显然,而且存储一棵线段树的空间复杂度为O(L)。
线段树支持最基本的操作为插入和删除一条线段。下面以插入为例,详细叙述,删除类似。
将一条线段[a,b] 插入到代表线段[l,r]的结点p中,如果p不是元线段,那么令mid=(l+r)/2。如果b

实际应用:

LAZY思想:根据Lazy思想,我们可以在不代表原线段的结点上增加一个值toadd,即为对这个结点,留待以后执行的插入操作k值的总和。对整个结点插入时,只更新sum和toadd值而不向下进行,这样时间复杂度可证明为O(logN)。
对一个toadd值为0的结点整个进行查询时,直接返回存储在其中的sum值;而若对toadd不为0的一部分进行查询,则要更新其左右子结点的sum值,然后把toadd值传递下去,再对这个查询本身,左右子结点分别递归下去。时间复杂度也是O(nlogN)。

样板题及代码:

题目地址:
http://acm.hdu.edu.cn/showproblem.php?pid=1166
代码如下

//解决线段树的基础问题//敌兵布阵//插入节点,删除节点,其中还有维护的过程//找出某个区间的信息//第一步把数据暂时录入一个数组之中//第二步实现线段树的建立//第三步各种操作//大思想递归#include<iostream>#include<stdio.h>#include<algorithm>#include<string.h>using namespace std;#define Max 50005int num[Max];//题目要求士兵的最大数量int tree[Max<<2];//建树的空间开成需处理空间的4倍void build(int p,int l,int r)//p为根节点,l为左端点,r为右端点{    if(l==r) {tree[p] = num[l];return;}//当左右端点相等时代表该点是叶子节点,赋初值并递归出口    int mid=(l+r)>>1;//相当于(l+r)/2    build(p<<1,l,mid);//p<<1相当于p*2,是左儿子    build(p<<1|1,mid+1,r);//p<<1|1相当于p*2+1,是右儿子    tree[p]=tree[p<<1]+tree[p<<1|1];//父亲的值等于左右儿子的和}//建树的思路//扫描整个临时区间//先维护左节点和右节点//再根据左子树和右子树的信息,来确定其父亲节点。//递归的过程//几乎完美的函数void change(int p,int l,int r,int x,int num)//此处num代表需要更新的数据,加用正数,减用负数{    if(l==r) {tree[p]+=num;return;}//如果是叶子节点,就是对应p位置更新操作    int mid=(l+r)/2;    if(x<=mid) change(p*2,l,mid,x,num);//如果是左孩子,就在左孩子里找    else change(p<<1|1,mid+1,r,x,num);    tree[p]=tree[p<<1]+tree[p<<1|1];}//节点增加和删除元素//函数接口的理解//p父节点,l左端点,r右端点//x欲修改的节点的位置//num修改的数目的大小int Find(int p,int l,int r,int x,int y){    if(x<=l&&r<=y) return tree[p];    //如果[l,r]要查询的区间包含root节点,表示直接返回root节点的sum值    int mid=(l+r)>>1;    if(y<=mid) return Find(p<<1,l,mid,x,y);//如果y小于等于mid小的,则说明区间在左子树    if(x>mid) return Find(p<<1|1,mid+1,r,x,y);//如果x比mid大,则说明区间在有子树    return (Find(p<<1,l,mid,x,mid)+Find(p<<1|1,mid+1,r,mid+1,y));    //否则说明横跨两个区间,需要在两个区间同时查找}//区间查找//递归的思想//高效int main(){    int T,N,a,b;    scanf("%d",&T);    string str;    int ans=1;    while(T--)    {        scanf("%d",&N);        for(int i=1;i<=N;i++) scanf("%d",&num[i]);        build(1,1,N);        printf("Case %d:\n",ans++);        //cout<<"Case "<<ans++<<":"<<endl;        while(cin>>str)        {            if(str=="End") break;            scanf("%d%d",&a,&b);            if(str=="Query")            {                if(a>b) swap(a,b);                printf("%d\n",Find(1,1,N,a,b));            }            if(str=="Add")            {                change(1,1,N,a,b);            }            if(str=="Sub")            {                //b=-b;                change(1,1,N,a,-b);            }        }    }    return 0;}