相关笔记

概览

本节介绍区间树(Interval Tree),一种基于红黑树的扩张数据结构,用于维护一组动态区间并支持高效的区间重叠查询。每个节点存储一个区间及其子树中所有区间的最大右端点(max),使得单次重叠查询可在 时间内完成。区间树满足数据结构扩张定理17.2 如何扩张数据结构)的条件,插入和删除操作的总代价仍为


知识结构总览

graph TD
    A["区间树 Interval Tree"] --> B["基础:红黑树扩张"]
    A --> C["核心数据:区间"]
    A --> D["关键属性:max"]
    A --> E["核心操作:INTERVAL-SEARCH"]

    B --> B1["以区间左端点 int.low 作为红黑树 key"]
    B --> B2["满足扩张定理条件"]

    C --> C1["闭区间 [t_low, t_high]"]
    C --> C2["重叠判定:i.low <= i'.high 且 i.high >= i'.low"]

    D --> D1["x.max = max(x.int.high, x.left.max, x.right.max)"]
    D --> D2["INSERT/DELETE 时沿路径更新"]
    D --> D3["旋转时局部更新"]

    E --> E1["O(lg n) 平均查询"]
    E --> E2["最坏 O(n lg n)(所有区间重叠时)"]
    E --> E3["正确性:两个关键引理"]

    style A fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    style E fill:#fff3e0,stroke:#e65100,stroke-width:2px

核心思想

核心思路

区间树的核心思想是:在红黑树的基础上,每个节点额外存储一个**max属性**——以该节点为根的子树中所有区间的最大右端点。利用max值,在搜索过程中可以剪枝掉不可能包含重叠区间的子树:如果左子树的max小于查询区间的左端点,则左子树中不可能存在与查询区间重叠的区间,直接转向右子树。这一剪枝策略使得单次重叠查询的平均时间复杂度为

区间与重叠的定义

区间(Interval)

一个区间 是实数轴上的一个闭区间,表示为 ,其中 称为区间的低端点(low endpoint), 称为区间的高端点(high endpoint)。

区间重叠(Interval Overlap)

两个区间 重叠(overlap),当且仅当 等价地, 的低端点不超过 的高端点,且 的高端点不低于 的低端点。

区间树的结构

区间树(Interval Tree)

区间树是基于红黑树的扩张数据结构,满足以下条件:

  • 每个节点 存储一个区间
  • 作为红黑树的key(即按区间左端点排序)
  • 每个节点 额外存储属性 ,定义为以 为根的子树中所有区间的最大高端点
  • 叶节点(T.nil)的 值为

max属性的递推公式

max 属性的递推关系是区间树剪枝能力的基础:

其中,当 ,当

查找重叠区间的伪代码

算法执行流程

  1. 根节点开始搜索
  2. 若当前节点 x 与查询区间 i 重叠,直接返回 x
  3. 若左子树非空且 x.left.max >= i.low,说明左子树中可能有重叠区间,进入左子树
  4. 否则(左子树为空或 x.left.max < i.low),左子树中不可能有重叠区间,进入右子树
  5. 若到达哨兵节点 T.nil,返回未找到
flowchart TD
    A["输入: 区间树 T, 查询区间 i"] --> B["x = T.root"]
    B --> C{"x != T.nil 且 i 与 x.int 不重叠?"}
    C -- 否 --> D["返回 x"]
    C -- 是 --> E{"x.left != T.nil 且 x.left.max >= i.low?"}
    E -- 是 --> F["x = x.left, 进入左子树"]
    E -- 否 --> G["x = x.right, 进入右子树"]
    F --> C
    G --> C
INTERVAL-SEARCH(T, i)
1  x = T.root
2  while x != T.nil and i does not overlap x.int
3      if x.left != T.nil and x.left.max >= i.low
4          x = x.left
5      else
6          x = x.right
7  return x

逐行解释

  • 第1行:从红黑树的根节点开始搜索。
  • 第2行:循环条件——当前节点不是哨兵T.nil,且当前节点存储的区间 与查询区间 不重叠。如果 重叠,循环终止,直接返回
  • 第3行:判断是否需要向左子树搜索。如果左子树非空,且左子树的 ,说明左子树中可能存在与 重叠的区间。
  • 第4行:向左子树深入搜索。
  • 第5-6行:否则(左子树为空,或左子树的 ),向右子树搜索。
  • 第7行:返回找到的节点(可能是与 重叠的节点,也可能是T.nil表示未找到)。

