k_d tree其实就是多维二叉树(空间二叉树的一种特殊情况), 里面储存着k维的点的信息,是对k维空间进行划分的一种数据结构。本文看了博客KD-Tree详解: 从原理到编程实现_kdtree ts实现-CSDN博客,这篇博客写的很详细,只是这里对这篇博客进行了扩展。
在点云操作中,常常需要从大量点云中找到距离输入点最近的点,如果使用线性搜索,逐个判断与输入点的距离,那么搜寻耗时将会与点云规模呈线性上升趋势,时间复杂度为O ( N ) O(N)O(N)。显然这不是我们想看到的,在数据结构与算法中,对一维数据常用的搜索方法是二分查找,时间复杂度为O(logN),它本质上是构建一棵二叉搜索树,以空间换取时间。而KD-Tree(K-Dimension Tree),也就做K维树,则可以看做是二叉树在K维空间的扩展,它的搜索效率也能近似达到O(logN)。
1.k_d tree要解决的问题
假如有有如下的7个一维数据:
假如我们要在X中找与2.5距离小于3.2的所有元素。那么我们可以用常规的思路解决这个问题。比如:
(1)对X中每个元素与2.5求距离,将所有距离小于3.2的元素提取出来,从而就解决了上面的问题;
(2)我们首先排序,比如进行升序排序,再用二分查找找到离2.5距离最近的元素,再从这个元素两边分别遍历,直到遇到与2.5距离大于等于3.2的元素则遍历终止。
显然,方法(1)只需要每个元素与2.5做两次计算即可(一次差值计算,一次与3.2的大小比较计算),即需要计算14次即可解决问题;方法(2)首先做一次排序,然后用二分查找找到离2.5最近的元素,再遍历距离小于3.2的元素即可(不是遍历所有元素)。如果7个数没有区别,假如有几万个数据或者几十万个数据呢,方法(2)的优势就很明显了。
k_d tree其实就是运用二分查找法的原理构造数据结构,以方便查找近邻数据、查找限定条件的数据等,数据量越大,这个数据结构的优势越明显,经常用于比近邻算法、DBSCAN算法等,尤其是点云数据的处理中。
2.一维数据的k_d tree构造
我们再以上面的数据为例构造一个一维数据的二叉树。假设有如下的7个一维数据:
得到的二叉树结构如下:
观察这个树结构很容易发现它的规律,它的构造步骤如下:
(1)将以上数据排序,如升序排序后:X = [1, 2, 3, 4, 5, 6, 7];
(2) 然后取X的最中间的值作为二叉树最顶端的值(如上图最顶端的值为4);
(3)其实步骤(2)将X分为左半部分 [1, 2, 3] 和右半部分 [5, 6, 7] ,再将这两个列表分别做步骤二的处理,得到两个数列最中间的值分别是2和6,分别放在4下面的左边和右边,就得到如下图:
(4)经过步骤(3)后,列表[1, 2, 3]又被分为[1]和[3]两部分,数列[5, 6, 7] 又被分为[5]和[7]两部分,对两部分重复上面的步骤即可得到下图:
在这里,对于一维数据的二叉树的构造就说完了,那么,假如我们构造一个如下列表的二叉树呢
X = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
结果如下:
通过这两个例子大家能找到规律了吧,这个结构的每个节点的离它距离较近的点都在它的附件节点上。
python代码如下:
class Node:
def __init__(self, value, left, right):
self.value = value
self.left = left
self.right = right
def make_tree(data):
if len(data)==0:
return None
mid = len(data) // 2 # 取出中点
data.sort() # 按照升序排列
node = Node(
data[mid],
make_tree(data[0:mid - 1]), # 左子树
make_tree(data[mid + 1: len(data)]) # 右子树
)
return node
if __name__ == '__main__':
data = [3, 6, 5, 2, 4, 1, 7]
node = make_tree(data)
print(node.value)
运行结果如下:
也可以打印子树如下:
print(node.right.right.value)
运行结果如下:
2.二维数据的k_d tree构造
假设有如下由七个二维数据组成的二维数据集:
与一维数据类似,先进行排序,然后交叉进行构造数据结构如下:
python代码如下:
from operator import itemgetter
class Node:
def __init__(self, value, left, right):
self.value = value
self.left = left
self.right = right
def kd_tree(points, depth):
if 0 == len(points):
return None
cutting_dim = depth % len(points[0])
medium_index = len(points) // 2
points.sort(key=itemgetter(cutting_dim))
node = Node(points[medium_index],
kd_tree(points[:medium_index], depth + 1),
kd_tree(points[medium_index + 1:], depth + 1)
)
return node
if __name__ == '__main__':
points = [(3, 7), (2, 6), (0, 5), (1, 8), (7, 5), (5, 4), (6, 7)]
depth = 2
node = kd_tree(points, depth)
print(node.value)
print(node.left.value)
print(node.right.left.value)
运行结果如下: