Professional Documents
Culture Documents
lAlgorithmique
Conception et Analyse des algorithmes
Structures de donnes fondamentales
Problmes en thorie des graphes
Philippe Giabbanelli
Mai 2006, version 1
http://www.aqualonne.fr.st
Partie 1
Chapitre 1 : Les questions fondamentales
7
8
8
10
11
12
13
15
16
18
20
21
Philippe Giabbanelli
Philippe Giabbanelli
Avant-Propos
Pourquoi ce livre ?
Il existe de trs nombreux ouvrages consacrs lalgorithmique, dont la plupart sappuient
sur des langages la mode (C++, Java) ou se contentent dtre des bibliothques
dalgorithmes. Notre but ici est damener le lecteur concevoir ce dont il a besoin, tout en
rsolvant certains cas bien connus de la littrature, titre dexemple.
Lutilisation de langages bien particuliers nest pas saine, car on a tendance relguer la
partie algorithmique au second plan, derrire les spcificits du langage. Pour rsoudre un
problme de faon gnrale, il nest pas utile de connatre la taille des entiers en C ou les
mthodes des diffrentes classes Java : seul le raisonnement compte, et lcriture est un dtail.
De plus, il est particulirement difficile de dmontrer la correction dun programme dans des
langages impratifs, cause de la seule affectation. Dans une dmonstration mathmatique,
les objets ne changent pas au cours du temps ! En outre, aprs deux millnaires de pratique
mathmatique, il ny a toujours pas eu de ncessit dintroduire les boucles : le raisonnement
par rcurrence suffit pour faire lessentiel des tches. Il est nest donc pas rigoureux de
vouloir penser immdiatement un algorithme en termes de for, while et daffectations.
Ainsi on utilisera essentiellement un langage pdagogique bases de rgles, permettant une
formalisation solide des diffrentes notions. Notre langage sera centr sur la rcursivit, dont
on montrera que les boucles ne sont que des cas particuliers. Une fois que le lecteur aura
compris la dmarche menant lalgorithme, alors il pourra le traduire dans un langage de son
choix ; quelques exemples seront donns en C, Scheme et Java.
A qui sadresse-t-il ?
Le lecteur na pas besoin davoir de grandes connaissances en informatique. Une pratique
rudimentaire dun langage pourra lui permettre de coder les exemples prsents dans ce livre,
ainsi que les algorithmes simples quil pourra concevoir par la suite.
Il nest pas non plus ncessaire davoir des connaissances pousses en mathmatiques. Un
lve moyen sortant dun baccalaurat scientifique devrait pouvoir saisir lessentiel des
notions. Certaines techniques un peu plus avances, comme la rsolution des quations de
rcurrence ou les sries, sont absolument ncessaires du point de vue algorithmique mais non
requises pour le reste de louvrage.
Lessentiel est davoir une certaine capacit dabstraction et un raisonnement rigoureux.
Lalgorithmique nest pas un code que lon crit rapidement, puis que lon dbugue jusqu
le faire marcher ; cest avant tout une science de la rflexion : on commence par penser,
ensuite on formalise, et si lon veut coder alors on essaye de le faire bien du premier coup.
Introduction lAlgorithmique, mai 2006
Philippe Giabbanelli
Philippe Giabbanelli
Philippe Giabbanelli
Partie 1
Formalisation et criture des algorithmes
Complexit en temps et en espace
Structures de donnes
Philippe Giabbanelli
Chapitre 1
Un algorithme est un ensemble dinstructions traitant une information, ce qui prend la forme en
informatique dun programme utilis pour rsoudre un problme donn. Pour chaque donne du problme,
lalgorithme doit fournir une rponse aprs un nombre fini dtapes : le programme doit finir par sarrter !
Dun point de vue formel, on peut voir un problme abstrait comme tant lensemble < E, A, S > o E est
une entre (i.e. un des cas du problme), A lalgorithme, et S la sortie (i.e. rsultat au cas donn).
Un algorithme est vu comme < C I >, soit un ensemble de rgles (clauses) qui entranent des
traitements (instructions).
Ces rgles forment une partition de lunivers :
- (1) Si on fait lunion des rgles, on doit retrouver tous les cas possibles.
- (2) Lintersection de deux rgles doit tre vide.
Autrement dit, lalgorithme ne doit pas oublier un cas (1), et on ne peut pas tre dans deux cas la fois (2).
Philippe Giabbanelli
On vient dcrire un algorithme comme un ensemble de rgles ( gauche) entranant des actions ( droite).
Si on fait lunion de ces rgles, on retrouve bien tous les cas possible :
(i > n) (i n ^ T[i] > max) (i n ^ T[i] max) = {i, T[i]}
Si on fait lintersection des rgles deux deux, elle est ncessairement vide. Par exemple :
(i n ^ T[i] > max) (i n ^ T[i] max) = {}, llment ne peut pas tre la fois plus petit et plus grand.
(i > n) (i n ^ T[i] max) = {}, on ne peut pas tre la fois dans le tableau et hors du tableau.
Autrement dit, tous les cas sont bien grs, et lalgorithme ne peut pas tre dans deux cas la fois.
Commentaires :
1. Il faut toujours initialiser lalgorithme. Il attend des donnes, on lui donne ce quon a dans notre
cas. On commence parcourir le tableau T partir de la 1re case, et il y a initialement 0
occurrences dtectes.
2. Si nous navons pas fini de parcourir le tableau et que llment courant est , alors on continue de
parcourir le tableau et on augmente le rsultat de 1.
3. Si nous navons pas fini de parcourir le tableau et que llment courant nest pas , on se
contente de continuer parcourir le tableau. Le nombre doccurrences ne bouge pas.
4. Si on a fini de parcourir le tableau, alors on renvoie le rsultat quon a accumul.
En y rflchissant, lalgorithme a-t-il ncessairement besoin dun indice de parcours i ? Quel est son rle ?
Cet indice de parcours doit regarder toutes les cases du tableau, soit de 1 n. Or, on peut tout aussi bien
manipuler lindice n : partons de n, et descendons jusqu arriver 1. Ainsi, on conomise une variable.
De mme, avons-nous besoin dune variable pour accumuler les rsultats ? Si on dtecte une occurrence,
il suffit de dire quil y en aura une de plus quau stade suivant. Ainsi :
1.
2.
3.
4.
NbOcc(T, n, )
n 1 ^ T[n] =
n 1 ^ T[n]
n<1
= NbOcc(T, |T|, )
NbOcc(T, n, ) = 1 + NbOcc(T, n-1, )
NbOcc(T, n, ) = NbOcc(T, n-1, )
NbOcc(T, n, ) = 0
Si on a notre lment, alors il y a une occurrence de plus que dans le reste du tableau.
Si ce nest pas notre lment, alors il y a le mme nombre doccurrence que dans le reste du tableau.
Si on est sorti du tableau, on ne peut pas trouver notre lment : on sarrte, et il y a 0 occurrences.
Introduction lAlgorithmique, mai 2006
Philippe Giabbanelli
II.
Quand une fonction se rappelle elle-mme, on dit quelle est rcursive. Une fonction rcursive est
caractrise par une, ou plusieurs, relation(s) de rcurrence. Par exemple, la factorielle :
n > 1 f(n) = n*f(n-1)
n = 1 f(1) = 1
La fonction rcursive se rappelle jusqu arriver sur un cas darrt (aussi appel cas de base), qui donne
la solution dans la situation la plus simple. Pour la factorielle, le cas de base est par exemple f(1) = 1.
Quand on construit des fonctions rcursives, le raisonnement adopt est en gnral :
- Comment traiter le cas le plus simple ?
- Comment me ramener dun cas plus compliqu au cas simple ?
10
Philippe Giabbanelli
3
empiler 3
empiler 1
4
4
1
3
empiler 4
1
3
depiler
8
3
depiler
empiler 8
depiler
3!
3
n
?
F(n)
2!
3!
2
3
n
?
?
F(n)
1!
2!
3!
1
2
3
n
1
?
?
F(n)
Maintenant quon est arriv au bout de la rcurrence, on peut calculer tout ce quon avait mit en attente. Il
suffit de remonter. On va ainsi dpiler les appels.
1
2
3
1
?
?
2
3
2
?
F(n)
F(n)
3
n
6
F(n)
11
Philippe Giabbanelli
= (T, |T|)
(T, n) = T[n] + (T, n-1)
(T, n) = 0
On remarque la ligne 2 que lappel est envelopp par laddition avec T[n]. On a une rcursivit
enveloppe, et on va donc utiliser la pile. On aimerait sen passer, et il faudrait donc traduire ceci en une
rcursivit terminale. Le but maintenant est de faire rentrer laddition avec T[n] dans la fonction.
Pour cela, on utilise un accumulateur : au dpart on le met 0, et chaque appel on y ajoute T[n]. A la fin,
il suffira de rendre la valeur accumule :
1. (T, n, acc) = (T, |T|, 0)
2. n 1
(T, n, acc) = (T, n-1, acc + T[n])
3. n < 1
(T, n, acc) = acc
On a obtenu une rcursivit terminale. Ceci na donc pas besoin de pile et peut scrire avec une boucle.
fonction F(T, n, acc)
tant que n 1
acc acc + T[n]
n n1
retourner acc
Toute rcursivit terminale peut scrire avec des boucles (i.e. de faon itrative), mais ce
nest pas pour autant que tous les compilateurs le font. Beaucoup ne dtectent pas cette
possibilit : ils noptimisent pas la rcursivit, et vont continuer utiliser de la pile alors que
ce nest pas ncessaire ; cest notamment le cas de Java En revanche, cest souvent
optimis dans les langages de la programmation fonctionnelle (comme Scheme) car ils
sappuient sur la rcursivit. En Java, on dispose de mots cls comme while, et cest au
programmeur de fournir leffort dcriture. Lalgorithmique rsout les problmes (une
dmarche rcursive est conseille) et la programmation soccupe de lcriture de la solution.
Vouloir absolument crire des boucles est absurde : ni toujours possible, ni toujours meilleur !
Introduction lAlgorithmique, mai 2006
12
Philippe Giabbanelli
V.
Etant donn un problme, il y a de nombreuses faons darriver la solution, avec des mthodes plus ou
moins efficaces. Parmi diffrents algorithmes, il faut donc savoir lequel choisir, et dans quelles conditions.
Nous allons voir deux exemples, puis mettre en place une formalisation du cot des algorithmes.
Il faut commencer par se donner une unit de mesure. Celle-ci ne peut pas tre le temps : le temps prit par
un algorithme va varier selon les performances des ordinateurs ! On choisit donc comme unit
lopration lmentaire , propre chaque algorithme. En additionnant toutes les oprations
lmentaires, on aura une bonne estimation du cot. Plus le cot est faible, meilleur est lalgorithme.
Regardons lalgorithme suivant, qui est purement pdagogique :
1. fonction F(T, n)
2.
res 0
3.
tant que n 1
4.
res res*2 + 5
5.
res res 4
6.
retourner res
Les oprations lmentaires sont ici les oprations arithmtiques des lignes 4 et 5. A chaque tour de la
boucle, on effectue 1 multiplication, 1 addition et 1 soustraction. La boucle va tourner n fois.
Lalgorithme a donc besoin de n*(1+1+1) = 3*n oprations. On voit que la performance de lalgorithme
dpend bien de ses entres. Si le tableau contient 1 million dlments ou sil en contient 10, lalgorithme
ne sera pas aussi rapide.
Reprenons prsent lalgorithme qui donnait le maximum dun tableau. Comme il sagit dune rcursivit
terminale, alors on peut le traduire avec une boucle, ce qui est (au dpart) plus simple pour calculer.
Max(T, n, i, max) = Max(T, |T|, 2, T[1]))
1. fonction Max(T, n)
i n ^ T[i] > max Max(T, n, i, max) = Max(T, n, i+1, T[i])
2.
max T[1]
i n ^ T[i] max Max(T, n, i, max) = Max(T, n, i+1, max)
3.
pour i de 2 n
i>n
Max(T, n, i, max) = max
4.
si T[i] > max
5.
max T[i]
6.
retourner max
On veut compter le nombre exacts doprations de cet algorithme : comptons les affectations et
comparaisons. Il y a 1 comparaison chaque tour de boucle, et il y a n 2 + 1 soit n 1 tours de boucle.
Il y a un problme : max T[i] nest excut que si T[i] > max ; or, ceci nest pas une opration
facilement quantifiable. Une boucle comme i de 1 n va tourner exactement n fois, mais on ne peut
pas savoir en soit le nombre de fois quon aura T[i] > max !
La condition dpend des donnes. Il faut donc distinguer plusieurs cas :
- si le maximum est au dbut du tableau, alors la condition T[i] > max ne sera jamais vrifie. Ainsi,
on a juste une affectation (ligne 2) et n 1 comparaisons soit un total de n oprations.
- si le tableau est tri dans lordre croissant, chaque lment est plus grand que le prcdent et donc
la condition sera toujours vrifie. On a alors (n 1)*2 + 1 = 2n 1 oprations.
- si le tableau est alatoire, on peut dire quen moyenne la condition a une chance sur 2 dtre
vrifie. On a alors (n 1)*(1 + ) + 1 = (3*n 1)/2.
Ainsi, le cot de lalgorithme dpend de la taille (ici n) des donnes, mais peu aussi tre dpendant de leur
nature : comment sont-elles ? Lexamen de la nature des donnes amne en gnral distinguer trois cas :
le meilleur des cas, le pire des cas et le cas moyen ; dfinissons les de faon formelle.
Introduction lAlgorithmique, mai 2006
13
Philippe Giabbanelli
Soit I lensemble des donnes dinstances dun problme abstrait (i.e. tous les cas possibles).
Notons In les donnes de taille n, car on a vu que le cot de lalgorithme dpend de la taille des donnes.
On dsigne par c(i) le cot de lalgorithme rsolvant le problme pour une donne iIn.
Le cot dans le pire des cas est celui tel que lalgorithme est le moins performant ; cest donc le plus lev
de tous les cots possibles, sur des donnes de taille n. On le note par W, pour Worst Case (pire des cas).
algorithme
> p 0 i1 $c 0 i1
i2I
n
En gnral, on suppose que tous les cas ont la mme probabilit dapparatre. Autrement dit, on considre
que la distribution des probabilits est uniforme. Il suffit donc de sommet le cot de tous les cas, et de
diviser par le nombre de cas, cest--dire par la taille de lensemble In des cas de taille n.
algorithme
>
= 1$
c 0 i1
:I : i 2 I
n
Il existe un autre cot, que lon nomme le cot amorti, utilis en particulier lorsquon fait des suites
doprations sur des structures de donnes (comme une pile). Pour avoir le cot total des oprations, on
fait la somme du cot pour chaque opration. Le plus simple est de majorer le cot de chaque opration,
en utilisant le pire des cas ; on obtient une estimation qui nous dit, au pire, le cot de la suite doprations.
Or, cette estimation qui consiste majorer par le pire des cas est bien trop excessive. En effet, on observe
que le pire des cas se rpte rarement. Dans de nombreuses structures, ce pire des cas rsulte dun
dsquilibre exceptionnel, mais le traitement de ce cas va rtablir lquilibre. On a donc un phnomne de
compensations entre oprations conscutives, do un amortissement des cots.
Pour illustrer les trois principaux cots prsents, reprenons lexemple de lalgorithme du maximum.
On veut dterminer sa complexit dans le pire des cas ; la question qui se pose est alors : quelle
organisation du tableau entrane le pire des cas ? Quest-ce que signifie, en pratique, ce pire des cas ?
Dans cet algorithme, le pire des cas est celui qui valide le plus souvent llment courant est suprieur
au maximum . Or, il y a une organisation du tableau telle que, si on le parcours de 2 n, llment
courant soit toujours plus grand que le maximum tabli : cest le tableau tri en ordre croissant.
Une fois le pire des cas connu, on peut calculer le cot ; nous avons vu quil est de 2n 1 oprations.
Pour le meilleur des cas, on applique le mme raisonnement : cest lorganisation du tableau telle que la
condition llment courant est suprieur au maximum soit le moins souvent valide. Or, dans un
tableau tri en ordre dcroissant, le maximum est au dbut, et sera suprieur tous les lments parcourus.
Introduction lAlgorithmique, mai 2006
14
Philippe Giabbanelli
15
Philippe Giabbanelli
On parle dune complexit asymptotique : quand n devient trs grand, on peut dire que le
comportement de 3n + 4n + 2 est quivalent celui du terme en n. Autrement dit, pour un n trs grand,
le terme en n est le terme dominant. On peut alors dire que 3n + 4n + 2 est en (n). On a lhabitude de
noter 3n + 4n + 2 = (n), mais cest une sorte dabus. Il sagit davantage de lappartenance une classe
de fonctions, que lon devrait donc noter 3n + 4n + 2 (n) pour tre cohrent sur les objets manipuls.
Introduction lAlgorithmique, mai 2006
16
Philippe Giabbanelli
w n /C N 3 $2n
n
17
Philippe Giabbanelli
18
Philippe Giabbanelli
car log2(ea) = a
On a obtenu n.log2(n) n h. Le plus long chemin que lalgorithme puisse prendre pour trier n lments
mesure n.log2(n) n. Ainsi, tout tableau peut ncessiter jusqu n.log2(n) n pour tre tri en comparant.
On vient donc de prouver que la borne infrieure dun tri par comparaison est n.log2(n) n.
Introduction lAlgorithmique, mai 2006
19
Philippe Giabbanelli
IX. Entranement
Les exercices suivants sont nots sous la forme a.b), o a dsigne la partie du chapitre concerne et b
un numro propre lexercice. Par exemple, 1.3) dsigne le 3me exercice sur la partie 1 Quest-ce
quun algorithme ? . On trouvera la correction la fin du livre, page XXX.
Quand on veut un algorithme, on fait un exemple, puis on abstrait et en dernier on lcrit formellement.
1.1)
On a dit quun algorithme doit fournir une rponse aprs un nombre fini dtape. Expliquez
pourquoi les deux algorithmes sur le nombre doccurrence sarrteront ncessairement.
1.2)
Lalgorithme dEuclide qui calcule le plus grand diviseur commun de deux nombres m et n,
entiers positifs, est donn de la faon suivante dans le livre de Donald Knuth :
E1. Divisons m par n et appelons n le reste.
E2. Si le reste est nul, alors lalgorithme termine et la rponse est n.
E3. m prend la valeur n, n prend la valeur r, et on retourne ltape E1.
Traduisez en algorithme abstrait. Que se passerait-il si on enlevait la contrainte m et n sont deux
entiers positifs ?
1.3)
On dispose dun tableau T dentiers strictement positifs, et on veut savoir quel est le plus grand
cart entre deux lments du tableau.
1.4)
On dispose dun tableau T, contenant n nombres rels. Votre algorithme donnera un rel r qui
nappartient pas au tableau. Par exemple, T = [-2, 12.5, -8.8, -5, 1.5], et une des rponses valides
est 4.5.
2.1)
4.1)
On dispose dun tableau dont on ne connat pas la taille. On sait quil se termine ncessairement
par le caractre \0. Calculez sa taille des deux faons : rcursive enveloppe et rcursive terminale.
Ecrivez un quivalent de la version rcursive terminale en style impratif (tant que, pour).
6.1)
Donnez la complexit (cot en temps et en espace) des algorithmes 1.3), 1.4) et 4.1).
6.2)
On dispose dune matrice M, qui est un tableau deux dimensions, de largeur l et de hauteur h.
Votre premier algorithme donnera la somme des lments de la matrice.
Votre second algorithme donnera la somme de la diagonale de la matrice (appele la Trace de M).
Vous donnerez la complexit des deux algorithmes. Comme la manipulation des doubles boucles
nest pas trs pratique dans notre formalisation, vous pouvez commencer par un style impratif.
7.1)
Montrer que g(n) = [n*log(n) + (log n)4 + n]/(n+1) appartient la classe (log n).
8.1)
On dispose dun tableau T dentiers. Le tableau comprend tous les entiers de 0 n sauf un. Votre
algorithme donnera llment qui manque.
Par exemple, le tableau est 0 3 4 5 6 9 7 8 1, et llment 2 est manquant.
Expliquez pourquoi votre algorithme est optimal.
20
Philippe Giabbanelli
Chapitre 2
Le rle majeur des programmes consiste prendre des donnes et appliquer des algorithmes dessus. On
va effectuer trs souvent des oprations basiques dans ces donnes : chercher, ajouter, supprimer
Par exemple, dans un jeu en rseau, on dispose dun ensemble de joueurs. On voudra souvent chercher un
joueur, peut-tre plus rarement en ajouter ou en supprimer. Ainsi, il faut organiser les donnes de faon
ce que les oprations qui nous intressent soient effectues le plus rapidement possible. Dans ce cas, on
pourrait utiliser une structure, i.e. une organisation des donnes, nomme dictionnaire.
Supposons maintenant que nous avons un systme dexploitation, qui gre des tches avec des priorits.
On ajoute des tches, et on supprime celle avec la priorit maximale. La structure conseille est le tas.
Formellement, nous avons un univers U dans lequel vivent les donnes que nous manipulons. Ces
donnes elles-mmes sont un sous-ensemble S de cet univers. On souhaite appliquer un ensemble Op
doprations sur les donnes. Une structure de donne est donc formalise par <U, S, Op>.
Soit d une donne. Les oprations les plus courantes sont les suivantes :
- Member(d, S) : dS ? Ceci se lit Est-ce que d appartient S ? . Cest la recherche.
- Insert(d, S) : S S d. On ajoute la donne d au sous-ensemble S des donnes.
- Delete(d, S) : S S d. On supprime la donne d du sous-ensemble S des donnes.
- DeleteMax(d, S) : S S max(S). On supprime le maximum (exemple Systme dExploitation).
- Max(S), Min(S) : le maximum et le minimum.
- Suc(d, S), Pred(d, S) : le successeur (lment immdiatement plus grand que la donne) et le
prdcesseur (lment immdiatement infrieure la donne). Par exemple, dans 10 5 3 24 7 4, le
successeur de 7 est 10 et son prdcesseur est 5.
Le choix de la structure de donnes dpend des oprations demandes. Les structures fondamentales sont :
Nom de la structure
Points forts
Pile
Insertion, suppression du dernier lment entr.
File
Insertion, suppression du premier lment entr.
Dictionnaire
Insertion, suppression, recherche
File de priorit
Insertion, suppression de llment maximum
Tableaux ou listes chanes
Si nous ne sommes dans aucun des cas prcdents et quon veut
optimiser une seule opration.
Arbres
Si nous ne sommes dans aucun des cas prcdents et quon veut
optimiser plusieurs oprations.
On trouve rarement une structure de donnes qui soit parfaite pour tout ce quon veut faire. Cest donc
une affaire de compromis. Il faut par exemple choisir entre un programme qui tourne en moyenne le plus
rapidement possible, ou donner la priorit certaines oprations.
Enfin, il y a des questions intressantes se poser sur lunivers U et son sous-ensemble S de donnes :
- La taille. Si U ou S sont assez petits, on peut faire de nombreuses oprations en temps constant.
- La nature des donnes. Lunivers est-il ordonn ? S est-il un ensemble (i.e. un lment napparat
au maximum quune fois), ou un multi-ensemble (un lment peut apparatre plusieurs fois) ?
Exemple : Si un lment ne peut tre l quune fois, vrifier quil ny soit pas dj avant dajouter !
Introduction lAlgorithmique, mai 2006
21
Philippe Giabbanelli
II.
Les structures de donnes se construisent avec les tableaux et les pointeurs. Nous avons manipul des
tableaux, ou en avons au moins une ide intuitive. Nous allons donc prsenter les pointeurs, travers
lexemple des listes. Dans ces deux pages, nous tenons montrer au lecteur ce qui se passe physiquement ;
on dlaisse un instant lalgorithmique pour une pense plus Objet, avec des exemples de code en Java.
Une liste est un chanage : les donnes sont des maillons, et se connectent les unes aux autres.
donne
donne
donne
rien
Si partir dune donne on ne peut accder qu la donne suivante, alors cest un chanage simple.
Si on peut accder la suivante et la prcdente, alors on parle de chanage double, comme ci-dessous.
rien
donne
donne
donne
donne
donne
donne
rien
Les connections entre les donnes sont appeles des pointeurs : on pointe sur un autre objet.
Pour avoir une liste, il suffit davoir un lien sur le premier maillon, que lon nommera la tte . A partir
du premier, on pourra accder et manipuler tout le reste. Une liste est donc un pointeur sur le 1er maillon.
public class LinkedList{
public Maillon tete ;
public LinkedList(){
tete = null ;
}
La notion de rien est trs utile. Au dpart, la liste est vide, elle contient donc rien. De mme, lorsquon
cre un maillon, au dpart il pointe sur rien. Les questions se formulent alors de faon trs logique :
La liste est-elle vide ?
Vide?(L) = L =
Introduction lAlgorithmique, mai 2006
22
Philippe Giabbanelli
tete
liste
maillon
tete
maillon
En gnral, on ajoute des donnes plutt que des maillons. Il faudra mettre la donne dans un maillon.
Ajouter(L, d)
public void ajouter(LinkedList L, Object o){
Soit M le maillon contenant d
Maillon M = new Maillon(o) ;
Le suivant de M est la tte de L
M . suivant = L..tete ;
La nouvelle tte de L est M
L . tete = M ;
}
Il est trs important de commencer par lier le maillon lancienne tte de liste. En effet, si on liait
directement la liste au maillon, alors on aurait perdu tout lien sur la tte, et donc on perdrait la liste !
Supprimer(L, d)
Si cest la tte,
alors je lavance.
Sinon, je me positionne sur la tte
et javance tant que je ne suis pas au bout.
Si je trouve llment supprimer,
alors je le saute.
23
Philippe Giabbanelli
a.b.c.
rien
algorithmique
Il ny a rien de plus facile que dajouter un lment une liste L. Regardons lquivalence :
b
rien
b.c.
a.b.c.
rien
La notion de maillons est bonne pour une reprsentation objet, mais ne nous importe pas en algorithmique.
Il ne nous reste que des lments, un . de liaison et quand cest vide. Rajouter un lment la liste,
cest simplement le lier avec cette liste :
Ajouter(, L) = .L
A partir de qui reprsente la liste vide et de cette fonction nous permettant dajouter, on peut crer
nimporte quelle liste. La liste a. est obtenue avec ajouter(a, ). Ainsi, pour obtenir la liste a.b.c., on a :
Soit L Ajouter(a, Ajouter(b, Ajouter(c, )))
On a une base mathmatique solide : lalgorithmique procde par composition de fonctions. Notre faon
de faire nest pas purement thorique, elle peut tout fait se coder, pratiquement telle quelle. Pour cela,
au lieu dutiliser les langages du type C, C++, et Java, trs connus du grand public, on utilise des langages
de programmation fonctionnelle comme Lisp, Scheme et Haskell. Lannexe 1 la fin du livre, page
XXX, dfini une srie de macros Scheme permettant dcrire nos algorithmes dune faon trs proche.
Le Scheme est prfix, la diffrence des langages habituels : on crit (<op> a b) au lieu de (a <op> b).
Ainsi, si on veut crire (5 + 4) en Scheme, on fera (+ 5 4). Pour montrer au lecteur que notre formalisme
se code dans la pratique, on donnera lquivalent entre nos algorithmes et le code Scheme1. Commenons :
Ajouter(, L) = .L
(algorithme ajouter(a L) = (lier a L))
Scheme1
Ce quon crit nest pas exactement du langage Scheme : le but de ce livre nest pas dapprendre le langage Scheme ou un
quelconque autre langage. Simplement, ce quon crit se transforme en code du langage Scheme par une srie de macros qui
sont dfinies dans lannexe 1 la fin du livre, page XXX. Des macros sont des substitutions textuelles : notre code, vu
en tant que texte, sera transform sans que le lecteur ait quelque manipulation que ce soit effectuer ; cest transparent.
Par exemple, le lecteur marque (algorithm ajouter(a L) = (lier a L)), et cest transform (expans) en langage Scheme :
(algorithm ajouter(a L) = (lier a L))
(define (ajouter a L)
(cons a L)
Bien entendu, pour que tout ceci fonctionne, le lecteur devra prendre la peine de recopier les macros de notre annexe au
dbut de son programme. Il peut les mettre dans un fichier part, et demander les charger en dbut de programme avec
(require nom_du_fichier.ss ) si le fichier est dans le mme rpertoire. On recommande PLT Scheme pour commencer.
24
Philippe Giabbanelli
Si on a du mal voir ce qui se passe, il faut toujours drouler la rcursivit, cest--dire suivre le
fonctionnement de lalgorithme sur un petit exemple. Demandons construire la liste des entiers de 4 1 :
Construire(4) = 4.Construire(3) = 4.3.Construire(2) = 4.3.2.Construire(1) = 4.3.2.1.Construire(0) = 4.3.2.1.
Passons maintenant la suppression dun lment dans un ensemble reprsent sous forme de liste ;
autrement dit, on veut supprimer llment d de la liste L, o un lment est prsent au plus une fois. On
aborde lalgorithmique des listes. Dans la plupart des cas, on peut adopter la dmarche rcursive suivante :
- Cas de base : jai une liste vide.
- Si ma liste nest pas vide, comment traiter llment courant et passer la suite ?
Cest une dmarche assez simple en cela quelle est naturelle. Regardons ce qui se passe pour Supprimer :
- Jai une liste vide. Je nai donc rien supprimer dedans, do Supprimer() = .
- Ma liste nest pas vide. Regardons llment courant.
Si cest llment que je veux supprimer, alors il suffit de loublier et de renvoyer la suite.
c = d Supprimer(c.L, d) = L
(o c est le 1er lment de la liste et L la suite)
Si ce nest pas llment que je veux supprimer, je continue de traiter la suite.
c d Supprimer(c.L, d) = c.Supprimer(L, d)
Faisons le bilan de notre algorithme qui supprime llment d de lensemble reprsent par la liste L :
1. Supprimer(L, d)
2.
L = Supprimer(L, d) =
3.
c = d Supprimer(c.L, d) = L
4.
c d Supprimer(c.L, d) = c.Supprimer(L, d)
A nouveau, faisons un exemple qui nous permette de drouler cette rcurrence pour bien voir.
Supprimons llment 7 de la liste 3.4.7.9.3.6.. Notons que la liste reprsente bien un ensemble car
chaque lment napparat au plus quune fois ; les donnes de lexemple sont conformes aux contraintes.
Supprimer(3.4.7.9.3.6., 7) = 3.Supprimer(4.7.9.3.6., 7) = 3.4.Supprimer(7.9.3.6., 7) = 3.4.9.3.6.
Si on a un multi-ensemble au lieu dun ensemble, lalgorithme change. En effet, jusqu l on sest dit
on a un ensemble, llment ne peut-tre prsent au plus quune fois, donc quand je le trouve jai fini .
Or, dans le cas du multi-ensemble, llment peut-tre prsent plusieurs fois : pour dclarer quon a fini le
traitement, il faudra avoir trait toutes les donnes ; autrement dit, la ligne 3 doit poursuivre le traitement :
1. Supprimer(L, d)
2.
L = Supprimer(L, d) =
3.
c = d Supprimer(c.L, d) = Supprimer(L, d)
4.
c d Supprimer(c.L, d) = c.Supprimer(L, d)
Suppression de d en multi-ensemble
je continue le traitement, pas fini !
Il faut toujours penser ce que la structure doit reprsenter, en termes mathmatiques. La question la plus
fondamentale est de savoir si on a un ensemble ou un multi-ensemble. Aprs, on pourra peut-tre affiner
sur les valeurs que peuvent prendre les lments, lorganisation, les rapports entre eux On a vu par
exemple en page 15 quil y avait dj des optimisations possibles pour rechercher un lment dans un
tableau si deux donnes successives ont une diffrence de +- 1.
Cependant, on tire en gnral davantage parti des rapports entre les donnes en sappuyant sur un tableau
plutt que sur une liste. En effet, dans un tableau on a un accs en temps constant : si on veut aller une
case, on sadresse la zone concerne, et on y est ; tandis que dans une liste, on doit passer par tous les
lments prcdents, ce qui nous donne un accs en temps linaire.
Introduction lAlgorithmique, mai 2006
25
Philippe Giabbanelli