Algorithmes de recherche

publicité
'
$
Algorithmes de recherche
&
%
1
'
$
1. La recherche dichotomique et applications
— Calcul rapide de l’exposant
— Calcul de la racine carrée
2. Introduction à la technique diviser pour régner
— Somme maximale des sous-tableaux
— Recherche de la k -ème plus petite valeur dans un tableau
3. La recherche de sous-chaı̂ne
— Recherche exhaustive
— L’algorithme de Knuth, Morris, et Pratt
— L’algorithme de Boyer-Moore
&
%
2
'
$
Recherche dichotomique et applications
• Idée principale : faire avancer le calcul en divisant en deux l’espace de recherche
• Exemple-clé : la recherche dichotomique
• Autres exemples :
— Calcul rapide de l’exposant
— Calcul de la racine carrée
&
%
3
'
$
Calcul rapide de l’exposant - Approche 1
P
≡ x = x0 ∧ n = n0 > 0
Q ≡ x = x0 ∧ n = n0 ∧ w = xn
I1 ≡ x = x0 ∧ n = n0 ∧ wk = xn ∧ 1 ≤ k ≤ n
&
%
4
'
$
float fexp1(float x, int n) {
float w = x;
int k = n;
while (k>1) {
w=w*x;
k--;
}
return w;
}
Listing 1 – Calcul classique de l’exposant – O(n)
&
%
5
'
$
Calcul rapide de l’exposant - Approche 2
P
≡ x = x0 ∧ n = n0 > 0
Q ≡ x = x0 ∧ n = n0 ∧ w = xn
I2 ≡ x = x0 ∧ n = n0 ∧ y × wk = xn ∧ 1 ≤ k ≤ n
&
%
6
'
$
float fexp(float x, int n) {
int k=n;
float w=x;
float y=1;
while (k>1)
if (k%2==0) {
k=k/2;
w=w*w;
} else {
k=(k-1)/2;
y=y*w;
w=w*w;
}
return (w*y);
}
Listing 2 – Calcul rapide de l’exposant – O(log2 n)
&
%
7
'
$
Calcul de la racine carrée
#define PRECISION 0.00001
double rcarree(double a) {
double s,e,m;
s=0;
e=a;
while (e-s>PRECISION) {
m=(s+e)/2;
if (m*m==a)
return m;
else if (m*m<a)
s=m;
else
e=m;
}
return (s+e)/2;
}
&
Listing 3 – Calcul de la racine carrée
8
%
'
$
Introduction à la technique diviser pour régner
• Idée-clé : afin de résoudre un problème donné,
— on divise le problème en n sous-problèmes (n étant minimum 2) ;
— on résout chaque sous-problème de façon récursive ;
— on combine les n solutions obtenues en une solution pour le problème complet
• Exemples :
— Somme maximale des sous-tableaux
— Recherche de la k -ème plus petite valeur dans un tableau
&
%
9
'
$
Somme maximale des sous-tableaux
Pour un environnement
#define N
int a[N];
int somme;
on définit les assertions P et Q suivantes comme, respectivement, pré- et postcondition :
P
≡
Q ≡
a = a0 ∧ N > 0



a = a0 ∧ 0 ≤ i ≤ j + 1 ≤ N



Pk=j
somme = k=i a[k]




 ∀n, l : 0 ≤ n ≤ l + 1 ≤ N ⇒ Pk=l a[k] ≤ somme
