KDTree
先搬一下 OI-wiki 的定义!
KDTree (K-Demension Tree) 是一种可以高效处理 \(k\) 维信息的数据结构。
在结点数远大于 \(2^k\) 时,KDT的时间效率很好。
在算法竞赛的题目中,一般有 \(k=2\) 。
KDT具有二叉搜索树的形态,每个节点对应 \(k\) 维空间内的一个点。每个子树中的点 \(\Leftrightarrow\) 一个超长方体中的点。
接下来我们进行的分析都是在 \(k=2\) 的前提下进行的(肯定不是今天没做出来),在时间复杂度分析中,认为 \(k\) 是常数。
建树
步骤(初始想法):
- 如果当前超长方体里只有一个点,返回即可
- 如果多于一个点,那我们选择一个维度,并按照这个维度把当前超长方体分为两个超长方体
- 在选择的维度上选一个点,该维值小于该点的归入左子树超长方体,其余的归入右子树超长方体
- 将选择的点作为该子树的根,处理左右子树
我们简单想一下,便可以发现第二步中维度轮流选肯定是较好的,而第三步中为了均分,我们可以选中位数。这样,树高最多为 \(\log n+O(1)\) 。
下面是一个简单的例子。
对这些点进行划分,其中红色是第一轮,绿色是第二轮,选中的分割点用X标出。
设左下角的点坐标为 \((0,0)\),画出得到的kdt如图:
那,我们怎么建树呢?
步骤 \(1\) ,步骤 \(2\) 的选维度和步骤 \(4\) 是好做的,所以瓶颈在于如何选中位数作为分割点。在这一步中,我们需要找到中位数并将其放在序列中间(即正确的位置)。考虑快排的思想,快排每次把一个数与序列中每一个数进行比较,将小的放在左边,大的放在右边,保证该数此时在最终序列的正确的位置,然后递归做左右两边的排序。由于我们只需要把中位数放好,我们只需要做包含中位数的一侧的排序,这样的期望时间复杂度是 \(O(n)\) 的。
其实 algorithm
库中有一个叫 nth_element()
的函数,实现的是同样的功能。要找到 s[l]
和 s[r]
之间的值按照 cmp
排序后在 s[mid]
上的值,只需写 nth_element(s+l,s+mid,s+r+1,cmp)
。
由此我们可以 \(O(n\log n)\) 建出KDT。
操作
查询
记录每个节点子树中每一维坐标最大值,最小值。查询一个超长方体时:
- 节点子树与该超长方体无交,不搜了
- 节点子树对应的超长方体被查询的包含,返回子树节点信息
- 有交,不包含:判断该点是否被查询,更新答案并算左右子树
OIwiki证明了复杂度是 \(O(n^{1-\frac{1}{k}})\) ,但是我没看懂(((((((((
插入/删除
有两种维护方法,感觉有点抽象,没太懂
根号重构
插入的时候,存下插入点, \(B\) 次插入进行一次重构。
删除打个标记。
修改复杂度 \(O(\frac{n\log n}{B})\) ,查询 \(O(B+n^{1-\frac1k})\) ,\(B=O(\sqrt {n\log n})\) ,则两者同阶。
二进制分组
维护若干棵大小为 \(2\) 的自然数次幂的KDT,大小相加为 \(n\) 。
插入时加入一棵大小为 \(1\) 的KDT,然后我们把相同大小的树合并,合并方式是暴力重构,实现时可以只重构一次。该方法加入点的复杂度均摊 \(O(n\log^2n)\) ,查询复杂度不变。
本文作者:stawalr
本文链接:https://www.cnblogs.com/ikusiad/p/18709725
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步