1. 原理篇
我们用大白话讲讲KD-Tree是怎么一回事。
1.1 线性查找
假设数组A为[0, 6, 3, 8, 7, 4, 11],有一个元素x,我们要找到数组A中距离x最近的元素,应该如何实现呢?比较直接的想法是用数组A中的每一个元素与x作差,差的绝对值最小的那个元素就是我们要找的元素。假设x = 2,那么用数组A中的所有元素与x作差得到[-2, 4, 1, 6, 5, 2, 9],其中绝对值最小的是1,对应的元素是数组A中的3,所以3就是我们的查找结果。
1.2 二分查找
如果我们有大量的元素要在数组A中进行查找,那么1.1的方式就显得不是那么高效了,如果数组A的长度为N,那么每次查找都要进行N次操作,即算法复杂度为O(N)。
1、我们把数组A进行升序排列,得到[0, 3, 4, 6, 7, 8, 11];
2、令x = 2,数组中间的元素是6,2小于6,所以2只可能存在于6的左边,我们只需要在数组[0, 3, 4]中继续查找;
3、左边的数组中间的元素是3,2小于3,所以2只可能存在于3的左边,即数组[0];
4、由于数组[0]无法再分割,查找结束; x需要跟我们最终找到的0,以及倒数第二步找到的3进行比较,发现2离3更近,所以查找结果为3。
这种查找方法就是二分查找,其算法复杂度为O(Log2(N))。
1.3 BST
除了数组之外,有没有更直观的数据结构可以实现1.2的二分查找呢?答案就是二分查找树,全称Binary Search Tree,简称BST。把数组A建立成一个BST,结构如下图所示。我们只需要访问根节点,进行值比较来确定下一节点,如此循环往复直到访问到叶子节点为止。
1.4 多维数组
现在我们把问题加点难度,假设数组B为[[6, 2], [6, 3], [3, 5], [5, 0], [1, 2], [4, 9], [8, 1]],有一个元素x,我们要找到数组B中距离x最近的元素,应该如何实现呢?比较直接的想法是用数组B中的每一个元素与x求距离,距离最小的那个元素就是我们要找的元素。假设x = [1, 1],那么用数组B中的所有元素与x求距离得到[5.0, 5.4, 4.5, 4.1, 1.0, 8.5, 7.0],其中距离最小的是1,对应的元素是数组B中的[1, 2],所以[1, 2]就是我们的查找结果。
1.5 再次陷入困境
如果我们有大量的元素要在数组B中进行查找,那么1.4的方式就又显得不是那么高效了,如果数组B的长度为N,那么每次查找都要进行N次操作,即算法复杂度为O(N)。
1.6 什么是KD-Tree
这时候已经没办法用BST,不过我们可以对BST做一些改变来适应多维数组的情况。当当当当~,这时候该KD-Tree出场了。废话不多说,先上图:
1.7 如何建立KD-Tree
您可能会问,刚在那张图的KD Tree又是如何建立的呢? 很简单,只需要5步:
-
建立根节点;
-
选取方差最大的特征作为分割特征;
-
选择该特征的中位数作为分割点;
-
将数据集中该特征小于中位数的传递给根节点的左儿子,大于中位数的传递给根节点的右儿子;
-
递归执行步骤2-4,直到所有数据都被建立到KD Tree的节点上为止。
不难看出,KD Tree的建立步骤跟BST是非常相似的,可以认为BST是KD Tree在一维数据上的特例。KD Tree的算法复杂度介于O(Log2(N))和O(N)之间。
1.8 特征选取
您可能还会问,为什么方差最大的适合作为特征呢? 因为方差大,数据相对“分散”,选取该特征来对数据集进行分割,数据散得更“开”一些。
1.9 分割点选择
您可能又要问,为什么选择中位数作为分割点呢? 因为借鉴了BST,选取中位数,让左子树和右子树的数据数量一致,便于二分查找。
1.10 利用KD-Tree查找元素
KD Tree建好之后,接下来就要利用KD Tree对元素进行查找了。查找的方式在BST的基础上又增加了一些难度,如下:
-
从根节点开始,根据目标在分割特征中是否小于或大于当前节点,向左或向右移动。
-
一旦算法到达叶节点,它就将节点点保存为“当前最佳”。
-
回溯,即从叶节点再返回到根节点
-
如果当前节点比当前最佳节点更接近,那么它就成为当前最好的。
-
如果目标距离当前节点的父节点所在的将数据集分割为两份的超平面的距离更接近,说明当前节点的兄弟节点所在的子树有可能包含更近的点。因此需要对这个兄弟节点递归执行1-4步。
1.11 超平面
所以什么是超平面呢,听起来让人一脸懵逼。
以[0, 2, 0], [1, 4, 3], [2, 6, 1]的举例:
-
如果用第二维特征作为分割特征,那么从三个数据点中的对应特征取出2, 4, 6,中位数是4;
-
所以[1, 4, 3]作为分割点,将[0, 2, 0]划分到左边,[2, 6, 1]划分到右边;
-
从立体几何的角度考虑,三维空间得用一个二维的平面才能把空间一分为二,这个平面可以用y = 4来表示;
-
点[0, 2, 0]到超平面y = 4的距离就是 sqrt((2 - 4) ^ 2) = 2;
-
点[2, 6, 1]到超平面y = 4的距离就是 sqrt((6 - 4) ^ 2) = 2。