数据结构:线段树

线段树

一、线段树的定义

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

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

资料: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. 复杂区间操作:结合懒标记可以处理区间加、区间乘等区间更新操作。

七、扩展 懒标记

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

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

相关推荐
CQ_YM17 小时前
数据结构之哈希表
数据结构·算法·哈希算法·哈希表
FMRbpm17 小时前
顺序表实现队列
数据结构·c++·算法·新手入门
飞天狗11117 小时前
G. Mukhammadali and the Smooth Array
数据结构·c++·算法
CQ_YM17 小时前
数据结构之树
数据结构·算法·
小李小李快乐不已18 小时前
哈希表理论基础
数据结构·c++·哈希算法·散列表
AuroraWanderll18 小时前
C++11(二)核心突破:右值引用与移动语义(上)
c语言·数据结构·c++·算法·stl
CoderYanger18 小时前
第 479 场周赛Q1——3769. 二进制反射排序
java·数据结构·算法·leetcode·职场和发展
sin_hielo19 小时前
leetcode 1925
数据结构·算法·leetcode
CoderYanger19 小时前
A.每日一题——1925. 统计平方和三元组的数目
java·开发语言·数据结构·算法·leetcode·哈希算法