k-d树的最近邻搜索算法

在k-d tree树中进行数据的k近邻搜索是特征匹配的重要环节,其目的是检索在k-d tree中与待查询点距离最近的k个数据点。

最近邻搜索是k近邻的特例,也就是1近邻。将1近邻改扩展到k近邻非常容易。下面介绍最简单的k-d tree最近邻搜索算法。

基本的思路很简单:首先通过二叉树搜索(比较待查询节点和分裂节点的分裂维的值,小于等于就进入左子树分支,大于就进入右子树分支直到叶子结点),顺着“搜索路径”很快能找到最近邻的近似点,也就是与待查询点处于同一个子空间的叶子结点;然后再回溯搜索路径,并判断搜索路径上的结点的其他子结点空间中是否可能有距离查询点更近的数据点,如果有可能,则需要跳到其他子结点空间中去搜索(将其他子结点加入到搜索路径)。重复这个过程直到搜索路径为空。

算法:kdtreeFindNearest  

输入:Kd  

target  

输出 : nearest  

dist  

1. 如果Kd是空的,则设dist为无穷大返回 

2. 向下搜索直到叶子结点 

pSearch = &Kd
while(pSearch != NULL)
{
  pSearch加入到search_path中;
  if(target[pSearch->split] <= pSearch->dom_elt[pSearch->split])
  {
    pSearch = pSearch->left;
  }
  else
  {
    pSearch = pSearch->right;
  }
}
取出search_path最后一个赋给nearest 

dist = Distance(nearest, target);  

3. 回溯搜索路径 

while(search_path不为空)
{
  取出search_path最后一个结点赋给pBack 

  if(pBack->left为空 && pBack->right为空)
  {
    if( Distance(nearest, target) > Distance(pBack->dom_elt, target) )
    {
      nearest = pBack->dom_elt;
      dist = Distance(pBack->dom_elt, target);
    }
  }
  else
  {
    s = pBack->split;
    if( abs(pBack->dom_elt[s] - target[s]) < dist)
    {
      if( Distance(nearest, target) > Distance(pBack->dom_elt, target) )
      {
        nearest = pBack->dom_elt;
        dist = Distance(pBack->dom_elt, target);
      }
      if(target[s] <= pBack->dom_elt[s])
        pSearch = pBack->right;
      else
        pSearch = pBack->left;   

      if(pSearch != NULL)
        pSearch加入到search_path中
    }
  }
} 

现在举一些例子来说明上面的最近邻搜索算法,假设我们的k-d tree就是上面通过样本集{(2,3), (5,4), (9,6), (4,7), (8,1), (7,2)}创建的。将上面的图转化为树形图的样子如下:

我们来查找点(2.1,3.1),在(7,2)点测试到达(5,4),在(5,4)点测试到达(2,3),然后search_path中的结点为<(7,2), (5,4), (2,3)>,从search_path中取出(2,3)作为当前最佳结点nearest, dist为0.141;

然后回溯至(5,4),以(2.1,3.1)为圆心,以dist=0.141为半径画一个圆,并不和超平面y=4相交,如下图,所以不必跳到结点(5,4)的右子空间去搜索,因为右子空间中不可能有更近样本点了。

于是在回溯至(7,2),同理,以(2.1,3.1)为圆心,以dist=0.141为半径画一个圆并不和超平面x=7相交,所以也不用跳到结点(7,2)的右子空间去搜索

至此,search_path为空,结束整个搜索,返回nearest(2,3)作为(2.1,3.1)的最近邻点,最近距离为0.141。

再举一个稍微复杂的例子,我们来查找点(2,4.5),在(7,2)处测试到达(5,4),在(5,4)处测试到达(4,7),然后search_path中的结点为<(7,2), (5,4), (4,7)>,从search_path中取出(4,7)作为当前最佳结点nearest, dist为3.202;

然后回溯至(5,4),以(2,4.5)为圆心,以dist=3.202为半径画一个圆与超平面y=4相交,如下图,所以需要跳到(5,4)的左子空间去搜索。所以要将(2,3)加入到search_path中,现在search_path中的结点为<(7,2), (2, 3)>;另外,(5,4)与(2,4.5)的距离为3.04 < dist = 3.202,所以将(5,4)赋给nearest,并且dist=3.04。

回溯至(2,3),(2,3)是叶子节点,直接判断(2,3)是否离(2,4.5)更近,计算得到距离为1.5,所以nearest更新为(2,3),dist更新为(1.5)

回溯至(7,2),同理,以(2,4.5)为圆心,以dist=1.5为半径画一个圆并不和超平面x=7相交, 所以不用跳到结点(7,2)的右子空间去搜索

至此,search_path为空,结束整个搜索,返回nearest(2,3)作为(2,4.5)的最近邻点,最近距离为1.5。

两次搜索的返回的最近邻点虽然是一样的,但是搜索(2, 4.5)的过程要复杂一些,因为(2, 4.5)更接近超平面。研究表明,当查询点的邻域与分割超平面两侧的空间都产生交集时,回溯的次数大大增加。最坏的情况下搜索N个结点的k维kd-tree所花费的时间为:

关于k-d tree还有很多扩展。由于大量回溯会导致kd-tree最近邻搜索的性能大大下降,因此研究人员也提出了改进的k-d tree近邻搜索,其中一个比较著名的就是 Best-Bin-First,它通过设置优先级队列和运行超时限定来获取近似的最近邻,有效地减少回溯的次数。

