Cours d'algorithmique 7 - Intranet 1 27 novembre 2006 Cours dAlgorithmique Dérécursion (début) : Équivalences entre programmes récursifs et programmes itératifs avec ou sans gestion de pile.
27 novembre 2006Cours d'algorithmique 7 - Intranet2 Trier et chercher, recherche textuelle Trier et chercher, recherche textuelle Listes et arbres Listes et arbres Le back-track Le back-track Arbres équilibrés Arbres équilibrés Récursivité et induction sur la structure Récursivité et induction sur la structure Divide and conquer Divide and conquer Minimax, alpha-beta Minimax, alpha-beta Dérécursion Dérécursion Divers problèmes particuliers Divers problèmes particuliers Logique de Hoare Logique de Hoare Programmation dynamique Programmation dynamique Complexité et calculabilité Complexité et calculabilité Les grandes lignes du cours
27 novembre 2006Cours d'algorithmique 7 - Intranet3 Introduction Si nous programmons à laide de fonctions récursives : Si nous programmons à laide de fonctions récursives : – la gestion des différents appels est faite par le système, – à travers la gestion de la pile. Si nous programmons de manière itérative (boucle while) : Si nous programmons de manière itérative (boucle while) : – nous devons gérer nous-mêmes toutes les instances des calculs – et donc gérer éventuellement une pile. Questions : Questions : – Comment passer du récursif à litératif et vice-versa ? – Et pourquoi on préfère souvent la récursion à litération...
27 novembre 2006Cours d'algorithmique 7 - Intranet4 Théorème fondamental Tout programme qui comporte des itérations while et des fonctions récursives : Tout programme qui comporte des itérations while et des fonctions récursives : – peut être transformé en un programme qui comporte uniquement des fonctions récursives, – et même une seule fonction récursive. Tout programme qui comporte des itérations while et des fonctions récursives : Tout programme qui comporte des itérations while et des fonctions récursives : – peut être transformé en un programme qui comporte uniquement des itérations while, – et même une seule itération while.
27 novembre 2006Cours d'algorithmique 7 - Intranet5 Plan Chaque appel récursif engendre au plus un autre appel ! f ( x ) = if (... ) if (... ) return( valeur ) return( valeur ) else else return(... f( )... ) return(... f( )... ) f ( x ) = if (... ) if (... ) return( valeur ) return( valeur ) else else if (... ) if (... ) return(... f( )... ) return(... f( )... ) else else return(... f( )... ) return(... f( )... ) mais aussi : Nombre dappels récursifs Un Deux
27 novembre 2006Cours d'algorithmique 7 - Intranet6 Plan Chaque appel récursif engendre au plus deux autres appels ! f ( x ) = if (... ) if (... ) return( valeur ) return( valeur ) else else if (... ) if (... ) return(... f( )... ) return(... f( )... ) else else return(... f( )... f( )... ) return(... f( )... f( )... ) Nombre dappels récursifs Un Deux
27 novembre 2006Cours d'algorithmique 7 - Intranet7 Plan Fonction enveloppe Non Oui Nombre dappels récursifs Un Deux On dit que lappel récursif est « terminal ». f ( x ) = if (... ) if (... ) return( valeur ) return( valeur ) else else return( f(... ) ) return( f(... ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet8 Plan Fonction enveloppe Non Oui Nombre dappels récursifs Un Deux On dit que lappel récursif est « non terminal ». f ( x ) = if (... ) if (... ) return( valeur ) return( valeur ) else else return( h(... f( )... ) ) return( h(... f( )... ) ) La fonction « h » est lenveloppe !
27 novembre 2006Cours d'algorithmique 7 - Intranet9 Plan Fonction enveloppe Non Oui Nombre dappels récursifs Un Deux Enveloppe associative Non Oui « h » est associative si, et seulement si, on a toujours : h ( a, h ( b, c ) ) = h ( h ( a, b ), c )
27 novembre 2006Cours d'algorithmique 7 - Intranet10 Plan Fonction enveloppe Non Oui Nombre dappels récursifs Un Deux Enveloppe associative Non Oui Avec élément neutre Non Oui « h » admet le neutre « e » si, et seulement si, on a toujours: h( e, a ) = a = h( a, e ) h( e, a ) = a = h( a, e )
27 novembre 2006Cours d'algorithmique 7 - Intranet11 Plan Fonction enveloppe Non Oui Nombre dappels récursifs Un Deux Enveloppe associative Non Oui Avec élément neutre Non Oui Appréciations ! Sympas ! ! ! Assez sympa ! Il existe des transformations !
27 novembre 2006Cours d'algorithmique 7 - Intranet12 Plan Fonction enveloppe Non Oui Nombre dappels récursifs Un Deux Enveloppe associative Non Oui Avec élément neutre Non Oui Appréciations ! Sympas ! ! ! Assez sympa ! Beurk ! ! ! ! ! Il existe des transformations !
27 novembre 2006Cours d'algorithmique 7 - Intranet13 Plan Nombre dappels récursifs Un Deux
27 novembre 2006Cours d'algorithmique 7 - Intranet14 Plan Nombre dappels récursifs Un Deux Fonction auto-enveloppée Non Oui La fonction récursive est sa propre enveloppe ! f ( x ) = if (... ) if (... ) return( valeur ) return( valeur ) else else return( f(... f( )... ) ) return( f(... f( )... ) ) La fonction enveloppe de « f » est « f » ! Lappel externe « f » est terminal.
27 novembre 2006Cours d'algorithmique 7 - Intranet15 Plan Nombre dappels récursifs Un Deux Fonction auto-enveloppée Non Oui Une fonction « h » enveloppe les deux appels récursifs ! f ( x ) = if (... ) if (... ) return( valeur ) return( valeur ) else else return( h(... f( )... f( )... ) ) return( h(... f( )... f( )... ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet16 Plan Nombre dappels récursifs Un Deux Fonction auto-enveloppée Non Oui Enveloppe associative Oui Non Avec élément neutre Oui Non Appréciations ! Transformation intéressante ! Celle-là aussi ! Sans espoir ! ! !
27 novembre 2006Cours d'algorithmique 7 - Intranet17 Un appel récursif, terminal Un seul appel récursif qui est terminal. Un seul appel récursif qui est terminal. Cest le cas de base, il est fondamental. Cest le cas de base, il est fondamental. Il se transforme en boucle while sans pile. Il se transforme en boucle while sans pile. Les appels non terminaux avec une enveloppe associative se ramènent à ce cas. Les appels non terminaux avec une enveloppe associative se ramènent à ce cas. Tout ce qui ne se ramène pas à ce cas nécessitera une pile ! ! ! Tout ce qui ne se ramène pas à ce cas nécessitera une pile ! ! !
27 novembre 2006Cours d'algorithmique 7 - Intranet18 Un appel récursif, terminal Exemple : Exemple : – Soient des couples ( x, y ) avec x >= y. – On définit le « pgcd » comme ci-dessous. – La notation est proche dune notation mathématique. res <- pgcd( ( a, b ) ) pgcd ( ( m, n ) ) = si ( n = 0 ) si ( n = 0 ) m sinon sinon pgcd( ( n, m % n ) ) pgcd( ( n, m % n ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet19 Un appel récursif, terminal Ce programme est de la forme : Ce programme est de la forme : Les calculs réalisés sont les suivants : Les calculs réalisés sont les suivants : – Si ( v ) alors a( v ) et sinon : – Si ( ( v ) ) alors a( ( v ) ) et sinon : – Si ( ( ( v ) ) ) alors a( ( ( v ) ) ), etc... res <- pgcd( ( a, b ) ) pgcd ( ( m, n ) ) = si ( n = 0 ) si ( n = 0 ) m sinon sinon pgcd( ( n, m % n ) ) pgcd( ( n, m % n ) ) res <- f( v ) f ( x ) = si ( ( x ) ) si ( ( x ) ) a( x ) a( x ) sinon sinon f( ( x ) ) f( ( x ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet20 Un appel récursif, terminal Ce programme est de la forme : Ce programme est de la forme : Le résultat final sera : Le résultat final sera : – a( ( v ) ) – avec k = i. i N et ( ( v ) ) – où i.... signifie « le plus petit i tel que ». k i I res <- pgcd( ( a, b ) ) pgcd ( ( m, n ) ) = si ( n = 0 ) si ( n = 0 ) m sinon sinon pgcd( ( n, m % n ) ) pgcd( ( n, m % n ) ) res <- f( v ) f ( x ) = si ( ( x ) ) si ( ( x ) ) a( x ) a( x ) sinon sinon f( ( x ) ) f( ( x ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet21 Un appel récursif, terminal Le programme itératif correspondant : Le programme itératif correspondant : Le résultat sera dailleurs le même : Le résultat sera dailleurs le même : – a( ( v ) ) avec k = i. i N et ( ( v ) ) ( m, n ) <- ( a, b ) while ( ( n = 0 ) ) ( m, n ) <- ( n, m % n ) ( m, n ) <- ( n, m % n ) res <- m k i I x <- v while ( ( x ) ) x <- ( x ) x <- ( x ) res <- a( x )
27 novembre 2006Cours d'algorithmique 7 - Intranet22 Un appel récursif, terminal Léquivalence se généralise à deux ou plusieurs arguments : Léquivalence se généralise à deux ou plusieurs arguments : x <- v y <- w while ( ( x, y ) ) m <- ( x, y ) m <- ( x, y ) y <- ( x, y ) y <- ( x, y ) x <- m x <- m res <- a( x, y ) res <- f( v, w ) f ( x, y ) = si ( ( x, y ) ) si ( ( x, y ) ) a( x, y ) a( x, y ) sinon sinon f( ( x, y ), f( ( x, y ), ( x, y ) ) ( x, y ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet23 Un appel récursif, non-terminal Un appel récursif ayant une enveloppe « h ». Un appel récursif ayant une enveloppe « h ». Nous avons la forme générale suivante : Nous avons la forme générale suivante : Si « h » est associative, nous pouvons nous ramener au cas récursif terminal précédent, Si « h » est associative, nous pouvons nous ramener au cas récursif terminal précédent, et donc nous ramener à une itération sans pile. et donc nous ramener à une itération sans pile. res <- f( v ) f ( x ) = si ( ( x ) ) si ( ( x ) ) a( x ) a( x ) sinon sinon h( ( x ), f( ( x ) ) ) h( ( x ), f( ( x ) ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet24 Un appel récursif, non-terminal Posons simplement : Posons simplement : F( acc, x ) = h( acc, f( x ) ) F( acc, x ) = h( acc, f( x ) ) Pourquoi ? ? ? Parce que ça marchera ! ! ! Pourquoi ? ? ? Parce que ça marchera ! ! ! Déjà, si « h » admet le neutre « e » : Déjà, si « h » admet le neutre « e » : f( x ) = h( e, f( x ) ) = F( e, x ) f( x ) = h( e, f( x ) ) = F( e, x ) – Nous pouvons donc remplacer lappel f( v ) par lappel f( v ) par lappel F( e, v ) lappel F( e, v ) res <- f( v ) f ( x ) =......
27 novembre 2006Cours d'algorithmique 7 - Intranet25 Un appel récursif, non-terminal Il reste à exprimer F à laide delle-même et de manière récursive terminale. h est associative. Nous avons : Il reste à exprimer F à laide delle-même et de manière récursive terminale. h est associative. Nous avons : F( acc, x ) = h( acc, f( x ) ) F( acc, x ) = h( acc, f( x ) ) = h( acc, si ( ( x ) ) = h( acc, si ( ( x ) ) a( x ) a( x ) sinon sinon h( ( x ), f( ( x ) ) ) ) h( ( x ), f( ( x ) ) ) ) = si ( ( x ) ) = si ( ( x ) ) h( acc, a( x ) ) h( acc, a( x ) ) sinon sinon h( acc, h( ( x ), f( ( x ) ) ) ) h( acc, h( ( x ), f( ( x ) ) ) ) h( h( acc, ( x ) ), f( ( x ) ) ) h( h( acc, ( x ) ), f( ( x ) ) ) F( h( acc, ( x ) ), ( x ) ) F( h( acc, ( x ) ), ( x ) ) « f » est remplacée par sa définition ! Associativité ! Définition de F ! F( a, b ) = h( a, f( b ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet26 Un appel récursif, non-terminal Pour h est associative, nous obtenons donc la définition récursive terminale suivante : Pour h est associative, nous obtenons donc la définition récursive terminale suivante : F( acc, x ) = si ( ( x ) ) F( acc, x ) = si ( ( x ) ) h( acc, a( x ) ) h( acc, a( x ) ) sinon sinon F( h( acc, ( x ) ), ( x ) ) F( h( acc, ( x ) ), ( x ) ) Le programme itératif correspondant : Le programme itératif correspondant : while ( ( x ) ) acc <- h( acc, ( x ) ) acc <- h( acc, ( x ) ) x <- ( x ) x <- ( x ) res <- h( acc, a( x ) ) Doù le nom daccumulateur !
27 novembre 2006Cours d'algorithmique 7 - Intranet27 Un appel récursif, non-terminal Linitialisation est simple si « h » admet un neutre « e » : Linitialisation est simple si « h » admet un neutre « e » : f( v ) = F( e, v ) f( v ) = F( e, v ) x <- v acc <- e while ( ( x ) ) acc <- h( acc, ( x ) ) acc <- h( acc, ( x ) ) x <- ( x ) x <- ( x ) res <- h( acc, a( x ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet28 Un appel récursif, non-terminal Léquivalence se généralise à deux ou plusieurs arguments : Léquivalence se généralise à deux ou plusieurs arguments : res <- f( v, w ) f ( x, y ) = si ( ( x, y ) ) si ( ( x, y ) ) a( x, y ) a( x, y ) sinon sinon h( ( x, y ), h( ( x, y ), f( ( x, y ), f( ( x, y ), ( x, y ) ) ) ( x, y ) ) ) x <- v y <- w acc <- e while ( ( x, y ) ) acc <- h( acc, ( x, y ) ) acc <- h( acc, ( x, y ) ) m <- ( x, y ) m <- ( x, y ) y <- ( x, y ) y <- ( x, y ) x <- m x <- m res <- h( acc, a( x, y ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet29 Un appel récursif, non-terminal Cest un peu plus embêtant si « h » est sans neutre. Nous devons alors traiter le premier appel « f( v ) » à part, en observant que : Cest un peu plus embêtant si « h » est sans neutre. Nous devons alors traiter le premier appel « f( v ) » à part, en observant que : f( x ) = si ( ( x ) ) f( x ) = si ( ( x ) ) a( x ) a( x ) sinon sinon h( ( x ), f( ( x ) ) ) h( ( x ), f( ( x ) ) ) F( ( x ), ( x ) ) F( ( x ), ( x ) ) x <- v si ( ( x ) ) res <- a( x ) res <- a( x )sinon acc <- ( x ) acc <- ( x ) x <- ( x ) x <- ( x ) while ( ( x ) ) while ( ( x ) ) acc <- h( acc, ( x ) ) acc <- h( acc, ( x ) ) x <- ( x ) x <- ( x ) res <- h( acc, a( x ) ) res <- h( acc, a( x ) ) Le premier élément est traité à part ! Et nous passons au second élément !
27 novembre 2006Cours d'algorithmique 7 - Intranet30 Un appel récursif, non-terminal Léquivalence se généralise à deux ou plusieurs arguments : Léquivalence se généralise à deux ou plusieurs arguments : res <- f( v, w ) f ( x, y ) = si ( ( x, y ) ) si ( ( x, y ) ) a( x, y ) a( x, y ) sinon sinon h( ( x, y ), h( ( x, y ), f( ( x, y ), f( ( x, y ), ( x, y ) ) ) ( x, y ) ) ) x <- v y <- w si ( ( x, y ) ) res <- a( x, y ) res <- a( x, y )sinon acc <- ( x, y) acc <- ( x, y) m <- ( x, y ) m <- ( x, y ) y <- ( x, y ) y <- ( x, y ) x <- m x <- m while ( ( x, y ) ) while ( ( x, y ) ) acc <- h( acc, ( x, y) ) acc <- h( acc, ( x, y) ) m <- ( x, y ) m <- ( x, y ) y <- ( x, y ) y <- ( x, y ) x <- m x <- m res <- h( acc, a( x ) ) res <- h( acc, a( x ) )
27 novembre 2006Cours d'algorithmique 7 - Intranet31 Sous forme darbre … = ou bien = ou bien h ( v ) ( v ) { h f( ( v ) ) h ( v ) ( v ) a( ( v ) ) h ( ( v ) ) ( ( v ) ) f( ( ( v ) ) ) Une valeur ! Une autre valeur ! h e f( v ) Une valeur initiale !
27 novembre 2006Cours d'algorithmique 7 - Intranet32 Le cas général --- h non associative f( v ) = h ( v ) ( v ) h ( v ) ) ( v ) ) f( ( v ) ) = a( ( v ) ) h ( ( v ) ) ( ( v ) )... kk-1 Soit k = i. i N et ( ( v ) ) i I k res < - a( ( v ) ) k res < - h( ( v ) ), res ) k-1
27 novembre 2006Cours d'algorithmique 7 - Intranet33 Le cas général --- h non associative f( v ) = h ( v ) ( v ) h ( v ) ) ( v ) ) f( ( v ) ) h ( ( v ) ) ( ( v ) )... kk-1 Ou sont-elles ? ? ? Sur la pile, voyons ! La pile... ( ( v ) ) ( ( v ) ) ( v ) ( v ) La dynamique ! ! ! ( v ) ) ( v ) ) k-1 res < - a( ( v ) ) k
27 novembre 2006Cours d'algorithmique 7 - Intranet34 Le cas général --- h non associative f( v ) = h ( v ) ( v ) h ( v ) ) ( v ) ) f( ( v ) ) h ( ( v ) ) ( ( v ) )... kk-1 Ou sont-elles ? ? ? Sur la pile, voyons ! La pile La dynamique ! ! ! res < - a( ( v ) ) k res < - h( ( v ) ) res ) k-1 res < - h( ( v ) res )
27 novembre 2006Cours d'algorithmique 7 - Intranet35 Le cas général --- h non associative Légende des fonctions : - I( ) initialise une pile vide. - E( e, p ) empile e sur p. - S( p ) rend une copie du sommet de p. - D( p ) supprime le sommet de p. - V( p ) dit si p est vide ou non. p <- I() x <- v while ( ( x ) ) p <- E( ( x ), p ) p <- E( ( x ), p ) x <- ( x ) x <- ( x ) res <- a( x ) while ( V( p ) ) res <- h( S( p ), res ) res <- h( S( p ), res ) p <- D( p ) p <- D( p ) V( I() ) = VRAI V( E( e, p ) ) = FAUX S( E( e, p ) ) = e D( E( e, p ) ) = p
27 novembre 2006Cours d'algorithmique 7 - Intranet36 Le cas général --- h non associative p <- I() x <- v while ( ( x ) ) p <- E( ( x ), p ) p <- E( ( x ), p ) x <- ( x ) x <- ( x ) res <- a( x ) while ( V( p ) ) res <- h( S( p ), res ) res <- h( S( p ), res ) p <- D( p ) p <- D( p ) Linitialisation ! Nous descendons en empilant les différents éléments. La valeur initiale du résultat ! Nous remontons en combinant les éléments empilés !
27 novembre 2006Cours d'algorithmique 7 - Intranet37 Un appel récursif, non-terminal Léquivalence se généralise à 2 ou plus darguments pour f : Léquivalence se généralise à 2 ou plus darguments pour f : res <- f( v, w ) f ( x, y ) = si ( ( x, y ) ) si ( ( x, y ) ) a( x, y ) a( x, y ) sinon sinon h( ( x, y ), h( ( x, y ), f( ( x, y ), f( ( x, y ), ( x, y ) ) ) ( x, y ) ) ) p <- I() x <- v y <- w while ( ( x, y ) ) p <- E( ( x, y ), p ) p <- E( ( x, y ), p ) m <- ( x, y ) m <- ( x, y ) y <- ( x, y ) y <- ( x, y ) x <- m x <- m res <- a( x, y ) while ( V( p ) ) res <- h( S( p ), res ) res <- h( S( p ), res ) p <- D( p ) p <- D( p )
27 novembre 2006Cours d'algorithmique 7 - Intranet38 Un appel récursif, non-terminal Léquivalence se généralise à 3 ou plus darguments pour h : Léquivalence se généralise à 3 ou plus darguments pour h : res <- f( v ) f ( x ) = si ( ( x ) ) si ( ( x ) ) a( x ) a( x ) sinon sinon h( ( x ), h( ( x ), ( x ), ( x ), f( ( x ) ) ) f( ( x ) ) ) p <- I() x <- v while ( ( x ) ) p <- E( ( x ), p ) p <- E( ( x ), p ) x <- ( x ) x <- ( x ) res <- a( x ) while ( V( p ) ) m <- S( p ) m <- S( p ) p <- D( p ) p <- D( p ) res <- h( S( p ), m, res ) res <- h( S( p ), m, res ) p <- D( p ) p <- D( p )
27 novembre 2006Cours d'algorithmique 7 - Intranet39 Un appel récursif --- résumé ) Appel récursif terminal ! 1) Appel récursif terminal ! x <- v while ( ( x ) ) x <- ( x ) x <- ( x ) res <- a( x ) res <- f( v ) f ( x ) = si ( ( x ) ) si ( ( x ) ) a( x ) a( x ) sinon sinon f( ( x ) ) f( ( x ) ) Code itératif raisonnable !
27 novembre 2006Cours d'algorithmique 7 - Intranet40 Un appel récursif --- résumé ) Appel récursif avec enveloppe associative et neutre « e » ! 2) Appel récursif avec enveloppe associative et neutre « e » ! res <- f( v ) f ( x ) = si ( ( x ) ) si ( ( x ) ) a( x ) a( x ) sinon sinon h( ( x ), f( ( x ) ) ) h( ( x ), f( ( x ) ) ) x <- v acc <- e while ( ( x ) ) acc <- h( acc, ( x ) ) acc <- h( acc, ( x ) ) x <- ( x ) x <- ( x ) res <- h( acc, a( x ) ) Code itératif raisonnable !
27 novembre 2006Cours d'algorithmique 7 - Intranet41 x <- v si ( ( x ) ) res <- a( x ) res <- a( x )sinon acc <- ( x ) acc <- ( x ) x <- ( x ) x <- ( x ) while ( ( x ) ) while ( ( x ) ) acc <- h( acc, ( x ) ) acc <- h( acc, ( x ) ) x <- ( x ) x <- ( x ) res <- h( acc, a( x ) ) res <- h( acc, a( x ) ) Un appel récursif --- résumé ) Appel récursif avec enveloppe associative, sans neutre ! 3) Appel récursif avec enveloppe associative, sans neutre ! res <- f( v ) f ( x ) = si ( ( x ) ) si ( ( x ) ) a( x ) a( x ) sinon sinon h( ( x ), f( ( x ) ) ) h( ( x ), f( ( x ) ) ) Code itératif à peu près raisonnable !
27 novembre 2006Cours d'algorithmique 7 - Intranet42 Un appel récursif --- résumé ) Appel récursif avec enveloppe non associative ! 4) Appel récursif avec enveloppe non associative ! res <- f( v ) f ( x ) = si ( ( x ) ) si ( ( x ) ) a( x ) a( x ) sinon sinon h( ( x ), f( ( x ) ) ) h( ( x ), f( ( x ) ) ) Code itératif non raisonnable à cause de la gestion de pile ! p <- I() x <- v while ( ( x ) ) p <- E( ( x ), p ) p <- E( ( x ), p ) x <- ( x ) x <- ( x ) res <- a( x ) while ( V( p ) ) res <- h( S( p ), res ) res <- h( S( p ), res ) p <- D( p ) p <- D( p )