web-dev-qa-db-fra.com

Recherche de plusieurs entrées avec une recherche binaire

J'utilise standard recherche binaire pour renvoyer rapidement un seul objet dans une liste triée (par rapport à une propriété pouvant être triée). 

Maintenant, je dois modifier la recherche pour queTOUTES LES ENTRÉEScorrespondantes soient renvoyées. Comment devrais-je le faire au mieux?

18
Gruber

Eh bien, la liste étant triée, toutes les entrées qui vous intéressent sont contiguës . Cela signifie que vous devez trouver le premier élément égal à l'élément trouvé, en regardant en arrière à partir de l'index généré par la recherche binaire. Et la même chose pour le dernier élément.

Vous pouvez simplement revenir en arrière à partir de l'index trouvé, mais de cette façon, la solution peut être aussi lente que O(n) s'il y a beaucoup d'éléments égaux à celui trouvé. Vous devriez donc mieux utiliser recherche exponentielle : doublez vos sauts au fur et à mesure que vous trouvez des objets égaux. De cette façon, toute votre recherche est toujours O (log n).

17
Vlad

Tout d’abord, rappelons l’extrait de code de recherche binaire naïf:

int bin_search(int arr[], int key, int low, int high)
{
    if (low > high)
        return -1;

    int mid = low + ((high - low) >> 1);

    if (arr[mid] == key) return mid;
    if (arr[mid] > key)
        return bin_search(arr, key, low, mid - 1);
    else
        return bin_search(arr, key, mid + 1, high);
}

Cité par Prof. Skiena: Supposons que nous supprimions le test d’égalité si (s [milieu] == clé) Retour (milieu); à partir de l'implémentation ci-dessus et renvoie l'index bas au lieu de −1 à chaque recherche infructueuse. Toutes les recherches seront maintenant Infructueuses, car il n'y a pas de test d'égalité. La recherche se poursuit Vers la moitié droite chaque fois que la clé est comparée à un élément de tableau Identique, se terminant finalement à la limite droite. Si vous répétez la recherche Après avoir inversé le sens de la comparaison binaire, Nous mènera à la limite gauche. Chaque recherche prend O(lgn) temps, de sorte que nous pouvons Compter les occurrences en temps logarithmique, quelle que soit la taille du bloc.

Donc, nous avons besoin de deux tours de binary_search pour trouver le lower_bound (trouver le premier nombre pas moins que la clé) et le upper_bound (trouver le premier nombre plus grand que la clé).

int lower_bound(int arr[], int key, int low, int high)
{
    if (low > high)
        //return -1;
        return low;

    int mid = low + ((high - low) >> 1);
    //if (arr[mid] == key) return mid;

    //Attention here, we go left for lower_bound when meeting equal values
    if (arr[mid] >= key) 
        return lower_bound(arr, key, low, mid - 1);
    else
        return lower_bound(arr, key, mid + 1, high);
}

int upper_bound(int arr[], int key, int low, int high)
{
    if (low > high)
        //return -1;
        return low;

    int mid = low + ((high - low) >> 1);
    //if (arr[mid] == key) return mid;

    //Attention here, we go right for upper_bound when meeting equal values
    if (arr[mid] > key) 
        return upper_bound(arr, key, low, mid - 1);
    else
        return upper_bound(arr, key, mid + 1, high);
}

J'espère que c'est utile :)

12
user2696499

Si je suis votre question, vous avez une liste d'objets qui, aux fins de comparaison, ressemblent à {1,2,2,3,4,5,5,5,6,7,8,8,9}. Une recherche normale de 5 va frapper l'un des objets comparés à 5, mais vous voulez tous les avoir, n'est-ce pas?

Dans ce cas, je suggérerais une recherche binaire standard qui, après l'atterrissage sur un élément correspondant, commence à chercher à gauche jusqu'à ce qu'elle cesse de correspondre, puis à nouveau (à partir de la première correspondance) jusqu'à ce qu'elle cesse de correspondre.

Veillez à ce que la structure de données que vous utilisez ne remplace pas les éléments qui se comparent aux mêmes!

Sinon, envisagez d'utiliser une structure qui stocke des éléments comparables à ceux d'un compartiment dans cette position.

6
Miquel

Une fois que vous avez trouvé une correspondance avec bsearch, il suffit de faire une recherche récursive des deux côtés jusqu'à ce qu'il n'y ait plus de correspondance.

pseudo code:

    range search (type *array) {
      int index = bsearch(array, 0, array.length-1);

      // left
      int upperBound = index -1;
      int i = upperBound;
      do {
         upperBound = i;
         i = bsearch(array, 0, upperBound);
      } while (i != -1)

      // right
      int lowerBound = index + 1;
      int i = lowerBound;
      do {
         lowerBound = i;
         i = bsearch(array, lowerBound, array.length);
      } while (i != -1)

      return range(lowerBound, UpperBound);
}

Aucun cas d'angle n'est couvert cependant. Je pense que cela va garder votre complexité à (O (logN)).

3
yngccc

Je ferais deux recherches binaires, une recherche du premier élément comparant> = la valeur (en termes C++, lower_bound), puis une recherche du premier élément comparant> la valeur (en termes C++, upper_bound). Les éléments de lower_bound à juste avant la limite supérieure correspondent à ce que vous recherchez (en termes de Java.util.SortedSet, sous-ensemble (clé, clé)).

