树状数组于线段树的实现c++

先说一下这两个东西的作用;

树状数组的作用有局限性,其大部分是用来动态求前缀和的,而线段树可以说是吃百家饭的,可以动态地求出一个区间的性质,比如一个区间中的最值和总和。

那么线段树就一定比树状数组优秀吗?从功能上看是这样的,但是树状数组的用代码实现的复杂度是线段树无法比拟的。

那么先讲一下树状数组吧。

由于这个东西的证明十分复杂,所以这边就省得绕晕各位了。

有兴趣的可以由我给出的函数的功能推出具体的证明,这个是行得通的。

来一张具体的树状数组的图;

其中一个下标的二进制后缀0的个数为k个时,这个数就在第k层

第一个函数 lowbit

void lowbit(int x){//返回2^k,k是x二进制的后缀0的个数
     return x&-x;
}

第二个函数 query 返回1~x的前缀和,时间复杂度是log(n);

int query(int x){
     
     int sum=0;

     for(int i=x;i!=0;i-=lowbit(i)) sum+=tr[i]; //tr[i]为树中下标为

     return sum;
     
}

第三个函数 modify  在第k个值上加w 时间复杂度是log(n);

void modify(int x,int w){
     
     for(int i=x;i<=n;i+=lowbit(i))
         tr[x]-=w;
   
}

这就是树状数组的查询和改数操作;


那么下面是线段树了,这个数据结构就很复杂了,一般可用到4个函数,分别是更新节点、 构建树、查询树、修改树,四个操作,其底层实现的原理是dfs;

我们需要一个结构体来构建树;

struct node{

    int l,r;
    int sum;

}tr[N];//tr[i]的保存着原数组l~r区间的和,其子节点是tr[i<<1]和tr[i<<1|1];

这边以维护前缀和为例

第一个函数 push_up 更新节点函数

void push_up(int x){
  
    tr[x].sum=tr[x<<1].sum+tr[x<<1|1].sum;//tr[x]的子节点是tr[x<<1]和re[x<<1|1],等价于x*2和x*2+1;

}

第二给函数 build   创建一个区间的线段树; 时间复杂度nlog(n)



void build(int x,int l,int r){//x为当前节点的下标,l、r为当前节点的区间端点
    
    if(l==r){
        
        tr[x]={l,r,st[r]};//st[r]存储着原数组中下标为r的数
        
    }else{
        
        tr[x]={l,r};//讲tr[x].l和tr[x].r更新
        
        int mid=l+r>>1;
        
        build(x<<1,l,mid); build(x<<1|1,mid+1,r); //递归创建子节点
        
        push_up(x); //创建完两个子节点后由两个子节点更新父节点
        
        
    }
    
}

第三个函数 query 询问l~r的区间和 时间复杂度log(n);



int query(int x,int l,int r){//x为当前树中点的下标,l、r为所求区间的端点
    
    if(tr[x].l>=l&&tr[x].r<=r)//如果这个节点所维护的区间为所求区间的子集,那么我们直接返回这个节点的sum
        return tr[x].sum;
    else{
        
        int sum=0;
        
        int mid=tr[x].l+tr[x].r>>1;
        
        if(l<=mid) sum+=query(x<<1,l,r);//筛选于所求区间有交集的集合
        if(r>=mid+1)  sum+=query(x<<1|1,l,r);//筛选于所求区间有交集的集合
        
        return sum;
        
    }
    
}

第4个函数 modify 修改某个数;


void modify(int x,int p,int w){//下为当前点在树中的下标,p为需在原数组中修改的位置,w为需要加上的值
    
    if(tr[x].l==tr[x].r){//此刻tr[x]为存储位置为p~p的前缀和
        
        tr[x].sum-=w;
        return;
        
    }else{
        
        int mid=tr[x].l+tr[x].r>>1;
        
        if(p<=mid) modify(x<<1,p,w);//寻找p所在区间的位置
        else modify(x<<1|1,p,w);
        
        push_up(x);//因为p点的数字改变,所以得更新树
        
    }
    
}

大概就是这几个函数,一定要结合上面的图来理解,要不然会晕掉;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值