简单线段树学习

1.什么是线段树:

​ 线段树简单来说,就是存放整段区间的信息的一种数据结构,它其实是一棵二叉树,而每个节点保存的就是某段区间的信息,比如说区间和,区间内的最值。

2.为什么要使用线段树

​ 在上面的介绍中,我们知道了每个节点保存的是一段区间的信息,但是我们可以通过枚举的方法来得到比如说区间最值,区间和之类的,为什么还要用线段树呢?其实之所以用线段树,是因为你查询的时候有些时候会查询多次,而且有些时候会修改区间的某些值,这样的话多次查询就会很费时间。

而线段树的话依赖树状结构,可以每次查询的时候都降低到log(N),就要比普通操作要节约时间。

3.线段树的构造

​ 定义线段树的数据结构的时候可以选择结构体的方式,或者有些时候将数组开大4倍来保存某个节点的左儿子和右儿子节点的信息

​ eg.1

接下来以求区间和为例,单点更新

struct TREE{
    int data;
    int left;
    int right;
}tree[maxn<<2];

​ 或者

int sum[maxn<<2];
int max[maxn<<2];

然后建一颗线段树可以通过递归来建立


void push_up(int rt)
{
    sum[rt]=sum[rt<<1]+sum[rt<<1+1];
}


void build(int l,int r ,int rt)
{
    int mid=(l+r)>>2;
    if(r==l)
    {
       int tmp;
       cin>>tmp;
       sum[r]=tmp;
        return;
     }
     build(l,mid,rt<<1);
     build(mid,r,rt<<1+1);
     push_up(rt)
    
}

建立之后我们改如何更新呢

难道我们直接就在数组上改?

那这样的话我们的线段树上的信息就还是修改之前的,就等于没有修改,那我们该怎样修改呢?

其实仔细想一下,因为我们最终是在树里面查询我们想要的信息,我们每次更新的时候都要把与该节点相关的信息都要变动一下。打个比方,一个公司里某个职位的变动后,就会在那个职位的上级那里去更新信息,然后上级在向上级的上级 继续更新信息。

假设

​ 我们的更新操作是将某个位置的值替换掉


void update(int L,int R,int l,int r,int rt,int change)
{
    int m=(l+r)>>1
    if(l==r)
    {
        sum[l]=change;//改成加号就表示把某个位置上加上多少
        return ;
     }
    if(m>L)
           update(L,C,l,m,rt<<1,change);
    else update(L,C,m,r,rt<<1+1,change);
    return ;
}

线段树的查询

理解了线段树的更新原理后,也就理解了线段树的查询原理,两者差不多


int  query(int L,int R,int l,int r,int rt)//R,L代表查询区间,r,l,代表的是该节点管辖的区间
{
    int ans=0;
    if(L==l&&r==R)
    {
       return sum[rt];
     }
    int m=(l+r)>>1;
    if(R<=m)//
        query(L,C,l,m,rt<<1);
    else if(L>m)
        query(L,C,m,r,rt<<+1);
    else 
        return query(L,C,l,m,rt<<1)+query(L,C,m,r,rt<<1+1);
    
}

以上就是一个最简单的线段树的例子。

其实可以发现,这个操作和二分有点类似,就是不断的变换节点,如果这个结点在区间里面的话,就返回这个节点的信息,如果区间在m的左边的话,就全部查询左边的,同理,如果在右边的话,就查右子树的,其他情况就把两种的值加起来。

那么如果我要进行的区间操作呢?

如果按照我们之前的操作的话,更新一个点会是logn,那么我们更新n个点就是nlogn,多次询问就会很老火。

那么我们如何解决呢。在这里要介绍一下lazy标记,就是我在更新到一个节点的时候,按照普通的思路应该往下继续更新嘛,这个时候我就不往下更新了,因为我已经把这个节点所管辖的区间的值都已经加到这个sum[rt]了,就没有必要更新子节点了,等到下一次要用子节点的时候再去更新,这样就可以省去很多的麻烦 以及节约时间。

区间修改:某段区间都加c


int lazy[maxn];
void PushDown(int rt,int m) //向下更新,m代表区间的长度
{
  if (lazy[rt]) 
  {
    lazy[rt<<1] = lazy[rt<<1|1] = lazy[rt];
    sum[rt<<1] = (m - (m >> 1)) * lazy[rt];
    sum[rt<<1|1] = ((m >> 1)) * lazy[rt];
    lazy[rt] = 0;
  }
}

void update(int L,int R,int c,int l,int r,int rt)
{
 
  if (L <= l && r <= R)
  {
    lazy[rt] = c;
    sum[rt] = c * (r - l + 1);
    
    return ;
  }
  PushDown(rt , r - l + 1);
  int m = (l + r) >> 1;
  if (L <= m) update(L , R , c , l,m,rt<<1);
  if (R > m) update(L , R , c ,m+1,r,rt<<1+1);
  PushUp(rt);
}
int  query(int L,int R,int l,int r,int rt)
{
  if (L <= l && r <= R)
  {
    return sum[rt];
  }
  PushDown(rt , r - l + 1);
  int m = (l + r) >> 1;
    int  ret = 0;
  if (L <= m) ret += query(L , R , l,m,rt<<1);
  if (m < R) ret += query(L , R , m+1,r,rt<<1+1);
  return ret;
}

转载于:https://www.cnblogs.com/IAMjm/p/9386166.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值