线段树
作者:互联网
线段树的定义
线段树是一颗二叉搜索树,它的每一个节点都有两个子节点,每一个节点存储着一个区间的有关信息(可以是区间和,也可以是区间的最大值等)
下图所示为 arr 数组区间和在线段树上的表示方法。
线段树的实现
使用数组存储二叉树的形式对线段树进行存储,在图中我们使用红色圆圈标记每一个节点的序号。即如果某个非叶结点的序号为 k ,那么它的左儿子序号为 2k ,右儿子序号为2k+1。
使用位运算来表示左右子树:
左子树: k<<1
右子树: k<<1|1
存储线段树所需要的空间:
结论:对于一个维护序列长度为n的线段树,使用堆结构存储的话,数组大小应该开4n,这样,在n为正整数的所有情况下都够用,且在最极限的情况下刚好够用。
证明:当n为2的正整数次幂时,设n = 2^k,此时线段树是一颗叶子数为n的满二叉树。若n再大一些但是不超过 2^(k+1)时,也是需要按照 n = 2^(k+1) 的情况开。
这启⽰我们,当2^k <n<=2^(k+1),即k<log n<=k+1时,线段树所开大小与叶子数为2^(k+1)的满二叉树大小相同,为 2^(k+2)-1 个
由 k < log n <= k+1 有 k + 1 = ceil(log n)
所以 size = 2^(k+2)-1 = 2 ^ (ceil(log 2n)) - 1
size < 2 ^ (log 2n + 1) = 4n 证毕
const int SIZE = 4 * N
int arr[N] , tree[SIZE];
//更新节点的值,这里每个节点表示所示区间的最大值
void reload(int index){
tree[index] = max(tree[index<<1], tree[index<<1|1]);
}
void build(int index, int l,int r){
if( l == r) //左端点等于右端点 为叶子节点,直接赋值就可以
tree[index] = arr[l];
else{
int m = (l + r) >> 1;
build(index << 1, l , m); // 构造左儿子节点
build(index << 1|1, m+1 , r);// 构造右儿子节点
reload(index); // 更新父节点
}
}
线段树的基本操作
点的更新
// l 是区间的左端点 r 是区间的右端点
//target 是要修改的目标索引 value是要修改的值
//index 是该区间在线段树中的索引
void update(int target,int value,int l, int r, int index){
if( l == r){ // 找到目标点,更新数组和线段树
arr[index] += value;
tree[index] += value;
}
else{
int m = ( l + r) >> 1;
if(target <= m) update(target,value,l,m,index<<1);
else update(target,value,m+1,r,index<<1|1);
reload(index);
}
}
区间更新
每一个节点对于要搜寻的区间有三种情况 1. 真包含 2. 有交集 3. 毫无关系
延迟标记(懒标记):每个节点新增加一个标记,记录这个节点是否进行了某种修改(这种修改操作会影响其子节点),对于任意区间的修改,我们先按照区间查询的方式将其划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标记上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个节点p,并且决定考虑其子节点,那么我们就要看节点p是否被标记,如果有,就要按照标记修改其子节点的信息,并且给子节点都标上相同的标记,同时消掉节点p的标记。
就是在更改一个节点的时候,先不对它的子节点进行递归修改,当我们决定要访问它的子节点的时候,再将它懒标记传递下去。
void pushDown(int index){
if(lazy[index]){
//处理懒标记
tree[index << 1] += lazy[index];
tree[index << 1 | 1] += lazy[index];
//这一步是为了 子节点的子节点 我们为子节点打上懒标记 标志子节点被更新
lazy[index << 1] += lazy[index];
lazy[index << 1|1] += lazy[index];
//懒标记清空
lazy[index] = 0;
}
}
void updateInterval(int targetL,int targetR,int l ,int r,int index,int value){
if(l > targetR || r < targetL) return ; // 毫无关系
if(l >= targetL && targetR >= r){// 真包含
tree[index] += value;
lazy[index] += value;
}
pushDown(index); // //延迟标记向下传递
int m = (l + r) >> 1;
updateInterval(targetL,targetR,l,m,index<<1);
updateInterval(targetL,targetR,m+1,r,index<<1|1);
reload(index);
}
线段树实战
732. 我的日程安排表 III
当 k 个日程安排有一些时间上的交叉时(例如 k 个日程安排都在同一时间内),就会产生 k 次预订。
给你一些日程安排 [start, end) ,请你在每个日程安排添加后,返回一个整数 k ,表示所有先前日程安排会产生的最大 k 次预订。
实现一个 MyCalendarThree 类来存放你的日程安排,你可以一直添加新的日程安排。
MyCalendarThree() 初始化对象。
int book(int start, int end) 返回一个整数 k ,表示日历中存在的 k 次预订的最大值。
示例
输入:
["MyCalendarThree", "book", "book", "book", "book", "book", "book"]
[[], [10, 20], [50, 60], [10, 40], [5, 15], [5, 10], [25, 55]]
输出:
[null, 1, 1, 2, 3, 3, 3]
解释:
MyCalendarThree myCalendarThree = new MyCalendarThree();
myCalendarThree.book(10, 20); // 返回 1 ,第一个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。
myCalendarThree.book(50, 60); // 返回 1 ,第二个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。
myCalendarThree.book(10, 40); // 返回 2 ,第三个日程安排 [10, 40) 与第一个日程安排相交,所以最大 k 次预订是 2 次预订。
myCalendarThree.book(5, 15); // 返回 3 ,剩下的日程安排的最大 k 次预订是 3 次预订。
myCalendarThree.book(5, 10); // 返回 3
myCalendarThree.book(25, 55); // 返回 3
class MyCalendarThree {
public:
MyCalendarThree() {}
map<int,pair<int,int>> tree;
void reload(int index){
if(index > 0)
tree[index].first = max(tree[index<<1].first, tree[index<<1|1].first);
}
void pushDown(int index){
if(tree[index].second != 0 && index > 0){
tree[index<<1].first += tree[index].second;
tree[index<<1|1].first += tree[index].second;
tree[index<<1].second += tree[index].second;
tree[index<<1|1].second += tree[index].second;
tree[index].second = 0;
}
}
void addDate(int start,int end, int l,int r,int index){
if(start > r || end < l) // 毫无关系
return;
if(l >= start && end >= r){//真包含
tree[index].first++;
tree[index].second++;
}
else{
pushDown(index);
int m = (l + r) >> 1;
if(index > 0){
addDate(start,end,l,m,index<<1);
addDate(start,end,m+1,r,index<<1|1);
}
reload(index);
}
}
int book(int start, int end) {
addDate(start,end-1,0,1e9,1);
return tree[1].first;
}
};
/**
* Your MyCalendarThree object will be instantiated and called as such:
* MyCalendarThree* obj = new MyCalendarThree();
* int param_1 = obj->book(start,end);
*/
要注意的是leetcode对数据范围的检查非常严格,对于左移(<<)右移(>>)等操作,必须要声明操作数非负,纵然index永远都不会小于零,但是也需要给出限制语句
标签:index,int,线段,book,日程安排,节点 来源: https://www.cnblogs.com/wuqiu/p/16351819.html