线段树入门、总结 Interval Tree

本文详细介绍了线段树的基本概念、操作方法及其在解决区间RMQ问题、存储值、优化DP等方面的应用,通过实例代码展示了如何建立、查询和修改线段树。

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

线段树入门级总结
By Jsn1993
最近学习了线段树这一种数据结构,也做了不少关于线段树的入门级题目,大致对线段树有
了一个了解。稍稍总结一下(文字没有参考别人的成分)。
一、 初识线段树
1、 称谓及意义
线段树我习惯用英文称呼它(Interval Tree)。Interval 代表区间,恰恰说明了线段树是一种基
于区间的数据结构。
2、 线段树的表示
线段树是一棵二叉树,它的左右儿子也都是一棵线段树。
在最坏情况下节点的标号最大为N shl 2(图1)。
线段树的每个节点代表一个区间[L,R],其中,假设根代表一个区间[L0,R0]。
取 Mid=(L0+R0) shr 1,则根的左儿子代表的区间为[L0,mid],右儿子为[Mid+1,R0]。
线段树的每个叶子节点代表的区间[L,R],其中L=R。
具体可参考图1(Quote From Rujia Liu’s ppt):
3、 线段树区间的意义
在实际应用中,[L,R]往往只代表这个区间的左右界,并不是代表具体的值。
往往在需要对某个区间进行操作并且进行了操作后,由于区间的包含性与传递性,需要更新
这个区间的父亲与儿子。
二、线段树的操作
1、 建立一棵线段树
显然,线段树是递归定义的
对于一个线段树中的区间,若L<R,则必能被分作[L,Mid] , [Mid+1,R], Mid=(L+R) shr 1。
若 L=R,则为递归的边界,此时该区间为一个点。
由此思想,建树时需要利用递归(或者模拟堆栈)。
另外,由于这类线段树是二叉树式存储:
节点 T的左儿子节点是T shl 1, 代表[L,Mid]区间。
节点 T的右儿子节点是T shl 1+1,代表[Mid+1,R]区间。
这样便建立完了一棵线段树。
伪代码:
Void Build(int T,L,R)
{
If (L==R)
{
Add values to the leave node;
Return;
}
Int mid=(L+R)>>1;
Build(T<<1,L,Mid);
Build(T<<1+1,Mid+1,R);
Update the current node with his left son and right son;
}
其中 add value 与update root 都需要在具体的情境中采用具体的过程。
简单举例,在维护区间Max值时
当 L=R,因为只有一个数,说明[L,R]的最大值便是序列中的原数。
Tree[T]=List[L](List[R]亦可)
易知,父区间的最大值便为左右儿子区间最大值的最大值(。。。)
Tree[T]=Max(Tree[T<<1],Tree[T<<1+1]);
执行了Build(1,1,Len)之后,
便建立完了一棵以[1,Len]为根区间,节点1 为根节点的线段树。
至此,建立部分完毕。
2、 查询某个区间
线段树最基本的用途便是保存各个区间内的特定值,
但只有保存,并不够,正如程序只有Input 而没有Output。
所以线段树是被用来处理某些RMQ 问题的常用数据结构。
显然,在[L,R]中查询[LL,RR]的时候有三种情况。
第一种,[LL,RR]被[L,Mid]包含,此时直接在[L,Mid]中查询[LL,RR]
第二种,[LL,RR]被[Mid+1,R]包含,此时直接在[Mid+1,R]中查询[LL,RR](同理)
第三种,较复杂的一种,[LL,RR]被Mid 从中间截断,此时在[L,Mid]中查询[LL,Mid],在
[Mid+1,R]中查询[Mid+1,RR],然后将两个查询得到的结果进行Update,返回Update的结果。
这样便处理完了查询。
伪代码:
Int Query(int T,L,R,LL,RR) //如果需要返回多个结果,可以考虑用Struct存储并返回Struct
{
If (LL=L&&RR=R)
Return Tree[t];
Int Mid=(L+R)>>1
If (RR<=Mid)
Return Query(T<<1,L,Mid,LL,RR);
If (LL>Mid)
Return Query(T<<1+1,Mid+1,R,LL,RR);
Int Left=Query(T<<1,L,Mid,LL,Mid);
Int Right=Query(T<<1+1,Mid+1,R,Mid+1,RR);
Int Temp;
Update(Temp,Left,right);
Return Temp;
}
再以查询区间最大值为例
当 LL=L且RR=R
返回的即为区间[L,R]的最大值,
当全部落在[L,Mid],便在区间[L,Mid]内查询[LL,RR],
当全部落在[Mid+1,R],便在区间[Mid+1,R]查询[LL,RR]。
当被 Mid截断,便查询[L,Mid],[Mid+1,R],并返回这两个区间查询结果Update后的结果。
执行完 Ans=Query(1,1,N,LL,RR),
便得到了在[1,N]查询[LL,RR]的结果
至此,查询部分完毕。
3、 修改某个区间或点
当某个问题需要查询并修改一个区间或者点的值的时候,线段树也能支持这一操作。
本质上,建立一棵线段树相当于向一棵线段树加入许多点,或者说将原来空的点修改为值。
线段树支持修改,第一种是将某个点的值改变,并递归更新他的父亲,
第二种是将某个区间加上一个delta 值,或者改变为一个cover 值。
修改点:
显然,在[L,R]中修改[LL,RR](LL=RR)的时候有三种情况。
第一种,[LL,RR]被[L,Mid]包含,此时直接在[L,Mid]中修改[LL,RR]
第二种,[LL,RR]被[Mid+1,R]包含,此时直接在[Mid+1,R]中修改[LL,RR](同理)
然后递归更新父亲。
不用考虑被截断(因为只是一个点)。
修改区间
显然,在[L,R]中修改[LL,RR](LL<RR)的时候有三种情况。
第一种,[LL,RR]被[L,Mid]包含,此时直接在[L,Mid]中修改[LL,RR]
第二种,[LL,RR]被[Mid+1,R]包含,此时直接在[Mid+1,R]中修改[LL,RR](同理)
第三种,较复杂的一种,[LL,RR]被Mid 从中间截断,此时在[L,Mid]中修改[LL,Mid],在
[Mid+1,R]中修改[Mid+1,RR],然后将两个查询得到的结果进行Update,递归更新父亲。
这样便处理完了修改。
伪代码(修改区间的,修改点的只需要简单调整):
VoidModify(int T,L,R,LL,RR,Value)
{
If (LL=L&&RR=R)
Modify the node with Value
Int Mid=(L+R)>>1
If (RR<=Mid)
Modify(T<<1,L,Mid,LL,RR,Value);
else
If (LL>Mid)
Modify(T<<1+1,Mid+1,R,LL,RR,Value);
Else
{
Modify(T<<1,L,Mid,LL,Mid);
Modify(T<<1+1,Mid+1,R,Mid+1,RR);
}
Update the current node with his left son and right son;
}
三、线段树的应用
解决一些区间RMQ 问题,存储值,优化DP等
习题:
Pku (Acm.pku.edu.cn/JudgeOnline)
2104 (较难,树套表,二分答案)
2299(逆序对)
2777(区间Cover)
2823(最大最小)
3264(最大最小)
3368(左右两边连续的长度)
3468(区间和,支持delta)
A product of JSN1993
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值