www.agora.ro
Polinoamele și școala.

Zero-polinoame

Mihai Scorțaru
Pare-se, există o cale lungă de la definiția polinomului, învățat/neînvățat în școala generală și până la programele care implementează eficient operațiile cu polinoame.

Matematică

  Unul dintre cele mai interesante domenii ale matematicii, cu numeroase aplicații în diferite științe (fizică, chimie, informatică etc.) este studiul polinoamelor.

Prima "întâlnire" cu polinoamele (în cl. a-VIIa)

În clasa a VII-a, într-una dintre orele de matematică, atenția elevilor va fi captată de către profesor, în momentul în care acesta va prezenta noul subiect al lecției: Astăzi vom vorbi despre monoame și polinoame! Majoritatea elevilor, contrariați de acest ciudat subiect, își vor întrerupe activitățile obișnuite (jocuri de tip X-O) și vor privi interogativ spre profesor, așteptând ca acesta, printr-o scurtă frază, să le dezvăluie misterul problemei. Noua "imagine" a clasei - elevii interesați de subiect - va bucura profesorul. Prima definiție (Monomul este un produs între o constantă și una sau mai multe variabile.) va adânci și mai mult misterul subiectului orei de matematică; lecția continuă până la o nouă definiție: Polinomul este o sumă de monoame, care va descuraja elevii și aceștia, derutați, își vor relua activitățile obișnuite.

A doua "întâlnire" cu polinoamele (în cl. a-IXa)

În clasa a IX-a, la ora de matematică, un alt profesor va încerca să prezinte clasei un nou subiect: Astăzi vom vorbi despre șiruri. În amintirea elevilor va apărea scenariul din clasa a VII-a care nu i-a impresionat deloc. La o întrebare a profesorului: Cine știe ce este un polinom? cineva, care probabil nu a avut partener la X-O în clasa a VII-a, răspunde: O sumă de monoame!!! Zâmbind, profesorul îl aprobă: Da, dar să dăm acum o definiție riguroasă! Colegii celui care a avut ideea de a răspunde la întrebare îi aruncă priviri amenințătoare, privind speriați spre profesorul care dă o nouă definiție: Un polinom este un șir ai cărui termeni sunt nuli începând de la o anumită poziție. Prima oră despre polinoame se va încheia fără ca elevii să fie pe deplin lămuriți în ceea ce privește noile noțiuni.

Elevii claselor de informatică vor întâlni acest subiect și în orele de specialitate, când vor încerca să simuleze operațiile cu polinoame. Profesorul va face o scurtă recapitulare a noțiunilor, prezentându-le mai întâi din punct de vedere matematic, iar apoi informatic: un polinom va fi reprezentat ca un șir în care elementul cu indicele 0 va fi coeficientul lui X0, elementul cu indicele 1 va fi coeficientul lui X1 etc.

O "întâlnire" specială cu polinoamele (în Gazeta de Informatică)

Aici vom întâlni o nouă abordare a acestui subiect: Reprezentarea clasică a polinoamelor nu este convenabilă în cazul în care acestea au mulți coeficienți nuli. Dacă partenerul de X-O lipsește, vă recomandăm să citiți cele ce urmează!!!

Definiție și reprezentare

  Vom numi zero-polinom un polinom pentru care numărul coeficienților nuli este mult mai mare decât numărul celor nenuli.

Vom reprezenta un zero-polinom ca o listă ordonată în care fiecare element va conține gradul unui monom nenul și valoarea coeficientului. Această listă va fi ordonată descrescător după gradul monoamelor. Pentru a ușura implementarea diferitelor operații cu polinoame, vom folosi o listă ordonată cu santinelă la capăt. Dacă doriți să economisiți spațiul de memorie pe care îl va ocupa această santinelă, vă propunem să încercați implementarea operațiilor care vor urma, folosind o listă fără santinele.

Interfața

  În continuare vă vom prezenta un unit care conține proceduri pentru implementarea operațiilor uzuale cu polinoame: citirea și scrierea unui polinom, atribuirea valorii unui polinom altui polinom, adunarea, scăderea, înmulțirea și împărțirea a două polinoame. Interfața acestui unit arată astfel:

 