Vous avez donc besoin de deux légères modifications à la recherche binaire standard: vous testez toujours et utilisez la comparaison au niveau de la sonde pour affiner la zone dans laquelle la valeur que vous recherchez doit se situer, mais maintenant, par exemple. pour lower_bound si vous atteignez l'égalité, tout ce que vous savez, c'est que l'élément que vous recherchez (le first equal value) se situe quelque part entre le premier élément de la plage et la valeur que vous venez de rechercher - vous pouvez ' t revenir immédiatement.

3
mcdowella

Cela dépend de l’implémentation de la recherche binaire que vous utilisez:

  • En Java et .NET, la recherche binaire vous donnera un élément arbitraire. vous devez chercher dans les deux sens pour obtenir la plage que vous recherchez.
  • En C++, vous pouvez utiliser equal_range pour obtenir le résultat souhaité en un seul appel.

Pour accélérer les recherches dans Java et .NET dans les cas où la plage égale est trop longue pour une itération linéaire, vous pouvez rechercher un élément prédécesseur et le successeur, et prendre des valeurs situées au milieu de la plage que vous trouvez, à l'exclusion de les fins.

Si cela est trop lent à cause d'une deuxième recherche binaire, envisagez d'écrire votre propre recherche qui recherche les deux extrémités en même temps. Cela peut paraître un peu fastidieux, mais cela devrait aller plus vite.

2
dasblinkenlight

Je commencerais par rechercher l'index d'un seul élément en fonction de la propriété de tri (en utilisant une recherche binaire "normale"), puis par regarder à la fois à gauche et à droite de l'élément dans la liste, en ajoutant tous les éléments correspondant à la recherche. critère, s’arrêtant à une extrémité lorsqu'un élément ne satisfait pas le critère ou s’il n’y a plus d’éléments à traverser, et s’arrête complètement lorsque les deux extrémités gauche et droite remplissent les conditions d’arrêt susmentionnées.

2
Óscar López

votre recherche binaire renvoie-t-elle l'élément ou l'index de l'élément? Pouvez-vous obtenir l'index? 

Puisque la liste est triée, tous les éléments correspondants doivent apparaître adjacents. Si vous pouvez obtenir l'index de l'élément renvoyé dans la recherche standard, il vous suffit de rechercher dans les deux sens à partir de cet index jusqu'à ce que vous trouviez des non-correspondances.

1
Colin D

Essaye ça. Cela fonctionne étonnamment. 

exemple de travail, Cliquez ici

   var arr = [1, 1, 2, 3, "a", "a", "a", "b", "c"]; // It should be sorted array.
   // if it arr contain more than one keys than it will return an array indexes. 

   binarySearch(arr, "a", false);

   function binarySearch(array, key, caseInsensitive) {
       var keyArr = [];
       var len = array.length;
       var ub = (len - 1);
       var p = 0;
       var mid = 0;
       var lb = p;

       key = caseInsensitive && key && typeof key == "string" ? key.toLowerCase() : key;

       function isCaseInsensitive(caseInsensitive, element) {
           return caseInsensitive && element && typeof element == "string" ? element.toLowerCase() : element;
       }
       while (lb <= ub) {
           mid = parseInt(lb + (ub - lb) / 2, 10);

           if (key === isCaseInsensitive(caseInsensitive, array[mid])) {
               keyArr.Push(mid);
               if (keyArr.length > len) {
                   return keyArr;
               } else if (key == isCaseInsensitive(caseInsensitive, array[mid + 1])) {
                   for (var i = 1; i < len; i++) {
                       if (key != isCaseInsensitive(caseInsensitive, array[mid + i])) {
                           break;
                       } else {
                           keyArr.Push(mid + i);

                       }
                   }
               }
               if (keyArr.length > len) {
                   return keyArr;
               } else if (key == isCaseInsensitive(caseInsensitive, array[mid - 1])) {
                   for (var i = 1; i < len; i++) {

                       if (key != isCaseInsensitive(caseInsensitive, array[mid - i])) {
                           break;
                       } else {
                           keyArr.Push(mid - i);
                       }
                   }
               }
               return keyArr;

           } else if (key > isCaseInsensitive(caseInsensitive, array[mid])) {
               lb = mid + 1;
           } else {
               ub = mid - 1;
           }
       }

       return -1;
   }
0
shekhardtu

Vous pouvez utiliser le code ci-dessous pour votre problème. L’objectif principal ici est d’abord de trouver la limite inférieure de la clé, puis de déterminer la limite supérieure de la même chose. Plus tard, nous obtenons la différence des indices et nous obtenons notre réponse. Plutôt que d'avoir deux fonctions différentes, nous pouvons utiliser un indicateur qui peut être utilisé pour trouver la limite supérieure et la limite inférieure dans la même fonction.

#include <iostream>
#include <bits/stdc++.h>
using namespace std;

int bin_search(int a[], int low, int high, int key, bool flag){
long long int mid,result=-1;
while(low<=high){
    mid = (low+high)/2;
    if(a[mid]<key)
        low = mid + 1;
    else if(a[mid]>key)
        high = mid - 1;
    else{
        result = mid;
        if(flag)
            high=mid-1;//Go on searching towards left (lower indices)
        else
            low=mid+1;//Go on searching towards right (higher indices)
    }
}
return result;
}

int main() {

int n,k,ctr,lowind,highind;
cin>>n>>k;
//k being the required number to find for
int a[n];
for(i=0;i<n;i++){
    cin>>a[i];
}
    sort(a,a+n);
    lowind = bin_search(a,0,n-1,k,true);
    if(lowind==-1)
        ctr=0;
    else{
        highind = bin_search(a,0,n-1,k,false);
        ctr= highind - lowind +1;   
}
cout<<ctr<<endl;
return 0;
}
0
Deril Raju