【KD-Tree】基于k-d树的KNN算法实现

文章目录

    • 一、什么是KD-Tree?
    • 二、k-d树的结构
    • 三、k-d树的创建
    • 四、k-d树的应用
    • 五、KD-Tree的优缺点
  • 例题
    • JZPFAR

一、什么是KD-Tree?

KD-Tree,又称(k-dimensional tree),是一种基于二叉树的数据结构。它可以用来高效地处理多维空间搜索问题,例如 最近邻搜索(nearest neighbor search)范围搜索(range search) 等。


二、k-d树的结构

KD-Tree 是每个节点都为 k k k 维点的二叉树。所有非叶子节点可以视作用一个超平面把空间分割成两个半空间。节点左边的子树代表在超平面左边的点,节点右边的子树代表在超平面右边的点。

选择超平面的方法如下:每个节点都与 k k k 维中垂直于超平面的那一维有关。因此,如果选择按照 x x x 轴划分,所有 x x x 值小于指定值的节点都会出现在左子树,所有 x x x 值大于指定值的节点都会出现在右子树。这样,超平面可以用该 x x x 值来确定,其法线为 x x x 轴的单位向量。


三、k-d树的创建

有很多种方法可以选择轴垂直分割面( axis-aligned splitting planes ),所以有很多种创建 KD-Tree 的方法。

最典型的方法如下:

  • 随着树的深度轮流选择轴当作分割面。(例如:在三维空间中根节点是 x 轴垂直分割面,其子节点皆为 y 轴垂直分割面,其孙节点皆为 z 轴垂直分割面,其曾孙节点则皆为 x 轴垂直分割面,依此类推。)

  • 点由垂直分割面之轴座标的中位数区分并放入子树

这个方法产生一个平衡的k-d树。每个叶节点的高度都十分接近。然而,平衡的树不一定对每个应用都是最佳的。


四、k-d树的应用

  • 最邻近搜索(Nearest Neighbor Search)

最邻近搜索是一种简单的分类或回归方法,它的基本思想是找到与待分类样本最接近的已知类别的样本,并将待分类样本归为该类别。最邻近搜索可以应用于各种不同的数据类型,例如文本、图像、音频等。

最邻近搜索用来找出在树中与输入点最接近的点。

k-d树最邻近搜索的过程如下:

  1. 从根节点开始,递归的往下移。往左还是往右的决定方法与插入元素的方法一样(如果输入点在分区面的左边则进入左子节点,在右边则进入右子节点)。

  2. 一旦移动到叶节点,将该节点当作"当前最佳点"。

  3. 解开递归,并对每个经过的节点运行下列步骤:

    (1)如果当前所在点比当前最佳点更靠近输入点,则将其变为当前最佳点。

    (2)检查另一边子树有没有更近的点,如果有则从该节点往下找。

  4. 当根节点搜索完毕后完成最邻近搜索。


  • 范围查询(range searches)

范围查询就是给定查询点和查询距离的阈值,从数据集中找出所有与查询点距离小于阈值的数据。

k-d 树范围查询的过程如下:

  1. 从根节点开始,递归地往下移,直到叶节点。
  2. 如果当前节点所代表的区域与查询范围没有交集,则返回。
  3. 如果当前节点所代表的区域完全包含在查询范围内,则将该节点下所有的数据点全部加入结果集中。
  4. 如果当前节点所代表的区域与查询范围有交集,则分别对左右子树递归执行上述步骤。

  • K近邻搜索(K-Nearest Neighbor Search)

K近邻查询是一种基于距离度量的搜索算法,它可以查找与给定点最近的 k 个数据点。当 k=1 时,就是最近邻查询(nearest neighbor searches)


