Algo

download Algo

of 25

description

algorithmique

Transcript of Algo

  • Introduction 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

  • Table des Matires

    Avant-propos 4 Partie 1 7 Chapitre 1 : Les questions fondamentales 8 Quest-ce quun algorithme ?. . . . . . . . . . . . . . . . . . . . . . . 8 Quest-ce que la rcursivit ? . . . . . . . . . . . . . . . . . . . . . . . 10 Comment grer la rcursivit ? . . . . . . . . . . . . . . . . . . . . . . 11 Quand est-ce quune fonction rcursive a besoin de pile ? . . . . . . . . 12 Comment dcrire la performance de mon algorithme ? . . . . . . . . . . 13 Quels sont les diffrents critres de performance ? . . . . . . . . . . . . 15 Comment faire abstraction de la machine ? . . . . . . . . . . . . . . . . 16 Comment savoir si mon algorithme est le meilleur ? . . . . . . . . . . . 18 Entranement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 Chapitre 2 : Le problme des structures de donnes 21 Prsentation formelle du problme. . . . . . . . . . . . . . . . . . . . . 21 Un pointeur du point de vue physique. . . . . . . . . . . . . . . . . . . 22 Un pointeur du point de vue algorithmique. . . . . . . . . . . . . . . . . 24

    Introduction lAlgorithmique, mai 2006 Philippe Giabbanelli 2

  • Introduction lAlgorithmique, mai 2006 Philippe Giabbanelli 3

  • 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 4

  • Comment le livre est-il organis ?

    On adopte une division en trois parties. La premire donnera les bases de lalgorithmique, en rpondant aux questions les plus fondamentales : quest-ce quun algorithme, comment savoir si mon programme est rapide ? On montrera ensuite le problme des structures de donnes, avec les exemples de pile, file et liste. On verra quelques algorithmes sur les listes, dans le but de se familiariser avec notre criture. Enfin, on terminera cette partie en prsentant des structures plus avances comme les tables de hachage, les arbres, et les tas. Dans la seconde partie, on commencera ltude des techniques algorithmiques, avec lapproche diviser pour rgner . On sen servira en particulier pour le problme du tri, avec les algorithmes de tri par comparaison. Le chapitre qui suit sera consacr lutilisation du tri, mettant en avant la dichotomie. Dans la troisime et dernire partie, on propose une introduction aux graphes par la construction darchitectures : le problme -d. Une fois prsents lintrt des graphes et quelques problmes classiques, on met en place les structures de donnes utilises pour la reprsentation des graphes. On ne traitera pas dans ce livre des problmes de flot, des automates ou de la gomtrie. Quelles sont les inspirations de ce livre ?

    Certains des exemples de la premire partie sont tirs des exercices de Jean-Paul Roy1 ou du cours de Jean-Clarence Nosmas2. La seconde partie reprend quelques thmes des cours de Francis Avnaim3 et Fabrice Huet4. Enfin, le problme -d de la troisime partie provient des cours de Jean-Claude Bermond5. Tous enseignent ou ont enseign luniversit de Nice Sophia-Antipolis. On utilise galement Elments dAlgorithmique de D. Beauquier, J. Berstel et Ph. Chrtienne, ainsi que Graphes et Algorithmes de Michel Gondran et Michel Minoux.

    1 Professeur agrg, responsable des cours de programmation fonctionnelle en 2nd anne. 2 Professeur de mathmatiques discrtes. 3 Professeur agrg, co-responsable du cours de structures de donnes en 1re anne. 4 Matre de confrence, co-responsable du cours de structures de donnes en 1re anne. 5 Directeur de recherche, auteur de plus de 120 articles sur la thorie des graphes, le calcul parallle et distribu, les tlcommunications.

    Introduction lAlgorithmique, mai 2006 Philippe Giabbanelli 5

  • Introduction lAlgorithmique, mai 2006 Philippe Giabbanelli 6

  • Partie 1

    Formalisation et criture des algorithmes Complexit en temps et en espace

    Structures de donnes

    Introduction lAlgorithmique, mai 2006 Philippe Giabbanelli 7

  • Chapitre 1 Les questions fondamentales

    I. Quest-ce quun algorithme ? 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). Exemple 1 : Dterminer le maximum dun tableau Lentre est un tableau T dentiers. Les cases de T sont numrotes de 1 n. On dit que n est sa taille. La sortie est le plus grand entier du tableau ; autrement dit, lentier suprieur tout lment du tableau T. On peut noter la sortie n T[i] i[|1, n|] , o T[i] dsigne llment situ la case i du tableau T. Avant dcrire de faon abstraite un algorithme, il faut avoir les ides claires sur le problme. On doit commencer par traiter un exemple : tant donn une instance du problme, comment trouver la sortie ? On prend un tableau 2 3 1 4 5 3 2 1 4 3 5 1, et on veut son maximum. On commence par considrer son premier lment comme tant un maximum potentiel, et on regarde sil y a plus grand que lui. On voit que 3 est plus grand, le nouveau maximum est donc 3. On continue : 1 nest pas plus grand, par contre 4 est plus grand. On a ensuite que 5 est plus grand que 4, et rien nest plus grand que 5 : cest donc le maximum. Une fois quon a compris ce quon vient de faire, on peut labstraire :

    - je considre le premier lment du tableau comme maximum - si je trouve un lment plus grand que lui, cest mon nouveau maximum - une fois que jai tout parcouru, je renvoie mon dernier maximum : rien trouv de plus grand

    Ecrivons alors lalgorithme. Cest une fonction, on lui donne donc des objets sur lesquels travailler :

    Max(T, n, i, max) = Max(T, |T|, 2, T[1]))

    Lalgorithme est initialis : il a toutes les donnes dentre dont il a besoin. On peut maintenant dtailler les rgles qui vont nous conduire la solution : comment trouver le maximum de ce tableau ?

    Lalgorithme attend un tableau T, une taille n. Il va parcourir le tableau partir dun indice i, et au dpart il a besoin dun maximum

    Je lui donne mon tableau, et sataille. Comme je lui donne le 1erlment comme maximum, je parcours partir du 2nd.

    i n ^ T[i] > max Max(T, n, i, max) = Max(T, n, i+1, T[i]) Si je nai pas parcouru tout le tableau et que llment actuel est suprieur au max, cest le nouveau max. i n ^ T[i] max Max(T, n, i, max) = Max(T, n, i+1, max) Si mon lment actuel nest pas suprieur au max, alors je continue simplement de parcourir. i > n Max(T, n, i, max) = max Si jai parcouru tout le tableau, je renvoie le max.

    Introduction lAlgorithmique, mai 2006 Philippe Giabbanelli 8

  • 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. Exemple 2 : Dterminer le nombre doccurrences dun lment dans un tableau En entre, on a un tableau T dentiers non ncessairement distincts (i.e. un entier peut y tre plusieurs fois). En sortie, on veut le nombre de fois que lentier est prsent ; cest son nombre doccurrences . Exemple dinstance du problme : 2 3 1 4 5 3 2 1 4 3 5 1, on compte llment 1, il est prsent 3 fois. La solution est simple : on parcours le tableau, et chaque fois que lon voit llment qui nous intresse, alors son nombre doccurrences augmente de 1. Cependant, mme avec une solution nonce de faon aussi lmentaire, il y a de nombreuses faons dcrire lalgorithme correspondant. La premire, et peut-tre la plus habituelle pour les personnes habitues crire avec un langage impratif ou objet (C, Java, C++), est de considrer quil nous faut le tableau T, sa taille n, lindice de parcours i, llment , et une variable de rsultat res dans laquelle on va accumuler le nombre doccurrences.

    1. NbOcc(T, n, i, , res) = NbOcc(T, |T|, 1, , 0) 2. i n ^ T[i] = NbOcc(T, n, i, , res) = NbOcc(T, n, i+1, , res + 1) 3. i n ^ T[i] NbOcc(T, n, i, , res) = NbOcc(T, n, i+1, , res) 4. i > n NbOcc(T, n, i, , res) = res

    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. NbOcc(T, n, ) = NbOcc(T, |T|, ) 2. n 1 ^ T[n] = NbOcc(T, n, ) = 1 + NbOcc(T, n-1, ) 3. n 1 ^ T[n] NbOcc(T, n, ) = NbOcc(T, n-1, ) 4. 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 9

  • II. Quest-ce que la rcursivit ? 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 ?

    Exemple 1 : Dfinir la multiplication Imaginons quon ne sache pas faire la multiplication y*x. Dans le cas le plus simple, on sait faire 1*x = x. Comment nous ramener dun cas plus compliqu ce cas l ? Il faut faire chuter y jusqu arriver 1. Autrement dit, il nous faut une relation de rcurrence sur y. Or, on a y*x = x + (y-1)*x. On peut alors dfinir la multiplication uniquement comme une succession dadditions :

    y > 1 multiplic tion(x,y) = x + multiplication(x, y- ) y = 1 multiplication( ,1) = x

    Pour bien voir ce qui se passe, on droule la rcu ence , cest--dire quon reg rde tamultiplication(3, 5) = 5 + multiplication(2, 5) = 5 + (5 + multiplication(1,5)) = 5 + (5 + (5 Exemple 2 : Nombre de rgions dlimites par n frontires rectilignes On a n droites en position gnrale , cest--dire quelles ne sont pas parallles et ntoutes en un mme point. On veut calculer le nombre de rgions dlimites par ces n droi Par exemple, une droite dlimite 2 rgions ; 2 droites dlimitent 4 rgions, et 3 droites d Le cas darrt de la rcursivit est donc celui o on a une seule droite. On pose alors regioOn veut maintenant une relation de rcurrence sur le nombre de droites. Une relation enmme chose quune relation entre n+1 et n ; selon les cas, certains parlent plus que dautvident de se dire si on a dj n droites, que se passe-t-il lorsque jen ajoute une ? . La nouvelle droite va couper toutes les prcdentes : il y a donc n points dintersections.nos exemples que sil y a n points dintersections, alors cela veut dire quon a fait n+1 rgquand la 2nd droite vient couper la premire en un point, il y a 1 + 1 = 2 nouvelles rgiodroite vient couper les deux prcdentes en 2 points, il y a 2 + 1 = 3 nouvelles rgions. On a donc region(n+1) = region(n) + n + 1, ce qui est quivalent region(n) = region(n-1 On obtient ainsi lalgorithme donnant le nombre de rgions que dlimitent n frontires re

    n > 1 region(n) = region(n-1) + n n = 1 region(1) = 2

    2

    1

    34 7

    62

    1

    1 2

    Notons que dans des algorithmes aussi simples, on peut enlever la rcursivit et exprimrsultat en fonction de n. On verra au chapitre < ?> page XXX la rsolution des quations

    Introduction lAlgorithmique, mai 2006 Philippe Giabb10p par pe : rra

    xa

    )

    ete

    li

    ntre

    Oi

    n

    )

    ct

    e

    ane

    ) = 15.

    se cous.

    mitent

    (1) = 2re n et ns. Ici, i

    n remaon. Pars. Quan

    + n.

    ilignes

    3

    5

    r directde rcu

    elli ta1pent pas

    7 rgions.

    . -1 est la

    l est plus

    rque sur exemple, d la 3me

    :

    4

    ement le rrence.

  • III. Comment grer la rcursivit ? Quand la fonction est (vritablement) rcursive, lordinateur est bien oblig de stocker les calculs quelque part. Il utilise pour cela une pile ; il sagit dun objet dfini par deux oprations :

    - empiler (ajouter un lment au sommet) - dpiler (prendre llment qui est au sommet)

    Faisons un exemple ci-dessous :

    3 31

    314

    31

    3 38

    3

    814

    empiler 3 empiler 1 empiler 4 depiler depiler empiler 8 depiler Exemple 1 : Gestion de la pile pour la factorielle On a lalgorithme suivant pour le calcul de la factorielle :

    F(n) = F(m) (notons quil y toujoursn > 1 F(n) = n*F(n-1) n = 1 F(1) = 1

    Calculons F(3). On ne pourra calculer F(3) quaprs avoir calcul F(2) : oncalcul en attente, pour ne pas loublier ; ainsi, on va empiler lappel F(3). Oet on besoin de F(1). On empile lappel F(2). On arrive F(1). On sait le

    1 ! 2 ! 3 !

    2 ! 3 ! 3 ?

    2 ? 3 ?

    n F(n) n F(n)3 !

    Maintenant quon est arriv au bout de la rcurrence, on peut calculer tout suffit de remonter. On va ainsi dpiler les appels.

    1 1 2 ? 3 ?

    n F(n)

    2 2 3 ?

    n F(n)

    Exemple 2 : Une factorielle qui na pas besoin de pile On dispose maintenant dun algorithme pour la factorielle utilisant une var

    F(n,acc) = F(m, 1) n > 1 F(n, acc) = F(n-1, n*acc) n = 1 F(1, acc) = acc

    Il ny a pas besoin ici de mettre des calculs en attente : on peut les enchaneRegardons pour F(3) : F(3, 1) = F(2, 3*1) = F(1, 2*3) = 6 Dans les deux exemples, notre algorithme est crit de faon rcursive : la fmme. Pourtant, il y a un cas o on a besoin dutiliser la pile, et un cas o quil y a ainsi deux types de rcursivit

    Introduction lAlgorithmique, mai 2006 P11 une initialisation)

    doit donc mettre une partie du n veut maintenant calculer F(2)

    calculer : F(1) = 1. a 1 1 2 ? 3 ? n F(n)

    ce quon avait mit en attente. Il

    3 6 n F(n)

    iable acc, qui accumule :

    r. Ainsi, on se passe de la pile.

    onction F se rappelle bien elle-on sen passe. Nous allons voir

    hilippe Giabbanelli

  • IV. Q Si la fonctioa alors pas b- F(n, acc) - F(1, acc) Lorsquil yAutrement enveloppe Quand est ecomme whipossibles : o Exemple : FaOn dispose Le principe

    - lorsqvale

    - lorsq

    1. (T2. n 3. n <

    On remarquenveloppercursivit t Pour cela, oil suffira de

    1. (T2. n 3. n <

    On a obtenufonction F(T tant

    retou

    Introductionuand est-ce quune fonction rcursive a besoin de pile ?

    n est du type F(x1, , xn) = G(y1, , yn), on dit que cest une rcursivit terminale . Il ny esoin de pile pour continuer le calcul. Dans le calcul de la factorielle sans pile, on avait ainsi : = F(n-1, n*acc). On peut identifier y1 = x1 1, y2 = n*x2 et G = F. = acc. On peut identifier y1 = 0, y2 = x2 et G = y2.

    a quelque chose autour de la fonction G, on dit que cest une rcursivit enveloppe . dit, il y a des oprations qui enveloppent la fonction : F(n) = n*F(n-1), o la fonction est

    par une multiplication avec n. Dans ce cas, on est oblig dutiliser une pile.

    n prsence dune rcursivit terminale, la fonction est strictement quivalente des boucles le ou for. En revanche, si cest une rcursivit enveloppe, alors il ny a pas dautres critures n pourrait dire que cest la vraie rcursivit.

    ire la somme des lments dun tableau dun tableau T dentiers, et on veut faire la somme de tous ses lments. est similaire lalgorithme pour compter le nombre doccurrences : ue je suis sur une case, alors la somme est la valeur de cette case laquelle on ajoute la

    ur de toutes les autres ue je suis sorti du tableau, la somme est ncessairement 0 (i.e. llment neutre de la somme)

    , n) = (T, |T|) 1 (T, n) = T[n] + (T, n-1) 1 (T, n) = 0

    e la ligne 2 que lappel est envelopp par laddition avec T[n]. On a une rcursivit , et on va donc utiliser la pile. On aimerait sen passer, et il faudrait donc traduire ceci en une erminale. Le but maintenant est de faire rentrer laddition avec T[n] dans la fonction.

    n utilise un accumulateur : au dpart on le met 0, et chaque appel on y ajoute T[n]. A la fin, rendre la valeur accumule : , n, acc) = (T, |T|, 0) 1 (T, n, acc) = (T, n-1, acc + T[n]) 1 (T, n, acc) = acc

    une rcursivit terminale. Ceci na donc pas besoin de pile et peut scrire avec une boucle. , n, acc)

    que n 1 acc acc + T[n] n n 1

    rner 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 !

    lAlgorithmique, mai 2006 Philippe Giabbanelli 12

  • V. Comment dcrire la performance de mon algorithme ? 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 Philippe Giabbanelli 13

  • 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).

    Walgorithme(n) = max{c(i) | iIn} Le cot dans le meilleur des cas est celui tel que lalgorithme est le plus performant : cest le plus faible de tous les cots possibles sur des donnes de taille n. On le note par B, pour Best Case (meilleur des cas).

    Balgorithme(n) = min{c(i) | iIn} Le cot dans cas moyen suppose quon connat la probabilit p(i) de chaque cas i. Pour avoir le cot dun cas, on multiplie la probabilit davoir ce cas par son cot : p(i)*c(i). Le cot total est donc la somme des cots pour tous les cas. On note ce cot par A pour Average Case (cas moyen).

    Aalgorithme

    = >i 2 I

    n

    p 0 i1 $c 0 i1

    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.

    Aalgorithme

    = 1:In:

    $ >i 2 I

    n

    c 0 i1

    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 Philippe Giabbanelli 14

  • VI. Quels sont les diffrents critres de Comme on sen doute, le nombre dopration nest quun e choisir. Il faut non seulement valuer son cot en calculs dont il a besoin. Certains algorithmes utilisent un petit nodes tableaux intermdiaires, ou font appel la pile car la r Lefficacit dun algorithme est dsigne sous le terme dentre n donne, par deux fonctions : le nombre dopra(complexit en espace). Dterminer ces fonctions est le do Il est particulirement important de mesurer la mmoire uou dont lutilisation peut savrer trs coteuse du point dPour un problme donn, on peut donc tre amen chbeaucoup de mmoire, et un algorithme plus lent qui utilune question de cible : un algorithme qui a besoin de 1 Gomit sur des ordinateurs personnels, mais on peut y songer En outre, la nature des donnes quon soumet lalgoprofessionnelle, les algorithmes servent rarement desdinformations complmentaires. Par exemple, si on doit algorithme qui est bon dans le cas gnral, mais si le tablalgorithme qui est plus efficace pour ce cas. Il faut examiner la complexit des algorithmes, la nature d Exemple 1 : Calcul de complexit 1. fonction Algo(n) 2. crer une matrice Mn,n3. pour i de 1 n 4. pour j de 1 n 5. Mi,j i*j 7. retourner Mn,n La complexit sera toujours la mme : il ny a pas rpuisquil ny a visiblement aucune condition relative la nLopration que lon considre comme lmentaire pour lAvec la boucle j, on ralise n fois cette opration ; commeon a un total de n oprations lmentaires. Concernalments, do un cot de n en espace. Au final, lalgorit Exemple 2 : Recherche dans un tableau particulier Quand on veut tester la prsence dun lment dans unregarder tous les lments. Intressons nous maintenant de +- 1 avec llment suivant. Par exemple on a 3 2 1 1 1Comme 3 a une diffrence de +-1 avec le suivant, alors sonous intresse pas. Par contre, deux cases aprs 3, on peutCest un 1, donc llment quon cherche, sil est prsent,Algo(T, i) = Algo(T, n) i 1 ^ T[i] x Algo(T, i |x T[i]|) i 1 ^ T[i] = x vrai i < 1 faux

    Introduction lAlgorithmique, mai 2006 15 performance ?

    e partie des critres pour savoir quel algorithm

    (le nombre doprations), mais aussi la mmoire mbre de variables, dautres ont besoin de crer

    cursivit est enveloppe

    de complexit . On la mesure, sur une taille tions (complexit en temps) et la place mmoire maine de lanalyse de complexit .

    tilise, car cest une ressource qui peut-tre rare e vue nergtique (imaginons les satellites). oisir entre un algorithme rapide mais utilisant

    ise la mmoire de faon modre. Cest ensuite de mmoire dans la plupart des cas ne sera pas

    sur des serveurs ou stations graphiques.

    rithme a une certaine importance. Dans la vie donnes gnriques ; on dispose souvent trier un tableau quelconque alors on utilisera un eau est presque dj tri alors on fera appel un

    es donnes, et ventuellement la machine cible.

    egarder un cot dans le meilleur des cas, etc., ature des donnes.

    e temps de calcul est laffectation Mi,j i*j . la boucle j est elle-mme excute n fois, alors

    nt la mmoire, on dclare un tableau de n*n hme a une complexit de n en temps et espace.

    tableau, pour le cas gnral on est oblig de un tableau tel que tout lment a une diffrence 1 2 3 4 5 6 7 8 9 et on cherche 5. n suivant peut-tre 2 ou 1 ; on sait dj quil ne

    avoir un 5 : on y va, 3 2 1 1 1 1 2 3 4 5 6 7 8 9. ne peut-tre que 4 cases plus loin etc. etc.

    Dans un tableau aussi particulier, on verra par la suite quon pourra utiliser la technique de la dichotomie.

    Philippe Giabbanelli

  • VII. Comment faire abstraction de la machine ? La plupart du temps, il nest pas ncessaire de compter prcisment le nombre doprations car on entre dans des considrations plus matrielles . Dans nos exemples prcdents, on a nglig la gestion de la boucle. Pourtant, crire pour i de 1 n ncessite de rserver une case mmoire pour la variable i, et de faire n incrmentations. Selon les langages, la variable i peut-tre place dans le registre pour un accs rapide, et lincrmentation est une opration optimise ; autrement dit, le cot est assez variable On cherche donc se dbarrasser le plus possible des caractristiques des machines : on veut un ordre de grandeur sur la performance de lalgorithme. Les constantes ne nous intressent plus ; que ce soit 3n + 4 ou 20n, on est en n. Dans certains cas de la vie professionnelle, il est plus rentable dacheter assez de machines pour utiliser rapidement lalgorithme qui cote 20n, plutt que de passer du temps vouloir dvelopper un meilleur algorithme ; une quipe mobilise 6 mois cote davantage que 10 machines Pour exprimer notre ordre dide, on va se donner quelques complexits de rfrence. De la meilleure (algorithme le plus performant) la pire (algorithme difficilement exploitable), nous avons :

    - Complexit constante : f(n) = 1 - Complexit logarithmique : f(n) = log(n) - Complexit linaire : f(n) = n - f(n) = n.log(n) - Complexit quadratique : f(n) = n - Complexit cubique : f(n) = n3 - Complexit polynomiale : f(n) = np - Complexit exponentielle : f(n) = 2n

    On situe ensuite notre algorithme par rapport ces rfrences : est-il au pire en n ? Au mieux en n ? Exactement en n ? Pour cela, on a introduit et dfini formellement les notations dites de Landau :

    - Quand la complexit g(n) de lalgorithme est majore par f(n), on dit quil est en O(f(n)). O(f(n)) = { g, n0, c>0, n>n0, g(n) c.f(n) }

    Il existe un certain nombre partir duquel g(n) est toujours infrieur f(n)*constante. - Quand la complexit g(n) de lalgorithme est minore par f(n), on dit quil est en (f(n)).

    (f(n)) = { g, n0, c>0, n>n0, g(n) c.f(n) } Il existe un certain nombre partir duquel g(n) est toujours suprieur f(n)*constante.

    - Quand la complexit g(n) de lalgorithme est exactement en f(n), on dit quil est en (f(n)). Sil est exactement en (f(n)), cela veut dire quil est born par deux multiples de f(n). Autrement dit, il peut-tre minor et major par des multiples ; cest donc lintersection des classes de complexit prcdentes : (f(n)) = O(f(n)) (f(n))

    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 Philippe Giabbanelli 16

  • Exemple 1 : Choisir un algorithme Nous avons lalgorithme F dont la complexit en temps est donn par la fonction f(n) = -4*n + 2*n + 3. Nous avons lalgorithme G dont la complexit en temps est donn par la fonction g(n) = log(n) + 300*n. On remarque que la fonction f est minore par n et majore par 3*n : elle est donc en (n). De mme, la fonction g est minore par n et majore par 1000n, par exemple ; elle est donc en (n). Pour une valeur de n trs grand, nous avons vu que la complexit n est pire que la complexit en n. Donc, si nous avons une valeur de n trs grand, nous choisirons lalgorithme G, qui est linaire. En revanche, pour des valeurs plus petites de n, il faut sintresser prcisment aux fonctions. Y a-t-il un intervalle sur laquelle f est plus petite que g, ou lalgorithme G est-il toujours meilleur ? Comparons f et g. On pose h(n) = f(n) g(n) = -4*n + 2*n2 + 3 - log(n) - 300*n. On rsout ceci, par exemple avec des logiciels de calcul formel, et on obtient que h(n) est ngatif sur [0, 152] et positif partir de 153. Autrement dit, sur [0, 152] il est plus coteux dutiliser lalgorithme G que lalgorithme F ! Si on est certain davoir des valeurs de n fortes, lalgorithme G est meilleur. Sinon, on procde une tude, qui conclue que pour n dans [0, 152] alors on utilisera lalgorithme F, et sinon lalgorithme G. Exemple 2 : Dmontrer rigoureusement lappartenance la classe O On a dit que si g(n) est en O(f(n)), alors g(n) est majore par f(n) pour des valeurs assez fortes de n. Ainsi, en regardant g(n) / f(n) en n = +, puisque f est nettement plus fort on doit trouver une constante. Par exemple, on a g(n) = 3*n + 4n. Prouvons que cest bien en O(n). On peut utiliser un logiciel de calcul formel ; en Maple : limit((3*n^2+4*n)/n^2), n=+infinity), et on a la constante 3. Sinon, on calcule :

    3 $ n2 C 4 $ nn2

    w n /C N3 $ n2

    n2= 3 , o a ~n+ b signifie que a est quivalent b lorsque n tend

    vers + linfini. Les quivalences permettent de simplifier. Exemple 3 : Evaluer vue la complexit dun algorithme Si on sait suffisamment comment fonctionne un algorithme, on peut parfois en dduire facilement son ordre de grandeur. Par exemple, lalgorithme du maximum parcours tous les lments et effectue chaque fois un traitement lmentaire (si suprieur au maximum courant, alors je remplace). Comme il y a exactement n lments parcourus, on en dduit que lalgorithme du maximum est en (n). Lalgorithme pour dterminer si un lment est prsent ou non dans un ensemble sarrte ds quil a trouv llment. Dans le pire des cas, llment ny est pas et on doit donc tout parcourir : on en dduit que lalgorithme est en O(n). Dans le meilleur des cas, on rencontre directement llment, et on est donc en (1). Notons que la complexit ne nous fourni quun ordre de grandeur, et lorsquon veut une analyse plus fine, il faut tenter de compter le mieux possible les oprations fondamentales. Voyons une autre ide : Cherche(, T, n) = Cherche(x, T, 1) v Cherche(, T[1n], |T|) T[n] = ^ n 1 Cherche(, T, n) = Vrai T[n] = ^ n = 1 Cherche(, T, n) = Faux T[n] Cherche(, T, n) = Cherche(, T, n-1) Lalgorithme de recherche de sarrte logiquement lorsquil a trouv . En temps normal, il faudrait voir si n nest pas sortie du tableau. Pour viter ceci, on place la 1re case (la dernire visite) : on met en sentinelle pour nous avertir quon sarrte ici. Puisque maintenant on est certain de sarrter, il est inutile de tester les valeurs de n. On commence par regarder si est en 1, et sinon on fait le parcours. Cet algorithme effectue donc deux fois moins de comparaison que lalgorithme de recherche classique, grce la technique de la sentinelle. Pourtant, les deux auront exactement les mmes complexits asymptotiques : O(n) et (1). Ces notations permettent denlever les constantes lies la machine et aux langages, mais font aussi disparatre certaines optimisations intressantes pour lalgorithmique. Mfiance !

    Introduction lAlgorithmique, mai 2006 Philippe Giabbanelli 17

  • VIII. Comment savoir si mon algorithme est le meilleur ? Etant donn un problme, on veut connatre la complexit minimale ncessaire pour le rsoudre ; autrement dit, la borne infrieure de complexit. Une fois cette borne connue, on pourra savoir si lalgorithme dont nous disposons est passable, ou sil peut-tre vraiment amlior. Par exemple, on trouve quun problme a besoin dau moins 2*n n oprations pour tre rsolu : si notre algorithme est en (n), on est assez rassur ; sil est plutt en O(n3) ou plus, en revanche, on sait quil doit pouvoir tre amlior de faon significative. Attention : mme si notre algorithme est dans la classe de complexit de la borne infrieure (ici un O(n)), il peut rester quelques amliorations faire. Un algorithme en 300n + 100 + 50 est certes en O(n), mais on aimerait pouvoir baisser ses constantes, jusqu peut-tre arriver lalgorithme idal qui serait en 2*n n. Cest en gnral assez difficile de connatre prcisment la borne infrieure dun problme. Pour avoir une ide, on peut la calculer de faon asymptotique, cest--dire en lexprimant avec des classes de complexit. Par exemple, pour trouver le maximum dun tableau, comme on est oblig de regarder tous les lments, alors on est en (n). Comme lopration lmentaire est la comparaison, qui nest quune constante, alors on sait que la borne infrieure pour le problme du maximum est (n). De mme, si on veut trouver le deuxime plus grand lment dun tableau, on sait quon est en (n). En effet, on doit regarder chaque lment donc dj (n), et on connat un algorithme linaire pour rsoudre le problme : on cherche le maximum du tableau, on lenlve, on cherche nouveau le maximum, et cest bien le deuxime plus grand lment. Par contre, si on veut exprimer prcisment la borne infrieure de ce problme, cest--dire en donnant les constantes, alors il faudra tre beaucoup plus subtil Exemple 1 : Prouver que lalgorithme du maximum est optimal 1. Max(T, n, i, max) = Max(T, |T|, 2, T[1])) 2. i n ^ T[i] > max Max(T, n, i, max) = Max(T, n, i+1, T[i]) 3. i n ^ T[i] max Max(T, n, i, max) = Max(T, n, i+1, max) 4. i > n Max(T, n, i, max) = max On commence par calculer la complexit exacte de notre algorithme. Lopration fondamentale est ici la comparaison : dterminons le nombre de comparaisons effectues. On commence parcourir le tableau partir de la case 2 et on sarrte lorsquon dpasse la case n. Les lignes 2 et 3 sont donc examines n 2 + 1 = n 1 fois. Ces lignes contiennent chacune deux comparaisons, ce qui nous fait un total de 2*(n 1) = 2n 2 comparaisons. En pratique, on nglige trs souvent les comparaisons dindices, car cela porte sur de petits nombres et lopration peut donc seffectuer rapidement. En revanche, les comparaisons entre contenus du tableau sont nettement plus significatives ! Il serait donc courant de dclarer simplement n 1 comparaisons. On calcule la borne infrieure. Il y a n lments. On choisit comme maximum potentiel le premier lment. Tout lment doit avoir t compar une fois au maximum, sinon on ne peut pas savoir sil est le maximum. Seul le maximum ne sera pas compar. Ainsi, tout algorithme de recherche du maximum doit faire au moins n 1 comparaisons. On compare la complexit de notre algorithme et la borne infrieure. Or, notre algorithme effectue prcisment n 1 comparaisons. Il a atteint la borne infrieure, il est donc optimal.

    Introduction lAlgorithmique, mai 2006 Philippe Giabbanelli 18

  • Or, log2(n!) nest pas trs parlant pour nous donner la complexit. On va donc transformer lexpression. Pour cela, il faut connatre la formule de Stirling : n! (n/e)n * (2n). Pour mesurer la complexit, on peut ngliger le terme constant. On obtient donc : log2(n!) h log2[(n/e)n] h n.log2(n/e) h n.log2(n) n.log2(e) h n.log2(n) n h On a obtenu n.log2(n) n h. Le plus long chemin que lalgorithme puisse prendre pour trier n lments

    On commence par comparer les deux premiers lments, a1 et a2 . Selon le rsultat, on va avoir besoin de comparer dautres lments. On arrive ainsi reprsenter le raisonnement par un arbre, dit arbre de dcision . Lexcution de lalgorithme de tri est donc un simple chemin dans larbre. Lorsquon arrive au bout, lalgorithme a dtermin lordre dans

    s n s

    car log2(ea) = a car log2(a/b) = log2(a) log2(b)car log2(ab) = b.log2(a)

    Notons h la longueur maximale des chemins dans larbre, aussi appele la hauteur. Pour une hauteur de 1, on voit quon a au plus 2 feuilles. Si on augmente la hauteur de 1, nos feuilles peuvent avoir chacune au plus deux feuilles, ce qui nous fait un maximum de 4 feuilles. On montre par rcurrence que pour une hauteur de h, le nombre maximum de feuilles est 2h.

    Logiquement, le nombre de feuilles de notre arbre de dcision est infrieur au nombre maximal de feuilles que peut avoir tout arbre. On a donc n! 2h. Le reste est maintenant une histoire de mathmatiques de niveau terminale. Pour rcuprer la hauteur, qui est actuellement en exposant du 2, on applique le logarithme base 2 :

    n! 2h log2(n!) log2(2h) log2(n!) h

    e chemins ou encore de feuilles (les lments au bout) de larbre. Pour cela, on se donne n lments : le nombre de combinaisons que peuvent former n lments est n! En effet, on a n possibilits de choisir le premier lment, donc (n-1) de choisir le 2nd, etc., et le dernier est forc ; soit n*(n-1)*(n-2)**1 = n!. Larbre de dcision a donc n! feuilles. Quand on excute un algorithme de tri sur nos donnes, il va emprunter un des chemins possibles. Trouver la plus grande longueur du chemin que peut prendre lalgorithme revient trouver la complexit ncessaire pour rsoudre le pire des cas ; autrement dit, la complexit telle que tout cas puisse tre rsolu. Cette complexit nest rien dautre que la borne infrieure du tri par comparaison !

    Exemple 2 : Trouver la borne infrieure du tri par comparaison Etant donn un tableau, comment trier ses lments en les comparant ? Regardons trois lments, a1a2a3 : 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.

    lequel sont les lments, et effectue lepermutations : jchange a1 avec a2, ou Ovoit que pour 3 lments, il existe 6 finpossibles.

    On veut savoir le nombre de fins possibles quil y a dans le cas gnral, autrement dit le nombre dIntroduction lAlgorithmique, mai 2006 Philippe Giabbanelli 19

  • 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) veut savoir quel est le plus grand

    1.4) algorithme donnera un rel r qui

    , 1.5], et une des rponses valides

    2.1)

    4.1)

    6.1) 6.2)

    7.1) 8.1)

    IntrodOn dispose dun tableau T dentiers strictement positifs, et on cart entre deux lments du tableau.

    On dispose dun tableau T, contenant n nombres rels. Votre nappartient pas au tableau. Par exemple, T = [-2, 12.5, -8.8, -5est 4.5.

    En sinspirant du raisonnement pour le nombre de rgions

    dtermines par n frontires rectilignes , donnez le nombre de rgions dtermines par n cercles. Les cercles sont en position gnrale : deux cercles ne sont pas tangents ni entirement superposs. On a R(1) = 2, R(2) = 4, R(3) = 8 et R(4) = 14. Aidez vous en faisant les dessins pour les cas 3 et 4 cercles.

    On dispose dun tableau dont on ne connat pas la taille. On saitpar le caractre \0. Calculez sa taille des deux faons : rcursive enEcrivez un quivalent de la version rcursive terminale en style im

    Donnez la complexit (cot en temps et en espace) des algorithme

    On dispose dune matrice M, qui est un tableau deux dimensionsVotre premier algorithme donnera la somme des lments de la mVotre second algorithme donnera la somme de la diagonale de la mVous donnerez la complexit des deux algorithmes. Comme la mnest pas trs pratique dans notre formalisation, vous pouvez comm

    Montrer que g(n) = [n*log(n) + (log n)4 + n]/(n+1) appartient la

    On dispose dun tableau T dentiers. Le tableau comprend tous lesalgorithme donnera llment qui manque. Par exemple, le tableau est 0 3 4 5 6 9 7 8 1, et llment 2 est manExpliquez pourquoi votre algorithme est optimal.

    uction lAlgorithmique, mai 2006 20 quil se termine ncessairement veloppe et rcursive terminale. pratif (tant que, pour).

    s 1.3), 1.4) et 4.1).

    , de largeur l et de hauteur h. atrice.

    atrice (appele la Trace de M). anipulation des doubles boucles encer par un style impratif.

    classe (log n).

    entiers de 0 n sauf un. Votre

    quant.

    Philippe Giabbanelli

  • Chapitre 2 Le problme des structures de donnes

    I. Prsentation formelle du problme 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 . 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 Philippe Giabbanelli 21

  • II. Un pointeur du point de vue physique 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. riendonne donne donne donne donne

    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. Les connections entre les donnes sont appeles des pointeurs : on pointe sur un autre objet.

    rien riendonne donne donne donne

    Comment est faite une liste par chanage simple ? La liste est forme de maillons. Pour dfinir la liste, il faut donc commencer par dire ce quest un maillon : Un maillon est dfini par : public class Maillon{ Une donne public Object donnee ; Un lien vers le maillon suivant public Maillon suivant ; Pour crer un maillon, on prend une donne public Maillon(Object o) { On stocke la donne donnee = o ; Par dfaut, il ny a pas de maillon suivant suivant = null ; } } 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. Une liste par chanage simple est dfinie par : public class LinkedList{ Le premier maillon public Maillon tete ; Pour crer une liste, pas besoin de paramtres public LinkedList(){ On initialise le premier maillon rien 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 ? Est-ce que la liste contient rien ? On peut crire une premire version de lalgorithmVide?(L) L = Vide?(L) = vrai L Vide?(L) = faux Il est cependant plus cours de renvoyer seulement l

    Vide?(L) = L =

    Introduction lAlgorithmique, mai 2006 e pour tester si la liste est vide : public boolean vide(LinkedList L){ if(L..tete == null) return true; if(L..tete != null) return false ; }

    e rsultat du test.

    public boolean vide(LinkedList L){ return L . tete == null;}

    Philippe Giabbanelli 22

  • Comment ajouter un lment ? Pour ajouter un maillon, il suffit de dire que cest notre nouvelle tte, et dy rattacher la liste. En gnral, on ajoute des donnes plutt que des maillons. Il faudra mettre la donne dans un maillon.

    liste tete

    maillon

    liste

    maillon

    tete

    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 ! Comment supprimer un lment ? Pour supprimer un lment dune liste, il suffit que plus aucun maillon ne pointe dessus. Autrement dit, on dcroche le maillon, on relie les autres, et on loublie. En Java, le Garbage Collector fera le mnage.

    A supprimer

    3 2 1 2

    3 1

    Ainsi, aprs 1 on a 3 comme prvu. On ne pourra plus accder au 2 en parcourant la liste, ce qui revient donc bien le supprimer. Le principe est donc de trouver le nud dcrocher, et de relier son prcdent avec son suivant. On voit facilement quil y a un cas particulier : la tte na pas de prcdent, ainsi sil sagit de llment supprimer alors il faudra lavancer. Supprimer(L, d) public void ajouter(LinkedList L, Object o){ Si cest la tte, if(L.tete.donnee == o){

    alors je lavance. L . tete = L . tete . suivant ; return ;} Sinon, je me positionne sur la tte Maillon M = L . tete ; et javance tant que je ne suis pas au bout. while(M . suivant != null){ Si je trouve llment supprimer, if(M . suivant . donnee == o){ alors je le saute. M . suivant = M . suivant . suivant ; return ; } M = M . suivant ; Si jai tout parcouru et rien trouv, alors } l'utilisateur voulait supprimer un lment System . err . println( Element introuvable. ) ; qui nexiste pas. } Parcours gnral dune liste Dans des langages similaires Java (C, C++, ), le parcours dune liste se fait avec une boucle. On a utilis ici une syntaxe, il peut y en avoir une autre. Les deux sont bien entendus strictement quivalentes : Maillon m = L..tete ; for(Maillon m = L..tete ; m.suivant != null ; m = m.suivant){ w m.suivant != null){ ctions propres lalgorithme> } o }

    Ihile(m = m.suivant ;

    ntroduction lAlgorithmiquee, mai 2006 23

  • III. Un pointeur du point de vue algorithmique Lalgorithmique se concentre sur la rflexion et la complexit des solutions trouves. On ne sintresse pas explicitement tout ce qui relve de la manipulation de la mmoire, autrement dit des pointeurs : ce sont des choses de trop bas niveau, trop proche de la machine. Pour concevoir un algorithme, on ne veut pas sencombrer de ce genre de dtails, il nous faut un haut niveau dabstraction. On va donc simplement noter par un . la liaison entre deux lments, et par la liste vide. Ainsi, on a lquivalence suivante :

    physique algorithmique a b rienc a.b.c. Il ny a rien de plus facile que dajouter un lment une liste L. Regardons lquivalence : c rien a

    ab.c.

    b c rienba

    a.b.c. 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 ( a b) au lieu de (a 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)) Soit L Ajouter(a, Ajouter(b, Ajouter(c, ))) (Soit L

  • 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) Suppression de d dans un ensemble 2. L = Supprimer(L, d) = liste vide : d ny est pas, cest valide. 3. c = d Supprimer(c.L, d) = L si jai trouv d, jai fini le travail. 4. c d Supprimer(c.L, d) = c.Supprimer(L, d) sinon, je continue de chercher 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) Suppression de d en multi-ensemble 2. L = Supprimer(L, d) = 3. c = d Supprimer(c.L, d) = Supprimer(L, d) je continue le traitement, pas fini ! 4. c d Supprimer(c.L, d) = c.Supprimer(L, d) 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 Philippe Giabbanelli 25