Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
July 19, 2021 07:45 pm GMT

L'algorithme de recherche binaire

La recherche binaire (binary search) fait partie des algorithmes fondamentaux de l'informatique que tout bon informaticien se doit de connatre. Dans leur clbre livre A Method Of Programming, Edsger W. Dijkstra et W.H.J. Feijen qualifient mme cet algorithme de quasi canonique:

The binary search [...] is an important, almost canonical, algorithm. It should be familiar in all its details to any educated computer scientist.

Je constate cependant que rares sont les programmeurs qui aujourd'hui, sont capables de comprendre et d'implmenter correctement cet algorithme. Ils l'ont peut-tre oubli, ou peut-tre ne l'ont-ils jamais vraiment appris.

Le but de cet article est d'expliquer cet algorithme l'aide de prdicats logiques et de vous permettre de l'implmenter de manire efficiente et correcte.

L'algorithme de recherche binaire permet de trouver efficacement un lment dans un tableau tri. Commenons par dfinir formellement ce qu'est la recherche d'un lment dans un tableau:

Si aaa un tableau de taille NNN, avec des index 0i<N0 \leq i < N0i<N et si xxx est un lment de ce tableau, trouver xxx dans aaa revient trouver l'index ixi_xix tel que a[ix]a[i_x]a[ix] soit gal xxx.

ix:0xi<N:xaa[ix]=xi_x : 0 \leq x_i < N : x \in a \wedge a[i_x] = xix:0xi<N:xaa[ix]=x

Nous avons aussi dit que le tableau est tri (du plus petit au plus grand). Ca signifie que pour tout iii et jjj, si iii est plus petit ou gal jjj alors a[i]a[i]a[i] est aussi plus petit ou gal a[j]a[j]a[j].

i,j:0ij<N:a[i]a[j]\forall i,j : 0 \leq i \leq j < N : a[i] \leq a[j] i,j:0ij<N:a[i]a[j]

Nous pouvons gnraliser l'objectif de la recherche dans le cas o l'lment recherch xxx est prsent plusieurs fois. Dans ce cas, nous aimerions avoir l'index du premier xxx. Autrement dit, l'lment qui prcde xxx doit tre strictement plus petit que xxx:

ix:0xi<N:xaa[ix]=xa[ix1]<xi_x : 0 \leq x_i < N : x \in a \wedge a[i_x] = x \wedge a[i_x-1] < xix:0xi<N:xaa[ix]=xa[ix1]<x

Cette nouvelle condition peut s'avrer problmatique si xxx est le tout premier lment du tableau aaa. Dans ce cas, ix1i_x-1ix1 est gal 1-11 et a[1]a[-1]a[1] n'est pas dfini. Nous pouvons rsoudre ce problme en dfinissant un lment virtuel la position 1-11 qui soit plus petit que tous les autres lments du tableau:

a[1]=a[-1] = - \inftya[1]=

Nous sommes partis du principe que xxx soit prsent dans aaa, mais nous pouvons lever cette contrainte en spcifiant que si xxx n'est pas dans aaa, alors on aimerait avoir l'index o il devrait tre. Si xxx est strictement plus petit que a[0]a[0]a[0], alors le prdicat est satisfait en mettant ixi_xix gal 000. Si xxx est strictement plus grand que a[N1]a[N-1]a[N1], l'index o il devrait tre c'est NNN, mais a[N]a[N]a[N] n'est pas dfini.Nous contournons le problme de la mme manire que pour a[1]a[-1]a[1] en dfinissant un nouvel lment virtuel la position NNN qui soit plus grand que tous les autres lments du tableau:

a[N]=+a[N] = + \inftya[N]=+

On peut rcrire le prdicat de la recherche ainsi:

ix:0xiN:a[ix]xa[ix1]<xi_x : 0 \leq x_i \leq N : a[i_x] \geq x \wedge a[i_x-1] < xix:0xiN:a[ix]xa[ix1]<x

Au dbut, l'intervalle de recherche est [0..N][0..N][0..N], l'ide de l'algorithme de recherche binaire consiste rduire cet intervalle chaque itration afin de trouver ixi_xix. Nous dfinissons l'intervalle de recherche l'aide de deux nouvelles variables : LLL (pour left ou lower) et RRR (pour right). Ces nouvelles variables devront en tout temps satisfaire l'invariant suivant:

L,R:0L<RN:a[L]<xa[R]xL,R : 0 \leq L < R \leq N : a[L] < x \wedge a[R] \geq xL,R:0L<RN:a[L]<xa[R]x

