线段树(Segment Tree)
字数 1817 2025-11-07 22:15:37

线段树(Segment Tree)

描述
线段树是一种二叉树数据结构,用于高效处理数组的区间查询和区间更新操作。它特别适用于需要频繁查询和更新数组某个区间信息(如区间和、区间最大值、区间最小值等)的场景。线段树将整个数组区间递归地划分成若干个子区间,每个树节点存储一个特定区间的聚合信息。通过这种结构,区间查询和区间更新的时间复杂度都能达到O(log n)。

基本概念与结构

  1. 问题场景:假设有一个数组arr,需要支持两种操作:(1)查询区间[L, R]的和(或最大值等);(2)将区间[L, R]内的每个元素增加一个值。朴素方法中,查询需要O(n)时间,更新也需要O(n)。线段树旨在将这两种操作优化到O(log n)。

  2. 树结构构建

    • 线段树是一棵近似的完全二叉树(通常用数组存储以节省空间)。
    • 根节点表示整个数组区间[0, n-1]。
    • 每个非叶子节点表示一个区间[L, R],将其平分为两半:左子节点区间为[L, mid],右子节点区间为[mid+1, R],其中mid = (L + R) // 2。
    • 叶子节点表示长度为1的区间(即单个元素)。
    • 每个节点存储其对应区间的聚合值(例如区间和)。
  3. 示例:对于数组arr = [1, 3, 5, 7, 9, 11],构建求和线段树的过程如下:

    • 根节点表示区间[0,5],值为区间和36。
    • 左子节点表示[0,2],值为1+3+5=9;右子节点表示[3,5],值为7+9+11=27。
    • 递归划分直到叶子节点(如[0,0]值为1)。

线段树的构建

  1. 递归构建:从根节点开始,递归地创建左右子树,直到区间长度为1时设置叶子节点值。
  2. 节点值计算:非叶子节点的值由其左右子节点的值聚合得到(例如求和时,父节点值=左子节点值+右子节点值)。
  3. 时间复杂度:构建树需要初始化所有节点,共有约2n个节点(实际为2n-1),因此构建时间为O(n)。

区间查询操作

  1. 查询过程:查询区间[L, R]的和时,从根节点开始递归:
    • 若当前节点区间完全包含在[L, R]内,直接返回该节点存储的聚合值。
    • 若当前节点区间与[L, R]无重叠,返回0(对求和而言)。
    • 若当前节点区间部分重叠[L, R],则递归查询左右子树,合并结果(如求和时相加)。
  2. 示例:查询[1,4]的和(即3+5+7+9=24):
    • 根节点[0,5]部分重叠,查询左右子树。
    • 左子树[0,2]:区间[1,4]与[0,2]部分重叠,继续递归。其右子树[1,2]完全包含于[1,4],返回值3+5=8。
    • 右子树[3,5]:区间[3,4]完全包含于[3,5],返回值7+9=16。
    • 合并结果:8+16=24。
  3. 效率:每次查询最多遍历树的两条路径,深度为O(log n),因此时间复杂度为O(log n)。

区间更新操作

  1. 懒惰传播(Lazy Propagation):直接更新每个叶子节点效率低(O(n))。引入懒惰标记(Lazy Tag):
    • 更新区间[L, R]时,若当前节点区间完全包含在[L, R]内,则更新该节点值,并设置懒惰标记(表示子节点待更新)。
    • 查询或更新过程中,若当前节点有懒惰标记,先将标记下推给子节点(更新子节点值和标记),再清除当前标记。
  2. 更新步骤
    • 检查当前节点区间与更新区间的重叠情况(类似查询)。
    • 完全包含时,更新节点值并设置懒惰标记。
    • 部分重叠时,先下推懒惰标记(如果需要),再递归更新左右子树,最后更新当前节点值(基于子节点新值)。
  3. 示例:将区间[1,3]的每个元素加2:
    • 根节点[0,5]部分重叠,下推标记(无操作),递归更新左右子树。
    • 左子树[0,2]:部分重叠,递归后更新其右子树[1,2](完全包含),节点值增加(2+2)*2=8(区间长度2,每个元素加2),设置懒惰标记。
    • 右子树[3,5]:部分重叠,递归更新其左子树[3,3](完全包含),节点值增加2,设置标记。
    • 回溯更新根节点值。
  4. 时间复杂度:使用懒惰标记后,更新操作也优化为O(log n)。

应用场景与变体

  • 线段树适用于区间求和、区间最值、区间赋值等问题。
  • 变体包括:二维线段树(处理矩阵)、持久化线段树(支持历史版本查询)等。

