KD树
k-d树
在计算机科学里,k-d树( k-维树的缩写)是在k维欧几里德空间组织点的数据结构。k-d树可以使用在多种应用场合,如多维键值搜索(例:范围搜寻及最邻近搜索)。k-d树是空间二分树(Binary space partitioning )的一种特殊情况。[1]
可以看到,KD树是基于欧式距离度量的。
简介:
k-d树是每个节点都为k维点的二叉树。所有非叶子节点可以视作用一个超平面把空间分区成两个半空间( Half-space[失效链接] )。节点左边的子树代表在超平面左边的点,节点右边的子树代表在超平面右边的点。选择超平面的方法如下:每个节点都与k维中垂直于超平面的那一维有关。因此,如果选择按照x轴划分,所有x值小于指定值的节点都会出现在左子树,所有x值大于指定值的节点都会出现在右子树。这样,超平面可以用该x值来确定,其法线为x轴的单位向量。
KD树的算法主要分为KD树的构造和查找两部分。
KD树的构造:
方法一:
有很多种方法可以选择轴垂直分区面( axis-aligned splitting planes ),所以有很多种创建k-d树的方法。 最典型的方法如下:
- 随着树的深度轮流选择轴当作分区面。(例如:在三维空间中根节点是 x 轴垂直分区面,其子节点皆为 y 轴垂直分区面,其孙节点皆为 z 轴垂直分区面,其曾孙节点则皆为 x 轴垂直分区面,依此类推。)
- 点由垂直分区面之轴座标的中位数区分并放入子树
这个方法产生一个平衡的k-d树。每个叶节点的高度都十分接近。然而,平衡的树不一定对每个应用都是最佳的。[1]
这是维基百科的方法,就是轮流用各个维度来划分。
方法二:
k-d树是一个二叉树,每个节点表示一个空间范围。表1给出的是k-d树每个节点中主要包含的数据结构。
表1 k-d树中每个节点的数据类型
域名 | 数据类型 | 描述 |
Node-data | 数据矢量 | 数据集中某个数据点,是n维矢量(这里也就是k维) |
Range | 空间矢量 | 该节点所代表的空间范围 |
split | 整数 | 垂直于分割超平面的方向轴序号 |
Left | k-d树 | 由位于该节点分割超平面左子空间内所有数据点所构成的k-d树 |
Right | k-d树 | 由位于该节点分割超平面右子空间内所有数据点所构成的k-d树 |
parent | k-d树 | 父节点 |
从上面对k-d树节点的数据类型的描述可以看出构建k-d树是一个逐级展开的递归过程。表2给出的是构建k-d树的伪码。
表2 构建k-d树的伪码
算法:构建k-d树(createKDTree) |
输入:数据点集Data-set和其所在的空间Range |
输出:Kd,类型为k-d tree |
1.If Data-set为空,则返回空的k-d tree |
2.调用节点生成程序: (1)确定split域:对于所有描述子数据(特征矢量),统计它们在每个维上的数据方差。以SURF特征为例,描述子为64维,可计算64个方差。挑选出最大值,对应的维就是split域的值。数据方差大表明沿该坐标轴方向上的数据分散得比较开,在这个方向上进行数据分割有较好的分辨率; (2)确定Node-data域:数据点集Data-set按其第split域的值排序。位于正中间的那个数据点被选为Node-data。此时新的Data-set' = Data-set\Node-data(除去其中Node-data这一点)。 |
3.dataleft = {d属于Data-set' && d[split] ≤ Node-data[split]} Left_Range = {Range && dataleft} dataright = {d属于Data-set' && d[split] > Node-data[split]} Right_Range = {Range && dataright} |
4.left = 由(dataleft,Left_Range)建立的k-d tree,即递归调用createKDTree(dataleft,Left_ Range)。并设置left的parent域为Kd; right = 由(dataright,Right_Range)建立的k-d tree,即调用createKDTree(dataleft,Left_ Range)。并设置right的parent域为Kd。 |
以上述举的实例来看,过程如下:
由于此例简单,数据维度只有2维,所以可以简单地给x,y两个方向轴编号为0,1,也即split={0,1}。
(1)确定split域的首先该取的值。分别计算x,y方向上数据的方差得知x方向上的方差最大,所以split域值首先取0,也就是x轴方向;
(2)确定Node-data的域值。根据x轴方向的值2,5,9,4,8,7排序选出中值为7,所以Node-data = (7,2)。这样,该节点的分割超平面就是通过(7,2)并垂直于split = 0(x轴)的直线x = 7;
(3)确定左子空间和右子空间。分割超平面x = 7将整个空间分为两部分,如图2所示。x < = 7的部分为左子空间,包含3个节点{(2,3),(5,4),(4,7)};另一部分为右子空间,包含2个节点{(9,6),(8,1)}。
图2 x=7将整个空间分为两部分
如算法所述,k-d树的构建是一个递归的过程。然后对左子空间和右子空间内的数据重复根节点的过程就可以得到下一级子节点(5,4)和(9,6)(也就是左右子空间的'根'节点),同时将空间和数据集进一步细分。如此反复直到空间中只包含一个数据点,如图1所示。最后生成的k-d树如图3所示。
图3 上述实例生成的k-d树
注意:每一级节点旁边的'x'和'y'表示以该节点分割左右子空间时split所取的值。
该方法参考资料来自【2】
k-d树上的最邻近查找算法
在k-d树中进行数据的查找也是特征匹配的重要环节,其目的是检索在k-d树中与查询点距离最近的数据点。这里先以一个简单的实例来描述最邻近查找的基本思路。
星号表示要查询的点(2.1,3.1)。通过二叉搜索,顺着搜索路径很快就能找到最邻近的近似点,也就是叶子节点(2,3)。而找到的叶子节点并不一定就是最邻近的,最邻近肯定距离查询点更近,应该位于以查询点为圆心且通过叶子节点的圆域内。为了找到真正的最近邻,还需要进行'回溯'操作:算法沿搜索路径反向查找是否有距离查询点更近的数据点。此例中先从(7,2)点开始进行二叉查找,然后到达(5,4),最后到达(2,3),此时搜索路径中的节点为<(7,2),(5,4),(2,3)>,首先以(2,3)作为当前最近邻点,计算其到查询点(2.1,3.1)的距离为0.1414,然后回溯到其父节点(5,4),并判断在该父节点的其他子节点空间中是否有距离查询点更近的数据点。以(2.1,3.1)为圆心,以0.1414为半径画圆,如图4所示。发现该圆并不和超平面y = 4交割,因此不用进入(5,4)节点右子空间中去搜索。
图4 查找(2.1,3.1)点的两次回溯判断
再回溯到(7,2),以(2.1,3.1)为圆心,以0.1414为半径的圆更不会与x = 7超平面交割,因此不用进入(7,2)右子空间进行查找。至此,搜索路径中的节点已经全部回溯完,结束整个搜索,返回最近邻点(2,3),最近距离为0.1414。
一个复杂点了例子如查找点为(2,4.5)。同样先进行二叉查找,先从(7,2)查找到(5,4)节点,在进行查找时是由y = 4为分割超平面的,由于查找点为y值为4.5,因此进入右子空间查找到(4,7),形成搜索路径<(7,2),(5,4),(4,7)>,取(4,7)为当前最近邻点,计算其与目标查找点的距离为3.202。然后回溯到(5,4),计算其与查找点之间的距离为3.041。以(2,4.5)为圆心,以3.041为半径作圆,如图5所示。可见该圆和y = 4超平面交割,所以需要进入(5,4)左子空间进行查找。此时需将(2,3)节点加入搜索路径中得<(7,2),(2,3)>。回溯至(2,3)叶子节点,(2,3)距离(2,4.5)比(5,4)要近,所以最近邻点更新为(2,3),最近距离更新为1.5。回溯至(7,2),以(2,4.5)为圆心1.5为半径作圆,并不和x = 7分割超平面交割,如图6所示。至此,搜索路径回溯完。返回最近邻点(2,3),最近距离1.5。k-d树查询算法的伪代码如表3所示。
图5 查找(2,4.5)点的第一次回溯判断
图6 查找(2,4.5)点的第二次回溯判断
表3 标准k-d树查询算法
算法:k-d树最邻近查找 |
输入:Kd, //k-d tree类型 target //查询数据点 |
输出:nearest, //最邻近数据点 dist //最邻近数据点和查询点间的距离 |
1. If Kd为NULL,则设dist为infinite并返回 |
2. //进行二叉查找,生成搜索路径 Kd_point = &Kd; //Kd-point中保存k-d tree根节点地址 nearest = Kd_point -> Node-data; //初始化最近邻点 while(Kd_point) push(Kd_point)到search_path中; //search_path是一个堆栈结构,存储着搜索路径节点指针 /*** If Dist(nearest,target) > Dist(Kd_point -> Node-data,target) nearest = Kd_point -> Node-data; //更新最近邻点 Max_dist = Dist(Kd_point,target); //更新最近邻点与查询点间的距离 ***/ s = Kd_point -> split; //确定待分割的方向 If target[s] <= Kd_point -> Node-data[s] //进行二叉查找 Kd_point = Kd_point -> left; else Kd_point = Kd_point ->right; nearest = search_path中最后一个叶子节点; //注意:二叉搜索时不比计算选择搜索路径中的最邻近点,这部分已被注释 Max_dist = Dist(nearest,target); //直接取最后叶子节点作为回溯前的初始最近邻点 |
3. //回溯查找 while(search_path != NULL) back_point = 从search_path取出一个节点指针; //从search_path堆栈弹栈 s = back_point -> split; //确定分割方向 If Dist(target[s],back_point -> Node-data[s]) < Max_dist //判断还需进入的子空间 If target[s] <= back_point -> Node-data[s] Kd_point = back_point -> right; //如果target位于左子空间,就应进入右子空间 else Kd_point = back_point -> left; //如果target位于右子空间,就应进入左子空间 将Kd_point压入search_path堆栈; If Dist(nearest,target) > Dist(Kd_Point -> Node-data,target) nearest = Kd_point -> Node-data; //更新最近邻点 Min_dist = Dist(Kd_point -> Node-data,target); //更新最近邻点与查询点间的距离 |
上述两次实例表明,当查询点的邻域与分割超平面两侧空间交割时,需要查找另一侧子空间,导致检索过程复杂,效率下降。研究表明N个节点的K维k-d树搜索过程时间复杂度为:tworst=O(kN1-1/k)。
参考资料:
[1]https://zh.wikipedia.org/wiki/K-d树
[2]http://www.cnblogs.com/eyeszjwang/articles/2429382.html
KD树的更多相关文章
- 利用KD树进行异常检测
软件安全课程的一次实验,整理之后发出来共享. 什么是KD树 要说KD树,我们得先说一下什么是KNN算法. KNN是k-NearestNeighbor的简称,原理很简单:当你有一堆已经标注好的数据时,你 ...
- 2016 ICPC青岛站---k题 Finding Hotels(K-D树)
题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=5992 Problem Description There are N hotels all over ...
- kd树和knn算法的c语言实现
基于kd树的knn的实现原理可以参考文末的链接,都是一些好文章. 这里参考了别人的代码.用c语言写的包括kd树的构建与查找k近邻的程序. code: #include<stdio.h> # ...
- PCL点云库:Kd树
Kd树按空间划分生成叶子节点,各个叶子节点里存放点数据,其可以按半径搜索或邻区搜索.PCL中的Kd tree的基础数据结构使用了FLANN以便可以快速的进行邻区搜索.FLANN is a librar ...
- KNN算法与Kd树
最近邻法和k-近邻法 下面图片中只有三种豆,有三个豆是未知的种类,如何判定他们的种类? 提供一种思路,即:未知的豆离哪种豆最近就认为未知豆和该豆是同一种类.由此,我们引出最近邻算法的定义:为了判定未知 ...
- k临近法的实现:kd树
# coding:utf-8 import numpy as np import matplotlib.pyplot as plt T = [[2, 3], [5, 4], [9, 6], [4, 7 ...
- 从K近邻算法谈到KD树、SIFT+BBF算法
转自 http://blog.csdn.net/v_july_v/article/details/8203674 ,感谢july的辛勤劳动 前言 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章 ...
- bzoj 3489: A simple rmq problem k-d树思想大暴力
3489: A simple rmq problem Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 551 Solved: 170[Submit][ ...
- k近邻法的C++实现:kd树
1.k近邻算法的思想 给定一个训练集,对于新的输入实例,在训练集中找到与该实例最近的k个实例,这k个实例中的多数属于某个类,就把该输入实例分为这个类. 因为要找到最近的k个实例,所以计算输入实例与训练 ...
- bzoj 3053 HDU 4347 : The Closest M Points kd树
bzoj 3053 HDU 4347 : The Closest M Points kd树 题目大意:求k维空间内某点的前k近的点. 就是一般的kd树,根据实测发现,kd树的两种建树方式,即按照方差 ...
随机推荐
- leetcode series:Two Sum
题目: Given an array of integers, find two numbers such that they add up to a specific target number. ...
- HTTPS原理浅析
HTTPS(Hypertext Transfer Protocol Secure)协议用于提供安全的超文本传输服务. 其本质上是SSL/TLS层上的HTTP协议, 即所谓的"HTTP ove ...
- HTTP/2之服务器推送(Server Push)最佳实践
商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. WeTest 导读 HTTP/1.X出色地满足互联网的普遍访问需求,但随着互联网的不断发展,其性能越来越成为瓶颈.IETF在2015 ...
- COM组件转换为.NET元数据
.net开发中,需要调用一些COM组件,COM组件的元素转化为.net的元数据后才能很好的调用. 下面贴出我转的过程. 首先,打开C:\Program Files (x86)\Microsoft SD ...
- iKcamp团队制作|基于Koa2搭建Node.js实战(含视频)☞ 中间件用法
中间件用法--讲解 Koa2 中间件的用法及如何开发中间件
- LintCode-落单的数 III
给出2*n + 2个的数字.除当中两个数字之外其它每一个数字均出现两次,找到这两个数字. 您在真实的面试中是否遇到过这个题? Yes 例子 给出 [1,2,2,3,4,4,5,3].返回 1和5 挑战 ...
- NYOJ 480 Fibonacci Again!
Fibonacci Again! 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描写叙述 求第n个斐波那契数是否是一个素数,n为整数 f[n]=f[n-1]+f[n-2] ( ...
- Javascript自动化文档工具JSDuck在Windows下的使用心得
作者: zyl910 一.工具比较 为了让前端JavaScript程序更具可维护性,更利于团队开发,文档非常重要.此时便需要使用自动化文档工具了. 我对比了各种JavaScript自动化文档工具,发现 ...
- android 分享一个处理BaseAdapter,getView()多次加载的方法
一:BaseAdapter介绍 BaseAdapter是listview,gridview等列表,使用的数据适配器,它的主要用途是将一组数据传到ListView.Spinner.Gallery及Gri ...
- 使用WebEx录制视频并转换为MP4
1.下载并安装WebEx http://www.question-defense.com/wp-content/uploads/2010/05/ateditor-version-3.0.zip 2.打 ...