基于线段树RMQ的操作:
在给定数列a0,a1,…,an-1的情况下,可以在O (logn)时间内完成下面操作:
- 给定s和t,求区间[s,t)的最小值
- 给定i和x,将ai的值改为x
假设给定初始数组 date[8]={6,3,1,9,7,4,8,5}
所得线段树如图:
实际上,每个节点隐含有对应的区间,这是线段树所必须具备的元素,而其他元素则根据题目而改变,例如,RMQ则为对应区间的最小值。
所以实际上的线段树如图:
1.RMQ查询
如果要求区间[0,6)的最小值,我们只需查找如下三个节点
从根节点出发,查询分为三种情况:
- 当查询区间与节点区间完全无交叉时返回一个infinity值
- 当查询区间完全包含节点区间时,返回节点的值
- 当有一部分交叉时,则递归儿子节点
2.更新叶子节点
若用2将7代替,则需要跟新3个节点的值
此程序直接初始化数组时建树,也可以通过不断更新叶子节点的方式建树
时间复杂度:对于每个深度最多访问常数个节点,因此为O(logn)
#include<iostream>
#include<stdlib.h>
#include<algorithm>
#define MAX 100
#define INF 100000
using namespace std;
int n;
int date[MAX];//存储原始数据
int tree[MAX];//存储线段树
//创建线段树
void init() {
int count = 2 * n - 2;
for (int i = n - 1; i <= count; i++)
tree[i] = date[i - n + 1];
int p = (count - 1) / 2;
while (p >= 0) {
int lch = 2 * p + 1;
int rch = 2 * p + 2;
tree[p] = min(tree[lch], tree[rch]);
p--;
}
}
//更新叶子节点值,也可以通过不断跟新叶子节点的方式建树
void update(int k,int a) {
tree[k + n - 1] = a;
int p = (k + n - 2) / 2;
while (p > 0) {
int lch = 2 * p + 1;
int rch = 2 * p + 2;
tree[p] = min(tree[lch], tree[rch]);
p = (p - 1) / 2;
}
}
//查询RMQ,k为当前节点,[l,r)为当前节点区间,[a,b)为需要查询的区间
int query(int k,int a,int b,int l,int r) {
//当[a,b)与[l,r)不相交的时候,返回一个较大值
if (a >= r || b <= l) return INF;
//当[a,b)完全包含[l,r)时,则返回节点的值
else if (a<=l&&b>=r) return tree[k];
//当包含一部分,则递归
else {
int mid = (l + r) / 2;
int lch = query(2 * k + 1, a, b, l, mid);
int rch = query(2 * k + 2, a, b, mid, r);
return min(lch, rch);
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++)
cin >> date[i];
init();
for (int i = 0; i < 2 * n - 1; i++)
cout << tree[i];
cout << endl << query(0, 0, 6, 0, 8);
}