本文首先介绍Kd-Tree的构造方法,然后介绍Kd-Tree的搜索流程及代码实现,最后给出本人利用C#语言实现的二维KD树代码。这也是我自己动手实现的第一个树形的数据结构。理解上难免会有偏差,敬请各位多多斧正。

1. KD树介绍

Kd-Tree(KD树),即K-dimensional tree,是一种高维索引树形数据结构,常用于在大规模的高维数据空间进行最邻近查找和近似最邻近查找。我实现的KD树是二维的Kd - tree。目的是在点集中寻找最近点。参考资料是Kd-Tree的百度百科。并且根据百度百科的逻辑组织了代码。

2. KD树的数学解释

3. KD树的构造方法

这里是用的二维点集进行构造Kd-tree。三维的与此类似。

树中每个节点的数据类型:

    public class KDTreeNode
{
/// <summary>
/// 分裂点
/// </summary>
public Point DivisionPoint { get; set; } /// <summary>
/// 分裂类型
/// </summary>
public EnumDivisionType DivisionType { get; set; } /// <summary>
/// 左子节点
/// </summary>
public KDTreeNode LeftChild { get; set; } /// <summary>
/// 右子节点
/// </summary>
public KDTreeNode RightChild { get; set; }
}

3.1 KD树构造逻辑流程

  1. 将所有的点放入集合a中
  2. 对集合所有点的X坐标求得方差xv,Y坐标求得方差yv
  3. 如果xv > yv,则对集合a根据X坐标进行排序。如果 yv > xv,则对集合a根据y坐标进行排序。
  4. 得到排序后a集合的中位数m。则以m为断点,将[0,m-2]索引的点放到a1集合中。将[m,a.count]索引的点放到a2的集合中(m点的索引为m-1)。
  5. 构建节点,节点的值为a[m-1],如果操作集合中节点的个数大于1,则左节点对[0,m-2]重复2-5步,右节点为对[m,a.count]重复2-5步;反之,则该节点为叶子节点。

3.2 代码实现

private KDTreeNode CreateTreeNode(List<Point> pointList)
{
if (pointList.Count > 0)
{
// 计算方差
double xObtainVariance = ObtainVariance(CreateXList(pointList));
double yObtainVariance = ObtainVariance(CreateYList(pointList)); // 根据方差确定分裂维度
EnumDivisionType divisionType = SortListByXOrYVariances(xObtainVariance, yObtainVariance, ref pointList); // 获得中位数
Point medianPoint = ObtainMedian(pointList);
int medianIndex = pointList.Count / 2; // 构建节点
KDTreeNode treeNode = new KDTreeNode()
{
DivisionPoint = medianPoint,
DivisionType = divisionType,
LeftChild = CreateTreeNode(pointList.Take(medianIndex).ToList()),
RightChild = CreateTreeNode(pointList.Skip(medianIndex + 1).ToList())
};
return treeNode;
}
else
{
return null;
}
}

4. KD树搜索方法

Kd-Tree的总体搜索流程先根据普通的查找找到一个最近的叶子节点。但是这个叶子节点不一定是最近的点。再进行回溯的操作找到最近点。

4.1 KD树搜索逻辑流程

  1. 对于根据点集构建的树t,以及查找点p.将根节点作为节点t进行如下的操作
  2. 如果t为叶子节点。则得到最近点n的值为t的分裂点的值,跳到第5步;如果t不是叶子节点,进行第3步
  3. 则确定t的分裂方式,如果是按照x轴进行分裂,则用p的x值与节点的分裂点的x值进行比较,反之则进行Y坐标的比较
  4. 如果p的比较值小于t的比较值,则将t指定为t的左孩子节点。反之将t指定为t的右孩子节点,执行第2步
  5. 定义检索点m,将m设置为n
  6. 计算m与p的距离d1,n与m的距离d2。
  7. 如果d1 >= d2且有父节点,则将m的父节点作为m的值执行5步,若没有父节点,则得到真正的最近点TN; 如果d1 < d2就表示n点不是最近点,执行第8步
  8. 若n有兄弟节点,则 n = n的兄弟节点;若n没有兄弟节点,则 n = n的父节点。删除原来的n节点。将m的值设置为新的n节点;执行第6步。

4.2 代码实现

public Point FindNearest(Point searchPoint)
{
// 按照查找方式寻找最近点
Point nearestPoint = DFSSearch(this.rootNode, searchPoint); // 进行回溯
return BacktrcakSearch(searchPoint, nearestPoint);
} private Point DFSSearch(KDTreeNode node,Point searchPoint,bool pushStack = true)
{
if(pushStack == true)
{
// 利用堆栈记录查询的路径,由于树节点中没有记载父节点的原因
backtrackStack.Push(node);
}
if (node.DivisionType == EnumDivisionType.X)
{
return DFSXsearch(node,searchPoint);
}
else
{
return DFSYsearch(node, searchPoint);
}
} private Point BacktrcakSearch(Point searchPoint,Point nearestPoint)
{
// 如果记录路径的堆栈为空则表示已经回溯到根节点,则查到的最近点就是真正的最近点
if (backtrackStack.IsEmpty())
{
return nearestPoint;
}
else
{
KDTreeNode trackNode = backtrackStack.Pop(); // 分别求回溯点与最近点距查找点的距离
double backtrackDistance = ObtainDistanFromTwoPoint(searchPoint, trackNode.DivisionPoint);
double nearestPointDistance = ObtainDistanFromTwoPoint(searchPoint, nearestPoint); if (backtrackDistance < nearestPointDistance)
{
// 深拷贝节点的目的是为了避免损坏树
KDTreeNode searchNode = new KDTreeNode()
{
DivisionPoint = trackNode.DivisionPoint,
DivisionType = trackNode.DivisionType,
LeftChild = trackNode.LeftChild,
RightChild = trackNode.RightChild
};
nearestPoint = DFSBackTrackingSearch(searchNode, searchPoint);
}
// 递归到根节点
return BacktrcakSearch(searchPoint, nearestPoint);
}
}