Pour commencer, nous pouvons initialiser LLL 1-11 et RRR NNN. Grce nos deux lments virtuels, l'invariant est satisfait, car a[L]=<xa[L] = - \infty < xa[L]=<x et a[R]=+xa[R] = + \infty \geq xa[R]=+x. Il nous suffit maintenant de rduire l'intervalle ]L,R]]L,R]]L,R] et quand LLL sera juste ct de RRR, nous aurons L=R1L = R-1L=R1 et si l'invariant est toujours vrai, alors on aura R:0xiN:a[R]xa[R1]<xR : 0 \leq x_i \leq N : a[R] \geq x \wedge a[R-1] < xR:0xiN:a[R]xa[R1]<x et RRR sera le ixi_xix que nous cherchons.

Avant de coder la recherche binaire, il nous faut encore juste trouver comment rduire l'intervalle ]L,R]]L,R]]L,R] tout en conservant l'invariant. La solution consiste prendre un hhh n'importe o entre LLL et RRR ( L<h<RL < h < RL<h<R) et si a[h]a[h]a[h] est strictement plus petit que xxx, alors nous pouvons donner LLL la valeur de hhh, car a[L]a[L]a[L] serait strictement plus petit que xxx comme nous ne modifions pas RRR, l'invariant tiendrait toujours. Au contraire, si a[h]a[h]a[h] est plus grand ou gal xxx, alors nous pouvons donner RRR la valeur de hhh, car a[R]a[R]a[R] serait plus grand ou gal xxx comme nous ne modifions pas LLL, l'invariant tiendrait encore.

Tant que L<R1L < R-1L<R1, on peut toujours trouver un hhh entre LLL et RRR. On peut par exemple prendre h=L+1h = L+1h=L+1 ou h=R1h = R-1h=R1, mais notre algorithme ne ferait que des tout petits pas vers la solution. Une manire plus efficace consiste prendre hhh au milieu de l'intervalle ]L,R]]L,R]]L,R], autrement dit h=L+R2h = \frac{L+R}{2}h=2L+R. hhh est un index et doit tre un nombre entier; nous devons alors arrondir hhh vers un entier. On peut sans autre arrondir hhh vers le haut ou vers le bas et nous dcidons ici de l'arrondir vers le bas : h=L+R2h = \lfloor\frac{L+R}{2}\rfloorh=2L+R. Si L<R1L < R-1L<R1, alors L<h=L+R2<RL < h = \lfloor\frac{L+R}{2}\rfloor < RL<h=2L+R<R. Si L=R1L = R-1L=R1, la recherche est termine et nous n'avons plus besoin de rduire l'intervalle.

Nous avons maintenant assez de logique et de math pour pouvoir crire un algorithme de recherche binaire correct. Pour cet article, nous dcidons de coder cet algorithme en Python:

def binary_search(a: list, x) -> int:    L:int = -1    R:int = len(a)    while (L < R-1):        h: int = (L+R) // 2        if a[h] < x:            L = h        else:            R = h    return R

Python n'a pas de problme de dpassement de capacit (overflow) avec les entiers, mais la plupart des autres langages n'ont pas ce confort et l'opration L+R pourrait provoquer un dpassement de capacit. On pourrait alors sans autre remplacer h = (L+R) // 2 par h = L + (R-L) // 2 et ainsi supprimer le risque de dpassement de capacit.

Si on souhaite savoir si xxx est prsent dans aaa et retourner un boolean, on peut alors crire

def present(a: list, x) -> bool:    R = binary_search(a, x)    return R < len(a) and a[R] == x

Pour complter, voici l'algorithme cod en C:

#include <stdbool.h>int binary_search(int* a, int n, int x) {    int L = -1;    int R = n;    while (L < R-1) {        int h = L + (R-L) / 2;        if (a[h] < x) {            L = h;        } else {            R = h;        }    }    return R;}bool present(int* a, int n, int x) {    int R = binary_search(a, n, x);    return (R < n && a[R] == x);}

De nos jours, on crit souvent des programmes ou des apps en utilisant des frameworks et en copiant des bouts de code trouvs sur Internet. Les programmeurs amateurs diront qu'il est inutile de comprendre les dtails des algorithmes fondamentaux, car ils ont dj t implments par des personnes trs comptentes et qu'il suffit d'utiliser la bonne bibliothque. L'informaticien qui se respecte ne peut se satisfaire d'une telle dmarche et sa saine curiosit ne sera satisfaite que lorsqu'il aura compris et implment lui-mme l'algorithme.

J'espre que cet article aura raviv la mmoire de certains et veill chez d'autres l'envie d'tudier plus en dtail la science et l'art de l'informatique. Voici pour conclure quelques livres qui vous permettront d'en savoir plus:


Original Link: https://dev.to/supcik/l-algorithme-de-recherche-binaire-1jg8

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To