k=n
&
%
10
'
$
3
−4
6
2
9
−5
&
8
−9
−2
8
%
11
'
$
int maxsomme_bf(int a[N]) {
int i,j,k,sum;
int max=0;
for(i=0;i<N;i++) {
sum=0;
for(j=i;j<N;j++) {
sum=sum+a[j];
max=max2(max,sum);
}
}
return max;
}
Listing 4 – Somme maximale, force brute
&
%
12
'
$
int maxs_a(int a[N], int d, int f) {
int m, max_m;
if (d<=f) {
m=(d+f)/2;
max_m=max_repartie(a,d,f,m);
return max3(max_m, maxs_a(a,d,m-1), maxs_a(a,m+1,f));
} else
return 0;
}
int maxsomme_dq(int a[N]) {
return maxs_a(a,0,N-1);
}
Listing 5 – Somme maximale, diviser pour régner
&
%
13
'
$
int max_repartie(int a[N],int d, int f, int m) {
int sum, mgauche, mdroite,i;
sum=0;
mgauche=0;
for(i=m-1;i>=d;i--) {
sum=sum+a[i];
mgauche=max2(mgauche,sum);
}
sum=0;
mdroite=0;
for(i=m+1;i<=f;i++) {
sum=sum+a[i];
mdroite=max2(mdroite,sum);
}
return mgauche+a[m]+mdroite;
}
Listing 6 – La fonction maxRepartie
&
%
14
'
$
Recherche de la k -ème plus petite valeurs dans un tableau
— soit le tableau
— redistribuer les éléments autour d’une valeur pivot v , aléatoirement choisie. Prenons
v =5:
— rediriger et limiter la recherche à l’une des trois parties. Par exemple, chercher la
8-ème plus petite valeur de S revient à chercher la 3-ème plus petite valeur de SR .
— Représentation :
&
%
15
'
$
La procédure redistribuer (invariant)
∀j : d ≤ j < i − m
⇒ a[j] < v
∀j : i − m ≤ j < i
⇒ a[j] = v
∀j : i ≤ j < p
⇒ a[j] > v
&
%
16
'
$
void redistribuer(int a[],int d, int f, int v, int *i, int *m) {
int p,t;
*i=d;
p=d;
*m=0;
while ((p<=f) && (*i<=f)) {
if (a[p]==v) {
a[p]=a[*i];
a[*i]=v;
(*m)++;
(*i)++;
&
%
17
'
$
} else if (a[p]<v) {
t=a[*i];
a[*i-*m]=a[p];
a[p]=t;
if (*m>0)
a[*i]=v;
(*i)++;
}
p++;
}
}
&
%
18
'
$
int k_plus_petite(int a[], int d, int f, int k) {
int i,m,v;
v=a[f];
redistribuer(a,d,f,v,&i,&m);
if (k<=i-d-m) {
return k_plus_petite(a,d,(i-d-m-1),k);
} else if (k<=i-d) {
return v;
} else {
return k_plus_petite(a,i,f,(k-i+d));
}
}
&
%
19
'
$
3. Recherche de sous-chaı̂ne
Pour un environnement
#define N
#define M
char s[N];
char p[M];
int k;
&
%
20
'
$
on définit
pré
post
≡ N






≡





≥ M > 0 ∧ s = s0 ∧ p = p0
s = s0 ∧ p = p0
0≤k ≤N −M
⇒ match(k , M ) ∧ ∀k 0 < k : ¬match(k 0 , M )
k = −1
⇒
∀k 0 : ¬match(k 0 , M )
où
match(i, j) ⇔ ∀r : 0 ≤ r < j : s[i + r] = p[r]
Quel sera l’invariant ?
&
%
21
'
$
Approche 1 : recherche exhaustive
Proposition pour l’invariant :
I≡



 s = s0 ∧ p = p0
0≤k <N ∧0≤j ≤M



