【C++】浅析线段树(Segment Tree)

本文详细介绍了线段树的概念、代码实现、基础操作和拓展,包括建树、查找、修改等,并讨论了线段树的优势和劣势,如空间优化和区间最值。同时提到了懒标记和zkw线段树等高级主题。

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


When Will It End?

在树状数组BIT后,第二篇极长的数据结构文章……

0.引入

同样的引入:神题:A+B Problem
有几种方法怕有人说我博客很水就不放了,详情参见我的这篇博客

1.线段树简介

之前可能学过树状数组,没错,这东西,和树状数组是 几乎 (记住说明文语言要准确严谨) 互通的,不过有一点不同(这里都针对最基础的来讲),就是线段树可以查询最值,就像ST表一样(个人认为ST表的全名是Segment Tree Table))。
好了,引入正题。

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为 O ( l o g N ) O(logN) O(logN)。而未优化的空间复杂度为 O ( 2 N ) O(2N) O(2N),实际应用时一般还要开 4 N 4N 4N的数组以免越界,因此有时需要离散化让空间压缩。 ——百度百科

博主语文水的很,再解释也解释不清楚了

2.代码实现

2.1. 附上(大)部分介绍

首先,上一个结构体

struct node{
   
   
    int l,r;
    ...//这里储存你需要的各种信息
};

呃呃呃……不会这么简单吧……当然不会。这个只是一个线段树的节点,然后又这些节点构成一棵线段树,如图。
在这里插入图片描述
其实,这个图可以表示成一个线段组成的树,如下。
在这里插入图片描述
还要清楚一个区间的概念:
中括号([])表示闭区间,就是包括边缘的区间;小括号(())表示开区间,就是不包括边缘的区间。
另外说明一下,真正的线段树可不是这样的——这个只是表示了整数的1~6的区间,而实际上是表示的所有的区间,就是 [ 1 , 2 )   [ 2 , 3 ) . . . . . . [1,2)\ [2,3)...... [1,2) [2,3)......一直到 [ n , n + 1 ) [n,n+1) [n,n+1)
额……所以,还有一个问题,就是说,有多少个节点?
按照线段树的定义, [ 1 , n + 1 ) [1,n+1) [1,n+1)的线段树有 n n n个叶子结点,又因为线段树是一颗二叉树,所以线段树一共有 2 ∗ n 2*n 2n个节点。
不过,数组只开 2 n 2n 2n就够了吗?我们进行线段树的运算,是根据完全二叉树来进行计算的,所以任意一个节点 p p p的左儿子就是 p ∗ 2 p*2 p2,右儿子就是 p ∗ 2 + 1 p*2+1 p2+1,对应上方区间 [ 1 , 7 ) [1,7) [1,7)的线段树,可以得知,我们至少要把空间开到13(因为图中区间 [ 4 , 5 ] [4,5] [4,5]最少是下标6,所以 [ 5 , 5 ] [5,5] [5,5]即此区间的右儿子的下标就是13)!这时候,如果开12,那不爆才怪……
那开多少呢? 4 n 4n 4n。为什么?也就是多留了一层的节点出来(原来最下层 n n n个节点,因为是二叉树,乘个2,再加上原来的 2 n 2n 2n,就是 4 n 4n 4n了)
So,真正的线段树,是这样的

struct node{
   
   
    int l,r;
    ...//这里储存你需要的各种信息
}tr[MAXN<<2];//后面会介绍的左移操作

好了,神奇的基础概念终于介绍完了……

2.2.基础操作

额,其实上面已经提到了——基础操作就是左儿子和右儿子!
因为线段树是一棵不太完全的完全二叉树,所以我们就可以按照完全二叉树的方式来使用线段树的左儿子和右儿子,如下

inline int ls(int p) {
   
    return p << 1; }
inline int rs(int p) {
   
    return p << 1 | 1; }

嗯?好像和上面的写法不一样?
为什么要这样写呢?因为位运算节省时间啊!
为什么是对的呢?因为 x &lt; &lt; 1 x&lt;&lt;1 x<<1就和 x ∗ 2 x*2 x2一样啊!1
Tips:使用位运算一定要多打括号,因为位运算的优先级,很玄学,比加减乘除都还低……
当然,就算用了inline,函数还是有调用时间的,更常用的做法是宏定义。

#define ls(p) ( p << 1 )
#define rs(p) ( p << 1 | 1 )

2.3.延伸操作

以下操作都是在同一个背景下——单点修改+区间求和
首先,为了做这个操作,我们需要这样的一个结构体:

struct node{
   
   
    int l,r;
    long long sum;
};

这个sum,指的是区间l~r范围内的和。

2.3.1.建树

树状数组都不用这东西,怪不得很多人都只打树状数组,不打线段树

2.3.1.1.实现

怎么建树呢?等一下,不要想复杂了,线段树的建树就是给每一个节点附上初始值。

inline void build(int p,int l,int r) {
   
   
    tr[p].l=l; tr[p].r=r; tr[p].sum=0;
    if(l==r) {
   
    return ; }
    int mid=(l+r)>>1;
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
}
2.3.1.2.分析

天哪!什么东西!真是短小精悍!
好吧,简而言之,build(p,l,r)的意思是,将下标为 p p p的这个区间的左端点赋值为

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值