5. 源码交流

https://github.com/CreamMilk/C-Kd-Tree

C# 通过KD树进行距离最近点的查找.的更多相关文章

  1. kd树解平面最近点对

    早上起来头有点疼,突然就想到能不能用kd树解平面最近点对问题,就找了道题试了一下,结果可以,虽然效率不高,但还是AC了~ 题目链接:http://acm.hdu.edu.cn/showproblem. ...

  2. <转>从K近邻算法、距离度量谈到KD树、SIFT+BBF算法

    转自 http://blog.csdn.net/likika2012/article/details/39619687 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章待写:1.KD树:2.神经 ...

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

    转载自:http://blog.csdn.net/v_july_v/article/details/8203674/ 从K近邻算法.距离度量谈到KD树.SIFT+BBF算法 前言 前两日,在微博上说: ...

  4. 一看就懂的K近邻算法(KNN),K-D树,并实现手写数字识别!

    1. 什么是KNN 1.1 KNN的通俗解释 何谓K近邻算法,即K-Nearest Neighbor algorithm,简称KNN算法,单从名字来猜想,可以简单粗暴的认为是:K个最近的邻居,当K=1 ...

  5. 空间划分的数据结构(网格/四叉树/八叉树/BSP树/k-d树/BVH/自定义划分)

    目录 网格 (Grid) 网格的应用 四叉树/八叉树 (Quadtree/Octree) 四叉树/八叉树的应用 BSP树 (Binary Space Partitioning Tree) 判断点在平面 ...

  6. BZOJ - 2648 KD树 最近点查询

    省赛后躺尸几天又回来更新了,内容是说好的KD树.. 具体操作从代码中感受一下 感觉已经把KD树尽量封装好了(虽然全局的D看着极不顺眼) 需要注意的是估值函数的判断条件 #include<bits ...

  7. KNN算法与Kd树

    最近邻法和k-近邻法 下面图片中只有三种豆,有三个豆是未知的种类,如何判定他们的种类? 提供一种思路,即:未知的豆离哪种豆最近就认为未知豆和该豆是同一种类.由此,我们引出最近邻算法的定义:为了判定未知 ...

  8. k临近法的实现:kd树

    # coding:utf-8 import numpy as np import matplotlib.pyplot as plt T = [[2, 3], [5, 4], [9, 6], [4, 7 ...

  9. 从K近邻算法谈到KD树、SIFT+BBF算法

    转自 http://blog.csdn.net/v_july_v/article/details/8203674 ,感谢july的辛勤劳动 前言 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章 ...

随机推荐

  1. [区块链\理解BTCD源码]GO语言实现一个区块链原型

    摘要 本文构建了一个使用工作量证明机制(POW)的类BTC的区块链.将区块链持久化到一个Bolt数据库中,然后会提供一个简单的命令行接口,用来完成一些与区块链的交互操作.这篇文章目的是希望帮助大家理解 ...

  2. SpringBoot从零单排 ------ 拦截器的使用

    在项目开发中我们常常需要对请求进行验证,如登录校验.权限验证.防止重复提交等等,通过拦截器来过滤请求.自定义一个拦截器需要实现HandlerInterceptor接口.代码如下: import org ...

  3. 前端笔记之ES678&Webpack&Babel(中)对象|字符串|数组的扩展&函数新特性&类

    一.对象的扩展 1.1对象属性名表达式 ES6可以在JSON中使用[]包裹一个key的名字.此时这个key将用表达式作为属性名(被当做变量求值),这个key值必须是字符串. var a = 'name ...

  4. [转]为什么复制构造函数的参数需要加const和引用

    [转]为什么复制构造函数的参数需要加const和引用 一.引言 1.0在解答这个问题之前,我们先跑个小程序,看下调用关系. #include <iostream> using namesp ...

  5. 设计模式 | 模板方法模式(template method)

    定义: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤. 结构:(书中图,侵删) 一个定义整体框架的父类 若干不同具体实现 ...

  6. [ArcGIS API for JavaScript 4.8] Sample Code-Get Started-popups简介

    [官方文档:https://developers.arcgis.com/javascript/latest/sample-code/intro-popup/index.html] 一.Intro to ...

  7. Android Service与Activity的交互

    Android中有时候需要在Service中改变Activity的UI,或者在Activity中修改Service中的数值.首先必须使用与Activity绑定的Service,有三种方式可以实现.第一 ...

  8. java.lang.UnsatisfiedLinkError:dlopen failed: “**/*/arm/*.so” has unexpected e_machine: 3

    转载请标明出处,维权必究:https://www.cnblogs.com/tangZH/p/10458448.html 今天在做APP的时候使用so库,可结果一加载so库的时候便发生了这个莫名其妙的错 ...

  9. 2017-12-09 JavaScript实现ZLOGO子集: 测试用例

    续前文JavaScript实现ZLOGO子集: 前进+转向. 在添加新功能之前, 先添加测试用例, 以应对日益复杂的代码. 选择使用QUnit编写运行测试用例. 暂时对比较复杂和I/O无关的部分进行测 ...

  10. SQL内模糊查询语句拼接时单引号'问题

    下面以存储过程查询所有为例,非存储过程(或不是查询所有将*替换为你想要查询的列即可)更为简单, 语法:select * from 表名 where 列名like'%条件%' 拼接后的set @变量名 ...