web-dev-qa-db-fra.com

Rechercher le premier élément d'un tableau trié supérieur à la cible

Dans une recherche binaire générale, nous recherchons une valeur qui apparaît dans le tableau. Parfois, cependant, nous devons trouver le premier élément qui est supérieur ou inférieur à une cible.

Voici ma vilaine solution incomplète:

// Assume all elements are positive, i.e., greater than zero
int bs (int[] a, int t) {
  int s = 0, e = a.length;
  int firstlarge = 1 << 30;
  int firstlargeindex = -1;
  while (s < e) {
    int m = (s + e) / 2;
    if (a[m] > t) {
      // how can I know a[m] is the first larger than
      if(a[m] < firstlarge) {
        firstlarge = a[m];
        firstlargeindex = m;
      }
      e = m - 1; 
    } else if (a[m] < /* something */) {
      // go to the right part
      // how can i know is the first less than  
    }
  }
}

Existe-t-il une solution plus élégante pour ce type de problème?

50
SecureFish

Une manière particulièrement élégante de penser à ce problème est de penser à faire une recherche binaire sur une version transformée du tableau, où le tableau a été modifié en appliquant la fonction

f(x) = 1 if x > target
       0 else

Maintenant, le but est de trouver la toute première place que cette fonction prend sur la valeur 1. Nous pouvons le faire en utilisant une recherche binaire comme suit:

int low = 0, high = numElems; // numElems is the size of the array i.e arr.size() 
while (low != high) {
    int mid = (low + high) / 2; // Or a fancy way to avoid int overflow
    if (arr[mid] <= target) {
        /* This index, and everything below it, must not be the first element
         * greater than what we're looking for because this element is no greater
         * than the element.
         */
        low = mid + 1;
    }
    else {
        /* This element is at least as large as the element, so anything after it can't
         * be the first element that's at least as large.
         */
        high = mid;
    }
}
/* Now, low and high both point to the element in question. */

Pour voir que cet algorithme est correct, considérez chaque comparaison effectuée. Si nous trouvons un élément qui n'est pas supérieur à l'élément cible, alors il ne peut pas correspondre à tout ce qui se trouve en dessous, il n'est donc pas nécessaire de rechercher cette région. Nous pouvons rechercher récursivement la moitié droite. Si nous trouvons un élément qui est plus grand que l'élément en question, alors tout ce qui le suit doit également être plus grand, donc ils ne peuvent pas être l'élément premier qui est plus grand et donc nous n'avons pas besoin de chercher leur. L'élément central est donc le dernier endroit possible qu'il pourrait être.

Notez qu'à chaque itération, nous retirons au moins la moitié des éléments restants de la considération. Si la branche supérieure s'exécute, les éléments de la plage [bas, (bas + haut)/2] sont tous rejetés, ce qui nous fait perdre du plancher ((bas + haut)/2) - bas + 1> = (bas + haut)/2 - bas = (haut - bas)/2 éléments.

Si la branche inférieure s'exécute, les éléments de la plage [(bas + haut)/2 + 1, haut] sont tous rejetés. Cela nous perd haut - plancher (bas + haut)/2 + 1> = haut - (bas + haut)/2 = (haut - bas)/2 éléments.

Par conséquent, nous finirons par trouver le premier élément supérieur à la cible dans les itérations O (lg n) de ce processus.

EDIT: Voici une trace de l'algorithme exécuté sur le tableau 0 0 1 1 1 1.

Au départ, nous avons

0 0 1 1 1 1
L = 0       H = 6

Donc, nous calculons mid = (0 + 6)/2 = 3, donc nous inspectons l'élément à la position 3, qui a la valeur 1. Depuis 1> 0, nous mettons high = mid = 3. Nous avons maintenant

0 0 1
L     H

Nous calculons mid = (0 + 3)/2 = 1, donc nous inspectons l'élément 1. Puisque celui-ci a la valeur 0 <= 0, nous fixons mid = low + 1 = 2. Nous nous retrouvons maintenant avec L = 2 et H = 3:

0 0 1
    L H

Maintenant, nous calculons mid = (2 + 3)/2 = 2. L'élément à l'index 2 est 1, et puisque 1 ≥ 0, nous fixons H = mid = 2, à quel point nous nous arrêtons, et en effet nous cherchons au premier élément supérieur à 0.

J'espère que cela t'aides!

76
templatetypedef

Vous pouvez utiliser std::upper_bound si le tableau est trié (en supposant que n est la taille du tableau a[]):

