MR-SPI-A1 amel_naceur@yahoo.fr Amel Naceur Algorithmique et complexité 2 A propos du module Qu’est-ce que l’algorithmique ? Algorithme: Un algorithme est une suite finie d’opérations élémentaires constituant un schéma de calcul ou de résolution d’un problème. Algorithmique : L’algorithmique désigne le processus de recherche d’algorithme. Différences entre algorithmes et programmes : Un programme est la réalisation (l’implémentation) d’un algorithme au moyen d’un langage donné (sur une architecture donnée). Il s’agit de la mise en œuvre du principe.
Amel Naceur Algorithmique et complexité 3
A propos du module Préparer une présentation de 30 mn expliquant : Principe générale
Description de l’algorithme
Exemple de résolution sur un exemple (un problème)
Amel Naceur Algorithmique et complexité 4
A propos du module Les sujets de présentations: 1. Les problèmes d’optimisation combinatoires et la classification des méthodes de résolution: Les méthodes exactes et les méthodes approchées 2. La recherche Tabou (Tabu Search) 3. Le recuit simulé (simulated annealing) 4. Les algorithmes génétiques (Genetic algorithm) 5. Les algorithmes de colonies de fourmis (Ant optimization algorithm) 6. L’optimisation par essaim de particules (Particle Swarm Optimization) Amel Naceur Algorithmique et complexité 5 Plan du module Chapitre 1: La récursivité et le paradigme « diviser pour régner »
Chapitre 2: Les structures de données arborescentes
Chapitre 3: Les algorithmes gloutons
Chapitre 4: La programmation dynamique
Chapitre 5: la complexité et NP-complétude
Amel Naceur Algorithmique et complexité 6
La récursivité et le paradigme « diviser pour régner » La récursivité Récursivité Moyen simple et élégant de résoudre certain problème Tout objet est dit récursif s’il se définit à partir de lui-même On appelle récursive toute fonction ou procédure qui s’appelle elle même. De même, une structure est récursive si un de ses attributs en est une autre instance Exemple
Amel Naceur Algorithmique et complexité 8
La récursivité Exemple : Le calcul de la factorielle de N N != N*(N-1)*(N-2)*…*2*1 , on peut écrire ainsi N != N*(N-1)! La factorielle de N est définie en fonction de la factorielle de N-1 La fonction a besoin d’elle-même pour donner un résultat. Pour calculer N! il faut savoir calculer (N-1) ! et pour calculer (N-1) ! Il faut savoir calculer (N-2) ! et ainsi jusqu’à 1! qui est égal à 1 et qui permet à la récursivité de s’arrêter après une série d’auto appels. Il est impératif donc de prévoir une condition d’arrêt à la récursion sinon le programme ne s’arrête jamais! A l’opposé de la récursion, l’itération utilise les structures de contrôle répétitives comme POUR, TANT QUE, REPETER JUSQU'À
Amel Naceur Algorithmique et complexité 9
La récursivité
Amel Naceur Algorithmique et complexité 10
La récursivité
Amel Naceur Algorithmique et complexité 11
La récursivité Dans un module récursif (procédure ou fonction) les paramètres doivent être clairement spécifiés. Dans le corps du module il doit y avoir : un ou plusieurs cas particuliers: ce sont les cas simples qui ne nécessitent pas d'appels récursifs. un ou plusieurs cas généraux: ce sont les cas complexes qui sont résolus par des appels récursifs. L'appel récursif d'un cas général doit toujours mener vers un des cas particuliers.
Amel Naceur Algorithmique et complexité 12
La récursivité : la pile d’exécution La Pile d’exécution est un emplacement mémoire destiner à mémoriser les paramètres, les variables locales ainsi que les adresses de retour des fonctions en cours d’exécution. Elle fonctionne selon le principe LIFO (Last-In-First-Out) : dernier entré premier sorti. Attention ! La pile à une taille fixée, une mauvaise utilisation de la récursivité peut entraîner un débordement de pile. L’exécution d’un appel récursif passe par deux phases, la phase de descente et la phase de remontée. Dans la phase de descente, chaque appel récursif fait à son tour un appel récursif. En arrivant à la condition terminale, on commence la phase de remontée Amel Naceur qui se poursuit jusqu’à ce que l’appel initial soit Algorithmique et complexité 13
terminé, ce qui termine le processus récursif.
La récursivité : la pile d’exécution
Amel Naceur Algorithmique et complexité 14
La récursivité : la pile d’exécution Ordre des appels récursifs (exemple avec 3 appels récursifs) Proc rec() Proc rec() 1 { <inst pré> si (cont) 2 { <inst pré> Proc rec() rec() si (cont) { <inst pré> rec() 3 <inst post> 6 si (cont) <inst post> 5 rec() } } <inst post> 4 }
appel récursif : branchement
retour récursif : instruction suivante de la fonction appelante Amel Naceur Algorithmique et complexité 15 La récursivité : type Récursivité simple C’est une fonction qui contient dans son corps un seul appel récursif.
Amel Naceur Algorithmique et complexité 16
La récursivité : type Récursivité multiple Une définition récursive peut contenir plus d’un appel récursif. Nous voulons calculer ici les combinaisons Cnp en se servant de la relation de Pascal :
Amel Naceur Algorithmique et complexité 17
La récursivité : type Récursivité mutuelle Des définitions sont dites mutuellement récursives si elles dépendent les unes des autres. Ça peut être le cas pour la définition de la parité :
Amel Naceur Algorithmique et complexité 18
La récursivité : type Récursivité imbriquée La fonction d’Ackermann est définie comme suit :
Amel Naceur Algorithmique et complexité 19
Récursivité terminale vs non terminale Un module récursif est dit terminal si aucun traitement n’est effectué après la remontée d’un appel récursif (sauf le retour du module). Un module récursif est dit non terminal si le résultat de l’appel récursif est utilisé pour réaliser un traitement (en plus du retour du module). Exemple: fonction factorielle
Amel Naceur Algorithmique et complexité 20
Récursivité terminale vs non terminale
Amel Naceur Algorithmique et complexité 21
Récursivité terminale vs non terminale Une fonction récursive terminale est en théorie plus efficace (mais souvent moins facile à écrire) que son équivalent non terminale; il n'y a qu'une phase de descente et pas de phase de remontée. Il est possible de transformer de façon simple une fonction récursive terminale en une fonction itérative : c'est la dérécursivation. Dérécursiver, c’est transformer un algorithme récursif en un algorithme équivalent ne contenant pas d’appels récursifs.
Amel Naceur Algorithmique et complexité 22
Le paradigme « diviser pour régner » Principe: Spécifier la solution du problème en fonction de la (ou des) solution(s) d’un (ou de plusieurs) sous-problème(s) plus simple(s) traité(s) de façon récursive. Le paradigme « diviser pour régner » donne lieu à trois étapes à chaque niveau de récursivité : Diviser : le problème en un certain nombre de sous-problèmes ; Régner : sur les sous-problèmes en les résolvant récursivement ou, si la taille d’un sous-problème est assez réduite, le résoudre directement ; Combiner : les solutions des sous-problèmes en une solution complète du problème initial.
Amel Naceur Algorithmique et complexité 23
Le paradigme « diviser pour régner »
Amel Naceur Algorithmique et complexité 24
Le paradigme « diviser pour régner » Exemple 1: Recherche maximum Soit Tab un tableau à n éléments, écrire une fonction récursive permettant de rechercher l’indice du maximum dans Tab en utilisant le paradigme « diviser pour régner ».
Amel Naceur Algorithmique et complexité 25
Le paradigme « diviser pour régner »
Amel Naceur Algorithmique et complexité 26
Le paradigme « diviser pour régner » Exemple 2: recherche dichotomique Soit Tab un tableau trié (ordre croissant) à n éléments.
La recherche par dichotomie compare l’élément cherché x
avec l’élément en position m situé en position du milieu du sous-tableau: Si Tab[m] = x alors on a trouvé l’élément x en position m. Si Tab[m] > x il est possible que x se trouve avant la position m du tableau. Il reste à traiter uniquement la moitié inférieure du tableau. Enfin Si Tab[m] < x il est possible que x se trouve après la position m. Il reste à traiter uniquement la moitié supérieure du tableau. On continue ainsi la recherche jusqu’à trouver l’élément ou bien aboutir à un tableau de taille nulle, dans ce cas x n’est pas présent et la recherche s’arrête. Amel Naceur Algorithmique et complexité 27 Le paradigme « diviser pour régner »
Amel Naceur Algorithmique et complexité 28
Le paradigme « diviser pour régner » Exemple 3: tri par fusion L’algorithme de tri par fusion est construit suivant le paradigme « diviser pour régner » : 1. Il divise la séquence de n nombres à trier en deux sous- séquences de taille n/2. 2. Il trie récursivement les deux sous-séquences.
3. Il fusionne les deux sous-séquences triées pour produire la
séquence complète triée. La récursion termine quand la sous-séquence à trier est de longueur 1 car une telle séquence est toujours triée.
Amel Naceur Algorithmique et complexité 29
Le paradigme « diviser pour régner »
Amel Naceur Algorithmique et complexité 30
Le paradigme « diviser pour régner »
Amel Naceur Algorithmique et complexité 31
Le paradigme « diviser pour régner »
Etant donné un tableau (ou une liste) de T [1, . . . , n] :
o si n = 1, retourner le tableau T o sinon : 1. Trier le sous-tableau T[1 . . . n/2 ] 2. Trier le sous-tableau T[ n/2 + 1 . . . n] 3. Fusionner ces deux sous-tableaux. Il s’agit d’un algorithme « diviser pour régner ».
Amel Naceur Algorithmique et complexité 32
Le paradigme « diviser pour régner » Procédure Tri_fusion (var T:tab; deb, fin: entier) Var milieu : entier Début si (deb<fin) alors milieu← (deb + fin)/2 Tri_fusion (T, deb, milieu) Tri_fusion (T, milieu+1, fin) Fusionner (T, deb, milieu, fin) Finsi Fin