Master SIS – Maîtrise – M1 Complexité Algorithmique Feuille de TD n° 1 - Septembre 2011 Rappel des techniques de base en algorithmique, structures de données, complexité et preuves. Problème 1 – Preuves et complexité Nous considérons la fonction C donnée ci-dessous où les valeurs de a et de b en entrée sont supposées entières et positives : i n t C_Quoi(int a, int b) { i n t x,y,m; m = 0 ; x = a ; y = b ; while ( y > 0 ) { if ( y % 2 == 1 ) { y = y-1 ; m = m+x; } else { x = 2*x ; y = y/2; } } return(m) ; } Question 1. Donnez les spécifications externes de la fonction C_Quoi. Question 2. Démontrez la validité de la fonction C_Quoi par la méthode des invariants en considérant les spécifications externes que vous aurez données dans la question 1. Question 3. Donnez la complexité de la fonction C_Quoi en justifiant votre évaluation. Question 4. Donnez une implémentation récursive de la fonction C_Quoi dont vous démontrerez la validité. Analysez sa complexité. Problème 2 – Représentation de suites par des structures de données. Dans ce problème, on cherche à représenter des suites dans des tables, des listes ou des arbres. Partie 1 On considère ici la suite entière un définie par u0 = 1 et un = 4. un-1 + 3 pour n > 0. Question 1. Ecrire une fonction C qui prend en entrée un entier n puis renvoie la valeur de un et construit également un tableau t de type tabent, et de sorte que t[i] = ui pour tout entier i vérifiant 0 ≤ i ≤ n. Le type tabent est défini par : typedef int tabent[MAX] ; où MAX est une valeur supposée supérieure à tout entier n considéré. Le tableau résultant du calcul figurera dans les arguments de la fonction C. Evaluez la complexité en temps de votre fonction. Question 2. On se pose la même question que ci-dessus en considérant non plus un tableau, mais une liste simplement chaînée. Ecrire une fonction C qui prend en entrée un entier n puis renvoie la valeur de un et construit également une liste simplement chaînée dont les éléments seront définis avec les types C suivant : typedef struct ch { int valeur; struct ch *suivant; } chainon; typedef chainon *liste; La fonction C aura dans ses arguments, un pointeur vers la liste ainsi construite et celle-ci organisée de telle sorte que le premier élément de la liste contienne u0, et le ième contienne ui pour tout i vérifiant 0 ≤ i ≤ n. Il faudra que cette liste soit construite sans recours à un tableau. Evaluez la complexité en temps de votre fonction. Question 3. On se pose la même question exactement que dans la question 1, mais on considère maintenant la suite de Fibonacci qui est définie ici par f0 = 1, f1 = 1 et fn = fn-1 + fn-2 pour n > 1. Ecrire une fonction C qui prend en entrée un entier n puis renvoie la valeur de fn et affecte les cases d’un tableau t de type tabent, et de sorte que t[i] = fi pour tout i vérifiant 0 ≤ i ≤ n. Evaluez la complexité en temps de votre fonction. Question 4. On se pose la même question exactement que dans la question 3, mais en construisant cette fois-ci une liste simplement chaînée. Evaluez la complexité en temps de votre fonction. Partie 2 Pour le cas où la suite récurrente entière considérée est définie pour un un sur la base de plusieurs prédécesseurs (comme pour le cas de la suite de Fibonacci), on peut utiliser une structure de données arborescente pour représenter ses éléments. Par exemple, pour représenter la suite de Fibonacci, on peut utiliser un arbre binaire. On aurait ainsi, pour représenter f0 et f1 des arbres restreints chacun à un unique nœud dont les valeurs seraient respectivement 1 et 1. Pour représenter fn avec n > 1, il faudrait un arbre dont la racine aurait pour valeur fn et possédant deux fils, le fils gauche représentant fn-1, et le fils droit fn-2. Par exemple, f3 serait représenté par un arbre dont la racine aurait pour valeur f3, et dont le fils gauche de valeur f2, aurait lui-même pour fils gauche le nœud représentant f1, et pour fils droit le nœud représentant f0. De plus, la racine aurait pour fils droit le nœud représentant f1. Dans cette partie, nous représenterons des arbres binaires avec les types C suivants : typedef struct triplet { int valeur; struct triplet *fg; struct triplet *fd; } noeud; typedef noeud *arbre; Question 5. On se pose la même question que précédemment en considérant cette fois-ci un arbre binaire. Ecrire une fonction C appelée fibabr qui prend en entrée un entier n puis renvoie la valeur de fn et construit un arbre binaire défini comme ci-dessus, c’est-à-dire dont les nœuds contiennent les valeurs correspondantes dans la suite de Fibonacci. La fonction C aura donc dans ses arguments, un pointeur vers l’arbre ainsi construit. Evaluez la complexité en temps et en espace de votre fonction. Question 6. Démontrez que le nombre de feuilles de l’arborescence binaire construite dans la question 6, est précisément égal à la valeur de fn . Question 7. Ecrire une fonction C qui prend en entrée un arbre binaire résultant de l’exécution de la fonction fibabr avec un entier n en entrée qui fournit la valeur de fn comme résultat, sans utiliser le champ valeur du type noeud mais par le simple dénombrement des feuilles de l’arborescence (cf. exploitation de la question 6). Question 8. Il existe une structure de données, abusivement appelée « arbres cousus » qui permet un gain significatif d’espace pour la représentation d’arbres quand certains nœuds sont répétés. Le type C permettant de les représenter est identique au précédent, la différence se présentant au niveau de l’exploitation qui en est faite. Dans un tel arbre, si deux nœuds possèdent un fils de même nature, alors un seul nœud sera utilisé pour le représenter et les deux nœuds se partageront ce même fils. Par exemple, l’arbre associé à f3 serait représenté par une structure de données ne contenant que 4 nœuds. La racine qui aurait pour valeur f3 , aurait un fils gauche de valeur f2 , et un fils droit représentant f1 . Le nœud représentant f2 aurait pour fils droit le nœud représentant f0 , alors qu’il aurait comme fils gauche (de valeur f1 ) le nœud fils droit de la racine. Ecrire une fonction C qui prend en entrée un entier n, construit un arbre cousu défini comme indiqué ci-dessus, puis renvoie la valeur de fn. La fonction C aura donc dans ses arguments, un pointeur vers l’arbre cousu ainsi construit. Evaluez la complexité en temps et en espace de votre fonction. Problème 3 – Arbres binaires de recherche. On considère dans ce problème des arbres binaires de recherche (ABR). Il est rappelé que dans un ABR, les valeurs (supposées différentes) sont mémorisées de telle sorte que pour un nœud donné contenant une valeur x, les valeurs contenues dans les nœuds situés dans sa sous-arborescence gauche sont inférieures à x et celles contenues dans les nœuds situés dans sa sous-arborescence droite sont supérieures à x. Pour représenter les ABR, vous utiliserez les types C suivants : typedef struct noeud { int elt; /* valeur du noeud */ struct noeud *fg, *fd; /* pointeurs vers fils */ } triplet; t y p e d e f triplet *ABR; A partir de la question 2, vous donnerez systématiquement une analyse de la complexité de la fonction programmée. Question 1. Dessinez un arbre binaire de recherche contenant les valeurs 3, 15, 26, 7, 0, 45, 12, 19, 14 et 8. Indiquez l’ordre dans lequel seront visités les nœuds par un parcours post-fixé de l’arbre. Question 2. Ecrivez une fonction en C appelée correct qui prend en entrée un pointeur a de type ABR et qui vérifie si ce pointeur pointe bien vers une structure de données représentant un arbre binaire de recherche (on doit vérifier que la position des valeurs mémorisées vérifient la propriété de base des ABR). Question 3. Ecrivez une fonction en C appelée présent qui prend en entrée un pointeur a de type ABR et un entier x et qui a pour résultat 1 si x figure dans l’arbre binaire de recherche pointé par a. Le résultat vaudra 0 sinon. Question 4. Ecrivez une fonction en C appelée maximum qui prend en entrée un pointeur a de type ABR et qui a pour résultat la plus grande valeur mémorisée dans l’arbre binaire de recherche pointé par a. Le résultat vaudra 0 sinon. Question 5. Ecrivez une fonction en C appelée egaux qui prend en entrée deux pointeurs a et b de type ABR, qui pointent vers des arbres binaires de recherche et qui vérifie si ces arbres binaires de recherche contiennent exactement les mêmes entiers. Dans ce cas le résultat de la fonction vaudra 1, et dans le cas contraire, il vaudra 0. Question 6. Ecrivez une fonction en C appelée identique qui prend en entrée deux pointeurs a et b de type ABR, qui pointent vers des arbres binaires de recherche et qui vérifie si ces arbres binaires de recherche contiennent exactement les mêmes entiers et ont exactement la même forme. Dans ce cas le résultat de la fonction vaudra 1, et dans le cas contraire, il vaudra 0.