int* p = std::upper_bound( a, a + n, x );
if( p == a + n )
     std::cout << "No element greater";
else
     std::cout << "The first element greater is " << *p
               << " at position " << p - a;
9
Grigor Gevorgyan

Que diriez-vous de l'approche récursive suivante:

    public static int minElementGreaterThanOrEqualToKey(int A[], int key,
        int imin, int imax) {

    // Return -1 if the maximum value is less than the minimum or if the key
    // is great than the maximum
    if (imax < imin || key > A[imax])
        return -1;

    // Return the first element of the array if that element is greater than
    // or equal to the key.
    if (key < A[imin])
        return imin;

    // When the minimum and maximum values become equal, we have located the element. 
    if (imax == imin)
        return imax;

    else {
        // calculate midpoint to cut set in half, avoiding integer overflow
        int imid = imin + ((imax - imin) / 2);

        // if key is in upper subset, then recursively search in that subset
        if (A[imid] < key)
            return minElementGreaterThanOrEqualToKey(A, key, imid + 1, imax);

        // if key is in lower subset, then recursively search in that subset
        else
            return minElementGreaterThanOrEqualToKey(A, key, imin, imid);
    }
}
2
John R.B. Palmer

Après de nombreuses années d'enseignement d'algorithmes, mon approche pour résoudre les problèmes de recherche binaire consiste à définir le début et la fin sur les éléments, pas en dehors du tableau. De cette façon, je peux sentir ce qui se passe et tout est sous contrôle, sans ressentir de magie à propos de la solution.

Le point clé pour résoudre les problèmes de recherche binaire (et de nombreuses autres solutions basées sur les boucles) est un ensemble de bons invariants. Choisir le bon invariant transforme le problème en un gâteau. Il m'a fallu de nombreuses années pour saisir le concept invariant, bien que je l'avais appris au collège il y a de nombreuses années.

Même si vous souhaitez résoudre des problèmes de recherche binaire en choisissant début ou fin en dehors du tableau, vous pouvez toujours y parvenir avec un invariant approprié. Cela étant dit, mon choix est indiqué ci-dessus de toujours définir début sur le premier élément et fin sur le dernier élément du tableau.

Pour résumer, jusqu'à présent, nous avons:

int start = 0; 
int end = a.length - 1; 

Maintenant, l'invariant. Le tableau que nous avons actuellement est [début, fin]. Nous ne savons encore rien des éléments. Tous peuvent être supérieurs à l'objectif, ou tous peuvent être plus petits, ou certains plus petits et certains plus grands. Nous ne pouvons donc pas faire d'hypothèse pour l'instant sur les éléments. Notre objectif est de trouver le premier élément supérieur à la cible. Nous choisissons donc les invariants comme ceci:

Tout élément à droite de l'extrémité est supérieur à la cible.
Tout élément à gauche du début est inférieur ou égal à la cible.

Nous pouvons facilement voir que notre invariant est correct au début (c'est-à-dire avant d'entrer dans une boucle). Tous les éléments à gauche du début (aucun élément fondamentalement) sont inférieurs ou égaux à la cible, même raisonnement pour la fin.

Avec cet invariant, lorsque la boucle se termine, le premier élément après la fin sera la réponse (rappelez-vous l'invariant que le côté droit de la fin est tous plus grand que la cible?). Donc answer = end + 1.

Nous devons également noter que lorsque la boucle se termine, le début sera un de plus que la fin. c'est-à-dire début = fin + 1. Donc, de manière équivalente, nous pouvons également dire que début est la réponse (invariant était que tout ce qui se trouve à gauche du début est inférieur ou égal à la cible, donc le début lui-même est le premier élément plus grand que la cible).

Donc, tout étant dit, voici le code. Vous devriez vous sentir à l'aise avec chaque ligne de ce code et vous ne devriez ressentir aucune magie. Sinon, veuillez commenter quelle est l'ambiguïté et je serai plus qu'heureux de répondre.

public static int find(int a[], int target) {
    int st = 0; 
    int end = a.length - 1; 
    while(st <= end) {
        int mid = (st + end) / 2;   // or elegant way of st + (end - st) / 2; 
        if (a[mid] <= target) {
            st = mid + 1; 
        } else { // mid > target
            end = mid - 1; 
        }
    }
    return st; // or return end + 1
}

Quelques notes supplémentaires sur cette façon de résoudre les problèmes de recherche binaire:

Ce type de solution réduit toujours la taille des sous-réseaux d'au moins 1. Cela est évident dans le code. Le nouveau début ou la fin sont +1 ou -1 au milieu. J'aime mieux cette approche que d'inclure le milieu des deux ou d'un côté, puis je raisonne plus tard pourquoi l'algo est correct. De cette façon, c'est plus tangible et plus exempt d'erreurs.

La condition pour la boucle while est st <= end. Ne pas st < end. Cela signifie que la plus petite taille qui entre dans la boucle while est un tableau de taille 1. Et cela correspond totalement à ce que nous attendons. Dans d'autres façons de résoudre les problèmes de recherche binaire, parfois la plus petite taille est un tableau de taille 2 (si st <end), et honnêtement, je trouve beaucoup plus facile de toujours traiter toutes les tailles de tableau, y compris la taille 1.

J'espère donc que cela clarifie la solution à ce problème et à de nombreux autres problèmes de recherche binaire. Traitez cette solution comme un moyen de comprendre et de résoudre professionnellement de nombreux autres problèmes de recherche binaire sans jamais hésiter à savoir si l'algorithme fonctionne pour les cas Edge ou non.

2
apadana

voici une modification recherche binaire code dans Java avec la complexité du temps O(logn) cette :

  • retour indice de l'élément à rechercher si l'élément est présent
  • renvoie l'index de l'élément supérieur suivant si l'élément recherché n'est pas présent dans le tableau
  • renvoie -1 si un élément supérieur au plus grand élément du tableau est recherché
public static int search(int arr[],int key) {
    int low=0,high=arr.length,mid=-1;
    boolean flag=false;

    while(low<high) {
        mid=(low+high)/2;
        if(arr[mid]==key) {
            flag=true;
            break;
        } else if(arr[mid]<key) {
            low=mid+1;
        } else {
            high=mid;
        }
    }
    if(flag) {
        return mid;
    }
    else {
        if(low>=arr.length)
            return -1;
        else
        return low;
        //high will give next smaller
    }
}

public static void main(String args[]) throws IOException {
    BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
    //int n=Integer.parseInt(br.readLine());
    int arr[]={12,15,54,221,712};
    int key=71;
    System.out.println(search(arr,key));
    br.close();
}
1
pankaj

Mon implémentation suivante utilise la condition bottom <= top qui est différent de la réponse de @templatetypedef.

int FirstElementGreaterThan(int n, const vector<int>& values) {
  int B = 0, T = values.size() - 1, M = 0;
  while (B <= T) { // B strictly increases, T strictly decreases
    M = B + (T - B) / 2;
    if (values[M] <= n) { // all values at or before M are not the target
      B = M + 1;
    } else {
      T = M - 1;// search for other elements before M
    }
  }
  return T + 1;
}
1
Duke

kind = 0: correspondance exacte, kind = 1: juste supérieur à x, kind = -1: juste inférieur à x;

Il renvoie -1 si aucune correspondance n'est trouvée.

#include <iostream>
#include <algorithm>

using namespace std;


int g(int arr[], int l , int r, int x, int kind){
    switch(kind){
    case 0: // for exact match
        if(arr[l] == x) return l;
        else if(arr[r] == x) return r;
        else return -1;
        break;
    case 1: // for just greater than x
        if(arr[l]>=x) return l;
        else if(arr[r]>=x) return r;
        else return -1;
        break;
    case -1: // for just smaller than x
        if(arr[r]<=x) return r;
        else if(arr[l] <= x) return l;
        else return -1;
        break;
    default:
        cout <<"please give "kind" as 0, -1, 1 only" << ednl;
    }
}

int f(int arr[], int n, int l, int r, int x, int kind){
    if(l==r) return l;
    if(l>r) return -1;
    int m = l+(r-l)/2;
    while(m>l){
        if(arr[m] == x) return m;
        if(arr[m] > x) r = m;
        if(arr[m] < x) l = m;
        m = l+(r-l)/2;
    }
    int pos = g(arr, l, r, x, kind);
    return pos;
}

int main()
{
    int arr[] = {1,2,3,5,8,14, 22, 44, 55};
    int n = sizeof(arr)/sizeof(arr[0]);
    sort(arr, arr+n);
    int tcs;
    cin >> tcs;
    while(tcs--){
        int l = 0, r = n-1, x = 88, kind = -1; // you can modify these values
        cin >> x;
        int pos = f(arr, n, l, r, x, kind);
        // kind =0: exact match, kind=1: just grater than x, kind=-1: just smaller than x;
        cout <<"position"<< pos << " Value ";
        if(pos >= 0) cout << arr[pos];
        cout << endl;
    }
    return 0;
}
0
praveen chaudhary