news 2026/7/15 2:00:50

区间树:反向映射的前置数据结构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
区间树:反向映射的前置数据结构

区间树:反向映射的前置数据结构

这篇只讲一个前置数据结构:区间树

先不展开anon_vmaanon_vma_chainvmafolio这些内核结构。真正讲匿名页反向映射时,再把这些结构套进来。

先给一个简短定义:

区间树可以理解成一棵被增强过的有序平衡树。原本平衡树的节点通常保存一个具体的 key;区间树把节点保存的内容换成一个区间[start,end],排序和查找时主要比较区间的start端点。然后,它在每个节点上额外维护一个maxEnd字段,表示这棵子树里所有区间最大的end。当树上的区间插入、删除或变化时,底层平衡树要继续维护平衡,区间树这一层还要同步维护受影响节点的maxEnd

现在只抓住一个核心问题:

给定一个查询点或查询区间, 在很多已经保存好的区间里, 快速找出所有和它重叠的区间。

两个闭区间:

A = [a_start, a_end] B = [b_start, b_end]

相交条件是:

a_start <= b_end && b_start <= a_end

如果查询是一个点q = 100,可以把它看成区间[100,100]。所以点查询和区间查询可以统一起来。

一、最小问题:找所有重叠区间

先有一批区间:

[ 80, 120] [100, 140] [300, 360] [ 96, 111]

现在查询:

Q = [100, 101]

逐个判断:

[ 80, 120] 命中 [100, 140] 命中 [300, 360] 不命中 [ 96, 111] 命中

最直接的实现是数组扫描:

for each interval: if interval.start <= Q.end && Q.start <= interval.end: hit

复杂度是:

O(n)

区间数量少时,这没问题。区间很多、查询频繁、还要插入删除时,就需要索引。

二、先把区间放进普通搜索树

一个自然想法是:按区间起点start建一棵搜索树。

key = interval.start

每个节点先只保存自己的区间和左右孩子:

node ├─ interval = [start, end] ├─ left 所有 start 更小的区间 └─ right 所有 start 更大的区间

把这些区间插进去:

[ 80, 120] [100, 140] [300, 360] [ 96, 111]

一棵可能的树是:

[80,120] \ [100,140] / \ [96,111] [300,360]

这时候树已经形成了。它是一棵按start排序的普通区间搜索树,但还不是完整的 interval tree。

这棵树能帮我们做一部分剪枝。比如查询[100,101]时,节点[300,360]start = 300已经大于查询右端101,它和它右边那些start更大的节点都不可能命中。

但只按start排序还不够。

三、只按 start 排序为什么不够

看一棵按start排序的树:

[100,140] / \ [80,120] [300,360] / [1,1000000]

查询:

Q = [500, 501]

站在根节点[100,140]看,左子树里的区间起点都更小:

[80,120].start = 80 [1,1000000].start = 1

但是左子树不能直接跳过。因为:

[80,120] 80 <= 501 && 500 <= 120 不命中 [1,1000000] 1 <= 501 && 500 <= 1000000 命中

也就是说,只知道左子树的start都更小,不能说明左子树里所有区间都已经结束了。

普通按start排序的树缺少一个信息:

这棵子树里,最大的 end 是多少?

如果不知道这个信息,就无法判断一整棵子树能不能被跳过。最坏情况下,还是会退化成大量扫描。

到这里,再把层次关系分清楚就比较自然了:

底层树: 负责维护有序结构,比如红黑树、AVL 树、B-tree。 区间树这一层: 把每个树节点里的数据换成一个区间 [start,end]; 用 start 作为主要排序 key; start 相同时再用 end 或节点身份做 tie-break; 再额外维护“这棵子树里最大的 end”这个摘要字段; 查询时用这个摘要字段跳过不可能命中的整棵子树。

所以 interval tree 不是说发明了一套新的平衡规则。它更像是:

ordered balanced tree + interval stored in each node + subtree summary field + overlap query algorithm

下一节就给这个摘要字段起一个名字:maxEnd

四、加增强字段:maxEnd

区间树的关键就是在普通搜索树节点上加一个增强字段:

maxEnd = 以当前节点为根的整棵子树里,所有区间最大的 end

节点变成:

node ├─ interval = [start, end] ├─ left ├─ right └─ maxEnd

要注意:

x.maxEnd 表示以 x 为根的整棵子树里的最大 end。 node.left.maxEnd 表示 node 的左子树里的最大 end。 node.right.maxEnd 表示 node 的右子树里的最大 end。

maxEnd的作用是判断某棵子树有没有必要进去查。

比如查询:

Q = [100, 101]

有这样一棵树:

[100,140], maxEnd=360 / \ [40,60], maxEnd=90 [300,360], maxEnd=360 / \ [10,20], maxEnd=20 [70,90], maxEnd=90

站在根节点[100,140],看左孩子:

left.maxEnd = 90 Q.start = 100

因为:

90 < 100

所以左子树里所有区间都在Q.start之前结束,不可能和[100,101]相交。整棵左子树可以跳过:

[40,60] [10,20] [70,90]

当前节点[100,140]和查询区间相交,命中。

右子树[300,360]start > Q.end,不命中。

这就是maxEnd的价值:它不是判断某个单独节点,而是判断一整棵子树有没有可能包含答案。

五、查询逻辑

