看到UESTC的数据结构专题快要结束了,感觉自己真心浪费了好多时间,没有像鑫航学姐那样叮嘱的一样,紧紧的跟住训练。
所以下决心认认真真的开始学习下线段树的知识,以前对于线段树的学习都是一知半解的,就是说,我只知道线段树是用来单点更新和区间查值的,其实,线段树的功能远远不止这些。
先来说下,线段树是用来求解有关区间问题的绝逼法宝,为什么说绝逼呢。因为它能够在logn的时间内完成每次的查询。
查询的分类:
1.区间查询
-访问某段区间的某些性质(比如说,最大值,最小值,连续和,等等)
2.区间更新
-某些操作影响了某段区间(比如说,统一加一个值)
归于以上两个查询,我们总结出了线段树能够解决的以下三个问题.
-更新点,查询区间
-更新区间,查询点
-更新区间,查询区间
要想深入的理解线段树,我们从一个比较经典的例子开始了解吧。
我现在有一个长度为 n的一维数组(a[1]~a[n])
我们每次对这个数组只允许有以下的操作:
~1.修改数组中某个元素的值
- [1,5,4,1,6] ---(a[2] = 3)---[1,3,4,1,6]
~2.询问数组中某段区间的最大值
-[1,5,4,1,6] ---(max(1,4)=?) ---> 5
~3.询问数组中某段区间的和
-[1,5,4,1,6] ---(sum(3,5)=?) ---> 11
在没有知道线段树之前,我们知道,要对一个数组进行这样的操作是很简单的每次只要O(n)的时间扫一遍就是可以得到各种询问的ans的。
那么,如果我要进行Q次询问该怎么办呢?这样的话就变成了O(nQ)的复杂度了,,擦,,这也太慢了吧,随便给你一组数据,你就T了
所以,为了在更快的时间里解决这些查询问题,我们引入了一个叫做线段树的数据结构,线段树就是一个能在logn的时间内完成每次操作。
线段树的本质是一棵二叉树,不同于一般的二叉树来说,线段树的每个节点维护的都是一段区间的信息。
比如下面这个二叉树,对于一个长度为5的数组a[1]~a[5]
[1,.5]
[1,3] [4,5]
[1,2] [3,3] [4,4] [5,5] 对于这个树的构建有几点说明,对于任意一个非叶子节点来说,如果它所维护的区间是[l,r]的话,它的左儿子区
[1,1] [2,2] 间就是[l,(l+r)/2], 右儿子区间就是[(l+r)/2+1,r];
线段树 ---实现
每个节点记录的信息
1 struct Segtree 2 { 3 int left,right;//区间的端点 4 int mx,mn;//区间内的最大值和最小值 5 int sum;//区间内元素的总和 6 }tree[MAX*4];
我们先用一个数组a[]来记录节点,并且根节点的下标为1,然后,对于任意一个节点来说a[k]来说,他的左儿子是a[k<<1],右儿子是a[k<<1|1]
建立一棵线段树代码实现
void build ( int id,int l,int r ) { tree[id].left = l; tree[id].right = r; if ( l==r ) { tree[id].sum = tree[id<<1].sum+tree[id<<1|1].sum; tree[id].mx = max(tree[id<<1].mx,tree[id<<1|1].mx); tree[id].mn = min(tree[id<<1].mn,tree[id<<1|1].mn); } else { int mid = (l+r)>>1; build(id<<1,l,mid); build(id<<1|1,mid+1,r); tree[id].sum = tree[id<<1].sum+tree[id<<1|1].sum; tree[id].mx = max(tree[id<<1].mx,tree[id<<1|1].mx); tree[id].mn = min(tree[id<<1].,mn,tree[id<<1|1].mn); } }