KD-Tree的原理及其在KNN中的应用(附Python代码(1)

l

o

g

N

)

O(logN)

O(logN)。

三、KD-Tree

3.1 对KD-Tree的理解

对于多维数据,可以使用二叉树在 K 维(在激光雷达中,一般使用三维点云,所以KD-Tree的维度K=3)空间上的扩展 KD-Tree,它的时间复杂度也能近似达到

O

(

l

o

g

N

)

O(logN)

O(logN),实际上它的时间复杂度介于

O

(

l

o

g

N

)

O(logN)

O(logN)和

O

(

N

)

O(N)

O(N)之间。**KD-Tree本质上是一种特殊的数据结构——基于空间的平衡二叉树。**KD-Tree是每个节点都有k维数据的平衡二叉树,每个节点代表一个超平面,该超平面垂直于当前划分维度的坐标轴,并在该维度上将空间划分为两部分。KD-Tree两个关键问题:① 树的建立;②最近邻域搜索。

KD-Tree和二叉搜索树的不同点在于,二叉搜索树每个节点只有一维特征,所以构建二叉搜索树时只需要根据这一维数据进行划分即可;对于多维数据,KD-Tree的划分策略是交替地使用每一维特征进行划分。KD-Tree会将三维空间分割成下图形式:

**KD-Tree本质上就是一种数据结构,**它的优点:① 搜索效率高;② 它是自平衡的,所以插入、删除数据也能保持高效;③ 易于实现。
在这里插入图片描述

3.2 生成KD-Tree

(1)初始化树深为

d

e

p

t

h

=

0

depth=0

depth=0和KD-Tree维度

K

K

K为数据维度;

(2)计算当前划分维度

s

p

l

i

t

=

d

e

p

t

h

%

K

split=depth,%,K

split=depth%K,对当前维度数据进行排序并取其中位数;

(3)将该中位数作为分割点,并将其所在数据对作为当前根节点;

(4)将该维度数据小于该中位数的数据对传给当前根节点的左子树,将该维度数据大于该中位数的数据对传给当前根节点的右子树;

(5)递归执行步骤(2)~(4),直到所有数据都被建立到KD-Tree的节点上为止。

3.3 最近邻搜索

(1)从根节点出发进行查找,根据树深计算当前的分割维度split,若目标结点在分割维度上的数据小于当前节点,则进入左子树遍历,否则进入右子树遍历;

(2)重复步骤一,直到找到叶子节点,记录当前目标节点和当前节点的距离为最小距离,当前节点为最近节点,并开始回溯;

(3)回溯过程中若当前节点与目标节点距离更近,则更新最近节点及最小距离;并且判断是否需要遍历另一边子树:若当前节点在分割维度上的数据与目标节点在分割维度上的数据间的距离小于当前最小距离,则进入另一边查找,否则向上回溯;

(4)回溯到根节点,同样比较根节点在当前分割维度上的数据与目标节点在分割维度上的数据间的距离小于当前最小距离,则需要查找根节点的另一边子树,否则直接退出。

整体思想:二分查找+回溯。

**细节点:**回溯到某个节点,比较该节点和目标节点之间的距离时并不是计算欧氏距离(其实使用欧氏距离也可以),而是在当前分割维度上的数据之差,这是因为多维数据进行二分查找分割时,使用的是平行于某个坐标轴的超平面,**每次判断是否查找另一边,是通过以目标节点为圆心,当前最小距离为半径做圆,判断当前分割面是否和该圆相交,**如果不相交,那么说明该分割面另一边的点距离目标节点都更远,则不需要再查找;若相交,则说明分割面另一边可能存在距离目标节点更近的点,则需要查找另一边。
在这里插入图片描述

Python手撸机器学习系列(十一):KNN之kd树实现

【精选】KD-Tree详解: 从原理到编程实现_白鸟无言的博客-CSDN博客

最近邻搜索 KD树 生动图示理解笔记

【量化课堂】kd 树算法之详细篇 - JoinQuant量化课堂 - JoinQuant

