线段树
前言
线段树这种数据结构主要的用来对区间的查询和更新同时进行优化的。
如果仅仅需要对查询进行优化,那么使用前缀和就可以使单次查询的时间复杂度达到O(1)。
如果是考虑更新操作那么要分成两种情况:单点更新,区间更新。无论是单点更新还是区间更新,最坏的时间复杂度达到了O(n)。
那么我们就考虑使用线段树这种数据结构来优化查找和更新操作。
在线段树中查找的时间复杂度为:O(logn);更新(单点更新和区间更新)的时间复杂度均为:O(logn)。
线段树的空间复杂度为O(n),但是一般需要开2倍的空间,通常会开4倍的空间,防止越界。
适用情况
必须满足法f(a + b) = f(a) + f(b)这种形式,也就是满足可加性。
比如维护区间的最小(大)值,满足min(a + b) = min(min(a) , min(b)),解释:区间a + b的最小值等于区间a的最小值,区间b中的最小值中两者中的最小值。
区间的和,满足sum(a + b) = sum(sum(a),sum(b)),解释:区间a + b的和等于区间a的和以及区间b的和两者之和。
补充
本人平时使用线段树的场景并不是太多,对于线段树的理解暂时还处于早期阶段,个人理解为线段树的作用更多是为了以空间换取时间,进而优化查找以及更新的时间复杂度。
代码
单点更新和查找
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
typedef struct Node{
// l:区间的左端点;r:区间的右端点;data:区间中存放的数据。
// mid:(方法)取区间中点。
int l,r;
int data;
int mid(void){
return l + ((r - l) >> 1);
}
} Node;
Node sum[maxn << 2]; // 注意不能把这个数组放到类中!!!
class SegmentTree{
vector<int> nums;
// 上浮
void push_up(int root){
sum[root].data = sum[root << 1].data + sum[root << 1 | 1].data;
}
public:
SegmentTree(vector<int>& nums){
this->nums = nums;
};
// 建立线段树
void build(int root,int l,int r){
sum[root].l = l,sum[root].r = r;
if (sum[root].l == sum[root].r){
sum[root].data = nums[sum[root].l];
return ;
}
int mid = sum[root].mid();
build(root << 1,l,mid);
build(root << 1 | 1,mid + 1,r);
push_up(root);
}
// 在p位置加v(单点更新)
void update(int root,int p,int v){
if (sum[root].l == sum[root].r){
sum[root].data += v;
return ;
}
int mid = sum[root].mid();
if (p <= mid) update(root << 1,p,v);
else if (p > mid) update(root << 1 | 1,p,v);
push_up(root);
}
// 查询
int query(int root,int l,int r){
int ans = 0;
if (l <= sum[root].l && sum[root].r <= r) return sum[root].data;
int mid = sum[root].mid();
if (l <= mid) ans += query(root << 1,l,r);
if (mid + 1 <= r) ans += query(root << 1 | 1,l,r);
return ans;
}
};
int main(void){
vector<int> nums{1,2,3,4,5,6,7};
SegmentTree tree(nums);
tree.build(1,0,6);
cout << "2-5: " << tree.query(1,2,5) << endl;
cout << "1-7: " << tree.query(1,1,7) << endl;
cout << "3-3: " << tree.query(1,3,3) << endl;
tree.update(1,3,10);
cout << "------------------------" << endl;
cout << "2-5: " << tree.query(1,2,5) << endl;
cout << "1-7: " << tree.query(1,1,7) << endl;
cout << "3-3: " << tree.query(1,3,3) << endl;
return 0;
}
区间更新和查找
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
typedef struct Node{
int l,r;
int data;
int mid(void){
return l + ((r - l) >> 1);
}
} Node;
Node sum[maxn << 2];
int lazy[maxn] = {0};
// 懒惰标记,每次只更新当前区间的数据,然后将lazy下沉。
// 类似于redis中惰性删除的思想,也很像copy on write的思想。
// 即查找到当前区间才更新当前更新,减少无谓的更新,提高效率。
class SegmentTree{
vector<int> nums;
void push_up(int root){
sum[root].data = sum[root << 1].data + sum[root << 1 | 1].data;
return ;
}
// 下沉函数
void push_down(int root){
if (lazy[root]){
lazy[root << 1] = lazy[root];
lazy[root << 1 | 1] = lazy[root];
sum[root << 1].data = lazy[root] * (sum[root << 1].r - sum[root << 1].l + 1);
sum[root << 1 | 1].data = lazy[root] * (sum[root << 1 | 1].r - sum[root << 1 | 1].l + 1);
lazy[root] = 0;
}
return ;
}
public:
SegmentTree(vector<int>& nums){
this->nums = nums;
}
void build(int root,int l,int r){
sum[root].l = l,sum[root].r = r;
if (sum[root].l == sum[root].r){
sum[root].data = nums[sum[root].l];
return ;
}
int mid = sum[root].mid();
build(root << 1,l,mid);
build(root << 1 | 1,mid + 1,r);
push_up(root);
}
// [l,r]区间内的值修改为v(区间更新)
// lazy[i] = 0:默认没有标记
void update(int root,int l,int r,int v){
if (l <= sum[root].l && sum[root].r <= r){
sum[root].data = (sum[root].r - sum[root].l + 1) * v;
lazy[root] = v;
return ;
}
push_down(root);
int mid = sum[root].mid();
if (l <= mid) update(root << 1,l,r,v);
if (mid + 1 <= r) update(root << 1 | 1,l,r,v);
push_up(root);
}
int query(int root,int l,int r){
int ans = 0;
if (l <= sum[root].l && sum[root].r <= r){
return sum[root].data;
}
push_down(root);
int mid = sum[root].mid();
if (l <= mid) ans += query(root << 1,l,r);
if (mid + 1 <= r) ans += query(root << 1 | 1,l,r);
return ans;
}
};
int main(void){
vector<int> nums{1,2,3,4,5,6,7};
SegmentTree tree(nums);
tree.build(1,0,6);
cout << "0-2: " << tree.query(1,0,2) << endl;
cout << "3-4: " << tree.query(1,3,4) << endl;
cout << "1-5: " << tree.query(1,1,5) << endl;
cout << "----------------------------------" << endl;
tree.update(1,3,5,-1);
cout << "0-2: " << tree.query(1,0,2) << endl;
cout << "3-4: " << tree.query(1,3,4) << endl;
cout << "1-5: " << tree.query(1,1,5) << endl;
cout << "----------------------------------" << endl;
tree.update(1,2,4,10);
cout << "0-2: " << tree.query(1,0,2) << endl;
cout << "3-4: " << tree.query(1,3,4) << endl;
cout << "1-5: " << tree.query(1,1,5) << endl;
return 0;
}