五、KD-Tree的优缺点

  • 优点
  1. KD-Tree可以高效地处理多维空间搜索问题,例如最近邻搜索和范围搜索等。

  2. KD-Tree的构建和搜索时间复杂度均为O(log n),其中n为数据点的数量。

  3. KD-Tree的空间复杂度比朴素的暴力搜索算法要小很多。

  • 缺点
  1. KD-Tree的构建和搜索过程都需要大量的计算,对于高维数据集来说,效率可能会变得很低。

  2. KD-Tree的查询结果可能会受到数据分布的影响,例如如果数据点都集中在某个区域,那么查询结果可能会偏向该区域。

  3. KD-Tree需要占用较大的内存空间,因为每个节点都需要存储多个数据点。


例题

JZPFAR

P2093 [国家集训队]JZPFAR

在这里插入图片描述


思路:

KD-Tree 模板题。

存储每个节点的信息:二维坐标:x[2]表示x、y坐标,id对应节点编号。

struct Point {
	int x[2], id;
	bool operator<(const Point& A) const {
		return x[type] < A.x[type];
	}
} a[N];

k-d 树的节点:ls、rs表示当前节点的左右孩子,maxp 和 minp 分别表示该节点所代表的区域在每个维度上的最大和最小值,id 表示该节点所代表的数据点的编号,v 表示该节点所代表的在 k 维空间中的数据点。

#define ls tr[rt].ls
#define rs tr[rt].rs
struct kdtree {
	int ls, rs; 
	int maxp[2], minp[2];
	int id;
	Point v;
} tr[N];

答案结构:维护优先队列的小根堆,id 表示查询点的编号,val 表示查询点与待查找点之间的距离。

struct ask {
	int id, val; 
	bool operator<(const ask& A) const {
		if (val == A.val) return id < A.id;
		return val > A.val;
	}
};

build 用于构建 k-d 树:rt 表示当前节点的编号,l 和 r 分别表示当前区间的左右端点,d 表示当前处理的维度。

具体实现过程如下:

  1. 如果当前区间为空,则返回。
  2. 计算当前区间的中间位置 mid。
  3. 根据当前处理的维度 d,将 a[l] 到 a[r] 中第 mid - l + 1 小的元素(即中位数)放在 a[mid] 的位置上。
  4. 创建一个新节点,将其坐标设置为 a[mid],id 设置为 a[mid].id。
  5. 递归地构建左子树,区间为 [l, mid - 1],维度为 d ^ 1。
  6. 递归地构建右子树,区间为 [mid + 1, r],维度为 d ^ 1。
  7. 更新当前节点的 maxp 和 minp,即将左右子树的 maxp 和 minp 合并到当前节点上。

通过这样的方式,我们可以构建出一棵 k-d 树来进行 KNN 算法的查询。

void build(int &rt, int l, int r, int d) {
	if(l > r) return ;
	rt = ++cnt; 
	
	int mid = l + r >> 1;
	
	type = d;
	nth_element(a + l, a + mid, a + r + 1);
	
	tr[rt].v = a[mid]; 
	tr[rt].id = a[mid].id; 
	
	build(ls, l, mid - 1, d ^ 1);
	build(rs, mid + 1, r, d ^ 1);
	update(rt);
}

query 用于在 k-d 树中查找与给定点 v 最近的 k 个数据点:rt 表示当前节点的编号,v 表示待查找的点。

由于是求距离给定点最大的第 k k k 个点,所以从根节点开始询问,遇到更大距离的点即 if(t.val > q.top().val),就更新小根堆,动态维护着最大的 k k k 个点。

这里做 if (l < r) 的判断,然后区分先递归左右子树,尽可能地缩小搜索范围,可以大大减少查询的次数,以减少不必要的计算。

因此,如果当前节点的左子树在目标点的某个维度上比当前节点更接近目标点,那么我们应该先遍历右子树,再遍历左子树;否则,应该先遍历左子树,再遍历右子树。(因为是对q.top()的比较增改,先查询会先压入更大的值,减少回溯后其他分支的查询概率)