参考资料

1.An intoductory tutorial on kd-trees Andrew W.Moore

2.《图像局部不变特性特征与描述》王永明 王贵锦 编著 国防工业出版社

3.kdtree A simple C library for working with KD-Trees

时间: 2024-09-29 22:47:32

k-d树的最近邻搜索算法的相关文章

【BZOJ 1901】【Zju 2112】 Dynamic Rankings 动态K值 树状数组套主席树模板题

达神题解传送门:http://blog.csdn.net/dad3zz/article/details/50638360 说一下我对这个模板的理解: 看到这个方法很容易不知所措,因为动态K值需要套树状数组,而我一开始根本不知道该怎么套,, 学习吧,,, 然后我自己脑补如果不套会如何?后来想到是查询O(logn),修改是O(nlogn),很明显修改的复杂度太大了,为了降低修改的复杂度,我们只得套上树状数组来维护前缀和使它的n的复杂度降低为logn,从而修改的复杂度变为O(log2n).但因为我们套

HDU 5412 CRB and Queries(区间第K大 树套数 按值建树)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5412 Problem Description There are N boys in CodeLand. Boy i has his coding skill Ai. CRB wants to know who has the suitable coding skill. So you should treat the following two types of queries. Query 1:

BZOJ 1901 Zju 2112 Dynamic Rankings 动态维护第k小 树套树

题目大意:动态维护第k小. 思路:线段树套treap,裸题,就是不怎么好写. CODE: #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define MAX 50010 #define INF 1e9 #define LEFT (pos << 1) #define RIGHT (pos << 1|1) #define SIZ

静态区间第k大 树套树解法

然而过不去你谷的模板 思路: 值域线段树\([l,r]\)代表一棵值域在\([l,r]\)范围内的点构成的一颗平衡树 平衡树的\(BST\)权值为点在序列中的位置 查询区间第\(k\)大值时 左区间在\([l,r]\)范围内的树的大小与\(k\)比较 大了进去,小了减掉换一边 关于建树 递归建估计是\(O(nlog^2n)\)的 Code: #include <cstdio> #include <cstdlib> #include <algorithm> #includ

UVALive 7148 LRIP 14年上海区域赛K题 树分治

题意 n个点组成一棵树, 带有点权. 求最长不降的路径的长度, 且路径上最大值最小值之差不超过D. 显然是树分治, 但是分治之后如何维护答案呢. 假设当前重心为g, 分别记录g出发不降路径的长度,以及最大值, 和不升路径的长度以及最小值. 这里用到一个map和二分, 线段树也可以, 但是如果用线段树还要考虑负值, 再加上线段树的clear以及稍微暴力的查询.  常数大小不好说. 1 #include <bits/stdc++.h> 2 using namespace std; 3 typede

从K近邻算法、距离度量谈到KD树、SIFT+BBF算法

从K近邻算法.距离度量谈到KD树.SIFT+BBF算法 从K近邻算法.距离度量谈到KD树.SIFT+BBF算法 前言 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章待写:1.KD树:2.神经网络:3.编程艺术第28章.你看到,blog内的文章与你于别处所见的任何都不同.于是,等啊等,等一台电脑,只好等待..”.得益于田,借了我一台电脑(借他电脑的时候,我连表示感谢,他说“能找到工作全靠你的博客,这点儿小忙还说,不地道”,有的时候,稍许感受到受人信任也是一种压力,愿我不辜负大家对我的信任)

KD树

什么是KD树 Kd-树是K-dimension tree的缩写,是对数据点在k维空间(如二维(x,y),三维(x,y,z),k维(x,y,z..))中划分的一种数据结构,主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索).本质上说,Kd-树就是一种平衡二叉树. 首先必须搞清楚的是,k-d树是一种空间划分树,说白了,就是把整个空间划分为特定的几个部分,然后在特定空间的部分内进行相关搜索操作.想像一个三维空间,kd树按照一定的划分规则把这个三维空间划分了多个空间,如下图所示: KD树的构建

KD tree

Kd-树 其实是K-dimension tree的缩写,是对数据点在k维空间中划分的一种数据结构.其实,Kd-树是一种平衡二叉树. 举一示例: 假设有六个二维数据点 = {(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},数据点位于二维空间中.为了能有效的找到最近邻,Kd-树采用分而治之的思想,即将整个空间划分为几个小部分.六个二维数据点生成的Kd-树的图为: 对于拥有n个已知点的kD-Tree,其复杂度如下: 构建:O(log2n) 插入:O(log n) 删除:O(l

k近邻法的C++实现:kd树

1.k近邻算法的思想 给定一个训练集,对于新的输入实例,在训练集中找到与该实例最近的k个实例,这k个实例中的多数属于某个类,就把该输入实例分为这个类. 因为要找到最近的k个实例,所以计算输入实例与训练集中实例之间的距离是关键! k近邻算法最简单的方法是线性扫描,这时要计算输入实例与每一个训练实例的距离,当训练集很大时,非常耗时,这种方法不可行,为了提高k近邻的搜索效率,常常考虑使用特殊的存储结构存储训练数据,以减少计算距离的次数,具体方法很多,这里介绍实现经典的kd树方法. 2.构造kd树 kd