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)回溯到根节点,同样比较根节点在当前分割维度上的数据与目标节点在分割维度上的数据间的距离小于当前最小距离,则需要查找根节点的另一边子树,否则直接退出。
整体思想:二分查找+回溯。
**细节点:**回溯到某个节点,比较该节点和目标节点之间的距离时并不是计算欧氏距离(其实使用欧氏距离也可以),而是在当前分割维度上的数据之差,这是因为多维数据进行二分查找分割时,使用的是平行于某个坐标轴的超平面,**每次判断是否查找另一边,是通过以目标节点为圆心,当前最小距离为半径做圆,判断当前分割面是否和该圆相交,**如果不相交,那么说明该分割面另一边的点距离目标节点都更远,则不需要再查找;若相交,则说明分割面另一边可能存在距离目标节点更近的点,则需要查找另一边。
【精选】KD-Tree详解: 从原理到编程实现_白鸟无言的博客-CSDN博客
【量化课堂】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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
5e862fe4e9.png)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
[外链图片转存中…(img-8zvFRLMs-1712425046322)]