3

我正在尝试构建 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; 
 }
}
4

4 回答 4

5

找到中位数的排序可能是这里最糟糕的罪魁祸首,因为这是 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

于 2011-02-15T12:16:36.217 回答
3

不是你的问题的真正答案,但我强烈推荐http://ompf.org/forum/上的论坛, 他们在那里有一些关于在各种情况下快速构建 kd-tree 的精彩讨论。也许你会在那里找到一些灵感。

编辑:
OMPF 论坛已经关闭,尽管目前可以在http://ompf2.com/上找到直接替代品

于 2010-11-17T10:43:10.787 回答
3

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

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

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

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

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

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

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

于 2015-05-09T03:28:14.450 回答