Secțiunea interface a unit-ului interface
 type Reference=^Element; 
 Element=
record
 value:Real; { valoarea } 
 degree:Longint; { gradul } 
 next:Reference 
 { legatura spre următorul element } 
 end; 
 Polynominal=
record
 first,last:Reference 
 end; 
 { tipul Polynominal este un articol care } 
 { contine cate o referinta la primul, } 
 { respectiv ultimul element al polinomului } 
 
 procedure ReadPolynominal(var P:Polynominal);    
 { Citirea } 
 procedure WritePolynominal(P:Polynominal; 
 poz:Byte); 
 { Afisarea } 
 procedure AssignPolynominal(P:Polynominal; 
  var Q:Polynominal); 
 { Atribuirea valorii unui polinom } 
 { altui polinom } 
 procedure AddPolynominals(P,Q:Polynominal; 
 var R:Polynominal); 
  { Adunarea a doua polinoame } 
 procedure SubtractPolynominals 
 (P,Q:Polynominal; var R:Polynominal); 
 { Scaderea a doua polinoame } 
 procedure MultiplyPolynominals 
 (P,Q:Polynominal; var R:Polynominal); 
 { Inmultirea a doua polinoame } 
 procedure DividePolynominals(P,Q:Polynominal;    
 var R,S:Polynominal); 
 { Impartirea a doua polinoame } 

Implementarea

  În paginile următoare vom prezenta implementarea procedurilor care au apărut în interfața unit-ului. Nu vom explica modalitățile de adăugare a unui element într-o listă ordonată, cei nefamiliarizați putând consulta diferite manuale care tratează acest subiect [1].

