3 votes

Arbre KD, construction lente de l'arbre

J'essaie de construire un arbre KD (cas statique). Nous supposons que les points sont triés sur les coordonnées x et y.

Pour une profondeur de récursion égale, l'ensemble est divisé en deux sous-ensembles avec une ligne verticale passant par la coordonnée x médiane.

Pour une profondeur de récurrence impaire, l'ensemble est divisé en deux sous-ensembles avec une ligne horizontale passant par la coordonnée y médiane.

La médiane peut être déterminée à partir de l'ensemble trié selon les coordonnées x/y. Je fais cette étape avant chaque division de l'ensemble. Et je pense que c'est la cause de la lenteur de la construction de l'arbre.

  1. Pouvez-vous m'aider à vérifier et à optimiser le code ?
  2. Je n'arrive pas à trouver le k-ième plus proche voisin, quelqu'un pourrait-il m'aider avec le code ?

Merci beaucoup pour votre aide et votre patience...

Veuillez consulter l'exemple de code :

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

5voto

Olli Etuaho Points 246

Le tri pour trouver la médiane est probablement le pire coupable ici, puisque c'est O(nlogn) alors que le problème est soluble en O(n) temps. Vous devriez utiliser nth_element à la place : http://www.cplusplus.com/reference/algorithm/nth_element/ . Cela permettra de trouver la médiane en temps linéaire en moyenne, après quoi vous pourrez diviser le vecteur en temps linéaire.

La gestion de la mémoire dans les vecteurs est également quelque chose qui peut prendre beaucoup de temps, surtout avec les grands vecteurs, puisque chaque fois que la taille du vecteur est doublée, tous les éléments doivent être déplacés. Vous pouvez utiliser la méthode reserve de vector pour réserver exactement assez d'espace pour les vecteurs dans les noeuds nouvellement créés, afin qu'ils n'aient pas besoin d'augmenter dynamiquement lorsque de nouveaux éléments sont ajoutés avec push_back.

Et si vous avez absolument besoin des meilleures performances, vous devez utiliser un code de niveau inférieur, en supprimant les vecteurs et en réservant des tableaux simples à la place. Les algorithmes de "sélection" ou de "nième élément" sont facilement disponibles et ne sont pas trop difficiles à écrire soi-même : http://en.wikipedia.org/wiki/Selection_algorithm

3voto

Bart Points 10767

Ce n'est pas vraiment une réponse à vos questions, mais je vous recommande vivement le forum suivant http://ompf.org/forum/ Il y a d'excellentes discussions sur les constructions rapides de kd-tree dans divers contextes. Vous y trouverez peut-être de l'inspiration.

Edit :
Les forums de l'OMPF ont depuis été fermés, mais un remplacement direct est actuellement disponible à l'adresse suivante http://ompf2.com/

2voto

Anony-Mousse Points 24646

Quelques conseils pour optimiser le kd-tree :

  • Utilisez un algorithme de recherche de la médiane en temps linéaire, tel que QuickSelect.
  • Évitez d'utiliser réellement des objets "nœuds". Vous pouvez stocker l'arbre entier en utilisant les points seulement avec ZERO information supplémentaire. Essentiellement en triant simplement un tableau d'objets. Le nœud racine se trouve alors au milieu. Un réarrangement qui place la racine en premier, puis utilise une disposition en tas sera probablement plus agréable pour le cache mémoire du CPU au moment de la requête, mais plus délicat à construire.

1voto

Votre premier coupable est de trier pour trouver la médiane. Il s'agit presque toujours du goulot d'étranglement pour la construction d'un arbre K-d. L'utilisation d'algorithmes plus efficaces dans ce cas sera vraiment rentable.

Cependant, vous construisez également une paire de vecteurs de taille variable à chaque fois que vous les divisez et leur transférez des éléments.

Ici, je recommande la bonne vieille liste à liens simples. La beauté de la liste liée est que vous pouvez transférer des éléments du parent à l'enfant en modifiant simplement next pour pointer sur le pointeur Root de l'enfant au lieu de celui du parent.

Cela signifie qu'il n'y a aucune surcharge de tas pendant la construction pour transférer des éléments des nœuds parents aux nœuds enfants, seulement pour agréger la liste initiale des éléments à insérer dans la racine. Cela devrait faire des merveilles également, mais si vous voulez être encore plus rapide, vous pouvez utiliser un allocateur fixe pour allouer efficacement les noeuds pour la liste liée (ainsi que pour l'arbre) et avec de meilleures contiguïtés/caches.

Enfin, si vous êtes impliqué dans des tâches de calcul intensif faisant appel à des arbres K-d, vous avez besoin d'un profileur. Mesurez votre code et vous verrez exactement ce qui se trouve à l'origine du problème, et avec des distributions temporelles exactes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X