Vous êtes sur la page 1sur 118

512

PROBLEMES
CORRIGES
PASCAL C++ LISP PROLOG

1995

Louis Gacôgne
Institut d'Informatique d'Entreprise
AVANT - PROPOS

Ce recueil d'exercices est tiré de cours et de travaux dirigés donnés en diverses classes
préparatoires et dans trois écoles d'ingénieurs.

La première partie a pour but de passer en revue les différentes notions communes à la plupart
des langages de programmation classiques : instructions de base, exemples graphiques,
procédures et fonctions, problème du passage des paramètres et types standards d'objets, mais
en insistant d'emblée sur le découpage d'un problème en sous-problèmes. Les exercices et
problèmes de cette première partie sont corrigés en Pascal, qui est le langage le plus
représentatif de la programmation structurée impérative, car le plus cohérent et le plus clair.
Cette partie, grâce aux exemples classiques qui s'y trouvent correspond au premier cycle
universitaire et aux classes préparatoires.
Cette première partie est suivie d'un chapitre sur la programmation par objets qui en est un
prolongement, avec le langage C++. Cependant ce type de programmation nécessite une
grande connaissance des bibliothèques prédéfinies et est surtout adapté aux grandes
applications, c'est pourquoi nous ne présentons que l'essentiel de son apport.

La troisième partie est consacrée à la programmation fonctionnelle à travers le Lisp qui est le
plus répandu et le plus ancien de ces langages. En introduisant le plus tôt possible la
récursivité, et le découpage en petites procédures se passant la main, les chapitres précédents
ont ouvert la voie à ce type de programmation plus adapté à la programmation approfondie.
Les applications que l'on y trouve correspondent pour la plupart à des thèmes d'intelligence
artificielle étudiés en maîtrise ou en école d'ingénieurs.
Enfin nous étudions la programmation logique avec Prolog et un langage de représentation de
connaissances floues, Fril.
Ce panorama des styles de programmation est en outre complété par une annexe sur le Forth.

L'ambition de cet ouvrage n'est pas d'étudier complètement tel ou tel langage, ce qui n'est
nécessaire que lors de la mise au point de grandes applications. Il s'efforce au contraire de
faire comprendre les algorithmes et d'écrire l'essentiel des programmes, en faisant en sorte que
le lecteur n'ait à l'esprit que le minimum de mots ou signes signifiants. (Une cinquantaine pour
Pascal, une vingtaine pour Lisp, alors que la nature des exemples traités est sans doute plus
délicate à saisir, et enfin une demi-douzaine en Prolog.)

Remarque : parmi les 29 exercices ou problèmes des 24 chapitres, 27 d'entre eux ne sont pas
corrigés, soit parcequ'ils ne font que complèter d'autres, soit parceque faciles.
Quatrième de couverture

Ce recueil d'exercices et de problèmes de programmation s'adresse aussi bien aux débutant


qu'aux programmeurs confirmés. Il présente en effet plusieurs états d'esprits dont les deux
principaux sont la programmation classique en Pascal pour les étudiants du premier cycle
universitaire, et la programmation fonctionnelle en Lisp pour le second cycle.
Ce livre constitue un panorama (non exhaustif, mais suffisant) sur les langages de
programmation, et offre une grande variété dans les sujets traités : graphique, calcul
matriciel, traitements de chaînes de caractères, graphes, intelligence artificielle ...

L'auteur

Louis Gacôgne, agrégé de mathématiques et docteur ès sciences, enseigne à l'Institut


d'Informatique d'Entreprise d'Evry qui est une grande école d'ingénieurs du Conservatoire
National des Arts et Métiers, et participe aux recherches du LAFORIA à l'Université Paris
VI, qui est le principal laboratoire en intelligence artificielle.
2

CHAPITRE 1

INTRODUCTION

Les grands principes de programmation à résumer pourraient être les suivants. Tout d'abord
tout problème consiste à fournir des résultats à partir de données, entre les deux il doit donc y
avoir un algorithme c'est à dire des méthodes de calcul (une boîte restant noire pour
l'utilisateur), ce qui prime donc c'est :

Dès que le problème est important, il faudra le découper en morceaux, le hiérarchiser, faire en
sorte que le programme principal soit un directeur faisant appel à des subordonnés, lesquels
feront eux-même appel à des sous-traitants etc... chacun d'entre eux devant spécifier avec
exactitude le travail qu'il demande pour en récupérer les fruits, (problème du passage des
paramètres) mais n'ayant pas vraiment besoin de savoir comment fait son employé ni avec
quels outils. (Pas d'outils servant à tous le monde donc pas de variables globales ou le moins
possible, par contre des outils analogues peuvent porter le même nom.) Cette conception du
partage ouvre la voie à la compilation séparée d'unités ayant ses algorithmes mais aussi ses
propres types d'objets, c'est la :

Enfin une des méthodes essentielles de la programmation évoluée, qui doit à son tour
influencer jusqu'à la façon de penser et d'analyser les problèmes, c'est la :

Créé en 1970 par N. Wirth à Zürich, le langage Pascal permet d'écrire des programmes
relativement bien structurés, pseudo-modulaires (avec des sous-programmes bien distincts),
qui les rendent facilement compréhensibles dans la mesure où on n'est pas avare de
commentaires, où on choisit des "identificateurs "(noms des variables) significatifs, et où on
fait très attention à la mise en page.
Cette qualité de lisibilité est la chose la plus importante à acquérir pour un débutant, la 3
rédaction rigoureuse de tout travail écrit. Une des qualités du Pascal est, encore une fois, cette
lisibilité qui permet de retrouver plus facilement une erreur et qui permet en outre de modifier
facilement certaines parties du programme sans toucher aux autres.

Une fois écrit, un programme Pascal doit subir une compilation pour être exécuté. Cette phase
consiste en une traduction dans le langage (binaire) de la machine, après une vérification de la
morphologie (l'orthographe) des mots employés (mots réservés du Pascal et identificateurs
choisis par le programmeur), une vérification syntaxique (grammaticale) sur les
enchaînements de mots, et enfin une vérification sémantique sur le nombre et le type des
différents objets manipulés par le programme.
Tous ces types d'erreur sont signalés lors de la compilation, par contre les erreurs de
conception, à propos de ce que doit réaliser le programme, ne peuvent être décelées que lors
de son exécution.

La première tâche, en vue de la résolution automatique d'un problème, c'est la spécification


ou description précise du but que l'on se fixe. Il s'agit avant tout d'un travail d'expression
écrite, essentiel pour tous les scientifiques : savoir exposer pour d'autres qu'eux-mêmes, et
notamment pour une machine, les données techniques d'un problème.

En second lieu, il faut déterminer un algorithme, c'est-à-dire une méthode de résolution.


Conformément à l'esprit cartésien on tente de découper le problème en "sous-problèmes"
suffisamment simples pour ce qui concerne leur résolution, et tels, que le problème principal
puisse se planifier suivant une hiérarchie de ces petits sous-problèmes. Cette tâche d'analyse
n'est jamais facile et peut naturellement recevoir bien des solutions différentes dès que le
problème est important.

Comment faire ce découpage ? Peut-on suivre une analyse "descendante" allant de la


compréhension générale d'un problème à sa représentation hiérarchisée ?
Ou bien peut-on avoir une stratégie "ascendante" allant des questions simples et réduites, vers
celles, complexes, qui en sont des assemblages ? Il est très difficile de répondre à ces
questions, l'activité cognitive, même des programmeurs expérimentés, ne suivant pas
nécessairement une stratégie bien fixée.
Il est illusoire, par ailleurs, de vouloir donner à tout prix une description d'algorithme
indépendante du langage de programmation, car celle-ci n'est que l'aboutissement formalisé
d'une suite de descriptions de plus en plus rigoureuses partant du langage naturel souvent
flou, incomplet pour certains détails, redondant pour d'autres, et passant par des descriptions
écrites souvent données par ce qu'on appelle le "pseudo-Pascal".

Plus précisément, les principales instructions d'un langage de description d'algorithme sont :

l'affectation (attribution d'une valeur à une variable notée comme X ← 3 ou X := 3 ),


notons encore qu'une variable est dite incrémentée lorsque sa valeur augmente d'une unité, et
décrémentée lorsqu'elle diminue de 1.
la sélection (si...alors...sinon...),
l'itération (commander un travail répétitif),
le débranchement (aller à... , retourner vers...), et bien sûr, l'appel à un sous-programme et
en particulier :
les entrées et sorties de valeurs au clavier de résultat à l'écran ou vers d'autres périphériques.

Nous allons donner dans cette introduction, tout d'abord un exemple, simple en apparence,
qui montrera les difficultés d'énonciation du problème et de l'algorithme. La recherche de la
rigueur, conduisant nécessairement au travers de plusieurs étapes, à l'utilisation d'un langage
formalisé.
Le second exemple que nous donnons est au contraire assez simple et bien connu pour son
énoncé, mais nous voulons montrer qu'il est indispensable de le décomposer en sous-
problème, si l'on ne veut pas arriver à des formules inextricables.
4
1-1° Exemple de problème simple difficile à spécifier : l'escargot

a) Présentation "humainement compréhensible"


On peut considérer que le schéma ci-dessus constitue déjà une description de ce que l'on veut
faire, mais nous allons préciser le problème lors de différentes phases accompagnées de
discussions plus techniques sur la représentation des données et le partage des tâches.
b) Première description informelle
"Remplir un tableau avec les entiers consécutifs, en partant de la case en haut à gauche
occupée par 1, et en tournant dans le sens de l'horloge, tout en longeant les bords déjà
occupés, et en finissant au centre du tableau sur la dernière case vide."
On voit à la complexité de cette phrase, ne rendant d'ailleurs pas complètement compte du
cheminement, toute la difficulté d'exprimer quelque chose d'évident sur un schéma. On est
conduit à choisir des noms de variables, et à décrire dans l'ordre et par le menu les opérations
qu'il faut effectuer sur ces variables, si l'on veut préciser davantage.
c) Seconde description plus formelle
Soit un tableau A de N lignes et P colonnes et X=1 placé dans la première case. On remplit
alors, tant qu'elles sont vides, les cases de la première ligne, de la dernière colonne, de la
dernière ligne de droite à gauche, et enfin de la première colonne de bas en haut, avec X
incrémenté à chaque fois. Puis on recommence avec le sous-tableau de N-2 lignes et P-2
colonnes encore libre et le X suivant la dernière valeur qui a été placée.
Cette phrase, sans doute plus complète, néglige encore un détail important: celui de l'arrêt du
processus, mais, et c'est ce qui est très important, elle sous-entend l'idée de récurrence sur un
tour de tableau, ce qui représente déjà une idée pour la programmation ultérieure.
Remarque : l'idée suivant laquelle on peut chercher une formulation du terme d'indice i et j
dans A, constitue un autre algorithme qui non seulement risque d'être assez laborieux à
analyser, mais encore n'apporterait pas d'amélioration au programme : il y aura toujours N*P
"affectations". Il est beaucoup plus élégant de construire quatre tâches se succédant.
d) Division du problème en sous-tâches et représentation des données
Parmi tous les programmes (écriture formalisée d'un algorithme) possibles, on choisira celui
qui découpe au maximum le travail à faire, en sous-tâches .
On écrira pour cela quatre "procédures" indépendantes: ligne-haute, colonne-droite, ligne-
basse, colonne-gauche se passant le relais à tour de rôle, avec à chaque fois un paramètre
modifié : le numéro de ligne ou de colonne qui vient d'être rempli.
On désignera par X la dernière valeur logée dans une case, et par L0, L1, C0, C1 les numéros
de lignes et de colonnes à remplir dans le tableau A. En pseudo-Pascal:
LGN.HAUTE (L0, L1, C0, C1)
Pour C incrémentant de C0 à C1 faire X ← X + 1 puis A (L0, C) ← X
Si L0 < L1 alors COL.DROITE (L0 + 1, L1, C0, C1)

COL.DROITE (L0, L1, C0, C1)


Pour L incrémentant de L0 à L1 faire X ← X + 1 puis A (L, C1) ← X
Si C0 < C1 alors LGN.BASSE (L0, L1, C0, C1-1)
LGN.BASSE (L0, L1, C0, C1)
Pour C décrémentant de C1 à C0 faire X ← X + 1 puis A (L1, C) ← X
Si L0 < L1 alors COL.GAUCHE (L0, L1-1, C0, C1)
COL.GAUCHE (L0, L1, C0, C1) 5
Pour L décrémentant de L1 à L0 faire X ← X + 1 puis A (L, C0) ← X
Si C0 < C1 alors LGN.HAUTE (L0, L1, C0 + 1, C1)

L'arrêt pouvant se situer à la fin de l'une quelconque des quatre procédures.

e) Programme en langage évolué


Le programme proprement dit nécessite encore la demande des valeurs de N et P , puis
l'exécution de la machinerie sera lancée par LGN.HAUTE pour les paramètres (1, N, 1, P), X
ayant été préalablement initialisée à 0. Enfin il faut éditer à l'écran le tableau A, on peut bien
sûr concevoir directement cette édition sans utiliser A.
program escargot;
var n, p, x : integer ; {déclaration des 3 variables du programme}
procedure ecrire (var x : integer; L, C : integer); {modifie et écrit X}
begin x := x + 1; gotoxy (5*C - 4, L); write (x) end;
procedure lignehaute (L0, L1, C0, C1 : integer); forward;
{Ce mot "forward" indique au compilateur que la définition de "lignh" se trouve plus loin}

procedure colonnegauche (L0, L1, C0, C1 : integer);


var L : integer;
begin for L := L1 downto L0 do ecrire (x, L, C0);
if c0 < c1 then lignehaute (L0, L1, C0 + 1, C1) end;

procedure lignebasse (L0, L1, C0, C1 : integer);


var C : integer;
begin for C := C1 downto C0 do ecrire (x, L1, C);
if L0 < L1 then colonnegauche (L0, L1 - 1, C0, C1) end;

procedure colonnedroite (L0, L1, C0, C1 : integer);


var L : integer;
begin for L := l0 to L1 do ecrire (x, L, c1);
if C0 < C1 then lignebasse (L0, L1, C0, C1 - 1) end;

procedure lignehaute; { (L0, L1, C0, C1 : integer) ne se répète pas }


var C : integer;
begin for C := C0 to C1 do ecrire (x, L0, C);
if L0 < L1 then colonnedroite (L0 + 1, L1, C0, C1) end;

procedure escargot (n, p : integer);


begin x := 0; lignehaute (1, n, 1, p) end;

{Début du programme réalisé pour 24 lignes et 16 colonnes au maximum}


begin write ('Combien de lignes ? '); readln (n); write ('Combien de colonnes ? '); readln (p);
escargot (n, p) end.

Exécution pour 8 lignes et 12 colonnes.

Dans la suite, on écrira directement en Pascal car il n'y a que les détails de syntaxe vus au
cours des prochains chapitres, qui ne doivent pas arrêter un débutant, celui-ci les apprenant
petit à petit en travaux pratiques, et aussi éventuellement une gêne due à la traduction des
mots anglais. La conception générale des programmes doit, par contre, être étudiée avec soin
sur le papier.
6
1-2° Equation complexe du second degré, construire un programme calculant les deux
solutions complexes d'une équation du type az2 + bz + c = 0 où a, b, c sont trois complexes
donnés.

Ce problème est intéressant, sans même qu'il soit question pour l'instant de définir un type de
donnée complexe, car il doit recevoir en entrée trois complexes c'est à dire six réels, les
parties réelles et imaginaires de a, b, c, et fournir deux sorties complexes, c'est à dire les
quatre réels parties réelles et imaginaires des deux solutions de l'équation. Nous rappelons
que les formules z = (-b ± d) / (2a) où +d et -d sont les racines carrées de ∆ = b2 - 4ac, sont
toujours valables.
Ce problème des racines carrées d'un complexe est à traiter indépendamment de celui de
l'équation et il pourra d'ailleurs être repris pour un autre programme. Considérons
formellement un complexe a + ib, ses racines carrées se trouvent en posant (x + iy)2 = a + ib,
ce qui en développant conduit au système :
x2 - y2 = a
2xy = b
Le fait d'égaler les modules donne une troisième équation (redondante) mais qui permet de
résoudre simplement le système : x2 + y2 = m où m est le module a + ib.
2 2
On a donc x = (m + a) / 2 et y = (m - a) / 2, le signe de b permet d'avoir le signe de y
correspondant à un x positif D'autre part on sait que tout complexe admet deux racines
carrées opposées, ce qui achève d'expliquer le sous-programme ci dessous.
Un autre sous-problème mérite d'être traité à part, c'est celui de la division de deux
complexes, car il intervient deux fois ici.

program complexe;
function module (a, b : real) : real; ( fonction donnant le module d'un complexe a + ib }
begin module := sqrt (sqr (a) + sqr (b)) end;
procedure racinecarree (a, b : real; var x, y : real);
{ calcule une racine carrée x + iy de a + ib, l'autre étant -x - iy}
var m : real;
begin m := module (a, b);
x := sqrt ((m + a) / 2); y := sqrt ((m - a) / 2);
if b < 0 then y := -y end;
procedure division (a, b, c, d : real; var e, f : real); { calcule e + if = (a + ib) / (c + id) }
var r : real;
begin r := sqr (c) + sqr (d);
e := (a*c + b*d) / r; f := (b*c - a*d) / r end;
procedure affiche (a, b : real); {affiche a + ib avec 2 décimales, chaque valeur sur 5 colonnes}
begin writeln (a : 5 : 2, ' + i ', b : 5 : 2) end;

var ar, ai, br, bi, cr, ci, dr, di, zr, zi : real; { sont les variables du programme proprement dit }
begin
write ('Donnez les 6 réels correspondant aux parties réelles et imaginaires des coeff a, b, c de ax2 + bx + c = 0 ');
readln (ar, ai, br, bi, cr, ci);
racinecarree (sqr (br) - sqr (bi) - 4*ar*cr + 4*ai*ci, 2*br*bi - 4*ar*ci - 4*ai*cr, dr, di);
division (- br - dr, - bi - di, 2*ar, 2*ai, zr, zi); affiche (zr, zi);
division (- br + dr, - bi + di, 2*ar, 2*ai, zr, zi); affiche (zr, zi);
end.

Execution, pour (4 - i) z2 + (-29 + 3i)z + 75 + 28i = 0, on obtient les solutions 2 + 3i et 5 - 2i.


7

CHAPITRE 2

INSTRUCTIONS DE BASE

Tous les langages de programmation impératifs possèdent des instructions pour affecter,
sélectionner, débrancher ou itérer, et des procédures prédéfinies d'entrées et de sorties des
données.
L'affectation est l'instruction permettant d'attribuer une valeur à une variable.
Par exemple, pour échanger les valeurs de deux variables de noms A et B, il est logique de
passer par un intermédiaire ( une variable "tampon" ) X de la façon suivante: X := A ; A :=
B ; B := X ; A et B ont été réaffectés au moyen de l'affectation temporaire d'une troisième
variable.
Sélectionner, c'est décrire les termes d'alternatives "si ... alors ... sinon ... " qui vont s'écrirent
par des "if ... then ... else" emboîtés.
Itérer, c'est répéter, par exemple, on veut afficher une table de multiplication ou une table de
conversion entre unités physiques etc... Trois instructions font cela couramment .
Exemple de cas emboîtés : maximum de trois nombres
L'exemple de la recherche du maximum de trois nombres donnés montre l'utilisation de
l'instuction "if...then...else..." sélectionnant les actions à effectuer suivant la valeur de vérité
de la proposition logique qui suit le mot "if" et que l'on appelle un test. Dans le cas où le test
est assuré, la ou les instructions intercalées entre "then" et "else" sont exécutées et ce peut
d'ailleurs être une autre instruction de sélection comme dans l'exemple présent.
Il est alors essentiel de rédiger de la façon suivante, les termes "then" et "else" correspondant
aux deux alternatives d'un même test, étant placés l'un en dessous de l'autre. Il n'y a alors pas
de confusion possible pour le lecteur humain même débutant. L'analyseur syntaxique n'a pas
ces problèmes; si tout était écrit à la file mais conformément à la syntaxe, celui-ci s'y
retrouverait alors que le texte serait illisible pour l'homme.
program max ;
var A, B, C : real;
begin write ('Donnez trois nombres ') ;
readln (A , B, C) ;
if A < B then if B < C then write (C)
else write (B)
else if A < C then write (C)
else write (A)
end.

On remarquera plusieurs détails du Pascal : la déclaration obligatoire des noms A, B, C des


trois variables utilisées, avec leur type "réel"; la procédure "write" permettant la copie (en
général sur l'écran) de ses arguments; et la procédure "read" permettant la lecture d'arguments
(en général depuis le clavier).
Notons à ce propos que les trois valeurs doivent être rentrées au clavier séparées par un
espace, et suivies, pour la dernière seulement, du caractère "return".
De plus le Pascal utilise toutes sortes de signes séparateurs comme le point-virgule (;)
séparant deux instructions ou deux déclarations, le : signalant l'appartenance à un type, le
"begin" et "end" jouant le rôle de parenthèses, et le . (point final du programme), la virgule 8
étant le séparateur de données.

Les accolades { } ont pour mission d'encadrer toutes sortes de commentaires jugés bons par le
programmeur, pour la compréhension du programme. Tout ce qui est donc encadré par des
accolades sera ignoré lors de la compilation du programme.

Ce programme ne comportait que trois instructions au premier niveau; même si la dernière


était composée de deux autres sélections. Cette dernière instruction n'est pas suivie du point-
virgule car le "end" qui suit n'est pas une instruction, mais un séparateur signalant à
l'analyseur syntaxique la fin d'un bloc ayant dû débuter par un "begin".

Remarque sur la disposition (l'indentation) des textes en Pascal

Tous les exemples présentés dans ce livre suivent la règle consistant à écrire sur une même
"verticale" des instructions qui se suivent (à la rigueur sur une même ligne, si elles forment un
bloc relativement court en nombre de caractères, nous éviterons l'usage trop répandu
consistant à écrire un mot par ligne, ce qui allonge démesurément les programmes).
Lorsqu'une instruction est composée, ses "composantes" sont placées sur une autre marge,
décalée vers la droite comme on l'a déjà fait remarquer ci-dessus pour les tests emboîtés.
On verra ainsi par exemple des instructions "for" ... décrivant toute une suite d'actions à
effectuer plusieurs fois, ces actions étant écrites sous le "for" mais décalées, afin de respecter
une recommandation de lisibilité, et encadrées par "begin" et "end" en Pascal ou par des
accolades en C, afin de respecter une obligation liée à la logique du programme.
D'une manière plus générale, si une instruction est composée d'autres instructions, on écrira
ces dernières plus à droite que celle qui les "contient". On pratique une "indentation"
échelonnée.

Exemple de sélections : le calendrier

Une autre instruction, en attendant une écriture beaucoup plus puissante dans la spécification
des cas avec la "cond" du Lisp, permet de sélectionner différentes actions suivant les valeurs
d'une variable, c'est le "case ... of ... : ... end;" qu'il faut traduire par "suivant les valeurs de ...
parmi ... faire ...".
L'exemple du calendrier met en oeuvre cette instruction, il s'agit de trouver le jour de la
semaine pour une date du calendrier grégorien.
La formule donnée dans le programme utilise les fonctions "div" (division entière), ou bien
"trunc" (partie entière) et "mod" (reste de la division entière 23 mod 7 = 2 ).
Cette formule n'est valable qu'à la condition que janvier et février soient comptés comme mois
de n° 13 et 14 de l'année précédente. Les termes de la formule faisant intervenir l'année
s'expliquent par le fait qu'il y a une année bissextile tous les quatre ans sauf trois siècles
exacts sur quatre (1900 ne l'était pas, 2000 le sera). D'autre part, elle n'est valable qu'à partir
du vendredi 15 octobre 1582, enfin il faut supprimer les trois derniers termes pour les dates du
calendrier julien, toujours en vigueur dans l'église orthodoxe (adopté en 1752 en Gd Bretagne
et en 1918 en Russie).

program calendrier ;
var A, M, D, N : integer ; { Représenteront l'année, le mois, le jour, et un numéro de code du jour de la
semaine tel que zéro soit le samedi, ... et six le vendredi. }
begin { Readln, comme Writeln, commandent un passage à la ligne après leur exécution }
write ('Quelle année ? '); readln (A); write ('Numéro du mois ? '); readln (M); write ('Date ? '); readln (D);
if M < 3 then begin M:= M + 12 ; A := A - 1 end ;
N := (D +2*M + (3*(M+1) div 5) + (5*A div 4) - (A div 100) + (A div 400) + 2) mod 7;
case N of 0 : write ('samedi');
1 : write ('dimanche');
2 : write ('lundi');
3 : write ('mardi');
4 : write ('mercredi');
5 : write ('jeudi');
6 : write ('vendredi') end end.
Remarques 9

Signalons tout de suite une erreur classique et pourtant grave, dans une instruction telle que
"if M = 1 then A := A-1", les signes = et := n'ont rien de commun. Le premier représente
véritablement l'égalité, c'est-à-dire que la proposition M = 1 est vraie ou fausse, M = 1
possède une valeur booléenne que l'on teste, et c'est tout ce qui est fait.
Le signe := est celui de l'affectation, (souvent écrit grâce à ← qui est plus explicite en pseudo-
pascal ou en LSE, attention, l'affectation est au contraire = en C, alors que l'égalité dans ce
langage est notée ==) A := A-1 est un ordre donné pour modifier la valeur numérique de A,
ici une décrémentation.
L'instruction "if M := 1 then A = A-1" n'a donc aucun sens, et ceci pour deux raisons, après le
"if", c'est une valeur de vérité qui est évaluée afin de décider où sera continuée l'exécution,
après le "then", c'est une instruction qui est commandée.
L'instruction "case" peut s'utiliser sous la forme "case N of -2 .. 5 : " suivi d'une instruction,
pour déterminer une action à faire dans le cas où N serait un entier compris dans l'intervalle
de -2 à 5. Les deux points alignés .. étant la marque d'un intervalle en Pascal. (chapitre 6)

Les boucles

Trois instructions Pascal permettent de décrire un travail itératif :


"for X := ... to (ou bien "downto" pour une décrémentation de X)... do ..." peut s'utiliser
chaque fois que l'on connaît le nombre de "boucles" à effectuer.
La variable dont on choisit le nom (ici X) peut servir à compter les boucles lorsque X va par
exemple de 1 à 12 comme ci-dessous. Mais sa valeur initiale peut être toute autre que 1, et
peut être donnée par toute expression calculable. Le "to" sous-entend une incrémentation
automatique de cette variable à chaque nouveau passage.
Dans le cas où l'arrêt de l'itération n'est déterminé que par une condition sans que l'on puisse
savoir le nombre de boucles , on pourra utiliser "while < condition > do < instruction >." ou
de manière analogue "repeat < instruction > until < condition>".
Les trois formes suivantes sont donc équivalentes :
a) for X := 1 to 12 do writeln ( '13 fois ' , X , ' égal ' , 13*X );
On doit lire: pour X allant de 1 à 12 faire...
b) X := 1 ; repeat writeln ( '13 fois ' , X , ' égal ' , 13*X ); X := X+1 until X = 13
(Répéter l'affichage de ... et l'incrémentation de X jusqu'à ce que X soit égal à 13)
c) X := 1 ; while X < 13 do begin writeln ( '13 fois ' , X , ' égal ' , 13*X ); X := X+1 end ;
(Tant que X est strictement inférieur à 13 faire ... )
"writeln" signifiant une édition à l'écran suivie d'un passage à la ligne.

Exemple de successions itérées de réaffectations : suite de Fibonacci

C'est la suite telle que chaque terme est égal à la somme des deux termes précédents, en
partant de 1. Les premiers termes sont donc 1 1 2 3 5 8 13 21 ... (Cette suite est déterminée
par : u0 = u1 = 1 puis un = un-1 + un-2 elle est presque géométrique pour les termes de rang
élevés). Pour ne pas employer trop de variables, (il serait évidemment maladroit de conserver
toutes les valeurs successives dans un tableau), on peut se servir simplement de trois variables
P Q R représentant toujours trois termes consécutifs.
program fibonacci ; { dérouler le programme à la main pour P < 20}
var P, Q, R : integer ;
begin P := 1; Q := 1; { P, Q, R désignent toujours 3 termes consécutifs de la suite }
while P < 1000 do begin write (P,' / ');
R := P + Q; { le troisième terme est la somme des 2 précédents }
P := Q; { on avance d'un cran ; le premier est l'ancien second }
Q := R { et le nouveau second est l'ancien troisième }
end
end.
Exemple de programme itératif, la méthode de Newton 10

Le programme suivant est celui, très classique, de la résolution d'une équation dont on connaît
l'existence d'une solution. Si x0 est une valeur voisine de la racine r, la tangente en x0 coupe
l'axe des abscisses en x1, la tangente en x1 coupe à nouveau l'axe en x2 , ... en réitérant on
obtient une suite xn généralement convergente vers r. Naturellement cette convergence pose
un problème mathématique, sans compter celui de l'existence même de la suite, si pour un xn,
la courbe passe par un sommet, la tangente y est parallèle à l'axe et ne le coupera donc pas. La
méthode de dichotomie vue plus loin, est alors plus appropriée.
On calcule aisément que x n+1 = xn - f(xn) / f '(xn)

L'intersection de la parabole y = 1 - x2/2 avec y = ln(x) conduit par exemple à une équation :
1 - x*x/2 - ln(x) = 0 dont on montre facilement qu'elle admet une solution unique entre 1 et
1,4 sans pouvoir la calculer exactement.
program newton ;

function fonc (X : real ): real ; { la fonction quel 'on veut annuler c'est à dire la différence des deux }
begin fonc :=1 - X*X / 2 - ln(X) end ; { c'est ainsi que l'on déclare une fonction en Pascal }

function der (X : real ) : real ; { sa dérivée }


begin der := -X - 1 / X end ;

function newton (DEP, EPS : real ) : real ;


{ DEP et EPS sont respectivement le point de départ et la précision }
var F, X : real ;
begin
X := DEP;
repeat F := fonc (X) ; X := X - F / der(X) until abs (F) < EPS;
newton := X
end;

begin write (newton (1, 0.000001)) end.

Ici les choses sont un peu mieux structurées, le programme proprement dit est réduit à la
dernière ligne ce qui est souvent le cas. On remarquera bien sûr que la fonction et sa dérivée
ne peuvent constituer des arguments de la "fonctionnelle" newton comme cela serait cohérent,
en Pascal, c'est en fait possible en C++ et en Lisp comme nous le verrons.
Exemples de modifications d'affectations : tri de valeurs méthode bulle 11

Le principe du tri-bulle est de parcourir une liste de nombres en comparant chaque terme avec
son suivant, s'ils ne sont pas dans l'ordre croissant, on les intervertit, et si au moins une telle
permutation a eu lieu, le parcours étant fini, alors on recommence.
On anticipe légèrement sur les chapitres suivants en créant un nouveau type de données "tab"
comme tableau de 10 entiers.

program tribul ;
const N = 10; { N sera une valeur fixe pour tout le programme.}
type tab = array [1.. N] of integer ;
{ On définit ainsi un nouveau type de données par un tableau indexé de 1 à N }
var J : integer ; L : tab ;

procedure echange (var A, B : integer );


var X : integer ;
begin X := A ; A := B ; B := X end;

function balai (var L : tab ; N : integer ) : boolean ; { Est un exemple de fonction manipulant des types de
données tout à fait différents, balai joue le rôle de signal ou "drapeau"., il vaut "vrai" si L est entièrement trié}
var I : integer, drap : boolean ;
begin drap := true;
for I := 1 to N - 1 do if L[I+1] < L[I] then begin echange (L[I], L[I+1]); drap := false end
balai := drap
end ;

begin { début des trois instructions du programme }


for J := 1 to N do read ( L[J] ) ; {lecture des données au clavier }
repeat until balai (L) = true ; {boucle où rien ne se passe car c'est l'évaluation de "balai" qui modifie L}
for J := 1 to N do write ( L[J] ,' / ' ) {écriture du résultat} end.

La solution ci-dessus propose à l'avant-dernière ligne, une boucle qui s'exécute jusqu'à ce
qu'une fonction ait une certaine valeur "vrai", rien ne se passe en apparence dans la boucle. En
fait, à chaque appel de la fonction, l'argument L est modifié. Ces questions seront vues en
détail au chapitre 4. On peut préférer une solution plus classique ne faisant pas appel à cette
fonction, mais à deux variables supplémentaires I de type entier et "drap" de type booléen. En
ce cas le programme utilisera comme seconde instruction :
repeat drap := true;
for I := 1 to N - 1 do if L[I+1] < L[I] then begin echange (L[I], L[I+1]); drap := false end
until drap

Remarque : "balai" uniquement peut remplacer "balai = true" puisque cela désigne une
variable de type booléenne c'est à dire que sa valeur est nécessairement "true" ou "false".

2-1° Simplifier en détaillant les calculs, l'expression booléenne suivante dans laquelle A et B
sont des réels: non [ (A ≤ 0 ou non B < 0) et (A < 0 → B ≤ 0)]

Avec la définition élémentaire de (P → Q) = (¬P ou Q), et grâce aux lois de Morgan (annexe),
non (P ou Q) = (non P) et (non Q) ainsi qu'à l'involutivité ¬¬P = P, on obtient rapidement que
la condition signifie A, B de même signe.

2-2° Quelle est la différence entre les deux actions de ?


if M < 3 then begin M:= M+12 ; A := A-1 end;
if M < 3 then M:= M+12 ; A := A-1;

2-3° Quelle erreur y a t-il dans l'écriture suivante ?


if M = 1 then M:= 13 ; else if M = 2 then M := 14 ;
12
2-4° Quel est le résultat de la séquence d'affectations X := X + Y ; Y := X - Y ; Y := X - Y ?

Un échange des deux (donc sans passer par un tampon).

2-5° Un écran est repéré par un nombre de lignes H+1 et un nombre de colonnes L+1, les
lignes sont numérotées de 0 à H de haut en bas, et les colonnes de 0 à L, de gauche à droite.
Donner les fonctions affines "col" et "lgn" définies respectivement sur les intervalles [A, B] et
[C, D] pour les variables respectives x et y, de façon à obtenir col(A) = lgn(D) = 0, col(B) =
L, lgn(C) = H.

Réponses : col(x) = L(x - A)/(B - A) lgn(y) = H(D - y)/(D - C)

2-6° Equation du second degré. Ecrire en disposant correctement les emboîtements de


conditions, le programme résolvant l'équation du second degré ax2+bx+c=0 , dans tous les
cas de nullité de a, b, c et du discriminant D

2-7° Date du lendemain. Faire un programme capable de donner la date du lendemain


lorsqu'on lui fournit le numéro du jour, le mois et l'année. Il est assez difficile de bien
disposer tous les cas, même si on ne tient pas compte des années bissextiles (le jour de l'an a
été fixé au premier janvier en 1564 par Charles IX).

2-8° Calcul du poids idéal (kg) en fonction de la taille (cm) et de l'âge (ans) par les formules:
(3*T-250) * (A + 270) / 1200 pour les hommes et (T / 2 - 30) * (180 + A) / 200 (femmes).

program poids;
var A, T, PR : integer; S : char; PI : real;
begin write ('Donnez votre âge en années '); readln (A);
write ('Quel est votre taille en cm ? '); readln (T);
write ('Votre poids en kg ? '); readln (PR);
write ('Votre sexe (M ou F) ? '); readln (S);
if S = 'M' then PI := (3*T-250) / 1200 * (A + 270)
else PI := (T/2 - 30) / 200 * (180 + A);
PI := round (10*PI)) / 10 ; {astuce pour ne garder qu'une décimale, "round" étant l'entier le plus proche }
D := PR - PI; { D est la différence entre le poids réel et le poids idéal }
if abs(D) < 3 then write ('Ca va')
else if D < 0 then write ('Vous pouvez prendre ', D, ' kg')
else write ('Vous devez maigrir de ', D, ' kg')
end.

2-9° Créér un tableau de correspondances de poids et tailles, se servir de l'instruction gotoxy


(C, L) localisant le curseur dans l'écran à la ligne L (1 < L < 24) et colonne C (1 < C < 80).

2-10° Faire un tableau des valeurs de P, Q, R dans Fibonacci pour les cinq premiers passages.

2-11° Soit le programme dont le corps est :


P := -2; Q := 3; for X := 1 to 5 do begin write (Q,' /'); R := 2*Q + P; P := Q; Q := R end.
Faire un tableau des valeurs successives de P, Q, R.
a) Que voit-on exactement à l'écran ?
b) ................................. si les deux dernières instructions d'affectation sont interverties ?

P -2 3 4 5 26
Q 3 4 11 26 63
R 4 11 26 63 152
L'effet est 3/4/11/26/63/ et dans l'autre cas P = Q, c'est 3/4/12/36/108/
13
2-12° Ecrire un programme déterminant la date P de Pâques en fonction de l'année A en
suivant la formule de Gauss-Delambre : B = (19 (A mod 19)+M) mod 30 et P = [2 (A mod 4)
+ 4 (A mod 7) + 6B+ N] mod 7 + B + 22 du mois de mars si P ≤ 31, et d'avril sinon. M et N
étant lentement variables dans le calendrier grégorien, actuellement égaux à 24 et 5. On
rappelle que X mod Y est le reste de la division entière de X par Y (exemple 47 mod 6 = 5)

program paques ;
var A, B, P : integer;
begin write ('Donnez l''année '); readln (A);
B := ((19* (A mod 19)+24) mod 30);
P := ((2*(A mod 4)+ 4*(A mod 7) + 6*B + 5) mod 7) + B+ 22;
if P < 32 then writeln ('Dimanche ', P,' mars')
else writeln ('Dimanche ', P-31,' avril')
end.

2-13° Jour de la semaine. Reprogrammer le jour de la semaine correspondant à une date,


suivant la formule de Zeller. Cette fois on indique que si le mois m est supérieur ou égal à 3
on le change en m-2 et sinon en m+10 ainsi que l'année a en a-1. On pose alors na le numéro
de l'année dans le siècle et s le numéro du siècle, et enfin f = j + na - 2*s + na div 4 + s div 4
+ (26*m - 2) div 10. Le code est donné par f mod 7 (0 si dimanche).
Grégoire XIII a imposé sa réforme du calendrier le jeudi 4 octobre 1582 qui fut donc suivi du
vendredi 15 octobre 1582. Avant ce changement, le programme doit également fonctionner en
ajoutant 3 à f.

On utilise le réel R = a + m/100 + j/10000 que l'on compare avec 1582,1004 et 1582,1015
Tester grâce à jeudi 24 octobre 1929, samedi 1-1-2000, mardi 14-7-1789 ...
En pseudo-pascal :
lire (j, m, a)
si m > 3 alors m ← m-2 sinon soient m, a ← m+10, a-1
soient s, na ← a div 100, a mod 100
f ← j + na - 2s + (na div 4) + (s div 4) + (26m - 2) div 10
si a + m /10 + j / 10000 < 1582,1004 alors f ← f + 3
jour ← f mod 7
conditions jour = 0 alors dimanche ... etc.

2-14° Calculer la somme alternée des inverses des entiers impairs en débutant par 1 et en
finissant à -1/199, puis multiplier par 4 le résultat et l'afficher.

2-15° Ecrire trois versions différentes d'un programme délivrant la suite des entiers jusqu'à
12, de leurs carrés, cubes, et puissance de 2. On utilisera writeln ( X : 3, X*X : 5, X*X*X : 5,
exp (X * ln(2)) : 10), "write (X : m : n)" a pour effet d'éditer X sur m caractères en tout avec n
chiffres après la virgule s'il s'agit d'un réel.

2-16° Conversion de degrés Farenheit et Celsius, sachant que F = 9*C/5 + 32, faire une
table de conversion.

2-17° Afficher les valeurs de n2 + n + 41 pour 0 ≤ n ≤ 39 . Qu'observe-t-on ?

2-18° Impôt sur le revenu. Programmer le calcul de l'impôt I sur les revenus, sachant que si
on note S le total annuel des salaires, F celui des revenus fonciers, C l'ensemble des charges à
déduire alors le revenu imposable R est 80% des 90% de S, auxquels on ajoute F et on
retranche C. Si N est le nombre de parts et QF = R / N, alors utiliser l'instruction "case" et les
explications fournies sur les déclarations annuelles pour calculer I (les fameuses tranches dont
les caractéristiques sont modifiées chaque année ). Représenter I en fonction de R.
14
2-19° Calculer la somme des inverses des puissances 4 des entiers impairs de 1 à 199,
multiplier par 96, extraire la racine quatrième, et afficher le résultat.

2-20° Pgcd et du ppcm de deux entiers

La méthode la plus simple est de chercher le premier multiple de a qui soit divisible par b,
sachant que le produit du pgcd par le ppcm est ab, on a les deux en même temps.
program pgcd;
var a, b, k : integer;
begin readln (a, b);
k := 0;
repeat k := k+1 until a*k modulo b = 0
writeln ('ppcm de ', a, ' et ', b, ' = ', a*k, ' pgcd = ', b div k)
end.

2-21° Constante de Vijayaraghavan, c'est la racine de l'équation x3 = x+1, la déterminer par


la méthode de Newton.

2-22° Loi de Bode : exprimer (en unités astronomiques) les distances des planètes au soleil,
en suivant la loi empirique de Bode (4 + 3*2n-2 ) / 10 pour n = 1 à 10, (les astéroïdes
constituant la cinquième planète).

2-23° Combinaisons C np, faire le programme en veillant à alterner les opérations de façon à
limiter l'erreur de calcul et à éviter les dépassements de capacité. (Le résultat provisoire en
cours de calcul doit demeurer entier ).

L'astuce consiste à alterner les multiplications et les divisions, en commençant par la


multiplication de deux entiers consécutifs. En effet, parmi eux il y en a necéssairement un qui
est pair, donc on peut immédiatement après diviser par 2, puis en multipliant par l'entier
suivant, des trois facteurs, là aussi, un des trois est multiple de 3, on peut donc aussitôt après
diviser par 3. En débutant par un premier facteur égal à n-p+1, et un premier diviseur égal à 1,
et en continuant ainsi, le dernier facteur sera n et le dernier diviseur sera p. Cette solution
permet de repousser le plus loin possible l'instant où la capacité en nombre entier sera
atteinte.

program comb;
var n, p, i, c : integer;
begin write ('Donnez deux entiers n et p ');
readln(n, p);
c := 1;
for i := 1 to p do c := (c*(n-i+p)) div p
write ('Le résultat est ', c)
end.

2-24° Le tri de Shuttle est le suivant :


for i := 1 to n-1 do begin j := i;
test := (t[j] > t[j+1]);
while test do begin ech(t[j], t[j+1])
j := j-1;
if j = 0 then test := false
else test := (t[j] > t[j+1])
end;
end;
Faire tourner le programme à la main pour t = [4, 7, 22, 3, 9, 11, 0, 32, 5].
Analyser ce que fait le programme.
15
2-25° Tri par extraction. Le principe du tri par extraction d'un tableau t est d'aller chercher
le plus petit élément pour le mettre en premier, puis de repartir du second... En utilisant une
procedure "echange", programmer ce tri, puis l'étendre à une extraction dans les deux sens.

Si t est indicé de 1 à n, l'essentiel du programme sera :


for k := 1 to n-1 do begin for i := k+1 to n do if t[i] < t[k] then echange (t[i], t[k]); write(t[k]) end;
write (t[n])

Si on extraie le plus petit, on peut également mettre le plus grand à la fin :

for k := 1 to n div 2 do begin for i := k+1 to n-k+1 do


begin if t[i] < t[k] then echange (t[i], t[k]);
if t[i] > t[n-k+1] then echange (t[i], t[n-k+1])
end;
write (t[k]) end;
for i := n div 2 + 1 to n do write(t[i]) {Ce tri est assez lent, mais meilleur que le tri-bulle}

2-26° Tri bulle boustrophédon : on balaye de gauche à droite suivant le principe du tri bulle,
puis on revient de droite à gauche et ainsi de suite.

L'intérêt de la méthode est qu'à chaque parcours, la valeur maximale est repoussée à sa
position définitive si c'est un parcours de gauche à droite, et c'est la valeur minimale qui est
repoussée au début sinon. Ceci permet de restreindre d'un cran l'étendue de chaque balayage.

for i := 1 to n div 2 do begin drap := false;


for k := i to n-i do if t[k] > t[k+1] then begin drap := true;
echange (t[k], t[k+1]) end;
if drap then begin for k := n-i-1 downto i do
if t[k] > t[k+1] then begin drap := true;
echange (t[k], t[k+1]) end;
end
else affiche(t) { affiche est une procédure que l'on mettra au point }
end;

Remarque, on peut améliorer encore en signalant les indices des derniers permutés, au
chapitre 4, nous donnons une solution mutuellement récursive de ce problème.

2-27° Tri Shell : consiste à trier séparément des sous-suites de la table, formées par les
éléments répartis de h en h. Empiriquement, Shell propose la suite d'incréments vérifiant h1 =
1, hn+1 = 3hn + 1 en réalisant les tris du plus grand incrément possible vers le plus petit.

type tab = array [1..20] of integer; {on demandera shell (t, n) pour un tableau t déjà entré de n éléments}

procedure shell (var t : tab; n : integer);


var h, i, j, k, x : integer;
begin h := 1 ; while h < n div 9 do h := 3*h + 1;
repeat for k := 1 to h do begin i := h + k;
while i <= n do begin x := t[i]; j := i-h;
while (j >= 1) and (t[j] > x) do begin
t[j+h] := t[j];
j := j-h;
t[j+h] := x end;
i := i+h end;
end;
h := h div 3
until h = 0
end;
16
2-28° Tri-arbre : on transforme un tableau en un arbre binaire, "tas" de telle sorte que
l'élément i ait deux fils de rang 2i et 2i + 1 plus petits que lui. (Un tel arbre est dit
"maximier"). Cette construction se faisant à reculons, le but est en même temps de modifier le
tableau pour que les deux fils soient inférieurs à leur père. Puis grâce à une transformation
également en arrière, on va chercher à modifier le tableau de gauche à droite et renvoyer le
plus grand élément entre à la fin. Faire tourner à la main l'algorithme pour une petite table en
dessinant l'arbre.

Solution : {Ce tri est aussi appelé tri par tas}


type tab = array [1..20] of integer; {On demandera tri (t, n) pour un tableau entré comportant n éléments.}

procedure echange (var x, y : integer);


var z : integer; begin z := x; x := y; y := z end;
procedure vue (var t : tab; n : integer);
var i : integer; begin for i := 1 to n do write (t[i] : 3); writeln end;

procedure maximier (var t : tab; i, j : integer);{les sous-arbres fils de t[i] à t[j] seront maximiers si leurs sous
arbres le sont déjà, ce qui signifie simplement que t[s] est supérieurs à ses deux fils t[2s] et t[2s+1]}
var s, f : integer; {s représente un sommet, f son fils}
begin s := i;
while 2*s <= j do
begin f := 2*s;{on échange le sommet avec le plus grand des deux fils}
if (f + 1 <= j) and (t[f+1] > t[f]) then f := f+1;
if t[f] > t[s] then echange (t[s], t[f]); s := f
end
end;
procedure tri (var t : tab; n : integer); {t étant maximier, on ordonne à reculons }
var i : integer;{on rend d'abord maximier à reculons}
begin for i := n div 2 downto 1 do maximier (t, i, n);
for i := n downto 2 do begin maximier (t, 1, i) ; echange (t[1], t[i]) end
{t est maximier de 1 à i et ordonné croissant de i+1 à n}
end;

2-29° Etudier la complexité du tri par arbre en supposant que le nombre n d'éléments est de la
forme 2m, on montrera que la construction de l'arbre est en O(n), puis que le tri proprement
dit est en O(n.log(n)).

2-30° Recherche des solutions entières de l'équation x 2-y2 = a, en cherchant les facteurs
(x+y) et (x-y) de même parité, a étant une constante fixée. Exemple pour x2-y2 = 45 ; si x-y
vaut successivement 1, 3, 5, et x+y, respectivement 45, 15, 9, alors x admet pour valeurs 23,
9, 7, avec respectivement y, 22, 6, 2.

Si x - y = p , x + y = q, pour que x, y ∈ N, il faut absolument que p et q aient la même parité,


c'est à dire que p + q soit pair, ce qui est équivalent. D'autre part, pour éviter les répétitions on
se limite aux couples (p, q) de diviseurs de a tels que 1 ≤ p ≤ √a ≤ q ≤ a

program eqentiere;
var a, p, q : integer;
procedure reso (p, q : integer) ; {Résoud x - y = p , x + y = q }
var x, y : integer;
begin x := (p+q) div 2 ; y := (q-p) div 2 ; writeln ('Le couple ',x,' , ',y,' est solution.') end;
function divise (m, n:integer) : boolean; {Indique si m divise n ou non}
begin divise := ((n mod m) = 0) end;
{c'est l'expression booléenne elle-même qui est la valeur de "divise"}

begin write ('Solutions entières de x2-y2 = a, donnez la valeur de a '); readln (a);
for p := 1 to round ( sqrt (a) ) do {notons que "round" est l'entier le plus proche, et que "odd" est "impair"}
if divise (p, a) then begin q := a div p; if odd (p) = odd (q) then reso (p, q) end
end.
17
2-31° Fonction logarithme néperien, on veut la réaliser sans avoir recours à celle qui est
déjà implantée. Pour cela, montrer que si x > 0 alors x s'écrit de façon unique sous la forme
2ky√2 avec k ∈ Z et y ∈ [1/√2, √2[, pour cet y, montrer en posant u = (y-1)/(y+1) que l'on a
ln(y) = Σ 2u2i+1/(2i+1), série pour i=0 jusqu'à l'infini. Ecrire la procédure "decompose" qui
va valculer ces k et y, pour un x > 0, puis la function "serie" qui va donner la somme partielle
pour i < 7 et y ∈ [1/√2, √2[, et enfin la function "lnapprox" qui pour x > 0, donne une valeur
approchée de ln(x).

Pour tout x positif, x= 2kz avec k unique dans Z, on pose z = y√2 d'où y ∈ [1/√2, √2[
u = (y-1) / (y+1) entraîne y = (1+u) / (1-u) et on vérifie que -1 < u < 1 donc ln(y) = ln(1+u) -
ln(1-u) = ∑ 2u2i+1/(2i+1) pour i entier.

procedure decompose (x : real ; var k : integer; var y : real);


var p : real; {p représente 2 puissance k}
begin k := 0; p := 1; {voir les cas de x = 1 ou 2}
if x >= 2 then repeat p := 2*p; k := k+1 until (x < 2*p)
else if x < 1 then repeat p := p/2; k := k-1 until (p <= x);
y := x / (p*sqrt(2)) end;

function serie (y : real) : real;


var u, v, s : real; i : integer; {s est la somme partielle, v est u 2i}
begin u := (y-1) / (y+1); s := 1; v := u*u;
for i := 1 to 6 do begin s := s + v / (2*i+1); v := v*v end;
serie := 2*u*s end;

function lnapprox (x : real) : real;


var k : integer; y : real;
begin decompose ( x, k, y); lnapprox := (k + 1/2)*0.69 + serie (y) end;

2-32° Nombres premiers : calculer tous les nombres premiers jusqu'à une certaine constante
m = 400 par exemple, en testant l'éventuelle divisibilité. On impose un exercice de style
consistant à ne pas utiliser "for", "repeat" et "while", mais "goto". L'utilisation se fait grâce à
"goto étiquette" où les "étiquettes" sont des identificateurs choisis par le programmeur et
déclarés par "label" e1, e2 ..., elles sont alors placées aux endroits voulus et suivies de deux
points.

Solution, pour chaque entier impair n à partir de 3 ou de 5, on teste sa divisibilité par p allant
de 2 en 2 depuis 3 jusqu'à la racine carrée de n :
program premiers ;
const m = 400 ;
label e1, e2, e3 ; { sont les trois labels choisis }
var n, p : integer ; { n est l'entier courant que l'on teste, p, son diviseur éventuel }
begin write (2 : 4, 3 : 4) ; {on traite à part les cas initiaux }
n := 5 ; { on commence à reconnaître si 5 est premier }
e1 : p := 3 ;
e2 : if n mod p = 0 then goto e3 ; { n est divisible, on passe au suivant }
p := p + 2 ;
if p > sqrt (n) then write (n : 4) { n vient d'être reconnu premier }
else goto e2 ;
e3 : n := n + 2 ; { à partir de 3, tous les nobres premiers sont impairs }
if n <= m then goto e1
end.
18
2-33° Le nombre caché : l'ordinateur choisit un nombre au hasard entre 1 et 1000 , il faut le
deviner en dix questions au plus, l'ordinateur ne répondant que par "trop petit" ou "trop
grand" ou "gagné". On se sert de X := random (1000) et on peut bien sur écrire une condition
logique composée avec "and" ou "or".

Cet exemple montre le cas très fréquent d'une boucle dont on peut sortir de deux façons
différentes. Le test de réitération comporte donc deux cas dont un est nécessairement répété
ensuite.
program jeu;
var X, R, Q : integer;
begin
X := random (1000); { en turbo-pascal sur PC, sur Mac, "random" seul, renvoie un réel entre 0 et 1 }
Q := 1; { numéro de la question }
repeat write ('Question numéro ', Q, ' quel est ce nombre ? ');
readln(R); {R est la réponse du joueur }
if R < X then writeln (' trop petit !')
else if R > X then writeln (' trop grand !')
else writeln (' Gagné !');
Q := Q + 1
until (Q > 10) or (R = X);
if R = X then writeln (' Perdu c''était ', X)
end.

2-34° Refaire le même programme que précédemment, mais en utilisant l'instruction de


débranchement "goto" endroit; dans laquelle "endroit" est une position ultérieure du
programme qui aura été déclarée comme un "label" au début du programme.

2-35° Deux personnes jouent avec un tas d'allumettes en prenant à tour de rôle de 1 à 3
allumettes sans les remettre. Le perdant est celui qui tire la dernière allumette. Simuler ce jeu.

2-36° Etudier la convergence de la suite x0 = 11/2, x1 = 61/11, et xn+2 = 111 - 1130/xn+1 +


3000/(xnxn+1), les limites possibles sont 5, 6 et 100.

2-37° Calculer le produit a*b de la manière suivante : on divise a par 2 tant que c'est
possible, en doublant b, sinon on décrémente a et on ajoute b au résultat.

Il s'agit en fait de toujours prendre le quotient entier de a par 2 dans une colonne en doublant
b dans une seconde colonne, puis le résultat sera la somme des nombres de la seconde
colonne situés en regard d'un nombre impair dans la première. Exemple (en ligne) :
26 13 6 3 1 0
54 108 216 432 864 sont additionnés 108 + 432 + 864 = 1404
program mult ;
var a, b, r : integer;
begin
write ('Entrez deux entiers '); readln (a, b);
r := 0; {a*b + r est dit un invariant de boucle }
while a <> 0 do if odd(a) then begin a := a-1; r := r + b end
else begin a := a div 2; b := 2*v end;
write ('Le résultat est ', b)
end.
19
2-38° Anniversaire Quelle est la probabilité p(n) qu'au moins deux personnes dans un groupe
de n personnes, aient leur anniversaire le même jour ? Ne pas tenir compte des années
bissextiles, et ne pas utiliser la formule de Strirling n! ~ nne-n√(2πn)

En considérant l'évenement contraire "chaque personne du groupe a une date d'anniversaire


qui lui est spécifique", la probabilité de cet évenement est le nombre d'injections de n
personnes vers 365 jours, alors que le nombre de cas possibles est le nombre d'applications
quelconques de n personnes vers 365 jours.
n
A365 365*364* ... (365 - n + 1)
pn = 1 - =1- n
n
365 365

program anniversaire;
function proba (n : integer) : real;
var i : integer; r : real;
begin r := 1; for i := 0 to n - 1 do r := r * (365 - i) / 365;
proba := 1 - r end;

var m : integer;
begin write ('Donnez le nombre de personnes dans le groupe '); readln (m);
write ('La proba que deux d''entre eux au moins soient nés le même jour est ', proba (m) : 5 : 3);
end.

2-39° Fonction Gamma, faire un programme de calcul de la fonction Γ par la formule :


Γ (x) = lim n! nx / [ x(x+1)(x+2) ... (x+n) ] pour x positif et n → + ∞

2-40° Calcul d'une intégrale par une méthode de Monte-Carlo: si sur [a,b] on a toujours 0
≤ f(x) ≤ M, on tire un grand nombre de fois N, un nombre x au hasard dans l'intervalle [a,b],
et un nombre y dans l'intervalle [0,M]. Si y ≤ f(x) a été obtenu n fois, alors l'intégrale est
approchée par nM(b-a) / N.

2-41° Intégrale par Simpson. Ecrire une procédure de calcul d'intégrale pour une fonction f
préalablement déclarée, entre deux valeurs A et B, en utilisant la formule d'approximation de
Simpson où le pas est h = (b - a) / 2n :
n-1 n-1
I = [f(a) + f(b) + 2* ∑ f(x 2p) + 4* ∑ f(x 2p+1)]
h
3 p=1 p=0

Solution, on a choisit la fonction dérivée de Arctan, de façon à retrouver π/4 en l'intégrant


entre 0 et 1 :

program simpson;
function f (x : real) : real;
begin f := 4 / (1 + x*x) end;
function simpson (a, b : real; n : integer) : real;
var p : integer; s, h : real;
begin h := (b - a) / 2 / n; s := f(a) + f(b) + 4*f(a + h);
for p := 1 to n - 1 do s := s + 2*f(a + 2*p*h) + 4*f(a + (2*p + 1)*h);
simpson := h*s / 3 end;
begin write ('vérification ', simpson (0, 1, 500) : 5 : 3) end.

2-42° Recalculer Γ(x) comme l'intégrale impropre pour t de 0 à + ∞ de la fonction e-t t x-1
20
2-43° Méthode de la plus grande pente de Cauchy. On cherche le minimum d'une fonction
réelle de plusieurs variables à partir d'un point M0 arbitraire. Si la fonction admet des
dérivées partielles, son gradient (qui mesure la plus grande pente en ce point) est le vecteur
ligne ∆f = (∂f/∂x, ∂f/∂y, ∂f/∂z) dans le cas de trois variables. La méthode consiste alors à
passer du point Mk au point Mk+1 en cherchant le t ≤ 0 qui permet de minimiser la valeur de
f(Mk - t ∆fk) en posant pour cette valeur t, Mk+1 = Mk - t ∆fk.

Solution : trouver le minimum de la fonction φ (t) = f(Mk - t ∆fk) n'est pas facile, aussi on
peut simplement procéder avec un pas dt assez petit, et augmenter t de ce pas tant que φ
dimimue. Quant à l'arrêt de la procédure, il est également déterminé par l'arrêt de diminution
de φ. En fait dans la solution ci-dessous nous avons testé l'arrêt sur un point singulier pour
lequel le gradient possède une norme de Hamming inférieure à un seuil.
Cette méthode donne bien le minimum sur tout domaine où la fonction est convexe, mais ne
donne qu'un minimum local sinon.
On pourra tester sur bol (x, y) = √(1 - x2 - y2) dont le minimum -1 est réalisé en (0, 0), mais
la fonction de Rosenbrock f(x, y) = (x - 1)2 + 10 (x2 - y)2 est plus intéressante à cause de sa
vallée en forme de banane, qui conduit au minimum 0 pour (1, 1).
program gradient;
var u, v, mini : real;
function f (x, y : real) : real; {fonction de Rosenbrock}
begin f := sqr(x-1) + 10* sqr (sqr(x) - y) end;
function df1 (x, y : real) : real; {dérivée partielle par rapport à x}
begin df1 := 2*(20*x*(sqr(x) - y) + x - 1) end;
function df2 (x, y : real) : real; {dérivée partielle par rapport à y}
begin df2 := -20*(sqr(x) - y) end;

procedure descente (var x, y, m0 : real; eps : real);


var t, dx, dy, m1 : real; n, p : integer;
begin n:= 0; p:= 1;
repeat writeln ('nouveau point ',p); m1 := f(x, y); dx := df1 (x, y); dy := df2 (x, y); t := 0;
repeat m0 := m1; t:= t + eps; x := x -t*dx; y := y - t*dy; m1 := f(x, y) ; n := n+1
until (m1 >= m0); {on a fini une descente suivant un gradient}
p := p + 1; x := x + t* dx; y := y + t*dy {on revient au point précédent }
until abs(dx) + abs(dy) < eps ;
end;
begin u := -1; v := 1; descente (u, v, mini, 0.001);
write ('Minimum ', mini:6:3, ' pour x = ', u:6:3, ' et y = ', v:6:3) end.

On part ici du point (-1, 1) pour s'apercevoir qu'on longe une vallée où x augmente et y
diminue presque à 0 avant de remonter vers 1. Pour ε = 0,001 il faut 4551 itérations et 619
mesures de dérivées partielles avant d'obtenir le minimum 7,3.10-7.
Une variante plus simple à réaliser, est de faire descendre le long du gradient d'un pas
constant, soit en appliquant toujours le même dt, soit en refaisant une mesure du gradient de
telle sorte que MkMk+1 soit une distance constante. Une autre variante consiste à faire
descendre le long de toutes les coordonnées l'une après l'autre (méthode de relaxation), mais
l'ordre n'est pas indifférent et la méthode est lente et inefficace.

2-44° Méthode du gradient conjugué. Pour trouver le minimum d'une fonction réelle de
plusieurs variables, on part également d'un point arbitraire M0, puis en posant u0 = ∆0, et ∆k
le gradient en Mk, on passe de Mk au point Mk+1, si A matrice symétrique définie positive
(on peut prendre l'identité ou le Hessien de f) en posant :
t uk t uk
MkMk+1 = -(u k.∆ k) et u k+1 = ∆ k+1 -(u k.A.∆ k+1)
2 2
uk uk
21
2-45° Plateau d'Arsac. Un tableau t indicé de 1 à n est trié par ordre croissant, on cherche la
longueur du plus grand plateau (suite d'éléments consécutifs égaux) dans t.
Programmer en une seule boucle et avec seulement deux variables (faire un exemple).

Solution
p := 1; i := 2;
while i < n do begin if t[i] = t[i-p] then p := p+1; { l'astuce consiste à regarder p crans derrière }
i := i+1
end;

A la fin de la boucle, p est le résultat cherché. Il est également possible de regarder en avant
en faisant p ← 1; i ← 1;
tant que i + p ≤ n faire si t[i] = t[i + p] alors p ← p + 1 sinon i ← i + 1

2-46° Problème de Dijkstra. Un tableau contient 3 couleurs bleu, blanc, rouge en nombre
quelconque et dans le désordre. On souhaite ranger le tableau dans l'ordre bleu, blanc, rouge
en une seule boucle.

Supposons le processus en cours d'exécution, soit p l'indice du dernier bleu, puis des éléments
encore non examinés dont le dernier possède l'indice q, et r l'indice du dernier blanc, les
éléments de r+1 à n étant rouges. On aura :
p := 0; q := n; r := n;
repeat if t[p+1] = 'bleu' then p := p+1
else if t[p+1] = 'blanc' then begin echange (t[p+1], t[q]); q := q-1 end
else begin echange (t[r], t[q]);
echange (t[r], t[p+1]);
r := r-1;
q := q-1
end;
until p = q
22

CHAPITRE 3

EXEMPLES GRAPHIQUES

Représentation de courbes
Les exemples donnés ici ne posent pas de grand problème de programmation, il faut savoir,
pour les lire qu'on accède aux bibliothèques de fonctions graphiques avec "uses graph ;" sur
PC et "uses memtypes, quickdraw ;" sur MAC.
Les différents modes graphiques sont tellement variés qu'il vaut mieux toujours poser
d'emblée des constantes L et H signifiant largeur et hauteur d'écran. Ainsi, le programme peut
commencer par l'attribution de deux valeurs, ce qui permet d'avoir une grille, par exemple, de
L = 640 colonnes numérotées de 0 à 639 et H = 200 lignes numérotées de 0 à 199 de haut en
bas.
Les instructions utilisées ici sont "moveto (u, v)" pour se placer à un point quelconque
(colonne u, ligne v) de l'écran, et "lineto (u, v)" pour tracer un segment dans la couleur
considérée, depuis le dernier point placé jusqu'à celui (u, v) qui est donné en paramètre.
Rappelons, en outre, que "trunc" est la fonction de troncature, trunc (45.123) = 45 par
exemple, et que "div" est la division entière 45 div 6 = 7.
Afin d'éviter les confusions nous écrirons systématiquement les coordonnées d'écran u et v ou
bien c (colonne) et l (ligne), et nous réserverons x, y pour le point de vue du dessin réel sur le
papier. C'est sur ce dessin que l'on doit définir la fenêtre A < x < B et C < y < D.
Dans ce premier programme, la fenêtre est fixée par le programme proprement dit, il faut y
remarquer la séparation très nette entre les déclarations : les deux fonctions f et g, et les deux
sous-programmes de tracé d'axes et de tracé de courbes. Le programme proprement dit n'est
constitué que par la dernière ligne fournissant des arguments à la procédure de trace. Ce point
sera examiné plus en détail au chapitre 4 .
program courbes ; {Courbes paramétrées X=f(t) et Y=g(t) l'exemple donné est une épicycloïde "rallongée" ou
trochoïde }
const L = 640; H = 200; {dimensions de l'écran}

function f (t : real) : real ;


begin f := -3*cos (t ) - 2*cos (3*t ) end ;

function g (t : real) : real ;


begin g := -3*sin (t ) + 2*sin (3*t ) end ;

procedure axes (A, B, C, D : real);


{ A, B, C, D seront des bornes données. Si on donne x entre A et B, et y entre C et D alors les coordonnées sur
l'écran sont U = L*(X-A) / (B-A) et V = H*(Y - D) / (C - D). On élargira cette question dans les programmes
graphiques suivants }
begin if A*B < 0 then begin moveto ( L*A div (A - B) , 0); lineto (L*A div (A - B), H) end;
if C*D < 0 then begin moveto (0, H*D div (D - C)); lineto (L, H*D div (D - C)) end end;
procedure trace (A, B, C, D, t1, t2, h : real ); 23
{ Réalise le tracé de la courbe pour A < X < B et C < Y < D et t1 < t < t2 avec t allant de h en H ( le pas de
calcul ).}
var t : real ;
begin axes (A, B, C, D) ; t := t1; moveto (L*(f(t) - A) div (B-A) , H*(g(t) - D) div (C-D));
repeat lineto (L*(f(t) - A) div (B-A) , H*(g(t) - D) div (C-D) ); t := t+h
until t > t2 ;
end ; { Le point (f(t), g(t)) décrit la courbe quand t va se 0 à 2π }

begin trace (-6, 6, -6, 6, 0, 6.28, 0.01) { t va de 0 à 2π } end.

Exécution

Remarques

Les multiples versions de Pascal et les multiples cartes grahiques obligent au maximum de
souplesse, c'est à dire qu'il est préférable de conserver les vraies coordonnées x, y dans
l'utilisation de procédures que l'on appelera "place (x, y)" et "trace(jusqu'à) (x, y)" lesquelles
seront fonction d'une fenêtre [A, B]*[C, D] que l'on définira en constantes, variables globales
ou paramètres suivant la souplesse désirée.
Par exemple pour Macintosh :
uses memtypes, quickdraw;
const A = -2; B = 2; C = -1; D = 1.5; L = 480; H = 270;
{Pour x entre A et B, et y entre C et D les coordonnées d'écran sont U = L*(X-A)/(B-A) et V = H*(Y-D)/(C-D)}

procedure place (x, y : real); {place le point de vraies coord x, y}


begin moveto ( (L*(x - A)) div (B - A), (H*(y - D)) div (C - D) ) end;
procedure trace (x, y : real); {trace le segment vers le point de vraies coord x, y}
begin lineto ( (L*(x - A)) div (B - A), (H*(y - D)) div (C - D) ) end;

On pourra utiliser aussi une procedure "grospoint" placant cinq ou neuf point autour de (x, y).
Dans l'ancienne version de Turbo-Pascal, on accède au mode graphique par "hires", un point
est placé par plot (U, V, couleur) et un segment de droite est décrit par draw (U1, V1, U2, V2,
couleur).
24
3-1° Compléter le programme de tracé de courbes pour des courbes paramétrées de
type x = f(t) et y = f(t) ou ρ = f(θ) ou des tracés aléatoires de courbes de Lissajoux x =
cos(nt) y = sin(mt), de rosaces ρ = a+cos(bθ/c), de cycloïdes x = (n+1)cos(t)-mcos(n+1)t et y
= (n+1)sin(t)-msin(n+1)t, en prenant des petites valeurs entières de m, n, a, b, c au hasard.

program courbes;{ Courbes paramétrées X=f(t) et Y=g(t) turbo-pascal sur mac}


uses memtypes, quickdraw;
const A = 0; B = 20; C = -2; D = 2 ; L = 480; H = 270;
procedure place (x, y : real);{place le point de vraies coord x,y}
begin moveto (L*(x-A)) div (B-A), (H*(y-D)) div (C-D)) end;
procedure trace (x, y : real);{trace le segment vers le point de vraies coord x,y}
begin lineto (L*(x-A) div (B-A), (H*(y-D)) div (C-D)) end;

function f (t : real) : real; begin F := t {ou autre définition} end;

function g (t : real; m : integer) : real;


var i, s : integer; v : real; {ici m est un paramètre servant au problème suivant }
begin v := 0; s := 1;
for i := 1 to m do begin v := v + s* sin(i*t) / i; s := -s end;
G:= v end;

function ro (t : real) : real; {cas de la spirale d'Archimède }


begin ro := t end;

procedure axes; begin if A*B <0 then begin place (0,D); trace (0,C) end;
if C*D <0 then begin place (A,0); trace (B,0) end end ;

procedure courbe ( x1, x2, h : real; m : integer);


var x : real;
begin axes ; x := x1; place (x, g(x, m)); repeat trace(x , g (x, m)); x:= x+h until x > x2 end;

procedure polaire (t1, t2, h : real) ;


var t : real;
begin t := t1; place ( ro(t)*cos(t), ro(t)*sin(t));
repeat trace (ro(t)*cos(t), ro(t)*sin(t)) ; t := t + h until t > t2 end ;

Puis un programme appelle "polaire", par exemple : ρ = 0.5 + cos(8θ/3)

Essayer x = cos 3t - 2sin3t, y = ln|sin2t - cos5t| pour une belle architecture, ρ = √(2|cos2θ) - 1
pour avoir les pétales avec la fleur et 1/ρ = √(1+sin2θ) + √(1-sin2θ) vous aurez une surprise!
25
3-2° Tracer une famille de courbes sur un même repère, exemple : f(x) = e-x|x|m pour m
allant de -2 à 4 de 1/2 en 1/2

On écrit , en plus de f, g et "courbe" du programme précédent :

procedure famille (deb, fin, pas : integer);


var m : integer; {trace les courbes Γm pour m de "deb" à "fin" avec le "pas" }
begin m := deb; repeat courbe (0, 20, 0.1, m); m := m + pas until m > fin end;

begin famille (-2, 4, 0.5) end. {©ette ligne constituant le programme pour les 13 courbes ci-dessous}

Ou bien, si f(x) = 1/(1 + x 2m) pour 1 < m < 9, on peut voir la convergence de cette suite de
fonction vers une fonction discontinue.

3-3° Dans un cadre carré où chaque côté est régulièrement subdivisé en 10, tracer des
segments joignant des points de deux côtés adjacents correspondant aux mêmes subdivisions,
à la façon des tableaux de fils tendus par des clous.

3-4° Produire une série de carrés emboîtés, chacun ayant ses sommets en des points divisant
dans le même rapport les côtés du carré dans lequel il s'inscrit.
26
3-5° Dessiner une toile d'araignée (une sorte de spirale octogonale).

3-6° Développement de Fourier. Etudier graphiquement la convergence d'un


développement, par exemple fn (x) = sin(x) - sin(2x) / 2 + ... + (-1)nsin(x) / n. La série de
Fourier converge vers la fonction de période π, nulle pour kπ (k entier) et valant x sur ]-π, π[.

On a représenté les tracés superposés de f 2, f8 et f30 sur les deux dernières arches.

3-7° Tracé de la clothoïde x = a ∫ 0,t cos πu2 /2 du y = a ∫0,t sin πu2/2 du (Intégrales de
Fresnel, u est la variable d'intégration). Cette courbe a un rayon de courbure proportionnel à
la distance parcourue sur la courbe (son équation intrinsèque R = πs/a2 est directement
utilisable si l'on dispose des instructions de manipulations de "tortues" au chapitre suivant.)

On se servira de :
function integrale (a, b : real; n: integer) : real; {donne l'intégrale de F sur [a, b]}
var s, h : real; i : integer;
begin h := (b-a)/n/2; s := f(a) + f(b) + 4*f(a + h);
for i := 1 to n-1 do s := s + 2*f(a + 2*i*h) + 4*f(a + (2*i+1)*h);
integ := h*s/3 end;

3-8° Tracé des fn(x) = 1/π ∫[0,π] cos|nu - sin xu| du, pour les entiers 1 ≤ n ≤ 5 et 0 ≤ x ≤ 10
avec Simpson.
27
3-9° Faire rebondir une balle à l'intérieur d'un cadre rectangulaire.

3-10° Ensemble de Mandelbrot : On cherche l'ensemble des complexes c de [-2, 1]2 tel que
l'itération de la suite déterminée par z0 = 0 et zn+1 = f(zn ) = zn 2 + c ait ses 10, 30, ou 60
premiers termes (différence entre les deux figures) dans un cercle de centre O et de rayon 3
(ce qui ne veut pas dire qu'elle converge, ni que cette "convergence" ait lieu pour les autres
complexes que 0, c'est l'ensemble des c tels que Kc ensemble de Julia, soit connexe). Pour
chaque point c d'une fenêtre de l'écran. (L'axe des x est dirigé ici vers le bas, l'axe des y vers
la droite. Par ailleurs la borne 2 peut suffire.)

Program mandelbrot; uses memtypes, quickdraw; {Accès aux fonctions graphiques sur Macintosh }
const A = - 3; B = 2; C = - 1.2; D = 1.2 ; L = 480; H = 270; { On définit une fenêtre }
procedure point(x, y : real);
var u, v : integer;
{Pour x entre A et B, et y entre C et D les coordonnées à l'écran sont U=L*(X-A)/(B-A) et V=H*(Y-D)/(C-D)}
begin u := round(L*(x-A)/(B-A)); v := round(H*(y-D)/(C-D)); moveto(u, v); lineto(u, v) end;

function conv (x, y : real) : boolean; {dit si la suite zn+1 = zn2+x+iy converge, le départ étant toujours 0}
var x0, y0, x1, y1 : real; it : integer;
begin x1 := x; y1 := y; it := 1;
repeat it := it + 1; x0 := x1; y0 := y1; x1:=x+x0*x0-y0*y0; y1 := y + 2*x0*y0
until ( abs(x0)+abs(y1) > 3) or (it > 30);
conv := (it > 30) end;

procedure cardio (p : real); { p est simplement un pas de calcul pour x et pour y }


var x, y : real;
begin x := -2;
repeat y := 0;
repeat if conv(x, y) then begin point (x, y); point(x,-y) end; y := y + p until y > 2;
x := x + p
until x > 1 end;

begin cardio (0.009) end.


Un résultat récent de Mitsuhiro Shishikura montre que la frontière de M est un ensemble 28
fractal de dimension 2.

3-11° Un autre programme inspiré de ce dernier produit de magnifique résultats, c'est l'étude
des points z0 de convergence de la suite zn+1 = f(zn ) = zn 2 + 0,78+ 0,2i. On calcule les
parties réelle et imaginaire x, y de zn avec n limité à 20, dès que x2 + y2 > 5, on considère
qu'il y a divergence, sinon on place le point et ses symétriques par rapport aux axes.

procedure f (x, y : real; var re, im : real); { calcule le complexe (re, im) image de (x, y) par f }
begin re := sqr(x) - sqr(y) - 0.8; im := 2*x*y - 0.2 end;

function converge (x, y : real): boolean;


{on calcule zn pour n < 20, dès que x2 + y2 > 10, on dit qu'il y a divergence}
var re, im : real; it : integer;
begin re := x; im := y; it := 0;
repeat it := it + 1; f (re, im, re, im)
until (it > 20) or (sqr(re) + sqr(im) > 5);
if it > 10 then converge := true else converge := false
end;

procedure dessin (p : real); var x, y : real;


{On cherche l'ensemble des complexes z de [-1, 1]2 telle que l'itération de z:=f(z) converge et en ce cas on
place le point et son symétrique par rappor à l'origine.}
begin x := 0;
repeat y := 0;
repeat if converge (x, y) then begin point (x, y) ; point(-x, y);
point (-x, -y); point(x, -y) end;
y := y + p
until y > 1;
x := x + p
until x > 2 end;

3-12° Pour construire un diaphragme, on trace un polygone régulier de n côtés mesurés par c.
Puis en avançant sur l'un des côtés de rc (avec 0 < r < 1) on trace un autre polygone régulier
de n sommets dont chacun se trouve sur un côté du polygone précédent au même rapport r.
(Elégante solution dans le langage Logo disposant d'instructions pour fixer un cap et calculer
une distance parcourue.)
29
3-13° Enveloppes de droite, sur un cercle de rayon r, on joint les points de coordonnées
polaires (r, θ) et (r , nθ) en faisant varier θ de p en p (par exemple p = 5°). Pour n = 2 on
obtient une cardioïde, n = 3 une néphroïde etc...

procedure env (p : real ; n : real ); {lancée avec : n = 1,5 ou env (0.1 , 4.5) pour le dernier dessin.}
var a : real;
begin a := 0;
repeat place (cos (a), sin (a)) ; trace (cos (n*a), sin (n*a)) ; a := a + p
until a > 8*pi
end;

3-14° (Rien à voir avec π) Simulation d'un vol de mouettes, [Bonabeau 94] on place
aléatoirement, mais vers le coin gauche en haut de l'écran, n = 20 points. Chacun ayant une
vitesse entre 2 et 20 pixels par intervalle de temps et une direction (initialement vers le coin
en bas à droite). Chaque oiseau doit contrôler ses camarades dans un rayon de 10 pixels et
éviter les collisions en modifiant sa direction (±5° par exemple), puis (règle moins prioritaire)
atteindre leur vitesse moyenne en modifiant éventuellement la sienne de ±1, sinon il doit
modifier sa direction pour s'approcher de leur centre de gravité.
30
3-15° Morphologie mathématique
Etant donné un ensemble E dans R2, et B un "élément structurant" qui peut être un voisinage
fermé et connexe de 0. On peut considérer par exemple B = {x / |x| ≤ ε}, mais le plus simple
est de prendre la norme "sup" pour laquelle B est un carré dans le plan, et Bz est donc le carré
de côté 2ε centré sur z. On définit :
Dilatation de E par rapport à B : DilB(E) = {x / E∩Bx ≠ Ø }
Erosion de E : EroB(E) = {x / Bx < E}
Il s'agit donc d'une interprétation tout à fait discrète des notions topologiques d'intérieur et
d'adhérence.
Ouverture de E : Dil (Ero (E)) qui donne de bons résultats en traitement d'images, le but étant
de rectifier des formes et d'obtenir des images schématiques. On veut une image non bruitée à
partir d'une image floue. Faire un petit logiciel de démonstration avec quelques images tests,
réalisant la dilatation de l'érosion floue en permettant un choix de a.

Solution : c'est un excellent exemple de manipulations de boucles.


program morphologie ;
uses memtypes, quickdraw ; {sur Macintosh}
const m = 80 ;
type tab = array [1..m, 1..m] of boolean ; { on anticipe sur le chapitre 5 }
var E, DE, RE, RDE, DRE : tab ;{ Ce sera l'essai du programme } eps : integer ;

procedure afficher (T : tab ; u, v : integer ) ;


{ affiche le tableau T avec le coin haut à gauche en ligne u, colonne v }
var i , j : integer ;
begin moveto (u, v); lineto (u+m+2, v) ; lineto (u+m+2, v+m+2);
lineto (u, v+m+2); lineto (u, v) ;
for i := 1 to m do for j := 1 to m do
if T [i, j] then begin moveto (u+j+1, v+i+1); lineto (u+j+1, v+i+1) end
end;

function max (a, b : integer) : integer ; begin if a < b then max := b else max := a end ;

function min (a, b : integer) : integer ; begin if a < b then min := a else min := b end ;

function interieur (l, c , a : integer ; T : tab) : boolean ; {teste si tous les éléments de T autour de (l = ligne, c =
colonne) dans un "rayon" de a, sont dans T }
var i, j : integer ; test : boolean ; {test = "tout ce qui a été vu est dans T"}
begin i := max (l - a, 1); test := true ;
while (i <= min (l + a, m)) and test do
begin j := max (c - a, 1);
while T[i, j] and (j <= min (c + a, m)) do j := j + 1;
test := (j > min (c + a, m)) ;
i := i+1 end ;
interieur := (i > min (l+a, m)) end;

procedure erosion (T : tab; a : integer ; var ER : tab); {construit TR à partir de T}


var i, j : integer ;
begin for i := 1 to m do for j := 1 to m do ER [i, j] := interieur (i, j , a, T) end;

function adherent (l, c , a : integer ; T : tab) : boolean ; { teste si au moins un élément autour de (l = ligne, c =
colonne) dans un "rayon" de a, est dans T }
var i, j : integer ; test : boolean ;
begin i := max (l-a, 1);
repeat j := max (c-a, 1);
repeat test := T [i, j] ; j := j + 1 until test or (j > min (c+a, m)) ; i := i + 1
until test or (i > min (l + a, m)) ;
adherent := test end;
procedure dilatation (T : tab; a : integer ; var DL : tab); {construit DL à partir de T} 31
var i, j : integer ;
begin for i := 1 to m do for j := 1 to m do DL[i, j] := adherent (i, j , a, T) end;

procedure creation (var T : tab) ;


{pour le premier ensemble E, on choisit les points (i, j) où une sinusoïde en 1 / ij, est positive}
var i, j : integer;
begin for i := 1 to m do for j := 1 to m do T[i, j] := (0 < sin(9*m*m/(i*j)) )
{et si création aléatoire : T[i, j] := (2 < 3*(1-abs(i/m-0.5)-abs(j/m-0.5)) + random) }
end;

begin eps := 1; creation (E) ; afficher (E, 30, 70);


erosion (E, eps, RE); afficher (RE, 170, 20);
dilatation (RE, eps, DRE); afficher (DRE, 300, 20);
dilatation (E, eps, DE); afficher (DE, 170, 130);
erosion (DE, eps, RDE); afficher (RDE, 300, 130) end.

Pour ne pas avoir à rentrer une image point par point, on peut aussi en créer une
aléatoirement.

Naturellement il faut expérimenter sur une véritable image (bruitée) pour pouvoir juger. Ici
epsilon était fixé à 1, ce qui signifie qu'au cours d'une transformation, pour chaque point, 9
appartenances à l'image de points voisins ont été testés.
32
3-16° Méthode d'Euler pour les systèmes différentiels du type :
x' = f(x, y)
x et y sont fonctions de la variable t, et leurs dérivées vérifient
y' = g(x, y)
En tout point M0 du plan passe une solution courbe paramétrée x(t), y(t), (une trajectoire), le
principe de la méthode d'Euler est de confondre cette trajectoire avec la tangente en M0 sur
une petite longueur ds. On trace donc un petit segment M0M1 en calculant les coordonnées
du point M1 : x1 = x0 + dt*f(x0, y0) y1 = y0 + dt*g(x0, y0)
Si on tient à avoir des segments de part et d'autres, on changera le signe de dt, d'où le
paramètre s (signe) dans la procédure Euler.
Si on désire que le segment M 0M1 ait toujours la même longueur ds, alors il suffit d'après le
théorème de Pythagorre d'avoir ds2 = dx2 + dy2 , soit, de prendre une variation de la
variable:
ds
dt =
2 2
f (x 0 , y 0) + g (x 0 , y 0)
La mise au point du programme demande encore que les trajectoires soient inscrites dans une
fenêtre A ≤ x ≤ B, C ≤ y ≤ D que le nombre d'applications de la méthode d'Euler ne soit pas
trop grand (on s'est fixé ici 50 itérations au maximum), et que l'on ne soit pas au voisinage
d'un point singulier |x'| + |y'| < epsilon.

Exemples de trajectoires

x' = x + y et y' = -2x + y x' = y(y-1)(y+1) y' = sin(x+y)

x' = (x-cos y)(y-cos x) et y' = sin x x' = (x2+y 2-1)(x2+y 2-9) et y' = x2+y2-4
program systdiff; { turbo-pascal sur mac } 33
uses memtypes, quickdraw ;
const A = -2; B = 3; C = -3; D = 2; eps = 0.05;

function f (x, y : real) : real; begin f := y-x*x*x/3 end; { figure ci-dessous }

function g (x, y : real) : real; begin g := -x end;

procedure segment (x, y : real); { Si on donne x entre A et B, et y entre C et D alors les coordonnées sur l'écran
sont U = 510*(X-A)/(B-A) et V = 340*(Y-D)/(C-D) }
begin lineto ( 510*(x-A) div (B-A) , 340*(y-D) div (c-d)) end;

procedure euler (S : integer; var err : boolean; x0, y0 : real;var x1, y1 : real);
{calcule un point M1 voisin du point M0 donné, S=±1 est le sens}
var dx, dy, dt : real;
begin err := false; dx := f (x0, y0); dy := g (x0, y0);
if abs(dx) + abs(dy) < eps
then err := true
else begin dt := ds/sqrt(dx*dx+dy*dy); x1 := x0 + S*dt*dx; y1 := y0 + S*dt*dy end
end ;

procedure trajectoire (x,y real);


{dessine un morceau de la trajectoire passant en x,y}
var N, S : integer; x0, y0, x1, y1 : real; message : boolean;
begin for S := 0 to 1 do {deux morceaux à gauche et à droite}
begin x0 := x; y0 := y; N := 1;
moveto ( 510*(x-A) div (B-A) , 340*(y-D) div (C-D));
repeat euler (2*S-1,message, x0, y0, x1, y1);
if not (message) then begin segment (x1, y1); N := N+1; x0 := x1; y0 := y1 end
until message or (N>50) or (x1<A) or (x1>B) or (y1<C) or (y1>D)
end
end;

procedure dessin ; { On distribue les points de passage de trajectoires en les disposant régulièrement sur des
cercles centrés dans l'écran, toutes sortes d'autres choix sont possibles. }
var i, j, diviseur : integer;
begin
diviseur := 10 ;
for j:= 2 to 5 do for i:= 1 to diviseur do
trajectoire((B+A)/2 + (B-A)*cos(2*pi*i/diviseur)/j, (C+D)/2 + (D-C)*sin(2*pi*i/diviseur)/j )
end;

var ds : real;
begin {le programme} ds := (B-A)/50; dessin end.

Et voilà le résultat pour f(x, y) = y - x*x*x/3 et g(x, y) = -x


34

Exemple : système différentiel x' = x - xy et y' = xy - y sur [0, 8]2


On pourra résoudre également des équations différentielles polaires ρ' = f(ρ, θ), ainsi ρ' =
ρsin(ρθ) , ρ' = tan(8atan(sinθ)) ou ρ' = ρtan(5atan(sin2θ)) donnent de beaux résultats.

3-17° Méthode de Kutta-Runge : Pour le même genre de système différentiel :


x' = f(x, y)
x et y sont fonctions de la variable t, et leurs dérivées vérifient
y' = g(x, y)
En un point M 0 (x0, y0) correspondant à t0, passe une trajectoire de tangente TE sur laquelle
on définit par la méthode d'Euler le point Me (x0 + dt*f(x0, y0), y0 + dt*g(x0, y0)).
Soit P le milieu du segment [M0, Me], en ce point passe une autre trajectoire de tangente TP,
la méthode de Runge - Kutta consiste alors à prendre le point Mr correspondant à t0+ dt sur la
parallèle TR passant en M0 à TP.
Faire un schéma et calculer les coordonnées de Mr, puis construire une procédure en pascal,
calculant ce point à partir de M 0 en appelant deux fois f et deux fois g : xP = x0 +
dt*f(x0,y0)/2 et yP = y0 + dt*g(x0,y0)/2 puis xR = x0 + dt*f(xP,yP) et yR = y0 + dt*g(xP,yP)

3-18° Les lignes équipotentielles pour deux champs de gravitation comme la terre et la
lune sont données par U(x, y) = K(m/√[(x - xf)2 + y2] + m'/√[(x - xf')2 + y2]) constant.
Cela donne le système différentiel x' = -Ky (m/√[(x - xf)2 + y2] + m'/√[(x - xf')2 + y2]) et y' =
K (m(x - xf)/√[(x - xf)2 + y2] + m'(x - xf')/√[(x - xf')2 + y2]). En faire des tracés.

3-19° Equations du second ordre, par la même méthode, on pourra traiter des problèmes
tels que:
Pendule de Foucault x'' = -kx + eps*sin(lat*y')
y'' = -ky - eps*sin(lat*x')
où latitude en radian = π/4, k = 1, eps = 0.05 et initialement x0 = 0,5, y0 = 0, x'0 = 0, y'0 = 1
Pendule sphérique x'' = -kx/√(1-x2-y2) + eps sin(lat*y')
y'' = -ky /√(1-x2-y2) - eps sin(lat*x')
Champ en r2 x'' = -krx - qx'
y'' = -kry - qy'
Champ newtonien x'' = -kx/r3 - qx'
y'' = -ky/r3 - qy'
35
3-20° Balle de tennis (problème du professeur Alba)
Pour une balle de tennis ou de golf, on dit qu'elle est "liftée" si sa vitesse de rotation N est
positive en étant perpendiculaire au plan de sa trajectoire, elle est "coupée" si N < 0, elle est
alors beaucoup plus "longue" et, lorsque N prend de grande valeurs (vers 100 tours à la
seconde) la trajectoire peut faire en théorie de curieuses boucles (effet Magnus). Les
équations différentielles du second ordre qui déterminent la trajectoire s'écrivent en fonction
des caractéristiques de la balle (les deux coefficients seront pris égaux à 0,01) de g = 9,81 et
de N.
2 2
k d 2 2 2aM k d 2 2 2aM
x' ' = - M( ) x' x' + y' + Ny' et y' ' = - M( ) y' x' + y' - Nx' - g
m 2 m m 2 m

program magnus; { turbo-pascal sur mac }


uses memtypes, quickdraw ;
const L = 480; H = 270;
var A, B, C, D, n, r, pas, v0, a0, y0 : real;
procedure place (x, y : real); {place le point de vraies coord x,y}
begin moveto (L*(x-A) div (B-A), (H*(y-D) div (C-D)) end;
procedure trace (x, y : real); {trace le segment vers le point de vraies coord x,y}
begin lineto (L*(x-A) div (B-A), (H*(y-D) div (C-D)) end;

function f (dx, dy : real) : real; begin f := -0.01*sqrt (sqr (dx) + sqr (dy)) * dx + 0.01*N*dy end;
function g (dx, dy : real) : real; begin g := -0.01*sqrt (sqr (dx) + sqr (dy)) * dy - 0.01*N*dx - 9.81 end;
procedure euler ( pas : real; var x, y, dx, dy : real) ; {trace un segment et modifie le point et ses dérivées}
begin place (x, y); x := x + pas*dx; y := y + pas*dy;
dx := dx + pas*f(dx, dy); dy := dy + pas*g(dx, dy); trace(x,y) end;
procedure trajectoire (cote, vitesse, angle, r : real ; var n : real); {trace une courbe avec des données initiales et
toujours abscisse=0, n est la vitesse de rotation initiale, r le coefficient d'amortissement}
var x, y, dx, dy : real;
begin x := 0; y := cote; dx := vitesse*cos (angle); dy := vitesse*sin (angle);
while (0<=x) and (0<= y) and (x < B) and (y < D) do begin euler (pas, x, y, dx, dy); n := n*r end
end;
begin writeln ('Lancer d''une balle de tennis ou de golf');
write ('Donnez la cote initiale d''où est lancée la balle en mètres '); readln (y0);
write ('Donnez la vitesse initiale (m/s) '); readln (v0);
write ('Donnez l''angle initial (degrés) '); readln (a0);
write ('Quelle est l''altitude maximale ? '); readln (D); C := 0;
write ('Quelle est la distance maximale ? '); readln (B); A := 0; pas := (B-A)/1000;
write ('Rotation initiale en nb de tours/s ? '); readln (n);
write ('Coefficient d''amortissement de cette rotation (ex. 1 ou 0,9) ? '); readln (r);
clearscreen; axes; trajectoire(y0, v0, pi*a0/180, r, n) end.

Un résultat pour une balle tirée de 1m de haut à 10° de l'horizontale à 30 m/s et N=-150 sur
une distance de 200 m. (r = 1)
36
3-21° L'évolution de populations telles que les pucerons et les coccinelles peut être
modèlisée de la façon suivante :
Soit P l'effectif des prédateurs et V celui des victimes, partant des équations dV/dt = aV -bP
et dP/dt = cP - dP où a est le taux de croissance des proies en l'absence de prédateurs, b est le
nombre de proies capturées par prédateur et par unité de temps, c le taux de conversion de
proies en prédateurs et d le taux de mortalité des prédateurs par manque de proies.
On dispose du modèle de Lokta-Volterra pour lequel a est constant, b = b'V, c est négatif et
constant, d = b'd'V, alors pour des valeurs données de a, b', c et d' les trajectoires convergent
vers un point P = a/b' et V = -c/b'd'.
Le modèle à densité et satiété dépendant est : dV/dt = r(1-V/k)V - c(1- exp(-aV/c))VP
dP/dt = -mP + bc(1- exp(-aV/c))VP

3-22° Dans la théorie générale de la dynamique des systèmes un système linéaire du


premier ordre a un équilibre stable si ses valeurs propres ont leurs parties réelles toutes
négatives (si au moins une est positive ou nulle : équilibre instable) sinon oscillations. Pour
un organisme on définit une règle de construction (anabolisme) et une de destruction
(catabolisme) proportionnelle au volume soit à la masse x. On aura donc x' = axα - bx où α
est le degré de métabolisme (la règle de surface est la proportionnalité de l'anabolisme à la
surface donc α = 2/3). Programmer cette équation dont les solutions sont
x1- α = x01- α e-(1- α )bt + a(1-e-(1- α )bt)/b

3-23° Loi de verhulst. Une population d'effectif xn au moment n, a un taux de croissance r -


cxn, c'est à dire xn+1 = (1+r)x n - c(xn)2
a) Montrer que si r < 2, il y a convergence vers L vérifiant cL = r
b) Tracer les points d'une telle suite dans ce cas, et montrer que si r > 2 il y a deux valeurs
d'adhérence.

3-24° Loi de Mira. Le couple (xn, yn ) satisfait a une relation de récurrence :


xn+1 = byn + f( xn ) et yn+1 = -xn + f(xn+1 ) avec f(x) = ax + 2(1 - a)x2 / (1 + x2 ) et a = 0,31
et b = 1 représenter la suite de ces points dans [-30, 30]*[-30, 30] pour x0 = y0 = 0.

3-25° Une suite chaotique. soit une suite pn vérifiant une relation de récurrence très simple
pn+1 = mpn.(1 - pn) Avec p0 = 0.1 ou 0.4 par exemple, représenter pn en abscisse et le nombre
de générations en ordonnées logarithmiques. On observera alors que cette suite converge pour
m < 3, puis qu'elle est alternée, puis vers m = 3.5 elle est alternée sur 4 valeurs d'adhérences,
enfin vers m = 3.6, elle se met à parcourir très rapidement 4, 8, 16 ... valeurs d'adhérences, ce
qui produit le dessin d'un bel arbre binaire.

program chaos ;
uses memtypes, quickdraw;
const A = 0.1; B = 1; C = - 0.1; D = 7 ; L = 480; H = 270;

procedure place (x, y :real);{place le point de vraies coord x,y}


var u, v : integer;
begin u := round (L*(x-A)/(B-A)); v := round (H*(y-D)/(C-D)); moveto (u, v); lineto (u, v) end;

procedure suite (ni : integer; m : real); {ni est le nombre d'itérations}


var n : integer; p : real;
begin p := 0.1; place (p, 0);
for n := 1 to ni do begin p := m*p*(1-p); place (p, ln (n)) end; end;

begin suite (500 , 3.7) end.


37
3-26° Courbes de Bezier. Etant donnés des points de contrôles ou pôles P0, P1, .... Pn, l'idée
est de construire une courbe qui soit "attirée" par ces pôles, sans nécessairement y passer. (Le
problème de trouver la courbe passant vraiment par des points est résolu avec les polynôme
de Lagrange) L'application évidente étant la possibilité en CAO de créér des formes à partir
de points que l'utilisateur pourra déplacer à son gré dans un but technique ou artistique.
Ce problème a reçu une solution sous la forme de courbe de Bezier associée aux points P0, P1
.... Pn : c'est la courbe définie grâce au paramètre t par :
n
OM(t) = ∑ B k,n(t).OP k kk
où B k,n(t)= C nt (1-t)
n-k
sont les polynômes de Bernstein
k=0
On vérifie que la somme des poids vaut 1, que cette courbe passe en M0 et en Mn, elle est
tangente en M0 à M0 M 1 , et en Mn à Mn-1 M n , par ailleurs, si n = 2, M1 a une tangente
parallèle à M0M2.
Construction récurrente, par un jeu de factorisations, on peut montrer que :
OM(t) = B 0,n-1(t)[(1-t)OP 0 + tOP 1] + B 1,n-1(t)[(1-t)OP 1 + tOP 2] + ......
+ B n-1,n-1(t)[(1-t)OP n-1 + tOP n]
On en déduit le résultat suivant que le point Mt relatif aux n+1 points de départ, est le même
que celui relatif à la courbe de Bezier déterminée par les n points P' tels que:
OP' k(t) = (1-t)OP k + tOP k+1
En réitérant cette propriété, on arrive à déduire Mt comme relatif à deux seuls points, puis
pour finir, à un seul, en déduire un algorithme de tracé (algorithme de De Casteljau).

Entrée des points P0, ... , Pn


Pour t allant de 0 à 1 de h en h
Pour i allant de 0 à n faire Mi ← Pi {recopie des points initiaux}
Pour i allant de 1 à n
Pour j allant de 0 à n-i faire Mj ← (1-t)Mj + tMj+1
Tracer M0
Ainsi, en débutant, M0, ... ,Mn, sont remplacés par d'autres points toujours apelés M0, ..., Mn-
1, pour i = 1, puis pour i = 2, ils le sont à leur tour par M0, ... ,Mn-2. Ainsi arrivé à i = n les
deux points M0, M1 sont remplaces par un M0 unique qui est tracé. Cet algorithme est de
complexité analogue, mais il permet d'épargner le calcul des polynômes de Bernstein.

program bezier;
uses memtypes, quickdraw; {turbo-pascal sur macintosh}
const A = 0 ; B = 10 ; C = 0 ; D = 10 ; M = 10 ; {tout à fait conventionnel}
type matrice = array [0..M, 1..2] of real;
var nb : integer ; poles : matrice;
function col (X : real) : integer ; begin col:= 480 * (X-A) div (B-A))end;
function lgn (Y : real) : integer; begin lgn := (270 * (Y-D) div (C-D) end;
procedure points (N : integer ; P: matrice); {On place les N points de la matrice P sur l'écran}
var i : integer;
procedure grospoint (l, c : integer) ; { trace un petit carré}
begin moveto (l-1, c-1); lineto (l-1, c+1); lineto (l+1, c+1); lineto (l+1, c-1); lineto (l-1, c-1) end;
begin for i := 0 to N do grospoint (col (P [i, 1]), lgn (P [i, 2])) end;
procedure trace (N : integer ; P : matrice); {On reçoit N+1 points rangés dans la matrice P} 38
var M : matrice; t, h : real; i, j : integer;
begin t := 0; h := 1 / (6*N) ; moveto (col (P[0, 1]), lgn (P[0, 2]));
repeat t := t + h ;
for i := 0 to N do begin M[i, 1] := P[i, 1] ; M[i, 2] := P[i, 2] end;
{On recopie les points initiaux dans M afin de les modifier}
for i := 1 to N do for j := 0 to N-i do
begin M[j, 1]:=(1-t)*M[j, 1]+ t*M[j+1, 1]; M[j, 2]:=(1-t)*M[j, 2]+ t*M[j+1, 2] end;
lineto (col (M[0, 1]) , lgn (M[0, 2]))
until t >= 1 end;
procedure entree (var N : integer ; var P : matrice);
var i : integer;
begin clearscreen; write('Combien de points allez vous donner ? '); readln (N); N := N -1;
for i := 0 to N do begin write ('abscisse du point n° ', i ,' = '); read (P[i, 1]);
write (' ordonnée = '); readln (P[i, 2]) end;
end;
begin entree (nb, poles); clearscreen; points (nb, poles); trace (nb, poles) end.

On peut ensuite concevoir toutes sorte de procedures :


procedure demo1; {On fixe un point de départ et un point d'arrivée}
var i : integer;
begin poles [0, 1] := 7; poles [0, 2] := 0; poles [2, 1] := 10; poles[2, 2] := 0;
for i := 0 to 4 do {On fait varier le point intermédiaire}
begin poles [1, 1]:=2*i; poles [1, 2] := 10; points (2, poles); trace (2, poles) end;
end;

procedure demo2; {On fixe un point de départ et un point d'arrivée. On fait varier deux points intermédiaires,
l'un monte, tandis que celui de droite descend}
var i : integer;
begin poles [0, 1] := 0; poles [0, 2] := 0; poles [3, 1] := 10; poles [3, 2] := 3;
for i := 0 to 10 do
begin poles [1, 1] := 2; poles [1, 2] := i; poles [2, 1] := 3+i/2; poles [2, 2] := 10-i;
points (3, poles); trace (3, poles) end end;
39
3-27° Les courbes Splines. En deux mots, c'est une généralisation de ce qui précede, si P0 ....
Pn sont les n + 1 points de contrôle, la courbe "spline" est donnée par :
n
OM(t ) = ∑ OP i N i,j (t ) pour 0 ≤ t ≤n
i=0
Formule dans laquelle est choisie un degré i (généralement 2 ou 3 en CAO), et une
subdivision (vecteur des noeuds) t 0 = 0 ≤ t1 ≤ t2 ≤ ... pour le paramètre t, dans laquelle on a
des entiers consécutifs où seuls le premier 0 et le dernier peuvent être répétés m fois. Le plus
souvent on prend m = j + 1, car en ce cas la courbe passera aux points extrêmes. Chaque Ni,j
est un polynôme (de Riesenfield) de degré j calculé par :
N i,0 (t ) = si t i ≤ t ≤t i+1 alors 1 sinon 0
(t - t i)N i,j-1 (t ) (t i+j+1 - t)N i+1,j-1 (t ) 0
N i,j (t ) = + (avec = 0)
(t i+j - t i) (t i+j+1 - t i+1) 0
Les transformations sur les points de contrôle restent locales à ces points et leurs voisins, et
on peut changer l'ordre j sans changer les points de contrôles. Si pour n + 1 points, le vecteur
noeud est formé par n + 1 fois 0 puis n + 1 fois 1, alors on retrouve les courbes de Bezier où
les polynômes Ni,n sont les polynômes de Bernstein de degré n.
Programmer le calcul des polynômes puis le tracé des splines. Tester avec la spline carrée
(ordre 2) pour les points (-1, 0), (-1, 2), (2, 2), (3, 1), (1, 0), et le vecteur noeud (0 0 0 1 2 3 3
3). Vérifier que la courbe est tangente aux milieux des côtés du polygone, sauf aux deux
extrémités où elle est tangente à ces deux côtés en leur extrémité.

3-28° Représentation de surfaces à partir de points de contrôles, on utilise les formules


suivantes pour la données d'une famille de points Pi,j déjà en quadrillage.
n m
Surface de Bézier : OM(u, v ) = ∑ ∑ OP i,j B i,n (u)B j,m (v) pour 0 ≤ u ≤ n et 0 ≤ v ≤ m
i = 0j = 0

n m
Surface Spline : OM(u, v ) = ∑ ∑ OP i,j N i,k (u)N j,l (v) pour 0 ≤ u ≤ n et 0 ≤ v ≤ m
i = 0j = 0

3-29° Elimination des parties cachées par l'algorithme de Hugh


On veut dessiner des courbes d'avant en arrière (au contraire de l'algorithme du peintre) en
conservant pour chaque 0 ≤ X ≤ L les ordonnées bas(X) et haut(X) des points d'abscisse X
déjà placés dans deux tableaux.
Si par la suite, à l'abscisse X on a bas(X) < Y < haut(X), alors cela signifie que (X, Y) ne doit
pas être placé mais que seule la partie visible du segment doit être dessinée.
Si (X0, Y0) est la position précédente alors on trace jusqu'au point de coordonnées :
(X0 + (X - X 0) [Y0 - bas(X0)] / [bas(X) - bas(X0) - Y + Y 0], [Y0bas(X) - Ybas(X0)]/ [bas(X)
- bas(X0) - Y + Y0]) si Y0 < bas(X0) et si haut(X0) < Y même relations avec "haut".
Si au contraire (X0, Y0) n'est pas tracé car bas(X0) < Y0 < haut(Y0) et que Y < bas(X) alors
on doit tracer un segment de (X0 + (X - X0) [Y0 - bas(X0)] / [bas(X) - bas(X0) - Y + Y0],
[Y0bas(X) - Ybas(X0)]/ [ bas(X) - bas(X0) - Y + Y0]) à (X, Y) et si Y > haut(X) c'est le point
analogue avec "haut".
Expérimenter cet algorithme sur le tracé d'une famille de sinusoïdes f(x) = sin (x + k) + k
pour k allant de 0 à n fixé.
40
3-30° Représentation de surfaces, si l'objet à représenter est dans le repère Oxyz, que le
point de vue est O' dont les coordonnées sphériques (r, θ, φ ) et que la fenêtre d'écran est
figurée par le plan perpendiculaire à OO' situé à la distance d de O', le but est de définir la
transformation M (x, y, z) --> M' (X, Y) dans l'écran.
z
O'
X

M'
O φ M
y
Y
θ

x
r permet de donner l'aspect du dessin à l'écran, si r est infini c'est une projection, sinon une
perspective.
d permet de régler la taille du dessin sans changer son aspect.
Les formules sont issues de la composition d'une translation de l'origine en O', d'une rotation
de - θ autour de O'z, d'une rotation de φ + π/2 autour de O'y et d'un passage en repère
indirect. Les coordonnées homogènes en facilitent l'écriture puisqu'elles permettent d'écrire
une translation en dimension 3 par un produit maticiel en dimension 4. Touver les formules
de transformation. On représentera en faisant une double boucle sur x et y, des surfaces telles
que z = (sin ρ ) / ρ pour un bel effet ou encore z = (sin x)(sin y) / (xy) ou le paraboloïde
hyperbolique z = x2 - y2

X -1 0 0 0 1 0 0 0 sin θ -co sθ 0 0 1 0 0 -rcosnθco sφ x


Y 0 -1 0 0 0 -sin φ co sφ 0 cosθ sinθ 0 0 0 1 0 -rsin θcosφ y
=
Z 0 0 1 0 0 -cosφ -sinφ 0 0 0 1 0 0 0 1 -rsin φ z
T 0 0 0 1 t
0 0 0 1 0 0 0 1 0 0 0 1
Ce qui permet d'obtenir en faisant Z = d, dans le cas où r est fini :
x sinθ - y cosθ z cosφ - x cosθ sinφ - y sinθ sinφ
X=d et Y = d
x cosθ cosφ + y sinθ cosφ + z sinφ - r x cosθ cosφ + y sinθ cosφ + z sinφ - r
et dans le cas d'une projection : X = y cosθ -x sinθ et Y = x cosθsinφ + y sinθsinφ - z cosφ
Remarques : Si par exemple θ = φ = 0, l'écran ne coupe qu'un axe, c'est la perspective à un
point de fuite.
Deux points de fuites : l'écran coupe deux axes pour par exemple φ = 0 et θ non droit.
Trois points de fuites si l'écran coupe les trois axes.
Perspective oblique si l'écran n'est pas perpendiculaire à OO', elle peut être cavalière si cet
angle est 45°, les lignes fuyantes ne sont pas raccourcies, et elle peut être "cabinet" pour
arccotan (0,5) = 63,4°
La projection orthogonale est dite axonométrique "isométrique" si les trois axes sont modifiés
dans le même rapport θ = 45° et φ = 35,26°, elle est dimétrique si deux axes seulement, par
exemple θ = 22,2° et φ = 20,7°
41

CHAPITRE 4

LE PASSAGE DES PARAMETRES


ET LA RECURSIVITE

Ce chapitre est le plus important de la première partie, on y expose les notions de variables
globales et locales ainsi que de paramètres passés par valeur ou par adresse, notions qui
sont extrêmement mal comprises des débutants.
Le douloureux problème du passage des paramètres
Une procédure est un sous-programme réalisant certaines actions suivant les valeurs de ses
paramètres. Ainsi peut-on décrire le travail à effectuer pour afficher une table de
multiplication, et lorsqu'on en désirera une, il sera évidemment nécessaire de préciser
laquelle, la table de 8 par exemple. On aura donc une procédure à un seul paramètre. Pour
programmer le travail en question, ce paramètre devra avoir un nom, S par exemple, et lors de
"l'appel", S prendra la valeur 8 (transmission par valeur). Lors d'un autre appel, S prendra une
autre valeur. Si maintenant on désire produire une table de longueur variable, (jusqu'à 10 ou
12 ou 15 etc...), on construira une procédure à deux paramètres de façon à l'utiliser en
donnant deux valeurs numériques, la table, et jusqu'où on veut aller.
Une fonction en Pascal est une procédure qui renvoie un résultat, ce n'est rien d'autre que la
notion mathématique de fonction (si bien sûr il n'y a pas de vilains effets de bord). La
différence visible entre la notion de fonction et celle de procédure réside dans le fait que dans
l'en-tête de la seconde figure aussi la liste de ses paramètres avec leurs types, alors qu'en plus,
pour une fonction, celle-ci est suivie de la déclaration de type de son résultat.
Il faut bien comprendre que les noms choisis pour les paramètres sont tout à fait arbitraires et
n'ont à voir que fortuitement avec ceux choisis pour les variables ou constantes du
programme. De façon plus imagée, on définit couramment en mathématique la fonction f(x)
= x+3, il est évident que la fonction f(y) = y+3 est la même. Si on pose x = 5, f(x) aura pour
valeur 8, x n'ayant pas été modifié ce qui n'aurait aucun sens.
La transmission par adresse (ou par référence) est fort différente, signalée en Pascal par le
préfixe "var" devant le paramètre formel, elle indique que le paramètre réel (l'objet lors de
l'appel) peut être altéré, voire créé (initialisé) lors de l'appel de la procédure. C'est ce que l'on
appelle un paramètre accessible en lecture et en écriture, ou encore un paramètre
donnée/résultat. Par exemple la fonction Pascal définie par :
f (var x : integer) : integer ; begin x := x + 3 ; f := x end;

aura l'effet suivant, si on initialise x à 5 et y à 10, f(x) et f(y) auront bien pour valeurs
respectives 8 et 13, mais après ces calculs, x et y conserveront les dites valeurs 8 et 13. On dit
alors que la fonction f provoque un effet de bord et c'est ce qu'il faudra s'efforcer d'éviter.
Rappel : la notion de variable est celle qui désigne au contraire un objet physique, ainsi X :=
3 affecte la valeur 3 à un objet nommé X qui n'a rien à voir avec l'objet Y ou Z ... Une
variable est globale si elle est utilisable par l'ensemble du programme, locale à une procédure
si elle n'a de sens que dans celle-ci. Ces 4 notions sont illustrée dans le programme du
paragraphe suivant.
La récursivité 42

C'est une notion fondamentale que l'on peut décrire simplement en parlant de la faculté d'une
procédure de s'appeler elle-même. On cite souvent la définition récursive du verbe "marcher"
comme : "marcher" = "mettre un pied devant l'autre" puis "marcher", en programmation une
telle définition bouclerait indéfiniment (exercice 3).
Le programme qui suit est destiné à montrer les différentes façons d'appeler une procédure ou
une fonction, à bien marquer les distinctions entre variables globales et locales d'une part ;
paramètres accessibles en lecture ou en écriture d'autre part ; et enfin à introduire la notion
essentielle de récursivité. On se propose de programmer des choses très simples telles que les
valeurs de 1+2+3+....+n , 1*2*3*....*n , 2n etc...
program potpourri ;
var X : real ; Y : integer ; { Ce sont les variables globales du programme. }
function som (P : integer ) : integer; { P est un paramètre formel }
var I, J : integer ;
{ Sont des variables locales à la fonction "som" elles ne servent que temporairement au calcul du résultat.}
begin J := 0 ;
for I := 0 to P do J := J + I; { Définition itérative de som }
write ('La somme des entiers inférieurs vaut :');
{ Ceci est un effet de bord, c'est-à-dire que l'impression à l'écran de cette phrase ne se fera que si le programme
appelle som, mais elle se produira chaque fois qu'il l'appellera .}
som := J end;
function fac (Q : integer ): integer ;
begin if Q = 0 then fac := 1 else fac := Q*fac (Q-1) end;
{ Définition récursive de "fac", le programmeur n'a pas besoin de variables locales. "fac" s'appelle elle-même, le
même nom Q servant à désigner toute une suite d'instanciations, par exemple fac(2) appelle fac(1) qui appelle
fac(0). On constatera qu'une définition itérative impose toujours l'emploi d'une variable locale, ne serait-ce que
pour évaluer le test d'arrêt, alors qu'ici, ce test est décidé par la valeur d'un paramètre.}
function pui (R : integer ) : real ;
function sgn (S : real ) : integer ;
{ Fonction locale à une autre fonction, "sgn" ne pourra être appelée en dehors de "pui" }
begin if S < 0 then sgn := -1
else if S > 0 then sgn := 1
then sgn := 0
end ;
begin { Début de la définition récursive de "pui" suivant le signe du paramètre }
case sgn (R) of 1 : pui := 2*pui (R - 1);
0 : pui := 1;
-1 : pui := 1/ pui (-R) end {du "case"}
end ;
procedure mul (S : integer ) ;
{ Contrairement aux fonctions, les procédures ne renvoient pas un résultat. On désire simplement faire
apparaître une table de multiplication. }
var T : integer ;
begin for T := 1 to 12 do writeln (T, ' * ', S ,' = ', T*S); end ;

procedure lec (var U : real ; var V : integer );


{U et V sont des paramètres accessibles en écriture, ce qui est précisé par le mot "var". Le but de cette procédure
est seulement d'obtenir un entier. Si U et V avaient des valeurs avant l'appel de "lec", celles-ci sont modifiées,
sinon elles sont crées.}
begin write ('Donnez un nombre '); readln (U); V := round (U) end;

{Ici commence le programme proprement dit, qui, comme la plupart des programmes Pascal, doit se contenter
d'appeler ses sous-programmes. }
begin lec (X, Y) ; if Y > 0 then writeln (som (Y),' factorielle : ' , fac (Y) ) ;
writeln ('exponentielle en base deux : ', pui (Y)) ; mul (Y) end.

Ce programme demande encore quelques commentaires; un effet de bord ou effet marginal,


a-t-on dit, est une action quelconque (affectation , impression ...) produite lors d'un appel de
fonction, il est recommandé de les éviter sauf naturellement si on souhaite expressément de
telles actions, ce qui est le cas des procédures "mul" et "lec".
Récursivité terminale 43

Supposons que la fonction f soit définie par f(x) = si R(x) alors g(x) sinon h(x, f(k(x))) où x
est éventuellement un n-uplet de valeurs. C'est l'expression générale d'une fonction récursive
"non terminale" : si la relation R est satisfaite, alors le calcul se termine avec celui de
l'évaluation de la fonction g, par contre si R n'est pas satisfaite, alors au moyen d'une
transformation k, il faut faire un autre appel de f (appel récursif) qui lorsqu'il sera terminé
n'achevera pas le calcul initial car il faut avoir conservé la valeur de x pour exécuter h (x, ...).
L'appel n'est pas terminal, signifie du point de vue informatique que chaque appel doit avoir
une zone propre en mémoire où il conserve tout son contexte : les paramètres qu'on lui a
passé et surtout la marque (si plus d'un appel) de l'endroit où il a été interrompu afin de
pouvoir "terminer" son travail.
Tout autre est la définition f(x) = si R(x) alors g(x) sinon f(k(x)) car, hormis le cas où l'appel
termine tout de suite quand R(x) est vrai, l'appel suivant pour la valeur k(x) termine en même
temps l'appel précédent. La récursivité terminale n'a pas besoin d'une occupation de la
mémoire qui soit proportionnelle au nombre d'appels, elle est constante tout comme pour un
calcul itératif.
Exemple, la fonction factorielle de façon récursive terminale :

function facbis (n, r : integer) : integer;


begin if n = 0 then facbis := r { cas où on termine vraiment, le résultat est r }
else facbis (n - 1, n*r)
end;
function fac (n : integer) : integer;
begin fac := facbis (n, 1) end; { c'est un simple appel à la fonction générale }

Récursivité et graphique

Les courbes fractales constituent un bon exemple de programme graphiques récursifs, très
longs et difficiles à programmer sans récursivité. Prenons le cas des courbes de Von Koch où
un segment va être divisé en n (ici n = 4) segments chacun égaux à 1/r du segment initial (ici
r est égal à 3 et la profondeur c est imagée de 0 à 5)
Plus généralement si le segment sert de premier vecteur d'une base orthonormée du plan, et 44
que l'on découpe en n segments non nécessairement égaux, on pourra placer les coordonnées
des
points de jointure dans deux tableaux u et v avec u(0) = v(0) = 0 et u(n) = 1, v(n) = 0.
L'algorithme est donc le suivant :
dragon (c, n : entiers x1, y1, x2, y2 : réels u, v : tableaux)
{va développer la fractale d'ordre c sur le segment joignant (x1, y1) et (x2, y2) }
si c = 0 alors on place (x1,y1) et on trace (x2, y2)
sinon pour i allant de 0 à n-1 on éxecute le dragon (c-1, n) du point
x1+u[i]*(x2-x1)+v[i]*(y1-y2), y1+u[i]*(y2-y1)+v[i]*(x2-x1) vers le point
x1+u[i+1]*(x2-x1)+v[i+1]*(y1-y2), y1+u[i+1]*(y2-y1)+v[i+1]*(x2-x1)
avec les mêmes tableaux u et v

Dimension d'une courbe fractale : par analogie avec les surfaces et volumes on dit que si
c'était une surface, d'une étape à l'autre on en a n fois plus mais r fois plus petit, on devrait
avoir n = r2 et si c'était un volume n = r3. Or ici on a n = rd qui peut donner la dimension d =
ln(n) / ln(r) soit ln(4) / ln(3) pour la courbe de Von Koch. Si on exerce sur un motif du dessin
une similitude de rapport r, et que l'on obtient n de ces mêmes motifs alors d est le rapport des
log de n et de d.
Manipulation de la tortue

Il s'agit d'une bibliothèque de fonctions présente dans le turbo-pascal mais surtout connue au
travers du langage LOGO lui même dérivé du LISP, dans laquelle un mobile appelé "tortue"
peut être déplacé grâce à des instructions du genre "avancer", "reculer", "tourner à droite",
"lever le crayon", "gommer", etc...
Par exemple la construction d'un polygone régulier étoilé ou non se fait très simplement par:
program figures; {turbo-pascal sur Mac}
uses memtypes, quickdraw , turtle;

procedure poly (n, C : integer); {polygone à n côtés valant tous C}


var i : integer;
begin for i := 1 to n do begin forwd (C); turnleft (360 div n) end end;

procedure etoile (n, p, C : integer); {étoile à n sommets joints p à p, de côté C}


var i : integer;
begin for i := 1 to n do begin forwd (C); turnleft ((360*p) div n ) end end;

begin etoile (7, 3, 100) end.


45

Le repère est centré sur l'écran et sur le Macintosh est [-240, 240]*[-140, 140], les
instructions sont :
clear; efface l'écran et remet la tortue au centre
home; remet la tortue au centre dirigée vers le haut (north)
setposition(..., ...); force la position de la tortue dans le repère
setheading(...); force sa direction en degrés
penup; ne trace pas lors des déplacements
pendown; trace à l'écran
forwd (...); avance de la distance spécifiée qui doit être un nombre entier
back (...); recule
turnleft (...); fait tourner à gauche de l'angle spécifié en degrés
turnright (...); à droite
On peut utiliser en outre, les constantes "north" = 90°, "south", "east" et "west", et les
fonctions "xcor", "ycor", "heading" donnent respectivement l'abscisse, l'ordonnée et la
direction de la position actuelle de la tortue.
Un autre exemple (récursif) peut être donné par une suite de carrés emboités :
procedure carres (C : integer);
begin
if C > 5 then begin poly (4, C); forwd (C div 2);
turnleft (45); carres (round (C / sqrt(2)) )
end
end;

On appelera la procédure avec par exemple : setposition (240, -140); carres (250);

Naturellement, la géométrie de la tortue doit être utilisée à propos, ainsi si une courbe n'est
donnée que par son équation intrinsèque c'est à dire par une équation donnant le rayon de
courbure R = ds/dθ où s est la longueur parcourue sur la courbe, et θ l'angle polaire de la
tangente, alors on peut faire tracer la courbe par une succession de petits pas où le virage à
prendre d φ (variation de tangente) est calculé en fonction de ds. Le meilleur exemple est 46
quand même la spirale de Cornu où R est proportionnel à s, ici on a pris ds = 9 et dφ = 0,1s.
Le dessin sera demandé par le programme :

begin home; cornu (0,0); home; setheading (south); cornu (0,0) end.

utilisant le sous-programme :

procedure cornu (L, A: integer);


var da : integer;
begin if L < 600 then {l'arrêt se fait sur une longueur maximale de 600}
begin da := round (L*0.1); turnleft (da); forwd (9); cornu (L+9, A+da) end
end;

Les essais successifs ou "backtracking", exemple des reines de Gauss


Nous exposerons cet algorithme sur un exemple bien connu : les reines de Gauss. Etant donné
un échiquier 8*8, on cherche à placer 8 reines qui ne soient pas en position de prises
mutuelles, cela oblige à avoir une reine sur chaque ligne, une sur chaque colonne, et jamais
deux sur toute transversale.
L'algorithme général d'un tel problème est :
Back(i) donne vrai si c'est possible de continuer avec le choix numéro i arrêté, faux sinon.
Si i est le dernier choix à faire alors c'est fini, on renvoie vrai.
Sinon, on examine toutes les possibilités de choix satisfaisant aux contraintes du problème,
pour l'étape suivante, en stoppant cet examen dès que l'une indique que back (i+1) est vrai, et
en ce cas back(i) sera vrai.
Si tous ces appels répondent faux, alors back(i) sera faux.
Le problème général est étudié au chapitre sur les pointeurs.
Pour les dames de Gauss, on utilisera une table t, telle que t[i] contienne le numéro de
colonne de la reine située dans la ligne i, t sera une variable globale.
program gauss;
const n = 8;
var t : array [1. n] of integer ;

procedure afficher (m : integer) ; {affiche le tableau t de dimensions m*m}


var i, j : integer;
begin for i := 1 to m do begin for j := 1 to t[i-1] do write ('-': 3); write ('R': 3);
for j := t[i+1] to m do write ('-': 3); writeln
end end;
function nonprise (i, j, k, l : integer) : boolean; 47
{indique avec i < k que les reines située en i, j et en k, l ne sont pas en prise}
begin if j = l then nonprise := false
else if abs (j-l) = k-i then nonprise := false
else nonprise := true
end;

function caselibre (i, j : integer) : boolean;


{vérifie que la position i, j est possible par rapport à toutes les lignes précédentes}
var l : integer; {sera un numéro de ligne} drap : boolean; {sera le résultat }
begin l := 1; drap := true;
while drap and (l < i) do begin drap := nonprise (l, t[l], i, j);
l := l+1
end;
caselibre := drap
end;

function reine (i : integer) : boolean;


{répond vrai si, t étant bien rempli jusqu'à i compris, il est possible de continuer le remplissage du tableau}
var k : integer; poss : boolean;
begin if i=n then reine := true
else begin poss := false; k := 1;
repeat if caselibre (i+1, k) then begin t[i+1] := k;
poss := reine (i+1)
end ;
k := k+1
until poss or (k > n)
reine := poss
end;
end; {bien regarder la structure de ce programme et indenter de cette façon}

begin {programme} if reine (0) then afficher (n) end.


{la procédure "afficher (n)" devant faire un affichage du tableau "reine" jusque n*n}

Voici le résultat pour n = 8, on pourra maintenant modifier le programme afin d'afficher


toutes les solutions. Ce problème sera réexaminé au chapitre 7. Maintenant il n'est pas
difficile d'obtenir toutes les solutions (n est toujours une constante) grâce à :
procedures dames (i : integer) ;
var k : integer;
begin if i = n then afficher (n)
else for k := 1 to n do if caselibre (i+1, k) then begin t[i+1] := k;
dames (i+1)
end
end;

Le programme serait alors simplement lancé par "dames (0)".


48
4-1° Ecrire "som" récursivement, puis par la formule 1+2+...n = n(n+1)/2 .

4-2° Que se passe-t-il si on introduit dans "fac" un effet de bord analogue à celui de "som" ?

4-3° Que se passe-t-il si on ne définit la fonction fac (N) seulement que par N*fac (N - 1)?

4-4° Ecrire "fac" itérativement.

4-5° Suite de Syracuse. On considère la suite dont le premier terme est un entier positif
rentré au clavier, et dont chaque terme est ensuite obtenu comme la moitié du terme
précédent si celui-ci est pair , ou bien le successeur de son triple dans le cas contraire. Ecrire
un programme donnant tous les termes de la suite jusqu'à ce que l'un d'entre eux soit égal à 1.
(Faire des essais à la main avec tout entier de départ, pour constater que le processus s'arrète
toujours. ) Proposer d'abord une solution itérative, puis une fonction récursive provoquant
l'impression d'un terme de la suite comme effet marginal. Il existe un prédicat Pascal "odd
(n)" qui est vrai si et seulement si n est impair.

4-6° Construire récursivement en pascal la fonction h définie sur N* par :


1 1 1 1
h(n)= 1+ + + + ...... +
2 3 4 n

4-7° Construire itérativement en pascal la fonction w définie sur N* par :


1*3* ... *(2n + 1) 1*3*5*7 105
w (n) = exemple : w (3) = =
2*4* ... (2n) 2*4*6 48

4-8° Soit la suite u définie par son terme de rang 0 égal à 7, et sinon un+1 calculé comme la
racine de 2+un
a) Construire un programme donnant les termes de la suite jusqu'à ce que |un+1-un| < 10-3,
grâce à la boucle "repeat".
b) Même question mais en utilisant une procédure récursive à un paramètre réel.

Solution : u := 7; repeat v := u; write (u,'/'); u := sqrt (2+u) until abs (u-v) < 0.001
ou bien :
procedure suite (u : real);
var v : real;
begin
write (u,'/'); v := sqrt (2+u) ;
if abs (u-v) > 0.001 then suite (v)
end;

4-9° Quelles sont dans l'ordre les affichages à l'écran que va provoquer ce programme ?
program truc;
var A, B : integer;
procedure machin ; begin A := A + B end;
procedure bidule ;
var A, B : integer;
begin A := 2 ; B := 4; machin ; write (A) end;
begin A := 3; B := 1; machin ; write (A); bidule ; write (A) end.

Réponse : 4 2 5
49
4-10° Donner toutes les explications nécessaires, et l'effet produit par le programme suivant :
program khasthaite;
var x, y : integer;
function f (x, y : integer): integer;
var z : integer;
begin z := x+y; f := z*z*z end;
procedure m (x, y : integer; var a : integer);
var z : integer;
begin z := f (x-y, x+y) ; write (z,'/'); a := f (a,1); writeln (a) end;
begin y := -2; for x := 0 to 3 do
m (x-1, (exp (x) + atan (x+2*y)) / sqrt (sin (pi*x)+2), y) ;write (y);
end.

Réponse : On voit tout de suite que f(x,y) = (x+y)3, et que m est une procédure qui écrit 8x3
puis qui modifie a en (a+1)3
Le résultat à l'écran est -8/-1 puis à la ligne 0/0 puis 8/1, 64/8 et enfin 2.

4-11° Quelles sont dans l'ordre les affichages à l'écran que va provoquer ce programme ?
program amstramgram;
var Z : integer;
function colegram (X : integer) : integer;
var Z : integer;
begin Z := 2*(X+1) ; write (X) ; colegram := Z*(Z + 3) end;
begin for Z := 1 to 5 do writeln (Z , colegram (Z)) end.

Réponses : 1 1 28 / 2 2 54 / 3 3 88 / 4 4 130 / 5 5 180 /, car colegram (x) = 4x2 + 14x + 10

4-12° Si "trunc" désigne la fonction "partie entière d'un réel", si (N div P) désigne la quotient
entier de N par P, et si (N mod 10) désigne le reste de la division entière de N par 10,
expliquer le rôle joué par la fonction F définie par :
function F (N : integer) : boolean;
begin
if N < 0 then F := F(- N)
else if N = 0 then F := true
else if N < 10 then F := false
else if (N mod 10) = 0 then F := true
else F := F(N div 10)
end;

Réponse : vrai dès qu'un nombre entier N comporte au moins un zéro dans son
développement décimal, et faux sinon. Prendre des exemples.

4-13° Calculer la somme des inverses des entiers de 1 à N en sautant ceux comportant au
moins un zéro parmi leurs chiffres. (Se servir de la fonction précédente. Cette série converge
très lentement vers 23,10345....)

4-14° Soit la fonction F définie par :


function F (N : integer) : integer;
begin if abs (N) <= 2 then F := 3
else if N > 0 then F := F(N-1) + 2*F(1-N)
else F := 3*F(N+1) - F(-N)
end;
Que vaut F(5) et combien d'appels de la fonction ont été nécessaires à son calcul ?

Réponse F(5) = -9 lui-même plus 24 autres, obtenus en développant l'arbre F(4) est calculé 2
fois, F(3) 4 fois.
50
4-15° Permutations d'un tableau de m entiers où m est fixé.

Solution, en supposant écrite une procédure d'échange et d'affichage, on considère une


procédure de paramètres n, qui doit afficher toutes les permutations du tableau qui laissent
invariants les éléments après n.

procedure perm (var t : tableau; n : integer); {affiche les permutations de t entre 1 et n}


var j : integer;
begin if n=1 then afficher (t, m)
else begin perm (t, n-1);
for j := 1 to n-1 do begin echange (t[n], t[j]);
perm (t, n-1);
echange (t[n], t[j])
end;
end;

4-16° Tri-bulle boustrophédon : le parcours d'une liste à trier se fait alternativement de


gauche à droite et de droite à gauche avec la méthode du tri bulle. En remarquant que chaque
balayage a pour effet de pousser les éléments extrêmes à leurs places définitives, écrire une
autre version du programme de tri-bulle où l'étendue du parcours est diminuée à chaque fois.
Se servir pour celà de deux procédures mutuellement récursives réalisant les balayages en
avant et en arrière.

program boustro;
type liste = array[1..100] of real;
procedure echange (var x,y : real);
var t : real; {tampon} begin t := x; x := y; y := t end;
procedure arriere (var L : tab; d,f : integer); forward;
procedure avant (var L: tab; d,f : integer);
{réalise le balayage dans la sens des indices d à f croissants}
var drap : boolean; i : integer;
begin drap := false;
for i := d to f-1 do if L[i] > L[i+1] then begin echange (L[i], L[i+1]); drap := true end;
if drap then arriere(L, f-1, d)
end;
procedure arriere ; {réalise le balayage dans la sens des indices d à f décroissants}
var drap : boolean; i : integer;
begin drap := false;
for i := d downto f+1 do if L[i] < L[i-1] then begin echange (L[i], L[i-1]); drap := true end;
if drap then avant (L, f+1, d)
end;
procedure tri (var L : tab; n : integer);{réalise le tri du tablau L des indices 1 à n}
begin avant (L, 1, n) end; { On fera suivre ceci d'un programme d'essai. }

4-17° Disposition traditionnelle de la multiplication en Russie (et en Inde), sur deux


colonnes, on double le facteur de gauche tout en divisant par deux celui de droite (quotient
entier) jusqu'à obtenir 1 à droite. la somme des termes de la première colonne, qui sont placés
à gauche d'un nombre impair, donne alors le résultat. On construira une fonction à trois
paramètres : u, v, et r le résultat partiel.

function multiplic (u, v, r : integer) : integer;


begin if v = 0 then multiplic := r
else if odd(v) then multiplic := multiplic (2*u, v div 2, r + u)
else multiplic := multiplic (2*u, v div 2, r)
end;

function multiplic (u, v, r integer) integer;


begin if v = 0 then multiplic = r
else if odd (v) then multiplic = multiplic(2*u, v div 2, r + u)
else multiplic = multiplic(2*u, v div 2, r)
end;
51
4-18° Palindromes, reconnaître si les éléments d'un tableau de caractères forment un
palindrome c'est à dire un mot symétrique. Exemples (sans tenir compte des espaces, accents
ou majuscules) "Esope reste et se repose", "élu par cette crapule", "Eric, notre valet, alla te
laver ton ciré".

function palind (t : tableau; i, j : integer) : boolean;


begin if i >= j then palind := true
else if t[i]=t[j] then palind := palind (t, i+1, j-1)
else palind := false
end;

Si le tableau est indicé de 1 à n, on demandera la valeur de palind (t, 1, n).

4-19° Dichotomie. Reprendre le problème de la recherche d'une solution d'une équation à


une inconnue f(x) = 0, dont on est sûr de l'existence dans un intervalle [a, b]. On utilise
l'algorithme de dichotomie consistant à couper l'intervalle en deux, et à recommencer dans
celle des deux moitiés qui contient la solution.
a) Comment savoir si la racine se situe dans la première ou seconde moitié de [a, b ] ?
b) Sur quel critère devrait-on stopper ce découpage ?
c) Ecrire une fonction récursive des trois variables a , b , ε (précision), renvoyant la valeur de
la racine à ε près.
d) On appliquera la dichotomie pour résoudre x3 - x - 1 = 0 puis, trois fois afin de réaliser la
fonction :
3 3
2 3 2 3
q q p q q p
φ(p, q) = - + + + - - +
2 4 27 2 4 27

Quand 4p3 + 27q2 > 0 c'est la seule solution de x3 + px + q = 0


e) Etablir que la complexité de la dichotomie en fonction de ε et de |a-b| est donnée par un
nombre d'appels approximativement (ln(|a-b| - log 10(eps)) / ln(2)

4-20° Soit la procédure récursive Tros (I0, J0) définie par :


S ← A[I0] ; T ← A[J0] ; I ← I0 ; J ← J0
tant que I ≠ J faire si S > T alors A[I] ← T; I incrémenté ; T ← A[I]
sinon A[J] ← T; J décrémenté; T ← A[J]
A[I] ← S
si I > I0+1 alors tros (I0, J - 1)
si J < J0-1 alors tros (I + 1, J0)
Pour un tableau A dont les éléments sont initialement (3 5 7 1 9 6 4), développer les appels de
TROS en précisant les valeurs I0, J0 d'entrée, la valeur S, celles de T, et l'état de A à chaque
sortie. (I, J, S et T sont des variables locales).
Essayer de nouveau pour A = (3 2 0 5 8 1 1 0 4 6). Que fait la procédure ? En quoi le terme
de segmentation est-il bien adapté ?

Réponse : I0 J0 S valeurs successives de T I=J final


1 7 3 4691575 2
3 7 7 4596 6
3 5 4 654 3
4 5 5 6 4
A chaque appel, le I=J final détermine l'indice d'un élément de A qui est à sa place définitive,
c'est à dire que tous les éléments de A de rang inférieur lui sont plus petits, sans être
obligatoirement rangés entre eux, et tous les éléments de A de rang supérieur lui sont plus
grands. Les deux dernières instructions consistent à appeler le tri pour chacun de ces deux
segments. (Tri par segmentation.)
52
4-21° Tri d'un tableau t par segmentation, entre des indices g et d, on part de l'élément x
situé "au milieu" d'indice p = (g+d) div 2, et on rétrécit l'encadrement [g, d] en intervertissant
à l'occasion ses extrêmités de façon à déterminer l'indice p (pivot de la partition) où x sera à
sa place définitive, c'est à dire que tous les éléments à gauche de x lui seront inférieurs et
chaque élément à droite lui sera supérieur. Ceci ne voulant pas dire que les deux segments
partagés ainsi sont triés. Ecrire une fonction "partition" calculant ce p en modifiant t, puis une
procédure de tri récursive réalisant cet algorithme.

function part (var t : tableau; g,p,d : integer) : integer;


begin
if t[g] < t[p] then part := part (t, g+1, p, d)
else if t[p] < t[d] then part := part (t, g, p, d-1)
else begin echange (t[g], t[d]);
if (g < p) and (p < d) then part :=part (t, g+1, p, d-1)
else if g < p then part := part (t, g, g, d-1)
else if p < d then part := part (t, g+1, d, d)
else part := p
end
end; {on a indenté les if avec les else pour des raisons de place }

procedure tri (var t : tableau; deb, fin : integer);


var pivot : integer;
begin pivot := part (t, deb, (deb + fin) div 2, fin);
if deb < pivot - 1 then tri (t, deb, pivot - 1);
if pivot + 1 < fin then tri (t, pivot + 1, fin)
end;

4-22° Complexité du tri rapide : trouver le nombre Tn de tests à effectuer en moyenne pour
trier n éléments.

Solution :
On doit d'abord considérer qu'au pire, le pivot est toujours le plus petit élément, le nombre de
tests pour une table de n éléments est alors (n+1) + n + (n - 1) + ... + 3, soit de l'ordre de n2.
Mais en moyenne, on dit que si le pivot vient occuper la place p (les places étant
équiprobables), la fonction "partition" qui détermine cette place, demande n + 1 tests, et donc,
on a : T0 = T1 = 0 T2 = 2, Tn = n + 1 + ∑0 ≤ p ≤ n (Tp +Tn-p) / n, on en déduit que :
n(T n - n -1) = 2(∑1 ≤ p ≤ n-1 T p ) en écrivant la même relation pour n - 1 on obtient une
récurrence simple.
Tn T 2
nT n = (n + 1)T n-1 + 2n d' où = n-1 + donc :
n+1 n n+1
Tn 1 1 1 1 3
= 2( + +... + ) = 2( - + H n) où H n est la somme harmonique jusqu' à n.
n+1 n+1 n n n+1 2
Tn = 2 + 2(n + 1)Hn - 3(n + 1) or Hn est équivalent à l'infini à C + ln(n) où C est la constante
d'Euler, donc Tn est de l'ordre de n.ln(n).
Ce résultat est à rappocher de celui du tri-bulle qui est en n2, et de celui du tri par fusion qui
est aussi en n.ln (n) mais au pire.

4-23° Les tours de Hanoï : sur trois emplacements : gauche, milieu, droite, on peut empiler
n disques de diamètres différents. La règle est de toujours mettre un disque plus petit sur un
disque qui lui est de taille supérieure. Grâce à une procédure élémentaire "déplacer un disque
(a, b)" signifiant que le disque supérieur en a est placé sur la pile déjà en b, et à une procédure
récursive "mouvement (n, a, b, c)" où n est le nombre de disques et a, b, c les noms de trois
empacements, on écrira la procédure principale "hanoi (n)" qui ne sera lancée que pour n
relativement petit.
53
4-24° Courbes de Cantor
a) Sur l'intervalle [0, 1], en partant de la fonction f0(x) = x, on coupe en 3 cet intervalle et on
définit f1 comme constante égale à 1/2 sur [1/3, 2/3] égale à f0 en 0 et en 1, et affine par
morceaux, f2 est définie de même à partir de f1, et ainsi de suite les paliers étant conservés.
On montre que la suite des fonctions ainsi définie converge uniformément vers une fonction
croissante, continue, de longueur totale 2, d'intégrale 1/2, et ayant en tout point de K
(triadiques de Cantor) une dérivée à droite et une dérivée à gauche distinctes.
Faire le programme capable de tracer l'une quelconque des fn
b) On définit cette fois f1 en lui donnant la valeur 2/3 en 1/3 et 1/3 en 2/3, la suite converge
alors vers une fonction continue non dérivable à droite ni à gauche en tout point de [0, 1].
Faire le programme traçant les représentations de f 0 à f5.

program fractal; { Courbes de Cantor turbo-pascal sur mac }


uses memtypes, quickdraw;
const A = -2; B = 2; C = -1; D = 1.5; L = 480; H = 270;{ Pour x entre A et B, et y entre C et D les coordonnées
sur l'écran sont U=L*(X-A)/(B-A) et V=H*(Y-D)/(C-D) }

procedure place (x, y :real); {place le point de vraies coord x,y}


begin moveto (round (L*(x-A)/(B-A)), round (H*(y-D)/(C-D))) end;

procedure trace (x, y :real); {trace le segment vers le point de vraies coord x,y}
begin lineto (round (L*(x-A)/(B-A)), round (H*(y-D)/(C-D))) end;

procedure cantor (m : integer ; a, b, c, d : real);


begin if m=0 then begin place (a,b); trace (c,d) end
else begin
cant (m -1, a, b, (2*a+c) / 3,(b+ d) / 2);
trace ((a+2*c) / 3, ( b+d) / 2);
cant (m -1 , (a+2*c) / 3, ( b+d) / 2, c, d)
end
end;

begin {place (0, 0) peut être mis ici une seule fois} cantor (4, 0, 1, 1,1 ) end.

Les résultats pour m allant de 0 à 3 :


Pour l'autre courbe, il suffit de modifier un peu la procedure : 54

procedure diable (m : integer ; a, b, c, d : real);


begin if m = 0 then begin place (a, b); trace (c, d) end
else begin
diable (m-1, a, b, (2*a+c)/3, (b+2*d)/3);
diable (m-1 , (2*a+c)/3, (b+2*d)/3 , (a+2*c)/3, (2*b+d)/3);
diable (m-1 , (a+2*c)/3, (2*b+d)/3, c, d)
end
end;
{Pour les étapes 4 et 8 uniquement : } var i : integer ;
begin for i:=1 to 8 do diable (i, 0, 0, 1, 1 ) {si on les veut superposées} end.

4-25° Courbes fractales Réaliser les courbes de Von Koch, à plat puis sur un triangle
équilatéral. Imaginer d'autres découpage, comme par exemple :

program fractal; { Courbes de Von Koch turbo-pascal sur mac }


uses memtypes, quickdraw;
const A = -2; B = 2; C = -1; D = 1.1; L = 480; H = 270;
type tab = array[0..10] of real;
var i : integer;
procedure place (x,y : real); begin moveto (L*(x-A) div (B-A), H*(y-D) div (C-D)) end;
procedure trace (x,y : real); begin lineto (L*(x-A) div (B-A), H*(y-D) div (C-D)) end;
procedure dragon (c, n : integer; x1, y1, x2, y2 : real; u, v : tab); 55
{développe la fractale d'ordre c sur le segment}
var i : integer ;
begin
if c = 0 then begin place (x1,y1); trace (x2,y2) end
else for i :=0 to n-1 do
dragon (c-1,n,x1+u[i]*(x2-x1)+v[i]*(y1-y2), y1+u[i]*(y2-y1)+v[i]*(x2-x1),
x1+u[i+1]*(x2-x1)+v[i+1]*(y1-y2), y1+u[i+1]*(y2-y1)+v[i+1]*(x2-x1),u,v)
end;

procedure choix (c, k : integer);


{c est l'ordre, k le type de dessin : triangles, cristaux, arbres, nuages, peano, petits carrés...}
var i,n : integer; u,v : tab;
begin u[0] := 0; v[0] := 0; case k of
1:begin n:=4;u[1]:=1/3;u[2]:=0.5;u[3]:=2/3; v[1]:=0;v[2]:=-sqrt(3)/6;v[3]:=0 end;
2:begin n:=4;u[1]:=0.5;u[2]:=0.5;u[3]:=0.5; v[1]:=0;v[2]:=0.3;v[3]:=0 end;
3:begin n:=4;u[1]:=0.25;u[2]:=0.35;u[3]:=0.5; v[1]:=0;v[2]:=0.5;v[3]:=0 end;
4:begin n:=4;u[1]:=0.1;u[2]:=0.3;u[3]:=0.5; v[1]:=0;v[2]:=0.3;v[3]:=0 end;
5:begin n:=5;u[1]:=0.2;u[2]:=0.4;u[3]:=0.8; u[4]:=0.6;v[1]:=0;v[2]:=0.8;v[3]:=0.6;v[4]:=0.2 end;
6:begin n:=9;u[1]:=1/3;u[2]:=1/3;u[3]:=2/3; u[4]:=2/3;u[5]:=1/3;u[6]:=1/3;u[7]:=2/3;u[8]:=2/3;
v[1]:=0;v[2]:=1/3;v[3]:=1/3;v[4]:=0;v[5]:=0;v[6]:=-1/3;v[7]:=-1/3;v[8]:=0 end ;
7:begin n:=5;u[1]:=1/3;u[2]:=1/3;u[3]:=2/3; u[4]:=2/3;v[1]:=0;v[2]:=1/3;v[3]:=1/3;v[4]:=0 end
end;
u[n] := 1; v[n] := 0;
dragon(c, n, 0, 0, 1, 1, u, v)
end;
begin {du programme} choix (5, 2) end.

Pour l'étoile de Von koch, on fera :


dragon (c, n, 0, 1, 1, -0.5, u, v) ; dragon (c, n, -1, -0.5, 0, 1, u, v) ; dragon (c, n, 1, -0.5, -1, -0.5, u, v)

Anecdote pour l'étoile de Von Koch [Mandelbrot], si on part d'un triangle équilatéral de côté
1, donc de périmètre p0 = 3 et d'aire s0 = √3/4 pour le premier développement, on a 3
nouveaux triangles donc 3*s0/9 d'aire supplémentaire, soit s1 = 4s0/3.
La longueur du côté est an = 1/3n, le nombre de côtés est cn = 3*4n, et donc, si le périmètre
tend vers l'infini lorsque n tend vers l'infini, par contre, l'aire vérifie sn = sn-1 +3s0/4(4/9)n
d'où on déduit sn = s0(1+3/5(1-(4/9)n)) dont la limite est 8s0/5.
Les cristaux (k = 5) : 56

Les petits carrés avec u1 = u2 = u5 = u6 = 1/3, u3 = u4 = 2/3, v1 = v4 = 0, v2 = v3 = 1/3 :

Pour une succession d'étape, on pourra demander :


for i := 0 to 5 do dragon (i, n, -2+0.6*i, 1 -0.3*i, -1.2+0.6*i, 1 -0.3*i, u, v)

4-26° Tracer la courbe de Peano, obtenue en transformant chaque segment en une suite de 9
segments tous égaux au tiers du segment précédent (suivre les flèches du schéma ci-dessous B
et I sont des points doubles). On obtient à la limite une fonction de [0, 1] dans [0, 1]2 qui est
continue et surjective, ce qui démontre l'équipotence de R et de R2. D'ailleurs la dimension
fractale de cette courbe est donnée par ln(n) / ln(r) = ln(9) / ln(3) = 2

Le choix k = 6 de la procedure choix ci-dessus.


57
4-27° Manipulation de la tortue, tracer en utilisant la tortue, la cardioïde dont l'équation
intrinsèque, avec l'angle a de la tangente avec l'axe des x, a en radians, est ds = k * sin(a /3),
l'astroïde d'équation intrinsèque ds = k*sin (a/2), la cycloïde ds = k sinφ.dφ, et enfin le trèfle
d'équation ds = 1/√|cos(2*(a - pi/2)/3 )|.

Program equintrinseques;
uses memtypes, quickdraw , turtle; {tous les angles sont en degrés}

procedure cardioide ( A : integer);


begin if A < 570 then {arrêt par trois demi-tours plus une petite marge}
begin forwd (round (5*sin (a*pi/180/3))); turnleft (5); cardioide (A + 5) end
end;

procedure astroide ( A : integer);


begin if A < 570 then {arrêt défini de la même façon}
begin forwd (round (8*sin ( a*pi/90 ))); turnleft (5); astroide (A + 5) end
end;

procedure trefle ( A : integer);


begin if A < 1200 then {arrêt défini expérimentalement}
begin forwd (round (2/sqrt (abs (cos (2*(a*pi/180 - pi/2)/3 ))))); turnleft (5); trefle (A + 5) end
end;

begin clear; cardioide(0); astroide (0); trefle (1) end.

Pour la cycloïde il suffit de remplacer ds = k sinφ.dφ dans le "forwd".

Essayer aussi la développée de la cardioïde :


procedure dev (n : integer) ;
begin if n < 500 then
begin forwd (round (4*sin (1/(3*n))));
turnleft (90); forwd (round (40*sin (1/3/n))); {on va au centre de courbure}
back (round (40*sin (1/3/n))); {on en revient}
turnright (90); turnleft (5);
dev (n+2)
end;

Appel avec dev (0).


58
4-28° Problème du Professeur Facon : étant donné une suite de nombres entiers [e1, e2, ...
en] et une somme S donnée, on souhaite réaliser, si cela est possible, la somme S avec des
entiers pris dans la suite (il peut donc y avoir des répétitions dans la suite e).

On définit un tableau de booléen "choisi" tel que choisi (i) = vrai si l'entier d'indice i fait
partie de la somme SP en construction.

function somme (SP, S : integer) : boolean; {On suppose e, un tableau d'entiers positifs}
var i : integer; poss : boolean;
begin
if SP = S then somme := true
else begin poss := false; i := 1;
repeat if not (choisi[i]) and (SP + e[i] <= S)
then begin choisi[i] := true;
poss := somme (SP + e[i], S);
if not (poss) then begin choisi[i] := false;
i := i+1
end
else i := i + 1
until poss or (i > n);
somme := poss end
end;

Le programme consiste alors à initialiser n, S et le tableau e, puis "choisi" avec des "false", et
si somme (0, S) est vrai alors afficher les entiers choisis sinon c'est impossible.
Exemple si S = 7 et e = {1, 2, 3, 4} alors pour l'appel somme (0, 7) on a choisi ={}, mais
choisi (1) est mis à vrai, et l'appel somme (1, 7) met à son tour choisi (2) à vrai. L'étape
suivante est l'appel de somme (3, 7) qui choisit 3 et appelle à son tour somme (6, 7) qui cette
fois répond faux. Il y a alors depuis l'appel précédent qui était somme (3, 7) une
"désaffectation" de choisi (3) pour choisir 4 et appeler somme (7, 7) puis remontée au premier
appel qui termine.

4-29° Parcours du cavalier dans un échiquier, réaliser le parcours du cavalier dans un


échiquier n*n où n et la position initiale du cavalier sont donnés. On rappelle que le cavalier
se déplace en diagonale de tout rectangle de 2 sur 3.

Nous allons représenter le parcours par la suite numérotée des sauts du cavalier, la position 1
étant celle donnée par l'utilisateur. L'échiquier est naturellement représenté par un tableau n*n
qui va donc, s'il y a une solution, être rempli par les entiers de 1 à n2. Afin de regarder tous
les sauts possibles à chaque étape, on les met en coordonnées relatives (1 ligne de moins, 2
colonnes de plus par exemple) dans un petit tableau TS contenant les 8 voisins possibles.
Program cavalier; {uses crt en turbo 4 à 6 sur PC}
const m=10;
var ech : array [1. m,1..m] of integer; ts : array [1..8,1..2] of integer;
l0, c0, n : integer; {représentent la case de départ et la dimension n < 10}
function libre (l, c : integer) : boolean;
begin libre := (0 < l) and (l < n+1) and (0 < c) and (c < n+1) and (ech [l, c] = 0) end;
procedure aff (n : integer); {affiche le tableau global "ech" sur n*n}
var i, j : integer;
begin writeln;
for i := 1 to n do begin for j := 1 to n do write (ech [i, j] : 4); writeln end end;
function cheval (i, l, c : integer) : boolean; 59
{la position i est déjà placée dans la case l, c lors de l'appel, la position éventuelle suivante est nommée
localement ls, cs, la fonction renvoie "vrai" s'il est possible}
var res : boolean; k, ls, cs : integer;
begin if i = n*n then begin aff (ech, n); cheval := true end
else begin k := 1; res := false;
repeat ls := l + ts [k, 1]; cs := c + ts [k, 2];
if libre (ls, cs) then begin ech [ls, cs] := i + 1;
res := cheval (i+1, ls, cs, n);
ech [ls, cs] := 0
end;
k := k + 1
until res or (k = 9);
cheval := res
end
end;

begin { programme } {initialisation du tableau des sauts relatifs possibles ts}


ts[1,1] := 1; ts[1,2] := 2 ts[2,1] := 2; ts[3,1] := 2; ts[3,2] := -1; ts[4,1] := 1; ts[4,2] := -2; ts[5,1] := -1;
ts[5,2] := -2; ts[6,1] := -2; ts[6,2] := -1; ts[7,1] := -2; ts[7,2] := 1; ts[8,1] := -1; ts[8,2] := 2;
write ('Quelle dimension du tableau ? '); readln (n);
write ('Ligne initiale ? '); readln (l0);
write ('Colonne initiale ? '); readln (c0);
ech [l0, c0] := 1;
if cheval (1, l0, c0) then write ('Voilà !') else write ('Pas de solution')
end.

Une solution avec retour pour n = 8, étudiée par Vandermonde en 1771 et Jordan en 1888

4-30° Le labyrinthe : on trace un carré avec des cases libres et d'autres occuppées par des
murs ou des obstacles divers, en laissant une case libre sur le mur gauche (l'entrée) et une sur
le mur droit (la sortie). On doit essayer de traverser si c'est possible, en visualisant la trace (la
stratégie est d'essayer les quatre directions).

program laby;
const mur = '‡'; vide = ' '; entree = 'E' ; sortie = 'S';
var n {taille du tableau} : integer; ch : char; lab : array [1..25, 1..50] of char; indic : array[1..4] of char;

procedure affiche (n : integer);


var i, j : integer;
begin clearscreen; for i:= 1 to n do for j := 1 to 2*n do begin gotoxy(j, i); write(lab [i, j]) end end;

{La procédure de construction ci dessous n'a rien de satisfaisant, l'idéal est de créer une interface de façon à ce
que l'utilisateur puisse noircir des cases et corriger avec la souris, mais ceci entraîne de nombreux détails propres
aux systèmes utilisés }
procedure construc (var n : integer); 60
var i, j : integer;
begin write ('Quelle dimension ? '); readln (n); indic[1]:='<'; indic[2]:='v';
indic[3]:='^'; indic[4]:='>';
for i := 1 to n do for j:=1 to 2*n do lab[i,j]:= vide;
for i := 1 to 2*(n div 3) do {On donne ici un exemple}
begin lab [i, n div 3] := mur; lab [n-i, 2*(n div 3)]:= mur;
lab [n div 4, 2+i+ n div 4] := mur;
lab [5+ n div 2, i + n div 3] := mur; lab[i, n-i-3] := mur;
lab [n div 2 +3, round (sqrt (i))] := mur;
lab [i+2, n+2]:= mur; lab [n-i, 2*((2*n) div 3)] := mur; lab [i, 2*n - i+1] := mur end;
for i := 1 to n do begin lab [i, 1] := mur; lab [i, 2*n] := mur end;
for j := 1 to 2*n do begin lab [1, j] := mur; lab [n, j] := mur end;
lab [n div 2, 1] := entree; lab [n div 2, 2*n] := sortie end;

function solution (x, y : integer) : boolean; {indique si étant en x, y, il est possible de gagner la sortie}
var xs, ys, k : integer; {coordonnées de la case suivante} poss, impasse : boolean;
begin
if lab[x, y] = sortie then solution := true
else begin poss := false; impasse := true; k := 1;
repeat xs := (k mod 3) - 1 + x; ys := y + (k div 2) - 1;
if lab[xs, ys] in [vide, sortie]
then begin impasse := false; lab [x, y] := indic[k];
poss := solution (xs, ys) end ;
k := k+1
until poss or (k > 4);
if not (poss) then if impasse then lab[x, y] := '*' else lab[x,y] := vide ;
solution := poss end
end;

begin {programme}
repeat construc (n); affiche (n);
if solution (n div 2, 1) then affiche (n); gotoxy (1,24); write ('Recommencer ? '); ch := readchar
until ch = chr (13)
end.

Execution pour n = 23
61
4-31° Une courbe de Hilbert
program hilbert;
uses memtypes, quickdraw;
const n = 5; h0 = 256;
var i, h, x, y, x0, y0 : integer;
procedure A (i : integer); forward;
procedure B (i : integer); forward;
procedure C (i : integer); forward;
procedure D (i : integer); forward;
procedure A;
begin if i > 0 then begin
A(i - 1); x := x + h; y := y - h; lineto(x, y); B(i - 1); x := x + 2 * h; lineto(x, y);
D(i - 1); x := x + h; y := y + h; lineto(x, y); A(i - 1) end end;
procedure B;
begin if i > 0 then
begin B(i - 1); x := x - h; y := y - h; lineto(x, y); C(i - 1); y := y - 2 * h; lineto(x, y);
A(i - 1); x := x + h;y := y - h; lineto(x, y); B(i - 1) end end;
procedure C;
begin if i > 0 then
begin C(i - 1); x := x - h; y := y + h; lineto(x, y); D(i - 1); x := x - 2 * h; lineto(x, y);
B(i - 1); x := x - h; y := y - h; lineto(x, y); C(i - 1) end end;
procedure D;
begin if i > 0 then
begin D(i - 1); x := x + h; y := y + h; lineto(x, y); A(i - 1); y := y + 2 * h; lineto(x, y);
C(i - 1); x := x - h; y := y + h; lineto(x, y); D(i - 1) end end;
begin i := 0; h := h0 div 4; x0 := 2 * h; y0 := 3 * h;
for i := 1 to n do begin x0 := x0 - h; h := h div 2; y0 := y0 + h; x := x0; y := y0; moveto(x, y);
A(i); x := x + h; y := y - h; lineto(x, y); B(i); x := x - h; y := y - h; lineto(x, y);
C(i); x := x - h; y := y + h; lineto(x, y); D(i); x := x + h;y := y + h; lineto(x, y) end;
end.
Que peut bien faire ce sacré programme ?

Réponse :
62

CHAPITRE 5

LES TABLEAUX

Les tableaux dont il a déjà été question dans les chapitres précédents vont être naturellement
utilisés pour tout le calcul matriciel, le programme des carrés magiques présenté comme
premier exemple n'est pas difficile, celui de la triangulation par la méthode de Gauss est très
classique, il est ici complété avec l'inversion des matrices carrées par la méthode de Gauss-
Jordan et divers problèmes de calcul matriciel, on reverra enfin des problèmes de
"backtracking".

5-1° Algorithme de Bachet de Meziriac


Dans cet exemple, on construit les éléments d'un tableau carré dans un ordre bien déterminé
de façon à obtenir un carré magique. Un carré magique est un tableau de nombres tel que la
somme des éléments de toutes les lignes, colonnes et des deux diagonales soit la même.
L'algorithme présenté ici permet, pour un tableau carré de côté impair, de le remplir avec des
entiers consécutifs.
1 est placé juste sous le milieu, les suivants se placent en descendant vers la droite, quand on
arrive à un bord on va à l'extrémité opposée, et si une case est déjà remplie, le suivant est dans
la même colonne mais 2 lignes en dessous.
Pour N=5, on peut facilement suivre 1, 2, puis le 3 devant se trouver à la sixième ligne est
placé à la première ligne, le 4 qui devrait être à la sixième colonne est placé dans la première
colonne, le 6 ayant déjà sa case prise est placé deux cases en dessous du 5, etc.

program carre_magique;
type mat = array [1..19,1..19] of integer ;
{ Remarquer qu'une indexation de 0 à 18 rendrait possible l'utilisation de la fonction "mod" au lieu de la fonction
F définie plus loin. }
var N, D : integer ; A : mat;

procedure lecture (var N : integer ; var P : integer );


begin write ('Carrés magiques de coté impair avec des entiers consécutifs positifs.');
write ('Nombre de lignes et de colonnes: ');
repeat readln (N) until odd (N); {seule une valeur impaire est acceptée}
write ('Valeur de départ: '); readln (P) end ;

procedure ecriture (M : mat ; N, P : integer );


{ Affichage d'une matrice de N lignes et P colonnes }
var I, J : integer ;
begin for I := 1 to N do begin for J := 1 to P do write (M[I, J] : 4 );
writeln { passage à la ligne} end end ;
{ Il est tout à fait possible de se dispenser de cette procédure d'affichage, en plaçant à l'écran les valeurs au fur et
à mesure de leur construction, grâce à l'instruction "gotoxy" (colonne , ligne) }
function f (X, N : integer ) : integer ; 63
{F est construite pour donner un résultat entre 1 et N dans tous les cas, on peut bien sûr se contenter de : "si
X<N alors X sinon X-N ", mais indexer le tableau de 0 à n-1 permet d'utiliser "mod" et donc de se dispenser de F
begin if X < 1 then F := F(X + N ,N)
else if X > N then F := F(X - N,N)
else F := X
end;

procedure construc (N, D : integer ; var A : mat ); {D est la valeur initiale, N la dimension du carré}
var I, J, X : integer ; {I , J sont les coordonnées de la case courante }
begin for I := 1 to N do for J := 1 to N do A [I, J] := 0; {Initialisation nécessaire}
I := (N div 2) + 1 ; J := I-1;
for X := 0 to (N*N) -1 do
begin if A[F(I+1, N), F (J+1, N)] = 0
then begin I := F (I+1, N); J := F (J+1, N) end
else repeat I := F (I+2, N) until A[I, J] = 0;
{ Si la case suivante est vide, alors la case courante devient cette case-là.}
A[I, J] := X + D
end; { D est la valeur de départ }
end;

begin
lecture (N, D); construc (N, D, A); ecriture (A, N, N); writeln ('La somme vaut : ', (N*(N*N-1) div 2) + N*D )
end.

5-2° Triangulation des matrices par la méthode de Gauss


L'algorithme de triangulation pour une matrice de N lignes et P colonnes ( ici on a P > N et P
= N+1 pour un système ayant autant d'équations que d'inconnues, voir le schéma ci-dessous )
consiste pour chaque colonne J ≤ N-1 à :
a) Chercher à partir de la diagonale pour J ≤ I ≤ N la première ligne I où le coefficient M =
A[I, J] est non nul (pivot).
ecrire une fonction "rgpivot" donnant ce numéro de ligne.
b) S'ils sont tous nuls, on passe à la colonne suivante.
c) Si I est différent de J, on échange les lignes I et J. (procédure "ech" )
d) Diviser toute la ligne J par ce pivot (on a donc 1 sur la diagonale) à l'aide d'une procédure
"unit" .
e) Remplacer chaque ligne au-dessous par une combinaison linéaire de cette ligne et de la
ligne de référence J de façon à obtenir 0 dans la colonne J ( procédure "comb").

program matrice;
type mat = array [1..20,1..20] of real ;
var A, B, C, X : mat ; N, P, Q, K, R : integer ; D : real ;
procedure lecture (var M : mat ; var N, P : integer ); 64
{ Demande au clavier les éléments d'une matrice M de N lignes et P colonnes. }
var I, J : integer ;
begin write ('Nombre de lignes '); readln (N) ; write ('Nombre de colonnes '); readln (P);
for I := 1 to N do
begin write ('Donnez les éléments de la ligne ', I, ': ');
for J := 1 to P do begin read (M[I,J]) ; write (' ') end ; writeln end end ;

procedure ecriture (M : mat ; N, P : integer );


{ Edition à l'écran d'une matrice M de N lignes et P colonnes.}
var I, J : integer ;
begin for I := 1 to N do begin for J := 1 to P do write (M [I, J], ' '); writeln end end ;

procedure ech (var M : mat ; I, J, P : integer);


{ Réalise l'échange des lignes I et J dans la matrice M de P colonnes , M est donc modifiée }
var K : integer ; X : real;
begin for K := 1 to P do begin X := M [I, K] ; M [I, K] := M [J, K] ; M [J, K] := X end end;

procedure unit (var A : mat ; L, P : integer ; M : real );


{A pour effet de diviser tous les termes de la ligne L sur la matrice A, par le même réel M }
var K : integer ;
begin for K := 1 to P do A [ L,K ] := A [ L,K ] / M end ;

procedure comb ( var A : mat ; L, J, P : integer; M : real ); {A pour effet de remplacer dans la matrice A, la
ligne L, par cette ligne moins M fois la ligne J (combinaison linéaire ) }
var K : integer ;
begin for K := 1 to P do A[L,K] := A[L,K] - M*A[J,K] end;

function rgpivot (A : mat ; J , N : integer ) : integer ; { Est la fonction qui donne le rang (n° de la ligne) où se
trouve le premier élément non nul de la colonne J à partir de la ligne J, dans la matrice A. Par convention, ce
rang sera 0 au cas où tous sont nuls. }
var I : integer ;
begin I := J;
while ( (I < N+1) and (B[I , J] = 0) ) do I := I+1;
if A[ I , J ] = 0 then rgpivot := 0 else rgpivot := I end ;

procedure trig (A : mat ; var B : mat; N,P : integer ; var R : integer ; var D : real);
{Réalise la triangulation B de la matrice A de N lignes et P colonnes, et calcule le rang R et le déterminant D du
carré N*N . Trig sera utilisé avec P = N+1 ici.}
var I, J, K : integer ; M : real ;
begin D := 1; { D sera le déterminant }
R := min (N, P); { rang valable pour les matrices carrées seulement ! }
for I := 1 to N do for J := 1 to P do B[I, J] := A[I, J]; { B est une copie de A au départ }
for J := 1 to N-1 do
begin I := rgpivot (B, J, N );
if I=0 then begin D := 0 ; R := R-1 end
else begin if I <> J then begin ech (B, I ,J ,P); D:= -D end ;
unit (B, J, P, B[J, J] ) ; D := D*B[ J, J ];
for I := J+1 to N do comb (B, I, J, P, B[ I, J ]);
end
end;
D := D*B[ N , N]
end;

5-3° Résolution d'un système linéaire de N équations et N inconnues, la méthode consiste,


après triangulation, à calculer les inconnues en remontant de la dernière à la première. En ce
qui concerne l'inversion d'une matrice N*N, elle s'obtient ici en résolvant N systèmes: en effet
si Ei désigne le vecteur colonne nul sauf 1 à la i-ième ligne, la i-ième colonne Ci de A-1 est le
produit A-1Ei, il faut donc la trouver en résolvant le système ACi = Ei, c'est ce que l'on fait
pour 1 ≤ i ≤ N. Terminer le programme précédent par la multiplication des matrices (afin de
vérifier l'inversion) et un menu.
procedure reso (A : mat; var X : mat ; N : integer ); { X est la colonne des inconnues construite à partir d'une 65
matrice A déjà triangulaire de N lignes et N+1 colonnes. }
var I, K : integer ;
begin X [N, 1] := A [N, N+1] / A[N, N];
for I:=N-1 downto 1 do begin X[I, 1] := A[I, N+1] ;
for K := I+1 to N do X[I, 1] := X[I, 1] - A[I, K]*X[K, 1] end end ;

procedure concat (A, B : mat ; var C : mat ; N, P, Q : integer );


{Construit une matrice C de N lignes et P+Q colonnes en "concaténant" celles de A et de B. }
var I, J : integer ;
begin for I := 1 to N do begin for J := 1 to P do C[I, J] := A[I, J];
for J := 1 to Q do C[I, P+J] := B[I, J] end end ;

procedure jordan (var A : mat; var IA : mat; N : integer ; var R : integer );


{ A est la matrice donnée au départ, IA son inverse, N sa dimension, à A on ajoute tour à tour une dernière
colonne : les colonnes successives de la matrice identité, cette nouvelle matrice est triangulée en B, et le système
ainsi formé admet la solution E; IA est constitué de la concaténation des colonnes E successives. }
var I : integer ; D : real ; B, E : mat ;
begin A[1, N+1] := 1; for I := 2 to N do A[I, N+1] := 0; trig (A, B, N, N+1, R, D);
if D = 0 then writeln ('Déterminant nul, matrice non inversible.')
else begin writeln ('Dét = ', D);
reso (B, IA, N) ; A[1, N+1] := 0;
for I := 2 to N do
begin A[I, N+1] := 1;
trig (A, B, N, N+1, R, D);
reso (B, E, N);
concat (IA, E, IA, N, I-1, 1);
A[I, N+1] := 0 end;
ecriture (IA, N, N);
end;
end;

procedure mult (A, B : mat ; N, P, K : integer ; var C : mat); { Calcule le produit matriciel C de A par B }
var I, J, Q : integer ;
begin for I := 1 to N do for J := 1 to K do
begin C[I, J] = 0; for Q := 1 TO P do C[I, J] := C[I, J] + A[I, Q]*B[Q, J] end end;

begin { Début du programme. } writeln ('1: Multiplication de deux matrices ');


writeln ('2: Déterminant et inversion'); writeln ('3: Résolution de système '); readln (N);
case N of 1 : begin writeln ('Matrice de gauche'); lecture (A, N, P); writeln ('Matrice de droite');
lecture (B, Q, K);
if P <> Q then write ('Impossible')
else begin mult (A, B, N, P, K, C); ecriture (C, N, K) end end ;
2 : begin lecture (A, N, P);
if N <> P then writeln ('un carré !')
else begin jordan (A, B, N, R); writeln ('Rang =', R) end end ;
3 : begin lecture (A, N, P);
if P = N+1 then begin trig (A, B, N, P, R, D);
if D <> 0 then begin reso (B, X, N);
ecriture (X, N, 1);
writeln ('Dét = ', D) end
else write ('Dét. nul Rang =', R) end
else write ('Non de Cramer') end end {du "case"}
end.

Exemple à tester : 1 2 3 4 5 6 7 sont les solutions de :


-2x +3y -z +4t - u - v +5w = 41
3x -z +5t - 3u + v -7w = -38
4x -y +2z -u +3v + w = 28
x -y +3z -t + u +v +w = 22
x -2y +5z -7t +2u -3v - w = -31
-x -y -z +3u +w = 16
-7x -5y + z -3t +4u - v +2w = 2
66
5-4° Placer dans un tableau les éléments successifs d'un développement en fraction
continue et renvoyer la valeur.(exemple 1 2 2 2 2 ... pour √3, 2 1 2 1 1 4 1 1 6 1 1 8 ... est
e, et 1 1 1 2 1 2 1 2... est (1+√5)/2 le nombre d'or).
b1
Exemple pour la suite a 1, b 1, a 2, b 2, a 3, b 3,.... F = a 1 +
b2
a2 +
b3
a3 +
b4
a4 +
a 5 + .....

program frac;
var A : array [1..20] of real; n, k : integer; f : real;
begin writeln ('Donnez les éléments d'un dév. en fraction continue ');
write ('donnez en le nombre de couples '); readln (n);
for k := 1 to n do read (A[k], B[k]);
f := B[n] / A[n];
for k := n-1 downto 1 do f := A[k] + B[k]/f;
write ('Le résultat est ', f)
end.

5-5° Modifier la procédure "reso" en introduisant un nouveau type de données : les


colonnes, qui seront des tableaux à une dimension.

5-6° Ecrire une procédure "cof" construisant une sous-matrice obtenue à partir d'une
matrice, en lui retirant une ligne et une colonne. Utiliser cette procédure pour construire une
fonction récursive à deux paramètres : une matrice A et un rang N, calculant le déterminant de
A en développant par rapport à la première ligne. On rappelle que det(A) = S (-1) 1+k
det(B1,k) pour k allant de 1 à N, étant la sous-matrice de A obtenue en retirant la première
ligne et la k-ième colonne.

5-7° Quelle est l'erreur dans for K := 1 to P do A[L, K] := A[L, K] / A[L, J] que l'on
pourrait mettre dans la procédure "trig" afin de supprimer "unit" ?

5-8° Modifier la fonction "rgpivot" afin qu'elle donne le numéro de la ligne portant le plus
grand nombre en valeur absolue. L'intérêt de ce pivot est de minimiser les erreurs dans les
divisions ultérieures.

5-9° Problème du professeur A. Roze : produire le triangle de Pascal à l'écran en se servant


d'une matrice-ligne uniquement. Utiliser la symétrie du triangle et la relation :
C(n, p) = C(n-1, p-1) + C(n-1, p)

On transforme le tableau de droite à gauche en recalculant ses éléments à l'envers, et on


l'affiche dans le même temps :
program triangle;
var p, n : integer; C : array[0..15] of integer; {on ne fait que 16 lignes}
begin C[0] := 1; writeln (C[0]:5);
for n := 1 to 15 do begin C[n] := 1; write (C[n]:5);
for p := n-1 downto 1 do begin C[p] := C[p] + C[p-1];
write (C[p]:5)
end;
writeln (C[0]:5) end
end.
67
5-10° Quadrillages déformés [J.P.Delahaye 85]
On considère un quadrillage 20*20 par exemple, 441 points. Pour chacun de ces points :
a) On se ramène aux coordonnées cartésiennes dans un repère où le carré sera inscrit dans le
carré [-1, 1]*[-1, 1]
b) On passe en coordonnées polaires ρ, θ
c) On exerce une transformation telle que ρ ← ρ2 localisée dans le cercle unité comme ci-
dessous.
d) On reconvertit en coordonnées cartésiennes, puis dans le repère de l'écran.
e) On joint chacun de ces points en lignes et en colonnes pour bien visualiser un maillage.

ρ ← ρ2 ρ ← ρ0,25

θ ← θ + π (1 − ρ) / 2 θ ← θ + 3,5 (1 − ρ) et ρ ← ρ3

program tole ; { Version Macintosh }


uses memtypes, quickdraw;
var A, B, N, L, H, CH : integer; P, Q : array [0..20,0..20] of real;
{tableaux renfermant les modules et arguments des 441 points}

procedure rtgle (A, B : integer; var U, V : integer); {calcule U, V à partir des coord. polaires}
begin U := round (P[A, B]*cos (Q[A, B])*H/2+L/2);
V := round (P[A, B]* sin (Q[A, B])*H/2+H/2+50)
end;

procedure ecran; {éxecute le dessin}


var A, B, U, V : integer;
begin for B := 0 to N do begin rtgle (0, B, U, V); moveto (U, V);
for A := 1 to N do begin rtgle (A, B, U, V); lineto (U, V) end end;
for A := 0 to N do begin rtgle (A, 0, U, V); moveto (U, V);
for B := 1 to N do begin rtgle (A, B, U, V); lineto (U, V) end end
end;
procedure polaire (X, Y : real; var M, G: real); 68
begin M := sqrt ((X*X)+(Y*Y));
if X = 0 then if Y>0 then G := PI/2
else if Y<0 then G := 1.5*PI else G := 0
else begin G := arctan (Y/X); if X<0 then G := G+PI; end end;

procedure init; {remplit les tableaux avec les coord. polaires des noeuds du quadrillage}
var X, Y : real;
begin for B := 0 to N do for A := 0 to N do
begin X := 2*(A - N / 2) / N; Y := 2*(B-N/2)/N; polaire (X, Y, P[A,B], Q[A,B])
end end;

begin {programme} L := 250; H := 150; N := 16; {largeur, et hauteur d'écran, dim. du quadrillage}
clearscreen; write ('Initialisation '); init; clearscreen; write ('Votre choix ? (1 à 8) '); readln (CH);
for A := 0 to N do for B := 0 to N do begin if P[A, B]<1 then begin
case CH of 1: Q[A, B] := Q[A, B] + (PI*(1-P[A, B]))/2;
2: begin Q[A, B] := Q[A,B] + 3.5*(1-P[A,B]); P[A, B] := P[A, B]*P[A, B]*P[A, B] end;
3: Q[A, B] := Q[A, B] +2*PI* (1-P[A, B]);
4: Q[A, B] := Q[A, B] +PI* sin (2*PI*(1-P[A, B]))/4;
5: begin Q[A, B] := Q[A,B]+PI* sin(PI*(1-P[A, B]))/2; P[A, B] := P[A, B]*P[A, B] end;
6: P[A, B] := sqrt (sqrt (P[A, B]));
7: P[A, B] := P[A, B]*P[A, B];
8: begin Q[A, B] := Q[A, B]+PI* sin (PI*(1-P[A, B]))/2; P[A, B] := sqrt (P[A, B]) end
end end end;
clearscreen; ecran end.

θ ← θ + 2 π ( 1 − ρ) θ ← θ + π sin (2p (1 - ρ)) / 4


En supprimant la condition ρ < 1, l'effet n'est pas mal non plus :
69
5-11° Le jeu de la vie (Conway). Sur un quadrillage 20 lignes et 80 colonnes par exemple,
que l'on considère "toroïdal" (la 21° ligne est la première et la 81° colonne est la première),
chaque case est soit vivante, soit morte. Etant donné un état du tableau (un état initial sera un
petit motif donné par le joueur), on a les règles suivantes pour chaque case :
Elle est vivante et elle touche au plus une voisine vivante, alors à l'étape suivante, elle meurt
par isolement.
Elle est vivante et possède plus de trois voisines vivantes, alors elle meurt d'étouffement.
Dans les autres cas où elle est vivante, elle reste vivante.
Une case morte qui possède exactement trois voisines vivantes, provoque une naissance.
On peut former deux tableaux ou bien un tableau à trois dimensions où le troisième indice
appartient à un type défini par (ancien , nouveau). Les deux autres indices peuvent être entiers
0..19 et 0..79 par exemple. Rappelons que les différentes générations ne sont pas mélangées.
Certains motifs disparaissent, d'autres se déplacent en restant identiques, d'autres sont
constants, mais la plupart sont périodiques. Ainsi un motif de 7 enzymes alignés consécutifs
devient périodique en 17 étapes, pour 8, il devient constant au bout de 48 étapes, et pour 9, il
devient de période 2 après 21 étapes.

Exemples de motifs constants, sauf le quatrième qui meurt en huit générations.:

Exemples de transformations (feux de navigation) :

program enzymesgloutons;
var A : array[1..20,1..20,1..2] of integer; R:char; ng :integer;
{A est une variable globale qui représente une matrice à trois dimensions}
function nbvoisins (L, C, I : integer) : integer; {donne le nombre de voisins vivants de la case L,C,I} 70
function tore (X : integer) : integer; {fonction locale à nbvoisins}
begin case X of 0 : tore := 20;
21 : tore := 1;
otherwise tore := X end end; {tore}
begin nbvoisins := A[tore(L-1), tore(C-1), I] + A[tore(L-1), tore(C), I] + A[tore(L-1), tore(C+1), I]
+A[tore(L), tore(C-1), I] + A[tore(L), tore(C + 1), I] + A[tore(L+ 1), tore(C-1), I]
+ A[tore(L + 1), tore(C), I] + A[tore(L+1), tore(C + 1), I] end;
procedure entree; {initialise le tableau A pour I=1 avec la population initiale}
var L, C, I, N:integer;
begin N := 1; ng := 0; {ng est une variable globale : le numéro de génération}
for I := 1 to 2 do for L := 1 to 20 do for C := 1 to 20 do A[L, C, 1]:= 0;
writeln ('donnez les coordonnées des points (0 pour finir)');
repeat write ('enzyme numero ',N ,' ligne '); read (L);
write (' colonne '); readln (C); A[L, C, 1] := 1 ; N := N+1
until (L = 0) or (C = 0) end;
procedure affiche (I : integer); {affiche le tableau à l'écran, le premier à gauche, le second à droite sur l'écran
les lignes 2 à 21 et col. 10 à 29 pour I = 1 ou 50 à 69 pour I=2}
var L,C, n :integer;
begin n := 0; for L := 1 to 20 do for C := 1 to 20 do
begin gotoxy (C - 31 + 40*I, L+1); n := n + A[L, C, I]; write (chr(32+3*A[L, C, I])) end;
{si la case est 0 on affiche un blanc de code 32, sinon # de code 35}
gotoxy (40*I-30, 24); write ('Nouvelle population ', n);
gotoxy (90-40*I, 24); write ('Ancienne population ') end;
procedure cadres; {construit deux cadres afin que l'on puisse analyser chaque passage d'un état à son suivant}
var X : integer;
begin clearscreen;
for X := 1 to 22 do begin gotoxy (8+X, 22); write ('_'); gotoxy (48+X,22); write ('_');
gotoxy (9, X); write ('|'); gotoxy (30, X); write ('|'); gotoxy(49, X); write ('|');
gotoxy (70, X); write('|'); gotoxy (8+X, 1); write('_'); gotoxy (48+X,1); write ('_') end
end;
procedure transfo (I : integer); {I = 1 ou 2 suivant la trabche à transformer}
{va calculer l'autre tranche (3-I) du volume A à partir de la tranche I}
var L, C, N : integer;
begin for L := 1 to 20 do for C := 1 to 20 do
begin N := nbvoisins (L, C, I);
if A[L, C, I] = 1 then if (N = 2) or (N = 3) then A[L, C, 3-I] := 1
else A[L, C, 3-I] := 0
else if N=3 then A[L, C, 3-I] := 1
else A[L, C, 3-I] := 0
end
end;
procedure jeu (I : integer); {est la procedure essentielle, elle demande si on veut poursuivre, et si oui, se
rappelle elle-même.}
var R : char;
begin affiche (I); transfo(I) ; gotoxy (26,25); write ('Génération ', ng, ' suite (O/N) '); ng := ng + 1;
R := readchar ; if (R = 'O') or (R = 'o') then jeu (3 - I) end;

begin entree; cadres ; jeu (1) end. {constitue le programme }

Le dernier motif se répète au bout de 14 générations. Le motif en "T couché" se multiplie


énormément puis amorce une décroissance vers la 80° étape, et enfin devient constant à la
114° étape en étant formé de 4 carrés.
71

Les deux premiers sont des "planeurs" qui sont de période 4 en se déplaçant en biais, le chat
arrive en 7 étapes à un carré stable de 4.

5-12° Le corail de S.Ulam. [Heudin 94] Sur une grille 21*21, les cellules peuvent être mortes
ou vivantes (jeunes ou vieilles). Les voisines d'une cellule ne sont que les 4 cellules au dessus,
en dessous, à gauche et à droite. A chaque génération une cellule morte ayant une seule
voisine vivante prend vie (elle est jeune), sinon elle reste morte, une cellule jeune devient
vieille, une vieille devient morte (la vie ne dure que deux générations). On peut partir d'une
seule cellule vivante ou d'un motif plus complexe. Trouver une structure apte à la
programmation (matrice de booléens à 3 couches ou bien matrice ordinaire d'entiers).

5-13° Karel le robot doit trouver la sortie d'un labyrinthe (un tableau carré de booléens) en
longeant toujours le mur gauche (ce n'est pas du tout le même problème que celui du
labyrinthe, il n'y a pas de backtrack ici). On écrira une procedure
"bonne direction" (x, y ; var a)
quart de tour gauche; tant que mur face à x, y, a faire quart de tour droite
et une procedure "générale" :
quart de tour gauche;
tant que non sorti faire bonne direction (x, y, a) puis avancer d'un pas

5-14° Jeu du Tic Tac Toe Sur un carré 3*3, l'utilisateur doit jouer contre l'ordinateur. Chacun
à tour de rôle, met sa marque sur une case libre. Le premier ayant réussi un alignement de 3
cases a gagné. Trouver une représentation des données adaptée à ce jeu, et programmer sans
stratégie particulière.

Correction sommaire : On utilise les cases des touches du clavier, soit 7 8 9 en haut, 4 5 6 au
milieu et 1 2 3 pour la ligne du bas. Le tableau t[1..9] va contenir 0 (libre) ou 1 (pour le
joueur) ou 5 (l'ordinateur), et un tableau p[1..24] toujours constant avec les éléments [1 2 3 4
5 6 7 8 9 1 4 7 2 5 8 3 6 9 1 5 9 3 5 7].
Procedure joue (var gagne, perdu, nul : boolean; var c : integer);
{gagne pour l'ordinateur, perdu si c'est le joueur qui gagne, et nul : on arrête}
{c est la case à jouer par l'ordinateur}
Sur les 8 cas du tableau p :
On trouve une somme 3 dans t, alors fini le joueur a gagné,
si une somme 10 alors l'ordinateur gagne avec la case c,
si une somme 12, l'ordinateur joue sur la case libre (il est sur la défensive)
Si toutes les cases sont pleines et toutes les sommes > 7 alors match nul
Sinon l'ordinateur joue la première case libre.
Programme jeu
t est initialisé à 0
Voulez-vous commencer ? si oui case départ ? i
t[i] := 1; gagné, perdu, nul := false
tant que non (gagné ou perdu ou nul) faire
c := 0
Joue (gagne, perdu, nul, c);
Si c ≠ 0 alors t[c] := 5 puis affichage;
Jouez! ; read(i); t[i] := 1
affichage
si gagné ..., perdu ..., nul ...
72
5-15° Méthode de Bairstow pour la résolution des équations algébriques. On divise le
polynôme par un trinôme du second degré et on cherche en modifiant ce diviseur par petites
retouches, à annuler le reste. Lorsque x2-sx+p divise le polynôme, on peut donc en donner
deux racines, et reprendre la méthode pour le quotient. La première étape est donc :
Calcul du quotient : pour un polynôme A de degré n, on pose :
A(x) = a0xn+a1xn-1+a2xn-2+....+a0
= (x2-sx+p)(b0xn-2+...+b n-2)+ bn-1(x-s)+bn
ce qui conduit à b0 = a0 b1 = a1+ sb0 bi = ai + sbi-1-pbi-2 si on pose bn-1 = f(s, p) et
bn = g(s, p), on souhaite donc résoudre le système f(s, p) = 0 g(s, p) = 0, on utilise pour cela
la formule de Taylor à l'ordre 1 pour deux variables, qui peut s'écrire :
f(s+h, p+k) = f(s, p) + hf' x(s, p) + kf' y(s, p) ; le système permet alors de déduire
h = (fg'y- gf'y) / D et k = (gf'x - fg'x) / D où D = f'yg'x - f'xg'y
Calcul des dérivées partielles en s et p, par récurrence :
En notant ci = dbi / ds et di = dbi / dp = - ci-1 on a : c0 = 0, c1 = b0 + sc0 et ci = bi-1 + sci-
1 - pci-2 donc partant de s0 et p0 il est possible de calculer les bi et les ci
puis D= c2n-1 - cncn-2 et enfin les candidats suivants :
s1 = s0 + (bncn-2 - bn-1cn-1) / D et p1 = p0 + (bncn-1 - bn-1cn) / D que l'on teste à nouveau.

Solution : cet exemple assez long, montre qu'une bonne division du travail permet de s'y
retrouver. Il utilise le type de données le plus commode, à savoir les tableaux, pour
représenter les polynômes. On testera l'arrêt sur (|h|+|k|) / (|s|+|p|) < ε où par exemple ici ε =
10-6

Schéma général du programme

Rappels, "sqr"(x) est le carré de x, et "sqrt"(x) en est la racine carrée. Le Pascal impose un
ordre impératif dans les déclarations de constantes, types, variables, procédures et fonctions
(le Turbo-Pascal, imposant d'ailleurs, moins de contraintes que les autres versions).
Pour ces dernières, elles ne peuvent qu'en appeler d'autres définies antérieurement. En cas
d'appels mutuels, comme ici, on prévient l'analyseur par le mot réservé "forward".
program equation ; { Ce programme résout les équations algébriques }
type pol = array [0..20] of real ; { Un polynôme est une suite de 21 réels }
var A : pol ; N : integer ;
{ A sera le polynôme de degré N, ce sont les seules variables globales. }
procedure lecture (var A : pol; Q, N : integer); 73
{ Q est N moins le degré du terme . Cette procédure est récursive et elle écrit au fur et à mesure le polynôme,
tout en demandant les coefficients. }
begin
if Q < N then begin read (A[Q]); write ('X', N - Q,'+'); lecture (A, Q+1, N) end
else begin read (A[N]); writeln ('=0') end
end;

procedure racine (S, P : real) ; { Cette procédure résout l'équation x2 - Sx + P = 0 en affichant les résultats, elle
ne renvoie rien et ne modifie aucune variable.}
var D, X : real;
begin D := S*S - 4*P;
if D = 0 then writeln (S/2,' (double)')
else if D > 0 then begin X := (S + sqrt (D)) /2;
writeln (X); writeln (S-X) end
else writeln (S/2, '(+ -)i', sqrt (-D) / 2, ' complexes conjugués')
end;

procedure factor (A, B, C : pol ; N : integer ; S0, P0, S1, P1, EPS : real);
forward ; { déclaration nécessaire lors de récursivité croisée.}

procedure division (A : pol ; var B, C : pol ; N : integer ; S0, P0 : real);


var D : real ; Q : integer ;
begin B[0] := A[0]; B[1] := A[1] + S0*A[0]; C[0] := 0; C[1] := B[0];
for Q := 2 to N do begin B[Q] := A[Q] + S0*B[Q-1] - P0*B[Q-2] ;
C[Q] := B[Q-1] + S0*C[Q-1] - P0*C[Q-2]
end ; { On construit itérativement B et C }
D := sqr (C[N-1]) - C[N]*C[N-2] ; if D = 0 then D := 0.1;
{ On produit S1 P1 pour l'envoyer à factor: }
factor (A, B, C, N, S0, P0, S0 + (B[N]*C[N-2] - B[N-1]*C[N-1]) / D,
P0+ (B[N]*C[N-1]- B[N-1]*C[N]) / D, 0.000001)
end ;

procedure bairstow (var A : pol ; N : integer) ; forward ;

procedure factor ;
{la liste des paramètres (A, B, C : pol ; N : integer ; S0, P0, S1, P1, EPS : real) a déjà été donnée}
begin if (abs (S0-S1) + abs (P0-P1)) / (abs (S0) + abs (P0)) > EPS
then division (A, B, C, N, S1, P1)
else begin racine (S1, P1) ; bairstow (B, N - 2) end ;
end ;

procedure bairstow ; { (var A : pol; N : integer ) déjà dit }


var B,C : pol ; Q : integer ;
begin while A[0] = 0 do begin for Q := 0 to N - 1 do A[Q] := A[Q+1] ; N := N - 1 end ;
case N of 1 : writeln (-A[1] / A[0]);
2 : racine ( -A[1] / A[0], A[2] / A[0]);
else division (A, B, C, N, 1, 1) end ; {else sur PC, otherwise sur MAC}
end ;

begin { début du programme }


writeln ('Résolution d ''équations algébriques de degré < 20 '); {la double apostrophe signale à la procédure
"write" que le message n'est pas fini, et qu'il faut afficher une apostrophe}
write ( 'Degré du polynome ? ' ) ; readln (N);
writeln ('Donnez les coef. du polynome dans l''ordre décroissant.'); lecture (A, 0, N); bairstow (A, N)
end.

5-16° Trouver des exemples pour vérifier le programme de résolutions d'équations

-5 -2 1 3 4 6 sont les racines de x6 -7x5 -21x4 +204x3 -140x2 -756x +720 = 0


-2, 1 et 3 doubles, sont les racines de x6 -4x5 -6x4 +32x3 +x2 -60x +36 = 0
2x7 - 13x6 - 49x5 + 385x4 - 77x3 -1652x2 + 684x + 720 = 0 admet -5 -2 -1/2 1 3 4 6
74
5-17° Décomposition LR par l'algorithme de Crout Etant donné une matrice carrée A de
dimension n*n, il est possible sous certaines conditions d'obtenir A = L*R où L est une
matrice triangulaire inférieure dont la diagonale est remplie de 1 et R, une matrice triangulaire
supérieure.
a) Ecrire les équations auxquelles donne lieu cette décomposition, en déduire la première
ligne de R et la première colonne de L.
b) Prouver que l'on peut déterminer de manière unique L et R en calculant au fur et à mesure
la ligne j de L et la colonne j de R pour j allant de 2 à n.
c) En déduire une procédure transformant la matrice A de telle sorte qu'elle devienne la
juxtaposition de R avec L sans sa diagonale (aucune autre matrice n'intervient).
d) Déduire de cette décomposition une méthode de résolution de système AX = Y

Solution :
a) A = LR impose pour j ≥ i, ai,j = ri,j + ∑li,krk,j avec 1 ≤ k ≤ i-1 et pour j ≤ i, ai,j = li,jrj,j +
∑li,krk,j avec 1 ≤ k ≤ j-1. En particulier r1,j = a1,j et li,1 = ai,1 / r1,1

b) Des deux formules générales on tire :


li,j = [ ai,j - ∑ (1 ≤ k ≤ j-1) li,k rk,j ] / rj,j qui est donc calculé en fonction des coefficients
du bloc des lignes et colonnes d'indice inférieurs à i, et en second lieu :
ri,j = ai,j - ∑ (1 ≤ k ≤ i-1) li,k rk,j
Ces coefficients n'existent donc qu'à la condition qu'aucun des ri,i ne soit nul.
Ainsi, en changeant les indices de L, on va pouvoir calculer les coefficients lj,i avec la
contrainte 2 ≤ i ≤ j-1 puis les r i,j pour i allant de 2 à j, et ceci pour j allant de 2 à n, on peut
mettre ces coefficients à la place de ceux de A ce qui permet de n'utiliser qu'une seule matrice
(figure ci-dessous où les flèches indiquent l'ordre des calculs).

Exemple :
2 1 0 1 2 1 0 1
-1 2 -2 1 -0,5 2,5 -2 1,5
Pour A = , A devient
1 1 -2 0 0,5 0,2 -1,6 -0,8
2 -2 -1 2 1 -1,2 2,125 4,5

c) à condition d'avoir déclaré : const max = 20; type mat = array [1..max, 1..max] of real;
procedure LR (var A : mat; n : integer); {transforme la matrice A n*n en L, R}
var i, j, k : integer; s : real;
begin for j := 2 to n do A[j, 1] := A[j, 1] / A[1, 1];
for j := 2 to n do for i := 2 to j do
begin s := A[j, i]; {calcul de la ligne j jusqu'à j-1}
if i <> j then begin for k := 1 to i-1 do s := s - A[j, k]*A[k, i];
A[j, i] := s / A[i, i] end; {puis de la colonne j jusqu'à j :}
s := A[i, j]; for k := 1 to i-1 do s := s - A[i, k]*A[k, j]; A[i, j] := s end end;
On pourra faire une vérification grâce à : 75

procedure trianginf (L : mat; n : integer; var A : mat);


var i, j : integer; {reconstruit A tri. inférieure à partir d'une "moitié" de A}
begin
for i := 1 to n do begin for j := 1 to i-1 do A[i, j] := L[i, j]; A[i, i] := 1; for j := i+1 to n do A[i, j] := 0 end end;

procedure triangsup (R : mat; n : integer; var A : mat);


var i, j : integer;
begin for i := 1 to n do begin for j := 1 to i-1 do A[i, j] := 0; for j := i to n do A[i, j] := R[i, j] end end;

procedure entree (var A : mat; var n, p : integer);


var i, j : integer;
begin write ('Combien de lignes '); readln (n); write ('Combien de colonnes '); readln (p);
for i := 1 to n do begin write (' Ligne ', i,' ');
for j := 1 to p do begin write (' A (', i:3, ',', j:3, ') = ' ); read (A [i, j]) end; writeln end end;

procedure sortie (A: mat; n, p : integer);


var i, j : integer;
begin for i := 1 to n do begin for j := 1 to p do write (A[i, j] : 8 : 3); writeln end end;

d) Pour un système AX = Y, dans le cas où A se décompose en LR et si A n'est plus utilisée


après, en posant Z = RX, on va d'abord résoudre le système triangulaire LZ = Y, puis
résoudre très simplement l'autre système triangulaire RX = Z.

5-18° Décomposition QR de Householder et tridiagonalisation


a) Si V est un vecteur colonne de dimension n, on note At, la transposée de A et on pose H(V)
= I -2VVt / || V||2 la matrice n*n dite de Householder de V. Construire les procédures
nécessaires.
b) Pour toute matrice carrée A, on peut trouver une décomposition A = QR où R est
triangulaire supérieure et Q orthogonale (Q -1 = Qt) en utilisant l'algorithme suivant :
Si Ak-1 a déjà ses termes sous la diagonale nuls pour les colones < k, on pose Vk le vecteur
colonne k obtenu en prenant les n-k+1 dernières composantes.
Si Vk est nul on pose Q k la matrice identité d'ordre n-k+1, sinon on pose Qk la matrice de
Householder de Vk diminué de ||Vk|| dans sa première composante.
On borde alors Q k avec un bloc Ik-1 en haut à gauche et deux blocs nuls en haut et sur la
gauche de façon que Qk soit une matrice symétrique orthogonale d'ordre n.
En posant Ak = QkA k-1 on peut montrer que Ak possède des zéros sous sa diagonale jusqu'à
la colonne k, et on recommence.
Si S = Qn-1...Q1 et R = SA alors R, triangulaire et Q = St répondent à la question. Construire
les procédures nécessaires.
c) Quelle application peut-on en tirer pour la résolution des systèmes linéaires ?
d) Pour une matrice symétrique A de dimension n*n, on pose V le vecteur de la première
colonne privé de son premier élément, et H la matrice de householder de V diminué de ||V||
dans sa première composante. En bordant H en haut à gauche par un 1 et des zéros ailleurs on
obtient une matrice U telle que l'on peut vérifier que UA possède des zéros dans sa première
colonne à partir de la ligne 2. De plus UAU t a la même propriété et possède en outre des
zéros sur sa première ligne à partir de la colonne 2. En réitérant pour le bloc des n-1 dernières
lignes et colonnes de la matrice obtenue, on acquiert une matrice T tridiagonale. Ecrire cette
procédure de construction de T.

Solution a, b)
procedure transpo (M : mat; n, p : integer; var TM : mat); {TM transposée de M}
var i, j : integer;
begin for i := 1 to n do for j := 1 to p do TM[j, i] := M[i, j] end;
procedure muscal (M : mat; n, p : integer; x : real; var R : mat); {R = xM où x est réel}
var i, j : integer;
begin for i := 1 to n do for j := 1 to p do R[i, j] := x * M[i, j] end;
procedure Householder (V : mat; n : integer; var H : mat); 76
{construit pour le vecteur V de dimension n, sa matrice H n*n, égale à I-2VtV / |V|2 }
var i : integer; x : real; W : mat;
begin x := 0; for i := 1 to n do x := x + sqr(V[i, 1]); x := - 2 / x;
transpo (V, n, 1, W); mult ( V, W, n, 1, n, H); muscal(H, n, n, x, H);
for i := 1 to n do H[i, i] := H[i, i] + 1 end;

procedure mult (A, B : mat; n, p, q : integer; var C : mat);


{multiplication de A n*p avec B p*q rendant le résultat C n*q}
var i, j, k : integer; s : real;
begin for i := 1 to n do for j := 1 to q do
begin s := 0; for k := 1 to p do s := s + A[i, k]*B[k, j]; C[i, j] := s end
end;

function norme (V : mat; n : integer) : real;


var i : integer; s : real;
begin s := 0; for i := 1 to n do s := s + sqr(V[i, 1]); norme := sqrt(s) end;

procedure QR (A : mat; var Q, R : mat; n : integer);


var S, V, H : mat; vnul : boolean; i, j, k : integer;
begin for i := 1 to n do for j := 1 to n do begin R[i, j] := A[i, j]; S[i, j] := 0 end;
for i := 1 to n do S[i, i] := 1;{S est initialisé à Identité}
for k := 1 to n-1 do
begin for i := 1 to n do for j := 1 to n do Q[i, j] := 0;
for i := 1 to n do Q[i, i] := 1; {on prépare la matrice Qk}
vnul := true; for i := 1 to n-k+1 do
begin V[i, 1] := R[i+k-1, k]; if V[i, 1] <> 0 then vnul := false end;
if not (vnul) then begin V[1, 1] := V[1, 1] - norme(V, n-k+1);
householder (V, n-k+1, H);
for i := k to n do for j := k to n do Q[i, j] := H[i-k+1, j-k+1] end;
mult (Q, R, n, n, n, R); mult (Q, S, n, n, n, S)
end;
transpo (S, n, n, Q) end;

c) Pour un système d'inconnue X, AX = Y, en décomposant A = QR, on peut d'abord


effectuer le produit Z = tQY puis résoudre simplement de bas en haut le système RX = Z au
cas où R n'a aucun zéro sur sa diagonale.
d) On va construire T à partir de A, ainsi que la matrice orthogonale Q vérifiant A = tQAQ
procedure tridiag (A : mat; n : integer; var T, Q : mat);{A doit être symétrique}
var U, TU, H, V : mat; i, j, k : integer;
begin for i := 1 to n do for j := 1 to n do begin Q[i, j] := 0; T[i, j] := A[i, j] end; {copie de A}
for i := 1 to n do Q[i, i] := 1;
for k := 1 to n-1 do
begin for i := 1 to n do for j := 1 to n do U[i, j] := 0;
for i := 1 to n do U[i, i] := 1; {on prépare la matrice U}
for i := 1 to n-k do V[i, 1] := T[i+k, k];
V[1, 1] := V[1, 1] + norme(V, n-k); householder (V, n-k, H);
for i := 1 to n-k do for j := 1 to n-k do U[i+k, j+k] := H[i, j];
transpo (U, n, n, TU); mult (U, T, n, n, n, T);
mult (T, TU, n, n, n,T); mult (U, Q, n, n, n, Q) end
end;
Exemple :
1 2 -1 3 4 1 -5,477 0 0 0
2 0 -2 5 2 -5,477 6,9 -2,468 0 0
A = -1 -2 1 3 -1 semblableà T = 0 -2,468 -3,052 -4,158 0
3 5 3 4 0 0 0 -4,158 4,605 -0,601
4 2 -1 0 5 0 0 0 -0,601 1,547

Cette méthode permet ensuite de calculer les valeurs propres de A comme celles de T qui lui
est semblable.
77
5-19° Résolution de systèmes linéaires par la méthode de Jacobi : il s'agit, pour résoudre
AX = B, d'une méthode itérative où A est décomposé en A = D - L - U. Dans cette formule,
D, -L, -U étant respectivement les matrices diagonale, triangulaire inférieure et triangulaire
supérieure formées avec les parties respectivement diagonale, inférieure et supérieure de A.
Initialement on part de X0 quelconque et on calcule X n+1 = D-1[(L + U)Xn + B].
Cette suite converge vers la solution si et seulement si le rayon spectral (max des valeurs
propres en valeur absolue) de I - D -1A est inférieur strictement à 1. Une condition suffisante
de convergence est que A soit à diagonale strictement dominante c'est à dire que chaque |aii| >
∑ |aij| pour les j ≠ i.
Développer à la main le calcul des 4 premiers termes dérivant (0, 0) pour le système :
2x + y = 3 et x + 3y = 4 de solution (1, 1).
Concrètement on stoppera le calcul pour une stabilisation des composantes de X à un ε près.

5-20° Recherche du polynôme caractéristique par la méthode de Krylov : si A est une


matrice carrée d'ordre n, son polynôme caractéristique P est le polynôme det (A - xI) dont les
racines x sont les valeurs propres de A.
Le théorème de Cayley-Hamilton indique que P(A) = 0, en le multipliant par (-1)n, il devient
normalisé, ce qui fait que l'équation caractéristique s'écrit : An + ∑ciAn - i = 0 pour i allant de
1 à n, les ci fourniront le polynôme cherché.
La méthode consiste alors, en partant d'un vecteur quelconque V de n composantes, à calculer
V0 = AnV, V1 = An - 1V, .... , V n - 1 = AV, V n = V, puis l'équation ci-dessus appliquée à V
entraîne : c1V1 + c2V2 + ... + cn Vn = -V0
Si C est la matrice dont les colonnes sont V1, ... , Vn, les ci seront rangés dans la solution X
du système CX = -V0. Programmer la procédure construisant cet X à partir de A et de n.

5-21° Recherche du polynôme caractéristique par la méthode de Souriau : si A est une


matrice carrée d'ordre n, et tr(A) sa trace, c'est à dire la somme de ses éléments diagonaux, on
calcule la suite des matrices et de leurs traces A1 = A, c1 = -tr(A1) puis A i = (Ai - 1 + ci - 1 I)A
et ci = -tr(Ai) / i jusqu'à An = (An - 1 + cn - 1 I)A et cn = -tr(An) / n
On peut montrer que les ci sont les coefficients du polynôme caractéristique suivant les
puissances décroissantes (le premier étant 1).

5-22° Multiplication des matrices suivant la méthode de Strassen. Soient A et B deux


matrices carrées d'ordre pair, "partitionnés" en 4 blocs de mêmes dimensions chacune, en
effectuant le produit A*B, montrer qu'on éxecute 7 produits matriciels et 18 additions et en
déduire que la complexité du produit matriciel de matrices carrées d'ordre 2n est en 7n+1 -
6*4 n à comparer avec la méthode naïve. Exposer un algorithme mixte pour le produit de
matrices carrées d'ordre m quelconque qui soit de complexité m2,81 et le programmer.

5-23° Calcul de l'inverse généralisée de Moore-Penrose d'une matrice. Si A est une


matrice n*p, A + est une matrice p*n vérifiant AA+ A = A et A+AA+ = A+ ainsi que (A + A)t =
A+A et (AA+ )t = AA+, et bien sûr A+ = A-1 si A inversible (A est non nécessairement
unique). Si r est le rang de A, A est semblable à la matrice J qui est nulle sauf les r premiers
termes 1 de la diagonale, soit A = PJQ avec P et Q inversibles, alors Q-1 JP -1 remplit la
première condition. Méthode itérative de calcul de Greville : Si A est une matrice-colonne,
alors A+ = (At .A)-1.At. Si maintenant A est une matrice rectangulaire de p lignes et n
colonnes, n itérations donnent A+. On pose ak la k-ième colonne de A et Ak la matrice formée
par les k premières colonnes de A. Si a1 = 0, on pose A1+ = 0 sinon A1+ est la pseudo-inverse
de la colonne. A chaque etape, A(k-1)+ étant connue, on pose dk = A(k-1)+ a k , ck = ak - A(k-
1)dk, et enfin bk = si ck = 0 alors (1 + dkt.dk)-1dkt. A(k-1)+ sinon ck+. La matrice Ak+ est alors
formee par A(k-1)+ - dk.bk a laquelle on rajoute en dessous la ligne bk. A la fin A+ = An+
78
5-24° Matrices de Toeplitz. Ce sont des matrices carrées telle que A[i, j]= A[i-1, j-1] pour 2
≤ i ≤ n et 2 ≤ j ≤ n.Trouver une représentation de ces matrices et montrer qu'elle peuvent
s'additionner en O(n). Construire un algorithme permettant de multiplier une telle matrice par
un réel en O(n1,58). Montrer que le produit de deux telles matrices peut s'effectuer en
O(n2,81 ). Construire un autre algorithme de produit en O(n 2,58 ) et le programmer.

5-25° Systèmes quelconques. Compléter le programme de résolution de systèmes pour N


équations et P inconnues. L'algorithme est le suivant: lorsque sur la colonne J on ne peut
trouver de pivot, alors on cherche le premier K tel que J < K ≤ P et A[J, K] ≠ 0 (XK sera dite
inconnue principale et XJ non principale ). Si ce K existe, on permute les colonnes J et K de A
en prenant soin de signaler cette interversion des noms des inconnues ( on pourra utiliser un
tableau IND des indices qui sera [1 , 2 , ... , P ] au départ ).
Si un tel K n'existe pas, alors on est en présence, sur la ligne J, d'une équation du type 0 =
A[J, P+1] qui est soit , impossible, soit 0 = 0, en ce dernier cas on peut la supprimer et
décrémenter le rang R, puis tasser les lignes de la matrice (il n'y en a plus que N-1 )
La triangulation doit se faire pour des numéros de colonnes allant de 1 à min(N,P+1), en
terminant la méthode sans qu'il y ait eu d'impossibilité, on doit obtenir une matrice
triangulaire de R lignes n'ayant que des 1 sur la diagonales.
IND[R+1], ... , IND[P] constituent les indices des inconnues dont on peut choisir des valeurs,
pour ensuite trouver les autres en résolvant un système de Cramer de R équations.
Exemple :
2 2 -3 2 0 2 2 -3 2 0
1 -2 6 3 -2 0 -3 15/2 2 -2
A= de rang 3, se triangularise en B =
1 4 -6 5 1 0 0 3 6 -1
0 0 3 6 1 0 0 0 0 0

5-26° Algorithme du simplexe : un problème de programmation linéaire est la recherche du


minimum ou du maximum d'une fonction (linéaire) de coût ∑cixi avec comme contraintes
que les inconnues xi soient positives et un certain nombre d'équations ou d'inéquations
linéaires ∑aijx j ≤ bj. On montre que l'extremum cherché ne peut être qu'en un sommet de
l'intersection de ces hyperplans (polyèdre convexe). On suivra la démarche :
a) Ramener les inéquations à la forme ∑aijxj ≤ bj puis à des équations ∑aijxj + yj = bj où la
nouvelle variable yj (dite d'écart) sera donc positive.
b) Ramener les équations à d'autres équations en ajoutant simplement une variable
(artificielle) zk dans le membre gauche, et en modifiant la fonction de coût ∑cixi + Mzk avec
M très grand positif si on minimise et négatif si on maximise. On veut de cette façon avoir les
variables artificielles nulles à l'optimum.
Les variables artificielles peuvent s'exprimer en fonction des variables du problème, et donc
la fonction de coût peut être modifiée. Les contraintes s'expriment donc par un système dont
on rangera les coefficients en tableau.
c) On part d'une solution dite "de base" où les variables du problème sont toutes nulles, on
peut donc calculer les autres. La "base" est par définition l'ensemble des variables n'ayant que
des 0 sauf un 1 dans leur colonne du tableau. S'il y a n inconnues et m contraintes, on aura
donc m lignes et n + m colonnes plus une représentant les constantes b. De plus, avec une
valeur fixée pour M on peut rajouter une ligne représentant la fonction de coût égale à 0. C'est
sur ce tableau que l'on va travailler.
d) Si dans la fonction de coût à minimiser, tous les coefficients des variables "hors base"
(celles qui sont nulles) sont positifs ou nuls, l'optimum est atteint.
e) Sinon, on échange la variable i dont le coefficient dans la fonction de coût est le plus
fortement négatif en lui donnant la valeur x avec la première variable k de "base" telle que x ≤
min(bk / aki) les xk étant les variables de l'ancienne "base".
La ligne k qui réalise ce minimum est choisie, et la colonne i est modifiée par un jeu de
combinaisons linéaires sur l'ensemble du tableau de façon à être nulle sauf un 1 à la ligne k.
La variable xi devient "de base".
79
5-27° Carrés magiques construits récursivement.
Ecrire un programme récursif produisant des carrés magiques formés avec des entiers pris au
hasard. On considère pour cela qu'une matrice magique (n-2)*(n -2) de somme s peut-être
"bordée" des 4 côtés pour former une matrice magique n * n de somme S.
Montrer que (n-2)S = ns, on choisit arbitrairement tous les éléments de la première ligne sauf
le dernier, qui en est déduit, ainsi que toute la dernière ligne. On choisit alors au hasard n-1
éléments de la première colonne et on en déduit le dernier et toute la dernière colonne.

Supposons que la matrice intérieure de dimension n-2 soit déjà magique de somme s, nous
allons proceder au remplissage des bords dans l'ordre indiqué pour former une matrice
magique de dimension n et de somme S pour l'instant indéterminée.
1° On choisit au hasard, tous les éléments de la bande 1
2° On en déduit par soustraction avec S le dernier de la ligne.
3° On déduit le premier de la dernière ligne avec S somme de la seconde diagonale.
4° On déduit de même le dernier de la dernière ligne.
5° On déduit le reste de la dernière ligne, colonne par colonne. C'est alors qu'il faut vérifier
que cette ligne fait bien une somme S : ∑i≤k≤jaj,k = ∑(S-s-ai,k) = n(S-s) - ∑ai,k = n(S-s) - S
donc pour que cette somme soit S, il faut obligatoirement S = n(S-s) - S, soit : ns = (n-2)S
6° On choisit au hasard les n-3 éléments de la bande 6
7° On en déduit le dernier inconnu de la première colonne.
8° On déduit le reste de la dernière colonne, et on vérifie de même que sa somme vaut S.
Pour arrêter les appels récursifs, il faudra aboutir à une matrice de dimension 1 si n est impair,
sinon, à une matrice magique de dimension 2, qui est alors nécessairement avec ses 4
éléments égaux à s/2. D'où la procédure :
procedure maj (S, i, j : integer; var A : tab); {Fonctionne avec S multiple de n = j-i+1}
var k, n, s0 : integer;
begin n := j - i + 1;
if i = j then A[i, j] := S
else if j = i + 1 then begin A[i,i] := S div 2; A[i,j]:= S div 2; A[j,i]: = S div 2;
A[j,j] := S div 2 end
else begin s0 := (n-2)*S div n;
for k := i to j-1 do A[i, k] := random(10);
A[i, j] := S; for k := i to j-1 do A[i, j] := A[i, j] - A[i, k];
A[j, i] := S - s0 - A[i, j]; A[j, i] := S - s0 - A[i, i];
for k := i+1 to j-1 do A[j, k] := S - A[i, k] - s0;
for k := i+1 to j-2 do A[k, i] := random(10);
A[j-1, i]:=S - A[j,i];
for k := i to j-2 do A[j-1, i] := A[j-1, i] - A[k, i];
for k := i+1 to j-1 do A[k, j] := S - A{k, i] - s0;
maj( (n-2)*S div n, i+1, j-1, A) {récursivité terminale} end
end;

Remarque : les carrés magiques de dimension n forment un espace vectoriel de dimension


vérifiant dn = dn-2 + 2n-3 on en déduira que si n est pair alors dn = n(n-1)/2 et si n est impair
dn = n(n-1)/2 + 1
80
5-28° Valeurs propres des matrices tridiagonales Si n est un entier plus grand que 2 et Tn
est une matrice symétrique nulle sauf sur ses trois diagonales où aucun bi n'est nul, on peut
séparer les n valeurs propres distinctes, et les calculer par dichotomie.

On prendra une constante n, un type vecteur = array [0..n] of real et deux variables globales
A, B de type vecteur, contenant respectivement la diagonale principale avec A[0]=0, et l'autre
diagonale avec B[0]=B[n]=0. Ces données sont supposées déjà en mémoire.
Soit Pn le polynôme caractéristiques de Tn pour n > 1
a) En posant P0(t) = 1 et P1(t) = a1 - t, établir Pn(t) = (an - t)Pn-1(t) - bn-12Pn-2(t), et prouver
que Pn et Pn-1 n'ont pas de racine commune.
b) Ecrire une fonction "carac" d'une variable réelle u, calculant la valeur Pn(u) en utilisant des
variables locales p0, p1, p2 contenant respectivement, à un certain stade Pk-1(u), Pk(u),
Pk+1(u)
c) Si u est un réel fixé, on dit que deux polynômes consécutifs Pk et Pk-1 présentent une
concordance de signe en u si Pk (u) et Pk-1(u) ont le même signe, (si Pk (u) est nul par
convention son signe sera contraire à celui de Pk-1(u) )
On admettra que le nombre de valeurs propres strictement supérieures à u est égal au nombre
de concordances de signe en u de la suite ( P0(u) P1(u) ,......., Pn(u) )
Programmer la function "conc" (x, y) et la function "nvpsup" donnant ce nombre.
On utilisera une variable locale nv contenant ce nombre construit au fur et à mesure, et on
expliquera la signification des autres variables utilisées.
d) En supposant par ailleurs que toutes les valeurs propres appartiennent à l'intervalle [R, S]
tel que R = min{ai - |bi-1| - |bi| / 1 ≤ i ≤ n} et S = max{ai + |bi-1| + |bi| / 1 ≤ i ≤ n} écrire la
procedure intervalle( var R, S : real); qui construira ces deux valeurs.
e) Ecrire la procedure separation(var U : vecteur); qui fournira n+1 valeurs croissantes dans
[R, S] telles que chacun des n intervalles ainsi délimités contienne une et une seule valeur
propre de Tn.
f) Ecrire la procedure calcul(eps : real; var V : vecteur); qui calcule par dichotomies les n
valeurs propres de Tn à eps près.
g) Soit n = 6 et T la matrice tridiagonale dont tous les bi sont égaux à 1, vérifiant a1 = a6 = 5,
a2 = a5 = 3, et a3 = a4 = 1

Si on développe par rapport à la dernière colonne, on obtient la relation. Si Pn et Pn-1 avaient


une racine commune, alors elle serait commune d'après la relation de récurrence avec Pn-2, et
donc avec tous les polynômes précédents donc avec P1 et P0 qui n'en a pas.
function conc (x, y : real) : integer; {est 1 s'ils sont de même signe, si l'un des deux est nul ce sera 0, et les deux
ne sont pas nuls lors de l'utilisation de "conc" }
begin if x*y > 0 then conc := 1 else conc := 0 end;

function nvsup (u : real) : integer;


var p0, p1, p2 : real; k, nvp : integer;
p0 := 1; p1 := A[1] - u; nvp := conc (p0, p1);
for k := 2 to n do begin p2 := (A[k] - u)*p1 - sqr (B[k-1])*p0;
nvp := nvp + conc (p1, p2); p0 := p1 ; p1 := p2 end;
nvpsup := nvp end;
function carac (u : real) : real; {calcule Pn(u) } 81
var p0, p1, p2 : real; k : integer;
begin p0 := 1; p1 := A[1]-u;
for k := 2 to n do begin p2 := (A[k]-u)*p1 - sqr(B[k-1])*p0;
p0 := p1; p1 := p2
end;
carac := p2 end;

procedure intervalle (var r, s : real);


var p, q : real; k : integer;
begin r := A[1] - abs(B[1]); s := A[1] + abs(B[1]);
for k := 2 to n do begin p := A[k]) - abs(B[k-1]) - abs(B[k]);
q := 2*A[k] - p;
if p < r then r := p;
if s < q then s := q end end;

procedure separation (var U : vecteur);


var r, s : real; nb, k : integer;
begin intervalle(r, s); U[0] := r; U[1] := s;
for k := 1 to n-1 do begin nb := nvpsup(U[k]);
while nb <> n-k do if nb < n-k then U[k] := U[k] + (U[k]-U[k-1])/2
else U[k] := U[k] - (U[k]-U[k-1])/2;
U[k+1] := s end end;

Cet algorithme assez compliqué consiste à revenir à mi-chemin (vers la gauche) s'il n'y a pas
assez de valeurs propres à droite, et à repartir vers la droite de la moitié du segment de gauche
au cas où il y a trop de valeurs propres à droite.
Supposons k-1 valeurs propres déjà séparées et nvpsup (Ui = n-i pour 0 ≤ i < k-1, alors si uk1
est en s (trop fort) uk2 revient à la moitié de la distance, on les note u1, u2, u3 .. sur le

schéma:
procedure calcul (eps : real; var V : vecteur);
var U : vecteur; k : integer; a, b, c : real;
begin separation (U);
for k := 0 to n-1 do begin a := U[k]; b := U[k-1];
repeat c := (a+b)/2; if carac(c)*carac(a) < 0 then b :=c else a := c
until b - a < eps;
V[k+1] := c end
end;

g) On trouve R = -1 et S = 7

5-29° Le plus court chemin d'une station de métro à une autre. Ce problème assez long
peut recevoir beaucoup de solutions de programmation différentes, parmi toutes les idées de
représentation, on pourra affecter chaque ligne du tableau de ses stations, mais les
correspondances pourront figurer aussi dans un tableau à trois entrées :
C = array [1..16 , 1..16 , 1 .. 2] of integer où l'élément C[I, J, 1] est le numéro (éventuellement
nul) sur la ligne I de la première station commune avec la ligne J, et C[I, J, 2] de la deuxième.

5-30° Valeurs propres par l'algorithme de Rutihauser : pour la matrice régulière A, on


décompose A suivant la méthode de Crout A = LU et on pose A1 = UL (semblable à A, donc
de mêmes valeurs propres) puis si An = LnUn de la même façon, on pose An + 1 = UnLn. Si
A est symétrique définie positive, la suite converge vers une matrice triangulaire supérieure
donnant sur sa diagonale toutes les valeurs propres de A.
82
5-31° Valeurs et vecteurs propres par itération-déflation
Supposons qu'une matrice A n*n possède exactement n valeurs propres distinctes
λ 1,...,λn rangées par valeurs absolues décroissantes et V1 ... V n des vecteurs propres
correspondants. On part d'un vecteur X0 quelconque, par exemple celui dont toutes les
composantes sont 1, et on considère la suite Xk des vecteurs unitaires au sens de
||X||=sup(|x1|,...,|xn |) , formée par X1 = AX0/m0 , X2 = AX1/m1 etc... en posant m k =
||AXk||. On montre alors que : λ1 = lim mk et V1 = lim Xk quand k tend vers + ∞.
Si on cherche de la même façon le premier vecteur propre W1 (associé à λ 1 ) de A t
(transposée de la matrice A), alors on montre que la matrice définie par A1 = A - λ 1V1.W1t /
W1t.V1 admet les vecteurs propres V1 ... Vn pour les valeurs 0, λ2,...,λn , on peut donc
recommencer sur A 1.
Expliquer cet algorithme sur un exemple. Expliquez comment vous organisez le travail et le
répartissez en différentes procédures réalisant les opérations necéssaires à cette recherche, en
particulier une procédure principale calculant les couples λ, V.

Solution partielle, exemple :


13 1 0,66 0,786 0,735
A= X0 = , X1 = , m 1 = 4,66 X 2 = , X3 = ,
42 1 1 1 1
0,756
m 3 = 4,94 X 4 = , m 4 = 5,02...(le calcul donne une valeur propre 5, l' autre étant 2)
1
1 -8 6 1 -1 1
puis A1 = X0 = , X1 = , X 2 = , m2 = 2
7 8 -6 1 1 1
Les deux procedures essentielles sont les suivantes (eps est une constante) :
procedure propre (A : matrice; n : integer; var X : matrice; var m : real);
{calcule un vecteur propre X pour la plus petite valeur propre m de la matrice A de dim n}
var m0 : real; X0 : matrice; k : integer;
begin m := supabs(X); repeat for k := 1 to n do X[k, 1] := X0[k, 1];
mult (A, n, n, 1, X, X0); m0 := m; m := supabs(X)
until (abs (m-m0) < eps) and (dist (X, X0, n) < eps) end;

procedure calcul (A : matrice)


var i, k : integer; V, W : matrice;
begin for k := 1 to n do X0[k] := 1;
for i := 1 to n do begin for k := 1 to n do V[k, 1] := X0[k, 1];
propre (A, n, V, m); sortie (m, V); transpo (A, n, n, B);
propre (B, n, W, m); transpo (W, n , 1, W); mult (W, 1, n, 1, V, B);
scalaire (W, n, m / B[1, 1], W); mult (V, n, 1, n, W, B); soust (A, n, n, B, A) end end;

Les autres procédures presque transparentes sont à mettre au point. Application :


1001 1/3 0 0 1
0212 1 1 1 0
donne propre pour la valeur 4 pour 2 pour 3 pour 1
0030 0 1 0 0
0040 1 0 0 0

-0,2 1,8 -1,8 -1,4 0


-4 14 -11 -18 6
Exemple, valeurs propres de -4,4 9,6 -6,6 -12,8 6
0,4 2,4 -2,4 -3,2 0
2 -4 4 6 -3
Ce sont plus généralement : -3, -1, 0, 2, 3.
83
5-32° Améliorer le parcours du cavalier sur un échiquier, en triant les coups possibles à
partir de chaque position suivant le nombre de possibilités qu'ils ont eux-mêmes, suivant un
ordre croissant. On arrivera ainsi plus vite aux impasses.

En reprenant le programme du chapitre 4, nous rajoutons une :


function fils (l, c : integer) : integer; {nombre de cases libres dans le tableau "ech" autour de la case l, c}
function v (b : boolean) : integer; begin if b then v := 1 else v := 0 end;
begin fils := v(libre (l+1, c+2)) + v(libre (l+2, c+1)) + v(libre (l+2, c-1)) + v(libre (l+1, c-2))
+ v(libre (l-1, c-2)) + v(libre (l-2, c-1)) + v(libre (l-2, c+1)) + v(libre (l-1, c+2)) end;

procedure tri (var t : tab; d : integer); {réalise un tri de t suivant la clé t[.,2] croissante, méthode bulle}
var j, x : integer; test : boolean;
begin repeat test := true;
for j := 1 to d-1 do
if t[j,2]>t[j+1,2] then begin x := t[j, 0];t[j, 0]:= t[j+1, 0]; t[j+1, 0] := x;
x := t[j, 1]; t[j, 1] := t[j+1, 1]; t[j+1, 1] := x;
x := t[j, 2]; t[j, 2] := t[j+1, 2]; t[j+1, 2] := x;
test := false end;
until test end;

procedure voisins (l, c, n : integer; var d : integer; var tv : tab);


{construit le tableau tv des d cases libres autour de l, c qui est déjà rempli, triéés suivant leurs degrés croissants,
mais si on trouve un voisin libre en impasse, on force le backtrack grâce à d=0}
var i, f, x, y : integer;
begin i := 1; d := 0; f := 7;
repeat x := x+ts[i, 1]; y := c + t[i, 2]; {coordonnées d'un voisin}
if libre(x, y) then begin d := d+1; f := fils(x, y); tv[d, 0] := x; tv[d, 1] := y; tv[d, 2] := f end;
i := i+1
until (f=0) or (i=9);
if f = 0 then if ech[l, c]= n*n-1 then d := 1 else d := 0
else if d > 1 then tri (tv, d) end;

function zebre (i, l, c, n : integer) : boolean; {réalise la même chose que la fonction cheval, mais en sautant
d'abord dans les cases où il y a le plus de chances de continuer,, i se trouve déjà dans la cese l, c}
var k, d, ls, cs : integer; tv : tab; res : boolean;
begin if i = n*n then begin aff (ech, n, n); zebre := true end
else begin voisins (l, c, n, d, tv);
if d =0 then zebre := false
else begin k := 1; res := false;
repeat ls := tv[k, 0]; cs := tv[k, 1]; ech[ls, cs] := i+1;
res := zebre (i+1, ls, cs, n);
if not(res) then begin ech[ls, cs] := 0; k := k+1 end
until res or (k > d);
zebre := res end end end;

Le programme consistera à "if zebre (1, l0, c0, n) then write ('Voila !')"
Un autre développement consiste à trouver toutes les solutions, on utilise deux variables
globales ns nombre de solutions sans retour et nr nombre de solutions avec retour.
procedure ecurie (i, l, c, n : integer);
var k, ls, cs : integer;
begin if i = n*n then begin aff (ech, n, n); if revenu (l, c, l0, c0) then nr := nr + 1 else ns := ns + 1 end
else begin for k := 1 to 8 do
begin ls := l + ts[k, 1]; cs := c + ts[k, 2];
if libre (ls, cs) then begin ech [ls, cs] := i+1;
ecurie (i+1, ls, cs, n);
ech [ls, cs] := 0 end
end
end end;

Mais attention pour une case de départ donnée, on trouve environ 300 solutions pour n = 5.
La fonction "revenu" reste à écrire.
84
5-33° Les mariages [knuth 73]. Etant donnés n femmes et n hommes, chacun ayant établi une
liste de préférence des n personnes de l'autre sexe, on dit que le couple (x1, y1) est instable
avec (x2, y2) si x1 préfère y2 à y1 et que y2 préfère x1 à x2. Chercher l'algorithme permettant
de former des couples de façon à ce qu'il n'y ait aucune instabilité. Il n'y a pas de symétrie, il
faudra donc vérifier que (x1, y1) stable avec (x2, y2) et que (x2, y2) stable avec (x1, y1).

Il est possible de représenter les données par un tableau pref [1..2, 1..n, 1..n] définissant :
pref (1, h, f) est le rang de la femme de numéro f dans le classement de l'homme h
pref (2, h, f) ................de l'homme h.................................... la femme f
Ce choix de structuration des données simplifie considérablement l'écriture de "stable".
Le tableau "mari" est construit petit à petit, mari(f) désignant l'homme attribué à la femme f.
function stable (f, h : integer) : boolean; {vrai si le fait d'accoupler f et h n'apporte pas d'instabilité sachant que
le tableau "mari" est rempli et stable jusqu'à f-1 } var i : integer;
begin i := 0;
repeat i := i+1 {On teste l'instabilité du nouveau couple avec tous les anciens dans les deux sens}
until (i >= f) or ((pref[1, mari[i], f] < pref[1, mari[i], i]) and (pref[2, mari[i], f] < pref[2, h, f]))
or ((pref [1, h, i] < pref [1, h, f]) and (pref [2, h, i] < pref [2, mari [i], i]))
if i = f then stable := true else stable := false
end;

function libre (h : integer) : boolean; { vrai si l'homme h n'est pas encore dans "mari"}
var f : integer;
begin f := 0;
repeat f := f+1 until (mari[f] = h) or (f = n);
if mari[f] = h then libre := false else libre := true
end;

function mariage (f : integer) : boolean; {vrai s'il est possible de continuer, i étant le numéro de la dernière
femme casée} var h : integer; poss : boolean;
begin if f = n then mariage := true
else begin h := 1; poss := false;
repeat if (libre(h)) and (stable (f+1, h)) then begin mari [f+1] := h;
poss := mariage (f+1)
end;
h := h+1
until poss or (h > n);
mariage := poss
end;

On appelera "if mariage (0) then afficher (mari)" avec une procedure d'affichage.
Par exemple si n = 3 et :
1 321 1 213
Si les préférences des hommes sont : 2 3 1 2 et celles des femmes : 2 1 2 3
3 231 3 321
Alors mariage(0) va appeler mariage(1) une première fois et tomber dans une impasse, puis
une deuxième fois pour appeler mariage(2) puis mariage (3) qui terminera avec le tableau des
maris [2, 1, 3] (pour les femmes respectives 1, 2, 3).

5-34° Décomposition en inverses : Tout nombre entier peut-il être décomposé en une somme
de longueur quelconque d'entiers distincts, dont la somme des inverses vaut 1. Exemple 78 =
2 + 6 + 8 + 10 + 12 + 40 et 1/2 + 1/6 + 1/8 + 1/10 + 1/12 + 1/40 = 1 (En fait c'est toujours vrai
à partir de 78, mais avant ?)

Solution (la fonction "possinv" est interne à la procédure "decompinv" :


procedure decompinv (n : integer);
var L : array [0..100] of integer; eps : real;
function possinv (n, i, s : integer; si : real) : boolean; 85
{s'il est possible de continuer à emplir le tableau L après L[i] avec des entiers entre k=L[i] et n (il y en aura b) de
telle sorte que n=L1+L2+...Lb et 1=1/L1+1/L2+1/L3+...+1/Lb On note s=L1+L2+...+Li et si=1/L1+...+1/Li}
var j : integer ; poss : boolean;
begin
if (s=n) and (abs (si-1) < eps) then begin writeln ; write (n,' = ');
for j := 1 to i-1 do write (L[j], '+');
write (L[i], ' et 1 = ');
for j := 1 to i-1 do write ('1/', L[j], ' + ');
writeln ('1/', L[i]); possinv := true end
else if (s>n) or (si > 1+eps) then possinv := false
else begin j := L[i] + 1; poss := false;
repeat L[i+1] := j; poss := possinv (n, i+1, s+j, si+1/j); j := j+1
until poss or (j > n-s);
possinv := poss end
end;
begin eps := 0.00001; L[0]:=0; if possinv (n, 0, 0, 0) then write ('voila ') else write (' pas de solution pour ',n)
end;

Quelques résultats : 11 = 2 + 3 + 6 24 = 2 + 4 + 6 + 12
30 = 2 + 3 + 10 + 15 31 = 2 + 4 + 5 + 20 32 = 2 + 3 + 9 + 18
38 = 3 + 4 + 5 + 6 + 20 43 = 2 + 4 + 10 + 12 + 15 45 = 2 + 4 + 9 + 12 + 18

5-35° Etude des puzzles carrés constitué uniquement de carrés :


Exemple 1122 = 22 + 42 + 62 + 72 + 82 + 92 + 112 + 152 + 162 + 172 + 182 + 192 + 242 +
252 + 272 + 292 + 332 + 352 + 372 + 422 + 502 (découvert en 1978 par A.Duijvestijn)
a) Le prouver pour 608 avec 26 carrés
b) Faire un programme déterminant une solution, le tester avec 112, 175 et 608
c) ........................................... toutes les solutions.

procedure decompquadra (n : integer);


var i, b : integer; L : array [1..100] of integer;
function possquadra (n, s, i, v : integer) : boolean; {vrai s'il est possible de continuer à remplir le tableau L avec
des entiers décroissants de [1, v-1] sachant que L[i] sera v, la somme des carrésde L[1] à L[i-1] étant s ≤ n*n}
var j : integer ; poss : boolean ;
begin if (v<>0) and (s<>0) then writeln ('somme=',s,' rang=',i,'->',v);
if sqr(n) = s then begin L[i] := v; b := i; possquadra := true end
else if (sqr(n) < s) or (v = 1) then possquadra := false
else begin j := min(v-1, trunc (sqrt (n*n-s)));
repeat poss := possquadra (n, s+j*j, i+1, j); j := j-1 until (j = 0) or poss;
if poss then begin L[i] := v; possquadra := true end end
end;
begin b := 0;
if possquadra (n,0,0,n) then begin write (sqr (n),' = carré (', n, ') = ');
for i := 1 to b-1 do write ('carré (', L[i] ,') + ');
writeln ('carré (', L[b], ')') end end;

Maintenant pour avoir toutes les décompositions :


procedure touquadra (n, s, i: integer); {cherche à continuer de remplir le tableau t avec des entiers décroissants
de [1, ti] sachant que la somme des carrés de t[1] à t[i-1] étant s ≤ n*n}
var j : integer;
begin t[0] := n;
if sqr (n) = s then begin write (sqr(n),' = carré (',n,') = ');
for j := 1 to i-1 do write ('carré (',t[j],') + ');
writeln('carré (',t[i], ')') end
else for j := min (t[i]-1, trunc (sqrt (n*n-s))) downto 1 do begin t[i+1] := j; touquadra (n, s+j*j, i+1) end
end;

Un résultat (ils sont très nombreux):


112 = 102 + 42 + 22 + 12 = 92 + 62 + 22 = 82 + 62 + 42 + 22 + 12
86
5-36° Indépendamment des procédures du 34°, étant donné une solution algébrique pour un
entier n donné, trouver une représentation et visualiser une solution du puzzle (elles sont très
peu nombreuses), c'est à dire une disposition géométrique des carrés. Trouver toutes les
solutions géométriques pour une même solution algébrique.

Indication : si b nombre sont déjà acquis dans le tableau L[1..b]. L'idée est de considérer un
tableau t[0..n, 0..n] de booléens indiquant les places déjà occuppées dans le carré n*n. On
construit le tableau P[1..b] formés par les points (x, y) du coin haut gauche d'un carré de côté
L[i] (en ordre décroissant). On peut aussi se servir du problème du professeur Facon (chapitre
précédent). On définiera :

continuer (i)
{vérifiant qu'il est possible de terminer avec le i-ième carré placé en P[i].x, P[i].y}
si i = b alors demander une touche pour continuer et afficher les séparations
sinon x := -1; y := 0;
répéter x := x + 1;
tant que t[x, y] faire y := y + 1;
tant que L[i + 1] ≤ n - y faire emplir (x, y, L[i + 1])
continuer (i + 1)
vider (x, y, L[i + 1])
jusque n - x < L[i + 1]
emplir (x, y, v, drap) {drap booléen pour savoir si emplir ou vider}
pour i := 0 à v - 1 faire pour j := 0 à v - 1 faire t[x + i, y + j] : = drap
fixer la couleur à noir si drap et à blanc sinon
placer (x + v, y); tracer (x + v, y + v); tracer (x, y + v)

Solution pour n = 112 côté d'un carré dont la décomposition est donnée à l'exercice précédent:
87

CHAPITRE 6

CHAINES DE CARACTERES
ET AUTRES TYPES DE DONNEES

De même qu'il existe un grand choix d'organisation d'une programmation en tâches et sous-
tâches pour un même algorithme, de même il existe une grande variété dans l'organisation
des données. Pour les exemples étudiés jusqu'ici la question se posait peu, mais en d'autres
domaines la réflexion que l'on peut avoir sur cette structuration des données n'est pas
indépendante de l'algorithmique et peut être tout aussi longue. Certains langages évolués
comme CAML permettent de travailler avec des types d'objets comme ceux qui sont
habituellement définis en mathématiques : applications de E dans F etc. Mais aussi, ils
permettent de parler de types abstraits où seules les opérations sur ce type sont connus des
autres modules. Nous présentons ici les chaînes de caractères, les ensembles et les
enregistrements.
En Pascal, les types simples prédéfinis sont "integer" (entiers codés sur deux octets), "real"
(sur 6 octets), entiers et réels ayant donc des sens beaucoup plus restrictifs qu'en
mathématiques, "char" (un octet), désignant les caractères qui sont codés par un nombre entre
0 et 255 puisqu'il y a 28 = 256 codes possibles, "boolean" (un bit) représentant soit 0 pour le
faux, soit 1 pour le vrai .
Types définis par énumération ou intervalle
Les types peuvent être définis par énumération comme carte = (coeur, carreau, trèfle, pique)
par exemple, ou par intervalle , ainsi :
lettre = A..Z; ou encore entier = 0..100;
Pour un type défini par intervalle ou par énumération, "ord" est la fonction qui en donne le
numéro d'ordre à partir de 0,. par exemple ord ('A') = 65.
On peut alors commander : for J := lundi to vendredi do ...
Les types structurés les plus usuels sont les tableaux array [ ... , ... , ... ] of ...; (chapitre 5) les
chaînes de caractères string[...] et les fichiers file of ... dont il sera question au chapitre 7.
Chaînes de caractères
Les chaînes de caractères (c'est-à-dire tous les mots ou plus généralement les assemblages de
lettres et de chiffres) sont déclarées par le type "string" suivi du maximum de leur longueur
placé entre crochets, leur nombre de caractères est obtenu par la fonction "length", et le n-
ième caractère de C est obtenu par C[n] .
Le rang d'un élément faisant partie d'un type défini par énumération (et en particulier le code
A.S.C.I.I.) est obtenu par la fonction "ord" dont la fonction inverse est "chr".
Exemple ord ('B') = 66 (consulter la table des codes ASCII ), chr (67) = 'C' , succ ('C') = 'D'
est la fonction successeur dans un type donné, et pred ('F') = 'E' est la fonction prédécesseur.
D'autres procédures et fonctions prédéfinies sont encore utiles, notamment pour certains des
exercices figurant dans ce chapitre.
"delete" (C , I , J) transforme la chaîne C en lui retirant J caractères à partir du I-ième.
Exemple si C est 'alligators', et que la procédure delete(C, 3 , 5) est exécutée, alors C devient
'alors'.
"insert" (C , S , I) est une procédure d'insertion de la chaîne C dans la chaîne S à la I-ième
position. Exemple insert ('ligat' , C , 3) aura pour effet de redonner la valeur 'alligators' à la
variable C.
"concat "(c1 , c2 , ... ) renvoie la chaîne obtenue en concaténant tous ses arguments. Exemple 88
concat ('Buda' , 'Pest') = 'BudaPest' . Concat est abrégé par le signe +
"copy" (C , I , J) renvoie les J caractères de C à partir du I-ième. substr (C, I, J) dans d'autres
pascal.
Exemple copy ('loxodromie' , 4 , 2) = 'od'

Exemple de conversion de chaîne en valeur numérique

On met l'accent dans cet exemple sur le fait qu'une valeur numérique comme 123 et la
succession des caractères "123" sont, pour la machine, deux objets complètement distincts. Le
passage peut cependant se faire de l'un à l'autre.

Considèrons une suite d'entiers dont chaque terme est la somme des carrés des chiffres qui
composent le terme précédent dans la suite. On démontre qu'une telle suite est toujours
périodique et qu'il n'y a que deux périodes possibles. Essayer avec 7, avec 43 avec n'importe
quel entier.
Traîter les termes de la suite comme des chaînes de caractères, est particulièrement adapté à
ce problème. Chaque terme N est converti en la chaîne de ses chiffres CH grâce à la
procédure prédéfinie "str", puis chacun des chiffres, ayant une valeur X, on en tire le carré qui
s'ajoute à M, lequel va constituer le terme suivant; d'où la procédure "suite".
La procédure str (string = chaîne) possède une procédure inverse "val" à trois paramètres, la
chaîne, la valeur qui en est déduite, et une valeur servant au contrôle.

program suitebizarre ;
var N : integer;

procedure suite (N : integer);


var C, M, I, X : integer ; CH : string [6];
{Est une procédure récursive décryptant chaque chiffre X de la chaîne CH correspondant à la valeur N, et
calculant la valeur suivante M }
begin
write (N,'/'); { Le nombre est écrit suivi d'un séparateur / }
if N in [1,4] { On utilise un ensemble pour tester si N est 1ou 4}
then writeln (' fini ')
else begin M := 0;
str (N, CH);
for I := 1 to length (CH) do
begin val (CH[I], X, C); M := M+X*X end ;
suite (M) end
end;

begin write ('Donnez un entier positif ') ; readln (N) ; suite (N) end.

Remarque : on pourra définir la somme des carrés des chiffres récursivement par :
som (n) = si n < 10 alors n2 sinon som (n div 10) + sqr (som (n mod 10))

Exemple pour un départ à 7 : 7 / 49 / 97 / 130 / 10 / 1 / fini.

Exemple d'utilisation d'ensembles : suite partitionnant les entiers avec ses différences

Le programme qui suit utilise le type ensemble "set of..." pour lequel "in" est l'appartenance,
+ est l'union et [ ] représente l'ensemble vide. Ce type de représentation est assez limité en
Pascal, c'est pourquoi on l'emploiera peu.
On considère la suite croissante d'entiers telle qu'avec la suite formée par toutes les
différences de termes consécutifs de cette suite, les deux suites ainsi formées constituent une
partition des entiers.

Le début est nécéssairement 1 (+ 2 = ) 3 (+ 4 = ) 7 (+ 5 = ) 13 (+ 6 = ) 19 (+ 8 = ) etc ...


program partition ; 89
const M = 100; { une constante dans le programme }
type entier = 0..255; { On choisit de définir un nouveau type }
var N, D : integer ; S : set of integer ;

begin N := 1; D := 2; S := [1] ; { initialisation des premiers termes }


write (N) ;
while N < M do if (N + D) in S then D := D + 1
else begin N := N + D; S := S + [N];
write (' (+', D, ')-->', N); D := D + 1
while D in S do D := D + 1
end;
end.

Pour analyser le programme, le mieux est de le faire tourner à la main sur le papier pour M
inférieur à 60 .
On appelle pour cela N le terme courant de la première suite, D la différence avec le terme
suivant, et S l'ensemble des termes de la première suite jusqu'à N.
Si N+D n'est pas déjà dans l'ensemble S, on l'y place, on affiche une indication et on cherche
la plus petite différence D suivante qui n'est pas dans S.

Exemple d'utilisation de tableau de booléen : les nombres premiers

Le crible d'Eratosthène est un algorithme de recherche des nombres premiers jusqu'à M, qui
n'est pas fondé sur des tests de divisibilité mais sur l'élimination des multiples des premiers
éléments restants (d'où le nom de nombre premier). 2 étant le départ, on l'inscrit comme étant
premier, puis on raye tous ses multiples, le premier non rayé est alors 3 qui est à son tour
déclaré premier, et on raye ses multiples, le premier suivant est 5 etc...

Cet algorithme est très rapide, mais utilise l'ensemble de tous les entiers de 1 à M. On réduit
considérablement cet encombrement de la mémoire en utilisant un tableau de booléens où
l'élément de rang N ayant la valeur "false" signifiera qu'il a été rayé.

program eratosthene;
const M = 10000;
var P, N : integer; T : array[2..M] of boolean; { Ce tableau T ne sera rempli que de 0 ou de 1 }
begin
for P := 2 to M do crib[P] := true; {initialisation de tous les entiers entre 2 et M comme non rayés}
P := 2;
repeat write (P : 6); N := P;
repeat crib [N] := false; N := N + P until N > M;
repeat P := P + 1 until crib [P]
until P > M
end.

Remarque : on peut même rayer P, puis P2, P2 + P etc... car les autres ont déjà été rayés.

Les "records" ou "enregistrements"

Quoique d'une utilisation un peu compliquée au départ, le constructeur de type structuré


"record", permet de créer des "enregistrements" ou "articles" réunissant des données de types
distincts, voire avec "case", en nombre variable, par exemple:

etudiant = record nom , prenom : string[20];


année : integer;
sexe : boolean;
case controle of math : (alg, ana, géom : integer);
info : (exam, projet : integer);
chinois : (écrit, oral : integer) end;
On descend en cascade dans un tel arbre par le point, ainsi si A est un étudiant, A.info.exam 90
sera sa note d'examen en informatique. Les parenthèses des trois dernières lignes sont
indispensables, quant au "end", il termine à la fois le "case" et le "record" ici.

Exemple de types donnés par énumération : les annonces du bridge

Dans une main de 13 cartes, on totalise les points d'honneur 4 pour as, 3 pour le roi, jusqu'à 1,
et les points dedistribution : 3, 2 ou 1 suivant que l'on a respectivement 0, 1 ou 2 cartes
seulement d'une couleur. On suppose l'existence d'une fonction "ord" qui donne le rang à
partir de 0 d'un objet dans un type énuméré. Pour créér cette fonction, on peut définir :

type couleur = (trefle, carreau, coeur, pique);


valeur = (as, roi, dame, valet, dix, .... , deux);
carte = record coul : couleur; val : valeur end;
main = array [1..13] of carte;

function distribution (m : main) : integer;


var compte : array [trefle..pique] of integer;
d, i : integer; c : couleur;
begin
for c := trefle to pique do compte [c] := 0;
d := 0;
for i := 1 to 13 do begin compte[m[i].coul] := 1 + compte [m[i].coul];
if main [i].val < dix then d := d + 4 - ord (main[i].val)
for c := trefle to pique do case compte[c] of 0 : d := d + 3;
1 : d := d + 2;
2 : d := d + 1 end;
distribution := d
end;

Remarque : si on déclare le type "valeur" = (sansvaleur, valet, dame, roi, as), alors on a une
simplification d := d + ord (main[i].val)

6-1° La machine à écrire endiablée: faire en sorte que la pression de chaque lettre au clavier
provoque la sortie sur l'écran, non pas de la lettre mais d'un mot, par exemple DIABLE au
lieu de D etc... (utiliser c := getchar;).

6-2° Epeler un mot en séparant les lettres par un tiret.

6-3° Faire apparaître la liste des caractères et de leurs codes ASCII

program ascii; var i: integer;


begin for i := 33 to 255 do write (i : 4,'-->', chr (i),' ') end.

6-4° Ecrire un mot à l'envers. Essayer sur "Laval" ou "Esope reste ici et se repose".

6-5° Produire à l'écran une pyramide avec les parties centrales d'un mot.

6-6° Doubler les lettres d'un mot.

6-7° Effectuer les permutations circulaires d'un mot. (LUC UCL CLU)

6-8° Cryptogramme : Un prénom est codé par translation de longueur aléatoire, (exemple
LUC par la translation -3 devient IRZ). La même méthode appliquée aux mots d'une phrase,
en les comparant à ceux d'un petit lexique, donne des résultats extraordinaires.
91
6-9° On veut épeler un nom de trente lettres maximum à la façon de N comme NOEMIE etc...
On définit pour cela un type que l'on appelle "nom", et qui représente des objets différents de
ceux typés par "prenom".

program telephone ;
type prenom = string [15]; nom = string [30];
var P : array[1..26] of prenom ; N : nom;
{Le fait de ranger les prénoms par ordre alphabétique dans un tableau de vingt-six éléments permet de calculer
très facilement la place de chacun d'entre eux suivant sa première lettre. Etant donné un caractère qui est noté X
(pas nécessairement la lettre X !), la fonction "ord" renvoie son code, il suffit alors de savoir que le code (en
décimal) de la lettre A est 65, pour obtenir l'indice où se trouve le prénom commençant par ce caractère dans le
tableau. C'est ce que réalise la fonction "comme".}

function comme (X : char) : prenom;


begin comme := P [ ord (X) - 64 ] end;
procedure epelle (Y : nom);
var I : integer ;
begin for I := 1 to length (Y) do writeln (Y[I],' comme ', comme (Y[I]) ) end;
begin { Début du programme }
P[1] := 'ANNA'; P[2] := 'BASILE'; P[3] := 'CESAR'; P[4] := 'DANIEL';
P[5] := 'ERNEST'; P[6] := 'FABRICE'; P[7] := 'GASTON';
{ - Ici on continuera avec les prénoms de son choix - }
write ('Donnez un nom ') ; readln (N) ; epelle (N)
end.

6-10° Nombre de Champernowne 0,123456789101112...construit avec les entiers


successifs, l'écrire en se limitant à 20 lignes d'écran.

6-11° Chercher les nombres entiers positifs entre 1 et 500, qui sont égaux à la somme des
cubes de leurs chiffres (on doit en trouver cinq).

6-12° Trouver le plus petit entier N dont le chiffre des unités est 6, et tel que quand on efface
6, et que celui-ci est placé à gauche du nombre sans modifier les autres chiffres, on obtienne
le quadruple de N.

6-13° Réaliser une fonction binaire donnant la chaîne des chiffres binaires correspondant à un
entier.

function binaire (n : integer) : string;


begin case n of 0 : binaire := '0';
1 : binaire := '1';
otherwise binaire := concat (binaire(n div 2), binaire (n mod 2)) end end;

6-14° Distance de Levenstein entre deux chaînes : c'est le coût minimal calculé comme
somme des coûts des opérations élémentaires de substitution ƒ(a, b), d'insertion ƒ(Ø, a) et de
destruction ƒ(a, Ø) de caractères. Programmer l'algorithme de Wagner-Fisher qui détermine
ce coût ainsi que la suite des opérations pour 2 chaînes x = a1a2 ... an et y = b1b2...bm, on
note xi le préfixe a1a2...ai et ∂(i, j) la distance entre xi et yj.
∂(0, 0) ← 0
Pour i de 1 à n faire ∂(i, 0) ← ∂(i-1, 0) + ƒ(ai, Ø)
Pour j de 1 à m faire ∂(0, j) ← ∂(0, j-1) + ƒ(Ø, bj)
Pour i de 1 à n faire Pour j de 1 à m m1 ← ∂(i-1, j-1) + ƒ(ai, bj)
m2 ← ∂(i-1, j) + ƒ(ai, Ø)
m3 ← ∂(i, j-1) + ƒ(Ø, bj)
∂(i, j) ← min (m1, m2, m3)
La distance entre les chaîne x et y est alors ∂(n, m).
Essayer à la main pour x = "abc" et y = "dacb".
92
6-15° Permutations ?
Voici une procédure où A contient 7 lettres, U et V étant deux tableaux d'entiers indexés de 1
à 8000, "ech" est la procedure d'échange de deux variables entières :

procedure remplissage (n: integer) ; {fonctionne pour 0 ≤ n ≤ 7}


var p, k, m, s : integer;
begin v[1] := 1; v[2] := 1; m := 2;
for p := 3 to n do begin k := 0;
repeat for s := 1 to p - 1 do begin u[k*p+s] := s; u[k*p+p+s] :=p-s end
u[k*p+p] := v[k+1] ;
u[k*p+p+p] := v[k+2] + 1 ;
k := k+2
until k = m-2;
m := m*p;
for k := 1 to m do v[k] := u[k]
{marque 1} end; {marque 2}
for p := 1 to m do begin ech ( A[v[p]], A[v[p] + 1]);
write (p, ' : ');
for k := 1 to n do write (A[k]) ; writeln
end;
end;

Quelle est la valeur de m au début de la seconde grande boucle (marque 2) ?


Dérouler à la main le programme pour n = 2 puis pour n = 3.
Que contient le tableau V à la marque 1 ?
Que va-t-il se produire en définitive ?

6-16° Suite de Kaprekar : si u0 est un nombre de 4 chiffres, on calcule u1 comme la


différence entre le nombre obtenu en ordonnant les chiffres de u0 dans l'ordre décroissant et
celui obtenu avec l'ordre croissant. Recommencer jusqu'à obtenir 6174.

6-17° Un montage électrique formé de résistance va être codé par une chaîne de "s" pour série
et "p" pour parallèle, suivis chaque fois de deux sous-circuits, les résistances étant des chiffres
de 1 à 9.
a) Programmer la fonction circuit lisant les caractères un à un au clavier et donnant la
résistance du circuit en appelant deux autres fonctions.
b) Programmer une fonction résitance faisant la même chose toute seule.

program resis;
var a : integer;

function chiffre (c : char) : boolean;


begin chiffre := (47 < ord(c)) and (ord(c) < 58) end;

function parallele : real; forward;


function serie : real; forward;
function circuit : real;
var c : char;
begin c := readchar; write(c);
if chiffre (c) then circuit := ord (c) - 48
else if c = 'p' then circuit := parallele
else circuit := serie end;

function parallele ;
var a, b : real;
begin a := circuit; b := circuit ; parallele := a*b / (a+b) end;

function serie ;
var a, b : real;
begin a := circuit; b := circuit ; serie := a+b end;
Une autre solution avec un point de vue moins "effet de bord" est : 93

function resistance (s : string; var i: integer): real; {autre possibilité}


var a, b : real; {i est la position courante dans la lecture de s}
begin if chiffre(s[i]) then resistance := ord (s[i]) - 48
else if s[i] = 's' then begin i := i + 1; a:= resistance (s, i);
b:= resistance (s, i);
resistance := a + b end
else if s[i] = 'p' then begin i := i + 1;
a := resistance (s, i);
b := resistance (s, i);
resistance := a*b / (a+b) end
end;
begin a := 1; {pour la première solution :} {writeln ('résistance ', circuit : 8 : 3);}
{ pour la seconde } writeln (resistance ('ss5ps23s4p236', a) : 8 : 3) end. {donnera 15}

6-18° Alphabet Morse faire un programme de traduction de message en morse écrit avec des
points et des tirets, et inversement.

program tradmorse ;
type mot = string [9];
var PH : string; M : array ['A'..'Z'] of mot; {On ne considère que les majuscules }
procedure init ;
begin M['A'] := '._'; M['B'] := '_...'; M['C'] := '_._.'; M['D'] := '_..'; M['E'] := '.'; M['F'] := '.._.';
M['G'] := '_ _.'; M['H'] := '....'; M['I'] := '..'; M['J'] := '._ _ _'; M['K'] := '_._';
M['L'] := '._..'; M['M'] := '_ _'; M['N'] := '_.'; M['O'] := '_ _ _'; M['P'] := '._ _.';
M['Q'] := '_ _._'; M['R'] := '._.'; M['S'] := '...'; M['T'] := '_'; M['U'] := '.._';
M['V'] := '..._'; M['W'] := '._ _'; M['X'] := '_.._'; M['Y'] := '_._ _'; M['Z'] := '_ _..';
end;
procedure son (C : mot); {il faut un pascal ayant une procédure capable de générer un son }
var i : integer ;
begin for i := 1 to length (C) do
if C[i] = '-' then sound (130, 5) else sound (440, 2) end;
procedure traducmorse (C : string); {écrit et sonorise la suite C de caractères}
var i : integer; X : char;
begin for i := 1 to length (C) do
begin X := C[i] ;
if X in ['A'..'Z'] then begin write (M[X], ' '); son (M[X]) end
else write (' / ')
end end ;
procedure alphabet ;
var Z : char ;
begin for Z := 'A' to 'Z' do begin write (Z : 5, ' : ', M[Z] : 5); son (Z) end; writeln end;

begin init; alphabet; write ( 'Ecrivez une phrase en majuscules :'); readln (PH); traducmorse (PH) end.

6-19° Faire fonctionner à la main le programme du crible d'Eratosthène, jusqu'à une valeur
raisonnable de N.

6-20° Ecrire l'algorithme d'Eratosthène en utilisant deux ensembles PREM, CRIBLE, plutôt
qu'un tableau.

6-21° Ecrire un programme réalisant un ensemble formé par les lettres données parmi tous les
caractères entrés au clavier sur une ligne (on peut utiliser le prédicat "eoln" indiquant la fin
d'une ligne).

program mots;
type lettre = 'A'..'Z'; mot = set of lettre; {deux définitions de nouveaux types}
var ch : lettre; m : mot;
begin m := []; {initialisation d'un ensemble vide} while not eoln do begin read (ch); m := m + [ch] end end.
94
6-22° Programmer une fonction récursive lisant une suite de caractères sans parenthèses
suposée représenter une molécule comme OHH ou CHOHHCHOCHHHHH et devant donner
le résultat (OH)H pour la première et (CH(OH)H)(CH(O(CHHH))H) pour la seconde. Les
radicaux sont de la forme H ou bien (XR1R2...Rn) si les Ri sont eux-même des radicaux et X
un corps de valence n. Les radicaux ont une connexion libre, une molécule est formée par
deux radicaux.

La solution consiste à avancer (pos) dans la lecture de la chaîne en sachant que l'on va devoir
écrire deux radicaux.

function valence (x : char) : integer ;


begin case x of 'C' : 4; 'O' : 2 ; 'H' : 1 end end; { On peut mettre davantage de cas }

function radical (n, l : integer; ch : string; var pos : integer) : string;


{n est le nombre de connexions libres, l la longueur de la chaîne ch}
var x : char;
begin if (n = 0) or (pos > l) then radical := ''
else begin pos := pos + 1; x := ch [pos];
if x = 'H' then radical := x + radical (n-1, l, ch, pos)
else radical := '(' + x + radical (valence (x), l, ch, pos) + ')' +
radical (n-1, l, ch, pos)
end;

On appelera radical (2, ch[0], ch, pos) avec pos := 1

6-23° Les différentes couches électronique d'un atome correspondent à des niveaux
d'énergie, les couches sont numérotées 1, 2, 3, 4, .... (n) et contiennent chacune 4 sous
couches (k) nommées s, p, d, f. L'ordre de remplissage des orbitales se fait suivant n + k
croissant, si deux électrons doivent avoir le même n + k, c'est celui du plus petit n qui est
rempli en premier. Ainsi pour le manganèse de numéro atomique Z = 25, on aura la
disposition électronique 1s2-2s2-2p6-3s2-3p6-4s2-3d5. La sous-couche k possède un
maximum de 4k-2 électrons (donc la couche n qui possède n sous-couches en a 2n2).
En se servant d'un tableau tab[1..9, 0..3] d'entiers qui sera construit au fur et à mesure,
programmer la fonction structure (Z : integer) : string; donnant la disposition électronique des
corps purs.

6-24° Code de Vigenere. Pour crypter un message, le code de César consiste à décaler
l'alphabet en comptant l'espace pour 0, A, pour 1, ... si par exemple on décale de 3, A sera
codé D, et Y sera A. L'analyse statistique vient rapidement à bout de ces codes, aussi le code
de Vigenere reprend ce principe mais en codant grâce à une clé, un mot tel que "TIGRE
JAUNE", qui reproduit indéfiniement permet de donner les différents décalages le long du
message. Ainsi pour coder "RENDEZ VOUS DEUX HEURES TROCADERO", on placera
la clé donnant le nombre de lettres à ajouter :
R E N DEZ V O U S D EUX HEURES TROCADERO
T I G RE J A U N E T IGRE JAUNE TIGRE JAUNE TIGRE JAUNE
20 9 7 18 5 0 10 1 21 14 5 0 20
K NU VJ Z J W I H X X
Programmer les fonctions d'encodage et de décodage.

function encode (texte, cle : string) : string;


var st : string; i, j : integer;
begin j := 0; st := '';
for i := 1 to length (texte) do begin if j < length (cle) then j := j + 1 else j := 1;
st := st + chr (((asc(texte[i]) + asc(cle[j]) - 128) mod 27) + 64) end;
encode := st
end;

Pour "decode", il suffit d'écrire ((asc(texte[i]) - asc (cle[j])) mod 27)


95
6-25° L'horloge, la transcription littérale de l'heure en adoptant le parler usuel (cinq heures
moins vingt ; midi et quart ; etc ... ) fournit un bel exemple de programme se découpant en
petites fonctions conduisant à des emboîtements de conditions qui demandent une certaine
attention.

Rappel : round est la fonction Pascal renvoyant l'entier le plus proche, ainsi, round (4.63) = 5,
alors que trunc (4.23) = 4 .

program horloge ;
type mot = string [40];
var H, M, S, X : integer;
TH : array [1..13] of mot ; TM : array[1..6] of mot;
{ TH est le tableau contenant les données une, deux,trois, etc... alors que TM contient les noms
des minutes cinq, dix, quart, etc... }

function minute (M : integer) : mot;


{produit les chaînes "moins dix" , "et quart", etc ... suivant la valeur M des minutes, en arrondissant de cinq en
cinq minutes. Lorsque la demie est passée, on fait bien sûr la soustraction avec 60 minutes.}
var A : mot ;
begin case M of 3..28 : A := TM[M div 5)];
29..33 : A := TM[6];
34..57 : A := TM[(60 - M) div 5];
otherwise A := ' ' end;
if A=TM[3] then if M < 30 then A := ' et ' + A else A := 'le ' + A;
{ cas de "et quart" ou "moins le quart"}
case M of 34..57 : minute := ' moins ' + A;
otherwise minute := ' ' + A end
end;

function heure (H : integer) : mot;


{produit les chaînes "midi", "dix heures", etc ... en prenant garde au pluriel, et au fait que "midi" et "minuit" ne
sont pas suivis de "heures"}
var A : mot;
begin if (H=0) or (H=24) then A := 'MINUIT'
else IF H > 12 then A := TH[H-12]
else A := TH[H];
if (H mod 12) <> 0 then begin A := A + ' heure';
if (H <> 1) and (H <> 13) then A := A + 's';
end;
heure := A
end;

function ampm (H : integer) : mot; { produit le complément "du matin", "du soir", etc}
begin case H of 1..11 : AMPM := ' du matin';
13..17 : AMPM := ' de l''après-midi';
18..23 : AMPM := ' du soir';
otherwise AMPM := '' end
end;

function litteral (H, M : integer) : mot;


begin
if M > 33 hen litteral := heure (H+1) + minute (M) + ampm (H+1) + ' '
else litteral := heure (H) + minute (M) + ampm (H) + ' '
{ Les blancs sont destinés à couvrir l'inscription antérieure } end;

procedure remisalheure (var H : integer ; var M : integer );


begin clrscr; { Instruction pour effacer l'écran }
write ('Quelle heure ? '); readln (H);
write ('Combien de minutes ? '); readln (M);
clrscr { sur PC, "clearscreen" sur MAC } end;
begin { début du programme } 96
TM[1] := 'CINQ'; TM[2] := 'DIX';TM[3] := 'QUART';
TM[4] :='VINGT'; TM[5] := 'VINGT-CINQ'; TM[6] :='ET DEMIE';
TH[1] := 'UNE'; TH[2] := 'DEUX';TH[3] := 'TROIS';
TH[4] := 'QUATRE'; TH[5] := 'CINQ'; TH[6] := 'SIX';
TH[7] := 'SEPT'; TH[8] := 'HUIT'; TH[9] := 'NEUF';
TH[10] := 'DIX'; TH[11] := 'ONZE'; TH[12] := 'MIDI';
remisalheure (H, M); { Initialisation de H, M et S }
S := 0;
repeat gotoxy (5, 12); {permet de localiser le curseur à la colonne 5 et la ligne 12}
write (H, ' h ', M,' mn ', S, ' s ', litteral (H, M));
if S = 60 then begin S := 0 ; M := M + 1 end;
if M = 60 then begin M := 0 ; H := H + 1 end;
{Ici il suffit d'introduire une pause pour que l'horloge soit réellement à l'heure.}
if H = 24 then H := 0;
S := S+1
until keypressed {Boucle interrompue par la pression d'une touche au clavier.}
end.

6-26° Transcription littérale des nombres Transcrire littéralement en français un entier


entre 1 et 1000 (exemple 31 = trente et un ; 279 = deux cent soixante-dix-neuf ) puis prévoir
les versions belges et suisses.
Reprendre ce problème avec une autre langue, notamment une numération vigésimale (breton
ou basque) ou encore le cambodgien dont la numération quinaire est assez régulière : muy pir
bei buon pram sont les 5 premières unités et 10 = dap, 100 = roy, 1000 = pan, ce qui fait que:
2369 = pir-pan-bei-roy-pram-muy-dap-pram-buon

L'analyse est un travail d'expression française, ce qui n'a rien à voir avec un commentaire de
programme fait après coup. Il convient de commencer par les grandes lignes du problème, on
peut pour cela distinguer :
_ les numérations régulières (chinois, japonais, corréen, finnois, espéranto...) où seuls des
mots de un à dix suffisent avec cent, mille, ... ainsi 67 se dira six-dix sept.
243781 = ducent kvardek tri mil sepcent okdek unu
= (2*100 + 4*10 + 3)*1000 + 7*100 + 8*10 + 1 en esperanto.
_ semi-régulières comme la plupart des langues européennes où il faut un mot pour chaque
nombre de 1 à 20 et pour 30, 40, ...
_ les numérations vigésimales (langues celtiques, mexicaines ...) nécessitant des mots pour les
nombres de 1 à 20 puis pour 100... ainsi 56 serait deux-vingt seize.
_ les numérations quinaires (cambodgien, wolof du Sénégal ...)
3267 = niente djouni niarre temer djiourome bene fonk djiourome niarre
= 3*100 + 2*100 + (5 + 1)*10 + (5 + 2) en wolof.
_ le français, qui est de loin le plus compliqué avec ses survivances vigésimales (base 20),
telles que quatre-vingt et l'hôpital des quinze-vingts (300 lits). Si L ∈ {F, B, S} est une langue
telle qur français, belge ou suisse, et si C, D, U sont les chiffres de centaine, dizaine et unité,
on doit au minimum donner des conditions logiques telles que :
Si L = F et (D = 7 ou D = 9) alors la transcription est celle correspondant à D-1 et U+10
Si U = 1 et (L = F ⇒ (D ≠ 8 et D ≠ 9)) et (L = B ⇒ (D ≠ 8)) alors on intercale une conjonction
"et". Mais est-ce l'écriture logique la plus simple ? (noter que "huitante" se dit en Suisse et
"octante" subsiste au Canada)
Les pluriels (mille invariable, vingt et cent au pluriel s'ils ne sont pas suivis, le cas de million
etc ...) ne sont pas considérés ici.

Parallèlement à l'exposé de l'algorithme, on doit s'interroger sur la représentation des données,


le problème est intéressant car il n' a pas de solution unique. En effet, on peut tout dire au sein
même des procédures, cela n'est guère recommandé en regard de la souplesse du programme.
Cependant il n'est pas possible de séparer véritablement les données des algorithmes. Car
même si, comme cela paraît naturel, on déclare un tableau à double entrée renfermant les
mots nécessaires aux différentes langues, et même si on prévoit autant de procédures que les
types de numérations énumérés ci-dessus, alors on est en butte à une foule de cas particulier.
Ainsi le japonais, le grec ou l'éthiopien prévoient-ils un mot spécial pour 10000, le breton, 97
vigésimal, dit cependant "hanter-kant" (demi-cent) pour 50, l'allemand et l'arabe inversent-ils
les dizaine et unité ("ein und zwanzig" pour 21), certaines langues disent-elles "cent", et
d'autres "un-cent" etc ....
Du point de vue de la programmation il est évident qu'il faut arriver à définir deux ou trois
fonctions telles que :
C → mot de la centaine
D → mot de la dizaine
U → mot pour l'unité
Mais encore faut-il que ce soient de véritables fonctions, et non pas des morceaux de
programme agissant sur des variables globales. Des fonctions sans paramètre n'ont pas
d'intérêt, il faut prévoir que pour un nombre comme 164 238 475 102, elles seront appelées
quatre fois chacune. On peut également prévoir la forme archaïque 1789 = dix-sept cent
quatre-vingt neuf, pour laquelle la même fonction serait appelée pour les paramètres 1 et 7,
puis pour 8 et 9.

program nombres;
type mot = string[40];
var TU : array [0..19] of string[10]; TD : array[2..9] of string[10];
TE : array [1..8] of string[10];

procedure initialise;
begin TU[0]:='';TU[1]:='un '; TU[2] := 'deux '; TU[3]:='trois '; TU[4] := 'quatre ';
TU[5] :='cinq '; TU[6] := 'six '; TU[7] := 'sept '; TU[8]:='huit '; TU[9] := 'neuf ';
TU[10] :='dix '; TU[11]:='onze '; TU[12] := 'douze '; TU[13] := 'treize ';
TU[14] :='quatorze '; TU[15] :=' quinze '; TU[16] := 'seize '; TU[17] := 'dix-sept ';
TU[18] :='dix-huit '; TU[19] := 'dix-neuf ';
TD[2] :='vingt '; TD[3] := 'trente ';TD[4]:='quarante '; TD[5] := 'cinquante ';
TD[6] :='soixante '; TD[7] := 'septante ';TD[8] := 'huitante '; TD[9] := 'nonante ';
TE[1] := 'mille '; TE[2] := 'million'; TE[3] := 'milliard'; TE[4] := 'trillion ' end;

procedure decomp (N : integer; var C, D, U : integer); {calcule les 3 derniers chiffres C D U d'un nombre N}
function unite (X : integer) : integer; {fonction locale à "decomp"}
begin unite := X mod 10 end;
begin U := unite (N); D := unite (N div 10) ; C := unite (N div 100) end;

function diz (D, U : integer; L : char): mot; {traduit les nombres < 99}
begin if D < 2 then diz := TU [10*D+U]
else if ((D = 7) or (D = 9)) and (L='F') then diz: = diz (D-1, U+10, L)
else if (D=8) and (L <>'S') then diz := 'quatre-vingt '+ TU[U]
else if U mod 10 = 1 then diz := TD[D] + 'et ' + TU[U]
else diz:=TD[D] + TU[U]
end;

function cent (N : integer; L : char) mot;


{traduit les nombres < 999, les pluriels de cent et quatre-vingt ont été négligés ici }
var C, D, U:integer;
begin decomp (N, C, D, U);
case C of 0 : cent := diz(D, U, L);
1 : cent := 'cent ' + diz (D, U, L);
otherwise cent := TU[C] + 'cent ' + diz (D, U, L) end
end;

begin {début du programme} initialise;


writeln ('278 = ', cent(278, 'F'), ' en français');
writeln ('394 = ', cent(394, 'B'), ' en belge');
writeln ('485 = ', cent(485, 'S'), ' en suisse');
end.

Voici maintenant une extension pour des nombres plus grands N, considérés comme chaînes
de caractères.
function echelle (N : mot; K : integer; L : char) : mot; {transcrit les trois derniers chiffres de N en fonction de 98
l'échelle K, K est l'exposant de 10 3. Val est une fonction donnant la valeur d'une chaîne composée de chiffres,
elle est laissée en exercice.}
var M : integer;
begin M := val (N);
case M of 0 : echele := '';
1 : if K = 1 then echelle := TE[1] else echelle := TU[1] + TE[K] + ' ' ;
otherwise if K=1 then echelle := cent (M, L) + TE[1]
else echelle := cent (M, L) + TE[K] + 's ' end end;

function trans (N : mot; L : char) : mot; {traduit les nombres < 1012 écrits comme mots, trans est à utiliser en
place de "cent" dans toute utilisation.}
var K : integer;
begin K := trunc ((length (N) - 1) /3);
if K = 0 then trans := cent (val (N), L)
else trans := echelle (copy (N, 1, length (N) - 3*K), K, L)
+ trans (copy (N, length (N) - 3*K+1, 3*K), L)
end;

6-27° Logiciel de solfège j k l Il s'agit de créer un logiciel ayant le menu minimum


suivant :
a) Exercice de lecture sur la portée (une suite de notes sera reconnue par leur noms entrés au
clavier par l'utilisateur)
b) Dictée musicale (même chose mais reconnaissance auditive en supposant une procédure
"sound")
c) Jeu libre au clavier (un octave et demi avec les touches s=si, d=do, éventuellement un
octave de plus avec les majuscules) chaque note devra s'inscrire sur la portée tandis qu'elle
sera jouée (éventuellement avec la durée de pression de la touche). L'utilisateur dira
auparavant s'il veut créér un fichier enrégistrant son morceau.
d) Enregistrement d'un air dans un fichier, les notes étant écrites par leurs noms au clavier.
e) Jeu d'une mélodie déjà entrée dans un fichier de notes (prévoir au moins deux ou trois
petits airs de démonstration, la partition se déroulant à l'écran au cours de l'exécution du
morceau)

On ne considèrera que la clé de sol. Deux ou trois lignes de portée sont suffisantes à l'écran.
Les notes repésentées d'une manière adéquate iront seulement du LA2 au MI4 en notant LA-1
la note la plus basse du piano, LA6 la plus haute et DO3 celle du milieu. La fréquence du LA 3
est 435 Hz. On rappelle qu'un octave est en fait une succession de 12 demi-tons dont les
fréquences sont disposées en suite géométrique dont la raison est la racine douzième de 2
(DOn+1 a une fréquence double de celle de DOn). Les temps et les figures correspondantes
(rondes, blanches, noires, croches ...) peuvent être ignorées et n'être prises en compte par la
suite.

6-28° Crible d'Eratosthène avec ensembles

program crible;
const n = 250; type entier = 0..n; var p, m : entier; C set of entier;
begin C := [0. n]; p := 1;
repeat repeat p := p+1; until p in C; write(p, ' '); while m <= n do begin C := C - [m]; m := m+p end
until C = [] end.
99

CHAPITRE 7

FICHIERS, POINTEURS ET LISTES

Fichiers à acces direct en turbo-pascal


En turbo-pascal sur PC le type "file of ... " permet les fichiers de longueur variable, il faut
pour utiliser des fichiers sur disquette connaître les ordres de création "rewrite", d'ouverture
"reset" de fermeture "close" etc...
On se limite volontairement dans le programme de carnet d'adresses, aux opérations
élémentaires sur un fichier à accès direct (chaque élément se trouve à une adresse précise),
aller chercher une fiche, en créer une nouvelle, et lire la totalité. Les différentes instructions
de manipulation de fichiers sont commentées au fur et à mesure dans le programme.

program adresses ; Fichier à accès direct }


type individu = record nom, prenom : string [20];
adresse : string [80]; telephone: string [12] end;
fichier = file of individu; {On définit deux types d'objets, les individus et les fichiers}
var G : fichier; X : integer ;

procedure creation (var F : fichier); { ne servira qu'une seule fois }


begin rewrite (F); close (F) end;

procedure ecriture (var F : fichier); { Ecrit une ou plusieurs nouvelles fiches dans F }
var A : individu ; C : char;
begin reset (F); C := 'O';
seek (F, filesize (F)); { permet de se positionner sur la fin du fichier en cherchant (seek) dans F
la dernière position (filesize(F) donne le nombre de fiches)."filesize" est inconnu sur Macintosh}
with A do while C = 'O' do begin
write ('NOM: '); readln (nom); write ('PRENOM: '); readln (prenom);
write ('ADRESSE: '); readln (adresse); write ('TEL: '); readln (telephone);
write (F, A);{ Ecriture dans le fichier F et non sur l'écran}
write ('AUTRE ? (O/N) ') ; readln (C) end;
{ With A do... est une instruction dont il n'a pas été question jusqu'à présent, elle permet de ne pas répéter à
chaque fois A nom, A.prenom etc...}
close (F) end; { fin de la procédure d'écriture }

procedure edition A : individu); {réalise une édition à l'écran de l'individu A }


var I : integer;
begin writeln (A.nom,' ', A.prenom,' ', A.adresse, ' TEL:', A.telephone);
for I := 1 to 80 do write ('-') {Place une ligne séparatrice de tirets} end;

procedure lecture var F : fichier); {Lecture complète du fichier F}


var A : individu ;
begin reset (F);
repeat read (F,A); {lecture depuis F sur la disquette et non depuis le clavier, "read" et "write"
avancent toujours d'un cran la position courante dans le fichier, après leur exécution, cela est très commode pour
la procédure présente }
write ( filepos (F),' ') ; edition (A)
until eof ; { répéter jusqu'à la fin du fichier (end of file)}
writeln ('Il y a ', filesize (F), ' fiches.'); close (F) end;
begin assign (G, 'ADRESSES.DAT'); { Permet d'assigner à la variable globale G le nom précis du fichier on 100
peut prévoir de demander le nom (commande propre au turbo-pascal sur PC)}
writeln (' Fichier d adresses. ');
write (' 0: Création 1: Lecture complète 2: Nouvelle fiche '); readln (X);
case X of 0 : creation (G);
1 : lecture (G);
2 : ecriture (G) end; { "recherche" et "destruc" restent à écrire }
end.

Ce programme ne fournit donc qu'une initiation, naturellement les gros fichiers posent bien
d'autres problèmes mais on trouvera en exercice des idées d'améliorations.

Variables dynamiques ou pointeurs

Jusqu'à présent lorsqu'on définissait une variable X entière en l'affectant de la valeur 3, on


avait de façon "statique" durant toute l'éxecution du programme, une place déterminée en
mémoire où étaient rangés le nom X et la valeur 3 (éventuellement modifiée). Pour une
variable dynamique, sa création ou destruction pourra être faite durant l'éxecution suivant les
besoins, on ne pourra y accéder que par une "variable pointeur" contenant son adresse en
mémoire.
Un pointeur est défini par exemple par var Y : ^integer; pour un pointeur sur un entier, et le
contenu de ce vers quoi elle pointe sera accessible par Y^ qui est donc un entier.
La procedure de création d'un pointeur est new (Y), cette nouvelle variable Y est alors
rangée dans une pile spéciale appelée "tas".
Sa destruction est opérée par dispose (Y), en ce cas sa place dans le tas est récupérée.
Le fait de ne pointer sur rien est décidé (quelquesoit le type du pointeur) par Y := nil

Exemple, si var X, Y : ^integer ; a été déclaré,


Les trois séquences d'instructions new (X); X^ := 5; Y := X; write (Y^)
new (X); new (Y); X^ := 5; Y := X; write (Y^)
new (X); X^ := 5; Y := X; dispose (X); write (Y^) donneront toutes 5, par contre :
new (X); X^:= 5; Y := X; Y^:= 3; write (X^); donnera 3.

Utilisation pour la construction d'une liste chaînée


On devra pour un exemple minimal parler de "doublets" c'est à dire d'une partie information
et d'une partie "pointeur" pour aller chercher le doublet suivant. Ainsi pour une liste d'entiers
on définiera deux types (se définissant récursivement) :
type ptr = ^doublet ;
doublet = record numero : integer; suivant : ptr ;

Exemple : tenue à jour d'une course


On souhaite en temps réel, à chaque arrivée de coureur, entrer ce coureur (son numéro de
dossard, son nom et le temps qu'il vient de faire), l'insérer dans la liste de ceux qui sont déjà
arrivés et réafficher le classement provisoire.
On dispose donc en permanence de la liste des coureurs ayant concouru, triée par ordre de
temps croissant.

type ptr = ^doublet; {doit être défini avant}


coureur = record num : integer; nom : string[12]; temps : real end;
doublet = record individu : coureur; suivant : ptr end;
var tete, nouveau : ptr;

procedure arrivee (var nouveau : ptr); {crée un nouvel individu à son arrivée}
begin nouveau^.suivant := nil;
write (' Quel numéro ') ; readln (nouveau^.individu num);
write (' Quel temps ') ; readln (nouveau^.individu.temps);
write (' Quel nom ') ; readln (nouveau^.individu.nom)
end;
101

procedure insertion (var tete : ptr; nouveau : ptr); {suivre la figure ci-dessus}
var courant : ptr; {pointeur local servant à parcourir la liste}
begin courant := tete;
if tete = nil then tete := nouveau {cas du tout premier concourant}
else begin
while (courant^.suivant <> nil) and
(courant^.suivant^.individu.temps < nouveau^.individu.temps)
do courant := courant^.suivant;
if courant = tete then begin nouveau^suivant := tete ; tete := nouveau end
else begin nouveau^.suivant := courant^.suivant ;
courant^.suivant := nouveau end
end {nouveau a été intercalé entre courant et le suivant du courant}
end;

Autre version :
procedure insertion (var liste : ptr; nouv : ptr) ;
var pred : ptr; {on s'intéresse au précédent "pred"}
begin new (pred); pred^.suivant := liste; liste := pred;
while (pred^.suivant <> nil) and (nouv^.temps > pred^.suivant^.temps)
do pred := pred^.suivant ;
nouv^.suivant := pred^.suivant;
pred^.suivant := nouv;
liste := liste^.suivant {on rétablit les choses}
end;

procedure resultat (tete : ptr);


var courant : ptr;
begin courant := tete; writeln ('Voici la liste des coureurs arrivés ');
while courant <> nil do
begin writeln (courant^.individu num, ' ', courant^.individu.nom, ' ',
courant^.individu.temps); courant := courant^.suivant end
end;
Le programme sera (avec les déclarations d'usage) :
new (tete); while fini = 'n' do begin arrivee (nouveau); insertion (tete, nouveau); resultat (tete);
write ('fini ? (o/n) '); readln (fini) end

Exemple de construction d'un arbre binaire

Pour définir une arborescence de caractères où chaque noeud possède un fils droit et un fils
gauche, il suffit de déclarer :
type ptr = ^triplet;
triplet = record noeud : char; filsdroit, filsgauche : ptr end;

Sa construction pourra se faire (dans l'ordre racine-gauche-droite) par :


procedure consrtuc (var A : ptr);
var chg, chd : char;
begin write ('Pour ', A^.noeud, ' Fils gauche '); read (chg); new (A^ filsgauche);
if chg = chr(13) then A^ filsgauche := nil
else A^ filsgauche^.noeud := chg ;
write (' Fils droit '); readln (chd); new (A^.filsdroit) ;
if chd = chr (13) then A^ filsdroit := nil else A^.filsdroit^.noeud := chd;
if chg <> chr(13) then construc (A^ filsgauche);
if chd <> chr(13) then construc (A^ filsdroit) end;

begin new(A); write ('Donnez la racine '); readln (A^.noeud); construc (A) end.
Le parcours racine-gauche-droite d'une arborescence se fera par : 102

procedure parcours (A : ptr);


begin if A <> nil then begin write (A^ noeud); parcours (A^.filsgauche); parcours (A^ filsdroit) end end;

Problème général du parcours avec retour en arrière

Dans une arborescence de racine n (en fait à chaque noeud n se pose le même problème), les
paramètres intervenant sont le chemin ch suivi au dessus de n (donc la liste vide si n est
vraiment la racine), l'ensemble des solutions sol déjà obtenues, l'ensemble "fr" des frères de
droite de n et la liste "efr" des ensembles de frères non encore explorés correspondant à
chacune des étapes du chemin ch. (Ici n n'est pas dans ch, par n&ch, nous entendons le
chemin débutant par n et se poursuivant par ch.)
La procédure générale, en parcourant l'arborescence dans le sens racine-gauche-droite, est:

Parcours (n, fr, ch, efr, sol)


si feuille (n) alors si solution (n1&ch)
alors parcours (premier frère de n, queue de fr, ch, efr, sol ∪ {ch})
si fr vide alors si ch vide alors fin, on renvoie sol
sinon parcours (premier de ch,
premier ensemble de efr,
queue (ch), queue (efr), sol)
sinon parcours (premier frère, queue de fr, ch, efr, sol)
sinon parcours (premier fils de n, autres fils, premier fils de n & ch, fr & efr)

Ecriture sous forme d'une fonction booléenne au cas où on ne cherche que la première
solution:
f(n) = "vrai ssi il y a une solution passant en n"
= [(n est une feuille) et (n est l'aboutissement d'une solution)]
ou [il existe un fils i de n tel que f(i)]
= si feuille (n) alors si solution(n) alors fin (succès) f(n) ← vrai
sinon fin (échec) f(n) ← faux
sinon drap ← faux
k←1
répéter drap ← f(fils de n)
incrémenter (k)
jusqu'à drap ou k > card(fils(n))
f(n) ← drap
On stoppe ainsi à la première solution qui dépend bien sûr de l'ordre de rangement des fils.

Exemple de fichiers séquentiels en pascal standard : fusion de deux fichiers croissants


existants en un seul.

Les instructions sont :


rewrite (F) pour la création
reset (F) pour l'ouverture
get (F) avancée en lecture du pointeur
put (F) avancée en écriture
read (F, X) affectation de l'élément courant de F sous le nom de X et avancée
write (F, X) écriture dans F de X à la position courante et avancée du pointeur

program fusion (F1, F2, F3);


var F1, F2, F3 : file of integer;
begin reset (F1); reset (F2); rewrite (F3);
while not (eof(F1)) and not (eof(F2)) do begin if F1^ < F2^ then read (F1, F3^) else read (F2^, F3^);
put (F3) end;
while not (eof(F1)) do begin read (F1, F3^); put (F3);
while not (eof(F2)) do begin read (F2, F3^); put (F3) end.

Mais chacune des suites d'instructions lecture-écriture peut être modifiée en write (F3, F1^);
get (F1) par exemple.
103
7-1° Procédure de lecture complète d'un fichier F en accès direct

procedure lecture (var F : fichier); { Lecture complète du fichier F }


var A : individu ;
begin reset (F); seek (F, 1);{ on ne considère pas la fiche n° 0 }
repeat write (filepos (F),' '); read (F, A); edition (A)
until eof (F);
writeln ('Il y a ', filesize (F)-1,' fiches.');
close (F) end;

7-2° Ecrire une procédure intercalant X dans le fichier F en accès direct à la place I.

procedure intercaler (X: individu; var F : fichier; I : integer);


{ intercale l'individu X à la place I dans le fichier F ouvert prévoir le cas I=n+1}
var J : integer; A : individu;
begin for J := filesize (F) downto I do
begin seek (F, J-1); read (F, A); write (F,A) end;
{repousse tous les éléments de F d'un cran à partir de I }
seek (F, I) ; write (F, X) end;

7-3° Ecrire une procédure de destruction d'une fiche, en faisant glisser toutes les fiches de
numéros supérieurs d'un cran (programmme dit de ramasse-miettes), utiliser la procédure
Pascal "truncate" (F) afin de supprimer réellement la ou les dernières fiches.

procedure destruc (var F : fichier);


var I, K : integer; A : individu;
begin lecture (F);
write ('Quel numéro '); readln (I) ; reset (F) ; seek (F, I);
for K := I+1 to filesize (F) - 1 do { ramasse miettes }
begin seek (F, K); read (F, A); seek (F, K-1); write (F, A) end;
truncate (F); { le dernier élément est alors supprimé }
lecture (F) end;

7-4° Dans le cas où l'insertion se fait suivant l'ordre alphabétique, écrire une fonction à deux
paramètres, un mot N et un fichier F, renvoyant le rang où devrait s'insérer un individu de
nom N.

On peut utiliser pour cela une fonction dite de "hachage" calculant approximativement cette
position en fonction de la première lettre, puis par comparaison on avance ou on recule dans
le fichier.

function rechercheplace : (N : individu ; var F : fichier ) : integer ;


{Donne le rang alphabétique où doit s'insérer A , F est ouvert}
var I, K : integer ; A : individu ;
begin K := filesize (F);
I := trunc ( K*(ord (N nom [1]) - 64) / 26 ); { ici on peut trouver une fonction plus adaptée aux noms français }
if I = 0 then I := 1;
if K = 0 then rechercheplace := 1
else begin seek (F, I); read (F, A);
if A nom < N nom then if I = K then rechercheplace := K + 1
else begin
repeat read (F, A); I := I+1;
until eof or (N.nom < A nom);
rechercheplace := I end
else if I = 1 then rechercheplace := 1
else begin
repeat I := I - 1; seek (F, I) ; read (F, A)
until (I = 0) or (A.nom < N.nom) ;
rechercheplace := I + 1 end
end end;
104
7-5° Prévoir une procédure d'insertion qui, après chaque nouvelle fiche obtenue, la place
automatiquement dans le fichier à sa place suivant l'ordre alphabétique.

Solution faisant appel à "rechercheplace" :

procedure demande (var F : fichier);


var A : individu; C: char;
begin C := 'O'; reset (F);
with A do while C = 'O' do begin
write ('NOM:'); readln (nom);
write ('PRENOM:'); readln (prenom);
write ('ADRESSE:'); readln (adresse);
write ('TEL:'); readln (telephone);
intercaler (A, F, rechercheplace (A, F) );
write ('Autre fiche ? (O / N)'); readln (C)
end;
close (F) end;

7-6° Ecrire une procédure de recherche, prévoyant un tableau pour les homonymes contenant
les prénoms et les numéros de fiches. Ecrire une procédure de modification des différentes
rubriques pour une fiche. "modif" peut très bien être définie comme une sous-procédure à
l'intérieur de "recherche".

procedure recherche (var F : fichier);


type index = record prenom : string [20];
num : integer end; { n° de fiche }
var X, Y: string [20] ; I, J : integer ; A : individu;
T : array [1..10] of index; { T contient les homonymes }
procedure modif (var F : fichier; I : integer);
var K : integer;
begin write ('Modif du nom 1, prénom 2, adresse 3, tel 4 '); readln (K);
case K of 1: { à compléter } end {du case}
end;

begin {de recherche} write ('Quel nom :'); readln (X); J := 0;


reset (F); for I := 1 to filezise (F) do
begin read (F, A);
if A nom = X then begin edition (A); J := J+1;
T[J].prenom := A.prenom ;
T[J] num := I end end;
case J of 0 : writeln ('Ne figure pas au fichier') ;
1 : modif (F, T[1].num);
else begin write ('Quel prenom :'); read (Y);
for I := 1 to J do if Y=T[I].prenom then modif (F, I)
else recherche (F)
end;
end; {du case}
close (F) end;

7-7° Recherche par dichotomie d'un élément X dans un tableau [1..m] d'individus triés
suivant leur âge.

On suppose qu'"individu" est un type défini par :


type individu = record nom, prenom : string[15]; age, telephone : integer end;
table = array[1..m] of individu:

On défini alors une fonction rang donnant la place de X dans T s'il y est, et la place qu'il
devrait avoir sinon.
function rang (X : individu; T : table; i, j : integer) : integer; {rang de X entre les indices i et j compris} 105
begin
if X.age < T[i].age then rang := i - 1
else if X.age = T[i].age then rang := i
else if X.age = T[j] then rang := j
else if X.age > T[j] then rang := j + 1
else if j = i + 1 then rang := j
else if T[(i + j) div 2].age < X.age then rang := rang(X, T, (i + j) div 2 + 1, j - 1)
else rang := rang(X, T, i + 1, (i + j) div 2)
end;

On appelera rang (X, T, 1, m).

7-8° Procédure sauvant un fichier séquentiel avec des villes et coordonnées à partir d'un
tableau.

const nv = 162;{nb de villes};


type entier = 0..280;
ville = record nom : string [20]; x, y : entier end;
fvilles = file of ville;
var v : array [1. nv] of ville; {on suppose que le tableau v est bien rempli}

procedure sauver (n : entier; nom : string);{sauve le tableau v de 1 à n dans "nom"}


var j : entier; fichier : fvilles;
begin rewrite (fichier, nom); for j := 1 to n do write (fichier, v[j]) ; close( fichier) end;

7-9° Procédure chargeant un tableau depuis un fichier séquentiel de villes (noms et


coordonnées).

procedure charger (nom : string ) {charge le fichier de villes "nom" dans v};
var i : entier; fichier : fvilles;
begin reset (fichier, nom); i := 0;
while not (eof(fichier)) do begin i := i+1; read (fichier, v[i]) end
end;
begin charger ( 'pays'); for i := 1 to nv do write (v[i] nom) end.
106
7-10° Ecrire un Lisp en pascal (on se limite à des listes plates d'entiers).

Il est nécessaire de connaître les bases du Lisp (chapitres suivants) pour comprendre les
opérations relatives à la structure de liste qui suivent.
type liste = ^doublet;
doublet = record tete : integer; queue : liste end;

function car (l : liste) : integer; {donne le premier élément de L}


begin if l = nil then writeln ('erreur dans car') else car := l^.tete end;

function cdr (l : liste) : liste; {donne la liste L privée de son premier élément}
begin if l = nil then cdr := nil else cdr := l^.queue end;

function cons n : integer; l : liste) : liste;


var p : ptr; begin new(p); p^.tete := n; p^.queue := l; cons := p end;

function vide (L : liste) : liste;


begin vide := (L = nil) end;

function longueur (l : liste) : integer;


begin if l = nil then longueur := 0 else longueur := 1 + longueur(cdr(l)) end;

function concat (l1, l2 : liste) : liste; {concaténation de deux listes }


begin if l1 = nil then concat := l2 else concat := cons (car(l1), concat (cdr (l1), l2)) end;

7-11° Tri par fusion pour les listes précédentes. Une liste d'entiers sera triée par fusion si ses
sous-listes d'éléments de rangs pairs et impairs sont triées séparément suivant la même
méthode, puis fusionnées.

Nous adoptons une stratégie consistant à séparer une liste en deux listes constituées par les
éléments d'ordre impairs ou pairs, de façon à ne parcourir la liste qu'une seule fois. Puis, le tri
proprement dit se fait sur ces deux parties qui sont ensuite fusionnées.

function fusion (L1, L2 : liste) : liste;


begin if vide (L1) then fusion := L2
else if vide (L2) then fusion := L1
else if L1^.tete < L2^.tete then fusion := cons (L1^.tete, fusion (L1^.queue, L2))
else fusion := cons (L2^.tete, fusion (L1, L2^.queue)) end;

procedure separ pair : boolean; var LP, LI, LR : liste) ; {construit deux listes LP et LI à partir de L}
begin if not (vide (LR)) then
if pair then separ (false, cons (LR^.tete, LP), LI, LR^.queue)
else separ (true, LP, cons (LR^.tete, LI), LR^.queue)
end;

function trifus (L : liste) : liste ; {produit la liste triée à partir de L}


var LP, LI : liste;
begin if vide (L) then trifus := L
else begin new (LP); new (LI) ; LP := nil; LI := nil;
separ (true, LP, LI, L); trifus := fusion (trifus (LP), trifus (LI)) end end;

7-12° Lire un fichier séquentiel F en construisant au fur et à mesure deux fichiers FP et FI


contenant dans le même ordre les éléments de F respectivement d'ordres pairs et impairs.
107
7-13° Petit système expert : on veut toutes les propositions déduites d'une liste d'axiomes et
d'une liste d'implications. Par exemple si A, C, D et les règles A & C → B, B & F → E, B &
F → I, B & D → F, A & G → K on aura A, B, C, D, E, F, I.

program minisystemexpert;
const m = 10; {10 règles au maximum}
type ptr = ^doublet;
doublet = record prop : char; suivant : ptr end;
{ainsi est définie une liste de propositions}
implic = record conclusion : char; premisses : doublet end;
{une règle est une suite de propositions (les hypothèses), et la conclusion}
var nr : integer; tf : array [A .. Z] of boolean; tr : array[1 .. m] of implic;

procedure entreeprop (var np : integer; var tp : array[A..Z] of boolean);


{entrée de np propositions (mises à vrai), les autres lettres étant mises à faux, tp contiendra donc les valeurs de
vérité de tous les faits à chaque instant}
procedure entreeimplic (var ni : integer; var ti : array[1..m] of implic);
{on pourrait aussi chaîner les implications et même les mélanger aux axiomes}
var tete : ptr; j : integer; lettre : [A..Z];
begin write ('combien de règles ? '); readln(ni);
for j := 1 to ni do begin write('imlication n° ', j, ' entrez les prémisses (des lettres) séparées par "return" ');
new (tete); tete := nil; readln (lettre);
repeat tete^.suivant := tete;
tete^.premisses := lettre;
readln (lettre)
until lettre = '';
write (' conclusion '); readln (lettre);
ti[j].conclusion := lettre; ti[j].premisses := tete
end;
end;

procedure vue (regle : implic);


var suiv : ptr;
begin write ('On applique la règle '); new (suiv); suiv := regle.premisses;
while suiv <> nil do begin write (suiv^.prop, ' '); suiv := suiv.suivant end;
write ('--> ', regle.conclusion)
end;

function declench (regle : implic; t : array [A..Z] of boolean) : boolean;


var suiv : ptr; poss : boolean ;
begin poss := true; suiv := regle.premisses ;
repeat poss := t[suiv^.prop];
suiv := suiv^.suivant
until not (poss) or (suiv = nil);
declench := poss end;

procedure chainage (var t : array[A..Z] of boolean; ni : integer; ti : array[1..m] of implic);


var drap : boolean ; j : integer ;
begin
repeat drap := false;
for j := 1 to ni do
if not (t[ti[j].conclusion]) and declench (ti[j], t) then
begin t[ti[j].conclusion] := true;
drap := true; vue (ti[j]) end
until not (drap) end;

procedure sortie (t : array[A..Z] of boolean);


{affiche les indices des éléments de t qui sont vrais, donc tous les faits acquis}
var lettre : char;
begin write ('Faits acquis : '); for lettre := A to Z do if t[lettre] then write (lettre, ' ') end;

begin {program} entreeprop (tf); entreeimplic (nr, tr); chainage (tf, nr, tr); sortie (tf) end.
108
7-14° Dérivation des fonctions, faire un programme de dérivation de fonctions d'une
variable entrées sous forme préfixées. On utilisera une structure d'arbre pour les expressions,
en se servant de champs différents pour les opérateurs à 0 place (les constantes), à 1 place (les
fonctions) et à 2 places (les lois binaires).

program derivation;
type tp = ^expression;
expression = record op : string [3]; {cas d'une variable ou constante}
case nbop : 0..2 of 1 : (arg : tp); {cas d'une fonction}
2 : (g, d : tp) {cas d'un opérateur binaire} end;
var E, D : tp; ch : char;
procedure affiche (E : tp) ;
begin if E <> nil then case E^.nbop of
0 : write (E^.op); {"op" est ici une constante ou une variable}
1 : begin write (E^.op, '('); affiche (E^.arg); write (')') end;
2 : begin write('('); affiche (E^.g);write (')', E^.op, '('); affiche (E^.d); write (')') end end
end;
function oper2 (a : string) : boolean;
begin oper2 := (a = '+') or (a = '-') or (a ='*') or (a ='/') end;
function oper1 (a : string) : boolean;
begin oper1 := (a = 'sin') or (a = 'cos') or (a ='ln') or (a ='exp') end;
procedure entree (var E : tp); {demande une expression préfixée }
var a : string [3];
begin new (E); write ('Entrez le terme '); readln (a); E^.op := a;
if oper2 (a) then begin E^ nbop := 2; write ('Premier opérande de ', a, ' : ');
entree (E^.g); write ('Second opérande de ', a, ' : '); entree (E^.d) end
else if oper1 (a) then begin E^.nbop := 1; write ('Argument de ', a, ' : '); entree (E^.arg) end
else E^ nbop := 0
end;
function der (E : tp; v : char) : tp;
var R : tp;
begin new (R);
if E^.op = 'sin' then begin R^.op := '*'; R^.nbop := 2;
R^.g := der (E^.arg, v); R^.d^.op := 'cos'; R^.d^ nbop := 1; R^.d^.arg := E^.arg end
else if E^.op = 'exp' then begin R^.op := '*'; R^.nbop := 2;
R^.g := der (E^.arg, v); R^.d^.op := 'exp'; R^.d^ nbop := 1; R^.d^.arg := E^.arg end
else if (E^.op = '+') or (E^.op = '-') then begin R^.op := E^.op;
R^.nbop := 2; R^.g := der (E^.g, v); R^.d := der (E^.d, v) end
else if E^.op = '*' then begin R^.op := '+'; R^.nbop := 2;
R^.g^.op := '*'; R^.g^ nbop := 2; R^.g^.g := der (E^.g, v);R^.g^.d := E^.d ;
R^.d^.op := '*'; R^.d^ nbop := 2; R^.d^.g := E^.g; R^.d^.d := der (E^.d, v) end
else begin R^.nbop := 0; if E^.op = v then R^.op := '1' else R^.op := '0' end;
der := R {dispose (R) est ici inutile, R est un pointeur local} end;
begin entree (E); affiche (E); write (' Dérivée '); new (D); D := der(E, 'x'); affiche (D); dispose (E) end.

Remarque : on continuera sur le modèle,


if E^.op = '/' then begin R^.op := '/'; R^.g^.op := '-'; R^.g^.nbop := 2;
with R^.g^.g^ do begin op := '*'; nbop := 2; g := der (E^.g, v); d := E^.d end;
with R^.g^.d^ do begin op := '*'; nbop := 2; g := E^.g; d := der (E^.d, v) end;
with R^.d^ do begin op := '*'; nbop := 2; g := E^.d; d := E^.d end

Exemple "3exp(x+2) - 5sin(x)" une fois entré est donné en sortie par :
((3) * (exp ((x) + (2)))) - ((5) * (sin (x))) et dérivé en :
(((0) * (exp ((x) + (2)))) + (3) * (((1) + (0)) * (exp ((x) + (2))))) - (((0) * (sin (x))) + ((5) * ((1)
* (cos (x)))))

7-15° Continuer le problème précedent par des fonctions de simplifications (calcul des termes
dont les arguments sont numériques, retirer les parenthèses inutiles, appliquer des règles
telles que 0 = x = x, 1*x = x, etc).
109
7-16° Images réciproques d'une application quasi-affine [Nehlig 92, Reveilles 91] Pour x,
y entiers on définit l'application "quasi-affine" donnant x' = E[(ax + by + e) / w], et y' = E[cx
+ dy + f) / w] où a, b, c, d, e, f sont des entiers relatifs et w un entier positif non nul. L'image
réciproque d'un point est l'intersection de deux "droites épaisses" et l'image réciproque d'ordre
k est un certain domaine connexe limité par une courbe fractale. Ainsi pour 0, l'image
réciproque d'ordre k est {(u, v) / F(k)(u, v) = (0, 0)} pour les applications quasi-affines du type
x' = [(ax + by) / (a2 + b2)], y' = [(ay - bx) / (a2 + b2)] on admet que :
a / b = 1/ (q1 + 1 /(1 + q2)) développée en fraction continue, ce contour s'exprimerait par un
mot XYZXYZ de l'alphabet {1, b, h, d, g} symbolisant le mot vide, les déplacements bas,
haut, droite et gauche.
Le contraire (qui est souligné) est involutif et défini par b = h, d = g, pour un chemin abcdef...
c'est le reflet en miroir des contraires, soit le chemin ...fedcba.
φq1, q2 est l'application définie pour trois mots par :
φq1, q2 (x, y, z) = (y, (zx)q1z(y(zx)q1-1z)q2, x(zx(yx) q1)q2+1 )
Enfin si (X, Y, Z) = φ(k)q1, q2 (1, d, h) alors le contour est XYZXYZ ainsi pour k = 1 ce
contour est décrit par le mot "dhhdhhghggbgbbdbdb".
Programmer la fonction f et le tracé en affichant le mot en question.
(Voir le systèmes de Lindenmayer [Heudin 94]).

Réponse pour k = 3 et q1 = q2 = 1 (dessin de gauche)

Deux résultats pour q1 = q2 = 0 et k = 7 (à droite) puis k = 15 (an centre).

program quasiaffine;
uses memtypes, quickdraw;
type mot = ^doublet;
doublet = record prem : char ; suivant : mot end;

procedure affiche (m : mot); {procédure de vérification}


begin if m <> nil then begin write (m^.prem); affiche (m^.suivant) end end;
function contraire (m, r : mot) : mot; {r sera le résultat, la fonction doit donc être appelée avec r = nil} 110
var x : char; p : mot;
begin if m = nil then contraire := r
else begin new(p); x := m^.prem;
case x of 'b' : p^.prem := 'h'; 'h' : p^.prem := 'b';
'g' : p^.prem := 'd'; 'd' : p^.prem := 'g' end;
p^.suivant := r; contraire := contraire (m^.suivant, p) end
end;

function concat (m, n : mot) : mot;


var p : mot;
begin if m = nil then concat := n
else begin new (p) ; p^.prem := m^.prem;
p^.suivant := concat (m^.suivant, n); concat := p end
end;

function puissance (x : mot ; n : integer) : mot;


var p : mot;
begin if n < 1 then puissance := nil
else puissance:= concat (x, puissance (x, n-1))
end;

procedure phi (q1, q2 : integer; var x, y, z : mot);


var xc, a, b, c : mot;
begin new(xc); new(a); new(b); new(c); xc := contraire (x, nil);
a := concat (z, xc);
b := concat (puissance (a, q1 - 1), z);
c := puissance (concat (contraire (y, nil), xc), q1);
x := y; z := concat (xc, puissance (concat (a, c), q2 + 1));
y := concat (b, puissance (concat (y, b), q2)); y := concat (a, y)
end;

procedure parcours (var u, v : integer; m : mot);


{Le point courant étant (u, v), la procédure déplace ce point suivant les déplacements élémentaires figurant dans
le mot m}
var p : mot; x : char;
begin if m <> nil then begin x := m^.prem;
case x of 'b' :v:=v-1; 'h' :v:=v+1; 'd' :u:=u+1; 'g' :u:=u-1 end;
lineto (u, v);
parcours (u, v, m^.suivant) end
end;

procedure dessin (q1, q2, k : integer);


var i, u, v : integer;x, y, z, xc, yc, zc : mot;
begin u := 150; v := 160; moveto(u, v); new(x); new(y); new(z);
x := nil; y^.prem := 'd'; z^.prem :='h'; y^.suivant := nil; z^.suivant := nil;
if (q1 = 0) and (q2 = 0) then for i := 1 to k do begin xc := contraire (x, nil);
x := y; y := z; z := concat (xc, concat (z, xc)) end
else for i := 1 to k do phi(q1, q2, x, y, z);
xc := contraire (x, nil); yc := contraire (y, nil); zc := contraire(z, nil);
parcours (u, v, x); parcours (u, v, y); parcours (u, v, z);
parcours (u, v, xc); parcours (u, v, yc); parcours (u, v, zc);
affiche(x); write ('-'); affiche (y); write ('-'); affiche (z); write ('-'); affiche (xc) ; write ('-');
affiche (yc); write ('-'); affiche (zc); {Ces affichages peuvent bien sûr être supprimés}
end;

begin dessin (0, 0, 15) end.


111
7-17° Solutions non isométriques du problème des reines de Gauss
En cherchant les solutions sans considérer les 8 isométries du carré, on risque d'avoir
plusieurs fois les mêmes, mais certaines sont globalement invariantes par une rotation. Plutôt
que de vérifier à chaque fois si une solution a déjà été obtenue, il serait plus rapide de
restreindre le champ de liberté de chaque reine.
On considère pour cela sur le damier n*n, l'ensemble Sk des solutions telles que la dame la
plus proche d'un coin soit en k-ième place (le coin étant la première). On a alors la partition S
= S1 + S2 + S3 + ... + SE(n/2) . Ces ensembles sont en effet disjoints et dans S1, il n'y a que
trois dames sur le pourtour. Si n est impair, une dame en E(n/2)+1d'un coin est en fait en
E(n/2) du coin opposé.
La première limitation à faire est donc, lorsque toutes les solutions de Sk auront été trouvées
avec la dame de la première ligne en k, d'interdire les emplacements de distance ≤ k à un
coin.
Le champ de la première dame sera de 1 à E(n/2).
Cas de S1 : il faut remarquer que c'est seulement en ce cas que l'on peut avoir deux solutions
symétriques par rapport à la diagonale, pour les éviter, dès qu'une solution de S1 est trouvée
et si la position de la seconde dame est en k, on interdit la case symétrique : colonne 2 et
ligne k.
Cas des rotations : pour k > 1, on peut avoir lorsqu'une solution est trouvée avec 2 ou 3 dames
sur le pourtour en position k, on ne la prendra en compte que si la 4° l'est aussi.
Pour le reste on introduit une liste chaînée des solutions de Sk en testant si une des trois
rotations d'une solution s'y trouve déjà.

n=4 n=5 n=6


program gauss;
const n = 8;
type table = array[1..n] of integer;
ptr = ^doublet;
doublet = record tab : table; suivant : ptr end;
var t : table; ES : set of 1. n; ch : char; SOL : ptr; v : integer;

function rotdroit t1,t2 : table): boolean; {teste si t2 est la rotation d'un droit de t1}
var i : integer; non : boolean;
begin i := 1; non := false;
repeat non := (t2[t1[i]] <> n-i+1); i := i+1 until non or (i > n);
rotdroit := not (non) end;

function symcentre (t1, t2 : table): boolean;{test si 2 solutions sont symétriques / centre}


var i : integer; non : boolean;
begin i := 1; non := false;
repeat non := (t2[n-i+1] <> n-t1[i]+1); i := i+1 until non or (i > n);
symcentre := not (non) end;
procedure rajouter (t : table ; var liste : ptr ); {rajoute t en tête de la liste}
var i : integer; aux : ptr;
begin new (aux); aux^.suivant := liste;
for i := 1 to n do aux^.tab[i] := t[i];
liste:= aux end;
function present (t : table; liste : ptr): boolean;{teste si une rotation de t est dans file}
begin
if liste = nil then present := false
else if rotdroit (t, liste^.tab) then present := true
else if rotdroit (liste^.tab, t) then present := true
else if symcentre (t, liste^.tab) then present := true
else present := present (t, liste^.suivant) end;
function nonprise (i, j, k, l : integer) : boolean;{k<i ici} 112
begin if j = l then nonprise := false
else if abs (j-l) = k-i then nonprise := false else nonprise := true
end;

function caselibre (l, c : integer) : boolean;{teste si en prise avec les lignes du dessus}
var i : integer; drap : boolean;
begin i := 1; drap := true;
while drap and (i<l) do begin drap := nonprise (i, t[i], l, c ); i := i+1 end;
caselibre := drap
end;

procedure place (l, c, k : integer; var nb : integer); forward;

procedure reine (i, k : integer; var nb : integer);


{tente de continuer à emplir t avec la dame i en t[i] déjà mise, k étant t[1]}
var j : integer;
begin if i=0 then for j := 1 to n div 2 do begin SOL := nil; place(1, j, j, nb) end
else if i=n then if not (present (t, SOL)) then
begin gotoxy (1, 20); nb := nb + 1;
rajouter (t, SOL);
if k = 1 then ES := ES + [t[2]];
write ('solution n° ', nb, ' continuer ? '); ch := readchar;
end
else if i = n-1 then for j := k+1 to n-k+1 do place (n, j, k, nb)
else if (i < k-1) or (i > n-k) then for j:=2 to n-1 do place (i+1, j, k, nb)
else for j := 1 to n do place(i+1,j,k, nb)
end;

procedure place; {pour l, c, k affecte le tableau t et inscrit '#' sur l'écran}


begin
if caselibre(l, c) and ((k <> 1) or (c <> 2) or not (l in ES)) then
begin t[l] := c; gotoxy (2*c, l); write ('#'); reine (l, k, nb); gotoxy (2*c, l); write ('.') end
end;

var l, c : integer; t1, t2 : table; { début du programme }


begin clearscreen; new (SOL); ES := []; v:=0;
for l:=1 to n do for c := 1 to n do begin gotoxy(2*c, l); writeln('.') end;
c := 0; reine (0, 0, c) end.

Les six solutions pour n = 7


113

Les douze solutions pour n = 8

7-18° Jeu du morpion


Sur un damier n*n, les noeuds sont occuppés par -1 ou 1 (les deux joueurs) ou bien 0 (case
vide), -1 représente les croix déjà plaçées par l'adversaire. Le but du jeu est d'aligner k pions à
soi pour un k ≤ n (on peut tester pour n = k = 3). Le programme doit jouer contre l'utilisateur
avec la stratégie suivante : à chaque étape où le programme doit jouer il faut vérifier les
alignements.
S'il y a un (k-1)- alignement à soi, alors il faut le compléter, le jeu est gagné.
S'il y a un (k-1)- alignement de l'adversaire, il faut le contrer.
Sinon on va se placer dans la case correspondant à la croisée des alignements dont le total de
ses pions et de ceux de l'adversaire est maximum. En fait il faudra pondérer les alignements
en favorisant ceux qui sont presque achevés.
Représenter le tableau de tous les alignements possibles (2n + 2 si k = n), chacun d'entre eux
représentant une case de départ, un sens (ligne, colonne, diagonale montante ou
descendante), le nombre de cases à 1 déjà placé, à -1 et enfin un indicateur pour le fait ou cet
alignement ne peut plus conduire à aucun succès pour les deux joueurs.

7-19° Faire une procédure de dessin d'un feuillage pour une profondeur donnée et une
procédure récursive lisant un arbre binaire dont les fils gauche et droit sont formés chacun
d'un facteur 0 < k < 1 et d'un virage 0 < a < 45° signifiant que si d est la longueur du rameau
précédent, les deux suivants auront les longueurs réduites kgd et kdd déviés de ag et ad par
rapport à la direction du rameau dont elles sont issues.
On peut commencer par écrire une procédure très simple où ces paramètres kg, kd, ag et ad
sont toujours les mêmes, auquel cas il n'y a pas d'arbre à demander à l'utilisateur, seule la
longueur d du premier rameau (le tronc) comptera.
114
7-20° Lignes de niveaux
Etant donnée une fonction réelle f de deux variables, on souhaite tracer k lignes de niveaux
régulièrement répartis dans un intervalle [p, q] où l'on sait par un moyen quelconque que f
prend ses valeurs sur un domaine donné [a, b]*[c, d]. Le problème est de relier ces points de
façon à former des lignes.
Chaque ligne correspondant à un fichier séquentiel dans lequel chaque nouveau point admis
devra s'insérer suivant une stratégie à définir.
La procédure, une fois mise au point, sera surtout utilisée pour des fonctions n'ayant pas
d'expression mathématique simple.

Le programme consiste simplement en un appel de lignes avec les arguments souhaités.

Exemple, les fenêtres de Viviani sont les intersections d'une sphère et d'un cylindre circulaire
droit dont une génératrice est diamètre de la sphère. Leurs projections sur un plan tangent
contenant ce diamètre sont :
(1-x 2 ) 2 = µ(1 - x 2 - y 2 ), on pourra les avoir comme lignes de niveaux de la fonction :
(x, y) ∈ [-1, 1] 2 --> µ = (1 - x 2 )2 / (1 - x 2 - y 2 )

uses memtypes, quickdraw;{uses crt, graph; en turbo 6}


const H = 270 ; L = 480; {hauteur, largeur de l'écran}
A = -1.8; B = 2; C = -1; D =1 ; {une fenêtre}
m = 20; {maximum de nombre de lignes de niveaux}

type ptr = ^triplet;


triplet = record x, y : real; suivant : ptr end;
var t : array [1. m] of ptr;
{contient des files de points x, y de même valeur à epsilon près (on en veut k < m)}

procedure place (x, y : real);{place le point de vraies coord x,y}


begin xa:=x; ya:=y;moveto ( L*(x-A) div (B-A), H*(y-D) div (C-D)) end;

procedure trace(x, y :real);{trace le segment vers le point de vraies coord x,y}


begin lineto ( L*(x-A) div (B-A)), H*(y-D) div (C-D)) end;

procedure point (x, y : real); begin place (x, y); trace (x, y) end;

function dis (r, s, t, u : real) : real; {donne la distance euclidienne entre (r, s) et (t, u)}
begin dis := abs (r-t) + abs (s-u) end;
procedure inserer (u : integer; i, j : real); 115
{insère le point i,j dans t[u] pour 0 < u < k, l'insertion doit se faire là où la somme des distances entre deux
points de la file est minimale y compris entre le premier et le dernier }
var un, deux, nouveau : ptr; d0, d1 : real; k, p : integer;
begin new(nouveau); un:= t[u]; k := 1; p:= 0; {il faudra insérer après p}
nouveau^.x:= i; nouveau^.y:= j; nouveau^.suivant := nil;
if un <> nil then begin deux:= un^.suivant;
if deux <> nil then begin d0 := dis (un^.x, un^.y, i, j) + dis (i, j, deux^.x, deux^.y);
un := deux; deux := deux^.suivant; k := k+1 end;
while deux <> nil do begin
d1 := dis (un^.x, un^.y, i, j) + dis (i, j, deux^.x, deux^.y);
if d1 < d0 then begin d0 := d1; p := k end;
un := deux; deux := deux^.suivant; k := k+1 end ;
if (k <> 1) and (dis (un^.x, un^.y, i, j) + dis (i, j, t[u]^.x, t[u]^.y) < d0) then p := k ;
un := t[u]; deux := un^.suivant;
for k := 1 to p-1 do begin un := deux; deux := deux^.suivant end;
un^.suivant := nouveau; nouveau^.suivant := deux end
else t[u] := nouveau end;

procedure lignes (n, k : integer; a, b, c, d, p, q, eps : real); {construit un tableau de k files dont chacune est
formée par des points de [a, b]*[c, d] correspondant à eps près aux k valeurs régulièrement réparties pour la
fonction f dans [p, q]. Le domaine est parcouru suivant un quadrillage n*n}
var i, j, u : integer; x, y, v : real ; courant : ptr;
begin
place(a, c); trace(a, d) ; trace(b, d); trace(b, c);trace(a, c); place(a, 0); trace(b, 0); place(0, d); trace(0, c);
for i := 1 to n do for j := 1 to n do
begin x := a + (b-a)*(i-1)/(n-1); y := c + (d-c)*(j-1)/(n-1); v := f(x, y);
if (p <= v) and (v <= q) then begin
u := round (1 + (v-p)*(k-1)/(q-p)); {est le rang éventuel de cette valeur}
if abs (v-p-(q-p)*(u-1) / (k-1)) < eps then
begin point (x, y); inserer (u, x, y) end
end
end;
for u := 1 to k do if t[u] <> nil then
begin courant := t[u]; place (t[u]^.x, t[u]^.y);
while courant <> nil do begin trace( courant^.x, courant^.y); courant := courant^.suivant end
end;
end; {on appelera "lignes" avec les paramètres désirés}

Solution insérant les points suivant les abscisses croissantes :

procedure inserer (v, i, j : integer);


{insère le point i,j suivant les i croissants dans t[v] pour -m < v < m }
var courant, pred, nouveau : ptr; fini : boolean;
begin new (nouveau); courant := t[v]; nouveau^.x := i; nouveau^.y := j; fini := false;
while (courant <> nil) and not (fini) do
if courant^.x < i then begin pred := courant; courant := courant^.suivant end
else fini := true;
nouveau^.suivant := courant;
if courant = t[v] then t[v] := nouveau
else pred^.suivant := nouveau
end;
© Edition le libre savoir

Vous aimerez peut-être aussi