∀r : 0 ≤ r < j ⇒ p[r] = s[k + r]
Exploiter cet invariant donne lieu à un algorithme implémentant une recherche exhaustive
(angl. brute force)
&
%
22
'
$
int match(char *s, char*p) {
int j,k,n,m;
n=strlen(s);
m=strlen(p);
k=0;
j=0;
while ((j<m) && (k<=n-m)) {
if (p[j]==s[k+j]) {
j++;
} else {
k=k+1;
j=0;
}
}
if (j<m)
return -1;
else
&
return k;
%
}
23
'
$
• Quel est le pire des cas pour l’algorithme ?
• Quelle est la complexité, au pire des cas ?
&
%
24
'
$
Approche 2 : l’algorithme KMP
• inventé par D. Knuth, J.H. Morris, V. Pratt (1977)
• l’idée : exploiter des informations concernant le pattern (p) pour éviter de comparer
une deuxième fois les caractères du texte (s) déjà comparés :
Echec de comparaison (s[k+1]6=p[j+1] (a), déplacement effectué par l’algorithme
exhaustif (b), déplacement effectué par KMP (c)
&
%
25
'
$
Besoin de connaı̂tre, pour chaque position j dans p, la longueur du plus long préfixe étant
aussi suffixe de p[0..j
− 1].
&
%
26
'
$
void compute_prefix_f(char *p, int pi[]) {
int m,k,q;
m=strlen(p);
pi[0]=0;
k=0;
q=1;
while (q<m) {
if (p[q]==p[k]) {
k++;
pi[q]=k;
q++;
} else if (k!=0) {
k=pi[k];
} else {
pi[q]=0;
q++;
}
&}
%
}
27
'
$
int match_kmp(char *s, char *p) {
int n,m,j,k;
int pi[strlen(p)];
compute_prefix_f(p,pi);
n=strlen(s);
m=strlen(p);
k=0;
j=0;
while ((j<m) &&(k<=n-m)) {
if (p[j]==s[k+j]) {
j++;
} else if (j==0) {
k++;
} else {
k=k+j;
j=pi[j-1];
&
}
%
}
28
'
$
if (j<m)
return -1;
else
return k;
}
&
%
29
'
$
• Quelle est la complexité de l’algorithme KMP au pire des cas ?
• Comment l’algorithme se comporte dans le cas moyen ?
• Y-a-t il un moyen pour faire mieux, et de passer en-dessous de la linéarité ?
&
%
30
'
$
Approche 3 : Boyer-Moore (version simplifiée)
• inventé par R.S. Boyer and J.S. Moore (1977)
• idée : utiliser des informations à propos du pattern pour éviter de considérer des
caractères du texte dont on sait qu’ils ne pourraient pas faire partie d’un match
• Complexité au cas moyen : O(N/M )
&
%
31
'
$
int match_bm(char *s, char *p) {
int n,m,j,k;
int last[CHAR_MAX] = { 0 };
n=strlen(s);
m=strlen(p);
for(j=0;j<m;j++) {
last[p[j]]=j;
}
k=0;
j=m-1;
while ((j>=0) && (k<=n-m)) {
if (p[j]==s[k+j]) {
j--;
} else {
k=k+max2(1,j-last[s[k+j]]);
j=m-1;
}
&}
%
32
'
$
if (j!=-1)
return -1;
else
return k;
}
&
%
33
'
$
I



p = p0 ∧ s = s0



≡
−1 ≤ j ≤ M ∧ 0 ≤ k ≤ N − M




 p[j + 1..M − 1] = s[k + j + 1..k + M − 1]
&
%
34
'
$
A noter :
• Pour cette version simplifiée, la complexité au pire des cas est de O(N M )
• L’algorithme complet de Boyer-Moore utilise des connaissances supplémentaires à
propos du pattern (comme KMP) pour arriver à une complexité au pire de cas de
O(N + M ).
&
%
35
'
$
Le tri et ses applications
&
%
36
'
$
1. Algorithmes de tri
— Tri par sélection
— La méthode Quicksort
2. Quelques algorithmes de tri avancé
— L’algorithme heapsort
— Le tri en ordre linéaire : radix sort
3. Applications de tri
— Calcul de l’enveloppe convexe
— Le problème du mariage stable
&
%
37
'
$
Tri par sélection
Iext


a permutation de a0




 0≤i<N
≡

∀k : i + 1 ≤ k < N − 1 ⇒ a[k] ≤ a[k + 1]




 ∀k : 0 ≤ k ≤ i ⇒ a[k] ≤ a[i + 1]
Iint ≡



 max = a[pos]
−1 ≤ j ≤ i − 1



∀k : j + 1 ≤ k ≤ i ⇒ a[k] ≤ max
&
%
38
'
$
int i,j,pos;
float max;
i=N-1;
while (i>=1) {
max=a[i];
pos=i;
j=i-1;
while (j>=0) {
if (a[j]>max) {
max=a[j];
pos=j;
}
j--;
}
a[pos]=a[i];
a[i]=max;
i--;
&
}
%
}
39
'
$
La méthode Quicksort void quicksort(int a[N]) {
quicksort_a(a,0,N-1);
}
void quicksort_a(int a[N], int d, int f) {
int p;
if (d < f) {
p = partager(a,d,f);
quicksort_a(a,d,p-1);
quicksort_a(a,p+1,f);
}
}
Listing 7 – L’algorithme Quicksort (a)
&
%
40
'
$
int partager(int a[N], int d, int f) {
int v,x,i,j;
v = a[f];
i = d-1;
j = f;
do {
do i++; while (a[i] < v);
do j--; while (a[j] > v);
if (i < j) {
x = a[i];
a[i] = a[j];
a[j] = x;
}
} while (j > i);
a[f] = a[i];
a[i] = v;
return i;
}
&
%
41
'
$
L’algorithme Heapsort
Pour rappel : le tas comme structure de données
— arbre binaire presque complet
— la valeur de chaque noeud est supérieure (ou égale) aux valeurs de ses descendants
1
100
N
19
36
17
3
25
&
1
2
7
%
42
'
$
Algorithme pour effectuer le tri d’un tableau a :
1. transformer le tableau a en un tas t
2. ∀i
∈ {N, . . . , 1} :
a) a[i]
← root(t)
b) retransformer t en un tas en prenant la dernière feuille
comme nouvelle racine et la ”poussant” vers le bas
&
%
43
'
$
iheap_t init_heap(void) {
iheap_t h;
int i;
h = malloc(sizeof(struct iheap));
h->nb = 0;
for (i = 0; i < MAX_TAS; i++)
h->els[i] = 0;
return h;
}
void add_element(iheap_t h, int x) {
if (has_room(h)) {
h->els[h->nb] = x;
reheap_up(h, h->nb);
h->nb = h->nb + 1;
}
}
&
%
44
'
$
void reheap_up(iheap_t h, int i) {
int p;
p = parent(i);
while (p >= 0 && h->els[p] < h->els[i]) {
exchange(h, i, p);
i = p;
p = parent(i);
}
}
&
%
45
'
$
int take_root(iheap_t h) {
int x;
if (h->nb >= 0) {
x = h->els[0];
h->els[0] = h->els[h->nb - 1];
h->nb = h->nb - 1;
reheap_down(h);
return x;
} else {
return -INT_MAX;
}
}
&
%
46
'
$
void reheap_down(iheap_t h) {
int i, maxchild;
bool stop;
i = 0;
stop = false;
do {
if (right(i) < h->nb) {
if (h->els[left(i)] < h->els[right(i)]) {
maxchild = right(i);
} else {
maxchild = left(i);
}
} else {
if (left(i) < h->nb) {
maxchild = left(i);
} else {
maxchild = i;
&
}
%
}
47
'
$
if (h->els[i] < h->els[maxchild]) {
exchange(h, i, maxchild);
i = maxchild;
} else {
stop = true;
}
} while (!stop);
}
&
%
48
'
$
void heap_sort(int a[], int n) {
iheap_t h;
int i;
h = init_heap();
for (i=0;i<n;i++) {
add_element(h,a[i]);
}
for(i=n-1;i>=0;i--){
a[i] = take_root(h);
}
}
&
%
49
'
$
Remarques :
• On peut prouver que O(nlog2 n) est une borne inférieure au tri basé sur la
comparaison des clés ; ce qui veut dire qu’il n’y a pas vraiment moyen de faire mieux
• Mais : si le type des clés est discret (et limité), il y a moyen de faire mieux : radix sort.
&
%
50
'
$
Le tri en ordre linéaire : Radix Sort
&
%
51
'
$
&
%
52
'
$
struct list {
int info;
struct list *next;
};
typedef struct list *list_ptr;
int charac(int i, int x) {
while (i>1) {
x=x/BASE;
i--;
}
return x%BASE;
}
&
%
53
'
$
void radix(list_ptr *r) {
list_ptr head[BASE], tail[BASE];
int i,j,h;
for(i=1;i<DIGITS;i++) {
for(j=0;j<BASE;j++)
head[j]=NULL;
/* partition de la liste */
while (*r!=NULL) {
h=charac(i,(*r)->info);
if (head[h]==NULL) {
head[h]=*r;
} else
tail[h]->next=*r;
tail[h]=*r;
*r=(*r)->next;
tail[h]->next=NULL;
}
&
%
54
'
$
/* reconstruction de la liste */
*r=NULL;
for(j=BASE-1;j>=0;j--) {
if (head[j]!=NULL) {
tail[j]->next=*r;
*r=head[j];
}
}
}
}
&
%
55
'
$
Evaluation expérimentale
N
sélection
par tas
radix sort
1.000
2
0
0
5.000
38
1
0
10.000
152
3
2
100.000
15.127
35
20
TABLE 1 – Quelques temps d’exécution des 3 algorithmes de tri (en millisecondes).
&
%
56
'
$
Applications du tri
• Calcul de l’enveloppe convexe
• Le problème du mariage stable
&
%
57
'
$
Calcul de l’enveloppe convexe
• Définition (dans le plan 2D)
• Algorithme naı̈f : O(n3 ) où n représente le nombre de points.
&
%
58
'
$
Approche 1 : Algorithme exhaustive
procedure EC(a set of points P )
H←∅
for all (p, p0 ) ∈ P × P do
v ← true
for all q ∈ P with q 6= p and q 6= p0 do
→
0
if q is on the left of pp then
v ← f alse
if v then
→
0
H ← H ∪ {pp }
return H
&
%
59
'
$
Approche 2 : L’algorithme Graham scan
Comment imposer une structure sur l’ensemble des points ?
&
%
60
'
$
Proposition (R. Graham, 1972) : trié au sens inverse des aiguilles d’une montre autour du
point se trouvant le plus bas.
&
%
61
'
$
procedure EC(a set of points P )
let p1 be the lowest point in P
let p2 , . . . , pN be the remaining points sorted counterclockwise (by angle) around p1
S ← empty stack
S ← push(S, p1 , p2 , p3 )
for i from 4 to N do
while (St−1 , St , pi ) do not form a left turn do
S ← pop(S)
S ← push(S, pi )
return S
Dans l’algorithme, St et St−1 représentent, respectivement, l’élément au sommet de S et
l’élément précédent dans S
&
%
62
'
$
&
%
63
'
$
&
%
64
'
$
&
%
65
'
$
&
%
66
'
$
Questions
• Quelle est la complexité de l’algorithme ?
• Quel choix pour les structures de données ?
&
%
67
'
$
Mariage stable
Supposons N hommes et N femmes qui veulent se marier. Etant donné que
• pour chaque homme la liste comprend les N femmes en ordre de préférence
décroissante
• pour chaque femme la liste comprend les N hommes en ordre de préférence
décroissante
trouver un mariage stable.
Un mariage est stable s’il n’y a aucune personne X telle que
— X préfère une personne Y à son partenaire actuel
— Y préfère la personne X à son partenaire actuel
Principe de cet algorithme à la base de plusieurs algorithmes utiles
&
%
68
'
$
Par exemple, soient les préférences suivantes :
Julien
→ Amélie, Bernadette
Amélie
→ Julien, Christian
Christian
→ Amélie, Bernadette
Bernadette
→ Christian, Julien
(Julien,Bernadette), (Christian,Amélie)
pas stable
(Julien,Amélie), (Christian,Bernadette)
stable
Mariages possibles :
(Gale & Shapley 1962) : il existe toujours une solution stable pour n’importe quel N
&
%
69
'
$
∈ M and w ∈ W are free
while ∃m ∈ M free and hasn’t proposed to every woman do
choose such a man m ∈ M
let w be the highest-ranked woman in m’s preference list to whom m has not proposed
if w is free then
(m, w) become engaged
Initially all m
else
let m0 be the man w is currently engaged to
if w prefers m to m0 then
(m, w) become engaged
m0 becomes free
else
m remains free
return the set S of engaged pairs
&
%
70
'
$
Analyse de l’algorithme
• Une fois associée à un partenaire, w ∈ W reste engagée et elle se voit attribuée des
partenaires de plus en plus bonne qualité (selon la liste de ses préférences)
• Lors du processus, un homme m ∈ M propose à une suite de femmes de moins en
moins bonne qualité (selon la liste de ses préférences)
• Quel est l’invariant de la boucle ?
• Comment mesurer le progrès effectué par l’algorithme ?
&
%
71
'
$
Comment mesurer le progrès effectué par l’algorithme ?
• Soit P (t) l’ensemble de couples (m, w) tels que m a proposé à w lors des t
premières itérations.
• Alors on a ∀t : P (t + 1) > P (t) et ∀t : P (t) ≤ N 2 où N = |M | = |W |
• D’où O(N 2 )
&
%
72
'
$
Analyse de l’algorithme (2)
• L’algorithme est non-déterministe, mais toutes les exécutions calculent la même
attribution :
S = {(m, best(m)|m ∈ M }
où best(m) représente le partenaire le plus préféré attribué à m dans l’une des
attributions stables.
• Mais. . .dans S chaque w ∈ W se voit attribuée le partenaire le moins préféré !
• Exemple :
m préfère w sur w0
m0 préfère w0 sur w
w préfère m0 sur m
w0 préfère m sur m0
Quelle des deux attributions stables est calculée par l’algorithme ?
&
%
73
Téléchargement