KD树,慢树建设

时间:2010-11-17 10:19:57

标签: c++ nearest-neighbor kdtree

我正在尝试构建KD Tree(静态情况)。我们假设点在x和y坐标上排序。

对于偶数递归深度,该集合被分成两个子集,垂直线穿过中间x坐标。

对于奇数递归深度,该集合被分成两个子集,水平线穿过中间y坐标。

可以根据x / y坐标从排序集确定中值。这一步我在每次拆分之前做的。我认为这会导致树的构造缓慢。

  1. 你能帮我检查一下并优化代码吗?
  2. 我找不到第k个最近的邻居,有人可以帮我提供代码吗?
  3. 非常感谢你的帮助和耐心......

    请参阅示例代码:

    class KDNode
    {
    private:
    Point2D *data;
    KDNode *left;
    KDNode *right;
        ....
    };
    
    void KDTree::createKDTree(Points2DList *pl)
    {
    //Create list
    KDList kd_list;
    
    //Create KD list (all input points)
    for (unsigned int i = 0; i < pl->size(); i++)
    {
    kd_list.push_back((*pl)[i]);
    }
    
    //Sort points by x
    std::sort(kd_list.begin(), kd_list.end(), sortPoints2DByY());
    
    //Build KD Tree
    root = buildKDTree(&kd_list, 1);
    }
    
    
    KDNode * KDTree::buildKDTree(KDList *kd_list, const unsigned int depth)
    {
    //Build KD tree
    const unsigned int n = kd_list->size();
    
     //No leaf will be built
     if (n == 0)
     {
      return NULL;
     }
    
     //Only one point: create leaf of KD Tree
     else if (n == 1)
     {
      //Create one leaft
      return new KDNode(new Point2D ((*kd_list)[0]));
     }
    
     //At least 2 points: create one leaf, split tree into left and right subtree
     else
     {
      //New KD node
      KDNode *node = NULL;
    
      //Get median index
      const unsigned int median_index = n/2;
    
      //Create new KD Lists
      KDList kd_list1, kd_list2;
    
      //The depth is even, process by x coordinate
      if (depth%2 == 0)
      {
       //Create new median node
       node = new KDNode(new Point2D( (*kd_list)[median_index]));
    
       //Split list
       for (unsigned int i = 0; i < n; i++)
       {
        //Geta actual point
        Point2D *p = &(*kd_list)[i];
    
        //Add point to the first list: x < median.x
        if (p->getX() < (*kd_list)[median_index].getX())
        {
         kd_list1.push_back(*p);
        }
    
        //Add point to the second list: x > median.x
        else if (p->getX() > (*kd_list)[median_index].getX())
        {
         kd_list2.push_back(*p);
        }
       }
    
       //Sort points by y for the next recursion step: slow construction of the tree???
       std::sort(kd_list1.begin(), kd_list1.end(), sortPoints2DByY());
       std::sort(kd_list2.begin(), kd_list2.end(), sortPoints2DByY());
    
      }
    
      //The depth is odd, process by y coordinates
      else
      {
    
       //Create new median node
       node = new KDNode(new Point2D((*kd_list)[median_index]));
    
       //Split list
       for (unsigned int i = 0; i < n; i++)
       {
        //Geta actual point
        Point2D *p = &(*kd_list)[i];
    
        //Add point to the first list: y < median.y
        if (p->getY() < (*kd_list)[median_index].getY())
        {
         kd_list1.push_back(*p);
        }
    
        //Add point to the second list: y < median.y
        else if (p->getY() >(*kd_list)[median_index].getY())
        {
         kd_list2.push_back(*p);
        }
       }
    
       //Sort points by x for the next recursion step: slow construction of the tree???
       std::sort(kd_list1.begin(), kd_list1.end(), sortPoints2DByX());
       std::sort(kd_list2.begin(), kd_list2.end(), sortPoints2DByX());
    
      }
    
      //Build left subtree
      node->setLeft( buildKDTree(&kd_list1, depth +1 ) );
    
      //Build right subtree
      node->setRight( buildKDTree(&kd_list2, depth + 1 ) );
    
      //Return new node 
      return node; 
     }
    }
    

4 个答案:

答案 0 :(得分:5)

找到中位数的排序可能是这里最糟糕的罪魁祸首,因为那是O(nlogn),而问题在O(n)时间内是可解的。您应该使用nth_element:http://www.cplusplus.com/reference/algorithm/nth_element/。这将平均找到线性时间的中位数,之后您可以在线性时间内分割矢量。

向量中的内存管理也需要花费很多时间,特别是对于大向量,因为每次向量的大小加倍时,所有元素都必须移动。您可以使用向量的保留方法为新创建的节点中的向量保留足够的空间,因此在使用push_back添加新内容时,它们无需动态增加。

如果您绝对需要最佳性能,则应使用较低级别的代码,取消向量并保留普通数组。第N个元素或“选择”算法随时可用,并且不难写自己:http://en.wikipedia.org/wiki/Selection_algorithm

答案 1 :(得分:3)

不是您的问题的答案,但我强烈推荐论坛 http://ompf.org/forum/ 他们在那里进行了一些很好的讨论,以便在各种情况下快速进行kd-tree构造。也许你会在那里找到一些灵感。

修改
OMPF论坛已经失败,尽管目前可以在http://ompf2.com/

进行直接替换

答案 2 :(得分:2)

优化kd-tree的一些提示:

  • 使用线性时间中位数查找算法,例如QuickSelect。
  • 避免实际使用“节点”对象。您可以使用点存储整棵树,并使用ZERO附加信息。基本上只需对对象数组进行排序。然后根节点将位于中间。将root放在第一位,然后使用堆布局的重新排列可能会在查询时间内对CPU内存缓存更好,但构建起来会更棘手。

答案 3 :(得分:1)

你的第一个罪魁祸首是排序以找到中位数。这几乎总是K-d树构建的瓶颈,在这里使用更高效的算法真的会有所回报。

但是,每次分割和传递元素时,你也构建了一对可变大小的矢量。

在这里,我推荐好的单链表。链表的优点在于,您只需将next指针更改为指向子指针的根指针而不是父指针,即可将元素从父元素传输到子元素。

这意味着在构造期间没有任何堆开销将元素从父节点传输到子节点,只是聚合要插入到根节点的元素的初始列表。这也应该有奇迹,但如果你想要更快,你可以使用固定分配器有效地为链表(以及树)分配节点,并具有更好的连续性/缓存命中。

最后但同样重要的是,如果您参与了需要K-d树的密集型计算任务,那么您需要一个分析器。测量你的代码,你会看到罪魁祸首,确切的时间分布。