线段树详解

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;
	
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值