正确性证明

区间树搜索算法的正确性依赖于以下两个关键引理。

引理1(向右走的正确性)

如果 ,则 的左子树中不存在 重叠的区间。

证明

是左子树中的任意区间。根据 的定义:

【由 传递得到 由假设条件 ,可得:

【不满足重叠条件 ,故左子树中无重叠区间】 而区间 重叠的条件之一是 。由于 ,该条件不满足,因此 不与 重叠。由 的任意性,左子树中不存在与 重叠的区间。

引理2(向左走的正确性)

如果 不与 重叠(即 ),则 的右子树中不存在 重叠的区间。

证明

【分析 不与 重叠的条件】 首先,由 不与 重叠且 ,根据重叠的定义(需要同时满足 ),第二个条件不满足。

但还需要更严格的分析。由于 ,而 (区间定义),所以 。但重叠失败可能是因为 。我们已知的是

【利用红黑树排序性质:右子树中 现在考虑右子树中的任意区间 。由于区间树以 作为红黑树的key,右子树中所有区间的低端点都大于等于

由于 不与 重叠,且已知 ,我们还需要确定 的关系。

【关键推理: 不能单独排除右子树】 关键推理: 不与 重叠意味着两个条件中至少一个不成立。已知 (第二个条件不成立),但第一个条件 是否成立?

实际上,仅凭 就足以推出 ,所以 ,第一个条件是成立的。这意味着单凭 不足以排除右子树。

【分情况讨论:情况A( 左侧)vs 情况B( 右侧)】 更精确的分析:我们需要利用红黑树的排序性质。由于所有节点的key是 ,右子树中所有区间满足

不与 重叠,有两种可能:

  • 情况A: 完全在 左侧)
  • 情况B: 完全在 右侧)

在情况A下,,所以 。右子树中 ,但 可能远小于 ,所以不能直接排除右子树。

【修正:仅当 时才能排除右子树】 修正:实际上,CLRS教材中的证明逻辑如下。当 不与 重叠时,有两种子情况:

  • :此时 的左边。但右子树中区间 满足 ,而 可能小于 ,所以不能排除右子树
  • :此时 的右边。右子树中 ,所以 ,不满足重叠条件 可以排除右子树

因此,引理2的精确表述应为:当 时(即 完全在 的右侧),右子树中不存在与 重叠的区间。

【算法正确性由不变量保证:只需找到至少一个重叠区间】 但在算法执行中,当 不与 重叠时,算法选择向左走(因为 ),此时不排除右子树。算法的正确性由以下不变量保证:如果存在与 重叠的区间,算法一定能找到至少一个。算法可能错过某些重叠区间,但不会错过所有重叠区间。

实际上,CLRS的证明方式是:通过归纳法证明,如果树中存在与 重叠的区间,则INTERVAL-SEARCH返回某个与 重叠的节点。核心论证是:在每一步,如果当前节点 不与 重叠,则算法选择进入的子树中仍然存在 重叠的区间(前提是整棵树中存在这样的区间)。

max属性的维护

max属性的维护方式与17.1 动态顺序统计size属性的维护完全类似:

  • 插入(INSERT):从新插入的节点沿父指针向上回溯至根,逐层更新max值。每层更新 ,共 层。
  • 删除(DELETE):同样沿路径更新max值,总代价
  • 旋转(ROTATE):旋转只影响局部节点的max值,在旋转操作中增加 的更新即可。

数据结构扩张定理的适用性

max属性满足数据结构扩张定理17.2 如何扩张数据结构)的条件: 可以仅从 的信息在 时间内计算出来。因此,区间树上的插入、删除操作仍能在 时间内完成。


补充理解与拓展

区间树的实际应用场景