void query(int rt, Point v) {
	ask t; t.id = tr[rt].id; t.val = dis(v.x[0], v.x[1], tr[rt].v.x[0], tr[rt].v.x[1]);
	if(t.val > q.top().val) q.pop(), q.push(t);
	
	int l = -2e18, r = -2e18; 
	if (ls) l = getdis(ls, v);
	if (rs) r = getdis(rs, v);
	if (l < r) {
		if(r >= q.top().val) query(rs, v);
		if(l >= q.top().val) query(ls, v);
	} else{
		if(l >= q.top().val) query(ls, v);
		if(r >= q.top().val) query(rs, v);
	}
}

代码:

#include <bits/stdc++.h>
using namespace std;

#define int long long 
const int N = 100010;

int root, type, cnt;

struct Point {
	int x[2], id;
	bool operator<(const Point& A) const {
		return x[type] < A.x[type];
	}
} a[N];

struct kdtree {
	int ls, rs; 
	int maxp[2], minp[2];
	int id;
	Point v;
} tr[N];

struct ask {
	int id, val; 
	bool operator<(const ask& A) const {
		if (val == A.val) return id < A.id;
		return val > A.val;
	}
};

priority_queue<ask> q;

#define ls tr[rt].ls
#define rs tr[rt].rs

void update(int rt){
	for(int i = 0; i < 2; i++) { 
		tr[rt].maxp[i] = tr[rt].minp[i] = tr[rt].v.x[i];
		if(ls) {
			tr[rt].maxp[i] = max(tr[rt].maxp[i], tr[ls].maxp[i]);
			tr[rt].minp[i] = min(tr[rt].minp[i], tr[ls].minp[i]);
		}
		if(rs){
			tr[rt].maxp[i] = max(tr[rt].maxp[i], tr[rs].maxp[i]);
			tr[rt].minp[i] = min(tr[rt].minp[i], tr[rs].minp[i]);
		}
	}
}

void build(int &rt, int l, int r, int d) {
	if(l > r) return ;
	rt = ++cnt; 
	
	int mid = l + r >> 1;
	
	type = d;
	nth_element(a + l, a + mid, a + r + 1);
	
	tr[rt].v = a[mid]; 
	tr[rt].id = a[mid].id; 
	
	build(ls, l, mid - 1, d ^ 1);
	build(rs, mid + 1, r, d ^ 1);
	update(rt);
}

int getdis(int rt, Point v) {
	int res = 0;
	for(int i = 0; i < 2; i++) {
		int t = max(abs(v.x[i] - tr[rt].maxp[i]), abs(v.x[i] - tr[rt].minp[i]));
		res += t * t;
	}
	return res;
}

int dis(int x, int y, int xx, int yy) {
	return (x - xx) * (x - xx) + (y - yy) * (y - yy);
}

void query(int rt, Point v) {
	ask t; t.id = tr[rt].id; t.val = dis(v.x[0], v.x[1], tr[rt].v.x[0], tr[rt].v.x[1]);
	if(t.val > q.top().val) q.pop(), q.push(t);
	
	int l = -2e18, r = -2e18; 
	if (ls) l = getdis(ls, v);
	if (rs) r = getdis(rs, v);
	if (l < r) {
		if(r >= q.top().val) query(rs, v);
		if(l >= q.top().val) query(ls, v);
	} else{
		if(l >= q.top().val) query(ls, v);
		if(r >= q.top().val) query(rs, v);
	}
}

signed main(){
	int n; cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i].x[0] >> a[i].x[1]; a[i].id = i;
	}
	
	build(root, 1, n, 0);
	
	int m; cin >> m;
	
	while(m--) {
		int k;
		Point v;
		cin >> v.x[0] >> v.x[1] >> k;
		while(!q.empty()) q.pop();
		
		while(k--) q.push(ask{0, -1});
		query(root, v);
		cout << q.top().id << endl; 
	}
	return 0;
}

以上是二维的 KD-Tree 例题,后续有时间在多更新几题。

三维的写法可参考:https://github.com/cloudwu/kdtree

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/21550.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习项目实战-能源利用率 Part-2(探索性数据分析)

