Plan Langage C
• Typedef
• Initiation aux pointeurs Algorithmique
• Borne inférieure sur les tris
• Tables : recherche, insertion, suppression
• Hachage
Typedef
Pour créer des noms de type de données.
Typedef int Entier;
rend Entier synonyme de int.
Entier n, m;
Utilisation
(1) clarté des programmes
(2) portabilité (si on change de machine, il suffit de changer les typedef)
Pointeurs
Un pointeur est un couple (adresse, type)
formé d'une adresse en mémoire et d'un type de données.
Un pointeur sur un objet de type t est un couple de la forme
(adresse, t)
formé de l'adresse de l'objet et de son type.
short n;
n
2 octets
p
*pOpérateur d'indirection *
Opérateur de prise d'adresse &
&x
xint x;
&x est l'adresse du premier octet de x
*p désigne l'objet pointé par p
Variables de type pointeurs
char *p;
int *q;
déclare p (resp. q) comme pointeur sur un char (resp. sur un int)
x = 1
p
y = 2int x = 1, y = 2;
int *p; /* Déclare p comme pointeur sur un entier */
p = &x; /* p contient l'adresse de x */
y = *p; /* y vaut maintenant 1 */
*p = 3; /* x vaut maintenant 3 */
p = &x;
y = *p;
p
x = 1 y = ?*p = 3;
p
x = 3 y = 1*&x est égal à x
&*p est égal à p
Pointeurs et tableaux
int t[5];
Déclare t comme constante de type "pointeur sur un entier".
t pointe sur le premier élément t[0]
Conséquences logiques (1) *t est égal à t[0]
(2) t est égal à &t[0]
(3) *(t+i) est égal à t[i]
t
t[0] t[1] t[2] t[3] t[4]t + 1 t + 2
Variables pointeurs et tableaux
Un tableau d'entiers t est une
constante de type "pointeur sur un entier". Ce n'est pas une variable !
int x = 2;
int *p;/* pointeur sur un entier */
int t[5]; /* tableau d'entiers */
Sont correctes les instructions:
p = &x; /* adresse de x */
p++; /* adresse suivante */
p = t; /* adresse de t[0] */
En revanche, sont incorrectes
t = p; /* Aucun sens !! */
t++; /* Aucun sens !! */
Conséquences logiques
Borne inférieure sur les tris
Combien faut-il de comparaisons pour trier une liste ?
Chaque comparaison générant deux possibilités, k comparaisons permettront de génerer au plus 2k permutations distinctes. Or il y a n! permutations possibles pour un ensemble à n éléments. Il faut donc log2(n!) comparaisons au minimum, soit, à l'aide de Stirling,
n (log2 n - log2 e) + 1/2 log2 n
= n log2 n + o(n log2 n)
Tables
• Table = Ensemble de couples appelées entrées de la table.
• Entrée = (clef, information)
• Opérations sur les tables - création
- insertion
- modification d'une entrée - suppression
- recherche de l'information associée à une clé
Représentation d'une table
• Par un tableau
entrées : (rang, nom)
typedef char Texte[30];
Texte PromoX[450];
PromoX[187] = "Martin";
• Par deux tableaux
entrées : (nom, numéro de poste)
Texte PromoX[450];
int Tel [450];
• Par deux tableaux ordonnés
• Par hachage (cf plus loin)
• Par arbre (cf plus loin)
Représentation par un seul tableau
• création : O(n)
• insertion : O(n)
• recherche : O(1)
• correction : O(1)
• suppression : O(n)
C'est la recherche "en accès direct"
Recommandé pour de petites tables numériques.
Représentation par deux tableaux Entrées : (nom, numéro de poste) Les entrées ne sont pas triées !
Texte PromoX[450];
int Tel [450];
• création : O(n)
• insertion : O(1)
• correction : O(n)
• suppression : O(n)
• recherche : O(n)
(n/2 + o(n) en moyenne en cas de succès, n + o(n) en cas d'échec) C'est la recherche "séquentielle"
Recherche séquentielle
#include <stdio.h>
#include <string.h>
#define LONG_MAX_TEXTE 20
#define LONG_MAX_TABLE 1000
typedef char Texte[LONG_MAX_TEXTE];
Texte Nom[LONG_MAX_TABLE];
int Tel[LONG_MAX_TABLE];
int n; /* Nombre d'entrées. */
Texte x;
int Recherche(Texte x) { int i = 0;
while ((i < n) && strcmp(x, Nom[i])) return (i < n) ? Tel[i] : -1;i++;
}
Représentation tableaux ordonnés entrées : (nom, numéro de poste)
Texte Nom[450];
int Tel [450];
On suppose les noms triés par ordre alphabétique.
• insertion : O(n)
(n/2 + o(n) en moyenne)
• correction : O(n)
• suppression : O(n)
• recherche : O(log n)
(en cas de succès et d'échec )
C'est la recherche "dichotomique"
Recherche dichotomique
int RechercheDichotomique (Texte x) { int i, g, d;
g = 0;
d = n-1;
while (g <= d) { i = (g + d) / 2;
if (strcmp(x, Nom[i]) == 0) return Tel[i];
if (strcmp(x, Nom[i]) < 0) d = i - 1;
elseg = i + 1;
}return -1;
}
Recherche par interpolation
• On suppose que les clés sont
numériques et que la distribution
des clés est uniforme sur l'intervalle [v0, vn] où
v0 = cles[0] et vn = cles[n]
• On recherche la clé v. La plus grande probabilité est qu'elle soit proche de cles[k], avec
k = n
• On itère le procédé d'interpolation
• Complexité moyenne inférieure à 1 + log log n
v - v0 vn - v0
Hachage
• Compromis Espace-Temps
(temps de recherche proportionnel au taux de remplissage de la table)
• Les clés ne sont pas triées
• Principe : la table est divisée en M tables (en accès séquentiel par exemple). Une fonction, dite de hachage
h : Clés → [0 .. M-1]
donne, pour chaque clé c, le numéro h(c) de la sous-table associée à c.
• Taux de remplissage : r = N/M
Exemple de hachage
Temps de recherche moyen*
Succès : (8.1 + 5.2 + 3.1)/14 = 1, 5 Echec : (2.0 + 3.1 + 4.2 + 1.3)/10 = 1,4
* Il faudrait ajouter le calcul de h(c)
25 32 43 51 67 89 20 11 13 53 57 59 14 15
5 2 3 1 7 9 0 1 3 3 7 9 4 5
0 (20, Martin)
1 (51, Pierre), (11, Sophie) 2 (32, Jean)
3 (43, Sylvie), (13, Robert), (53, Paul) 4 (14, Annie)
5 (25, Jacques), (15, Adeline) 6
7 (67, Catherine), (57, Danièle) 8
9 (89, Julie), (59, Philippe)
Temps de recherche moyen pour la résolution des collisions par chainage
n éléments, table de taille m, r = n/m
Calcul du hachage : Ο(1)
Temps total moyen, calcul du hachage inclus:
Succès : 2 + r/2 - 1/2m Echec : 1 + r
Si r = 1,4 on trouve 2,7 et 2,4
Hachage à adressage ouvert
• Même principe, mais les tables sont constituées d'un seul élément.
Autrement dit, on suppose qu'il y a au plus N entrées à ranger, et on choisit M > N et une fonction de hachage
h : Clés → [0 .. M-1]
Soit c une clé. Si Table[h(c)] n'est pas occupé, on y range l'entrée associée à c. Sinon on la range dans la première place non occupée :
Table[h(c)+1], Table[h(c)+2], …
h(c) = c mod 20
Temps de recherche en cas de succès :
27/14
0 20 1
2 3 4
5 25 45 6 45 7 67
8
9 89 10
11 51 11 12 32
13 73 13 53
14 11 14
15 13
16 53
17 57
18 14
19 59
25 32 73 51 67 89 20 11 13 53 57 59 14 15 5 12 13 11 7 9 0 11 13 13 17 19 14 5
Complexité du hachage à adressage ouvert Si r = N/M est le taux de
remplissage, le temps moyen de recherche est au plus
• En cas de succès
• En cas d'échec
• Pour r = 2/3, on trouve 2 et 5, (indépendamment de N).
• Désastreux pour r proche de 1
Phénomènes de regroupement
Le hachage simple produit des phénomènes de regroupement autour des clés qui sont insérées au premier essai.
Probabilité pour que la case i soit choisie à la prochaine insertion (en supposant que h(c) est distribuée uniformément)
P(1) = 2/14 P(4) = 3/14 P(5) = 1/14 P(10) = 5/14 P(11) = 1/14 P(13) = 2/14
0 1 2 3 4 5 6 7 8 9 10 11 12 13
Double hachage
Une solution pour éviter les phénomènes de regroupement est d'utiliser un double hachage :
On a donc deux fonctions de hachage
h : Clés → [0 .. M-1]
h' : Clés → [1 .. r]
Soit c une clé. Si Table[h(c)] n'est pas occupé, on y range l'entrée associée à c. Sinon on la range dans la première place non occupée :
Table[h(c)+h'(c)], ou à défaut Table[h(c)+2h'(c)], ou à défaut Table[h(c)+3h'(c)], etc.
Hachage quadratique
Une autre solution pour la résolution des collisions est de ranger l'entrée associée à c dans Table[h(c)] ou à défaut dans
Table[h(c) + 1], Table[h(c) + 4], Table[h(c) + 9],
Table[h(c) + 16], etc.
On démontre que le temps moyen de recherche est au plus
• En cas de succès
1 - ln(1-r) - r/2
• En cas d'échec
1/(1-r) - ln(1-r) - r
Complexité du double hachage
On démontre (sous des hypothèses de distribution uniforme) que si r
= N/M est le taux de remplissage, le temps moyen de recherche pour un hachage double est au plus
• En cas de succès
-ln(1-r)/r
• En cas d'échec
1/(1-r)
• Pour r = 9/10, on trouve 2,56 et 10 (indépendamment de N).
• Pour r = 99/100, on trouve 4,65 et 100 (indépendamment de N).
Choix des fonctions de hachage
• Paradoxe des anniversaires : si on prend une fonction de hachage
h : [1..23] → [1 .. 365]
au hasard, la probabilité pour que deux clés (au moins) aient la même image par h est > 1/2 (!)
• Il est donc difficile de trouver une fonction injective de [0..N-1]
dans [0..M-1]
• Un choix classique est h(c) = c mod M
où M est un nombre premier.
Choix des fonctions de hachage (2)
• Pour des clés alphabétiques, on peut choisir
(c[1]Bn-1 + c[2]Bn-2 + … + c[n]) mod N
où B = 256 et N est premier
Exemple c = PASCAL, N = 143
h(c) = 80. 264 + 65. 232 + 83. 224 + 67. 216 + 65. 28 + 76 mod 143
• Pour les fonctions de hachage secondaires, on peut prendre
h'(c) = 8 - (c mod 8) ou h'(c) = c mod (N-1)
Choix des fonctions de hachage (3)
• Autre choix efficace
[
(c[1]Bn-1 + … + c[n]) mod 2w]
mod Noù B = 131 et w est la longueur du mot machine.
En effet, Bi a un cycle maximal pour 8 ≤ k ≤ 64.
Les conseils de la semaine…
• Penser aux techniques de hachage!
• Ne jamais remplir une table de hachage à plus de 90%
• Avant d'utiliser un double hachage, faire un "devis"