Citirea unui zero-polinom

  Pentru a inițializa un zero-polinom vom citi gradul și valoarea pentru fiecare element, în ordinea descrescătoare a gradelor. Valorile trebuie să fie nenule. În cazul în care se vor introduce valori greșite, se va cere introducerea unei noi valori. Vom considera acțiunea terminată în cazul în care se va introduce un grad negativ. Elementele vor fi întotdeauna adăugate la capătul listei ordonate, acest lucru fiind posibil datorită modului în care am făcut citirea. Procedura este următoarea:

 Procedura de citire a polinomului  
 procedure ReadPolynominal(var P:Polynominal);    
 var value:Real; 
 degree,lastdegree:Longint; 
 r:Reference; 
 begin 
 with p do 
 begin 
 Writeln; 
 Write('Please enter the value and the '); 
 Write('degree of the terms of the '); 
 Witeln('polynominal:'); 
 Write('Enter the degree of the term:'); 
 Readln(degree); 
 lastdegree:=degree; 
 New(first); { initializarea } 
 first^.next:=nil; { santinelei } 
 last:=first; 
 while degree>=0 do 
 begin 
 repeat 
 Write('Enter the value of the term:'); 
 Readln(value); 
 if value = 0 
 then Writeln('Wrong value:'); 
 until value<>0; 
 { Nu se accepta valoarea 0 } 
 { Adaugarea unui element în lista } 
 New(r); 
 r^.value:=value; 
 r^.degree:=degree; 
 r^.next:=nil; 
 last^.next:=r; last:=r; 
 repeat 
 Write('Enter the degree of the '); 
 Write(term:'); 
 Readln(degree); 
 if degree>=lastdegree 
 then Writeln('Wrong degree!!!'; 
 until degree<lastdegree; 
 { Gradul trebuie sa fie mai mic decat } 
 { gradul elementului anterior } 
 lastdegree:=degree 
 end 
 end 
 end; 

Propunem ca exercițiu implementarea acestei proceduri în cazul în care elementele polinomului nu sunt introduse în ordinea descrescătoare a gradelor. (Indicație: se va construi o listă ordonată descrescător după grade.)

Afișarea unui zero-polinom

  Vom afișa un polinom într-un format destul de obișnuit; de exemplu polinomul –5X4+X2–X+2 va apărea pe ecran astfel: -5X^4+X^2–X+2.

Pentru a realiza acest lucru este necesar să tratăm separat cazurile în care gradul este 1 sau 0, cazul în care valoarea absolută a coeficientului este 0 și cazul primului element pentru care nu va trebui să afișăm semnul ‘+', iar semnul ‘-‘ nu va trebui separat de valoarea coeficientului printr-un spațiu. În cazul polinomului nul vom afișa doar un 0. (Un polinom, reprezentat în modul ales de noi, este nul dacă valoarea elementului imediat următor santinelei va fi nil.)

Luând în considerare aceste observații, ajungem la următoarea procedură de afișare a unui polinom "creat" cu procedura ReadPolynominal:

 Procedura de afișare a polinomului  
 procedure WritePolynominal(P:Polynominal; 
 poz:Byte); 
 { poz indica numarul de zecimale care } 
 { vor fi afisate pentru fiecare coeficient } 
 var r:Reference; 
 begin 
 with p do 
 begin 
 if first^.next<>nil 
 then 
 begin 
 r:=first^.next; 
 while r<>nil do 
 begin 
 if r<>first^.next 
 then Write(' '); 
 { vom pune spatiu numai daca } 
 { nu suntem la primul termen (**) }    
 if r^.value<0 
 then Write('-') 
 { punem semnul '-' in cazul in care } 
 { numărul este negativ } 
 
else
 if r<>first^.next 
 then Write('+'); 
 { punem semnul '+' numai daca numarul } 
 { este pozitiv si nu suntem la primul termen } 
 if r<>first^.next 
 then Write(' '); 
 { se pune spatiu numai daca } 
 { nu suntem la primul termen (**) }    
 if (Abs(r^.value)<>1) 
or
 (r^.degree=0) 
 then Write(Abs(r^.value):0:poz); 
 { se scrie numarul numai daca } 
 { valoarea sa absoluta nu este 1 } 
 { sau daca suntem la ultimul element } 
 if r^.degree>0 
 then Write('X'); 
 { se afiseaza un 'X' numai } 
 { daca gradul nu este 0 } 
 if r^.degree>1 
 then Write('^',r^.degree); 
 { se afiseaza puterea, numai daca } 
 { gradul este mai mare decat 1 } 
 r:=r^.next 
 { se trece la elementul urmator } 
 
end
 
end
 
else
Write('0') { Pentru polinomul nul } 
 
end
 end; 

În rândurile marcate cu (**) se arată că spațiul nu trebuie afișat în cazul primului termen, pentru a putea respecta modelul de afișare cerut. Dacă nu am fi pus aceste condiții, polinomul –5X4+X2–X+2 ar fi arătat astfel:

-5X^4+X^2–X+2.

S-a pus o condiție și pentru semnul ‘+' pentru a se evita o afișare de genul:

+5X^4+X^2–X+2

pentru polinomul 5X4+X2–X+2.

Parametrul poz a fost introdus pentru a permite alegerea numărului de cifre care vor fi afișate după punctul zecimal.

Atribuiri pentru zero-polinoame

  Pentru a atribui unei variabile de tip polinom o altă variabilă de tip polinom, vom copia elementele din lista unui polinom, în lista celuilalt polinom. Deoarece această procedură este simplă, vom trece direct la reproducerea codului sursă corespunzător:

 Procedura de atribuire  
 procedure AssignPolynominal(P:Polynominal; 
 var Q:Polynominal); 
 var r,s:Reference; 
 
begin
 with q 
do
 
begin
 New(first); { initializarea } 
 first^.next:=nil; { santinelei } 
 last:=first; 
 r:=p.first^.next; 
 while r<>nil do  
 begin 
 { se copiaza un element din } 
 { lista primului polinom in lista } 
 { celui de-al doilea polinom } 
 New(s); 
 s^.value:=r^.value; 
 s^.degree:=r^.degree; 
 s^.next:=nil; 
 last^.next:=s; 
 last:=s; 
 r:=r^.next 
 { se trece la elementul urmator } 
 end 
 end 
 end; 

Nu se poate face o atribuire directă de tipul P:=Q deoarece câmpurile articolelor P și Q nu conțin decât referințe la elementele polinomului, iar o atribuire de genul acesta nu ar face decât ca cele două variabile să indice că elementele polinomului se află în aceeași zonă de memorie. Pentru a face o dublură "reală" a primului polinom, trebuie să procedăm așa cum s-a arătat mai sus.

Adunarea a două zero-polinoame

  Datorită modului de reprezentare a polinoamelor, algoritmul de obținere a polinomului sumă seamănă foarte mult cu algoritmul de interclasare a două liste ordonate. Să presupunem, pentru început, că toate elementele care apar în cele două polinoame au gradele distincte. În acest caz polinomul sumă va fi obținut într-adevăr prin interclasarea celor două liste în care sunt păstrate elementele celor două polinoame. Dacă vor exista elemente cu același grad, acestea vor fi tratate separat, în polinomul sumă fiind introdus un element care va avea gradul celor două elemente și valoarea egală cu suma valorilor celor două elemente. Trebuie să verificăm dacă această sumă este 0, caz în care nu vom mai adăuga nici un element. Nu insistăm asupra procedeului de interclasare a două liste; cititorii care doresc explicații suplimentare pot consulta diferite cărți în care este prezentat acest subiect([1]). Procedura pe care o propunem pentru adunarea a două polinoame este următoarea:

 Procedura de adunare a două polinoame  
 
 procedure AddPolynominals(P,Q:Polynominal; 
 var R:Polynominal); 
 var s,t,u:Reference; 
 degree:Longint; 
 value:Real; 
 begin 
 with r do 
 begin 
 New(first); { initializare } 
 first^.next:=nil; { santinela } 
 last:=first; 
 s:=p.first^.next; 
 t:=q.first^.next; 
 while (s<>nil) and (t<>nil)    do 
 begin 
 if s^.degree>t^.degree 
 then 
 begin 
 { adaugam un element din prima lista si } 
 { trecem la elementul ei urmator } 
 degree:=s^.degree; 
 value:=s^.value; 
 s:=s^.next 
 end 
 
else
 if s^.degree<t^.degree 
 
then
 begin 
 { adaugam un element din a doua lista } 
 { si trecem la elementul ei următor } 
 degree:=t^.degree; 
 value:=t^.value; 
 t:=t^.next 
 end 
 else 
 begin 
 { adaugam suma elementelor din cele } 
 { doua liste si trecem la elementul } 
 { urmator în ambele liste } 
 degree:=s^.degree; 
 value:=t^.value + s^.value; 
 s:=s^.next; 
 t:=t^.next 
 end; 
 if value<>0 
 then { nu putem adauga valoarea 0 } 
 begin 
 New(u); 
 u^.degree:=degree; 
 u^.value:=value; 
 u^.next:=nil; 
 last^.next:=u; 
 last:=u 
 end 
 end; 
 { adaugam elementele ramase in prima lista } 
 while s<>nil do  
 begin 
 New(u); 
 u^.degree:=s^.degree; 
 u^.value:=s^.value; 
 u^.next:=nil; 
 last^.next:=u; last:=u; 
 s:=s^.next 
 end; 
 { adaugam elementele ramase in a doua lista } 
 while t<>nil do 
 begin 
 New(u); 
 u^.degree:=t^.degree; 
 u^.value:=t^.value; 
 u^.next:=nil; 
 last^.next:=u; last:=u; t:=t^.next 
 end 
 end 
 end; 

Este evident că nu pot rămâne elemente în ambele liste, deci corpul cel puțin uneia din cele două instrucțiuni while din finalul procedurii nu va fi executat nici măcar o dată. Există și posibilitatea să determinăm în care din cele două liste au rămas elemente și să adăugăm numai elementele din acea listă, ceea ce ar duce la eliminarea unuia din cele două cicluri. Propunem ca exercițiu implementarea procedurii de adunare a două polinoame în acest mod.

Scăderea a două zero-polinoame

  Procedura pentru scăderea a două polinoame nu diferă în mod esențial de procedura de adunare, singura diferență fiind faptul că elementele celui de-al doilea polinom trebuie considerate cu semn schimbat. Lăsăm pe seama cititorilor scrierea acestei proceduri.

O altă posibilitate de implementare a scăderii a două polinoame ar fi schimbarea semnelor elementelor din cel de-al doilea polinom și efectuarea unei adunări. Pentru aceasta ar trebui implementată o procedură care să determine opusul unui polinom. Polinomul obținut ar fi trebuit și el memorat, iar aceasta ar fi dus la creșterea spațiului de memorie necesar pentru ca programul să se excute în bune condiții.

Înmulțirea a două zero-polinoame

  Pentru a înmulți două polinoame vom lua pe rând monoamele din primul polinom și le vom înmulți cu monoamele din cel de-al doilea polinom. Vom obține astfel mai multe polinoame (câte unul pentru fiecare monom conținut de primul polinom) pe care va trebui să le adunăm. Pentru a scurta timpul de execuție am efectuat aceste operații simultan, așa cum se vede în procedura următoare:

 Procedura de înmulțire a două polinoame 
 procedure MultiplyPolynominals 
 (P,Q:Polynominal; var R:Polynominal); 
 var s,t,u,v:Reference; 
 value:Real; 
 degree:Longint; 
 
begin
 with r do 
 begin 
 New(first); { initializare } 
 first^.next:=nil; { santinela } 
 last:=first; 
 s:=p.first^.next; 
 while s<>nil do 
 begin 
 t:=q.first^.next; 
 u:=first^.next; 
 while t<>nil do 
 begin 
 { se calculeaza valoarea si gradul } 
 { elementului din produs } 
 value:=s^.value*t^.value; 
 degree:=s^.degree+t^.degree; 
 while (u^.degree>degree) 
and
 (u<>nil) 
do
 u:=u^.next; 
 { se determina pozitia in care } 
 { trebuie introdus elementul } 
 if u=
nil
 
then
 
begin
 { elementul trebuie adăugat pe } 
 { ultima pozitie a listei } 
 New(v); 
 v^.degree:=degree; 
 v^.value:=value; 
 v^.next:=nil; 
 last^.next:=v; 
 last:=v 
 end 
 else 
 if u^.degree=degree 
 then 
 u^.value:=u^.value+value 
 { exista deja un monom cu acest grad (**)} 
 
else
 begin 
 { se insereaza un element in lista } 
 New(v); 
 v^:=u^; 
 u^.degree:=degree; 
 u^.value:=value; 
 u^.next:=v 
 end; 
 t:=t^.next 
 { se trece la elementul urmator } 
 { din al doilea polinom } 
 end; 
 s:=s^.next 
 { se trece la elementul următor } 
 { din primul polinom } 
 end; 
 u:=first^.next; 
 while u<> 
nil do
 if u^.value=0 
 
then
 
begin
 { se sterg elementele care au valoarea 0 } 
 v:=u^.next; 
 u^:=u^.next^; 
 Dispose(v) 
 end 
 else u:=u^.next 
 end 
 end; 

În final trebuie eliminate elementele care au valoarea 0, deoarece în momentul efectuării adunării marcate cu (**) nu am testat dacă rezultatul este 0. Propunem ca exercițiu implementarea procedurii de înmulțire a două polinoame, ținând cont de această observație. Datorită unui rezultat matematic destul de ușor de demonstrat, nu a trebuit să testăm, în momentul ștergerii, dacă suntem la ultimul element, deoarece acesta este întotdeauna nenul.

Împărțirea a două zero-polinoame

  Pentru a împărți două polinoame vom proceda într-o manieră "convențională", implementând algoritmul de împărțire învățat (mai mult sau mai puțin) de elevii claselor a VII-a. Vom inițializa un polinom S cu valoarea deîmpărțitului și, la fiecare pas, vom găsi un monom care va fi adăugat câtului. Acest monom va avea gradul egal cu diferența dintre gradul lui S și gradul împărțitorului și valoarea egală cu câtul dintre valorile primelor elemente din cele două polinoame. Vom înmulți acest monom cu împărțitorul și polinomul rezultat îl vom scădea din S. Algoritmul se va termina în momentul în care gradul lui S va fi mai mic decât gradul împărțitorului. În final în S vom obține câtul împărțirii. Procedura corespunzătoare este:

 Procedura de împărțire a două polinoame  
 Procedure DividePolynominals(P,Q:Polynominal;    
 var R,S:Polynominal); 
 var t,v:Polynominal; 
 u:Reference; 
 value:Real; 
 degree:Longint; 
 begin 
 AssignPolynominal(p,s); 
 { initializarea polinomului S } 
 with r do 
 begin 
 New(first); { initializare } 
 first^.next:=nil; { santinela } 
 last:=first; 
 while (s.first^.next <> nil) and     
 (s.first^.next^.degree>= 
 q.first^.next^.degree) do 
 begin 
 { determinarea monomului care va fi } 
 { adaugat in lista elementelor catului } 
 value:=s.first^.next^.value/ 
 q.first^.next^.value; 
 degree:=s.first^.next^.degree– 
 q.first^.next^.degree; 
 { adaugarea elementului } 
 New(u); 
 u^.degree:=degree; 
 u^.value:=value; 
 u^.next:=nil; last^.next:=u; last:=u; 
 AssignPolynominal(q,t); 
 u:=t.first^.next; 
 { inmultirea dintre monom si impartitor } 
 while u<>
nil do
 
begin
 u^.value:=u^.value*value; 
 u^.degree:=u^.degree+degree; 
 u:=u^.next 
 end; 
 { scaderea din S a polinomului obtinut } 
 { in urma inmultirii precedente } 
 SubtractPolynominals(s,t,v); 
 AssignPolynominal(v,s) {(**)} 
 end 
 end 
 end; 

Înaintea liniei marcate cu (**) ar fi trebuit eliberat spațiul de memorie ocupat de vechiul polinom S pentru ca acest spațiu să poată fi refolosit. Pentru aceasta ar mai trebui implementată o procedură de ștergere DeletePoly, a cărei implementare o lăsăm pe seama cititorilor.

Încheiere

  Dacă partenerul de X-O nu s-a întors între timp, înseamnă că ați citit încă un articol "inutil" despre polinoame; informațiile articolului "pot fi de folos" pentru foarte puțini dintre dumneavoastră. Totuși, după cum probabil nu ați observat, în prima parte nu am spus nimic despre ce face, de obicei, un profesor universitar când trebuie să țină cursul despre polinoame. S-ar putea ca unii dintre dumneavoastră, să-i facă o surpriză profesorului dând dovada deținerii unui bagaj de cunoștințe destul de bogat în acest domeniu într-un moment nu tocmai potrivit. De exemplu, este posibil ca scopurile didactice urmărite de către profesor să nu fie "convergente" cu dorința voastră firească de a semnala că sunteți as în "de-ale programării".

Atenție!!! Surprizele pot fi și neplăcute! Pot avea consecințe imprevizibile asupra notei finale pe care o veți obține la materia respectivă!...

A sosit timpul să vă urăm succes la X-O. În final, am dori să ne cerem scuze față de profesorii care nu au ținut lecția despre polinoame în modul descris de noi. (Ne-am "jucat" cu subiectul și evident, nu ne-am fi putut "juca" dacă nu am fi avut profesori de la care să învățăm multe.) Le adresăm pe această cale cele mai sincere felicitări.

Bibliografie

1. Rancea, D. - Limbajul Turbo Pascal, Editura Libris - 1994

2. Horowitz, E., Sahni, S., Mehta, D. - Fundamentals in Data Structures in C++, Computer Science Press - 1995

Mihai Scorțaru este student la Universitatea Tehnică Cluj; poate fi contactat prin e-mail la adresa: scort@licj.soroscj.ro

[cuprins]