(这本是 P4169 的一篇无法提交的题解)
而 KD - Tree 本身在竞赛中的应用就比较少,又几乎不作为正解出现,所以这里只拿一道例题来举例讲解
题意:
给定一个二维点集 S ,每次有两种操作:
操作 1 : 往点集 S 中加入一个点
操作 2 : 给定一个点 ( x , y ) (x, y) (x,y) ,询问该点到点集 S 中每个点曼哈顿距离的最小值
两点 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1, y_1), (x_2, y_2) (x1,y1),(x2,y2) 之间的曼哈顿距离定义为:
∣ x 1 − x 2 ∣ − ∣ y 1 − y 2 ∣ |x_1 - x_2| - |y_1 - y_2| ∣x1−x2∣−∣y1−y2∣
题解:
这题正解是一个 O ( ( n + m ) log 2 2 ( n + m ) ) O((n + m) \log_2^2~ (n + m)) O((n+m)log22 (n+m)) 复杂度的做法,需要用到 CDQ 分治
不过既然是 KD - Tree 的学笔,我们就用 KD - Tree 来做这道题
首先来介绍一下 KD - Tree :
1. 定义 & 前置知识
KD - Tree ,原名 K - Dimension Tree ,是一种可以高效处理 k 维空间信息的数据结构
(然而 OI 中很少考到高维的 DS 题目)
(微信步数显然不是 DS 题目吧)
所以 KD - Tree 在 OI 中的应用大多数是二维(例如这道题),少数会有三维
而且 KD - Tree 据说可以配合其它的一些算法 比如乱搞 来获得较高分数
在学习 KD - Tree 之前,你需要了解如下东西:
-
平衡树(这里特指替罪羊树)
-
STL 中的 nth_element 函数
没了,就这些,所以 KD - Tree 的入门是比较简单的,学会了上手就会很快
2. 建树过程
类似平衡树,KD - Tree 是一种 BST
但是和 BST 不同,在 k 维空间中,KD - Tree 每个结点参与比较的键值是有 k 个的(即每一维的坐标)
不过 KD - Tree 在检索的时候仍然能保证一定的次序
我们只需要在每一个深度都定义一个参与比较的维度,那么这样在检索时,每次究竟是往左子树走还是往右子树走,只要看这个点在当前检索到的深度的“比较维度”上的键值是多少即可
建树的算法已经给你了,关键是如何选择恰当的方式优化,避免像 BST 一样退化成链
那么有两个问题:
-
每个深度怎样选维度?
-
每个深度选好维度后怎样选点?
对于第一个问题,我们可以选择 1 ~ k 维轮流来
但是有一种更优的选择方式:选择方差最大的一维,具体证明不明,而且实现据说极为复杂
(识相点就选第一种吧,反正很少有出题人会想到要卡你)
对于第二个问题,显然选这个维度的中位数即可,这样左右两边的结点数是相等的
以上两种方式基本可以保证建树时间复杂度为 O ( n log 2 n ) O(n\log_2n) O(nlog2n),深度为 O ( log 2 n ) O(\log_2n) O(log2n)
具体实现时,中位数可以使用 STL 中的 nth_element 函数,不会影响复杂度,用法请上网查找
这样讲会很抽象,给一张例图:
首先我们以横坐标为关键字,找到这一维的中位数
排序后即为:
A , H , C , B , E , D , G , F A,H,C,B,E,D,G,F A,H,