Choisissez votre style : style 1,    style 2,    sans commentaire,    impression,    ancien.

Correction
Tableaux et chaînes de caractères

Exercice 1 Exercice 2 Exercice 3 Exercice 4 Exercice 5 Exercice 6

Exercice 1 : générateur automatique de lettres

Exercice n°16 (pages 54 et 217) de l'ouvrage C++ par la pratique.

Première version du code :

#include <iostream>
using namespace std;
 
void genereLettre()
{
  cout 
    << "Bonjour chère Mireille,"                                     << endl
    << "Je vous écris à propos de votre cours."                      << endl
    << "Il faudrait que nous nous voyons le 18/12 pour en discuter." << endl
    << "Donnez-moi vite de vos nouvelles !"                          << endl
    << "Amicalement, John."                                          << endl;
}
 
int main()
{
  genereLettre();
  return 0;
}
 

Seconde version du code :

#include <iostream>
#include <string>
using namespace std;
 
void genereLettre(bool masculin, string destinataire, string sujet,
                  unsigned int jour, unsigned int mois,
                  string politesse, string auteur)
{
  cout << "Bonjour ";
  if (masculin) cout << "cher";
  else cout << "chère";
  cout << " " << destinataire << "," << endl;
 
  cout 
    << "Je vous écris à propos de " << sujet << endl
    << "Il faudrait que nous nous voyons le " << jour << "/" << mois 
    << " pour en discuter." << endl
    << "Donnez-moi vite de vos nouvelles !" << endl
    << politesse << ", " << auteur << endl;
}
 
int main()
{
  genereLettre(false, "Mireille", "votre cours" , 18, 12,
               "Amicalement", "John");
  cout << endl;
  genereLettre(true, "John", "votre demande de rendez-vous", 16, 12,
               "Sincèrement", "Mireille");
  return 0;
}
 

On peut bien sûr optimiser le code précédent en évitant les copies de chaînes de caractères dans le passage d'arguments en remplaçant les arguments de type string par des passages par références constantes (string const&) ou, encore mieux, depuis C++17 par des string_view.


Exercice 2 : segmentation en mots

Exercice n°19 (pages 56 et 219) de l'ouvrage C++ par la pratique.

Note : en version C++17, on préfèrera remplacer les const string& de la solution ci-dessous par des string_view.

#include <iostream>
#include <string>
using namespace std;
 
bool nextToken(const string& str, size_t& from, size_t& len);
bool issep (char c); // teste si le caractère est un separateur
 
int main()
{
  string phrase;
 
  cout << "Entrez une chaîne : ";
  getline(cin,  phrase);
 
  cout << "Les mots de \"" << phrase << "\" sont :" << endl;
  size_t debut(0);
  size_t longueur(0);
  while (nextToken(phrase, debut, longueur)) {
    cout << "'" << phrase.substr(debut, longueur) << "'" << endl;
    debut += longueur;
  }
  return 0;
}
 
/* La fonction suivante teste si le caractère est un séparateur.
 *
 * Ecrire une fonction présente l'avantage de pouvoir redéfinir facilement 
 * la notion de séparateur (et éventuellement d'en définir plusieurs).
 */
bool issep (char c)
{
  return (c == ' ');
}
 
/* Il y a de multiples façons d'écrire cette fonction.
 * Nous trouvons celle-ci est assez élégante.
 */
bool nextToken(const string& str, size_t& from, size_t& len)
{
  const size_t taille(str.size());
 
  // saute tous les separateurs à partir de from
  while ((from < taille) and issep(str[from])) ++from;
 
  // avance jusqu'au prochain séparateur ou la fin de str
  len = 0;
  for (size_t i(from); ((i < taille) and not issep(str[i])); ++len, ++i);
 
  return (len != 0);
}
 

Commentaires

Le corrigé proposé pour nextToken est assez compact (cf commentaire donné avant) et nécessite d'avoir bien compris les structures de contrôle (while, for), ce qui le rend un peu plus difficile à comprendre que d'autres solutions.

Comme indiqué, vous pouvez le faire de plein de façons différentes ; mais examinons justement celle proposée :

