【问题标题】:KD tree, slow tree constructionKD树,建树慢
【发布时间】:2011-05-11 08:16:24
【问题描述】:

我正在尝试构建 KD 树(静态案例)。我们假设点在 x 和 y 坐标上都排序。

对于均匀的递归深度,该集合被分成两个子集,垂直线穿过中位 x 坐标。

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

可以根据x / y坐标从排序集中确定中位数。我在每次拆分集合之前执行此步骤。而且我认为它会导致树的构建缓慢。

  1. 请您帮我检查并优化代码吗?
  2. 我找不到第 k 个最近的邻居,有人可以帮我写代码吗?

非常感谢您的帮助和耐心...

请看示例代码:

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; 
 }
}

【问题讨论】:

  • 类型KDList是如何定义的?
  • @Space: typedef std::vector KDList;
  • Points2DList 是如何定义的?
  • @Space 和 KDList 一样,但是它存储了一些拓扑关系。因此项目在 KDList 上移动。

标签: c++ nearest-neighbor kdtree


【解决方案1】:

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

向量中的内存管理也可能需要很多时间,尤其是对于大型向量,因为每次向量的大小增加一倍时,所有元素都必须移动。您可以使用vector的reserve方法为新创建的节点中的vector预留足够的空间,因此它们不需要随着push_back添加新东西而动态增加。

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

【讨论】:

    【解决方案2】:

    关于优化 kd-tree 的一些提示:

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

    【讨论】:

      【解决方案3】:

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

      编辑:
      OMPF 论坛自此下线,但目前可通过http://ompf2.com/ 获得直接替代品

      【讨论】:

      • 就像我说的,你可能不会直接在那里找到答案。但是,如果您积极参与论坛并在那里提出您的问题,那么您很可能会得到回复以帮助您。如果有一个论坛详细讨论 KD 树或其他层次结构、它们的属性、快速构建方法等,那就是那个。
      • @mkb 确实如此。我已经用 NHVT 的 Jacco Bikker 的替换论坛更新了答案。
      【解决方案4】:

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

      但是,每次您将元素拆分并传输给它们时,您也会构建一对可变大小的向量。

      在这里,我推荐好的 ol' 单链表。链表的美妙之处在于,您只需将next 指针更改为指向子节点的根指针而不是父节点的根指针,就可以将元素从父节点传递到子节点。

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

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

      【讨论】:

        猜你喜欢
        • 2016-09-22
        • 2022-10-15
        • 2017-08-09
        • 1970-01-01
        • 2012-11-09
        • 2011-05-18
        • 2011-07-27
        • 2012-10-16
        • 2012-06-27
        相关资源
        最近更新 更多