码迷,mamicode.com
首页 > 其他好文 > 详细

线段树

时间:2019-11-28 21:05:28      阅读:86      评论:0      收藏:0      [点我收藏+]

标签:href   sub   通过   构建   图片   位运算   一段   microsoft   序列   

引入:

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

 


目录

  基础

    ├ 关于线段树

    ├ 建树

    ├ 区间查询

    └ 单点修改

   进阶

    ├ 延迟标记

    │   区间修改 单点查询

    区间修改 区间查询

               标记下传

               标记永久化


基础篇

关于线段树

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

       技术图片

  不难发现,根节点对应的区间时[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个区间

  技术图片

   那么在查询过程中,会有以下三种情况  

    ├ 当前区间与需查询区间无交集,返回 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 } 

 


 

进阶篇

延迟标记

  

 


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

 

线段树

标签:href   sub   通过   构建   图片   位运算   一段   microsoft   序列   

原文地址:https://www.cnblogs.com/Dfkuaid-210/p/11953598.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!