Algo by AzedinLeMeknessi

VIEWS: 6 PAGES: 179

									             e
    Universit´ de Provence (Aix-Marseille I)




  e
El´ments d’algorithmique en Java




       Solange COUPET-GRIMAL
                                                                                                   i

                       a      e
    Ce cours s’adresse ` des d´butants. Les notions essentielles de programmation y sont introduites
     c
de fa¸on volontairement non exhaustive, dans le but de favoriser une acquisition correcte, rapide
                                           e          a
et intuitive des concepts fondamentaux n´cessaires ` la mise en œuvre en Java des algorithmes
e    e
´tudi´s par la suite.
ii
              e
Table des mati`res

I         e
    Pour d´buter rapidement en Java                                                                                                           1

1 Fondements                                                                                                                                   3
           e                    e
  1.1 Repr´sentation des donn´es en machine . . . . . . . . . . . . . . .                                 .   .   .   .   .   .   .   .   .    4
                   e
      1.1.1 La m´moire . . . . . . . . . . . . . . . . . . . . . . . . . . .                              .   .   .   .   .   .   .   .   .    4
                            e                   e       e
      1.1.2 Codage en m´moire d’une donn´e : pr´sentation informelle                                      .   .   .   .   .   .   .   .   .    4
      1.1.3 Types simples et chaˆ                  e
                                    ınes de caract`res . . . . . . . . . . . .                            .   .   .   .   .   .   .   .   .    6
  1.2 Les variables et l’affectation . . . . . . . . . . . . . . . . . . . . . .                           .   .   .   .   .   .   .   .   .    7
               e
      1.2.1 D´claration de variable . . . . . . . . . . . . . . . . . . . .                               .   .   .   .   .   .   .   .   .    7
      1.2.2 Affectation . . . . . . . . . . . . . . . . . . . . . . . . . . .                              .   .   .   .   .   .   .   .   .    8
               e
      1.2.3 D´claration avec initialisation . . . . . . . . . . . . . . . . .                             .   .   .   .   .   .   .   .   .    8
      1.2.4 Signification du nom de la variable . . . . . . . . . . . . . .                                .   .   .   .   .   .   .   .   .    8
      1.2.5 Optimisations d’affectations . . . . . . . . . . . . . . . . . .                               .   .   .   .   .   .   .   .   .    9
      1.2.6 Retour sur les types simples : conversions . . . . . . . . . .                                .   .   .   .   .   .   .   .   .    9
  1.3 Instructions et expressions . . . . . . . . . . . . . . . . . . . . . . .                           .   .   .   .   .   .   .   .   .   10
      1.3.1 L’affectation comme expression . . . . . . . . . . . . . . . .                                 .   .   .   .   .   .   .   .   .   10
                                    e                  e e
      1.3.2 Cas particulier : incr´mentations et d´cr´mentations . . . .                                  .   .   .   .   .   .   .   .   .   11
                           a
      1.3.3 Expressions ` effet de bord . . . . . . . . . . . . . . . . . .                                .   .   .   .   .   .   .   .   .   11
  1.4 Blocs et boucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                          .   .   .   .   .   .   .   .   .   11
      1.4.1 Blocs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                             .   .   .   .   .   .   .   .   .   12
      1.4.2 Les instructions de branchement . . . . . . . . . . . . . . .                                 .   .   .   .   .   .   .   .   .   12
      1.4.3 Instructions while et do-while . . . . . . . . . . . . . . . . .                              .   .   .   .   .   .   .   .   .   14
      1.4.4 Instruction for . . . . . . . . . . . . . . . . . . . . . . . . .                             .   .   .   .   .   .   .   .   .   15

2 Java sans objet                                                                                                                             19
  2.1 Programme minimum . . . . . . . . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   19
                  e
      2.1.1 La m´thode main . . . . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   19
      2.1.2 Les arguments d’un programme . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   20
            e
  2.2 Les m´thodes de classes (ou statiques) . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   21
      2.2.1 Etude d’un exemple . . . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   21
      2.2.2 Commentaires dans un programme . .            .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   22
      2.2.3 Nombres en argument de programme .            .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   22
                                    e
      2.2.4 Radiographie de la m´thode pgcd . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   22
                       c
      2.2.5 Comment ¸a marche ? . . . . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   23
                                 e
      2.2.6 Passage des param`tres par valeur . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   24
               u                     e
      2.2.7 Coˆt d’un appel de m´thode . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   24
      2.2.8 L’instruction return . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   25
               e
  2.3 Modularit´ . . . . . . . . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   25
  2.4 Conclusion provisoire . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   26

                                                  iii
iv                                                                                                                 `
                                                                                                     TABLE DES MATIERES

3 Les objets                                                                                                                                         27
                                         e
  3.1 Classes comme structures de donn´es . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   27
      3.1.1 Qu’est ce qu’un objet ? . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   27
               e
      3.1.2 D´finir les classes d’objets . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   27
                e
      3.1.3 Cr´er des objets . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   29
                        ıtes a        e
  3.2 Classes comme boˆ ` outils s´curis´es e        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   31
               e
      3.2.1 M´thodes d’instance . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   31
      3.2.2 Privatisation des champs . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   32
                   e
      3.2.3 La m´thode toString . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   33
  3.3 Variables statiques et constantes . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   34
      3.3.1 this implicite . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   36

4 Les tableaux                                                                                                                                       37
               a
  4.1 Tableaux ` une dimension . . . . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   37
              e
       4.1.1 D´clarations . . . . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   37
               e
       4.1.2 Cr´ation . . . . . . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   37
       4.1.3 Taille et indexation d’un tableau .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   38
                u          e
       4.1.4 Coˆt des acc`s . . . . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   38
       4.1.5 Tableaux d’objets . . . . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   39
               a
  4.2 Tableaux ` plusieurs dimensions . . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   39
              e                e
       4.2.1 D´claration et cr´ation . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   39
                u          e
       4.2.2 Coˆt des acc`s . . . . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   41
                                           e
  4.3 Effet du codage sur l’occupation m´moire            .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   41
                         e
       4.3.1 Puissance ´gyptienne . . . . . . . .        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   42
       4.3.2 Puissance d’une matrice . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   42


II     e
     El´ments d’algorithmique                                                                                                                        47
              e e
5 Principes g´n´raux                                                                                                                                 49
  5.1 Qu’est-ce qu’un algorithme ? . . . . . . . . . . . . . .                   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   49
               e
      5.1.1 D´finition informelle . . . . . . . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   49
                                                       e
      5.1.2 Les grandes questions de la calculabilit´ . . .                      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   49
                      e           e
      5.1.3 Le probl`me de l’arrˆt . . . . . . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   50
      5.1.4 Pour conclure . . . . . . . . . . . . . . . . . .                    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   52
  5.2 Conception et expression d’algorithmes . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   53
                e                   e
      5.2.1 Sp´cification du probl`me . . . . . . . . . . .                       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   53
                                                 e
      5.2.2 Analyse descendante - Modularit´ . . . . . .                         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   53
      5.2.3 Correction de l’algorithme . . . . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   54
                    e
  5.3 Algorithmes r´cursifs . . . . . . . . . . . . . . . . . .                  .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   56
      5.3.1 Introduction sur des exemples . . . . . . . . .                      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   56
                                              e
      5.3.2 Terminaison des algorithmes r´cursifs . . . .                        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   58
                                           e
      5.3.3 Correction des algorithme r´cursifs . . . . . .                      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   60
                 e
  5.4 Complexit´ . . . . . . . . . . . . . . . . . . . . . . .                   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   61
                 u              e
      5.4.1 Coˆt et complexit´ en temps d’un algorithme                          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   62
      5.4.2 Asymptotique . . . . . . . . . . . . . . . . . .                     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   65

                e
6 Structures s´quentielles                                                                                                                           75
  6.1 Les listes . . . . . . . . . . . . . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   75
      6.1.1 Description abstraite . . . . . . . . . .            .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   75
      6.1.2 Mise en œuvre par des tableaux . . . .               .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   76
      6.1.3 Mise en œuvre par des listes chaˆ ees  ın´           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   80
                                      e
      6.1.4 Cas des listes ordonn´es . . . . . . . .             .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   84
                                        ın´
      6.1.5 Tableaux ou listes chaˆ ees ? . . . . .              .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   88
              `
TABLE DES MATIERES                                                                                                                                   v

   6.2   Les piles . . . . . . . . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    89
         6.2.1 Description abstraite . . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    89
                              a
         6.2.2 Application ` la fonction d’Ackermann           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    89
         6.2.3 Mise en œuvre par des tableaux . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    91
         6.2.4 Mise en œuvre par des listes chaˆ ees ın´       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    92
                              a
         6.2.5 Application ` la fonction d’Ackermann           .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    93
   6.3   Les files d’attente . . . . . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    93
         6.3.1 Description abstraite . . . . . . . . . .       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    93
         6.3.2 Mise en œuvre par des tableaux . . . .          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    94
         6.3.3 Mise en œuvre par des listes chaˆ ees ın´       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    96
   6.4   Exercices . . . . . . . . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .    98

7 Structures arborescentes                                                                                                                         101
         e
  7.1 Pr´sentation informelle . . . . . . . . . . . . . . . . . . . . . . . .                              .   .   .   .   .   .   .   .   .   .   101
  7.2 Les arbres binaires . . . . . . . . . . . . . . . . . . . . . . . . . .                              .   .   .   .   .   .   .   .   .   .   102
      7.2.1 Description abstraite . . . . . . . . . . . . . . . . . . . . .                                .   .   .   .   .   .   .   .   .   .   102
      7.2.2 Les divers parcours d’arbres binaires . . . . . . . . . . . .                                  .   .   .   .   .   .   .   .   .   .   103
      7.2.3 Mise en œuvre en Java . . . . . . . . . . . . . . . . . . . .                                  .   .   .   .   .   .   .   .   .   .   106
  7.3 Arbres binaires de recherche (ABR) . . . . . . . . . . . . . . . .                                   .   .   .   .   .   .   .   .   .   .   107
      7.3.1 Recherche . . . . . . . . . . . . . . . . . . . . . . . . . . .                                .   .   .   .   .   .   .   .   .   .   108
      7.3.2 Adjonction aux feuilles . . . . . . . . . . . . . . . . . . . .                                .   .   .   .   .   .   .   .   .   .   108
                          a
      7.3.3 Adjonction ` la racine . . . . . . . . . . . . . . . . . . . .                                 .   .   .   .   .   .   .   .   .   .   109
      7.3.4 Suppression . . . . . . . . . . . . . . . . . . . . . . . . . .                                .   .   .   .   .   .   .   .   .   .   111
                e        e
  7.4 Complexit´ des op´rations sur les ABR . . . . . . . . . . . . . . .                                  .   .   .   .   .   .   .   .   .   .   113
      7.4.1 Instruments de mesure sur les arbres . . . . . . . . . . . .                                   .   .   .   .   .   .   .   .   .   .   113
                       e
      7.4.2 Arbres al´atoires de recherche (ABRn ) . . . . . . . . . . .                                   .   .   .   .   .   .   .   .   .   .   115
      7.4.3 Profondeur moyenne des nœuds dans ABRn . . . . . . .                                           .   .   .   .   .   .   .   .   .   .   118
      7.4.4 Profondeur moyenne des feuilles des ABRn . . . . . . . .                                       .   .   .   .   .   .   .   .   .   .   120
      7.4.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . .                                 .   .   .   .   .   .   .   .   .   .   123
                                                      e
  7.5 Arbres binaires parfaits partiellement ordonn´s (Tas) . . . . . . .                                  .   .   .   .   .   .   .   .   .   .   124
      7.5.1 Codage d’un arbre binaire parfait . . . . . . . . . . . . . .                                  .   .   .   .   .   .   .   .   .   .   124
      7.5.2 Tas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .                              .   .   .   .   .   .   .   .   .   .   125
      7.5.3 Suppression du minimum d’un tas . . . . . . . . . . . . .                                      .   .   .   .   .   .   .   .   .   .   125
      7.5.4 Transformation d’une liste en tas . . . . . . . . . . . . . .                                  .   .   .   .   .   .   .   .   .   .   127
                               ee       a
      7.5.5 Adjonction d’un ´l´ment ` un tas . . . . . . . . . . . . . .                                   .   .   .   .   .   .   .   .   .   .   128
      7.5.6 Application : Tri par Tas . . . . . . . . . . . . . . . . . .                                  .   .   .   .   .   .   .   .   .   .   129
  7.6 Les arbres quelconques . . . . . . . . . . . . . . . . . . . . . . . .                               .   .   .   .   .   .   .   .   .   .   130
      7.6.1 Description abstraite . . . . . . . . . . . . . . . . . . . . .                                .   .   .   .   .   .   .   .   .   .   130
      7.6.2 Parcours d’arbres quelconques . . . . . . . . . . . . . . .                                    .   .   .   .   .   .   .   .   .   .   131
      7.6.3 Mise en œuvre en Java de la structure d’arbre quelconque                                       .   .   .   .   .   .   .   .   .   .   135

8 Les  graphes                                                                                                                                     137
  8.1     e
       Pr´sentation informelle . . . . . . . . .    . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   137
  8.2  Terminologie . . . . . . . . . . . . . .     . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   138
  8.3  Parcours de graphes . . . . . . . . . .      . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   140
       8.3.1 Parcours en profondeur . . . .         . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   141
       8.3.2 Parcours en largeur . . . . . .        . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   142
            e
   8.4 Repr´sentations des graphes . . . . . .      . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   143
       8.4.1 Les matrices d’adjacence . . . .       . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   143
       8.4.2 Les listes d’adjacence . . . . .       . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   145
                      e
   8.5 Graphes orient´s acycliques (dag) . . .      . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   147
       8.5.1 Tri topologique . . . . . . . . .      . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   148
               e
       8.5.2 D´composition par niveaux . .          . . . . . .        . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   149
   8.6 Recherche de plus courts chemins dans        un graphe          valu´e          .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   150
vi                                                                                                          `
                                                                                              TABLE DES MATIERES

         8.6.1   L’algorithme de Dijkstra . . . . . .   . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   150
         8.6.2   Correction de l’algorithme . . . . .   . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   151
         8.6.3             e
                 Complexit´ . . . . . . . . . . . . .   . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   153
         8.6.4   Une mise en œuvre de l’algorithme      en Java       .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   154

9 Algorithmique du tri                                                                                                                        159
           ee
  9.1 Tris ´l´mentaires . . . . . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   159
                          e
      9.1.1 Le tri par s´lection . . . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   159
      9.1.2 Le tri par insertion . . . . . . . . . . . .      .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   160
  9.2 Tri rapide . . . . . . . . . . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   160
  9.3 Tri fusion . . . . . . . . . . . . . . . . . . . . .    .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   166
  9.4 Tri par tas . . . . . . . . . . . . . . . . . . . . .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   167
                 e           e
  9.5 Complexit´ du probl`me du tri . . . . . . . . .         .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   167
                 e
  9.6 Complexit´ des algorithmes diviser pour r´gner e        .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   168
  9.7 Exercices . . . . . . . . . . . . . . . . . . . . .     .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   169
              e
         Premi`re partie

      e
Pour d´buter rapidement en Java




                1
Chapitre 1

Fondements

                               e
   Un ordinateur est constitu´ de divers modules :
                          e                                                  e
   – le processeur qui ´value des expressions (effectue des calculs) et qui ex´cute des instructions
           e                                     e
   – la m´moire dans laquelle sont enregistr´es les informations (les programmes, les donn´es   e
             e                         e               e
     et les r´sultats de calculs effectu´s sur ces derni`res)
           e      e                          e                                          e
   – les p´riph´riques, dont le clavier, l’´cran, les imprimantes, le disque dur, les r´seaux . . .
                                                                             e
Ces divers constituants communiquent entre eux au moyen de canaux appel´s bus.




                                Processeur
                                                     Mémoire centrale


                                 Clavier


                                  Ecran              Imprimantes, DD, réseaux ...




                             Figure 1.1 – structure d’un ordinateur




                                                           e a            e            e
Instructions et expressions Les programmes sont amen´s ` modifier l’´tat de la m´moire (en
                    e
y enregistrant des r´sultats par exemple) au moyen d’instructions. Dans la suite de ce document,
                                                                                        e
on appellera effet de bord toute action du processeur provoquant un changement de l’´tat de la
   e               e                         e                  e
m´moire (on dira d´sormais un changement d’´tat sans plus de pr´cision). Une instruction a donc,
sauf exception, un effet de bord.
         e                                                     e
Ils font ´galement effectuer par le processeur des calculs cod´s sous forme d’expressions. Par
                                                                     e
exemple, 2*3+2 et 5>8 sont des expressions que le processeur peut ´valuer pour produire 8 et
                          e                                                     e         e
false respectivement. L’´valuation de ces deux expressions ne modifie pas l’´tat de m´moire.
Elles sont sans effet de bord.

                                               e                       e              e     e
Les programmes prennent en compte des donn´es pour produire des r´sultats. Donn´es et r´sultats
         e                                                 e        e                  e
peuvent ˆtre de types simples (entiers, flottants, caract`res, bool´ens) ou structur´s (chaˆ  ınes de
      e                                                                  e       a       c
caract`res, tableaux, dictionnaires, fiches clients . . .). Avant de s’int´resser ` la fa¸on dont les
                    e e                          a
programmes sont ex´cut´s par le processeur et ` leurs effets, il convient donc de bien comprendre
                  e          e                 e             e
comment les donn´es et les r´sultats sont cod´s dans la m´moire.

                                                 3
4                                                                                                                    CHAPITRE 1. FONDEMENTS

1.1                  e                  e
                 Repr´sentation des donn´es en machine
1.1.1                e
                 La m´moire
     e                                             e                                   ee
La m´moire d’un ordinateur est une suite ordonn´e de bits. Un bit est un composant ´l´mentaire,
                        e                                 e         e
pouvant prendre deux ´tats et deux seulement, commun´ment not´s 0 et 1, tout comme un inter-
        e                                   e               e                 e                  e
rupteur ´lectrique n’a que deux positions : ´teint et allum´. Les bits de la m´moire sont regroup´s
par huit : un groupe de huit bits est un octet (byte en anglais).

    0    0   0   0   0   0   0   0   0    0   0    0   0   0   0   0   0    0   0   0   0   0   0   0   0    1   1   0   0   0   0   1




    64                               72                                80                               88


                                                                         e
                                                  Figure 1.2 – Une zone m´moire de 4 octets

                              e e          e                     e
Enfin, bits et octets sont num´rot´s. Ce num´ro d’ordre est appel´ adresse (du bit ou de l’octet).
                 e                      e              e
La figure 1.2 repr´sente une zone de la m´moire constitu´e des quatre octets d’adresses respectives
64, 72, 80, 88.

  e            e                      e                                           e
L’´tat de la m´moire est la suite des ´tats (0 ou 1) des bits qui la composent. L’´tat de la zone
  e           e  e
m´moire repr´sent´e sur la figure 1.2 est donc :

                                                       00000000000000000000000001100001


                                                                                 e
attention ! Tout comme un interrupteur est toujours dans une des deux positions “´teint”
           e               e                                e
ou “allum´”, une zone m´moire est toujours dans un certain ´tat. Il est donc absurde de
                                     e
dire qu’il n’y a rien dans une zone m´moire.




1.1.2                       e                e      e
                 Codage en m´moire d’une donn´e : pr´sentation informelle
                e                                         e     e
   Toute donn´e, aussi complexe soit-elle, est donc repr´sent´e in fine par une suite binaire. Inver-
                e                e                                              e              e
sement, il faut ˆtre capable de d´coder une suite binaire pour retrouver la donn´e qu’elle repr´sente.
                                   e
Examinons tout d’abord les donn´es de type simple.

                                        e                          e                   e
En ce qui concerne les entiers, une id´e naturelle est de les repr´senter par leur d´veloppement
                                                             ea                        e
en base 2. Pour prendre en compte le signe, on est amen´ ` utiliser un bit suppl´mentaire, dit
                                                                 e
bit de signe, qui est 0 pour les entiers positifs et 1 pour les n´gatifs. Par exemple, sachant que
97 = 1 + 25 + 26 , on peut envisager de coder l’entier 97 par 01100001 et l’entier -97 par 11100001.

En ce qui concerne les caract`res (symboles comme : a, A, 3, π, :, {, `, . . .), il existe un standard
                                e                                           a
                                           e                    e             e
international, qui est une liste ordonn´e de tous les caract`res, appel´e unicode. L’unicode est
   e                                                                                      e
tr`s complet. En informatique, on utilise souvent une sous-liste de l’unicode appel´e code ASCII.
                                            e            a
Le code ASCII contient tous les caract`res obtenus ` partir d’un clavier QWERTY (y compris
l’espace, le “retour chariot”, la tabulation et ceux obtenus par combinaison de touches comme les
                                                      e             e                 e
lettres majuscules), mais ne contient ni les caract`res accentu´s ni les caract`res des alphabets
                                                                                        e
autres que l’alphabet latin. Dans cette liste (ASCII ou unicode), chaque caract`re a un num´ro     e
                e      e                  e                           e
d’ordre bien d´termin´ et c’est donc l’´criture binaire de ce num´ro d’ordre qui code le caract`re.e
                              e                                                             ıtre
Voici ci-dessous, par curiosit´, la table ASCII. Dans la pratique, il est inutile de connaˆ ces tables.
         ´                  ´
1.1. REPRESENTATION DES DONNEES EN MACHINE                                                         5


        000    (nul)   016 (dle) 032 sp     048   0   064   @   080   P   096   ‘   112   p
        001    (soh)   017 (dc1) 033 !      049   1   065   A   081   Q   097   a   113   q
        002    (stx)   018 (dc2) 034 "      050   2   066   B   082   R   098   b   114   r
        003    (etx)   019 (dc3) 035 #      051   3   067   C   083   S   099   c   115   s
        004    (eot)   020 ¶(dc4) 036 $     052   4   068   D   084   T   100   d   116   t
        005    (enq)   021 §(nak) 037 %     053   5   069   E   085   U   101   e   117   u
        006    (ack)   022 (syn) 038 &      054   6   070   F   086   V   102   f   118   v
        007    (bel)   023 (etb) 039 ’      055   7   071   G   087   W   103   g   119   w
        008    (bs)    024 (can) 040 (      056   8   072   H   088   X   104   h   120   x
        009    (tab)   025 (em) 041 )       057   9   073   I   089   Y   105   i   121   y
        010    (lf)    026 (eof) 042 *      058   :   074   J   090   Z   106   j   122   z
        011    (vt)    027 (esc) 043 +      059   ;   075   K   091   [   107   k   123   {
        012    (np)    028 (fs) 060 <       076   L   092   \   108   l   198   l   124   |
        013    (cr)    029 (gs) 045 -       061   =   077   M   093   ]   109   m   125   }
        014    (so)    030 (rs) 046 .       062   >   078   N   094   ^   110   n   126   ~
        015    (si)    031 (us) 047 /       063   ?   079   O   095   _   111   o   127

                                                   e         a
              Table ASCII standard (codes de caract`res de 0 ` 127)


                                                                e    e
Ainsi, sachant que le code de la lettre a est 97, elle sera repr´sent´e par 01100001.

    e                   e e                        a                             e
Il r´sulte de ce qui pr´c`de que 01100001 code ` la fois l’entier 97 et le caract`re a. Se pose
              e        e              e e                                         ıtre
donc le probl`me du d´codage, en g´n´ral au moment de l’affichage. Pour reconnaˆ la donn´e     e
     e    e                                    e               ıtre                   e
repr´sent´e par 01100001, il est absolument n´cessaire de connaˆ le type de la donn´e que l’on
                                                 e
cherche : en l’occurrence, un entier ou un caract`re.

                           e                        e                             e
Il est donc essentiel de pr´ciser le type d’une donn´e au moment du codage et du d´codage.

                e                                                       e
Une source fr´quente de confusion provient du fait que si, dans les ´crits courants, le nombre en-
                                    e     ea
tier quatre-vingt dix sept est repr´sent´ ` l’aide de deux symboles 9 et 7, le nombre entier sept est
     e     e                                                          e                       e    e
repr´sent´ par un unique symbole 7. Ainsi l’entier sept et le caract`re 7 sont tous deux repr´sent´s
          e                        ıt´               e
par le mˆme symbole. L’ambigu¨ e est alors lev´e par le contexte. Dans la phrase 7 plus 11 font
                                                                 ee
18, il s’agit d’un nombre. Dans la phrase le nom de code du c´l`bre agent secret Hubert Bonisseur
                                                    e                    ıt´
de La Bath se termine par 7, il s’agit du caract`re. Une telle ambigu¨ e ne peut subsister dans les
                              e ae                           e e
programmes qui sont destin´s ` ˆtre automatiquement ex´cut´s sans prise en compte du contexte.
Les langages de programmation utilisent donc des notations permettant de faire le distinguo. En
Java :
                                     e                              e
                                 7 d´signe l’entier et ’7’ le caract`re
                                                      e   e
                                    la lettre a est d´sign´e par ’a’

      e e                     e        e    e                e
Plus g´n´ralement, tout caract`re est d´sign´ par un symbole ´crit entre deux apostrophes. Ainsi
                             e                                e                            e
’7’, dont le code ASCII (et ´galement unicode) est 55 d’apr`s la table ci-dessus, est cod´ par
010111 puisque 55 = 25 + 24 + 22 + 2 + 1. Alors que 7 est cod´ par 0111.
                                                             e

             e                        e                            c      e
Enfin, la m´moire d’un ordinateur ´tant de taille finie, on con¸oit ais´ment que l’on ne peut
    e                       e                                                  e               e
repr´senter toutes les donn´es. Par exemple, il est hors de question de repr´senter l’infinit´ des
                      e                  a
entiers relatifs. Et mˆme en s’en tenant ` une partie finie de cet ensemble, il n’est pas raisonnable
                                                                 e
de coder un entier tellement grand qu’il occuperait toute la m´moire, ce qui rendrait impossible
                e
toute autre op´ration.

                                           e         e                                 e
Ainsi, on convient de coder toutes les donn´es d’un mˆme type, sur un nombre d’octets d´pendant
                                                      e            e
uniquement du type. Par exemple, en Java, les caract`res sont cod´s sur 16 bits :
6                                                                          CHAPITRE 1. FONDEMENTS


                                   e    e
                       ’7’ est repr´sent´ en machine par 0000000000010111
                                   e    e
                       ’a’ est repr´sent´ en machine par 0000000001100001

                                         a                     e            e
Dans tout codage binaire, le bit le plus ` droite (bit des unit´s) est appel´ bit de poids faible.

1.1.3                         ınes de caract`res
          Types simples et chaˆ             e
                                  Taille
        Type    Signification                    Constantes               e
                                                                       Op´rateurs
                                 en bits
      char              e
                  caract`re        16      ’3’,’\n’, ’\’’      +       -    *   /       %
      byte                          8
      short                        16               97
                   entiers                                     +       -    *   /       %
       int                         32              -97
      long                         64
      float                        32      97.   0.97E2
                   flottants                                        +        -   *   /
     double                        64          9.7e1
     boolean          e
                  bool´ens          8       true false         &&          ||   !   ?:

                                 e                                   e
Pour les types entiers, les op´rateurs binaires / et % ont pour r´sultat respectivement le quo-
                                     e         e
tient et le reste de la division enti`re des op´randes. Par exemple : 7% 2 = 1 7/2 = 3 -7%2 = -1
                           e                                         e
-7/2 = -3. Lorsque les op´randes sont de type char, il s’agit de l’op´ration sur l’entier qui les code.

Les valeurs de type float sont ´valu´es avec une pr´cision de 10−6 , celles de type double avec
                                 e    e               e
une pr´cision de 10−15 . Les constantes (par exemple 0.97) sont consid´r´es comme ´tant de type
      e                                                               e e          e
                                         e
double. Pour en faire un float, il faut ´crire 0.97f.

     e            e        e                e                  e
L’op´rateur ! d´signe la n´gation et les op´rateurs && et || d´signent respectivement la conjonc-
                                                                      e
tion et la disjonction logiques. Il est bon de savoir que lors de l’´valuation d’une expression
     e                                                    e    e                        e
bool´enne de la forme e1 &&e2 , l’expression e2 n’est pas ´valu´e si e1 est fausse. De mˆme pour
e1 ||e2 si e1 est vraie.

  e
Op´rateurs de comparaison                                        e               e
                                    Les types simples supportent ´galement les op´rateurs suivants :

                                     >     >=     <      <=   == !=

              e                                              e       e
Ce sont des op´rateurs binaires qui renvoient une valeur bool´enne, r´sultat de la comparaison des
        e
deux op´randes.

                                                           e          e
Exemple 1.1 x!=0 && 1/x>10E3 est une expression bool´enne. Son ´valuation ne provoque ja-
                                e    e
mais d’erreur puisque 1/x n’est ´valu´ que si l’expression x!=0 a pour valeur true.

                                                           e
Le type String C’est un type un peu particulier qui d´signe les chaˆ                    e
                                                                         ınes de caract`res et qui
                                      ınes de caract`res sont not´es entre guillemets : "Ceci est
n’est donc pas un type simple. Les chaˆ             e            e
                                              e          e e                     e
une cha^ne de caract`res". Elles peuvent ˆtre concat´n´es au moyen de l’op´rateur binaire +
        ı               e
        e a e
et affich´es ` l’´cran au moyen de l’instruction :

                                                   ıne        e
                              System.out.print(<chaˆ de caract`res>)

ou encore
                                                 ıne        e
                          System.out.println(<chaˆ de caract`res>)
1.2. LES VARIABLES ET L’AFFECTATION                                                             7

          e                   a e             ıne        e
La deuxi`me instruction affiche ` l’´cran la chaˆ de caract`re suivie d’un retour chariot. Ainsi
les trois instructions :

System.out.println("Il y a dans les bois\nDes arbres fous d’oiseaux")
System.out.print("Il y a dans les bois\nDes arbres fous d’oiseaux\n")
System.out.print("Il y a dans les bois\n" +"Des arbres fous d’oiseaux\n")
provoquent l’affichage suivant (on y a fait figurer la position du curseur) :

Il y a dans les bois
Des arbres fous d’oiseaux


                e                                                  ınes caract`res.
Noter que les op´rateurs de comparaison ne s’appliquent pas aux chaˆ          e

     e                                                             a
L’op´rateur ternaire : ? Il permet de construire une expression ` partir d’une expression
    e
bool´enne et de deux expressions <expr1 > et <expr2 > comme suit :
                                     e
                           <expr bool´enne> ? <expr1 > : < expr2 >

                                                                     e
Cette expression a pour valeur celle de <expr1 > si l’expression bool´enne vaut true et celle
de <expr2 > sinon.
Exemple 1.2 System.out.print(x==0? "x est nul":"x est non nul")
                     a e
a pour effet d’afficher ` l’´cran x est nul ou x est non nul selon que la variable x est
nulle ou pas.

Affichage des valeurs de types simples Quand une telle valeur se trouve en lieu et place
         ıne        e                                                           e
d’une chaˆ de caract`res, elle est automatiquement convertie par Java en sa repr´sentation sous
forme de String.

                                                                                             e
Exemple 1.3 Supposons que a, b et d soient trois variables de type int et que l’on ait calcul´
                                     a
dans d le plus grand diviseur commun ` a et b. L’instruction :
               System.out.print("PGCD(" + a + ", " + b + ") = " + d)
       a e
affiche ` l’´cran :    PGCD(15, 12) = 3         si les valeurs de a et b sont respectivement 15 et
                     e                                                                          e
12 au moment de l’ex´cution de l’instruction. Java a en particulier converti l’entier quinze cod´
                                                ı                    e                    e
en machine par : 0000000000001111 en la chaˆne "15", des caract`res constituant son ´criture
            e    e                     ee
dans le syst`me d´cimal sans qu’il ait ´t´ besoin d’appeler pour cela une quelconque fonction de
conversion.


1.2     Les variables et l’affectation
                               e         e e a                           e                  e e
   Une variable est une zone m´moire d´di´e ` l’enregistrement d’une donn´e. Elle est caract´ris´e
par :
   – son adresse : celle du premier octet qu’elle occupe
                     e                            e
   – son type : il d´finit sa taille et le codage/d´codage
                           e    a                 e     e         e                      u
   – sa valeur : c’est son ´tat ` un instant donn´. Cet ´tat peut ´videmment varier, d’o` le nom
      de variable.

1.2.1     e
         D´claration de variable
                        e                         e     e    e       e
   Toute variable utilis´e dans un programme doit ˆtre d´clar´e au pr´alable comme suit.

                                 <type> < nom de variable> ;
8                                                                 CHAPITRE 1. FONDEMENTS

Exemple 1.4      int age;
                 char initiale;
                          e                                                      e
Dans un programme, la d´claration d’une variable provoque l’allocation en m´moire de l’espace
 e                              e                   e                            e            e
n´cessaire pour coder les donn´es du type indiqu´. L’adresse de la zone allou´e est associ´e au
                                  a                                      e
nom de la variable. A ce stade l`, la valeur de la variable existe (bien ´videmment, la variable se
                       e                   e       e        e            e      e         e
trouve dans un certain ´tat !) mais est ind´termin´e : la m´moire allou´e a pu ˆtre utilis´e par un
                         e e        e                                      e
autre programme puis lib´r´e en l’´tat. La variable est dite non initialis´e. Plusieurs variables de
  e                 e     e     e            e                               e e
mˆme type peuvent ˆtre d´clar´es simultan´ment. Leurs noms sont alors s´par´s par des virgules.
Exemple 1.5       float perimetre, surface ;

1.2.2     Affectation
                             a
   Une affectation consiste ` enregistrer une valeur dans une variable. Il s’agit donc d’une ins-
truction (effet de bord). La syntaxe des affectations est la suivante.

                             <nom de la variable> = <expression> ;

Exemple 1.6       age = 97;
                  initiale = ’a’;

   e    e                                   e
Apr`s ex´cution de ces deux instructions, l’´tat de la variable age est :

                               00000000000000000000000001100001

et celui de la variable initiale est : 0000000001100001.

1.2.3      e
          D´claration avec initialisation
                        e             e                   e
    Toute variable peut ˆtre initialis´e au moment de sa d´claration selon la syntaxe suivante :

                          <type> <nom de la variable> = <expression> ;

               e
Ainsi, on peut ´crire :
                                                                            int age;
                                                                            char initiale;
int age = 97;                                                               float surface;
char initiale = ’a’;                                                        float perimetre;
                                             au lieu de
float surface = 0.16,                                                       age = 97;
       e   e
      p´rim`tre = 1.6;                                                      initiale = ’a’;
                                                                            surface = 0.16;
                                                                            perimetre = 1.6;
                                                                       e
Attention : ne pas confondre l’affectation age = 97 et l’expression bool´enne age == 97.

1.2.4     Signification du nom de la variable
                              e      a
   Le nom d’une variable d´signe ` la fois son adresse et sa valeur. Dans une affectation, l’am-
    ıt´        e                       e
bigu¨ e est lev´e par le contexte. L’ex´cution de l’instruction :

                                        age = 2*age +1;

                                                                                 e        a
a pour effet de rajouter 1 au double de la valeur de age et d’enregistrer le r´sultat ` l’adresse
             a                                          e                                          a
age. Ainsi, ` gauche du signe d’affectation, le nom age d´signe l’adresse de la variable, tandis qu’`
           e
droite il d´signe sa valeur.
1.2. LES VARIABLES ET L’AFFECTATION                                                                 9

1.2.5    Optimisations d’affectations
Affectations de la forme x = x < op > <expression> ;
         e                                  e
   Consid´rons l’instruction x = x+2. Son ex´cution requiert :
                                                e       e   e
  1. le calcul de l’adresse de x pour aller en m´moire r´cup´rer sa valeur (lecture)
       e                          a
  2. l’´valuation de l’expression ` droite du signe d’affectation
     a                                                    e                      e
  3. ` nouveau le calcul de l’adresse de x pour aller en m´moire enregistrer le r´sultat obtenu
      e
     (´criture)
                                       a
On peut optimiser ce type d’affectation ` l’aide de la syntaxe suivante.

                                  <nom> <op>= <expression> ;

                 e e
Pour l’exemple pr´c´dent on obtient x+=2.
                        a                                                                       e
L’optimisation consiste ` n’effectuer le calcul de l’adresse qu’une seule fois (suppression de l’´tape
3). Ainsi,
   n = n/2;                                                       n /= 2;
   x = x*x;                                      e
                     sont avantageusement remplac´es par          x *= x;
   r = r * x;                                                     r *= x;

                         e                    e e
Cas particulier de l’incr´mentation et de la d´cr´mentation
                            e                   e                           e e
    Soit i une variable enti`re. On appelle incr´mentation (respectivement d´cr´mentation) de i
                                                    a
le fait de rajouter 1 (respectivement retrancher 1) ` sa valeur. Par exemple i=i+1; et i=i-1;
               e                  e e                        e          e e              e
sont une incr´mentation et une d´cr´mentation de i. D’apr`s ce qui pr´c`de, on peut am´liorer
       e
cette ´criture en i+=1; et i-=1; respectivement.

        e                e e           a e e               e e
Les incr´mentations et d´cr´mentations ` r´p´tition sont tr`s fr´quentes en programmation. Lors-
                            ee            e       a                                   a
qu’on parcourt une suite d’´l´ments index´e de 0 ` n, on fait varier un indice i de 0 ` n (respec-
              a             e                             e e            a          e
tivement de n ` 0) en l’incr´mentant (respectivement le d´cr´mentant) ` chaque it´ration. Il est
donc important d’essayer d’optimiser ces instructions.

                             e
On remarque alors qu’incr´menter un entier peut se faire beaucoup plus simplement qu’une addi-
                                                                e
tion normale qui demande d’examiner tous les bits des deux op´randes. Par exemple, si le bit de
                                                         e           e e
poids faible est 0, il suffit de changer ce bit en 1. De mˆme pour la d´cr´mentation. Si l’on utilise
       e
les op´rateurs + ou -, avec l’une ou l’autre des deux syntaxes ci-dessus, c’est bien l’algorithme
  e e                                                  e
g´n´ral d’addition et de soustraction qui sera effectu´. La syntaxe permettant un calcul optimis´  e
         e               e e
de l’incr´ment et du d´cr´ment est la suivante :


    e
Incr´ment :             <nom de la variable> ++ ;            ou          ++ <nom de la variable> ;



 e e
D´cr´ment :             <nom de la variable> - - ;          ou         - - <nom de la variable> ;

                                                        e
Ainsi par exemple, i++; et - -i; ont respectivement le mˆme effet de bord que i=i+1; et
i=i-1; mais sont beaucoup plus efficaces.

1.2.6    Retour sur les types simples : conversions
                                                    e
Conversions implicites Certains types peuvent ˆtre implicitement convertis en d’autres. Par
                         e                                                       e
exemple tout entier peut ˆtre converti implicitement en un flottant et tout caract`re en un entier.
              e
On peut ainsi ´crire :
10                                                                CHAPITRE 1. FONDEMENTS

float longueur = 2; short x = ’a’;
au lieu de
float longueur = 2.0f; short x = 97;



                                                     e        e
Conversions explicites Certaines conversions peuvent ˆtre forc´es (cast dans le jargon infor-
matique). La syntaxe est la suivante :

                                     ( <type>) <expression>


Exemple 1.7       (int) (9.7*x + 2.3)
                  (short) ’a’
                                              e                                          e
Dans le premier cas, on obtient la partie enti`re et dans le second, le code 97 du caract`re ’a’,
   e
cod´ sur 16 bits.

                                 ıtris´   e                                       e        e
Les conversions explicites mal maˆ ees rel`vent de l’acrobatie et sont fortement d´conseill´es
aux programmeurs non avertis.



1.3     Instructions et expressions
             e     e
    Comme ´voqu´ en introduction, on peut distinguer les instructions des expressions. Les ins-
                  e e                e                  e           e
tructions sont ex´cut´es et leur ex´cution affecte l’´tat de la m´moire, c’est ce que l’on appelle
                                     e    e           e
l’effet de bord. Les expressions sont ´valu´es et leur ´valuation par le processeur provoque un calcul
                             e
et produit, sauf erreur, un r´sultat.
Ce qui suit va remettre en cause cette distinction. En Java, comme en C, certaines instructions
                                      a                                   a
sont des expressions, en ce sens qu’` la fois elles indiquent un calcul ` effectuer et elles ont une
              e
valeur. De mˆme certaines expressions ont un effet de bord.



1.3.1    L’affectation comme expression
            e                             e
    Consid´rons l’expression 2*x+1. A l’´valuation, le processeur effectue une multiplication et une
                e                       e                             e                      e
addition. Le r´sultat obtenu est appel´ valeur de l’expression. La m´moire n’est pas affect´e par
ce calcul. Il n’y a pas d’effet de bord. L’expression n’est donc pas une instruction.

                                                                    e
En revanche, les affectations sont des instructions. Par exemple l’ex´cution de l’instruction
x = 2*x+1; provoque :
       e
   - l’´valuation de l’expression 2*x+1
                                    e a
   - l’affectation de la valeur trouv´e ` x (effet de bord)

             e
En Java (et ´galement en C), une affectation a non seulement un effet de bord, mais ´galemente
                                       e a
une valeur : celle de l’expression situ´e ` droite du signe d’affectation. En ce sens, c’est aussi une
expression.
                                   
                                    type : celui de x
                   x = < expr >         valeur : la valeur v de <expr>
                                   
                                        effet de bord : affectation de v ` x   a

En supposant par exemple que x et y sont deux variables de type int et de valeurs respec-
                                e                                           ee
tives 3 et 5, il est possible d’´crire : x = y =2*(x+y). Ce code est interpr´t´ comme suit :
1.4. BLOCS ET BOUCLES                                                                            11

x   = y      =   2 ∗ (x + y);
                 effet de bord : aucun
                 valeur : v1 = 16

                                        a
        effet de bord : affectation de v1 ` y
        valeur : v2 = v1

                                a
effet de bord : affectation de v2 ` x
valeur : v3 = v2

       e                                e a         a
Il en r´sulte que la valeur 16 est affect´e ` y puis ` x.


1.3.2                           e               e e
          Cas particulier : incr´mentations et d´cr´mentations
                               e                  e e
    Examinons le cas des incr´mentations et d´cr´mentations efficaces d’une variable i de type
                                                    e                                     e
entier. Les instructions i++; et ++i; ont le mˆme effet de bord : elles changent l’´tat de la
                          a                             e e
variable i en rajoutant 1 ` sa valeur. Toutefois, consid´r´es comme des expressions, elles n’ont pas
     e                                                           e
la mˆme valeur. L’expression i++ a la valeur de i avant incr´mentation tandis que l’expression
                          e      e
++i a la valeur de i apr`s incr´mentation.

                        e                                                                     e
Le tableau ci-dessous r´sume les effets de bords et les valeurs des quatre instructions concern´es
             u
dans le cas o` la valeur initiale de la variable est 5.

                    i avant     expression             e
                                                  i apr`s
                                                             valeur de l’expression
                     e
                   ex´cution    instruction   effet de bord
                       5            i++             6                  5
                       5            ++i             6                  6
                       5            i- -            4                  5
                       5            - -i            4                  4


1.3.3                 a
          Expressions ` effet de bord
                      e                      a                                            e
   On peut maintenant ´crire des expressions ` effet de bord, dont le tableau ci-dessous pr´sente
quelques exemples.

                  expression      effet de bord           valeur de l’expression
                   i++ < n            e
                                  incr´mente       true si (ancienne valeur de i) < n
                                  la variable i    false sinon
                  - -i = = O        e e
                                  d´cr´mente       true si (nouvelle valeur de i) = 0
                                  la variable i    false sinon
                 (x /=2) != 1     divise par 2     true si (nouvelle valeur de x) =1
                                  la variable x    false sinon

                                 a                   e
Le maniement de ces expressions ` effet de bord est d´licat. Il requiert une bonne connaissance de
    e
la s´mantique des expressions et des instructions Java et une grande rigueur.


1.4      Blocs et boucles
                                    e         a e
    Les seules instructions rencontr´es jusqu’` pr´sent sont des affectations, sous des formes di-
verses. Toutes se terminent par un point-virgule. Le but de cette section est d’introduire des
instructions plus complexes.
12                                                                CHAPITRE 1. FONDEMENTS

1.4.1    Blocs
                                     e                                               e
   Les blocs permettent de composer s´quentiellement une suite d’instructions en les ´crivant entre
                             e e
accolades. Un bloc est consid´r´ comme une unique instruction.

Exemple 1.8       {r = a%b;
                   a = b;
                   b = r;}

                                 e
Un bloc peut aussi contenir des d´clarations de variables. Ces variables sont locales au bloc. Elles
        ee                    e                         e     e               e               e
sont cr´´es au moment de l’ex´cution du bloc et restitu´es d`s la fin de l’ex´cution. Il en r´sulte
                    e                      a     e
qu’elles sont bien ´videmment inconnues ` l’ext´rieur du bloc. Si une variable locale porte le
  e                                           e             e
mˆme nom qu’un variable globale, cette derni`re est masqu´e (donc inconnue) dans le bloc par la
 e
d´claration locale.

Exemple 1.9 {int     aux;
             aux     = a;
             a =     b;
             b =     aux; }

                                                                                e
est un bloc qui utilise temporairement une variable auxiliaire aux permettent d’´changer les valeurs
                                 e     e e            e   e                               e
de deux variables a et b suppos´es pr´c´demment d´clar´es de type int. L’espace occup´ par aux
       e e    e      e                  e
est lib´r´ apr`s l’ex´cution de la derni`re instruction b = aux;.


1.4.2    Les instructions de branchement
                                                               a e
    Le principe des instructions dites de branchement consiste ` ex´cuter certaines instructions
        e        a                 e e                             e
d’une s´quence ` partir d’un proc´d´ d’aiguillage. Un cas particuli`rement simple et usuel est
celui de la conditionnelle.


L’instruction conditionnelle Elle est de la forme :

                                                      e
                                  if (<expression bool´enne>)
                                     <instruction1 >
                                  else
                                     <instruction2 >



               e e                                           e                             ae
Elle est consid´r´e comme une unique instruction dont l’ex´cution consiste tout d’abord ` ´valuer
                  e                             o                                   e
l’expression bool´enne (dite expression de contrˆle). Puis l’instruction 1 (ou premi`re branche) ou
                      e                   e e                               e
l’instruction 2 (deuxi`me branche) est ex´cut´e selon que la valeur trouv´e est true ou false.

Exemple 1.10 Supposons que n est une variable de type int et x et r des variables de type float.
        e
On peut ´crire :

if (n%2 = = 0)
   {n /= 2; x *= x;}
else
   {n- -; r *= x;}
       e                                       e             e                  e
Il en r´sulte que si n est pair, n est remplac´ par sa moiti´ et x par son carr´. Sinon, n est
 e e      e                             e
d´cr´ment´ et la variable r est multipli´e par x. Remarquer que l’instruction :
1.4. BLOCS ET BOUCLES                                                                              13

if (n%2 = = 0)
   x = n/2;
else                                e           e
                               peut ˆtre remplac´e par :               x = n%2= =0? n/2 : n-1;
   x = n-1 ;
    e         e                         e
De mˆme, en pr´sence d’une variable bool´enne pair, l’instruction
if (n%2 = = 0)
   pair = true;
else                                   e           e
                                  peut ˆtre remplac´e par :                pair = n%2 = = 0;
   pair = false ;

                           e
Remarque 1.1 La deuxi`me branche de l’instruction conditionnelle est facultative. Dans ce cas,
                        o                                            e            a
si l’expression de contrˆle a pour valeur false, l’instruction est ´quivalente ` l’instruction vide,
                               e                a
instruction virtuelle dont l’ex´cution consiste ` ne rien faire. Par exemple, l’instruction
                                         if(b<a) a = b;
           e a
est destin´e ` enregistrer dans la variable a le minimum de a et de b. La variable a n’est pas
       e                    ea
modifi´e si elle contenait d´j` le minimum.

                                                            e
L’instruction switch Cette instruction a pour but d’ex´cuter toutes les instructions d’une
 e         a                             e e                                       o
s´quence, ` partir de certains points rep´r´s par la valeur d’une variable de contrˆle de type
entier (char, byte, short, int ou long). La syntaxe est la suivante.


                                                 e
                           switch (<variable enti`re>){
                              case <valeur1 > : <instruction1 >
                              case <valeur2 > : <instruction2 >
                               ...
                               case <valeurn > : <instructionn >
                               default        : <instructionn+1 >
                               }


          a                                                       e
Il s’agit ` nouveau d’une unique instruction. Les valeurs doivent ˆtre distinctes. Lorsque la variable
prend la valeur <valeuri >, toutes les <instructionj > pour j ∈ {i, . . . (n + 1)} sont ex´cut´es
                                                                                               e e
 e
s´quentiellement. Le mot default est un mot clef et remplace toute valeur ne figurant pas dans
              e e                  e
les case pr´c´dents. Cette derni`re ligne est facultative. En son absence, pour toute autre valeur
de la variable, l’instruction se comporte comme l’instruction vide. De plus, si une <instructioni >
est absente, elle se comporte comme l’instruction vide. Enfin, si pour les valeurs <valeurj > o`      u
j ∈ {1, . . . i} on ne souhaite pas ex´cuter les instructions de rang sup´rieur ` i, on utilise l’ins-
                                      e                                    e      a
truction break qui a pour effet de sortir de la boucle.

Exemple 1.11 On illustre ici, non seulement le fonctionnement de l’instruction switch, mais
e                              e
´galement le codage des caract`res et les conversions explicites (cast). Supposons que ch soit une
                                                       e                    e          e
variable de type char et nb une variable de type int pr´alablement initialis´es. Consid´rons le bloc
suivant.

{boolean chiffre = false, minuscule = false;
 switch(ch){
   case’0’: case’1’: case’2’: case’3’:case’4’:
   case’5’: case’6’: case’7’:case’8’:case’9’:
      chiffre = true;break;
   case’a’: case’b’: case’c’: case’d’:case’e’:
   case’f’: case’g’: case’h’:case’i’:case’j’:
   case’k’: case’l’: case’m’:case’n’:case’o’:
14                                                                 CHAPITRE 1. FONDEMENTS

     case’p’: case’q’: case’r’:case’s’:case’t’:
     case’u’: case’v’: case’w’:case’x’:case’y’:
     case’z’ : minuscule = true ;break;
     default : System.out.println("Minuscule ou chiffre attendu");
    }
    if (chiffre){
      nb = 10*nb + ch-’0’;
      System.out.println("nb = " + nb);
    }
    else if (minuscule){
      ch += ’A’-’a’;
      System.out.println("ch a ete transforme en " + ch);
      System.out.println("Code de " + ch +": " + (short)ch);
    }
}

     e              e
Le r´sultat de l’ex´cution de ce bloc est le suivant :
                                             e
    – Si ch contient initialement un caract`re autre qu’un chiffre ou une minuscule, il s’affiche ` a
         e
       l’´cran :
       Minuscule ou chiffre attendu
                                           e                    a e
    – Si ch contient initialement le caract`re ’t’, il s’affiche ` l’´cran :
       ch a ete transforme en T
       Code de T: 84
                                           e                                      a e
    – Si ch contient initialement le caract`re ’3’ et nb la valeur 52, il s’affiche ` l’´cran :
       nb = 523
           e
Le deuxi`me cas montre comment transformer une minuscule en majuscule, en utilisant le fait que
                                                e                                e             e
dans les codes standards, les minuscules, de mˆme que les majuscules, sont cons´cutives et rang´es
                  e
par ordre alphab´tique (voir la table ASCII section 1.1.2). On remarquera que ch s’affiche T alors
                                                              e                             e
que (short)ch s’affiche 84 qui est l’entier codant le caract`re ’T’. L’affichage (i.e. le d´codage)
s’est donc fait selon le type (char pour ch et short pour (short)ch).

          e                                             e                      e
Le troisi`me cas utilise l’expression ch-’0’ (cf. op´rations sur les caract`res, section 1.1.3) qui,
              u
dans le cas o` ch contient un chiffre (ici ’3’), a pour valeur l’entier correspondant (ici 3). Cela re-
                                                                     e             e         a
pose sur le fait que dans les codes standards, les chiffres sont cons´cutifs et rang´s de ’0’ ` ’9’. On
                  c                                    e
peut de cette fa¸on transformer une suite de caract`res, par exemple ’5’, ’2’ et ’3’ en un entier,
                                            a          e
ici 523. Il suffit pour cela d’initialiser nb ` 0 et d’it´rer l’instruction nb = 10*nb + ch-’0’;.

1.4.3     Instructions while et do-while
                                                       e
    Les boucles while et do-while permettent d’it´rer une instruction tant qu’une expression
     e                             e e                         e
bool´enne a la valeur true. En g´n´ral, cette expression d´pend de variables dont la valeur est
       e a          e                                                                  e
modifi´e ` chaque it´ration. Il convient de s’assurer que la valeur de l’expression bool´enne converge
                                         e                                     e        e
vers true au bout d’un nombre fini d’it´rations. Sans quoi, la boucle s’ex´cute ind´finiment. On
dit que “l’on ne sort jamais de la boucle” et que le programme “boucle”.

L’instruction while
                                             e e
    Cette instruction est la boucle la plus g´n´rale du langage Java : toutes les autres ne sont que
                             e a                     e            e
des particularisations destin´es ` gagner en efficacit´ et lisibilit´. La syntaxe de l’instruction while
est la suivante :

                                                        e
                                 while (<expression bool´enne>)
                                    <instruction>
1.4. BLOCS ET BOUCLES                                                                               15


                                                           e
Il s’agit d’une unique instruction, dont l’ex´cution consiste ` ´valuer tout     a e
                           e                    o
d’abord l’expression bool´enne (dite de contrˆle). Si elle a la valeur false, l’instruction (corps
                             e e
de la boucle) n’est jamais ex´cut´e : on “n’entre pas dans la boucle”. La boucle while se comporte
                                 e
comme l’instruction vide et l’ex´cution du programme se poursuit par l’instruction suivante. Si
                                                    e e                   e
elle a la valeur true, le corps de la boucle est ex´cut´ et le processus ´valuation de l’expression
    e          e                                  e e e                           e
bool´enne - ex´cution du corps de la boucle est r´it´r´ tant que l’expression bool´enne reste vraie.

Exemple 1.12 (L’algorime d’Euclide) Cet algorithme permet de calculer le plus grand divi-
seur commun (pgcd) de deux entiers a et b. Il s’appuie sur le fait que :

                                  P GCD(a, b) = P GCD(b, a%b)                                    (1.1)

            a                                                     e e
Il consiste ` effectuer des divisions successives, la valeur cherch´e ´tant celle du dernier reste non
                                           e
nul. Soit trois variables a, b et r, suppos´es de type int et a0 et b0 les valeurs initiales de a et b.
Le code de l’algorithme est le suivant :

              while   (b!=0){
                  r   = a%b;
                  a   = b;
                  b   = r;
              }

                                                                                e    e
On remarque que l’on ne calcule le reste de la division de a par b qu’apr`s s’ˆtre assur´ que   e
                           a           e                           e
b est non nul. De plus, ` chaque it´ration, la valeur de b d´croit strictement puisqu’elle est
         e                                e                                                     e
remplac´e par le reste d’une division enti`re dont elle est le diviseur. Donc elle atteint 0 apr`s un
                 e                                                           e
nombre fini d’it´rations. Cette instruction ne “boucle” donc pas. On peut d´montrer en utilisant la
                      e          e                     e       e
formule (1.1) qu’apr`s chaque it´ration (sauf la derni`re apr`s laquelle b est nul) P GCD(a, b) =
                                              e           e                  e
P GCD(a0 , b0 ). Une telle relation, conserv´e par les it´rations est appel´e invariant de boucle.
                                  a
Il est alors facile de prouver qu’` la sortie de la boucle, a a pour valeur le plus grand diviseur
commun de a0 et b0 .

L’instruction do-while
   L’instruction do-while est de la forme :

                                do
                                   <instruction>
                                                       e
                                while (<expression bool´enne>);

       e           a
et est ´quivalente ` :

                                 <instruction>
                                                        e
                                 while (<expression bool´enne>)
                                    <instruction>


                                                         e
Autrement dit, le corps de la boucle do-while s’ex´cute au moins une fois apr`s quoi      e
                     o       e    e                                   a
l’expression de contrˆle est ´valu´e et le comportement est identique ` celui de la boucle while.
“On entre toujours dans la boucle”.

1.4.4     Instruction for
            o
    Le contrˆle de la boucle for se fait au moyen d’une unique variable que l’on appellera provi-
soirement compteur. On indique :
16                                                                  CHAPITRE 1. FONDEMENTS

   – la valeur initiale du compteur,
                                                       e
   – une expression qu’il doit satisfaire pour que s’ex´cute le corps de la boucle,
                              e                 a          e
   – une instruction qui fait ´voluer sa valeur ` chaque it´ration.
Sa syntaxe est la suivante :

                                                  e
               for (<initialisation> ; <expr. bool´enne> ; <transformation > )
                  <instruction>


Elle se comporte comme :


                                   <initialisation>
                                                     e
                                   while (<expr. bool´enne>){
                                      <instruction>
                                      <transformation>
                                   }


     e                                                   e
La d´claration de la variable compteur peut se faire en mˆme temps que l’initialisation. Elle
                 a
est alors locale ` la boucle.

                                                      e          e
Exemple 1.13 (La suite de Fibonacci) C’est une suite r´currente d´finie par les relations sui-
vantes :
       u0 = 1
       u1 = 1
       ∀n, un+2 = un+1 + un

                          a             e
Il est facile de calculer ` la main le d´but de cette suite : 1, 1, 2, 3, 5, 8, 13, 21, . . . en partant
                                            a         e
des deux premiers termes et en ajoutant, ` chaque ´tape, les deux derniers termes obtenus pour
                                          e    e                                  u
en calculer un nouveau. En supposant d´clar´e une variable n de type int dˆ ment initialis´e, on  e
                          e
peut ainsi calculer le ni`me terme de la suite comme suit.

{int a=1, b=1, nouveau ;
    for (int i=1 ; i<n ; i++){
      nouveau = a+b;
      a = b;
      b = nouveau;
    }
}
                        e    e                                              a
Ici, la variable i est d´clar´e au moment de l’initialisation et est locale ` la boucle. Il est facile
      e            a                                        e
de d´montrer qu’` la sortie b contient le terme un cherch´. En effet, ceci est vrai si n vaut 0 ou
                                        o
1 car dans ce cas, l’expression de contrˆle est fausse et on n’entre pas dans la boucle. Sinon, on
               e           e                                                                  e
prouve qu’apr`s chaque it´ration, a = ui−1 et b = ui (invariant de boucle). On obtient le r´sultat
                     a
en remarquant qu’` la sortie i = n.
On remarque enfin que l’on peut utiliser la variable n comme compteur :
{int a=1, b=1, nouveau ;
    for ( ; n>1 ; n--){
      nouveau = a+b;
      a = b;
      b = nouveau;
    }
}
1.4. BLOCS ET BOUCLES                                                                      17




                                                               e
Dans ce cas, l’initialisation est “vide” puisque n est suppos´e connue. En notant n0 la valeur
                   e                             e           e
initiale de n, on d´montre que si n0 > 1, apr`s chaque it´ration, a = un0 −n et b = un0 −n+1 .
         a                       e             e
Comme ` la sortie n vaut 1, le r´sultat cherch´ est donc la valeur de b.

                               e
Remarque La boucle while utilis´e dans l’exemple 1.12 pour calculer le pgcd de a et de b
     e           e
peut ˆtre remplac´e par :
for ( ; b!=0 ; b=r){
    r = a%b;
    a = b;
}
      e                   e
Cette ´criture n’est pas n´cessairement plus lisible !
Chapitre 2

Java sans objet

2.1     Programme minimum
                                     e                  e
   Un programme Java est structur´ en modules appel´s classes. Informellement, une classe est
        e      e                  e             e                                  e
constitu´e de d´clarations et de m´thodes. Les m´thodes sont des sous-programmes pr´sentant une
                                                    e
analogie avec les fonctions du langage C ou les proc´dures du langage Pascal.

                           e      e e
Un programme susceptible d’ˆtre ex´cut´ doit :
  1. contenir au moins le texte suivant :

                          public class <nom> {

                               public static void main (String[ ] args){
                                                e
                                 < corps de la m´thode main>
                               }
                          }
     e                                       e
  2. ˆtre contenu dans un fichier portant le mˆme nom <nom> que la classe avec l’extension
     .java.
     e          e
  3. ˆtre compil´ par la commande : javac <nom>.java.
                                 a
     En l’absence d’erreurs ` la compilation, un                    fichier       e
                                                                               ex´cutable        e
                                                                                            appel´
     <nom>.class est alors produit.
     e      e e
  4. ˆtre ex´cut´ par la commande : java <nom> <arg1 > . . .<argn>
                              e e                                         e
     <arg1> . . .<argn> sont s´par´s par au moins un espace et sont appel´s arguments du pro-
                                e    e        a       e
     gramme. Ce sont des donn´es n´cessaires ` son ex´cution. Si le programme n’attend pas de
          e                                 a   e
     donn´es, aucun argument n’est fourni ` l’ex´cution.
                        e     e
Ces divers points sont d´taill´s dans les sections suivantes.

2.1.1        e
         La m´thode main
                                e              e          e           e
Le mot main est le nom d’une m´thode. Cette m´thode est tr`s particuli`re. C’est la seule dont le
              e              e                               a e             e
nom soit impos´. De plus l’ex´cution d’un programme consiste ` ex´cuter sa m´thode main.

Voici un exemple          de     programme     Java,    suppos´
                                                              e            e
                                                                  enregistr´    dans   le   fichier
LePrintemps.java.

public class LePrintemps{
   public static void main (String[ ] args){
      System.out.println("Il y a dans les bois");

                                                 19
20                                                          CHAPITRE 2. JAVA SANS OBJET

         System.out.println("Des arbres fous d’oiseaux");
     }
}

                                         e                                 a     e
Ce programme ne requiert aucune donn´e. On n’indiquera donc aucun argument ` l’ex´cution.
                  e                                     e
Voici une copie d’´cran qui montre les diverses phases d´crites ci-dessus.
$ javac LePrintemps.java
$ java LePrintemps
Il y a dans les bois
Des arbres fous d’oiseaux
$

2.1.2      Les arguments d’un programme
         e                                ınes de caract`res s´par´es par au moins un caract`re
Les param`tres d’un programme sont des chaˆ             e     e e                           e
                                                    e                    e
d’espacement. Supposons que le programme soit appel´ avec un nombre n (´ventuellement nul) de
      e
param`tres.

      e                        e              e                          e
L’ex´cution du programme d´bute par la cr´ation d’un tableau appel´ args dans la m´thode    e
               ee                                   ea                 ınes de caract`res. Le type
main, dont les ´l´ments sont de type String, destin´ ` recevoir des chaˆ             e
                            e                         u     e
“tableau de String” est not´ en Java String[ ], d’o` la d´claration String[ ] args dans l’en-
 e           e                                    e                           ee
tˆte de la m´thode. On dit que args est un param`tre de main. Le nombre d’´l´ments du tableau
   ee                                             e                    e
cr´´ est exactement le nombre n d’arguments pass´s au moment de l’ex´cution. Les tableaux Java
e           e a                                 e      a        ee
´tant index´s ` partir de 0, args est donc index´ de 0 ` n-1. L’´l´ment d’indice i du tableau est
    e
not´ args[i].

                                  e
Voici un exemple de programme requ´rant deux arguments.
public class MonNom{
  public static void main (String[ ] args){
    System.out.println("Vous etes " + args[0] + " " +args[1]);
  }
}
                     e e
Et voici une trace d’´x´cution :
$java MonNom Georges Sand
Vous etes Georges Sand
                          e           e                e               e         e a
L’utilisateur ayant indiqu´ deux param`tres, une zone m´moire est allou´e, destin´e ` recevoir
                  a      ee                                    e
un tableau args ` deux ´l´ments, args[0] et args[1] qui eux-mˆmes recoivent respectivement
"Georges" et "Sand".

                                          e                e
Si l’utilisateur n’indique pas de param`tres lors de l’ex´cution du programme ci-dessus, le ta-
                                                      e                     a
bleau args est vide. Or l’unique instruction de la m´thode main fait appel ` args[0] et args[1]
                                                              e                    ee
, qui n’existent pas, puisqu’il n’y a pas eu d’allocation de m´moire pour ces deux ´l´ments. Ceci
                         ee
provoque un message c´l`bre :

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0

             e    e            a     e                  e
Les erreurs d´tect´es par Java ` l’ex´cution sont appel´es exceptions. Le message ci-dessus in-
                    e                      e
dique que dans la m´thode main on a utilis´ un indice (ici 0) hors des bornes du tableau.

Sachant     que  la    longueur    du    tableau    args est       e e
                                                                 pr´d´finie en     Java par
                                   e                    e
args.length, il est prudent pour ´viter l’erreur provoqu´e par l’abscence d’arguments de mo-
                                     c
difier le programme ci-dessus de la fa¸on suivante :
          ´
2.2. LES METHODES DE CLASSES (OU STATIQUES)                                                     21

public class MonNom{

    public static void main (String[ ] args){
      if(args.length>=2)
        System.out.println("Vous etes " + args[0] + " " +args[1]);
      else
        System.out.println("Deux arguments attendus");
      }
}
                                     e e     e         c
Enfin, on peut rajouter souplesse et g´n´ralit´ de la fa¸on suivante :
public class MonNom{

    public static void main (String[ ] args){
     if (args.length ==0)
      System.out.println("je ne sais qui vous etes");
     else{
      System.out.print("Vous etes");
        for(int i=0;i<args.length;i++)
          System.out.print(" " + args[i]);
        System.out.println("\n");
      }
     }
}
                 e
On obtient les ex´cutions suivantes :

$java MonNom
Je ne sais qui vous etes

$java MonNom Marie de France
Vous etes Marie de France

$java MonNom Marie Madeleine Pioche de La Vergne, Comtesse de La Fayette
Vous etes Marie Madeleine Pioche de La Vergne, Comtesse de La Fayette


2.2           e
         Les m´thodes de classes (ou statiques)
                                     e e              e                 e
    Les classes Java comportent en g´n´ral plusieurs m´thodes, chacune ´tant un sous programme
  e ea        a             e           e          e                  e
d´di´ ` une tˆche particuli`re. Une m´thode doit ˆtre relativement br`ve. Dans le cas contraire,
         ee                                   e                       e               e
il est pr´f´rable de la scinder en plusieurs m´thodes plus courtes apr`s avoir identifi´ les divers
calculs qui la constituent.

2.2.1    Etude d’un exemple
                              e
Supposons que l’on souhaite ´crire un programme calculant le pgcd de deux entiers par l’algo-
                                              e          e
rithme d’Euclide (cf. section 1.4.3). On peut ´crire la m´thode main de telle sorte qu’elle calcule
                               e           e               a e         e
le pgcd de deux entiers pass´s en param`tre et affiche ` l’´cran le r´sultat. Cependant il est
   ee          e            e                    e e
pr´f´rable de d´finir une m´thode uniquement d´di´e au calcul du pgcd. On obtient ainsi :
/*1*/public class ProgPgcd{

/*2*/   public static int pgcd (int a, int b){
/*3*/     int r;
22                                                            CHAPITRE 2. JAVA SANS OBJET

/*4*/    while (b!=0){
/*5*/        r=a%b;
/*6*/        a=b;
/*7*/        b=r;
/*8*/    }
/*9*/    return a;
/*10*/ }

/*11*/ public static void main(String[] args){
/*12*/   int x = Integer.parseInt(args[0]);
/*13*/   int y = Integer.parseInt(args[1]);
/*14*/   System.out.println("PGCD("+ x+"," +y+") = "+ pgcd(x,y));
/*15*/ }
/*16*/}
                 e
Dont voici une ex´cution :
$ java ProgPgcd 15 20
PGCD(15, 20) = 5
$
Cet exemple appelle divers commentaires et permet d’illustrer un certain nombre de notions.

2.2.2      Commentaires dans un programme
On peut commenter un programme en rajoutant du texte compris entre /* et */. Ce texte est
     ea                                                                        e
ignor´ ` la compilation. Nous avons pu ainsi faire figurer en commentaire le num´ro des lignes du
                             e
programme sans changer l’ex´cutable.

     e                     e                                                               e
La s´quence des deux caract`res // a pour effet de commenter tout le texte qui suit sur la mˆme
ligne.

2.2.3     Nombres en argument de programme
           e                                                                     ıne        e
On a insist´ dans la section 1.1.2 sur la distinction entre l’entier 97 et la chaˆ de caract`res "97".
             e                e                             e                          a
Nous avons ´galement signal´ dans la section 1.1.3 que d`s qu’un nombre se trouve ` la place d’une
    ıne          e                                                  ıne            e
chaˆ de caract`res, il est automatiquement converti en la chaˆ qui le repr´sente. En revanche,
la conversion inverse ne se fait pas implicitement.

                                                                                           e
Les arguments d’un programme sont toujours de type String (cf. section 2.1.2). Dans l’ex´cution
       e                                                                                 e
montr´e ci-dessus, args[0] prend la valeur "15" et args[1] prend la valeur "20". Il est n´cessaire
                  e          e e                      e                          e
d’appeller une m´thode pr´d´finie en Java et nomm´e Integer.parseInt qui ´tant donn´e une    e
    ıne          e                                e
chaˆ de caract`res, renvoie l’entier qu’elle repr´sente. Ainsi, Integer.parseInt("15") est l’en-
                                                             e             e
tier 15. Ceci explique les lignes 12 et 13 de l’exemple. Une ´criture erron´e du nombre provoque
une exception.

      e
Les m´thodes Byte.parseByte, Short.parseShort, Long.parseLong, Float.parseFloat,
                                                               e
Double.parseDouble sont les analogues pour les autres types num´riques.

2.2.4                         e
          Radiographie de la m´thode pgcd
                           e      e          e
     – La ligne 2 est appel´e en-tˆte de la m´thode.
                                    e
     – Le mot public sera expliqu´ par la suite.
                                                 e                                e
     – Le mot static indique qu’il s’agit d’une m´thode dite statique, ou encore m´thode de classe.
                                                                           u
       Cette notion ne prendra tout son sens qu’au chapitre suivant o` seront introduites les
        e
       m´thodes non statiques.
          ´
2.2. LES METHODES DE CLASSES (OU STATIQUES)                                                       23

                                                      e                  e
   – Le mot int indique le type de retour : toute ex´cution de cette m´thode produit une valeur
                              e
     de ce type. Lorqu’une m´thode ne renvoie aucune valeur, le type de retour est void (c’est le
     cas de main).
                                      e                   e
   – Le mot pgcd est le nom de la m´thode et est laiss´ au choix de l’utilisateur.
                                   e           e            e
   – Les entiers a et b sont appel´s les param`tres de la m´thode.
                       e                         a      e            e
   – A la ligne 3 on d´clare une variable locale ` la m´thode appel´e r.
                  a
   – Les lignes 5 ` 8 calculent le pgcd des valeurs initiales de a et b. A la sortie de la boucle, ce
     pgcd est dans a (cf. section 1.4.3).
                                              e
   – La ligne 9 indique que la valeur renvoy´e est celle de a. Le type de cette valeur est bien le
                           e            e           e
     type de retour indiqu´ dans l’en-tˆte de la m´thode.

2.2.5                c
             Comment ¸a marche ?
              e                                e
Appel de m´thode A la ligne 14 dans la m´thode main figure l’expression pgcd(x,y). On dit
   a              a                    e                e
qu’` ce moment-l`, main appelle la m´thode pgcd. L’´valuation de cette expression provoque
    e                e                               ee         e
l’ex´cution de la m´thode pgcd. Si le programme a ´t´ appel´ avec les arguments "15" et "20",
                                                                      e           e
au moment de l’appel les variables x et y ont pour valeur 15 et 20. L’´tat de la m´moire est le
                                       e    e                   e
suivant (le tableau args n’est pas repr´sent´ par souci de clart´) :

        x        y
        15       20

          e            e           e               e                    e       e
Voici le d´roulement pr´cis de l’ex´cution de la m´thode pgcd, illustr´ par les ´tats successifs
        e
de la m´moire.
                     e
   1. Allocation de m´moire pour 3 nouvelles variables a, b et r de type int.

             x        y   a      b        r
            15       20   ?       ?       ?

                                         e                                              e
  2. Initialisation automatique des param`tres formels a et b avec les valeurs des param`tres ef-
     fectifs x et y.

             x        y   a       b       r
            15       20   15     20        ?


       e
  3. Ex´cution de la boucle while qui se termine quand b vaut 0.

             x        y   a       b        r
            15       20   20      15      15


            15       20   15      5        5


            15       20   5       0        0


                      a      e
  4. Communication ` la m´thode appelante (ici main) de l’entier 5, valeur de la variable a et
                               e          e a e
     restitution de toute la m´moire allou´e ` l’´tape 1 (i.e. a, b et r).
         x       y
            15       20
24                                                            CHAPITRE 2. JAVA SANS OBJET

2.2.6                      e
          Passage des param`tres par valeur
    e                  e e            e                                                     e
Il r´sulte de ce qui pr´c`de que param`tres et variables locales n’existent que pendant l’ex´cution de
      e                                                                          e
la m´thode. Ceci explique qu’ils soient inconnus partout ailleurs. De plus apr`s l’appel pgcd(x,y),
      e                                                      e
la m´thode travaille sur des copies des variables x et y (´tapes 1 et 2). Par suite, ces variables
            e e                              e             e                e
sont prot´g´es et en aucun cas affect´es par l’ex´cution de la m´thode. Ceci est un avan-
                                          e
tage puisque le calcul d’une expression d´pendant de variables ne doit pas modifier leurs valeurs,
                              e           e            e                                    e
sauf cas particulier express´ment indiqu´. Ceci empˆche toutefois d’utiliser des param`tres pour
                            a      e                                     e
communiquer des valeurs ` l’ext´rieur. Il est par exemple absurde d’´crire :

public static void echange(int a, int b){
  int aux;
  aux=a; a=b; b=aux;
}

                e                               e                     e         e
dans l’espoir d’´changer les valeurs des param`tres effectifs. Cette m´thode n’´change que les va-
                e e e
leurs de copies ´ph´m`res et est donc sans effet sur l’environnement. Si par exemple, deux variables
                                 a                e                         e          e
x et y ont pour valeur 15 et 20 ` l’appel de la m´thode echange(x, y), l’´tat de la m´moire est :

     x      y
     15    20

            e              e                   e                e
Pendant l’ex´cution de la m´thode echange, la m´moire prend les ´tats suivants :
   x       y       a       b     aux
     15    20       15      20        ?


     15    20       20      15       15

   e    e
Apr`s ex´cution :

     x      y
     15    20

      e                         e ea   e
La m´thode a effectivement proc´d´ ` un ´change, mais sur les copies de x et y dans les variables
                          e
locales perdues en fin d’ex´cution.


2.2.7       u                  e
          Coˆ t d’un appel de m´thode
                                            e                     e
Il ne faut jamais oublier qu’un appel de m´thode provoque l’ex´cution d’un calcul et a donc un
   u               e                                   a                           e
coˆt en temps d’ex´cution. Ainsi, si l’on doit affecter ` une variable carre le carr´ du pgcd de x
                      e      e
et y, on se gardera d’´crire ´tourdiment :

carre = pgcd(x, y) * pgcd(x, y);

    e                                                 e              e          o
math´matiquement correct mais qui provoque 2 fois le mˆme calcul. On ´crira plutˆt :

int d = pgcd(x, y);
carre = d*d;

                                                                                        e
Ce genre de maladresse peut provoquer des explosions combinatoires dans les programmes r´cursifs.
On en verra des exemples par la suite.
              ´
2.3. MODULARITE                                                                                  25

2.2.8    L’instruction return
                       e                         u
Cette instruction peut ˆtre absente dans le cas o` le type de retour est void. Cette instruction
              e      e           e              e
provoque l’arrˆt imm´diat de l’ex´cution de la m´thode.

      e
Consid´rons par exemple l’instruction :
if(x==0 || y==0)
 return(0);
else
 return(pgcd(x,y));
     e                e e                                      e           c e
D’apr`s la remarque pr´c´dente, le else y est inutile. On peut ´crire de fa¸on ´quivalente :
if(x==0 || y==0) return(0);
return(pgcd(x,y));
                                                            e                        e
Si l’une des deux valeurs au moins de x et y est nulle, la m´thode renvoie 0 et s’arrˆte. Les
                                         e
instructions suivantes ne sont pas examin´es.


2.3               e
         Modularit´
                                  e                    e                 e
    Imaginons que l’on souhaite d´velopper une biblioth`que pour l’arithm´tique. Pour faire simple,
                                                                                  e
supposons qu’elle fournisse un calcul du pgcd et du ppcm. Une telle biblioth`que est suscep-
        e          e                                        e
tible d’ˆtre utilis´e par divers autres programmes. En cons´quence, elle ne comportera pas elle-
  e          e                           e
mˆme de m´thode main. On pourra d´finir une classe publique Arithmetique, dans un fichier
                                             e
Arithmetique.java, contenant ces deux m´thodes.

public class Arithmetique{

    static int pgcd (int a, int b){
      int r=0;
      while (b!=0){
         r=a%b; a=b; b=r;
      }
      return a;
    }
    static int ppcm(int a, int b){
      int d = pgcd(a,b);
      return (a*b)/d ;
    }
}

               e          e         e              e            e
Cette biblioth`que peut-ˆtre utilis´e par des m´thodes situ´es dans une autre classe. Pour cet
                       e
exemple, on a ainsi d´fini une classe ProgTestArith dans un fichier ProgTestArith.java du
  e     e                              e                                    e
mˆme r´pertoire. Elle contient une m´thode main qui peut appeler les m´thodes pgcd et ppcm, `      a
                          e      a
condition toutefois de pr´ciser ` quelle classe elles appartiennent. L’appel ne se fera plus simple-
                                                                            e
ment par pgcd(x,y) mais par Arithmetique.pgcd(x,y) qui signifie la m´thode pgcd de la classe
                    u                e                                                      e
Arithmetique, d’o` l’expression m´thode de classe. Ceci permet l’existence de diverses m´thodes
      e                   e                                                               e
de mˆme nom pgcd, situ´es dans diverses classes C1 , . . . Cn . On peut alors appeler la m´thode de
                                                                     e              e
la classe Ci de son choix par Ci .pgcd. Dans l’exemple qui nous int´resse, on peut ´crire :
public class ProgTestArith{

    public static void main(String[] args){
      int x = Integer.parseInt(args[0]);
26                                                             CHAPITRE 2. JAVA SANS OBJET

         int y = Integer.parseInt(args[1]);
         int d = Arithmetique.pgcd(x,y);
         int m = Arithmetique.ppcm(x,y);

         System.out.println("PGCD("+ x +", " + y+ ") = " + d);
         System.out.println("PPCM("+ x +", " + y+ ") = " + m);
     }
}

                           e                         e
Remarque Dans une mˆme classe, plusieurs m´thodes peuvent porter le mˆme nom, `    e         a
                            e       e     e
condition qu’elles puissent ˆtre diff´renci´es par la liste de leurs arguments (en nombre ou en
type).

                        e            e    e                       e       e
Si des classes sont situ´es dans le mˆme r´pertoire, elles ont acc`s aux m´thodes les unes des autres,
` condition que ces m´thodes ne soient pas d´clar´es avec l’attribut private (auquel cas elles ne
a                       e                       e    e
                                                                                     e
sont accessibles que de leur propre classe). L’organisation des classes dans divers r´pertoires repose
          e
sur un m´canisme dit de package que nous n’exposerons pas ici. Les attributs public, private
                                                                      e                       e
ainsi que l’absence d’attributs sont relatifs aux permissions d’acc`s aux champs et aux m´thodes
d’une classe depuis une autre classe selon le package dans lequel elle se trouve. Notons que la
compilation d’une classe provoque automatiquement la compilation de toutes celles qu’elle utilise.


2.4         Conclusion provisoire
                              e                e
   On est loin d’avoir expos´ toutes les caract´ristiques du langage Java. On a vu pour l’ins-
                   a                                                                       e
tant qu’il conduit ` l’organisation des programmes en divers modules : les classes, elles-mˆmes
       e                     a                                                      e
regroup´es en packages mis ` disposition de certains utilisateurs au moyen d’un m´canisme de
permission. Ceci permet de gagner en :

               e            e            `
     – fiabilit´ : le fait d’ˆtre conduit a structurer les programmes comme un ensemble de modules
                                     a                          e                          e
       simples dont les diverses tˆches ainsi que leur port´e sont clairement identifi´es, aide `     a
                     a
       l’analyse et ` la conception de programmes corrects
             e                                                            e
     – clart´ : les divers modules sont courts et donc facilement compr´hensibles, ce qui est un atout
                                                           e
       important pour l’utilisation correcte des biblioth`ques et la maintenance des programmes.
         e e       e                                                                     a e e
     – g´n´ralit´ : dans un processus de conception de programme, on est conduit ` g´n´raliser
                  a                                                 e e       e e             e
       (souvent ` peu de frais) certaines parties ad hoc. L’int´rˆt de g´n´raliser une d´marche
                         e                          e                       e    e
       scientifique est ´vident : meilleure compr´hension de divers ph´nom`nes ayant des points
                                                     e            e
       communs, abstraction permettant de se d´faire des d´tails inutiles en se concentrant sur
                                       e    e            e e            e      e                 a
       l’essentiel ainsi clairement d´gag´ et simplifi´, r´utilisabilit´ des r´sultats obtenus ` des
       situations diverses.

                                           e                                 ıssent comme des
Dans les aspects de Java que nous avons d´crits jusqu’ici, les classes apparaˆ
                                          e                                           e
modules regroupant un certain nombre de m´thodes dites de classes ou statiques (leur d´claration
comporte l’attribut static). On peut dire sommairement que ces classes sont des portions de
programmes au sens classique du terme. Le chapitre suivant montre que la notion de classe en
Java est bien plus riche que cela.
Chapitre 3

Les objets

3.1                                     e
        Classes comme structures de donn´es
3.1.1    Qu’est ce qu’un objet ?
                                                                  e e
Reprenons l’exemple de la classe Arithmetique du chapitre pr´c´dent. Le calcul du ppcm requ´-     e
                                         e           e                             a
rant celui du pgcd, on peut imaginer d´finir une m´thode PgcdPpcm qui renvoie ` la fois le pgcd
                                  e                             e
et le ppcm de deux entiers pass´s en arguments. Mais une m´thode ne peut renvoyer qu’un seul
 e            e                e                     e        e          e
r´sultat. L’id´e est alors de d´finir une unique entit´ compos´e, en l’esp`ce, d’un couple d’entiers.
                        e           e                          e                          e
En Java de telles donn´es structur´es (sauf les tableaux abord´s au chapitre 4) sont appel´s objets.


                                     e          e
                         Objet = Donn´e structur´e (autre qu’un tableau)

              e                              e
Comment donc d´finir le type de retour de la m´thode PgcdPpcm ?


3.1.2     e
         D´finir les classes d’objets
On rajoute pour cela dans le fichier Arithmetique.java une classe, sans l’attribut public puis-
                         e                            e a e
qu’elle ne porte pas le mˆme nom que le fichier, destin´e ` d´finir les couples d’entiers. On choisit
        e
de la d´finir comme suit :

class coupleInt{
   /*1*/ int div, mul;
    /*2*/ coupleInt (int a, int b){
    /*3*/    this.div = a;
    /*4*/    this.mul = b;
    /*5*/ }
}

                               e                             e e                          e
Cette classe n’est pas constitu´e comme celles du chapitre pr´c´dent : elle comporte une d´claration
a                                           e
` la ligne 1 et elle ne comporte pas de m´thodes. En bref, elle n’a de commun avec les classes
  e e                 e          a
pr´c´dentes que la d´claration ` l’aide du mot class.

                                                    o                       e
La classe coupleInt n’est pas un programme mais plutˆt une structure de donn´es (ou un
type) : celle des couples d’entiers.



                                                27
28                                                                     CHAPITRE 3. LES OBJETS

this et ses champs
                                e                             ee
Pour expliquer le propos et le m´canisme d’une telle classe, r´f´rons-nous au texte suivant :
        Soit c=(x, y). Quand x vaut 23 et y vaut 4/2, c vaut (8, 2)

Dans la partie      Soit c=(x, y)                         e
                                    qui n’est autre que l’´criture concise de :

                                 Supposons que c soit un couple (x, y)

   e                                 e
c d´signe un couple virtuel, hypoth´tique, qui n’a pas d’existence propre, contrairement au couple
                                         c                 e
(8, 2) dont il s’agit par la suite. De fa¸on analogue, la d´claration dans la classe coupleInt :

                                            int div, mul;

     e             e           a
est s´mantiquement ´quivalente ` :

                                                u
                        Soit this = (div, mul) o` div et mul sont des entiers

                e                             e                                e        e
Le mot this d´signe donc un objet hypoth´tique, qui n’a pas d’existence r´elle en m´moire. C’est
                                       a      e                       u
l’objet virtuel dont il s’agit partout ` l’int´rieur de la classe, d’o` le nom this : “celui-ci, que la
                        e
classe a pour but de d´crire”.

                                      e                   e
Les deux composants de this sont appel´s ses champs et not´s respectivement this.div et
this.mul.

Le constructeur
     e                                       a
La d´claration de la ligne 1 ne permet pas ` elle seule de construire des objets de la classe
                              e                               a                      e        e
coupleInt. Il faut pour cela d´finir un constructeur (lignes 2 ` 5). Il s’agit d’une m´thode tr`s
         e
particuli`re puisque :
                         e e e
     1. elle n’est pas pr´c´d´e du mot static.
     2. elle n’a pas de nom
     3. son type de retour est celui des objets de la classe, soit ici coupleInt.
                          a        e                                                   e
Ce constructeur permet ` toute m´thode qui le souhaite, de construire des objets bien r´els de la
            e
classe en pr´cisant les deux valeurs prises par les champs div et mul.

En conclusion
                                           e
Une classe, vue comme structure de donn´es, est une description d’objets. Elle en indique les
                                                                                         e
champs qui les constituent et fournit un ou plusieurs constructeurs pour permettre les cr´ations
   e                           e
ult´rieures. Tout comme les m´thodes, les divers constructeurs doivent se distinguer par la liste
               e
de leurs param`tres.

a retenir
                                            c                     e
     1. Il s’agit d’une description de la fa¸on dont sont constitu´s les objets de cette classe
                                e                      e                         e e a
     2. this est un objet hypoth´tique sans existence r´elle dont la classe est d´di´e ` la
        description.
                                                e      e               e
     3. Il faut un constructeur au moins pour cr´er ult´rieurement de r´els objets
                 ee                                                    e
     4. On n’a cr´´ ici aucun objet : il n’y a pas eu d’allocation de m´moire
                                     ´
3.1. CLASSES COMME STRUCTURES DE DONNEES                                                             29

 e
D´sormais, on dira comme il se doit en Java objet de classe coupleInt et non objet de type
coupleInt.


3.1.3       e
          Cr´er des objets
        e              e                             e
Le m´canisme de cr´ation d’objets est illustr´ par l’exemple de la m´thode           e
PgcdPpcm. Un fichier ArithBis.java contient la classe coupleInt de la section 3.1.2 suivie de
la classe :
public class ArithBis{

    public static coupleInt PgcdPpcm(int a, int b){
        int r, a0 = a, b0 = b;
        coupleInt resultat;
        while(b!=0){
           r = a % b;
           a = b;
           b = r;
        }
        resultat = new coupleInt(a, (a0*b0)/a);
        return resultat;
    }
}

                                     e
On remarque que l’on a sauvegard´ les valeurs initiales de a et b dans a0 et b0 puisqu’elles
           e
sont utilis´es pour le calcul du ppcm.

                   e e
Les objets sont rep´r´s par leurs adresses
      e         e
Consid´rons la d´claration :

                                       coupleInt resultat;

               a                                          e
Contrairement ` ce que pourrait laisser croire une telle d´claration, la variable resultat n’est pas
      e a                                                                            c e
destin´e ` recevoir un objet de la classe coupleInt, mais l’adresse de (ou de fa¸on ´quivalente
                            ee         a                                   e              e
un pointeur vers, ou une r´f´rence `) un tel objet. Au moment de l’ex´cution, cette d´claration
                                     e            ea
provoque l’allocation d’un espace m´moire destin´ ` recevoir une adresse qui prend automatique-
                                  e e              ee
ment la valeur null, constante pr´d´finie qui ne r´f´rence aucun objet. Soit :

                                              resultat

                                                  null


  e
Cr´ation
                   e             ee               e                              e
    L’objet n’est r´ellement cr´´ dans cette m´thode qu’au moment de l’´valuation de l’expres-
sion : new coupleInt(a, (a0*b0)/a). On utilise ici le constructeur coupleInt, avec les valeurs
                                                          e e e                e
effectives des champs, soit a et (a0*b0)/a, le tout pr´c´d´ par le mot cl´ new. Ceci a pour effet
                       e
d’allouer une zone m´moire pour les deux champs, l’un de valeur a, l’autre de valeur (a0*b0)/a.
            e            e                                                       e
De plus le r´sultat de l’´valuation est l’adresse de cette zone. Ceci est illustr´ par la figure 3.1 dans
        u
le cas o` les valeurs initiales a0 et b0 sont 15 et 20.


              e
Par suite, apr`s l’affectation resultat = new coupleInt(a, (a0*b0)/a);, la variable resultat
30                                                                     CHAPITRE 3. LES OBJETS

                              new coupleInt(a, (a0*b0)/a)


                          A                                        q
                    Valeur                                    Effet de bord
                                   e
           adresse de la zone allou´e                     allocation/initialisation
                                                E              5           60
                                               e
                                Figure 3.1 – Cr´ation d’un objet



                                                             ee                              e
a pour valeur l’adresse du couple d’entiers nouvellement cr´´. Ce couple d’entiers est appel´ objet
de la classe coupleInt, ou encore instance de cette classe. Les champs div et mul de cette instance
         e         e    e
particuli`re sont d´sign´s respectivement par resultat.div et resultat.mul.


                   resultat                           resultat.div       resultat.mul

                                                E              5           60


                                    e                                             e e ee
Il est clair qu’il est absurde d’acc´der aux champs d’un objet qui n’a pas encore ´t´ cr´´. Ainsi la
 e
s´quence : coupleInt resultat;
              resultat.div = 0;
provoque une exception : NullPointerException, qui indique que le pointeur resultat vaut null
et donc resultat.div et resultat.mul n’existent pas.

Toute expression de la forme <nom>.<attribut> provoque une exception quand le
                                                  e           ee     e
pointeur <nom> vaut null, i.e. quand l’instance pr´tendument r´f´renc´e par <nom> n’a
    e e ee
pas ´t´ cr´´e.

                 e                               e e
De plus, il r´sulte de tout ce qui pr´c`de, qu’une simple affectation
                                                  a
c1 = c2 d’une variable de la classe coupleInt ` une autre ne recopie pas l’objet
mais uniquement son adresse. Pour dupliquer un objet, il faut le faire explicitement de la
  c
fa¸on suivante :
                      c1 = new coupleInt(c2.div, c2.mul);


                                     e                                            e
On peut remarquer maintenant que la m´thode PgcdPpcm de la classe ArithBis peut s’´crire plus
simplement

     public static coupleInt PgcdPpcm(int a, int b){
        int r, a0 = a, b0 = b;
         while(b!=0){
            r = a % b;
            a = b;
            b = r;
         }
         return (new coupleInt(a, (a0*b0)/a));
     }

       e              e
Cette m´thode est test´e par le petit programme ci-dessous :
3.2. CLASSES COMME BOˆ    `         ´     ´
                     ITES A OUTILS SECURISEES                                                    31

public class ProgTestArithBis{

    public static void main(String[] args){
      int x = Integer.parseInt(args[0]);
      int y = Integer.parseInt(args[1]);
      coupleInt divmul = ArithBis.PgcdPpcm(x, y);

        System.out.println("PGCD("+ x +"," + y+ ") = " + divmul.div);
        System.out.println("PPCM("+ x +"," + y+ ") = " + divmul.mul);
    }
}
                          e
dont voici un exemple d’ex´cution :
$ java ProgTestArithBis 21 15
PGCD(21, 15) = 3
PPCM(21, 15) = 105


3.2                        ıtes ` outils s´curis´es
           Classes comme boˆ    a         e     e
                                       e    e                e
    Dans le chapitre 2, nous avons pr´sent´ des classes qui ´taient des portions de programmes,
         e        e                                                         e    e
structur´es en m´thodes dites de classes. Dans la section 3.1, nous avons pr´sent´ une classe qui
                e                             e                    e
ne faisait que d´finir une structure de donn´es et permettre la cr´ation d’objets ou instances de
                                                 e                                          e e
cette classe. Ces deux cas particuliers assez diff´rents au premier abord ne s’opposent en r´alit´
                                                             e e
pas. En effet, la notion de classe en Java est suffisamment g´n´rale pour offrir un cadre unique `  a
ces deux situations.

3.2.1       e
           M´thodes d’instance
                              e                    e             e e
Supposons que l’on souhaite d´velopper une biblioth`que pour la g´om´trie plane. Les points du
             e              e            e   e
plan (suppos´ muni d’un rep`re) seront d´sign´s par un couple de flottants, leur abscisse et leur
        e      c             a                                    e e                 e
ordonn´e. De fa¸on analogue ` la classe coupleInt de la section pr´c´dente, on peut d´finir une
classe point comme suit :
public class point{
    double abs, ord;

        public point (double x, double y){
            this.abs = x;
            this.ord = y;
        }
}
                    e e        e
La structure de donn´e ´tant d´finie par les champs et le constructeur, il est possible de rajouter
      e                                          e
des m´thodes dans cette classe. Par exemple une m´thode qui calcule le milieu de deux points :
public static point milieuStatique(point p1, point p2){
        return (new point((p1.abs+p2.abs)/2, (p1.ord+p2.ord)/2));
}
        e                 e                                            e
Cette m´thode est une m´thode statique de la classe point. Ses param`tres sont deux pointeurs p1
                                                                         e
et p2 vers des points. Elle construit un nouveau point dont les coordonn´es sont les demi-sommes
             e                ee      e         e                                       e
des coordonn´es des points r´f´renc´s en param`tres et elle renvoie son adresse. A l’ext´rieur de la
                                        ee                                                   e
classe point et en supposant avoir cr´´ deux points a et b, on appelle sans surprise cette m´thode
                                         e
en indiquant ainsi qu’il s’agit de la m´thode milieuStatique de la classe point :
32                                                                      CHAPITRE 3. LES OBJETS

point c = point.milieuStatique(a,b);

                    e             a
La classe point d´finit donc ` la fois des objets (les points) et une m´thode        e
                                 e e                   e
milieuStatique. En ce sens elle g´n´ralise comme annonc´, les deux notions de classes (pro-
                           e
gramme ou structure de donn´es).

                                          ee           e          e                           e
Toutefois, dans cette situation, il est pr´f´rable de d´finir des m´thodes (ici calculant des r´sultats
                    e e                           a                                      e
en liaison avec la g´om´trie) qui s’appliquent ` l’objet virtuel this que la classe d´finit, plutˆt  o
    a                                       e                         e
qu’` des points quelconques. De telles m´thodes ne sont plus qualifi´es par le mot static. Ainsi,
a      e                                 ee
` la m´thode milieuStatique, on pr´f`rera :
public point milieu(point p){
     return (new point ((this.abs+p.abs)/2, (this.ord+p.ord)/2));
}
         e                 e          e                                                    ee
De toute ´vidence cette m´thode cr´e un nouveau point, milieu de this et du point r´f´renc´ par    e
        e              e                                  e                                e
le param`tre p. A l’ext´rieur de la classe point, et en pr´sence de deux points a et b, on ´crira alors :

                                      point m = a.milieu(b);

                                    e                                                       e
On indique ici qu’il s’agit de la m´thode milieu pour laquelle le point virtuel this que d´finit
                      e                    e         u            e                          e
la classe est instanci´ par le point bien r´el a. D’o` le nom de m´thode d’instance. Si l’on ´crit
maintenant :

                                    point quart = m.milieu(a);

               e                                                           e
on appelle la m´thode milieu de l’instance m qui remplace this pendant l’ex´cution.

                            e         e e            ee                 e
La classe point peut de la mˆme mani`re ˆtre compl´t´e par diverses m´thodes d’instance relatives
a     e e                                          e                                        e
` la g´om´trie, comme celle qui calcule le translat´ de this ou celle qui calcule son homoth´tique
                  e
dans une homoth´tie de centre O et de rapport k, pour ne citer qu’elles.

3.2.2     Privatisation des champs
                       e                                         e        ee            ıte a
La classe point peut ˆtre vue comme une structure de donn´es compl´t´e d’une boˆ ` outils (les
  e                                                                e       e          e
m´thodes d’instance) pour manipuler correctement ces donn´es. L’id´e est de d´finir une bonne
                         c     u                                     e              e
fois pour toutes et de fa¸on sˆre, le calcul du milieu, du translat´, de l’homoth´tique et de toutes
      e           e                          a
les op´rations int´ressantes, en interdisant ` l’utilisateur de manipuler les points autrement qu’avec
      e
ces m´thodes. C’est la philosophie de la programmation objet.

                                                         e
Supposons qu’un utilisateur de la classe point, en pr´sence d’un point a, souhaite le transla-
                               e                       e             e
ter d’un vecteur de coordonn´es (2, -2). Il pourrait ´tourdiment ´crire : a.abs += 2; a.ord
                                  e                                   e e          e
-=2; et perdre ainsi la valeur ant´rieure de a, ce qui n’est pas en g´n´ral souhait´.

     e                     e                                   e
La m´thode translate ci-apr`s conserve le point auquel on s’int´resse (autrement dit this) et
                    e
calcule son translat´ :

public point translate (double x, double y){
   return (new point(this.abs+x, this.ord+y));
}

     c
La fa¸on correcte de translater alors un point a serait la suivante :

                                 point b = a.translate(2, -2);
3.2. CLASSES COMME BOˆ    `         ´     ´
                     ITES A OUTILS SECURISEES                                                     33

    e                                                       a
On ´vite ce type d’erreurs en contraignant les utilisateurs ` ne manipuler les points qu’au moyen
      e                                                              e      e
des m´thodes que leur fournit la classe point. Pour ce faire, on prot`ge en ´criture les champs des
                                                                                         e
objets en les affectant du qualificatif private. Il deviennent alors inaccessibles de l’ext´rieur de la
classe : on ne peut donc en modifier la valeur. Mais on ne peut plus de ce fait avoir connaissance
               e
des coordonn´es des points qu’on utilise, ce qui n’est pas souhaitable. On fournira donc deux
  e                                                           e
m´thodes qui renverront ces valeurs. Voici une classe point ´crite dans cet esprit.
public class point{
    private double abs,ord;

    public point (double x, double y){
        this.abs = x;
        this.ord = y;
    }
    public double getAbs(){
        return this.abs;
    }
    public double getOrd(){
        return this.ord;
    }
    public point milieu(point p){
      return (new point ((this.abs+p.abs)/2,(this.ord+p.ord)/2));
    }
    public point translate(double x, double y){
        return (new point(this.abs+x, this.ord+y));
    }
    public point homothetique(double k){
        return (new point(this.abs*k, this.ord*k));
    }
    public String toString(){
        return( "(" + this.abs + ", " + this.ord + ")");
    }
}
                             e
Si a est un point, on pourra ´crire :

x = a.getAbs(); y = a.getOrd(); b = a.translate(1.2, 1.3);

                                                               e
Mais en aucun cas on ne pourra modifier directement les coordonn´es de a. Pour ce faire, il
       e                                 a
faut cr´er un nouveau point et l’affecter ` a.

a = new point (..., ...);

            e                                                 e                      a       e
ce qui est s´mantiquement correct puisque changer les coordonn´es d’un point revient ` consid´rer
un nouveau point.

                          e                           e              c                e
Cet exemple nous donne ´galement l’occasion de pr´senter une fa¸on d’afficher agr´ablement des
                                                     e             e    e
objets. C’est le propos de la section suivante, qui d´crit la derni`re m´thode de la classe point.

3.2.3        e
         La m´thode toString
               e                         e                      e
Toute classe d´finissant des objets peut ˆtre pourvue d’une m´thode dont le nom : toString et
                                      e                     ıne        e
le type de retour : String sont impos´s. Elle renvoie la chaˆ de caract`res que l’on choisit pour
                              e
afficher l’objet this. Si l’on d´cide par exemple d’afficher un point comme le couple (x, y) de ses
         e       e
coordonn´es, on ´crira :
34                                                                  CHAPITRE 3. LES OBJETS

    public String toString(){
           return( "(" + this.abs + ", " + this.ord + ")");
}
               a     e                                                     e
Supposons, qu’` l’ext´rieur de la classe point, a soit le point de coordonn´es 1.2 et 3.4. L’affichage
` l’´cran de :
a e
a : (1.2, 3.4)
devrait logiquement se faire par la commande :
System.out.println("a : " + a.toString());

     e e                    e
L’int´rˆt de cette m´thode est que a sera automatiquement remplac´ par                e
                 e                                u         ıne         e
a.toString() d`s qu’il se situe dans un contexte o` une chaˆ de caract`res est attendue. C’est
                           e                            e
ainsi que l’on obtient le mˆme affichage que ci-dessus en ´crivant simplement :

System.out.println("a : " + a);

                              ee e                                          ınes de caract`res
Cela explique qu’il n’ait pas ´t´ n´cessaire de convertir les nombres en chaˆ             e
                                                  e         e
pour les afficher : chaque type de nombres est dot´ d’une m´thode toString que l’on a appel´e e
implicitement sans le savoir.


3.3       Variables statiques et constantes
                         a          e          e        e
    La classe point peut ` son tour ˆtre utilis´e pour d´finir une classe relative aux cercles comme
suit :
public class cercle{

      private point centre;
      private double rayon;

      public cercle (point c, double r){
          this.centre = c;
          this.rayon = r;
      }
      public point getCentre(){
          return this.centre;
      }
      public double getRayon(){
          return this.rayon;
      }
      public double circonference(){
          return(2*Math.PI * this.rayon);
      }
      public double surface(){
          return(Math.PI * this.rayon * this.rayon);
      }

      public cercle translate(double x, double y){
          return (new cercle(this.centre.translate(x,y), this.rayon));
      }
      public cercle homothetique(double k){
          return(new cercle(this.centre.homothetique(k), rayon*k));
      }
      public String toString(){
          return ("Centre : " + this.centre +"\nRayon: "+ this.rayon +"");
      }
}
3.3. VARIABLES STATIQUES ET CONSTANTES                                                              35

     e                                     e
La cr´ation de cercles se fait selon le sch´ma suivant :
cercle C = new cercle(new point(x,y), r) ;
 u                      e e                                    e                    e
o` x, y et r sont suppos´s ˆtre des variables de type double pr´alablement initialis´es.

    e                 eee
La m´thode toString a ´t´ ´crite pour produire un affichage des cercles comme suit :
Centre : (3.0, 3.0)
Rayon: 2.0

                   e                          e e
On a de plus utilis´ la constante Math.PI, pr´d´finie dans la classe Math. Les constantes des
        e                e                                                      e e    a
biblioth`ques Java sont ´crites en majuscules et les programmeurs se plient en g´n´ral ` cette
                                         e
convention bien qu’elle ne soit pas impos´e.

                                                                      e                e
Imaginons que l’on ne souhaite pas une valeur de π d’une telle pr´cision, et que l’on d´cide de
 e                                                            e     e     e
d´finir sa propre constante π = 3.14 par exemple. La premi`re id´e est de d´finir une variable pi
                               e a
dans la classe cercle initialis´e ` cette valeur. On aurait ainsi :

public class cercle{

   double pi = 3.14;
   private point centre;
   private double rayon;
   ...

Cette solution n’est pas la bonne car pi devient un nouveau champ des cercles : un cercle est
       e
alors d´fini par son centre, son rayon et “son pi” !. Il y aura ainsi autant de copies de pi que
d’instances de cercles, ce qui est absurde. On souhaite donc qu’il n’y ait qu’une variable pi, com-
      a                                            e
mune ` tous les cercles. Cette variable ne devant d´pendre que de la classe cercle et non pas des
                  e
instances, on la d´clare avec l’attribut static. On dit alors que pi est une variable de classe ou
                                           e
une variable statique. La classe cercle d´bute ainsi :

public class cercle{
   static double pi = 3.14;
   private point centre;
   private double rayon;
   ...

                         a      e
Le nom de cette variable ` l’ext´rieur de la classe est cercle.pi.

                                             e                        e
a retenir Tout comme on distingue m´thodes d’instance et m´thodes statiques (ou de
classe), on distingue les variables d’instance (ou champs) des variables statiques (ou de classe)
qui n’existent qu’en un seul exemplaire, quel que soit le nombre d’instances en cours.

                                       o         e                               ee           e
Enfin, dans le cas de cet exemple, plutˆt que de d´finir une variable, il serait pr´f´rable de d´clarer
                                  e               e ae           e
une constante. Le valeur de pi n’´tant pas destin´e ` ˆtre modifi´e dans le programme, il faut la
    e       e             e
prot´ger en ´criture pour ´viter des erreurs du genre :
                               pi*=2; return (pi*this.rayon);

                             e   e                              e   e
dans le but de calculer le p´rim`tre de this. Chaque calcul de p´rim`tre doublerait la valeur
de pi pour toute la suite du programme !

       e
Pour d´clarer la valeur de π sous forme non modifiable, on utilise l’attribut final. On obtient
donc ici :
36                                                                  CHAPITRE 3. LES OBJETS

public class cercle{
   final static double PI = 3.14;
   private point centre;
   private double rayon;
   ...
                                                        a      e
Dans la classe cercle, cette constante a pour nom PI et ` l’ext´rieur, elle se nomme cercle.PI.

3.3.1    this implicite
                                            e                               e       e
Il faut enfin signaler que le mot this peut ˆtre omis dans la classe qui le d´finit, d`s que l’on fait
       a
appel ` l’un de ses attributs. Par exemple :
public class point{
    double abs, ord;
    public point (double x, double y)
        { this.abs = x; this.ord = y; }

     public point translate (double x, double y)
         { return (new point(this.abs+x, this.ord+y)); }
}

     e           e
peut ˆtre remplac´ par :
public class point{
    double abs, ord;
    public point (double x, double y)
        { abs = x; ord = y; }

     public point translate (double x, double y)
         { return (new point(abs+x, ord+y)); }
}
Chapitre 4

Les tableaux

4.1              a
        Tableaux ` une dimension
                                            ee              e                         e
   Il s’agit d’enregistrer une suite finie d’´l´ments de mˆme type dans des zones m´moires conti-
 u                                                                    e      e
g¨es. Par exemple, la suite d’entiers (10, 8, -2, 5, 4) sera enregistr´e en m´moire comme suit :

                  ...        10         8        -2        5         4         ...


4.1.1     e
         D´clarations
    e
La d´claration des tableaux se fait selon la syntaxe suivante :

                                  <type> [ ] <nom du tableau> ;

 u         e                  ee
o` <type> d´signe le type des ´l´ments du tableau.
Exemple 4.1       int [ ] T;
                  String [ ] args;
                  e
On a vu que la d´claration d’une variable de type simple provoque l’allocation d’une portion
     e                      e              e
de m´moire de taille adapt´e au type. Les d´clarations de tableaux comme celles de l’exemple
                                   e                              a             a
ne permettent pas l’allocation de m´moire pour un tableau, puisqu’` ce moment-l`, le nombre
  ee                            e            c
d’´l´ments est inconnu. En cons´quence, de fa¸on analogue aux objets, aucun tableau n’est
  ee                      e                           ee
cr´´ au moment de la d´claration. Les variables cr´´es (ici T et args) sont des adresses
de tableaux dont la valeur initiale est null.

4.1.2      e
         Cr´ation
                            e                                                       e
Comme pour les objets, la cr´ation d’un tableau se fait explicitement avec le mot cl´ new, selon
la syntaxe :

                         <nom du tableau> = new <type> [<taille>];

Exemple 4.2                                                    e       e
                  T = new int [5]; provoque une allocation de m´moire d´crite par la figure 4.1.
                    e                                        e
Cinq variables cons´cutives de la taille d’un entier, nomm´es T[0], T[1], T[2], T[3] et T[4]
     ee       e               a                                    e
ont ´t´ allou´es. A ce stade-l`, elles ne sont pas encore initialis´es. La variable T a pour valeur
                     ee                                       ee
l’adresse du premier ´l´ment du tableau. L’initialisation des ´l´ments d’un tableau se fait par les
affectations :


                                                37
38                                                               CHAPITRE 4. LES TABLEAUX

                          T



                              T[0]      T[1]      T[2]      T[3]      T[4]
                          c
                               ?         ?         ?         ?            ?

                                   e                             a ee
                    Figure 4.1 – Cr´ation d’un tableau d’entiers ` 5 ´l´ments


T[0] = 10; T[1] = 8; T[2] = -2 ; T[3] = 5; T[4] = 4;

  e                          e
L’´tat du tableau est alors d´crit par la figure 4.2.
                          T



                              T[0]      T[1]      T[2]      T[3]      T[4]
                          c
                              10         8        -2         5         4

                                                                      e
                              Figure 4.2 – Tableau d’entiers initialis´


                          e                    e       e                                  a
Autre syntaxe Il est ´galement possible de d´clarer, cr´er et initialiser un tableau tout ` la
fois. Sur l’exemple ci-dessus, l’instruction :

                                int [ ] T = {10; 8; -2; 5; 4}

 e                                                                                         e
d´clare la variable T, alloue un espace pour cinq entiers et leur affecte les valeurs indiqu´es. On se
retrouve alors dans la situation de la figure 4.2.

4.1.3    Taille et indexation d’un tableau
                                                               e e           e
Toute variable T de type tableau est munie d’une primitive pr´d´finie nomm´e T.length dont la
                       ee                                            e
valeur est le nombre d’´l´ments de T. Elle est donc nulle avant la cr´ation.

                           e       a
Tout tableau T est index´ de 0 ` T.length-1. Ces deux valeurs sont les bornes du tableau.
Si i∈ {0, . . . , T.length-1}, toute tentative d’acc`s T[i] ` l’´l´ment d’indice i de T provoque
     /                                              e       a ee
l’exception :
                             ArrayIndexOutOfBoundsException: i

                      a                                                       e
Le parcours de gauche ` droite d’un tableau T peut ainsi se faire selon le sch´ma suivant :
                  for (int i=0; i < T.length; i++)
                                                                              .
                         <traitement de T[i]>

4.1.4      u          e
         Coˆ t des acc`s
              ee
L’adresse des ´l´ments d’un tableau n’est pas directement accessible comme celles des variables de
                            ee            e                                                     e
type simple. L’adresse de l’´l´ment T[i] r´sulte d’un calcul faisant intervenir l’adresse T du d´but
                                 e e
du tableau et l’indice i. Plus pr´cis´ment :

                  adresse de T[i] = T + i × (taille du type des ´l´ments de T)
                                                                ee
              `
4.2. TABLEAUX A PLUSIEURS DIMENSIONS                                                                39


            e a      ee                    a
Ainsi, l’acc`s ` un ´l´ment d’un tableau ` une dimension requiert une addition et une multi-
                             u
plication et est donc plus coˆteux que celui d’une variable simple.

4.1.5    Tableaux d’objets
     ee
Les ´l´ments des tableaux de chaˆ                e                                   ınes ou les objets
                                   ınes de caract`res ou d’objets ne sont pas les chaˆ
       e                                      e
eux-mˆmes, mais leurs adresses. Ainsi, apr`s l’appel de programme (cf. section 2.1.2) :
java MonNom Marie de France
                      ee             e
le tableau args est cr´´ et initialis´ comme suit :
                         args


                           cargs[0] args[1] args[2]



                                c         c          c

                              "Marie" "de"         "France"



4.2              a
        Tableaux ` plusieurs dimensions
                 a                       e    e                     a
    Les tableaux ` n dimensions sont repr´sent´s comme des tableaux ` une dimension de tableaux
de dimension n-1. Par exemple, une matrice de format n × m sera cod´e par un tableau de n
                                                                        e
               e       e                        a   ee                                   e
lignes, elles-mˆmes cod´es comme des tableaux ` m ´l´ments. Ainsi l’enregistrement en m´moire
de la matrice :
                                            1 2 3
                                            8 10 12
        e            e        e    e
peut sch´matiquement ˆtre repr´sent´ comme suit :

  M                                        M[0][0] M[0][1] M[0][2]
                E
             M[0]                     E       1           2        3
             M[1]                     E       8          10       12
                                           M[1][0] M[1][1] M[1][2]

4.2.1     e               e
         D´claration et cr´ation
             a                  e
Les tableaux ` 2 dimensions se d´clarent ainsi :

                               <type> [ ] [ ] <nom du tableau> ;

       e e                       a                   e                                       e
Plus g´n´ralement, les tableaux ` n dimensions se d´clarent avec n paires de crochets. Les cr´ations
                                  e            a
et initialisations se font de mani`re analogue ` celles des tableaux de dimension 1.

                 e
Exemple 4.3 D´claration : int[ ][ ] M;
                  e
               Cr´ation :       M = new int [2][3];
               Initialisation : M[0][0] = 1; M[0][1] = 2; M[0][2] = 3;
                                M[1][0] = 8; M[1][1] = 10; M[2][2]= 12;
Cr´ation avec initialisation : M = {{1,2,3},{8,10,12}};
  e
40                                                               CHAPITRE 4. LES TABLEAUX

                             e
De plus, il est possible de d´finir des tableaux dont les lignes ont des longueurs variables. Au
                e                                                              ee
moment de la cr´ation, on indique le nombre de lignes. Puis chaque ligne est cr´´e individuellement
en fonction de sa longueur.

                                e            e            e
Exemple 4.4 On se propose d’´crire une m´thode qui cr´e et renvoie l’adresse d’un triangle de
                                 e          e
Pascal dont la dimension est pass´e en param`tre. Un triangle de Pascal est la matrice triangulaire
                                                                                                 p
inf´rieure P des coefficients binˆmiaux. Plus pr´cis´ment : ∀n, ∀p, t.q. 0 ≤ p ≤ n, Pnp = Cn .
   e                            o               e e
                   o                   a
Les coefficients binˆmiaux se calculent ` l’aide des relations :

                           n    0
                          Cn = Cn = 1
                           p    p      p−1
                          Cn = Cn−1 + Cn−1        si 0 < p < n
Voici un programme qui calcule et affiche un triangle de Pascal.

public class pascal{

     static int[][] Pascal (int n){

                                          e
         int [][] P = new int[n+1][]; //cr´ation du tableau
                                      //des adresses des lignes

         for(int i=0; i<=n; i++) {     // calcul de la ligne i
           P[i] = new int[i+1];             e
                                       // cr´ation de la ligne i
           P[i][0] = P[i][i] = 1;                                e   e
                                       // initialisation des extr´mit´s
           for(int j=1; j<i;j++)       // calcul des autres coefficients
              P[i][j] = P[i-1][j] +    P[i-1][j-1];
         }
         return P;
     }

     static void afficher(int[][]T){

            for(int i=0; i<T.length; i++){
                for(int j=0; j<T[i].length; j++)
                   System.out.print( T[i][j]+" ");
                System.out.println();
            }
     }

     public static void      main(String [ ] args){

            afficher(Pascal(Integer.parseInt(args[0])));
     }
}

                     e
Voici un exemple d’ex´cution :

$java     pascal 5
1
1 1
1 2       1
1 3       3 1
1 4       6 4 1
1 5       10 10 5    1
                                       ´
4.3. EFFET DU CODAGE SUR L’OCCUPATION MEMOIRE                                                   41

4.2.2      u          e
         Coˆ t des acc`s
Le calcul de l’adresse de M[i][j] s’obtient en recherchant l’adresse M[i] de la ligne i, puis celle
de son jieme coefficient. On obtient ainsi :

                        adresse de M[i] = M + i × <taille des adresses>
                      adresse de M[i][j] = M[i] + j × <taille des ´l´ments>
                                                                  ee

      e a       ee
L’acc`s ` un ´l´ment d’un tableau de dimension 2 requiert donc deux additions et deux mul-
                                e                         e
tiplications. Il est bon en cons´quence de limiter ces acc`s chaque fois que c’est possible.

Exemple 4.5 Consid´rons deux matrices A de format m × n et B de format n × l. Leur produit
                    e
                              n−1
C a pour coefficients Cij = Σk=0 Aik × Bkj . Si l’on transcrit litt´ralement ces formules en un
                                                                 e
programme Java calculant le produit, on obtient :

int m = A.length, l = B[0].length, n = B.length;

for(int i=0; i<m; i++)
  for(int j=0; j<l; j++){      // calcul de C[i][j]
    C[i][j] = 0;
    for(int k=0; k<n; k++)
      C[i][j] = C[i][j] + A[i][k]*B[k][j];
  }

                                                 e                 e a            e
Dans la boucle la plus interne, chacune des n it´rations fait 2 acc`s ` C[i][j] (n´cessitant en
          e                            e
tout 4n op´rations). On remarque que l’´criture de l’affectation sous la forme :

                                 C[i][j] += A[i][k]*B[k][j]

                      e
en supprime la moiti´. Cependant, l’utilisation d’une variable auxiliaire comme ci-dessous per-
                                   e
met de ne faire plus qu’un seul acc`s :

int m = A.length, l = B[0].length, n = B.length;
for(int i=0; i<m; i++)
  for(int j=0; j<l; j++){       // calcul de C[i][j]
    int aux = 0;
    for(int k=0; k<n; k++)
      aux += A[i][k]*B[k][j];
    C[i][j] = aux;
  }

                                           e                                              e
Ceci illustre le fait qu’un programme ne s’´crit pas uniquement comme une formule math´matique
pour laquelle seule importe la correction. Le codage d’une telle formule sous forme de programme
induit des calculs en machine plus ou moins importants selon la forme que prend le codage. Mˆmee
                                                        a          e
si le programme est valide, ces calculs peuvent nuire ` son efficacit´, voire provoquer une explosion
combinatoire, ce qui signifie qu’il est en fait inutilisable.



4.3                                      e
        Effet du codage sur l’occupation m´moire
                                                                    e
   Les programmes ne consomment pas uniquement du temps, mais ´galement de l’espace. Ecrits
       e                                             e                                      e
sans pr´caution, ils peuvent rapidement saturer la m´moire. Le but de cette section est une ´tude
                                                         e
de cas illustrant l’influence du codage sur l’occupation m´moire.
42                                                               CHAPITRE 4. LES TABLEAUX

4.3.1               e
          Puissance ´gyptienne
        e         e                                         e
Consid´rons la m´thode suivante, qui calcule la puissance ni`me d’un flottant x en appliquant
                         e
l’algorithme parfois nomm´ “puissance egyptienne” :

static float puiss(float x, int n){
   float p = 1;
   while(n!=0){
     if(n%2 ==1)
       {n--; p *= x;}
     else
       {n /= 2; x *= x;}
   return p;
}

Correction
Cette m´thode renvoie la valeur de xn . En effet, si on note x0 et n0 les valeurs initiales de x et n,
         e
on d´montre que la relation p × xn = xn0 est un invariant de la boucle. En effet, p valant initia-
    e                                     0
                              e e                                                   e         e
lement 1, cette relation est v´rifi´e avant d’entrer dans la boucle. Elle est conserv´e par it´ration
du fait que :

p × xn = (p × x) × xn−1 si n est impair et
p × xn = p × (x × x)n/2 si n est pair.

En cons´quence, elle est v´rifi´e ` la sortie quand n vaut 0 et donc, ` ce moment l`, p = xn0 .
       e                  e e a                                      a            a       0


         e
Complexit´
                                                 e
Remarquons tout d’abord que les entiers n dont l’´criture binaire comporte k + 1 chiffres en base
deux sont ceux tels que 2k ≤ n < 2k+1 . On en d´duit successivement les relations :
                                               e

log2 (2k ) ≤ log2 (n) < log2 (2k+1 )
k ≤ log2 (n) < k + 1
k = ⌊log2 (n)⌋.

                        e                               e                                  e
Supposons donc que l’´criture binaire de n soit constitu´e de (k + 1) symboles : chaque it´ration
                                                                                         e
de la boucle while a pour effet, si n est impair, de transformer le chiffre 1 des unit´s en un
0, si n est pair, de supprimer le 0 final. Ainsi, le nombre d’it´rations est minimal si n = 2k :
                                                                 e
                   e                                                                 e     a
dans ce cas, son d´veloppement binaire est de la forme 10...0 (avec k symboles ´gaux ` 0) et
              e               e                                            e
donc k + 1 it´rations.sont n´cessaires avant d’obtenir 0. Le nombre d’it´rations est maximal si
    e                                   e                   e      a
le d´veloppement binaire est constitu´ de k + 1 symboles ´gaux ` 1 : dans ce cas il y a 2k + 1
  e                   e                   u       e                                             e
it´rations. Chaque it´ration ayant un coˆt born´, l’algorithme est logarithmique. Il est donc tr`s
    e      a          e                   a                                        e     a
sup´rieur ` celui, lin´aire, qui consiste ` effectuer n multiplications de facteurs ´gaux ` x. Cet
                         e
exemple est repris en d´tail dans la section 5.4.

4.3.2     Puissance d’une matrice
    e                         e                                  e                     e
L’id´e est d’appliquer cette m´thode au calcul de la puissance ni`me d’une matrice carr´e. L’al-
gorithme s’appuie donc sur les formules :

M0 = I
M n = (M × M )n/2 si n est pair et >0
M n = M × M n−1 si n est impair
                                       ´
4.3. EFFET DU CODAGE SUR L’OCCUPATION MEMOIRE                                                  43

     e                                                              e                      e a
On d´finit donc une classe Puiss, comportant un certain nombre de m´thodes statiques destin´es `
                                    a                                             e
traiter des matrices de flottants et ` programmer cet algorithme. Cette classe poss`de notamment
       e                             a e
une m´thode permettant d’afficher ` l’´cran une matrice donn´e :e

public class Puiss{

        public static void imprime(float[][]M){

            for(int i=0;i<M.length;i++){
                for(int j=0; j<M[i].length;j++)
                    System.out.print(M[i][j] + " ");
                System.out.println();
            }
    }
                 ...

                                                               e           e           e
Pour calculer la puissance d’une matrice, il est tout d’abord n´cessaire d’´crire une m´thode mult
                                              e                                     e       e
calculant le produit de deux matrices suppos´es de dimensions compatibles. La m´thode cr´e une
      e
troisi`me matrice P de dimensions convenables pour recevoir le produit des deux matrices M et
        e            e
N pass´es en param`tre, en calcule les coefficients et en renvoie l’adresse.

        private static float[][] mult(float[][] M, float[][]N){

            int n = M.length-1, m = N.length-1, p=N[0].length-1;

            float[][] P = new float[n+1][p+1];
            for(int i=0;i<=n;i++)
                for(int k=0; k<=p; k++){
                    int aux=0;
                    for(int j=0; j<=m; j++)
                        aux+=M[i][j]*N[j][k];
                    P[i][k]=aux;
                }
            return(P);
        }

               e       ee           e          e
La matrice unit´ est cr´´e et renvoy´e par la m´thode suivante :

        private static float[][] unite(int dim){

            float[][] I = new float[dim][dim];

            for(int i=0; i<=dim-1; i++){
              for(int j=0; j<=dim-1; j++) I[i][j]=0;
              I[i][i]=1;
            }
            return I;
        }

                                                                               e
On peut alors utiliser ces outils pour programmer l’algorithme de la puissance ´gyptienne :

        public static float[][] puiss_egypt(int n, float[][] M){

            float[][] P = unite(M.length);

            while (n>0)
44                                                               CHAPITRE 4. LES TABLEAUX

             if (n%2==1){
              n--;
              P = mult(M, P); // cree un espace memoire pour une nouvelle matrice
             }                // un new a chaque nouvel appel de mult
             else {
               n=n/2;
               M = mult(M, M); // cree un espace memoire pour une nouvelle matrice
             }                 // un new a chaque nouvel appel
            return(P);
       }

                                                                                     ee a
Comme l’indiquent les commentaires, une nouvelle matrice de la taille de M est cr´´e ` chaque
it´ration. L’espace occup´ est de l’ordre de 2 × dim2 × lg2 (n). Bien que les complexit´s logarith-
  e                      e                                                             e
                                     e                                                      e
miques soient de bonnes complexit´s, il n’en reste pas moins vrai que la taille de la m´moire
occup´e tend vers l’infini avec n et que le coefficient dim2 peut ˆtre tr`s gros.
      e                                                          e     e

                                                            e
On remarque que ce programme gaspille beaucoup de m´moire car les anciennes matrices M et
                 a            e                                                e
P sont perdues ` chaque it´ration mais occupent toujours de la place en m´moire. Ces espaces
             e    e      e
pourraient ˆtre r´utilis´s pour de nouveaux calculs quand les matrices qu’ils codent sont devenues
                                                                          e
inutiles. Pour ce faire, il faut pouvoir choisir l’espace dans lequel la m´thode mult calcule son
 e                                        e
r´sultat : il suffit de l’indiquer en param`tre comme suit.

        private static void mult(float[][] M, float[][] N, float[][] P){

            int n = M.length-1, m = N.length-1, p = N[0].length-1;

            for(int i=0;i<=n;i++)
              for(int k=0; k<=p; k++){
                int aux=0;
                for(int j = 0; j<=m; j++)
                  aux+=M[i][j]*N[j][k];
                P[i][k]=aux;
            }
        }

            e                                                                  e
Une telle m´thode calcule correctement le produit de M et de N dans le param`tre P. Ceci pourra
    ıtre                                                       u              e               e
paraˆ en contradiction avec la remarque de la section 2.2.6, o` l’on a expliqu´ que les param`tres
e         e                  e                                                  e
´tant pass´s par valeur, la m´thode travaille sur une copie locale de ces param`tres et donc ils ne
                      e         e                           e        a
peuvent en aucun cas ˆtre utilis´s pour communiquer des r´sultats ` l’environnement.

                  e             e                                                                  e
Toutefois, si la m´thode mult cr´e (et travaille sur) sa propre copie de la variable P, cette derni`re
         ee        a                    e e e               ee
est une r´f´rence ` un tableau (suppos´ pr´c´demment cr´´). Ainsi, c’est une adresse qui est re-
    e                            e                                         e                 ee
copi´e (et non la matrice elle-mˆme) et donc le produit est bien calcul´ dans l’espace r´f´renc´.    e
On pourrait alors ˆtre tent´ de transposer la m´thode puiss egypt de la fa¸on suivante :
                    e      e                      e                            c


        public static float[][] puiss_egypt(int n, float[][] M){

            float[][] P = unite(M.length);

            while (n>0)
              if    (n%2==1) {n--; mult(M, P, P);}
              else           {n=n/2; mult(M, M, M);}
            return(P);
       }
                                       ´
4.3. EFFET DU CODAGE SUR L’OCCUPATION MEMOIRE                                                     45

                               e                                   e
Mais ceci est totalement erron´ puisque, par exemple, lors de l’ex´cution de mult(M, M, M) on
e                    e                                      e                        e
´crit dans l’espace m´moire d’adresse M les coefficients du r´sultat, et ce faisant on ´crase les an-
                          e          a
ciennes valeurs pourtant n´cessaires ` la poursuite du calcul.

         e        e           e                                                 ee         a
Ce probl`me se r´soud en cr´ant une matrice auxiliaire Aux dont l’adresse r´f´rencera, ` chaque
  e                                                                    e         e
it´ration, un tableau dont la valeur est sans importance et peut donc ˆtre utilis´e pour un nouveau
                    ee
calcul. Cette propri´t´ de Aux est un invariant de la boucle. Ainsi, pour calculer le produit de M et
                                                                          e
de M dans M, on commencera par calculer le produit dans Aux, puis on ´changera Aux et M : l’an-
                                                     a                       e     e    e
cienne valeur de la matrice M se trouve maintenant ` l’adresse Aux et peut ˆtre ´cras´e. Toutefois,
    e                           a                                    e   u
cet ´change ne consistera pas ` faire trois recopies de matrices (tr`s coˆteuses en temps), mais `  a
e
´changer simplement leurs adresses :

                    {float[][] temp; temp = M; M = Aux; Aux = temp;}.

                      e                                     e                         e
On note aussi que la d´claration de la variable temp ne cr´e pas une matrice suppl´mentaire
                                     a e
mais simplement une adresse qui sert ` l’´change de deux adresses : peu de place, peu de temps.

                                                 u           e
Pour terminer, on note que dans la version coˆteuse en m´moire, les valeurs successives de la
            ee     e                                             e            e           ee
matrice M (r´f´renc´es dans une copie locale de l’adresse M pass´e en param`tre) sont cr´´es dans
                                                                            e           e    a
de nouveaux espaces, et donc la matrice initiale dont l’adresse M est pass´e en param`tre ` l’ap-
            e             e    e                                                          e
pel de la m´thode est pr´serv´e. Ceci est indispensable : le calcul de la puissance ni`me de M
ne doit pas affecter la matrice M. Dans la nouvelle version, ce n’est plus le cas. Le contenu des
                                            e        a          e
tableaux dont les adresses sont M, Aux et P ´voluent ` chaque it´ration : ainsi par exemple, l’appel
Puiss.puiss egypt(A.length, A) modifierait la matrice A ! On prend donc soin de travailler
donc pas directement sur la matrice M mais sur une copie de celle-ci. On obtient ainsi :
        private static float[][] copie (float[][]M){

            int dim = M.length;
            float[][] C = new float[dim][dim];

            for(int i=0; i<=dim-1; i++)
                 for(int j=0; j<=M[0].length-1; j++)
                   C[i][j] = M[i][j];
            return(C);
        }

        public static float[][] puiss_egypt(int n, float[][]M){

            int dim = M.length;
            float[][] P = unite(dim), M1 = copie(M), temp,
                      Aux = new float[dim][dim];

            while (n>0)
              if (n%2==1){
                n--; mult(M1, P, Aux);
                temp = P; P = Aux; Aux = temp;
              }
              else {
                n=n/2; mult(M1, M1, Aux);
                temp = M1; M1 = Aux; Aux = temp;
              }
            return(P);
        }
          e
     Deuxi`me partie

  e
El´ments d’algorithmique




            47
Chapitre 5

           e e
Principes g´n´raux

5.1      Qu’est-ce qu’un algorithme ?
5.1.1      e
          D´finition informelle
                                          e              e                           e
Un cours d’algorithmique se devrait de d´buter par la d´finition simple, claire et pr´cise de la no-
                               a                                        e
tion d’algorithme. Mais c’est l` chose impossible. En effet, donner une r´ponse aux deux questions :

                                   Qu’est-ce qu’un algorithme ?
                             Que peut-on calculer par des algorithmes ?

                    e                               e a          e                   e
est l’objet d’une th´orie profonde et difficile, situ´e ` la crois´e des chemins math´matique, logique
                                    e                       e
et informatique : il s’agit de la th´orie de la calculabilit´, qui est hors du propos de ce document.

                                                     c    e
Si ces deux questions fondamentales sont de fa¸on ´vidente en amont de toute l’informatique,
                                e     e       a
la notion d’algorithme est tr`s ant´rieure ` l’apparition des premiers calculateurs. Ethymologique-
                                                      e                      a        ı    e
ment, le mot algorithme provient du nom du math´maticien perse al-Khowˆ-Rismˆ (IXi`me si`cle),    e
     e                           e                      e                               ee
qui ´crit un manuel d’arithm´tique utilisant la num´ration arabe. Les algorithmes ´l´mentaires de
         e                                            e      e
l’arithm´tique sont connus de tous : il s’agit des r`gles op´ratoires pour le calcul d’une somme, du
                                                            ee
produit, du quotient, du plus grand diviseur commun (c´l`bre algorithme d’Euclide) etc. Dans la
vie courante, recettes de cuisine, notices de montage, indications pour se rendre d’un point ` un  a
                                             ee                   e                           a
autre sont autant d’algorithmes. La vari´t´ des termes employ´s (recette, notice, marche ` suivre,
                             e
plan, instructions . . .) refl`te l’aspect assez flou du concept. On peut toutefois s’accorder sur le fait
                     e
qu’un algorithme d´crit un automatisme et qu’il doit pour cela satisfaire les conditions suivantes :
          e               e                    e
  1. il op`re sur une donn´e pour produire un r´sultat
            e                   e                          e                  e      e
  2. il poss`de un ensemble de d´finition (ensemble des donn´es valides) bien d´termin´
                    e                         e                          e
  3. pour toute donn´e valide, il calcule le r´sultat en un nombre fini d’´tapes
                   e                                  e
  4. pour une donn´e non valide, il peut produire un r´sultat ou pas. Dans ce dernier cas, on dit
     qu’il boucle.
                    e    e                           e           e       e
  5. il produit le mˆme r´sultat chaque fois qu’il op`re sur la mˆme donn´e.
             e         e                             e       e
  6. les donn´es, les r´sultats et l’algorithme lui-mˆme poss`dent une description finie.

5.1.2     Les grandes questions de la calculabilit´
                                                  e
                    e                                                          e       e
Initialement, deux d´finitions formelles de la notion d’algorithme furent donn´es ind´pendemment
           e
et simultan´ment par Church (Lambda-Calcul) et Turing (machines de Turing) dans les ann´es 30.e
               e
On peut consid´rer le Lambda-Calcul et les machines de Turing comme deux langages de program-
                                                     e        a               e            e
mation en ce sens qu’il s’agit de langages formels, r´pondant ` une syntaxe pr´cise et poss´dant une

                                                  49
50                                                                              ´ ´
                                                         CHAPITRE 5. PRINCIPES GENERAUX

 e                   e e                                e
s´mantique bien sp´cifi´e. Un algorithme est alors d´fini comme un terme syntaxiquement correct
                                                                            e e        e
(intuitivement un programme syntaxiquement correct) du langage consid´r´. La diff´rence essen-
                                  e
tielle entre ces langages et les v´ritables langages de programmation actuels (autrement dit entre
                        e               e                                          e       e e
les algorithmes appliqu´s par le math´maticien et les programmes susceptibles d’ˆtre ex´cut´s par
                                                                                e            e
un ordinateur) est que les premiers ne font pas intervenir de limitation de m´moire. Ant´rieurs ` a
                                              e              e e
l’apparition des premiers calculateurs, ils n’´taient pas ex´cut´s automatiquement par une machine
                      e                  e                 e            e                  e
physique de capacit´ finie. La quantit´ de papier utilis´e par le math´maticien pour d´rouler un
                                                                 e       e                e
algorithme est virtuellement infinie. Si la description des donn´es, des r´sultats et du d´roulement
                           e                                                            e
des algorithmes se fait en ´crivant un nombre fini symboles, ce nombre n’est pas major´. Toutefois,
ces langages abstraits servirent de paradigmes aux langages de programmation d’aujourd’hui : les
langages de programmation fonctionnelle (Lisp, Scheme, ML, Caml, OCaml . . .) sont directement
       e                                          e
inspir´s du Lambda-Calcul et les langages imp´ratifs (Pascal, C, C++, Java etc) des machines de
Turing.

                                 e              e                      e                       e e
La question qui s’est alors imm´diatement pos´e fut : L’une de ces d´finitions est-elle plus g´n´rale
                          e                       e       e
que l’autre ? Il fut prouv´ (par Turing) qu’elles ´taient ´quivalentes en ce sens que tout algorithme
e                                        e
´crit dans l’un des deux langages peut ˆtre traduit dans l’autre. Qui plus est, cette traduction se
           e
fait elle-mˆme par un algorithme du langage consid´r´. e e

                       e
La question suivante ´tait alors : Ces langages sont-ils suffisamment puissants pour exprimer tous
                                                   e      a                       e
les algorithmes, au sens intuitif du terme ? La r´ponse ` cette question ne peut ˆtre l’objet d’un
   e e           e                                 e                        e
th´or`me math´matique : en effet, comment d´montrer rigoureusement l’´quivalence entre une
                                                           e
notion formelle d’algorithme et une notion intuitive ? L’´quivalence entre ces deux concepts, l’un
                                         e    e                  e
formel, l’autre intuitif, est ainsi appel´e Th`se de Church ou Th`se de Church-Turing. Le fait que
             e                           e                                             e
l’on ait pu d´montrer que toutes les d´finitions formelles d’algorithmes introduites ult´rieurement
      e                              e                   e                           e          e
sont ´quivalentes aux deux premi`res, conforte cette th`se qui, par essence, ne peut ˆtre prouv´e.

                         e                               e
La notion d’algorithme ´tant alors rigoureusement d´finie et universellement admise, la troisi`me  e
          e
question ´tait : Toute fonction est-elle calculable par un algorithme ? (on dit simplement calculable).
                                                NN
La r´ponse est n´gative. En effet l’ensemble I I de toutes les fonctions sur les entiers naturels et
    e             e
` valeurs dans les entiers naturels, a pour cardinal la puissance du continu ℵ1 : il est ´quipotent `
a                                                                                        e           a
R.          e                                                               e
I Or on d´montre (facilement en utilisant le fait que les algorithmes s’´crivent au moyen d’un al-
                                                NN
phabet fini) que l’ensemble des fonctions de I I qui sont calculables est d´nombrable (´quipotent
                                                                               e           e
` I ). Il y a donc infiniment plus de fonctions non calculables que de fonctions qui le sont.
a N

                                     e
Si les ouvrages d’algorithmique pr´sentent une vaste collection de fonctions calculables, rencon-
                                                  e
trer une fonction qui ne l’est pas n’est pas si fr´quent. Il se trouve que tout algorithme, quel que
                                                                        NN
soit le probl`me qu’il est suppos´ r´soudre, calcule une fonction de I I . Une fa¸on d’en prendre
             e                     e e                                              c
                                            e          e
conscience est de remarquer que les donn´es et le r´sultat d’un algorithme, quelle que soit leur
                      e          e                                                       e
apparente complexit´, sont cod´s in fine dans un ordinateur par une suite binaire repr´sentant un
                          e            e                e
nombre entier. Un probl`me est dit d´cidable s’il poss`de une solution algorithmique (on dit aussi
                             e          e                      a
effective). Exhiber un probl`me non d´cidable revient donc ` exhiber une fonction non calculable.
C’est l’objet de la section suivante.

5.1.3            e           e
         Le probl`me de l’arrˆt
        e           e              e        e                    e    e          c
Le probl`me de l’arrˆt est un probl`me non d´cidable et il est pr´sent´ ici de fa¸on informelle.

                    e
Un programme est ´crit dans un langage de programmation (code source). Ce programme source
             e      e                                                            e e             e
est enregistr´ en m´moire (donc sous forme d’un entier binaire). Puis il est en g´n´ral transform´
                                         e         e                     e
par un compilateur en un programme ex´cutable, ´galement enregistr´ sous forme d’un entier
                                                               e
binaire. Un compilateur est donc un programme (codant lui-mˆme un algorithme), qui prend en
    e
entr´e un programme source, qui l’analyse syntaxiquement, et qui en l’absence d’erreurs produit
    e             e
en r´sultat un ex´cutable.
5.1. QU’EST-CE QU’UN ALGORITHME ?                                                              51


                e                          e
On pourrait l´gitimement envisager d’am´liorer les compilateurs en affinant leur analyse de telle
               e                                                    e          e
sorte qu’ils d´tectent non seulement les erreurs de syntaxe, mais ´galement d’´ventuelles erreurs
          e
dans l’ex´cution des programmes syntaxiquement corrects. Ils pourraient en particulier signaler si
     e                                       e       e
l’ex´cution d’un programme P sur une entr´e n s’arrˆte au bout d’un temps fini ou boucle : c’est
                            e            e
ce que l’on appelle le probl`me de l’arrˆt. On peut imaginer examiner toutes les boucles while
              e                               o
de P pour v´rifier que l’expression de contrˆle prend la valeur false au bout d’un nombre fini
     e                               e          a          e
d’it´rations, en examinant comment ´voluent, ` chaque it´ration, les variables qu’elle fait inter-
            e                         e                      e
venir. Un r´sultat important de la th´orie de la calculabilit´ montre que ceci est impossible : on
                            e           e         e
prouve en effet que le probl`me de l’arrˆt est ind´cidable. Voici une esquisse du raisonnement.
       e                                                          e    e                      e
Consid´rons les programmes source Java. Chacun d’eux est repr´sent´, lorsqu’il est enregistr´ en
   e                                                                         e
m´moire, par un entier binaire n. On note Pn ce programme. Toutefois, le d´veloppement binaire
                                        e
d’un entier n quelconque ne code pas n´cessairement un programme Java correct (c’est le cas par
                                                                     e
exemple de l’entier codant dans la machine la source L TEX du pr´sent document). On convient
                                                       A
                                                               e
dans ce cas, de noter Pn le programme suivant (qui boucle ind´finiment) :

public class loop{
  public static void main(String[] args){
    while(true);
  }
}

                       e e
La suite infinie (avec r´p´titions) de tous les programmes Java
                                         P0 , P1 , . . ., Pn , . . .
                        e
est donc parfaitement d´finie : tout programme Java dont le code en machine est l’entier binaire
                 e                                                             a
n se trouve au ni`me rang dans la liste. Quant au programme loop, il se trouve ` tous les rangs
           e
qui ne repr´sentent pas un programme Java syntaxiquement correct.

         e                          a
On consid`re alors le tableau infini ` deux dimensions suivant :

 Argument →
                  0     1    2         ...        n       ...
 Programme ↓

      P0          1     0     1       ...         1        ...
      P1          0     0     1       ...         1        ...
      P2          1     0     1       ...         1        ...
      ...        ...   ...   ...      ...        ...       ...
      Pn          1     1     0       ...         1        ...
      ...        ...   ...   ...      ...        ...       ...



               a
Le coefficient ` l’intersection de la ligne Pn et de la colonne i indique, selon qu’il vaut 0 ou
                                                 e
1, que le programme Pn boucle ou pas sur la donn´e i.

                            e                                               a
On utilise pour prouver le r´sultat, un argument de diagonalisation amenant ` un paradoxe. Sup-
                         e                     e          e                             e
posons qu’il existe une m´thode Java Arret qui ´tant donn´ un entier n renvoie 0 si l’ex´cution de
                     e                    e                           e              ee
Pn boucle sur l’entr´e n et 1 sinon. La m´thode Arret est donc suppos´e calculer l’´l´ment arrˆte
   NN e
de I I d´fini par :

   e                                e
arrˆt(n) = 0 si Pn boucle sur l’entr´e n
   e                                    e
arrˆt(n) = 1 si Pn se termine sur l’entr´e n

                e                         e                             e   e e
On se propose d’´crire un programme P diff´rent de tous les programmes ´num´r´s dans le tableau
(paradoxe puisque le tableau est cens´ les contenir tous), en ce sens que ∀n ∈ I P(n) = Pn (n)
                                     e                                          N,
52                                                                             ´ ´
                                                        CHAPITRE 5. PRINCIPES GENERAUX

   e   ee                                             e e                              e
(ni`me ´l´ment de la diagonale). Cette condition est r´alis´e par le programme P ci-apr`s :

public class P{
  public static void main(String[] args){
    int n = Integer.parseInt(args[0]);
    while(Arret(n) == 1);
    System.out.println(n);
  }
}

                                                   e                                    e          e
Pour tout entier n, si Pn boucle sur la donn´e n, Arret(n) renvoie 0 et donc P s’arrˆte sur la donn´e
                             e                    e
n et renvoie n. Si Pn s’arrˆte sur la donn´e n, Arret(n) revoie 1 et donc P boucle sur la donn´e   e
n. Dans les deux cas, on peut affirmer que P=Pn puisque ces deux programmes se comportent
   e                       e                                         e
diff´remment sur la donn´e n, et ce pour tout entier n. On en d´duit que le programme P ne figure
pas dans la liste P0 , P1 , . . ., Pn , . . . de tous les programmes Java, ce qui est absurde.

    e
La d´monstration rigoureuse est analogue, si ce n’est qu’elle fait intervenir des algorithmes (termes
du Lambda-Calcul ou machines de Turing) et non des programmes.

                                   e           e         e                 c e
On peut ainsi conclure que le probl`me de l’arrˆt est ind´cidable ou, de fa¸on ´quivalente, que la
                  NN
fonction arrˆt de I I n’est pas calculable.
            e

5.1.4      Pour conclure
                                                                                            a
Avant de s’engager dans l’algorithmique et la programmation, il convient d’avoir clairement `
                        e
l’esprit les questions-r´ponses suivantes :

          e       e                                    e
Tout probl`me poss`de-t-il une solution effective ? La r´ponse est non

                    e           e                               e             e
L’exemple du probl`me de l’arrˆt montre que l’on ne peut pr´tendre tout r´soudre algorithmi-
                                                  a                                e         e
quement, et donc les ordinateurs sont impuissants ` produire la solution de quantit´ de probl`mes.

                         u          ıt                                                     e
Dans le cas favorable o` l’on connaˆ une solution effective (i.e. algorithmique) d’un probl`me,
                   e                                      e
est-t-on capable d’´crire un programme susceptible de s’ex´cuter sur un ordinateur pour produire
     e
un r´sultat ? Autrement dit :

                                               e
     Tout algorithme est-il programmable ? La r´ponse est : oui

Les langages de programmation actuels, et Java en particulier, sont complets : ils permettent
d’exprimer tout algorithme.

     Tout programme correct est-il utilisable ?       e
                                                  La r´ponse est : non.

                                                 u                    e              e
Nous verrons que certains programmes sont si coˆteux en temps d’ex´cution, que mˆme les ordi-
                                                e                  e
nateurs les plus rapides ne peuvent obtenir le r´sultat dans des d´lais raisonnables. C’est le cas
                                     e     a                                               e
par exemple quand le nombre d’op´rations ` effectuer est exponentiel en la taille de la donn´e. On
                                                                     e                     e
dit alors qu’il se produit une explosion combinatoire. D’autres requi`rent tant d’espace m´moire
            e
qu’ils sont ´galement inutilisables.

                 e                                e                 a
Le processus de r´solution informatique d’un probl`me consiste donc ` :
                            e                                        a         e        e
     1. Examiner s’il est r´aliste de chercher une solution effective ` un probl`me donn´ (ne pas se
                        e e                                                             e
        lancer inconsid´r´ment dans la recherche d’une solution algorithmique d’un probl`me comme
                      e
        celui de l’arrˆt).
     2. Si oui, concevoir un algorithme.
5.2. CONCEPTION ET EXPRESSION D’ALGORITHMES                                                      53

      e
  3. D´montrer que l’algorithme est correct.
                         e
  4. Etudier sa complexit´ en temps et en espace (se convaincre qu’il est utilisable)
  5. Last but not necessarily least, programmer l’algorithme.
                             e e a
Les sections suivantes sont d´di´es ` ces divers points.


5.2     Conception et expression d’algorithmes
                                               e
   Cette section introduit sur un exemple les m´thodes de conception et de preuve d’algorithme.


5.2.1      e                  e
         Sp´cification du probl`me
                 e              e               e     a e                     e
Il convient de d´crire avec pr´cision le probl`me ` r´soudre. Cette premi`re phase n’est pas
  e                               e         e                               e e      e
n´cessairement triviale : le probl`me peut ˆtre complexe et n’est pas en g´n´ral pos´ par la per-
                                              e                                 e
sonne qui doit concevoir la solution. On s’int´resse dans cette section au probl`me suivant :

                      e
            Etant donn´ un texte, calculer le nombre d’apparitions des mots du texte.

                                      e         e
Cette formulation doit tout d’abord ˆtre affin´e : on peut convenir qu’il s’agit de construire le
                                                              e
lexique de tous les mots apparaissant dans le texte, accompagn´s du nombre de leurs apparitions.
               e             e e                                      e                  e
On souhaitera ´galement en g´n´ral que les mots du lexique soient rang´s par ordre alphab´tique.
                                        e
La recherche d’une solution peut alors d´buter.


5.2.2                                   e
         Analyse descendante - Modularit´
     e    a                                     e            a      e
Proc´der ` une analyse descendante du probl`me consiste ` l’appr´hender dans sa globalit´ danse
                               e                                 e
un premier temps, sans se pr´occuper (et se perdre) dans les d´tails : on commence par en cher-
                                  ea e                                   e
cher une solution en supposant d´j` r´solus un certain nombre de probl`mes plus simples. Chacun
                  e          a                   e          e                    e     e
de ces sous-probl`mes sera ` son tour examin´ selon la mˆme approche pour ˆtre d´compos´ en     e
           a                  a                              e
nouvelles tˆches, et ce jusqu’` ne plus obtenir que des probl`mes dont la solution tient en quelques
                        e                            e               ea
lignes. A chacune des ´tapes du processus, on est ´galement amen´ ` introduire de plus en plus
      e
de pr´cision dans l’expression.

                           e        e        e
Dans le cas auquel on s’int´resse, d´buter l’´tude en se posant les questions :
                      e
   Comment est stock´ le texte ?
                               e     e
   Comment les mots sont-ils d´limit´s ?
   Comment saisir un mot ?
   Que fait-on des signes de ponctuation ?
                         e
   Comment faire pour d´tecter que le texte est fini ?
    a                  a        e        e                            c
est ` proscrire puisqu’` l’oppos´ d’une d´marche descendante. La fa¸on correcte de traiter ce
      e             o    e                 e                    c
probl`me serait plutˆt d’´baucher une premi`re solution de la fa¸on suivante :
Calculer le nombre des apparitions des mots d’un texte c’est :
   - saisir le mot courant
   - le rechercher dans le lexique
                 ea       e
   - s’il y est d´j`, incr´menter le nombre de ses apparitions
     sinon
       * entrer le mot dans le lexique
                     a
       * initialiser ` 1 le nombre de ses apparitions
                           a
   - recommencer jusqu’` la fin du texte

   e             e                        e             a                                  e
Apr`s cette premi`re approche assez grossi`re, on passe ` un niveau plus concret et plus pr´cis par
54                                                                               ´ ´
                                                          CHAPITRE 5. PRINCIPES GENERAUX

                         c            e
une expression en fran¸ais structur´ (ou pseudo-code) faisant intervenir des boucles, des affecta-
                        e
tions . . . , sur le mod`le des langages de programmation.

nombre-des-apparitions
tant   que le texte n’est pas fini faire
   -   saisir le mot courant
   -   rechercher le mot dans le lexique
   -   si il y est
               e
           incr´menter le nombre de ses apparitions
       sinon
           * entrer le mot dans le lexique
                         a
           * initialiser ` 1 le nombre de ses apparitions

                                          e       e                            e
On peut alors introduire de la modularit´, par d´composition en sous-probl`mes comme indiqu´     e
  e e                                   e                                          e           e
pr´c´demment. Le parcours de la totalit´ du texte en vue de construire le lexique r´pond au sch´ma
                          c
suivant, qui exprime de fa¸on concise le fonctionnement de l’algorithme au plus haut niveau :

nombre-des-apparitions
tant que le texte n’est pas fini faire
   - saisir le mot courant
   - traiter le mot courant

               e   e       e                                                          e
On a ainsi d´gag´ le m´canisme, simple et clair, de l’analyse du texte, sans se pr´occuper de
 e                              e
d´tails. Il convient alors de pr´ciser ce que l’on entend par traiter le mot courant.

traiter un mot
     - rechercher le mot dans le lexique
     - si il y est
               e
           incr´menter le nombre de ses apparitions
       sinon
           entrer le mot dans le lexique

                                                 ae       e e
L’ajout d’un nouveau mot dans le lexique demande ` ˆtre pr´cis´ soigneusement :

entrer un mot dans le lexique
          e                e                      e
     - ins´rer le mot en pr´servant l’ordre alphab´tique
                   a
     - initialiser ` 1 le nombre de ses apparitions

                          e
Se posent alors les probl`mes de la recherche et de l’insertion d’un mot dans un lexique sup-
    e        e                   e                                         e
pos´ ordonn´ par l’ordre alphab´tique. Les solutions sont diverses et d´pendent essentiellement
                        e                    e                    e                            e
de la structure de donn´e choisie pour repr´senter le lexique. L’´tude des structures de donn´es
            e                                                  e
et des probl`mes de recherche/insertion dans un ensemble tri´ sont des grands classiques de l’in-
formatique et font l’objet de chapitres entiers dans les ouvrages d’algorithmique. C’est seulement
                                      ee e                                    e
lorsque toutes ces questions auront ´t´ r´solues, que l’on abordera le probl`me plus technique,
                          e e
mais sans grande difficult´ th´orique, de la saisie des mots.


5.2.3     Correction de l’algorithme
Correction partielle et correction totale On dit qu’un algorithme est partiellement correct
                      e                                         e
lorsque l’on a prouv´ que s’il se termine, alors il produit le r´sultat attendu. On dit qu’il est correct,
                                                          e                           c           e
lorsqu’il est partiellement correct et que l’on a de plus ´tabli sa terminaison. De fa¸on synth´tique :
5.2. CONCEPTION ET EXPRESSION D’ALGORITHMES                                                                   55


                             correction = correction partielle + terminaison

           e
Modularit´ Comme pour la conception, la preuve de correction d’un algorithme se fait de fa¸onc
descendante et modulaire : on suppose dans un premier temps que les divers modules sont corrects
      e
pour d´montrer la correction de l’algorithme global. Puis l’on prouve la correction des modules.

                                                                   e
Invariant de boucle La correction totale d’un algorithme it´ratif se fait au moyen d’un in-
variant de boucle 1 . Un invariant de boucle est un pr´dicat (propri´t´) sur les variables intervenant
                                                      e             ee
dans le boucle qui est :
                           e
   - satisfait avant l’entr´e dans la boucle
              e       e
   - conserv´ par it´ration.

        e         e                                      a
On en d´duit imm´diatement qu’un invariant est satisfait ` la sortie de la boucle. Dans l’exemple
      e e
consid´r´ on peut choisir l’invariant I comme suit :

I(lexique) ⇔ le lexique contient exactement l’ensemble, ordonn´ par l’ordre alphab´tique, des mots
                                                                e                 e
         e            e                   a             e               e
rencontr´s depuis le d´but du texte jusqu’` l’instant pr´sent, accompagn´s du nombre de leurs ap-
paritions

                                                               e
Il faut donc prouver que l’invariant est vrai avant l’entr´e dans la boucle de l’algorithme
                                 a               a                         ee
nombre-des-apparitions. Or, ` ce moment l`, aucun mot n’a encore ´t´ saisi, donc le lexique
     e                   c    a                                            e e
doit ˆtre vide. On s’aper¸oit ` cet instant que ce fait n’est nullement pr´cis´ : si le lexique n’est
                        u e                                                                a
pas vide au moment o` d´bute l’algorithme, il n’y a aucune chance qu’il contienne ` la fin le
 e                                    ee          e                     e
r´sultat attendu. Le lexique n’a pas ´t´ initialis´. On vient ainsi de d´tecter une faute (volontai-
                                                              ea
rement !) commise pour illustrer le propos. On est donc amen´ ` modifier l’algorithme comme suit :

nombre-des-apparitions
                       a
Initialiser le lexique ` vide
tant que le texte n’est pas fini faire
   - saisir le mot courant
   - traiter le mot courant

                                ee                        e              e
    La satisfaction de la propri´t´ d’invariance est assur´e avant l’entr´e dans le boucle par une
   initialisation correcte des variables.

                                      e       e                a e
Le fait que l’invariant est conserv´ par it´ration est facile ` ´tablir. On suppose pour l’instant
                                                                            e
que tous les autres modules sont corrects. On suppose de plus (hypoth`se H) que l’invariant est
                       e
satisfait avant une it´ration. Celle-ci provoque la lecture du mot suivant et son traitement. Ce
                     a     e
dernier consistant ` incr´menter le nombre n des apparitions du mot s’il est dans le lexique (donc
        eae e           e             e          e               a             a                   a
s’il a d´j` ´t´ rencontr´ n fois d’apr`s l’hypoth`se H) ou sinon ` le rajouter ` la bonne place (grˆce
                                               a                  a
au module ins´rer) tout en initialisant n ` 1, l’invariant est ` nouveau satisfait par la nouvelle
                  e
                       e     e
valeur du lexique apr`s l’it´ration.

                                                        o
A la sortie de la boucle tant que, la condition de contrˆle prend la valeur false. Le texte est donc
       e                                  a             a         e
termin´. Mais comme l’invariant est vrai ` ce moment l`, on en d´duit que le lexique contient exac-
                            e                   e                                     e
tement l’ensemble, ordonn´ par l’ordre alphab´tique, des mots du texte, accompagn´s du nombre
de leurs apparitions.

           La correction partielle s’obtient en combinant invariant et condition de sortie.

                e               ee
   1. On pourra ´galement se r´f´rer aux deux exemples du calcul du pgcd dans la section 1.4.3 et de la puissance
e                                                 a
´gyptienne dans la section 4.3.1, qui contribuent ` illustrer ce paragraphe
56                                                                             ´ ´
                                                        CHAPITRE 5. PRINCIPES GENERAUX


Enfin, la preuve de terminaison repose sur la correction de la saisie d’un mot nouveau et celle
       e                                                  e
de la d´tection de la fin du fichier. Si le texte est compos´ d’un nombre fini de mots (l’algorithme
                  a                                                                         e
ne s’applique pas ` l’ensemble, virtuellement infini, des mots de la toile !), le nombre d’it´rations
    e
est ´gal au nombre de mots du texte, donc il est fini. cqfd.


5.3                  e
        Algorithmes r´cursifs
                 e e               e            e                   e            e
    La section pr´c´dente a illustr´ comment d´composer un probl`me en probl`mes plus simples.
                             e                 e
Une autre approche, appel´e diviser pour r´gner (en anglais divide and conquer), consiste `         a
 e               e                   e                   e         e      ea e
r´soudre le probl`me sur une donn´e en supposant le mˆme probl`me d´j` r´solu sur des donn´es     e
                                                       e                             e
plus simples. Un algorithme A qui, pour calculer le r´sultat A(x) sur une donn´e x, utilise des
 e                    e                       e                  e       a               e
r´sultats A(y) suppos´s connus sur des donn´es y de taille inf´rieure ` x, est appel´ algorithme
r´cursif. Par exemple, si l’on suppose que l’on connait (n − 1)!, alors la formule n! = n × (n − 1)!
 e
                                                                                      n
                              e
permet de calculer n!. De mˆme, si l’on suppose que l’on sait trier un tableau `   a     ee
                                                                                         ´l´ments, on
                                                                                       2
                              ee                                               e
peut trier un tableau de n ´l´ments en triant tout d’abord ses deux moiti´s, et en fusionnant
                                               e                             e              e
ensuite ces deux parties qui sont alors ordonn´es. La suite de la section pr´cise cette pr´sentation
informelle.

5.3.1    Introduction sur des exemples
                    e      e                      e                                e
Une fonction f est d´finie r´cursivement quand sa d´finition fait intervenir f elle-mˆme.

Exemple 5.1 (La factorielle)
  fact(0) = 1
  fact(n) = n × fact(n-1) ∀n ≥ 1

                    e e    e
Exemple 5.2 (La d´riv´e ni`me)
  f(0) = f
  f(n) = (f ’)(n−1) ∀n ≥ 1

Exemple 5.3 (La puissance ´gyptienne) ∀x ∈ I
                            e              R
  x0 = 1
  xn = (x2 )n/2 ∀n > 0, n pair
  xn = x × xn−1 ∀n > 0, n impair.

Exemple 5.4 (L’algorithme d’Euclide) ∀a ∈ I , ∀b ∈ I ∗
                                          N        N
  ∆(a, b) = b si b divise a,
  ∆(a, b) = ∆(b, a mod b) sinon.

Exemple     5.5 (Le pgcd : autre m´thode) ∀a ∈ I , ∀b ∈ I ∗
                                      e               N       N
  δ(0, b)   =b
  δ(a, b)   = 2 × δ(a/2, b/2) si a et b sont pairs et a > 0
  δ(a, b)   = δ(a/2, b)    si a est pair et b impair et a > 0
  δ(a, b)   = δ(a, b/2)    si a est impair et b pair
  δ(a, b)   = δ(a − b, b)  si a et b impairs et a ≥ b
  δ(a, b)   = δ(a, b − a)  si a et b impairs et a < b.

       e     e
Etude d´taill´e de l’exemple 5.1
                             e        e                                                      e
L’algorithme induit La d´finition r´cursive de la fonction fact exprime un algorithme r´cursif.
                    a
Elle permet en effet ` un automate de calculer fact(n) pour tout entier n. Si n = 6, un tel automate
     e          e               e                                          e               e
proc`derait par ´tapes comme d´crit par la figure 5.1. L’algorithme peut ˆtre programm´ en Java
                                                                   e
en traduisant directement les deux conditions qui constituent la d´finition.
                  ´
5.3. ALGORITHMES RECURSIFS                                                                           57

fact(6) =   6 × fact(5)                                     (1)
        =   6 × 5 ×fact(4                                   (2)
        =   6 × 5 × 4 × fact(3)                             (3)
        =   6 × 5 × 4 × 3 × fact(2)                         (4)
        =   6 × 5 × 4 × 3 × 2 × fact(1)                     (5)
        =   6 × 5 × 4 × 3 × 2 × 1× fact(0)                  (6)
        =   6 × 5 × 4 × 3 × 2 × 1× 1                        (7)
        =   6×5×4×3×2×1                                     (8)
        =   6×5×4×3× 2                                      (9)
        =   6×5×4×6                                        (10)
        =   6 × 5 × 24                                     (11)
        =   6 × 120                                        (12)
        =   720                                            (13)

                                  e
                    Figure 5.1 – D´roulement de l’algorithme fact pour n=6


static int fact (int n) {                     ou encore              static int fact (int n) {
  if (n==0)                                                            if (n==0) return 1;
    return 1;                                                          return(n*fact(n-1));
  else                                                               }
    return(n*fact(n-1));
}
         e            e                                                                  e
Cette m´thode est r´cursive puisque si n est strictement positif, elle se rappelle elle-mˆme sur
l’argument (n − 1). Le d´roulement par ´tapes de l’algorithme d´taill´ figure 5.1 pour la valeur
                         e                 e                        e e
              e                 e               e                            e
n = 6, suit tr`s exactement l’ex´cution de la m´thode. Chacune des six premi`res lignes comporte
                    e                                                              e
en gras un appel r´cursif. Les multiplications sont mises en attente tant que l’ex´cution de cet
       e                        e
appel r´cursif n’est pas termin´e. Par exemple la ligne (4) comporte l’appel fact(2), dont les
e              e                  e           e     a
´tapes de l’ex´cution sont soulign´es et s’ach`vent ` la ligne (9).


           e         e                               e              ıt,
Pile de r´cursivit´ La longueur des six premi`res lignes croˆ tout comme l’espace occup´ en        e
   e         u                                               e                          ea
m´moire, o` la nouvelle valeur mise en attente est empil´e sur celles qui le sont d´j`. La notion
                             e                                                          ee         e e
de pile sera introduite en d´tail par la suite. Informellement, il s’agit d’une suite d’´l´ments g´r´e
                                                                        e   e      c
comme une pile au sens courant du terme (et mentalement repr´sent´e de fa¸on verticale) : on
              ee                    a           e   e                     e
rajoute un ´l´ment en l’empilant ` une extr´mit´ de la liste, on d´pile en supprimant le dernier
ee              e                                     e    e
´l´ment empil´. Sur la figure 5.1, la pile est repr´sent´e horizontalement, son sommet en ´tant   e
      e    e                             e                 e      e
l’extr´mit´ droite. Cette pile, constitu´e des valeurs m´moris´es en attente de la fin des appels
 e                 e            e         a                                     e           e
r´cursifs dont le r´sultat est n´cessaire ` la poursuite des calculs, est appel´e pile de r´cursivit´.e
                        e e
Dans l’exemple consid´r´, sa taille maximale est n fois la taille d’un entier, si n est la donn´e.   e
               e                      a                 e      a e               e
Cet espace m´moire, contrairement ` ceux rencontr´s jusqu’` pr´sent et qui ´taient explicitement
      e            e
allou´s par une d´claration (types simples) ou une instruction new (objets et tableaux), est cr´´     ee
                                                             e                  e
dynamiquement et automatiquement du fait des appels r´cursifs. Lors de l’´valuation de l’espace
   e         e       a      e                            e
m´moire n´cessaire ` l’ex´cution d’un algorithme r´cursif, il convient donc de bien prendre en
                       e        e            e                   e
compte cette pile de r´cursivit´ dont la cr´ation et la gestion ´chappe au programmeur.


                                                         e
Cas terminal et terminaison Le dernier appel r´cursif (ligne (6)) aboutit au cas terminal
               e                                     e
(n=0) de la d´finition, pour lequel le calcul du r´sultat (qui vaut 1) ne requiert pas de nouvel
       a
appel ` la fonction : c’est ce qui assure la terminaison de l’algorithme. Les sommets de pile sont
       e e       a          e           e a
alors d´pil´s un ` un pour ˆtre multipli´s ` la valeur courante (en gras), et ce tant que la pile n’est
pas vide.

    e          e
Apr`s cette pr´sentation informelle, les deux sections qui suivent traitent respectivement de la
                                                 e
terminaison et de la correction des algorithmes r´cursifs.
58                                                                               ´ ´
                                                          CHAPITRE 5. PRINCIPES GENERAUX

5.3.2                                  e
          Terminaison des algorithmes r´cursifs
                     e                                  e
   Un algorithme r´cursif peut boucler sur une donn´e quand le calcul de celle-ci provoque une
      e           e                                                            e
infinit´ d’appels r´cursifs, sans que jamais un cas terminal ne soit atteint. L’´tude de la terminaison
                   e                a                              e
d’un algorithme r´cursif consiste ` trouver l’ensemble des donn´es pour lesquelles l’ensemble des
        e
appels r´cursif est fini.

Etude de deux exemples
La factorielle La terminaison de l’algorithme se prouve en remarquant que pour tout entier
strictement positif n, f act(n) appelle r´cursivement f act(n − 1). Les appels se font donc succes-
                                         e
sivement sur les arguments :
                                      n, n − 1, n − 2, n − 3, . . .
                                            e                   e
Cette suite atteint donc l’argument 0 apr`s un nombre fini d’´tapes (n exactement). Or le cas
                                          e              e
n = 0 est un cas terminal pour lequel le r´sultat est imm´diat. Donc l’algorithme se termine pout
tout entier n ≥ 0.

                                                                                          e
L’algorithme d’Euclide Pour tout couple (a, b) de l’ensemble de d´finition, si b n’est pas un
                                  e                                u
diviseur de a, ∆(a, b) appelle r´cursivement ∆(b, r0 ) o` r0 est le reste de la division de a par b.
Les appels se font donc successivement sur :
                     (a, b), (b, r0 ), (r0 , r1 ), (r1 , r2 ), (r2 , r3 ),. . ., (ri , ri+1 ) . . .
o`, pour tout indice i ≥ 0, ri+1 est le reste de la division euclidienne de ri−1 par ri (avec la
 u
                                           e                    e
convention : r−1 = b). Par suite, le reste ´tant strictement inf´rieur au diviseur,
                                         ∀i ≥ −1, ri > ri+1 .
                             a                e                           ee
L’algorithme calcule donc, ` chaque appel r´cursif, un nouvel ´l´ment de la suite
                                   b = r−1 , r0 , r1 , r2 , . . . , ri , . . .
                                e
qui est une suite strictement d´croissante d’entiers positifs : elle est donc finie. Par suite, le nombre
          e
d’appels r´cursifs est fini, ce qui prouve que l’algorithme se termine.

                   e
Induction bien fond´e
     Plus g´n´ralement, pour prouver qu’un algorithme r´cursif se termine sur toute donn´e x0 ∈ D,
           e e                                                   e                       e
il faut montrer que les suites des arguments
                                       x0 , x1 , . . . , xi , xi+1 , . . .
             e                                          c               e  a
des appels r´cursifs successifs sont finies. Une fa¸on de proc´der consiste ` trouver une relation >
telle que
                                   x0 > x1 > · · · > xi > xi+1 > . . .
                                                           e                      e
et pour laquelle on prouve qu’une telle suite est nessaic´rement finie. On en d´duit alors que la
                  e                           e
suite des appels r´cursifs est finie, ce dont d´coule la terminaison de l’algorithme.
  e                                     e
D´finition 5.1 (Relations bien fond´es) Soit E un ensemble et une relation binaire sur E
    e                        ee
not´e >. Toute suite (xn ) d’´l´ments de E telle que :

                                   x0 > x1 > · · · > xi > xi+1 > . . .

         e     ı                                                                   e
est appel´e chaˆne descendante pour la relation >. La relation > est dite bien fond´e si toute
    ı                ee
chaˆne descendante d’´l´ments de D est finie.

  e e                                                        e            e
Th´or`me 5.2 Soit D un ensemble muni d’une relation bien fond´e > et un pr´dicat P sur D.
    e                                        e
On d´montre le principe d’induction bien fond´e suivant.

                            ∀x ∈ D     (∀y ∈ D, y < x ⇒ P (y)) ⇒ P (x)
                                                                                                  (5.1)
                                           ∀x ∈ D P (x)
                  ´
5.3. ALGORITHMES RECURSIFS                                                                     59

                       e                  ee
Autrement dit, pour d´montrer que tout ´l´ment de D satisfait P , il suffit de prouver que tout x
                                e            ee           e      a
de D satisfait P , sous l’hypoth`se que tout ´l´ment y inf´rieur ` x satisfait P .

                                e    e                                    e
Ce principe d’induction est pr´sent´ dans la formule 5.1 sous forme de s´quent : il s’agit d’une
                               e          e                  e        e
notation fractionnaire, le num´rateur repr´sentant l’hypoth`se et le d´nominateur la conclusion.
La r`gle 5.1 signifie donc que pour prouver ∀x ∈ D P (x), il suffit, pour tout x de D, de d´montrer
    e                                                                                   e
                    e
P (x) sous l’hypoth`se dite d’induction :

                                      ∀y ∈ D, y < x ⇒ P (y)

            e e        e
Preuve Le th´or`me se d´montre par l’absurde. Supposons que

                       ∀x ∈ D     (∀y ∈ D, y < x ⇒ P (y)) ⇒ P (x)      (H)

                   ee                                       e          e            e
et qu’il existe un ´l´ment x0 qui ne satisfait pas P . D’apr`s l’hypoth`se (H), on d´duit que la
proposition
                                    ∀y ∈ D, y < x0 ⇒ P (y)
                                   ee
est fausse et donc qu’il existe un ´l´ment x1 < x0 qui ne satisfait pas P . A partir de x1 et avec
      e                     e                    ee
les mˆmes arguments, on d´duit l’existence d’un ´l´ment x2 < x1 qui ne satisfait pas P . On peut
                                                           e ee
ainsi de proche en proche, prouver l’existence d’une infinit´ d’´l´ments ne satisfaisant pas P et
tels que :
                                   x0 > x1 > . . . > xk > . . .
                                                          e
Ceci est contraire au fait que la relation > est bien fond´e. 2

   e e                                                           e
Th´or`me 5.3 Soit D un ensemble muni d’une relation bien fond´e >. Soit A un algorithme
 e        e                                 e                      e
r´cursif d´fini sur D. Si, pour toute donn´e x de D, A(x) appelle r´cursivement A(y) sur des
     e
donn´es y telles que y < x, alors l’algorithme se termine.

                  e                          e    e e
Preuve On proc`de par induction bien fond´e (th´or`me 5.2).
Soit x ∈ D. Supposons (hypoth`se d’induction) que l’agorithme se termine sur toute donn´e y < x.
                                e                                                       e
                                               e                           e
Alors il se termine sur x puisque les rappels r´cursifs ont lieu par hypoth`se sur des arguments
y < x.
Conclusion : l’algorithme se termine pour tout x de D. 2

Corollaire 5.4 Soit D un ensemble muni d’une fonction :
                                          taille : D → IN
                        e                             e
Soit A un algorithme r´cursif sur D. Si tout appel r´cursif se produit sur un argument de taille
               e       a                e
strictement inf´rieure ` l’argument de d´part, alors A se termine.

                      e
En effet, la relation d´finie par
                                  x > y ⇔ taille(x) > taille(y)
             e
est bien fond´e, ce qui permet de prouver la terminaison de l’algorithme.

                                                      e
Pour l’exemple de la factorielle, taille est l’identit´ et pour l’algorithme d’Euclide, il suffit de
  e
d´finir taille(a, b) = b.
                                                                                 e         a
L’existence d’une telle fonction taille est une condition suffisante, mais non n´cessaire ` la ter-
minaison. La preuve de terminaison de la fonction d’Ackermann (cf. section 6.2.2) s’appuie sur
                                    e               e     e    a
l’existence d’une relation bien fond´e qui ne peut ˆtre d´finie ` partir d’une taille.

                                                              e                 e       e
Il faut savoir que prouver la terminaison d’un algorithme r´cursif peut s’av´rer extrˆmement
                                                                    a                 e
difficile. La terminaison de l’algorithme syracuse (exercice 5.3) est ` ce jour un probl`me ouvert.

              e
Exercice 5.1 D´montrer la terminaison des algorithmes des exemples 5.3 et 5.5.
60                                                                              ´ ´
                                                         CHAPITRE 5. PRINCIPES GENERAUX

5.3.3                               e
         Correction des algorithme r´cursifs
                                e                  e          e
Comme pour les algorithmes it´ratifs, on peut synth´tiser la d´finition de la correction d’un algo-
        e
rithme r´cursif par la formule :

                           correction = correction partielle + terminaison

                                 a e                                                       e
La correction partielle consiste ` d´montrer que si l’algorithme se termine sur une donn´e, alors il
            e                                                         e         e
produit le r´sultat attendu. La correction partielle d’un algorithme r´cursif s’´tablit par induction
          e         e                                                     e     a
bien fond´e. En pr´sence d’une fonction taille sur l’ensemble des donn´es, ` valeurs dans l’en-
                                          e                   e                               e
semble des entiers naturels, il suffit de d´montrer que le r´sultat produit pour une donn´e x est
                                                   e                           e        a
correct en supposant qu’il l’est pour toute donn´e y de taille strictement inf´rieure ` x. Dans les
                           e                                e
cas les plus simples, une r´currence sur la taille des donn´es suffit.

                                                       e
Cas de la factorielle Etablir la correction de la m´thode Java fact, autrement dit de l’algo-
                      e                                   a
rithme induit par la d´finition de l’exemple 5.1, consiste ` prouver que pour tout n, fact(n)=n!,
 u         e
o` n! est d´fini par :
                                                           n
                           0! = 1 et       ∀n > 0, n! =         i.   (Def )
                                                          i=0
Ceci est imm´diat pour n = 0.
            e                    Soit n ∈ I quelconque. Supposons que fact(n)=n!. Alors :
                                          N
 fact(n+1) = n×fact(n)                 e
                                 par d´finition de l’algorithme fact
              = n × n!                 e         e       e
                                 d’apr`s l’hypoth`se de r´currence
              = (n + 1)!               e     e
                                 d’apr`s la d´finition de (Def ) de la factorielle.

                                  e
Cas de l’algorithme d’Euclide La d´finition de la fonction ∆ de l’exemple 5.4 induit la
 e
m´thode Java :

public static int delta(int a, int b){
     int r = a%b;
     if(r == 0) return b;
     return (delta(b, r));
}

On montre que :
- pgcd(a, b) = b si b divise a.
- pgcd(a, b) = pgcd(b, a mod b)       sinon.

         e                   e
La premi`re condition est ´vidente. La seconde s’obtient en remarquant que si r et q sont res-
pectivement le reste et le quotient de la division euclidienne de a par b, du fait de la relation
                                              e
a = bq + r, les couples (a, b) et (b, r) ont mˆme ensemble de diviseurs communs.

On peut conclure en remarquant que la m´thode delta d´finissant une unique fonction sur I ×I ∗ ,
                                         e              e                                  N N
d’apr`s la preuve de terminaison, toute fonction f d´finie sur I × I ∗ et qui satisfait les relations
     e                                              e         N N
  e                                                       e         e
d´finissant l’algorithme est exactement la fonction calcul´e par la m´thode. Et c’est le cas de la
                             e                        e         a
fonction pgcd. Ce type de d´monstration s’applique ´galement ` la factorielle

Une autre d´monstration, plus “proc´duri`re”, peut se faire par induction. Soit (a, b) ∈ I × I ∗ ,
             e                        e  e                                                   N N
quelconque, fix´. Supposons que pour tout couple (a′ , b′ ) ∈ I × I ∗ , tel que b′ < b, le r´sultat soit
                 e                                            N N                          e
correct, c’est-`-dire : delta(a’,b’) = pgcd(a′ , b′ ). Montrons que delta(a,b) = pgcd(a, b).
               a
    e                 e
Le r´sultat est imm´diat si b divise a.
              e                e
Sinon, par d´finition de la m´thode delta, on sait que delta(a,b) = delta(b, a mod b), et
      e            e
d’apr`s l’hypoth`se d’induction delta(b, a mod b) = pgcd(b, a mod b) , enfin on a montr´ que      e
                                           e        e       e        e
pgcd(b, a mod b)= pgcd(a, b). De ces trois ´galit´s, on d´duit le r´sultat.
              ´
5.4. COMPLEXITE                                                                                    61

Exercice 5.2 Prouver la correction partielle des algorithmes des exemples 5.3 et 5.5.

Exercice 5.3 Prouver que l’algorithme d´fini sur I ∗ par :
                                           e         N
    – syracuse(1) = 1
    – syracuse(n) = syracuse(n/2) si n est pair
    – syracuse(n) = syracuse(3*n+1) si n est impair
calcule, s’il se termine sur toute donn´e n ≥ 1, la fonction constante et ´gale ` 1 sur I ∗ .
                                       e                                  e     a       N

 e            e
R´cursif ou it´ratif ?
         e                             e        e                 e
   On d´montre que tout algorithme r´cursif poss`de une version it´rative. Voici, par exemple,
              e              e
une version it´rative de la m´thode fact :

static int fact (int n) {
    int r = 1;
    for(int i=1; i<=n; i++) r *= i;
    return r;
}

                  e       e                e                                       e
Tout comme la m´thode r´cursive, cette m´thode effectue n multiplications. De mˆme, l’exemple
                     e                                                  e
5.4 est une version r´cursive de l’algorithme d’Euclide. Une version it´rative de cet algorithme
  ee       e
a ´t´ donn´e dans l’exemple 1.12 de la section 1.4.3 et reprise dans la section 2.2.1. Il s’agit du
  e                      e     e                                                        e
mˆme algorithme, exprim´ diff´remment : les divisions euclidiennes successives effectu´es par les
deux formes de l’algorithme sont identiques.

                           e                              e                       e
Toutefois l’espace occup´ dans l’un et l’autre cas diff`re. Pour la version it´rative du calcul de
                                    e                                                           e
la factorielle, trois entiers sont n´cessaires (n, i et r) quelle que soit la valeur de la donn´e n :
                 e                            e                                     e       e
l’espace occup´ est constant. La version r´cursive met en place une pile de r´cursivit´ de taille
                   a                                             e                e
proportionnelle ` n. Pour l’algorithme d’Euclide, la version it´rative requiert ´galement un espace
   e                                                     a            e
m´moire constant (trois entiers pour a, b et r). Quant ` la version r´cursive, elle empile les couples
                                 e                                                     e     ıt
(a,b) arguments des appels r´cursifs, en attente du cas terminal : l’espace occup´ croˆ avec la
     e
donn´e initiale.

                      e       e                   e
En conclusion, la r´cursivit´ est un outil pr´cieux qui offre un style de programmation de haut
                    e                                   e              e
niveau, souvent tr`s proche de la formulation math´matique. Elle d´charge le concepteur de tˆches a
                                               e                                         o
bureaucratiques comme la description d’it´rations au moyen de structure de contrˆles (boucles)
                                                                                              e
et la fastidieuse gestion de pile. Elle permet de plus les approches du type diviser pour r´gner qui
                                       e a                  a
permettent des solutions claires, ais´es ` concevoir et ` prouver. La contrepartie de ces avantages
                        e                                                                   e
est que l’expression r´cursive est parfois tellement proche conceptuellement des math´matiques,
                                             e e                                      e
que l’on peut en oublier qu’il s’agit en r´alit´ d’algorithmes qui effectuent des it´rations cach´es e
                  u                    e                u          e                e
et qui ont un coˆt en temps et en m´moire. Ces coˆts masqu´s par la simplicit´ de la formulation
              a
sont souvent ` l’origine d’explosions combinatoires. On en verra plusieurs exemples dans la suite de
ce cours. Il convient donc de bien maˆ             e                         e                  e
                                         ıtriser l’´valuation de la complexit´ en temps et en m´moire
                    e
d’un algorithme r´cursif avant de l’adopter.

                                                           u
On peut maintenant pressentir que les questions de coˆt en temps et en espace d’un algorithme
                                                        e
sont essentielles : la section suivante leur est consacr´e.


5.4              e
        Complexit´
                                                e                           e
   Il y a deux notions distinctes de complexit´ d’algorithmes : la complexit´ en temps et la com-
      e                                           e
plexit´ en espace. Il s’agit respectivement d’une ´valuation du comportement asymptotique (pour
         e                                                               e        e         a
des donn´es de taille arbitrairement grande) du temps et de l’espace m´moire n´cessaires ` leur
62                                                                            ´ ´
                                                       CHAPITRE 5. PRINCIPES GENERAUX

  e
ex´cution.

          e                     a            e            e     e
On peut ´tendre cette notion ` la complexit´ des probl`mes, d´finie comme celle du meilleur des
                      e           e                     e          e
algorithmes qui les r´sout. La th´orie de la complexit´ des probl`mes et des algorithmes est une
   e
th´orie difficile, hors du propos de ce cours. Elle comporte de nombreuses questions ouvertes. Par
                                                                                            e
exemple, on ne sait pas toujours prouver qu’un algorithme est optimal (meilleure complexit´) pour
         e          e                                e                 e
un probl`me donn´. De plus, si l’on se propose de d´finir le temps d’ex´cution d’un algorithme en
                              e                                                  ee e
fonction de la taille des donn´es, encore faut-il que de ces deux concepts aient ´t´ pr´alablement
                  e         e
rigoureusement d´finis ind´pendamment de tout langage de programmation et de toute machine.

                                 a               e                                      e
Dans cette section, on s’attache ` donner une pr´sentation pragmatique de la complexit´ en temps
                      o                                             e
des programmes plutˆt que des algorithmes, tout en raisonnant ind´pendemment du langage de
                                                     e           e
programmation et de la machine sur laquelle ils s’ex´cutent. La d´finition de la taille d’un entier
                        e                      e                           e
permet de faire la diff´rence entre ces deux d´marches. Lorsque l’on s’int´resse aux algorithmes
                       e                      e                          e
dans une approche th´orique de la complexit´, la taille d’un entier est d´finie comme le nombre
                    e
de chiffres de son d´veloppement binaire. L’algorithme de multiplication de deux entiers est alors
                 u                 e                                                  e     a
d’autant plus coˆteux que les op´randes sont grands. Lorsque, au contraire, on s’int´resse ` des
                          e      e e
programmes, on consid`re en g´n´ral que les entiers ont une taille fixe (puisque c’est le cas en
                u
machine), le coˆt d’une multiplication est donc constant. C’est ce dernier point de vue qui est
      e
adopt´ dans cette section.


5.4.1       u               e
          Coˆ t et complexit´ en temps d’un algorithme
                 e            u                       e                            e
    Le temps d’ex´cution ou coˆt d’un algorithme est d´fini comme le nombre d’op´rations signifi-
                   e                       e e                                   e               e
catives (OS) effectu´es. Ce nombre est en g´n´ral fonction de la taille de la donn´e. La complexit´
              e
en temps est d´finie comme le comportement asymptotique du coˆt.   u


               e        e
Taille des donn´es et op´rations significatives (OS)

         e                               e                   e
   Les d´finitions de la taille de la donn´e et des OS sont li´es. Il s’agit en pratique de mettre
   e                 e                 e                ıt                 e
en ´vidence un param`tre sur les donn´es avec lequel croˆ le temps d’ex´cution. Voici, sur trois
                       e                                                    e
exemples, comment se d´termine en pratique le choix de la taille des donn´es et des OS.
                        ee                                                 a
     Recherche d’un ´l´ment dans un ensemble L’algorithme consiste ` parcourir l’ensemble
                                     ee      a                        e              e
      en comparant chaque nouvel ´l´ment ` celui qui est recherch´. Le temps d’ex´cution est
                                                  e                     e
      fonction du nombre de comparaisons effectu´es, et celui-ci pourra ˆtre d’autant plus grand
                  ee
      qu’il y a d’´l´ments dans l’ensemble. Les OS sont donc ici les comparaisons et la taille de
              e
      la donn´e est le cardinal de l’ensemble.
                                                               a
     Produit de deux matrices Le calcul du produit consiste ` effectuer des sommes de produits
                                           e e
      de coefficients. Il est d’autant plus ´lev´ que les matrices sont grosses. Les OS sont donc
      les sommes et produits de flottants, et c’est le format des matrices qui est ici pris en
                         e
      compte pour la d´finition de la taille.
                                 e                                                e
     Calcul de la puissance ni`me d’une matrice Un calcul de la puissance ni`me d’une ma-
                e                             a
      trice carr´e de dimension dim consiste ` effectuer un certain nombre de produits de telles
                       u                                e
      matrices. Le coˆt de chacun de ces produits ne d´pend que de dim. Optimiser l’algorithme
                     a
      revient donc ` en minimiser le nombre (cf. section 4.3), puisque l’on ne peut en aucune
            e                      u      e           e
      mani`re influer sur leur coˆt. La d´marche int´ressante est ici de choisir le produit de
                            e
      deux matrices carr´es de format dim pour OS et puisqu’alors le nombre d’OS croˆ           ıt
      avec l’exposant n, c’est n qui est choisi comme taille.

                                                                                        e
On verra par la suite que si un doute subsiste entre plusieurs choix d’OS paraissant coh´rents,
                                                              u
ceux-ci n’influent pas sur le comportement asymptotique du coˆt.
              ´
5.4. COMPLEXITE                                                                                    63

  u
Coˆ t en moyenne et dans le pire des cas
                              e                                              e           e
    Le nombre d’OS peut d´pendre non seulement de la taille des donn´es, mais ´galement de
                                                ee                             ee
leur configuration. Lorsque l’on recherche un ´l´ment x dans une liste de n ´l´ments, la recherche
      e      e                                ee
s’arrˆte imm´diatement si x est le premier ´l´ment de la liste : c’est le meilleur des cas. En re-
vanche il faut faire n comparaisons, s’il est en fin de liste ou s’il n’y figure pas. On dit que la
              e                              u
fonction C d´finie par C(n) = n est le coˆt de l’algorithme dans le pire des cas. Lorsqu’il y a
une telle distorsion entre le meilleur et le pire des cas, il est pertinent de rechercher le nombre
             e                                          e              e
d’OS effectu´es en moyenne. En supposant qu’il y ait ´quiprobabilit´ des n+ 1 situations possibles
(l’´l´ment cherch´ n’est pas dans la liste, ou il est au i`me rang pour i ∈ {1, . . . , n}), le nombre
   ee             e                                       e
moyen d’OS est :
                                                n
                                          1                n    n
                           Cmoy (n) =        (    i + n) =   +     .
                                        n + 1 i=1          2   n+1

             e               u
Techniques d’´valuation du coˆ t
                    e                 e
   Le nombre d’OS s’´value selon les r`gles suivantes :

                                  u
   Suite d’instructions Le coˆt est la somme de celui de chaque instruction.
                           u                                                         u
   Conditionnelle Le coˆt de l’instruction if(C) I1 else I2 est la somme du coˆ t de l’´va-  e
                                                        u
     luation de la condition C et du maximum des coˆts des l’instructions I1 et I2 .
                    u                                                    u             e
   Boucles Le coˆt d’une boucle (while, for, . . .) est la somme des coˆts de chaque it´ration et
                e                                  o
     de celui l’´valuation de la condition de contrˆle.
                  e                          e       e            u          e              e
   Appel de m´thode S’il n’y a pas de r´cursivit´, c’est le coˆt de la m´thode. S’il y a r´cur-
          e                               e                              e
     sivit´, on obtient des relations de r´currence qu’il conviendra de r´soudre.

           e                 e                  e                        e
On consid`re par exemple la m´thode calculant it´rativement la puissance ´gyptienne d’un flottant
(cf. section 4.3.1).
static float puiss(float x, int n){
   float p = 1;
   while(n!=0){
     if(n%2 ==0)
       {x *= x; n /= 2;}
     else
       {p *= x; n--;}
   return p;
}
                                                     e
On a vu dans la section 4.3.1 que le nombre d’it´rations de la boucle while est compris entre
⌊lg2 (n)⌋ + 1 ` 2⌊lg2 (n)⌋ + 1. Si les OS sont les divisions, les multiplications, les d´cr´mentations
              a                                                                         e e
                                 e                                u
et les comparaisons, chaque it´ration effectue cinq OS. Le coˆt C(n) de l’algorithme est donc tel
que
                                5⌊lg2(n)⌋ + 5 ≤ C(n) ≤ 10⌊lg2(n)⌋ + 5
             e                                     e                               e            e
Une version r´cursive, directement traduite de la d´finition de l’exemple 5.3, peut-ˆtre programm´e
comme suit :
static float puiss(float x, int n){
  if (n==0) return 1;
  if (n%2 ==0) return(puiss(x*x, n/2));
  return(x*puiss(x, n-1));
}
Si n = 0, le calcul de xn requiert une comparaison de n ` 0 , un calcul du reste de la division de
                                                        a
n par 2, et :
64                                                                               ´ ´
                                                          CHAPITRE 5. PRINCIPES GENERAUX

                                                             e
   - soit, si n est pair, une multiplication (de x par lui-mˆme), une division de n par 2 et un calcul
                n
de puissance i`me.e
                2
                                        e e
   - soit, dans le cas contraire, une d´cr´mentation, une multiplication par x et un calcul de puis-
sance (n − 1)i`me.
                e
         e                    u           u                 e
On en d´duit, en notant coˆt(n) le coˆt de la version r´cursive pour un exposant n, que la suite
   u                 e                        e
(coˆt(n))n∈I est d´finie par les formules r´currentes suivantes :
              N


       u
     coˆt(0) = 0
       u         u
     coˆt(n) = coˆt(n/2)+5 si n> 0 et pair
       u         u
     coˆt(n) = coˆt(n-1)+5 si n> 0 et impair

      c        e          e                                       e       a       e        e
Une fa¸on de r´soudre ces ´quations consiste, comme pour le cas it´ratif, ` consid´rer le d´veloppe-
                         e
ment binaire de n. Si u d´signe une suite binaire, on note u.0 ou u.1 la suite de symboles obtenue
         e       a                                                           e             e e
en concat´nant ` la suite u respectivement le symbole 0 ou 1. Ainsi, les ´quations pr´c´dentes
          e
peuvent s’´crire :

       u
     coˆt(0) = 0                (1)
       u           u
     coˆt(u.0) = coˆt(u)+5      (2)
       u           u
     coˆt(u.1) = coˆt(u.0)+5    (3)

                                e                                                 e     e
Par exemple si n=19, il a pour d´veloppement binaire 10011. On obtient la suite d’´galit´s sui-
vantes :

coˆt(n) = coˆt(10011) = cout(10010) + 5 = coˆt(1001) + 2 × 5 =
  u          u                               u
coˆt(1000) + 3 × 5 = coˆt(100) + 4 × 5 = coˆt(10) + 5 × 5 =
  u                    u                   u
coˆt(1) + 6 × 5 = coˆt(0) + 7 × 5 = 35.
  u                 u

       e e                                        e                                    e
Plus g´n´ralement, notons bk bk−1 . . . b1 b0 le d´veloppement binaire de n. On a montr´ (cf. sec-
tion 4.3.1) qu’alors k = ⌊log2 (n)⌋. On sait de plus que :

                                    u         u
                                  coˆt(n) = coˆt(bk bk−1 . . . b1 b0 ).

                            a                 e        e               u           a e
Le calcul de cette valeur ` partir de la d´finition r´cursive de coˆt aboutira ` l’´quation (1)
apr`s avoir utilis´ l’´quation (3) pour tous les indices i ∈ {0, . . . k} tel que bi = 1, donc un
    e             e e
                                               e
nombre de fois compris entre 1 et k + 1, et l’´quation (2) exactement k fois. Chaque utilisation de
l’´quation (2) ou (3) rajoutant 5 au r´sultat, on en d´duit que le 5(1 + k) ≤coˆ t(n)≤ 5(2k + 1),
  e                                     e              e                         u
      a
c’est-`-dire :
                             5⌊log2 (n)⌋ + 5 ≤coˆt(n) ≤ 10⌊log2(n)⌋ + 5
                                                 u

     u                                          e           e
Le coˆt est identique pour les deux versions (it´rative et r´cursive) de l’algorithme.


         e
Complexit´ en temps
                 e                  e                                                 u
    La complexit´ en temps est d´finie comme le comportement asymptotique du coˆ t de l’algo-
                                 e     u                                       e                 e
rithme. Comme l’on a distingu´ le coˆt en moyenne et dans le pire des cas, on d´finit la complexit´
                                                                                            e e
en temps en moyenne et dans le pire des cas. La notion de comportement asymptotique est pr´cis´e
                                                                                         u a
dans la section 5.4.2. Il s’agit de comparer le comportement en +∞ de la fonction de coˆ t ` celui
                                              o
des fonctions classiques (logarithmes, polynˆmes, exponentielles . . .).

                    e                                                      e                ee
Ainsi, les complexit´s dans le pire des cas et en moyenne de la recherche s´quentielle d’un ´l´ment
                                    e
dans une liste sont toutes deux lin´aires en ce sens qu’elles se comportent comme des polynˆmeso
        e                                       u              o
de degr´ 1 en +∞ : dans le pire des cas le coˆt est le polynˆme C(n) = n et en moyenne, il est
              n
´quivalent ` . Pourtant, le second est approximativement deux fois plus faible que le premier.
e           a
              2
              ´
5.4. COMPLEXITE                                                                                              65

      a                              e
Quant ` l’algorithme de la puissance ´gyptienne, il est logarithmique en la taille de l’exposant.

                                      e
La section suivante a pour objet de pr´ciser la notion de comportement asymptotique.

5.4.2     Asymptotique
         e                                                                 e
    L’id´e est de comparer le comportement des algorithmes sur des donn´es de grande taille. On
       e                                                        e          e              u
consid`re par exemple deux algorithmes A et B, solutions du mˆme probl`me, dont les coˆ ts sont
respectivement d´finis par CA (n) = kn et CB (n) = k ′ n2 , avec k > 0, k ′ > 0. Le premier est dit
                   e
   e                                                     e
lin´aire et le second quadratique. En termes de complexit´, A est meilleur que B effet :
                               ∃N0 ∈ IN ∀n ≥ N0 CA (n) < CB (n)
                u                                                  e                       e
Par suite le coˆt de A sera moindre que celui de B pour toute donn´e dont la taille est sup´rieure
ou ´gale ` N0 2 .
    e      a

Relations de comparaison
  e
D´finition 5.5 Soient f et g deux fonctions de l’ensemble des entiers naturels dans l’ensemble
              e                                    e
des nombres r´els positifs. On dit que f est domin´e asymptotiquement par g, ou que l’ordre de
                                  e         e    a
grandeur asymptotique de f est inf´rieur ou ´gal ` celui de g, et on note f=O(g) ou encore f∈O(g)
si et seulement si :
                          ∃k > 0 ∃N0 ∈ I   N ∀n ≥ N0 f (n) ≤ k.g(n)

  e
D´finition 5.6 Soient f et g deux fonctions de l’ensemble des entiers naturels dans l’ensemble
                e                                      e
des nombres r´els positifs. On dit que que f et g ont mˆme ordre de grandeur asymptotique, et on
                                                                             e
note f=Θ(g) ou encore f∈Θ(g) si et seulement si l’une des trois propositions ´quivalentes suivantes
est satisfaite :
   1. f=O(g) et g=O(f )
   2. ∃k1 > 0     ∃k2 > 0    ∃N0 ∈ I
                                   N       ∀n ≥ N0     k1 .g(n) ≤ f (n) ≤ k2 .g(n)
   3. ∃k1 > 0     ∃k2 > 0    ∃N0 ∈ I
                                   N       ∀n ≥ N0     k1 .f (n) ≤ g(n) ≤ k2 .f (n)

  e                                             a e               e
L’´quivalence des trois propositions est facile ` ´tablir et laiss´e au lecteur. On remarque les
                        e      a                                                    e           e
relations O et Θ sont d´finies ` une constante multiplicative strictement positive pr`s (cf. la r`gle
3 de la proposition 5.7 ci-dessous).

Proposition 5.7 Soient f, f1 , f2 , g, g1 , g2 des fonctions de l’ensemble des entiers naturels dans
                        e                     e            e
l’ensemble des nombres r´els positifs. On d´montre les r`gles suivantes.
   1. f=O(f )
   2. f=O(g) et g=O(h) ⇒ f=O(h)
   3. f=O(g)⇒ ∀a > 0, a.f=O(g)
   4. f1 =O(g1 ) et f2 =O(g2 ) ⇒ f1 +f2 = O(max(g1 , g2 ))
   5. f1 =O(g1 ) et f2 =O(g2 ) ⇒ f1 -f2 = O(g1 )
   6. f1 =O(g1 ) et f2 =O(g2 ) ⇒ f1 .f2 = O(g1 .g2 )
  7. f ≤ g ⇒ O(f+g) = O(f )
        f
  8. lim = a > 0 ⇒ f = Θ(g)
     +∞ g
        f
  9. lim = 0 ⇒ f = O(g) et g = O(f )
     +∞ g

        f
 10. lim = +∞ ⇒ g = O(f ) et f = O(g)
     +∞ g


   2. Il n’en reste pas moins vrai que si k ′ = 1 et k = 109 , B est plus efficace que A pour toute donn´e dont la
                                                                                                      e
              e       a
taille est inf´rieure ` cent millions.
66                                                                                  ´ ´
                                                             CHAPITRE 5. PRINCIPES GENERAUX

                                                                                            f
     e             e ae               e
Ces r`gles sont ais´es ` ´tablir. La r`gle 8, par exemple, provient du fait que si lim        = a > 0,
                                                                                       +∞   g
                   ∀ǫ > 0   ∃N0    ∀n ≥ N0      (a − ǫ).g(n) < f (n) < (a + ǫ).g(n).
En choisissant ǫ < a, on obtient la propri´t´ 2 de la d´finition 5.6 avec k1 = a − ǫ et k2 = a + ǫ.
                                          ee           e

Il d´coule en particulier de ces r`gles que tout polynˆme P (n) = ak .nk + . . . + a1 .n + a0
    e                             e                   o
est en Θ(nk ) si ak > 0.


Echelle de comparaison

                                        e         e
    Les fonctions de comparaison utilis´es pour ´valuer le comportement asymptotique des fonc-
           u
tions de coˆt sont, par ordre croissant, les suivantes :

                   1, log(n), n, nlog(n), nα (α > 1), an (a>1), n !,            nn

                                                e
Chacune des fonctions de cette liste est domin´e asymptotiquement par la suivante. La premi`re   e
                  e      a                 e      e                                          e
est constante et ´gale ` 1. Θ(1) est d’apr`s la d´finition 5.6 l’ensemble des fonctions born´es en
                     u
+∞. Il s’agit du coˆt des algorithmes dits en temps constant (en fait, on devrait dire en temps
     e                                                                     ee
born´). Il en est ainsi par exemple d’un algorithme qui renvoie le premier ´l´ment d’une liste : son
  u          e                      ee                                          e
coˆt est ind´pendant du nombre d’´l´ments de la liste puisque le parcours s’arrˆte sur le premier.
                                                               e e                  e
On remarque de plus que la base des logarithmes n’est pas pr´cis´e. En effet d’apr`s la formule :

                                                         logb (n)
                                         loga (n) =
                                                         logb (a)

                                             e
les deux logarithmes loga (n) et logb (n) diff`rent d’une constante multiplicative strictement positive
  e                                             e       a           e
d`s que les bases a et b sont strictement sup´rieures ` 1. On en d´duit que :

                            ∀a > 1    ∀b > 1     Θ(loga (n)) = Θ(logb (n))

                                                      a            e
Il s’agit de l’ordre de grandeur asymptotique commun ` la complexit´ de tous les algorithmes dits
logarithmiques.
             e
Enfin d’apr`s la formule de Stirling, au voisinage de +∞ :
                                                 √     n
                                          n! ∼    2πn.( )n
                                                       e

Par suite, si a > 0 :
                                         n!   √       n n
                                           n
                                             ∼ 2πn.(     )
                                         a           e.a
et donc :
                                                         n!
                                      ∀a > 0,      lim      = +∞
                                                   +∞    an
    e
De mˆme,
                                          n!  √     1
                                             ∼ 2πn.( )n
                                          nn        e
et donc
                                                   n!
                                             lim      =0
                                             +∞    nn
Ceci explique que n! domine asymptotiquement toutes les exponentielles et soit domin´ par nn .
                                                                                    e
              ´
5.4. COMPLEXITE                                                                                      67

Quelques nombres

    Imaginons que l’ordinateur effectue 108 OS par seconde. Le tableau ci-dessous donne une
e                                                                     e
´valuation du temps de calcul en fonction de la taille n de la donn´e, pour des algorithmes dont
      u                         e          e
les coˆts respectifs sont indiqu´s en premi`re ligne. On utilise les notations suivantes :

 ns : nanosecondes        µs : microsecondes            ms : millisecondes
 " : secondes             ’ : minutes                   j : jours
 M millions d’ann´es
                 e        M M : milliards d’ann´es
                                               e        ∞ : plus de 3.10285 milliards d’ann´es
                                                                                           e
     e            eee
Les r´sultats ont ´t´ ´tablis sachant que log2 (10) = 3.321928094887362.

 Coˆt →
   u
               1      log2 (n)       n     nlog2 (n)       n2               n3             2n
   n↓
   102      10   ns    66 ns        1 µs    6,6 µs        0,1 ms         10 ms         4.105 MM
   103      10   ns    99 ns       10 µs     99 µs        10 ms            10"             ∞
   104      10   ns   0,13 µs     0.1 ms    1,3 ms           1"           2h46’            ∞
   105      10   ns   0,17 µs      1 ms    16,6 ms          1,7’       3 mois 25j          ∞
   106      10   ns    0,2 µs     10 ms       0,2"         2h46’        317 ans            ∞
   107      10   ns   0,23 µs       0.1"     2,33"        11,5 j      317.000 ans          ∞
   108      10   ns   0,26 µs        1"      26,6"     3 ans 2 mois      317 M             ∞
   109      10   ns    0,3 µs       10"        5’        317 ans        317 MM             ∞

                            e                                       a                         e
Pour bien comprendre la port´e de ces chiffres, il faut savoir que l’ˆge de l’univers est estim´
` 13,7 milliards d’ann´es.
a                     e

                       a e
On portera attention ` l’´volution du temps de calcul de l’algorithme quadratique sur des donn´es e
de taille 10k pour k ≥ 5. On remarquera ´galement la diff´rence de temps de calcul de l’algorithme
                                          e              e
en n3 sur les donn´es n = 1000, n = 10000, et n = 100000. Enfin la derni`re colonne, entre autres,
                    e                                                       e
illustre ce qu’est une explosion combinatoire et montre qu’il est illusoire de fonder des espoirs sur
          e                                                            e
les progr`s techniques pour traiter informatiquement certains probl`mes. Multiplier par mille la
                                                                              e
vitesse des ordinateurs serait une belle performance, mais resterait d’un pi`tre secours dans le cas
                        e
de certaines complexit´s.

                           a
L’exemple suivant permet ` nouveau de mesurer ce qu’est une explosion combinatoire. Suppo-
sons qu’un algorithme sur les matrices ait un coˆt C(n) = 2n o` n est le nombre de coefficients de
                                                u             u
la matrice. Supposons maintenant que cet algorithme traite en une heure une matrice de format
10 × 10 (donc pour laquelle n = 100). Le coˆt du traitement d’une matrice de format 11 × 11 est :
                                            u

                                 C(121) = 2121 = 2100 × 221 = 221 C(100)

Il faut ainsi 221 fois plus de temps pour traiter la matrice 11 × 11 que pour traiter la matrice
10 × 10. Par suite, il faut 221 heures = 231 ans pour traiter la matrice de format 11 × 11.


Quelques exemples

                                        ı)         e      ee     e
Exemple 5.6 (Les tours de Hano¨ Ce probl`me a ´t´ pos´ par Edouard Lucas, math´maticien      e
         e     e               e                                e                            e
du XIX`me si`cle. On consid`re trois pieux et n disques perc´s en leur centre et de diam`tres tous
    e                              e                          e          e
diff´rents. Ces disques sont enfil´s sur l’un des pieux, appel´ pieu de d´part, par ordre de diam`tre e
 e                              e                               e             e
d´croissant. Le but est de les d´placer sur un autre pieu, appel´ pieu d’arriv´e, en utilisant si besoin
             e                                   e                       e         e
est le troisi`me pieu, dit pieu auxiliaire. Les d´placements doivent ob´ir aux r`gles suivantes :
                   e                       a
    – on ne peut d´placer qu’un disque ` la fois
                          e      e    e                             e       e
    – un disque ne peut ˆtre d´plac´ que sur un disque de diam`tre sup´rieur au sien ou sur un
       emplacement vide.
68                                                                                      ´ ´
                                                                 CHAPITRE 5. PRINCIPES GENERAUX

                    e           e        e          c    e               e
Il s’agit d’un probl`me qui se r´soud ais´ment de fa¸on r´cursive. Consid´rons l’algorithme hano¨
                                                                                                ı
      e                  e                                                       e
poss´dant quatre param`tres : le nombre n de disques et les trois pieux appel´s respectivement
                                                       e
d´part, auxiliaire et arriv´e. Cet algorithme est d´fini comme suit :
  e                            e

          ı     e           e
      hano¨(n, d´part, arriv´e, auxiliaire)
      si n>0
                   ı       e                       e
         /*1*/ hano¨(n-1, d´part, auxiliaire, arriv´e)
                e        e           e
         /*2*/ d´placer(d´part, arriv´e)
                   ı                       e    e
         /*3*/ hano¨(n-1, auxiliaire, arriv´e, d´part)

                                         e
Si n > 0, cet algorithme se rappelle r´cursivement deux fois. Les cas terminaux sont ceux pour
lesquels n = 0 : l’algorithme ne fait rien dans ces cas-l` 3 .
                                                         a

                                                      e         e                      e
Terminaison On choisit ici comme taille des donn´es, le param`tre n. On montre par r´currence
                      e
sur la taille des donn´es que l’algorithme se termine toujours.
Si n = 0, l’algorithme ne fait rien, donc il se termine.
Soit n ≥ 0, quelconque, fix´. Supposons que l’algorithme se termine sur toute donn´e de taille
                             e                                                       e
                    e                                    e                       e
n. Pour une donn´e de taille n + 1 les deux appels r´cursifs se font sur des donn´es de taille n,
                                   e      e                 e
et donc se terminent par hypoth`se de r´currence. En cons´quence, l’algorithme se termine pour
             e
toute donn´e de taille n + 1.

                                      `
Correction Si n = 0, il n’y a rien a faire, donc l’algorithme est correct. Soit n > 0 et sup-
posons que l’algorithme est correct pour n − 1 disques. La situation de d´part est la suivante :
                                                                         e




                      e
                     d´part                       auxiliaire                            e
                                                                                   arriv´e


Apr`s le premier appel r´cursif (instruction /*1*/), l’algorithme ´tant suppos´ correct pour n − 1
    e                    e                                        e           e
                                                               e        e
disques et le plus grand des disques n’influant pas sur les r`gles de d´placement des disques de
      e      e
diam`tre inf´rieur, on se retrouve dans la situation suivante :




                      e
                     d´part                       auxiliaire                            e
                                                                                   arriv´e


                              a e                                               e
L’instruction /*2*/ consiste ` d´placer le disque au sommet du pieu de d´part (dans ce cas il
                                           e                                             e
n’y en n’a plus qu’un) vers le pieu d’arriv´e, ce qui peut se faire sans enfreindre les r`gles. On est
alors dans la situation suivante :


     3. La seconde branche de la conditionnelle serait "sinon rien" et est ici sous-entendue (cf. section 1.1)
              ´
5.4. COMPLEXITE                                                                                  69




                 e
                d´part                    auxiliaire                       e
                                                                      arriv´e


L’instruction /*3*/, que l’on a suppos´e correcte, d´place les n − 1 disques du pieu auxiliaire
                                         e             e
                   e                             e                                     e
sur le pieu d’arriv´e, en utilisant le pieu de d´part comme auxiliaire. Le disque pr´alablement
    e                    e      e                    e    e        e      a
enfil´ sur le pieu d’arriv´e ne g`ne en rien, son diam`tre ´tant sup´rieur ` tous les autres, tout se
passe comme s’il n’existait pas. On obtient ainsi :




                 e
                d´part                    auxiliaire                       e
                                                                      arriv´e

L’algorithme est donc correct.

            e                       e                                          e
Complexit´ Les OS sont ici les d´placements des disques et la taille n du probl`me est le nombre
                                           e             e        a
de disques. Soit donc C(n) le nombre de d´placements n´cessaires ` l’algorithme hano¨ pour trai-
                                                                                    ı
            e                                         e           e
ter un probl`me de taille n. Les relations suivantes d´coulent imm´diatemment de l’algorithme :

   C(0) = 0
   C(n) = 2C(n − 1) + 1      ∀n > 0
    e               e         e
On d´duit de cette d´finition r´currente le calcul suivant pour n > 0 :
            C(n) = 2C(n-1)+1
2     × |   C(n-1)= 2C(n-2)+1
22    × |   C(n-2)= 2C(n-3)+1
                  ...
2n−1 × |    C(1) = 2C(0)+1
2n × |      C(0) = 0

            C(n) = 1 + 2 + 22 + . . . + 2n−1 = 2n − 1 = Θ(2n )


                                              e          e
Exemple 5.7 (La suite de Fibonacci) Elle est d´finie par r´currence comme suit :

     F0 = 1
     F1 = 1
     Fn = Fn−1 + Fn−2     si n > 1 ;
       e        e               e       e
On en d´duit imm´diatement une m´thode r´cursive calculant cette suite :

int fib(int n){
   if (n==0 || n==1)
70                                                                               ´ ´
                                                          CHAPITRE 5. PRINCIPES GENERAUX

        return(1);
     return(fib(n-1)+ fib(n-2));
}

                                                             e
Les OS sont ici les additions d’entiers et la taille du probl`me est le rang n du terme que l’on
              u                                     e
calcule. Le coˆt C de l’algorithme ainsi programm´ satisfait les relations :

     C(0) = C(1) = 0     (I)
     C(n) = C(n − 1) + C(n − 2) + 1      ∀n > 1     (E)
        e           a e          e
Le probl`me revient ` r´soudre l’´quation du second ordre(E) avec les conditions initiales (I).

        e e              e                  a e          e             e         e a
La premi`re ´tape de la r´solution consiste ` r´soudre l’´quation homog`ne associ´e, ` savoir :

                            C(n) − C(n − 1) − C(n − 2) = 0 (H)

                                         e
L’ensemble des suites solutions de cette ´quation est un espace vectoriel de dimension 2, donc de
la forme :
                                {(λϕn + µϕ′ )n∈I / (λ, µ) ∈ IR2 }
                                            n   N

o` (ϕn )n et (ϕ′ )n sont deux solutions non nulles. On cherche ces solutions sous la forme de suites
 u             n
g´om´triques (rn )n∈I , avec r = 0. Ces suites devant satisfaire (H), on obtient la condition :
 e e                  N

                                ∀n > 1, rn − rn−1 − rn−2 = 0

                                                   e           a
qui, puisque l’on suppose que r n’est pas nul, est ´quivalente ` :

                                           r2 − r − 1 = 0

      e
Cette ´quation a deux solutions :
                                  √                                       √
                               1+ 5                                    1− 5
                          ϕ=              (nombre d′ or)        ϕ′ =
                                 2                                       2
                   e             e                                             e e
Les solutions de l’´quation homog`ne (H) sont donc toutes les suites de terme g´n´ral :

                                    λ.ϕn + µ.ϕ′n     (λ, µ) ∈ I 2
                                                              R

          e   e          e                  a                               e        e
La deuxi`me ´tape de la r´solution consiste ` trouver une solution particuli`re de l’´quation (E). La
                                         e                           e
suite constante (−1)n convient. On en d´duit que les solutions de l’´quation (E) sont exactement
                     e e
les suites de terme g´n´ral :

                                λ.ϕn + µ.ϕ′n − 1          (λ, µ) ∈ I 2
                                                                   R

Sachant que l’on recherche parmi ces solutions, celles qui satisfont les conditions initiales (I), on
    e
en d´duit qu’il faut et il suffit que : λ + µ = 1
                                      λϕ + µϕ′ = 1
On trouve ainsi :
                                        √                                       √
                    1 − ϕ′     ϕ      5+ 5                                    5− 5
              λ =          =        =                      et    µ =1−λ =
                    ϕ − ϕ′   ϕ − ϕ′    10                                      10
                     e             e
On obtient alors le r´sultat cherch´ :
                                       √        √
                                     5+ 5 n  5 − 5 ′n
                              C(n) =     ϕ +      ϕ − 1
                                      10       10
Puisque −1 < ϕ′ < 0, on en d´duit que
                            e
                                                      √
                                                   1+ 5 n
                          C(n) = Θ(ϕn ) = Θ(            ) ≈ Θ(1, 618n)
                                                     2
              ´
5.4. COMPLEXITE                                                                                         71

                                                             e
Il s’agit donc d’un algorithme exponentiel. Cette inefficacit´ s’explique par le fait que l’algorithme
                                        e                  e
effectue un grand nombre de fois le mˆme calcul. La m´thode fib(n) fait deux appels r´cursifs e
sur (n − 1) et (n − 2). Chacun de ces appels fait ` son tour deux nouveaux appels r´cursifs, etc. On
                                                  a                                 e
          e                                     e                       e
peut sch´matiser ces appels par un arbre de r´cursion, partiellement d´crit par la figure 5.2 dans
le cas n = 11. De chaque nœud n de l’arbre sont issus deux autres nœuds, arguments des deux
          e              e                                                                e
appels r´cursifs effectu´s par fib(n). On peut noter que fib(7) par exemple est appel´ cinq fois.
                                      e                                     e
Chacun de ces cinq appels fait lui-mˆme des calculs redondants. C’est ce d´faut de conception qui
provoque l’explosion combinatoire.



                                                          11


                                          9                           10


                                  7               8           8                9


                              5       6       6       7   6       7   7            8


                                                                               6       7




                                                   e
                          Figure 5.2 – L’arbre de r´cursion de fib(11)

                                   e                                     e
Il existe toutefois une solution it´rative bien plus efficace pour ce probl`me (cf. section 1.4.4,
                      e       e
exemple 1.13) rappel´e ci-apr`s.

int fib(int n){
  int a=1, b=1, nouveau ;
    for (int i=1 ; i<n ; i++){
      nouveau = a+b;
      a = b;
      b = nouveau;
 }
 return b;
}
Chaque it´ration fait un nombre constant d’op´rations. Puisqu’il y a (n − 1) it´rations, on en
          e                                      e                                e
 e                               e                                                    e e
d´duit que cet algorithme est lin´aire et donc infiniment plus efficace que la version pr´c´dente.
                         e
Exemple 5.8 (Complexit´ de l’algorithme d’Euclide) Cet algorithme de recherche du
                   e                                   e
pgcd se programme r´cursivement (cf. exemple 5.4) et it´rativement (cf. section 2.2.1) comme
suit :

static int delta(int a, int b){                                            static int pgcd (int a, int b){
    int r = a%b;                                                                   int r;
    if(r == 0) return b;                                                           while (b!=0)
    return (delta(b, r));                                                           {r=a%b; a=b;b=r;}
}                                                                                  return a;
                                                                           }
72                                                                             ´ ´
                                                        CHAPITRE 5. PRINCIPES GENERAUX

      u                 e                                                 e
Le coˆt de la version r´cursive est proportionnel au nombre d’appels r´cursifs, celui de la version
  e                                         e
it´rative est proportionnel au nombre d’it´rations. Clairement, ces deux nombres sont identiques,
                        e            e                   e
puisque chaque appel r´cursif, de mˆme que chaque it´ration, remplace le couple courant (a, b) par
                                              e                   e               u
le couple (b, a mod b) et la condition d’arrˆt est identique. L’´valuation du coˆ t de l’algorithme
d’Euclide, quelle qu’en soit la version, utilise la suite de Fibonacci (Fn )n∈I .
                                                                              N


Lemme 5.8 Soient a et b deux entiers tels que a > b > 0. Si l’algorithme d’Euclide fait n ≥ 1
it´rations (ou appels r´cursifs) sur (a, b), alors a ≥ Fn+2 et b ≥ Fn+1 .
  e                    e

                         e
La preuve se fait par r´currence sur n.
Si n = 1, b ne divise pas a, donc b ≥ 2 = F2 et puisque a > b, n´cessairement a ≥ 3 = F3 .
                                                                     e
Soit n ≥ 1, quelconque fix´. Supposons que la propri´t´ ` d´montrer est vraie pour n et consid´rons
                             e                          e ea e                                   e
                                                                 e
un couple (a, b) pour lequel l’algorithme fait (n + 1) appels r´cursifs. On en conclut que le premier
       e                                                           e
appel r´cursif, qui se fait sur le couple (b, a mod b), rappelle r´cursivement l’algorithme n fois. On
d´duit alors de l’hypoth`se de r´currence que : b ≥ Fn+2 et a mod b ≥ Fn+1 .
 e                         e       e
Or, en posant r = a mod b, on sait que a est de la forme : a = bq + r, et que le quotient q est
    e         e    a                            e                     e
sup´rieur ou ´gal ` 1, puisque l’on a suppos´ que a > b. On en d´duit que :

                                 a ≥ b + r ≥ Fn+2 + Fn+1 = Fn+3
2

Th´or`me 5.9 (Th´or`me de Lam´) Soient a et b deux entiers tels que a > b > 0. Soit n ≥ 0
   e e               e e               e
un entier tel que b < Fn+2 , o` Fn+2 d´signe le terme de rang (n + 2) de la suite de Fibonacci.
                               u        e
                                        e                      e
L’algorithme d’Euclide fait au plus n it´rations (ou appels r´cursifs) sur un tel couple (a, b). De
plus, ce nombre est atteint pour certains couples (a, b) satisfaisant ces conditions.

      e e       e           e                          e e
Ce th´or`me d´coule imm´diatement du lemme pr´c´dent, puisque s’il y avait (n + 1) appels
r´cursifs ou plus, on aurait b ≥ Fn+2 . De plus, on montre par r´currence sur n, qu’il y a exacte-
 e                                                                e
ment n appels r´cursifs pour le couple (a, b) = (Fn+2 , Fn+1 ).
                 e
Si n=0, (Fn+2 , Fn+1 ) = (2, 1). Il n’y a dans ce cas aucun rappel r´cursif.
                                                                      e
Soit n ≥ 0. Supposons qu’il y ait exactement n appels r´cursifs pour le couple (Fn+2 , Fn+1 ).
                                                             e
Sur le couple (Fn+3 , Fn+2 ), l’algorithme s’appelle r´cursivement une premi`re fois avec (Fn+2 , r)
                                                      e                     e
o` r est le reste de la division de Fn+3 par Fn+2 . Or, puisque Fn+3 = Fn+2 + Fn+1 et que
 u
Fn+2 > Fn+1 , on en d´duit que r = Fn+1 .
                       e
Finalement, sur le couple (Fn+3 , Fn+2 ), l’algorithme fait un premier rappel r´cursif sur
                                                                                        e
(Fn+2 , Fn+1 ), qui d’apr`s l’hypoth`se de r´currence effectue lui-mˆme n appels r´cursifs. Donc il
                         e            e      e                      e              e
                              e
y a au total (n + 1) appels r´cursifs. 2

                      e                      e
On peut maintenant ´valuer la complexit´ de l’algorithme pour tous les couples (a, b) tels que
                                                ee e                                             u
a > b > 0. Rappelons que la fonction taille a ´t´ d´finie par taille(a, b) = b. La fonction de coˆ t
                                                 e e             e
est donc une fonction de b. De tout ce qui pr´c`de, on peut d´duire qu’il existe une constante
strictement positive k telle que, dans le pire des cas pour Fn+1 ≤ b < Fn+2 , coˆt(b)=k.n. Il reste
                                                                                u
      ae
donc ` ´valuer n en fonction de la taille b.

Le calcul de Fn se fait de fa¸on analogue ` celui de la fonction C de l’exemple pr´c´dent. L’´quation
                             c            a                                       e e        e
 e e                    e                                                   e e
g´n´rale est ici homog`ne et a donc pour solutions les suites de terme g´n´ral :

                                   λ.ϕn + µ.ϕ′n       (λ, µ) ∈ I 2
                                                               R
            √                √
         1+ 5           ′ 1− 5
avec ϕ =              ϕ =      .
           2                2
                                                         e     a         e
Des conditions initiales (les termes de rang 0 et 1 sont ´gaux ` 1), on d´duit : λ + µ = 1
                                                                                 λϕ + µϕ′ = 1
              ´
5.4. COMPLEXITE                                                                                73

On trouve ainsi :
                                                       √                      √
                         n        ′n                 5+ 5                   5− 5
                Fn = λϕ      + µϕ       avec     λ =               et   µ =
                                                      10                     10
Par suite, la condition Fn+1 ≤ b < Fn+2 ´quivaut ` :
                                        e        a

                             λ.ϕn+1 + µ.ϕ′n+1 ≤ b < λ.ϕn+2 + µ.ϕ′n+3
Puisque −1 < ϕ′ < 0, et que n tend vers l’infini quand b tend vers l’infini, ` partir d’une certaine
                                                                           a
valeur b0 de b :
                     λ n+1            λ                λ   3λ n+2
                       .ϕ  < λ.ϕn+1 −   ≤ b < λ.ϕn+2 +   <    .ϕ
                     2                2                2    2
De l’encadrement :
                                     λ n+1           3λ n+2
                                       .ϕ     <b<       .ϕ
                                     2                2
    e
on d´duit qu’il existe deux constantes k1 et k2 telles que pour tout b > b0

                                    α.n + k1 < log(b) < α.n + k2

 u                                                   u
o` α = log(ϕ) > 0. Donc n = Θ(log(b)) et par suite coˆt(b)=Θ(log(b)).

             ee                      e                                      u
Ce calcul a ´t´ fait pour des donn´es (a, b) telles que a > b. Le cas o` a = b est un cas ter-
                                     e                                            e
minal et ne pose donc pas de probl`me. Enfin, si a < b, le premier reste calcul´ est a et donc le
                 e                                                                   e e
premier rappel r´cursif se fait sur le couple (b, a) qui remplit les conditions du th´or`me. On ne
                                e           e
fait dans ce cas qu’un rappel r´cursif suppl´mentaire, ce qui ne change pas la complexit´.e

                                     e              e
En ce qui concerne l’espace occup´, la version it´rative requiert trois variables quelle que soit
       e               e                                                  e             a
la donn´e. La version r´cursive empile les couples arguments des appels r´cursifs jusqu’` ce qu’un
                                                    e        e
cas terminal soit atteint. La taille de la pile de r´cursivit´ est donc proportionnelle au nombre
          e
d’appels r´cursifs.

On peut donc conclure par la proposition suivante.
                                e
Proposition 5.10 La complexit´ en temps, dans le pire des cas, de l’algorithme d’Euclide pour
le calcul du pgcd d’un couple (a, b) ∈ I × I ∗ est en Θ(log(b)).
                                       N   N
              e                                         e
La complexit´ en espace est en Θ(1) pour la version it´rative et en Θ(log(b)) pour la version
 e
r´cursive.
Chapitre 6

            e
Structures s´quentielles

                                                                           e
    Ce chapitre et les deux suivants, traitent de la structuration des donn´es complexes et de leur
                                                  ea
traitement algorithmique. Chacun est consacr´ ` l’une des trois grandes classes de structures :
                 e
les structures s´quentielles (ou listes), les structures arborescentes (ou arbres) et les structures
relationnelles (ou graphes).

                        e     a                                  e
Toute structure sera d´finie ` deux niveaux : l’un abstrait, ind´pendant de tout langage de pro-
                                                                                  e
grammation, l’autre consistant en une ou plusieurs mises en œuvre en Java. La d´finition abstraite
                         e             e                                            e
d’une structure de donn´e sera compos´e non seulement d’une description des donn´es elles-mˆmese
      e                                      ea
mais ´galement d’un jeu de primitives destin´ ` leur manipulation, le tout constituant en quelque
            ı a            e a
sorte une boˆte ` outils prˆte ` l’emploi. Au niveau concret, il s’agira d’une classe Java, dont les
             e                e           e
champs repr´sentent la donn´e et les m´thodes d’instance les primitives. Le programmeur peut
                                  e             a        e                                   e
ainsi disposer d’outils bien pens´s, sans avoir ` les red´finir : on gagne alors en efficacit´ et en
        e
fiabilit´.

     e                                  e                                      e           e
On d´signe sous le nom de structures s´quentielles, les structures de donn´es qui repr´sentent
                   ee             e                                                    e e
des suites finies d’´l´ments d’un mˆme type. Parmi celles-ci, les listes sont les plus g´n´rales.


6.1      Les listes
6.1.1     Description abstraite
    e
Donn´e

                               e                                                      ee
   Il s’agit d’une suite finie, ´ventuellement vide, l = (l0 , . . . , ln−1 ) dont les ´l´ments sont tous
        e                        e e                                            a N
d’un mˆme type T . On consid`re ´galement un type position, isomorphe ` I . A chaque ´l´ment    ee
                      e                                            ee         a           u
de la liste est affect´e une position qui, informellement, fait r´f´rence ` l’endroit o` il se trouve
                        e      e e
dans la liste et sera d´finie pr´cis´ment dans les mises en œuvre.


Primitives

                ee                                        e
   Soient x un ´l´ment de type T et p une position. On d´finit les primitives suivantes.
                                    e                           ee
   fin(l) : la position qui suit imm´diatement celle du dernier ´l´ment de la liste. Intuitivement,
                                          ee                           a
     f in(l) est la position d’un nouvel ´l´ment que l’on rajouterait ` la fin de la liste. Si l est
                                                                                ee
     vide, c’est une position choisie par convention qui sera celle de l’unique ´l´ment de la liste
                                ee      a
     obtenue en rajoutant un ´l´ment ` la liste vide.
                                      e
   vide(l) : renvoie une valeur bool´enne indiquant si l est vide ou non.
    e                                          ee
   d´but(l) : renvoie la position du premier ´l´ment de la liste l si elle n’est pas vide. Si l est
     vide, renvoie f in(l).

                                                  75
76                                                                          ´
                                                    CHAPITRE 6. STRUCTURES SEQUENTIELLES

                               e                                       ee
     suivant(p, l) : n’est d´fini que si p est la position d’un ´l´ment li de la liste. Renvoie alors la
                                  ee
        position de li+1 si cet ´l´ment existe, et f in(l) sinon.
        e e                          e                                     ee
     pr´c´dent(p, l) : n’est d´fini que si p est la position d’un ´l´ment li de la liste autre que le
        premier. Renvoie alors la position de li−1 .
     listeVide() : renvoie une liste vide.
                                                              e
     position(x, l) : renvoie la position de la premi`re occurrence de x dans la liste si elle existe,
        et f in(l) sinon.
     ee                           e                                        ee
     ´l´ment(p, l) : n’est d´fini que si p est la position d’un ´l´ment de la liste. Renvoie cet
        ee
        ´l´ment.
                                       e                                     ee
     rajouter(x, p, l) : n’est d´fini que si p est la position d’un ´l´ment li de la liste ou f in(l).
                                                  e
        Dans le premier cas, l est transform´e en l = (l0 , . . . , li−1 , x, li , . . . , ln−1 ), et dans le
        second en l = (l0 , , . . . , ln−1 , x)
                                     e                                     ee
     supprimer(p, l) : n’est d´fini que si p est la position d’un ´l´ment li de la liste. La liste est
                   e
        transform´e en l = (l0 , . . . , li−1 , li+1 , . . . , ln−1 ) ou en l = (l0 , . . . , ln−2 ) selon que li
                                        ee                                   ee                  e
        n’est pas ou est le dernier ´l´ment de la liste. La valeur de l’´l´ment supprim´ est renvoy´e.        e

          e                           ee
On peut d´sormais utiliser ces outils ´l´mentaires pour concevoir des algorithmes sur les listes. Le
                                                     e
parcours d’une liste l se fait par exemple sur le sch´ma suivant :

                    pour(p ←d´but(l); p = fin(l); p ← suivant(p,l)) ...
                             e

                                                                                      e e
Voici, en illustration, un algorithme purger qui supprime d’une liste toutes les r´p´titions. Il
         a              e                              e                     a          e
consiste ` parcourir it´rativement la liste depuis le d´but. On suppose qu’` chaque it´ration, la
                                             ee                  e e          e            ee
partie de la liste comprise entre le premier ´l´ment et celui pr´c´dant imm´diatement l’´l´ment
              ea       e       a                                            e e           ee
courant est d´j` purg´e, c’est-`-dire que la liste ne contient plus aucune r´p´tition des ´l´ments
de ce segment initial.


                        ea      e
                Partie d´j` purg´e

                                                          T
                                                      posCour

              e                      a               e            e e             ee
La nouvelle it´ration consiste alors ` supprimer les ´ventuelles r´p´titions de l’´l´ment courant (de
position posCour) dans la suite de la liste.

purger(l)
     p, posCour : position
     pour(posCour ← d´but(l); posCour = fin(l); posCour ← suivant(posCour,l))
                      e
       p ← suivant(posCour,l)
       tant que (p = fin(l)) faire
          si identique(´l´ment(p,l), ´l´ment(posCour,l))
                       e e           e e
             supprimer(p, l)
          sinon
             p ← suivant(p,l)

                          e                e                                              e
La fonction identique d´pendra des impl´mentations. On remarquera, et c’est le point d´licat de
                                                                         ee
l’algorithme, qu’il ne faut pas modifier p si l’on vient de supprimer l’´l´ment x de position p. En
                                       ee                        e    a                e
effet, p devient alors la position de l’´l´ment suivant, qui doit ˆtre ` son tour examin´.

6.1.2      Mise en œuvre par des tableaux
          e          e                                                                    ee
    On d´cide de m´moriser une liste dans un tableau de dimension 1. La position d’un ´l´ment
                                           e
est ici son indice. Le tableau n’est pas n´cessairement totalement rempli, la liste pouvant n’en
                  e                             e
occuper que le d´but. Aussi, la liste est-elle d´finie non seulement par le tableau, mais aussi par
6.1. LES LISTES                                                                                      77

                        e                 e                       a      e
l’indice fin de la premi`re case non occup´e, ce qui est conforme ` la sp´cification de la primitive
abstraite fin.
                                                   e                 e
                           fin = indice de la premi`re case non occup´e

                 ea e
On est ainsi amen´ ` d´finir la classe Java suivante :
public class Liste{

     private int[]T;
     private int fin;

     Liste(int n){
         T = new int[n];
         fin = 0;
     }
       e                                  e
Pour ´viter une certaine lourdeur dans l’´criture, on utilise la notation avec this implicite (cf. sec-
                                    e     e                                      e
tion 3.3.1). Les deux champs sont d´clar´s avec l’attribut private, selon la m´thodologie indiqu´e   e
                                       e
dans la section 3.2.2. La primitive cr´erVide est mise en œuvre par le constructeur Liste : on
   e                     ea                                            a
cr´e un tableau T destin´ ` recevoir la future liste. L’initialisation ` 0 du champ fin assure que la
liste est vide.

                           e                              e               e
Certaines primitives sont ´videntes et ne demandent pas l’´criture d’une m´thode. Ainsi,
debut(l) est simplement 0 et suivant(i,l) est i+1. Quant aux primitives element, fin et
                             e
vide elles se programment ais´ment comme suit.
     int element(int p) {return(T[p]);}
     boolean vide() {return(fin == 0);}
     int fin(){return fin;}
                                      ee                          ee               e a
La recherche de la position d’un ´l´ment x dans la liste a ´t´ ici programm´e ` l’aide d’une
                                                                                         a e
sentinelle. Cela suppose que la liste n’occupe pas tout le tableau. Le principe consiste ` m´moriser
                 e                  e
x dans la premi`re case non occup´e (instruction /*1*/), qui porte alors le nom de sentinelle. Ceci
                          a
ne revient en aucun cas ` modifier la liste, puisque la sentinelle n’y appartient pas. Elle assure
                                                         e
cependant la sortie de la boucle while (ligne /*2*/) mˆme si x ne figure pas dans la liste. Dans ce
          e                                              a      e
cas, la m´thode renvoie i = fin, ce qui est conforme ` la sp´cification de la primitive position.
     int position(int x){
         int i = 0;

          /*1*/ T[fin] = x;
          /*2*/ while(T[i]!=x) i++;
          /*3*/ return(i);
     }

                                                                          e
Si l’on n’utilise pas de sentinelle et que x n’est pas dans la liste, l’ex´cution de l’instruction

                                       while(T[i]!=x) i++;

                                   e                      e     e        a
provoque des comparaisons erron´es puisque i est incr´ment´ jusqu’` ce qu’il prenne la valeur
                                              ea       ee
fin. A partir de cette valeur, x est compar´ ` des ´l´ments qui ne sont pas dans la liste. L’al-
gorithme est donc faux. Mais de plus, si x ne figure pas dans le tableau, i finit par prendre la
                                                                  e
valeur T.length. La comparaison T[i]!=x provoque alors un d´bordement de tableau, puisque i
                   e      a                     e
est strictement sup´rieur ` l’indice de la derni`re case du tableau, et l’affichage de l’exception :
                              ArrayIndexOutOfBoundsException: i
78                                                                      ´
                                                CHAPITRE 6. STRUCTURES SEQUENTIELLES

                                                 a                            a
On est donc contraint de comparer la valeur de i ` fin avant de comparer T[i] ` x.
int position (int x){
    int i = 0;

     while(i!=fin && T[i]!=x) i++;
     return i;
}
                                                                            e       a
Il faut souligner que la version sans sentinelle n’est pas uniquement plus d´licate ` mettre en
                e
œuvre, elle est ´galement moins efficace puisqu’elle demande deux fois plus de comparaisons que
l’autre.

                   ee          a                              e
Pour rajouter un ´l´ment x ` l’indice p, il convient de d´caler d’un rang vers la droite tous
    ee                                                          e              a
les ´l´ments de rang p≤i< fin. On remarque qu’il faut proc´der de droite ` gauche, sous peine
              ee                                                                    e
de recopier l’´l´ment d’indice p dans toutes les variables T[i], pour p<i<fin en ´crasant tous les
autres. Enfin, le rajout n’est possible que si le tableau T n’est pas plein, ce qui est implicitement
       e                       e
suppos´ dans la version ci-apr`s.
void rajouter (int x, int p){
    for(int i = fin-1; i>=p ; i--)
      T[i+1] = T[i];
    fin++;
    T[p]=x;
}
                    ee                             e                 e                  e
La suppression d’un ´l´ment d’indice p revient, apr`s avoir sauvegard´ la valeur supprim´e pour la
            e             a e                                                   ee
renvoyer ult´rieurement, ` d´caler d’un rang vers la gauche tous les ´l´ments d’indices
              a e e
p <i<fin et ` d´cr´menter la variable fin.
int supprimer(int p){
    int aux= T[p];
    for(int i = p+1; i!=fin;i++)
      T[i-1]=T[i];
    fin--;
    return(aux);
}
                       e                                    e
On peut rajouter, une m´thode permettant de visualiser les r´sultats.
void imprimer(){
    for(int i=0; i<fin(); i++)           System.out.print(T[i]+" ");
    System.out.println();
}
                 u           e     e         e
Ces primitives dˆment impl´ment´es, la m´thode qui purge une liste suit exactement l’algorithme
  e e            e                             e e                                  e    e a       e
pr´c´demment d´crit, et c’est ce qui fait l’int´rˆt de cette approche. Si elle est d´clar´e ` l’int´rieur
                               e                                                    e e
de la classe Liste, c’est une m´thode d’instance, qui purge donc this. On a acc´d´ ici directement
     ee                    e                       a     e
aux ´l´ments du tableau, ´vitant ainsi un appel ` la m´thode element, qui ne fait rien d’autre que
              ee                                     e          e          e         e
de renvoyer l’´l´ment de T dont l’indice est indiqu´ en param`tre. De mˆme, on ´vite un acc`s ` la e a
  e                          a                                                a      e
m´thode fin, qui se borne ` renvoyer la valeur de la variable, accessible ` l’int´rieur de la classe,
     e
de mˆme nom.
void purger(){
    int p;

     for(int posCour=0; posCour!=fin; posCour++){
6.1. LES LISTES                                                                                79

         p= posCour+1;
         while(p!=fin)
             if (T[posCour] == T[p])        supprimer(p);
             else   p++;
    }
}

        e                      e                                      e
Si la m´thode est programm´e dans une autre classe, il s’agit d’une m´thode statique, qui purge
              e            e                                e      ee                        a
une liste pass´e en param`tre. On n’a plus directement acc`s aux ´l´ments du tableau T, ni ` la
                                            e         e
variable fin puisqu’il s’agit de champs priv´s de la m´thode Liste. On est contraint d’appeler les
  e
m´thodes element et fin.

static void purger(Liste l){
    int p;

    for(int posCour=0; posCour !=l.fin(); posCour ++){
        p= posCour +1;
        while(p!=l.fin())
            if (l.element(posCour) == l.element(p)) l.supprimer(p);
            else p++;
    }
}

                       e                 ee e                e
En supposant que la m´thode purger ait ´t´ d´finie comme une m´thode d’instance de la classe
Liste, on peut la tester au moyen du programme suivant.

import java.util.*;

public class ProgListes{

    public static void main(String [] args){
        Scanner sc = new Scanner(System.in);
        int n =args.length, x, p;
        Liste l = new Liste(n);

         for(int i=0;i<n; i++)
             l.rajouter(Integer.parseInt(args[i]), l.fin());
         l.purger();
         l.imprimer();
         try{
             while(true){
                 System.out.println("Elt a rechercher?");
                 x = sc.nextInt();
                 p=l.position(x);
                 if(p==l.fin())
                     System.out.println(x + " est absent");
                 else
                     System.out.println("Position de " + x + " : "+ p);
             }
         }
         catch(NoSuchElementException e){
         System.out.println("fin");
         }
    }
}
80                                                                     ´
                                               CHAPITRE 6. STRUCTURES SEQUENTIELLES

                         e
dont voici une trace d’ex´cution :
$ java ProgListes 3 67 1 90 8 7 5 67 78 90 67 67 1 1 1
3 67 1 90 8 7 5 78
Elt a rechercher?
98
98 est absent
Elt a rechercher?
90
Position de 90 : 3
Elt a rechercher?
0
0 est absent
Elt a rechercher?
78
Position de 78 : 7
Elt a rechercher?
fin

             e         ee e      e                    e
La fin de l’ex´cution a ´t´ d´tect´e par un Ctrl D, tap´ par l’utilisateur.

6.1.3                                     ın´
          Mise en œuvre par des listes chaˆ ees
                   ın´           e                   e                        e
    Une liste chaˆ ee est compos´e d’objets appel´s maillons, chacun ´tant constitu´ de deuxe
                          ee
champs : l’un portant un ´l´ment de la liste, l’autre l’adresse de (i.e. un pointeur vers) le maillon
                 e                                                         e             e     e
suivant. La repr´sentation en machine de la suite l = (l1 , l2 , l3 ) peut ˆtre alors sch´matis´e de la
  c
fa¸on suivante :


           E     l1              E       l2              E       l3   null

    l
                                                                      ee       a
Toutefois, une liste est souvent construite par ajouts successifs d’´l´ments ` partir de la liste vide
                                               e e                 e
(cf. le programme ProgListe de la section pr´c´dente). La repr´sentation de la liste vide est donc
                                                      e             e
essentielle. Comment la choisir si, comme indiqu´ dans le sch´ma, la variable l pointe vers le
                              ee                                                 ee
maillon portant le premier ´l´ment de la liste ? Que faire s’il n’y a aucun d’´l´ment ? Il serait
                    a                                   a       ee
tentant de donner ` l la valeur null dans ce cas-l` : pas d’´l´ment, donc pas de maillon. Mais
                                e e ee          e                             e
quand aucune liste n’a encore ´t´ cr´´e, l a ´galement la valeur null. D`s lors, on ne peut plus
             e
faire de diff´rence entre l’abscence de liste et l’existence d’une liste vide.

                             e                       e
Par analogie, si sous le syst`me Unix et dans un r´pertoire vide, on tape la commande touch fic,
                       e             ıt           e             e
un fichier vide nomm´ fic apparaˆ dans le r´pertoire. Le r´pertoire n’est plus vide, il contient
                                          e
un fichier, qui a un nom, une date de cr´ation, un jeu de permissions, etc. Ce fichier est vide mais
                                                          e
pourra par la suite faire l’objet d’ajouts. Il en est de mˆme avec les listes : il faut pouvoir faire la
   e                                                                 e            ee a
diff´rence entre l’absence de liste et l’existence d’une liste vide, r´el objet cr´´ ` l’aide d’un new,
          e                        e                                                    a        e
comme c’´tait le cas dans la repr´sentation par des tableaux. La solution consiste ` faire d´buter
                                        e      e                       ee
toutes les listes par un maillon, appel´ en-tˆte, qui ne porte aucun ´l´ment de la liste.


                                              E      ?    null

                                     l                  e
                                                    en-tˆte


                                         e
                        Figure 6.1 – Repr´sentation de la liste vide : l = ()
6.1. LES LISTES                                                                                       81

                                  e      a          e                e
La liste vide est alors la liste r´duite ` son en-tˆte, comme illustr´ par la figure 6.1. Dans le
      e e                    ee                   ın´      e
cas g´n´ral d’une liste a n ´l´ments, la liste chaˆ ee poss`de n + 1 maillons. Ainsi, la figure 6.2
    e                a       ee
repr´sente une suite ` trois ´l´ments.



           E      ?               E     l1              E      l2              E     l3    null

   l                  e
                  en-tˆte

                                       e
                      Figure 6.2 – Repr´sentation de la suite l = (l1 , l2 , l3 )

                     e                                                               ee
Avec ce type de repr´sentation, on peut parcourir la liste du premier au dernier ´l´ment, en pas-
                                                                                        e
sant d’un maillon au maillon suivant. Il est en revanche impossible de revenir en arri`re, puisqu’il
               e                e e
n’y a pas d’acc`s au maillon pr´c´dent.
                        a                                                  e               e e
De plus, en cas d’ajout ` un certain endroit de la liste, cet endroit est n´cessairement pr´cis´ par
                        e                      ee                                           e e
l’adresse du maillon apr`s lequel on rajoute l’´l´ment, donc par l’adresse du maillon qui pr´c`dera
  ee            e                                                                  e e
l’´l´ment rajout´. En cas de suppression, il conviendra de modifier le maillon pr´c´dant le maillon
a                                                  e                       ee
` supprimer. C’est ainsi que l’on convient de d´finir la position d’un ´l´ment par l’adresse du
           e e
maillon pr´c´dent.

                                    ee                             e e
                      position d’un ´l´ment = adresse du maillon pr´c´dent


       e            e                                  ee
Il en r´sulte imm´diatement que la position du premier ´l´ment l1 d’une liste l est l’adresse
         e          a             e
de l’en-tˆte, c’est-`-dire l lui-mˆme.
                                              e
                                             d´but(l) = l

                    e                e
La classe Liste d´bute par la d´claration de deux champs, l’un elt de type entier puisque l’on
     e          a
s’int´resse ici ` des listes d’entiers, l’autre qui porte l’adresse du maillon suivant, donc lui aussi de
                            e e      e
type Liste. Ils sont prot´g´s en ´criture par l’attribut private. Par abus de langage, on dira par
la suite le maillon l pour le maillon d’adresse l.

public class Liste {

       private int elt;
       private Liste suiv;

       Liste() {this.suiv = null;}
       Liste(int n, Liste s) {this.elt = n; this.suiv = s;}

       boolean vide() {return (this.suiv == null);}

       int element() {return this.suiv.elt;}
       Liste suivant() {return this.suiv;}

                                              a
Le premier constructeur initialise une liste ` vide ; en stipulant que le suivant du maillon est null,
                                                                e
on indique qu’il n’y a qu’un seul maillon : this. On ne pr´cise pas la valeur du champ elt puis-
                                         e                                           e
qu’elle est non significative dans l’en-tˆte. Le second constructeur permet de cr´er un maillon dont
                                           e             e            a
les valeurs des deux champs sont indiqu´es en param`tres. Quant ` la primitive vide, elle renvoie
                                                                                       e
la valeur true si et seulement si la liste est vide, donc si et seulement si elle est r´duite au maillon
       e
d’en-tˆte, donc si et seulement si son champ suiv est null.

           e         e e     e                 e              a      e
Les champs ´tant prot´g´s en ´criture, on y acc`de en lecture ` l’ext´rieur de la classe par les
82                                                                       ´
                                                 CHAPITRE 6. STRUCTURES SEQUENTIELLES

  e                                ee                                             e
m´thodes element et suivant : l’´l´ment de position this est this.suiv.elt d’apr`s les conven-
                     e
tions choisies pour d´finir les positions, et le suivant est simplement this.suiv.

                                                     e
Le parcours de la liste this se fait alors sur le sch´ma suivant :

                              for (p = this; !p.vide(); p=p.suiv)

                         ee e                                                    ee
La primitive fin(l) ayant ´t´ d´finie comme la position suivant celle du dernier ´l´ment, la position
           ee       e                                   e e
du dernier ´l´ment ´tant l’adresse du maillon qui le pr´c`de, la fin de la liste est donc l’adresse du
                                                                 c
dernier maillon ! La fin de la liste this se calcule donc de la fa¸on suivante :

Liste fin (){
   Liste p;
   for (p = this; !p.vide(); p=p.suiv);
   return (p);
}

                  a        e                           u      e a
Contrairement ` la repr´sentation avec tableau o` l’acc`s ` la fin se fait en temps constant,
                    u                 e
ce calcul est ici coˆteux puisqu’il n´cessite le parcours de toute la liste. On remarque toutefois que
                                    a
la comparaison d’une position p ` la fin de la liste peut se faire par p.vide() (temps constant)
    o                                         e e
plutˆt que par p==this.fin() (complexit´ lin´aire).

                          La fin de la liste est la position p telle que p.vide()

                                        ee                    a
La recherche de la position d’un ´l´ment x consiste ` renvoyer le maillon p tel que
                                                                     e     a
p.element() == x. Comme pour les tableaux, il faut pouvoir s’arrˆter ` la fin de la liste si x
         ee       e                             ee                         a
n’a pas ´t´ trouv´. Ainsi, avant d’examiner l’´l´ment de position p, c’est-`-dire avant de faire un
       a                                                  ee
appel ` p.element(), il convient de s’assurer qu’un tel ´l´ment existe bien, autrement dit que p
n’est pas vide, faute de quoi il se produit l’erreur NullPointerException (cf. section 3.1.3).

    Liste position (int x){
           Liste p = this;
           while (!p.vide() && p.element() != x) p=p.suiv;
           return p;
}

                  e
On remarque que l’´valuation de l’expression

                                  !p.vide() && p.element() != x

                                           e              e    e e
ne provoque pas d’exception. En effet, l’op´rateur && est ´valu´ s´quentiellement (cf. section 1.1.3) :
                                                               e    e a
si !p.vide() prend la valeur false, l’expression globale est ´valu´e ` false sans que la seconde
                               e    e                               e a ee
partie de l’expression ne soit ´valu´e. Ainsi, on ne tente pas d’acc`s ` l’´l´ment de position p si p
est la fin de la liste.
                                 e                              e e
On remarque enfin que cette m´thode renvoie bien, comme sp´cifi´, la fin de la liste si x n’y figure
pas.

                                                       ee              e
On se propose maintenant de rajouter un ´l´ment x en premi`re position dans la liste this.
                                                   e                                  e
Autrement dit, si this pointe vers la repr´sentation de la liste (l1 , l2 , l3 ), apr`s insertion de x, il
                                                                                          e          a
doit pointer vers (x, l1 , l2 , l3 ). Il convient donc de rajouter comme suivant de l’en-tˆte, c’est-`-dire
                                                          ee a
comme suivant de this, un nouveau maillon, cr´´ ` l’aide du second constructeur. Celui-ci est
           e                                         e
alors appel´ avec x comme premier param`tre, et l’adresse du maillon qui suivait this comme
second.
6.1. LES LISTES                                                                               83



                            x


                                         ‚

          E      ?                   l1             E     l2             E     l3   null

this       this.elt this.suiv

Ceci se programme simplement comme suit.
void rajouter(int x){
   this.suiv = new Liste(x, this.suiv);
}
             ee                             a               e
L’ajout d’un ´l´ment x en position p, c’est-`-dire juste apr`s le maillon d’adresse p, se fait en
             e        e e             a
appelant la m´thode pr´c´dente avec p ` la place de this, soit p.rajouter(x).

                             ee                              e               e
La suppression du premier ´l´ment de la liste consiste, apr`s avoir sauvegard´ sa valeur pour
               e            a
la renvoyer ult´rieurement, ` modifier le maillon suivant this.
int supprimer(){
   int aux = this.element();
   this.suiv = this.suiv.suiv;
   return(aux);
}
        e
Cette m´thode provoque une erreur si la liste est vide, puisque dans ce cas, this.suiv vaut null
et donc this.suiv.suiv n’existe pas. Comme pour l’ajout, la suppression du maillon suivant celui
                                       e                             a
d’adresse p se fait en appelant cette m´thode sur l’instance p c’est-`-dire : p.supprimer().

    e                                                                       e
La m´thode imprimer fait un parcours classique et permet de visualiser les r´sultats.
void imprimer(){
   for (Liste p = this; !p.vide(); p = p.suiv)
       System.out.print(p.element() + " ");
   System.out.println();
}
      e                          e
La m´thode purger, comme c’´tait le cas dans la mise en œuvre par les tableaux, se transcrit
                               a            e        ea e            o
directement de l’algorithme ` l’aide des m´thodes d´j` d´finies. Plutˆt que de ne pas renvoyer de
 e                                                                                  e
r´sultat, on a choisi ici de renvoyer this ce qui ne change pas grand-chose au probl`me. Voici la
        a                                          e
version ` rajouter dans la classe Liste comme m´thode d’instance.
Liste purger(){
   Liste p;

    for(Liste posCour =this; !posCour.vide(); posCour=posCour.suiv){
        p=posCour.suiv;
        while(!p.vide())
            if(p.element() == posCour.element()) p.supprimer();
            else p=p.suiv;
    }
    return(this);
}
84                                                                    ´
                                              CHAPITRE 6. STRUCTURES SEQUENTIELLES

                                    e     a      e                                            e
Si l’on devait programmer cette m´thode ` l’ext´rieur de la classe Liste, il s’agirait d’une m´thode
                               e          e                                      e
statique purgeant la liste pass´e en param`tre. Noter que l’on n’a plus alors acc`s au champ l.suiv
            ea                        u            ea       e
qui est priv´ ` la classe Liste. D’o` l’appel oblig´ ` la m´thode suivant.

static Liste       purger(Liste l){
   Liste p;

         for(Liste posCour = l; !posCour.vide(); posCour=posCour.suivant()){
             p=posCour.suivant();
             while(!p.vide())
                 if(p.element() == posCour.element())
                      p.supprimer();
                 else
                     p=p.suivant();
         }
         return(l);
}

                             e                 e                 a              e
On remarque que cette derni`re version de la m´thode purger est ` nouveau calqu´e sur l’algo-
        a        e                                      a ee         e
rithme, ` ceci pr`s que la condition posCour != l.fin() ` ´t´ remplac´e par !posCour.vide()
                           e
pour des raisons d’efficacit´.

                                                                         e
On peut maintenant clore la classe Liste et tester quelques unes de ses m´thodes par le petit
                                                          e
programme suivant, que l’on a fait suivre d’une trace d’ex´cution.

public class ProgListe{

     public static void main (String [] args) throws IOException {
        int i;
        Liste l = new Liste();

          for(i=0; i<args.length; i++)
              l.rajouter(Integer.parseInt(args[i]));
          System.out.println("Liste saisie"); l.imprimer();
          l.purger();
          System.out.println("Liste purgee"); l.imprimer();
     }
}

$ java ProgListe 6 8 9 10 9 7 4 6 8 4 8 3 9 1 6 10 6 5
Liste saisie
5 6 10 6 1 9 3 8 4 8 6 4 7 9 10 9 8 6
          e
Liste purg´e
5 6 10 1 9 3 8 4 7

                               e     e                        e                  ee        e
On notera que l’on a rajout´ en tˆte de liste, les uns apr`s les autres, les ´l´ments pass´s en
                                                                                         e
arguments du programme : la liste construite est donc dans l’ordre inverse de la donn´e. La
                                              e
construction de la liste dans l’ordre des donn´es est l’objet de l’exercice 6.1.

6.1.4                             e
             Cas des listes ordonn´es
    La plupart du temps, le type de base des ´l´ments de la liste est muni d’une relation d’ordre ≤,
                                              ee
ce qui permet d’ordonner les listes en vue de faciliter leur traitement. On suppose ainsi, dans cette
                                   e                              e
section, que les listes sont ordonn´es par ordre croissant. La sp´cification de la primitive position
          e                                         e                ee             e
est modifi´e de telle sorte que la position renvoy´e est celle de l’´l´ment x pass´ en argument si
6.1. LES LISTES                                                                                       85

                                                           a                     e          e
celui-ci figure dans la liste et, s’il n’y figure pas, celle ` laquelle il devrait ˆtre rajout´ pour que la
                  e
liste reste ordonn´e.

Tableaux et recherche dichotomique
                             e
    Dans le cas d’une repr´sentation par tableaux, on peut mettre en œuvre une recherche dite
 e                        a                          e       e              ee
s´quentielle, semblable ` celle du cas non ordonn´ : on d´bute au premier ´l´ment de la liste et
          e    e                     ee
l’on s’arrˆte d`s l’obtention d’un ´l´ment sup´rieur ou ´gal ` x ou, si x est sup´rieur ` tous les
                                                e          e    a                 e     a
ee                    a                             e e
´l´ments de la liste, ` la fin de celle-ci. Comme pr´c´demment, cette recherche se programme avec
ou sans sentinelle. La version avec sentinelle est la suivante.
int position(int x){
        int i;

          T[fin]=x;
          for(i=0; T[i]<x; i++);
          return(i);
}
A la sortie de la boucle, soit T[i] = x et i est bien la position de x dans le tableau, soit i est le
                                                           a                           e
plus petit indice tel que T[i]>x et c’est bien la position ` laquelle il faudrait l’ins´rer pour que le
                   e          e             e
tableau reste rang´. Cette m´thode est lin´aire dans le pire des cas.

          e                                                         e            c
La repr´sentation permet toutefois de programmer cette m´thode de fa¸on beaucoup plus effi-
          a a                                                e                      ee
cace, grˆce ` une recherche dite dichotomique. Grossi`rement, on compare l’´l´ment recherch´ `        ea
                                                 e                                    e
celui qui est au milieu de la liste. S’il est inf´rieur, on le recherche dans la moiti´ gauche, sinon, on
                             e
le recherche dans la moiti´ droite. L’algorithme de recherche dichotomique est ici directement pro-
         e                e                                    e                 e
gramm´ en Java. La m´thode renvoie un couple constitu´ d’une valeur bool´enne ilyEst indiquant
     ee                  ee       e
si l’´l´ment a ou non ´t´ trouv´, et d’un entier rang indiquant :
                      u             e ee
     – soit le rang o` l’on a trouv´ l’´l´ment,
                  ee
     – soit, si l’´l´ment ne figure pas dans la liste, celui auquel on doit le rajouter pour qu’elle reste
                e
        ordonn´e.
                                e                                                       e
A cette fin, on rajoute au pr´alable dans la classe Liste une classe boolInt d´finissant ce type
de couples (cf. section 3.1.2).
class boolInt{
   boolean ilyEst; int rang;
   boolInt(boolean b, int r)
       {ilyEst = b; rang = r;}
}
    e
La m´thode dichotomie est alors la suivante :
public boolInt dichotomie(int x){
      int premier=0, dernier= fin-1, milieu;

       while(premier<=dernier){
           milieu = (premier+dernier)/2;
           if(x<=T[milieu]) dernier = milieu -1;
           if(x>=T[milieu]) premier = milieu + 1;
       }
       return new boolInt(premier==dernier+2, dernier+1);
}
Cet algorithme, et notamment les deux instructions if sans branche else ainsi que la condition
         o                                         e              a
de contrˆle de la boucle while (qui provoque des it´rations jusqu’` faire se croiser les variables
                               ıtre
premier et dernier), peut paraˆ surprenant. C’est cependant l’une des meilleures versions de
la recherche dichotomique.
86                                                                     ´
                                               CHAPITRE 6. STRUCTURES SEQUENTIELLES

Correction
         e                              a
   On d´montre (cf. exercice 6.2) qu’` la sortie de la boucle :
                                                                  a
   – soit premier == dernier+2, dans ce cas x est dans la liste ` l’indice dernier+1 du tableau.
   – soit premier == dernier+1, dans ce cas x ne figure pas dans la liste et l’indice auquel on
                                                e
      doit le rajouter pour qu’elle reste ordonn´e est dernier+1.
                  e              e          e
Ceci explique le r´sultat renvoy´ par la m´thode.

         e
Complexit´
         u                                                   e
    Le coˆt de l’algorithme est proportionnel au nombre d’it´rations de la boucle while. Chacune
         e                              ee
de ces it´rations diminue le nombre d’´l´ments de la liste dans laquelle s’effectue la recherche.
                           e                                      ee             e     e
Supposons qu’avant une it´ration, la recherche se fasse parmi n ´l´ments. Apr`s l’it´ration, si les
                                                             e
valeurs des variables premier et dernier ne se sont pas crois´es, la recherche se fera, si n est pair :
                    n
     – soit parmi les ´l´ments dont l’indice est dans {milieu+1, ..., dernier}
                      ee
                    2
                    n
   – soit parmi les − 1 ´l´ments dont l’indice est dans {premier, ..., milieu -1}
                          ee
                    2
                                                       n−1
Si n est impair, une nouvelle recherche a lieu sur les      ee
                                                            ´l´ments dont l’indice est soit dans
                                                        2
{premier, ..., milieu-1}, soit dans {milieu+1, ..., dernier}.

        e                 e                   a   ee
En cons´quence, toute it´ration sur une liste ` n ´l´ments restreint, dans le pire des cas, la
                        n
recherche ` une liste ` ´l´ments.
          a           a   ee
                        2
                                                                       e       e      e      e
Par suite, si les variables premier et dernier ne se sont pas crois´es, apr`s la i`me it´ration,
                                                                     n
                                           a
celle-ci a restreint l’espace de recherche ` une liste qui a au plus i ´l´ments. Il en r´sulte que la
                                                                       ee               e
                                                                    2
                     o                                               e
condition de contrˆle de la boucle reste vraie pendant au plus k it´rations avec
                                            n       n
                                                <1≤ k
                                           2k+1    2
               a                    ee                                                            e
A ce moment l`, il reste un unique ´l´ment dans l’espace de recherche et la boucle se termine apr`s
         e     e              e     e                 e
une derni`re it´ration. Des in´galit´s ci-dessus, on d´duit successivement les relations suivantes :

n < 2k+1 ≤ 2n,      log2 (n) < k + 1 ≤ 1 + log2 (n), k ≤ log2 (n) < k + 1,     k = ⌊log2 (n)⌋.

Le nombre d’it´rations est donc born´ par ⌊log2 (n)⌋ + 1. Cette borne est effectivement atteinte
               e                      e
quand l’´l´ment est sup´rieur ` tous ceux de la liste, et que n = 2k . Dans ce cas, chaque it´ration
         ee            e      a                                                              e
                                                                          ee
divisera exactement par 2 l’espace de recherche en le restreignant aux ´l´ments dont les indices
sont dans {milieu+1,... , dernier}.

                              e
Proposition 6.1 La complexit´ en temps dans le pire des cas de la recherche dichotomique d’un
ee
´l´ment parmi n, est en Θ(log(n)).

Application
                                                           e
    Le programme suivant utilise (et donc teste) cette m´thode. A partir d’une liste d’entiers
            e        e            e e             e
non ordonn´e, avec d’´ventuelles r´p´titions, donn´e en argument, il construit une liste croissante
      e e            e                    e         ee                 a     e    a
sans r´p´tition. La m´thode consiste, apr`s avoir cr´´ une liste vide, ` proc´der ` une recherche
                                                a          a
dichotomique de chaque entier en argument, et ` un rajout ` l’endroit correct de cet entier s’il n’y
         ea
est pas d´j`.

public class ProgListes{

      public static void main(String [] args){
6.1. LES LISTES                                                                                        87

          int n=args.length;
          Liste l = new Liste(n);

          for(int i=0; i<n; i++){
              int e = Integer.parseInt(args[i]);
              Liste.boolInt resultat = l.dichotomie(e);
              if(!resultat.ilyEst) l.rajouter(e, resultat.rang);
          }
          l.imprimer();
      }
}

                       e
En voici une trace d’ex´cution :

$java ProgListesTableaux 3 67 1 90 8 7 5 67 78 90 67 67 1 1 1
1 3 5 7 8 67 78 90

                       ee                                                           ee
En supposant que les ´l´ments sont tous distincts, les recherches dans la liste des ´l´ments succes-
                                                        a
sifs sont donc, dans le pire des cas, proportionnelles ` :
                                   0, log(2), log(3), . . ., log(n-1)
                                               n−1
     u
Le coˆt total des recherches est donc en Θ(          log(i)) = Θ(nlog(n)) (cf. REFERENCES).
                                               i=2
                                                           u              e
En ce qui concerne les rajouts, le pire des cas est celui o` la liste pass´e en argument est strictement
 e                                                     e e                 e
d´croissante. Chaque nouvel entier sera en effet ins´r´ dans la premi`re case du tableau et donc le
rajout du i`me ´l´ment n´cessitera i − 1 d´calages vers la droite. Le coˆt total des insertions sera
           e     ee       e                  e                                u
           n−1
                         n(n − 1)
donc en Θ(      i) = Θ(            ) = Θ(n2 ).
            i=1
                            2
     u                           e                      u
Le coˆt des recherches est donc n´gligeable devant le coˆt des insertions.

          ın´         e
Listes chaˆ ees ordonn´es
                               ın´              e             ee       a
    Dans le cas des listes chaˆ ees, faute d’acc`s direct aux ´l´ments ` partir de la seule adresse de
                                   e
la liste, on ne peut utiliser de m´thode dichotomique. On met en œuvre, comme dans le cas non
         e                      e
ordonn´, une recherche dite s´quentielle.

    Liste position (int x){
           Liste p = this;
           while (!p.vide() && p.element()<x) p=p.suiv;
           return p;
}

                               u          e      a          ee
Le pire des cas est ici celui o` x est sup´rieur ` tous les ´l´ments de la liste : il faut alors parcourir
   a              ee                                e
un ` un tous les ´l´ments. La recherche reste lin´aire en temps.

                                          e                                     e           e e
Voici un programme, suivi d’une trace d’ex´cution, qui construit la liste ordonn´e et sans r´p´tition
                e
des entiers donn´s en arguments.

public class ProgListe{

    public static void main (String [] args) throws IOException {

          int i;
          Liste l = new Liste();
88                                                                     ´
                                               CHAPITRE 6. STRUCTURES SEQUENTIELLES

           for(i=0; i<args.length; i++){
               int n = Integer.parseInt(args[i]);
               Liste p = l.position(n);
               if(p.vide() || p.element()!= n)
                   p.rajouter(n);
           }
            l.imprimer();
     }
}


$ java ProgListe 6 8 9 10 9 7 4 6 8 4 8 3 9 1 6 10 6 5
1 3 4 5 6 7 8 9 10


                                                                              o
Si p.vide() prend la valeur true, la seconde partie de l’expression de contrˆle de l’instruction if
          e    e                                       e a ee
n’est pas ´valu´e. Il n’y a donc pas de tentative d’acc`s ` l’´l´ment de position p si p est la fin de
la liste.


6.1.5                            ın´
           Tableaux ou listes chaˆ ees ?
        e      e
    Apr`s la pr´sentation de ces deux types de mises en œuvre, il faut se demander laquelle offrira
                     e                           u       e              e
la meilleure efficacit´ en temps et le moindre coˆt en m´moire. La r´ponse n’est pas simple et
  e          e e            e        e                 e                               e
d´pend en g´n´ral du probl`me trait´. On peut synth´tiser les avantages et les inconv´nients de
            e
chaque repr´sentation dans le tableau suivant.

                                  Tableaux                                  ın´
                                                                  Listes chaˆ ees
                           e       ee
                       Acc`s aux ´l´ments en Θ(1)               Insertion en Θ(1)
     Avantages                e e
                           Pr´d´cesseur en Θ(1)                Suppression en Θ(1)
                                                     e
                    Recherche en Θ(log(n)) (si ordonn´)       Pas de cellules inutiles
                             Insertion en Θ(n)                  Recherche en Θ(n)
          e
    Inconv´nients          Suppression en Θ(n)                       e e
                                                           Fin et pr´d´cesseur en Θ(n)
                         Encombrement maximal                   Taille des maillons

                                             e                            e
Le point fort des tableaux est la possibilit´, en cas d’ensemble ordonn´, d’une recherche dichoto-
            e
mique extrˆmement rapide. Le point faible en est la lenteur des insertions et des suppressions. De
              e      e            e
plus, il peut ˆtre n´cessaire de v´rifier que le tableau n’est pas plein avant chaque insertion, ce qui
                                                                                            ın´
ralentit encore le processus. C’est exactement l’inverse qui se produit avec les listes chaˆ ees qui,
            e            ee                                            e
faute d’acc`s direct aux ´l´ments, ne permettent que des recherches s´quentielles (dont exponentiel-
                u
lement plus coˆteuses que les recherches dichotomiques). En revanche, insertions et suppressions
se font en temps constant et la place dont on dispose pour de nouvelles insertions est virtuellement
                   e                                 e                                   e
infinie (c’est la m´moire de l’ordinateur). On privil´giera donc l’une ou l’autre des repr´sentations,
selon que l’on traite de listes relativement stables dans lesquelles on fait essentiellement des re-
cherches, ou au contraire de listes fluctuantes dans lesquelles on fait de nombreuses modifications.

                                      e            e
En ce qui concerne l’espace occup´ par la repr´sentation par tableau, il faut remarquer qu’elle
                    a                                                                ee            e
est proportionnelle ` la taille de la liste maximale. En effet, la suppression d’un ´l´ment ne lib`re
         e                                               ın´                 ee                   e
pas de m´moire. Ceci n’est pas le cas avec les listes chaˆ ees puisque si un ´l´ment est supprim´, le
                                                                       e       e e        e     e
maillon correspondant devient inaccessible et est donc susceptible d’ˆtre lib´r´ par le r´cup´rateur
     e
de m´moire Java (cf. section REFERENCE), ou explicitement par le programmeur dans d’autres
                            e
langages. Ceci conforte l’id´e de consacrer les tableaux aux listes stables. Il faut toutefois nuancer
                                                                 e        a
cette comparaison par le fait que la taille des maillons est sup´rieure ` celle des cases de tableau
puisque chacun porte, en plus de l’information, l’adresse du maillon suivant.
6.2. LES PILES                                                                                   89

6.2      Les piles
6.2.1      Description abstraite
   Une pile (stack en anglais), ou structure FIFO (de l’anglais First In First Out), est une suite
 e e        c            e                                                     ee
g´r´e de fa¸on particuli`re. On s’interdit de rajouter ou de supprimer un ´l´ment n’importe o`     u
                                                               a     e        e   e       e
dans la liste. Les ajouts et les suppressions se font toujours ` la mˆme extr´mit´, appel´e sommet
                    e                       ee               e          e e                 e
de la pile. Il en r´sulte que le premier ´l´ment supprim´ (on dit d´pil´) est le plus r´cent. Le
                                                                                    ee
principe est celui d’une pile d’assiettes : on ne peut rajouter d’assiette ou en pr´l`ver une, qu’au
sommet de la pile.

            ee
Soient x un ´l´ment et p une pile. Les primitives sont les suivantes :
   pileVide() : renvoie la pile vide.
     e                   e                                                  ee
   d´piler(p) : n’est d´fini que si la pile p n’est pas vide. Supprime un ´l´ment de la pile et
      renvoie sa valeur.
   empiler(x, p) : empile x au sommet de la pile p.
                                      e
   vide(p) : renvoie une valeur bool´enne indiquant si la pile p est vide ou pas.


6.2.2                  a
           Application ` la fonction d’Ackermann
                                                                                               e e
    On peut illustrer une utilisation des piles sur l’exemple de la fonction d’Ackermann. L’int´rˆt
                                    e               ee
de cette fonction est purement th´orique. Elle a ´t´ introduite comme exemple de fonction dont
                      e       a                                                 e            a
la croissance est sup´rieure ` celle de n’importe quelle fonction primitive r´cursive, c’est-`-dire
          e              e
pouvant ˆtre programm´e en n’utilisant que des boucles de la forme :
              for(int i=0; i<=n;i++) ou for(int i=n; i>=0;i--) avec n ∈ I           N
                    e             e
(on parle alors d’it´rations born´es).

                                           e      e                    c
La fonction d’Ackermann est la fonction A d´finie r´cursivement de la fa¸on suivante :

A(0, n) = n + 1 ∀n ∈ I
                     N
A(m, 0) = A(m − 1, 1) ∀m ∈ I ∗
                            N
A(m, n) = A(m − 1, A(m, n − 1)) ∀m ∈ I ∗ , ∀n ∈ I ∗
                                     N          N

                                            e                                                e
On se propose de trouver un algorithme it´ratif calculant cette fonction. Pour cela, on d´bute
          a
un calcul ` la main sur un exemple. C’est ce qui est fait sur la partie gauche de la figure 6.3, o`u
                 e                                           e          e
figurent les premi`res expressions obtenues en appliquant la d´finition r´cursive ci-dessus au calcul
de A(3, 4).

                            e
                Algorithme r´cursif                                           e
                                                                 Algorithme it´ratif
                                                                  Pile p             n

 A(3,   4) =                                                      3                  4
 A(2,   A(3, 3)) =                                                2   3              3
 A(2,   A(2, A(3, 2))) =                                          2   2   3          2
 A(2,   A(2, A(2, A(3, 1)))) =                                    2   2   23         1
 A(2,   A(2, A(2, A(2, A(3, 0))))) =                              2   2   223        0
 A(2,   A(2, A(2, A(2, A(2, 1))))) = . . .                        2   2   222        1

                                 e
                   Figure 6.3 – D´roulement de l’algorithme sur le couple (3, 4)

          c       a         e                   e
On s’aper¸oit qu’` chaque ´tape, on est en pr´sence :
                                                                    ee
   – de la liste des premiers arguments en attente. Cette liste a ´t´ reproduite sur chaque ligne.
                                       e    e                                            ee
     Il s’agit en fait d’une pile p, pr´sent´e ici horizontalement, dont le sommet est l’´l´ment le
           a
     plus ` droite de la ligne.
90                                                                        ´
                                                  CHAPITRE 6. STRUCTURES SEQUENTIELLES

               e                                     a
     – du deuxi`me argument du dernier appel en date ` la fonction A (en gras dans les expressions).
                                       e
       Cet entier n figure dans le derni`re colonne.

       e e                                   e e                                         e
Propri´t´ d’invariance Dans le cas g´n´ral du calcul de A(m0 , n0 ), chaque it´ration se fera
          e                                             e
donc en pr´sence d’une pile p = (p0 , . . . , ps ) (ps d´signe ici le sommet de la pile) et d’une valeur
n qui devront satisfaire (et ce sera l’invariant de la boucle) la relation :

               A(m0 , n0 ) = A(p0 , A(p1 , . . . , A(ps , n) . . . ))                (I)
  e                e                                                             a
L’´tape suivante d´termine de nouvelles valeurs de la pile et de n, satisfaisant ` nouveau l’invariant
                           a e
(I). Puisqu’elle consiste ` d´buter le calcul de A(ps , n), elle se fait en examinant le sommet ps
                                                e e          e                               e
de la pile ainsi que la valeur de n. Elle est r´alis´e d’apr`s les trois conditions de la d´finition,
amenant aux trois cas suivants :
                                e            e                           a
    – si ps = 0, A(ps , n) doit ˆtre remplac´ par n + 1, ce qui revient ` supprimer ps de la pile et
           e
      incr´menter n.
    – si ps = 0 et n = 0, A(ps , n) doit ˆtre remplac´ par A(ps − 1, 1), ce qui conduit ` d´piler ps ,
                                         e           e                                   a e
      empiler ps − 1 et donner ` n la valeur 1.
                                  a
    – si ni ps ni n ne sont nuls, A(ps , n) doit ˆtre remplac´ par A(ps − 1, A(ps , n − 1)), ce qui
                                                 e             e
      conduit ` d´piler ps , empiler successivement ps − 1 et ps et d´cr´menter n.
               a e                                                    e e

                  e                            e
On est alors assur´ que l’invariant est conserv´.

                                                                                          e     e
Initialisations Il faut aussi faire en sorte que l’invariant soit satisfait avant la premi`re it´ration.
                                  a               a                     ee
Il suffit pour cela d’initialiser n ` n0 et la pile ` celle dont l’unique ´l´ment est m0 .

                    e                   a e                                e
Conditions d’arrˆt Enfin, il reste ` d´terminer les conditions d’arrˆt de l’algorithme. Le calcul
                                                                     a e         e e              e
se termine lorsque la pile est vide : cela n’a pu se produire que si ` l’´tape pr´c´dente la pile ´tait
        e                ee                               e           ee                a
constitu´e d’un unique ´l´ment de valeur nulle. D’apr`s la propri´t´ d’invariance, ` ce moment
 a                                              ee     e          e       e
l` : A(m0 , n0 ) = A(0, n). La pile a ensuite ´t´ vid´e et n incr´ment´. La valeur de n est alors le
 e                e              e
r´sultat recherch´ et doit donc ˆtre renvoy´e.e

       e
On en d´duit l’algorithme suivant :

Ackermann(m, n)
   p ← pileVide()
   empiler(m, p)
   faire
       m ← depiler(p)
       si (m=0)
          n ← n+1
       sinon
          si (n=0)
             empiler(m-1, p)
             n ← 1
          sinon
             empiler(m-1, p)
             empiler(m, p)
             n ← n-1
   tant que non vide(p)
   renvoyer(n)

                          e                          e                       e
Terminaison Si l’on d´montre que l’algorithme r´sursif induit par la d´finition de la fonction
                                          e                           e
d’Ackermann se termine, on pourra en d´duire que l’algorithme it´ratif se termine aussi. En ef-
                                                           e                 a     e         e
fet, il y a une correspondance bijective entre les appels r´cursifs en cours ` une ´tape donn´e de
6.2. LES PILES                                                                                      91

    e                        e            e                                      a     e   e
l’ex´cution de l’algorithme r´cursif et l’´tat de la pile p et de la variable n, ` la mˆme ´tape de
               e
l’algorithme it´ratif, comme l’indique la figure 6.3.

                            e
En examinant l’algorithme r´cursif, on remarque que le calcul de A(m, n) provoque des appels
 e
r´cursifs de la forme
   – soit A(m − 1, 1) si n = 0
   – soit A(m − 1, x) et A(m, n − 1) sinon.

Un appel sur un couple (m, n) fait donc un ou deux rappels r´cursifs sur des couples (m′ , n′ ) tels
                                                            e
que :
   – soit m > m′
   – soit (m = m′ et n > n′ ).

                 ea                 e
On est ainsi amen´ ` introduire la d´finition suivante.
D´finition 6.2 On appelle ordre lexicographique sur I × I la relation >lex d´finie par :
 e                                                 N   N                   e
      ∀(m, n), (m′ , n′ ) ∈ I × I
                            N   N    (m, n) >lex (m′ , n′ ) ⇔ (m > m′ ) ou (m = m′ et n > n′ )
                                                                                 e
Proposition 6.3 La relation lexicographique est une relation transitive bien fond´e.
  e                     e                             e        e
D´monstration La d´monstration de la transitivit´ est laiss´e au lecteur.
                                      e        e       e
Montrons que la relation est bien fond´e. D’apr`s la d´finition 5.1, il suffit de montrer qu’il n’existe
                                  e
pas de suite infinie strictement d´croissante. On raisonne par l’absurde en supposant qu’il existe
                               e
une suite infinie strictement d´croissante de couples d’entiers naturels tels que :
                                 c0 >lex c1 >lex . . . >lex ck . . .
                           e      e                                                e            e
avec ci = (mi , ni ). D’apr`s la d´finition de la relation >lex , la suite des premi`res coordonn´es de
                 e
ces couples est n´cessairement telle que :
                                     m0 ≥ m1 ≥ . . . ≥ mk . . .
                                                     e
Puisqu’il n’existe pas de suite infinie strictement d´croissante d’entiers naturels, il existe donc
          e                               e
une infinit´ de termes de cette suite tous ´gaux. Appelons a leur valeur commune. On peut donc
                                                 e     a
extraire une sous-suite (mϕ(k) )k∈I constante et ´gale ` a :
                                  N

                            a = mϕ(0) = mϕ(1) = . . . = mϕ(k) = . . .
                           e
Mais alors, par transitivit´, la suite extraite (cϕ(k) )k∈I est une suite infinie de couples strictement
                                                           N
 e
d´croissante :
                               cϕ(0) >lex cϕ(1) >lex . . . >lex cϕ(k) = . . .
                                       e                        e
Puisque ces couples ont tous pour premi`re composante a, on en d´duit que :
                                 nϕ(0) > nϕ(1) > . . . nϕ(k) > . . .
                                         e
On a ainsi obtenu une suite strictement d´croissante d’entiers naturels, ce qui est absurde. 2

                                          e               e
La terminaison de l’algorithme Ackermann d´coule alors imm´diatement de cette proposition et
     e e
du th´or`me 5.2.

6.2.3    Mise en œuvre par des tableaux
                        e                               e e
   Dans ce type de repr´sentation, les piles sont caract´ris´es par un tableau et l’indice du dernier
ee                  ee                                                    ee         e
´l´ment. Le dernier ´l´ment est ici le sommet de la pile, et son indice a ´t´ appel´ top.

                                                                   e      e
Dans l’implantation ci-dessous, la taille maximale de la pile peut ˆtre d´finie par l’utilisateur
              e                                 e          e    e       e        e
comme param`tre d’un constructeur. Elle peut ´galement ˆtre d´termin´e par d´faut au moyen
                       e
d’une constante MAX fix´e par le concepteur de la classe Pile. On obtient ainsi :
92                                                                     ´
                                               CHAPITRE 6. STRUCTURES SEQUENTIELLES

public class Pile{
    public static final MAX=300000;
    private int top;
    private int[]T;

     public Pile(int n){
         top=-1;
         T = new int[n];
     }

     public Pile(){
         top=-1;
         T = new int[MAX];
     }

     public   void empile(int x) {T[++top] = x;}
     public   int depile() {return(T[top--]);}
     public   boolean vide() {return(top == -1);}
     public   boolean pleine() {return(top == T.length-1);}
}

                              e                                                 e
On remarquera l’opportunit´ d’une primitive pleine dans ce type de repr´sentation, puisque la
                       e          e
taille dont on dispose ´tant limit´e, l’utilisateur pourra s’assurer que la pile n’est pas pleine avant
                     ee
d’empiler un nouvel ´l´ment.


6.2.4                                    ın´
         Mise en œuvre par des listes chaˆ ees
                                                          e
     La mise en œuvre est un cas particulier de la repr´sentation des listes quelconques par des
          ın´                              e                                      e
listes chaˆ ees. Pour des raisons d’efficacit´, rajouts et suppressions se font en tˆte de la liste. Les
   e                                                         e
m´thodes empiler et depiler sont donc identiques aux m´thodes rajouter et supprimer de la
            e                      ın´
classe impl´mentant les listes chaˆ ees.

public class Pile{

     private int elt;
     private Pile suiv;

     public Pile(){suiv=null;}

     public Pile(int n, Pile s){
         elt = n;
         suiv = s;
     }

     public boolean vide(){return(suiv==null); }

     public void empile(int n){suiv = new Pile(n,suiv);}

     public int depile(){
         int aux = suiv.elt;
         suiv = suiv.suiv;
         return(aux);
     }
}
6.3. LES FILES D’ATTENTE                                                                            93

6.2.5                 a
          Application ` la fonction d’Ackermann
                                               e                e e
    La fonction d’Ackermann peut maintenant ˆtre programm´e it´rativement en utilisant indif-
 e                                       e
f´remment l’une ou l’autre des classes d´finissant les piles. Il faut remarquer que le programme
                  e
ci-dessous est ind´pendant de la mise en œuvre choisie.

public class Ackermann{

      public static int Ack(int m, int n){
         Pile P = new Pile();

          P.empile(m);
          do{
             m=P.depile();
             if (m==0)
               n++;
             else
                 if (n==0){
                   P.empile(m-1);
                   n=1;
                 }
                 else{
                   P.empile(m-1);
                   P.empile(m);
                   n--;
               }
           }
           while (!(P.vide()));
           return(n);
      }

      public static void main(String[] args){

          if (args.length!=2)
             System.out.println("La fonction attend 2 entiers en arguments");
          else{
             System.out.println("A(" + args[0] + ", " + args[1]+") = " +
                Ack(Integer.parseInt(args[0]), Integer.parseInt(args[1])));
             }
      }
}

           e              e                                                 e
La difficult´ est ici de pr´voir la taille de la pile dans le cas d’une repr´sentation par tableau. De
         c                                        e                       e
toutes fa¸ons, la croissance de la fonction calcul´e est telle, que l’on d´passe rapidement les limites
                                                        e
raisonnables en temps, et les limites physiques en m´moire et dans le codage des entiers.


6.3       Les files d’attente
6.3.1     Description abstraite
                                                                                 e e        c
    Tout comme les piles, les files d’attente (queue en anglais) sont des listes g´r´es de fa¸on par-
      e                                                             e   e         e
ticuli`re. Insertions et suppressions se font chacunes aux deux extr´mit´s oppos´es. Les insertions
        a                           ee                     e         e                ee
se font ` la fin et c’est le premier ´l´ment qui est supprim´. En cons´quence, c’est l’´l´ment le plus
                                a                         u
ancien qui est sorti de la file ` chaque suppression, d’o` le nom de structure FIFO (de l’anglais
94                                                                      ´
                                                CHAPITRE 6. STRUCTURES SEQUENTIELLES

First In First Out). C’est exactement le fonctionnement des files d’attente au sens commun du
terme. Les primitives du type abstrait sont les suivantes :
                                                ee
   debut(f ) : renvoie la position du premier ´l´ment de la file
   fin(f ) : renvoie la position de la fin de la liste
   fileVide() : renvoie une file vide
                                      e
   vide(f ) : renvoie une valeur bool´enne indiquant si la file f est vide ou pas.
                               ee       a
   enfiler(x, f ) : rajoute un ´l´ment ` la file.
                 e                                    ee
   defiler(f ) : d´fini si f n’est pas vide. Supprime l’´l´ment le plus ancien et renvoie sa valeur.


6.3.2        Mise en œuvre par des tableaux
                                         e                              ea
    Comme toujours dans ce type de repr´sentation, le tableau destin´ ` recevoir la file n’est pas
 e                  e             e                                      e
n´cessairement enti`rement occup´, puisque la taille de la file est suppos´e varier avec les insertions
                                         e        ee        a                   e
et suppressions. Si l’on commence par ins´rer les ´l´ments ` partir de la premi`re case du tableau,
                      e
on se retrouvera apr`s un certain nombre d’insertions dans la situation suivante :



      Partie          e
                 occup´e       par       la         file

        T                                                    T
     debut                                                  fin

                                  e                    e        e       e
Les suppressions se faisant au d´but, on pourrait ˆtre tent´ de d´caler pour ce faire, tous les
ee                                       e
´l´ments restants vers la gauche : ces op´rations se feraient alors en Θ(n). On peut gagner en effi-
     e                                                                         e
cacit´ et obtenir des suppressions en Θ(1) en remarquant qu’il suffit d’incr´menter l’indice debut
      o      ee                                        e
pour ˆter un ´l´ment de la file. Ce faisant, la file se d´place avec le temps vers la droite du tableau.


                   Partie           e
                               occup´e        par          la     file

                   T                                                     T
                debut                                                   fin

                                                                    ee
Avec un certain nombre d’insertions et de suppressions, le dernier ´l´ment de la file finit par se
          a
retrouver ` la fin du tableau, ce qui rend impossible tout nouvel ajout en position fin.


                               Partie          e
                                          occup´e           par    la   file

                              T                                                      T
                           debut                                                    fin

                        a              a     e                                e      a          e
On pourrait envisager ` ce moment l` de d´caler toute la file pour la refaire d´buter ` la premi`re
                           e         u           a        e    e e
case du tableau. Cette op´ration coˆteuse peut ` son tour ˆtre ´vit´e en poursuivant les insertions
                          e        e                            a
dans les cases libres situ´es au d´but du tableau. Ceci revient ` chaque insertion et chaque sup-
         a      e                                                   ee
pression ` incr´menter les indices fin et debut modulo le nombre d’´l´ments du tableau. On peut
alors ainsi se retrouver dans la situation suivante :




                     T                                       T
                    fin                                    debut

                              e                      e     e
De ce fait, le tableau peut ˆtre mentalement repr´sent´ comme un anneau sur lequel la file se
 e                                                                      e             e
d´place vers la droite. C’est pour cela que les files d’attente ainsi cod´es sont appel´es files circu-
6.3. LES FILES D’ATTENTE                                                                              95

laires.
                    e               e
Le dernier point d´licat est la d´tection d’une file pleine ou d’une file vide. Une file pleine occupe
                                                                                               e
tout le tableau. Si c’est le cas, la fin rejoint le premier de la file et une nouvelle insertion ´craserait
  ee                                                                      e             ee
l’´l´ment le plus ancien. Mais si la file est vide, les suppressions ant´rieures des ´l´ments de rang
                                e      e                                   e     e
debut ont successivement incr´ment´ debut qui a ainsi rejoint la fin. L’´galit´ entre la fin et le debut
                              a
de la file pouvant survenir ` la fois si la file est pleine et si la file est vide, cette condition ne peut
        e                                `                                    a
caract´riser aucune de ces situations a elle seule. Une solution consiste ` introduire un champ de
                                       e              ee                                 a      a
type entier dans lequel est enregistr´ le nombre d’´l´ments de la file et qui est mis ` jour ` chaque
modification de la file. On obtient ainsi :
public class Files {

     public static final int MAX=100;

     private int[] T;
     private int debut, fin, nombre;

     public Files (){
         T = new int[MAX];
         fin=nombre=debut=0;
     }

     public Files (int n){
         T = new int[n];
         nombre=debut=fin=0;
     }

     public boolean vide() {return(nombre==0);}
     public boolean pleine() {return(nombre==T.length);}
     public int taille(){return nombre;}

     public int defile(){
         int aux;

          aux=T[debut];
          debut = (debut+1)% MAX;
          nombre--;
          return(aux);
     }

     public void enfile(int x){
         nombre++;
         T[fin] = x;
         fin = (fin+1)%MAX;
     }
}
    e
La m´thode imprime permet de visualiser le contenu de la file
     public void imprime(){
         int i = debut;

          for(int t= nombre; t!=0;t--){
              System.out.print(T[i]+" ");
              i= (i+1)%MAX;
96                                                                     ´
                                               CHAPITRE 6. STRUCTURES SEQUENTIELLES

          }
          System.out.println();
     }
Voici un exemple d’utilisation de cette classe.
import java.util.*;
public class ProgFile{

     public static void main(String[] args){
         Files f = new Files();
         int i;
         Scanner sc = new Scanner(System.in);

          if (args.length > Files.MAX)
                                    e                    e
               System.out.println("D´passement de capacit´");
          else{
               for(i=0; i<args.length;i++)
                   f.enfile(Integer.parseInt(args[i]));
               System.out.println(" J’ai lu :");
               f.imprime();

                System.out.println("Combien d’elements voulez-vous supprimer?");
                i= sc.nextInt();
                if(i>f.taille()){
                    System.out.print("On ne peut supprimer plus de ");
                    System.out.println(f.taille()+ " elements");
                }
                else{
                    for(; i>0; i--) f.defile();
                    f.imprime();
                }
           }
     }
}
On remarquera comment les tests f.pleine() avant chaque ajout et f.vide() avant chaque
                       e    e e
suppression ont ici pu ˆtre ´vit´s.

6.3.3                                     ın´
          Mise en œuvre par des listes chaˆ ees
                         ın´       e             ee
    Dans toute liste chaˆ ee, l’acc`s au premier ´l´ment se fait en temps constant, tandis que l’acc`s e
                                 e
au dernier se fait en temps lin´aire. C’est ainsi que pour les piles, on a choisi de faire figurer le
                      ee                            ın´
sommet en premier ´l´ment de la structure chaˆ ee : insertions et suppressions se font alors en
temps constant.
                                     e                      a                          e   e         e
En revanche, pour les files, il est n´cessaire de travailler ` la fois sur les deux extr´mit´s : au d´but
                        a                                                   e
pour les suppressions, ` la fin pour les insertions, ou l’inverse. Pour ´viter de recalculer la fin de
         a                                                      e
la liste ` chaque insertion par exemple, il convient de la m´moriser dans une variable mise ` jour a
` chaque ajout. Toutefois, une d´claration analogue ` celle de la classe liste, avec le rajout d’un
a                                  e                    a
              e
champ suppl´mentaire fin comme suit :


public class Files{
  private int element;
  private Files suiv, fin;
}
6.3. LES FILES D’ATTENTE                                                                            97

             e                                                               e
est particuli`rement maladroite : en effet chaque maillon est alors constitu´ de trois champs : celui
portant l’information, celui portant l’adresse du maillon suivant et celui portant l’adresse de la fin ;
                          e                                                                     e
la fin serait donc dupliqu´e autant de fois qu’il y a de maillons dans la liste ! Ce n’est assur´ment
                             e                       a e                                e a
pas la configuration souhait´e. La solution consiste ` d´finir une classe maillon priv´e ` la classe
                        e                   a                        e
Files permettant de d´finir des maillons ` deux champs, puis de d´finir une file comme un couple
                          e
de maillons : celui d’en-tˆte et celui de fin. On obtient ainsi :

public class Files{

     private class maillon{
         private int element;
         private maillon suiv;

          maillon (int n, maillon s){element=n; suiv=s;}
          maillon(){suiv=null;}
     }

     private maillon debut;
     private maillon fin;

                                                              a
Les constructeurs de la classe maillon sont identiques ` ceux de la classe Liste. Comme c’est
                            e     e                       ın´                              e
le cas pour les suites repr´sent´es par des listes chaˆ ees, une file vide est constitu´e d’un seul
               e                     a e       a             e                 u                   e
maillon, l’en-tˆte, qui dans ce cas l` d´signe ` la fois le d´but et la fin. D’o` le constructeur cr´ant
une file vide :

     public Files(){
         fin = new maillon();
         fin = debut;
     }

                e                                    e
Le fait que le d´but et la fin soient confondus caract´risent ainsi les files vides.

     public boolean vide(){return(fin==debut);}

                              a                            ee                    a         e
La suppression est analoque ` la suppression du premier ´l´ment d’une liste, ` ceci pr`s qu’il
                   a                           ee             ee                        a
convient de mettre ` jour la variable fin si l’´l´ment supprim´ ´tait le dernier, c’est-`-dire si la
                                  ee
file ne comportait plus qu’un seul ´l´ment.

     public int defile(){
         int aux = debut.suiv.element;

          if(fin==debut.suiv) fin=debut;
          debut.suiv = debut.suiv.suiv;
          return(aux);
     }

               ee                          ea            e                      a
Le rajout d’un ´l´ment se fait comme annonc´ ` la fin et n´cessite donc une mise ` jour de la
variable fin.

     public void enfile(int n){

          fin.suiv= new maillon(n, fin.suiv);
          fin=fin.suiv;
     }

          e                    e
Enfin, la m´thode imprime ci-apr`s permet d’illustrer un parcours de la file.
98                                                                     ´
                                               CHAPITRE 6. STRUCTURES SEQUENTIELLES

      public void imprime(){
          maillon m;

          for(m=debut; m!=fin; m=m.suiv)
              System.out.print(m.suiv.element+" ");
          System.out.println();
      }
}
                                                                      e            a
Le programme ci-dessous illustre le fonctionnement de cette classe, tr`s similaire ` celui de la
                       e e
classe de la section pr´c´dente.
import java.util.*;
public class ProgFile{

      public static void main(String[] args){
          Files f = new Files();
          int i;
          Scanner sc = new Scanner(System.in);

          for(i=0; i<args.length;i++)
              f.enfile(Integer.parseInt(args[i]));
          System.out.println(" J’ai lu :");
          f.imprime();

          System.out.println("Combien d’elements voulez-vous supprimer?");
          i= sc.nextInt();
          for(; i>0; i--)
             if(!f.vide()) f.defile();
          f.imprime();
      }
}
On remarque qu’il est inutile de s’assurer que la file n’est pas pleine avant les insertions. En ce qui
                                                         e      e                 a      e
concerne les suppressions, on aurait pu dans la cas pr´sent ´viter les appels ` la m´thode vide
                                                                          e
avant chaque suppression en comparant i et args.length avant la derni`re boucle for.


6.4       Exercices
                                                                            c a e
Exercice 6.1 Modifier le programme ProgListes de la section 6.1.3, de fa¸on ` pr´server l’ordre
                  e                                               a
des arguments, en ´vitant toutefois de reparcourir toute la liste ` chaque nouvel ajout.
Exercice 6.2
  1. Faire tourner l’algorithme de recherche dichotomique sur plusieurs exemples, notamment
                    u                     a         e e              u                 ee
     dans les cas o` x est dans la liste (` une extr´mit´ ou pas), o` x est entre deux ´l´ments de
                u           e           e      a          ee
     la liste, o` x est sup´rieur ou inf´rieur ` tous les ´l´ments de la liste.
                                    e
  2. On peut sans changer le probl`me, supposer que le tableau commence et se termine par deux
                               e                           ee                      e     a
     cases fictives ; la premi`re, T[-1] contiendrait un ´l´ment strictement inf´rieur ` tous les
     ´l´ments de E, not´ par exemple −∞ ; l’autre T[fin] contiendrait un ´l´ment strictement
     ee                   e                                                     ee
         e       a          ee                e
     sup´rieur ` tous les ´l´ments de E, not´ +∞. Montrer que la proposition I1 ou I2 , avec :

      I1 : T[premier-1] < x < T[dernier +1]
      I2 : (x = T[premier-1]=T[dernier+1]) et (premier = dernier + 2)

      est un invariant de la boucle.
6.4. EXERCICES                                                                                  99

         e                                      e
  3. En d´duire que l’algorithme satisfait sa sp´cification.

Exercice 6.3 Modifier le programme calculant la fonction d’Ackermann, pour faire afficher, `a
         e                                                        e
chaque it´ration, le contenu de la pile. On traitera les deux impl´mentations des piles.

                                                                                       ı e
Exercice 6.4 Donner une mise en œuvre des listes par des structures doublement chaˆn´es, c’est-
a                                           a                                      e e
`-dire pour lesquelles chaque maillon porte ` la fois l’adresse du suivant et du pr´c´dent. On fera
                  e a
en sorte que l’acc`s ` la fin se fasse en temps constant.
Chapitre 7

Structures arborescentes

7.1       e
        Pr´sentation informelle
Un exemple bien connu Les arbres sont des structures bi-dimensionnelles, dont l’exemple le
                                   e e                      e
plus connu est celui des arbres g´n´alogiques, comme illustr´ par la figure 7.1. Les arbres sont
    e    e
repr´sent´s la racine en haut et les feuilles en bas.


                                               Albert


                      Bernard           Charles                  Denis


                Edouard Fernand Hubert Ivan Jean Louis            Martin     Norbert


                          Gontrand                             Didier Paul


                                                                e e
                     Figure 7.1 – Un exemple d’arbre : l’arbre g´n´alogique

                      e a      e                               e
Les arbres sont destin´s ` repr´senter un ensemble de donn´es, et donc, comme pour les listes, on
              e                               e
convient de d´finir un arbre vide qui repr´sentera l’ensemble vide. La terminologie est en partie
         e                     e e                                       e
emprunt´e au domaine de la g´n´alogie. Tout arbre non vide a poss`de des nœuds. Ce sont ici les
individus. La racine est un nœud particulier (ici Albert), qui n’a pas de parent. Chaque nœud est
` son tour racine d’un arbre qui est appel´ sous-arbre de l’arbre global. Un sous-arbre de a autre
a                                           e
                e
que a est appel´ sous-arbre propre de a. Les racines des sous-arbres issus d’un nœud sont appel´s   e
                                      e
les fils du nœud. Tout nœud est le p`re de ses fils. Une feuille est un nœud qui n’a pas de fils. Un
                                         e                  e e
chemin est une suite de nœuds chacun ´tant le fils du pr´c´dent. Une branche est un chemin dont
            ee
le premier ´l´ment est la racine, et le dernier une feuille. La taille d’un arbre est le nombre de ses
                                                  ee
nœuds. La hauteur d’un arbre est le nombre d’´l´ments de la plus longue branche, diminu´ de 1. e
                                   e      a     ee
Ainsi, un arbre de hauteur 0 est r´duit ` un ´l´ment.


                                        e                                        c    e
Des structures essentiellement r´cursives Si les listes se traitent de fa¸on s´quentielle, la
 e        e              e                 e
r´cursivit´ est particuli`rement bien adapt´e au traitement des arbres. Ainsi, les arbres peuvent-ils
e       e             e e             e        e
ˆtre ais´ment caract´ris´s par une d´finition r´cursive comme suit.
Un arbre sur un ensemble D est :
    – soit vide

                                                  101
102                                             CHAPITRE 7. STRUCTURES ARBORESCENTES

                            e      ee
   – soit un couple constitu´ d’un ´l´ment de D (la racine) et d’une suite d’arbres (ses sous-arbres
          e
     imm´diats, ou par abus de langage ses fils ).

                                                            e
La plupart des algorithmes sur les arbres se rappellent r´cursivement sur les fils, le cas terminal
e                                                                        e
´tant celui de l’arbre vide. La terminaison des algorithmes est assur´e par le fait que les rappels
 e
r´cursifs ont lieu sur des arbres de taille moindre. Ainsi, les relations suivantes permettent de
          e
calculer r´cursivement la taille d’un arbre a :
    – si a est vide, sa taille est nulle
                                                                                e
    – si a n’est pas vide sa taille est la somme des tailles de ses fils, augment´e de 1.

                                         e
Des structures exprimant des hi´rarchies L’aspect bi-dimensionnel des arbres permet de
     e             e                         e                                      a
repr´senter des hi´rarchies qui ne peuvent ˆtre prises en compte par des structures ` une dimension.
Un exemple classique est celui des expressions arithm´tiques, comme (3y + x)(4 + (2 − 3x)/y)
                                                          e
     e    e                                                                           e     e
repr´sent´e par l’arbre de la figure 7.2. On remarquera que l’emploi de parenth`ses, n´cessaire
                                 e         e                         e
pour rendre compte des priorit´s des op´rations dans l’expression ´crite selon l’usage comme une
                                       e                                       e              e
suite de symboles, est inutile dans l’´criture sous forme d’arbre. Les priorit´s sont exprim´es par
                                     e              e       e              ıt´
la structure de l’arbre : les deux op´randes sont d´termin´s sans ambigu¨ e respectivement par les
                                     e
fils gauches et les fils droits des op´rateurs.


                                                        ×


                                            +               +


                                        ×       x   4           /


                                    3       y               −           y


                                                        2       ×


                                                            3       x

                Figure 7.2 – Structure de l’expression (3y + x)(4 + (2 − 3x)/y)

On remarque que les nœuds de cet arbre :
   – soit n’ont pas de fils,
   – soit en ont deux.
                                                    a
On dit que l’arbre est un arbre binaire. Il s’agit l` d’un cas particulier important de structures
                                 e
arborescentes, auquel est consacr´e la section qui suit.


7.2     Les arbres binaires
7.2.1    Description abstraite
Definition 1 (Arbre binaire) Un arbre binaire sur un ensemble D est :
            ee                        e
  – soit un ´l´ment particulier appel´ arbre vide
                                   u          ee                e
  – soit un triplet (r, sag, sad) o` r est un ´l´ment de D appel´ racine de l’arbre et sag et sad
                                          e
    sont des arbres binaires sur D appel´s respectivement sous-arbre gauche et sous-arbre droit.
7.2. LES ARBRES BINAIRES                                                                          103

                              e         ee
On peut alors introduire les d´finitions ´l´mentaires suivantes.

                                                                                                e
Definition 2 (Feuille) Un arbre binaire dont les sous-arbres gauche et droit sont vides est appel´
feuille.

Definition 3 (Nœuds) Si a est un arbre non vide, un nœud de a est :
  – soit sa racine
  – soit un nœud de son sous-arbre gauche
  – soit un nœud de son sous-arbre droit

Definition 4 (Sous-arbres) Si a est un arbre non vide, un sous-arbre de a est :
                 e
  – soit a lui-mˆme
  – soit, si a n’est pas vide, un sous-arbre de son sous-arbre gauche ou de son sous-arbre droit

Les primitives
                                 e                             e
    Soit D un ensemble. Dans la d´finition qui suit, a, g et d d´signent des arbres binaires sur D,
         ee
et r un ´l´ment de D.

   ab(r, g, d) : renvoie l’arbre binaire de racine r, de sous-arbre gauche g et de sous-arbre droit d.
                   e
   arbreVide : d´signe l’arbre vide.
                                       e
   vide(a) : renvoie une valeur bool´enne indiquant si a est l’arbre vide ou non.
                       e
   racine(a) : n’est d´fini que si a n’est pas vide. Renvoie la racine de a.
                    e
   sag(a) : n’est d´fini que si a n’est pas vide. Renvoie le sous-arbre gauche de a.
                     e
   sad(a) : n’est d´fini que si a n’est pas vide. Renvoie le sous-arbre droit de a.
   feuille(a) : renvoie vrai si a est une feuille.

7.2.2     Les divers parcours d’arbres binaires
                                         a
    Parcourir un arbre binaire consiste ` visiter chacun de ses nœuds. On donne ici le sch´ma e
          e e
le plus g´n´ral d’un parcours gauche-droite en profondeur d’abord. Cela signifie que lorsque l’on
                                           e
arrive sur un nœud de l’arbre pour la premi`re fois, on commence par visiter tous les nœuds de son
sous-arbre gauche, avant de visiter tous ceux de sous-arbre droit. Lors d’un tel parcours, comme
       e                                        e
illustr´ par la figure 7.3, chaque nœud est visit´ trois fois :
                                          e                               e             e      e
  1. en arrivant sur le nœud pour la premi`re fois, en descente, juste apr`s avoir visit´ son p`re,
                                       e
  2. quand on y arrive en remontant apr`s avoir parcouru son sous-arbre gauche et avant de
                 a
     redescendre ` droite,
                                     e           e
  3. quand on y remonte pour la derni`re fois apr`s avoir parcouru son sous-arbre droit.

                                                      
                                             1            3

                                         C
                                                  2
                                                          ‚

                                  sag                         sad



        Figure 7.3 – Les visites d’un nœud lors d’un parcours gauche-droite en profondeur
104                                          CHAPITRE 7. STRUCTURES ARBORESCENTES

                                                                               e                e
A chacune de ces trois visites, le nœud peut faire l’objet de traitements qui d´pendent du probl`me
       e                      e
envisag´, comme mis en lumi`re dans les exemples introduits par la suite. Sachant que parcourir un
                    a
arbre vide consiste ` ne rien faire, on obtient l’algorithme du parcours gauche-doite en profondeur
de la figure 7.4.

                                      parcours(a)
                                      si (non vide(a))
                                          traitement 1
                                          parcours(sag(a))
                                          traitement 2
                                          parcours(sad(a))
                                          traitement 3



                                               e e
                        Figure 7.4 – Parcours g´n´ral d’un arbre binaire

                         e                                   e
On remarque que le sch´ma de la figure 7.3 reste valable mˆme si le nœud est une feuille : on “re-
            e                 e                                e             e
monte” imm´diatement apr`s la descente dans chaque fils, apr`s avoir constat´ qu’ils sont vides.
                                                      e
Pour une feuille, les trois traitements sont donc cons´cutifs.

        e                          e e
Ce sch´ma de parcours est le plus g´n´ral. On distingue cependant trois modes de parcours par-
ticuliers :

                 e           e                                       e
    L’ordre pr´fixe ou pr´ordre : seul le traitement 1 est effectu´. Un nœud est trait´ avante
       tous ses descendants.
                                                                 e                    e     e
    L’ordre infixe ou inordre : seul le traitement 2 est effectu´. Un nœud est trait´ apr`s tous
       ceux de son sous-arbre gauche et avant tous ceux de son sous-arbre droit.
                                                                       e
    L’ordre postfixe ou postordre : seul le traitement 3 est effectu´. Un nœud est trait´ pr`s e e
       tous ses descendants.
                          a                                                                e
Si le traitement consiste ` imprimer le nœud, on obtient pour l’arbre de la figure 7.2 les r´sultats
suivants :
    Pr´ordre : × + × 3 y x + 4 / − 2 × 3 x y.
        e
    Inordre : 3 × y + x × 4 + 2 − 3 × x / y
    Postordre : 3 y × x + 4 2 3 x × − y / + ×

     e                                         e                                          e
Les r´sultats obtenus par les parcours en pr´ordre et postordre de l’expression arithm´tique sont
      e
appel´s respectivement notation polonaise et notation polonaise inverse de l’expression. On peut
                                                  ae          e                          e
se convaincre que ces notations, qui consistent ` ´crire l’op´rateur soit avant soit apr`s ses deux
   e        e                            e
op´randes ´vitent l’emploi des parenth`ses : on peut en effet reconstruire l’expression habituelle `a
                                                                       ıt´                  e
partir de l’une ou l’autre des notations qui n’induisent aucune ambigu¨ e sur l’ordre des op´rations.
                                               e
La notation polonaise inverse fut jadis utilis´e par certaines calculatrices.


 prefixe(a)                          infixe(a)                            postfixe(a)
 si (non vide(a))                    si (non vide(a))                     si (non vide(a))
     traitement                          infixe(sag(a))                       postfixe(sag(a))
     prefixe(sag(a))                     traitement                           postfixe(sad(a))
     prefixe(sad(a))                     infixe(sad(a))                       traitement


                  Figure 7.5 – Les trois ordres de parcours des arbres binaires

                                           e          e    e
Si l’on souhaite afficher l’expression arithm´tique repr´sent´e sur la figure 7.2 sous la forme habi-
7.2. LES ARBRES BINAIRES                                                                           105

                                                                             e
tuelle, un parcours en inordre est insuffisant, puisqu’il n’exprime pas la hi´rarchie des calculs. On
                  a e                        e                              e
est donc conduit ` r´introduire un parenth´sage : chaque sous-arbre repr´sentant une expression,
                           e                                                                e
on affichera une parenth`se ouvrante avant le parcours de ce sous-arbre, et une parenth`se fer-
                e                      e              e
mante juste apr`s. On est ainsi assur´ d’un parenth´sage correct. Toutefois, lorsque le sous-arbre
     e     a                        e
est r´duit ` une feuille, le parenth´sage, bien que correct, est lourd et superflu.

                 e            e      a        e                          e e
Toutes ces consid´rations am`nent ` une am´nagement du parcours g´n´ral (fig.7.4), le traite-
                  a                    e                    a
ment 1 consistant ` ouvrir une parenth`se, le traitement 2 ` afficher le nœud, et le traitement 3
a                    e
` fermer une parenth`se, sauf dans le cas des feuilles pour lesquelles les traitements 1 et 3 sont
        e                                 e
supprim´s. On obtient ainsi l’algorithme r´cursif suivant :

            e
BienParenth´ser(a)
si (non vide(a))
   si (feuille(a))
      afficher(racine(a)))
   sinon
      afficher("(")
                 e
      BienParenth´ser(sag(a))
      afficher(racine(a))
                 e
      BienParenth´ser(sad(a))
      afficher(")")

On obtient ainsi, pour l’exemple de la figure 7.2 : (((3 × y) + x) × (4 + (2 − ((3 × x) / y)))).
       u                                                        e              e
Bien sˆr, des analyses plus fines, prenant en compte les priorit´s des divers op´rateurs, permet-
             e                 e
traient d’all´ger encore cette ´criture.

               e       e                           e           u             e
Enfin, si la r´cursivit´ permet une expression ais´e, claire, sˆ re et synth´tique des divers par-
                                                          e             e
cours, il est toutefois possible d’en donner une forme it´rative. L’id´e est la suivante. Lorsque,
                                                                                                   a
lors d’un parcours, on visite un nœud trois actions sont possibles : soit remonter, soit descendre `
                         a                               a             e
gauche, soit descendre ` droite. On se trouve alors face ` deux probl`mes :
                                a                                                  ıtre son
  1. Comment “remonter”, puisqu’` partir d’un sous-arbre il est impossible de connaˆ
      e
     p`re ?
  2. Quelle action choisir parmi les trois possibles ?

                  e         e
Le premier probl`me se r´soud selon le principe du fil d’Ariane : lorsque l’on descend sur une
                    a                              e
branche de l’arbre ` partir de la racine, on m´morise les sous-arbres sur lesquels on passe, pour
                          e                  e                 e
pouvoir y remonter ult´rieurement, apr`s les avoir quitt´s pour s’enfoncer plus profond´ment      e
                                                                                                e
dans l’arbre. Le fil d’Ariane n’est autre qu’une pile, qui contient exactement tous les ancˆtres du
                                                                  e                        e
sous-arbre courant dans l’ordre dans lequel on les a rencontr´s, le sommet de la pile ´tant le der-
        e                   a          e                                          e
nier ancˆtre en date, c’est-`-dire le p`re. Il faut donc empiler en descente, et d´piler pour remonter.

          e         e       e            e                              e          e
Le deuxi`me probl`me se r´soud en d´finissant un ensemble de trois ´tats, caract´risant les trois
               e                e
visites d’un mˆme sous-arbre d´crites sur la figure 7.3. Soit donc l’ensemble
                                        Etat = {D, RG, RD}
 u                                             e                       e
o` D signifie descente, RG signifie remont´e gauche, et RD remont´e droite. Par suite, au lieu
                                                                                     e
d’empiler simplement les sous-arbres sur lesquels on passe, on les accompagne d’un ´tat qui indi-
                                                            e                        e
quera, lors de la prochaine visite, s’il s’agit d’une remont´e gauche ou d’une remont´e droite.

           a          e                    e                                    e
Par suite, ` chaque it´ration, on est en pr´sence du sous-arbre courant a, d’un ´tat e, et d’une pile
         e                                   e                     e                           e
constitu´e de couples de la forme : (arbre, ´tat). Toutes ces donn´es permettent alors de d´cider
            a
de l’action ` mener.

              a e
Il reste enfin ` d´terminer la condition de sortie de la boucle. Le fait que la pile soit vide n’est pas
106                                            CHAPITRE 7. STRUCTURES ARBORESCENTES

                                   ee                                               e
suffisante, puisque cette propri´t´ signifie que le nœud courant n’a pas d’ancˆtre, donc que c’est
                                                      a
la racine. Cette condition est satisfaite trois fois, ` chaque visite de la racine de l’arbre. En fait, le
                     e                                                    e
parcours est termin´ quand la pile et vide (on est sur la racine) et l’´tat est RD (on y est pour la
      e            e
troisi`me et derni`re fois), et l’on doit alors effectuer le traitement 3. On obtient ainsi l’algorithme
  e
it´ratif suivant :
ParcoursIteratif(a)
   e                                e
e: ´tat, p: pile de couples (arbre, ´tat)

si (non vide(a))
   p ← pileVide()
   e ← D
   faire
      cas e :
         D : traitement 1
              si (non vide(sag(a))
                 empiler((a, RG), p)
                 a ← sag(a)
              sinon
                 e ← RG
         RG : traitement 2
              si (non vide(sad(a))
                 empiler((a, RD), p)
                 a ← sad(a)
                 e ← D
              sinon
                 e ← RD
         RD : traitement 3
                       e
              (a,e)← d´piler(p)
   tant que (non (vide(p) et e = RG))
   traitement 3

7.2.3      Mise en œuvre en Java
                  e
    La classe AB d´finissant en Java les arbres binaires comporte trois champs, chacun correspon-
      a     ee
dant ` un ´l´ment du triplet constituant un arbre binaire non vide. Contrairement aux listes,
        e              e
le probl`me de la repr´sentation de l’arbre vide n’a pas ici de solution simple. C’est donc par
                                 e                       a
null que l’on convient de le repr´senter, ce qui revient ` confondre les notions d’abscence d’arbre
                                                                       e               a      e
et d’arbre vide. La primitive vide(a) ne peut faire l’objet d’une m´thode, le test ` vide ´tant
  e                 e    a
n´cessairement ext´rieur ` la classe AB. On obtient ainsi :
public class AB{
    private int r;
    private AB g, d;

      AB   (int e, AB gg, AB dd){
           r = e;
           g = gg;
           d = dd;
      }

  int racine() {return r;}
  AB sag() {return g;}
  AB sad() {return d;}
  boolean feuille() {return (g==null) && (d==null);}
7.3. ARBRES BINAIRES DE RECHERCHE (ABR)                                                         107

              e                                                          e              e
Le parcours r´cursif d’un arbre s’achevant sur l’arbre vide ne peut donc ˆtre programm´ comme
  e
m´thode d’instance puisque l’arbre vide n’est pas une instance. L’affichage en in-ordre d’un arbre
                           e           e
binaire est ainsi programm´ par une m´thode de classe comme suit :


      private static void affiche_inordre(AB a){

          if(a!=null){
              affiche_inordre(a.g);
              System.out.print(a.r + " ");
              affiche_inordre(a.d);
          }
      }

                      e           e
On peut toutefois en d´duire une m´thode d’instance comme suit :

      void affiche_inordre(){
          affiche_inordre(this);
      }

                e         e                                                        e
Toutes les m´thodes n´cessitant un parcours peuvent se programmer selon ce sch´ma, o` la   u
  e                             e                                       e            e
m´thode d’instance utilise une m´thode de classe, comme par exemple la m´thode ci-apr`s, calcu-
lant la taille de l’instance.

private static int size(AB a){
        if(a==null)return 0;
        return(size(a.g)+size(a.d)+1);
    }

int size(){return(size(this)); }


7.3       Arbres binaires de recherche (ABR)
                                e                                                        e
   Dans cette section, on consid`re des arbres binaires sur un ensemble totalement ordonn´.

 e
D´finition 7.1 Un arbre a est un arbre binaire de recherche (ABR) :
   – soit si a est l’arbre vide
                                                                        e       a
   – soit si, pour chacun des sous-arbres b de a, la racine de b est sup´rieure ` tous les nœuds
                                     e       a
     du sous-arbre gauche de b et inf´rieure ` tous les nœuds du sous-arbre droit de b.

Les deux arbres ci-dessous, dans lesquels le symbole 2 figure l’arbre vide, sont des arbres binaires
de recherche sur les entiers naturels, repr´sentant tous deux l’ensemble {5, 7, 10, 12, 14, 15, 18}.
                                           e

                             10                                          15


                      5           14                                14            18


                     2 7    12         18                       5             2


                                  15        2               2       10


                                                                7    12
108                                            CHAPITRE 7. STRUCTURES ARBORESCENTES

                                                         e
Les arbres binaires de recherche permettent de repr´senter des dictionnaires dans lesquels les re-
                                                                      ee
cherches peuvent se faire efficacement. En effet, pour rechercher un ´l´ment x dans un ABR, il suffit
                  a                               e
de le comparer ` la racine : s’il ne lui est pas ´gal, on le recherche soit dans le sous-arbre gauche
                                                     e            e             c
soit dans le sous-arbre droit selon qu’il lui est inf´rieur ou sup´rieur. On con¸oit que si l’arbre est
              e       e       a
relativement ´quilibr´, c’est-`-dire tel que les sous-arbres gauche et droit de chaque nœud sont `    a
       e        e                                                           e
peu pr`s de mˆme taille, on divise par deux l’espace de recherche apr`s chaque comparaison, ce
                                                           a              e
qui conduit, comme pour la recherche dichotomique, ` une complexit´ en temps logarithmique.

La gestion de tels dictionnaires consiste donc en trois algorithmes, recherche, suppression, in-
                       e      e                 e
sertion, les deux derni`res op´rations devant pr´server la structure d’ABR.



7.3.1    Recherche

                                      ee
    L’algorithme suivant recherche l’´l´ment x dans l’arbre a. Il renvoie soit le sous-arbre dont x
                ee
est racine si l’´l´ment figure dans l’arbre, soit, dans le cas contraire, l’arbre vide.

adresse(x,a)

si (vide(a))
   renvoyer(arbreVide)
sinon
   si (x = racine(a))
      renvoyer(a)
   sinon
      si(x < racine(a))
         renvoyer(adresse(x,sag(a))
      sinon
         renvoyer(adresse(x,sad(a))



7.3.2    Adjonction aux feuilles

                                       `                    ee
    L’adjonction aux feuilles consiste a rajouter un nouvel ´l´ment comme feuille d’un ABR de
  c a                                                        ee
fa¸on ` ce que celui-ci demeure un ABR. Ainsi le rajout de l’´l´ment 13 comme feuille de l’arbre :


                                                  10


                                           5           14


                                          2 7    12         18


                                                       15        2


ne peut se faire que comme sous-arbre droit du sous-arbre de racine 12. L’analyse du processus qui
                   a                                                           e          e
permet d’aboutir ` cette constatation est la suivante : puisque 13>10, il doit ˆtre rajout´ dans le
                                                  c                                       a
sous-arbre droit sur lequel on est descend ; de fa¸on analogue, puisque 13<14 on descend ` gauche
                           e          e                                  e
et puisque 13>12, il doit ˆtre rajout´ au sous-arbre droit. Ce dernier ´tant vide, il est remplac´e
par une feuille portant 13.
7.3. ARBRES BINAIRES DE RECHERCHE (ABR)                                                           109

                                                 10


                                         5                14


                                     2       7       12         18


                                                 2    13       15    2


                                                           e      ee         a
L’algorithme d’adjonction aux feuilles prend en param`tre l’´l´ment x ` rajouter et l’ABR a. Il
                 u              e            c e                                              e
renvoie l’ABR dˆment modifi´. Il est con¸u r´cursivement : si a est vide, a est transform´ en une
                                      e      a                                                      e
feuille portant x. Sinon, si x est inf´rieur ` la racine de a, le sous-arbre gauche de a est remplac´
                                                  e                 e        c
par l’arbre obtenu en y rajoutant x (rappel r´cursif). On proc`de de fa¸on analogue dans le cas
contraire.

ajoutAuxFeuilles(x,a)

si (vide(a))
   a ← ab(x, arbreVide, arbreVide)
sinon
   si (x < racine(a))
      sag(a) ← ajoutAuxFeuilles(x,sag(a))
   sinon
      sad(a) ← ajoutAuxFeuilles(x,sad(a))
renvoyer(a)

                         a                                                                    ee
Si l’ABR est construit ` partir de l’arbre vide par adjonctions aux feuilles successives, les ´l´ments
          e
les plus r´cents sont alors les plus profonds dans l’arbre. Imaginons un cas de figure dans lesquels
     ee                  e            e
les ´l´ments les plus r´cents sont ´galement les plus instables : par exemple, dans une entre-
                   e            e               e                                          a
prise, les employ´s les plus r´cemment recrut´s feront certainement l’objet de mises ` jour plus
  e
fr´quentes : titularisation, augmentation de salaire, changements d’affectation, changement du sta-
                                                       u         e       ee                   e
tut familial, etc. Dans ce cas, pour minimiser le coˆt des acc`s aux ´l´ments les plus r´cents, il
      ee
est pr´f´rable de les rajouter le plus haut possible dans l’arbre. C’est ce qui motive le paragraphe
suivant.




7.3.3               a
         Adjonction ` la racine

                                             ee        a                  c a
    Le but est donc de rajouter un nouvel ´l´ment x ` un ABR a, de fa¸on ` obtenir un nouvel
ABR dont il est la racine. Il faut donc couper l’ABR initial en deux ABR, l’un, G, comportant
          ee               e      a                                                e
tous les ´l´ments de a inf´rieurs ` x, l’autre D portant tous ceux qui lui sont sup´rieurs. L’ABR
       e                                                      e
cherch´ sera donc le triplet (x, G, D). On se propose donc d’´crire un algorithme coupure, tel que
                          ee
si a est un ABR et x un ´l´ment :
                                              ee
                                 G, l’ABR des ´l´ments e de a tels que e < x
coupure(x, a) = (G, D) avec :
                                 D, l’ABR des ´l´ments e de a tels que e ≥ x
                                              ee
                   ee         a         a
Soit par exemple l’´l´ment 11 ` ajouter ` la racine dans l’arbre a de la figure 7.6.
110                                                CHAPITRE 7. STRUCTURES ARBORESCENTES

                                                             16


                                                       5                            18


                                          4                13                  17        24


                                                   8                 15


                                               2       12       14        2

                                                                a
                    Figure 7.6 – L’ABR a objet d’une adjonction ` la racine


                                                                                              11

Il faut donc construire un arbre de la forme :
                                                           G : ABR des                                  D : ABR des
                                                           ee
                                                           ´l´ments e de a tels                         ee
                                                                                                        ´l´ments e de a tels
                                                           que e < 11                                   que e ≥ 11


                                e     a
Puisque 11 est strictement inf´rieur ` la racine 16 de a, le sous-arbre D comportera donc exacte-
ment :
                            ee
   1. la racine et tous les ´l´ments du sous-arbre droit de a
          ee                                               e          e     a
   2. les ´l´ments du sous-arbre gauche de a qui sont sup´rieurs ou ´gaux ` 11.
                      ee                e           e       a                    e
Clairement, tous les ´l´ments de l’alin´a 2 sont inf´rieurs ` tous ceux de l’alin´a 1, du fait qu’ils
proviennent respectivement du sous-arbre gauche de a et de son sous-arbre droit (ou de sa racine).
                                                                     16

       e
On en d´duit que D est de la forme :
                                                         ee
                                              ABR des ´l´ments                           18
                                              e de sag(a) tels que
                                              e ≥ 11
                                                                                    17        24
Par suite, le sous-arbre gauche D’ de D est l’arbre tel que :
                                      coupure(11, sag(a)) = (G’, D’)
       a                  e    a             u ee        a                 e      a
Quant ` l’arbre G, il est ´gal ` G’. Le cas o` l’´l´ment ` rajouter est sup´rieur ` la racine se traite
     c        e                         e e       a
de fa¸on sym´trique. On obtient, en r´it´rant ` la main comme ci-dessus les calculs de coupures
sous les sous-arbres de a, les deux ABR : :


                                  5                                            16

                        G=                             D=
                              4       8                               13                      18


                                                                12         15            17        24


                                                                          14    2
7.3. ARBRES BINAIRES DE RECHERCHE (ABR)                                                           111

On peut maintenant mettre en forme l’algorithme

coupure(x, a)
G, D : arbres
si (vide(a))
   renvoyer(arbreVide, arbreVide)
sinon
   si (x< racine(a))
      D ← a
      (G, sag(D)) ← coupure(x, sag(a))
   sinon
      G ← a
      (sad(G), D) ← coupure(x, sad(a))
renvoyer(G,D)

                          a                 e        e
L’algorithme d’adjonction ` la racine s’en d´duit imm´diatement :

ajoutALaRacine(x, a)
G, D : arbres
(G,D) ← coupure(x, a)
renvoyer(ab(x, G, D))

    e                        a
Le r´sultat de l’ajout de 11 ` la racine dans l’arbre a de la figure 7.6 est ainsi :


                                                    11


                                       5                  16


                                   4       8        13                 18


                                               12        15       17        24


                                                    14        2



7.3.4    Suppression
                                   ee                                  e
    Il s’agit ici de supprimer un ´l´ment x d’un ABR a tout en pr´servant la structure d’ABR.
Soit a’ le sous-arbre de racine x. Soient g et d respectivement le sous-arbre gauche et le sous-arbre
                                                                                          e
droit de a’. Si g (resp. d) est vide, il suffit de remplacer a’ par d (resp. g). Sinon, l’id´e consiste,
a                                               ee                                 e
` remplacer la racine de a’ par le plus petit ´l´ment m de d. On est ainsi assur´ que la structure
                e     e
d’ABR est pr´serv´e puisque la nouvelle racine m :
       e                        e        `         ee
    – ´tant issue de d, est sup´rieure a tous les ´l´ments de g
       e                                 e      a          ee
    – ´tant le minimum de d, est inf´rieure ` tous les ´l´ments du nouveau sous-arbre droit, `      a
                      e
       savoir d priv´ de m.
                                                                     a                    ee
Ainsi, la suppression de 13 dans l’arbre de la figure 7.6, consiste ` le remplacer par l’´l´ment 14,
  e               o e
pr´alablement ˆt´ du sous-arbre droit. On obtient ainsi
112                                                              CHAPITRE 7. STRUCTURES ARBORESCENTES

                                         16                                                                16

` partir de
a                                                                       le nouvel arbre
                               5                            18                                    5                       18


                  4                 13                 17        24                       4           14             17        24


                           8                  15                                                  8             15


                       2       12         14       2                                          2       12
                                               c                          ea          e
Ceci n’est correct que si d n’est pas vide. Si ¸a n’est pas le cas, on a d´j` remarqu´ qu’il suf-
                                                                          ee
fit alors simplement de remplacer a’ par g. Par exemple, en supprimant l’´l´ment 14 de l’arbre :
               16                                                                  16

                                                                      on obtient
            5                       18                                                                5                   18


 4                14           17        24                                                   4             8        17        24


              8        2                                                                              2         12


        2         12
                                       a                                o
L’algorithme supprimer fait donc appel ` un algorithme supprimerMin qui ˆte le minimum d’un
ABR et renvoie sa valeur.

Le minimum d’un ABR non vide est soit le minimum de son sous-arbre gauche si celui-ci n’est
pas vide, soit, dans le cas contraire, sa racine. L’algorithme supprimant le minimum m d’un ABR
                                        e                              o       a
non vide a renvoie un couple constitu´ de m et de l’arbre obtenu en ˆtant m ` l’arbre a.

supprimerMin(a)

si (vide(sag(a)))
   renvoyer(racine(a), sad(a))
sinon
   (x,sag(a)) ← supprimerMin(sag(a))
   renvoyer(x, a)

                                ee                                             e
L’algorithme suivant supprime l’´l´ment x de l’arbre a et renvoie l’arbre modifi´ :

supprimer(x, a)

si (non vide(a))
   si (x<racine(a))
      sag(a) ← supprimer(x, sag(a))
   sinon
      si (x>racine(a))
         sad(a) ← supprimer(x, sad(a))
      sinon         /* x = racine(a) */
              ´       ´
7.4. COMPLEXITE DES OPERATIONS SUR LES ABR                                                      113

           si (vide(sag(a)))
               a ← sad(a)
           sinon
               si (vide(sad(a)))
                  a ← sag(a)
               sinon
                  (racine(a), sad(a)) ← supprimerMin(sad(a))
   renvoyer(a)

                       ee
On remarque que si l’´l´ment x n’est pas dans l’arbre, l’algorithme de suppression ne fait que
                            a         a                      e                                    e
parcourir une branche jusqu’` arriver ` l’arbre vide et s’arrˆte. Ainsi, l’arbre a n’est pas modifi´,
                    a     e
ce qui est conforme ` la s´mantique attendue.


7.4              e       e
        Complexit´ des op´rations sur les ABR
                e                                              u
   Il s’agit d’´valuer la comportement asymptotique des coˆts des recherches, des insertions et
                                                                      e                 e
des suppressions dans un ABR en fonction de la taille de l’arbre. Ce d´veloppement th´orique
 e
n´cessite l’introduction de quelques notions sur les arbres binaires.

Notation Dans toute la suite, si a est un arbre binaire, on notera respectivement ga et da les
sous-arbres gauche et droit de a.

7.4.1    Instruments de mesure sur les arbres
  e
D´finition 7.2 (Profondeur) On appelle profondeur d’un nœud x d’un arbre le nombre
               e                                             e                           a
p(x) de ses ancˆtres. Autrement dit, il s’agit du nombre d’arˆtes du chemin de la racine ` x.
Ainsi, la profondeur de la racine d’un arbre est 0, celle de ses fils est 1.
  e
D´finition 7.3 (Hauteur) On appelle hauteur d’un arbre non vide, la profondeur maximale de
ses nœuds.
                          e    e
La hauteur de l’arbre repr´sent´ sur la figure 7.6 est 4.
Proposition 7.4 La taille n et la hauteur h d’un arbre binaire non vide satisfont la relation
                                        log2 (n) < h + 1 ≤ n
Preuve Soit a un arbre binaire de taille maximale parmi tous ceux de hauteur h, donc ayant
                                                                      ee
h+1 niveaux. Le premier niveau porte la racine et n’a donc qu’un seul ´l´ment. Tout autre niveau
     e                   ee                          e                             e
poss`de deux fois plus d’´l´ments que le niveau imm´diatement au dessus. On en d´duit la taille
de l’arbre a :
                                                 h
                                  taille(a) =         2k = 2h+1 − 1
                                                k=0
De plus, un arbre de taille minimale parmi tous ceux de profondeur h a pour taille h+1. Il s’agit
des arbres binaires dont tout sous-arbre qui n’est pas une feuille a un unique fils. De tels arbres
                      a                      e e e e
binaires, isomorphes ` une liste, sont dits d´g´n´r´s. Par suite, tout arbre binaire de hauteur h
a pour taille un entier n tel que h + 1 ≤ n < 2h+1 . De la deuxi`me in´galit´ on d´duit que
                                                                     e      e    e      e
                           e       e
log2 (n) < h + 1 ce qui ach`ve la d´monstration. 2
  e
D´finition 7.5 (Longueur de cheminement, profondeur moyenne) On appelle longueur
de cheminement d’un arbre non vide a, et on note lc(a), la somme des profondeurs de ses nœuds.
La profondeur moyenne des nœuds de l’arbre est donc :
                                                        lc(a)
                                         P (a) =
                                                      taille(a)
114                                                CHAPITRE 7. STRUCTURES ARBORESCENTES

Par convention, si a est vide, on pose lc(a) = 0 et P (a) = −1.

Sur l’exemple de l’arbre a de la figure 7.6, on obtient :

                            lc(a) = 2 × 1 + 4 × 2 + 2 × 3 + 2 × 4 = 24

                                                         24
                                              P (a) =       = 2.18
                                                         11

Proposition 7.6 Soit un arbre binaire de taille n>0 et i la taille de son sous-arbre gauche. La
fonction P de profondeur moyenne des nœuds satisfait la relation :

                                     1
                         P (a) =       × (iP (ga ) + (n − i − 1)P (da ) + (n − 1))
                                     n

Preuve Si i ∈ {0, . . . , (n− 1)} est la taille de ga , alors la taille de da est n´cessairement (n− 1 − i).
                                                                                   e
     e
Par d´finition de la longueur de cheminement :

                                       lc(a) =           p(x) +           p(x)
                                                  x∈ga             x∈da


puisque la profondeur de la racine est nulle. Notons pg et pd respectivement les profondeurs des
                                                                                       e
nœuds dans les arbres ga et da . Pour tout nœud x de ga , sa profondeur dans a est sup´rieure de
  a                                e           c
1 ` sa profondeur dans ga . En proc´dant de fa¸on analogue pour le sous-arbre droit, on obtient :
∀x ∈ ga , p(x) = pg (x) + 1 et ∀x ∈ da , p(x) = pd (x) + 1. Par suite :

 lc(a) =          (pg (x) + 1 ) +          (pd (x) + 1) =          pg (x) + i +          pd (x) + (n − 1 − i) =
           x∈ga                     x∈da                    x∈ga                  x∈da


lc(ga ) + lc(da ) + (n − 1).

     e                                 e
Par d´finition de la fonction P, on en d´duit que :

                            1         1
                  P (a) =     lc(a) = (i × P (ga ) + (n − i − 1) × P (da ) + (n − 1))
                            n        n
2

  e
D´finition 7.7 (Longueur de cheminement externe) On appelle longueur de cheminement
externe d’un arbre non vide a, et on note lce(a), la somme des profondeurs de ses feuilles. Par
convention, la longueur de cheminement externe de l’arbre vide est nulle.

Sur l’exemple de l’arbre a de la figure 7.6, on obtient :

                                       lce(a) = 2 × 3 + 4 × 2 = 14

Proposition 7.8 Soit a un arbre binaire de taille n>0 et f(a) le nombre de ses feuilles. La lon-
gueur de cheminement externe lce(a) de a satisfait la relation :

                                     lce(a) = lce(ga ) + lce(da ) + f (a)

             e
Preuve Le r´sultat provient du fait que les feuilles de a sont exactement celles de ga et da et que
                                        e            a
la profondeur d’une feuille de a est sup´rieure de 1 ` sa profondeur dans le sous-arbre gauche ou
                             ıt.
droit dans lequel elle apparaˆ 2
              ´       ´
7.4. COMPLEXITE DES OPERATIONS SUR LES ABR                                                                  115

7.4.2                e
            Arbres al´atoires de recherche (ABRn )
Motivations
                        ee                  a                                            a
     La recherche d’un ´l´ment consiste ` descendre le long d’une branche jusqu’` rencontrer
  ee                                        u                            e
l’´l´ment ou atteindre une feuille. Le coˆt de l’algorithme est du mˆme ordre de grandeur que
la profondeur du nœud auquel on aboutit.
                                                       a                           a
Pour l’insertion aux feuilles, il faut descendre jusqu’` une feuille. L’insertion ` la racine requiert
                                 e                                           e
une coupure de l’arbre effectu´e par un algorithme dont chaque rappel r´cursif descend d’un ni-
            a        a                                        e                 a
veau jusqu’` arriver ` une feuille. Une fois la coupure effectu´e, l’adjonction ` la racine ne requiert
                e
plus que des op´rations qui se font en temps constant. Ces deux algorithmes ont donc un coˆ t du u
   e                                                      a
mˆme ordre de grandeur que la profondeur de la feuille ` laquelle il aboutit.
                                               a                                     a ee
Enfin, l’algorithme de suppression consiste ` descendre sur une branche jusqu’` l’´l´ment ` sup- a
             a                                                                   u
primer puis ` rechercher le minimum d’un sous-arbre. L’algorithme a un coˆ t proportionnel ` la    a
profondeur du nœud auquel il aboutit.

        e                                            u        e
Il en r´sulte que tous ces algorithmes ont un coˆt du mˆme ordre de grandeur que la profon-
                                                            e
deur soit d’une feuille, soit d’un nœud quelconque. D’apr`s la proposition 7.4, il peut donc varier
                                                                        e
dans le pire des cas et selon la hauteur de l’arbre, entre une complexit´ logarithmique et une com-
       e e                                                            a           e
plexit´ lin´aire selon la configuration de l’ABR. On est ainsi conduit ` faire une ´tude en moyenne,
sur l’ensemble de toutes les configurations possibles.

 e
D´finition des ABRn
           e                      a ee
    Consid´rons un ensemble ` n ´l´ments que l’on souhaite organiser en ABR. Les n! permuta-
               ee                                                          e
tions de ces ´l´ments donnent autant de configurations des donn´es, toutes consid´r´es comme    e e
e
´quiprobables. Chacune de ces configurations permet de construire un ABR par adjonctions suc-
cessives aux feuilles. Sur l’exemple de l’ensemble {0, 1, 2}, chacune des 3! permutations :
σ0 = (0, 1, 2), σ1 = (0, 2, 1), σ2 = (1, 0, 2), σ3 = (1, 2, 0), σ4 = (2, 0, 1), σ5 = (2, 1, 0)
produit les ABR suivants :
            0                   0                 1              1                  2              2

aσ0 =                   aσ1 =               aσ2 =           aσ3 =           aσ4 =             aσ5 =
        2       1               2       2           0   2           0   2            0    2            1     2


            2       2               1   2                                           2 1               0 2



On constate ici que deux permutations 1 distinctes donnent le mˆme arbre (aσ2 = aσ3 ). Par suite si
                                                                  e
                        e                          e
les permutations sont ´quiprobables, les ABR en r´sultant ne le sont plus. Pour tourner la difficult´,e
                                a                                           a
on est naturellement conduit ` travailler avec des multi-ensembles, c’est-`-dire informellement des
ensembles avec r´p´titions. En d’autres termes, deux ´l´ments identiques, comme aσ2 et aσ3 , sont
                  e e                                   ee
       e e
consid´r´s comme distincts s’ils proviennent de deux configurations (ou permutations) distinctes.
          ee                                                e                  e
Chaque ´l´ments d’un multi-ensemble se voit ainsi affect´ d’un mulyipkicit´, qui est le nombre de
                                                                             ee                     e
fois qu’il y figure. Le cardinal du multi-ensemble est alors le nombre de ses ´l´ments ainsi distingu´s
par les configurations dont ils sont issus. Les multi-ensembles se notant entre doubles accolades.

           e
Exemple On ´crit :
                                    ABR3 = {{aσ0 , aσ1 , aσ2 , aσ3 , aσ4 , aσ5 }}
Son cardinal est not´ #ABR3 et est ´gal ` 6. La multiplicit´ de l’´l´ment a = aσ2 = aσ3 vaut deux,
                     e                e  a                 e      ee
                 ee           e     a
celle des autres ´l´ments est ´gale ` 1.

                         e
  1. On emploie ici indiff´remment les termes configuration et permutation.
116                                                  CHAPITRE 7. STRUCTURES ARBORESCENTES

Enfin, on notera que seul l’ordre sur des nœuds de l’arbre (et non pas la valeur des nœuds
elles-mˆmes) est significatif dans cette ´tude. A partir d’un ensemble ordonn´ {x0 , x1 , x2 } avec
       e                                e                                   e
                                                                    e e
x0 < x1 < x2 on aurait obtenu un multi-ensemble isomorphe au pr´c´dent, en confondant tout
                                                          e e   e             e a        e
nœud xi avec son indice i. Ainsi, et sans perdre en g´n´ralit´, on sera amen´ ` consid´rer des
ABRn sur l’ensemble
                                      In = {0, . . . , (n − 1)}

 u                      e                      e   ee                             e a
o` chaque entier i doit ˆtre compris comme le i`me ´l´ment ni d’un ensemble ordonn´ ` n
´l´ments 2 .
ee

Notation On notera Sn l’ensemble des permutations de In .

D´finition 7.9 (ABR al´atoires) Soit n un entier naturel et σ un ´l´ment de Sn . On note
  e                        e                                           ee
                  a
aσ l’ABR produit ` partir de l’arbre vide par adjonctions successives aux feuilles de chacun des
´l´ments, pris dans l’ordre : σ(0), . . . , σ(n − 1).
ee
                                              e                           ee
On appelle arbre binaire de recherche al´atoire de taille n chacun des n! ´l´ments aσ du multi-
ensemble ABRn obtenu ` partir de chacune des n! permutations σ de Sn . Chacun de ces arbres
                         a
                                                                                        1
est d´fini ` un isomorphisme pr`s sur l’ensemble des nœuds et affect´ de la probabilit´ .
     e    a                      e                                  e                e
                                                                                       n!
On dira, par abus de langage, que a est un ABRn au lieu de a est un arbre binaire de recherche
  e                                                                           e e       e
al´atoire de taille n, confondant ainsi le nom du multi-ensemble et le terme g´n´rique d´signant
    ee
ses ´l´ments.


      e e
Propri´t´s des ABRn

                   e                                                  e
   Dans le but de d´crire la conformation des ABRn , on introduit la d´finition suivante permettant
de prendre en compte la taille des sous-arbres gauche et droit.

Proposition et d´finition 7.10 Soit i∈ In . On note An le multi-ensemble des ABRn de racine
                    e                               i
             ee                  e
i. Les propri´t´s suivantes sont ´quivalentes

   1. An est le multi-ensemble des ABRn dont la racine est i.
       i

   2. An est le multi-ensemble des ABRn dont le sous-arbre gauche est de taille i.
       i

   3. An est le multi-ensemble des ABRn dont le sous-arbre droit est de taille n-i-1.
       i

   4. An est le multi-ensemble des ´l´ments aσ de ABRn tels que σ(0) = i.
       i                           ee

De plus, An a pour cardinal (n − 1)!.
          i
On notera simplement Ai au lieu de An quand il n’y a pas d’ambigu¨t´ sur n.
                                      i                          ıe

La d´monstration de l’´quivalence des quatre propri´t´s est ´vidente. Le cardinal de An est celui
     e                e                             ee       e                           i
de l’ensemble des permutations σ de Sn telle que σ(0) = i, c’est donc le cardinal de l’ensemble des
permutations des (n − 1) ´l´ments restants, soit (n − 1)!.
                         ee

                               ee                                                      e
Les sous-arbres gauches des ´l´ments de Ai sont des arbres binaires de recherche al´atoires de
                         ee
taille i. Toutefois, des ´l´ments distincts de Ai peuvent avoir des sous-arbres gauches correspon-
dant ` la mˆme permutation de Si . Par exemple, si n = 5 et i = 3, les permutations :
      a      e

      σ1 = (3, 1, 0, 2, 4),      σ2 = (3, 1, 0, 4, 2),        σ3 = (3, 1, 4, 0, 2),       σ4 = (3, 4, 1, 0, 2)

    2. Plus formellement, si X = {x0 , . . . , xn−1 } et Y = {y0 , . . . , yn−1 } sont deux ensembles ordonn´s ` ne a
ee
´l´ments tels que ∀i, j ∈ {0, . . . , n − 1} i < j ⇒ (xi < xj et yi < yj ), il existe un unique isomorphisme φ de X vers
           e                                                    e
Y , celui d´fini par ∀i, φ(xi ) = yi . In n’est autre qu’un repr´sentant canonique de tous ces ensembles isomorphes.
              ´       ´
7.4. COMPLEXITE DES OPERATIONS SUR LES ABR                                                                117

                                                                                                      1

                                                             e
produiront toutes, comme sous-arbre gauche de la racine, le mˆme ABR3 : aρ =                 avec
                                                                                       0 2
ρ = (1, 0, 2) puisque 1, 0 et 2, apparaissent dans cet ordre dans les quatre configurations ci-
dessus. La proposition suivante permet de d´nombrer les permutations σ de Sn qui produisent
                                            e
                                         e                a
comme sous-arbre gauche de la racine le mˆme ABRi , c’est-`-dire les sous-arbres gauches issus de
la mˆme permutation ρ de Si .
    e

Proposition 7.11 Soient i∈ In fix´, ρ une permutation de Si , aρ l’ABRi correspondant, ρ′ une
                                   e
permutation de Sn−i−1 et aρ′ l’ABRn−i−1 correspondant.
Pour toute permutation σ de Sn telle que σ(0) = i, on d´signe respectivement par gσ et dσ les
                                                          e
                                                                                      e
sous-arbres gauche et droit de l’ABRn aσ (dont la racine est i puisque σ(0) = i). On d´montre
alors que :
                                                        (n − 1)!
                                #{σ ∈ Sn / gσ = aρ } =
                                                            i!
                                                                     (n − 1)!
                                 #{σ ∈ Sn / dσ = aρ′ } =
                                                                   (n − i − 1)!

                                        e
Preuve Il s’agit dans un premier temps d´nombrer toutes les permutations

                                        σ = (i, σ(1), . . . , σ(n − 1))

de Sn telles que gσ = aρ . Il faut et il suffit pour cela que les entiers ρ(0), . . . , ρ(i − 1) apparaissent
dans cet ordre dans le (n − 1)-uple (σ(1), . . . , σ(n − 1)).
        i
Il y a Cn−1 fa¸ons de choisir les i rangs de ρ(0), . . . , ρ(i − 1) dans le (n − 1)-uplet. Ces rangs ´tant
              c                                                                                       e
d´termin´s, il y (n − 1 − i)! fa¸ons de configurer les ´l´ments restants dans les emplacements rest´s
  e       e                     c                        ee                                              e
                                          i       (n − 1)!
vacants. Soit au total (n − 1 − i)! × Cn−1 =                  permutations.
                                                      i!
      e                                                               a        e
La d´monstration pour les sous-arbres droits est analogue, ` ceci pr`s que l’on consid`re des      e
permutations de l’ensemble {(i+1), . . . , (n−1)} et non de l’ensemble {0, . . . , (n−i−2)}, mais on a
                                            e      e                                    c e
vu tous les ensembles totalement ordonn´s de mˆme cardinal fini se traitent de fa¸on ´quivalente. 2

                                                                e
Corollaire 7.12 Soient n un entier naturel et α une fonction num´rique sur les ABR. On
 e         e     e
d´montre l’´galit´ suivante :
                                                α(ga ) =               α(da )
                                       a∈ABRn                a∈ABRn

                                                n−1
Preuve Il est clair que              α(ga ) =                                       e
                                                            α(ga ). Par suite, d’apr`s la proposition 7.11, on
                           a∈ABRn               i=0 a∈Ai
    e
en d´duit que :
                                                      n−1
                                                            (n − 1)!
                                         α(ga ) =                               α(a)
                                                      i=0
                                                               i!
                               a∈ABRn                                  a∈ABRi

Or,
                   n−1                                 n−1
                          (n − 1)!                             (n − 1)!
                                              α(a) =                                     α(a)
                    i=0
                             i!                        i=0
                                                             (n − 1 − i)!
                                     a∈ABRi                                 a∈ABRn−1−i

                                                 e              e
Ces deux sommes ont en effet exactement les mˆmes termes, ´crits toutefois en ordre inverse.
                    a
Enfin, en appliquant ` nouveau la proposition 7.11, on obtient :
n−1                               n−1
      (n − 1)!
                          α(a) =           α(da ) =          α(da )                      2
i=0
    (n − 1 − i)!                  i=0
                  a∈ABRn−1−i                   a∈Ai             a∈ABRn
118                                                                 CHAPITRE 7. STRUCTURES ARBORESCENTES

7.4.3      Profondeur moyenne des nœuds dans ABRn
   e e                                                                          e     e
Th´or`me 7.13 La profondeur moyenne Pn des nœuds des ABRn satisfait la double in´galit´
suivante :
                                         5
                          ∀n > 0 ln(n) − ≤ Pn ≤ 4ln(n)
                                         2
    e
La d´monstration utilise le lemme suivant :
                                                                                e
Lemme 7.14 Pour tout entier strictement positif n, Pn satisfait la relation de r´currence sui-
vante :
                                                    n−1
                                     (n − 1)     2
                              Pn =            + 2       iPi
                                        n        n i=1

            e e                              e     e
Preuve du th´or`me Supposons pour l’instant d´montr´ le lemme.

  1. La relation ∀n ≥ 1 Pn ≤ 4ln(n) se d´montre par r´currence sur n.
                                        e             e
     Soit n > 0 fix´. Supposons que ∀k, 0 < k < n, Pk ≤ 4ln(k). On d´duit du lemme pr´c´dent
                  e                                                 e               e e
     que :
                                                        n−1
                                         (n − 1)    8
                                  Pn ≤           + 2        iln(i)
                                            n      n i=1
      Or
                                       n−1                      n
                                                                                       n2         n2   1
                                             iln(i) ≤               xln(x)dx =            ln(n) −    +
                                       i=1                  1                          2          4    4
      Donc
                       (n − 1)                2               2 − n(n + 1)
                       Pn ≤    + 4ln(n) − 2 + 2 = 4ln(n) +                 ≤ 4ln(n)
                           n                  n                    n2
  2. Soit n ≥ 2 fix´. Les ABR de taille n pour lesquels la profondeur moyenne des nœuds est
                   e
     minimale sont ceux dont la hauteur h est minimale (noter que puisque n ≥ 2 n´cessairement
                                                                                 e
     h ≥ 1). Il s’agit des arbres dont chaque niveau i, sauf peut-ˆtre le dernier, comporte le
                                                                   e
     maximum de nœuds, soit 2i . Pour un tel arbre a, la profondeur moyenne Pa des nœuds est
        e          e      a                                                    e
     sup´rieure ou ´gale ` la profondeur moyenne des nœuds qui ne sont pas situ´s sur le dernier
                        e
     niveau h. On en d´duit que :
                                      h−1                 h−1                         h−1
                             1                        1                       1
      Pn ≥ Pa ≥          h−1
                                            i.2i ≥                  i.2i ≥                  x.2x dx.
                         i=0     2i   i=0
                                                     2h   i=1
                                                                             2h   0
                          x           x.ln(2)
      Sachant que 2 = e          , on en d´duit que la d´riv´e (2x )′ = ln(2).2x et par suite que
                                          e             e e
        1 x                          x
             2 est une primitive de 2 .
      ln(2)
                                                       1
      Une int´gration par parties montre alors que
               e                                            (ln(2)x.2x − 2x ) est une primitive de
                                                     ln(2)2
      x.2x . Par suite :
                       h−1
              1                                      1
      Pn ≥                   x.2x dx =                          ln(2)(h − 1).2h−1 − 2h−1 + 1              =
             2h    1                            2h ln(2)2

        1         ln(2)(h − 1) 1  1                               1          ln(2)(log2 (n) − 2) 1  1
                              − + h                       ≥                                     − + h             ≥
      ln(2)2           2       2 2                              ln(2)2                2          2 2
                                                                                                             e
                                                                                                       (d’apr`s la proposition 7.4)

        1                  1              1                                 1               1              1
             .log2 (n) −      2
                                . ln(2) +                            =         2
                                                                                 .ln(n) −      2
                                                                                                 . ln(2) +            ≥
      2ln(2)             ln(2)            2                              2ln(2)           ln(2)            2
              ´       ´
7.4. COMPLEXITE DES OPERATIONS SUR LES ABR                                                                                              119

              5
       ln(n) − .
              2
                           1                                  1              1
       En effet :                ≈ 1, 041 et                        . ln(2) +                ≈ 2, 49.
                        2ln(2)2                             ln(2)2           2
       Cette in´galit´ a ´t´ prouv´e pour n ≥ 2. Elle est ´videmment v´rifi´e pour n = 1.
               e     e ee         e                       e           e e
2

                                                               e
Preuve du lemme Soit n > 0. Sachant que Pn est par d´finition la somme des profondeurs
de tous les nœuds des arbres a de ABRn , qu’il y a (n − 1)! ´l´ments dans ABRn , chacun compor-
                                                            ee
tant n nœuds, on obtient :
                                                                                                                      n−1
             1                                 1                   1                   1                         1
Pn =                              p(x) =                       (             p(x)) =                   P (a) =                   P (a) =
          n! × n                               n!                  n                   n!                        n!
                   a∈ABRn x∈a                        a∈ABRn            x∈a                  a∈ABRn                    i=0 a∈Ai

    n−1
1            1
                              P (a).
n   i=0
          (n − 1)!
                       a∈Ai

         e
Or, d’apr`s la proposition 7.6 :
                                               1
                                 P (a) =                   (iP (ga ) + (n − 1 − i)P (da ) + (n − 1))
                                               n
                          a∈Ai                      a∈Ai

Enfin, on a vu que la cardinal de Ai est (n−1)!, il y a donc (n−1)! termes dans la somme ci-dessus.
Par suite :
       1                          1                           1                                               1
                       P (a) =           (n − 1) + i                            P (ga ) + (n − 1 − i)                         P (da )
    (n − 1)!                      n                        (n − 1)!                                        (n − 1)!
                a∈Ai                                                     a∈Ai                                          a∈Ai


                                                   e
De plus, en appliquant la proposition 7.11, on en d´duit que :
                                                       (n − 1)!
                                        P (ga ) =                                P (a) = (n − 1)! Pi
                                                          i!
                                 a∈Ai                                  a∈ABRi

et que
                                                (n − 1)!
                               P (da ) =                                          P (a) = (n − 1)! Pn−1−i
                                              (n − 1 − i)!
                        a∈Ai                                   a∈ABRn−1−i

Par suite :
                            1                               1
                                              P (a) =         ((n − 1) + iPi + (n − 1 − i)Pn−1−i )
                         (n − 1)!                           n
                                       a∈Ai

et donc :
                         n−1                                            n−1
                   1              1                          1
           Pn    =                                   P (a) = 2                 ((n − 1) + iPi + (n − 1 − i)Pn−1−i )
                   n     i=0
                               (n − 1)!                      n           i=0
                                              a∈Ai

Or, il se trouve que
                                               n−1             n−1
                                                     iPi =             (n − 1 − i)Pn−1−i
                                               i=0             i=0
                                            e            e   e e
puisque les deux sommes ont exactement les mˆmes termes, ´num´r´s en ordre inverse. On obtient
donc :
                                         n−1                     n−1
                           (n − 1)    2             (n − 1)   2
                    Pn =           + 2       iPi =          + 2      iPi
                              n      n i=0             n      n i=1
2
120                                                           CHAPITRE 7. STRUCTURES ARBORESCENTES

7.4.4       Profondeur moyenne des feuilles des ABRn
   e e
Th´or`me 7.15 Pour tout entier strictement positif n, la profondeur moyenne Fn des feuilles
                               e     e
des ABRn satisfait la double in´galit´ :

                                                   ln(n) ≤ Fn ≤ 10.ln(n)

    e                 e e                         e
La d´monstration du th´or`me requiert le calcul pr´alable du nombre moyen de feuilles dans les
ABRn .

              e
Notations On d´signe respectivement par :

 f (a)                                                       le nombre de feuilles d’un arbre a.
 lce(a)                                                      la longueur de cheminement externe d’un arbre a.
 ∀n ∈ IN, fn =                    f (a),                     le nombre des feuilles de tous les ABRn .
                         a∈ABRn
        1
 fn =      fn ,                                              le nombre moyen de feuilles dans ABRn .
        n!
             1                                 
     Fn =                    lce(a),    ∀n > 0 
            fn                                               la profondeur moyenne des feuilles de tous les ABRn .
                 a∈ABRn                           
     F0 = 0

  e e
Th´or`me 7.16 (Nombre moyen de feuilles dans les ABRn ) Pour tout entier naturel n, le
nombre moyen fn de feuilles des ABRn satisfait les relations suivantes :
                                                                                        n+1
                                     f0 = 0,     f1 = 1             et   ∀n ≥ 2, fn =
                                                                                         3
                               e            e
Preuve Si n vaut 0 ou 1, le r´sultat est imm´diat.
Soit donc n ≥ 2. La racine des ´l´ments de ABRn n’est pas une feuille. Par suite, les feuilles d’un
                                 ee
tel ABRn sont exactement celles de son sous-arbre gauche et celles de son sous-arbre droit. On
obtient donc le calcul suivant :

 fn =               f (a) =                f (ga ) +                 f (da ) =
        a∈ABRn                 a∈ABRn                  a∈ABRn
 2               f (ga ) =                                                       (corollaire 7.12)
     a∈ABRn
     n−1                       n−1
                                     (n − 1)!
 2               f (ga ) = 2                             f (a) =                 (proposition 7.11)
     i=0 a∈Ai                  i=0
                                        i!
                                                a∈ABRi
            n−1                                          n−1
                    1
 2(n − 1)!                        f (a) = 2(n − 1)!                 fi             e
                                                                                 (d´finition des fi ).
              i=0
                    i!                                   i=0
                         a∈ABRi
                                                       n−1
                                       1       2
Par suite : ∀n ≥ 2, fn =                  fn =               fi ,
                                       n!      n       i=0
                                       n−1
                              2                                 2         n           n+2
et donc : fn+1 =                             fi + fn     =                  fn + fn =     fn .
                             n+1       i=0
                                                               n+1        2           n+1
Une r´currence simple, s’appuyant sur ce r´sultat et sur le fait que f2 vaut 1, permet de conclure.
     e                                    e
2

    e                 e e
La d´monstration du th´or`me 7.15 s’appuie sur le lemme suivant.

                                                                                   e
Lemme 7.17 La profondeur moyenne des feuilles des ABRn satisfait les relations de r´currence
suivantes :
              ´       ´
7.4. COMPLEXITE DES OPERATIONS SUR LES ABR                                                                                               121

                       F1 = 0
                                                         n−1
                                   2
                       Fn = 1 +                                (i + 1)Fi          ∀n ≥ 2
                                n(n + 1)                 i=1

            e               e                                               e         e    a
Preuve Le r´sultat est imm´diat pour n = 1. Soit n un entier quelconque, sup´rieur ou ´gal ` 2.
                  e
En appliquant la d´finition de Fn on obtient le calcul suivant :
                 1                              1
 Fn =                          lce(a) =                          (lce(ga ) + lce(da ) + f (a)) =                   (proposition 7.8)
                fn                             fn
                     a∈ABRn                         a∈ABRn
            1                                                                           2
 1+                           lce(ga ) +                  lce(da )       =1+                          lce(ga ) =   (corollaire 7.12)
           fn                                                                          fn
                 a∈ABRn                    a∈ABRn                                           a∈ABRn
                n−1                                               n−1
            2                                       2(n − 1)!            1
 1+                       lce(ga ) = 1 +                                                    lce(a) =               (proposition 7.11)
           fn   i=1 a∈Ai
                                                       fn          i=1
                                                                         i!
                                                                                 a∈ABRi
                      n−1
           2(n − 1)!           fi
 1+                               Fi =                                                                               e
                                                                                                                   (d´f. de fn et Fi )
             n!fn       i=1
                               i!
                 n−1                                     n−1
            2                           2×3                    i+1
 1+                    fi Fi = 1 +                                 Fi =                                               e e
                                                                                                                   (th´or`me 7.16)
           nfn   i=1
                                       n(n + 1)          i=1
                                                                3
                       n−1
              2
 1+                           (i + 1)Fi
           n(n + 1)     i=1
2

              e e
Preuve du th´or`me 7.15
                                                               e
1.Il s’agit dans un premier temps, de trouver une constante mum´rique b > 0 telle
que :
                                ∀n ≥ 1 Fn ≤ b.ln(n)
 On proc`de par r´currence sur n. Si n = 1, F1 = 0 ≤ b.ln(1) ∀b > 0 .
         e       e
Soit n ≥ 2 un entier quelconque fix´ et supposons la propri´t´ vraie pour tout entier naturel
                                     e                       ee
1 ≤ i < n. On obtient alors :
                               n−1                                               n−1
                    2                                             2b
Fn = 1 +                             (i + 1)Fi ≤ 1 +                                   (i + 1)ln(i)
                 n(n + 1)      i=1
                                                               n(n + 1)          i=1
                     ae
On est ainsi conduit ` ´tudier l’expression :
n−1                        n−1                      n−1                      n                       n
         (i + 1)ln(i) =             i × ln(i) +           ln(i) ≤                xln(x)dx +              ln(x)dx
i=1                           i=2                   i=2                  2                       2

Or :
     n                                               n
                              x2         x2                    n2         n2
         xln(x)dx =              ln(x) −                 =        ln(n) −    − 2ln(2) + 1
 2                            2          4           2         2          4
et
     n
                                           n
         ln(x)dx = [xln(x) − x]2 = n × ln(n) − n − 2ln(2) + 2
 2

Par suite,
n−1
                              n(n + 2)         n(n + 4)                n(n + 2)         n(n + 4) 1
         (i + 1)ln(i) ≤                ln(n) −          + 3 − 4ln(2) ≤          ln(n) −         +
i=1
                                 2                4                       2                4      4
En effet, 3 − 4ln(2) ≈ 0.227411278
122                                                      CHAPITRE 7. STRUCTURES ARBORESCENTES


       e
On en d´duit que

                  2b n(n + 2)          n2 + 4n − 1       (n + 2)             n2 + 4n − 1
Fn ≤ 1 +                      ln(n) −              =1+b×         ln(n) − b ×             =
               n(n + 1)   2                 4            (n + 1)              2n(n + 1)
                   1                n2 + 4n − 1
b × ln(n) + b ×         ln(n) − b ×             +1
                (n + 1)              2n(n + 1)
Pour montrer que Fn ≤ b.ln(n), il suffit donc de trouver b tel que :

                  ln(n)      n2 + 4n − 1
∀n ≥ 2, b ×              −b×             +1<0
                 (n + 1)      2n(n + 1)
      a
c’est-`-dire tel que :

            1     n2 + 4n − 1     ln(n)
∀n ≥ 2, 0 <    ≤              −
             b     2n(n + 1)    (n + 1)
                              ln(n)      ln(n)
Il suffit donc, puisque tel que        ≥          de trouver b tel que que :
                                n       (n + 1)

                                                         1   n2 + 4n − 1 ln(n)
                                     ∀n ≥ 2,      0<       ≤            −                                              (7.1)
                                                         b    2n(n + 1)    n
                    n2 + 4n − 1               ln(n)
En posant f (n) =                   et g(n) =                                   e e
                                                    , on obtient les fonctions d´riv´es :
                      2n(n + 1)                 n
            3n2 − 2n − 1      (3n + 1)(n − 1)                1 − ln(n)
f ′ (n) = −             2 = −               2  et g ′ (n) =            .
            2 (n(n + 1))        2 (n(n + 1))                    n2

On en d´duit leurs tableaux de variation sur [1
       e                                                        + ∞[.


  n        1                        +∞             n       1             e          +∞

 f’(n)   0                −                       g’(n)         +        0      −

 f(n)      1                                      g(n)                  1
                                      1                               I e
                                z                                              q
                                      2                    0                        0

                                           1 1  1
Par suite, ∀n ≥ 1, f (n) − g(n) ≥           − >    .
                                           2 e  10
           1 1
En effet,    − ≈ 0, 132120559.
           2 e
                                                                    u      e e
Il suffit donc de choisir b = 10 pour satisfaire la condition 7.1, d’o` le th´or`me.

2. Il s’agit maintenant de trouver une constante a > 0 telle que ∀n ≥ 1 Fn ≥ a.ln(n).
      c            a            e                             e        e
De fa¸on analogue ` celle employ´e pour la majoration, on proc`de par r´currence sur n.
Si n = 1, F1 = 0 ≥ a.ln(1) ∀a > 0 .
Soit n ≥ 2 un entier quelconque fix´ et supposons la propri´t´ vraie pour tout entier naturel
                                    e                        ee
1 ≤ i < n. On obtient alors :
                          n−1                                  n−1                                   n−1
                  2                                  2a                                      2a
Fn = 1 +                        (i + 1)Fi ≥ 1 +                      (i + 1)ln(i) ≥ 1 +                    i × ln(i)
               n(n + 1)   i=1
                                                  n(n + 1)     i=1
                                                                                          n(n + 1)   i=2
              ´       ´
7.4. COMPLEXITE DES OPERATIONS SUR LES ABR                                                             123

Or l’expression :
n−1                  n−1                                 n−1
                                         x2         x2             (n − 1)2             (n − 1)2   1
      iln(i) ≥             xln(x)dx =       ln(x) −            =            ln(n − 1) −          +
i=1              1                       2          4    1            2                    4       4


et
n−1                  n−1
                                                 n−1
      ln(i) ≥              ln(x)dx = [xln(x) − x]1   = (n − 1)ln(n − 1) − (n − 1) + 1
i=1              1

Par suite,

            2a                        n−1            (n − 1)2 + 4(n − 1) − 5
Fn ≥ 1 +           (n − 1)ln(n − 1)(         + 1) −                                      =
         n(n + 1)                        2                      4
      2a      (n + 1)(n − 1)               (n − 2)(n + 4)
1+                           ln (n − 1) −                   =
   n(n + 1)         2                            4
      n−1               (n − 2)(n + 4)
1+a         ln(n − 1) −                    .
        n                 2n(n + 1)
On en d´duit que Fn − a.ln(n) ≥ 1 − a.α(n) avec :
       e

                        n−1               (n − 2)(n + 4)                  n−1
∀n > 1,      α(n) = ln(n) −  ln(n − 1) +                    ≥ ln(n) −           ln(n − 1)
                         n                  2n(n + 1)                       n
                           n−1                                           ln(n − 1)
La fonction β(n) = ln(n) −       ln(n − 1) a pour d´riv´e β ′ (n) = −
                                                      e e                           qui est stric-
                             n                                               n2
tement n´gative sur [2 + ∞[. Par suite, β est d´croissante sur cet intervalle et donc :
         e                                     e

∀n > 1 α(n) ≤ β(n) ≤ β(2) = ln(2)

       e
On en d´duit que

                                  1
                     0<a<               ⇒   Fn − a.ln(n) ≥ 1 − a.α(n) ≥ 1 − a.ln(2) > 0
                                ln(2)

                                   1
Il suffit de choisir a = 1 puisque       ≈ 1, 45
                                 ln(2)
2


7.4.5      Conclusion
                                           u                             ee
    On a vu dans la section 7.4.2 que le coˆt moyen de la recherche d’un ´l´ment dans un ABRn
         e
est du mˆme ordre de grandeur asymptotique que la profondeur moyenne des nœuds des ABRn .
      e                      u
On a ´galement vu que le coˆt moyen d’une suppression, d’une adjonction aux feuilles et d’une
           a                                    e
adjonction ` la racine dans un ABRn est du mˆme ordre de grandeur asymptotique que la pro-
fondeur moyenne des feuilles des ABRn .
               e           e e
On peut donc ´noncer le th´or`me suivant.

    e e                u                   e        ee
Th´or`me 7.18 (Coˆ t moyen des op´rations ´l´mentaires sur les ABR) On consid`re                 e
un ensemble E = {a0 , . . . an−1 } totalement ordonn´ ` n ´l´ments. Soit σ une permutation des
                                                      e a ee
ee                                              e                                         a
´l´ments de E. On suppose que E est configur´ en l’arbre binaire de recherche obtenu, ` partir de
                                              ee                                           e
l’arbre vide, par adjonctions successives des ´l´ments σ(a0 ) . . . , σ(an−1 ). On suppose ´galement
                                    e
que les n! permutations de E sont ´quiprobables.
               e                                                                  a
Les complexit´s en moyenne des recherches, des adjonctions aux feuilles ou ` la racine, et des
suppressions dans ces ABR sont logarithmiques en la taille n de l’arbre.
124                                                 CHAPITRE 7. STRUCTURES ARBORESCENTES

7.5                                                   e
         Arbres binaires parfaits partiellement ordonn´s (Tas)
7.5.1     Codage d’un arbre binaire parfait
  e                                                                             e
D´finition Un arbre binaire est dit parfait lorsque tous ses niveaux sont compl`tement remplis,
     e
sauf ´ventuellement le dernier niveau et dans ce cas les nœuds (feuilles) du dernier niveau sont
      e         a
group´s le plus ` gauche possible.

                e
Ceci est illustr´ par la figure ci-dessous.
                                                                3


                                                    5                    9

                                            6               8
                                                                    11           10

                                   18           12 14

                                Figure 7.7 – Un arbre binaire parfait



                                                          e             c   e
Codage Un arbre binaire parfait de taille n peut se repr´senter de fa¸on tr`s compacte dans un
                             e     a           e
tableau T de taille au moins ´gale ` n. La repr´sentation est la suivante :

La racine est en T[1] et si un nœud est en T[i], son fils gauche est en T[2i] et son fils droit
en T[2i+1].


                0       1      2        3       4       5           6        7        8    9    10
                 ?      3      5        9       6       8           11       10       18   12   14   ...

                            Figure 7.8 – Codage de l’arbre de la figure 7.7


L’arbre et chacun de ses sous-arbres sont rep´r´s par leur adresse i, 1 ≤ i ≤ n, dans le tableau. Il
                                                e e
                          e      e             e
est donc parfaitement d´termin´ par la donn´e du tableau T et de sa taille n qui est aussi l’indice
            e                          e              e
de la derni`re case du tableau occup´e par la repr´sentation.
Il r´sulte de cette repr´sentation qu’´tant donn´ un entier 1 ≤ i ≤ n,
    e                   e              e            e
    – si 2i + 1 ≤ n, l’arbre d’adresse i a deux fils d’adresses respectives 2i et 2i + 1,
    – si 2i = n l’arbre d’adresse i a un fils gauche et pas de fils droit,
    – si 2i > n n’a pas de fils : c’est une feuille.

Les primitives relatives ` l’ensemble des sous-arbres {1, . . . , n} de l’arbre parfait de taille n peuvent
                         a
      e     e               c
ainsi ˆtre d´finies de la fa¸on suivante :

racine(i) : T[i] si i > 0
sag(i) : si 2i ≤ n alors 2i
sad(i) : si 2i + 1 ≤ n alors 2i + 1.
pere(i) : i/2.

                      e
On remarque que le p`re de l’arbre global (d’adresse 1) a pour adresse 0, qui correspond ici
                                           e    e
au pointeur null quand les arbres sont repr´sent´s par des objets.
                                                  ´
7.5. ARBRES BINAIRES PARFAITS PARTIELLEMENT ORDONNES (TAS)                                                                                     125

7.5.2      Tas

                                e      e                          e
    Un arbre binaire parfait (´tiquet´ sur un ensemble ordonn´) est dit partiellement ordonn´ si    e
  e                               e      a
l’´tiquette de tout nœud est inf´rieure ` celle de ses fils. Un arbre parfait partiellement ordonn´    e
                e
est aussi appel´ tas. Un arbre parfait non vide est donc un tas si et seulement si chacun de ses
                                                               e      a
sous-arbres gauche et droit est un tas et si sa racine est inf´rieure ` celles de ses fils. C’est le cas
de l’exemple de la figure 7.7.
      e e                           e           e                                          a
L’int´rˆt de cette structure de donn´e est l’acc`s facile au minimum du tas, qui se trouve ` la racine.
Par exemple, l’algorithme de Dijkstra calculant le plus court chemin entre deux sommets d’un
             e                        e
graphe valu´ (cf. section 8.10) proc`de par suppressions successives du minimum d’un ensemble
       a e
jusqu’` ´puisement de ce dernier. L’utilisation d’une structure de tas est donc essentielle ` unea
                                ee                          e
mise en œuvre efficace de ce c´l`bre algorithme de la th´orie des graphes.




7.5.3      Suppression du minimum d’un tas

         e                             e                                    e               e
    L’id´e est de supprimer la derni`re feuille du dernier niveau apr`s avoir recopi´ son ´tiquettee
a
` la racine, puis, pour respecter la structure de tas, de faire redescendre cette valeur en la compa-
                                                                       e      a
rant au contenu de ses fils. Si l’un au moins d’entre eux est sup´rieur ` la racine, celle-ci doit ˆtre  e
e       e                                     e    e       e e
´chang´e avec leur minimum. Une fois d´plac´e, on r´it`re le processus en examinant ` nouveau    a
                       a                    e        a
ses fils, et ceci jusqu’` ce qu’elle soit inf´rieure ` ses fils, s’ils existent. Le fait de faire redescendre
                                                                          e
un nœud dans l’arbre pour respecter la structure de tas est appel´ tamisage (ou percolation) vers
                         e
le bas. La figure ci-apr`s illustre ce processus dans le cas de l’exemple.


                                   3                                              14                                              5


                   5                        9                             5                      9                       14                9

           6                   8                                 6                8                             6             8
                                       11        10                                     11            10                              11        10

     18        12 14                                   18            12                                    18       12

                                                                                  5
                           5

                                                                          6                  9
               6                       9
                                                                              8
                       8                                    12                         11            10
     14
                               11           10
                                                      18             14
18        12


                                        Figure 7.9 – Suppression du minimum d’un tas


On peut concevoir l’algorithme tamiserVersLeBas d’un nœud d’adresse i dans un tas T comme
suit.
126                                          CHAPITRE 7. STRUCTURES ARBORESCENTES

tamiserVersLeBas(T,i)
x←racine(i)
f←filsMin(T,i)
tant que f≤ n et x>racine(f)
    racine(i)←racine(f)
    racine(f)←x
    i←f
    f←filsMin(T,i)

                          e         ee
Dans cet algorithme, x d´signe l’´l´ment qui descend par tamisage dans l’arbre (14 dans l’exemple
de la figure 7.9). On remarque que x = racine(i) est un invariant de la boucle. La variable f
  e                                                                     e
d´signe celui des fils de i, s’il existe, dont la racine est minimale. L’´change entre les racines de f
         e                                                                               e     e
et i ne n´cessite que deux affectations puisque la valeur de racine(i) est toujours pr´serv´e dans
                                     e       a                                       e
la variable x. Enfin, si x est sup´rieure ` tous les descendant de l’arbre i pass´ en param`tre,   e
                                     a                                  e
elle descendra dans l’arbre jusqu’` devenir une feuille. Il est donc n´cessaire dans la condition de
la boucle, de s’assurer que le fils f retenu existe bien avant d’examiner sa racine en vue de la
           a                                    a
comparer ` x. Cela se fait en comparant f ` la taille n du tas.

La recherche du fils de i racine minimale peut se faire comme suit :

filsMin(T,i)
f=2*i
si (f<n et racine(f)>racine(f+1))
    f ← f+1

Mise en œuvre en Java
                                     e                           e
     Il est maintenant possible de d´finir une classe Java appel´e T as, permettant de manipuler
                          e
cette structure de donn´es. Elle comprend deux champs : un tableau d’entiers T , zone m´moire  e
       a                    e                                        ea
mise ` disposition pour m´moriser l’arbre et un entier taille destin´ ` recevoir la taille effective de
                               e                   e             e
l’arbre (le tableau n’est pas n´cessairement compl`tement utilis´). Elle comprend un constructeur
   e                                  e            e                e
cr´ant un arbre vide, le champ T ´tant initialis´ par un param`tre du constructeur. Un autre
                 e                                   e           e
constructeur cr´e un arbre dont la taille est indiqu´e en param`tre.
public class Tas{

      private int taille;
      private int [] T;

      Tas(int [] tab){
          taille = 0;
          T = tab;
      }

      Tas(int [] tab, int n){
          taille = n;;
          T = tab;
      }

      boolean vide() {return(taille==0);}
      int getTaille() {return taille;}
}
               e         e                              e         e
On peut alors d´finir la m´thode tamiserVersLeBas et en d´duire imm´diatement celle qui extrait
le minimum du tas.
                                                  ´
7.5. ARBRES BINAIRES PARFAITS PARTIELLEMENT ORDONNES (TAS)                                       127

   void tamiserVersLeBas(int i){
      int x=T[i],f=2*i;

        if(f<taille && T[f]>T[f+1]) f++;
        while (f<=taille && x>T[f]){
           T[i] = T[f]; T[f] = x;
           i = f; f=2*i;
           if(f<taille && T[f]>T[f+1]) f++;
        }
    }

   int extraireMin(){
      int r = T[1];
      T[1]=T[taille--];
      tamiserVersLeBas(1);
      return r;
   }

Clairement, les algorithmes d’infiltration et d’extraction du minimum sont en Θ(log(n)) dans le
                                  e                     e   a
pire des cas, puisque le nombre d’´changes est au plus ´gal ` la profondeur de l’arbre.

7.5.4    Transformation d’une liste en tas
                                                                  e             e                 c
    Une liste contenue dans la partie T [1..n] d’un tableau peut ˆtre transform´e en tas de la fa¸on
                                                                                     ea
suivante. On remarque tout d’abord que si les deux fils d’un arbre binaire sont d´j` organis´s ene
                                      e     a
tas, transformer l’arbre en tas se r´duit ` placer correctement sa racine par un tamisage vers le
         e
bas. L’id´e est alors de transformer successivement tous les sous-arbres en tas en partant des plus
                   e e                                a
profonds. Plus pr´cis´ment, on parcourt de droite ` gauche tous les niveaux, depuis celui qui est
                                     a                                     e
juste au dessus des feuilles, jusqu’` celui portant la racine. A chaque it´ration, le nœud courant
                                                    ea                            e
est la racine d’un sous-arbre dont les fils sont d´j` des tas. Il est alors lui-mˆme transform´ ene
                                                                          e e        e
tas par tamisage vers le bas de sa racine. Le premier sous-arbre consid´r´ est le p`re de la feuille
        a                                                       e e     e       a
la plus ` droite, soit celui d’indice i = taille/2. Puis i est d´cr´ment´ jusqu’` 1, ce qui assure le
parcours des niveaux comme indiqu´.    e

   void mettreEnTas(){
      for(int i=taille/2; i>0; i--)
         tamiserVersLeBas(i);
   }

         e
Complexit´ de la transformation
                           e                                    e                      e e       a
    Si l’arbre transform´ en tas a pour profondeur k, il poss`de k + 1 niveaux, num´rot´s de 0 ` k.
Pour tout i ∈ {0, . . . k − 1}, le niveau i poss`de 2i nœuds, le dernier niveau en poss´dant au moins
                                                e                                      e
1 et au plus 2k . La taille n de l’arbre est donc telle que
                     k−1                  k
                1+         2i ≤ n ≤           2i   c’est-`-dire
                                                         a          2k ≤ n ≤ 2k+1 − 1
                     i=0              i=0

Pour tout i ∈ {0, . . . k − 1}, chacun des 2i nœuds du niveau i subit un tamisage vers le bas qui
requiert au plus k − i ´changes. On en d´duit que dans le pire des cas, le nombre d’´changes
                          e                  e                                          e
       e
effectu´s pour transformer une liste de taille n en tas est :
                                    k−1                  k                  k
                                                                                 1
                           C(n) =         (k − i)2i =         i2k−i = 2k       i( )i
                                    i=0                 i=1                i=1
                                                                                 2
128                                                        CHAPITRE 7. STRUCTURES ARBORESCENTES

On rappelle que :
                                                            k
                                                                        1 − xk+1
                                              ∀x = 1,            xi =
                                                           i=0
                                                                          1−x

                      e
On obtient alors par d´rivation :

                          k
                                            kxk+1 − (1 + k)xk + 1      1
                               ixi−1 =                   2
                                                                  <                            si x < 1
                      i=1
                                                  (1 − x)           (x − 1)2

                                                             k                                     k
                                                                             x                       1
et par suite, en multipliant par x : ∀x < 1,                      ixi <            et donc         i( )i < 2. Comme de
                                                            i=1
                                                                          (x − 1)2             i=1
                                                                                                     2
plus 2k ≤ n, on obtient C(n) ≤ 2n.

Il est par ailleurs facile de s’apercevoir que :

                                                  k
                                                        1          n
                                   C(n) = 2k          i( )i ≥ 2k >                 d`s que k ≥ 2
                                                                                    e
                                                  i=1
                                                        2          2

On d´duit de cette d´monstration que C(n) ∈ Θ(n).
    e               e

                                     e
Proposition 7.19 La complexit´ en temps de l’algorithme de transformation d’une liste en tas
       e
est lin´aire en la taille de la liste.



7.5.5                      ee      a
           Adjonction d’un ´l´ment ` un tas
                       ee      a
   L’adjonction d’un ´l´ment ` un tas est duale de l’extraction du minimum. Elle consiste `      a
         ee      a                      e                       a                              e e
placer l’´l´ment ` rajouter comme derni`re feuille du tas, puis ` le faire remonter par un proc´d´
                                           a                                               e
de tamisage, pour le placer de telle sorte ` respecter la structure de tas, comme illustr´ sur la
figure 7.10.

                      3                                                                                        3
                                                                    3


            5                      9                        5                                            4              9
                                                                               9
                                                                                                               5
      6           8                                                                            6
                                                      6             4                                              11       10
                              11       10                                 11         10

18        12 14               4                                            8              18           12 14        8
                                             18           12 14


                                                                 ee
                                   Figure 7.10 – Adjonction de l’´l´ment 4 au tas


                    e                    a                        ee
L’algorithme propos´ ci-dessous consiste ` tamiser vers le haut l’´l´ment d’adresse i d’un tas.
Comme pour le tamisage vers le bas, on fait en sorte que racine(i) = x soit un invariant de la
                      e
boucle. Ceci permet l’´change de racine(i) et racine(pere) en deux affectations seulement.
                                                  ´
7.5. ARBRES BINAIRES PARFAITS PARTIELLEMENT ORDONNES (TAS)                                       129

tamiserVersLeHaut(T,i)
x←racine(i)
pere←i/2
tant que (pere>0 et x<racine(pere))
    racine(i)←racine(pere)
    racine(pere)←x
    i←pere
    pere←pere/2
     ee                                                                                   a
Si l’´l´ment x que l’on tamise vers le haut est le minimum du tas, il finit par arriver ` la racine
et la variable pere prend alors la valeur 0. Il faut dans ce cas sortir de la boucle sous peine de
  e                              e      e      e
d´bordement. Ceci explique la n´cessit´ de v´rifier que pere > 0 dans la condition de la boucle.
Toutefois, on remarque que l’on peut utiliser la case d’indice 0 du tableau pour y placer x en
                                                                            a
sentinelle. Ceci assure dans tous les cas la sortie de la boucle sans avoir ` tester la variable pere
` chaque it´ration. On obtient ainsi la m´thode Java tamiserV ersLeHaut utilis´e en suite par la
a           e                             e                                        e
   e
m´thode adjonction.

    void tamiserVersLeHaut(int i){
       int x= T[i];
       T[0]=x;
       for (int p=i/2; x < T[p]; p/=2){
          T[i] = T[p]; T[p] = x; i = p;
       }
    }

    void adjonction(int x){
       T[++taille] = x;
       tamiserVersLeHaut(taille);
    }

                              e                           u
L’algorithme d’adjonction est ´videmment en Θ(log2 (n)), o` n est la taille du tas.

7.5.6    Application : Tri par Tas
          eae      e                                      a
   On a d´j` ´voqu´ une application des structures de tas ` l’algorithme de Dijkstra, recherchant
un plus court chemin entre deux sommets d’un graphe valu´. e

                              e                                                              a
   Une autre application tr`s connue est celle d’un algorithme efficace de tri. La liste ` trier est
                                  e    e a
une liste d’entiers de taille n m´moris´e, ` partir de la case 1, dans un tableau T de taille n + 1.
L’algorithme consiste :
                                a
   – dans un premier temps, ` transformer la liste en tas,
   – puis ` supprimer le minimum du tas (celui-ci n’occupe plus alors que T [1 .. n − 1]), recopier
           a
      ce minimum en T [n] et recommencer l’op´ration sur T [1 .. n − 2], T [1 .. n − 3], . . .
                                                 e

                            e             e
La liste se trouve ainsi tri´e par ordre d´croissant. On obtient ainsi le programme Java :

class triParTas{

    static void tri(int [] T){
       Tas H = new Tas(T, T.length-1);
       H.mettreEnTas();
       for (int i = T.length-1; i>=1; i--)
          T[i] = H.extraireMin();
     }
}
130                                            CHAPITRE 7. STRUCTURES ARBORESCENTES

             u               e
Il est bien sˆr possible de r´aliser un tri par ordre croissant, il suffit alors de prendre l’ordre inverse
                e e                                    e     a
de celui consid´r´ (tout nœud du tas est ainsi sup´rieur ` ses fils).

         e
Complexit´
                                                                                   e
    L’algorithme du tri par tas commence par la mise en tas dont on a montr´ qu’elle est lin´aire   e
en la taille de la liste. Puis il effectue n extractions successives sur des tas dont la taille i varie de
  a
n ` 2. On a vu que chacune de ces extractions est en log(i) dans le pire des cas. Par suite le coˆ t   u
de la boucle for est
                                        n
                                             log(i) ∈ Θ(nlog(n))
                                       i=2

                         e
La mise en tas est donc n´gligeable devant la somme des extractions successives.

Proposition 7.20 Le tri par tas est en nlog(n).


7.6      Les arbres quelconques
                                                                                 e
   Un arbre est dit quelconque si le nombre des fils de ses nœuds n’est pas born´ a priori : c’est
                               e e                        e
par exemple le cas des arbres g´n´alogiques, comme illustr´ par la figure 7.11 ci-dessous.

                                                  Albert


                      Bernard           Charles                    Denis


                 Edouard Fernand Hubert Ivan Jean Louis            Martin      Norbert


                           Gontran                               Didier Paul


                                                                      e e
               Figure 7.11 – Un exemple d’arbre quelconque : l’arbre g´n´alogique


                                 e      e   e                   e    e
Le nombre des fils d’un nœud ´tant ind´termin´, ceux-ci sont repr´sent´s sous forme de liste. Une
                              e     e
telle liste d’arbres est appel´e forˆt.

                                  e             e                       e
Dans la pratique, arbres et forˆts sont cod´s en machine par un mˆme type d’objets, un arbre
e            e e                  e e         a            ee                   e
´tant consid´r´ comme une forˆt r´duite ` un unique ´l´ment. Arbres et forˆts, s’il ne sont pas
                        e     e                          e                       ee               e
vides, seront ainsi repr´sent´s par un triplet constitu´ de la racine du premier ´l´ment de la forˆt,
         e                         e           e
de la forˆt de ses fils et de la forˆt de ses fr`res suivants.

                                     e       e
Ainsi, l’arbre de la figure 7.11 peut ˆtre cod´ comme suit :



7.6.1     Description abstraite
Definition 5 Un arbre quelconque sur un ensemble D est :
            ee                        e
  – soit un ´l´ment particulier appel´ arbre vide
                                    u
  – soit un couple a = (r, fa, fs) o`
               ee                   e
    – r est un ´l´ment de D appel´ racine de l’arbre
                                        e                          ın´
    – fa est un arbre quelconque appel´, s’il n’est pas vide, fils aˆ e de a
7.6. LES ARBRES QUELCONQUES                                                                       131


        E               null
               Albert

  a
                          c
                               E                      E                 null
              Bernard                 Charles                   Denis


                         c                       c                       c
                        ...                     ...                     ...




                                        e                         e
      – fs est un arbre quelconque appel´, s’il n’est pas vide, fr`re suivant de a

                                e                                     e
   – Si fs n’est pas vide, un fr`re de a est soit fs, soit l’un des fr`res de ce dernier.
   – Si fa est vide, on dit que l’arbre a est une feuille. Dans le cas contraire, un fils de a est soit
               ı e                  e
     son fils aˆn´, soit l’un des fr`res de ce dernier.

   – Si a n’est pas vide, on appelle nœud de a
     – soit sa racine
     – soit un nœud de l’un de ses fils.

   – Un sous-arbre de a est :
                   e
     – soit a lui-mˆme
     – soit un sous-arbre de l’un de ses fils, s’il en existe.

Les primitives
                                e                               e
   Soit D un ensemble. Dans la d´finition qui suit, a, fa et fs d´signent des arbres quelconques
               ee
sur D, et r un ´l´ment de D.

                                                                  ın´          e
   nouvel-arbre(r, fa, fs) : renvoie l’arbre racine r, de fils aˆ e fa et de fr`re suivant fs.
   arbreVide : renvoie l’arbre vide.
                                       e
   vide(a) : renvoie une valeur bool´enne indiquant si a est l’arbre vide ou non.
                       e
   racine(a) : n’est d´fini que si a n’est pas vide. Renvoie la racine de a.
                  e                                              ın´
   fa(a) : n’est d´fini que si a n’est pas vide. Renvoie le fils aˆ e de a.
                  e                                          e
   fs(a) : n’est d´fini que si a n’est pas vide. Renvoie le fr`re suivant de a.
   feuille(a) : renvoie vrai si a est une feuille.

7.6.2    Parcours d’arbres quelconques
             e
   On consid`re deux type de parcours :
                                                           a
   – le parcours d’un arbre proprement dit, qui consiste ` visiter chacun de ses nœuds
                          e                                                            e
   – le parcours d’une forˆt, qui consiste parcourir l’arbre ainsi que chacun de ses fr`res.

Parcours d’un arbre
                                             e e
    Les parcours des arbres quelconques g´n´ralisent ceux des arbres binaires. On donne ici le
    e              e e
sch´ma le plus g´n´ral d’un parcours gauche-droite en profondeur d’abord, qui consiste, comme
       e                       e                                       e       a
illustr´ par la figure 7.12, apr`s avoir atteint un nœud pour la premi`re fois, ` visiter les nœuds
                                      a
de chacun de ses fils, pris de gauche ` droite, avant d’y revenir une seconde fois en remontant.
                                                          a
Chacun des deux passages sur le nœud peut donner lieu ` un traitement sur ce nœud. On obtient
                 e
ainsi une premi`re version du parcours :
132                                              CHAPITRE 7. STRUCTURES ARBORESCENTES


                                                     
                                             1            2

                                         C


                                          parcours des fils
                                                              E

      Figure 7.12 – Les visites d’un nœud lors d’un parcours gauche-droite en profondeur



parcours arbre(a)
si non vide(a)
    traitement 1
    a ← fa(a)                                                       ı e
                                            // descente sur le fils aˆn´
    tant que non vide(a) faire                             ee
                                           // parcours des ´l´ments de la liste des fils
       parcours arbre(a)
       a ← fs(a)
    traitement 2
On remarque que le test vide(a) se fait deux fois sur tout sous-arbre propre de l’arbre initial : une
      e               e                                                             e
premi`re fois dans l’´valuation de la condition de la boucle tant que puis au d´but du parcours.
      e                                   e                                  e
Pour ´viter cette redondance, on peut d´cider de supprimer le test en d´but de parcours, sachant
                                a
que celui-ci ne s’appliquera qu’` des arbres non vides. On obtient ainsi l’algorithme de la figure 7.13.

                                   parcours arbre(a)
                                   traitement 1
                                   a ← fa(a)
                                   tant que non vide(a) faire
                                       parcours arbre(a)
                                       a ← fs(a)
                                   traitement 2



                                               e
                       Figure 7.13 – Parcours r´cursif d’un arbre non vide

                                      e                     e                                 e
Le parcours de l’arbre est dit en pr´-ordre ou en ordre pr´fixe lorsque tout nœud est trait´ avant
                       a
ses descendants, c’est-`-dire s’il ne subit que le traitement 1. Il est dit en post-ordre ou en ordre
                                      e e                           a
postfixe lorsque tout nœud est trait´ pr`s ses descendants, c’est-`-dire s’il ne subit que le traite-
ment 2.

                          a
Si le traitement consiste ` afficher la racine de l’arbre, les deux parcours de l’arbre de la figure
                    e
7.11 donneront les r´sultats suivants :

        e
Ordre pr´fixe : Albert Bernard Edouard Fernand Gontran Charles Hubert Ivan Denis Jean
Louis Martin Olivier Paul Norbert

Ordre postfixe : Edouard Gontran Fernand Bernard Hubert Ivan Charles Jean Louis Olivier
Paul Martin Norbert Denis Albert
7.6. LES ARBRES QUELCONQUES                                                                       133


               prefixe arbre(a)                         postfixe arbre(a)
               traitement
               a ← fa(a)                                a ← fa(a)
               tant que non vide(a) faire               tant que non vide(a) faire
                   prefixe arbre(a)                         postfixe arbre(a)
                   a ← fs(a)                                a ← fs(a)
                                                        traitement



          Figure 7.14 – Les deux ordres de parcours des arbres quelconques non vides


               e
Parcours de forˆt
                           e                       a                        e        e
Une autre solution, propos´e figure 7.15, consiste ` parcourir toute une forˆt : apr`s un premier
                                            e                                  e            a
traitement de l’arbre courant, on rappelle r´cursivement le parcours sur la forˆt des fils ; ` la re-
      e                                                            e           e
mont´e, l’arbre subit un second traitement avant parcours de la forˆt de ses fr`res suivants.


                                               e
                                   parcours for^t(a)
                                   si non vide(a)
                                       traitement 1
                                                   e
                                       parcours for^t(fa(a))
                                       traitement 2
                                       parcours for^t(fs(a))
                                                   e



                                                   e                e
                           Figure 7.15 – Parcours r´cursif d’une forˆt


                                           e           e                 e
Comme c’est la cas pour les arbres, les forˆts peuvent ˆtre parcourues pr´-ordre ou en post-ordre,
             e          e
selon les sch´mas indiqu´s sur la figure 7.16


                            e
                 prefixe for^t(a)                      postfixe for^t(a)
                                                                   e
                 si non vide(a)                        si non vide(a)
                     traitement                                        e
                                                           postfixe for^t(fa(a))
                                e
                     prefixe for^t(fa(a))                  traitement
                                e
                     prefixe for^t(fs(a))                              e
                                                           postfixe for^t(fs(a))



                                                                       e
                      Figure 7.16 – Les deux ordres de parcours des forˆts

                                                                                      e
Cette solution peut s’utiliser pour parcourir un seul arbre, si celui-ci n’a pas de fr`re suivant : le
               e                                                                             a
parcours des fr`res est alors l’instruction vide. Ainsi, ces algorithmes peuvent s’appliquer ` l’arbre
                                     e      e                     e
de la figure 7.11, et donnent les mˆmes r´sultats que la premi`re version des parcours.

                                           e
Application : calcul de permutations et forˆt virtuelle
                               a                                                       ee
    On se propose d’afficher, ` raison de une par ligne, chacune des n! permutations des ´l´ments de
l’ensemble In = {0, . . . , n − 1}. Ce probl`me peut se r´soudre en parcourant une forˆt compos´e
                                            e            e                            e         e
134                                           CHAPITRE 7. STRUCTURES ARBORESCENTES

de n arbres n-aires de profondeur n − 1, dont les racines respectives sont les ´l´ments de In . Tout
                                                                                  ee
                                       e a                         e
nœud qui n’est pas une feuille poss`de ` son tour n fils, rang´s par ordre croissant, chacun ´tant e
    ee                                        ee                                         e e
un ´l´ment de In . Les permutations des ´l´ments de In sont les chemins sans r´p´tition d’une
        a                   e                                    e
racine ` une feuille. L’id´e est donc de parcourir cette forˆt en profondeur, en abandonnant la
            e                          e e              e
descente d`s que l’on trouve une r´p´tition. On m´morise dans un tableau T de longueur n les
                                    a
nœuds sur lesquels on descend, ` condition qu’ils soient des candidats acceptables pour obtenir
                          a                          e      a               e
une permutation, c’est-`-dire qu’ils ne soient pas ´gaux ` un de leur ancˆtres. On sait que l’on est
                                                         a
sur une feuille si le tableau est plein, et dans ce cas-l` on l’imprime. On remarque que si un nœud
est ´tiquet´ par a et n’est pas une feuille, son fils aˆ e est 0. De plus, tout nœud a < n − 1 a pour
    e       e                                          ın´
  e
fr`re suivant est a + 1.
              e
Ainsi, la forˆt que l’on parcourt est ici purement virtuelle en ce sens qu’elle n’est pas une structure
         e                                                           ıtre              e
de donn´e du programme : on est toujours en mesure de connaˆ le fils ou le fr`re d’un nœud `           a
                                                            ee          e    e
partir de la valeur de celui-ci et de l’indice du dernier ´l´ment m´moris´ dans le tableau T . Nul
                             e
n’est besoin d’implanter r´ellement la forˆt.e

public class perm{
   static int []T;
   static int n;

      static void afficher(){
         for(int i=0; i<n; i++)
            System.out.print(T[i]+" ");
         System.out.println();
      }

      static boolean acceptable(int a, int i){
         for(int k=0; k<i; k++)
            if(a==T[k]) return false;
         return true;
      }

      static void permutation(int nb){
         n = nb;
         T= new int[n];
         parcours_foret(0,0);
      }

      static void parcours_foret(int a, int i){
         if(a<n){                                 e
                    //T[1..(i-1)] contient les anc^tres de a
            if(acceptable(a, i)){
               T[i]=a;
               if(i==n-1)
                  afficher();
               else
                                                                   ı e
                  parcours_foret(0, i+1); //Descente sur le fils a^n´
            }
            parcours_foret(a+1, i);                          e
                                             //Passage au fr`re suivant
         }
      }

      public static void main(String[] args){
         permutation(Integer.parseInt(args[0]));
      }
}
7.6. LES ARBRES QUELCONQUES                                                                     135

On obtient alors la trace suivante :

$   java perm 3
0   1 2
0   2 1
1   0 2
1   2 0
2   0 1
2   1 0

                                                     e      e
On remarque enfin que l’on peut se passer du param`tre a en m´morisant le nœud courant dans
                                            c
T [i]. On modifie ainsi le programme de la fa¸on suivante :

     static boolean acceptable(int i){ //Le noeud courant est en T[i]
        for(int k=0; k<i; k++)
           if(T[i]==T[k])
              return false;
        return true;
     }

     static void permutation(int nb){
        n = nb;
        T= new int[n];
        T[0]=0; //Le noeud courant est en T[0]
        parcours_foret(0);
     }

     static void parcours_foret(int i){ //Le noeud courant est en T[i]
        if(T[i]<n){
           if(acceptable(i)){
              if(i==n-1)
                 afficher();
              else{
                 T[i+1]=0; //Le fils aine de T[i] en T[i+1]
                 parcours_foret(i+1);
              }
           }
           T[i]++; //Passage au frere suivant
           parcours_foret(i);
        }
     }

7.6.3    Mise en œuvre en Java de la structure d’arbre quelconque
                                                                   e
    Une classe Java mettant en œuvre des arbres quelconques (n´cessairement non vides) dont les
nœuds portent des chaˆ                 e
                        ınes de caract`res, comportera donc trois champs. L’un, de type String
                           e                                      a
contient l’information port´e par la racine, les deux autres sont ` leur tour des arbres quelconques
       e                                 ın´       e
et repr´sentent respectivement le fils aˆ e et le fr`re suivant de this.

public class AQ{

    String nom;
    AQ fa, fs;

    AQ (String n,AQ a, AQ s){
136                                         CHAPITRE 7. STRUCTURES ARBORESCENTES

      nom = n; fa = a; fs = s;
  }
                e                                                                      c
Les primitives d´finies dans la description abstraite sont alors mises en œuvre de la fa¸on suivante.

 nouvel-arbre(r, fa, fs) :     new AQ(r, fa, fs)
 arbreVide               :     null
 vide(a)                 :     a==null
 racine(a)               :     a.nom
 fa(a)                   :     a.fa
 fs(a)                   :     a.fs
 feuille(a)              :     Si a n’est pas vide, a.fa==null

                                                        e
On peut alors calculer la taille d’un arbre ou d’une forˆt en mettant en œuvre les algorithmes
                              e e
de parcours du paragraphe pr´c´dent.
  int taille_arbre(){
    int nb = 1;
    AQ a = fa;
    while(a!=null){
      nb+=a.taille_arbre();
      a=a.fs;
    }
    return(nb);
  }

  static int taille_foret(AQ a){
    if(a==null) return 0;;
    return(taille_foret(a.fa)+taille_foret(a.fs)+1);
  }

  int taille_foret(){
    return(taille_foret(this));
  }

                  e                     e
Exercice 7.1 Compl´ter la classe AQ en r´pondant aux questions suivantes.
                 e                                                      e
  1. Donner les m´thodes renvoyant la profondeur d’un arbre et d’une forˆt.
                 e                                                   e
  2. Donner les m´thodes renvoyant le minimum d’un arbre et d’une forˆt.
                  e                                     e                     e
  3. Donner les m´thodes qui impriment l’arbre et la forˆt en traduisant la hi´rarchie par une
     indentation.
  4. Implanter en Java les files d’attente d’arbres quelconques.
                            e e           e           e
  5. Utiliser la question pr´c´dente pour ´crire une m´thode permettant d’imprimer l’arbre par
     niveaux.
                   e                 e         e         ı            e
  6. Ecrire une m´thode position qui ´tant donn´e une chaˆne de caract`res s, renvoie le sous-
     arbre de racine s s’il existe.
                   e                       ı            e
  7. Ecrire une m´thode qui rajoute une chaˆne de caract`res s1 comme nouveau fils de s si s
     figure dans l’arbre.
                                                   e         e e
  8. Ecrire un programme permettant de tester les m´thodes pr´c´dentes.
Chapitre 8

Les graphes

8.1         e
          Pr´sentation informelle
                                                  e
    Les graphes, ou structures relationnelles, mod´lisent des relations sur des objets. Ces objets
sont les sommets du graphe et le fait qu’un objet s soit en relation avec un objet t se traduit par
l’existence d’une arˆte s — t ou d’un arc s → t, selon que la relation est sym´trique ou cas.
                    e                                                         e

                                              • Lille
                                                                        • Francfort
                                         • Paris                           • Stuttgart
• Brest
      • Nantes




                                              • Lyon


                           • Toulouse
                                                • Marseille
                                                e                      e
                  Figure 8.1 – Graphe non orient´ : carte de liaisons a´riennes



                   e                             e              e
La figure 8.1 repr´sente un graphe non orient´ de liaisons a´riennes : s’il existe une liaison d’un
                                                 e
point s vers un point t, il existe une possibilit´ de retour de t vers s. Il s’agit donc d’une relation
    e                                                       e               e
sym´trique. Les sommets du graphe sont les villes. La pr´sence d’une arˆte s—t traduit l’existence
               e
d’une liaison a´rienne directe entre les villes s et t.
On peut alors se poser un certain nombre de questions comme :
                                               a
   – existe-il un moyen d’aller de la ville s ` la ville t ?
   – quel trajet minimise-t-il le nombre d’escales entre deux villes ?
                                                                e
   – peut-on visiter chaque ville une et une seule fois (probl`me du voyageur de commerce)

                                                   137
138                                                               CHAPITRE 8. LES GRAPHES

                e                                                   a     a
Un autre probl`me classique est celui de l’ordonnancement des tˆches ` effectuer dans un pro-
                                                       e          e e                       e
cessus (industriel, militaire . . .) complexe. Le probl`me est mod´lis´ par un graphe orient´ dont
les sommets sont les tˆches. La pr´sence d’un arc s → t indique que l’ex´cution de s doit ˆtre
                        a              e                                     e                 e
       e                  e
termin´e avant que ne d´bute celle de t.



          c
      Cale¸on                             Chaussettes                        Montre


          c                           j        c
      Pantalons                   E       Chaussures


          c
       Ceinture     '                      Chemise


                                               c
                                           Cravate


                                               c
                                           Veste



                                e                       a           e
      Figure 8.2 – Graphe orient´ : ordonnancement des tˆches pour vˆtir le Pr. Tournesol



                                               e            e            a
Par exemple, le graphe de la figure 8.2 repr´sente la d´pendance des tˆches que doit effectuer le
                                 e                                         a
professeur Tournesol pour se vˆtir. Le traitement d’un tel graphe consiste ` trouver une indexation
     a                            e      e e
des tˆches telles que, si ti doit ˆtre ex´cut´e avant tj , alors i < j.

       e                                      e                                           e e
Consid´rons enfin un ensemble de villes reli´es par des routes. Ces villes sont mod´lis´es par
                                                                             e
les sommets d’un graphe, les routes entre deux villes sont des arcs ou des arˆtes selon qu’elles sont
` sens unique ou pas. A chaque route peut-ˆtre associ´e sa longueur, ce qui revient ` associer `
a                                           e           e                               a           a
                 e                                e
chaque arc ou arˆte du graphe un nombre appel´ valuation. On dit alors que le graphe (orient´       e
                e           e                       a
ou pas) est valu´. Un probl`me classique consiste ` rechercher un plus court chemin entre deux
sommets d’un graphe valu´.e


8.2      Terminologie
                e                                      e
Cette section pr´sente la terminologie usuelle de la th´orie des graphes.

                  e
Graphe non orient´ : c’est un couple (S,A) o`u
                            ee            e
  – S est un ensemble fini d’´l´ments appel´s sommets
                                                    e     e
  – A est un ensemble fini de paires de sommets appel´es arˆtes.

L’arˆte {s, t} est not´e s —t. Les sommets s et t sont les extr´mit´s de l’arˆte.
    e                 e                                        e   e         e
8.2. TERMINOLOGIE                                                                                    139

              e
Graphe orient´ : c’est un couple (S,A) o`u
                            ee            e
  – S est un ensemble fini d’´l´ments appel´s sommets
                                                               e
  – A est un ensemble fini de couples de sommets distincts appel´es arcs.

L’arc (s,t) est not´ s → t. Le sommet s est l’extr´mit´ initiale (tail en anglais) et t est l’extr´mit´
                   e                              e   e                                           e   e
                                                                                             e e
terminale (head en anglais). On dit aussi que t est un successeur de s et que s est un pr´d´cesseur
de t.

            e                    e e   e      e                            e
Graphe valu´ (on dit encore pond´r´ ou ´tiquet´ ). C’est un couple constitu´
                            e
  – d’un graphe (S,A) orient´ ou pas
  – d’une fonction C : A → I appel´e valuation ou fonction de coˆt ou encore fonction de poids.
                           R       e                            u

         e    e                                  e             e
On consid`re d´sormais un graphe G= (S,A), orient´ ou pas, valu´ ou pas.

                           e
Sous-graphe de G engendr´ par un sous-ensemble de sommets S’ : c’est le graphe dont les
                 ee                                   e                           e   e
sommets sont les ´l´ments de S’ et dont les arcs ou arˆtes sont ceux dont les extr´mit´s sont
dans S’.

                           e
Graphe partiel de G engendr´ par un sous-ensemble A’ de A : c’est le graphe G’=(S, A’).
Exemple 8.1
  1                     2                                 2                 1                    2



             3                                   3                                   3



             4                                   4                                   4

          Graphe                            Sous-graphe                         Graphe partiel
Adjacence, incidence
                     e                                          e   e
  – Deux arcs ou arˆtes sont dits adjacents s’ils ont une extr´mit´ commune.
                                e                                                   e        e
  – Dans un graphe non orient´, deux sommets sont dits adjacents s’ils sont reli´s par une arˆte.
                              e                 a
    On dit alors que cette arˆte est incidente ` ces sommets.
  – Dans un graphe orient´, un sommet s est dit adjacent ` un sommet t, s’il existe un arc s → t.
                           e                                a
                             a             e                  a             e
    Cet arc est dit incident ` s vers l’ext´rieur et incident ` t vers l’int´rieur.

    e
Degr´s, demi-degr´se
                               e        e                                      e           e
  – Dans un graphe non orient´, le degr´ d’un sommet s est le nombre d’arˆtes d’extr´mit´s s.   e
  – Dans un graphe orient´, on d´finit le demi-degr´ int´rieur d − (s) (respectivement ext´rieur
                          e      e                    e     e       ˚                          e
    d + (s)) comme le nombre d’arcs incidents ` s vers l’int´rieur (respectivement vers l’ext´rieur).
     ˚                                        a             e                                e
    Le degr´ de s est d´fini par d (s) = d + (s) + d − (s).
             e         e         ˚       ˚         ˚

Exemple 8.2         1                   2
                                    
                                                          d + (3)
                                                           ˚        =   2
                            ‚ ©
                             3                            d − (3)
                                                           ˚        =   3
                                                           ˚
                                                          d (3)     =   5
                               T
                              c
                              4
140                                                                  CHAPITRE 8. LES GRAPHES

   ınes et chemins
Chaˆ
   – Un chemin (respectivement une chaˆne) de longueur k ≥ 0 est une suite de sommets
                                                   ı
     s0 , s1 , . . . sk telle que pour tout i, 0 ≤ i ≤ k − 1, il existe un arc si → si+1 (respective-
                        e
     ment une arˆte si — si+1 ).
   – Un chemin (ou une chaˆ                                          e     a          ee
                                   ıne) de longueur nulle est donc r´duit ` un seul ´l´ment.
   – Si s0 = sk et k ≥ 2, le chemin (respectivement la chaˆ                     e
                                                                  ıne) est appel´ circuit (respectivement
     cycle).
   – Un chemin (ou une chaˆ                      ee                                                   e
                                    ıne) est dit ´l´mentaire s’il ne contient pas plusieurs fois le mˆme
                          a                          e   e
     sommet, mis ` part le premier qui peut ˆtre ´gal au dernier

Descendants et ascendants
   – L’ensemble des descendants d’un sommet s est l’ensemble des sommets t tels qu’il existe un
     chemin (ou une chaˆ          a
                        ıne) de s ` t.
   – L’ensemble des ascendants d’un sommet s est l’ensemble des sommets t tels qu’il existe un
     chemin (ou une chaˆ          a
                        ıne) de t ` s.
                      e
Dans le cas non orient´, les deux ensembles sont confondus.

        e
Connexit´s
                           e                                                                ıne
   – Un graphe non orient´ est dit connexe si pour toute paire de sommets, il existe une chaˆ
     qui les relie.
                                                                                          a
   – On appelle composante connexe d’un graphe tout sous-graphe connexe maximal (c’est-`-dire
     qui ne soit pas sous-graphe d’un autre sous-graphe connexe).
                       e
   – Un graphe orient´ est dit fortement connexe si pour tout couple de sommets, il existe un
     chemin qui les relie.
                                                                   e
   – On appelle composante fortement connexe d’un graphe orient´ tout sous-graphe fortement
                             a
     connexe maximal (c’est-`-dire qui ne soit pas sous-graphe d’un autre sous-graphe fortement
     connexe).

Sur l’exemple 8.1, le graphe et le sous-graphe sont connexes, le graphe partiel ne l’est pas et
comporte deux composantes connexes, sous-graphes respectifs engendr´s par {1} et {2, 3, 4}.
                                                                      e
Le graphe de l’exemple 8.2 n’est pas fortement connexe. Il comporte deux composantes fortement
connexes, sous-graphes respectifs engendr´s par {1}, {2, 3, 4}.
                                          e



8.3     Parcours de graphes
            e e
On peut en g´n´ral envisager deux types de parcours de graphe.

                                                         e
Le parcours en profondeur d’abord On consid`re, dans un premier temps, un algorithme
                                                                a                          e a
d’exploration en profondeur (depth first search). Il consiste, ` partir d’un sommet donn´, ` suivre
un chemin (ou chaˆ                                   a
                   ıne) le plus loin possible, jusqu’` atteindre un sommet dont tous les successeurs,
                  ea e e       e         `                           e
s’il y en a, ont d´j` ´t´ visit´s, puis a faire des retours en arri`re pour emprunter des chemins
      e                                                    e
ignor´s jusqu’alors. Cet algorithme est naturellement r´cursif.
On remarque de plus qu’il est possible que l’algorithme n’atteigne pas tous les sommets (ceci est
e                                                                             e      e
´vident quand il y a plusieurs composantes connexes). Pour parcourir l’int´gralit´ du graphe, il
convient donc de rappeler cet algorithme tant qu’il reste des sommets non visit´s.e

                                       `                                  e          e
Le parcours en largeur Il consiste a parcourir le graphe par niveaux : ´tant donn´ un sommet,
                                                                                a
on visite d’abord tous les sommets qui lui sont adjacents, puis les successeurs ` ces derniers, etc.
  e e
G´n´ralisant le parcours par niveaux des arbres quelconques (cf. exercice 7.1, section 7.6.3), ce
                                                  e          e
parcours utilise des files d’attente et est intrins`quement it´ratif.
8.3. PARCOURS DE GRAPHES                                                                         141

8.3.1    Parcours en profondeur
             e                  e                                             eae e        e
    Le probl`me est de pouvoir d´cider, en cours de parcours, si un sommet a d´j` ´t´ visit´. Pour
                 a                                  e
cela, on associe ` chaque sommet une marque bool´enne. Initialement toutes les marques ont la
valeur faux.


     parcours profondeur(G)                                 dfs(s)
     pour tout sommet s de G
         marque(s) ← faux                                   marque(s) ← vrai
     pour tout sommet s de G                                pour tout sommet t adjacent ` s
                                                                                        a
         si non marque(s)                                       si non marque(t)
            dfs(s)                                                 dfs(t)



                   Figure 8.3 – Parcours d’un graphe en profondeur d’abord

               e                    e
Comme indiqu´ sur la figure 8.3, on d´finit un premier algorithme d’exploration en profondeur dfs
     a                               e
qui, ` partir d’un sommet non marqu´ s, visite tous les descendants de s qui ne sont pas encore
       e
marqu´s.
                               a                          a                            e
Le parcours du graphe consiste ` appeler l’algorithme dfs ` partir de sommets non marqu´s et ce,
tant qu’il en existe.


                    6 '                         0           E 4 '                  7
                                                    ‰


                     %
                     c                    jc                   %                    c
                    5 '                    2                E 1 '                  3



                                                                                    c
                                                                                   8


                                                                  e
                             Figure 8.4 – Exemple de graphe orient´

Supposons que l’on parcoure les sommets du graphe de la figure 8.4 par ordre croissant. On visite
les sommets dans l’ordre suivant :

              0      2       1            5             4   6    3             8       7        (8.1)
                                                                                   df s(7)
                            df s(2)                                  df s(3)

                                      df s(0)

                           e                     a                                      e
On a fait figurer sur le sch´ma les divers appels ` l’algorithme dfs et les sommets visit´s par chacun
d’eux.

                  e                                                e
Parcours en pr´-ordre et en post-ordre Les parcours pr´fixe et postfixe des arbres se
 e e                                                 e                                          e
g´n´ralisent aux graphes, selon que les sommets visit´s par le parcours en profondeur sont trait´s
             e
avant ou apr`s leur descendants (cf. figure 8.5).
142                                                                    CHAPITRE 8. LES GRAPHES

                                                      a                             e
En admettant que le traitement d’un sommet consiste ` l’afficher, le parcours en pr´-ordre aura
      e
pour r´sultat l’affichage (8.3). En revanche l’affichage en post-ordre produit la ligne ci-dessous.

              1       5             2             4   6            0   8             3     7       (8.2)
                                                                                         df s(7)
                          df s(2)                                          df s(3)

                                        df s(0)




      dfs prefixe(s)                                           dfs postfixe(s)

      marque(s) ← vrai                                         marque(s) ← vrai
      traiter(s)                                               pour tout sommet t adjacent ` s
                                                                                           a
      pour tout sommet t adjacent ` s
                                  a                                si non marque(t)
          si non marque(t)                                            dfs postfixe(t)
             dfs prefixe(t)                                    traiter(s)



                                                      e
            Figure 8.5 – Explorations en profondeur pr´fixe et postfixe d’un graphe



8.3.2    Parcours en largeur
    L’algorithme de base est une exploration en largeur (breadth first search en anglais) dont le
                   a                                        e
principe consiste ` visiter tous les successeurs non marqu´s du sommet courant s avant de vi-
                                                                                         e     e
siter les autres descendants. L’algorithme utilise une file d’attente dans laquelle sont m´moris´s
                                                                                      e
les sommets adjacents au sommet courant pendant que l’on visite les sommets de mˆme niveau.
                                             e
Lorsque la visite de ces sommets est termin´e (on ne peut aller plus avant en largeur), on retire
un sommet en attente pour visiter ses successeurs non encore marqu´s. e
                                e e                                           e          e e
Cet algorithme ne marque en g´n´ral pas tous les sommets du graphe et doit ˆtre rappel´ it´rati-
vement par l’algorithme de parcours en largeur tant qu’il subsiste des sommets non marqu´s.e


      parcours largeur(G)                                 bfs(s)
      pour tout sommet s de G                             f← creerVide()
         marque(s) ← faux                                 enfiler(s,f)
      pour tout sommet s de G                             marque(s) ←vrai
         si non marque(s)                                 repeter
            bfs(s)                                            s←defiler(f)
                                                                                          a
                                                              pour tout sommet t adjacent ` s
                                                                 si non marque(t)
                                                                    marque(t)←vrai
                                                                    enfiler(t,f)
                                                          tant que non vide(f)


                           Figure 8.6 – Parcours d’un graphe en largeur

              e
L’algorithme d´crit sur la figure 8.6 fait intervenir les primitives sur les files, introduites dans la
section 6.3.
         ´
8.4. REPRESENTATIONS DES GRAPHES                                                                            143

Sur l’exemple de la figure 8.4, on obtient l’ordre de parcours suivant :

                                0     2     4      5   6     1      3     8         7                      (8.3)
                                             bf s(0)                bf s(3)       bf s(7)



8.4              e
             Repr´sentations des graphes
                       e                                                        e
    Il existe deux repr´sentations classiques des graphes, selon que l’on privil´gie les sommets ou
                e
les arcs (ou arˆtes).


8.4.1        Les matrices d’adjacence
                        e                                              e e                      e    e
    Dans ce type de repr´sentation, ce sont les sommets qui sont privil´gi´s. Le graphe est repr´sent´
                      e       e                                                    e
par une matrice carr´e index´e sur les sommets. Les coefficients sont des bool´ens qui indiquent
                                  e                                                      e
l’existence d’un arc ou d’une arˆte reliant les sommets en indice. La matrice est ´videmment
     e
sym´trique quand le graphe n’est pas orient´.  e

Exemple 8.3
    '
  1       E2                                                                        1       2    3    4
                                           e    e
                                    est repr´sent´ par la matrice             1     F       V    V    F
                                                                              2     V       F    F    V
     c                  c                                                     3     F       V    F    F
     3                 4                                                      4     F       F    F    F



     1                 2                                                            1       2    3    4
                                            e    e
                                    est repr´sent´ par la matrice             1     F       V    V    F
                                                                              2     V       F    V    V
     1                                                                        3     V       V    F    F
     3                 4                                                      4     F       V    F    F


                               e                                                             e
Dans le cas d’un graphe valu´, les coefficients de la matrice sont les poids des arcs ou des arˆtes.
                                          e
Il faut alors choisir une valeur particuli`re qui exprimera par convention l’absence de connexion
entre deux sommets. Par exemple :

         '   25
     1             E2                                                                1      2    3     4
             43                         e        e
                                    peut ˆtre repr´sent´ par la matrice
                                                       e                      1     -1      25   15   -1
15                12       10                                                 2     43      -1   -1   10
     c                  c                                                     3     -1      12   -1   -1
     3                 4                                                      4     -1      -1   -1   -1



          e            e
Cette repr´sentation pr´sente deux avantages :
             e
     1. l’acc`s aux arcs se fait en temps constant
             e        e e                              e                                        e
     2. l’acc`s aux pr´d´cesseurs d’un sommet s est ais´ : il suffit de parcourir la colonne index´e
        par s.

       e                 e
Elle pr´sente deux inconv´nients :
144                                                            CHAPITRE 8. LES GRAPHES

  1. l’encombrement de la repr´sentation est maximal. Il est en Θ(n2 ) quel que soit le nombre
                                   e
     d’arcs. Si celui-ci est faible par rapport au nombre de sommets, la matrice d’adjacence est
                                               e     a
     creuse : la plupart des coefficients sont ´gaux ` F .
                                                a                   e         e
  2. le parcours des sommets adjacents ` un sommet donn´ est en Θ(n) mˆme si ce som-
                  e
     met poss`de peu de sommets adjacents. Il consiste en effet, si l’ensemble des sommets est
     {1, . . . , n} et si G d´signe la matrice, ` effectuer la boucle
                             e                  a

                                  `
              Pour t variant de 1 a n
                 si G[s,t]
                    traiter(t)

    u
Coˆ t des parcours Les parcours visitent chacun des n sommets une et une seule fois. Chaque
                              e
fois qu’un sommet s est visit´, l’algorithme effectue un parcours de tous les autres sommets pour
examiner s’ils lui sont adjacents et examiner leur marque. Les parcours sont donc en Θ(n2 ).

Mise en oeuvre en Java
                                         a                                                e
   Voici une mise en oeuvre des graphes ` l’aide de matrices d’adjacence comportant une m´thode
                                     e                                      e
qui affiche les sommets en ordre pr´fixe. Le constructeur prend en param`tre le nombre n de
                                                                      a                 e
sommets et initialise tous les coefficients de la matrice d’adjacence ` faux. Cette derni`re sera
             a                            a     e
ensuite mise ` jour par appels successifs ` la m´thode ajoute arc.

  class Graph{

      boolean [][] arc;
      private int ns;
      private boolean [] marque;

      Graph (int n){
           ns=n;
           arc = new boolean[ns][ns];
           for(int s=0; s<ns; s++)
              for(int t=0; t<ns; t++)
                 arc[s][t]=false;
           marque = new boolean[ns];
       }

      void ajoute_arc(int s, int t)
           arc[s][t]=true;
      }

      void dfs_prefixe (int s){
         marque[s] = true;
         System.out.print(s + " ");
         for(int t=0; t<ns; t++)
            if (arc[s][t])
               if (!marque[t])
                  dfs_prefixe(t);
       }

      void affichage_prefixe (){
          int s;

          for(s=0; s<ns;s++) marque[s]=false;
         ´
8.4. REPRESENTATIONS DES GRAPHES                                                              145

            for(s=0; s<ns;s++)
                if (!marque[s])
                    dfs_prefixe(s);
        }
}

Voici un exemple de programme permettant de tester l’affichage. Le premier argument du pro-
                                                     e                     e
gramme est le nombre de sommets. Chaque arc ou arˆte de s vers t est indiqu´ en donnant les
                                e
entiers s et t en arguments cons´cutif du programme.


public class ProgGraph{

    public static void main (String [] args){
       if(args.length>=1){
          Graph g = new Graph(Integer.parseInt(args[0]));
          for(int i=1; i<args.length; i+=2)
          g.ajoute_arc(Integer.parseInt(args[i]), Integer.parseInt(args[i+1]));
          g.affichage_prefixe();
    }
}


Avec la commande

java ProgGraph 9 0 6 0 5 0 2 1 0 2 5 3 8 3 1 6 5 6 2 7 4 7 3 7 1

On obtient : 0 2 5 6 1 3 8 4 7

                                                              e
Exercice 8.1 Programmer le parcours en largeur avec cette repr´sentation.

8.4.2       Les listes d’adjacence
                                a          a
   L’autre alternative consiste ` associer ` chaque sommet la liste des sommets qui lui sont adja-
cents. Par exemple,


    1            2
                                   e    e
                     est alors repr´sent´ par   1    E      2    E    3

                                                2    E      1    E    3        E    4

                                                3    E      1    E    2
    3            4                              4    E      2


                         e                      e                    e
Dans la pratique, la repr´sentation est constitu´e d’un tableau index´ sur les sommets du graphe,
dont chaque cellule porte la liste des sommets adjacents au sommet qui l’indexe.

                           e
Les avantages de cette repr´sentation sont les suivants :
                                            u
    1. l’encombrement est en Θ(n + p) o` n est le nombre de sommets et p le nombre d’arcs
       (int´ressant si le nombre d’arcs ou d’arˆtes est en O(n2 )).
           e                                   e
    2. le parcours des sommets adjacents ` un sommet s est en d + (s) ≤ n. Si G est le tableau
                                             a                 ˚
           e
       repr´sentant le graghe, il se fait comme suit :

               a ← debut((G[s]))
               tant que (a= fin(G[s]))
146                                                                          CHAPITRE 8. LES GRAPHES

                    traiter(element(a))
                    a ← suivant(a)

En revanche
                         e                                                            a
    1. il n’y a pas d’acc`s direct aux arcs : pour savoir si un sommet t est adjacent ` un sommet s,
       il faut effectuer un parcours de la liste d’adjacence de s, qui est en Θ(n) dans le pire des cas.
    2. la recherche des pr´d´cesseurs d’un sommet n´cessite le parcours de toute la structure 1 .
                          e e                      e

   u
Coˆ t du parcours en profondeur Le parcours en profondeur visite chacun des n sommets
                                                           e
une et une seule fois. Chaque fois qu’un sommet s est visit´, l’algorithme effectue un parcours de
ses successeurs (au nombre de d + (s) ) pour examiner leur marque. Il y a donc n marquages et
                                ˚
p=      d + (s) examens de marques. Le parcours en profondeur est donc en Θ(n + p).
         ˚
      s∈S


Mise en oeuvre en Java
                                                                                    e
    Les listes d’adjacence sont mises en oeuvre dans une classe List partiellement d´crite ici. On
                                                                             e
utilise ici des listes circulaires, dont le dernier maillon pointe sur l’en-tˆte.

class List{
    int sommet;
    List suiv;

      List (int s, List l){
         sommet = s;
         suiv=l;
      }

      List(){
         suiv = this;
      }

      void rajoute(int s){
         suiv=new List(s, suiv);
      }
}

                                    e
Les graphes peuvent alors implant´s par la classe Graph ci-dessous. Le constructeur prend en
      e                                                               a                e
param`tre le nombre n de sommets et initialise les listes d’adjacence ` vide. Ces derni`res seront
              a                            a     e
ensuite mises ` jour par appels successifs ` la m´thode ajoute arc.

class Graph{

      List [] succ;
      int ns;
      private boolean [] marque;

      Graph (int n){
         ns = n;
         succ = new List [ns];
         for(int i=0; i<ns;i++)
            succ[i]= new List();
                               e                            e                           e e
   1. Il est possible de faire ´galement figurer dans la repr´sentation les listes des pr´d´cesseurs : les algorithmes
              u                          e
sont moins coˆteux en temps mais requi`rent davantage d’espace
                   ´
8.5. GRAPHES ORIENTES ACYCLIQUES (DAG)                                                       147

        marque = new boolean [ns];
   }

   void ajoute_arc(int s, int t){
      succ[s].rajoute(t);
   }

   void dfs_prefixe (int s){

       marque[s] = true;
       System.out.print(s + " ");
       for(List a =succ[s].suiv; a!=succ[s]; a = a.suiv)
          if (!marque[a.sommet])
          dfs_prefixe(a.sommet);
   }

   void affichage_prefixe (){
      int s;

   for(s=0; s<ns;s++) marque[s]=false;
      for(s=0; s<ns;s++)
         if (!marque[s]) dfs_prefixe(s);
   }

                   e                    e e
Le programme utilis´ dans la section pr´c´dente pour tester la mise en œuvre des graphes avec les
                           e           a                            e
matrices d’adjacence, peut ˆtre repris ` l’identique pour cette repr´sention.

                                                              e
Exercice 8.2 Programmer le parcours en largeur avec cette repr´sentation.


8.5                   e
        Graphes orient´s acycliques (dag)
                          e                      e
Dans toute la section, G d´signe un graphe orient´ (S,A).

                                                      e
Definition 6 (Dag) G est dit acyclique s’il ne poss`de pas de circuit. On dit souvent dag (de
                                                    e
l’anglais directed acyclic graph) pour graphe orient´ acyclique.


                   6 '                0                     4 '               7
                                          ‰


                    %
                    c               jc                       %                 c
                   5 '               2                      1 '               3



                                                                               c
                                                                              8

                                                            e
                       Figure 8.7 – Exemple de graphe orient´ acyclique


                                                           e    e
Definition 7 On appelle source de G tout sommet de demi-degr´ int´rieur nul. On appelle puits
                             e    e
de G tout sommet de demi-degr´ ext´rieur nul.
148                                                                   CHAPITRE 8. LES GRAPHES

                                                        e e
Autrement dit, une source de G est un sommet sans pr´d´cesseur, un puits est un sommet sans
successeur. Par exemple, le graphe de la figure 8.7 a une seule source : le sommet 7. Les sommets
4, 5 et 8 sont des puits.

Notations On notera n le nombre des sommets de G et p le nombre de ses arcs. De plus, pour
                                                                                              e
tout sous-ensemble S’ de l’ensemble S des sommets, on notera G(S’) le sous-graphe de G engendr´
                            e
par S’. Enfin, Sources(G) d´signera l’ensemble de ses sources.

                                           e
Proposition 8.1 Si G est acyclique, il poss`de au moins une source et puits.

                   e                                                      e     a
Preuve Le graphe ´tant acyclique, les chemins ont une longueur au plus ´gale ` (n-1). Tout
                             e      e
chemin de longueur maximale d´bute n´cessairement sur une source et se termine sur un puits. 2


8.5.1     Tri topologique
                                         e     e              e
    Le graphe G est maintenant suppos´ mod´liser un probl`me d’ordonnancement des tˆches      a
 e
d´crit dans l’introduction de ce chapitre. On se propose de concevoir un algorithme qui, lorsque
                                            e                                              e
c’est possible, renvoie un tableau num index´ sur l’ensemble des sommets du graphe repr´sentant
une num´rotation compatible des sommets, c’est-`-dire telle que s’il existe un arc s → t entre deux
         e                                       a
sommets, alors num[s] < num[t].

                                                                    e
Proposition 8.2 G est acyclique si et seulement si il existe une num´rotation compatible des
sommets.

                                     e                                   e
Preuve Il est clair que la pr´sence d’un circuit exclut toute num´rotation compatible.
                                                                    e
Supposons maintenant que le graphe soit acyclique, et consid´rons une suite de sous-graphes de G
                                                         e            a             e e        o
G0 = G, G1 . . . , Gi , Gi+1 . . . , Gn−1 , chacun d’eux ´tant obtenu ` partir du pr´c´dent en ˆtant l’une
de ses sources (il en existe toujours au moins une puisque tous ces sous-graphes sont acycliques). Si
                          o e                                                    e
l’on note si la source ˆt´e de Gi pour obtenir Gi+1 , on obtient une num´rotation compatible. En
             e                                             e e
effet, consid´rons deux sommets si et sj ainsi num´rot´s et supposons qu’il existe dans le graphe
un arc si → sj . Puisque sj est une source de Gj , si ne peut en ˆtre un sommet. Donc si est la
                                                                         e
source d’un sous-graphe Gi avec i < j. 2



      Tri topologique(G)                                  degreInt(G)
      deg←degreInt(G)                                     pour tout sommet s de G
      nb←-1                                                   deg[s]← 0
      sources←sources(G)                                  pour tout sommet s de G
      pour tout sommet s de G                                 pour tout sommet t adjacent ` s
                                                                                          a
          num[s]← -1                                             deg(t) ← deg(t) +1
      tant que non vide(sources)                          renvoyer(deg)
          s←supprimer(sources)
          nb←nb+1                                         sources(G)
          num[s]← nb                                      deg←degreInt(G)
          pour tout sommet t adjacent ` s
                                      a                     e
                                                          cr´erVide(S)
             deg[t]←deg[t] -1                             pour tout sommet s de G
             si deg(t)=0                                     si deg(s) =0
                rajoute(t, sources)                             rajoute(s, S)
          circuit ←nb+1<n                                 renvoyer(S)
      renvoyer(circuit,num)


                                    Figure 8.8 – Tri topologique
                   ´
8.5. GRAPHES ORIENTES ACYCLIQUES (DAG)                                                                   149

          e                e                                                   e
    On d´duit de cette d´monstration un algorithme pour trouver une num´rotation compatible
des sommets, s’il en existe. On suppose disposer d’un algorithme degreInt qui calcule le tableau
                e     e
des demi-degr´s int´rieurs des sommets du graphe et d’un algorithme sources qui calcule la liste
                                            e                        e
des sources du graphe. L’algorithme donn´ sur la figure 8.8 utilise ´galement les primitives usuelles
                                                    a                                          e
sur les listes (les suppressions et rajouts se font ` une position quelconque, par exemple en tˆte).
                                                                                   e
Si le graphe contient un cycle, on obtiendra au bout d’un certain nombre d’it´rations un sous-
                                                                     e e         e       e
graphe sans sources, et donc certains sommets ne seront pas num´rot´s. On d´tectera l’´ventuelle
                                                                       e e         e
existence de circuits au fait que le nombre nb+1 de sommets num´rot´s est inf´rieur au nombre
n de sommets.
                                                                     e
Exercice 8.3 Programmer le tri topologique pour chacune des deux repr´sentations des graphes.

8.5.2      e
          D´composition par niveaux
                              e
Definition 8 On appelle d´composition par niveaux de G l’ensemble des parties non vides S1 ,
                     e
S2 , . . ., Sk de S d´finies par :

   – S1 = Sources(G)
   – ∀i, 1 < i ≤ k + 1      Si = Sources(G(S −             Sj ))
                                                   1≤j<i
   – ∀i, 1 < i ≤ k      Si = ∅

   – Sk+1 = ∅

                 e
Par exemple, la d´composition par niveaux du graphe de la figure 8.4 est la suivante :
S1 = {7} S2 = {4, 3} S3 = {8, 1} S4 = {0} S5 = {6} S6 = {2} S6 = {5} .
                                           e
Proposition 8.3 Soit S1 , . . . , Sk une d´composition par niveaux de G. Les parties S1 , . . . , Sk
                                                                                  a
sont disjointes. De plus, si s et t sont deux sommets de G tel que t est adjacent ` s et s’il existe
deux indices a et b tels que s ∈ Sa et t ∈ Sb , alors a < b.
                                ee             e
Preuve Par construction, les ´l´ments de la d´composition par niveaux sont des parties disjointes
                                                   e
et non vides de l’ensemble des sommets. Consid´rons un sommet s et un sommet t qui lui est
adjacent. Supposons qu’il existe un entier a tel que s ∈ Sa et un entier b tel que t ∈ Sb . Le sommet
t ´tant adjacent ` s et t ´tant une source du sous-graphe G’ engendr´ par S −
  e              a        e                                            e                Sj , s ne peut
                                                                                         1≤j<b
e                                        e             a
ˆtre un sommet de G’. Donc s appartient n´cessairement `                    Sj . Donc il existe j < b tel que
                                                                    1≤j<b
s ∈ Sj et j = a puisque les ensembles de la d´composition sont disjoints deux ` deux. 2
                                             e                                a
                                                       e
Proposition 8.4 G est acyclique si et seulement si la d´composition par niveaux de G est une
partition de l’ensemble S des sommets.
                                          e                    a
Preuve Les ensembles S1 , S2 , . . . , Sk ´tant disjoints deux ` deux, pour qu’ils constituent une
partition de S, il faut et il suffit que tout sommet du graphe soit dans l’un des ensembles Si .
                                                                                     e
Partie directe Si G est acyclique, tout sous-graphe de G l’est aussi, et donc il poss`de des sources.
Dans ce cas, S − S1 est strictement inclus dans S puisque G poss`de au moins une source. De
                                                                      e
mˆme, si S − S1 n’est pas vide, G(S − S1 ) poss`de des sources et donc l’ensemble S − S1 − S2
  e                                                 e
est strictement inclus dans S − S1 etc. Ainsi, si S −        Sj est non vide, on peut construire un
                                                            1≤j<i
ensemble S −                                           e e                             e
                       Sj strictement inclus dans le pr´c´dent. L’ensemble des sommets ´tant fini, il
               1≤j≤i
existe n´cessairement un indice k tel que S −
        e                                                  Sj = ∅ et donc tel que S =             Sj .
                                                  1≤j≤k                                   1≤j≤k
 e                                   e
R´ciproque Supposons que la d´composition par niveaux constitue une partition du graphe G.
Consid´rons un chemin s0 , . . . , sl du graphe G. Il existe des ´l´ments Si0 , . . . , Sil de la partition
       e                                                         ee
                                                                         e
qui contiennent respectivement chacun des sommets s0 , . . . , sl . D’apr`s la proposition 8.3,

                                            i0 < i1 < · · · < il
150                                                                 CHAPITRE 8. LES GRAPHES

                         e
Ceci exclut la possibilit´ que s0 = sl . 2


            DecompositionParNiveaux(G)

            nb←0, niveaux← ∅
            deg←degreInt(G)
            sources←sources(G)

            tant que non vide(sources)
                niveaux ← niveaux ∪ {sources}
                nouveau ← ∅                               //Calcul des nouvelles sources
                pour tout sommet s de sources
                   nb←nb+1
                   pour tout successeur t de s
                      deg(t)←deg(t)-1
                      si deg(t)=0
                         rajoute(t, nouveau)
                sources←nouveau

            acyclique ←nb=n
            renvoyer(acyclique, niveaux)



                                           e
                             Figure 8.9 – d´composition par niveaux

                                                       a
Dans l’algorithme de la figure 8.9, la variable nb sert ` calculer le nombre de sommets affect´s  e
a                     a e           e      e
` un niveau et donc ` d´tecter la pr´sence ´ventuelle de circuits dans le graphe. Cet algorithme
                         e
renvoie une variable bool´enne acyclique indiquant si le graphe est acyclique ou pas et l’ensemble
                       e              e
niveaux des niveaux r´sultant de la d´composition.

                                                                e
Exercice 8.4 Programmer l’algorithme en Java, pour les deux repr´sentations possibles des graphes.


8.6                                                         e
        Recherche de plus courts chemins dans un graphe valu´
            e                     e       e
   On consid`re ici un graphe valu´ orient´ ou pas, dont l’ensemble des sommets est

                                        S = {0, . . . , (n − 1)}

                                                                  e
On suppose de plus que toutes les valuations wst des arcs (ou arˆtes) entre deux sommets s et t
                                      e                               e
sont strictement positives. Etant donn´ un sommet particulier, appel´ ici source, on se propose,
                                                                                    a
pour tout sommet s de S, de trouver, s’il existe, un plus court chemin de la source ` s.

8.6.1     L’algorithme de Dijkstra
                                                                       e
   L’algorithme de Dijkstra produit deux tableaux dist et pred, index´s sur l’ensemble des som-
mets. Pour tout sommet s, dist[s] et pred[s] contiennent respectivement la longueur d’un plus
                           a                      e e
court chemin de la source ` s (distance) et le pr´d´cesseur de s sur ce chemin. Le tableau pred
                                                                     e e              e e
permet alors de retrouver ce plus court chemin en remontant de pr´d´cesseur en pr´d´cesseur ` a
partir de s. Par convention, si un tel chemin n’existe pas, dist[s]=+∞ et pred[s]=-1.

                   e                                                                              ee
L’algorithme est d´crit sur la figure 8.10. Initialement, le fait qu’aucun plus court chemin n’ait ´t´
      e                                                 e e          a                              a
calcul´ se traduit par une initialisation de tous les pr´d´cesseurs ` -1 et de toutes les distances `
                                                         ´
8.6. RECHERCHE DE PLUS COURTS CHEMINS DANS UN GRAPHE VALUE                                       151

+∞, sauf celle de la source, qui prend la valeur nulle.
                                e                                            e
L’algorithme examine alors it´rativement chaque sommet. La variable Q d´signe l’ensemble des
                      a
sommets qui restent ` examiner. Initialement Q est l’ensemble de tous les sommets, et l’algorithme
se termine quand Q = ∅. Chaque it´ration consiste ` pr´lever dans Q un sommet dont la distance
                                      e             a e
                                                            a        a
est minimale (c’est l’objet de l’algorithme extraireMin) et ` mettre ` jour les attributs (distance
     e e
et pr´d´cesseur) de tous ses sommets adjacents.




            Dijkstra

            pour tout sommet i             //Initialisation
                dist(i) ← +∞
                pred(i) ← -1
            dist(source) ← 0
            Q ← {0, . . . , n − 1}

            tant que Q= ∅
                s ← extraireMin(Q)
                pour tout sommet t adjacent ` s
                                            a                        a
                                                              //Mise ` jour des attributs de t
                   d ← dist(s)+ Wst
                   si (dist(t)> d)
                      dist(t) ← d
                      pred(t) ← s

            renvoyer(dist, pred)



                              Figure 8.10 – L’algorithme de Dijkstra



8.6.2    Correction de l’algorithme
                                    ea
Notons V l’ensemble des sommets d´j` vus, autrement dit tous ceux qui ne sont pas dans Q. La
                e    e
correction peut ˆtre ´tablie en exhibant deux invariants de la boucle qui constitue le coeur de
l’algorithme.
                                    e                                         ee
Lemme 8.5 (Invariant I1) A chaque it´ration, la variable Q satisfait la propri´t´ suivante :
                                ∀t ∈ V     ∀t′ ∈ Q   dist(t) ≤ dist(t′ )
                      e     e                                    ee      e
Preuve Avant la premi`re it´ration, V est vide, donc la propri´t´ est ´videmment satisfaite.
                       ee                             e                                     e e
Supposons que la propri´t´ est satisfaite avant une it´ration et soit s le nouveau sommet pr´lev´
dans Q. On sait donc que
                                    ∀t ∈ V dist(t) ≤ dist(s)
De plus, s est choisi de telle sorte que
                                  ∀t′ ∈ Q − {s} dist(s) ≤ dist(t′ )
       e
On en d´duit que
             ∀t ∈ V    ∀t′ ∈ Q − {s} dist(t) ≤ dist(s) ≤ min(dist(t′ ), dist(s) + wst′ )
Par suite, apr`s mise ` jour des valeurs dist(t′ ) pour les sommets t′ de Q − {s}, on obtient
              e       a
                          ∀t ∈ V ∪ {s} ∀t′ ∈ Q − {s} dist(t) ≤ dist(t′ )
152                                                                   CHAPITRE 8. LES GRAPHES

2

                                          e
Lemme 8.6 (Invariant I2) A chaque it´ration, pour tout sommet s, dist(s) est la longueur d’un
                                a                       a
plus court chemin de la source ` s, ne contenant, mis ` part s, que des sommets de l’ensemble V
                         e e
et pred(s) contient le pr´d´cesseur de s sur ce chemin. Par convention, cette longueur vaut +∞
        e e
et le pr´d´cesseur vaut -1 si un tel chemin n’existe pas.

                                                                                       a
Preuve Notons, pour tout sommet s, spV (s) un plus court chemin de la source ` s ne passant
      a
(mis ` part s) que par des sommets de V, et lg(spV (s)) sa longueur. Il faut montrer que la pro-
pri´t´ : ∀s, dist(s) = lg(spV (s)) et pred(s) est le pr´d´cesseur de s sur spV (s) est un invariant de
    ee                                                 e e
l’algorithme.
Avant la premi`re it´ration, V = ∅ et l’initialisation de dist et de pred assure alors que la propo-
                 e     e
sition est satisfaite.
                                                e                  ee                o
Supposons qu’elle soit satisfaite avant une it´ration et soit s l’´l´ment que l’on ˆte de Q et dont
la distance dist(s) est minimale parmi toutes celles des sommets de Q.
                                                 e
Soit t un sommet quelconque. Trois cas se pr´sentent.

                                                          e          c e
1er cas : t=s Dans ce cas, dist(s) et pred(s) sont inchang´s et de fa¸on ´vidente, spV ∪{s} (s) =
spV (s).

2`me cas : t ∈Q-{s} Dans ce cas, si un plus court chemin jusqu’` t passant par des som-
  e                                                                        a
mets de V ∪ {s} contient s, t est n´cessairement adjacent ` s. En effet, si cela n’´tait, t serait
                                       e                        a                       e
          a                                    e
adjacent ` un sommet u de V et s serait situ´ entre la source et u sur ce chemin, ce qui est impos-
sible puisque dist(u) ≤ dist(s) d’apr`s le lemme 8.5. Le sommet t ne peut donc ˆtre qu’adjacent
                                       e                                             e
a
` s et le chemin spV ∪{s} (t) a alors pour longueur dist(s) + wst . Si c’est un plus court chemin,
                     e               a e              e    a
cette longueur est n´cessairement ` inf´rieure ou ´gale ` la valeur courante de dist(t) (qui est la
                                                                       a                    e
longueur d’un plus court chemin par des sommets de V ). La mise ` jour de cette derni`re assure
                                                         e e
alors que dist(t) = lg(spV ∪{s} (s)). De plus s est le pr´d´cesseur de t dans ce plus court chemin et
                                           e        a
devient la nouvelle valeur de pred(t) apr`s mise ` jour.

 e                                 e                 a                                    e
3`me cas : t∈V Dans ce cas, mˆme si t est adjacent ` s, dist(t) et pred(t) restent inchang´s
du fait que dist(t) ≤ dist(s) d’apr`s le lemme 8.5. Par hypoth`se, dist(s) = lg(spV (s)) et
                                      e                        e
                              e
dist(t) = lg(spV (t)). On en d´duit que

                                  lg(spV (t)) ≤ lg(spV (s))     (1)

Un plus court chemin de la source ` t ne contenant que des sommets de V ∪ {s}, s’il passait
                                        a
                                e                e        a                                    e
pas s, aurait une longueur n´cessairement sup´rieure ` lg(spV (s)), ce qui est impossible d’apr`s
    e     e                e
l’in´galit´ (1). On en d´duit que le plus court chemin spV ∪{s} (t) ne passe pas par s et donc
spV ∪{s} (t) = spV (t). L’invariant est donc encore satisfait.
2

                                                     a             u                 o e
Remarque Cette preuve montre en particulier qu’` partir du moment o` un sommet s est ˆt´
                                           e
de Q, dist(s) et pred(s) ne sont plus modifi´s. Par suite la ligne

                                                           a
                               pour tout sommet t adjacent ` s
     e           e
peut ˆtre remplac´e par :

                                                             a
                            pour tout sommet t de Q adjacent ` s

Proposition 8.7 L’algorithme de Dijkstra est correct.

      e         e                                     a
Ceci d´coule imm´diatement du lemme 8.6 et du fait qu’` la sortie de la boucle, l’ensemble Q est
vide.
                                                         ´
8.6. RECHERCHE DE PLUS COURTS CHEMINS DANS UN GRAPHE VALUE                                        153

8.6.3             e
         Complexit´


                 e                  e                          u a            e
    La compexit´ de l’algorithme d´pend en particulier du coˆt, ` chaque it´ration, de l’extraction
                                                                             u
du sommet s de Q dont la distance est minimale. Pour optimiser le coˆ t de l’extraction d’un
ee
´l´ment minimal d’un ensemble, on peut imaginer organiser l’ensemble Q en tas (cf. section 7.5),
                          e                e
l’ordre sur les sommets ´tant alors donn´ par le tableau dist. L’extraction de s est dans ce cas
                                 u                                               e       a
en Θ(lg(k)) (cf. section 7.5), o` k est la taille courante du tas. Toutefois, apr`s mise ` jour d’un
                          a                  ee                       e
sommet t de Q adjacent ` s, l’ordre sur les ´l´ments de Q est modifi´ du fait de la modification de
                               e
dist(t). Il convient alors de d´placer t dans le tas, en remarquant que puisque dist(t) n’a pu que
                  e         e                               e           e
diminuer, t doit ˆtre tamis´ vers le haut du tas. Cette op´ration est ´galement en Θ(lg(k)).




        Dijkstra

        pour tout sommet i   //Initialisation
            dist(i) ← +∞
            pred(i) ← -1
        dist(source) ← 0
        Q ← NouveauTas(n, dist);

        tant que non vide(Q)
            s ← extraireMin(Q)
            pour tout sommet t (de Q) adjacent ` s
                                               a                      a
                                                               //Mise ` jour des attributs de t
               d ← dist(s)+ Wst
               si (dist(t)>d)
                  dist(t) ← d
                  pred(t) ← s
                  tamiserVersLeHaut(t, Q)

        renvoyer(dist, pred)



                Figure 8.11 – Algorithme de Dijkstra utilisant une structure de tas



                e                       e
La figure 8.11 d´crit une version raffin´e de l’algorihme de Dijkstra, utilisant une organisation en
tas de l’ensemble Q.
                                                                     ee
La primitive NouveauTas(n, dist) construit un tas dont les ´l´ments sont les n sommets du
                e            e
graphe, l’ordre ´tant celui d´fini par le tableau dist. La solution pour transformer une liste en un
          e                            e
tas, donn´e dans la section 7.5 est lin´aire en la taille de la liste.
                                                                           e
La primitive extraireMin(t,Q) est logarithmique en la taille du tas. L’ex´cution des n extractions
               n
sera donc en         lg(k) ∈ Θ(nlg(n)).
               k=1
                                             e        ee
La primitive tamiserVersLeHaut(t,Q), d´place l’´l´ment t vers le haut dans le tas Q en prenant
             e                                                   c a e
en compte l’´ventuelle modification de la valeur de dist(t) de fa¸on ` pr´server la structure de tas
                                                   e                   u
et est logarithmique en la taille du tas. Elle est ´galement en lg(k) o` k est la taille du tas. Si p
                             e                         e                                      u
est le nombre d’arcs ou d’arˆtes, il y a au plus p ex´cutions de cette primitive, dont le coˆ t est
               e
toujours major´ par lg(n).
         e                      e
On en d´duit que la complexit´ de l’algorithme est en O((p + n)lg(n)).
154                                                                 CHAPITRE 8. LES GRAPHES

8.6.4      Une mise en œuvre de l’algorithme en Java
Mise en œuvre du tas
               e                                         e    e                       e
    Comme d´crit dans la section 7.5, le tas est repr´sent´ par un objet constitu´ d’un tableau T
                ee                                                         e
contenant les ´l´ments du tas (ici des sommets) virtuellement organis´s en arbre binaire et d’un
                                 ee
entier indiquant le nombre d’´l´ments du tas.
                              ee
La relation d’ordre sur les ´l´ments du tas est ici variable puisqu’elle sera induite par les distances.
                    e     e
Elle est donc repr´sent´e par un tableau dist d’entiers.
                          a                                             e           e      e
Le constructeur affecte ` la variable dist l’adresse d’un tableau pass´ en param`tre, cr´e le tableau
                       e       e               a                       c
T dans lequel sont m´moris´s les sommets ` partir du rang 1, de fa¸on que la case de rang 0 puisse
e          e                                             e
ˆtre utilis´e comme sentinelle. et le transforme imm´diatement en tas.
       e
Les m´thodes tamiserVersLeBas, mettreEntas, extraireMin tamiserVersLeHaut, sont identiques `          a
celles de la section 7.5, si ce n’est que l’ordre est maintenant induit par les valeurs du tableau dist.


public class tas{
   private int taille;
   private int [] T;
   private int[] dist;

      tas(int n, int[] d){
         taille = n;
         T = new int[n+1];
         for(int i=0;i<n; i++)
         T[i+1]=i;
         dist=d;
         mettreEnTas();
      }

      boolean vide(){ return(taille==0);}

      int element(int i){return(T[i]);}

      int getTaille(){return taille;}

      void tamiserVersLeBas(int i){
         int x=T[i],f=2*i,dx=dist[x];

          if(f<taille && T[f]>T[f+1]) f++;
          while (f<=taille && dx>dist[T[f]]){          // Invariant: T[i] = x
             T[i] = T[f]; T[f] = x;                  // Echange
             i = f;
             f=2*i;                                 // Choix du nouveau fils
             if(f<taille && dist[T[f]]>dist[T[f+1]]) f++;
          }
      }

      int extraireMin(){
         int r = T[1];
         T[1]=T[taille--];
         tamiserVersLeBas(1);
         return r;
      }
                                                         ´
8.6. RECHERCHE DE PLUS COURTS CHEMINS DANS UN GRAPHE VALUE                                       155

    void mettreEnTas(){
       for(int i=taille/2; i>0; i--)
          tamiserVersLeBas(i);
    }

    void tamiserVersLeHaut(int i){
       int x= T[i], dx=dist[x];

        T[0]=x;
        for(int p=i/2; dx<dist[T[p]]; p/=2){               // Invariant: T[i] = x
           T[i] = T[p]; T[p] = x; i = p;
        }
    }
}

Programmation de l’algorithme
                          e    e                               e                                    a
    Le graphe est ici repr´sent´ avec des listes d’adjacence, d´crites dans une classe List, locale `
                                   e
la classe Graph. Elles sont compos´es de maillons, chacun portant non seulement un sommet mais
                                       e
aussi un poids. Les ajouts se font en tˆte.

public class Graph{

    class List{
       int sommet;
       int poids;
       List suiv;

        List(){
           suiv = null;
        }

        List(int s, int p, List suivant){
           sommet = s;
           poids = p;
           suiv = suivant;
        }

        void ajoute(int s, int p){
           suiv = new List(s, p, suiv);
        }
    }

                                       e
Un graphe est donc un objet constitu´ d’un tableau succ de listes d’adjacence et d’un entier
    e                                                              e
repr´sentant le nombre de sommets. Le constructeur prend en param`tre le nombre n de sommets,
  e                                                          ee       a
cr´e le tableau succ de taille n et initialise chacun de ses ´l´ments ` la liste vide. Les listes
                                                                   a      e
d’adjacence seront effectivement construites par appels successifs ` la m´thode ajoute arc qui
                 e          e   e
prend en param`tre les extr´mit´s de l’arc et son poids.
                     e                                                              e     e
Deux champs suppl´mentaires dist et pred sont deux tableaux dans lesquels sont m´moris´es les
                   e e
distances et les pr´d´cesseurs.

    List [] succ;
    int ns;
    int[]dist, pred;
156                                                               CHAPITRE 8. LES GRAPHES

      Graph(int n){
          ns = n;
          succ = new List[ns];
          for(int i=0;i<ns;i++)
              succ[i]=new List();
      }

      void ajoute_arc (int i, int j, int p){
          succ[i].ajoute(j,p);
      }

                                         e      e       e
L’algorithme de Dijkstra est programm´ ci-apr`s. Apr`s extraction du minimum du s du tas Q,
         a                                            a
la mise ` jour se fait sur tous les sommets adjacents ` s (et non seulement sur ceux du tas) pour
                       e
des raisons d’efficacit´. Enfin, si le graphe n’est pas connexe, l’un des sommets s extraits de Q
sera tel que dits[s] = Integer.M AX V ALU E et il en sera ainsi de tous les sommets restants dans
Q. On sort alors de la boucle while par une instruction break, faute de quoi l’instruction aux =
                                       e
dist[s]+ l.poids provoquerait un d´bordement de capacit´.     e

      void Dijkstra(int source){
         int s, t, d;

          dist= new int[ns];
          pred = new int[ns];
          for(int i=0; i<ns; i++){
             dist[i] = Integer.MAX_VALUE;
             pred[i]=-1;
          }
          dist[source]=0;
          tas Q = new tas(ns, dist);

          while (Q.getTaille()!=0){
             s = Q.extraireMin();
             if(dist[s]<Integer.MAX_VALUE)
                for(List l=succ[s].suiv; l!=null;l=l.suiv){
                   t=l.sommet;
                   d = dist[s]+ l.poids;
                   if (dist[t]>d){ // t est necessairement dans Q
                      dist[t] = d;
                      pred[t] = s;
                      Q.tamiserVersLeHaut(t);
                   }
                }
              else break;
          }
      }

             e
Affichage des r´sultats
                               a                                 e
   Il est maintenant possible, ` l’aide d’un couple de tableaux r´sultat de l’algorithme de Dijkstra,
                                                                              a
d’afficher pour tout sommet s du graphe un plus court chemin de la source ` ce sommet, s’il existe,
ainsi que sa longueur.

      void affichePlusCourtsChemins(int source){
         Dijkstra(source);
         for(int s = 0; s<ns; s++){
                                                         ´
8.6. RECHERCHE DE PLUS COURTS CHEMINS DANS UN GRAPHE VALUE                                      157

            AfficheChemin(s);
            System.out.println();
        }
    }
     e                                a           e                e
La m´thode afficheChemin(s affiche, ` partir du r´sultat c de la m´thode Dijkstra, la distance de
la source au sommet s, suivie du plus court chemin, s’il existe. Pour afficher celui-ci, il suffit de le
                                           a            e            a             e e       a
parcourir en sens inverse, chaque sommet ` partir de s ´tant obtenu ` partir du pr´c´dent ` l’aide
                   e e                                              e e
du tableau des pr´d´cesseurs. La source est le sommet dont le pr´d´cesseur vaut -1. Puisque le
                                                     e
chemin est parcouru en ordre inverse de celui souhait´ pour l’affichage, ce parcours est un parcours
 e
r´cursif en post-ordre. On obtient ainsi :
   void AfficheChemin(int s){
      int d = dist[s];
      if(d==Integer.MAX_VALUE)
         System.out.print(s+ ":\tPas de chemin vers ce sommet");
      else{
         System.out.print(s + ":\tDistance = " + d + "\tChemin : ");
         AffChem(s);
      }
    }

   private void AffChem (int s){
      if (s != -1){
         AffChem(pred[s]);
         System.out.print( " " + s          +" ");
      }
    }
158   CHAPITRE 8. LES GRAPHES
Chapitre 9

Algorithmique du tri

             e          e                                    ee                        a
    La donn´e du probl`me est ici un tableau A dont les ´l´ments appartiennent ` un ensemble
                   e                                                                    ee
totalement ordonn´. On se propose de trier par ordre croissant la partie A[d .. f ] des ´l´ments dont
les indices sont compris entre d et f. Par convention A[d .. f ] = ∅ quand f < d.
      u                           e     e                                     e
Le coˆt des algorithmes de tris pr´sent´s dans ce chapitre est le nombre d’op´rations significatives
qu’ils effectuent en fonction de la taille n = (f − d + 1) de la donn´e. Les op´rations prises en
                                                                        e           e
                     e                 ee                                              ee       e
compte sont ici les d´placements des ´l´ments du tableau et leur comparaisons : ces ´l´ments ´tant
                                         e                   u
virtuellement complexes, ce sont les op´rations les plus coˆteuses.



9.1           ee
         Tris ´l´mentaires
                               e                                                         e
   Les algorithmes de tri par s´lection et de tri par insertion, sont deux algorithmes it´ratifs
ee
´l´mentaires.


9.1.1                 e
          Le tri par s´lection
Le principe

                             a                     a                          e    ea           e
    L’algorithme consiste ` parcourir le tableau, ` l’aide d’un indice j, incr´ment´ ` chaque it´ration
                                                                            e          e
et de valeur initiale d. L’invariant de cet algorithme est le suivant : apr`s chaque it´ration, A[d .. j]
est d´j` tri´e et contient les j − d + 1 plus petits ´l´ments de A[d .. f ]. Pour que cette propri´t´
      ea e                                              ee                                           ee
       e     e         e                    e     e                    e
soit pr´serv´e par it´ration, il suffit, apr`s incr´mentation de j, d’´changer A[j] et le minimum de
la partie A[j .. f ]. Cet invariant assure qu’apr`s l’it´ration pour laquelle j = f − 1, le tableau est
                                                 e      e
   e
tri´ par ordre croissant.


         TriParSelection(A, d, f)                                Minimum(A, d, f)
                                                                 indmin ← d
                  a
         pour j=d ` (f-1) faire                                  min ← A[d]
            min =Minimum(A,j,f)                                                a
                                                                 pour i = d+1 ` f faire
            si min=j                                                 si A[i]<min
               echanger(A[min], A[j])                                   min ← A[i]
                                                                        indmin ← i
                                                                 renvoyer(i)


                                                         e
                                   Figure 9.1 – Tri par s´lection



                                                  159
160                                                   CHAPITRE 9. ALGORITHMIQUE DU TRI

         e
Complexit´
   Le coˆt de la recherche du minimum dans un tableau de taille n requiert (n − 1) comparaisons.
         u
                                  n−1
                                         n(n − 1)
Le tri par s´lection effectue donc
            e                         i=          comparaisons et au plus (n − 1) ´changes. Il
                                                                                     e
                                  i=1
                                            2
est donc en Θ(n2 ), quelle que soit la configuration des donn´es.
                                                            e

9.1.2     Le tri par insertion
Le principe
                           a                      a                           e     ea           e
    L’algorithme consiste ` parcourir le tableau, ` l’aide d’un indice j, incr´ment´ ` chaque it´ration
                                                                                   e            e
et de valeur initiale d + 1. L’invariant de cet algorithme est le suivant : apr`s chaque it´ration,
                  e                                           ee          e     e      e
A[d .. j] est rang´ par ordre croissant. Pour que cette propri´t´ soit pr´serv´e par it´ration, il suffit,
apr`s incr´mentation de j, de placer la valeur x de A[j] dans A[1 .. (j − 1)] de fa¸on ` pr´server
    e       e                                                                           c a e
l’ordre des ´l´ments. Pour cela, tous les ´l´ments de A[1 .. (j − 1)] ` partir de celui d’indice (j − 1)
              ee                           ee                         a
                                          e e                                     ea
et strictement plus grands que x sont d´cal´s vers la droite, puis x est recopi´ ` la place du dernier
ee          e    e                              e     e
´l´ment d´plac´. Cet invariant assure qu’apr`s l’it´ration pour laquelle j = f , le tableau est tri´   e
par ordre croissant.

                             TriParInsertion(A, d, f)

                                        a
                             pour j=d+1 ` f faire
                                x ← A[j]
                                i ← j-1
                                tant que (i≥d) et (A[i]>x) faire
                                   A[i+1]←A[i]
                                   i← i-1
                               A[i+1] ← x



                                   Figure 9.2 – Tri par insertion


         e
Complexit´
              e                                              e              a    e
    Chaque it´ration de la boucle la plus externe fait le mˆme nombre (` 1 pr`s) de comparaisons
et de d´placements des ´l´ments du tableau. Ce nombre vaut 1 dans le meilleur des cas, (j − d)
       e                  ee
dans le pire des cas, et (j − d)/2 en moyenne.
Le meilleur des cas est celui pour lequel le tableau est d´j` tri´. L’algorithme effectue alors (n − 1)
                                                          ea e
                           e
comparaisons et aucun d´placement. Il est donc dans ce cas lin´aire.e
                                                                     n−1
Dans le pire des cas et en moyenne, le coˆt est proportionnel `
                                         u                    a             i = Θ(n2 ). L’algorithme est
                                                                      i=1
donc quadratique en moyenne et dans le pire des cas.


9.2      Tri rapide
Le principe
                                                      a
    Le tri rapide consiste, dans un premier temps, ` partitionner le tableau en deux sous-tableaux
                         e                           ee      e                                 e
de taille strictement inf´rieures, dans lesquels ont ´t´ plac´es les valeurs respectivement inf´rieures
   e              e          e      a             ee              e
ou ´gales et sup´rieures ou ´gales ` un certain ´l´ment, appel´ pivot, choisi arbitrairement dans le
                       ee                                 e              e         e    a      ee
tableau initial. Tout ´l´ment du premier sous-tableau ´tant alors inf´rieur ou ´gal ` tout ´l´ment
9.2. TRI RAPIDE                                                                                   161

                                      e
du second, il suffit d’appeler ensuite r´cursivement le tri sur chaque sous-tableau. La terminaison
         e                                                                       e       a
est assur´e par le fait que ces deux sous-tableaux sont de taille strictement inf´rieure ` celle du
                                                    e
tableau initial. Au bout d’un nombre fini d’appels r´cursifs, on obtiendra des tableaux de taille 1
                                         e
qui constituent les cas terminaux de la r´cursion.

         e                                                            e
Nous pr´sentons ici l’une des multiples variantes du tri rapide, qui d´pendent essentiellement
de l’algorithme de partition choisi (et en particulier du pivot).

                                                                         e
On suppose donc disposer d’un algorithme de partition, satisfaisant la sp´cification suivante :

Partition(A, d, f)          (On suppose que d < f )
      - choisit pour pivot A[d]
                   ee
      - classe les ´l´ments de A[d .. f ] par rapport au pivot
      - renvoie un indice j tel que d ≤ j < f et tel que :
                      ee                            e          e
           - tous les ´l´ments de A[d .. j] sont inf´rieurs ou ´gaux au pivot
                                                   e          e
           - tous ceux de A[(j + 1) .. f ] sont sup´rieurs ou ´gaux au pivot.
Le fait que d ≤ j < f assure que les deux sous-tableaux sont de taille strictement inf´rieure  e
a                                                                              e
` celle du tableau initial. Il suffit alors, pour trier A[d .. f ], de rappeler r´cursivement le tri sur
chacun des sous-tableaux A[d .. j] et A[(j + 1) .. f ].


                             TriRapide(A, d, f)

                             si d<f
                                j←Partition(A, d, f)
                                TriRapide(A, d, j)
                                TriRapide(A, j+1, f)



                                      Figure 9.3 – Tri rapide

                                  e                                        e
Cet algorithme est correct, sous r´serve que l’algorithme de partition donn´ figure 9.4 le soit.


                       Partition(A, d, f)

                      pivot ← A[d]
                      i ← d-1
                      j ← f+1
                       e e
                   (*)r´p´ter
                        (a) r´p´ter j ← j-1 tant que A[j]> pivot
                             e e
                        (b) r´p´ter i ← i+1 tant que A[i]< pivot
                             e e
                            si i<j
                                echanger(A[i], A[j])
                      tant que (i<j)
                      renvoyer(j)




                              Figure 9.4 – L’algorithme de partition
162                                                     CHAPITRE 9. ALGORITHMIQUE DU TRI

      e
Consid´rons par exemple la configuration initiale


                        8   6     2      13         5     0    20       4         9   11
                T                                                                           T
                i                                                                           j


                                                                            a          e
Le pivot choisi est prend ici la valeur 8. Voici les configurations obtenues ` chaque it´ration de
l’algorithme de partition.

                                             e     e
                                        Premi`re it´ration

                        8   6     2      13         5     0    20       4         9   11

                        4   6     2      13         5     0    20       8         9   11

                        T                                               T
                    i                                                   j


                                                  e
                                        Seconde it´ration

                        4   6     2      13         5     0    20       8         9   11

                        4   6     2      0     5         13    20       8         9   11

                                         T                T
                                         i                j


                                             e     e
                                        Derni`re it´ration

                        4   6     2      0     5         13    20       8         9   11
                                                T         T
                                                j         i


Correction de la partition
                                                                       e
    On suppose que d < f . On remarque qu’il se produit au moins une it´ration de la boucle (*).

               a        e                                                       ee
On montre qu’` chaque it´ration, ni (a) ni (b) ne bouclent, que les indices des ´l´ments du tableau
        e                                               e            e
examin´s sont toujours compris entre d et f et qu’apr`s chaque it´ration, on est dans l’une des
situations suivantes :

       ≤pivot                                             ≥pivot
[                   ]                               [               ]       (I)
d                   i                               j               f
9.2. TRI RAPIDE                                                                                             163
           ≤pivot                                ≥pivot
[                              ][                                          ]      et i≥j         (I’)
d                             j j+1                                        f



                            e      e                               e
Chacun des intervalles d´limit´s par les crochets est vide au d´part. Si i et j sont les valeurs
des indices avant une it´ration, on note i′ et j ′ leur nouvelle valeur apr`s l’it´ration. La boucle
                          e                                                e      e
(a) s’arrˆte d`s que l’on trouve un ´l´ment d’indice j ′ inf´rieur ou ´gal au pivot. On sort donc
          e     e                       ee                    e        e
toujours de cette boucle avec une valeur j ′ telle que d ≤ j ′ < j pour la premi`re it´ration ou en
                                                                                  e    e
i ≤ j ′ < j pour les autres. De mˆme on sort de la boucle (b) avec une nouvelle valeur i′ telle que
                                     e
i < i′ ≤ (j ′ + 1), puisque A[j ′ + 1] ≥ pivot. On sait donc que :
       (i) tous les ´l´ments d’indices > j ′ sont sup´rieurs ou ´gaux au pivot
                     ee                               e          e
       (ii) tous les ´l´ments d’indices < i′ sont inf´rieurs ou ´gaux au pivot
                      ee                             e          e
       (iii) A[i′ ] ≥ pivot
       (iv) A[j ′ ] ≤ pivot
       (v) i′ ≤ (j ′ + 1)

Deux cas sont possibles :
    1. Soit i′ < j ′ : l’´change de A[i′ ] et A[j ′ ] et les conditions (i) et (ii) assurent que l’invariant (I)
                         e
       est satisfait par les nouvelles valeurs i′ et j ′ .
    2. Soit i′ ≥ j ′ . D’apr`s (v) on en d´duit que seuls deux cas sont possibles. Soit i′ = j ′ et, d’apr`s
                             e              e                                                               e
       (iii) et (iv), A[i′ ] = A[j ′ ] = pivot. Soit i′ = j ′ + 1. Dans les deux cas, (i) et (ii) assurent que
       la condition (I’) est satisfaite.
                                                    e                  a                       e
Seule la situation (I’) satisfait la condition d’arrˆt et dans ce cas-l`, la valeur de j renvoy´e satisfait
           e
bien la sp´cification.

  u
Coˆ t de la partition
                         e                                        a
    On rappelle que n d´signe la taille de A[d .. f ]. On a vu qu’` la sortie de la boucle (*), soit i = j
                                      e                          e e
soit i = j + 1. Le nombre total d’incr´mentations de i et de d´cr´mentations de j est donc n + 1 ou
                               e                                                          ee
n + 2. Pour chacune de ces op´rations on fait une comparaison entre le pivot et un ´l´ment du ta-
                                                                    e                            e
bleau, soit n+2 comparaisons dans le pire des cas. Le nombre d’´changes est le nombre d’it´rations
                                                            n+2
de la boucle la plus externe, soit dans le pire des cas ⌊         ⌋. Le nombre d’´changes et de com-
                                                                                    e
                                                              2
paraisons requis par l’algorithme de partition d’un tableau de taille n est donc, dans le pire des cas :

                                                        3
                                              f (n) =     (n + 2)
                                                        2

  u
Coˆ t du tri rapide dans le pire des cas
                                                             u
    Intuitivement, il semble que le pire des cas est celui o` chaque sous-tableau de taille k est
partitionn´ en un sous-tableau de taille 1 et un sous-tableau de taille k − 1. Si l’on note C(n) le
           e
  u
coˆt du tri rapide d’un tableau de taille n dans ce type de configuration et sachant que C(1) = 0,
                             e
on obtient les relations de r´currence suivantes :
C(n)= C(n-1) + f(n)
C(n-1) = C(n-2) + f(n-1)
...
C(3) = C(2) + f(3)
C(2) = 0 + f(2)

                   a            e     e
En ajoutant membre ` membre ces ´galit´s, on obtient :
164                                                              CHAPITRE 9. ALGORITHMIQUE DU TRI

         n                    n           n
                         3                              3   n(n + 1)                        3 2
C(n)=          f (i) =              i+          2   =                − 1 + 2(n − 1)     =     (n + 5n − 6).
         i=2
                         2    i=2         i=2
                                                        2      2                            4
                                                                           e
Pour montrer qu’il s’agit effectivement du pire des cas, on va prouver par r´currence que, quelle
              c                       e
que soit la fa¸on dont sont partitionn´s les sous-tableaux :
                                                                 3 2
                                          ∀n ≥ 1        C(n) ≤     (n + 5n − 6)
                                                                 4
                         3 2
Pour n = 1 : C(1) = 0 =    (1 + 5 − 6).
                         4
Soit n ≥ 2, quelconque, fix´. Supposons (hypoth`se de r´currence) que :
                          e                   e       e
                                                                       3 2
                                    ∀p,     0 < p < n,        C(p) ≤     (p + 5p − 6)
                                                                       4
Dans l’hypoth`se o` l’intervalle est partitionn´ en deux tableaux de tailles respectives p et n − p,
             e    u                            e
C(n) = C(p) + C(n − p) + f (n). On en d´duit que :
                                           e
                                                                           3
                     C(n) ≤ max (C(p) + C(n − p)) + f (n) ≤                   max g(p) + f (n)
                                  0<p<n                                    4 0<p<n

avec g(p) = (p2 + 5p − 6) + ((n − p)2 + 5(n − p) − 6). Puisque g ′ (p) = 4p − 2n, on obtient le tableau
de variation ci-dessous, dans lequel :

max = g(1) = g(n − 1) = n2 + 3n − 10
        n        n       n         1           1
min = g( ) = 2 ( )2 + 5 − 6 = (n2 + 10n − 24) = (n + 12)(n − 2)
        2        2       2         2           2

                              n
  n       1                                          n−1
                              2

 g’(p)           −            0               +

 g(p)     max                                        max
                                              I
                     q
                             min

                                    3 2              3         3
On en d´duit que C(n) ≤
       e                              (n + 3n − 10) + (n + 2) = (n2 + 5n − 6). 2
                                    4                2         4
Th´or`me 9.1 Le tri rapide est en Θ(n2 ) dans le pire des cas.
  e e

Analyse en moyenne du tri rapide
                              e                         u
   Il semblerait qu’un cas tr`s favorable soit celui o` chaque partition coupe les tableaux en deux
                          e      a         e e           ee
sous-tableaux de tailles ´gales (` une unit´ pr`s si les ´l´ments sont en nombre impair). La fonction
     u                    e
de coˆt satisfait alors l’´quation :
                                                              n
                                                    C(n) = 2C( ) + f (n)                                      (9.1)
                                                              2
En supposant que n = 2k et donc que k = lg2 (n), on obtient le calcul suivant :
               C(n)          = 2 C(n/2)+f(n)
2     × |      C(n/2)        = 2 C(n/22 )+f(n/2)
22    × |      C(n/22 )      = 2 C(n/23 )+f(n/22 )
                             ...
9.2. TRI RAPIDE                                                                                                   165

2k−1 × |      C(n/2k−1 ) = 2C(1)+ f(n/2k−1 )

                                      k−1                      k−1
                                                                             n       k             3
              C(n)              =           2i f (n/2i ) = 3         (2i +     ) = 3( n + 2k − 1) = (nlg2 (n) + n − 1)
                                      i=0                      i=0
                                                                             2       2             2

et donc       C(n) ∈ Θ(nlg(n))

               e                             e e                                           e
On obtient un r´sultat analogue dans le cas g´n´ral en encadrant n par deux puissances cons´cutives
                                               e
de 2. C’est la meilleure des situations observ´es jusqu’ici. Se pose alors la question de la com-
      e                        e          e
plexit´ en moyenne : peut-elle ´galement ˆtre meilleure que quadratique ? Peut-on prouver qu’elle
    e
est ´galement en nlg(n) ?

                                                                              ee
Pour conduire une analyse en moyenne du tri rapide, on suppose que les ´l´ments du tableau
                                 e                                                 ee
sont tous distincts et on les num´rote par ordre croissant. On obtient ainsi les n ´l´ments :

                                                       a1 < a2 < . . . < an
                                   e                                                         e
On suppose que la probabilit´ pour que chacun d’eux soit le pivot de la partition est ´gale ` 1/n. a
Si le pivot est a1 , la partition de l’ensemble des ´l´ments du tableau ne peut ˆtre que {a1 } et
                                                         ee                             e
{a2 , . . . , an }.
Si le pivot est dans {a2 , . . . , an }, apr`s la premi`re it´ration de la boucle (*) de la partition, on
                                            e          e     e
                    e                                     e e
a i = d < j. On ´change alors A[d] et A[j] et on r´it`re, ce qui a pour effet de faire d´croˆ j e ıtre
                                                                   e      a
strictement. Par suite l’indice du pivot sera strictement sup´rieur ` la valeur finale de j, donc le
                                                                    e
pivot se trouve dans la partie droite de la partition. On en d´duit que le tableau suivant :


 pivot                   partition                                    C(n)


   a1           {a1 } {a2 , . . . an }                  C(1) + C(n − 1) + f (n)
   a2           {a1 } {a2 , . . . an }                  C(1) + C(n − 1) + f (n)
   a3          {a1 , a2 } {a3 , . . . an }              C(2) + C(n − 2) + f (n)

  ...                           ...                                    ...

 ak+1      {a1 , . . . , ak }     {ak+1 , . . . an }    C(k) + C(n − k) + f (n)

  ...                           ...                                    ...

  an           {a1 , . . . , an−1 }         {an }       C(n − 1) + C(1) + f (n)



       e          u
On en d´duit le coˆt en moyenne :
                                                                     n−1
                                       1
                         C(n) =          (C(1) + C(n − 1) +                (C(k) + C(n − k)) + f (n)
                                       n
                                                                     k=1

     e
D’apr`s l’analyse dans le pire des cas,
                                              3                            3
                          C(n − 1) ≤            ((n − 1)2 + 5(n − 1) − 6) = (n2 + 3n − 10)
                                              4                            4
. De plus, C(1) = 0. Par suite
           1                              3 2              3         3         10
             (C(1) + C(n − 1)) + f (n) ≤    (n + 3n − 10) + (n + 2) = (3n + 7 − )
           n                             4n                2         4         n
166                                                                CHAPITRE 9. ALGORITHMIQUE DU TRI

                                 n−1                n−1
En remarquant enfin que                  C(k) =            C(n − k), on en d´duit que :
                                                                           e
                                  k=1               k=1

                                                        n−1
                                                    2               3         10
                                        C(n) ≤                C(k) + (3n + 7 − )                                        (9.2)
                                                    n               4         n
                                                        k=1

On se propose de montrer qu’il existe une constante a strictement positive telle que :

                                                   ∀n ≥ 1, C(n) ≤ a nln(n)                                              (9.3)

        e        e
On proc`de par r´currence sur n.
Si n = 1, C(n) = 0. La propri´t´ est satisfaite d`s que l’on choisit a ≥ 0.
                             ee                  e
Soit n ≥ 2 et supposons que ∀k < n, C(k) ≤ a nln(k). On d´duit de la relation (9.2) que :
                                                              e
                           n−1                                              n−1
                       2                   3        10  2                                    3         10
             C(n) ≤              a kln(k) + (n + 7 − ) = (a                         kln(k)) + (3n + 7 − )               (9.4)
                       n                   4        n   n                                    4         n
                           k=1                                              k=1

Comme la fonction x → xln(x) est croissante :
      n−1              n−1                    n                             n            n                          n
                                                                 x2                          x      x2         x2
            kln(k) =         kln(k) ≤             xln(x)dx =        ln(x)       −              dx =    ln(x) −          =
                                          2                      2          2        2       2      2          4    2
      k=1              k=2

n2         1                  n2         1
   (ln(n) − ) + (1 − ln(4)) ≤    (ln(n) − ).
2          2                  2          2
                e
Par suite, d’apr`s la relation (9.4), on obtient :

                              1   3          10            a   3
             C(n) ≤ an(ln(n) − ) + (3n + 7 − ) ≤ a nln(n) − n + (3n + 7)
                              2   4           n            2   4
Et donc la condition
                                    C(n) ≤ a nln(n)
                e
est satisfaite d`s que
                                                     3           a
                                                       (3n + 7) − n ≤ 0
                                                     4           2
      a       e
c’est-`-dire d`s que
                                                          21 ≤ n(2a − 9)
et ce, pour tout n ≥ 1. Il suffit pour cela que 21 ≤ 2a − 9, et donc de choisir par exemple a = 15.

  e e
Th´or`me 9.2 Le tri rapide est en Θ(nlg(n)) en moyenne.


9.3         Tri fusion
                  e                                         a       e
   Comme indiqu´ sur la figure 9.5, le tri fusion consiste ` trier r´cursivement les deux moiti´se
                 a                                                e
du tableau, puis ` fusionner les deux sous-tableaux ainsi ordonn´s. La fusion de deux tableaux
    e                               a                       e               a           a
rang´s par ordre croissant consiste ` les parcourir simultan´ment de gauche ` droite et ` recopier
              e                             ee
dans un troisi`me tableau le minimum des ´l´ments courants de chacun d’eux.

                u                                             ee                           u
Soit f (n) le coˆt de la fusion de deux tableaux totalisant n ´l´ments. La fonction C de coˆ t du
                                                e
tri fusion d’un tableau de taille n satisfait l’´quation :

                                                  n
                                        C(n) = 2C( ) + f (n)            f (n) ∈ Θ(n)
                                                  2
9.4. TRI PAR TAS                                                                                 167


                              TriFusion(A, d, f)

                              si d<f
                                         (d + f)
                                   m ←
                                            2
                                   TriFusion(A, d, m)
                                   TriFusion(A, (m+1), f)
                                   Fusion(A, d, m, f)



                                         Figure 9.5 – Tri fusion


      e                     a e                e    e
Cette ´quation est analogue ` l’´quation (9.1) ´tudi´e dans l’analyse dans le pire des cas du tri
                   e
rapide. On a montr´ que sa solution est en Θ(nlg(n)).

  e e
Th´or`me 9.3 Le tri fusion est en Θ(nlg(n)).

On montrera dans la section suivante que ce tri est optimal. En revanche, il ne s’agit pas d’un tri
                                    e
en place en ce sens que la fusion n´cessite des recopies dans un tableau auxiliaire (cf. l’exercice
                                                                                    e
9.3). En ce sens, il est moins performant en moyenne que le tri rapide qui ne pr´sente pas cet
       e                       e
inconv´nient. Ce dernier peut ˆtre toutefois quadratique dans le pire des cas.

                                       e     e                                       a
Pour une mise en œuvre et une analyse d´taill´e du tri fusion, on pourra se reporter ` l’exer-
cice 9.3 de la section 9.7.


9.4        Tri par tas
            ee       e      c
   Ce tri a ´t´ trait´ de fa¸on exhaustive dans la section 7.5.


9.5                 e         e
           Complexit´ du probl`me du tri
  e e                e
Th´or`me 9.4 Le probl`me du tri est en Θ(nlg(n)) dans le pire des cas.

              e                                                                             ee
Nous allons d´montrer que, dans le pire des cas, il faut lg2 (n!) comparaisons pour trier n ´l´ments,
                                             c
et ce quel que soit l’algorithme choisi. Pla¸ons nous tout d’abord dans le cas d’un ensemble ` 3 a
ee                               ee       a                     e
´l´ments. Soit a1 , a2 et a3 les ´l´ments ` trier. L’arbre de d´cision suivant exprime les comparai-
      a                            ee                                           a
sons ` effectuer pour ranger les ´l´ments par ordre croissant. Une descente ` gauche signifie que
    e                                                  a
le r´sultat de la comparaison est vrai, une descente ` droite qu’il est faux.
                           a1 ≤ a2


             a2 ≤ a3                           a1 ≤ a3


a1 a2 a3         a1 ≤ a3          a2 a1 a3         a2 ≤ a3


           a1 a3 a2    a3 a1 a2              a2 a3 a1    a3 a2 a1

              e e                 a ee                                         a
Dans le cas g´n´ral d’un ensemble ` n ´l´ments, cet arbre est un arbre binaire ` n! feuilles (autant
                                                                                              a
de feuilles que de permutations d’un ensemble de cardinal n). Le nombre de comparaisons ` faire
168                                                              CHAPITRE 9. ALGORITHMIQUE DU TRI

               ee           e                          a
pour trier les ´l´ments est ´gal, dans le pire des cas ` la hauteur h de l’arbre. Or un arbre binaire
de hauteur h a au plus 2h feuilles (ce nombre est atteint quand tous les niveaux de l’arbre sont
remplis). On en d´duit donc que n! ≤ 2h et donc que lg2 (n!) ≤ h.
                   e
Par suite, tout tri requiert au moins lg2 (n!) comparaisons dans le pire des cas. Or lg2 (n!) =
 n
      lg2 (i) ∈ Θ(nlg(n))
i=2
Du point de vue asymptotique, le tri fusion et le tri par tas sont donc optimaux et le tri ra-
pide est optimal en moyenne.


9.6                 e
           Complexit´ des algorithmes diviser pour r´gner
                                                    e
                                              e
     Les algorithmes de type diviser pour r´gner (divide and conquer) sont ceux qui divisent le
      e    a e                                           e
probl`me ` r´soudre en un certain nombre de probl`mes identiques, dont les tailles sont stricte-
         e                e                                             e
ment inf´rieures au probl`me initial et sur lesquels ils se rappellent r´cursivement. Le tri fusion et le
                                                                                      e
tri rapide en sont deux exemples. Sur un tableau de taille n, le premier se rappelle r´cursivement sur
deux probl`mes de taille ⌊n/2⌋ et ⌈n/2⌉, puis combine les deux r´sultats par une fusion. Le second
            e                                                        e
partitionne l’ensemble des ´l´ments en deux tableaux de tailles k et n − k (avec 0 < k < (n − 1))
                             ee
                    e
puis se rappelle r´cursivement sur ces deux tableaux. Un autre exemple est celui des tours de
      ı                     u      e               e                                    e
Hano¨ (cf. section 5.4.2) o` l’on r´soud un probl`me de taille n par deux rappels r´cursifs sur des
probl`mes de taille n − 1.
      e

                         e                           e
En ce qui concerne l’´valuation de la complexit´ en temps de ce type d’algorithmes, un cas
   e                     u        e                                 e               e
int´ressant est celui o` le probl`me initial de taille n, est divis´ en a sous-probl`mes identiques
              e                                                    u
et tous de mˆme taille n/b, avec b > 1. Si l’on note C(n) le coˆt de l’algorithme sur une donn´e e
                           u       e          e         a                     e                e
de taille n et f (n) le coˆt des op´rations n´cessaires ` la scission du probl`me en sous-probl`mes
                              e                    e                       e
et de la combinaison des r´sultats des sous-probl`mes pour obtenir le r´sultat final, on obtient la
formule :
                                                    n
                                       C(n) = aC( ) + f (n)
                                                     b
                                   n
Si n n’est pas un multiple de b,      doit ˆtre compris comme ⌊n/b⌋ ou ⌈n/b⌉. Supposons dans un
                                           e
                            l
                                    b
premier temps que n = b et donc l = lgb (n). On obtient le calcul suivant :
              C(n)       = a      C(n/b)+f(n)
a      × |    C(n/b)     = a      C(n/b2 )+f(n/b)
a2     × |    C(n/b2 )   = a      C(n/b3 )+f(n/b2 )
                         ...
al−1 × |      C(n/bl−1 ) = a      C(1)+ f(n/bl−1 )
al × |        C(1)       =               f(1)

                                lgb (n)                                        lgb (n)−1
                                           i     i         lgb (n)
              C(n)          =             a f (n/b ) = a             f (1) +               ai f (n/bi ) + f (n)
                                 i=0                                             i=1



      Sachant que algb (n) = nlgb (a) , on obtient :
                                                            lgb (n)−1
                            C(n) = nlgb (a) f (1) +                     ai f (n/bi ) + f (n)
                                                               i=1

                                            e
Le comportement asymptotique de C(n) d´pend donc du terme dominant de la somme ci-dessus.
Le premier terme repr´sente la contribution des nlgb (a) cas terminaux (de taille 1). Le second celle
                      e
               e                   e                     e
des sous-probl`mes de taille interm´diaire entre le probl`me initial et les cas terminaux. Le dernier
    e            u      e                                        e                      e
repr´sente le coˆt suppl´mentaire pour obtenir les a sous-probl`mes et agencer leur r´sultats pour
9.7. EXERCICES                                                                                     169

            e                                                        e                 u
obtenir le r´sultat final. On admettra que cette formule est valable mˆme dans les cas o` n n’est pas
                           e e                         e
une puissance de b. Le th´or`me suivant fournit imm´diatement le comportement asymptotique
de la fonction C dans un grand nombre de cas.
Th´or`me 9.5 (“Master theorem”) Soit C : I → I + une fonction d´finie par la relation
   e e                                              N   R            e
    e
de r´currence :
                                                   n
                                      C(n) = aC( ) + f (n)
                                                   b
o` a ≥ 1 et b > 1 sont deux constantes r´elles et f : I → I + une fonction. Trois cas sont
 u                                           e          N  R
                    e e
envisageables, synth´tis´s dans le tableau ci-dessous :



                       e
                 Hypoth`ses                        Conclusions


        ∃ǫ > 0, f (n) = O(nlgb (a)−ǫ )            C(n) = Θ(nlgb a )         premier terme dominant


      ∃k ≥ 0, f (n) = Θ(nlgb a lg(n)k )   C(n) = Θ(nlgb a lg(n)(k+1) )      tous les termes comptent


       ∃ǫ > 0, f (n) = Ω(nlgb (a)+ǫ )
        ∃c < 1, a.f (n/b) ≤ c.f (n)              C(n) = Θ(f (n))            dernier terme dominant
         pour n suffisament grand

                    u                            e                                   u
On a vu que le coˆt du tri fusion est donn´ par : C(n) = 2C(n/2) + f (n), o` f (n) = Θ(n). Ici
a = b = 2, donc lgb (a) = 1 et par suite f (n) = Θ(nlgb (a) ). C’est la deuxi`me ligne du tableau qui
                                                                               e
                                                                                       e
s’applique ici : on retrouve le fait que le tri fusion est en Θ(nlg(n)). Il en est de mˆme pour le pire
des cas de l’algorithme du tri rapide.


9.7      Exercices
                                                                 e
Exercice 9.1 Trouver le comportement asymptotique des fonctions d´finies par les relations sui-
vantes :
  1. C(n) = 9C(n/3) + n
  2. C(n) = C(2n/3) + 1
  3. C(n) = 3C(n/4) + nlg(n)
  4. C(n) = 2C(n/2) + nlg(n)

                     e                     e
Exercice 9.2 Consid´rons deux matrices carr´es A et B d’ordre n. L’algorithme ci-dessous cal-
cule leur produit C.

          a
Pour i =1 ` n faire
             a
   Pour j =1 ` n faire
      s ← 0
                a
      Pour k =1 ` n faire
         s ← s + Aik ∗ Bkj
      Cij ← s

                          e                                      e e
1- Quelle est la complexit´ de cet algorithme (la taille des donn´es ´tant le format n des matrices,
       e                     e                                               ee                  e
les op´rations significatives ´tant les additions et les multiplications de r´`ls qui sont suppos´es
faites en temps constant) ?
170                                                 CHAPITRE 9. ALGORITHMIQUE DU TRI


On suppose maintenant que n = 2k , o` k est un entier positif. On peut d´composer la matrice
                                       u                                e
    e                                   e
carr´e d’ordre n en quatre matrices carr´es d’ordre n/2.
                   A11   A12                B11   B12                        C11   C12
           A=                        B=                     C = A∗B =
                   A21   A22                B21   B22                        C21   C22
                                e           e         e     e
On obtient ainsi un algorithme r´cursif fond´ sur les ´galit´s suivantes :
                C11 = A11 ∗ B11 + A12 ∗ B21             C12 = A11 ∗ B12 + A12 ∗ B22
                C21 = A21 ∗ B11 + A22 ∗ B21             C22 = A21 ∗ B12 + A22 ∗ B22
                           e                           e              e
2- Donner une relation de r´currence sur le nombre d’op´rations effectu´es par cet algorithme en
                                          e
fonction du format n des matrices et en d´duire le comportement asymptotique de la complexit´ e
en temps.

                  ee         e             e                                            e
Une seconde propri´t´ permet ´galement de r´aliser le produit de matrices. Elle est fond´e sur
     e   e                             e
la mˆme d´composition des matrices carr´es d’ordre n en quatre matrices d’ordre n/2. En po-
sant :
           P1   = (A11 + A22 ) ∗ (B11 + B22 )           P2 = (A21 + A22 ) ∗ B11
           P3   = A11 ∗ (B12 − B22 )                    P4 = A22 ∗ (−B11 + B21 )
           P5   = (A11 + A12 ) ∗ B22                    P6 = (−A11 + A21 ) ∗ (B11 + B12 )
           P7   = (A12 − A22 ) ∗ (B21 + B22 )
        e                                        e
on en d´duit un algorithme de type diviser pour r´gner pour calculerla matrice produit C. Il repose
sur les relations suivantes :
                    C11 = P1 + P4 − P5 + P7             C12 = P3 + P5
                    C21 = P2 + P4                       C22 = P1 − P2 + P3 + P6
                       e
3- Trouver la complexit´ de cet algorithme.
Exercice 9.3 On se propose de programmer en Java le tri fusion sur des tableaux d’entiers. On
                                             e
utilisera pour ce faire la classe Tris ci-apr`s.
public class Tris{

      static void affiche(int[]t){
           int i=0;

          while(i<t.length)
              System.out.print(t[i++]+ "          ");
          System.out.println();
      }

      public static void main (String [] args){

          int[] t = new int[args.length];
          int i;

          for(i=0;i<args.length;i++)
              t[i]=Integer.parseInt(args[i]);

          affiche(t);
          Tri_Fusion.tri(t);
          affiche(t);
      }
}
9.7. EXERCICES                                                                                      171

                                                                  e
Il s’agit donc de concevoir la classe Tri Fusion, comportant une m´thode tri effectuant un tri fusion
sur le tableau en argument.

       e                         e                              e
Consid´rons tout d’abord le probl`me de la fusion. Etant donn´es deux portions de tableaux, sup-
   e        e                                                                      e            a
pos´es rang´es par ordre croissant, on se propose de les fusionner dans un troisi`me tableau T, `
                                                                      e
partir d’un certain indice d. Ainsi sur les deux portions de tableau d´crites ci-dessous :
                                d1                         f1
                                  c                          c
      T1               ...       3      4     5      8     10       ...
                                 d2                                f2
                                  c                                 c
      T2               ...       -2     6     7      11    12     14       ...


                    e
on doit obtenir le r´sultat suivant :


                   d
                    c
      T     ...   -2         3   4      5     6      7      8     10      11     12    14     ...

                    e                                        e
1- Ecrire une m´thode fusionne qui prend en param`tres les tableaux T1 , T2 et T ainsi que les
                                                                  e                                  e
entiers d1 , f1 , d2 , f2 et d et qui fusionne les parties concern´es de T1 et T2 dans T comme indiqu´
sur l’exemple.

Consid´rons maintenant un tableau t pass´ en variable priv´e de la classe Tri Fusion. Soit
        e                                     e                   e
   e                                                                e                      e
(d´but, fin) un couple d’entiers. En supposant que les deux moiti´s de la portion de t d´butant `   a
           e                                  ee e
l’indice d´but et s’achevant l’indice fin ont ´t´ tri´es, il s’agira alors de les fusionner en place.
                                       e                                               e
L’expression en place signifie que le r´sultat de la fusion doit se retrouver dans la mˆme partie de
              e     a           e
t (celle qui d´bute ` l’indice d´but).

    e                                                                    e         e
2- D´montrer rigoureusement qu’il suffit pour cela de recopier la premi`re moiti´ de la portion
       e                                                             e
concern´e de t dans un tableau auxiliaire aux, puis de fusionner le r´sultat de cette copie avec la
     e         e
deuxi`me moiti´ dans le tableau t dans t.

                     e                                                                  e
3- Ecrire alors une m´thode fusion qui effectue cette fusion en place pour deux entiers d´but
          e          e                                      e
et fin pass´s en param`tre. Les tableaux t et aux sont suppos´s construits par ailleurs.

4- Ecrire une m´thode tri fusion qui applique le tri fusion ` un segment du tableau t dont les
                 e                                          a
            e                      e          e
indices de d´but et de fin sont pass´s en param`tres.

5- Terminer l’´criture de la classe Tri Fusion en donnant une m´thode tri qui prend en param`tre
              e                                                e                            e
un tableau A et lui applique le tri fusion.

								
To top