线段树是一种二叉树形数据结构,1977年由Jon Louis Bentley发明, 上面的每个节点用来存储区间和线段,特别的,叶子节点存储长度为1的线段,即一个值。对区间的查找和节点的更新都可以在 O ( l o g N ) O(logN) O(logN)的内完成。其空间复杂度为 O ( 4 N ) O(4N) O(4N)。运用延迟更新技术,其可用于区间的更新,时间复杂度仍然为 O ( l o g N ) O(logN) O(logN)。
const int MAXN = 100000;
struct Node
{
int start;
int end;
int data;
int mark;
}segment_tree[MAXN*4];
线段树的建立是一个线段的二分过程。采用递归建树的方法。
void build(int start, int end, int index)
{
/*
[start, end): 目前建立节点负责区间负责区间
index: 目前建立结点在segment_tree数组中的下标
使用方式: 如针对数据: int base[6] = {4,1,2,3,5,6};
调用build(0, 6, 0)完成建树
*/
// 赋值区间
segment_tree[index].start = start ;
segment_tree[index].end = end;
// 如果已经到了叶子节点
if(segment_tree[index].start == segment_tree[index].end - 1)
{
segment_tree[index].data = base[segment_tree[index].start];
segment_tree[index].mark = 0;
return;
}
// 递归建左子树和右子树
int mid = (start + end) >> 1;
build(start, mid, (index << 1) + 1);
build(mid, end, (index << 1) + 2);
// 根据左儿子右儿子更新当前结点的值
segment_tree[index].data = min(segment_tree[(index << 1) + 1].data, segment_tree[(index << 1) + 2].data);
segment_tree[index].mark = 0;
}
区间查询,即查询给定区间[a, b)的最小值。
int query(int a, int b, int index)
{
/*
[a, b): 查询区间
index: 当前查询节点在segment_tree数组中的下标
返回: [a,b)和[Node.start, Node.end)交集的最小值。 其中Node = segment_tree[index]
*/
// 情况一:完全覆盖
if(a <= segment_tree[index].start && segment_tree[index].end <= b)
{
return segment_tree[index].data;
}
// 情况二:交集为空
else if(a >= segment_tree[index].end || b <= segment_tree[index].start)
{
return INF;
}
// 情况三:细化查询
int v1 = query(a, b, (index << 1) + 1);
int v2 = query(a, b, (index << 1) + 2);
return min(v1, v2);
}
现在我们要改变线段树叶子节点的某一个值。比如将第1个值1改为3。很明显,我们需要先定位,更改,再将影响向上传播。示意图如下:
void update_one(int p, int x, int index)
{
/*
p: 要更改的元素位置
x: 将位置p的元素更改为x
index: 当前结点在segment_tree数组中的下标
*/
// 定位成功
if(segment_tree[index].start == p && segment_tree[index].start == segment_tree[index].end-1)
{
segment_tree[index].data = x;
return;
}
int mid = (segment_tree[index].start + segment_tree[index].end) >> 1;
// 需要更改的位置在右子树
if(p >= mid)
{
update_one(p, x, (index << 1) + 2);
}
// 需要更改的位置在左子树
else
{
update_one(p, x, (index << 1) + 1);
}
// 根据左右儿子更新当前结点值
segment_tree[index].data = min(segment_tree[(index << 1) + 1].data, segment_tree[(index << 1) + 2].data);
}
这里我们模拟将整个区间[a, b)整体加上一个数字c。 可用的策略是对区间[a, b)的每个数都进行一个单点更新,但是最终会导致O(NlogN)的时间复杂度。为了降低复杂度,我们引入延时更新策略:
现在我们逐一解释该策略:
void push_down(int index)
{
/*
将segment_tree[index]的段更新信息传递到子节点,并清除掉自己的标记
*/
int mark = segment_tree[index].mark;
if(mark != 0)
{
segment_tree[(index << 1) + 1].mark += mark;
segment_tree[(index << 1) + 2].mark += mark;
segment_tree[(index << 1) + 1].data += mark;
segment_tree[(index << 1) + 2].data += mark;
}
segment_tree[index].mark = 0;
}
void update_seg(int a, int b, int add, int index)
{
/*
[a, b): 需要更新的区间
add: 区间[a, b)需要增加的数
index: 当前结点在segment_tree中的下标
*/
// 情况一: 完全包含, 只维护该节点与其祖先
if(a <= segment_tree[index].start && segment_tree[index].end <= b)
{
segment_tree[index].data += add;
segment_tree[index].mark += add;
return;
}
// 情况二:完全不相交,跳过
else if(a >= segment_tree[index].end || b <= segment_tree[index].start)
{
return;
}
// 情况三:需要细化更新
push_down(index); // 将当前结点保存的更新信息推向儿子节点
update_seg(a, b, add, (index << 1) + 1);
update_seg(a, b, add, (index << 1) + 2);
segment_tree[index].data = min(segment_tree[(index << 1) + 1].data, segment_tree[(index << 1) + 2].data);
}
我们希望查询[0,2)的最小值。这时我们需要将[1,3)区间的节点的更新信息先传递下去。
这样,我们才能查询到[1,2)节点的最新信息。
int query(int a, int b, int index)
{
/*
[a, b): 查询区间
index: 当前查询节点在segment_tree数组中的下标
返回: [a,b)和[Node.start, Node.end)交集的最小值。 其中Node = segment_tree[index]
*/
// 情况一:完全覆盖
if(a <= segment_tree[index].start && segment_tree[index].end <= b)
{
return segment_tree[index].data;
}
// 情况二:交集为空
else if(a >= segment_tree[index].end || b <= segment_tree[index].start)
{
return INF;
}
// 情况三:细化查询
// --------------------加入此行---------------------------------
push_down(index);
// -----------------------------------------------------------
int v1 = query(a, b, (index << 1) + 1);
int v2 = query(a, b, (index << 1) + 2);
return min(v1, v2);
}
#include
using namespace std;
const int MAXN = 100000;
const int INF = 100000+5;
struct Node
{
int start;
int end;
int data;
int mark;
}segment_tree[MAXN*4];
int base[MAXN];
void build(int start, int end, int index)
{
/*
[start, end): 目前建立节点负责区间负责区间
index: 目前建立结点在segment_tree数组中的下标
使用方式: 针对数据: int base[6] = {4,1,2,3,5,6};
调用build(0, 6, 0)完成建树
*/
// 赋值区间
segment_tree[index].start = start ;
segment_tree[index].end = end;
// 如果已经到了叶子节点
if(segment_tree[index].start == segment_tree[index].end - 1)
{
segment_tree[index].data = base[segment_tree[index].start];
segment_tree[index].mark = 0;
return;
}
// 递归建左子树和右子树
int mid = (start + end) >> 1;
build(start, mid, (index << 1) + 1);
build(mid, end, (index << 1) + 2);
// 根据左儿子右儿子更新当前结点的值
segment_tree[index].data = min(segment_tree[(index << 1) + 1].data, segment_tree[(index << 1) + 2].data);
segment_tree[index].mark = 0;
}
void update_one(int p, int x, int index)
{
/*
p: 要更改的元素位置
x: 将位置p的元素更改为x
index: 当前结点在segment_tree数组中的下标
*/
// 定位成功
if(segment_tree[index].start == p && segment_tree[index].start == segment_tree[index].end-1)
{
segment_tree[index].data = x;
return;
}
int mid = (segment_tree[index].start + segment_tree[index].end) >> 1;
// 需要更改的位置在右子树
if(p >= mid)
{
update_one(p, x, (index << 1) + 2);
}
// 需要更改的位置在左子树
else
{
update_one(p, x, (index << 1) + 1);
}
// 根据左右儿子更新当前结点值
segment_tree[index].data = min(segment_tree[(index << 1) + 1].data, segment_tree[(index << 1) + 2].data);
}
void push_down(int index)
{
/*
将segment_tree[index]的段更新信息传递到子节点,并清除掉自己的标记
*/
int mark = segment_tree[index].mark;
if(mark != 0)
{
segment_tree[(index << 1) + 1].mark += mark;
segment_tree[(index << 1) + 2].mark += mark;
segment_tree[(index << 1) + 1].data += mark;
segment_tree[(index << 1) + 2].data += mark;
}
segment_tree[index].mark = 0;
}
void update_seg(int a, int b, int add, int index)
{
/*
[a, b): 需要更新的区间
add: 区间[a, b)需要增加的数
index: 当前结点在segment_tree中的下标
*/
// 情况一: 完全包含, 只维护该节点与其祖先
if(a <= segment_tree[index].start && segment_tree[index].end <= b)
{
segment_tree[index].data += add;
segment_tree[index].mark += add;
return;
}
// 情况二:完全不相交,跳过
else if(a >= segment_tree[index].end || b <= segment_tree[index].start)
{
return;
}
// 情况三:需要细化更新
push_down(index); // 将当前结点保存的更新信息推向儿子节点
update_seg(a, b, add, (index << 1) + 1);
update_seg(a, b, add, (index << 1) + 2);
segment_tree[index].data = min(segment_tree[(index << 1) + 1].data, segment_tree[(index << 1) + 2].data);
}
int query(int a, int b, int index)
{
/*
[a, b): 查询区间
index: 当前查询节点在segment_tree数组中的下标
返回: [a,b)和[Node.start, Node.end)交集的最小值。 其中Node = segment_tree[index]
*/
// 情况一:完全覆盖
if(a <= segment_tree[index].start && segment_tree[index].end <= b)
{
return segment_tree[index].data;
}
// 情况二:交集为空
else if(a >= segment_tree[index].end || b <= segment_tree[index].start)
{
return INF;
}
// 情况三:细化查询
push_down(index);
int v1 = query(a, b, (index << 1) + 1);
int v2 = query(a, b, (index << 1) + 2);
return min(v1, v2);
}
int main()
{
int n;
cout << "输入数组长度:" << endl;
cin >> n;
cout << "输入" << n << "个整数(空格隔开):" << endl;
for(int i=0; i> base[i];
}
cout << "正在建树.." << endl;
build(0, n, 0);
cout << "建树完成.." << endl;
int op;
int a, b;
int add;
int pos;
int x;
do{
cout << "输入操作:" << endl;
cout << "0: 退出\n1: 查询区间最小值\n2: 单点改变某个值\n3: 区间整体增加某个值\n";
cin >> op;
if(op == 1)
{
cout << "输入区间起始位置a,b(区间为:[a, b)):" << endl;
cin >> a >> b;
cout << "区间[" << a << "," << b << ")最小值为:";
cout << query(a, b, 0) << endl;
}
else if(op == 2)
{
cout << "输入改变值的位置:";
cin >> pos;
cout << "输入改变后的值:";
cin >> x;
update_one(pos, x, 0);
cout << "单点改变成功" << endl;
}
else if(op == 3)
{
cout << "输入区间其实位置a,b(区间为:[a, b))" << endl;
cin >> a >> b;
cout << "要增加的值:" << endl;
cin >> add;
update_seg(a, b, add, 0);
cout << "区间更改成功" << endl;
}
cout << endl << endl;
}while(op != 0);
return 0;
}