数据结构:线段树

线段树

一、线段树的定义

线段树是一种二叉搜索树,它将一个区间划分为若干个子区间,每个叶子节点对应原区间的一个单个元素,非叶子节点对应原区间的一个子区间。

线段树的主要作用是高效处理区间查询单点/区间更新操作,常见的应用场景包括区间最大值、最小值、区间和的查询与更新等。

资料:https://pan.quark.cn/s/43d906ddfa1bhttps://pan.quark.cn/s/90ad8fba8347https://pan.quark.cn/s/d9d72152d3cf

二、线段树的基本结构

  1. 节点含义

    • 每个节点存储对应区间的信息,例如区间和、区间最大值等。
    • 若一个节点对应的区间是 [l, r],且 l != r,则该节点会被划分为左右两个子节点,分别对应区间 [l, mid][mid+1, r],其中 mid = (l + r) / 2
    • 叶子节点对应的区间满足 l = r,存储的是原数组中单个元素的值。
  2. 树的形态

    • 线段树是一棵近似完全二叉树,对于长度为 n 的原数组,线段树的节点总数约为 4 * n,因此在实现时通常会开辟 4 * n 大小的数组来存储线段树。

三、线段树的核心操作

1. 构建(Build)

构建操作是将原数组的信息填充到线段树的各个节点中,自底向上进行。

  • 递归终止条件:当 l == r 时,当前节点的值等于原数组中下标为 l 的元素值。
  • 递归过程:先递归构建左子树和右子树,再根据左右子节点的值计算当前节点的值。

2. 单点更新(Point Update)

单点更新是修改原数组中某个位置的元素值,并更新线段树中相关的节点。

  • 递归终止条件:当 l == r 时,修改当前节点的值。
  • 递归过程:根据要更新的位置,判断是在左子树还是右子树,递归更新对应子树,然后回溯更新当前节点的值。

3. 区间查询(Range Query)

区间查询是查询原数组中某个区间 [ql, qr] 的统计信息,例如区间和、区间最大值等。

  • 递归终止条件:如果当前节点对应的区间 [l, r] 完全在查询区间 [ql, qr] 内,直接返回当前节点的值。
  • 递归过程:如果当前节点的区间与查询区间无交集,返回无效值;否则递归查询左右子树,合并左右子树的查询结果并返回。

四、线段树的时间复杂度

  • 构建操作:时间复杂度为 O(n),需要遍历所有节点一次。
  • 单点更新操作:时间复杂度为 O(log n),每次更新最多需要遍历树的高度次节点。
  • 区间查询操作:时间复杂度为 O(log n),每次查询最多需要遍历树的高度次节点。

五、线段树的实现示例 以区间和为例

python 复制代码
class SegmentTree:
    def __init__(self, data):
        self.n = len(data)
        self.tree = [0] * (4 * self.n)
        self.build(0, 0, self.n - 1, data)
    
    def build(self, node, l, r, data):
        if l == r:
            self.tree[node] = data[l]
            return
        mid = (l + r) // 2
        left_node = 2 * node + 1
        right_node = 2 * node + 2
        self.build(left_node, l, mid, data)
        self.build(right_node, mid + 1, r, data)
        self.tree[node] = self.tree[left_node] + self.tree[right_node]
    
    def update_point(self, idx, val):
        self._update(0, 0, self.n - 1, idx, val)
    
    def _update(self, node, l, r, idx, val):
        if l == r:
            self.tree[node] = val
            return
        mid = (l + r) // 2
        left_node = 2 * node + 1
        right_node = 2 * node + 2
        if idx <= mid:
            self._update(left_node, l, mid, idx, val)
        else:
            self._update(right_node, mid + 1, r, idx, val)
        self.tree[node] = self.tree[left_node] + self.tree[right_node]
    
    def query_range(self, ql, qr):
        return self._query(0, 0, self.n - 1, ql, qr)
    
    def _query(self, node, l, r, ql, qr):
        if r < ql or l > qr:
            return 0
        if ql <= l and r <= qr:
            return self.tree[node]
        mid = (l + r) // 2
        left_node = 2 * node + 1
        right_node = 2 * node + 2
        left_sum = self._query(left_node, l, mid, ql, qr)
        right_sum = self._query(right_node, mid + 1, r, ql, qr)
        return left_sum + right_sum

六、线段树的应用场景

  1. 区间统计类问题:如区间和、区间最大值、区间最小值、区间最大公约数等查询与更新。
  2. 离线与在线问题:线段树可以处理在线的动态更新和查询操作,相较于前缀和等静态方法更灵活。
  3. 复杂区间操作:结合懒标记可以处理区间加、区间乘等区间更新操作。

七、扩展 懒标记

当需要进行区间更新操作时,直接递归更新每个节点会导致时间复杂度升高,此时可以引入懒标记 (延迟标记)。

懒标记的核心思想是:当更新的区间完全覆盖当前节点的区间时,先记录更新操作,不立即向下更新子节点,等到后续查询或更新操作需要访问子节点时,再将标记下传,完成子节点的更新。

相关推荐
驭渊的小故事8 小时前
简单模板笔记
数据结构·笔记·算法
VT.馒头8 小时前
【力扣】2727. 判断对象是否为空
javascript·数据结构·算法·leetcode·职场和发展
历程里程碑10 小时前
Linux 库
java·linux·运维·服务器·数据结构·c++·算法
Sheep Shaun10 小时前
如何让一个进程诞生、工作、终止并等待回收?——探索Linux进程控制与Shell的诞生
linux·服务器·数据结构·c++·算法·shell·进程控制
Pluchon10 小时前
硅基计划4.0 简单模拟实现AVL树&红黑树
java·数据结构·算法
小龙报10 小时前
【51单片机】从 0 到 1 玩转 51 蜂鸣器:分清有源无源,轻松驱动它奏响新年旋律
c语言·数据结构·c++·stm32·单片机·嵌入式硬件·51单片机
dllxhcjla10 小时前
数据结构和算法
数据结构
历程里程碑12 小时前
普通数组----轮转数组
java·数据结构·c++·算法·spring·leetcode·eclipse
sin_hielo12 小时前
leetcode 1653
数据结构·算法·leetcode
李日灐12 小时前
C++进阶必备:红黑树从 0 到 1: 手撕底层,带你搞懂平衡二叉树的平衡逻辑与黑高检验
开发语言·数据结构·c++·后端·面试·红黑树·自平衡二叉搜索树