EDIT : Je dois m'excuser. Le code ci-dessous était erroné. J'ai le code corrigé, mais j'ai besoin de trouver un fichier de type icc compilateur pour refaire les mesures.
Les résultats de référence des algorithmes considérés jusqu'à présent
Pour le protocole et une brève description des algorithmes, voir ci-dessous. La première valeur est le temps moyen (secondes) sur 200 séquences différentes et la deuxième valeur est l'écart-type.
HeapSort : 2.287 0.2097
QuickSort : 2.297 0.2713
QuickMedian1 : 0.967 0.3487
HeapMedian1 : 0.858 0.0908
NthElement : 0.616 0.1866
QuickMedian2 : 1.178 0.4067
HeapMedian2 : 0.597 0.1050
HeapMedian3 : 0.015 0.0049 <-- best
Protocole : générer 27 flottants aléatoires en utilisant des bits aléatoires obtenus par rand(). Appliquer chaque algorithme 5 millions de fois d'affilée (y compris la copie préalable du tableau) et calculer la moyenne et le stdDev sur 200 séquences aléatoires. Code C++ compilé avec icc -S -O3 et exécuté sur Intel E8400 avec 8GB DDR3.
Algorithmes :
HeapSort : tri complet de la séquence en utilisant le tri par tas et en choisissant la valeur centrale. Implémentation naïve utilisant l'accès aux indices.
QuickSort : tri complet en place d'une séquence en utilisant le tri rapide et le choix de la valeur centrale. Implémentation naïve utilisant l'accès aux indices.
QuickMedian1 : algorithme de sélection rapide avec permutation. Implémentation naïve utilisant l'accès aux indices.
HeapMedian1 : méthode de tas équilibré en place avec permutation préalable. Implémentation naïve utilisant l'accès aux indices.
NthElement : utilise l'algorithme STL nth_element. Les données sont copiées dans le vecteur en utilisant memcpy( vct.data(), rndVal, ... ) ;
QuickMedian2 : utilise l'algorithme de sélection rapide avec des pointeurs et la copie dans deux tampons pour éviter le swaping. Basé sur la proposition de MSalters.
HeapMedian2 : variante de l'algorithme que j'ai inventé, utilisant des tas doubles avec des têtes partagées. Le tas de gauche a la plus grande valeur comme tête, le tas de droite a la plus petite valeur comme tête. Initialiser avec la première valeur comme tête commune et la première valeur médiane devinée. Ajouter les valeurs suivantes au tas de gauche si elles sont plus petites que la tête, sinon au tas de droite, jusqu'à ce que l'un des tas soit plein. Il est plein lorsqu'il contient 14 valeurs. Ensuite, on ne considère que le tas plein. S'il s'agit du tas droit, pour toutes les valeurs supérieures à la tête, on ouvre la tête et on insère la valeur. Ignorez toutes les autres valeurs. S'il s'agit du tas de gauche, pour toutes les valeurs inférieures à la tête, on ouvre la tête et on insère la valeur dans le tas. Ignorez toutes les autres valeurs. Lorsque toutes les valeurs ont été traitées, la tête commune est la valeur médiane. Il utilise un indice entier dans le tableau. La version utilisant les pointeurs (64bit) semble être presque deux fois plus lente (~1s).
HeapMedian3 : même algorithme que HeapMedian2 mais optimisé. Il utilise l'index des chars non signés, évite les échanges de valeurs et diverses autres petites choses. Les valeurs de moyenne et de stdDev sont calculées sur 1000 séquences aléatoires. Pour nth_element j'ai mesuré 0.508s et un stdDev de 0.159537 avec les mêmes 1000 séquences aléatoires. HeapMedian3 est donc 33 fois plus rapide que la fonction stl nth_element. Chaque valeur médiane retournée est comparée à la valeur médiane retournée par heapSort et elles correspondent toutes. Je doute qu'une méthode utilisant le hachage puisse être significativement plus rapide.
EDIT 1 : Cet algorithme peut être encore optimisé. La première phase où les éléments sont distribués dans le tas de gauche ou de droite en fonction du résultat de la comparaison n'a pas besoin de tas. Il suffit d'ajouter des éléments à deux séquences non ordonnées. La première phase s'arrête dès qu'une séquence est pleine, c'est-à-dire qu'elle contient 14 éléments (y compris la valeur médiane). La deuxième phase commence par la mise en tas de la séquence complète, puis procède comme décrit dans l'algorithme HeapMedian3. Je fournirai le nouveau code et le benchmark dès que possible.
EDIT 2 : J'ai implémenté et évalué l'algorithme optimisé. Mais il n'y a pas de différence de performance significative par rapport à heapMedian3. Il est même légèrement plus lent en moyenne. Les résultats affichés sont confirmés. Il pourrait y en avoir avec des ensembles beaucoup plus grands. Notez également que je choisis simplement la première valeur comme estimation initiale de la médiane. Comme suggéré, on pourrait profiter du fait que nous recherchons une valeur médiane dans des ensembles de valeurs "chevauchantes". L'utilisation de l'algorithme de la médiane de la médiane permettrait de choisir une bien meilleure estimation de la valeur médiane initiale.
Code source de HeapMedian3
// return the median value in a vector of 27 floats pointed to by a
float heapMedian3( float *a )
{
float left[14], right[14], median, *p;
unsigned char nLeft, nRight;
// pick first value as median candidate
p = a;
median = *p++;
nLeft = nRight = 1;
for(;;)
{
// get next value
float val = *p++;
// if value is smaller than median, append to left heap
if( val < median )
{
// move biggest value to the heap top
unsigned char child = nLeft++, parent = (child - 1) / 2;
while( parent && val > left[parent] )
{
left[child] = left[parent];
child = parent;
parent = (parent - 1) / 2;
}
left[child] = val;
// if left heap is full
if( nLeft == 14 )
{
// for each remaining value
for( unsigned char nVal = 27 - (p - a); nVal; --nVal )
{
// get next value
val = *p++;
// if value is to be inserted in the left heap
if( val < median )
{
child = left[2] > left[1] ? 2 : 1;
if( val >= left[child] )
median = val;
else
{
median = left[child];
parent = child;
child = parent*2 + 1;
while( child < 14 )
{
if( child < 13 && left[child+1] > left[child] )
++child;
if( val >= left[child] )
break;
left[parent] = left[child];
parent = child;
child = parent*2 + 1;
}
left[parent] = val;
}
}
}
return median;
}
}
// else append to right heap
else
{
// move smallest value to the heap top
unsigned char child = nRight++, parent = (child - 1) / 2;
while( parent && val < right[parent] )
{
right[child] = right[parent];
child = parent;
parent = (parent - 1) / 2;
}
right[child] = val;
// if right heap is full
if( nRight == 14 )
{
// for each remaining value
for( unsigned char nVal = 27 - (p - a); nVal; --nVal )
{
// get next value
val = *p++;
// if value is to be inserted in the right heap
if( val > median )
{
child = right[2] < right[1] ? 2 : 1;
if( val <= right[child] )
median = val;
else
{
median = right[child];
parent = child;
child = parent*2 + 1;
while( child < 14 )
{
if( child < 13 && right[child+1] < right[child] )
++child;
if( val <= right[child] )
break;
right[parent] = right[child];
parent = child;
child = parent*2 + 1;
}
right[parent] = val;
}
}
}
return median;
}
}
}
}
0 votes
Quelle est la valeur d'un voxel 3x3x3 ? Combien de valeurs différentes y a-t-il ? Comment sont-elles ordonnées pour choisir la médiane ?
0 votes
3x3x3 = 27 valeurs flottantes. Je dois trouver la valeur médiane de ces 27 valeurs flottantes. Les valeurs peuvent avoir n'importe quelle valeur flottante possible, mais en pratique, elles seront positives et assez proches en valeur.
12 votes
Conseil pour les personnes qui répondent : Toute référence à O(N) versus O(N log N) n'est absolument pas pertinente. O(N)=O(27)=O(1), et O(NlogN)=O(1) pour la même raison. Pour cette raison, vous ne voulez probablement pas des algorithmes standard.
0 votes
Ce serait une bonne idée d'afficher le code d'évaluation comparative. Et mettez le processus+thread en haute priorité pour réduire le bruit et obtenir un meilleur stddev.
0 votes
Je me demande comment les résultats sont influencés par le fait que les valeurs sont presque triées avant de commencer. 27 flottants aléatoires semblent totalement désordonnés, alors qu'il est très probable que les valeurs provenant du monde réel soient corrélées, et donc qu'elles soient presque triées.
0 votes
@Davy Je serais heureux de le faire, car il pourrait y avoir une marge d'amélioration dans eapMedian2. Est-ce possible avec StackOverflow ? Pouvez-vous me dire comment régler la priorité du processus+thread au maximum ? J'utilise Ubuntu 8.04, pas Windows. Je pense que le stdDev est plus élevé avec la sélection rapide à cause des pires cas. La séquence de valeurs aléatoires est exactement la même pour tous les tests.
0 votes
@bart, les valeurs sont très probablement proches les unes des autres mais très probablement non triées. Cela est dû au fait que les données sont obtenues à partir de mesures d'un processus distribué par poison (comptage de photons). Le fait que les valeurs soient proches les unes des autres peut nuire aux méthodes de hachage par force brute.
0 votes
@chmike Je n'ai pas développé sous Linux, mais si je ne me trompe pas, la bibliothèque POSIX vous donnerait suffisamment de contrôle pour changer la priorité du thread. Vous pourriez poster votre code d'évaluation dans une réponse dans laquelle vous incluriez d'abord votre solution et ajouteriez ensuite votre évaluation. Poster votre solution est toujours une bonne idée pour préserver la forme question+réponse de StackOverflow.
0 votes
Quelqu'un remarquerait-il que vous avez simplement ignoré les 8 voxels extérieurs ?
0 votes
@meutex Désolé, je ne peux pas publier mon email ici. Malheureusement, il n'y a pas de moyen d'envoyer des messages privés ici. Une autre suggestion pour entrer en contact ?
0 votes
Je sais que vous avez déjà choisi une réponse correcte à cette question, mais ce que vous recherchiez n'était pas simplement de calculer une médiane (pour laquelle la méthode que vous avez décrite était probablement parmi les plus rapides), mais de faire filtrage médian . Bien se familiariser avec le problème est la clé pour trouver une bonne solution :)