通过线段树,区间查询和更新操作均被优化到对数时间复杂度,是解决区间类问题的核心数据结构之一。

线段树(Segment Tree) 描述 线段树是一种二叉树数据结构,用于高效处理数组的区间查询和区间更新操作。它特别适用于需要频繁查询和更新数组某个区间信息(如区间和、区间最大值、区间最小值等)的场景。线段树将整个数组区间递归地划分成若干个子区间,每个树节点存储一个特定区间的聚合信息。通过这种结构,区间查询和区间更新的时间复杂度都能达到O(log n)。 基本概念与结构 问题场景 :假设有一个数组arr,需要支持两种操作:(1)查询区间[ L, R]的和(或最大值等);(2)将区间[ L, R ]内的每个元素增加一个值。朴素方法中,查询需要O(n)时间,更新也需要O(n)。线段树旨在将这两种操作优化到O(log n)。 树结构构建 : 线段树是一棵近似的完全二叉树(通常用数组存储以节省空间)。 根节点表示整个数组区间[ 0, n-1 ]。 每个非叶子节点表示一个区间[ L, R],将其平分为两半:左子节点区间为[ L, mid],右子节点区间为[ mid+1, R ],其中mid = (L + R) // 2。 叶子节点表示长度为1的区间(即单个元素)。 每个节点存储其对应区间的聚合值(例如区间和)。 示例 :对于数组arr = [ 1, 3, 5, 7, 9, 11 ],构建求和线段树的过程如下: 根节点表示区间[ 0,5 ],值为区间和36。 左子节点表示[ 0,2],值为1+3+5=9;右子节点表示[ 3,5 ],值为7+9+11=27。 递归划分直到叶子节点(如[ 0,0 ]值为1)。 线段树的构建 递归构建 :从根节点开始,递归地创建左右子树,直到区间长度为1时设置叶子节点值。 节点值计算 :非叶子节点的值由其左右子节点的值聚合得到(例如求和时,父节点值=左子节点值+右子节点值)。 时间复杂度 :构建树需要初始化所有节点,共有约2n个节点(实际为2n-1),因此构建时间为O(n)。 区间查询操作 查询过程 :查询区间[ L, R ]的和时,从根节点开始递归: 若当前节点区间完全包含在[ L, R ]内,直接返回该节点存储的聚合值。 若当前节点区间与[ L, R ]无重叠,返回0(对求和而言)。 若当前节点区间部分重叠[ L, R ],则递归查询左右子树,合并结果(如求和时相加)。 示例 :查询[ 1,4 ]的和(即3+5+7+9=24): 根节点[ 0,5 ]部分重叠,查询左右子树。 左子树[ 0,2]:区间[ 1,4]与[ 0,2]部分重叠,继续递归。其右子树[ 1,2]完全包含于[ 1,4 ],返回值3+5=8。 右子树[ 3,5]:区间[ 3,4]完全包含于[ 3,5 ],返回值7+9=16。 合并结果:8+16=24。 效率 :每次查询最多遍历树的两条路径,深度为O(log n),因此时间复杂度为O(log n)。 区间更新操作 懒惰传播(Lazy Propagation) :直接更新每个叶子节点效率低(O(n))。引入懒惰标记(Lazy Tag): 更新区间[ L, R]时,若当前节点区间完全包含在[ L, R ]内,则更新该节点值,并设置懒惰标记(表示子节点待更新)。 查询或更新过程中,若当前节点有懒惰标记,先将标记下推给子节点(更新子节点值和标记),再清除当前标记。 更新步骤 : 检查当前节点区间与更新区间的重叠情况(类似查询)。 完全包含时,更新节点值并设置懒惰标记。 部分重叠时,先下推懒惰标记(如果需要),再递归更新左右子树,最后更新当前节点值(基于子节点新值)。 示例 :将区间[ 1,3 ]的每个元素加2: 根节点[ 0,5 ]部分重叠,下推标记(无操作),递归更新左右子树。 左子树[ 0,2]:部分重叠,递归后更新其右子树[ 1,2](完全包含),节点值增加(2+2)* 2=8(区间长度2,每个元素加2),设置懒惰标记。 右子树[ 3,5]:部分重叠,递归更新其左子树[ 3,3 ](完全包含),节点值增加2,设置标记。 回溯更新根节点值。 时间复杂度 :使用懒惰标记后,更新操作也优化为O(log n)。 应用场景与变体 线段树适用于区间求和、区间最值、区间赋值等问题。 变体包括:二维线段树(处理矩阵)、持久化线段树(支持历史版本查询)等。 通过线段树,区间查询和更新操作均被优化到对数时间复杂度,是解决区间类问题的核心数据结构之一。