k-D Tree 具有二叉搜索树的形态,二叉搜索树上的每个结点都对应 $k$ 维空间内的一个点。其每个子树中的点都在一个 $k$ 维的超长方体内,这个超长方体内的所有点也都在这个子树中。
-假设我们已经知道了 $k$ 维空间内的 $n$ 个不同的点的坐标,要将其构建成一棵 k-D Tree ,步骤如下:
+假设我们已经知道了 $k$ 维空间内的 $n$ 个不同的点的坐标,要将其构建成一棵 k-D Tree,步骤如下:
-1. 若当前超长方体中只有一个点,返回这个点。
+1. 若当前超长方体中只有一个点,返回这个点。
-2. 选择一个维度,将当前超长方体按照这个维度分成两个超长方体。
+2. 选择一个维度,将当前超长方体按照这个维度分成两个超长方体。
-3. 选择切割点:在选择的维度上选择一个点,这一维度上的值小于这个点的归入一个超长方体(左子树),其余的归入另一个超长方体(右子树)。
+3. 选择切割点:在选择的维度上选择一个点,这一维度上的值小于这个点的归入一个超长方体(左子树),其余的归入另一个超长方体(右子树)。
-4. 将选择的点作为这棵子树的根节点,递归对分出的两个超长方体构建左右子树,维护子树的信息。
+4. 将选择的点作为这棵子树的根节点,递归对分出的两个超长方体构建左右子树,维护子树的信息。
这样的复杂度无法保证。对于 $2,3$ 两步,我们提出两个优化:
-1. 选择的维度要满足其内部点的分布的差异度最大,即每次选择的切割维度是方差最大的维度。
+1. 选择的维度要满足其内部点的分布的差异度最大,即每次选择的切割维度是方差最大的维度。
-2. 每次在维度上选择切割点时选择该维度上的 **中位数** ,这样可以保证每次分成的左右子树大小尽量相等。
+2. 每次在维度上选择切割点时选择该维度上的 **中位数** ,这样可以保证每次分成的左右子树大小尽量相等。
可以发现,使用优化 $2$ 后,构建出的 k-D Tree 的深度最多为 $O(\log_2 n)$ 。
我们来回顾一下快速排序的思想。每次我们选出一个数,将小于该数的置于该数的左边,大于该数的置于该数的右边,保证该数在排好序后正确的位置上,然后递归排序左侧和右侧的值。这样的期望复杂度是 $O(n\log_2 n)$ 的。但是由于 k-D Tree 只要求要中位数在排序后正确的位置上,所以我们只需要递归排序包含中位数的 **一侧** 。可以证明,这样的期望复杂度是 $O(n)$ 的。在 `algorithm` 库中,有一个实现相同功能的函数 `nth_element()` ,要找到 `s[l]` 和 `s[r]` 之间的值按照排序规则 `cmp` 排序后在 `s[mid]` 位置上的值,并保证 `s[mid]` 左边的值小于 `s[mid]` ,右边的值大于 `s[mid]` ,只需写 `nth_element(s+l,s+mid,s+r+1,cmp)` 。
借助这种思想,构建 k-D Tree 时间复杂度是 $O(n\log_2 n)$ 的。
-