NFP136 – VARI 2 Algorithmique et Structures de données

publicité
NFP136 – VARI 2
Algorithmique et
Structures de données
Prolongement de la partie « Algorithmique et
Programmation » du cours NFP 135 (VARI 1)
1
L'Informatique a de multiples facettes
- C'est à la fois une science : fondements théoriques de la
calculabilité et de l'algorithmique, principes de fonctionnement
et arithmétique des ordinateurs, etc.
- C'est également, par nature, un domaine de savoirs
technologiques : mise en œuvre par la programmation
(syntaxe et bonnes pratiques de l'utilisation des langages de
programmation), et applications directes dans le monde réel
(vidéo, son, interfaces hommes-machines, jeux vidéo, etc.).
- Dans le prolongement de NFP 135, cette UE se focalise sur
la présentation des structures de données « usuelles » et des
algorithmes associés, mais également sur leur implémentation
et la prise en main d'un langage de haut niveau, ainsi que sur
des connaissances de base liées aux systèmes d'exploitation.
2
Objectifs de cette partie du cours :
- Pouvoir manipuler efficacement des données plus
nombreuses (et plus complexes).
- Ce qui nécessite de les stocker dans des structures de
données « intelligentes », de façon à ce que les
opérations (algorithmes) avec lesquelles on les exploite
soient le plus efficace possible.
- Les informaticiens, au cours du temps, ont défini des
Structures de Données usuelles (tableaux, listes, arbres,
graphes, etc.) et des Algorithmes usuels (tri, manipulation
de données structurées, etc.).
- Avec un soucis d’abstraction et de modularité : on
cherche à dissocier une structure de données de la façon
dont elle est implémentée (notion de Type Abstrait).
3
Contenu :
- Structures de données élémentaires (tableaux,
tables de hachage, listes, piles, files) et opérations
élémentaires associées (création, parcours, tri).
- Mesure d’efficacité de ces algorithmes (et des
autres) : notions de complexité.
- Des structures plus complexes dédiées : graphes,
arbres (binaire ou non), tas.
- Modèles de calcul : langages formels et automates
(graphes d'état).
4
Contenu :
⇒ Langage d’implémentation : JAVA
Car :
- Langage de haut niveau et portable,
- Facilite l’abstraction et la programmation modulaire
par la structuration en classes d'objets,
- Dispose d’un mécanisme de « garbage collector »
(ramasse-miettes) rendant inutile la désallocation
« à la main » de l'espace mémoire inutilisé.
5
QUELQUES RAPPELS DE
PROGRAMMATION
(JAVA : Paramètres et Tableaux)
6
Variables et références
• Un programme JAVA utilise des variables
pour garder trace des opérations effectuées.
• En fait, le déroulement d'un programme peut
se voir comme une suite d'états de la mémoire
(vision de la programmation impérative).
• A chaque variable est associée une place
mémoire, qui possède une adresse.
• Une référence vers une variable est la valeur
de l'adresse de son emplacement mémoire.
7
Variables, fonctions et paramètres
• Lors d'un appel de fonction, les valeurs
passées en paramètres sont recopiées dans
des variables locales (paramètres formels) :
public static void f
(int i) {i=5;}
(…) int a=2; f(a); (…)
/* Ici, i et a ne référencent pas
du tout la même zone mémoire :
modifier la valeur de l'un n'a
donc aucune influence sur
celle de l'autre ! */
• Ce mécanisme de recopie est systématique :
quoi qu'un paramètre représente, sa valeur
est recopiée dans une variable locale.
8
Variables de type Objet
• Ce qu'on appelle un Objet en Java est en fait
une référence vers un Objet :
String str=new String("oui");//str
contient l'adresse d'un objet String
• Que vaut alors str après l'appel suivant ?
public static void f2(String s)
{s=new String("non");}
(…) f2(str); (…)
9
Paramètres objets
• Que valent p.x et p.y à la fin ?
public class Point {public int x=0, y=0;}
(…) public static void changePoint(Point p,
int x, int y) {p.x=x; p.y=y;} (…)
(…) Point pt=new Point(); (…)
(…) changePoint(pt, 1, 2); (…)
10
Lexicologie
• Dans un langage de programmation, passer
un paramètre par référence signifie fournir
l'adresse de son emplacement mémoire (et
non sa valeur – cf passage par valeur).
• Un objet Java est toujours passé par référence
– Ainsi, une variable String str contient l'adresse
d'un objet String,
– Mais cette adresse est recopiée localement
lorsque str est en paramètre d'une fonction.
11
Les tableaux
12
Tableau
• Un tableau est une structure de données
qui réunit un nombre prédéfini de données
d'un même type.
• On peut le voir comme une suite de cases
contiguës, chacune identifiée (indicée) par
un entier.
13
Tableau
• La taille (nombre de cases) du tableau doit
être connue au moment de sa création et
ne peut pas être modifiée. La mémoire
nécessaire à toutes les cases est allouée
une fois pour toutes.
• On a un accès direct en consultation et en
modification à chaque case du tableau
(repérée par son indice).
14
Tableaux en Java (rappels)
• Il existe un type tableau prédéfini en Java.
• Contrairement aux types primitifs (boolean,
int, float, etc.), une variable de type tableau :
– Contient une référence vers un espace mémoire
composé d'emplacements mémoire consécutifs.
– Plus précisément, si on déclare int[] t, alors
t contient l'adresse de la 1ère case du tableau.
15
Tableaux en Java (rappels), suite
• Exemple :
int[] T; /*déclare le tableau d’entiers T et l’initialise à null*/
int[] Tbis={1,2,3,4}; /*déclare le tableau Tbis et l’initialise à
une référence vers un espace mémoire contenant les 4 entiers 1, 2, 3, 4*/
int|] Tter=new int[4]; /*déclare le tableau d'entiers Tter et
l'initialise à une référence vers un espace mémoire contenant 4 entiers*/
• On peut faire toutes sortes d’opérations, dès lors que
le tableau n’est pas vide (!=null) :
– Tbis[0]=Tbis[0]+10;
– for(int i=0; i<Tbis.length; i++)
System.out.println(Tbis[i]);
16
Tableaux d'objets en Java
• Attention :
– L'instruction new permet d'allouer de la place pour un
certain nombre de références vers des objets, mais...
– De l'espace mémoire doit ensuite être alloué pour
chaque objet référencé par le tableau !
• Par exemple :
– String[] Tab=new String[3];
– if(Tab[0].equals("toto")) {return true;}
– Ce bout de code produit une exception, car Tab[0]
vaut null !
– Il manque : Tab[0]=new String();
17
Tableaux et références
• On déclare int[] tab=new int[6];
– Puis, on écrit :
(…) public static void f3(int[] t)
{for(int i=0;i<6;i++) t[i]=i+1;} (…)
(…) f3(tab); (…) /*que vaut tab ?*/
– On écrit ensuite :
(…) public static void f4(int[] t)
{t=new int[1]; t[0]=9;} (…)
(…) f4(tab); (…) /*que vaut tab ?*/
18
COURS ALGORITHMES
ET COMPLEXITÉ
•
•
•
•
•
PLAN
Définition d'un algorithme
Un exemple
Présentation des algorithmes
Évaluation d'un algorithme
Complexité
19
DÉFINITION D'UN ALGORITHME
• Procédure de calcul bien définie qui prend en
entrée un ensemble de valeurs et produit en
sortie un ensemble de valeurs.
(Cormen, Leiserson, Rivert)
• Ensemble d'opérations de calcul élémentaires,
organisé selon des règles précises dans le but
de résoudre un problème donné. Pour chaque
donnée du problème, il retourne une réponse
après un nombre fini d'opérations.
(Beauquier, Berstel, Chrétienne)
20
• Phase 1 ENONCÉ DU PROBLÈME
Spécification
• Phase 2 ÉLABORATION DE L'ALGORITHME
• Phase 3 VÉRIFICATION (PREUVE)
• Phase 4 MESURE DE L'EFFICACITÉ
Complexité
• Phase 5 MISE EN OEUVRE
Programmation
21
UN EXEMPLE
Le tri par sélection ordinaire
ÉNONCÉ DU PROBLÈME
• Entrée:
Un tableau d’entiers
• Sortie:
Le même tableau, trié par ordre croissant
22
L'ALGORITHME - Tri par sélection
Principe :
tant que il reste plus d'un élément non trié faire
- chercher le plus petit parmi les non triés,
- échanger le premier élément non trié avec
le plus petit trouvé.
fait
23
EXEMPLE (suite)
7
5
5
5
5
5
8
8
7
7
7
7
éléments triés
éléments non triés
15 5
10
15 7 10
15 8
10
8 15 10
8 10 15
8 10 15
24
Tri par sélection – en détails
tri-selection (tableau A de n entiers)
début
pour i = 0 à n-2
faire
// les éléments de 0 à i-1 sont déjà triés
// i : place où on doit mettre le suivant plus petit
// recherche de la place p du plus petit des non triés
p = i;
pour j = i+1 à n-1
faire
si A[j] < A[p] alors
p = j;
finsi
fait
échanger (A[i], A[p]);
fait
fin
25
Evaluation des algorithmes
Complexité des algorithmes
26
ÉVALUATION D'UN ALGORITHME
EFFICACITÉ
• TEMPS D'EXÉCUTION
• MÉMOIRE OCCUPÉE
EXEMPLE DE PROBLEME A RESOUDRE
n entier est-il premier ?
n = 1148 ? non, divisible par 2
n = 1147 ? ??? (en fait, 1147=31*37)
27
EVALUER LE NOMBRE D'OPÉRATIONS
NECESSAIRES POUR LA RESOLUTION DU
PROBLEME EN FONCTION DE LA TAILLE
DES DONNÉES ?
–
OPERATIONS « ELEMENTAIRES » :
+, -, *, /, >, <, =, ET, OU
–
TAILLE DES DONNEES = ESPACE OCCUPE
==> PLUSIEURS « MESURES » POSSIBLES !
–
–
–
MEILLEUR CAS (PEU PERTINENT),
PIRE DES CAS,
EN MOYENNE.
28
EXEMPLE
• PARTITIONNEMENT DE N ENTIERS
– Données : N entiers positifs
– Donc taille des données = nombre d'entiers = N
– Problème : existe-t-il un sous-ensemble de ces entiers
dont la somme est exactement la moitié de la somme
totale des N entiers ?
> Par exemple : 64, 36, 7, 10, 2, 15, 23, 74, 49, 30
– Algorithme pour résoudre le problème ?
• Enumérer les 2N sous-ensembles possibles,
• Evaluer la somme des entiers dans chacun d'entre eux, et
conserver celui (ou ceux) qui convien(nen)t, s'il(s) existe(nt).
29
Temps de calcul si 10-9 secondes
par opération (1 GHz)
N
10
Somme de
N nombres
0,01 µs 0,02 µs 0,05 µs
Enumération 1 µs
de 2N objets
20
1 ms
50
100
200
0,1 µs
0,2 µs
>10 jours >30 000 >3.1043
milliards années
d'années
30
EXEMPLE (ajout, sous condition, d’éléments du tableau B à des éléments
du tableau A ; on suppose que les 2 tableaux ont au moins m+n éléments)
AjoutTab (….)
début
pour
i = 0 à m-1
faire
A[i] := B[i] ;
pour j = i à n+i-1
m
n
fois
si
A[i] < B[j]
1
faire
alors
1
A[i] := A[i]+B[j] ; 2
fois
finsi ;
fait;
fait;
fin
31
nombre d'opérations
Pire des cas, test A[i]<B[j] toujours vrai
m(1+ 3n)= 3nm+m opérations
Meilleur cas, test A[i]<B[j] toujours faux
m(1+ n)= nm+m opérations
32
EXEMPLE (suite)
tri-selection
début
pour
i = 0 à n-2
faire
p = i;
1
pour j = i+1 à n-1 faire
si A[j] < A[p] alors 1
p = j;
1
Pour chaque i,
n-i-1
fois
1+2(n-i-1)+3
opérations
finsi
fait
échanger(A[i], A[p]);
fait
fin
3
33
nombre d'opérations (pire des cas)
n −2
n −1
i =0
i =1
4( n − 1) + 2 ∑ ( n − i − 1) = 4( n − 1) + 2 ∑ i =
4 (n-1)+ n(n-1) = n2 +3n - 4
Rappel : somme des n-1 premiers entiers = n(n-1)/2
34
Algorithme "efficace"
=
Algorithme polynomial
EXEMPLE (suite) :
n2 + 3n - 4
Problèmes "intraitables"
→
Théorie de la complexité
EXEMPLE : partitionnement d'entiers
35
COMPLEXITE DES ALGORITHMES
D(n) = {données de taille n}
coûtA(d) = coût (= temps de calcul) de
l'algorithme A sur la donnée d
• « PIRE DES CAS »
MaxA(n) = Maxd∈D(n) {coûtA(d)}
- borne supérieure du coût
- assez facile à calculer (en général)
- « souvent » réaliste
36
•
COMPLEXITE EN MOYENNE
p(d) = probabilité de la donnée d
MoyA (n) =
∑ p(d)*coût (d)
A
d∈Dn
- Nécessite de connaître (ou de pouvoir
estimer) la distribution des données
- Souvent difficile à calculer
- Intéressant si le comportement « usuel »
de l'algorithme est éloigné du pire cas
37
EXEMPLE : produit de 2 matrices
static float[][] produitMatrices (float[][]
A,float[][] B){
//Données A[m,n] et B[n,p] ; Résultat C[m,p]
float[][] C = new float[A.length][B[0].length];
for(int i=0;i< A.length;i++){
for(int j=0;j< B[0].length;j++){
float s=0;
p
n
for(int k=0;k< B.length;k++)
s=s+A[i][k]*B[k][j];
fois fois
C[i][j]=s;
}
}
return C;
}
pire cas = cas moyen=1+mp(3n+2) opérations
38
m
fois
NOTATIONS DE LANDAU
f , g fonctions
ℕ→ ℕ
• Notation O
f = O(g) lire : “ f est en O de g ” ⇔
Il existe n0 ∈ℕ et c constante t.q. f(n) ≤ c.g(n)
pour tout n ≥ n0
(En pratique : f=O(g)
il existe c’ t.q. f(n) ≤ c’.g(n))
39
EXEMPLE
Algorithme avec f(n) = 4n3+2n+1 opérations
- f(n)=O(?)
pour tout n ≥ 1: f(n) ≤ 7n3
n0=1 c=7 g(n)=n3
donc
f(n) = O(n3)
40
Notation O → majorant du nombre
d'opérations d'un algorithme
cg(n )
f(n)
n0
f = O(g(n))
41
Complexité →
• comportement des algorithmes quand la
taille des données augmente
• comparaison entre algorithmes
Algorithme polynomial
f(n) = a0np + a1np-1 +...+ ap-1n + ap
f = O(np)
EXEMPLE (fin) f(n) = n2 + 3n - 4
Le tri par sélection est un algorithme polynomial
en O(n2)
42
“Bonne complexité”
O(log n) ou O(n) ou O(n log n)
n3
n2
nlogn
n
logn
Allure de quelques courbes
n = 106
log2n
20µs
1 µs par opération
n nlog2n n2
n3
1s
20 s
12 j 32 Ka
43
De l’effet des progrès de la technologie
sur la résolution des problèmes
nombre d'
opérations
taille max des problèmes traitables en 1 heure
avec un ordinateur :
actuel
100 fois plus rapide 1000 fois plus rapide
(le plus rapide)
N
N1
100 N1
1000 N1
n2
N2
(soit N22 opérations)
N3
(soit N35 opérations)
N4
(soit 2N4 opérations)
10 N2
31,6 N2
2,5 N3
3,98 N3
N4 + 6,64
N4 + 9,97
N5 + 4,19
N5 + 6,29
n5
2n
3n
N5
(soit 3N5 opérations)
Même si la puissance des ordinateurs augmente, certains “gros”
problèmes ne pourront sans doute jamais être résolus
44
Une illustration de l’amélioration
de complexité :
recherche séquentielle vs
recherche dichotomique
d’un élément dans un tableau trié
45
Le problème
Etant donnés :
- Un tableau T de n entiers triés par ordre croissant
- Un entier x
Ecrire un algorithme qui teste si x appartient à T :
recherche de x dans T
46
Recherche séquentielle
Idée de l’algorithme
• On parcourt le tableau T, et on s’arrête :
– Soit parce qu’on trouve x,
– Soit parce qu’on trouve un élément > x.
Complexité au pire cas ?
Au pire, on parcourt les n cases du tableau en
faisant pour chaque case un nombre constant de
comparaisons, donc complexité au pire cas en O(n)
47
Recherche séquentielle (en Java)
static boolean rechercheLineaire (int x, int[] T) {
int i;
for (i=0 ; i < T.length ; i++) {
if (T[i]>=x) {
System.out.print("Nb d'iterations="+(i+1));
return(T[i]==x);
}
}
System.out.println("Nombre d'iterations="+i);
return(false);
}
48
Recherche dichotomique
Idée de l’algorithme :
- Eviter la recherche séquentielle en utilisant la
structure triée du tableau
- Complexité au pire cas en O(log n)
==> VOIR TD !
49
Récursivité
• On dit qu'une fonction est récursive lorsqu'elle
s'appelle elle-même (en général, avec un jeu
de paramètres différent)
• La récursivité est un concept puissant, qui
permet de traduire naturellement (et parfois
plus simplement que par une syntaxe itérative)
les processus inductifs (ou récurrents). Ex. :
public static int factorielle(int n) {
if(n>1) return(n*factorielle(n-1));
else return 1;
}
50
Récursivité (suite)
• Faire des appels récursifs en cascade peut
amener à en générer un très grand nombre
– Ainsi, calculer le déterminant d'une matrice de cette
façon-là génère un nombre exponentiel d'appels
• La complexité des algorithmes récursifs peut
être difficile à évaluer (compter le nombre
d'appels générés pouvant être problématique)
• A chaque appel, on recopie les paramètres
dans des variables locales : un algorithme
récursif consomme donc parfois beaucoup plus
de mémoire que son équivalent itératif
51
REMARQUES FINALES
• Comptage « grossier » des opérations (+, -, /,
*, tests, etc.) ; faire attention aux boucles !
• Ce qui est à l'extérieur des boucles est
souvent « négligeable ».
• On écrit plutôt O(log(n)) que O(log2(n)).
• Hiérarchie des complexités (pour n > 1) :
log(n) < n < n log(n) < n2 < n3 << 2n
52
Téléchargement