2010-08-17 24 views
6

Dans l'algorithme de recherche binaire, nous avons deux comparaisons:Est-il possible de n'avoir qu'une seule comparaison par itération d'un algorithme de recherche binaire?

if (key == a[mid]) then found; 

else if (key < a[mid]) then binary_search(a[],left,mid-1); 
     else binary_search(a[],mid+1,right); 

est-il un moyen par lequel je ne peux avoir qu'une seule comparaison au lieu des deux ci-dessus.

-

Merci

Alok.Kr.

+2

Comme toujours: avez-vous besoin d'obscurcir l'algorithme pour la performance? Est-ce dans le chemin du code critique? La performance de cette méthode spécifique affecte-t-elle la performance du programme global? Est-ce dû à la comparaison supplémentaire? Mon pari est que la comparaison supplémentaire n'a aucun effet global dans l'exécution du programme. Si vous avez vraiment besoin d'optimiser l'algorithme, mesurez d'abord, puis pensez à ce que vous faites. Je parie que le passage de récursif à itératif a) le rendra moins lisible (donc ne le fais que si c'est vraiment nécessaire) et b) améliorera la vitesse d'exécution beaucoup plus que la comparaison. –

+2

@David: Dans ce cas, il semble être une question académique importante à connaître, et l'application de l'optimisation rend l'algorithme plus canonique. – Potatoswatter

+0

@ David: Je voulais juste savoir si cela est possible indépendamment de l'optimisation ou de la complexité. @ Potatoswatter: La question a été posée une fois dans les documents de placement Adobe. Donc je voulais juste savoir si c'est possible. J'ai maintenant aussi posté une solution possible par moi-même, mais toujours curieux. Merci à Alok.Kr –

Répondre

13

Voir:

http://en.wikipedia.org/wiki/Binary_search_algorithm#Single_comparison_per_iteration

Taken du wiki:

low = 0 
    high = N 
    while (low < high) { 
     mid = low + ((high - low)/2) 
     if (A[mid] < value) 
      low = mid + 1; 
     else 
      //can't be high = mid-1: here A[mid] >= value, 
      //so high can't be < mid if A[mid] == value 
      high = mid; 
    } 
    // high == low, using high or low depends on taste 
    if ((low < N) && (A[low] == value)) 
     return low // found 
    else 
     return -1 // not found 

Avantages/inconvénients de wiki: « Cette approche renonce à la possibilité de résiliation anticipée de la découverte d'un match, ainsi les recherches réussies ont des itérations de log2 (N) au lieu d'itérations de log2 (N) - 1. En revanche, cette implémentation fait moins de comparaisons: log2 (N) est le ss que le nombre attendu de comparaisons pour les implémentations de deux tests de 1. 5 (log2 (N) - 1), pour N supérieur à huit. "

+0

J'aime que l'article lié énonce les avantages/inconvénients de cette technique. –

+0

Oui, je l'ai inclus pour le rendre explicite et clair, merci ... –

0

Cet algorithme est récursif. La première comparaison est le critère d'arrêt et la deuxième recherche réelle, vous ne pouvez donc pas les supprimer.

En premier vous demandez à chaque fois que vous avez déjà trouvé l'élément et en second dans quelle partie du tableau rechercher élément. Vous ne pouvez donc pas prendre ces décisions uniquement sur la base d'une comparaison.

+0

Il peut créer une fonction qui compare 'key' et' mid [a] 'et retourne le signe de la comparaison (un' int'). La comparaison de ce 'int' avec des valeurs fixes (' -1', '0' ou' 1') coûtera moins (ou pire, autant) que comparer 'key' et' mid [a] ', selon ce' key' et 'mid [a]' sont. – ereOn

4

Oui. Il ne suffit pas d'éliminer mid de l'appel récursif.

if (left == right) return NULL; 
if (left + 1 == right) return key == a[left]? &a[left] : NULL; 

mid = left + (right - left/2); 

if (key < a[mid]) return binary_search(a[],left,mid-1); 
else return binary_search(a[],mid,right); // include `mid` in next round 

Vous avez seulement besoin d'éliminer la moitié de l'ensemble à chaque récursion pour obtenir une performance O (logN). Vous allez au-delà en éliminant la moitié + 1.

