KD дерево, медленное построение дерева

Я пытаюсь построить дерево 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; 
 }
}

person Ian    schedule 17.11.2010    source источник
comment
Как определяется тип KDList?   -  person Björn Pollex    schedule 17.11.2010
comment
@Space: typedef std::vector‹Point2D› KDList;   -  person Ian    schedule 17.11.2010
comment
И как определяется Points2DList?   -  person Björn Pollex    schedule 17.11.2010
comment
@Space Подобно KDList, но хранит некоторые топологические отношения. Поэтому элементы перемещаются в KDList.   -  person Ian    schedule 17.11.2010


Ответы (4)


Сортировка для нахождения медианы, вероятно, является худшим виновником здесь, поскольку это O (nlogn), в то время как проблема решается за время O (n). Вместо этого следует использовать nth_element: http://www.cplusplus.com/reference/algorithm/nth_element/ . Это найдет медиану в среднем за линейное время, после чего вы сможете разделить вектор за линейное время.

Управление памятью в векторе также может занять много времени, особенно с большими векторами, поскольку каждый раз, когда размер вектора удваивается, все элементы должны перемещаться. Вы можете использовать метод резервирования вектора, чтобы зарезервировать достаточно места для векторов во вновь созданных узлах, поэтому они не должны динамически увеличиваться по мере добавления нового материала с помощью push_back.

И если вам абсолютно необходима лучшая производительность, вы должны использовать код более низкого уровня, отказавшись от векторов и вместо этого зарезервировав простые массивы. Алгоритмы N-го элемента или «выборки» легко доступны, и их не так сложно написать самостоятельно: http://en.wikipedia.org/wiki/Selection_algorithm

person Olli Etuaho    schedule 15.02.2011

Несколько советов по оптимизации kd-дерева:

  • Используйте линейный алгоритм нахождения медианного времени, такой как QuickSelect.
  • Избегайте фактического использования объектов "node". Вы можете хранить целое дерево, используя только точки, с НУЛЕВОЙ дополнительной информацией. По сути, просто сортируя массив объектов. Тогда корневой узел окажется посередине. Перегруппировка, при которой сначала ставится корень, а затем используется расположение кучи, скорее всего, будет лучше для кеша памяти ЦП во время запроса, но ее будет сложнее построить.
person Has QUIT--Anony-Mousse    schedule 01.01.2013

На самом деле это не ответ на ваши вопросы, но я настоятельно рекомендую форум по адресу http://ompf.org/forum/ Там ведутся отличные дискуссии о быстром построении дерева kd в различных контекстах. Возможно, вы найдете вдохновение там.

Редактировать:
С тех пор форумы OMPF закрылись, хотя прямая замена в настоящее время доступна по адресу http://ompf2.com/

person Bart    schedule 17.11.2010
comment
Как я уже сказал, вы, возможно, не найдете там прямого ответа. Но если вы активно участвуете в форумах и задаете там свой вопрос, вы, скорее всего, получите ответ, который поможет вам на вашем пути. Если и существует форум, на котором подробно обсуждаются деревья KD или другие иерархии, их свойства, методы быстрого построения и тому подобное, то это именно он. - person Bart; 18.11.2010
comment
@mkb Действительно. Я обновил ответ заменой форума Жакко Биккера в NHVT. - person Bart; 16.05.2012

Ваш первый виновник сортировки, чтобы найти медиану. Это почти всегда является узким местом для построения дерева K-d, и использование здесь более эффективных алгоритмов действительно окупится.

Однако вы также создаете пару векторов переменного размера каждый раз, когда разделяете и переносите в них элементы.

Здесь я рекомендую старый добрый односвязный список. Прелесть связанного списка в том, что вы можете передавать элементы от родителя к дочернему, просто изменяя указатели next так, чтобы они указывали на корневой указатель дочернего элемента, а не на родительский.

Это означает отсутствие накладных расходов на кучу во время построения для передачи элементов из родительских узлов в дочерние узлы, только для агрегирования начального списка элементов для вставки в корень. Это тоже должно творить чудеса, но если вы хотите еще быстрее, вы можете использовать фиксированный распределитель для эффективного выделения узлов для связанного списка (а также для дерева) и с лучшими попаданиями смежности/кеша.

И последнее, но не менее важное: если вы занимаетесь интенсивными вычислительными задачами, требующими деревьев K-d, вам нужен профилировщик. Измерьте свой код, и вы точно увидите, что лежит на виновнике, и с точным распределением времени.

person Community    schedule 09.05.2015