Planche 1
Cours 3
Structures de données dynamiques
Jean-Jacques.Levy@inria.fr
http://jeanjacqueslevy.net
tel: 01 39 63 56 89
secrétariat de l'enseignement:
Catherine Bensoussan
cb@lix.polytechnique.fr
Aile 00, LIX
tel: 01 69 33 34 67
http://www.enseignement.polytechnique.fr/informatique/
Planche 2
Plan
-
Listes -- Arbres
- Opérations de base
- Polymorphisme?
- Abstraction1: représentation des ensembles
- Abstraction2: paquetage de grands nombres
Répertoire pour stocker ses TD
/users/profs/info/TD/tc/g/n sur poly.polytechnique.fr où g Î {1, 12} est le numéro de groupe et
n Î {1, 2, 3, ... 10} le numéro du cours. Ne pas oublier
de mettre son nom dans le nom du fichier. (Répertoire en accès
en écriture pour tout le monde)
Planche 3
Listes
Comment représenter des ensembles dynamiques? Dont le nombre
d'éléments peut varier dans le temps? Les tableaux ont leur dimension
fixée à leur création. Il faut considérer le nombre maximal
de leurs éléments Þ gachis (en place mémoire).
Symétriquement, comment représenter des vecteurs (ou matrices)
creux? Un tableau prend l'espace mémoire nécessaire pour
toutes les valeurs de l'indice, et donc pour les valeurs inutiles.
D'où la structure de données des listes dont les éléments sont
les éléments non consécutifs de l'ensemble (ou des paires [indice,
valeur] dans le cas des vecteurs ou matrices creux). On ne peut
accèder que séquentiellement aux éléments d'une liste.
Planche 4
Représentation des listes
class Liste {
int hd; Liste tl;
Liste (int v, Liste a) {
hd = v; tl = a;
}
}
ou
class Liste {
int indice; int valeur; Liste suivant;
Liste (int i, int v, Liste a) {
indice = i; valeur = v; suivant = a;
}
}
Exercice 1Pour les fous de l'optimisation de l'espace-mémoire, il existe
des représentations encore plus compactes des listes, e.g. hash-consing. Savez-vous en trouver une?
Planche 5
Arbres
Les arbres représentent des ensembles dynamiques (parfois ordonnés)
avec accès rapide aux éléments (en O(log n) pour n
éléments).
Les arbres représentent aussi les termes des structures
algébriques (cf. plus tard), et sont donc la structure de base de
tous les systèmes de calcul formel (compilation,
démonstration automatique, Maple).
Planche 6
Opérations courantes sur les listes
- concaténation
- image mirroir
- appartenance
Deux styles de programmation
- listes non modifiables Þ clarté
- listes modifiables Þ efficacité
En Java, les types des données ne permettent pas de distinguer entre ces
deux styles de programmation (toute variable étant a priori modifiable).
L'inefficacité des listes non modifiables est contre-balancée par
l'existence d'un garbage collector, ie un glâneur de cellules qui récupère régulièrement les objets
non utilisés. Cet effet est d'autant plus fort que le GC
est efficace (en temps).
En majeure 1, les techniques de GC sont étudiées dans le cours
Langages de programmation
Planche 7
Concaténation non destructive -- Append
static Liste append (Liste a, Liste b) {
if (a == null) return b;
else return new Liste (a.hd, append (a.tl, b)) ;
}
Récurrence structurelle sur le premier argument. En O(n).
Planche 8
Concaténation destructive -- nConc
static Liste nConc (Liste a, Liste b) {
if (a == null) return b;
else {
a.tl = nConc (a.tl, b);
return a;
}
Récurrence structurelle sur le premier argument. En O(n).
Modification de la valeur du premier argument de nConc.
Programmation dangereuse, car création implicite d'alias.
Planche 9
Exemples sur listes - suite
static Liste nConcI (Liste a, Liste b) {
if (a == null) return b;
else {
Liste c = a;
while (c.tl != null)
c = c.tl;
c.tl = b;
return a;
}
}
On combine les inconvénients: itératif et destructif! En O(n).
Planche 10
Image mirroir -- version non destructive
static Liste reverse (Liste x) {
if (x == null) return null;
else return append (reverse (x.tl), new Liste (x.hd, null));
}
static Liste mirroir (Liste x) { return mirroir1 (x, null); }
static Liste mirroir1 (Liste x, Liste r) { // r accumulateur
if (x == null) return r;
else return mirroir1 (x.tl, new Liste(x.hd, r));
}
Récursivité terminale dans mirroir1 Þ
version itérative
static Liste mirroirI (Liste x) {
Liste r = null;
for (; x != null; x = x.tl)
r = new Liste (x.hd, r);
return r;
}
Planche 11
Image mirroir -- version destructive
static Liste nReverse (Liste x) {
if (x == null) return null;
else {
Liste y = nReverse (x.tl);
x.tl = null;
return nConc (y, x);
}
}
static Liste nReverseI (Liste x) {
Liste r = null;
while (x != null) {
Liste y = x.tl; x.tl = r; r = x; x = y;
}
return r;
}
Exercice 2Calculer la complexité de ces différentes fonctions.
Exercice 3Passer du récursif à l'itératif avec un accumulateur comme pour
mirroir.
Planche 12
Autres fonctions
static Liste listeAleatoire (int n) {
Liste r = null;
for (int i = 0; i < n; ++i)
r = new Liste ((int) (Math.random() * 100), r);
return r;
}
static void imprimer (Liste x) {
for (Liste y = x; y != null; y = y.tl)
System.out.print (y.hd + " ");
System.out.println ();
}
static Liste lireDansTableau (String[] a) {
Liste r = null;
for (int i = a.length - 1; i >= 0; --i)
r = new Liste (Integer.parseInt (a[i]), r);
return r;
}
Planche 13
Autres fonctions -- bis
static boolean member (int n, Liste x) {
if (x == null) return false;
else return n == x.hd || member (n, x.tl);
}
static boolean equal (Liste x, Liste y) {
if (x == null) return y == null;
else if (y == null) return x == null;
else return x.hd == y.hd && equal (x.hd, y.hd);
}
static boolean eq (Liste x, Liste y) {
return x == y;
}
Exercice 4Décrire la différence entre equal et eq.
Planche 14
Arbres -- représentation des arbres
class Arbre {
int val; Arbre fG, fD;
Arbre (int v, Arbre a, Arbre b) {
val = v; fG = a; fD = b;
}
}
Si l'arbre n'est pas binaire, plusieurs solutions avec des tableaux
class Arbre {
int val; Arbre[] fils;
Arbre (int v, Arbre[] a) { val = v; fils = a; }
}
ou avec des listes
class Arbre {
int val; ListeArbres fils;
Arbre (int v, ListeArbres x) { val = v; fils = x; }
}
class ListeArbres {
Arbre hd; ListeArbres tl;
ListeArbres (Arbre a, ListeArbres x) { val = v; fils = x; }
}
Planche 15
Fonctions sur les arbres
static void imprimer (Arbre a) {
if (a != null) {
System.out.print ("["); imprimer (a.fG);
System.out.print (" " + a.val + " ");
imprimer (a.fD); System.out.print ("]");
}
}
donne [ [ 3 ] 5 [ [ 22 ] 37 [ 40 ] ] ]
Planche 16
Fonctions sur les arbres
static int next;
static Arbre lireDansTableau (String[] s) {
if (!s[next].equals ("[") )
return null;
else {
++next; Arbre a = lireDansTableau (s);
int v = Integer.parseInt (s[next]);
++next; Arbre b = lireDansTableau (s);
if (!s[next++].equals ("]") ) {
System.err.println ("Mauvais arguments.");
System.exit (1);
}
return new Arbre (v, a, b);
}
}
Exercice 5Ecrire un programme qui compte le nombre de feuilles nf et de noeuds internes ni.
Exercice 6Vérifier que nf = ni + 1 si l'arbre est binaire (ie. tout
noeud interne a deux fils).
Planche 17
Fonctions sur les arbres
static Liste listeDesNoeuds (Arbre a) {
if (a == null) return null;
else return append (listeDesNoeuds (a.fG),
append (new Liste (a.val, listeDesNoeuds (a.fD))));
}
Exercice 7Faire une version plus efficace et esthétique.
Exercice 8Ecrire une fonction arbreDeLaListe transformant une liste
en un arbre (aussi équilibré que possible).
static boolean member (int n, Arbre x) {
if (x == null) return false;
else return n == x.val || member (n, x.fG) || member (n, x.fD);;
}
static boolean equal (Arbre x, Arbre y) {
if (x == null) return y == null;
else if (y == null) return x == null;
else return x.val == y.val && equal (x.fG, y.fG) && equal (x.fD, y.fD);;
}
Planche 18
Des listes baroques
Autrefois, l'espace mémoire et la vitesse étaient très critiques. Toutes
sortes d'optimisations avaient lieu.
- Listes doublement chaînées: pour aller rapidement au(x)
prédécesseur(s) dans la liste.
- Listes gardées: hack pour ne pas avoir à
singulariser le cas vide. Analogue des sentinelles pour éviter les
débordements dans les tableaux. (Si utilisé pour faire des
méthodes non statiques, c'est horrible.)
- Listes circulaires: autre technique pour accéder
au(x) prédécesseur(s).
On peut combiner ces 3 optimisations souvent bien inutiles et aussi
très laides.
Planche 19
Listes / Arbres polymorphes?
-
Il y a une classe Object de tous les objets.
- Conversions implicites:
C ® Object
C[] ® Object[]
t[] ® Object
- Conversions explicites: (t) e où
t type quelconque
- Les variables de types primitifs (byte, short, int, long, float, double, char,
boolean) ne sont pas convertibles en Object.
- Integer(n), Float(x), etc construisent des
objets contenant les valeurs n, x, etc, et dont les méthodes intValue(), floatValue(), etc donnent les valeurs n, x, etc.
Bref, Java n'a pas de types de données polymorphes. (cf. Generic Java,
http://www.cs.bell-labs.com/who/wadler/pizza/gj/, par Odersky
et Wadler)
Planche 20
Listes / Arbres polymorphes
class Liste {
Object hd; Liste tl;
Liste (Object v, Liste a) {
hd = v; tl = a;
}
static Liste append (Liste a, Liste b) {
if (a == null) return b;
else return new Liste (a.hd, append (a.tl, b)) ;
}
public static void main (String[] args) {
Liste x = new Liste(new Integer(2), new Liste( new Integer (3), null));
Liste y = new Liste(new Integer(4), new Liste( new Integer (5), null));
Liste z = append (x, y);
int u = ((Integer) z.hd).intValue();
System.out.println (u);
}
}
Exercice 9Faire mieux?
Planche 21
Abstraction -- Encapsulation
Par exemple, comment représenter un ensemble dont le nombre
d'éléments peut évoluer dynamiquement?
Par une liste, par un arbre?
Quelles sont les opérations à réaliser sur les ensembles? A fournir
dans l'interface ou encore API (application program
interface)
On veut donc construire un module avec certaines fonctions à exporter.
Tout le reste est à cacher, car il s'agit de la représentation interne
de notre abstraction.
Planche 22
Représentations d'ensembles
- listes á 2, 10, 4, 2, 1, 6ñ
- listes sans répétitions á 2, 10, 4, 1, 6ñ
- listes ordonnées á 1, 2, 4, 6, 10ñ
- arbres á [ [ 2 ] 10 [ 4 ] ] 2 [ 1 [ 6 ] ]ñ
- arbres de recherche á [ [ [ 1 ] 2 [ 4 ] ] 6
[ 10 ] ]ñ
Planche 23
Listes ordonnées
class Ensemble {
hd: int; tl: Ensemble;
Ensemble (int x, Ensemble e) { hd = x; tl = e; }
static boolean vide (Ensemble e) { return e == null; }
static Ensemble ajouter (int x, Ensemble e) {
if ( e == null || x < e.hd ) return new Ensemble (x, e);
else if (x == e.hd) return e;
else if (x > e.hd) return new Ensemble (e.hd, ajouter (x, e.tl));
}
static boolean appartient (int x, Ensemble e) {
if ( e == null || x < e.hd ) return false;
else return x == e.hd || appartient (x, e.tl);
}
static Ensemble enlever (int x, Ensemble e) {
if ( e == null || x < e.hd ) return e;
else if (x == e.hd) return e.hd;
else if (x > e.hd) return new Ensemble (e.hd, enlever (x, e.tl));
}
Planche 24
Listes et ensembles
Exercice 10Calculer l'union, l'intersection, la différence symétrique.
Exercice 11Donner des versions destructives de ces fonctions.
Exercice 12Donner des versions itératives et comparer?
Exercice 13Refaire ces programmes avec la représentation par des listes
non ordonnées. Quels en sont les coûts?
Exercice 14Comment passer d'une représentation à l'autre?
Exercice 15Faire le tri par fusion sur les listes non ordonnées.
Planche 25
Entiers en précision arbitraire
Un cas classique d'utilisation des listes est de faire un paquetage
d'opérations arithmétiques en précision arbitraire, ie au delà de
231 - 1.
Représentation d'un grand nombre. On suppose une base B donnée. Par
exemple B=10, mais plus vraisemblablement B=230.
-
Little endian: 1794 représenté par la liste á 4, 9,
7, 1ñ
Pentium
- Big endian: 1794 représenté par la liste á 1, 7, 9,
4ñ
IBM 370, PDP-10, Vax, Motorola, PPC.
- Petit et Grand: MIPS, Alpha
Nous prenons petit endien.
On fait facilement l'addition et la multiplication, la lecture et
l'impression. Comment faire la division et le test de primalité, utiles
pour faire de la cryptographie?
Planche 26
Grands nombres et division
On veut diviser u par v, ie u = q v + r où 0 £ r <
v. D'abord, on voit que toute la difficulté est de traiter le cas où
u/v < B. On fera ensuite l'opération chiffre par chiffre du
quotient.
Quand u/v < B, une bonne approximation du quotient s'obtient en
regardant les 2 premiers chiffres de u et le premier chiffre de v,
notamment quand le premier chiffre v1 de v vérifie v1 ³
ë B/2 û.
En effet, posons u = u0 u1...un et v = v1...vn en base B.
Calculons q' = min (ë (u0*B + u1)/v1 û, B-1 ).
Alors
Lemme 1: q' ³ q
Lemme 2: Si v1 ³ ë B/2 û, alors q'-2 £ q £ q'
Planche 27
DIVISION DE u PAR v (Knuth vol2, p.272)
Soient u = u1 u2 ... um+n et v = v1...vn
Calculer ë u/v û = q0 q1...qm et
u mod v = r1 ... rn
-
1- On normalise u et v en les multipliant par d = ë B/v1 +
1 û
-
2- Pour j variant de 0 à m, faire la séquence 3-4-5-6:
-
3- Si uj = v1, alors q' = B-1,
sinon q' = ë (uj * B + uj+1)/v1 û.
Tant que q' * v2 > (uj * B+uj+1) * B+uj+2, décrémenter q'.
-
4- Remplacer uj...uj+n par uj...uj+n - q'*v.
-
5- qj ¬ q'
Si résultat précédent négatif, faire 6:
-
6- Décrémenter qj et additionner v à
uj...uj+n
-
7- q0 q1...qm est le résultat
um+1...um+n/d est le reste (car faut dénormaliser)
Planche 28
Cryptographie à clé publique
Eviter le partage des secrets. La méthode RSA utilise un système à 2
clés: une publique e et une autre secrète d. (Cf. le cours
Morain en majeure Info ou le livre ``Applied Cryptography'' de
Schneier.)
Soit n=pq où p, q sont premiers. On encode avec une clé e
première avec (p-1)(q-1). On décode avec une clé d telle que
ed º 1 mod (p-1)(q-1)
On suppose les messages m coupés en morceaux mi < n, et on
fait
ci = mie mod n
On décode par
mi = cid mod n
On vérifie que
cid º mied º mi mod n
On oublie donc p et q. La clé e est publique.
Typiquement, pq a une centaine de chiffres décimaux.
Planche 29
Grands nombres premiers
Il faut donc trouver de grands nombres premiers p, q. (50 à 100
chiffres). Pour aller vite, on fait des tests probabilistes
pour savoir si p est premier:
Méthode 1 (Lehmann)
-
Choisir un nombre aléatoire a tel que a < p
- Calculer a(p-1)/2 mod p
- Si a(p-1)/2 ¬ º1 ou -1, répondre NON.
- Sinon p est premier à 50%.
(On itère donc pour 10 valeurs de a).
Planche 30
Méthode 2 (Rabin-Miller, 1980)
Soit b la plus grande puissance de 2 telle que p=1+2b m
-
Choisir un nombre aléatoire a tel que a < p
- Poser j=0 et z= am mod p
- Si z=1 ou z=p-1, alors répondre OUI.
- Si j>0 et z=1, alors répondre NON.
- Faire j=j+1. Si j<b et si z=z2 mod p ¹ p-1,
revenir en (4). Si z=p-1, alors répondre OUI.
- Si j=b et z = p-1 alors répondre NON.
Sur un nombre de 256 bits, se tromper après 6 tests est inférieur à
1/251.
This document was translated from LATEX by HEVEA.