最近邻居 - k-d 树 - ***证明

Posted

技术标签:

【中文标题】最近邻居 - k-d 树 - ***证明【英文标题】:nearest neighbor - k-d tree - wikipedia proof 【发布时间】:2010-12-10 06:47:43 【问题描述】:

在wikipedia entry for k-d trees 上,提出了一种算法,用于在 k-d 树上进行最近邻搜索。我不明白的是步骤3.2的解释。仅仅因为搜索点的分裂坐标与当前节点的差值大于搜索点的分裂坐标与当前最佳点的差值,你怎么知道没有更近的点呢?

最近邻搜索动画 使用二维 KD 树进行 NN 搜索

最近邻(NN)算法 旨在找到树中的点 最接近给定输入 观点。这个搜索可以完成 有效地使用树 属性快速消除大 部分搜索空间。 在 a 中寻找最近的邻居 kd-tree 进行如下:

    从根节点开始,算法向下移动 递归地,以同样的方式 如果搜索点是 插入(即它向右或向左 取决于点是否 大于或小于当前节点 在分割维度中)。 一旦算法到达叶节点,它就会将该节点点保存为 “当前最佳” 算法展开树的递归,执行 在每个节点执行以下步骤: 1.如果当前节点比当前最佳节点更接近,那么它 成为当前最好的。 2.算法检查是否有任何点 分裂面的另一边 离搜索点更近的 比目前最好的。在概念上, 这是通过相交 用 a 分割超平面 搜索点周围的超球面 其半径等于当前 最近的距离。由于 超平面都是轴对齐的 被实现为一个简单的比较 看看有没有区别 搜索的分割坐标 点和当前节点小于 距离(整体坐标) 从搜索点到当前 最好的。 1. 如果超球面穿过平面,可能有 另一侧较近的点 平面,所以算法必须向下移动 树的另一个分支 当前节点寻找更接近 点,遵循相同的递归 过程作为整个搜索。 2. 如果超球面不与分割平面相交, 然后算法继续走 爬上树,整个树枝都在 该节点的另一侧是 被淘汰。 当算法完成根节点的这个过程时,然后 搜索完成。

一般算法使用平方 避免比较的距离 计算平方根。此外, 它可以通过保持 平方当前最佳距离 用于比较的变量。

【问题讨论】:

检查这个***.com/a/57490663/1029599 它提供了一个算法——用非常清晰的术语和一个很好的解释 【参考方案1】:

仔细看animation on that page的第6帧。

当算法返回递归时,它所在的超平面的另一侧可能有一个更接近的点。我们已经检查了一半,但另一半可能还有更接近的点。

好吧,事实证明我们有时可以进行简化。如果另一半的点不可能比我们当前的最佳(最近)点更近,那么我们可以完全跳过该超平面的一半。这种简化是在第 6 帧中显示的。

通过比较从超平面到我们的搜索位置的距离来确定这种简化是否可行。因为超平面与轴对齐,所以从它到任何其他点的最短线将是一条沿一个维度的线,因此我们可以只比较超平面分割的维度的坐标。

如果从搜索点到超平面的距离比从搜索点到当前最近点的距离更远,那么就没有理由越过该分割坐标进行搜索。

即使我的解释没有帮助,图形也会。祝你的项目好运!

【讨论】:

这是理解算法的缺失环节。似乎其他解释都不需要时间来解释简化步骤(或者他们只是顺便提一下)。【参考方案2】:

是的,Wikipedia 上关于 KD 树中 NN(最近邻)搜索的描述有点难以理解。 很多在 NN KD Tree 搜索中的*** Google 搜索结果完全是错误的,这无济于事!

这里有一些 C++ 代码向您展示如何正确处理它:

template <class T, std::size_t N>
void KDTree<T,N>::nearest (
    const const KDNode<T,N> &node,
    const std::array<T, N> &point, // looking for closest node to this point
    const KDPoint<T,N> &closest,   // closest node (so far)
    double &minDist,
    const uint depth) const

    if (node->isLeaf()) 
        const double dist = distance(point, node->leaf->point);
        if (dist < minDist) 
            minDist = dist;
            closest = node->leaf;
        
     else 
        const T dim = depth % N;
        if (point[dim] < node->splitVal) 
            // search left first
            nearest(node->left, point, closest, minDist, depth + 1);
            if (point[dim] + minDist >= node->splitVal)
                nearest(node->right, point, closest, minDist, depth + 1);
         else 
            // search right first
            nearest(node->right, point, closest, minDist, depth + 1);
            if (point[dim] - minDist <= node->splitVal)
                nearest(node->left, point, closest, minDist, depth + 1);
        
    

