机器学习——详解KD-Tree原理

本文介绍了机器学习中的KD-Tree数据结构,从线段树概念出发,阐述了KD-Tree的定义、建树过程以及如何进行快速批量查询。通过KD-Tree,可以在K维空间中实现高效查询,尤其适用于优化KNN算法。同时讨论了KD-Tree的优化方法,包括使用快速选择减少建树时的排序成本,以及用堆优化查询性能。最后预告了插入和删除操作对KD-Tree的影响。

本文始发于个人公众号:TechFlow,原创不易,求个关注


今天是机器学习的第15篇文章,之前的文章当中讲了Kmeans的相关优化,还讲了大名鼎鼎的EM算法。有些小伙伴表示喜欢看这些硬核的,于是今天上点硬菜,我们来看一个机器学习领域经常用到的数据结构——KD-Tree

从线段树到KD树

在讲KD树之前,我们先来了解一下线段树的概念。线段树在机器学习领域当中不太常见,作为高性能维护的数据结构,经常出现在各种算法比赛当中。线段树的本质是一棵维护一段区间的平衡二叉树。

比如下图就是一个经典的线段树:

从下图当中我们不难看出来,这棵线段树维护的是一个区间内的最大值。比如树根是8,维护的是整个区间的最大值,每一个中间节点的值都是以它为树根的子树中所有元素的最大值。

通过线段树,我们可以在O(logN)O(logN)O(logN)的时间内计算出某一个连续区间的最大值。比如我们来看下图:

当我们要求被框起来的区间中的最大值,我们只需要找到能够覆盖这个区间的中间节点就行。我们可以发现被红框框起来的两个节点的子树刚好覆盖这个区间,于是整个区间的最大值,就是这两个元素的最大值。这样,我们就把一个需要O(n)O(n)O(n)查找的问题降低成了log(N)log(N)log(N),不但如此,我们也可以做到O(logN)O(logN)O(logN)复杂度内的更新,也就是说我们不但可以快速查询,还可以更新线段当中的元素。

当然线段树的应用非常广泛,也有许多种变体,这里我们不过多深入,感兴趣的同学可以期待一下周三的算法与数据结构专题,在之后的文章当中会为大家分享线段树的相关内容。在这里,我们只需要有一个大概的印象,线段树究竟完成的是什么样的事情即可。

线段树维护的是一个线段,也就是区间内的元素,也就是说维护的是一个一维的序列。如果我们将数据的维度扩充一下,扩充到多维呢?

是的,你没有猜错,从某种程度上来说,我们可以把KD-Tree看成是线段树拓展到多维空间当中的情况。

KD-Tree定义

我们来看一下KD-Tree的具体定义,这里的K指的是K维空间,D自然就是dimension,也就是维度,也就是说KD-Tree就是K维度树的意思。

在我们构建线段树的时候,其实是一个递归的建树过程,我们每次把当前的线段一分为二,然后用分成两半的数据分别构建左右子树。我们可以简单写一下伪代码,来更直观地感受一下:

class Node:
    def __init__(self, value, lchild, rchild):
        self.value = value
        self.lchild = lchild
        self.rchild = rchild   
        
def build(arr):
    n = len(arr):
    left, right = arr[: n//2], arr[n//2:]
    lchild, rchild = self.build(left), self.build(right)
    return Node(max(lchild.value, rchild.value), lchild, rchild)

我们来看一个二维的例子,在一个二维的平面当中分布着若干个点。

我们首先选择一个维度将这些数据一分为二,比如我们选择x轴。我们对所有数据按照x轴的值排序,选出其中的中点进行一分为二。

在这根线左右两侧的点被分成了两棵子树,对于这两个部分的数据来说,我们更换一个维度,也就是选择y轴进行划分。一样,我们先排序,然后找到中间的点,再次一分为二。我们可以得到:

我们重复上述过程,一直将点分到不能分为止,为了能更好地看清楚,我们对所有数据标上坐标(并不精确)。

如果我们

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值