区间信息维护与查询【线段树 】 - 原理1 线段树的基本操作

本文详细介绍了线段树的概念、结构及基本操作,包括如何创建线段树、进行点更新和区间查询。线段树作为一种数据结构,能高效处理区间内的最值查询和更新,适用于多种问题,如动态维护区间最值等。文章通过实例展示了线段树的存储方式和算法实现,帮助读者深入理解这一数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

区间信息维护与查询【线段树 】 - 原理1 线段树的基本操作

线段树(segment tree)是一种基于分治思想的二叉树,它的每个节点都对应一个[L , R ]区间,叶子节点对应的区间L =R 。每一个非叶子节点[L , R ]其左子节点的区间都为[L , (L +R )/2],右子节点的区间都为[(L +R )/2+1, R ]。

[1, 10]区间的线段树如下图所示:

在这里插入图片描述

因为线段树对区间进行二分,是一棵平衡二叉树,所以树高为O(logn ),树上的操作大多与树高相关。

线段树主要用于更新和查询,一般至少有一个是区间更新或查询。更新和查询的种类变化多样,灵活运用线段树可以解决多种问题。

【1】 线段树的存储方式

对于区间最值(最大值或最小值)查询问题,线段树的每个节点都包含三个域:l、r、 mx,其中l 和r 分别表示区间的左右端点,mx表示[l , r ]区间的最值。

本题以最大值为例,若有10个元素a [1…10]={5, 3, 7, 2, 12, 1, 6, 4, 8, 15},则构建的线段树如下图所示。

在这里插入图片描述

线段树除了最后一层,其他层构成一颗满二叉树,因此采用顺序存储方式,用一个数组tree[]存储节点。

若一个节点的存储下标为k ,则其左子节点的下标为2k ,其右子节点的下标为2k +1。

在这里插入图片描述

线段树根节点的存储下标为1,其左右子节点的存储下标分别为2、3,有10个元素的线段树,其存储下标如下图所示:

在这里插入图片描述

【2】创建线段树

可以采用递归的方法创建线段树,算法步骤如下:

① 若是叶子节点(l =r ),则节点的最值就是对应位置的元素值。

② 若是非叶子节点,则递归创建左子树和右子树。

③ 节点的区间最值等于该节点左右子树最值的最大值。

[ 算法代码]

void build(int k , int l , int r){ //创建线段树,节点的存储下标为k,节点的区间为 [l , r]
	tree[k].l = l;
	tree[k].r = r;
	
	if(l == r){
		tree[k].mx = a[l];
		return;
	}
	
	int mid , lc , rc;
	mid = (l + r) / 2; //划分点
	lc = k * 2; // 左子节点的存储下标
	rc = k * 2 + 1; //右子节点的 存储下标
	build(lc , l , mid);
	build(rc , mid + 1, r);
	
	tree[k].mx = max(tree[lc].mx , tree[rc].mx); //节点的最大值等于左右子节点 最值的最大值
}
【3】点更新

点更新指修改一个元素的值,例如将a [i ]修改为v 。采用递归进行点更新,算法步骤如下。

① 若是叶子节点,满足l =r 且l =i ,则修改该节点的最值为v。

② 若是非叶子节点,则判断是在左子树中更新还是在右子树中更新。

③ 返回时更新节点的最值。

例如,修改第5个节点的值为14时,先从树根向下找第5个元素所在的叶子节点,将其最值修改为14,返回时更新路径上所有节点的最值(左右子节点最值的最大值)。

在这里插入图片描述

[ 算法代码]

void update(int k , int i , int v){ // 将a[i] 更新为 v
	
	if(tree[k].l == tree[k].r && tree[k].l == i){ //找到a[i]
		
		tree[k].mx = v;
		return ;
	}
	
	int mid , lc, rc;
	mid = (tree[k].l + tree[k].r) / 2; //划分点
	
	lc = k * 2; // 左子节点的存储下标
	rc = k * 2 + 1; // 右子节点的 存储下标
	
	if(i <= mid){
		update(lc , i ,v); // 到左子树中 更新
	}else{
		update(rc , i , v); //到 右子树中更新
	}
	
	tree[k].mx = max(tree[lc].mx , tree[rc].mx); // 返回时更新最值
}
【4】区间查询

区间查询指查询一个[l , r ]区间的最值。采用递归的方法进行区间查询的算法步骤如下:

① 若节点所在的区间被查询区间[l , r ]覆盖,则返回该节点的最值。

② 判断是在左子树中查询,还是在右子树中查询。

③ 返回最值。

例如,在[1, 10]的线段树中查询[3, 5]区间的最值,过程如下。

① 计算树根[1, 10]的划分点,mid=(1+10)/2=5,待查询区间l=3、r =5、r ≤mid,说明查询区间在左子树中,到左子树中查询

在这里插入图片描述

② 计算左子树树根[1, 5]的划分点,mid=(1+5)/2=3,待查询区间l =3、r =5、r >mid、l ≤mid,说明查询区间横跨左右子树两个区间,需要到左、右子树中查询[3, 5],然后求最大值。

在这里插入图片描述

③ 计算左子树树根[1, 3]的划分点,mid=(1+3)/2=2,待查询区间l =3、r =5、l >mid,说明查询区间在右子树中,到右子树中查询,该右子树[3, 3]被查询区间覆盖,返回最值7。

在这里插入图片描述

④ 右子树为[4, 5],待查询区间为[3, 5],被查询区间覆盖,返回区间最值12。

在这里插入图片描述

⑤ 左右子树分别返回最值7、12,然后求最大值,即可得到查询区间[3, 5]的最值为12。

在这里插入图片描述

[ 算法代码]

int query(int k , int l,  int r){ // 求[l ,r] 区间的最值
	
	if(tree[k].l >= l && tree[k].r <= r){ // 查询区间覆盖该节点区间
		return tree[k].mx;	
	}
	
	int mid , lc , rc;
	mid = (tree[k].l + tree[k].r ) / 2; //划分点
	
	lc = k * 2; //左子节点的存储下标
	rc = k * 2 + 1; //右子节点的 存储下标
	
	int Max = -inf; //注意: 局部变量可以,全局变量不可以
	if(l <= mid){
		Max = max(Max , query(lc , l , r)); // 到左子树中查询
	}
	if(r > mid){
		Max = max(Max , query(rc , l , r)); //到右子树中查询
	}
	
	return Max;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

祝我天天开心,平安健康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值