线段树详解

线段树详解

线段树(Segment Tree)是一种基于分治思想的高级数据结构,主要用于解决区间查询和区间更新问题。它能够在 O(log n) 的时间内完成区间查询(如区间求和、区间最大值等)和单点/区间更新操作,适用于处理动态区间问题。


1. 线段树的核心思想

线段树的核心思想是将一个区间递归地分成若干个子区间,每个子区间对应线段树中的一个节点。通过预处理和存储这些区间的信息,线段树可以高效地回答区间查询和更新操作。


2. 线段树的基本性质

  1. 区间划分
    • 线段树的每个节点代表一个区间。
    • 根节点代表整个区间 [1, n]
    • 每个非叶子节点 [l, r] 会被划分为两个子区间:[l, mid][mid+1, r],其中 mid = (l + r) / 2
  2. 存储信息
    • 每个节点存储其对应区间的某种信息(如区间和、区间最大值等)。
  3. 递归构建
    • 线段树通过递归方式构建,叶子节点存储单个元素的值,非叶子节点存储其子节点的合并信息。

3. 线段树的实现

3.1 线段树的存储

线段树通常用数组实现,类似于堆的存储方式:

  • 根节点下标为 1
  • 对于节点 i
    • 左子节点下标为 2 * i
    • 右子节点下标为 2 * i + 1

3.2 线段树的构建

以区间求和为例,构建线段树的步骤如下:

  1. 递归划分区间
    • 从根节点开始,递归地将区间划分为左右子区间。
  2. 合并信息
    • 非叶子节点的值为其左右子节点的值之和。
def build_tree(arr, tree, node, start, end):
    if start == end:
        # 叶子节点,存储单个元素的值
        tree[node] = arr[start]
    else:
        mid = (start + end) // 2
        # 递归构建左子树
        build_tree(arr, tree, 2 * node, start, mid)
        # 递归构建右子树
        build_tree(arr, tree, 2 * node + 1, mid + 1, end)
        # 合并左右子树的信息
        tree[node] = tree[2 * node] + tree[2 * node + 1]

3.3 区间查询

查询区间 [l, r] 的和:

  1. 如果当前节点区间完全包含在查询区间内,直接返回当前节点的值。
  2. 如果当前节点区间与查询区间无交集,返回 0
  3. 否则,递归查询左右子区间,并合并结果。
def query_tree(tree, node, start, end, l, r):
    if r < start or l > end:
        # 当前区间与查询区间无交集
        return 0
    if l <= start and end <= r:
        # 当前区间完全包含在查询区间内
        return tree[node]
    # 递归查询左右子区间
    mid = (start + end) // 2
    left_sum = query_tree(tree, 2 * node, start, mid, l, r)
    right_sum = query_tree(tree, 2 * node + 1, mid + 1, end, l, r)
    return left_sum + right_sum

3.4 单点更新

更新某个位置的值:

  1. 递归找到对应的叶子节点。
  2. 更新叶子节点的值,并向上更新其父节点的值。
def update_tree(tree, node, start, end, idx, value):
    if start == end:
        # 找到目标叶子节点,更新值
        tree[node] = value
    else:
        mid = (start + end) // 2
        if idx <= mid:
            # 递归更新左子树
            update_tree(tree, 2 * node, start, mid, idx, value)
        else:
            # 递归更新右子树
            update_tree(tree, 2 * node + 1, mid + 1, end, idx, value)
        # 更新父节点的值
        tree[node] = tree[2 * node] + tree[2 * node + 1]

3.5 区间更新(延迟标记)

区间更新可以通过 延迟标记(Lazy Propagation) 优化:

  1. 在更新时,只更新当前节点,并将更新信息存储在延迟标记中。
  2. 在查询时,根据需要将延迟标记下推。

4. 线段树的应用场景

  1. 区间求和
    • 支持区间求和和单点/区间更新。
  2. 区间最值
    • 支持查询区间最大值或最小值。
  3. 区间覆盖
    • 支持区间赋值操作。
  4. 动态区间问题
    • 如区间内满足某种条件的元素个数。

5. 线段树的复杂度分析

  1. 时间复杂度
    • 构建:O(n)
    • 查询:O(log n)
    • 更新:O(log n)
  2. 空间复杂度
    • O(4n)(通常需要 4 倍于原数组的空间)。

6. 代码示例

以下是一个完整的线段树实现(区间求和):

class SegmentTree:
    def __init__(self, arr):
        self.n = len(arr)
        self.tree = [0] * (4 * self.n)
        self.build(arr, 1, 0, self.n - 1)

    def build(self, arr, node, start, end):
        if start == end:
            self.tree[node] = arr[start]
        else:
            mid = (start + end) // 2
            self.build(arr, 2 * node, start, mid)
            self.build(arr, 2 * node + 1, mid + 1, end)
            self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]

    def query(self, node, start, end, l, r):
        if r < start or l > end:
            return 0
        if l <= start and end <= r:
            return self.tree[node]
        mid = (start + end) // 2
        left = self.query(2 * node, start, mid, l, r)
        right = self.query(2 * node + 1, mid + 1, end, l, r)
        return left + right

    def update(self, node, start, end, idx, value):
        if start == end:
            self.tree[node] = value
        else:
            mid = (start + end) // 2
            if idx <= mid:
                self.update(2 * node, start, mid, idx, value)
            else:
                self.update(2 * node + 1, mid + 1, end, idx, value)
            self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]

# 示例用法
arr = [1, 3, 5, 7, 9, 11]
st = SegmentTree(arr)
print(st.query(1, 0, len(arr) - 1, 1, 3))  # 输出 15 (3 + 5 + 7)
st.update(1, 0, len(arr) - 1, 2, 10)       # 将 arr[2] 更新为 10
print(st.query(1, 0, len(arr) - 1, 1, 3))  # 输出 20 (3 + 10 + 7)

7. 总结

线段树是一种强大的数据结构,适用于解决动态区间问题。通过递归划分区间和合并信息,线段树能够高效地支持区间查询和更新操作。掌握线段树的实现和应用,可以极大地提升解决复杂问题的能力。

posted @   白色墨水  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示