1. KD树原理讲解

KD树(k-dimensional tree)是一种用于高维空间搜索的数据结构,尤其用于给定查询点时搜索最近邻或其他范围搜索任务。与K近邻算法中的线性搜索相比,KD树可以显著加快许多空间搜索任务的速度。

K-D树是将K维空间中的点进行分割的数据结构,D是dimensional(维度)的缩写,K-D树是BSP(Binary Space Partitioning)的一种。

简单的说,K-D树就是K维空间的二分查找树,是二分查找树在K维空间的泛化。

还是有点抽象。举个例子:

我们都知道二分查找树(BST)这样基础的数据结构,它是基于二分查找的思想实现 O ( l o g 2 N ) O(log_2N) O(log2N)的插入和查找速度。

如果把BST中的所有元素看成一维线段上的所有点,从树的根节点开始每个节点都会把线段分成两段。

在这里插入图片描述

所以,BST本质上就是一维K-D树(或者叫做1-D树)。

现在将树中的元素推广到二维平面上的点,树的每一层按照维度轮流划分。比如,奇数层按x轴划分,偶数层按y轴划分,这样就得到一棵二维K-D树(2-d树):

在这里插入图片描述

同样我们可以将元素推广到三维空间中的点,那样我们可以得到一棵3d-tree。

在这里插入图片描述

理论上k-d树可以继续到4维,5维… 作为一个三维空间里的生物,就没法用图来展示这样的元素了😅.

这里有一个二维K-D树的模拟程序能帮助我们更好的理解K-D树:

在这里插入图片描述

任何事物都有好坏的地方,K-D树也是如此。

K-D树的优点:

很明显,K-D树和BST一样不仅可以精确查找,也更适合做范围查询,但K-D树比BST更强,它能对多个维度进行范围查询。

比如:

Person1(age:18,hight:176),Person2(age:19,height:181) … Person10(age:23,height:171)

想要查找年龄在20岁以上并且身高在170到180之间的所有人,用K-D树就能很好的解决。

在这里插入图片描述

由于K-D树的结构要求,上面的例子中要求每个人的年龄和身高各项数据必须齐全,如果某个人只有年龄或只有身高,就无法使用K-D树索引了。所以实际上K-D树更常见的应用是经纬度定位,或者三维空间定位(某个维度数据缺失,其他维度数据也就没有意义了)

缺点:

和二分查找树一样,K-D树也有平衡性问题。

高纬度数据查找效率并不一定好,有时候可能不如最原始的暴力查找。

KD树是一种二叉树,其中每个节点表示数据集中的一个点。KD树的构造过程包括:

  1. 依次选择各维度作为分割维度,将数据集排序;
  2. 选择排序后中间元素作为根节点;
  3. 根据分割平面,将数据分为两部分:一部分位于当前分割维度的左侧,另一部分位于右侧;
  4. 重复这个过程,直到所有数据点都被构造成树的各个节点。

在KD树结构中查找最近邻的方法是:

  1. 从根节点开始,不断向下查找,将查询点与子节点的分割平面进行比较,根据比较结果前进到相应的子树,直到达到叶子节点;
  2. 回溯树结构,寻找更短的距离;
  3. 当回溯结束后,返回最近的节点。

2. KD树的C语言实现

以下是一个简化的KD树C语言实现示例。这里的实现仅包括KD树的构建部分,要让它完整,还需要添加用于搜索最近邻的功能。

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data[2];
    struct Node* left;
    struct Node* right;
} Node;

int compare(int dim, const void* a, const void* b) {
    return ((int*)a)[dim] - ((int*)b)[dim];
}

Node* buildKDTree(int data[][2], int n, int depth) {
    if (n <= 0) return NULL;

    int dim = depth % 2;
    qsort(data, n, 2*sizeof(int), compare(dim, a, b));

    Node* node = (Node*)malloc(sizeof(Node));
    node->data[0] = data[n/2][0];
    node->data[1] = data[n/2][1];

    node->left = buildKDTree(data, n/2, depth + 1);
    node->right = buildKDTree(data + n/2 + 1, n - n/2 - 1, depth + 1);

    return node;
}

int main() {
    int points[][2] = {{3, 6}, {17, 15}, {13, 15}, {6, 12}, {9, 1}, {2, 7}, {10, 19}};
    int n = sizeof(points) / sizeof(points[0]);

    Node* root = buildKDTree(points, n, 0);

    return 0;
}

上述代码实现了KD树结构的构建,可以自行扩展,以实现最近邻查询的代码。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