Explorer les Livres électroniques
Catégories
Explorer les Livres audio
Catégories
Explorer les Magazines
Catégories
Explorer les Documents
Catégories
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
L'auteur
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.
Plus précisément, les principales instructions d'un langage de description d'algorithme sont :
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
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.
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.
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.
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.
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
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 }
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 ;
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 ;
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-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.
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-10° Faire un tableau des valeurs de P, Q, R dans Fibonacci pour les cinq premiers passages.
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.
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-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.
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-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 ).
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-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.
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 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.
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.
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-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-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)
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-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
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;
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}
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)}
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.
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 ;
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
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).
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;
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;
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.
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;
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;
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-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;
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 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.
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
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-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;
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.
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é.
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
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
CHAPITRE 4
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 ;
{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.
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 :
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;
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 :
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-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-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.
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....)
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é.
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-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.
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;
begin {place (0, 0) peut être mis ici une seule fois} cantor (4, 0, 1, 1,1 ) 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 :
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
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
Program equintrinseques;
uses memtypes, quickdraw , turtle; {tous les angles sont en degrés}
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.
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;
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;
{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".
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 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.
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 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;
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;
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-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.
ρ ← ρ2 ρ ← ρ0,25
θ ← θ + π (1 − ρ) / 2 θ ← θ + 3,5 (1 − ρ) et ρ ← ρ3
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 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.
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;
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
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 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 ;
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
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
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;
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.
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;
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
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.
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;
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 ?)
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
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'
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;
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 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.
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.
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.
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 :
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-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-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".}
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.
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 :
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 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
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.
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.
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 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;
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.
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;
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;
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.
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
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 }
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.
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;
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;
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
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:
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.
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
7-2° Ecrire une procédure intercalant X dans le fichier F en accès direct à la place I.
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.
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.
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".
7-7° Recherche par dichotomie d'un élément X dans un tableau [1..m] d'individus triés
suivant leur âge.
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;
7-8° Procédure sauvant un fichier séquentiel avec des villes et coordonnées à partir d'un
tableau.
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 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;
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.
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;
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;
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.
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]).
program quasiaffine;
uses memtypes, quickdraw;
type mot = ^doublet;
doublet = record prem : char ; suivant : mot end;
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 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;
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.
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 )
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}