Académique Documents
Professionnel Documents
Culture Documents
Chapitre 8
http://ow.ly/35Jlt
Chapitre 8
Structures de donnes
avances
Une structure de donnes est une organisation logique des donnes permettant de simplifier ou
d'acclrer leur traitement.
8.1.
Pile
En informatique, une pile (en anglais stack) est une structure de donnes fonde sur le principe
dernier arriv, premier sorti (ou LIFO pour Last In, First Out), ce qui veut dire que les derniers
lments ajouts la pile seront les premiers tre rcuprs.
Le fonctionnement est donc celui d'une pile d'assiettes : on ajoute des assiettes sur la pile, et on
les rcupre dans l'ordre inverse, en commenant par la dernire ajoute.
Primitives
Voici les primitives communment utilises pour manipuler des piles :
empiler : ajoute un lment sur la pile. Terme anglais correspondant : Push .
dpiler : enlve un lment de la pile et le renvoie. En anglais : Pop
vide : renvoie vrai si la pile est vide, faux sinon
remplissage : renvoie le nombre d'lments dans la pile.
Applications
Didier Mller
Dans un navigateur web, une pile sert mmoriser les pages Web visites. L'adresse de
chaque nouvelle page visite est empile et l'utilisateur dpile l'adresse de la page
prcdente en cliquant le bouton Afficher la page prcdente .
L'valuation des expressions mathmatiques en notation post-fixe (ou polonaise
inverse) utilise une pile.
La fonction Annuler la frappe (en anglais Undo ) d'un traitement de texte
8-1
janvier 2015
Exercice 8.1
Implmentez en Python une classe pile avec ces quatre mthodes, ainsi qu'une mthode
afficher qui liste tous les lments de la pile, du dernier entr au premier entr.
8.2.
File
Une file (queue en anglais ) est une structure de donnes base sur le principe Premier entr,
premier sorti , en anglais FIFO (First In, First Out), ce qui veut dire que les premiers lments
ajouts la file seront les premiers tre rcuprs. Le fonctionnement ressemble une file
d'attente : les premires personnes arriver sont les premires personnes sortir de la file.
Primitives
Voici les primitives communment utilises pour manipuler des files :
ajouter : ajoute un lment dans la file. Terme anglais correspondant : Enqueue
enlever : renvoie le prochain lment de la file, et le retire de la file. Terme anglais
correspondant : Dequeue
vide : renvoie vrai si la file est vide, faux sinon
remplissage : renvoie le nombre d'lments dans la file.
Applications
En gnral, on utilise des files pour mmoriser temporairement des transactions qui
doivent attendre pour tre traites.
Les serveurs d'impression, qui doivent traiter les requtes dans l'ordre dans lequel elles
arrivent, et les insrent dans une file d'attente (ou une queue).
Certains moteurs multitches, dans un systme d'exploitation, qui doivent accorder du
temps-machine chaque tche, sans en privilgier aucune.
Un algorithme de parcours en largeur utilise une file pour mmoriser les nuds visits.
On utilise aussi des files our crer toutes sortes de mmoires tampons (en anglais
buffers).
Exercice 8.2
Implmentez en Python une classe file avec ces quatre mthodes, ainsi qu'une mthode
afficher permettant de voir tous les lments de la file, du premier entr au dernier entr.
Files priorits
Une file priorits est un type abstrait lmentaire sur laquelle on peut effectuer trois oprations :
insrer un lment
supprimer le plus grand lment
tester si la file priorits est vide ou pas
Les principales implmentations de ces files priorits sont le tas (voir 8.9), le tas binomial et
le tas de Fibonacci.
Didier Mller
8-2
janvier 2015
L'informatique au lyce
8.3.
Donald Knuth,
professeur mrite
Stanford, auteur
de l'ouvrage de
rfrence sur
l'algorithmique, en
plusieurs volumes,
intitul The Art
of Computer
Programming ,
considre les
arbres comme la
structure la plus
fondamentale de
l'informatique .
Chapitre 8
Arbres
Un arbre est un graphe sans cycle, o des nuds sont relis par des artes. On distingue trois
sortes de nuds :
les nuds internes, qui ont des fils ;
les feuilles, qui n'ont pas de fils ;
la racine de l'arbre, qui est l'unique nud ne possdant pas de pre.
Traditionnellement, on dessine toujours la racine en haut et les feuilles en bas.
La profondeur d'un nud est la distance, i.e. le nombre d'artes, de la racine au nud. La hauteur
d'un arbre est la plus grande profondeur d'une feuille de l'arbre. La taille d'un arbre est son nombre de
nuds (en comptant les feuilles ou non).
Les arbres peuvent tre tiquets. Dans ce cas, chaque nud possde une tiquette, qui est en
quelque sorte le contenu du nud. L'tiquette peut tre trs simple (un nombre entier, par
exemple) ou plus complexe : un objet, une instance d'une structure de donnes, etc.
Les arbres sont en fait rarement utiliss en tant que tels, mais de nombreux types d'arbres avec
une structure plus restrictive existent et permettent alors des recherches rapides et efficaces. Nous en
reparlerons bientt.
8.3.1.
Parcours
Parcours en largeur
Le parcours en largeur correspond un parcours par
niveau de nuds de l'arbre. Un niveau est un ensemble de
nuds ou de feuilles situs la mme profondeur.
Ainsi, si l'arbre ci-contre est utilis, le parcours sera A,
B, C, D, E, F, G.
Parcours en profondeur
Le parcours en profondeur est un parcours rcursif sur un arbre. Il existe trois ordres pour cette
mthode de parcours. Le parcours en profondeur prfix est le plus courant.
Parcours en profondeur prfix
Dans ce mode de parcours, le nud courant est trait avant le traitement des nuds gauche et
droit. Ainsi, si l'arbre prcdent est utilis, le parcours sera A, B, D, E, C, F, G.
Parcours en profondeur suffix
Dans ce mode de parcours, le nud courant est trait aprs le traitement des nuds gauche et
droit. Ainsi, si l'arbre prcdent est utilis, le parcours sera D, E, B, F, G, C, A. Ce mode de parcours
correspond une notation polonaise inverse, utilise par les calculatrices HP.
Didier Mller
8-3
janvier 2015
Exercice 8.3
Parcourez l'arbre ci-dessous selon les quatre mthodes vues prcdemment.
8.4.
Arbres binaires
Dans un arbre binaire, chaque nud possde au plus deux fils, habituellement appels gauche
et droit . Du point de vue des fils, l'lment dont ils sont issus au niveau suprieur est logiquement
appel pre.
8.4.1.
Didier Mller
8-4
janvier 2015
L'informatique au lyce
8.4.2.
Chapitre 8
Structure 3 nuds
Les arbres binaires peuvent tre construits de diffrentes manires. Dans un langage avec
structures et pointeurs (ou rfrences), les arbres binaires peuvent tre conus en ayant une structure
trois nuds qui contiennent quelques donnes et des pointeurs vers son fils droit et son fils gauche.
Parfois, il contient galement un pointeur vers son unique parent. Si un nud possde moins de deux
fils, l'un des deux pointeurs peut tre affect de la valeur spciale nulle.
L'arbre conservera un pointeur vers la racine, un pointeur vers le nud courant (qui permet de
simplifier l'ajout et la consultation) et ventuellement le nombre de nuds du graphe.
Avantages
Conu pour contenir un nombre variable de nuds.
Pas de gaspillage de mmoire.
Inconvnients
Implmentation dlicate raliser quand on est dbutant en programmation.
Pas d'accs direct un nud de l'arbre.
Tableau
La fonction
floor(x) retourne
l'entier infrieur
ou gal x.
Les arbres binaires peuvent aussi tre rangs dans des tableaux, et si l'arbre est un arbre binaire
complet, cette mthode ne gaspille pas de place, et la donne structure rsultante est appele un tas.
Dans cet arrangement compact, un nud a un indice i, et ses fils se trouvent aux indices 2i+1 et
2i+2, tandis que son pre se trouve l'indice floor((i1)/2), s'il existe.
La racine (nud 0) a pour fils les nuds 1 et 2. Le nud 1 a pour fils 3 et 4, etc.
Avantages
Implmentation facile raliser.
Possibilit d'accs direct un nud de l'arbre (un seul accs en mmoire).
Inconvnients
Conu pour contenir un nombre fixe de nuds.
Si l'arbre est profond mais contient peu de nuds, il se produit un gaspillage important
de mmoire.
Didier Mller
8-5
janvier 2015
Exercice 8.4
Programmez en Python les trois algorithmes de parcours en profondeur vus au 8.3.1 puis
excutez-les sur un tableau reprsentant l'arbre binaire ci-dessous :
8.5.1.
Exemples
Didier Mller
8-6
janvier 2015
L'informatique au lyce
Chapitre 8
8.5.2.
Notation
Type de complexit
O(1)
O(log(n))
complexit logarithmique
O(n)
complexit linaire
O(nlog(n))
complexit quasi-linaire
O(n2)
complexit quadratique
O(n3)
complexit cubique
O(np)
complexit polynomiale
O(2n)
complexit exponentielle
O(n!)
complexit factorielle
Exercice 8.5
Un programme rsout un problme de taille n en n! secondes. Combien de temps est ncessaire
pour rsoudre un problme de taille n = 12 ?
8.6.
Un arbre binaire de recherche (ABR) est un arbre binaire dans lequel chaque nud possde une
tiquette, telle que chaque nud du sous-arbre gauche ait une tiquette infrieure ou gale celle du
nud considr, et que chaque nud du sous-arbre droit possde une tiquette suprieure ou gale
celle-ci (selon la mise en uvre de l'ABR, on pourra interdire ou non des tiquettes de valeur gale).
Didier Mller
8-7
janvier 2015
8.6.1.
Recherche
La recherche dans un arbre binaire d'un nud ayant une tiquette particulire est un procd
rcursif. On commence par examiner la racine. Si l'tiquette de la racine est l'tiquette recherche,
l'algorithme se termine et renvoie la racine. Si l'tiquette cherche est infrieure, alors elle est dans le
sous-arbre gauche, sur lequel on effectue alors rcursivement la recherche. De mme, si l'tiquette
recherche est strictement suprieure l'tiquette de la racine, la recherche continue sur le sous-arbre
droit. Si on atteint une feuille dont l'tiquette n'est pas celle recherche, on sait alors que cette
tiquette n'est pas dans l'arbre.
Cette opration requiert un temps en O(log(n)) dans le cas moyen, mais O(n) dans le pire des cas
o l'arbre est compltement dsquilibr o chaque pre a un seul fils.
8.6.2.
Insertion
L'insertion d'un nud commence par une recherche : on cherche l'tiquette du nud insrer ;
lorsqu'on arrive une feuille, on ajoute le nud comme fils de la feuille en comparant son tiquette
celle de la feuille : si elle est infrieure, le nouveau nud sera gauche ; sinon il sera droite. Ainsi,
chaque nud ajout sera une feuille.
La complexit de l'insertion est O(log(n)) dans le cas moyen et O(n) dans le pire des cas.
Exercice 8.6
Insrez dans l'arbre binaire de recherche ci-dessous les valeurs 11 et 5.
Didier Mller
8-8
janvier 2015
L'informatique au lyce
8.6.3.
Chapitre 8
Suppression
Plusieurs cas sont considrer, une fois que le nud supprimer a t trouv :
Cela permet de garder une structure d'arbre binaire de recherche. Puis on applique
nouveau la procdure de suppression N, qui est maintenant une feuille ou un nud avec
un seul fils.
8-9
janvier 2015
8.7.
Arbres AVL
La dnomination arbre AVL provient des noms de ses deux inventeurs russes, Georgy
Maximovich Adelson-Velsky et Evgenii Mikhailovich Landis, qui l'ont publi en 1962 sous le titre
An algorithm for the organization of information.
Les arbres AVL ont t historiquement les premiers arbres binaires de recherche automatiquement quilibrs. Dans un arbre AVL, les hauteurs des deux sous-arbres d'un mme nud diffrent
au plus de un. La recherche, l'insertion et la suppression sont toutes en O(log(n)) dans le pire des cas.
Le facteur d'quilibrage d'un nud est la diffrence entre la hauteur de son sous-arbre droit et
celle de son sous-arbre gauche. Un nud dont le facteur d'quilibrage est +1, 0, ou -1 est considr
comme quilibr.
Un nud avec tout autre facteur est considr comme dsquilibr et requiert un rquilibrage.
Chaque fois qu'un nud est insr ou supprim d'un arbre AVL, le facteur d'quilibrage de chaque
nud le long du chemin depuis la racine jusqu'au nud insr (ou supprim) doit tre recalcul.
Si l'arbre est rest quilibr, il n'y a rien faire. Si ce n'est pas le cas, on effectuera des rotations
d'quilibrage de manire obtenir nouveau un arbre AVL.
8.7.1.
Rotations d'quilibrage
Une rotation est une modification locale d'un arbre binaire. Elle consiste changer un nud
avec l'un de ses fils.
Dans la rotation droite, un nud devient le fils droit du nud qui tait son fils gauche.
Dans la rotation gauche, un nud devient le fils gauche du nud qui tait son fils droit.
Les rotations gauches et droites sont inverses l'une de l'autre.
Les rotations ont la proprit de pouvoir tre implmentes en temps constant, et de prserver
l'ordre infixe. En d'autres termes, si A est un arbre binaire de recherche, tout arbre obtenu partir de
A par une suite de rotations gauche ou droite d'un sous-arbre de A reste un arbre binaire de recherche.
Une rotation simple droite est utilise quand un nud a un facteur d'quilibrage infrieur -1 et
que son fils gauche a un facteur d'quilibrage de -1.
Didier Mller
8-10
janvier 2015
L'informatique au lyce
Chapitre 8
Une rotation double droite est utilise quand un nud a un facteur d'quilibrage infrieur -1 et
que son fils gauche a un facteur d'quilibrage de +1. La double rotation droite est une rotation simple
gauche du sous-arbre gauche, suivi d'une rotation simple droite du nud dsquilibr. Cette
opration est aussi appele parfois une rotation gauche-droite.
Une rotation simple gauche est utilise quand un nud a un facteur d'quilibrage suprieur +1
et que son fils droit a un facteur d'quilibrage de +1.
Une rotation double gauche est utilise quand un nud a un facteur d'quilibrage suprieur +1
et que son fils gauche a un facteur d'quilibrage de -1. La double rotation gauche est une rotation
simple droite du sous-arbre droit, suivi d'une rotation simple gauche du nud dsquilibr. Cette
opration est aussi appele parfois une rotation droite-gauche.
Didier Mller
8-11
janvier 2015
8.7.2.
8.7.3.
Suppression
La suppression dans un arbre AVL peut se faire par rotations successives du nud supprimer
jusqu' une feuille (en choisissant ces rotations de sorte que l'arbre reste quilibr), et ensuite en
supprimant cette feuille directement. La suppression se fait aussi en O(log(n)).
8.7.4.
Recherche
La recherche dans un arbre AVL se droule exactement comme pour un arbre binaire de
recherche, et comme la hauteur d'un arbre AVL est en O(log(n)), elle se fait donc en O(log(n)).
Exercice 8.7
Insrez dans l'arbre AVL ci-dessous les valeurs 20 et 70.
Exercice 8.8
Observez sur l'applet du site compagnon comment insrer, supprimer et rechercher une valeur
dans un arbre AVL.
8.8.
Tas
On dit qu'un arbre binaire complet est ordonn en tas1 (on dit aussi parfois monceau ) lorsque
la proprit suivante est vrifie :
Pour tous les nuds de l'arbre, tiquette(pre) tiquette(fils).
Cette proprit implique que la plus grande tiquette est situe la racine du tas. Ils sont ainsi
trs utiliss pour implmenter les files priorits2 car ils permettent des insertions en temps
1
2
Heap en anglais
Voir 8.2. Le mot file est mal choisi, car une file de priorit ne ressemble pas une file, ni par son aspect, ni par son comportement.
Didier Mller
8-12
janvier 2015
L'informatique au lyce
Chapitre 8
8.8.1.
Primitives
Une caractristique fondamentale de cette structure de donnes est que la proprit du tas peut
tre restaure efficacement aprs la modification d'un nud. Si la valeur du nud est augmente et
qu'elle devient de ce fait suprieure celle de son pre, il suffit de l'changer avec celle-ci, puis de
continuer ce processus vers le haut jusqu'au rtablissement de la proprit du tas. Nous dirons que la
valeur modifie a t percole jusqu' sa nouvelle position.
Inversement, si la valeur du nud est diminue et qu'elle devient de ce fait infrieure celle d'au
moins un de ses fils, il suffit de l'changer avec la plus grande des valeurs de ses fils, puis de
continuer ce processus vers le bas jusqu'au rtablissement de la proprit du tas. Nous dirons que la
valeur modifie a t tamise jusqu' sa nouvelle position.
Plus formellement, la modification d'un nud se fait par les algorithmes suivants :
PROCEDURE percoler(T[0..n-1], i)
Donnes : tableau T, noeud i
Rsultat : T est de nouveau un tas
k := i
REPETER
j := k
SI j > 0 ET T[(j-1) div 2] < T[k] ALORS
k := (j-1) div 2
echanger T[j] et T[k]
JUSQU'A j = k
Didier Mller
8-13
janvier 2015
PROCEDURE tamiser(T[0..n-1], i)
Donnes : tableau T, noeud i
Rsultat : T est de nouveau un tas
k := i
REPETER
j := k
(* recherche du plus grand fils du noeud j *)
SI 2j+1 <= n-1 ET T[2j+1] > T[k] ALORS
k := 2j+1
SI 2j+1 < n-1 ET T[2j+2] > T[k] ALORS
k := 2j+2
echanger T[j] et T[k]
JUSQU'A j = k
PROCEDURE modifier_tas(T[0..n-1], i, v)
(* on veut changer la valeur v du noeud i en prservant la proprit
du tas *)
Donnes : tableau T, noeud i, valeur v
Rsultat : T est de nouveau un tas
x := T[i]
T[i] := v
SI v < x ALORS
tamiser(T, i)
SINON
percoler(T, i)
Cette proprit du tas en fait une structure de donnes idale pour trouver le maximum, liminer
la racine, ajouter un nud et modifier un nud. Ce sont prcisment les oprations voulues pour
l'implmentation efficace d'une liste de priorit dynamique : la valeur d'un nud indique la priorit
de l'vnement correspondant. L'vnement le plus prioritaire se trouve toujours la racine et il est
toujours possible de modifier dynamiquement la priorit d'un vnement.
PROCEDURE eliminer_racine(T[0..n-1])
Donnes : tableau T
Rsultat : T[0..n-2] est de nouveau un tas
T[0] := T[n-1]
tamiser(T,0)
supprimer(T[n-1])
PROCEDURE ajouter_noeud(T[0..n-1], v)
Donnes : tableau T, valeur v
Rsultat : T[0..n] est de nouveau un tas
T[n] := v
percoler(T,n)
Exercice 8.9
crivez un programme Python qui gre un tas : reprsentez le tas par un tableau. puis
programmez les procdures ou fonctions suivantes : percoler, tamiser, modifier_tas,
maximum, eliminer_racine, ajouter_noeud.
crivez enfin une procdure qui cre un tas de n valeurs tires alatoirement.
Didier Mller
8-14
janvier 2015
L'informatique au lyce
Chapitre 8
8.9.
Le terme vient de
retrievable memory,
mais l'usage dans la
littrature
francophone est
d'utiliser le
masculin.
Trie
Un trie ou arbre prfixe est un arbre numrique ordonn qui est utilis pour stocker une table
associative o les cls sont gnralement des chanes de caractres. Contrairement un arbre binaire
de recherche, aucun nud dans le trie ne stocke la chane laquelle il est associ. C'est la position du
nud dans l'arbre qui dtermine la chane correspondante.
Pour tout nud, ses descendants ont en commun le mme prfixe. La racine est associe la
chane vide. Des valeurs ne sont pas attribues chaque nud, mais uniquement aux feuilles et
certains nuds internes se trouvant une position qui dsigne l'intgralit d'une chane
correspondant une cl.
8.9.1.
Un trie en Python
class Trie:
def __init__(self, letter=""): # au dbut, l'arbre est vide
self.branches = {}
self.letter = letter
self.is_word_end = False
Didier Mller
8-15
janvier 2015
de l'arbre
une une
dans l'arbre
fin de mot
8.9.2.
Didier Mller
8-16
janvier 2015
L'informatique au lyce
Chapitre 8
print("Trie termin")
return trie
# -------------- programme principal -------------------trie = creer_trie('dictionnaire.txt')
while True:
donnees = input("Entrez un mot franais : ")
if donnees=="":
break
if trie.inTrie(donnees):
print(donnees,"est dans le dictionnaire")
else:
print(donnees,"n'est pas dans le dictionnaire")
Comme vous pourrez le constater en testant ce programme, la cration du trie prend un peu de
temps (quelques secondes). Par contre, une fois le trie cr, la rponse la question est instantane.
8.10.
Un graphe fini G = (V, E) est dfini par l'ensemble fini V = {v1, v2, ..., vn} dont les lments sont
appels sommets, et par l'ensemble fini E = {e1, e2, ..., em} dont les lments sont appels artes. Une
arte e de l'ensemble E est dfinie par une paire non-ordonne de sommets, appels les extrmits de
e. Si l'arte e relie les sommets a et b, on dira que ces sommets sont adjacents.
Prenons un exemple :
Ensemble des sommets :
V = {1, 2, 3, 4, 5, 6}
Ensemble des artes :
E = {(1, 2), (1, 5), (2, 5), (2, 3), (3, 4), (4, 5), (4, 6)}
Ce graphe peut se reprsenter graphiquement comme cicontre (il y a une infinit de reprsentations possibles).
8.10.1.
Listes d'adjacences
On peut aussi reprsenter un graphe en donnant pour chacun de ses sommets la liste des sommets
auxquels il est adjacent. C'est la mthode qui est utilise pour implmenter un graphe sur ordinateur.
Les listes d'adjacences du graphe ci-contre sont :
1:
2:
3:
4:
5:
6:
2, 5
1, 5
2, 4
3, 5, 6
1, 2, 4
4
8.10.2.
Ruzzle est un jeu de lettres aux rgles simples : une grille de quatre fois quatre lettres, et deux
minutes pour trouver le plus de mots possibles, avec pour seul impratif que les cases se touchent.
On ne peut pas utiliser deux fois la mme case, ce qui fait que la longueur maximale d'un mot sera de
16.
Didier Mller
8-17
janvier 2015
Dans la version que nous allons voir, on ne tiendra pas compte de ces bonus, ni de la valeur des
lettres ; on se contentera de trouver tous les mots possibles, du plus long au plus court.
Voici le programme complet que nos allons dcortiquer :
from trie_fr import Trie
# --------- case de la grille de Ruzzle ------------class Case:
def __init__(self, value):
self.value = value
self.neighbors = []
def addNeighbor(self, neighbor):
# ajoute un voisin la liste
self.neighbors.append(neighbor)
def getNeighbors(self):
# renvoie la liste des voisins d'une case
return self.neighbors
def getValue(self):
# renvoie la lettre d'une case
return self.value
# ---------------------------------------------------def creer_trie(dico):
print("Lecture du dictionnaire")
fichier = open(dico, 'r')
liste_mots = fichier.readlines()
fichier.close()
print("Cration du Trie")
trie = Trie()
trie.makeTrie(liste_mots)
print("Trie termin")
return trie
def creer_connexions(grille):
# cre les listes d'adjacences du graphe
for m in range(16):
case = grille[m]
i, j = m//4, m%4
for n in range(16):
Didier Mller
8-18
janvier 2015
L'informatique au lyce
Chapitre 8
x, y = n//4, n%4
if abs(i-x)<=1 and abs(j-y)<=1 and m != n:
case.addNeighbor(grille[n])
def chercher(noeud, case, vus=[]):
# cherche les mots franais dans le Trie
trouves = []
if noeud == None:
return trouves
if noeud.isWordEnd():
trouves.append(noeud.getLetter())
vus.append(case)
for voisin in case.getNeighbors():
if voisin not in vus:
results = chercher(noeud[voisin.getValue()], voisin, vus)
trouves.extend([noeud.getLetter()+ending for ending in results])
vus.remove(case)
return trouves
# -------------- programme principal -------------------trie = creer_trie('ruzzle_dictionnaire.txt')
while True:
donnees = input("Entrez la grille ligne par ligne : ")
while len(donnees)!=16 and len(donnees)!=0:
donnees = input("Entrez la grille ligne par ligne : ")
if donnees=="": # taper 'return' pour sortir de la boucle infinie
break
donnees = donnees.lower()
grille = [Case(lettre) for lettre in donnees]
creer_connexions(grille)
resultats = []
for case in grille: # premire lettre du mot chercher
noeud = trie
# on se place au dbut du Trie
mots_trouves = chercher(noeud[case.getValue()], case)
if len(mots_trouves) > 0:
resultats.extend(mots_trouves)
resultats = list(set(resultats)) # limine les doublons
output = sorted(resultats, key=lambda mot: [len(mot)], reverse=True)
print(len(output),"mots trouvs")
print(output)
print()
On voit sur la premire ligne que le programme importe la classe Trie du 8.9.1. La procdure
creer_trie a dj t utilise au 8.9.2
On a cr une classe case. Une case contient une lettre et la liste des lettres qui sont sur des
cases voisines (pas voisin, on entend qui jouxte horizontalement, verticalement ou en diagonale).
class Case:
def __init__(self, value):
self.value = value
self.neighbors = []
def addNeighbor(self, neighbor):
# ajoute un voisin la liste
self.neighbors.append(neighbor)
def getNeighbors(self):
# renvoie la liste des voisins d'une case
return self.neighbors
def getValue(self):
# renvoie la lettre d'une case
return self.value
Didier Mller
8-19
janvier 2015
La liste des lettres voisines d'une case est cre par la procdure creer_connexions(grille).
La grille est implmente par une liste de 16 lments.
Pour chacune des 16 lettres de la grille, on calcule sa ligne i et sa colonne j en fonction de la
position m dans grille. On parcourt ensuite les 15 autres cases de la grille et on calcule leur ligne x
et leur colonne y. Pour tre voisin de cette case, il faut que |i-x| soit infrieur ou gal 1 et de
mme pour |j-y|.
def creer_connexions(grille):
# cre les listes d'adjacences du graphe
for m in range(16):
case = grille[m]
i, j = m//4, m%4
for n in range(16):
x, y = n//4, n%4
if abs(i-x)<=1 and abs(j-y)<=1 and m!=n:
case.addNeighbor(grille[n])
u
a
e
e
s
l
r
u
i
i
e
a
o
t
s
y
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
a
u
a
e
u
u
a
e
s
s
l
r
i
i
i
e
s
e
e
r
a
a
e
e
l
l
r
u
i
i
e
a
l
s
l
u
l
e
e
r
i
r
u
e
t
e
a
s
u
s
i
o
a
l
i
t
e
r
e
s
e
u
a
y
l r
r u
i
s
l
e
o
i
i
s
i
r
u
a
t
e
a
y
i i e
i e a
o t s
t s y
o s
t y
La fonction la plus dlicate analyser est videmment chercher, car elle est rcursive.
On a vu comment chercher un mot dans le trie (8.9.2). Ici, le problme est un peu diffrent. En
effet, il ne serait pas trs efficace de chercher toutes les suites de lettres possibles (il y en a
12'029'624 selon [7]) et vrifier ensuite lesquels sont des mots franais dans le trie. Par exprience, il
y a gnralement entre 250 et 400 mots valides dans une grille.
On va faire l'inverse : on va parcourir le trie en utilisant les voisins.
On part du sommet du trie avec la premire lettre de la grille (celle en haut gauche).
On choisit son premier voisin dans la grille et on regarde si la suite de ces deux lettres existe dans
le trie.
Si la suite n'existe pas, on abandonne cette branche du trie, on marque ce voisin comme vu et on
essaie un autre voisin.
Si la suite existe, on regarde si c'est un mot complet. Si oui, on le mmorise. Si non, on prend un
voisin de la deuxime lettre et on analyse la suite des trois lettres.
Et ainsi de suite...
def chercher(noeud, case, vus=[]):
# cherche les mots franais dans le Trie
trouves = []
if noeud == None:
return trouves
if noeud.isWordEnd():
trouves.append(noeud.getLetter())
vus.append(case)
Didier Mller
8-20
janvier 2015
L'informatique au lyce
Chapitre 8
for voisin in case.getNeighbors():
if voisin not in vus:
results = chercher(noeud[voisin.getValue()], voisin, vus)
trouves.extend([noeud.getLetter()+ending for ending in results])
vus.remove(case)
return trouves
On parcourt selon ce principe le trie 16 fois, car il y a 16 cases possibles pour la premire lettre.
On limine finalement les doublons dans la liste des mots trouvs et on affiche tous les mots du plus
long au plus court.
for case in grille: # premire lettre du mot chercher
noeud = trie
# on se place au dbut du Trie
mots_trouves = chercher(noeud[case.getValue()], case)
if len(mots_trouves) > 0:
resultats.extend(mots_trouves)
resultats = list(set(resultats)) # limine les doublons
output = sorted(resultats, key=lambda mot: [len(mot)], reverse=True)
print(len(output),"mots trouvs")
print(output)
8.11.
Table de hachage
Une table de hachage (hash table en anglais) est une structure de donne permettant d'associer
une valeur une cl. Il s'agit d'un tableau ne comportant pas d'ordre (un tableau est index par des
entiers). L'accs un lment se fait en transformant la cl en une valeur de hachage (ou simplement
hachage) par l'intermdiaire d'une fonction de hachage. Le hachage est un nombre qui permet la
localisation des lments dans le tableau, typiquement le hachage est l'indice de l'lment dans le
tableau. Une case dans le tableau est appele alvole.
Didier Mller
8-21
janvier 2015
8.11.1.
Le fait de crer un hachage partir d'une cl peut engendrer un problme de collision, c'est--dire
qu' partir de deux cls diffrentes, la fonction de hachage pourrait renvoyer la mme valeur de
hachage, et donc par consquent donner accs la mme position dans le tableau. Pour minimiser les
risques de collisions, il faut donc choisir soigneusement sa fonction de hachage.
Les collisions tant en gnral rsolues par des mthodes de recherche linaire, une mauvaise
fonction de hachage, i.e. produisant beaucoup de collisions, va fortement dgrader la rapidit de la
recherche.
Le calcul du hachage se fait parfois en 2 temps :
1. une fonction de hachage particulire l'application est utilise pour produire un nombre
entier partir de la donne d'origine
2. ce nombre entier est converti en une position possible de la table, en gnral en calculant le
reste modulo la taille de la table.
Les tailles des tables de hachage sont souvent des nombres premiers, afin d'viter les problmes
de diviseurs communs, qui creraient un nombre important de collisions. Une alternative est
d'utiliser une puissance de deux, ce qui permet de raliser l'opration modulo par de simples
dcalages, et donc de gagner en rapidit.
Un problme frquent et surprenant est le phnomne de clustering qui dsigne le fait que des
valeurs de hachage se retrouvent cte cte dans la table, formant des clusters ( grappes en
franais). Ceci est trs pnalisant pour les techniques de rsolution des collisions par adressage
ouvert (voir ci-aprs). Les fonctions de hachage ralisant une distribution uniforme des hachages
sont donc les meilleures, mais sont en pratique difficile trouver.
La fonction de hash primaire (la flche en haut) calcule une adresse et gnre une collision.
La premire case libre en ordre croissant, ici en bleu, est trouve et utilise,
consolidant ainsi deux clusters, provoquant une congestion supplmentaire.
Exercice 8.10
Programmez en Python la fonction hashCode(s) de la chane s de longueur n, qui renvoie le
hachage ord(s[0])*32^(n1) + ord(s[1])*32^(n2) + ... + ord(s[n1]) mod N, o ord est la fonction
qui renvoie le code ASCII d'un caractre et N la taille de la table de hachage.
Testez cette fonction avec les noms Chico , Groucho , Gummo , Harpo , Zeppo et
N=5, puis N=11, puis N=12.
8.11.2.
Lorsque deux cls ont la mme valeur de hachage, ces cls ne peuvent tre stockes la mme
position, on doit alors employer une stratgie de rsolution des collisions.
Pour donner une ide de l'importance d'une bonne mthode de rsolution des collisions,
considrons ce rsultat issu du paradoxe des anniversaires 3. Mme si nous sommes dans le cas le
plus favorable o la fonction de hachage a une distribution uniforme, il y a 95% de chances d'avoir
une collision dans une table de taille 1 million avant qu'elle ne contienne 2500 lments.
3
Le paradoxe des anniversaires est l'origine une estimation probabiliste du nombre de personnes que l'on doit runir pour avoir une chance sur
deux que deux personnes de ce groupe aient leur anniversaire le mme jour de l'anne. Il se trouve que ce nombre est 23, ce qui choque
l'intuition. partir d'un groupe de 57 personnes, la probabilit est suprieure 99 %.
Didier Mller
8-22
janvier 2015
L'informatique au lyce
Chapitre 8
De nombreuses stratgies de rsolution des collisions existent mais les plus connues et utilises
sont le chanage et l'adressage ouvert.
Chanage
Cette mthode est la plus simple. Chaque case de la table est en fait une liste chane des cls qui
ont le mme hachage. Une fois la case trouve, la recherche est alors linaire en la taille de la liste
chane. Dans le pire des cas o la fonction de hachage renvoie toujours la mme valeur de hachage
quelle que soit la cl, la table de hachage devient alors une liste chane, et le temps de recherche est
en O(n). L'avantage du chanage est que la suppression d'une cl est facile ainsi que la recherche.
D'autres structures de donnes que les listes chanes peuvent tre utilises. En utilisant un arbre
quilibr, le cot thorique de recherche dans le pire des cas est en O(log(n)). Cependant, la liste
tant suppose tre courte, cette approche est en gnral peu efficace moins d'utiliser la table sa
pleine capacit, ou d'avoir un fort taux de collisions.
Adressage ouvert
L'adressage ouvert consiste, dans le cas d'une collision, stocker les valeurs de hachage dans
d'autres alvoles vides. On appelle ce procd une mthode de sondage : on essaie les alvoles
h(k,0), h(k,1), ..., jusqu' ce qu'on trouve une alvole vide. Il y a trois mthodes de sondage :
Sondage linaire
Soit H : U {0, ... N1} une fonction de hachage auxiliaire.
La fonction de sondage linaire sera : h(k,i) = (H(k) + i) mod N, avec i = 0, 1, ..., N1
Didier Mller
8-23
janvier 2015
Recherche
Lors d'une recherche, si l'alvole obtenue par hachage direct ne permet pas d'obtenir la bonne cl,
une recherche sur les cases obtenues par une mthode de sondage est effectue jusqu' trouver la cl,
ou tomber sur une alvole vide, ce qui indique qu'aucune cl de ce type n'appartient la table.
Suppression
Cette manire de
faire s'appelle en
anglais lazy
deletion ,
suppression
paresseuse.
Dans un adressage ouvert, la suppression d'un lment de la table de hachage est dlicate. Dans le
schma ci-aprs, si l'on supprime John Smith sans prcaution, on ne retrouvera plus Sandra
Dee . La manire la plus simple de s'en sortir est de ne pas vider lalvole o se trouvait John
Smith , mais d'y placer le mot Supprim . On distinguera ainsi les alvoles vides des alvoles o
un nom a t effac : une alvole contenant Supprim sera considre comme occupe lors d'une
suppression, mais vide lors d'une insertion.
Facteur de charge
Une indication critique des performances d'une table de hachage est le facteur de charge qui est la
proportion de cases utilises dans la table. Plus le facteur de charge est proche de 100%, plus le
nombre de sondages effectuer devient important. Lorsque la table est presque pleine, les
algorithmes de sondage peuvent mme chouer : ils peuvent ne plus trouver d'alvole vide, alors qu'il
y en a encore.
Le facteur de charge est en gnral limit 80%, mme en disposant d'une bonne fonction de
hachage. Des facteurs de charge faibles ne sont pas pour autant significatifs de bonne performance,
en particulier si la fonction de hachage est mauvaise et gnre du clustering.
Exercice 8.11
On souhaite stocker des nombres entiers positifs en utilisant une table de hachage. La table
possdant N emplacements (de 0 N1), on utilise la clef suivante: h(x)=x modulo N.
1. Calculez la valeur de la cl pour chacun des lments de la liste suivante (N=12) : 15, 24,
125, 4, 26, 6, 78, 55, 89, 16, 124
2. En supposant que les lments soient inclus dans la table de hachage dans l'ordre de la liste
et que la rsolution des collisions soit faite par chanage, dessinez la table obtenue.
3. En supposant que les lments soient inclus dans la table de hachage dans l'ordre de la liste
et que la rsolution des collisions soit faite par un sondage linaire, dessinez la table
obtenue.
Exercice 8.12
Utilisez la fonction de hachage programme dans l'exercice 8.8 dans une table de taille N=12.
Implmentez une classe table_de_hachage .
crivez une mthode inserer , qui insre un nouvel abonn (nom et numro de
tlphone) dans l'annuaire.
crivez une mthode rechercher , qui affiche le numro de tlphone d'un abonn. Si
plusieurs abonns portent le mme nom, la mthode affichera les diffrents numros.
crivez une mthode supprimer , qui retire un abonn de l'annuaire.
Testez ensuite votre programme avec les donnes suivantes :
Didier Mller
8-24
janvier 2015
L'informatique au lyce
Chico
Groucho
Gummo
Harpo
Zeppo
Zeppo
Chapitre 8
0324661193
0324667543
0324664578
0324668501
0324660031
0324660032
Faites cet exercice deux fois : une fois en rsolvant les collisions par chanage et l'autre fois en
les rsolvant par adressage ouvert (avec un sondage linaire).
Sources
[1] Wikipdia, Structures de donnes ,
<http://fr.wikipedia.org/wiki/Catgorie:Structure_de_donnes>
[2] Wikipdia, Arbre (Structures de donnes) ,
<http://fr.wikipedia.org/wiki/Catgorie:Arbre_(structure_de_donnes)>
[3] Wikipdia, Arbre binaire , <http://fr.wikipedia.org/wiki/Arbre_binaire>
[4] Wikipdia, Arbre binaire de recherche ,
<http://fr.wikipedia.org/wiki/Arbre_binaire_de_recherche>
[5] Wikipdia, Arbre AVL , <http://fr.wikipedia.org/wiki/Arbre_AVL>
[6] Ferraro Tyler, Ruzzle-Solver , <https://github.com/TylerFerraro/Ruzzle-Solver>
[7] Mannino Miro, Ruzzle Solver Algorithm ,
<http://miromannino.com/ruzzle-solver-algorithm/>
[8] Wikipdia, Table de hachage , <http://fr.wikipedia.org/wiki/Table_de_hachage>
[9] Wikipdia, Fonction de hachage , <http://fr.wikipedia.org/wiki/Fonction_de_hachage>
[10] Nicod Jean-Marc, Les tables de hachage ,
<http://ifc.univ-fcomte.fr/~nicod/slidesHashTableL3.pdf>
[11] Wikipdia, Thorie de la complexit des algorithmes ,
<http://fr.wikipedia.org/wiki/Thorie_de_la_complexit_des_algorithmes>
Didier Mller
8-25
janvier 2015