bool nextToken (string const& str, int& from, int& len)
{
   int const taille(str.size());

Bon, jusque là je pense que ça va... ; juste peut être préciser les arguments :

Continuons avec :

while ((from < taille) && issep(str[from])) ++from;

from est ici incrémenté(/augmenté) du nombre de séparateurs rencontrés. En effet, on ne peut pas commencer le nouveau « token » par un séparateur.

Comment fait-on pour sauter tous ces séparateurs ?
-> tant que le caractère courant est un séparateur, on avance. Ça, c'est le « while (issep(str[from])) ».

Mais il faut penser aussi à ne pas déborder de la chaîne (imaginez le cas où la chaîne ne contient que des séparateurs). Ça, c'est le « (from < taille) » dans le test du while.

Passons au bloc suivant. Son but est de trouver la fin du « token » (puisqu'on vient de trouver le début avec la boucle while précédente).

Cette fin de « token » est en fait indiquée par la longueur, len, du « token ». Au départ le « token » est vide (on a très bien pu sortir du while précédent par la condition « from ≥ taille ». Repensez encore une fois au cas d'une chaîne ne contenant aucun « token », mais uniquement des séparateurs). On a donc :

len = 0;

Puis on cherche la fin du « token » ; c'est-à-dire le prochain séparateur. C'est-à-dire que tant que l'on n'a pas de séparateur (« !issep(str[i]) »), on avance.

Ca veut dire quoi « on avance » ?
-> on passe au caractère suivant, ça c'est le « ++i », et on met à jour la taille du « token » en la faisant augmenter de 1, ça c'est le « ++len ».

D'où part-on ?
-> du caractère « from » précédemment trouvé.

Il ne reste plus qu'à ne pas oublier de ne pas déborder de la chaîne (« i < taille »), et le tour est joué :

for (int i(from); ((i < taille) && !issep(str[i])); ++len, ++i);

Pour essayer d'être encore plus clair, ceci peut aussi s'écrire de la façon moins compacte suivante (rappel : « from » représente le début de « token ») :

 bool continuer(true);
 int position_courante(from);
 
 do {
	// si on déborde de la chaine il faut s'arrêter
	if (position_courante >= taille) {
		continuer = false ;
	}
 
	// si on rencontre un séparateur, il faut s'arrêter
        // (c'est la fin du token)
	else if (issep(str[position_courante])) {
		continuer = false ;
	}
 
	// sinon, tout va bien, ...
	else {
		// ...on augmente la longueur du token de 1...
		len = len + 1;
 
		// ...et on passe au caractère suivant.
		++position_courante;
	}
 
 } while (continuer);
 

Voilà pour cette partie.

Et pour finir, on renvoit (« true » si on a trouvé un « token », c.-à-d. si len n'est pas nulle, et « false » sinon. Soit :

return (len != 0);
qui revient exactement à
if (len != 0) {
	return true;
} else {
	return false;
}

Exercice 3 : placements sans recouvrements

Exercice n°21 (pages 58 et 224) de l'ouvrage C++ par la pratique.

Le code fourni ici est en C++11. Pour une version compilant avec l'ancien standard (C++98) voir ci-dessous.

#include <iostream>
#include <array>
using namespace std;
 
constexpr size_t DIM(10);
 
// --------------------------------------------------------------------
bool remplitGrille(array<array<bool, DIM>, DIM>& grille,
                   unsigned int ligne, unsigned int colonne,
                   char direction, unsigned int longueur)
{
  int dx, dy;
 
  switch (direction) {
  case 'N': dx = -1; dy =  0; break;
  case 'S': dx =  1; dy =  0; break;
  case 'E': dx =  0; dy =  1; break;
  case 'O': dx =  0; dy = -1; break;
  }
 
  unsigned int i(ligne);     // les coordonnées de la case...
  unsigned int j(colonne);   // ...à modifier
  unsigned int l;            // la longueur modifiée
 
  bool possible(true); // est-ce possible de mettre tout l'élément ?
 
  // avant de modifier la grille, il faut vérifier si c'est possible de
  // mettre tout l'objet.
  for (// Initialisation 
       l = 0;
 
       /* Condition de continuation. Vu que i et j sont des "unsigned" *
        * pas besoin de tester ici les condition (i >= 0) et (j >= 0), *
        * lesquelles sont forcément vraies                             */
       (possible) and (i < grille.size()) and (j < grille[0].size()) 
         and (l < longueur);
 
       // Incréments
       ++l, i += dx, j += dy) {
    if (grille[i][j]) // c'est-à-dire la grille est déjà occupée
    {
      possible = false;  // on ne peut donc pas mettre tout l'objet voulu
    }
  }
 
  /* Si l == longueur c'est qu'on a pu tout placer.
   * Il se pourrait en effet qu'on soit sorti de la boucle ci-dessus
   * parce que i >= DIM ou j >= DIM... ...ce qui n'a pas modifié
   * "possible" jusqu'ici.
   */
  possible = possible and (l == longueur); 
 
  if (possible) {
    // on met effectivement l'objet, plus besoin de test ici
    for (l = 0, i = ligne, j = colonne; l < longueur; 
         ++l, i += dx, j += dy) {
      grille[i][j] = true;
    }
  }
 
  return possible;
}
 
// --------------------------------------------------------------------
void initGrille(array<array<bool, DIM>, DIM>& grille) 
{
  for (auto& ligne : grille) 
    for (auto& cell : ligne) 
      cell = false;
}
 
// --------------------------------------------------------------------
void ajouterElements(array<array<bool, DIM>, DIM>& grille)
{
  int x, y;
  do {
    cout << "Entrez coord. x : ";
    cin >> x;
 
    if (x >= 0) {
      cout << "Entrez coord. y : ";
      cin >> y;
 
      if (y >= 0) {
 
        char dir;
        do {
          cout << "Entrez direction (N,S,E,O) : ";
          cin >> dir;
        } while ((dir != 'N') and (dir != 'S') and
                 (dir != 'E') and (dir != 'O'));
 
        cout << "Entrez taille : ";
        unsigned int l;
        cin >> l;
 
        cout << "Placement en (" << x << "," << y << ") direction "
             << dir << " longueur " << l << " -> ";
 
        if (remplitGrille(grille, x, y, dir, l))
          cout << "succès";
        else
          cout << "ECHEC";
        cout << endl;
 
      }
    }
  } while ((x >= 0) and (y >= 0));
}
 
// --------------------------------------------------------------------
void afficheGrille(const array<array<bool, DIM>, DIM>& grille)
{
  for (auto ligne : grille) {
    for (auto cell : ligne) {
      if (cell) cout << '#';
      else      cout << '.';
    }
    cout << endl;
  }
}
 
// --------------------------------------------------------------------
int main()
{
  array<array<bool, DIM>, DIM> grille;
  initGrille(grille);
  ajouterElements(grille);
  afficheGrille(grille);
  return 0;
}
 

Voici la version en «ancien» code (C++98) avec des tableaux à la C :

#include <iostream>
using namespace std;
 
const unsigned int DIM(10);
 
// --------------------------------------------------------------------
bool remplitGrille(bool grille[DIM][DIM],
                   unsigned int ligne, unsigned int colonne,
                   char direction, unsigned int longueur)
{
  int dx, dy;
 
  switch (direction) {
  case 'N': dx = -1; dy =  0; break;
  case 'S': dx =  1; dy =  0; break;
  case 'E': dx =  0; dy =  1; break;
  case 'O': dx =  0; dy = -1; break;
  }
 
  unsigned int i(ligne);     // les coordonnées de la case...
  unsigned int j(colonne);   // ...à modifier
  unsigned int l;            // la longueur modifiée
 
  bool possible(true); // est-ce possible de mettre tout l'élément ?
 
  // avant de modifier la grille, il faut vérifier si c'est possible de
  // mettre tout l'objet.
  for (// Initialisation 
       l = 0;
 
       /* Condition de continuation. Vu que i et j sont des "unsigned" *
        * pas besoin de tester ici les condition (i >= 0) et (j >= 0), *
        * lesquelles sont forcément vraies                             */
       (possible) and (i < DIM) and (j < DIM) and (l < longueur);
 
       // Incréments
       ++l, i += dx, j += dy) {
    if (grille[i][j]) // c'est-à-dire la grille est déjà occupée
    {
      possible = false;  // on ne peut donc pas mettre tout l'objet voulu
    }
  }
 
  /* Si l == longueur c'est qu'on a pu tout placer.
   * Il se pourrait en effet qu'on soit sorti de la boucle ci-dessus
   * parce que i >= DIM ou j >= DIM... ...ce qui n'a pas modifié
   * "possible" jusqu'ici.
   */
  possible = possible and (l == longueur); 
 
  if (possible) {
    // on met effectivement l'objet, plus besoin de test ici
    for (l = 0, i = ligne, j = colonne; l < longueur; 
         ++l, i += dx, j += dy) {
      grille[i][j] = true;
    }
  }
 
  return possible;
}
 
// --------------------------------------------------------------------
void initGrille(bool grille[DIM][DIM]) 
{
  for (unsigned int i(0); i < DIM; ++i) 
    for (unsigned int j(0); j < DIM; ++j) 
      grille[i][j] = false;
}
 
// --------------------------------------------------------------------
void ajouterElements(bool grille[DIM][DIM])
{
  int x, y;
  do {
    cout << "Entrez coord. x : ";
    cin >> x;
 
    if (x >= 0) {
      cout << "Entrez coord. y : ";
      cin >> y;
 
      if (y >= 0) {
 
        char dir;
        do {
          cout << "Entrez direction (N,S,E,O) : ";
          cin >> dir;
        } while ((dir != 'N') and (dir != 'S') and
                 (dir != 'E') and (dir != 'O'));
 
        cout << "Entrez taille : ";
        unsigned int l;
        cin >> l;
 
        cout << "Placement en (" << x << "," << y << ") direction "
             << dir << " longueur " << l << " -> ";
 
        if (remplitGrille(grille, x, y, dir, l))
          cout << "succès";
        else
          cout << "ECHEC";
        cout << endl;
 
      }
    }
  } while ((x >= 0) and (y >= 0));
}
 
// --------------------------------------------------------------------
void afficheGrille(bool grille[DIM][DIM])
{
  for (unsigned int i(0); i < DIM; ++i) {
    for (unsigned int j(0); j < DIM; ++j) {
      if (grille[i][j]) 
        cout << "#";
      else
        cout << ".";
    }
    cout << endl;
  }
}
 
// --------------------------------------------------------------------
int main()
{
  bool grille[DIM][DIM];
  initGrille(grille);
  ajouterElements(grille);
  afficheGrille(grille);
 
  return 0;
}
 

Exercice 4 : Décimal en binaire

/* ======================================================================
 *
 * Decimal to binary and reverse conversions.
 *
 * Version: 1.0 (170901)
 * Author: J.-C. Chappelier (EPFL, 2017)
 *
 * ======================================================================
 */
#include <iostream>
#include <string>
using namespace std;
 
/* ======================================================================
 * This is the first algorithm of interest:
 * convertion from positive decimal to binary.
 */
string binary_nonnegative(int n)
{
  if (n <= 0) {
    return "0"s;
  }
 
  // here n >= 1
  string output;
  while (n > 0) {
    output = char('0' + n%2) + output;
    /* We could also make use of a less compact form, like:
       if (n%2 == 1) {
           output = '1' + output;
       } else {
           output = '0' + output;
       }
    */
 
    n /= 2;
  }
 
  return output;
}
 
/* ======================================================================
 * This is the second algorithm of interest:
 * convertion from positive binary (no sign bit) to decimal.
 */
unsigned int decimal_nonnegative(string const& binary)
// Note: from C++-17, make use of string_view rather than "string const&"
{
  unsigned int output(0);
  unsigned int power(1);
 
  /* There are more advanced way to iterate over a const string in C++ (crbegin(),
   * crend()). This is the very basic way for beginners in C++ programming.
   */
  if (not binary.empty()) {
    for (size_t i(binary.size() - 1); i < binary.size(); --i) {
      if (binary[i] == '1') {
        output += power;
      }
      power *= 2;
    }
  }
 
  return output;
}
 
/* ======================================================================
 * This is the third algorithm of interest:
 * two's complement of binary string
 */
string twos_complement(string const& binary)
// Note: from C++-17, make use of string_view rather than "string const&"
{
  string output(binary); // starts from a copy
 
  if (not binary.empty()) {
 
    // invert all bits left to the least-signicant 1 (LS1)
 
    // first search for LS1
    /* There are more advanced way to iterate over a string in C++ (cbegin(),
     * cend()). This is the very basic way for beginners in C++ programming.
     */
    size_t i(output.size() - 1);
    while ((i < output.size()) and (output[i] != '1')) --i;
 
    // skip that LS1
    --i;
 
    // then invert all "higher" bits
    while (i < output.size()) {
      if (output[i] == '1') {
        output[i] = '0';
      } else {
        output[i] = '1';
      }
      --i;
    }
  }
 
  return output;
}
 
/* ======================================================================
 * This is the forth algorithm of interest:
 * convertion from (signed) decimal to binary, making use of former algorithms.
 */
string binary(int n)
{
  // Add the bit sign to the corresponding "unsigned" representation
  if (n < 0) {
    return "1"s + twos_complement(binary_nonnegative(-n));
    /* could also be written as:
     * return twos_complement("0"s + binary_nonnegative(-n));
     */
  }
  return "0"s + binary_nonnegative(n);
}
 
/* ======================================================================
 * This is the fifth algorithm of interest:
 * convertion from binary (with sign bit) to decimal.
 */
int decimal(string const& binary)
// Note: from C++-17, make use of string_view rather than "string const&"
{
  // test sign bit
  if (not binary.empty() and (binary[0] == '1')) {
    return -decimal_nonnegative(twos_complement(binary));
  }
 
  return decimal_nonnegative(binary);
}
 
/* ======================================================================
 * All the rest below is not the core of the exercise but is just for
 * convenience.
 */
 
/* ======================================================================
 * Tool function: test an int value and its binary writing
 */
void test(int n)
{
  const string result(binary(n));
  cout << n << " is " << result << endl;
  cout << "and " << result << " is indeed " << decimal(result) << '.' << endl;
}
 
/* ======================================================================
 * Tool function: ask for some integer.
 */
int require_int()
{
  int value(0);
  cout << "Enter some integer: ";
  cin >> value;
  return value;
}
 
/* ======================================================================
 * Tool function: ask to continue or not.
 */
bool go_ahead() {
  char read('x');
  do {
    cout << "Shall we continue (y/n)? ";
    cin >> read;
  } while ((read != 'y') and (read != 'Y') and
           (read != 'n') and (read != 'N'));
  return (read == 'y') or (read == 'Y');
}
 
// ======================================================================
int main()
{
  const int t(42);
  cout << binary_nonnegative(t) << endl;
  test(t);
  test(-t);
 
  do {
    test(require_int());
  } while (go_ahead());
 
  return 0;
}
 

Exercice 5 : Entropie

#include <iostream>
#include <cmath> // pour log()
#include <cctype> // pour isalpha() et toupper()
#include <vector>
#include <array>
using namespace std;
 
// ======================================================================
typedef vector<double> Distribution;
 
// ======================================================================
Distribution calcule_probas(string const& s, bool compter_espace = false)
{
  array<double, 27> frequences; // 28 = 26 caractères alphabétiques + 1 espace
  for (auto& f : frequences) f = 0.0;
  double somme(0.0); /* Nombre de caractères pris en compte. 
                      * N'est pas forcément égal à s.size() si il y a des caractères parasites.
                      * Mis en double pour la division ultérieure. */
 
  for (const char c : s) {
    if (isalpha(c)) {
      ++frequences[toupper(c) - 'A'];
      ++somme;
    } else if ((c == ' ') and (compter_espace)) {
      ++frequences.back();
      ++somme;
    }
  }
 
  // Crée la distribution
  Distribution probas;
 
  for (auto& f : frequences) {
    if (f > 0.0) { // on ne retient que les non nuls ici
      probas.push_back(f / somme);
    }
  }
 
   return probas;
}
 
// ======================================================================
double log2(double x)
{
  if (x == 0.0) return 0.0; // pour éviter le NaN
  return log(x) / log(2.0);
}
 
// ======================================================================
double entropie(Distribution const& probas)
{
  double entropy(0.0);
  for (auto p : probas) {
    entropy -= p * log2(p);
  }
  return entropy;
}
 
// ======================================================================
double entropie(string const& s)
{
  return entropie(calcule_probas(s));
}
 
// ======================================================================
// Tests
// --------------------------------------------------
void test2(Distribution const& probas) {
  cout << "p=(";
  for (auto p : probas) cout << p << ", ";
  cout << ") --> H = " << entropie(probas) << " bit" << endl;
}
// --------------------------------------------------
void test1(double p0) {
  test2({ p0, 1.0 - p0 });
}
// --------------------------------------------------
void test3(string const& s) {
  cout << "Chaîne « " << s << " » :" << endl;
  test2(calcule_probas(s));
  cout << "et directement : H = " << entropie(s) << " bit" << endl;
}
 
// ======================================================================
int main()
{
  // ---- 1) tests entropie binaire
  test1(0.0);
  test1(0.3);
  test1(0.5);
  test1(0.7);
  test1(1.0);
 
  test2({ 0.1, 0.2, 0.3, 0.4 }); 
  test2(Distribution(8, 1.0/8.0));  // équirépartie
 
  test3("IL FAIT BEAU A IBIZA");
  test3("AAAAA");
  test3("A");
 
  cout << "Entrez une chaîne : ";
  string s;
  cin >> s;
  test3(s);
 
  return 0;
}
 

Exercice 6 : tours de Hanoï

Exercice n°35 (pages 85 et 257) de l'ouvrage C++ par la pratique.

Cet exercice est de niveau 3 en raison de sa longueur et de l'appel récursif double (hanoi s'appelle deux fois) qui peut, peut être, troubler une première fois.

Cependant l'énoncé est assez bien détaillé et guide relativement bien.
Voici donc directement le code solution en C++11. Pour une version compilant avec l'ancien standard (C++98), voir ci-dessous.

#include <iostream>
#include <vector>
#include <array>
using namespace std;
 
constexpr unsigned int N(4); // la taille de la tour de départ
 
// un disque sera représenté simplement par sa taille (rayon)
typedef unsigned int Disque; 
// 0 signifiant pas de disque
constexpr Disque PAS_DE_DISQUE(0);
 
typedef vector<Disque> Pilier; // un pilier est une pile de disques
typedef array<Pilier, 3> Jeu;  // le jeu est constitué de 3 piliers
 
// les fonctions
void affiche(char c, unsigned int n = 1);
void affiche(const Disque& d);
void affiche(const Jeu& jeu);
void init(Jeu& jeu, unsigned int taille = N);
size_t sommet(const Pilier& p);
void deplace(Pilier& origine, Pilier& destination);
unsigned int autre(unsigned int p1, unsigned int p2);
void hanoi(unsigned int taille, unsigned int origine,
           unsigned int destination, Jeu& jeu);
 
// ----------------------------------------------------------------------
int main()
{
  Jeu monjeu;
 
  init(monjeu);
  affiche(monjeu);
  hanoi(N, 0, 2, monjeu);
 
  return 0;
}
 
/* ----------------------------------------------------------------------
 * Initialise le jeu
 * ---------------------------------------------------------------------- */
void init(Jeu& jeu, unsigned int taille)
{
  // crée un jeu vide :
  for (auto& pilier : jeu) {
    pilier = Pilier(taille, PAS_DE_DISQUE);
  }
 
  // remplit le 1er pilier :
  for (size_t i(0); (i < jeu[0].size()) and (i < taille); ++i) {
    jeu[0][i] = Disque(i+1);
  }
}
 
/* ----------------------------------------------------------------------
 * Affiche n fois un caractère
 * Entrée : le caractère à afficher et le nombre de fois
 * ---------------------------------------------------------------------- */
void affiche(char c, unsigned int n)
{
  for (unsigned int i(0); i < n; ++i) cout << c;
}
 
/* ----------------------------------------------------------------------
 * Affiche un dique
 * Entrée : le dique à afficher
 * ---------------------------------------------------------------------- */
void affiche(const Disque& d)
{
  if (d == PAS_DE_DISQUE) {
    affiche(' ', N-1);
    affiche('|');
    affiche(' ', N); // N = N-1 de tour vide + 1 espace intermédiaire
  }
  else {
    affiche(' ', N-d);
    affiche('-', 2*d-1);
    affiche(' ', N-d+1); // +1 pour l'espace intermédiaire
  }
}
 
/* ----------------------------------------------------------------------
 * Affiche un jeu
 * Entrée : le jeu à afficher
 * ---------------------------------------------------------------------- */
void affiche(const Jeu& jeu)
{
  for (size_t i(0); i < jeu[0].size(); i++) {
    affiche(jeu[0][i]);
    affiche(jeu[1][i]);
    affiche(jeu[2][i]);
    cout << endl;
  }
  // le socle
  affiche('#', 6*N-1); // 3*(2*N-1)+2 = 6*N-1, ou autre choix...
  cout << endl << endl;
}
 
/* ----------------------------------------------------------------------
 * Retourne l'indice du sommet d'une tour sur un pilier. Retourne N si pas
 * de tour sur ce pilier
 * ---------------------------------------------------------------------- */
size_t sommet(const Pilier& p)
{
  size_t top;
  for (top = 0; (top < p.size()) and (p[top] == PAS_DE_DISQUE); ++top);
  return top;
}
 
/* ----------------------------------------------------------------------
 * Déplace le disque du top d'une tour à un autre pilier.
 * Vérifie que le mouvement est valide.
 * Entrée : le pilier d'où enlever le disque, et le pilier destination
 * ---------------------------------------------------------------------- */
void deplace(Pilier& origine, Pilier& destination)
{
  size_t top1(sommet(origine));
  if (top1 < origine.size()) { // si la tour d'origine existe bien
 
    size_t top2(sommet(destination));
    if ((top2 < destination.size()) and (destination[top2] < origine[top1])) {
      /* on essaye de mettre un disque plus gros (origine[top1])
       * sur un disque plus petit (destination[top2]) : ce n'est pas 
       * un déplacement autorisé !
       */
      cerr << "ERREUR : on ne peut pas déplacer un disque de taille "
	   << origine[top1] << " sur un disque de taille "
	   << destination[top2] << " !" << endl;
      return;
    }
 
    // effectue le mouvement
    destination[top2-1] = origine[top1];
    origine[top1] = PAS_DE_DISQUE;
  }
}
 
/* ----------------------------------------------------------------------
 * Étant donnés deux numéros de pilier p1 et p2, retourne l'indice du 3e
 * pilier
 * ---------------------------------------------------------------------- */
unsigned int autre(unsigned int p1, unsigned int p2)
{
  return 3 - p1 -p2;
}
 
/* ----------------------------------------------------------------------
 * Déplace une tour d'un pilier à un autre.
 * Vérifie que le mouvement est valide.
 * Entrée : la taille de la tour, le pilier de départ, le pilier de destination
 * ---------------------------------------------------------------------- */
void hanoi(unsigned int n, unsigned int origine, unsigned int destination,
	   Jeu& jeu)
{
  if (n != 0) {
    const unsigned int auxiliaire(autre(origine, destination));
    hanoi(n-1, origine, auxiliaire, jeu);
    deplace(jeu[origine], jeu[destination]);
    affiche(jeu);
    hanoi(n-1, auxiliaire, destination, jeu);
  }
}
 

Version C++98, les différences avec la version C++11 sont mises en évidence :

#include <iostream>
using namespace std;
 
const unsigned int N(4); // la taille de la tour de départ
 
// un disque sera représenté simplement par sa taille (rayon)
typedef unsigned int Disque; 
// 0 signifiant pas de disque
const Disque PAS_DE_DISQUE(0);
 
typedef Disque Pilier[N]; // un pilier est une pile d'au plus N disques
typedef Pilier Jeu[3]; // le jeu est constitué de 3 piliers
 
 
// les fonctions
void affiche(char c, unsigned int n = 1);
void affiche(Disque d);
void affiche(Jeu jeu);
void init(Jeu& jeu); /* Note: le passage par référence est ici facultatif
		      * vu que Jeu est un tableau "à la C".
		      * Mais je préfère marquer clairement par ce passage
		      * par référence que l'argument jeu est modifié par
		      * cette fonction */
 
unsigned int sommet(Pilier p);
void deplace(Pilier& origine, Pilier& destination); // même remarque (bis)
unsigned int autre(unsigned int p1, unsigned int p2);
void hanoi(unsigned int n, unsigned int origine, unsigned int destination,
	   Jeu& jeu); // même remarque (ter)
 
// ----------------------------------------------------------------------
int main()
{
  Jeu monjeu;
 
  init(monjeu);
  affiche(monjeu);
  hanoi(N, 0, 2, monjeu);
 
  return 0;
}
 
/* ----------------------------------------------------------------------
 * Initialise le jeu
 * ---------------------------------------------------------------------- */
void init(Jeu& jeu)
{
  for (unsigned int i(0); i < N; ++i) {
    jeu[0][i] = Disque(i+1);
    jeu[1][i] = PAS_DE_DISQUE;
    jeu[2][i] = PAS_DE_DISQUE;
  }
}
 
/* ----------------------------------------------------------------------
 * Affiche n fois un caractère
 * Entrée : le caractère à afficher et le nombre de fois
 * ---------------------------------------------------------------------- */
void affiche(char c, unsigned int n)
{
  for (unsigned int i(0); i < n; ++i) cout << c;
}
 
/* ----------------------------------------------------------------------
 * Affiche un dique
 * Entrée : le dique à afficher
 * ---------------------------------------------------------------------- */
void affiche(Disque d)
{
  if (d == PAS_DE_DISQUE) {
    affiche(' ', N-1);
    affiche('|');
    affiche(' ', N); // N = N-1 de tour vide + 1 espace intermédiaire
  }
  else {
    affiche(' ', N-d);
    affiche('-', 2*d-1);
    affiche(' ', N-d+1); // +1 pour l'espace intermédiaire
  }
}
 
/* ----------------------------------------------------------------------
 * Affiche un jeu
 * Entrée : le jeu à afficher
 * ---------------------------------------------------------------------- */
void affiche(Jeu jeu)
{
  for (unsigned int i(0); i < N; ++i) {
    affiche(jeu[0][i]);
    affiche(jeu[1][i]);
    affiche(jeu[2][i]);
    cout << endl;
  }
  // le socle
  affiche('#', 6*N-1); // 3*(2*N-1)+2 = 6*N-1, ou autre choix...
  cout << endl << endl;
}
 
/* ----------------------------------------------------------------------
 * Retourne l'indice du sommet d'une tour sur un pilier. Retourne N si pas
 * de tour sur ce pilier
 * ---------------------------------------------------------------------- */
unsigned int sommet(Pilier p)
{
  unsigned int top;
  for (top = 0; (top < N) and (p[top] == PAS_DE_DISQUE); ++top);
  return top;
}
 
/* ----------------------------------------------------------------------
 * Déplace le disque du top d'une tour à un autre pilier.
 * Vérifie que le mouvement est valide.
 * Entrée : le pilier d'où enlever le disque, et le pilier destination
 * ---------------------------------------------------------------------- */
void deplace(Pilier& origine, Pilier& destination)
{
  unsigned int top1(sommet(origine));
  if (top1 < N) { // si la tour d'origine existe bien
 
    unsigned int top2(sommet(destination));
    if ((top2 < N) and (destination[top2] < origine[top1])) {
      /* on essaye de mettre un disque plus gros (origine[top1])
       * sur un disque plus petit (destination[top2]) : ce n'est pas 
       * un déplacement autorisé !
       */
      cerr << "ERREUR : on ne peut pas déplacer un disque de taille "
	   << origine[top1] << " sur un disque de taille "
	   << destination[top2] << " !" << endl;
      return;
    }
 
    // effectue le mouvement
    destination[top2-1] = origine[top1];
    origine[top1] = PAS_DE_DISQUE;
  }
}
 
/* ----------------------------------------------------------------------
 * Étant donnés deux numéros de pilier p1 et p2, retourne l'indice du 3e
 * pilier
 * ---------------------------------------------------------------------- */
unsigned int autre(unsigned int p1, unsigned int p2)
{
  return 3 - p1 -p2;
}
 
/* ----------------------------------------------------------------------
 * Déplace une tour d'un pilier à un autre.
 * Vérifie que le mouvement est valide.
 * Entrée : la taille de la tour, le pilier de départ, le pilier de destination
 * ---------------------------------------------------------------------- */
void hanoi(unsigned int n, unsigned int origine, unsigned int destination,
	   Jeu& jeu)
{
  if (n != 0) {
    const unsigned int auxiliaire(autre(origine, destination));
    hanoi(n-1, origine, auxiliaire, jeu);
    deplace(jeu[origine], jeu[destination]);
    affiche(jeu);
    hanoi(n-1, auxiliaire, destination, jeu);
  }
}
 

Dernière mise à jour : $Date: 2017-08-25 11:39:58 +0200 (ven, 25 aoû 2017) $  ($Revision: 233 $)