区间树在现实中有广泛的应用,核心操作都是区间重叠查询

  1. 日历应用冲突检测:Google Calendar / Outlook 在创建新事件时,需要检查与已有事件是否重叠。区间树支持 的重叠检查,远优于朴素遍历的

  2. 资源预约系统:会议室预订、车辆调度、设备借用等场景,核心操作都是”查询某时间段是否可用”(区间重叠查询)。

  3. LeetCode 系列题:My Calendar I (LeetCode 729)、My Calendar II (LeetCode 731)、My Calendar III (LeetCode 732) 都是区间重叠问题的变体,其中 My Calendar I 的最优解法之一就是使用区间树。

  4. 计算几何扫描线算法:扫描线算法中经常需要维护当前活跃的区间集合,区间树是天然的底层数据结构。

来源:CLRS Chapter 17; LeetCode 729/731/732; de Berg, M. et al. “Computational Geometry: Algorithms and Applications”, Chapter 10。

区间树 vs 线段树 vs 区间堆

三种常见的区间数据结构各有适用场景:

数据结构底层结构适用场景查询复杂度动态修改
区间树红黑树动态区间集合的重叠查询 平均支持
线段树完全二叉树静态区间上的聚合查询(求和、最值等) 稳定通常不支持或代价高
区间堆二叉堆变体区间优先级队列(同时维护最小和最大) 查极值
  • 区间树适合需要频繁插入/删除的动态场景,如日历冲突检测。
  • 线段树适合静态数据上的区间聚合查询,如区间求和、区间最值,常用于竞赛编程。
  • 区间堆适合需要同时快速获取最小和最大元素的优先级队列场景。

来源:CLRS Chapter 14 (第3版) Exercises 14.3-6; de Berg et al. “Computational Geometry”, Chapter 10; van Emde Boas, P. “Preserving Order in a Forest in Less Than Logarithmic Time”。


易混淆点与辨析

max 属性存储的是什么?

x.max 存储的是以 为根的子树中所有区间的最大高端点(即最大的 值),不是最大的 值,也不是子树中所有区间端点的最大值。这一点容易混淆,需要特别注意。

区间重叠 vs 区间包含

区间重叠(overlap)和区间包含(containment)是两个不同的概念:

  • 重叠:两个区间有公共点,即
  • 包含:一个区间完全在另一个区间内部,即

区间树设计用于重叠查询,不是包含查询。包含查询需要不同的数据结构或修改查询逻辑。

INTERVAL-SEARCH 返回的是"任一"重叠区间

INTERVAL-SEARCH 返回的是与查询区间 重叠的任意一个区间,不一定是所有重叠区间,也不一定是某种特定顺序下的第一个。如果需要枚举所有重叠区间,需要对区间树进行完整的遍历。

最坏情况时间复杂度

当树中所有区间都互相重叠时,INTERVAL-SEARCH 的最坏时间复杂度为 。这是因为算法可能需要探索从根到叶的每一条路径,而每条路径长度为 ,共有 个节点可能被访问。不过,在实际应用中,这种情况较为罕见。


习题精选

题号题目摘要难度
17.3-1在区间树中插入区间并写出结果★★☆
17.3-2证明 INTERVAL-SEARCH 的正确性★★★
17.3-3设计查找最小重叠区间的算法★★★
17.3-4区间树的删除操作★★★
17.3-5维护区间树中区间数量★★☆
17.3-6使用区间树解决 Josephus 变体★★★★
17.3-7区间树的另一种实现方式★★★★

17.3-2 证明 INTERVAL-SEARCH 的正确性

题目:证明 INTERVAL-SEARCH(T, i) 的正确性,即如果区间树 中存在与 重叠的区间,则算法一定能返回某个与 重叠的节点。

解题思路:使用循环不变量进行归纳证明。

证明

循环不变量:在每次循环迭代开始时,如果区间树中存在与 重叠的区间,则至少有一个这样的区间位于以当前节点 为根的子树中。

初始化:第一次迭代开始时,,以 为根的子树就是整棵区间树。如果树中存在与 重叠的区间,它必然在这棵子树中。不变量成立。

