Académique Documents
Professionnel Documents
Culture Documents
SOMMAIRE
Les arbres 31
Les graphes 52
Méthodes de hachage 62
1- Notion d’algorithme
1-1- Définition
− Un algorithme est une suite finie de règles à appliquer dans un ordre déterminé à un nombre
fini de données pour arriver en un nombre fini d’étapes, à n certain résultat, et cela
indépendamment des données.
De plus, les algorithmes que l'on considère sont déterministes : étant donné un algorithme,
toute exécution de cet algorithme sur les mêmes données donne lieu à la même suite d'opérations.
1-2- Expression des algorithmes
Un algorithme permet de résoudre un problème donné.
Un algorithme doit être exprimé dans un langage de programmation pour être compris et
exécuté par un ordinateur. Cependant, il faut bien comprendre qu'un algorithme est indépendant
du langage de programmation utilisé. L'algorithme d'Euclide programmé en Pascal, en C ou en
Lisp, reste toujours l’algorithme d'Euclide.
Algorithmes + structures de données = Programme [Wirth]
1-3- Notion de programme
Synonymes
- Programme, application, logiciel.
Objectifs des programmes
- Utiliser l’ordinateur pour traiter des données afin d’obtenir des résultats
- Abstraction par rapport au matériel.
Un programme est une suite logique d’instructions que l’ordinateur doit exécuter
- Chaque programme suit une logique pour réaliser un traitement qui offre des services
(obtention des résultats souhaités à partir de données).
- Le processeur se charge d’effectuer les opérations arithmétiques et logiques qui
transformeront les données en résultats.
Programmes et données sont sauvegardés dans des fichiers
- Instructions et données doivent résider en mémoire centrale pour être exécutées.
2- Notion de complexité
La notion de complexité des algorithmes permet de donner un sens précis à l’idée intuitive
selon laquelle un programme a un coût. Par coût, on entend en général le temps d’exécution (T.e)
et éventuellement la quantité de mémoire requise. Nous ne nous intéresserons qu’au premier
aspect et nous allons étudier comment évaluer le temps d’exécution des programmes.
Entier pgd1(Entier n)
Entier i
Debut
in–1
Tant que ((n mod i) != 0) faire
i i -1
ftant que
retourner i
Fin
2. Une autre méthode est fondé sur le fait que le résultat cherché est n/i, où i
est le plus petit diviseur (ppd) de n supérieur à 1.
Si n est premier ce ppd est 1,
Sinon il est compris entre 1 et Vn
Entier pgd2(Entier n)
Entier i, j
Debut
i2
Tant que ((i< int(sqrt(n))) et ((n mod i) !=0)) faire
ii+1
ftant que
si ((n mod i)=0) alors
jn div i
sinon j1
fsi
retourner j
Fin
pgd1 a une boucle de 1 à n, donc plus long que pgd2 qui a une boucle de 2 à Vn
Par exemple si n =1010, pgd1 est impraticable alors que pgd2 conduit à 105 instructions
élémentaires.
a) Complexités dans le pire des cas (Tmax(n)) : temps d’exécution maximale d’un
algorithme pour des données de taille n.
b) Complexités en moyenne des cas (Tmoy(n)) : temps d’exécution moyen d’un
algorithme sur tous les jeux de données de taille n.
c) Complexités dans le meilleur des cas (Tmax(n)) : temps d’exécution minimale
d’un algorithme pour des données de taille n.
Exemple :
T(n) = 5n3 +3n2 (avec n0 = 0 et c=5)
= O(n3)
Si T(n) est le t.e. de l’algorithme A, alors : A est donc O(n3).
2-3-2-2- Règles de calcul
On applique les règles usuelles sur le comportement à l’infini des fonctions
numériques.
1. Dans un polynôme, seul le terme de plus haut degré compte.
Exemple : T(n) = n3 + 7n2 +77n est O(n 3)
2. L’exponentielle l’emporte sur la puissance et la puissance sur le log.
Exemples :
T(n) = 2n + n22 + 5n2 + 2n est O(2n)
T(n) = 5 Log(n) + 5n est O(n).
3. Si T1(n) = O(f(n)) et T2(n) = O(g(n))
Alors T1(n) + T2(n) = O(Max(f(n), g(n))
Et T1(n) . T2(n) = O(f(n).g(n))
2-3-2-3- Exemple
Supposons que l’on dispose, pour résoudre un problème donnée de 7 algorithmes dont
les complexités dans le pire des cas sont 1(constante), log2(n), n, nlog2(n), n2, n3, 2n sur un
ordinateur pouvant exécuter 106 opérations/seconde.
Les écarts entre les différents t.e. se creusent lorsque la taille de données croit.
Objectifs
Remarque :
Pour un tableau non trié :
Sinon
i1
Tant que ((i<n) et (x >= T[i])) faire
i i + 1
ftant que
pour j n+1 à (i+1) faire
T[j] T[j-1]
Fpour
T[i] x
n n+1
fsi
Fin
Remarque :
Pour un tableau trié :
2- Notion de tri
On considère un ensemble d’éléments, généralement des enregistrements, composés
d’un ou plusieurs champs.
Clé :
Un des champs d’un type, sur lequel il est possible de définir une relation d’ordre.
Données satellites :
Reste des champs, traités en même temps que la clé.
Exemple :
Fichier étudiant, où chaque étudiant est caractérisé par son numéro (clé), nom,
prénom, âge, (données satellites).
Le tri consiste à ordonner les données de l’ensemble selon un ordre croissant (ou
décroissant) des valeurs de leur champ clé.
Exemple :
{2, 25, 7, 15, 4] sera trié selon l’ordre croissant par {2, 4, 7, 15, 25} (ou selon l’ordre
décroissant par {25, 15, 7, 4, 2})
Fin
Etude de la complexité :
Etape i Total
Meilleur cas Cas moyen Pire cas Meilleur cas Cas moyen Pire
cas
Comparaisons 1 (i-1)/2 i-2 n-1 n2/2-3n/2+1 n2-
4n+3
Affectations 3 i+1 2i-1 3n-3 n2-n 2n2-
5n+3
La complexité du tri par insertion dans le pire des cas et en moyenne est O(n2), par contre
dans le meilleur des cas est O(n).
Complexité :
Cas moyen et meilleur cas :
Complexité du tri rapide O(nlog(n))
Type Booléen
Opérations
Vrai : Booléen
Faux : Booléen
Non : Booléen Booléen
Et : Booléen x Booléen Booléen
OU : Booléen x Booléen Booléen
Exemples :
1) TAD Booléen
Type Booléen
Opérations :
Vrai : Booléen
Faux : Booléen
Non : Booléen Booléen
Et : Booléen x Booléen Booléen
OU : Booléen x Booléen Booléen
Préconditions
Axiomes
Booléen a, b
Non (vrai) = faux
Non (non (a)) = a
Vrai et a = a
Faux et a = faux
a ou b = non (non(a) et non(b))
2) TAD Vecteur
Type Vecteur
Utilise Entier, Elément
Opérations :
Vect : Entier Vecteur
Changer_ième : Vecteur x Entier x Elément Vecteur
ième : Vecteur x Entier Elément
Taille : Vecteur Entier
Préconditions
Vect(i) est_défini_ssi i ≥0
ième(V, i) est_défini_ssi 0 ≤i ≤ Taille(V)
Changer_ième(V, i, elt) est_défini_ssi 0 ≤i ≤ Taille(V)
Axiomes
Soit i, j : Entier, elt : Elément, V : vecteur
Si 0 ≤i ≤ Taille(V) alors ième(Changer_ième(V, i, elt), i) =elt
Si 0 ≤i ≤ Taille(V) et 0 ≤j ≤ Taille(V) et i ≠ j alors
ième(Changer_ième(V, i, elt), j) = ième(V, j)
Taille(Vect(i)) = i
Taille(Changer_ième(V, i, elt), i) = Taille(V)
1-4 Opérateurs :
Trois catégorie d’opérateurs ou de primitives :
Constructeurs : type spécifié apparait uniquement comme résultat
Observateurs : type spécifié apparait uniquement comme argument
Transformateurs : type spécifié apparait à la fois comme argument et
comme résultat.
Constante : opérateurs sans argument
1-5 Opérations partielles :
Une opération peut ne pas être définie partout,
Cela dépend de son domaine de définition,
Ceci est traité dans le paragraphe préconditions
Exemple :
Opérateurs ième et changer_ième du TAD.
1-6 Réutilisations des TADs :
Quand on définit un type on peut réutiliser des types déjà définies.
La signature du type défini est l’union des signatures des types déjà utilisés, plus
de nouvelles opérations.
Le type hérite des propriétés des types qui le constitue.
Exemple :
Types Entier et Elément utilisé par le TAD Vecteur
On utilisera :
Les types élémentaires (Entier, réel, caractère, …),
Les tableaux et les enregistrements,
Les pointeurs,
Les types prédéfinis.
Plusieurs implémentations possibles pour un même TAD.
Tout module ou programme principale qui a besoin d’utiliser des fonctions de module
devra juste inclure le module.h
/* Fichier Booleen.h */
#Ifndef _Booleen
#define _Booleen
Typedef enum (vrai, faux) Booleen ;
Booleen et(Booleen x, Booleen y);
Booleen ou(Booleen x, Booleen y);
Booleen non(Booleen x);
#endif
/* Fichier Booleen.c */
#include Booleen.h
Booleen et(Booleen x, Booleen y)
{ if (x == faux )
return faux;
else return y;
}
Booleen ou(Booleen x, Booleen y)
{ if (x == vrai )
return vrai;
else return y;
}
Booleen non(Booleen x)
{ if (x == vrai )
return faux;
else return vrai;
}
2- Pile
Les piles sont très utilisées en informatique.
Notion intuitive :
Pile d’assiette
Pile de dossiers à traiter
…
Une pile est une structure linéaire permettant de stocker et de restaurer des
données selon un ordre LIFO (Last In First Out ou "dernier sorti premier entré")
Dans une pile les insertions (empilements) et les suppressions (dépilements) sont
restreintes à une extrémité appelée sommet.
Exemple :
Empiler 25
25
Empiler 36
36
25
Empiler 17
17
36
25
Dépiler
36
25
Représentation Contigüe:
/* Pile contigüe en C */
// Taille maximale pile
# Define max_pile 10
// Type des éléments
Typedef int Element;
// Type pile
Typedef Struct {
Element elements[max_pie];
int sommet;
} Pile;
Pile chaînée
/* Pile chaînée en C */
// type des éléments
typedef int element;
// type cellule
typedef struct cellule{
element valeur;
struct cellule *suivant;
} cellule ;
// type pile
typedef cellule *pile;
3- Les Files
Les files sont très utilisées en Informatique
Exemple :
File d’attente à un guichet,
File de documents à imprimer
…
Une file est une structure linéaire permettant de stocker et de restaurer des données selon
un ordre FIFO (First In First Out "Premier Entré Premier Sorti" ).
Dans une file :
Les insertions (enfilement) se font à une extrémité appelé queue de la file,
Les suppressions (défilement) se font à l’autre extrémité appelé tête de la file.
Exemple :
Enfiler 25
25
Enfiler 36
36
25
Enfiler 17
17
36
25
Défiler
17
36
/* File contigüe en C */
// taille maximale file
#define MAX_FILE 10
typedef struct {
Element tab[MAX_FILE];
int tête;
int queue;
} File ;
exit(-1);
}
(f.tete)++;
return f;
}
// valeur de l’élément en tête de la file
Element tete(File f){
if est_vide(f) {
printf(" Erreur file vide ! \n ");
exit(-1);
}
return (f.tab)[f.tete+1];
}
File chaînée
/* File chaînée en C */
// type des éléments
typedef int element;
// type cellule
typedef struct cellule{
element valeur;
struct cellule *suivant;
} cellule ;
// type File
typedef struct {cellule *tete;
cellule *queue;
} File;
4- Liste
Généralisation des piles et des files :
– Structure linéaire dans laquelle les éléments peuvent être traités les uns à la
suite des autres,
– Ajout ou retrait d’élément n’importe ou dans la liste,
– Accès à n’importe quel élément.
Une liste est une suite finie, d’éléments de même type repérés par leur rang dans la liste.
Chaque élément de la liste est rangé à une certaine place,
Les éléments de la liste sont ordonnés en fonction de leur place,
La longueur d’une liste est le nombre total de ses éléments.
Axiomes
Soit e : Elément , l1,l2: Liste, j, k : Entier
Si l1 = NULL alors longueur(l1) =0
Sinon si l1=inserer(l2, k, e) alors longueur(l1) = longueur(l2) +1
sinon si l1= supprimer(l2,k) alors longueur(l1) = longueur(l2) – 1
Si 1 ≤j < k alors kème(inserer(l1,k,e), j) = kème(l1, j)
Sinon si j=k alors kème(inserer(l1,k,e), j) = e
sinon kème(inserer(l1,k,e), j) = kème(l1, j-1)
Si 1 ≤j < k alors kème(supprimer(l1,k), j) = kème(l1, j)
Sinon kème(supprimer(l1,k), j) = kème(l1, j+1)
succ (l1, accès(l1, k)) = accès(l1, k+1)
contenu (l1, accès(l1, k)) = kème(l1, k)
Si 1 ≤k < j < longueur(l1) alors
contenu (l1, accès(supprimer(l1,j), k)) = contenu (l1, accès(l1, k))
Si 1 ≤k ≤ j < longueur(l1) alors
contenu (l1, accès(supprimer(l1,j), k)) = contenu (l1, accès(l1, k+1))
Si 1 ≤k < j < longueur(l1)+1 alors
contenu (l1, accès(inserer(l1,k,e), j)) = contenu (l1, accès(l1, j))
/* Liste contigüe en C */
#ifndef _LISTE_TABLEAU
#define _LISTE_TABLEAU
// définition dy type liste complémentaire par tableau
#define MAX_LISTE 100 /* taille maximale de la liste */
Typedef int élement /* les éléments sont des entiers */
Typedef int Place /* place = rang (est un entier) */
Typedef struct {
élement tab[MAX_LISTE] ;/*éléments de la liste */
int taille ;
} Liste;
// Déclaration des fonctions gérant la liste
Liste liste_vide(Void)
int longueur(Liste l);
Liste inserer(Liste l,int i, Element e);
Liste supprimer(Liste l, int i);
élement kème (Liste l, int k);
Place acces(Liste l, int i);
élement contenu(Liste l, Place i);
Place succ (Liste l, Place i);
#endif
/* Liste Chaînée en C */
//type des éléments
typedef int Element;
//type place
Typedef struct cellule* Place;
//type cellule
typedef struct {
Element valeur;
Struct cellule *suivant;
} Cellule ;
//type Liste
Typedef Cellule *Liste;
pc suivant =NULL;
if (i==0){pcsuivant =l;
l=pc; }
else {
int j; Liste p=l;
for (j=0; j<i-1; j++)
p=psuivant;
pcsuivant =psuivant;
p suivant =pc;}
return l; }
exit(-1); }
return contenu(l, acces(l, k)); }
#endif
Remarques :
Ajout au milieu d’une liste connaissant la place qui précède celle ou s’effectuera l’ajout
– ajouter : Liste x Place x Elément Liste
– ajouter(L, p, e) : liste obtenue à partir de L en ajoutant une place contenant
l’élément e, juste après la place p
Enlever un élément d’une liste connaissant sa place
– enlever : Liste x Place Liste
– enlever(L,p) : liste obtenue à partir de L en supprimant la place p et son
contenu
IV- ARBRE
1- Objectifs :
Etudier des structures non linéaires :
Arbre binaire
Arbre binaire de recherche
Arbre Maximier ou Tas
Graphe
…
2- Arbres
2-1- INTRODUCTION
2-1-1- Notion d’arbre
Les arbres sont les structures de données les plus importantes en Informatique.
Ce sont des structures non linéaires qui permettent d’obtenir des algorithmes plus
performants que lorsqu’on utilise des structures de données linéaires telles que les listes et
les tableaux.
Ils permettent une organisation naturelle des données.
2-1-2- Exemple d’utilisation d’arbre :
Organisation des fichiers dans les systèmes d’exploitation;
Organisation des informations dans un système de bases de données;
Représentation de la structure syntaxique des programmes sources dans les
compilateurs;
Représentation d’une table de matière;
Représentation d’un arbre généalogique;
2-2- Terminologie
• Un arbre est un ensemble d’éléments appelés nœuds (ou sommet), liés par une
relation (dite de priorité) induisant une structure hiérarchique parmi ces nœuds.
• Un nœud, comme tout élément d’une liste, peut être de n’importe quel type.
• Une structure d’arbre de type de base T est :
• Soit la structure vide;
• Soit un nœud de type T, appelé racine, associé à un nombre fini de structures
d’arbre disjointes du type de base T appelées sous arbres.
• C’est une définition récursive : la récursivité est une propriété des arbres et des
algorithmes qui les manipulent.
• Une liste est un cas particulier des arbres (arbre dégénéré), où tout nœud a au plus un
sous arbre
Exemple :
• Père :
– Tous les nœuds d’un arbre (sauf un), ont un père et un seul. Un nœud p est père
du nœud f si et seulement si f est fils de p;
– Dans l’exemple précédent : le père de 5 est 1, celui de 1 est 6;
• Fils :
– Chaque nœud d’un arbre pointe vers un ensemble éventuellement vide d’autres
nœuds; ce sont ses fils (ou enfants);
– Le nœud 6 a deux fils 3 et 1, le nœud 3 a un fils 2, le nœud 1 a trois fils 5, 7 et
8.
• Frère :
– Deux nœuds ayant le même père;
– Les nœuds 5, 7 et 8 sont des frères;
• Racine :
– Le seul nœud sans père;
– 6 est la racine de l’arbre précédent.
• Feuille :
– Nœud terminal (ou externe); ce sont des nœuds sans fils
– Exemple : 2, 5, 7 et 8
• Branche :
– Chemin qui commence à la racine et se termine à une feuille;
– Exemple : les chemins (6, 3, 2), (6, 1, 5), (6, 1, 7), (6, 1, 8);
• Nœud interne : 1, 3 et 6
– Un nœud qui n’est pas terminal
• Arbre binaire:
– Un arbre où chaque nœud a au plus deux fils.
– Quand un nœud de cet arbre a un seul fils, on précise s’i s’agit du fils gauche
ou du fils droit.
– Exemple : arbre binaire dont les nœuds contiennent des caractères ordonnés,
mais identiques comme arbres simples.
h-1 ≤ n ≤ 2h+1 - 1
– Le parcours préfixe;
– Le parcours infixe (ou symétrique);
– Le parcours post fixe (ou suffixe).
a) Parcours préfixe
En abrégé RGD (Racine, Gauche, Droit), consiste à effectuer dans l’ordre :
– Le traitement de la racine r;
– Le parcours préfixe du sous arbre gauche A1;
– Le parcours préfixe du sous arbre droit A2;
#define NB_MAX_NOEUDS 15
typedef int Element;
typedef struct nœud {
Element val;
int fg;
int fd;
} Nœud;
typedef Nœud TabN [NB_MAX_NOEUDS];
typedef struct arbre {
int nb_noeuds;
Int racine;
TabN les_noeuds;
} Arbre_Binaire;
return A ==NULL;
}
Pnoeud nouveau _noeud(Element e) {
Pnoeud p=(Pnoeud) malloc (sizeof(Nœud));
if (p !=NULL) {
Pval = e;
Pfg = NULL;
Pfd = NULL;
}
Return (p);
}
Arbre_Binaire cons(Nœud *r, Arbre_Binaire G, Arbre_Binaire D) {
rfg = G;
rfd = D;
Return r;
}
Nœud racine (Arbre_Binaire A) {
if (est_vide(A) {
printf ("Erreur : Arbre vide ! \n");
exit(-1);
}
return (*A);
}
Arbre_Binaire gauche(Arbre_Binaire A) {
if (est_vide(A) {
printf ("Erreur : Arbre vide ! \n");
exit(-1);
}
return Afg; /* ou bien (*A).fg; */
}
Arbre_Binaire droite(Arbre_Binaire A) {
if (est_vide(A) {
printf ("Erreur : Arbre vide ! \n");
exit(-1);
}
return Afd; /* ou bien (*A).fd; */
}
Element contenu (Nœud n) {
return n.val;
}
2-4- Arbre binaire de recherche
2-4-1- Notion d’Arbre binaire de recherche
C’est un arbre binaire particulier :
– Il permet d’obtenir un algorithme de recherche proche dans l’esprit de la
recherche dichotomique;
– Pour lequel les opérations d’ajout et de suppression d’un élément sont aussi
efficaces.
Cet arbre utilise l’existence d’une relation d’ordre sur les éléments, représentée par une
fonction clé, à valeur entière.
2-4-2- Définition :
Un arbre binaire de recherche (Binary Search Tree), ou ABR est un arbre binaire tel que
pour tout nœud :
– Les clés de tous les nœuds du sous-arbre gauche sont inférieures ou égales à la
clé du nœud.
– Les clés de tous les nœuds du sous-arbre droit sont supérieures à la clé du
nœud.
Chaque nœud d’un ABR désigne un élément qui est caractérisé par une clé (prise dans un
ensemble totalement ordonné) et des informations associés à cette clé.
Dans toute illustration d’un arbre binaire de recherche, seules les clés sont représentées.
On supposera aussi que toute clé identifie de manière unique un élément.
2-4-3- Exemple :
2-4-4- Remarque :
Plusieurs représentations possibles d’un même ensemble par un ABR.
La structure précise de l’ABR est déterminée par :
– L’algorithme d’insertion utilisé,
– L’ordre d’arrivée des éléments.
Exemple :
L’ABR suivant représente aussi E = {a, b, d, e, g, i , t}
b) Spécification
Extension Type Arbre_Rech
Utilise Elément, Booleen
Opérations
Rechercher : Element x Arbre_Rech Booleen
Axiomes
Soit x : Element, r : Nœud, G, D : Arbre_Rech
c) Réalisation en C
Booleen Rechercher(Arbre_Rech A, Element e) {
if (est_vide(A) == vrai)
return faux
else {
if (e == A->val)
return vrai;
else if (e < A->val)
return Rechercher(A->fg, e);
else
return Rechercher(A->fd, e);
}
}
d) Autre Spécification
Extension Type Arbre_Rech
Utilise Elément
Opérations
Rechercher : Element x Arbre_Rech Booleen
Axiomes
Soit x : Element, r : Nœud, G, D : Arbre_Rech
b) Réalisation
Fonction Ajouter_feuille(Elément x, Arbre_Rech A ) : Arbre_Rech
si (est_vide(A) ) alors
Pnoeud r = nouveau_noeud(x)
si est_vide(r) alors
ecrire ‘erreur’
fsi
Ajouter_feuille cons(r, arbre_vide(), arbre_vide())
sinon
si (x > contenu(racine(A)) alors
a) Spécification
Extension Type Arbre_Rech
Utilise Element
Opérations
Max : Arbre_Rech Element
SupprimerMax : Arbre_Rech Arbre_Rech
Supprimer : Element x Arbre_Rech Arbre_Rech
Pré-conditions
Max(A) est_défini_ssi est_vide(A) = faux
SupprimerMax(A) est_défini_ssi est_vide(A) = faux
Axiomes
Soit x : Element, r : Nœud, G, D : Arbre_Rech
Si est_vide(D) = vrai alors Max(<r, G, D>) = r
b) Réalisation
Fonction Max(A : Arbre_Rech) : Pnoeud
Si est_vide(droite(A)) alors max A
Sinon Max Max(droite(A))
Fsi
2-5-2- Exemple :
Préconditions
Max(T) est_défini_ssi est_vide(T) = faux
SupprimerMax(T) est_défini_ssi est_vide(T) = faux
Ajouter(T, e) est_défini_ssi Appartient(T, e) = faux
Axiomes
Soit T, T1 : Tas, e, e1 : Element
Si est_vide(T) = vrai alors Appartient(T, e) = faux
Sinon si T = ajouter(T1, e1) alors
Appartient(T, e) = (e=e1) ou Appartient(T1, e)
Sinon si T = SupprimerMax(T1) alors
appartient (T, e) = (e ≠ Max(T1)) et Appartient(T1, e)
Apparient(T, Max(T)) = vrai
Si Appartient(T, e) = vrai alors Max(T) ≥ e
c) Complexité
La complexité de la suppression est la même que celle de l’insertion, c.-à-d. O(log(n)) :
En effet, on ne fait que suivre un chemin descendant depuis la racine
Debut
Max t[1]
Fin
b) Pseudo code
Fonction ConstruireTas(Tableau T[1..n]) : Tas
Debut
Pour i n à 2 par pas -1 faire
echanger(T[1], T[i])
Fpour
n n-1
Entasser(T, i)
Tri_par_Tas T
Fin
c) Complexité
On montre que l’appel à construire Tas prend un temps O(n)
Chacun des (n-1) appels à Entasser prend un temps O(log(n))
Par conséquent, l’algorithme du tri par tas s’exécute en O(n log(n))
V- GRAPHE
1- Définitions et vocabulaire
1-1- Graphe orienté et non orienté
Un graphe orienté est un couple (V, E) où V est un ensemble fini et E une relation
binaire sur V.
V est l'ensemble des sommets (vertex en anglais, au pluriel vertices)
E est l’ensemble des arcs (edge en anglais)
Il y a alors possibilité d'un arc reliant un sommet à lui-même ce qu'on appelle une boucle.
Un graphe non orienté est un couple (V, E) où V est un ensemble fini et E une
relation binaire symétrique sur V et non réflexive. E est alors l'ensemble des arêtes
(edge également en anglais).
Etant donné un graphe non orienté G = (V, E), sa version orientée G' est le graphe G' =
(V, E') où (u, v) E' si et seulement si (u, v) E.
Chaque arête de G est donc recopiée en 2 arcs pour G'.
Etant donné un graphe orienté G = (V, E) sa version non orientée G' est le graphe G' =
(V, E') où (u, v) E' si et seulement si [ u ≠ v ] et [ (u, v) E ou (v, u) E ]
On enlève les sens des orientations et les boucles.
Exemple :
Un chemin (v0, v1, ..., vk) est un cycle si k> 0 et si v0 = vk. C'est un cycle simple si de
plus v1, v2,...,vk sont distincts.
Dans un graphe orienté, il existe de cycles de longueur 1: ce sont les boucles.
Dans un graphe non orienté, il n'y a pas de cycles de longueur 1. Les cycles de longueur 2
sont les cycles (u, v, u) qu'on fabrique avec une arête (u, v). Dans un graphe non orienté,
on réserve alors la dénomination de cycle à des cycles simples de longueur ≥ 3.
et on a alors:
succsuivant(s, s', g) = (i+1)ème-succ-de s dans g
coût : Sommet X Sommet X Graphe → Réel
Cette opération est définie pour toute paire ordonnée de sommets (s, s') et tout graphe g
tels que < s, s' > est-un-arc-de g = vrai.
Il faut modifier dans ce cas le profil de l'opération d'ajout d'un arc, car il faut prévoir le
coût de cet arc en opérande :
ajouter-l'arc < _, _> de-coût _ à _ : Sommet X Sommet X Réel X Graphe → Graphe
arc: on lui fait rendre pour ces paires une valeur dont on sait qu'elle ne peut être un coût.
D'autres opérations peuvent être définies à partir de la spécification ci-dessus, par exemple
les opérations nb-sommets et nb-arcs.
Très souvent, on rencontre dans les algorithmes sur les graphes le schéma suivant de
traitement des successeurs d'un sommet i: « pour chaque sommet j, successeur de i,
effectuer un certain traitement sur j ». Ce schéma s'écrit en utilisant les opérations de
base:
Pour j = 1 à d°+ de i dans g faire
traiter(jème-succ-de i dans g);
Déclaration en C :
int G[n][n];
Dans le cas où le graphe est non orienté, la matrice est symétrique. Dans le cas où le
graphe est valué, on utilise une matrice où l'éléments d'indices i et j a pour valeur le poids
et l'arc/arête du sommet i au sommet j, si cet arc/arête existe, et sinon une valeur dont on
sait qu'elle ne peut être un poids: par exemple, le plus grand entier utilisable si les poids
sont des entiers bornés supérieurement.
ordre. Ces listes sont appelées listes d'adjacence. Dans le cas où l'ensemble des sommets
n'évolue pas, ces listes sont accessibles à partir d'un tableau S, qui contient pour chaque
sommet, un pointeur vers le début de sa liste. On a donc une déclaration C du genre :
Il est facile de prendre en compte l'existence de poids sur les arcs en ajoutant une
donnée à la struct succ. L'intérêt de cette représentation est que l'espace mémoire utilisé
est, pour un graphe orienté avec n sommets et p arcs, en O(n+p). Dans le cas d'un graphe
non orienté avec p arêtes, l'espace mémoire est en O(n+2p). De plus, quand on a besoin de
faire un traitement sur les successeurs d'un sommet s, le nombre de sommets parcourus est
exactement le nombre de successeurs de s, soit d°+(s).
Exemple
5- Implémentation en C
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int extrem;
float valuation;
} arc;
typedef struct {
int nbsom;
int nbarc;
int *tab_som;
arc *tab_arc;
} graphe;
void alloue_graphe(int nbs, int nba, graphe *g)
{ g->nbsom = nbs; g->nbarc = nba;
g->tab_som = (int *)calloc(g->nbsom+2,sizeof(int));
g->tab_arc = (arc *)calloc(g->nbarc+0,sizeof(arc));
}
void affiche_arc(arc a)
{ printf("(%4d - %7.2f)",a.extrem, a.valuation); }
void affiche_graphe(graphe g)
{ int i;
printf("nombre de sommets = %d",g.nbsom);
printf(" nombre d'arcs = %d\n",g.nbarc);
printf("tableau des sommets = ");
for (i=1;i<=g.nbsom+1;i++) printf("%4d\t",g.tab_som[i]); printf("\n");
printf("tableau des arcs = ");
for (i=0;i<g.nbarc;i++) affiche_arc(g.tab_arc[i]); printf("\n");
}
int main()
{ graphe g;
charge_graphe("pcc.txt",&g);
affiche_graphe(g);
affiche_successeurs(1,g);
affiche_successeurs(4,g);
affiche_successeurs(5,g);
affiche_successeurs(6,g);
}
Le parcours de G "en largeur d'abord" découvre tous les sommets qui sont à distance k
de avant de découvrir un quelconque sommet à distance k+1.
C'est la généralisation du parcours d'un arbre "en largeur d'abord" (parcours par niveaux).
Il utilise une file, et un repérage des sommets en 3 couleurs :
- Blanc, pour les sommets qui n'ont pas encore été visités,
- Gris, pour les sommets qui sont "en cours" de visite : ils sont dans la file, mais
leurs successeurs n'y sont pas encore,
- Noir, pour les sommets pour lesquels la visite est terminée : on les retire de la
file, en y mettant tous leurs successeurs.
F = file_vide;
Enfiler(s,F);
Tant que F != file_vide faire
u= Tête(F);
Pour chaque sommet v successeur de u faire
si couleur [v] == blanc alors
couleur[v] = gris;
d[v] = d[u]+1;
Préd[v] = u;
Enfiler (v, F);
Défiler (F);
couleur [u] = noir;
Tant que F != file_vide faire
u= Tête(F);
Pour chaque sommet v successeur de u faire
si couleur [v] == blanc alors
couleur[v] = gris;
d[v] = d[u]+1;
Préd[v] = u;
Enfiler (v, F);
Défiler (F);
couleur [u] = noir;
fin
Visite_profondeur (u : sommet);
debut
couleur [u] = gris;
temps = temps+1;
début[u] = temps;
Pour chaque sommet v successeur de u faire
si couleur [v] == blanc alors
Préd[v] =u;
Visite_profondeur(v);
couleur[u] = noir;
temps = temps +1;
fin[u] = temps;
fin
h : fonction de hachage
T : table de hachage
m : taille tableau T
h:U {0, 1, …, m – 1}
2- Définition
Pour toute clé x, h(x) appelée valeur de hachage primaire, donne l'indice de la place
de x dans un tableau (ou une zone de mémoire) appelé table de hachage.
La fonction h est appelée fonction de hachage. Elle distribue les clés de façon aléatoire, mais
elle doit évidemment être déterministe: la valeur de hachage d'une clé donnée doit toujours
être la même pour pouvoir retrouver l'élément correspondant.
De plus, si h n’est pas injective on dit qu’il y’a collision, on attend de cette fonction qu'elle
soit uniforme afin de limiter les colisions c-à-d en termes de probabilité:
x et i ε [0,m-1], Proba(h(x) = i) = 1/m
Enfin, la fonction de hachage doit être facilement et rapidement calculable.
Exemple
Soit E l'ensemble de mots suivant:
E={serge, odile, luc, anne, annie, jean, julie, basile, paula, marcel, élise}
On considère ici que la clé est l'élément lui-même; un exemple de hachage consiste à associer
à chaque élément e de E un nombre h(e), compris entre 0 et 12, en procédant comme suit.
Remarque:
Dans cet exemple, la restriction de h à E est injective et l'on peut alors ranger chaque
élément e à l'adresse h(e).
On initialise le tableau au début par une valeur spéciale, afin de reconnaître les places
vides,
La valeur de m doit être premier, sinon la clé aura la même parité que m et donc les
indices de la table.
4- Méthodes de hachage
4-1- Méthodes de résolution des collisions par chaînage (ou hachage indirect):
Dans cette méthode, tous les éléments en collision sont chaînés entre eux à l'extérieur
de la table de hachage: la table contient seulement les têtes de m listes chaînées représentant
chacune un ensemble d'éléments qui rendent une même valeur par la fonction de hachage.
Exemple:
hachage par adjonctions successives des éléments:
e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13
ayant respectivement pour valeur de hachage
2, 0, 3, 0, 3, 0, 4, 8, 1, 5, 4, 2, 4
Remarque:
L’ajout est précédé d’un test d’appartenance, on ajoute en fin (ou au début de liste),
Pour rechercher, supprimer ou ajouter l’élément x, on travaille avec la liste Lh(x). (avec Li, la
liste dont la tête est T[i]).
Remarque :
Après ajout de e5, tout élément dont la valeur de hachage primaire est entre 3 et 8 ira se
placer à l’adresse 8
5- Double hachage
Pour éviter la formation de groupements d’éléments contigus, il faut essayer de les
disperser davantage lorsqu’il y a collision, on utilise une deuxième fonction de hachage.