在 KD 树上进行 NN 搜索的 API:

// Nearest neighbour
template <class T, std::size_t N>
const KDPoint<T,N> KDTree<T,N>::nearest (const std::array<T, N> &point) const 
    const KDPoint<T,N> closest;
    double minDist = std::numeric_limits<double>::max();
    nearest(root, point, closest, minDist);
    return closest;

默认距离函数:

template <class T, std::size_t N>
double distance (const std::array<T, N> &p1, const std::array<T, N> &p2) 
    double d = 0.0;
    for (uint i = 0; i < N; ++i) 
        d += pow(p1[i] - p2[i], 2.0);
    
    return sqrt(d);

编辑:有些人也在寻求数据结构方面的帮助(不仅仅是 NN 算法),所以这就是我使用的。根据您的目的,您可能希望稍微修改数据结构。 (注意:但您几乎可以肯定想要修改 NN 算法。)

KDPoint 类:

template <class T, std::size_t N>
class KDPoint 
    public:
        KDPoint<T,N> (std::array<T,N> &&t) : point(std::move(t))  ;
        virtual ~KDPoint<T,N> () = default;
        std::array<T, N> point;
;

KDNode 类:

template <class T, std::size_t N>
class KDNode

    public:
        KDNode () = delete;
        KDNode (const KDNode &) = delete;
        KDNode & operator = (const KDNode &) = delete;
        ~KDNode () = default;

        // branch node
        KDNode (const T                       split,
                std::unique_ptr<const KDNode> &lhs,
                std::unique_ptr<const KDNode> &rhs) : splitVal(split), left(std::move(lhs)), right(std::move(rhs))  ;
        // leaf node
        KDNode (std::shared_ptr<const KDPoint<T,N>> p) : splitVal(0), leaf(p)  ;

        bool isLeaf (void) const  return static_cast<bool>(leaf); 

        // data members
        const T                                   splitVal;
        const std::unique_ptr<const KDNode<T,N>>  left, right;
        const std::shared_ptr<const KDPoint<T,N>> leaf;
;

KDTree 类:(注意:您需要添加一个成员函数来构建/填充您的树。)

template <class T, std::size_t N>
class KDTree 
    public:
        KDTree () = delete;
        KDTree (const KDTree &) = delete;
        KDTree (KDTree &&t) : root(std::move(const_cast<std::unique_ptr<const KDNode<T,N>>&>(t.root)))  ;
        KDTree & operator = (const KDTree &) = delete;
        ~KDTree () = default;

        const KDPoint<T,N> nearest (const std::array<T, N> &point) const;

        // Nearest neighbour search - runs in O(log n)
        void nearest (const std::unique_ptr<const KDNode<T,N>> &node,
                      const std::array<T, N> &point,
                      std::shared_ptr<const KDPoint<T,N>> &closest,
                      double &minDist,
                      const uint depth = 0) const;

        // data members
        const std::unique_ptr<const KDNode<T,N>> root;
;

【讨论】:

我的 C++ 有点粗糙,但我认为您在这里遗漏了一些重要的代码。没有 KDNode 或 KDPoint 的定义。 distance(point, node-&gt;leaf-&gt;point); 我想这也会用该子区域中的所有点填充数组点?你能详细说明一下吗? @Project:问题是关于 NN 算法,但我添加了有关数据结构的信息,使其成为一个过于全面的答案。 :) @Axl: distance() 只是两点之间的距离。我编辑了答案以包含我的默认实现。希望这个简单但关键的概念现在更清楚了吗? @ScottSmedley 谢谢,我发现额外的代码很有帮助,对树结构不是很熟悉。

以上是关于最近邻居 - k-d 树 - ***证明的主要内容,如果未能解决你的问题,请参考以下文章

k-d树

使用树来加速具有周期性边界条件的 3D 阵列上的最近邻搜索

如何从 k-d 树实现 K-NN 分类?

最近邻搜索kdTree

高维数据中的最近邻?

点的第 k 个最近邻居的空间查询