线段树

引入:数组

咱们常常会遇到须要维护一个序列的问题,例如,给定一个整数序列,每次操做会修改某个位置或某个区间的值,或是询问你序列中的某个区间内全部数的和。或许你可能回去暴力出奇迹或者使用前缀和,可是当数据很大时,时间复杂度明显是受不了的。那么,就须要引入一种时间复杂度相对较小的数据结构 ——线段树数据结构

 


目录优化

  基础ui

    ├ 关于线段树spa

    ├ 建树3d

    ├ 区间查询code

    └ 单点修改blog

   进阶
递归

    ├ 延迟标记ip

    │   区间修改 单点查询

    区间修改 区间查询

               标记下传

               标记永久化


基础篇

关于线段树

  线段树是一课二叉树树上的每个结点对应序列的一段区间,以下图:

       

  不难发现,根节点对应的区间时[0,n-1],而每一个结点对应一个区间[l,r],且当l=r时,该结点为叶结点,无左右儿子,不然令 mid = (l + r) / 2 ,则左儿子对应的区间为[l,mid],右儿子为[mid+1,r]。同时,也不难发现,最后一行有n个结点,倒数第二行有n/2个结点,倒数第三行有n/4个结点,以此即可以推出一个线段树有n+n/2+n/4+...+1=2*n+1个结点,可是要注意,线段树数组务必要开4倍。设线段树的深度为h,那么h为O(logn)级别,当咱们须要维护的序列长度为2的整数次幂时,这个线段数为满二叉树,不然最后一层可能不满

 


建树

  注:接下来的一系列操做都以求区间和为例

  设序列a:5,3,7,9,6,4,1,2,咱们将其用线段树构建出来,即

       

  对应的区间和如图

  

  从图中能够看出除叶结点区间和为它自己外,其余节点的区间和均为其左右儿子区间和之和。以此,能够经过递归建树,并在递归后对其区间和进行维护

 1 void build(int k,int l,int r){   //k为结点编号,l为区间左端点,r为区间右端点 
 2     if (l == r){  //若是该点为叶结点 
 3         sum[k] = a[l];  //区间和即为自己 
 4         return;
 5     }
 6     int mid  = l + r >> 1; //取中间值,位运算优化常数 
 7     build(k << 1,l,mid); //构建左子树 
 8     build(k << 1 | 1,mid + 1,r); //构建右子树 
 9     sum[k] = sum[k << 1] + sum[k << 1 | 1]; //维护该区间区间和 
10 }

区间查询

  若是咱们要查询区间和[0,6],那么咱们需访问3个区间

  cha

   那么在查询过程当中,会有如下三种状况  

    ├ 当前区间与需查询区间无交集,返回 0

    ├ 当前区间被需查询区间彻底包含,返回该结点对应区间和

    └ 当前区间与需查询区间有交集,但不被彻底包含,递归其左右子树进行查询

1 int query(int k,int x,int y,int l,int r){ //k为结点编号,x为当前区间左端点,y为当前区间右端点,
2                                           //l为需查询区间左端点,r为需查询区间右端点 
3     if (x > r || y < l) return 0; //若是无交集,返回0 
4     if (x >= l && y <= r) return sum[k]; //若是彻底包含,返回该结点区间和 
5     int res = 0,mid = x + y >> 1;
6     res = query(k << 1,x,mid,l,r); //递归左子树 
7     res += query(k << 1 | 1,mid + 1,y,l,r); //递归右子树 
8     return res;
9 }

 

 单点修改

  假设要将a1加2,那么咱们要从新计算4个结点的值

  改前 

                                          ||

                     \/  

    改后

  那么,在修改时能够用递归的形式修改

  在递归中,可能会有如下几种状况

     当前区间不包括需修改区间,直接返回

     找到需修改的叶结点,修改,返回

     有包含,但不是叶结点,递归修改左右子树

 1 void change(int k,int l,int r,int p,int v){
 2     if (l > p || r < p) return; //若该区间不包含需修改点,直接返回 
 3     if (l == r && l == p){ //若当前即为需修改结点 
 4         sum[l] += v; //修改 
 5         return;
 6     }
 7     int mid = l + r >> 1;
 8     change(k << 1,l,mid,p,v);//递归修改左子树 
 9     change(k << 1 | 1,mid + 1,r,p,v);//递归修改右子树 
10     sum[k] = sum[k << 1] + sum[k << 1 | 1];//维护区间和 
11 } 

 


 

进阶篇

延迟标记

  有时咱们须要解决的不仅是单点修改、区间询问,而是区间修改、区间询问,那么单纯使用以上的方法已经没法解决问题了,由于咱们没有办法高效完成区间修改

   以区间内全部数同时加上一个值,以及单点询问为例进行引入

区间修改,单点查询

  考虑在每一个结点上维护一个值addsum,表示这个结点所对应的区间内的全部数都加上了addsum


第一次发布时间:2019.11.28  完成度:45%

相关文章
相关标签/搜索