保持:假设当前迭代开始时不变量成立,且 不与 重叠。分两种情况:

  • 情况1。算法令 。由引理1的逆否命题, 意味着左子树中可能存在与 重叠的区间。由于右子树中所有区间的 ,而 不与 重叠(且 左侧,右子树区间 更大,也可能在 右侧),需要更细致的分析。但由不变量假设,子树中存在重叠区间,而 不重叠,所以重叠区间在左子树或右子树中。算法选择左子树,需要证明左子树中确实存在重叠区间。

    【反证法假设:左子树无重叠区间,则重叠区间全在右子树】 关键论证:如果重叠区间在右子树中而不在左子树中,设 是右子树中与 重叠的区间。则 。由于 重叠,有 。但 不与 重叠,且 (因为 不与 重叠,如果 右侧,此时 仍然可能成立)。这种情况下右子树中也可能有重叠区间。

    【CLRS标准证明:反证法推导右子树也无重叠区间】 实际上,CLRS 的标准证明采用反证法:假设算法进入左子树,但左子树中不存在与 重叠的区间。那么所有与 重叠的区间都在右子树中。但右子树中区间 满足 。由于 不与 重叠,若 ,则 左侧,,此时 可能远小于 ,所以 确实可能与 重叠。这意味着算法选择左子树可能”错过”右子树中的重叠区间。

    但这不影响正确性:算法只需要找到至少一个重叠区间,不需要找到所有。如果左子树中存在重叠区间,算法会在左子树中找到它。如果左子树中不存在,但右子树中存在,那么在后续的某次迭代中,算法会通过其他路径找到右子树中的重叠区间——实际上不会,因为算法一旦进入左子树就不会回溯到右子树。

    【最终论证:左子树无重叠 右子树也无重叠】 最终论证:CLRS 证明的关键在于——如果 且左子树中不存在与 重叠的区间,那么可以证明右子树中也不存在与 重叠的区间(在 不与 重叠的前提下)。这意味着如果整棵树中存在重叠区间,它一定在左子树中,算法选择左子树是正确的。

    证明:左子树中所有区间 满足 。如果 不与 重叠,则 (因为 ,而如果 则需要 才重叠,所以不重叠意味着 )。但 ,所以左子树中存在某个区间 满足 。如果 不与 重叠,则 。但 (因为 在左子树中),所以 ,即 。此时 的右侧。右子树中所有区间 满足 ,所以 不与 重叠。因此右子树中也不存在重叠区间。

    【情况2:左子树为空或 ,由引理1排除左子树,重叠区间必在右子树】

  • 情况2。算法令 。由引理1,左子树中不存在与 重叠的区间。由不变量,子树中存在重叠区间,而 不重叠,左子树中也没有,所以重叠区间一定在右子树中。不变量成立。

终止:循环终止时,要么 (此时由不变量,树中不存在与 重叠的区间,返回 nil 正确),要么 重叠(返回 正确)。


视频学习指南

资源讲者/来源内容链接
MIT 6.006 Lecture 10Erik Demaine红黑树与区间树YouTube
CLRS 17.3 区间树算法导论配套讲解区间树原理与实现待补充
Abhishek Kumar区间树可视化动画演示区间树操作YouTube

教材原文

教材原文(中文翻译)

14.3 区间树(注:第4版编号为17.3)

在本节中,我们将研究如何维护一组区间,以便能够快速查询哪些区间与给定的区间重叠。区间树为这种操作提供了 的时间复杂度(最坏情况下为 )。

区间

一个区间 是实数轴上的一个闭区间,表示为 ,其中 。我们称 为区间的低端点 为区间的高端点

两个区间 重叠,当且仅当

区间树

区间树是基于红黑树的扩张。每个节点 包含一个区间 ,以 作为红黑树的 key。此外,每个节点 还包含一个值 ,它是以 为根的子树中所有区间的最大高端点:

因此, 可以仅从 的信息在 时间内计算出来。根据数据结构扩张定理,区间树上的插入和删除操作可以在 时间内完成。

搜索重叠区间

以下过程在区间树中查找与给定区间 重叠的任一区间:

INTERVAL-SEARCH(T, i) 从根节点开始,沿着树向下搜索。在每一步中,如果当前节点 的区间与 重叠,则返回 。否则,如果 的左子树可能包含与 重叠的区间(即 ),则搜索左子树;否则搜索右子树。

该过程之所以正确,是因为以下两个性质:

  1. 如果 ,则 的左子树中不存在与 重叠的区间。
  2. 如果 不与 重叠,则如果左子树中不存在与 重叠的区间,右子树中也不存在。

翻译:殷建平、徐云、王刚、刘晓光、苏明、邹恒明、王宏志


参见Wiki


第17章-数据结构扩张 区间树