Triangulation et inversion de matrices
Triangulation et inversion de matrices
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 ;
Chapitre 5 - Les tableaux 71
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 reso (A : mat; var X : mat ; N : integer ); { X est la colonne des inconnues construite à partir d'une
matrice A déjà triangulaire de N lignes et N+1 colonnes. }
var I, K : integer ;
begin X [N, 1] := A [N, N+1] / A[N, N];
for I:=N-1 downto 1 do begin X[I, 1] := A[I, N+1] ;
for K := I+1 to N do X[I, 1] := X[I, 1] - A[I, K]*X[K, 1] end end ;
procedure 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;
5-4° Placer dans un tableau les éléments successifs d'un développement en fraction
continue et renvoyer la valeur.(exemple 1 2 2 2 2 ... pour √3, 2 1 2 1 1 4 1 1 6 1 1 8 ... est
e, et 1 1 1 2 1 2 1 2... est (1+√5)/2 le nombre d'or).
b1
Exemple pour la suite a 1, b 1, a 2, b 2, a 3, b 3,.... F = a 1 +
b2
a2 +
b3
a3 +
b4
a4 +
a 5 + .....
program frac;
var A : array [1..20] of real; n, k : integer; f : real;
begin writeln ('Donnez les éléments d'un dév. en fraction continue ');
write ('donnez en le nombre de couples '); readln (n);
for k := 1 to n do read (A[k], B[k]);
f := B[n] / A[n];
for k := n-1 downto 1 do f := A[k] + B[k]/f;
write ('Le résultat est ', f)
end.
5-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.
program triangle;
var p, n : integer; C : array[0..15] of integer; {on ne fait que 16 lignes}
begin C[0] := 1; writeln (C[0]:5);
for n := 1 to 15 do begin C[n] := 1; write (C[n]:5);
for p := n-1 downto 1 do begin C[p] := C[p] + C[p-1];
write (C[p]:5)
end;
writeln (C[0]:5) end
end.
74 512 Problèmes corrigés - Pascal
ρ ← ρ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.
5-11° Le jeu de la vie (Conway). Sur un quadrillage 20 lignes et 80 colonnes par exemple,
que l'on considère "toroïdal" (la 21° ligne est la première et la 81° colonne est la première),
chaque case est soit vivante, soit morte. Etant donné un état du tableau (un état initial sera un
petit motif donné par le joueur), on a les règles suivantes pour chaque case :
Elle est vivante et elle touche au plus une voisine vivante, alors à l'étape suivante, elle meurt
par isolement.
Elle est vivante et possède plus de trois voisines vivantes, alors elle meurt d'étouffement.
Dans les autres cas où elle est vivante, elle reste vivante.
Une case morte qui possède exactement trois voisines vivantes, provoque une naissance.
On peut former deux tableaux ou bien un tableau à trois dimensions où le troisième indice
appartient à un type défini par (ancien , nouveau). Les deux autres indices peuvent être entiers
0..19 et 0..79 par exemple. Rappelons que les différentes générations ne sont pas mélangées.
Certains motifs disparaissent, d'autres se déplacent en restant identiques, d'autres sont
constants, mais la plupart sont périodiques. Ainsi un motif de 7 enzymes alignés consécutifs
devient périodique en 17 étapes, pour 8, il devient constant au bout de 48 étapes, et pour 9, il
devient de période 2 après 21 étapes.
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}
Chapitre 5 - Les tableaux 77
function nbvoisins (L, C, I : integer) : integer; {donne le nombre de voisins vivants de la case L,C,I}
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 [Link]. [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 ...
Chapitre 5 - Les tableaux 79
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".
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 ;
5-17° Décomposition LR par l'algorithme de Crout Etant donné une matrice carrée A de
dimension n*n, il est possible sous certaines conditions d'obtenir A = L*R où L est une
matrice triangulaire inférieure dont la diagonale est remplie de 1 et R, une matrice triangulaire
supérieure.
a) Ecrire les équations auxquelles donne lieu cette décomposition, en déduire la première
ligne de R et la première colonne de L.
b) Prouver que l'on peut déterminer de manière unique L et R en calculant au fur et à mesure
la ligne j de L et la colonne j de R pour j allant de 2 à n.
c) En déduire une procédure transformant la matrice A de telle sorte qu'elle devienne la
juxtaposition de R avec L sans sa diagonale (aucune autre matrice n'intervient).
d) Déduire de cette décomposition une méthode de résolution de système AX = Y
Solution :
a) A = LR impose pour j ≥ i, ai,j = ri,j + ∑li,krk,j avec 1 ≤ k ≤ i-1 et pour j ≤ i, ai,j = li,jrj,j +
∑li,krk,j avec 1 ≤ k ≤ j-1. En particulier r1,j = a1,j et li,1 = ai,1 / r1,1
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;
82 512 Problèmes corrigés - Pascal
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;
Chapitre 5 - Les tableaux 83
Cette méthode permet ensuite de calculer les valeurs propres de A comme celles de T qui lui
est semblable.
84 512 Problèmes corrigés - Pascal
5-19° Résolution de systèmes linéaires par la méthode de Jacobi : il s'agit, pour résoudre
AX = B, d'une méthode itérative où A est décomposé en A = D - L - U. Dans cette formule,
D, -L, -U étant respectivement les matrices diagonale, triangulaire inférieure et triangulaire
supérieure formées avec les parties respectivement diagonale, inférieure et supérieure de A.
Initialement on part de X0 quelconque et on calcule X n+1 = D-1[(L + U)Xn + B].
Cette suite converge vers la solution si et seulement si le rayon spectral (max des valeurs
propres en valeur absolue) de I - D -1A est inférieur strictement à 1. Une condition suffisante
de convergence est que A soit à diagonale strictement dominante c'est à dire que chaque |aii| >
∑ |aij| pour les j ≠ i.
Développer à la main le calcul des 4 premiers termes dérivant (0, 0) pour le système :
2x + y = 3 et x + 3y = 4 de solution (1, 1).
Concrètement on stoppera le calcul pour une stabilisation des composantes de X à un ε près.
5-24° Matrices de Toeplitz. Ce sont des matrices carrées telle que A[i, j]= A[i-1, j-1] pour 2
≤ i ≤ n et 2 ≤ j ≤ [Link] une représentation de ces matrices et montrer qu'elle peuvent
s'additionner en O(n). Construire un algorithme permettant de multiplier une telle matrice par
un réel en O(n1,58). Montrer que le produit de deux telles matrices peut s'effectuer en
O(n2,81 ). Construire un autre algorithme de produit en O(n 2,58 ) et le programmer.
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;
5-28° Valeurs propres des matrices tridiagonales Si n est un entier plus grand que 2 et Tn
est une matrice symétrique nulle sauf sur ses trois diagonales où aucun bi n'est nul, on peut
séparer les n valeurs propres distinctes, et les calculer par dichotomie.
On prendra une constante n, un type vecteur = array [0..n] of real et deux variables globales
A, B de type vecteur, contenant respectivement la diagonale principale avec A[0]=0, et l'autre
diagonale avec B[0]=B[n]=0. Ces données sont supposées déjà en mémoire.
Soit Pn le polynôme caractéristiques de Tn pour n > 1
a) En posant P0(t) = 1 et P1(t) = a1 - t, établir Pn(t) = (an - t)Pn-1(t) - bn-12Pn-2(t), et prouver
que Pn et Pn-1 n'ont pas de racine commune.
b) Ecrire une fonction "carac" d'une variable réelle u, calculant la valeur Pn(u) en utilisant des
variables locales p0, p1, p2 contenant respectivement, à un certain stade Pk-1(u), Pk(u),
Pk+1(u)
c) Si u est un réel fixé, on dit que deux polynômes consécutifs Pk et Pk-1 présentent une
concordance de signe en u si Pk (u) et Pk-1(u) ont le même signe, (si Pk (u) est nul par
convention son signe sera contraire à celui de Pk-1(u) )
On admettra que le nombre de valeurs propres strictement supérieures à u est égal au nombre
de concordances de signe en u de la suite ( P0(u) P1(u) ,......., Pn(u) )
Programmer la function "conc" (x, y) et la function "nvpsup" donnant ce nombre.
On utilisera une variable locale nv contenant ce nombre construit au fur et à mesure, et on
expliquera la signification des autres variables utilisées.
d) En supposant par ailleurs que toutes les valeurs propres appartiennent à l'intervalle [R, S]
tel que R = min{ai - |bi-1| - |bi| / 1 ≤ i ≤ n} et S = max{ai + |bi-1| + |bi| / 1 ≤ i ≤ n} écrire la
procedure intervalle( var R, S : real); qui construira ces deux valeurs.
e) Ecrire la procedure separation(var U : vecteur); qui fournira n+1 valeurs croissantes dans
[R, S] telles que chacun des n intervalles ainsi délimités contienne une et une seule valeur
propre de Tn.
f) Ecrire la procedure calcul(eps : real; var V : vecteur); qui calcule par dichotomies les n
valeurs propres de Tn à eps près.
g) Soit n = 6 et T la matrice tridiagonale dont tous les bi sont égaux à 1, vérifiant a1 = a6 = 5,
a2 = a5 = 3, et a3 = a4 = 1
Cet algorithme assez compliqué consiste à revenir à mi-chemin (vers la gauche) s'il n'y a pas
assez de valeurs propres à droite, et à repartir vers la droite de la moitié du segment de gauche
au cas où il y a trop de valeurs propres à droite.
Supposons k-1 valeurs propres déjà séparées et nvpsup (Ui = n-i pour 0 ≤ i < k-1, alors si uk1
est en s (trop fort) uk2 revient à la moitié de la distance, on les note u1, u2, u3 .. sur le
schéma:
procedure calcul (eps : real; var V : vecteur);
var U : vecteur; k : integer; a, b, c : real;
begin separation (U);
for k := 0 to n-1 do begin a := U[k]; b := U[k-1];
repeat c := (a+b)/2; if carac(c)*carac(a) < 0 then b :=c else a := c
until b - a < eps;
V[k+1] := c end
end;
g) On trouve R = -1 et S = 7
5-29° Le plus court chemin d'une station de métro à une autre. Ce problème assez long
peut recevoir beaucoup de solutions de programmation différentes, parmi toutes les idées de
représentation, on pourra affecter chaque ligne du tableau de ses stations, mais les
correspondances pourront figurer aussi dans un tableau à trois entrées :
C = array [1..16 , 1..16 , 1 .. 2] of integer où l'élément C[I, J, 1] est le numéro (éventuellement
nul) sur la ligne I de la première station commune avec la ligne J, et C[I, J, 2] de la deuxième.
5-32° Améliorer le parcours du cavalier sur un échiquier, en triant les coups possibles à
partir de chaque position suivant le nombre de possibilités qu'ils ont eux-mêmes, suivant un
ordre croissant. On arrivera ainsi plus vite aux impasses.
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.
Chapitre 5 - Les tableaux 91
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
5-36° Indépendamment des procédures du 34°, étant donné une solution algébrique pour un
entier n donné, trouver une représentation et visualiser une solution du puzzle (elles sont très
peu nombreuses), c'est à dire une disposition géométrique des carrés. Trouver toutes les
solutions géométriques pour une même solution algébrique.
Indication : si b nombre sont déjà acquis dans le tableau L[1..b]. L'idée est de considérer un
tableau t[0..n, 0..n] de booléens indiquant les places déjà occuppées dans le carré n*n. On
construit le tableau P[1..b] formés par les points (x, y) du coin haut gauche d'un carré de côté
L[i] (en ordre décroissant). On peut aussi se servir du problème du professeur Facon (chapitre
précédent). On définiera :
continuer (i)
{vérifiant qu'il est possible de terminer avec le i-ième carré placé en P[i].x, P[i].y}
si i = b alors demander une touche pour continuer et afficher les séparations
sinon x := -1; y := 0;
répéter x := x + 1;
tant que t[x, y] faire y := y + 1;
tant que L[i + 1] ≤ n - y faire emplir (x, y, L[i + 1])
continuer (i + 1)
vider (x, y, L[i + 1])
jusque n - x < L[i + 1]
emplir (x, y, v, drap) {drap booléen pour savoir si emplir ou vider}
pour i := 0 à v - 1 faire pour j := 0 à v - 1 faire t[x + i, y + j] : = drap
fixer la couleur à noir si drap et à blanc sinon
placer (x + v, y); tracer (x + v, y + v); tracer (x, y + v)
Solution pour n = 112 côté d'un carré dont la décomposition est donnée à l'exercice précédent: