Algorithme rapide pour rechercher un tableau trié de flottants pour trouver la paire de flotteurs entre crochets une valeur d'entrée

10

J'ai un tableau de flotteurs, triés du plus petit au plus grand, et je dois pouvoir choisir le flotteur le plus proche supérieur ou inférieur à une valeur d'entrée transmise. Cette valeur d'entrée n'est pas nécessairement présente en tant que valeur dans le tableau.

Une approche naïve serait de faire une simple recherche linéaire dans le tableau. Cela pourrait ressembler à ceci:

void FindClosestFloatsInArray( float input, std::vector<float> array, 
                               float *min_out, float *max_out )
{
    assert( input >= array[0] && input < array[ array.size()-1 ] );
    for( int i = 1; i < array.size(); i++ )
    {
        if ( array[i] >= input )
        {
            *min = array[i-1];
            *max = array[i];
        }
    }
}

Mais évidemment, à mesure que le tableau s'agrandit, cela deviendra de plus en plus lent.

Quelqu'un at-il une idée d'un algorithme qui me permettrait de trouver ces données de manière plus optimale? Je suis déjà passé à une recherche binaire, ce qui a quelque peu amélioré les choses, mais c'est toujours beaucoup plus lent que je ne le souhaiterais, et comme je ne recherche pas réellement une valeur spécifique qui existe dans le tableau, elle ne peut jamais se terminer de bonne heure.

Plus d'informations: Les valeurs à virgule flottante dans le tableau ne sont pas nécessairement réparties uniformément (c'est-à-dire que le tableau peut être composé des valeurs "1.f, 2.f, 3.f, 4.f, 100.f, 1200.f , 1203.f, 1400.f ".

Je fais cette opération des centaines de milliers de fois, mais je peux faire n'importe quelle quantité de prétraitement sur le tableau de flottants, si cela améliore le temps de recherche. Je peux absolument changer pour utiliser autre chose qu'un vecteur pour les stocker, si cela peut aider.

Trevor Powell
la source
Qu'est-ce qui vous fait penser que votre recherche binaire ne peut pas se terminer tôt? Vous pouvez sûrement simplement tester les éléments en i et i + 1 pour voir s'ils encadrent la valeur cible et terminer s'ils le font?
Paul R
Alternativement, je pourrais tester les éléments en i et i-1 pour voir s'ils encadrent la valeur cible. Je devrais également tester si «i» était> = array.size () - 1 pour que je puisse éviter de faire votre test, et si c'était <= 0 pour éviter de faire mon test ... c'est en fait beaucoup de des conditions supplémentaires à effectuer à chaque étape, afin de vérifier un départ anticipé. J'imagine qu'ils ralentiraient beaucoup l'algorithme, bien que j'avoue que je n'ai pas encore fait de profil.
Trevor Powell
3
Cela n'a pas besoin d'être si compliqué - si votre tableau est de taille N, il vous suffit de le traiter comme s'il était de taille N - 1. De cette façon, il y a toujours un élément valide à i + 1. Vous faites un recherche binaire sur N - 1 élément pour l'élément i qui est inférieur à votre valeur cible, l'élément i + 1 étant supérieur à la valeur cible.
Paul R

Réponses:

11

Le code dans la question (une recherche linéaire), comme vous le faites remarquer à juste titre, va ralentir pour les grands tableaux flottants. Techniquement, c'est O (n) où n est le nombre de valeurs flottantes dans votre tableau.

En général, le mieux que vous puissiez faire pour trouver une valeur dans un tableau ordonné est une recherche récursive dans un arbre (par exemple, une recherche binaire), auquel cas vous pouvez obtenir un temps de recherche O (log n) dans le nombre d'éléments dans votre tableau. O (log n) est bien meilleur que O (n) pour les grandes valeurs de n.

Mon approche suggérée serait donc une simple recherche binaire du tableau , c'est-à-dire:

  1. Définissez des indices entiers min / max pour couvrir l'ensemble de votre tableau flottant
  2. tester la valeur au milieu de la plage à l'indice moyen = (min + max / 2) par rapport à la valeur de recherche x
  3. si x est inférieur à cette valeur, définissez max sur mid, sinon paramétrez min sur mid
  4. répétez (2-4) jusqu'à ce que vous ayez trouvé la valeur correcte

Il s'agit d'un algorithme O (log n) qui devrait être assez rapide pour presque toutes les situations. Intuitivement, cela fonctionne en divisant par deux la plage à rechercher à chaque étape jusqu'à ce que vous trouviez la valeur correcte.

Il est vraiment difficile de lancer la recherche binaire simple, donc si vous l'avez déjà correctement implémentée, vous êtes peut-être déjà presque optimal. Cependant, si vous connaissez les distributions des données et / ou avez une plage limitée de valeurs de recherche (x), il existe encore d'autres astuces plus avancées que vous pouvez essayer:

  • Bucketing - créez des compartiments (par exemple pour chaque intervalle entre deux entiers), chacun contenant une liste triée plus petite des valeurs flottantes entre les deux entiers englobants plus deux valeurs immédiatement en dessous et immédiatement au-dessus de chaque plage. Vous pouvez alors commencer votre recherche à (trunc (x) +0.5). Cela devrait vous donner une bonne accélération si vous choisissez des godets de taille appropriée (cela augmente effectivement le facteur de ramification de l'arbre .....). Si les entiers ne fonctionnent pas pour vous, alors vous pouvez essayer des compartiments d'une autre précision à virgule fixe (par exemple des multiples de 1/16).
  • Mappage de bits - si la plage de valeurs de recherche possibles est suffisamment petite, vous pouvez essayer de créer une grande table de recherche indexée par la valeur au niveau du bit de x. Ce sera O (1) mais vous aurez peut-être besoin de beaucoup de mémoire qui sera très hostile sur votre cache ... utilisez donc avec prudence. Ceci est particulièrement désagréable car vous recherchez des valeurs flottantes, vous devrez donc peut-être plusieurs Go pour prendre en compte tous les bits les moins significatifs ......
  • Arrondi et hachage - les tables de hachage ne sont probablement pas la meilleure structure de données pour ce problème, mais si vous pouvez survivre en perdant un peu de précision, cela pourrait fonctionner - arrondissez simplement les bits les plus bas de vos valeurs de recherche et utilisez une table de hachage pour rechercher directement le valeur correcte. Vous devrez expérimenter le bon compromis entre la taille et la précision de la table de hachage, et vous assurer également que toutes les valeurs de hachage possibles sont remplies, ce qui peut être un peu délicat ...
  • Équilibrage des arbres - votre arbre idéal devrait avoir 50% de chances d'aller à gauche ou à droite. Donc, si vous créez un arbre basé sur la distribution des valeurs de recherche (x), vous pouvez optimiser l'arbre pour produire des réponses avec le minimum de tests. C'est probablement une bonne solution si beaucoup de valeurs dans votre tableau flottant sont très proches les unes des autres, car cela vous permettra d'éviter de rechercher trop souvent ces branches.
  • Arbres de bits critiques - ce sont encore des arbres (donc toujours O (log n) ...) mais dans certains cas: vous auriez cependant besoin de convertir vos flotteurs dans un format à virgule fixe afin de faire fonctionner les comparaisons

Cependant, à moins que vous ne soyez dans une situation très spéciale, je recommanderais probablement de vous en tenir à la simple recherche binaire. Les raisons:

  • c'est beaucoup plus facile à mettre en œuvre
  • c'est très rapide pour les cas les plus courants
  • la surcharge supplémentaire des approches plus complexes (par exemple, une utilisation plus élevée de la mémoire / une pression de cache) l'emporte souvent sur les gains théoriques mineurs
  • il sera plus robuste aux changements futurs dans les distributions de données ....
mikera
la source
1

Cela semble assez simple:

Effectuez une recherche binaire pour le flottant que vous souhaitez lier - heure O (log n).

Ensuite, l'élément à gauche de celui-ci est la limite inférieure, et l'élément à droite de celui-ci est la limite supérieure.

Ankit Soni
la source
0

La réponse évidente est de stocker les flotteurs dans un arbre . La prise en charge des opérations «précédente» et «suivante» est triviale dans un arbre. Il suffit donc de faire un «suivant» sur votre valeur, puis de faire un «précédent» sur la valeur que vous avez trouvée à la première étape.

David Schwartz
la source
1
C'est essentiellement la même chose qu'une recherche binaire.
kevin cline
-1

Cet article ("recherche sublogarithmique sans multiplications") pourrait être intéressant; il contient même du code source. Aux fins de comparaison, vous pouvez traiter un nombre flottant comme un entier avec le même motif binaire; c'était l'un des objectifs de conception de la norme à virgule flottante IEEE.

zvrba
la source