标签:空间 复杂度 回溯 http https 修改 版本 自己的 span
当我觉得我学习算法刚刚从萌新到入门的时候,一类给定一个区间然后给定一系列操作的题彻底的打击了我,那时我才醒悟,编程路上,我一直是萌新。
线段树是一个具有树特性的数据结构,它是一颗二叉搜索树。如下图为区间[1,10]所建立的线段树
将每一个区间序列二分成小区间,线段树就存储小区间的信息,也就是每个小区间对应线段树中的一个结点。比如上图根节点对应[1,10]
由于线段树的二分性质,对每一个子节点来说,左儿子的区间小于右儿子的区间,所以线段树也是平衡树。
将需要处理的信息看成一个个点,也就是叶子节点,然后通过父亲节点来将信息整合,做到通过树结构来进行操作对节点信息进行增删查改,大大降低复杂度。
最简单的应用就是记录区间是否被覆盖,随时查询当前被覆盖区间的总长度。
既然线段树利用区间二分建树,那么对子区间进行操作,只需要从根节点通过递归找到此区间即可,
一次操作时间肯定与树的高度有关,由于二叉树搜索树的关系,它的高度为log2(n),
所以完成一次操作的时间为O(log(n))。
那么回想前言中的题目:
给定区间n个数,n<=1000000,给定m个操作,m<=10000,对于每个操作,有两种情况
用枚举法肯定超时,然而有了线段树,就可以用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表示此区间的总信息(这里是元素之和)
于是可以用递归来实现。
代码:
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时,它是一个叶子节点,也就是我们需要查询和修改的对象,于是可以通过递归线段树来找到需要的节点信息。
标签:空间 复杂度 回溯 http https 修改 版本 自己的 span
原文地址:https://www.cnblogs.com/lastonepersonwhohavebitenbycompanies/p/11028535.html