线段树理解

闲话

当我觉得我学习算法刚刚从萌新到入门的时候,一类给定一个区间然后给定一系列操作的题彻底的打击了我,那时我才醒悟,编程路上,我一直是萌新。

前言

啥是线段树?

 线段树是一个具有树特性的数据结构,它是一颗二叉搜索树。如下图为区间[1,10]所建立的线段树

一颗[1,10]的线段树

将每一个区间序列二分成小区间,线段树就存储小区间的信息,也就是每个小区间对应线段树中的一个结点。比如上图根节点对应[1,10]

  • 对于每一个子节点而言,都表示整个序列中的一段子区间,如上图橙色节点;
  • 对于每个叶子节点而言,都表示序列中的单个元素信息,如上图绿色节点;
  • 子节点不断向自己的父亲节点传递信息,而父节点存储的信息则是它的每一个子节点信息的整合,也就是父亲是儿子们的

由于线段树的二分性质,对每一个子节点来说,左儿子的区间小于右儿子的区间,所以线段树也是平衡树

有啥用?

将需要处理的信息看成一个个点,也就是叶子节点,然后通过父亲节点来将信息整合,做到通过树结构来进行操作对节点信息进行增删查改,大大降低复杂度。

最简单的应用就是记录区间是否被覆盖,随时查询当前被覆盖区间的总长度。

既然线段树利用区间二分建树,那么对子区间进行操作,只需要从根节点通过递归找到此区间即可,

一次操作时间肯定与树的高度有关,由于二叉树搜索树的关系,它的高度为log2(n),

所以完成一次操作的时间为O(log(n))。

那么回想前言中的题目:

  给定区间n个数,n<=1000000,给定m个操作,m<=10000,对于每个操作,有两种情况

  • 操作1:更新第k个数的值
  • 操作2:查询区间[ l,r ]的和

  用枚举法肯定超时,然而有了线段树,就可以用log2(n)的时间完成每次操作,数据量很大时,不怕超时。

更多操作,接下来会更细致的讲解。

实现

建树

建一颗树,当然可以用递归和结构体数组。(还有非递归版本,暂时不给出)

int n;
struct node{ int left,right,sum; }node[4*n];//注意要开四倍空间

线段树是完全二叉树,一个序号为k的节点,它的左儿子序号为2*k,右儿子序号为2*k+1。、

即  k   --->  k << 1 

     --->  k << 1 | 1

对于一个[ l,r ]区间,结构体中的left,表示此节点代表此区间的左端点l,right表示此区间的右端点r,sum表示此区间的总信息(这里是元素之和)

  • 建树从node[1]开始,它是根节点,代表整个区间,所以left = l,right = r。
  • 对于一个节点将它代表的区间分成两块,m=(l+r)/2,那么左儿子代表的区间为[l,m],右儿子代表的为[m+1,r]

于是可以用递归来实现。

  • 当一个节点的 left = right 时,表示它为一个叶子节点,没有儿子,不需要接着递归,而且它表示区间里的单个元素,所以此时要给sum赋值。
  • 因为父亲节点的sum为儿子节点的并集,所以要通过回溯用已经赋过值的儿子节点来更新父亲节点的sum。
  • 此外,追求方便以及对结构体的利用,可以在结构体里给出左端点left和右端点right,以至于在建树的时候保存每一个节点代表的区间。
  • 用一个pushUp函数来表示将子节点信息整合到父节点

代码:

void pushUp(int n){
  
node[k].sum=node[2*k].sum+node[2*k+1].sum; //回溯过程,更新父亲节点
}
void build(int l,int r,int k){//[l,r]为初始区间,k为序号
   node[k].left=l;
   node[k].right=r;
if(l==r){ scanf("%d",node[k].sum);//给单个元素赋初值 return ; // 遍历到子节点返回 } int m=(l+r)/2; build(l,m,2*k); build(m+1,r,2*k+1); pushUp(k);  //这里表示回溯函数 }

 具体实现看下图:

图中红色箭头表示build递归创造节点,紫色箭头表示pushUp回溯整合信息,

节点上面表示创造的序号节点下面的数字表示节点代表的区间的总和

为什么要开四倍空间?

这个问题很重要,不然随意开空间可能会导致溢出。

假设一颗线段树最下面一层最多有n个节点,是一个满二叉树,那么此线段树则有log2(n)+1层。

但如果像[1,10]这样,不是满二叉树,那么(取整) log2(n) < log2(n)+1 ,所以这样的线段树最多有log2(n)+2层。

而二叉树节点的个数为2log2(n)+2=4*n,所以要开4倍空间。

基本操作

单点查询与更新

根据上面所述:当某个节点的left==right时,它是一个叶子节点,也就是我们需要查询和修改的对象,于是可以通过递归线段树来找到需要的节点信息。

原文地址:https://www.cnblogs.com/lastonepersonwhohavebitenbycompanies/p/11028535.html