Geometric Application of BSTs 平衡搜索树的几何应用
0. 前言
在前面的文章中,我们分析了符号表的许多基本操作,包括:查找、插入、删除等。现在我们新增两个操作:范围查找(range search) 和 范围统计(range count)。这两个操作中数据的SQL查询语句很常见。
1. 一维范围搜索
基本概念
一维查找的几何解释如下:(结合下图)
- key是作为一维线段上的点
- 查找/统计包含在一维区间上的点(一维区间最大就是一维线段)
这就是范围查找和范围统计的应用范围,因此,符号表可以用来实现一维查找。
基本操作
在前面的文章中也分析过,通过无序链表和有序数组来实现符号表存在性能问题,而通过二叉搜索树(BST)来实现才能满足 ~lgN的性能要求。
- 无序链表:插入很快,但查找需要遍历链表
- 有序数组:由于插入需要确定次序导致很慢,但查找可以使用二分法使得性能达到lgN
因此,我们将采用BST来实现一维查找。
一维统计
//统计lo和hi之间的节点个数
public int size(Key lo, Key hi){
//rank返每个节点的有序序列的排位
if (contains(hi)) return rank(hi) - rank(lo) + 1;
else return rank(hi) - rank(lo);
}
性能:查找到lo的路径+查找到hi的路径 ~lgN。
一维查找
一维查找是查找在lo和hi之间的所有所有节点。
- 递归地查找左子树的所有key
- 查找当前节点的的key
- 递归地查找右子树的所有key(保存满足条件的key)
性能:查找到lo的路径+查找到hi的路径+匹配的节点数 ~lgN+R (R就是匹配的节点数)
2. 线段相交
线段相交就是在一个二维平面内,查找所有相交的线段。传统的方法就是两两对比,确定是否相交,但这种方法需要平方级的时间要求。下面介绍一个高性能的方法。
扫描法
扫描法的基本原理:在二维平面内,用一条垂直的线从左到右进行扫描。x坐标作为触发事件,事件如下:
- 对水平的线段,当遇到线段的左端点时,将y坐标插入二叉搜索树中
- 对水平的线段,当遇到线段的右端点时,将y坐标从二叉搜索树中删除
- 对垂直的线段,当遇到端点时,将线段的上下端点作为查找范围,进行二叉搜索树的范围查找,查找的结果就是与该线段相交的其他线段(结合下图)
通过扫描法,就可以将二维的线段相交的问题转换成一维查找的问题。在N条线段存在R个相交点的场景下,时间性能表现为 ~NlgN+R。
3. kd树
分析kd树首先要明白二维正交范围查找(2-d orthogonal range search)的背景和基本概念。
二维正交范围查找
二维正交范围查找的几何解释为:(结合下图)
- 可以作为二维平面空间上的点
- 查找/统计包含在二维矩形内的点(二维矩形在二维平面空间内)
网格实现法
网格实现法是二维正交范围查找的最佳实现方法,网格实现法的描述如下:(结合下图)
- 将二维平面空间分割成 M*M 个网格
- 对每一个网格都创建一个列表,来存在落在该网格的节点
- 使用二维数组来直接索引相关的网格方块
- 插入:根据新节点的坐标索引到网关,再插入到网格对应的列表
- 范围查找:只查找与二维矩形相重叠的网关方块对应的节点列表即可
空间性能:M*M+N(M表示网格,N表示节点)
时间性能:平均每个网格的查找时间为 1+ N/M^2
因此,对M的选取是非常关键的。M太大网格就会小,导致浪费空间;M太小网格就会大,导致太多节点挤在同一个网格。理论上,最佳为M = √N。
kd树
在网格实现法中,存在一个不可忽视的问题——节点聚簇。当节点聚簇的时候,像上述那样均分网格就会导致节点挤在一小部分网格中(最终导致该网格对应的列表过长)。此时,需要一种合理地切分网格的方法,就是接下来要重点分析的kd树法。
kd树法就是递归地将空间分成两部分。与BST不同的是,kd树需要交替地使用x坐标和y坐标作为key。使用x坐标为key时就垂直切分,使用y坐标作为key时就水平切分。
范围查找
- 检查节点是否在矩形内
- 根据矩形的位置,判断往哪个方位继续查找
在下图中,节点1不在矩形内,根据位置需要查找左子节点3,忽略右子树全部节点。接着发现矩形横跨了节点3的分割线,所以节点3的左右节点均要查找。以此类推。
时间性能:平均情况下是R+lgN,最坏情况下是R+√N
最邻近节点
查找与查询节点最近的节点
- 计算节点与待查节点的距离l
- 分别计算左右子节点与待查节点的距离,如果出现小于l的则往该子节点路径走(有可能左右子节点都符合条件),如果大于l的就忽略
时间性能:平均情况下是lgN,最坏情况时N
kd树也可以推广到三维立体空间中,本文不作详述。
4. 区间搜索树interval search trees
基本概念
在一维区间查找中,有一个很重要的操作就是一维区间相交查询:给定一个区间(lo, hi),查找出于该区间相交的其他区间。(结合下图)
所谓的区间查找树,就是树的每一个节点存的是一个区间:(结合下图)
- 使用左端点作为节点的key(故比较对象是左端点)
- 每一个节点要存储以其为根的子树中的最大key值(包括节点本身)
基本操作
插入
以插入(lo, hi)为例
- 左端点lo作为key值,比较key值后确定其位置,然后插入
- 更新每一个节点所保存的最大key值
查找
查找是指查找出与给定节点相交的其他节点,以查找节点(lo, hi)为例
- 如果节点相交,则返回
- 如果左子树为空,往右边走
- 如果左子树最大key值小于lo,往右边走
- 其他情况往左边走
//根节点
Node x = root;
while (x != null){
if (x.interval.intersects(lo, hi)) return x.interval;//存在相交,返回
else if (x.left == null) x = x.right;//如果左子树为空
else if (x.left.max < lo) x = x.right;//如果左子树最大key值小于lo
else x = x.left;
}
return null
这么比较是因为有以下特殊情况:
- 如果往右边走,则表示一定不会在左边相交
- 如果往左边走,仍有可能会在右边相交,或者两边都不相交
为了保证时间性能,可以采用红黑树来实现区间搜索树,这样就可以将时间性能保持在 ~lgN的水平,本文不作详述。
5. 矩形相交
矩形相交是在一个二维平面内,查找所有相交的矩形。传统的方法就是两两对比,确定是否相交。但这种做法需要平方级的时间要求,因此,使用扫描法可以保证时间性能。
在二维平面内使用一条垂直的线段从左到右扫描,矩形的x坐标触发事件,事件包括:(结合下图)
- 当遇到矩形的左边界时,将y坐标区间插入区间搜索树中;在插入之前,先对待插入的坐标区间做区间查找,以确定相交情况。
- 当遇到矩形的由边界时,将y坐标区间从区间搜索树中删除
通过扫描法,就可以将二维的矩形相交的问题转换成区间查找的问题。在N条线段存在R个相交点的场景下,时间性能表现为 ~NlogN+RlogN。
小结