K-D Tree 笔记

知周所众,若是 在线 的话,一维信息可用多数数据结构维护,二维数据结构可以用某套某维护,K-D Tree 是一个可以维护 K 维信息的数据结构。

1. K-D Tree 的建立

在一个 k 维空间,存在一些点,K-D Tree 可以找出一个 k 维超立方体内的 权值和

1.1 K-D Tree 的思想

K-D Tree 本质是一个 BST,其为一个二叉树,每个节点储存的是一个包含其所有子节点k 维超立方体。

struct dat{
	int ls,rs,val;//子树
	int mi[k],mx[k];//k 维超立方体的边界
};

我们考虑如何建出一个 K-D Tree,主要思想是分割,我们选择一个维度,让该区间依照其分割成两块,显然我们取 中位数最佳,找中位数可以用 STL 中的 nth_element,可以 O(n) 查找,这样每次分成两块,复杂度是 O(knlogn) 的。

类似这样。
image
(图源为 OI-wiki)

1.2 维度的查找

如何选择一个维度呢?有一个暴力的方法是一个接着一个维度选即可,即轮换划分
还有一个方法是分别将每个维度求个方差,我们选择方差最大的维度。

这里以 2-D Tree 为例。

void build(int &p,int l,int r){
	if(l > r)return p = 0,void();
	w[0] = w[1] = av[0] = av[1] = 0;
	for(int i = l;i <= r;i++)
		for(int j = 0;j < 2;j++)av[j] += a[i].x[j];
	for(int j = 0;j < 2;j++)av[j] = av[j] / (r-l+1);
	for(int i = l;i <= r;i++)
		for(int j = 0;j < 2;j++)w[j] += (a[i].x[j] - av[j]) * (a[i].x[j] - av[j]);//求方差
	int mid = l + r >> 1,op = w[1] > w[0] ? 1 : 0;
	nth_element(a+l,a+mid,a+r+1,[&](made a,made b){return a.x[op] < b.x[op];});//找中位数
	p = mid;
	build(ls(p),l,mid-1),build(rs(p),mid+1,r);
	pushup(p); 
}

2. K-D Tree 的查询

我们类似线段树,根据每个节点所存的 k 维超立方体,我们与询问求交,若包含则返回,若有交则递归,否则返回

int cal(int p,int l,int r,int L,int R){
	if(!p || s[p].mi[0] > r || s[p].mx[0] < l || s[p].mi[1] > R || s[p].mx[1] < L)return 0;//无交
	if(l <= s[p].mi[0] && s[p].mx[0] <= r && L <= s[p].mi[1] && s[p].mx[1] <= R)return s[p].val;//包含
	int res = 0;
	if(l <= a[p].x[0] && a[p].x[0] <= r && L <= a[p].x[1] && a[p].x[1] <= R)res = a[p].z;
	return res + cal(ls(p),l,r,L,R) + cal(rs(p),l,r,L,R);//递归
}

复杂度分析

比较难理解。
考虑 2-D Tree,查询矩阵 R 时,我们将每个节点分为三种:

  • R 无交。
  • R 包含。
  • 部分被 R 包含。

我们只要注意第三类即可。
注意到部分被包含的节点,一定存在一条 R 的边穿过,这样我们只需计算 R 的每条边穿过的矩阵数即可,即任意一条线段最多能经过多少个点对应的矩阵

对于每个节点 u,其孙子将其分成了四个矩阵,则一条线段最多经过两个区域,即第三类点最多进入其孙子节点。

则有递归式:T(n)=2T(n4)+O(1)

根据主定理得 T(n)=O(n)

将递归式推广到 k 维得 T(n)=2k1T(n2k)+O(1),根据主定理得 T(n)=O(n11k)

3. K-D Tree 的插入

K-D Tree 虽然是一个 BST,但由于其结构不能旋转,所以直接插入的复杂度无法保证。

有一种类似 替罪羊树 的维护方法,但是复杂度貌似不对,建议不学。

有两种方法。

3.1 根号重构

在插入时存下来,设阈值为 B,我们每插入 B 次进行一次重构,查询时需要额外枚举未重构的点,总复杂度 O(n2lognB+nB+n21k),当 B=nlogn 取到 O(nnlogn)

2.3 二进制分组

这个不太懂..

4. 例题

I P4148 简单题

经典的二维矩阵问题,但是在线不能 CDQ,内存不能 树套树,这就是 K-D Tree唯一用途了。

有插入,根号重构一下。

复杂度 (nnlogn),时限 8s,最大点跑了 2s,也不慢。

代码

II P5471 [NOI2019] 弹跳

非常经典且厉害,首先这是一个二维平面,每个弹跳装置可以从一个点花费 ti 的时间到一个矩阵区间,显然可以 K-D Tree优化建图,然后跑一遍 dij,时空复杂度都是 O(nn),空间会爆,得分 52 pts,为什么这么少,我的实现可能有点问题

52pts 代码

然后我们考虑不建出边,直接在 dij 上跑,我们考虑每个弹跳装置,当前点为 u,则若使用该装置,则最短花费一定为 du+z,这样其实就是矩阵取 min,每个点存入其子节点的边界,在松弛操作中直接维护即可,空间复杂度 O(n)

代码

*III P4848 崂山白花蛇草水

口胡的做法,比较裸。

求第 k 大,可以 权值线段树K-D Tree,查询时字 线段树上二分 即可,因为有动态修改,不同方法复杂度不太一样。

  • 类似 替罪羊树 修改,正如在文章内所说,其复杂度是不确定的,但是不知道为什么都这样写。

  • 根号重构,貌似是 O(nnlognlogV+qnlogV)

  • 二进制分组,可能是 O(nlog2nlogV+qnlogV)

非常可能有错误,发现请私信作者 : )。

总结:除非强制在线,没人写 K-D Tree : (

参考文章:
数据结构专题-学习笔记:K-D Tree - Plozia
K-D Tree - OI wiki
K-D Tree,处理高维数据的利器 - EnofTaiPeople

posted @   oXUo  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
网站统计
点击右上角即可分享
微信分享提示