查询一棵区间树时,可以按下面的逻辑走:

query(node, Q): if node == null: return if node.left != null && node.left.maxEnd >= Q.start: query(node.left, Q) if node.interval 和 Q 相交: 记录 node.interval if node.interval.start <= Q.end: query(node.right, Q)

左子树判断:

node.left.maxEnd >= Q.start

含义是:

左子树里至少可能有一个区间的 end 到达 Q.start; 所以左子树可能有命中,需要进去查。

如果:

node.left.maxEnd < Q.start

说明:

左子树里所有区间都在 Q.start 之前结束; 整棵左子树都不可能命中。

右子树判断:

node.interval.start <= Q.end

因为右子树里的start都大于等于当前节点的start。如果当前节点的start已经大于Q.end,右子树只会更靠右,也不可能命中。

如果底层树保持平衡,查找所有重叠区间可以理解成:

O(log n + k) n = 树里区间数量 k = 本次命中的区间数量

这里的k不能省。如果确实有很多区间都和查询区间重叠,算法至少要把这些结果返回出来。

六、动态更新:插入、删除和区间变化

如果区间集合永远不变,那建好树之后只查就行。

但真实系统里的区间经常变化:

新增一个区间; 删除一个区间; 一个旧区间缩小、扩大或者被拆成多个区间。

所以区间树还要解决第二个问题:

树变了以后,maxEnd 怎么快速维护?

插入一个新区间时,过程和普通搜索树插入很像:

1. 按 start 找到它应该插入的位置。 2. 插入新节点。 3. 从插入路径往上回溯,重新计算沿途节点的 maxEnd。

maxEnd的重新计算只依赖三个值:

node.maxEnd = max(node.interval.end, node.left.maxEnd, node.right.maxEnd)

所以不需要重扫整棵树,只要更新被插入路径影响到的祖先节点。

删除一个区间也是类似的:

1. 按 start/end 找到要删除的节点。 2. 按普通搜索树或红黑树删除节点。 3. 从结构发生变化的位置往上,重新计算 maxEnd。

如果底层树是红黑树或 AVL 树,插入和删除过程中还可能发生旋转。旋转属于底层平衡树的维护动作;interval tree 这一层要做的是:旋转完成后,把被旋转影响到的几个节点重新计算maxEnd,再继续向上维护即可。

这篇 demo 选择 AVL 树做底层平衡树,所以代码里会看到一个height字段。它的作用不是查重叠区间,而是让 AVL 树判断自己有没有失衡:

balance = height(left) - height(right) balance > 1 左子树太高,需要旋转 balance < -1 右子树太高,需要旋转

如果不维护height,每次判断一个节点是否失衡都要重新计算左右子树高度,插入删除会多出很多重复扫描。维护height后,判断平衡因子就是O(1)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/15 2:00:14

基于 ArkUI C API 优化长列表,解决滑动卡顿问题

长列表滑动卡顿是鸿蒙应用开发中最常见的性能痛点之一&#xff1a;列表项越复杂、数据量越大&#xff0c;滑动时越容易出现掉帧、卡顿、加载白块。常规的 LazyForEach 优化能覆盖大部分场景&#xff0c;但面对上千条复杂列表项、多图混合布局、高频刷新的场景&#xff0c;JS 线…

作者头像 李华
网站建设 2026/7/15 1:59:50

电商多平台多店铺数据怎么统一管理?AI Agent方案 | 2026企业级端到端智能自动化选型指南

随着2026年全球电商进入深度全渠道运营阶段&#xff0c;品牌商家在淘宝、京东、拼多多、抖音以及跨境平台的布局已成为标配。然而&#xff0c;“多平台、多店铺”背后的数据孤岛问题日益凸显&#xff1a;不同平台的API开放程度不一、订单核销逻辑迥异、库存实时动态难以同步。传…

作者头像 李华
网站建设 2026/7/15 1:59:32

开源NCM文件解密工具深度解析:3步实现高效音频格式转换

开源NCM文件解密工具深度解析&#xff1a;3步实现高效音频格式转换 【免费下载链接】ncmdump ncmdump - 网易云音乐NCM转换 项目地址: https://gitcode.com/gh_mirrors/ncmdu/ncmdump ncmdump是一款专为网易云音乐NCM加密文件设计的开源Java解密工具&#xff0c;能够将受…

作者头像 李华
网站建设 2026/7/15 1:58:27

DonkeyCar硬件清单详解:从选型逻辑到避坑实战

1. 项目概述&#xff1a;为什么一张硬件清单比代码更决定DonkeyCar成败 DonkeyCar不是玩具车&#xff0c;也不是教育套件的简单拼装——它是一套面向真实自动驾驶学习场景的开源软硬协同平台。我带过三届高校智能车社团、帮七家中小学创客空间落地过课程&#xff0c;最常被问的…

作者头像 李华
网站建设 2026/7/15 1:57:37

PTA 选择结构实战:从BMI指数到健康评估系统

1. 从BMI计算到健康评估系统的升级之路刚学完C语言选择结构那会儿&#xff0c;我和所有新手一样&#xff0c;对着PTA平台上的BMI计算题反复调试。那段代码现在看简直简单得可爱——输入身高体重&#xff0c;用if-else判断胖瘦等级。但当我真正去医院体检时才发现&#xff0c;现…

作者头像 李华