Part-1部分的博客可见下&#xff1a; 机器学习项目实战-能源利用率 Part-1&#xff08;数据清洗&#xff09; 这部分进行的是探索性数据分析。 探索性数据分析 Exploratory Data Analysis 简单的说&#xff0c;就是画图来分析数据。 分析标签数据 data data.rename(colum…

平抑风电波动的电-氢混合储能容量优化配置(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Redis缓存架构详解

文章目录 Redis缓存结构详解前言Redis 缓存架构redis 和db数据一致性先写db还是写redis如果是先写db,再删除缓存呢&#xff1f;延迟双删 简单的缓存,并发不高,没啥流量简单的缓存,并发高,但是存在redis和 Db 双写不一致,读写并发不一致问题解决方案 1解决方案 2解决方案 3读写锁…

更高效便捷的开发体验——Cloud Studio 编辑器命令行工具

Cloud Studio 是一个云端在线开发平台&#xff0c;在 Cloud Studio 的控制台页面中&#xff0c;可以方便快捷创建或者打开一个工作空间。工作空间提供了在线编辑器给大家访问远端开发环境。大部分开发时间都与这个在线编辑器打交道&#xff0c;在线编辑器效果如下图所示&#x…

由浅入深Netty简易实现RPC框架

目录 1 准备工作2 服务器 handler3 客户端代码第一版4 客户端 handler 第一版5 客户端代码 第二版6 客户端 handler 第二版 1 准备工作 这些代码可以认为是现成的&#xff0c;无需从头编写练习 为了简化起见&#xff0c;在原来聊天项目的基础上新增 Rpc 请求和响应消息 Data …

chatgpt赋能Python-pythonendswith

Python endswith方法&#xff1a;介绍、用法和示例 在编程中&#xff0c;经常需要查找字符串是否以特定字符结尾。Python提供了一个方便易用的方法——endswith()。 什么是Python endswith()方法&#xff1f; Python endswith()方法是用于检查字符串是否以特定子字符串结尾的…

【经验分享】一种高内聚低耦合的消息分发模块的设计与实现

【经验分享】一种高内聚低耦合的消息分发模块的设计与实现 又到了每天的open话题&#xff1a;【代码面对面】时刻&#xff0c;让我们一起在摸鱼中学习技术吧。今天的话题是嵌入式的消息分发模块&#xff0c;你会怎么设计和实现&#xff1f; 1 写在前面 老套路&#xff0c;我先…

GitHub Copilot:神一样的代码助手

我肝肯定&#xff0c;很多很多小伙伴还不了解 Copilot 是什么&#xff0c;尤其是初学计算机的小伙伴&#xff0c;我这里普及一下吧&#xff01; GitHub Copilot 是一个基于 AI 的代码自动完成工具&#xff0c;由 GitHub 和 OpenAI 共同开发。 GitHub 和 OpenAI 想必大家都很清楚…

从零制作操作系统——环境搭建以及HelloWorld

从零制作操作系统——环境搭建以及HelloWorld 起因 最近在学习操作系统&#xff0c;尝试自己照着书搓一个出来。 环境搭建 基础环境 我们的操作系统在x86平台的Linux下进行编写和运行。编辑器用的VIM。 我的系统是Fedora 36&#xff0c;当然你也可以使用Ubuntu或者其他Li…

IBM 创新方案+SNP数据转型助一汽大众实现数据平稳、高效迁移

近日&#xff0c;IBM 采用基于SNP Bluefield技术迁移的IBM Rapid Move创新数据迁移方案, 成功为一汽-大众实施了企业运营数据系统从 ECC 到 S/4 的升级项目。该项目系统切换耗时仅三天&#xff0c;不仅助客户高效、平稳迁移了系统数据&#xff0c;升级了数据底座&#xff0c;还…

SpringBoot项目打包部署到Nginx【无需配置Nginx】

0.前置知识 springboot打包的项目共分为jar和war两种类型 jar包 jar类型项目使用SpringBoot打包插件打包时&#xff0c;会在打成的jar中 内置一个tomcat 的jar 所以我们可以使用jdk直接运行&#xff0c;将功能代码放到其内置的tomcat中运行。 war包 在打包时需要将 内置的tom…

关于单目视觉 SLAM 的空间感知定位技术的讨论

尝试关于单目视觉 SLAM 的空间感知定位技术的学习&#xff0c;做以调查。SLAM算法最早在机器人领域中提出&#xff0c;视觉SLAM又可以分为单目、双目和深度相机三种传感器模式&#xff0c;在AR应用中通常使用轻便、价格低廉的单目相机设备。仅使用一个摄像头作为传感器完成同步…

prettier 使用详细介绍

prettier 使用详细介绍 prettier是一个代码格式化工具&#xff0c;可以通过自定义规则来重新规范项目中的代码&#xff0c;去掉原始的代码风格&#xff0c;确保团队的代码使用统一相同的格式。 安装 npm i prettier -Dyarn add prettier --dev创建一个prettierrc.*配置文件&…

六级备考28天|CET-6|听力第二讲|长对话满分技巧|听写技巧|2022年6月考题|14:30~16:00

目录 1. 听力策略 2. 第一二讲笔记 3. 听力原文复现 (5)第五小题 (6)第六小题 (7)第七小题 (8)第八小题 扩展业务 expand business 4. 重点词汇 1. 听力策略 2. 第一二讲笔记 3. 听力原文复现 (5)第五小题 our guest is Molly Sundas, a university stud…

learn C++ NO.5 ——类和对象(3)

日期类的实现 在前面类和对象的学习中&#xff0c;由于知识多比较多和碎&#xff0c;需要一个能够将之前所学知识融会贯通的东西。下面就通过实现日期类来对类和对象已经所学的知识进行巩固。 日期类的基本功能&#xff08;.h文件&#xff09; //Date.h//头文件内容 #includ…

【数据结构】广度优先遍历(BFS)模板及其讲解

&#x1f38a;专栏【数据结构】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【勋章】 大一同学小吉&#xff0c;欢迎并且感谢大家指出我的问题&#x1f970; 目录 &#x1f381;定义 &#x1f381;遍历方法 &#x1f381;根…

什么是边缘计算盒子?边缘计算盒子可以做什么?一文带你了解边缘计算云服务器 ECS

上文&#xff0c;我们已经为大家介绍了什么是边缘计算、边缘计算的诞生、以及边缘计算与CDN之间的关系&#xff0c;感兴趣的小伙伴欢迎阅读以往文章&#xff1a; 边缘计算节点是啥&#xff1f;边缘计算与CDN有什么关系&#xff1f;一文带你了解边缘计算节点BEC&#xff08;1&am…

nest笔记十一:一个完整的nestjs示例工程(nestjs_template)

概述 链接&#xff1a;nestjs_template 相关文章列表 nestjs系列笔记 示例工程说明 这个工程是我使用nestjs多个项目后&#xff0c;总结出来的模板。这是一个完整的工程&#xff0c;使用了yaml做为配置&#xff0c;使用了log4js和redis和typeorm&#xff0c;sawgger&#…

Elasticsearch 集群部署插件管理及副本分片概念介绍

Elasticsearch 集群配置版本均为8以上 安装前准备 CPU 2C 内存4G或更多 操作系统: Ubuntu20.04,Ubuntu18.04,Rocky8.X,Centos 7.X 操作系统盘50G 主机名设置规则为nodeX.qingtong.org 生产环境建议准备单独的数据磁盘主机名 #各自服务器配置自己的主机名 hostnamectl set-ho…

STM32实现基于RS485的简单的Modbus协议

背景 我这里用STM32实现&#xff0c;其实可以搬移到其他MCU&#xff0c;之前有项目使用STM32实现Modbus协议 这个场景比较正常&#xff0c;很多时候都能碰到 这里主要是Modbus和变频器通信 最常见的是使用Modbus实现传感器数据的采集&#xff0c;我记得之前用过一些传感器都…
最新文章