1. 线段树概念
线段树是处理区间问题的好的解决方法, 当有n个元素时对区间的操作可以在O(logn)时间内完成, 有q个询问也不会超时, 根据节点维护的数据的不同, 线段树可以提供不同的功能, 下面以Rang Minimum Query(RMQ, 即查询区间内最小值)为例, 进行说明。
2. 基于线段树的RMG结构
对于数组{5, 3, 7, 9, 6, 4, 1, 2}, 线段树结构为
其维护区间与存储下标标记出来如下图:
区间用[a, b)表示, 表示a到b - 1 的区间内最小值,
圆圈内数字表示该数据存储在数组中的下标
观察可知, 对某一节点k, 其左节点为 k*2+1, 右节点为k*2+2
可有此关系从某一节点到其左右节点,
同样, 由左节点或右节点k可到其上一节点, (k-1)/ 2
3. 基于RMQ的查询
4. 基于RMQ的更新
更新a0的值时, 需要重新计算下图四个节点的值
5. 优化
对于元素个数是不能组成完美二叉树的, 可以扩大元素个数来解决,
例如五个元素的数组, 简单化为下图, 这个实现在初始化下面代码中的init()函数里面
6. 代码实现
实现输入n个数查询区间最小值。
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<string>
#include<vector>
#include<stack>
using namespace std;
typedef long long ll;
int n, n_;
const int maxn = 1 << 17;
int dat[2 * maxn - 1];
int inf = 1<< 29;
void init(int a) //初始化函数对于a个元素的数组,
// 将元素扩大到n个, 整个树的节点数为2 * n - 1
{
n = 1;
while(n < a) n *= 2;
for(int i = 0; i < 2 * n - 1; i++) dat[i] = inf;
}
void update(int k, int a) //更新函数, 将第k个值更新为a
{
k += n - 1; //第k个值在存储树的数组中的下标为 k + n - 1
dat[k] = a;
while(k) //向上更新
{
k = (k - 1) /2;
dat[k] = min(dat[k * 2 + 1], dat[k * 2 + 2]);
}
}
int query(int a, int b, int k, int l, int r) //查询[a, b)中最小值,
// k代表所查节点, l, r代表所查节点的左右边界
//每次都从根节点开始查询故外部调用时为query(a, b, 0, 0, n)
{
if(a >= r || b <= l) return inf; //[a, b) 与[l, r) 不相交, 返回inf
if(a <= l && r <= b) return dat[k]; //[a, b) 完全包含[l, r), 返回dat[k]
else //[a, b) 与[l, r) 部分相交, 需要到子节点去查询。
{
int v1 = query(a, b, k * 2 + 1, l, (l + r) / 2);
int vr = query(a, b, k * 2 + 2, (l + r) / 2, r);
return min(v1, vr);
}
}
int main()
{
cin >> n_;
init(n_);
for(int i = 0; i < n_; i++)
{
int x;
cin >> x;
update(i, x);
}
cout << query(0, 5, 0, 0, n) << endl;
cout << query(0, 3, 0, 0, n) << endl;
cout << query(0, 2, 0, 0, n) << endl;
}