(批量查询参考:机器学习——详解KD-Tree原理

3.4 Python代码

import numpy as np
import matplotlib.pyplot as plt

创建Node类型

class Node(object):
def init(self, value, left=None, right=None):
self.val = value
self.left = left
self.right = right

创建KDTree类型

class KDTree(object):
def init(self, K):
self.K = K

建树

def build_tree(self, data, depth):
l = len(data)
if l == 0:
return None

split = depth % self.K # 当前分割维度
sorted_data = sorted(data, key=lambda x: x[split]) # 按照某个维度数据进行排序
mid_idx = l // 2
left_data = sorted_data[:mid_idx]
right_data = sorted_data[mid_idx+1:] # mid_idx已经作为当前层根节点了,所以不能加mid_idx这个数据对
cur_node = Node(sorted_data[mid_idx]) # 将中位数作为当前根节点
cur_node.left = self.build_tree(left_data, depth + 1) # 递归构建当前根节点左子树
cur_node.right = self.build_tree(right_data, depth + 1) # 递归构建当前根节点右子树

return cur_node

计算欧氏距离

def cal_dis(self, point1, point2):
return np.linalg.norm(np.array(point1) - np.array(point2))

最近邻搜索(1个target)

def search_nn(self, tree, target):
self.near_dis = None
self.near_point = None

def dfs(node, depth):
if not node:
return

第一步要先找到叶子节点,之后再进行回溯

split = depth % self.K
if target[split] < node.val[split]:
dfs(node.left, depth + 1) # 该行包括下面的dfs行,第一个参数输入都是某个节点node.left或node.right,所以本质上该节点node并没有改变,每次改变的都是函数输入参数,所以当dfs递归结束并回退到该位置时,该节点没有变化,以此实现回溯上一个节点的过程!
else:
dfs(node.right, depth + 1)

======= 到这结束就已经找到了某个路径下最终叶子节点 =======

开始回溯,以当前叶子节点和目标节点的距离作为初始的最小距离

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img



既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
img

5e862fe4e9.png)

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
[外链图片转存中…(img-8zvFRLMs-1712425046322)]

  • 28
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
kdtree是一种数据结构,用于解决k近邻问题。可以方便地找到与给定点最近的k个点。 Python实现kdtree构建的过程可以分为以下几步: 1.定义树结点,包括坐标、分裂维度、左右子树 class KdNode(object): def __init__(self, coordinate=None, split=None, left=None, right=None): self.coordinate = coordinate self.split = split self.left = left self.right = right 2.定义分裂方法,按照轴的坐标进行划分,并返回分裂后的左右子树以及分裂维度。 def divide_tree(nodes, depth=0): if not nodes: return None, None, 0 dim = len(nodes[0]) #维度 axis = depth % dim #分裂维度 nodes.sort(key=lambda x: x[axis]) #按照轴的坐标排序 mid = len(nodes) // 2 left = nodes[:mid] right = nodes[mid+1:] node = KdNode(nodes[mid], axis) node.left = divide_tree(left, depth+1) node.right = divide_tree(right, depth+1) return node, node.left, node.right, axis 3.定义k近邻搜索方法,传入当前根节点、目标点和查找的k值,返回最近的k个点 import heapq def knn_search(root, target, k): heap = [] #使用堆来存储最近的k个点 def travel(node): if node: dist = sum((node.coordinate[i]-target[i])**2 for i in range(len(target))) if len(heap) < k: heapq.heappush(heap, (-dist, node.coordinate)) else: if dist < -heap[0][0]: heapq.heappop(heap) heapq.heappush(heap, (-dist, node.coordinate)) split = node.split #分裂维度 if target[split] <= node.coordinate[split]: travel(node.left) else: travel(node.right) travel(root) return [heapq.heappop(heap)[1] for _ in range(k)][::-1] 这样kdtree构建实现knnpython代码就完成了,具体使用时可以将数据集作为一个二维数组传入divide_tree()函数,返回根节点并保存,用knn_search()函数查找最近的k个点。kdtree能有效优化k近邻的搜索时间,并且在高维数据集表现更为出色,值得我们掌握。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值