Si vous n'utilisez que < pendant la récursion, l'algorithme trouvera le moins élément qui n'est pas moins de key (mais peut être supérieur à key). Terminez en effectuant un test d'égalité unique.

+0

N'auriez-vous pas besoin d'une autre comparaison (appel pré-récursif) pour déterminer si vous êtes à un seul élément et décidez d'effectuer le test d'égalité final? –

+1

@Damien: oui, j'ai ajouté cela comme vous l'avez commenté. Mais ce test n'est pas une comparaison * clé *, c'est une comparaison d'index, qui a vraisemblablement une complexité de calcul plus simple. – Potatoswatter

1

En assembleur, vous pouvez:

cmp key,a[mid] 
beq found 
bge else 

Donc, si votre compilateur est vraiment bon à l'optimisation des judas, il pourrait déjà le faire pour vous.

+1

Le compilateur fera probablement cela parce que les judas sont quelque chose que vous pouvez prendre pour acquis ... de toute façon, la différence est insignifiante. La question est importante de toute façon, puisqu'elle s'applique aux cas où la comparaison est une fonction coûteuse. – Potatoswatter

+2

Si la comparaison est coûteuse, créez une fonction qui retourne le signe de la comparaison afin que vous puissiez examiner le signe plusieurs fois. –

+0

Ce n'est pas une option en C++, où la recherche est effectuée par une fonction générique qui nécessite un foncteur typé 'bool'. – Potatoswatter

0

Tout d'abord: avez-vous besoin d'optimiser le programme? Avez-vous mesuré pour savoir où vous devez le faire? Est-ce dans cette fonction?

Pour les types primitifs, la seconde comparaison est une opération aussi rapide que possible. Le coût plus élevé de la comparaison charge l'élément dans le registre approprié, ce qui est nécessaire pour la première comparaison. Une fois cette comparaison exécutée, la valeur est déjà dans un registre et la seconde opération prend une instruction de processeur unique plus le coût possible de l'erreur de branchement.

En supposant des types entiers, le coût en temps de processeur de l'algorithme est très probablement dominé par le coût des appels récursifs si le compilateur n'est pas capable d'effectuer une optimisation de récurrence de queue. Si vous avez vraiment besoin d'optimiser cela, essayez de compiler avec tous les indicateurs d'optimisation et analysez l'assembleur pour déterminer si l'optimisation tail-récursion est appliquée. Sinon, convertissez manuellement l'algorithme de récursif en itératif.

Cela aura deux effets: obscurcir le code (évitez de modifier une solution propre sauf si vous en avez vraiment besoin) et éviter les appels de fonction.

Si vous parlez de C++ et le type est complexe et les opérateurs de comparaison surchargées sont chers, le plus rapide coup de pouce de la performance met en œuvre une méthode compare qui renverra un nombre négatif pour moins que, 0 pour l'égalité et un nombre positif si supérieur à. Ensuite, précalculez le résultat avant les comparaisons, puis effectuez des vérifications sur les entiers uniquement. Cela va supprimer le coût global de l'algorithme à un seul traitement des objets réels avec la comparaison coûteuse et vous remettre dans l'hypothèse originale.

0
for (step = 0; step < n; step <<= 1); 

for (i = 0; step; step >>= 1) 
    if (i + step < n && v[i + step] <= x) 
     i += step; 
0

Ok, c'était une question d'entrevue dans Adobe, et j'essayais juste de comprendre comment faire ceci.

Maintenant, j'ai solution a à, donc, m affichage

void binary_search (int a[] , int low , int high , int key) 
{ 
    int mid = (low+high)/2; 

    if (key == a[mid]) { 
     printf ("Number Found\n"); 
     return; 
    } 

    else { 
     int sign = Calc_sign (key-a[mid]); 
     low = low*sign + (1-sign)*mid; 
     high = mid*sign + (1-sign)*right; 
     binary_search (a,low,high,key); 
    } 
} 

int Calc_sign(int a) 
{ 
    return ((a & 80000000) >> 31); 
} 

Ainsi, dans le code, il n'y aura une comparaison pour vérifier si le keyvalue est eqaul à l'élément mi.

-

Merci

Alok Kr.

+1

Un problème possible avec de telles comparaisons basées sur les maths ('key-a [mid]') est qu'elles pourraient être insuffisantes. De plus, cela ne fonctionne qu'avec les types numériques. – UncleBens