You are on page 1of 224

UNIVERSITATEA SPIRU HARET

FACULTATEA DE MATEMATICĂ – INFORMATICĂ

GRIGORE ALBEANU
(coordonator)
LUMINIŢA RÎPEANU, LUMINIŢA RADU,
ALEXANDRU AVERIAN

TEHNICI DE PROGRAMARE
- Lucrări practice de programarea calculatoarelor -

EDITURA FUNDAŢIEI ROMÂNIA DE MÂINE,


BUCUREŞTI, 2003
Descrierea CIP a Bibliotecii Naţionale a României
Tehnici de programare / Grigore Albeanu, Luminiţa Rîpeanu,
Luminiţa Radu, Alexandru Averian. - Bucureşti: Editura Fundaţiei
România de Mâine, 2003
224 p., 20,5 cm
Bibliogr.
ISBN 973-582-665-8

I. Albeanu, Grigore
II. Rîpeanu, Luminiţa
III. Radu, Luminiţa
IV. Averian, Alexandru

004.42

© Editura Fundaţiei România de Mâine, 2003


ISBN 973-582-665-8

Coperta: Marilena BĂLAN

Bun de tipar: 24.01.2003; Coli tipar: 14


Format: 16/61 x 86

Editura şi Tipografia Fundaţiei România de Mâine


Splaiul Independenţei, nr. 313, Bucureşti, S. 6, O. P. 83
Tel.: 410 43 80; Fax. 410 51 62; www.spiruharet.ro
CUPRINS

Prefaţă ……………………………………………………………... 5
1. Operaţii de intrare/ieşire în limbajele Pascal şi C …………… 7
1.1. Fişiere în limbajul Pascal …………………………………... 7
1.2. Fişiere în limbajul C …………...…………………………... 17
2. Structuri de date ……………………………………………….. 31
2.1. Liste simplu înlănţuite ……………………………………... 32
2.2. Liste dublu înlănţuite ………………………………………. 46
2.3. Liste circulare ………………………………….…………... 50
2.4. Stive …………………………………...…………………… 53
2.5. Cozi …………………………………...……………………. 55
2.6. Grafuri …………………………………...…………………. 62
2.7. Arbori …………………………………...………………….. 87
3. Metode pentru rezolvarea problemelor ……………….……... 101
3.1. Metoda Divide et Impera 101
3.2. Metoda programării dinamice 107
3.3. Metoda Greedy 116
3.4. Metoda backtracking 125
3.5. Metoda Branch and Bound 135
4. Programare modulară în Turbo Pascal ……………….…….. 145
5. Introducere în programarea orientată obiect folosind
limbajul C++ …………………………………………………… 173
6.Teste pentru verificarea cunoştinţelor ………………………... 201
6.1. Teoria şi practica programării ………………………….…... 201
6.2. Probleme pentru concursuri ………………………………... 222
Bibliografie …………………………………...…………………… 224

3
Capitolele lucrării au fost elaborate după cum urmează:

Prof. univ. dr. Grigore ALBEANU


Prefaţa, Cap. IV, Cap. V 2, Cap VI 2, Bibliografia
Prof. gr. 2 Luminiţa RÎPEANU
Cap. II, Cap. III, 2, Cap. VI 1.
Prof. Luminiţa RADU
Cap. I
Prep. Alexandru AVERIAN
Cap III 1, 3, 4, 5, Cap. V 1.

Lectura textului integral al lucrării, structurarea materialului, verificarea


programelor (Pascal, C, C++) şi procesarea finală a materialului au fost
efectuate de Prof. univ. dr. Grigore Albeanu.

4
Prefaţă

Realizarea unui software complex presupune atât organizarea


intrărilor şi ieşirilor, cât şi o foarte bună gestiune a datelor şi părţilor
componente.
Această lucrare continuă demersul iniţiat în cadrul volumului
Algoritmică şi programare în limbajul Pascal, Editura Fundaţiei
România de Mâine, 2001, oferind cadrul teoretic şi practic în învă-
ţarea tehnicilor de programare fundamentale. Sunt tratate modalităţi
de lucru cu fişiere, structuri de date complexe, metode de elaborarea
algoritmilor, organizarea subprogramelor în unităţi de translatare
precum şi conceptele de bază ale metodei de proiectare bazată pe
lucrul cu obiecte.
Conceptele, tehnicile şi strategiile sunt ilustrate prin exemple,
atât în limbajul Pascal, cât şi în limbajul C. Ultima parte ilustrează
metodologia OOP (engl. Object Orieted Programming) cu ajutorul
limbajului C++.
Materialul este util atât elevilor şi studenţilor ce urmează specia-
lizările informatică şi matematică-informatică, absolvenţilor în vederea
examenelor de titularizare, definitivat, cât şi pregătirii complementare
în cadrul învăţământului postuniversitar de informatică.
Prin colecţia de exemple, probleme propuse (la nivelul fiecărui
capitol) şi teste de evaluare generală (capitolul 5) lucrarea este utilă
pregătirii individuale sau în cadrul cercurilor de elevi sau seminariilor
studenţeşti, pentru participarea la concursurile de programare şi la
diferite examene: bacalaureat, licenţă, titularizare etc.
Studenţii de la cursurile cu frecvenţă redusă şi învăţământ la
distanţă vor găsi exemple utile aprofundării noţiunilor transmise în
cadrul cursurilor: Algoritmică şi programare, Tehnici de programare,
Informatică şi Bazele informaticii.

Grigore ALBEANU

5
6
1. Operaţii de intrare/ieşire în limbajele Pascal şi C
1.1. Fişiere în limbajul Pascal

Definiţia 1.1.1. Se numeşte fişier, o structură de date ale cărei compo-


nente, numite înregistrări, de dimensiune fixă sau variabilă, sunt stoca-
te (depuse), de obicei, pe un suport magnetic (bandă sau disc) de pe
care pot fi citite/scrise direct din program.
Observaţia 1.1.1. Dacă înregistrările din fişier au o dimensiune
variabilă, se impune existenţa unor marcaje speciale numite separatori
de înregistrare.
Observaţia 1.1.2. Organizarea datelor în fişiere este necesară, dacă:
• volumul datelor prelucrate este foarte mare şi depăşeşte capa-
citatea memoriei interne disponibile.
• datele trebuie stocate în vederea unei prelucrări ulterioare, în
cadrul aceluiaşi program sau al altora.
Observaţia 1.1.3. În Turbo Pascal se poate lucra cu trei tipuri de
fişiere:
1) fişiere cu tip – componentele au acelaşi tip şi sunt stocate pe disc.
2) fişiere fără tip – componentele sunt blocuri de informaţii de
lungime fixă şi sunt stocate pe disc.
3) fişiere text – componentele sunt caractere structurate pe linii de
lungimi variabile putând fi stocate pe suport magnetic sau, fac
obiectul operaţiilor de intrare-ieşire cu consola (tastatura, ecran).
Observaţia 1.1.4. Tipul fişier se aseamănă cu tipul tablou prin faptul
că toate componentele au acelaşi tip, dar se deosebeşte de acesta prin
modul de acces la componente şi prin accea că numărul componen-
telor unui tip fişier nu este limitat.
Accesul la componentele unui fişier poate fi:
1) secvenţial - pentru a accesa o anumită componentă trebuie
parcurse toate componentele care o preced.
2) direct – orice componentă se poate accesa imediat dacă se
precizează numărul ei de ordine in fişier, fără să fie necesară
parcurgerea elementelor precedente.
Accesul direct este permis numai pentru fişierele cu sau fără tip
stocate pe disc. Discul este singurul suport adresabil, care permite
calculul adresei unei componente, dacă i se cunoaşte lungimea şi
numărul de ordine în fişier.
7
Fiecărui fişier cu sau fără tip stocat pe disc i se asociază de către
compilator o variabilă numită indicator de fişier care cuprinde, în tot
timpul prelucrării fişierului, numărul de ordine al componentei
curente.
Observaţia 1.1.5. Într-un program Turbo Pascal un fişier se identifică
printr-o variabilă de tip fişier. Această variabilă de tip fişier nu are
voie să apară în atribuiri sau expresii.
Prelucrarea fiecărui fişier trebuie precedată de apelul unei proceduri
standard ASSIGN, prin care variabilei de tip fişier i se asociază
numele unui anumit fişier fizic. Toate operaţiile în care se va specifica
variabila respectivă se vor face asupra fişierului fizic asociat prin
procedura assign.

Procedure assign
Forma generală Procedure assign (var fişier;nume:string);
Efect I se asociază variabilei de tip fişier din program,
numele fişierului fizic de pe suportul extern .
Apelul assign (fişier, nume); unde fişier reprezintă
identificatorul variabilei de tip fişier din
program, iar nume reprezintă o expresie de tip
string, a cărei valoare este numele fişierului fizic
de pe suport extern.
Observaţia 1.1.6. Asocierea rămâne valabilă până la un alt apel al
procedurii assign, asupra aceluiaşi fişier extern. Apelul se termină cu
eroare dacă fişierul fizic este deschis în momentul apelului.
Prelucrările admise asupra unui fişier sunt:
Crearea fişierului Scrierea componentelor în fişier.
Exploatarea fişierului Citirea şi prelucrarea componentelor
fişierului
Actualizarea fişierului Adăugarea, modificarea sau “ştergerea”
unor componente ale fişierului.
Observaţia 1.1.7. Oricare ar fi modul de prelucrare a unui fişier,
accesul la componentele acestuia este permis numai dacă acesta este
deschis. Deci orice prelucrare începe cu deschiderea fişierului şi se
încheie cu închiderea sa.

Deschiderea unui fişier se realizează prin comenzile reset (fişier) sau


rewrite(fişier). Închiderea fişierului prin comanda close(fişier).
8
Executarea procedurii reset (fişier) realizează:
• Iniţializarea indicatorului de fişier cu valoarea 0 (pentru fişiere cu
sau fără tip);
• Iniţializarea variabilei Filemode din unit-ul System astfel încît
să permită, în continuare, executarea de operaţii de citire pentru
fişiere de tip text sau operaţii de citire/scriere – pentru fişiere cu
tip sau fără tip.
Observaţia 1.1.8. Apelul se termină cu eroare, dacă fişierul fizic nu
există. Dacă fişierul este deschis în momentul apelului, acesta se
închide, apoi se redeschide.
Apelul procedurii rewrite are forma: Rewrite(fişier) şi executarea
procedurii constă în:
• Iniţializarea indicatorului de fişier cu valoarea 0 (pentru fişier cu
sau fără tip).
• Iniţializarea variabilei filemode pentru a permite în continuare:
operaţii de scriere pentru fişier de tip text, respectiv operaţii de
scriere/citire pentru fişiere cu sau fără tip.
Observaţia 1.1.9. Apelul se termină cu eroare, dacă nu este loc
suficient pe disc pentru noul fişier. Dacă fişierul fizic asociat există pe
suportul extern conţinutul său este şters. Dacă fişierul s-a deschis prin
executarea acestei proceduri, operaţiile de citire sunt permise numai
după ce s-au făcut scrieri.
Apelul procedurii close are forma: close(fişier) şi are ca efect închide-
rea fişierului prin care operaţiile de citire/scriere a componentelor lui
sunt interzise până la o nouă deschidere.
Definiţia 1.1.2. Poziţia curentă dintr-un fişier (zona în care se
efectuează operaţiile de intrare/ieşire) se numeşte indicator de fişier.

Fişiere text. Creare şi exploatare


Un fişier text este format din caractere structurate pe linii. Fiecare
linie este terminată cu un marcaj sfârşit de linie (eng. eol - end of line),
format din caracterele CR şi LF (tastei ENTER îi corespunde secvenţa
de caractere CR (eng. carriage return) şi LF (eng. line feed) cu
codurile ASCII 13, respectiv 10, secvenţă care reprezintă marcajul de
sfârşit de linie).
Observaţia 1.1.10. Fişierele text se stochează pe disc sau sunt
asociate unui dispozitiv logic (şi se termină cu caracterul ^Z)
9
Observaţia 1.1.11. Tipul fişier text se declară conform diagramei:

Tip fişier text


text

Dimensiunea zonei tampon asociată unui fişier text este, în general


128 bytes. Un fişier text poate fi deschis prin apelul procedurilor:
• rewrite - sunt permise numai operaţii de scriere
• reset - sunt permise numai operaţii de citire
• append - sunt permise numai operaţii de scriere; realizează
deschiderea la sfârşit a unui fişier creat pe disc şi permite
adăugarea unor noi linii la sfârşitul fişierului.
Observaţia 1.1.12. Accesul poate fi numai secvenţial (pas cu pas), nu
poate fi accesat direct.
Procedurile şi funcţiile pentru prelucrarea fişierelor text
Procedura read
Apelul: Read (fişier, v1 [, v2, ………, vn]);
Efect: Citeşte din fişierul fizic asociat variabilei fişier în
variabila (variabilele) v1[, …, vn] unde: fişier este
numele variabilei asociate fişierului fizic (dacă lipseşte
se consideră INPUT), iar v1, v2, …, vn sunt n variabile
(n>1) care pot fi de tip: char, string, integer şi
variantele, real şi variantele în virgulă mobilă.
Procedura readln
Apelul: Readln (fişier, v1[, v2 ,…, vn]);
Efect: Citeşte din fişierul fizic asociat variabilei fişier în
variabila v1[, …, vn] şi trece la linie nouă în fişier. Este
echivalent cu secvenţa: read(fişier, v1[, v2 ,…, vn]);
readln(fişier);
Procedura write
Apelul: Write(fişier, v1[, v2, …, vn]);
Efect: Scrie în fişierul fizic asociat variabilei fişier, variabilele
v1[, v2, …, vn].
Procedura writeln
Apelul: Writeln (fişier, v1 [, v2, …, vn]);
Efect: Scrie în fişierul fizic asociat variabilei fişier, variabilele
v1[, v2, …, vn] şi un marcaj de sfârşit de linie în fişierul
fizic asociat variabilei fişier. Este echivalent cu
secvenţa: write(fisier, v1[, v2, ..., vn]); writeln(fisier);
10
Funcţia EOF (End Of File) este de tip boolean, deci întoarce două
valori: true, dacă poziţia curentă în fişier este după ultimul caracter al
fişierului, sau fişierul este vid, respectiv false, în celelalte cazuri.
Apel: eof [(var fişier)]
Funcţia seekoln este de tip boolean, deci întoarce două valori: true,
dacă avem sfârşit de linie, respectiv false, în caz contrar
Apel: seekeoln[(fişier)];
Efect: atunci când caracterul curent ce urmează a fi citit din zona
tampon asociată fişierului este un caracter egal cu SPAŢIU sau TAB,
se creşte adresa caracterului curent din zona tampon astfel încât să fie
un caracter diferit de spaţiu sau tab. Returnează true, când caracterul
curent care urmează să fie citit din zona tampon reprezintă un marcaj
de sfârşit de linie sau de sfârşit de fişier; false altfel.
Funcţia EOLN returnează un rezultat de tip boolean, true dacă
indicatorul este la sfârşit de linie, iar false, în caz contrar.
Apel: eoln[(fişier)];
Observaţia 1.1.13. Funcţia seekeoln diferă de funcţia eoln prin
faptul că face salt peste caracterul SPAŢIU (blanc) sau TAB, înainte
de a testa sfârşitul de linie.
Funcţia SEEKEOF este de tip boolean
Apel Seekeof [(fişier)];
Efect a) se modifică adresa caracterului curent din zona
tampon astfel încât acesta să fie un caracter diferit de
spaţiu, tab sau sfârşit de linie (face salt peste ele)
b) returnează: true, când poziţia curentă în fişier este
după ultimul caracter al acestuia şi false, în caz contrar.
Observaţia 1.1.14. Funcţia seekeof diferă de eof prin faptul că face un
salt peste caracterele: tab, blanc, sfârşit de linie, înainte de a testa
sfârşitul de fişier.
Procedura APPEND(nume fişier) deschide fişierul în vederea adăugă-
rii la sfarşit în fişier.
Procedura ERASE
Apelul: Erase(fişier);
Efect: Realizează ştergerea fişierului fizic asociat variabilei
fişier. În momentul apelului fişierul trebuie să fie închis.
Procedura RENAME – schimbă în program numele fişierului de pe
suport extern. Apelul rename (fişier,nume) realizează redenumirea
11
fişierului fizic asociat variabilei fişier cu numele dat prin şirul de
caractere nume. În momentul apelului fişierul asociat variabilei fişier
trebuie să existe, să fie închis şi nu trebuie să existe un alt fişier cu
numele nume.
Exemplificare: rename(f,'document.dat') – schimbă numele
fizic asociat lui f cu numele fizic ‘document’.

Aplicaţia 1.1.1. Să se afişeze numărul cuvintelor citite dintr-un fişier


text, introdus de la tastatură (cuvintele sunt separate prin unul sau mai
multe spaţii).
Soluţie:
program nr_cuv_din_fis_INPUT;
var c:char; n:longint;
begin
n:=0; write('linia:');
while not eof do
begin
while seekeoln do
begin
read(c);
while (c<>' ') and not eoln do read(c);
n:=n+1
end;
readln;
write('Introduceţi linia:')
end;
writeln('Fişierul cuprinde:',n,'cuvinte')
end. {n reţine numarul de cuvinte din text}
Exemplu de executare:
Intrare: Ana are maşină Ieşire: Fişierul are 6 cuvinte.
Crina are calculator

Aplicaţia 1.1.2. Să se afişeze numărul numerelor din fiecare linie a


fişierului standard de intrare INPUT.
Soluţie:
program afis_nr_de_numere_din_liniile_fisierului_input;
var n,nr,i:byte;
begin
i:=1; write('linia', i,':');
12
while not eof do
begin
n:=0;
while not seekeoln do
begin read(nr); inc(n) end;
readln;
writeln('din linia', i, 's-au citit' ,n, 'numere');
inc(i);
write('linia',i,':')
end
end.
Exemplu de executare:
Intrare: Ieşire:
linia 1: 10 20 30 din linia 1 s-au citit 3 numere
linia 2: 40 50 din linia 2 s-au citit 2 numere.

Fişiere cu tip
Definiţia 1.1.3. Fişierele cu tip sunt construite din articole cu lungime
fixă, de acelaşi tip (pot fi: array, string, set, record sau tipuri simple de
date-intregi, char, etc); se mai numesc şi fişiere cu prelucrare la nivel
de articol.
Declararea unui fişier cu tip se realizează astfel:

FILE IDENTIFICATOR DE TIP


OF

Exemplul 1. Să declarăm un fişier ale cărui articole sunt numere


întregi.
Soluţie:
type fisint = file of integer;
var a:fisint;
sau var a: file of integer;
Exemplul 2. Să se declare un fişier cu articole de tip record.
Soluţie:
type inreg = record
nume:string[30];
varsta:byte;
end;
fisier = file of inreg;
var f:fisier;{f-reprezintă fişierul cu tip ce poate conţine articole }

13
Observaţia 1.1.15. Accesul la datele fişierului cu tip este secvenţial
sau direct.
Citirea şi scrierea din/in fişier
1) read(fişier, v1, v2, …, vn); - prelucrează n componente succesive
din fişier, începand cu componenta situată la poziţia indicatorului
de articol şi se memorează în variabilele v1, v2, …, vn (valoarea
indicatorului de articol creşte cu 1, după citirea unei componente).
2) write(fisier, v1, v2, …, vn); - se scrie conţinutul variabilelor
v1,v2,…,vn în componentele succesive ale fişierului fişier
(indicatorul creste cu 1 după scrierea fiecărei variabile), evident
tot începand de la poziţia iniţială a indicatorului.
Funcţii şi proceduri utile în prelucrarea fişierelor cu tip
1) EOF(fişier) întoarce true, dacă pointerul este la sfaesit de fişier,
sau false, altfe;
2) Filesize(fişier) întoarce numărul de componente al fişierului
asociat variabilei fişier;
3) Filepos(fişier) întoarce poziţia pointerului(numărul de ordine al
componentei curente);
4) Seek(fisier,n) - pointerul se poziţionează la valoarea n. Exemplu:
seek(fisier,7)-pointerul de fişier punctează a şaptea valoare.
5) Truncate(fişier) – şterge toate componentele fişierului începand cu
componenta indicată de pointerul de fişier pană la ultima
componentă din fişier.
6) Erase(fişier)- şterge fişierul;
7) Rename(fişier,’noul-nume’) – schimbă din program numele
fişierului de pe suport extern.
Aplicaţia 1.1.3. Se consideră fişierul cu înregistrări DATE , care are
următoarea structură:
cod_mat:string[5]; den_m:string[20]; cod_mag:integer;
cant:real; um :string[4]; pu:real;
Să se scrie un program care sortează fişierul descris anterior după
valorile campului cod material.
Soluţie:
type inr = record cod_mat:integer; den_mat:string[20]; cod_mag:integer;
cant:real; um:string[4]; pu:real; end;
fisier = file of inr;
var inr1, inr2:inr; f:fisier;ok:boolean; i, j, t, mag:integer;
m1,m2:integer; c_er:integer;

14
procedure sort1(i,j:integer);
var k:integer;
begin
repeat
ok:=true;
for k:=i to j-1 do
begin
seek(f,k); read(f,inr1,inr2);
if inr1.cod_mat>inr2.cod_mat then
begin seek(f,k); write(f,inr2,inr1); ok:=false end
end;
until ok;
end;
{program principal}
begin
assign(f,'date');reset(f);
repeat
ok:=true;
for i:=0 to filesize(f)-2 do
begin
seek(f,i); read(f,inr1,inr2);
if inr1,cod_mag>inr2.cod_mag then
begin seek(f,i); write(f,inr2,inr1);ok:=false end;
end
until ok;
close(f);
reset(f);read(f,inr1); mag:=inr1.cod_mag; i:=0;
writeln('Magazia:',inr1.cod_mag);
while not eof(f) do
begin
read(f,inr1);
with inr1 do
if mag=cod_mag then begin j:=filepos(f)-1; sort1(i,j) end
else
begin
i:=filepos(f)-1; mag:=inr1.cod_mag;
writeln(‘Magazia:’, cod_mag);
end;
end;
end.

15
Fişiere fără tip
Definiţia 1.1.4. Fişierele fără tip sunt constituite din blocuri de
lungime fixă şi aceeaşi pentru fiecare bloc.
Observaţia 1.1.15. Un bloc cuprinde o succesiune de octeţi.
Citirea/scrierea dintr-un fişier fără tip presupune transferul unuia sau
mai multor blocuri.
Declararea fişierelor fără tip se realizează astfel:

FILE

Deschiderea fişierelor fără tip se realizează astfel:


Rewrite(fişier,[dimensiune_bloc])
Reset(fişier,[dimensiune_bloc]);
unde dimensiune_bloc este de tip întreg şi reprezintă numărul de octeţi
pe care a fost scris blocul(implicit este 65536 octeţi)
Citirea şi scrierea:
Blockread(fişier, variabilă, nr_blocuri_care_se_vor_citi)-citeşte
din fişier în variabila un număr de blocuri specificate.
Blockwrite(fişier, variabilă, nr_blocuri_care_se_vor_scrie)-
scrie blocurile specificate.
Observaţia 1.1.16. Indicatorul de fişier reţine numărul blocului
curent. Accesul poate fi secvenţial sau direct.
Observaţia 1.1.17. Asemănator cu fişierele cu tip se pot folosi
următoarele funcţii/proceduri: seek, eof, filesize, filepos, erase, rename.
Aplicaţia 1.1.4. Să se creeze un fişier fără tip cu n inregistrări avand
câmpurile: nume:string[16]; varsta:integer;
type inregistrare=record nume:string[20]; varsta:integer; end;
var f:file;inr:inregistrare;
n,i:integer;
begin
assign(f,'date');write('n=');readln(n); rewrite(f);
for i:=1 to n do begin
write('Nume=');readln(inr.nume);
write('Varsta=');readln(inr.varsta);
blockwrite(f,inr,1);
end;
close(f);
end.
16
1.2. Fişiere în limbajul C
Fişierul este un grup de octeţi stocaţi pe un suport de memorie externă
care este tratat în mod unitar de către sistemul de operare.
Lucrul cu fişiere în C presupune cunoscut modul de lucru cu
date de tip struct şi cu pointeri. Se utilizează conceptul de flux
(stream).
Un flux se reprezintă, în C, printr-un pointer la o entitate de tip
FILE care conţine informaţii despre poziţia curentă în flux, indicatori
de eroare şi de sfârşit de fişier, zone tampon (eng. buffers) asociate.
Limbajul C permite folosirea a două tipuri de flux: text şi binare.
Un flux text presupune transferul de caractere organizate în linii
de caractere. Fluxul binar reprezintă o succesiune de octeţi care nu
suportă nici o modificare în timpul transferului.
Deschiderea şi închiderea unui fişier
Orice fişier înainte de a fi prelucrat trebuie deschis. După terminarea
prelucrării unui fişier acesta trebuie închis. Forma generală a funcţiei
pentru deschidere:
FILE *fopen(const char *numele_fis, const char *mod);
- "deschide un fişier" şi-i asociază numele respectiv;
- returnează un pointer spre tipul FILE (vezi fişierul STDIO.H) sau
Null, în caz de eroare
unde: numele_fis = pointer către numele fişierului ce urmează a fi
deschis; mod = pointer spre un şir de caractere care defineşte modul de
prelucrare al fişierului, după deschidere. Se defineşte astfel:
“r” = deschiderea unui fişier text pentru citire
“w” = deschiderea unui fişier text pentru scriere
“a” = deschiderea unui fişier text pentru adăugare
“r+” = deschiderea unui fişier text pentru modificare
“r+b” = citire/scriere binară
“rb” = citire binară
“wb” = scriere binară
Observaţia 1.2.1. Cu funcţia fopen, un fişier inexistent se poate
"deschide" în modul r, w sau a, iar în acest caz este şi creat.
Observaţia 1.2.2. Dacă se deschide un fişier existent în modul “w”
atunci se va crea unul nou cu acelaşi nume, vechiul conţinut se pierde.
Apelul: fp = fopen("INPUT.TXT") - la apel se deschide fişierul cu
numele INPUT.TXT şi se stabileşte o legătură logică între pointerul fp
şi fişierul INPUT.TXT.
17
Observaţia 1.2.3. Deschiderea unui fişier în modul “a” pentru
adăugarea de înregistrări după ultima înregistrare existentă în fişier.
Observaţia 1.2.4. Fişierele corespunzătoare dispozitivelor standard nu
se deschid de către utilizator, întrucât ele sunt deschise automat la
lansarea programului.
Observaţia 1.2.5. Fişierele disc şi dispozitivele periferice sunt gestio-
nate de variabila pointer.
Prelucrarea pe caractere a unui fisier
Fişierele standard pot fi scrise şi citite caracter cu caracter, folosind
două funcţii simple: putc şi getc. Forma generală:
int putc(int c, FILE *pf);
returnează valoarea lui c, reprezentand codul ASCII al caracterului
scris în fişier sau -1, dacă este eroare; pf reprezintă pointer spre tipul
FILE a cărui valoare a fost returnată de funcţia fopen la deschiderea
fişierului în care se scrie.
Observaţia 1.2.6. În particular pf poate fi unul din pointerii: stdout -
ieşire standard; stderr - ieşire pe terminal în caz de eroare; stdaux -
ieşire serială; stdprn - ieşire paralelă la imprimantă. Forma generală:
int getc(FILE *pf);
returnează codul ASCII al caracterului citit sau EOF la sfârşit de
fişier; pf - pointer spre tipul FILE a cărui valoare a fost returnată de
funcţia fopen la deschiderea fişierului. În particular pf poate fi
pointerul stdin (citire (intrare) de la tastatură).
Închiderea unui fişier (eliberarea spaţiului de memorie alocat folosind
fopen şi întreruperea legăturii logice) se realizează cu funcţia fclose
care are prototipul
int fclose (FILE *pf);
şi returnează valoarea: 0, la închidere normală sau -1, în caz de eroare.
Aplicaţia 1.2.1. Să se elaboreze un program pentru a copia intrarea
standard la ieşirea standard folosind funcţiile putc şi getc.
Soluţie:
#include <stdio.h>
void main(void) {
int c;
c= getc (stdin);
while (c != EOF) {putc(c, stdout); c=getc(stdin);}
}
18
Aplicaţia 1.2.2. Cum se copiază intrarea standard la imprimantă?
Soluţie:
# include <stdio.h>
void main(void){
int c;
c= getc (stdin);
while (c != EOF){putc(c, stdprn); c=getc(stdin);}
}
Operaţii de intrare/ieşire cu şiruri de caractere
Biblioteca standard a limbajului C conţine funcţiile: fgets şi fputs care
permit citire dintr-un, repectiv scrierea într-un fişier ale cărui înregis-
trări sunt şiruri de caractere. Prototipul: char *fgets (char *s, int n,
FILE *pf); returnează valoarea pointerului s sau NULL la întâlnirea
sfârşitului de fişier; s - pointerul spre zona în care se păstrează
caracterele citite (numele unui tablou de tip char de dimensiune cel
puţin n); n - dimensiunea în octeţi a zonei în care se citesc caracterele
din fişier; pf - pointerul spre tipul FILE a cărui valoare s-a definit la
deschiderea fişierului.
Observaţia 1.2.7. Citirea se opreşte la întâlnirea caracterului '\n' sau
după citirea a cel mult n-1 caractere. În acest caz, în zona receptoare
se transferă caracterul '\n' şi apoi caracterul '\0' (NULL)
Funcţia fputs scrie într-un fişier un şir de caractere care se termină prin
'\0' şi are prototipul int fputs(const char *s, FILE *pf);
Returnează codul ASCII al ultimului caracter scris în fişier sau -
1 la eroare; pf - pointer spre tipul FILE care defineşte fişierul în care
se scrie; s-pointerul spre zona care conţine şirul de caractere, care se
scrie.
Observaţia 1.2.8. Pentru a citi de la intrarea standard stdin, se poate
folosi funcţia gets, care nu mai are parametrii pf şi n. Parametrul pf
este implicit stdin.
Observaţia 1.2.9. La fişierele text, când este întalnit caracaterul '\n',
acesta este transformat într-o secvenţă "newline". Fişierele binare nu
produc transformări.
Operaţii de intrare-ieşire cu format
Acest lucru se realizează folosind funcţiile: fscanf- citire cu format
dintr-un fişier; fprintf - scriere cu format în fişier. Operează la fel ca
printf şi scanf cu excepţia faptului că lucrează cu fişiere, nu cu consola.
19
Apelul lui fscanf: fscanf(FILE *pf, char *format, par1, par2, …,parn) -
citeşte date, conform formatului, din stream; returnează valoare EOF,
la întalnirea sfarşitului de fişier; pf - pointer spre tipul FILE, a cărui
valoare a fost definită prin apelul funcţiei fopen; format - conţine texte
şi/sau specificatori de format (“_”). Parametrii par1, par2, …,parn
definesc zonele receptoare ale datelor citite prin intermediul funcţiei
fscanf.
Exemplificare: Apelul fscanf(pf, "%d %c %f", &i, &n, &c);
Apelul fprintf(FILE *pf, char *format, par1, …parn) - scrie date, în
stream, conform formatului.
Poziţionarea într-un fişier
Pană acum am citit/scris datele unui fişier în mod secvenţial de la
început până la sfarşit. Utilizând funcţia fseek se poate accesa orice
articol al unui fişier (acces aleator). Cu ajutorul ei se poate deplasa
capul de citire/scriere al discului în vederea prelucrării întregistrărilor
fişierului într-o ordine oarecare, diferită de cea secvenţială.
Prototipul Int fseek(FILE *pf, long deplasament, int origine);
Returnează 0, la poziţia corectă sau o valoare diferită de 0, în caz
de eroare;
unde: deplasament reprezintă numărul de octeţi peste care se va
deplasa capul de citire/scriere al discului, iar origine ia una din
valorile:
0 Deplasamentul se consideră de la începutul fişierului;
1 Deplasamentul se consideră din poziţia curentă a
capului de citire/scriere;
2 Deplasamentul se consideră de la sfarşitul fişierului.
O altă funcţie utilă în cazul accesului aleator este funcţia ftell, care
indică poziţia capului de citire/scriere din fişier.
Prototip Long ftell(FILE *pf);
Returnează O valoare de tip long care defineşte poziţia curentă a
capului de citire/scriere, şi anume reprezintă deplasa-
mentul în octeţi a poziţiei capului faţă de începutul
fişierului .
Aplicaţia 1.2.3. Folosind funcţia fseek( ) căutaţi şi citiţi orice byte
dintr-un fişier specificat

20
Soluţie:
#include <stdio.h>
#include<stdlib.h>
void main(void) {
FILE *pf;
long loc;
if ((pf= fopen("g1.cpp", "r"))==NULL) {
printf ("Nu pot deschide fisierul! ");
exit (1);
}
printf(“Introduceţi locaţia , în octeti, din fişier, ce se va vizualiza! ”);
scanf(“%d”, &loc);
if (fseek(fp, loc, 0)){
printf("Eroare de cautare! ");
exit(1);
}
printf("Valoarea de la locatia %ld este %c", loc, getc(pf));
fclose(pf);
}
Alte funcţii pentru prelucrarea fişierelor
1 Închiderea unui fişier se realizează cu ajutorul funcţiei fclose
care are prototipul: int fclose (FILE *f); Funcţia returnează
valoarea 0 la închidere normală, respectiv -1 în caz de eroare.
2 Testarea atingerii sfârşitului de fişier se poate realiza cu ajutorul
funcţiei feof, care are prototipul: int feof (FILE*f); Funcţia feof
returnează valoare 0, dacă indicatorul de fişier nu este la sfârşitul
fişierului, respectiv o valoare diferită de 0, dacă indicatorul de
fişier este la sfârşit.
3 int rename(const char *nume_vechi, const char *nume_nou);-
redenumeşte un fişier;
4 int remove(const char *filename); - şterge un fişier de pe
suportul de informaţie pe care este memorat;
5 size_t fread(void *ptr, size_t size, size_t n, FILE *stream);-
citeşte date dintr-un fişier; mai precis, citeşte n elemente, fiecare
cu lungimea size din stream, într-un bloc specificat de ptr;
6 size_t fwrite(const void *ptr, size_t size, size_t n, FILE *
stream) - adaugă fişierului stream n elemente, fiecare avand
dimensiunea size din blocul specificat de ptr.

21
1.3. Probleme rezolvate

R1.3.1. Se consideră un fişier text IN.txt ce conţine numere


întregi dispuse pe mai multe linii şi separate prin spaţii. Scrieţi
un program care creează un fişier OUT.txt ce conţine pe fiecare
linie media aritmetică a numerelor situate pe aceeaşi linie în
fişierul IN.txt. Media aritmetică va fi scrisă cu doua zecimale
Varianta C
Varianta Pascal
program medie; #include <stdio.h>
var x,s,i:integer; f,g:text; #include <string.h>
begin void main (void){FILE *fin, *fout;
assign(f,'IN.TXT'); char sir[256],sn[256];
RESET(f); long int n,i,j1,j2,suma; float media;
assign(g,'out.TXT'); if (!(fin=fopen("IN.txt","r"))){
REWRITE(g); printf("\n Nu pot deschide sursa!");
while not eof(f) do exit(1);}
begin if (!(fout=fopen("OUT.TXT","w"))){
s:=0; printf("\n Nu pot crea fisierul destinatie!");
i:=0; exit(1);}
repeat while (!feof(fin)){
read(f,x); fgets(sir,256,fin);
i:=i+1; n=0;i=0;j1=0;j2=0;suma=0;
s:=s+x; while(sir[i]){
until eoln(f); if (sir[i]=' ') {sn=substr(sir,j1,j2);
writeln(g,s/i:4:2); suma=suma+atoi(sn);
writeln(s/i:4:2); j1=j2=i+1;sn=' ';i++; }
readln(f); else {i++; j2++;}}
end; media=suma/n;
close(f); fprintf(fout, "%10.2f\n",media);
close(g);
}
end.
fclose(fin);fclose(fout);
}
char substr(char sir[256]; int j1,j2){
char ss[]="';int i;
if ((j1>strlen(sir)||(j2>strlen(sir))
return 1;
for(i=j1;i<j2;i++) ss[i-j1]=sir[i];
return ss;
}

22
R1.3.2. Scrieţi un program care verifică dacă două fişiere Input1.txt şi
Input2.txt au conţinut identic.
Soluţie:
Varianta Pascal Varianta C
Var i:integer; #include <stdio.h>
f,g:text; #include <string.h>
s,s1:string; void main (void){
identic:boolean; char sir1[256],sir2[256];
Begin FILE *f1, *f2;
assign(f.’Input1.txt’); int ind;
reset(f); if ((f1=fopen("Input1.txt","r"))
assign(g.’Input2.txt’); ==NULL)
reset(g); {
Identic:=true;
printf("\n Nu pot deschide fis1!");
While
(not eof(f)) exit(1);
and }
(not eof(g)) if((f2=fopen("Input2.txt","r"))
and (identic) ==NULL)
do {
Begin printf("\n Nu pot deschide fis2!");
readln(f,s); exit(1);
readln(g,s1); }
If s<>s1 then identic:=false; ind=0;
End; while
if eof(f) and not eof(g) then ((!feof(f1))&&(!feof(f2))){
Identic:=false; fgets(sir1,f1);
if eof(g) and not eof(f) then fgets(sir2,f2);
Identic:=false; if (strcmp(sir1,sir2)!=0)
if identic then ind=1
write(‘Fişierele sunt identice’) }
else clrscr();
write(‘Fisierele nu sunt identice’); if (ind=0)
close(f); printf("\n Fisierele sunt identice!");
close(g); else
end. printf("\n Fisierele nu sunt
identice!")
fclose(f1);
fclose(f2);
}

23
R1.3.3. Creaţi un program care transformă toate literele mici din
fişierul INPUT.txt în majuscule.
Soluţie:
program pr; while not eof(f) do
uses crt; begin
var f,f1:text;i:integer; readln(f,s);
s,s1:string; s1:='';
begin for i:=1 to length(s) do
clrscr; s1:=s1+upcase(s[i]);
assign(f,'in.txt'); writeln(f1,s1);
reset(f); end;
assign(f1,'in1.txt'); close(f);close(f1);
rewrite(f1); end.

Exerciţiu: Să se traducă programul de mai sus în limbajul C.


R1.3.4. Se consideră o matrice a cu n linii şi n coloane stocată în
fişierul Matrice.txt (pe prima linie este scrisă valoarea n, iar pe
următoarele n linii matricea a, linie cu linie). Se cere să se afişeze în
fişierul SUMA.TXT, suma elementelor de pe diagonala principală.

program matrice; close(g);


uses crt;type mat = readln;
array[1..10,1..10] of integer; end.
var a:mat;
i,j,s,n:integer; f,g:text;
begin
assign(f,'Matrice.txt');
reset(f);
assign(g,'Suma.txt');
rewrite(f);
readln(f,m);
readln(f,n);
for i:=1 to m do
begin
for j:=1 to n do read(f,a[i,j]);
readln(f)
end;
s:=0;
for i:=1 to n do s:=s+a[i,i];
writeln(g,s);
close(f);
24
Aplicaţii propuse
AP1. În fişierul SECVENTE.TXT sunt scrise mai multe linii. Fiecare
linie conţine cate o secvenţă, secvenţă formată din primele patru
numere prime (fiecare secvenţă conţine cel puţin odată fiecare număr
prim, fără spaţii între ele), şir format din cel mult 255 de cifre. Se cere
să se scrie în fişierul PERECHI.TXT, pe cate o linie, despărţite prin
spaţii, indicii elementelor ce reprezintă capetele secvenţei determinate
din cea curentă din fişierul de intrare astfel încat secvenţa din şir să
aibă lungime maximă şi să conţină un număr egal de cifre din fiecare
număr prim din cele date.
Exemplu: Pentru fişierul SECVENTA.TXT
55552753275325732357223
253257322
Atunci fişierul PERECHI.TXT va conţine
5 20
47
AP2. În fişierul NUMERE.TXT sunt scrise două numere cu maxim
două cifre fiecare. Se cere să se scrie în fişierul RASPUNS.TXT
mesajul DA, dacă unul dintre cuvintele este anagrama celuilalt (conţin
exact acelaşi cifre eventual în altă ordine) sau NU, în caz contrar.
AP3. În fişierul MATRICE.TXT se află o matrice în format pătratic
astfel: pe prima linie dimensiunea matricei, iar pe următoarele linii
despăţite prin spaţii elementele matricei. Pe ultima linie se află un
număr ce reprezintă linia k. Se cere să se afişeze în fişierul
ORDONAT.TXT matricea cu elementele în linia k ordonate
descrescator.
AP4. Se citesc doi vectori deja ordonaţi crescător (două linii) din
fişierul VECTORI.TXT; numerele pe linie sunt despărţite prin spaţii.
Se cere să se scrie în fişierul INTERCLASARE.TXT vectoul obţinut
prin interclasarea celor doi.
Exemplu: Pentru fişierul VECTORI.TXT
257
3 4 9 11
Atunci fişierul INTERCLASARE.TXT va conţine
2 3 4 5 7 9 11
AP5. Se citesc mai multe numere reale din fişierul INTRARE.TXT
Ele sunt dispuse pe mai multe linii, despărţite prin spaţii în cadrul

25
fiecărei linii. Să se scrie în fişierul IESIRE.TXT pe prima linie
numărul de numere distincte, iar pe următoarele linii, despărţite prin
spaţii, aceste numere ordonate crescător.
Exemplu: Dacă fişierul INTRARE.TXT conţine
2 3.7 2 5 8 5
Atunci fişierul IEŞIRE.TXT conţine
4
2 3.7 5 8

AP6. Se cere să se scrie în fişierul OUT.TXT numărul de numere


prime de pe fiecare linie din fişierul IN.TXT şi pe următoarea linii
numerele prime din fişierul de intrare.
AP7. Se dau două fişiere MULT1.TXT şi MULT2.TXT care conţin,
despărţite prin cate un spaţiu mai multe numere reale reprezentand
cate o mulţime fiecare. Se cere să se scrie în fişierul INT_REUN.TXT
pe prima linie, intersecţia celor 2 mulţimi, iar următoarea linie
reuniunea lor.
Exemplu: Dacă fişierul MULT1.TXT conţine
5.5 6 8 7.6
şi fişierul MULT2.TXT conţine
5.5 8 7
atunci fişierul INT_REUN.TXT
5.5 8
5.5. 6 7 7.6 8
AP8. Fişierul text FIS.DAT conţine numere şi cuvinte pe mai multe
linii, fiecare avand maximum 255 de caractere. Să se realizeze un
program care inventariază conţinutul fişierului, afisand pentru fiecare
linie şi pentru întregul fisier numărul de caractere, numărul de cuvinte
şi numărul de valori numerice.
AP9. Să se ordoneze descrescător după medii, înregistrările unui fişier
care conţine datele şi rezultatele candidaţiilor la un examen de
admitere în clasa a IX-a (sau la facultate). Fiecare înregistrare este
compusă din cinci câmpuri: un nume (variabilă de tip string cu 20 de
caractere), trei note reale reprezentand notele la cele două probe,
media obţinută la examen şi un câmp logic ce memorează rezultatul
final (ADMIS sau RESPINS). Programul va afişa rezultatul
examenului de admitere în funcţie de un număr citit de la tastatură
reprezentând numărul de locuri posibile.

26
AP10. Fişierul PRODUSE.DAT conţine informaţii despre produsele
dintr-un magazin alimentar: numele produsului (maxim 20 de
caractere), preţul unitar (număr întreg, maxim 100.000), cantitatea
(numar real) şi termenul de garanţie (sub forma zi-luna-an când expiră
termenul de folosinţă al produsului). Să se afişeze în funcţie de data (şi
să se elimine din fişier) produsele expirate şi costul lor.
AP11. Să se determina frecvenţa apariţiei caracterelor (date de codul
lor ASCII) dintr-un fişier precizat.
AP12. Să se afişeze pe ecran toate caracterele unui fişier text existent,
caracterele netiparibile (cu cod ASCII mai mic decât 32) fiind
înlocuite la scriere cu codul lor ASCII, precedat de caracterul '#'.
AP13. Să se creeze un fişier text avand ca sursă două fişiere text, al
doilea fiind adăugat în continuarea primului.
AP14. [Numere mari] Se consideră două numere naturale n şi m care
au cel puţin 200 de cifre. Să se determine produsul celor două numere.
Pe prima linie a fişierului de intrare INPUT.TXT este scris numărul n,
avand cifrele despărţite prin cate un spaţiu; pe a doua linie este scris
numarul m, în mod similar. În fişierul OUTPUT.TXT se va scrie pe
fiecare linie cate o cifra din rezultat.
AP15. [Goldbach] Se consideră un număr n>6 par. Să se determine
toate reprezentările lui n ca sumă de numere prime (sumă cu număr
minim de termeni). Rezultatele vor fi scrise în fişierul OUTPUT.TXT,
fiecare linie conţinand toţi termenii dintr-o reprezentare.
Exmplu: Pentru n = 8, atunci OUTPUT.TXT conţine
35
53
Nu se consideră perechile (1, 7) şi (7, 1) din cauza numărului 1.
AP16. [Factor prim] Se consideră un şir de n numere întregi. Să se
determine factorul prim care apare la puterea cea mai mare în des-
compunerea produsului celor n numere. Pe prima linie a fişierului
INPUT.TXT este scris numărul n, reprezentand numărul de elemente
din şir; pe următoarea linie sunt scrise cele n numere. Răspunsul se va
afişa pe ecran sub forma: i^j – cu semnificaţia: factorul i la puterea j
Exemplu:
INPUT.TXT pe ecran se va afişa :
4 3^4
24 9 17 21

27
AP17. [Cel mai mare divizor comun] Se consideră n numere scrise în
baza 16. Să se determine cel mai mare divizor comun al celor n
numere. Pe prima linie a fişierului INPUT.TXT este scris numărul de
numere n; pe următoarele n linii se găsesc numerele scrise în
hexazecimal. Numărul care reprezintă c.m.m.d.c. al celor n numere, se
va afişa pe ecran.
Exemplu: INPUT.TXT se va afisa :
4\n 1A \n 8 \n 10 \n 2 2
AP18. [Cifre romane] Să se scrie un program care transformă un
număr scris cu cifre romane în scrierea cu cifre arabe. În fişierul
INPUT.TXT sunt scrise mai multe numere în scriere romana (corecte),
fiecare pe câte o linie. Este posibil ca unele din cifrele romane sa fie
scrise cu litere mici. Răspunsurile se vor afişa pe ecran, fiecare număr
pe câte o noua linie.
Exemplu:
INPUT.TXT pe ecran se va afişa:
CDLIV 454
MMMMDLXXIII 4573
MCLXVI 1166
CMXLLVIII 948

AP19. [Propoziţii] Se consideră un text scris pe n linii (în fişierul de


intrare INPUT.TXT). Fiecare linie constituie o propoziţie. Cuvintele
sunt formate doar din litere mari şi litere mici şi sunt despărţite prin:
spaţiu, virgulă şi punct. Nici o propozitie nu are mai mult de 250 de
caractere. Textul nu conţine cuvinte în care să apară liniuţa de
despărţire (exemplu: "într-un").
Să se determine propoziţia cu cele mai multe cuvinte şi să se
afişeze pe ecran scriind fiecare cuvânt pe linie nouă având prima literă
majusculă; separatorii nu se vor afişa.

Exemplu:
INPUT.TXT se va afisa
Ce faci… Alo
Unde esti Este
O sa reusim… Ora
buna dimineata Sapte
Alo, este ora sapte si treizeci de minute, fix. Si
Treizeci
De

28
Minute
Fix
AP20. [Matrice] Se dă matricea A cu m linii şi n coloane. Să se scrie
un program care prin intermediul unui meniu realizează următoarele
operaţii:
♦ Elimină din matrice linia L şi coloana k fără a folosi o nouă
matrice;
♦ Ordonează crescător elementele de pe o coloană oarecare j prin
interschimbări de linii.
Datele de intrare se vor citi dintr-un fişier text.

AP21. [Ordonare] Se consideră un tablou bidimensional pătratic de


dimensiune n*n de numere întregi. Se cere să se inverseze liniile şi
coloanele acestuia astfel încât tabloul obţinut să conţină elemente de
pe diagonala principală în ordine crescătoare. Datele de intrare se
citesc din fişierul de intrare INPUT.TXT sub forma următoare: pe
prima linie este scris numărul n, reprezentând numărul de linii şi
coloane, iar pe următoarele n linii, se găsesc elementele tabloului linie
după linie.
Rezultatul va fi scris în fişierul OUTPUT.TXT. Pe fiecare linie a
fişierului se va scrie o linie a tabloului obţinut.
Exemplu:
INPUT.TXT OUTPUT.TXT
3 -1 8 7
243 234
156 165
-1 7 8

AP22. [Frecvenţe] Se consideră un fişier INPUT.TXT care conţine un


text. Se cere determinarea listei cu frecvenţa de apariţie maximă. In
cazul cand există mai multe soluţii se vor afişa toate. Fişierul de
intrare conţine pe fiecare linie cel mult 250 de caractere. Nu se va face
distincţie între litera mică şi litera mare. Rezultatul se va afişa pe
ecran.
Exemplu:
INPUT.TXT pe ecran se va afisa:
Mama si cu tata merg la piata… A apare de 7 ori
Memoriu, memoriu! M apare de 7 ori

29
AP23. [Ordonare în matrice] Se consideră o matrice de dimensiune
n*m. Să se reaşeze elementele în matrice astfel încât ele să apară în
ordine crescătoare atât pe linii cât şi pe coloane. Matricea A se găseşte
în fişierul de intrare INPUT.TXT, fiecare linie a matricei pe câte o
linie din fişier.
Nu sunt specificate valoarile pentru n şi m. Matricea obţinută se
va scrie, în mod similar, în fişierul OUTPUT.TXT. În cazul în care
există mai multe soluţii se va scrie în fişier numai una.
Exemplu:
INPUT.TXT OUTPUT.TXT
3 2 5 44 3 1 122222
6 7 55 4 3 2 333334
4 10 4 2 4 5 444445
34 5 6 8 2 4 555667
5 3 2 4 41 3 8 10 34 41 44 55

AP24. O matrice cu elemente întregi este memorată în fişierul text


in.txt sub următorul format: pe prima linie numărul n de linii şi
numărul m de coloane. Pe următoarele n linii, elementele de pe fiecare
linie a matricii. Citiţi matricea şi tipăriţi-o pe coloane în fişierul
out.txt.

AP25. Citiţi un text scris pe mai multe linii în fişierul text.txt.


Calculaţi, pentru fiecare literă a alfabetului, procentul de cuvinte care
încep cu acea literă. Separatori între cuvinte vor fi consideraţi:
blancul, punctul, virgula şi sfârşitul de linie.

AP26. Creaţi cu editorul Pascal un fişier text pe mai multe linii.


Scrieţi un program care calculează:
a) Numărul de cuvinte din fişier. Ca separatori între cuvinte se
vor considera blancurile şi sfârşitul de linie.
b) Tipăriţi liniile care conţin cel puţin o vocală.
c) Câte linii conţin mai mult de n cuvinte, n citit de la tastatură?

30
2. Structuri de date
O structură de date este o mulţime de date organizate într-un anumit
mod împreună cu relaţiile dintre acestea.
În funcţie de modul de alocare, structurile de date se clasifică în:
• structuri de date statice : tabloul, înregistrarea, mulţimea, fişierul.
Structurile de date statice ocupă o zonă de memorie constantă pe
toată durata de executare a blocului în care apare declaraţia şi
elementele ocupă poziţii fixe. Fişierele sunt structuri de date
externe (vezi capitolul 1).
• structuri de date semistatice: stiva alocată static, coada alocată
static. Structurile de date semistatice ocupă o zonă de memorie de
dimensiune constată, alocată pe toată durata executării blocului în
care apare declaraţia de structură, iar elementele ocupă poziţii
variabile pe parcursul executării programului.
• structuri de date dinamice: lista înlănţuită, structuri arborescente.
Structurile de date dinamice ocupă o zonă de memorie de
dimensiune variabilă, alocată dinamic. Alocarea dinamică permite
gestionarea memoriei în timpul executării programului. Ele sunt
mult mai flexibile decât cele statice şi din această cauză sunt
extrem de avantajoase.
Liste: O listă este o colecţie de elemente de informaţie (noduri)
aranjate într-o anumită ordine. Lungimea unei liste este numărul de
noduri din listă. Structura corespunzătoare de date trebuie să ne
permită să determinăm eficient care este primul/ultimul nod în
structură şi care este predecesorul/succesorul (dacă există) unui nod
dat. Limbajele Pascal sau C(++) oferă posibilităţi de implementare a
acestor structuri atât static cât şi dinamic. Operaţiile curente care se
fac în liste sunt: inserarea unui nod, ştergerea (extragerea) unui nod,
concatenarea unor liste, numărarea elementelor unei liste etc.
Implementarea unei liste se poate face în principal în două moduri:
• Implementarea secvenţială, în locaţii succesive de memorie,
conform ordinii nodurilor în listă. Avantajele acestei tehnici
sunt accesul rapid la predecesorul/succesorul unui nod şi
găsirea rapidă a primului/ultimului nod. Dezavantajele sunt
inserarea/ştergerea relativ complicată a unui nod şi faptul că
nu se foloseşte întreaga memorie alocată listei.
• Implementarea înlănţuită. Fiecare nod conţine două părţi:
informaţia propriu-zisă şi adresa nodului succesor. Alocarea
31
memoriei fiecărui nod se poate face în mod dinamic, în timpul
rulării programului. Accesul la un nod necesită parcurgerea
tuturor predecesorilor săi, ceea ce poate lua ceva mai mult
timp. Inserarea/ştergerea unui nod este, în schimb, foarte
rapidă.
Listele se pot clasifica după numărul de legături în:
• liste simple
• liste duble
• liste circulare
O listă simplă are o singură legătură, legătura ultimului element este
adresa NIL/NULL. Pentru a accesa lista avem nevoie de o variabilă
care să păstreze adresa primului element (cap sau prim). O listă
dublă are două legături: legătura de tip următor şi legătura de tip
precedent sau anterior. O lista circulară este o lista în care, după
ultimul nod, urmează primul, deci fiecare nod are succesor şi
predecesor.
Listele se pot clasifica după locul în care se fac operaţiile de
adăugare/eliminare astfel:
• stive
• cozi

2.1. Liste simplu înlănţuite

Între nodurile unei liste simplu înlănţuite este definită o singură relaţie
de ordonare. Fiecare nod conţine un pointer a cărui valoare reprezintă
adresa nodului următor din listă.
Limbajul Pascal
Limbajul C(++)
type lista = ^nod typedef struct nod {
nod=record tip inf;
inf:tip ; struct nod *urm;
urm: lista; }lista;
end;
unde urm este adresa următorului nod (pointer către următorul nod),
iar inf este un câmp de informaţie utilă.
Operaţii asupra listei simplu înlănţuite
Crearea unei liste simplu înlănţuite:
1. Se iniţializează pointerul prim cu Nil/NULL, deoarece lista la
început este goală;
2. Se rezervă zonă de memorie în memoria heap pentru nodul curent;
32
3. Se încarcă nodul curent cu date;
4. Se determină adresa ultimului nod şi se realizează legătura cu
nodul creat;
5. Se reia cu pasul 2 dacă se introduce un nod nou.
Inserarea unui element x al cărui câmp cheie a fost iniţializat în
faţa unei liste înlănţuite
LISTA-INSEREAZA(p,x)
1: urm(x) ←p
2: dacă p<>NIL atunci p←x
Căutarea într-o listă înlănţuită p a elementului x se realizează
prin subprogramul LISTA-CAUTA şi returnează pointerul la acest
obiect.
LISTA-CAUTA(p,x)
1. q←p
2. cât timp q<>NIL şi cheie(q)<>x execută q←urm(q)
3. returnează q

Probleme rezolvate
1. Fiind dată o listă simplu înlănţuită cu elemente numere întregi să se
realizeze un program care să execute următoarele operaţii: crearea,
parcurgerea, adăugarea unui nod la începutul listei, adăugarea unui nod
la sfârşitul listei, ştergerea unui nod de pe o poziţie dată.

Observaţii:
Limbajul Pascal: Limbajul C(++)
Procedura NEW(pointer)- alocarea Funcţia MALLOC se foloseşte
dinamică a memoriei pentru pentru a rezerva octeţi din
variabila dinamică pointer. memoria heap. Trebuie inclus
Procedura DISPOSE(pointer)- fişierul antet: stdlib.h sau
eliberarea memoriei ocupate de alloc.h
către variabila dinamică pointer.

Rezolvare:
Parcurgerea unei liste liniare simplu înlănţuite se realizează cu un
pointer care pleacă de la capul listei şi va referi pe rând fiecare nod,
prelucrând informaţiile din nod apoi se trece la următorul nod, prelu-
crăm informaţiile din nod etc.

33
Ştergerea unui nod de pe o poziţie dată p din interiorul listei se
realizează astfel: se parcurge lista până la pozitia p-1, se păstrează
nodul de pe poziţia p, se realizează legătura între nodul p-1 şi p+1 şi,
apoi se eliberează memoria ocupată de nodul p.
Implementare în Pascal:

type lista=^nod;
nod=record inf:integer; urm:lista end;
var p,x:integer;cap:lista;{adresa primului nod al listei}
procedure adaug(var cap:lista;x:integer); {adaugarea la sfarsitul listei}
var nou,q:lista;
begin
new(nou);nou^.inf:=x;nou^.urm:=nil;
if cap=nil then cap:=nou
else begin
q:=cap;
while q^.urm<> nil do q:=q^.urm;
q^.urm:=nou;
end;
end;
procedure adaug_inc(var cap:lista;x:integer); {adaugarea la inceput}
var nou:lista;
begin
new(nou);nou^.inf:=x;nou^.urm:=nil; {crearea nodului nou}
if cap=nil then cap:=nou
else begin
nou^.urm:=cap;cap:=nou;
{realizarea legaturii si primul nod devine nodul creat}
end;
end;
procedure listare(cap:lista);{listarea listei}
var t:lista;
begin
t:=cap;
while t<>nil do begin
write(t^.inf,' '); {prelucrarea informatiei}
t:=t^.urm; {trecerea la urmatorul nod}
end;
end;
procedure sterge(var cap:lista; p:integer);
34
{stergerea nodului de pe pozitia p}
var q,w,t:lista;i:integer;
begin
if cap=nil then writeln('Lista vida !!! ')
else if p=1 then begin
{stergere la inceputul listei}
q:=cap; cap:=cap^.urm; dispose(q);
end
else if (cap<> nil) then begin
t:=cap; i:=1;
while (t^.urm<> nil) and (i+1<p) do
begin inc(i) ;t:=t^.urm end;
if t^.urm=nil then write('nu exista suficiente pozitii')
else begin
w:=t^.urm; t^.urm:=t^.urm^.urm;
dispose(w)
end
end
end;
begin
read(x);
while x<>0 do begin adaug_inc(cap,x); read(x); end;
listare(cap);
write('pozitia');read(p);sterge(cap,p);
listare(cap);
write('informatia adaugata la sfarsit');read(x);adaug(cap,x);
listare(cap)
end.

Implementare în C:
#include <stdio.h>
#include<stdlib.h>
#include<alloc.h>
typedef struct nod {
int inf;
struct nod*urm;
}lista;
lista *prim;
lista *creare(void);
void parcurgere(lista *p);
35
lista* sterg_inc(lista *prim){
lista *p;
p=prim;
prim=prim->urm;
free(p);
return prim;
}
void adauga(lista*prim) {
/*adauga un nod la o lista simplu inlantuita
si returneaza pointerul spre nodul adaugat sau zero daca nu s-a realizat
adaugarea*/
lista *p,*q;
for (p=prim;p->urm!=NULL;p=p->urm)
q=(lista*) malloc(sizeof(q));
scanf("%d",&q->inf);
q->urm=NULL;
p->urm=q;
}
void main(){
lista *prim;
prim=creare(); parcurgere(prim); prim=sterg_inc(prim);
printf("\n");
parcurgere(prim); adauga(prim); parcurgere(prim);
}
lista *creare(){
int n,i,inf; lista *prim,*p,*q;
printf("nr. de elemente");scanf("%d",&n);
printf("informatia primului nod"); scanf("%d",&inf);
prim=(lista*)malloc(sizeof(prim)); prim->inf=inf;prim->urm=NULL;
for(q=prim,i=2;i<=n;i++){
printf("inf . %d:",i);scanf("%d",&inf);
p=(lista*)malloc(sizeof(p));
p->inf=inf;p->urm=NULL; q->urm=p; q=p;
}
return(prim);
}
void parcurgere(lista *p){
lista *q;
for (q=p;q;q=q->urm) printf("%d ",q->inf);
}

36
2. Inversarea legăturilor într-o listă simplu înlănţuită.

Rezolvare:
Se parcurge lista iniţială folosind trei variabile dinamice p1, p2, p3
care vor face referire la elementele consecutive din listă. p1 va fi
capul listei modificate.

Implementare Pascal:
program invers;
type lista=^nod;
nod=record inf:integer; urm:lista end;
var i,n,x:integer; p:lista;
procedure creare(var p:lista; x:integer);
var q:lista;
begin
if p=nil then begin new(p); p^.inf:=x; p^.urm:=nil end
else begin
q:=p; while q^.urm<>nil do q:=q^.urm;
new(q^.urm); q^.urm^.inf:=x; q^.urm^.urm:=nil;
end;
end;
procedure listare(p:lista);
var q:lista;
begin
q:=p; while q<>nil do begin write(q^.inf,' '); q:=q^.urm end;
end;
function inversare(p:lista):lista; Subprogramul de inversare în C:
var p1,p2,p3:lista; lista* invers(lista*p){
begin lista *p1,*p2,*p3;
p1:=p; p1=p;
p2=p->urm;
p2:=p^.urm;
p->urm=NULL;
p^.urm:=nil;
while (p2){
while p2<>nil do begin
p3=p2->urm;
p3:=p2^.urm;
p2->urm=p1;
p2^.urm:=p1;
p1=p2;
p1:=p2; p2=p3;
p2:=p3; }
end; return p1;
inversare:=p1; }
end;

37
begin
read(n);
for i:=1 to n do begin read(x); creare(p,x) end;
listare(p);writeln;
p:=inversare(p);
listare(p)
end.

3. Să se efectueze suma a două polinoame rare (polinom cu foarte


mulţi coeficienţi egali cu zero) folosind liste simplu înlănţuite.

Rezolvare:
Lista are ca informaţie gradul şi coeficientul fiecărui termen de
coeficient nenul. Pentru a calcula suma este necesar să parcurgem
listele celor două polinoame şi să adăugăm corespunzător în cea de-a
treia listă.

Implementare Pascal:
type lista=^nod;
nod=record grad:1..5000; coef:integer; urm:lista end;
var a,b,p,q,r:lista;
i,n,m:integer;
procedure creare(var p:lista);
begin
write('cati coeficienti nenuli are polinomul');readln(n);
new(p);readln(p^.coef,p^.grad);
p^.urm:=nil;b:=p; {b este adresa ultimului nod}
for i:=2 to n do begin
new(a);
write('coef ',i,':');readln(a^.coef); write('grad ',i,':');readln(a^.grad);
b^.urm:=a; b:=a; b^.urm:=nil
end
end;
procedure listare(p:lista);
var a:lista;
begin
a:=p;
while a<>nil do begin write(a^.coef,'x^', a^.grad,' +'); a:=a^.urm end;
writeln(#8,' ');
end;

38
procedure aduna(p,q:lista;var r:lista);
var a,b,c,d:lista;
begin
a:=p;b:=q; {c este adresa ultimului nod pentru lista suma}
while (a<>nil) and (b<>nil) do
if a^.grad=b^.grad then begin
if r=nil then begin
new(c);c^.grad:=a^.grad;
c^.coef:=a^.coef +b^.coef;
r:=c; r^.urm:=nil;
end
else begin
new(d); d^.grad:=a^.grad;
d^.coef:=a^.coef+b^.coef;
c^.urm:=d;c:=d;c^.urm:=nil
end;
a:=a^.urm;b:=b^.urm;
end
else if a^.grad<b^.grad then begin
if r=nil then begin
new(c); c^.grad:=a^.grad;
c^.coef:=a^.coef ; r:=c; r^.urm:=nil;
end
else begin
new(d); d^.grad:=a^.grad; d^.coef:=a^.coef;
c^.urm:=d;c:=d;c^.urm:=nil;
end;
a:=a^.urm;
end
else begin
if r=nil then begin
new(c);c^.grad:=b^.grad;
c^.coef:=b^.coef;r:=c;r^.urm:=nil;
end
else begin
new(d); d^.grad:=b^.grad; d^.coef:=b^.coef;
c^.urm:=d;c:=d;c^.urm:=nil;
end;
b:=b^.urm;
end;

39
if a<>nil then c^.urm:=a;
if b<>nil then c^.urm:=b;
end;
begin
creare(p);creare(q);listare(p);listare(q);
r:=nil;
aduna(p,q,r);
listare(r);
readln;
end.
Exerciţii:
1. Să se implementeze în C aplicaţia de mai sus.
2. Să se scrie un subprogram pentru calcularea produsului a două
polinoame rare.
3. Să se calculeze valoarea unui polinom într-un punct dat.
Indicaţie:
Calcularea produsului se va face prin parcurgerea tuturor perechilor de
termeni astfel:
-fiecare pereche generează un nod în polinomul produs
-gradul noului nod va fi suma gradelor nodurilor din pereche
-coeficientul noului nod va fi produsul coeficienţilor termenilor din
pereche
-se elimină nodurile din lista de termeni prin păstrarea fiecărui grad o
singură dată astfel: dacă există două noduri cu acelaşi grad unul din
ele va fi eliminat, iar coeficientul celuilalt va avea valoarea sumei
coeficienţilor celor doi termeni.
Valoarea unui polinom se va calcula prin parcurgerea listei şi
adăugând la valoare produsul dintre coeficient şi valoarea dată la
puterea gradului din nod.

4.Dându-se două liste simplu înlănţuite cu elemente numere intregi


distincte, să se afiseze diferenţa celor două liste şi elementele comune
celor două liste.
Rezolvare:
Diferenţa celor două liste conţine elementele primeia care nu
sunt în cea de-a doua. Se parcurge prima listă şi pentru fiecare nod se
verifică dacă este în cea de-a doua listă, dacă nu se află atunci se
adaugă listei trei. Intersecţia celor două liste se determină parcurgând

40
prima listă şi pentru fiecare nod se verifică dacă elementul se află şi în
cea de-a doua listă, dacă da atunci se adaugă în cea de-a treia listă.

Implementare Pascal:
type lista=^nod;
nod = record inf:integer; urm:lista end;
var q,cap1,cap2,cap3:lista;
gasit:boolean;
procedure creare(var cap:lista);
var n,i:integer;
nou,q:lista;
begin
write('nr elem'); read(n); new(cap); read(cap^.inf);
cap^.urm:=nil;
for i:=2 to n do begin
new(nou); read(nou^.inf); nou^.urm:=nil; q:=cap;
while q^.urm<>nil do q:=q^.urm;
q^.urm:=nou
end
end;
procedure diferenta(cap1,cap2:lista);
var x:integer;
q2:lista;
begin
q:=cap1;
while q<>nil do begin
x:=q^.inf; gasit:=false; q2:=cap2;
while q2<>nil do begin
if q2^.inf=x then gasit:=true; q2:=q2^.urm
end;
if not gasit then write(x, ' '); q:=q^.urm
end
end;
procedure afisare(cap:lista);
var q:lista;
begin
q:=cap;
while q<>nil do begin write(q^.inf,' '); q:=q^.urm end;
writeln;
end;

41
procedure intersectie(cap1,cap2:lista; var cap3:lista);
var q,q2,nou,q3:lista;
x:integer;
begin
q:=cap1;
while q<>nil do begin
x:=q^.inf; gasit:=false; q2:=cap2;
while q2<>nil do begin
if q2^.inf=x then gasit:=true;
q2:=q2^.urm;
end;
if gasit then if cap3=nil then
begin new(cap3); cap3^.inf:=x; cap3^.urm:=nil end
else begin
new(nou); nou^.inf:=x ; nou^.urm:=nil; q3:=cap3;
while q3^.urm<> nil do q3:=q3^.urm;
q3^.urm:=nou
end;
q:=q^.urm
end
end;
begin
creare(cap1); creare(cap2); afisare(cap1); afisare(cap2);
diferenta(cap1,cap2);
writeln; diferenta(cap2, cap1);
writeln('intersectie'); intersectie(cap1,cap2,cap3); afisare(cap3);
end.

Exerciţiu: Să se implementeze aplicaţia de mai sus in C.

5. Să se creeze o listă simplu înlănţuită, cu informaţie numerică astfel


încât la orice moment al inserării lista să fie ordonată crescător după
informaţie.

Rezolvare:
Paşii: -crearea unui nod nou
-dacă informaţia care se adaugă este mai mică decât informaţia
din capul listei atunci se inserează în faţa primului nod
-altfel se parcurge lista până la primul nod a cărei informaţie este
mai mare decât informaţia noului nod şi se inserează.

42
Implementare C:

#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
typedef struct nod { int inf; struct nod*urm;} lista;
lista *prim;
lista *creare(void);
void parcurgere(lista *p);
void main(){
lista *prim;
prim=creare();
parcurgere(prim);
}
lista *creare(){
int n,inf; lista *prim,*p,*q,*nou;
printf("nr. de elemente");scanf("%d",&n);
prim=NULL;
for(int i=1;i<=n;i++){
printf("informatia nod");scanf("%d",&inf);
nou=(lista *)malloc(sizeof(lista));
nou->inf=inf;
if (prim==NULL){
prim=nou;prim->urm=NULL;
}
else if (prim->inf>inf){
nou->urm=prim;prim=nou;
}
else {
p=q=prim;
while(p&&p->inf<inf){q=p; p=p->urm;}
if (p) {q->urm=nou; nou->urm=p;}
else{q->urm=nou; nou->urm=NULL;}
}
}
return prim;
}
void parcurgere(lista *p){ lista *q;
for (q=p;q;q=q->urm) printf("%d ",q->inf);
}

43
Exerciţiu: Să se implementeze aplicaţia de mai sus în Pascal.

6. Să se scrie un program pentru interclasarea a două liste ordonate


simplu înlănţuite.

Rezolvare:
Se va parcurge simultan cele două liste urmând ca introducerea unui
nod în lista finală să fie făcută din lista care are valoarea informaţiei
din nod mai mică.

Implementare C:
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
typedef struct nod {int inf; struct nod*urm;}lista;
lista *inter(lista *prim1, lista*prim2){
lista *prim,*ultim;
if (prim1->inf>prim2->inf){
prim=prim2;prim2=prim2->urm;
}
else {
prim=prim1;
prim1=prim1->urm;
}
ultim=prim;
while(prim1&&prim2)
if (prim1->inf>prim2->inf){
ultim->urm=prim2;
ultim=prim2;
prim2=prim2->urm;
}
else {ultim->urm=prim1;
ultim=prim1;
prim1=prim1->urm;
}
if (prim1) ultim->urm=prim1; else ultim->urm=prim2;
return prim;
}
lista *creare(void);
void parcurgere(lista *p);

44
void main(){
lista *prim1,*prim2,*prim;
prim1=creare();
prim2=creare();
/* parcurgere(prim1) */;
prim=inter(prim1,prim2);
parcurgere(prim1);
}
lista *creare(){
int n,inf; lista *prim,*p,*q,*nou;
printf("nr. de elemente");scanf("%d",&n);
prim=NULL;
for(int i=1;i<=n;i++){
printf("informatia nod");scanf("%d",&inf);;
nou=(lista *)malloc(sizeof(lista));
nou->inf=inf;
if (prim==NULL){
prim=nou;
prim->urm=NULL;
}
else if (prim->inf>inf){
nou->urm=prim;prim=nou;
}
else {
p=q=prim;
while(p&&p->inf<inf){q=p;p=p->urm;}
if(p) {q->urm=nou;nou->urm=p;}
else {
q->urm=nou;nou->urm=NULL;
}
}
}
return prim;
}
void parcurgere(lista *p){ lista *q;
for (q=p;q;q=q->urm) printf("%d ",q->inf);
}

Exerciţiu: Să se implementeze aplicaţia de mai sus în limbajul Pascal.

45
2.2. Liste dublu înlănţuite

Pentru liste duble create dinamic modul de definire a unui nod este:
Limbajul Pascal typedef struct nod{
type lista=^nod; inf tip;
nod=record struct nod *urm;
inf:tip; struct nod *ant;
urm, ant:lista; }lista;
end;

Operaţiile care se pot defini asupra listelor dublu înlănţuite sunt


aceleaşi ca şi în cazul listelor simplu înlănţuite:
- crearea unei liste dublu înlănţuite;
- accesul la un element al unei liste dublu înlănţuite;
- inserarea unui nod într-o listă dublu înlănţuită;
- ştergerea unui nod dintr-o listă dublu înlănţuită;
- ştergerea unei liste dublu înlănţuite.

Probleme rezolvate

1. Să se scrie un program care va conţine un meniu cu principale


operaţii asupra listei dublu înlănţuite.

Implementarea Pascal a soluţiei:


type lista=^nod;
nod=record inf:integer; urm,ant:lista end;
var cap:lista;
x:integer;
procedure creare(var cap:lista);
begin
new(cap); write('inf=');readln(cap^.inf); cap^.urm:=nil;cap^.ant:=nil;
end;
procedure adaugare(var cap:lista);
var q,nou:lista;
begin
new(nou);readln(nou^.inf);nou^.urm:=nil;
q:=cap; while q^.urm <> nil do q:=q^.urm; q^.urm:=nou;nou^.ant:=q;
end;
46
procedure inserare(var cap:lista);
var nou,q:lista;
k,i:integer;
begin
writeln('unde inserezi? ');
read(k);new(nou);
write('ce? ');readln(nou^.inf);
if k=1 then begin
cap^.ant:=nou; nou^.urm:=cap; nou^.ant:=nil; cap:=nou;
end
else begin
q:=cap;
i:=1;
while (q^.urm<>nil) and (i<k) do begin
q:=q^.urm; inc(i)
end;
if i=k then begin
nou^.ant:=q^.ant; q^.ant^.urm:=nou;
nou^.urm:=q; q^.ant:=nou;
end
else begin write('nu exista');
readln
end
end
end;
procedure stergere(var cap:lista);
var q,p:lista;
k,i:integer;
begin
write('unde stergi? '); readln(k);
if cap<>nil then
if k=1 then begin q:=cap; cap:=cap^.urm;
dispose(q);
cap^.ant:=nil;
end
else begin
p:=cap;i:=1;
while (p^.urm<>nil) and(i<k) do begin
p:=p^.urm; inc(i);
end;

47
if i=k then begin
q:=p; p^.ant^.urm:=p^.urm;
p^.urm^.ant:=p^.ant; dispose(q);
end
else begin write('nu exista'); readln end
end
end;
procedure parcurgere(var cap:lista);
var q:lista;
begin
q:=cap;
while q<>nil do begin write(q^.inf, ' '); q:=q^.urm end;
readln
end;
procedure parcurgere_inv(var cap:lista);
var q:lista;
begin
q:=cap;
while q^.urm<>nil do q:=q^.urm;
while q<>nil do begin write(q^.inf, ' '); q:=q^.ant end;
readln;
end;
begin
while x<> 7 do begin
writeln('1.creare'); writeln('2.adaugare');
writeln('3.inserare'); writeln('4.stergere');
writeln('5.parcurgere'); writeln('6.parcurgere_inv');
writeln('7.iesire');
readln(x);
case x of
1:creare(cap);
2:adaugare(cap);
3:inserare(cap);
4:stergere(cap);
5:parcurgere(cap);
6:parcurgere_inv(cap);
end
end
end.

48
2. Crearea şi parcurgerea listei dublu înlănţuite în limbajul C:

#include<stdio.h>
#include<stdlib.h>
typedef struct nod{int inf; struct nod*urm,*ant; } lista;
lista*prim ,*ultim;
void creare(int n);
void parcurg(lista*prim);
void parcurg1(lista*ultim);
void main(){
int n;
printf("numarul de elemente:");scanf("%d",&n);
creare(n);
puts("\n Parcurgere directa");parcurg(prim);
puts("\n Parcurgere indirecta");parcurg1(ultim);
}
void creare(int n){
int i;
lista *p;
prim=(lista*)malloc(sizeof(lista));
printf("informatia primului nod"); scanf("%d",&prim->inf);
prim->urm=prim->ant=NULL;
ultim=prim;
for(i=2;i<=n;i++){
p=(lista*)malloc(sizeof(lista));
printf("inf=");scanf("%d",&p->inf);
p->ant=ultim;ultim->urm=p;
p->urm=NULL;
ultim=p;
}
}
void parcurg(lista*p){
if (p){
printf("%d ",p->inf); parcurg(p->urm);
}
}
void parcurg1(lista *p){
lista *q;
for (q=p;q;q=q->ant) printf("%d ",q->inf);
}

49
2.3. Liste circulare
După numărul de legături, listele circulare se împart în: liste simple şi
liste duble. Listele circulare simple sunt liste simple care au în plus
propietatea că valoarea câmpului următor a ultimului nod este adresa
capului listei. Listele circulare duble sunt liste duble care au
propietatea că legătura următor a celui de-al doilea cap este primul
cap şi legătura anterior a primului cap este al doilea cap.

Crearea şi parcurgerea listei circulare simplu înlănţuite în limbajul


C:

#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
typedef struct nod{ int inf; struct nod *urm;}lista;
void main(){
lista *prim,*p,*q;
int i,n;
/*crearea listei circulare*/
printf("inf primului nod");
prim=(lista*)malloc(sizeof(lista));
scanf("%d",&prim->inf);prim->urm=NULL;
p=prim;
printf("nr de elemente");scanf("%d",&n);
for (i=2;i<=n;i++){
q=(lista*)malloc(sizeof(lista));
printf("inf=");scanf("%d",&q->inf);
p->urm=q;p=q;
}
p->urm=prim;
/*parcurgerea listei*/
printf("%d ",prim->inf);
for (q=prim->urm;q!=prim;q=q->urm)
printf("%d ",q->inf);
}

Problemă rezolvată: Se dă un grup de n copii aşezaţi în cerc, care


sunt număraţi din m în m. Copilul care a fost numărat cu valoarea m
este eliminat. Dându-se pasul de eliminare m se cere să se precizeze
ordinea ieşirii din cerc.
50
Rezolvare: Simularea eliminării copiilor se realizează cu o listă
circulară la care se realizează eliminarea nodului care are ca
informaţie numărul copilului scos din joc. Numărarea se va face
parcurgând m elemente de la poziţia de unde s-a efectuat ultima
ştergere.

Implementarea Pascal (Lista circulară este simplă.)


type lista=^nod;
nod=record inf:integer; urm:lista end;
var i,n,m,j:integer;
prim,q,p:lista;
procedure creare(var prim:lista;x:integer);
begin new(prim); prim^.inf:=x; prim^.urm:=prim end;
procedure adaugare(var prim:lista;x:integer);
var q,p:lista;
begin
new(q);
q:=prim;
while q^.urm<>prim do q:=q^.urm;
new(p); p^.inf:=x;
q^.urm:=p;p^.urm:=prim;
end;
procedure listare(prim:lista);
var q:lista;
begin
new(q);q:=prim;
write(q^.inf,' ');
while q^.urm<>prim do begin q:=q^.urm; write(q^.inf,' ') end;
end;
begin {program principal}
read(n); creare(prim,1);
for i:=2 to n do adaugare(prim,i); listare(prim);
read(m); p:=prim;
for i:=1 to n-1 do begin
if i=1 then for j:=1 to m-2 do p:=p^.urm
else for j:=1 to m-1 do p:=p^.urm;
q:=p^.urm; write(q^.inf,' '); p^.urm:=q^.urm; dispose(q);
end;
writeln('castigator=',p^.inf);
end.

51
Implementarea C (Lista circulară este dublă).
#include <stdio.h>
#include<stdlib.h>
typedef struct nod{int inf; struct nod *urm,*ant; }lista;
lista *prim,*p,*q;
void creare(void);
void parcurgere(void);
int n,m,i,k;
void main(){
creare();
printf("pasul de numarare");scanf("%d",&m);
parcurgere();
while (p!=p->ant){
for( i=1;i<m;i++)p=p->urm;
printf("%d ",p->inf);
p->ant->urm=p->urm;
p->urm->ant=p->ant;
q=p;p=p->urm;free(q);
}
printf("%d",p->inf);
}
void parcurgere(){
for (p=prim,i=1;i<=n;i++,p=p->urm) printf("%d",p->inf);
/* p=p->urm;*/
}
void creare(){
printf("nr. de copii");scanf("%d",&n);
prim=(lista*)malloc(sizeof(lista));prim->inf=1;
prim->urm=prim->ant=NULL; p=prim;
for (i=2; i<=n; i++){
q=(lista*)malloc(sizeof(lista));
q->inf=i;
p->urm=q;
q->ant=p;
p=q;
}
q->urm=prim;prim->ant=q;
}

52
2.4. Stive
O stivă (stack) este o listă liniară cu proprietatea că operaţiile de
inserare/extragere a nodurilor se fac în/din vârful listei. Ultimul nod
inserat va fi şi primul şters, stivele se mai numesc şi liste LIFO (eng.
Last In First Out) sau liste pushdown.

push
pop

Cel mai natural mod de reprezentare pentru o stivă este implementarea


secvenţială într-un tablou S[1 .. n], unde n este numărul maxim de
noduri. Primul nod va fi memorat în S[1], al doilea în S[2], iar ultimul
în S[top], unde top este o variabilă care conţine adresa (indicele)
ultimului nod inserat. Algoritmii de inserare şi de ştergere (extragere)
a unui nod:

function push(x, S[1 .. n])


{adauga nodul x in stiva}
if top = n then return “stiva plina”
top ←top+1
S[top]← x
return “succes”

function pop(S[1 .. n])


{sterge ultimul nod inserat din stiva si il returneaza}
if top =0 then return “stiva vida”
x ← S[top]
top ← top-1
return x

Cei doi algoritmi necesită timp constant, deci nu depind de mărimea


stivei.
53
Problemă rezolvată: Realizaţi un meniu în limbajul Pascal care să
conţină operaţii asupra stivei.

Rezolvare:
Implementarea Pascal:
type stiva=^nod;
nod=record inf:integer; urm:stiva end;
var cap:stiva;
x:integer;
procedure adauga(var cap:stiva);
var nou:stiva;
begin
new(nou);
writeln('Ce sa adaug? ');
readln(nou^.inf);
nou^.urm:=nil;
if cap=nil then cap:=nou
else begin
nou^.urm:=cap;
cap:=nou;
end;
end;
procedure sterge(var cap:stiva);
var q:stiva;
begin
if cap=nil then writeln('Stiva vida')
else begin
q:=cap;
cap:=cap^.urm;
dispose(q)
end;
end;
procedure parcurgere(cap:stiva);
var q:stiva;
begin
q:=cap;
while q<> nil do begin
writeln('|',q^.inf,'|'); q:=q^.urm end;
writeln('___')
end;

54
begin
while x<> 4 do begin
writeln('1.Adaugare');
writeln('2.Stergere');
writeln('3.Listare');
writeln('4.Iesire');
writeln('Dati optiunea');
readln(x);
case x of
1:adauga(cap);
2:sterge(cap);
3:parcurgere(cap)
end
end
end.

Exerciţiu: Să se implementeze în limbajul C aplicaţia de mai sus.


Probleme propuse
1. Să se scrie un program care citind numere întregi din fişierul in.txt
creează o stivă şi o afişează. Să se transforme un număr din baza
10 în baza b folosind o stivă.
2. Pe o linie de cale ferată se găsesc, într-o ordine oarecare, n
vagoane numerotate de al 1 la n. Linia continuă cu alte k linii de
manevră. Cunoscând ordinea iniţială a vagoanelor, să se obţină la
ieşire vagoanele în ordine: 1,2 ,n; liniile de manevră sunt destul
de lungi încât să încapă pe o singură linie toate cele n vagoane.
Indicaţie: Se doreşte partiţionarea vagoanelor în k submulţimi care au
vagoanele ordonate crescător
2.5. Cozi
O coadă (eng. queue) este o listă liniară în care inserările se fac doar
în capul listei, iar extragerile doar din coada listei. Cozile se numesc şi
liste FIFO (eng. First In First Out). O reprezentare secvenţială pentru
o coadă se obţine prin utilizarea unui tablou C[0 .. n-1], pe care îl
tratăm ca şi cum ar fi circular: după locaţia C[n-1] urmează locaţia
C[0]. Fie p variabila care conţine indicele locaţiei predecesoare primei
locaţii ocupate şi fie u variabila care conţine indicele locaţiei ocupate
ultima oară. Variabilele p şi u au aceeaşi valoare atunci şi numai

55
atunci când coada este vidă. Iniţial, avem p= u= 0. Inserarea şi
ştergerea (extragerea) unui nod necesită timp constant.

Operaţii asupra cozii:


function insert-queue(x, C[0 .. n-1])
{adauga nodul x in capul cozii}
p ← (p+1) mod n
if p=u then return “coada plina”
C[p] ← x
return “succes”
function delete-queue(C[0 .. n-1])
{sterge nodul din coada listei si il returneaza}
if p=u then return “coada vida”
u ← (u+1) mod n
x ← C[u]
return x
Testul de coadă vidă este acelaşi cu testul de coadă plină. Dacă s-ar
folosi toate cele n locaţii, atunci nu am putea distinge situaţia de
“coadă plina” şi cea de “coadă vidă”, deoarece în ambele situaţii am
avea p = u. Se folosesc efectiv numai n-1 locaţii din cele n ale
tabloului C, deci se pot implementa astfel cozi cu cel mult n-1 noduri.

Problemă rezolvată: Relizarea unui meniu pentru implementarea


statică a cozii.

Rezolvare: Cea mai simplă implementare a cozii static este folosirea


unui tablou. Pentru gestionarea cozii este nevoie de două elemente: p
poziţia primului element şi u poziţia de după ultimul element din
coadă. Se va face o utilizare circulară a spaţiului alocat astfel:
următoarea poziţie este p mod n +1.

Implementarea Pascal:
TYPE coada=array[1..50] of integer;
var c:coada; p,n,u,o,x:integer;
procedure adaugare(var c:coada; var p,u:integer;x:integer);
begin
if p<>(u mod n)+1 then
begin c[u]:=x; u:=u mod n +1 end
else writeln('coada plina');
end;
56
procedure stergere(var c:coada; var p,u,x:integer);
begin
if p<>u then begin x:=c[p]; p:=p mod n+1 end
else writeln('coada goala');
end;
procedure listare(c:coada;p,u:integer);
var i:integer;
begin
if p=u then writeln('coada goala')
else begin
i:=p;
while i<>u do begin write(c[i],' '); i:=i mod n+1 end;
end;
end;
begin
writeln('nr max de elem');
read(n);n:=n+1;
p:=1;u:=1;
repeat
writeln('1..adaugare');
writeln('2..stergere');
writeln('3..listare');
write('citeste optiunea');readln(o);
case o of
1: begin
write('introduceti numarul adaugat');readln(x);
adaugare(c,p,u,x);
end;
2: begin
stergere(c,p,u,x);
if p<>u then writeln(x);
end;
3: listare(c,p,u);
end;
writeln;
until o=4;
end.

Exerciţiu: Să se implementeze aplicaţia de mai sus în limbajul C++.

57
Problemă rezolvată: Să se creeze un program care să conţină un
meniu cu operaţii asupra unei cozi alocate dinamic.

Rezolvare:
Implementarea în limbajul C++:
#include <conio.h>
#include<stdlib.h>
#include<iostream.h> // intrări-ieşiri în C++
typedef struct nod {int inf; struct nod *urm; }lista;
void adaug(lista* &prim, int x){
lista *nou, *p;
nou=new lista; // crearea unui nod în C++
nou->inf=x;nou->urm=NULL;
if (prim==NULL) prim=nou;
else {
nou->urm=prim; prim=nou;
}
}
lista * creare(){
int n,x;
lista*prim;
prim=NULL;
clrscr();
cout<<"\n nr. de noduri=";cin>>n;
for (int i=0;i<n;i++){
cout<<"inf "<<(i+1)<<":";
cin>>x;
adaug(prim,x);
}
return prim;
}
void parcurgere(lista *prim){
lista *p;
clrscr();
p=prim;
while (p!=NULL){
cout<<p->inf<<' '; p=p->urm;}
cout<<'\n';
getch();
}

58
void sterg(lista *&prim){
lista *p;
if (prim==NULL) cout<<"coada este vida";
else {
p=prim;
while (p->urm->urm!=NULL){p=p->urm;}
delete p->urm; p->urm=NULL;
}
}
void main(){
lista *prim=NULL;
int opt,x;
do{
clrscr();
cout<<"Coada\n";
cout<<"1.Creare coada\n";
cout<<"2.Adaugare\n";
cout<<"3.Stergere\n";
cout<<"4.Parcurgere\n";
cout <<"5.Iesire\n";
do{
cout<<"optiunea";cin >>opt;
}while (opt<1&&opt>5);
switch(opt){
case 1:{prim=creare();break;}
case 2:{
clrscr();cout<<"inf. noua:";cin>>x;
adaug(prim,x);break;
}
case 3:{
clrscr();
cout<<"inf stearsa="<<(prim->inf);
sterg(prim);break;
}
case 4:{
parcurgere(prim); getch();break;
}
}
}while (opt!=5);
}

59
Exerciţiu: Să se realizeze programul în Pascal corespunzător
aplicaţiei de mai sus.

Probleme propuse:
1. Fiind dată o listă simplu înlănţuită având informaţii numere întregi
să se elimine numerele negative.
2. Să se realizeze interclasarea a n liste simplu înlănţuite ordonate
crescător.
3. Fiind date două liste simple cu informaţii de tip întreg, să se
realizeze subprograme pentru următoarele operaţii: intersecţia,
reuniunea, diferenţa şi diferenţa simetrică a elementelor celor
două liste.
4. Să se scrie un program care citeşte cuvintele dintr-un fişier text
in.txt, cuvintele sunt separate prin spaţii şi afişează numărul de
apariţii al fiecărui cuvânt din fişier. Se vor folosi liste simplu
înlănţuite.
5. Să se elimine dintr-o listă simplu înlănţuită toate nodurile care au
o informaţie dată.
6. Să se realizeze operaţii cu matrici rare folosind alocarea dinamica.
7. Fiind dată o listă dublă, să se separe în două liste elementele de pe
poziţii pare de elementele de pe poziţii impare.
8. Fiind dată o listă dublă cu informaţii de tip şir de caractere, să se
facă ordonarea elementelor listei.
9. Fiind dată o listă dublă cu informaţii de tip întreg să se
construiască o listă dublă numai cu elemente prime.
10. Pe o tijă sunt n bile colorate cu cel mult k culori, fiecare bilă
având o etichetă cu un număr de la 1 la n. Să se mute bilele pe alte
k tije, pe fiecare punând numai bile din aceeaşi culoare. Să se
afişeze bilele de pe fiecare din cele k tije, folosind structuri
dinamice.
Indicaţie: Tija iniţială reprezintă o stivă, informaţia dintr-un nod va fi
compusă din numărul bilei şi numărul culorii. Se va parcurge stiva şi
se adaugă în stiva corespunzătoare culorii, apoi se şterge din stiva
iniţială. Se va lucra cu un vector de stive pentru cele k tije.

11. Să se implementeze subprograme pentru operaţiile de bază cu liste


circulare duble.
12. Fie un traseu circular ce cuprinde n oraşe. O maşină trebuie să
parcurgă toate oraşele şi să se întoarcă de unde a plecat.
Parcurgerea se poate face în ambele sensuri. Se cere să se

60
determine un traseu posibil de parcurgere astfel încât să nu rămână
în pană ştiind că în fiecare oraş există o staţie de benzină cu o
cantitate de benzină suficientă, iar maşina consuma c litri la 100
de kilometri.
13. Fiind dată o listă circulară dublă, să se facă un subprogram de
inserare a unui element pe o poziţie dată.
14. Să se realizeze un subprogram care şterge elementele egale cu
zero dintr-o listă circulară.
15. Să se creeze o listă de liste, să se parcurgă şi să se şteargă.
16. Să se scrie un program care citeşte cuvintele dintr-un text şi
afişează numărul de apariţii al fiecărui cuvânt din textul respectiv.
17. Să se scrie un program care citeşte cuvintele dintr-un text şi scrie
numărul de apariţie al fiecărui cuvânt, în ordinea alfabetică a
cuvintelor respective.
18. Într-o gară se consideră un tren de marfă ale cărui vagoane sunt
inventariate într-o listă. Lista conţine, pentru fiecare vagon,
următoarele date: codul vagonului, codul conţinutului vagonului,
adresa expeditorului, adresa destinatarului. Deoarece în gară se
inversează poziţia vagoanelor, se cere listarea datelor despre
vagoanele respective în noua lor ordine.
Indicaţie: se creează o stivă în care se păstrează datele fiecărui vagon.
Datele corespunzătoare unui vagon constituie un element al stivei,
adică un nod al listei simplu înlănţuite. După ce datele au fost puse în
stivă, ele se scot de acolo şi se listează.

19. La o agenţie CEC există un singur ghişeu care se deschide la ora 8


şi se închide la ora 16, dar publicul aflat la coadă este deservit în
continuare. Deoarece la ora închiderii coada este destul de mare se
ridică problema oportunităţii deschiderii a încă unui ghişeu la
agenţia respectivă. Se cunosc operaţiile efectuate la agenţie şi
timpul lor de execuţie. În acest scop se realizează o simulare pe
calculator a situaţiei existente care să stabilească o medie a orelor
suplimentare efectuate zilnic pe o perioadă de un an.
Indicaţie: Programul de simulare a problemei indicate construieşte o
coadă de aşteptare cu persoanele care sosesc la agenţie în intervalul de
timp indicat. Se va folosi funcţia random pentru a determina operaţia
solicitată şi apoi pentru a determina intervalul de timp între două
persoane care vin la agenţie.

61
20. Să se realizeze un program care implementează subprograme
pentru crearea şi exploatarea unei cozi duble.
21. Se dă o matrice rară memorată într-o listă. Se cere:
a) să se afişeze toate elementele diferite de zero de pe diagonala
principală, secundară;
b) să se afişeze pe ecran matricea sub forma obişnuită, fără
reconstituirea ei în memorie.

2.6. Grafuri
Noţiuni introductive
Un graf este o pereche G = <V, M>, unde V este o mulţime de vârfuri,
iar M = VxV este o mulţime de muchii. O muchie de la vârful a la
vârful b este notată cu perechea ordonată (a, b), dacă graful este
orientat, şi cu mulţimea {a, b}, dacă graful este neorientat. Două
vârfuri unite printr-o muchie se numesc adiacente. Un drum este o
succesiune de muchii de forma (a1, a2), (a2, a3), ..., (an-1, an) sau de
forma {a1, a2}, {a2, a3}, ..., {an-1, an} după cum graful este orientat sau
neorientat. Lungimea drumului este egală cu numărul muchiilor care îl
constituie. Un drum simplu este un drum în care nici un vârf nu se
repetă. Un ciclu este un drum care este simplu, cu excepţia primului şi
ultimului vârf, care coincid. Un graf aciclic este un graf fără cicluri.
Un subgraf al lui G este un graf <V', M'>, unde V' ⊆ V, iar M' este
formată din muchiile din M care unesc vârfuri din V'. Un graf parţial
este un graf <V, M">, unde M" ⊆ M.
Un graf neorientat este conex, dacă între oricare două vârfuri
există un drum. Pentru grafuri orientate, această noţiune este întărită:
un graf orientat este tare conex, dacă între oricare două vârfuri i si j
există un drum de la i la j şi un drum de la j la i.
În cazul unui graf neconex, se pune problema determinarii
componentelor sale conexe. O componenta conexă este un subgraf
conex maximal, adică un subgraf conex în care nici un vârf din
subgraf nu este unit cu unul din afară printr-o muchie a grafului iniţial.
Împărţirea unui graf G = <V, M> în componentele sale conexe
determina o partiţie a lui V şi una a lui M.
Un arbore este un graf neorientat aciclic conex. Sau, echivalent,
un arbore este un graf neorientat în care există exact un drum între
oricare două vârfuri. Un graf parţial care este arbore se numeşte
arbore partial. Vârfurilor unui graf li se pot ataşa informaţii numite

62
uneori valori, iar muchiilor li se pot ataşa informaţii numite uneori
lungimi sau costuri.

Metode de reprezentare a grafurilor


a) Matricea de adiacenţă
Cea mai cunoscută metodă de memorare a grafurilor neorientate este
matricea de adiacenţă, definită în felul următor: 1, dacă există muchie
(arc) de la vârful i la vârful j şi 0, în caz contrar. În cazul grafurilor
neorientate, această matrice este simetrică, folosindu-se efectiv numai
jumătate din spaţiul matricei. În unele probleme este eficient ca
cealaltă jumătate a matricei să fie folosită pentru reţinerea altor
informaţii. Matricea de adiacenţă este folosită în general pentru grafuri
cu un număr mic de noduri, deoarece dimensiunea ei este limitată de
dimensiunea stivei.

Exemplu de graf orientat:

8 0 1 2 3 4 5 6 7 8
0 0 0 1 0 0 1 0 0 0
7 1 0 0 0 0 0 0 1 0 0
2 0 0 0 0 0 0 1 0 0
5 3 3 0 0 0 0 1 0 0 0 0
4 0 0 0 0 0 1 0 0 0
2 0 4 5 0 0 0 1 0 0 0 1 0
6 0 0 0 0 0 0 0 0 0
6 1 7 0 0 1 0 0 0 0 0 1
8 0 0 0 0 0 0 0 0 0

b) Listele de muchii
Această metodă de memorare presupune reţinerea unei matrice cu 2
linii şi m coloane, pe fiecare coloană fiind reţinute două extremităţi ale
unei muchii. Aceasta reprezentare este eficientă atunci când avem de
examinat toate muchiile grafului.
63
Listele de muchii sunt folosite în cazul algoritmilor care prelucrează
secvenţial muchiile, cum ar fi de exemplu algoritmul Kruskal de aflare
a arborelui parţial de cost minim în cazul grafurilor rare (cu număr
mic de muchii).

c) Listele de vecini.
Prin liste de vecini (adiacenţă) se realizează ataşarea la fiecare vârf i a
listei de vârfuri adiacente lui (pentru grafuri orientate, este necesar ca
muchia să plece din i). Într-un graf cu m muchii, suma lungimilor
listelor de adiacenţă este 2m, dacă graful este neorientat, respectiv m,
dacă graful este orientat. Dacă numărul muchiilor în graf este mic,
această reprezentare este preferabilă din punct de vedere al memoriei
necesare. Este posibil să examinăm toţi vecinii unui vârf dat, în medie,
în mai puţin de n operaţii. Pe de altă parte, pentru a determina dacă
două vârfuri i şi j sunt adiacente, trebuie să analizăm lista de adiacenţă
a lui i (şi, posibil, lista de adiacenţă a lui j), ceea ce este mai puţin
eficient decât consultarea unei valori logice în matricea de adiacenţă.
Listele de vecini sunt cele mai recomandate în tratarea algoritmilor din
teoria grafurilor din două motive principale:
1. spatiul folosit este gestionat dinamic, putându-se memora astfel
grafuri de dimensiuni mari;
2. complexitatea optimă pentru majoritatea algoritmilor fundamen-
tali din teoria grafurilor (parcurgeri, conexitate, muchii şi puncte
critice, algoritmi de drum minim etc.) este obţinută numai folosind
liste de vecini.
Există două modalităţi de implementare a listelor de vecini.
a) Prima dintre ele foloseşte o matrice T cu 2 linii şi 2m coloane şi
un vector C cu n elemente care reţine pentru fiecare nod indicele
coloanei din T pe care este memorat primul element al listei
nodului respectiv (sau 0 dacă vârful respectiv nu are vecini). Apoi,
pentru o anumită coloană i din T, T(i,1) reţine un element din lista
curentă, iar T(i,2) reţine coloana pe care se găseşte următorul
element din lista respectivă sau 0 dacă lista s-a terminat.
b) Cea de-a doua implementare foloseşte pentru fiecare nod o listă
simplu înlănţuită memorată în heap. Pentru fiecare listă este
suficient să păstrăm o singură santinelă (cea de început a listei),
introducerea vârfurilor făcându-se mereu la începutul listei
(deoarece ordinea vârfurilor în listă nu contează).

64
Exemplu:

8
0 2 5
1 6
7 2 6
3 4
5 3 4 5
5 3 7
2 0 4 6
7 2 8
8
6 1

Parcurgerea grafurilor
Parcurgerea unui graf presupune examinarea în vederea prelucrării
tuturor vârfurilor acelui graf într-o anumită ordine, ordine care să
permită prelucrarea optimă a informaţiilor ataşate grafului.
a) Parcurgerea DF (parcurgerea în adâncime)
Parcurgerea DF (Depth First) presupune ca dintr-un anumit nod v,
parcurgerea să fie continuată cu primul vecin al nodului v nevizitat
încă. Parcurgerea în adâncime a fost formulată cu mult timp în urmă
ca o tehnică de explorare a unui labirint. O persoană care caută ceva
într-un labirint şi aplică această tehnică are avantajul ca “următorul loc
în care caută” este mereu foarte aproape.
Parcurgerea vârfurilor în adâncime se
1 2 face în ordinea: 1 3 4 5 2 6
3
Cel mai cunoscut mod de imple-
mentare a parcurgerii DF se realizează
cu ajutorul unei funcţii recursive, dar
există şi cazuri în care este recomandată
4
o implementare nerecursivă.
6
5 Implementarea acestei metode se
face folosind o stivă. Aceasta este iniţia-
65
lizată cu un nod oarecare al grafului. La fiecare pas, se ia primul vecin
nevizitat al nodului din vârful stivei şi se adaugă în stivă dacă nu mai
există se coboară în stivă.
Algoritmul recursiv:
procedure DF(G)
for fiecare v ∈ V do marca[v] ← nevizitat
for fiecare v ∈ V do
if marca[v] = nevizitat then ad(v)

procedure ad(v)
{varful v nu a fost vizitat}
marca[v] ← vizitat
for fiecare virf w adiacent lui v do
if marca[w] = nevizitat then ad(w)
Algoritmul iterativ:
procedure iterad(v)
S ← stiva vida
marca[v] ← vizitat
push(v, S)
while S nu este vida do
while exista un varf w adiacent lui ftop(S)
astfel incat marca[w] = nevizitat do
marca[w] ← vizitat
push(w, S)
pop(S)

unde funcţia ftop returnează ultimul vârf inserat în stivă.


Parcurgerea în adâncime a unui graf nu este unică; ea depinde
atât de alegerea vârfului iniţial, cât şi de ordinea de vizitare a
vârfurilor adiacente.
Problemă: Cât timp este necesar pentru a parcurge un graf cu n
vărfuri şi m muchii?
Rezolvare:
Deoarece fiecare vârf este vizitat exact o dată, avem n apeluri ale
procedurii ad. În procedura ad, când vizităm un vârf, testăm marcajul
fiecărui vecin al său. Dacă reprezentăm graful prin liste de adiacenţă,
adică prin ataşarea la fiecare vârf a listei de vârfuri adiacente lui,
atunci numărul total al acestor testări este m, dacă graful este orientat,
66
şi 2m, dacă graful este neorientat. Algoritmul necesită un timp O(n)
pentru apelurile procedurii ad şi un timp O(m) pentru inspectarea
mărcilor. Timpul de executare este O(max(m, n)) = O(m+n).
Dacă reprezentăm graful printr-o matrice de adiacenţă, se obţine
un timp de executare de O(n2).

b) Parcurgerea BF (parcurgerea în lăţime)


Parcurgerea BF (Breath First) presupune faptul că după vizitarea unui
anumit nod v, sunt parcurşi toţi vecinii nevizitaţi ai acestuia, apoi toţi
vecinii nevizitaţi ai acestora din urmă, până la vizitarea tuturor nodu-
rilor grafului.
Parcurgerea în lăţime este folosită de obicei atunci când se
explorează parţial anumite grafuri infinite, sau când se caută cel mai
scurt drum dintre două vârfuri.
Implementarea acestei metode se face folosind o coadă. Aceasta
este iniţializată cu un nod oarecare al grafului. La fiecare pas, se
vizitează nodul aflat în vârful cozii şi se adaugă în coadă toţi vecinii
nevizitaţi ai nodului respectiv.

Parcurgere_BF(nod start)
Coada ← start
Vizitat(start) ←adevărat
cât timp coada nu este vidă
ic ←nodul de la începutul cozii
adaugă toti vecinii nevizitati ai lui ic în coadă
Vizitat(i) ← adevărat, unde i este un vecin nevizitat al
nodului ic

Pentru a efectua o parcurgere în laţime a unui graf (orientat sau


neorientat), aplicăm următorul principiu: atunci când ajungem într-un
vârf oarecare v nevizitat, îl marcăm şi vizităm apoi toate vârfurile
nevizitate adiacente lui v, apoi toate vârfurile nevizitate adiacente
vârfurilor adiacente lui v etc. Spre deosebire de parcurgerea în
adâncime, parcurgerea în lăţime nu este în mod natural recursivă.

procedure lat(v)
C ← coada vida
marca[v] ← vizitat
insert-queue(v, C)

67
while C nu este vida do
u ← delete-queue(C)
for fiecare virf w adiacent lui u do
if marca[w] = nevizitat then marca[w] ← vizitat
insert-queue(w, C)
Pentru compararea celor două metode apelăm procedurile iterad şi lat
din procedura parcurgere(G)

procedure parcurge(G)
for fiecare v ← V do marca[v] ← nevizitat
for fiecare v ← V do
if marca[v] = nevizitat then {iterad sau lat} (v)

Aplicaţia 2.6.1: Algoritmul lui Dijkstra


Fie G = (X,U) un graf orientat, unde X este mulţimea vârfurilor şi U
este mulţimea muchiilor. Fiecare muchie are o lungime nenegativă.
Unul din vârfuri este desemnat ca vârf sursă. Problema este să
determinăm lungimea celui mai scurt drum de la sursă către fiecare
vârf din graf. Vom folosi un algoritm greedy, datorat lui E.W. Dijkstra
(1959). Notăm cu C mulţimea vârfurilor disponibile (candidaţii) şi cu
S mulţimea vârfurilor deja selectate. În fiecare moment, S conţine
acele vârfuri a căror distanţă minimă de la sursă este deja cunoscută,
în timp ce mulţimea C conţine toate celelalte vârfuri. La început, S
conţine doar vârful sursă, iar în final S conţine toate vârfurile grafului.
La fiecare pas, adăugam în S acel vârf din C a cărui distanţă de la
sursă este cea mai mică.
La fiecare pas al algoritmului, un tablou D conţine lungimea
celui mai scurt drum special către fiecare vârf al grafului. După ce
adăugăm un nou vârf v la S, cel mai scurt drum special către v va fi,
de asemenea, cel mai scurt dintre toate drumurile către v. Când
algoritmul se termină, toate vârfurile din graf sunt în S, deci toate
drumurile de la sursă către celelalte vârfuri sunt speciale şi valorile din
D reprezintă soluţia problemei.
Matricea M dă lungimea fiecarei muchii, cu M[i, j] = +∞, dacă
muchia (i, j) nu există. Soluţia se va construi în tabloul D[2 .. n].
Algoritmul este:

S = {0}
for i = 1 to n D[i] = M[1][i]

68
for i = 1 to n-2
caut cel mai mic D[v] pentru fiecare v ∉ S
S = S ∪ {v}
for toate vârfurile u ∉ S
if (D[u] > D[v] + M[v][u]) then D[u] = D[v] + M[v][u]

Implementare în C:

#include <stdio.h>
#include <string.h>
#define FALSE 0
#define TRUE 1
#define MAXN 20
int setupDijkstra
(int *nod_p, int a[MAXN][MAXN], int *startnod_p);
void doDijkstra
(int nod, int a[MAXN][MAXN], int startnod);
void traceinfo
(int S[MAXN], int D[MAXN], int nod);
int main(){
int nod, startnod;
int a[MAXN][MAXN];
printf("Algoritmul lui Dijkstra\n");
if (setupDijkstra(&nod, a, &startnod)) return 1;
doDijkstra(nod, a, startnod);
return 0;
}
int setupDijkstra
(int *nod_p, int a[MAXN][MAXN], int *startnod_p){
int i, j;
do{
printf("Introduceti 0 pentru a specifica graful,sau 1 pentru exemple ");
scanf("%d", &i);
}while ((i < 0) || (i > 1));
if (i == 1){
*nod_p = 5;
for (i=0; i<*nod_p; i++)
for (j=0; j<*nod_p; j++) a[i][j]=99;
a[0][1]=a[1][0]=1; a[1][2]=a[2][1]=3; a[0][3]=a[3][0]=2;
a[3][4]=a[4][3]=4; a[1][4]=a[4][1]=2; a[2][4]=a[4][2]=1;

69
printf("A Matrix: a b c d e\n");
for (i=0; i<*nod_p; i++){
printf(" %c", 'a'+i);
for (j=0; j<*nod_p; j++)
printf("%3d", a[i][j]);
printf("\n");
}
*startnod_p = 0;
}
else {
printf("Enter number of nod (1-%d) ", MAXN);
if (scanf("%d", nod_p) != 1) return 1;
if ( (*nod_p < 1) || (*nod_p > MAXN) ) return 2;
for (i=0; i<*nod_p; i++){
printf("Introduceti randul %d matricii: ", i+1);
for (j=0; j<*nod_p; j++)
if (scanf("%d", &a[i][j]) != 1) return 3;
}
printf("introduceti nodul (1-%d) ", *nod_p);
if (scanf("%d", &i) != 1) return 4;
if ( (i < 1) || (i > *nod_p) ) return 5;
*startnod_p = i-1;
}
return 0;
}
void doDijkstra
(int nod, int a[MAXN][MAXN], int startnod){
int S[MAXN];
int D[MAXN]; int i, j, urmnod, mic;
/* initializare */
for (i=0; i<nod; i++) S[i] = FALSE;
S[startnod] = TRUE;
for (i=0; i<nod; i++) {D[i] = a[startnod][i]; }
printf("Initializare\n"); traceinfo(S, D, nod);
for (i=1; i<nod; i++){
urmnod = 999; mic = 999;
for (j=0; j<nod; j++){
if (!S[j]){
if (D[j] < mic){ mic = D[j];urmnod = j;}
}}

70
if (urmnod >= nod) return;
// printf(" D[%c]=%2d, ", 'a'+urmnod, mic);
printf("adauga nodul %c to S\n", 'a'+urmnod);
S[urmnod] = TRUE;
for (j=0; j<nod; j++){
if (!S[j]){
/* printf("Compara D[%c]=%2d si D[%c]+M[%c][%c]=%2d, ", \
'a'+j, D[j], 'a'+urmnod, 'a'+urmnod, 'a'+j, \
D[urmnod]+a[urmnod][j]);
*/
if (D[j] > D[urmnod] + a[urmnod][j]){
D[j] = D[urmnod] + a[urmnod][j];
/*printf("D[%c] schimba %2d\n", 'a'+j, D[j]); */
}
else
/* printf("nu schimba\n"); */
}
}
traceinfo(S, D, nod);
}
}
void traceinfo(int S[MAXN], int D[MAXN], int nod){
int i;
printf(" S = {");
for (i=0; i<nod; i++) if (S[i]) printf("%c", 'a' + i);
printf("}\n");
printf(" D = ");
for (i=0; i<nod; i++) printf("%3d", D[i]);
printf("\n");
}

Aplicaţia 2.6.2: Algoritmul lui Bellman-Ford (determinarea drumu-


rilor minime dintre un nod şi celelalte noduri într-un graf care poate
să aibă cicluri negative).
Dacă graful conţine muchii de cost negativ, algoritmul Dijkstra nu mai
funcţionează corect, deoarece nu putem găsi - acum - nodurile cele
mai apropiate de sursă, în ordine crescătoare a distanţei faţă de
aceasta. Această problemă este rezolvată de algoritmul Bellman-Ford,
care în plus determină şi existenţa ciclurilor de cost negativ care pot fi
atinse pornind din nodul sursă. Ca şi în cazul algoritmului Dijkstra,
71
vom folosi vectorul D care reţine distanţa minimă găsită la un moment
dat de la s la celelalte noduri. Algoritmul foloseste o coadă Q, care
este iniţializată cu nodul s. La fiecare pas, algoritmul scoate un nod v
din coadă şi găseşte toate nodurile w a căror distanţă de la sursă la
acestea poate fi optimizată prin folosirea nodului v. Dacă nodul w nu
se află deja în coadă, el este adăugat acesteia. Aceşti paşi se repetă
până când coada devine vidă.
Algoritmul utilizează tehnica de relaxare, procedând la
descreşterea estimării d[v] a drumului minim de la sursa s la fiecare
vârf v ∈V până când este obţinut costul adevărat δ(u,v) corespunzător
unui drum minim. Algoritmul returnează adevarat dacă şi numai dacă
nu conţine cicluri de cost negativ accesibile din sursă.
Algoritmul este:
BELLMAN-FORD(G,c,s)
1.INIT(G,s)
2. pentru i 1,|X[G]|-1 executa
3. pentru fiecare muchie (u,v) ∈U
4. RELAXEAZA(u,v,c)
5. pentru fiecare muchie (u,v) ∈U executa
6. daca d[v]>d[u]+c(u,v)
7. returneaza FALS
8. returneaza ADEVARAT

Implementare în C:
#include <stdio.h>
#include <string.h>
#define FALSE 0
#define TRUE 1
#define MAXN 26
#define INFINIT 999
int setupBellman(int *nod_p, int a[MAXN][MAXN], int *start_p);
int doBellman(int nod, int a[MAXN][MAXN], int start);
void traceinfo(int D[MAXN], int nod);
int main(){
int nod, start; int a[MAXN][MAXN];
printf("Algoritmul lui Bellman-Ford\n");
if (setupBellman(&nod, a, &start)) return 1;
if (doBellman(nod, a, start)){
printf(" Ciclu negativ\n"); return 2;}
return 0;}
72
int setupBellman
(int *nod_p, int a[MAXN][MAXN], int *start_p){
int i, j;
do{
printf("Introduceti 0 pentru graf, or 1 pentru exemple ");
scanf("%d", &i);
}while ((i < 0) || (i > 1));
if (i == 1){
*nod_p = 5;
for (i=0; i<*nod_p; i++) for (j=0; j<*nod_p; j++) a[i][j]=99;
a[0][1]=a[1][0]=1; a[1][2]=a[2][1]=3; a[0][3]=a[3][0]=2;
a[3][4]=a[4][3]=4; a[1][4]=a[4][1]=2; a[2][4]=a[4][2]=1;
printf("A Matrix: a b c d e\n");
for (i=0; i<*nod_p; i++){
printf(" %c", 'a'+i);
for (j=0; j<*nod_p; j++) printf("%3d", a[i][j]);
printf("\n");
}
*start_p = 0;
}
else{
printf("Introduceti nr. noduri (1-%d) ", MAXN);
if (scanf("%d", nod_p) != 1) return 1;
if ( (*nod_p < 1) || (*nod_p > MAXN) ) return 2;
for (i=0; i<*nod_p; i++){
printf("Introduceti randul %d al matricii: ", i+1);
for (j=0; j<*nod_p; j++)
if (scanf("%d", &a[i][j]) != 1) return 3;
}
printf("Introduceti nodul de start (1-%d) ", *nod_p);
if (scanf("%d", &i) != 1) return 4;
if ( (i < 1) || (i > *nod_p) ) return 5;
*start_p = i-1;
}
return 0;
}

int doBellman(int nod, int a[MAXN][MAXN], int start){


int D[MAXN]; int i, u, v;
/* initializare */

73
for (i=0; i<nod; i++){
if (i == start) D[i] = 0; else D[i] = INFINIT;
}
printf("Initializared\n"); traceinfo(D, nod);
for (i=1; i<nod; i++){
for (u=0; u<nod; u++){
for (v=0; v<nod; v++){
if (a[u][v] < 99){
/*
printf("Compara D[%c]=%3d si D[%c]+M[%c][%c]=%3d, ",\
'a'+v, D[v], 'a'+u, 'a'+u, 'a'+v, D[u]+a[u][v]);
*/
if (D[v] > D[u] + a[u][v]){
D[v] = D[u] + a[u][v];
printf("D[%c] schimba to %2d\n", 'a'+v, D[v]);
}
else printf("nu schimba\n");
}
}
}
traceinfo(D, nod);
}
for (u=0; u<nod; u++){
for (v=0; v<nod; v++){
if (a[u][v] < 99){
/*printf("Compara D[%c]=%3d and D[%c]+M[%c][%c]=%3d, ",\
'a'+v, D[v], 'a'+u, 'a'+u, 'a'+v, D[u]+a[u][v]);
*/
if (D[v] > D[u] + a[u][v]) return 1;
else printf("nu exista ciclu negativ\n");
}
}
}
return 0;
}
void traceinfo(int D[MAXN], int nod){
int i;
printf(" D = ");
for (i=0; i<nod; i++) printf("%4d", D[i]);
printf("\n");}

74
Probleme rezolvate

R2.6.1. Să se afişeze componentele conexe folosind parcurgerea DF a


grafului. Graful este reprezentat prin matricea de adiacenţă citită din
fişier TEXT.

Rezolvare: Implementarea Pascal folosind parcurgerea DF recursivă:


var a:array[1..100,1..100] of integer;
viz:array[1..100] of integer;
f:text;n,i,j:integer;
procedure df(X:integer);
var i:byte;
begin
write(x, ' ');
viz[x]:=1;
for i:=1 to n do if (a[x,i]=1) and (viz[i]=0) then df(i);
end;
procedure citire;
begin
assign(f,'matrice.txt');
reset(f);
readln(f,n);
for i:=1 to n do begin
for j:=1 to n do read(f,a[i,j]);
readln(f);
end;
end;
begin
citire;
for i:=1 to n do viz[i]:=0;
for i:=1 to n do if viz[i]=0 then begin df(i);writeln end;
end.

Exerciţiu:
Implementaţi în lumbajul C aplicaţia de mai sus folosind funcţia Df
(iterativă) de mai jos.

void df(int x){


int i,k,y,s[10],gasit;
k=1;

75
s[1]=x;viz[x]=1;printf("%d ",x);
while (k>=1) /*cat timp stiva este nevida */ {
y=s[k];gasit=0;
for(i=1;i<=n;i++)
if (viz[i]==0&&a[y][i]==1) {
printf("%d ",i); //vecin nevizitat
viz[i]=1;
s[++k]=i;
gasit=1;
break;
}
if (!gasit) k--;
}
}

R2.6.2. Să se parcurgă un graf reprezentat prin matricea sa de


adiacenţă folosind parcurgerea BF.
Rezolvare:
Implementare în limbajul Pascal:
Var a:array[1..100,1..100] of integer;
viz,c:array[-2..100] of integer;
f:text; n,i,j,v,p,u,x:integer;
procedure citire;
begin
assign(f,'matrice.txt');
reset(f);
readln(f,n);
for i:=1 to n do begin
for j:=1 to n do read(f,a[i,j]);
readln(f);
end;
write('Dati nodul de plecare ');
readln(v);
end;
procedure bf(v:integer);
begin
viz[v]:=1;write(v,' ');
c[1]:=v;
p:=1;
u:=1;
76
while p<=u do begin
x:=c[p]; p:=p+1;
for i:=1 to n do if (viz[i]=0) and (a[i,x]=1) then begin
write(i,' '); viz[i]:=1; u:=u+1; c[u]:=i;
end;
end;
end;
begin citire; for i:=1 to n do viz[i]:=0; bf(v) end.
Exerciţiu: Să se implementeze aplicaţia de mai sus în limbajul C.

R2.6.3. Să se determine componentele tari conexe ale unui graf


orientat.
Rezolvare:
Componenta tare conexă care va conţine vârful i va fi dată de sub-
graful indus de multimea nodurilor j pentru care a[i,j]=a[j,i]=1, unde a
este matricea drumurilor .
Implementare Pascal:
program tare_conexe;
const nmax=100;
type graf = array[1..nmax,1..nmax] of byte;
marcaj = array[1..nmax] of boolean;
var a:graf;
i,j,k,n:integer;
m:marcaj;
modificare:boolean;
begin
write('Dati nr de noduri: '); readln(n);
writeln('dati arcele grafului (i,j) :');
writeln('0 0 pentru sfarsit ');
readln(i,j);
while i*j<>0 do begin
a[i,j]:=1; readln(i,j);
end;
for k:=1 to n do for i:=1 to n do for j:=1 to n do
if a[i,j]=0 then a[i,j]:=a[i,k]*a[k,j];
k:=1;
for i:=1 to n do
if not m[i] then begin
77
write('componenta tare conexa ',k,':',i,' ');
for j:=1 to n do
if (j<>i) and (a[i,j]<>0)and (a[j,i]<>0) then
begin
write(j,' ');
m[j]:=true;
end;
inc(k);
writeln;
end;
end.
Implementare în limbajul C:
#include <stdio.h>
int n, a[10][10],viz[10];
void citire(){
int m,i,j,x,y;
printf("n=");scanf("%d",&n); printf("m=");scanf("%d",&m);
for(i=1;i<=n;i++) for(j=1;j<=n;j++)a[i][j]=0;
/*citirea arcelor si construirea matricei de adiacenta */
for(i=1;i<=m;i++) { scanf("%d %d",&x,&y);a[x][y]=1;}
}
void main(){
int k,i,j,m[10];
citire();
//se construieste matricea drumurilor
for (k=1;k<=n;k++) for(i=1;i<=n;i++) for(j=1;j<=n;j++)
if(a[i][j]==0) a[i][j]=a[i][k]&&a[k][j];
k=1; for (i=1;i<=n;i++)m[i]=0;
for(i=1;i<=n;i++)
if(!m[i]) {
/* daca varful i nu a fost introdus intr- componenta tare conexa */
printf("componenta tare conexa %d: %d ",k,i);
for (j=1;j<=n;j++)
if (j!=i&&a[i][j]==1&&a[j][i]==1){
printf("%d ",j); m[j]=1;
}
k++;
printf("\n");
}
}
78
R2.6.4. Dându-se două grafuri reprezentate prin matricea de adia-
cenţă să se precizeze dacă cel de-al doilea graf este graf parţial sau
subgraf al primului graf.
Rezolvare:
Implementarea Pascal este:
uses crt;
type mat=array[1..100,1..100] of integer;
var a,b:mat;
n,m,n1,m1,i,j:integer;
procedure creare;
var x,y,i,j:integer;
begin
writeln(' MARTICEA 1');
write('Dati numarul de varfuri');
readln(n);
write('Dati numarul de muchii');
readln(m);
for i:=1 to n do for j:=1 to n do a[i,j]:=0;
for i:=1 to m do begin
write('Dati primul capat al muchiei ',i,' : ');
readln(x);
write('Dati al doilea capat al muchiei ',i,' : ');
readln(y);
a[x,y]:=1; a[y,x]:=1
end
end;
procedure creare1;
var x,y,i,j:integer;
begin
WRITELN(' MATRICEA 2');
write('Dati numarul de varfuri');readln(n1);
write('Dati numarul de muchii');readln(m1);
for i:=1 to n1 do for j:=1 to n1 do b[i,j]:=0;
for i:=1 to m1 do begin
write('Dati primul capat al muchiei ',i,' : '); readln(x);
write('Dati al doilea capat al muchiei ',i,' : '); readln(y);
b[x,y]:=1;
b[y,x]:=1;
end;
end;
79
procedure subgraf;
var s:boolean;
begin
s:=true;if n1>n then s:=false;
for i:=1 to n1 do for j:=1 to n1 do
if (a[i,j]=1) and (b[i,j]=0) then s:=false;
if s=false then writeln(' B nu e subgraf al lui A.')
else writeln(' B e subgraf al lui A.');
end;
procedure graf_p;
var g:boolean;
begin
g:=true;if n<> n1 then g:=false;
for i:=1 to n do for j:=1 to n do
if (a[i,j]=0) and (b[i,j]=1) then g:=false;
if g=false then writeln(' B nu e graf partial al lui A.')
else writeln(' B e graf partial al lui A.');
end;
begin
clrscr;
creare;creare1;
graf_p;subgraf;
end.
Exerciţiu: Să se implementeze aplicaţia de mai sus în limbajul C.

R2.6.5. Determinarea drumurilor minime între oricare două noduri.


Rezolvare: Se foloseşte algoritmul lui ROY-FLOYD. Se porneşte de
la matricea costurilor C.
pentru k=1,n execută
pentru i=1,n execută
pentru j=1,n execută
c[i,j]=min(c[i,j],c[i,k]+c[k,j])
Simultan cu determinarea lungimilor minime ale drumurilor pot fi
reţinute drumurile folosind un tablou d, unde d[i,j] reprezintă mulţi-
mea predesesorilor lui j pentru drumul minim de la i la j.
Implementare Pascal:
Var c:array[1..100,1..100] of longint; v:array[1..100]of integer;
d:array[1..100,1..100] of set of 1..10;
n,m,i,j,k,nr:integer; x,y:integer;
80
procedure citire;
var cost,x,y:integer;
begin
write('nr. de noduri');readln(n);
write('nr. de muchii');readln(m);
for i:=1 to n do
for j:=1 to n do if i<>j then c[i,j]:=maxint else c[i,j]:=0;
for i:=1 to m do begin
write('Dati capetele arcului si costul ');
readln(x,y,cost); c[x,y]:=cost;
end;
end;
procedure initializare;
{determina multimea predecesorilor, initializeaza multimea D}
begin
for i:=1 to n do
for j:=1 to n do
if (i<>j) and (c[i,j]<maxint) then d[i,j]:=[i] else d[i,j]:=[];
end;
procedure drum(i,j:integer); {reconstituirea drumului}
var k:integer;
begin
if i<>j then
for k:=1 to n do if k in d[i,j] then begin
inc(nr);
v[nr]:=k;
drum(i,k);
dec(nr);
end
else begin
for k:=nr downto 1 do write(v[k], ' ');
writeln;
end;
end;
begin
citire;initializare;
for k:=1 to n do for i:=1 to n do for j:=1 to n do
if c[i,j]>c[i,k]+c[k,j] then
begin c[i,j]:=c[i,k]+c[k,j]; d[i,j]:=d[k,j] end
else if c[i,j]=c[i,k]+c[k,j] then d[i,j]:=d[i,j]+d[k,j];

81
for i:= 1 to n do begin
for j:=1 to n do write(c[i,j]:4);
writeln
end;
write('Dati doua noduri'); readln(x,y);
writeln('costul minim= ', c[x,y], ' drumurile minime=' );
for x:=1 to n do for y:=1 to n do
begin
writeln('drum de la ',x,'la ',y);
if c[x,y]=maxint then write('nu exista')
else begin
nr:=1;v[1]:=y;
drum(x,y)
end
end
end.
Exerciţiu: Să se implementeze algoritmul Roy-Floyd în limbajul C.

R2.6.6. Să se verifice dacă un graf reprezentat dinamic este conex sau


complet.
Rezolvare:
Se foloseşte un tablou de liste unde bl[i] reprezintă lista vecinilor lui i.
Implementare Pascal:
type lista=^nod;
nod= record inf:integer; urm:lista end;
var bl:array[1..100] of lista;
f:text;
i,n,x:integer;
complet:boolean;
procedure adaugare (var p:lista;x:integer);
var t,q:lista;
begin
new(q);q^.inf:=x;q^.urm:=nil;
if p=nil then p:=q
else begin
t:=p;while t^.urm<>nil do t:=t^.urm;
t^.urm:=q;
end;
end;
82
procedure listare(p:lista);
var t:lista;
begin
t:=p;
while t<> nil do begin
write(t^.inf,' ');
t:=t^.urm
end;
writeln;
end;
procedure df(i:integer);
var q,t,st:lista;
viz:array [1..100] of integer;
conex,gasit:boolean;
j:integer;
begin
for j:=1 to n do viz[j]:=0;viz[i]:=1;write(i,' ');new(q);q^.inf:=i;
q^.urm:=nil;st:=q;
while st<>nil do begin
x:=st^.inf; t:=bl[x]; gasit:=false;
while t<> nil do begin
if viz[t^.inf]=0 then begin
new(q); q^.inf:=t^.inf; q^.urm:=nil; viz[t^.inf]:=1;
write(t^.inf,' '); q^.urm:=st; gasit:=true; st:=q; break;
end;
t:=t^.urm;
end;
if not gasit then begin t:=st; st:=st^.urm; dispose(t) end;
end;
conex:=true;
for j:=1 to n do if viz[j]=0 then conex:=false;
if conex then write('conex') else write('nu este conex');
end;
function numarare( p:lista):integer;
var nr:integer; t:lista;
begin
nr:=0;t:=p;
while t<>nil do begin nr:=nr+1; t:=t^.urm end;
numarare:=nr
end;

83
begin
assign(f,'in.txt');reset(f);
readln(f,n);
for i:=1 to n do begin
while not eoln(f) do begin
read(f,x);
adaugare(bl[i],x)
end;
readln(f);
end;
writeln;
df(1);
complet:=true;
for i:=1 to n do if numarare(bl[i])<>n-1 then complet:=false;
if complet then write('complet') else write ('incomplet');
end.
Exerciţiu: Să se implementeze aplicaţia de mai sus în limbajul c.
R2.6.7. Fiind dat un graf, să se verifice dacă este aciclic.
Rezolvare:
Un graf este aciclic dacă este fără cicluri. Presupunem că fiecare nod
face parte dintr-o componentă conexă. Se ia fiecare muchie şi dacă
extremităţile sunt în aceeaşi componentă conexă atunci adăugând
aceea muchie se formează ciclu; dacă nu sunt în aceeaşi componentă
conexă toate nodurile care sunt în aceeaşi componentă cu extremitatea
a doua trec în componenta conexă a extremităţii 1.
Implementare Pascal:
Var f:text;
a:array[1..1000]of integer; n:integer; nume:string;
procedure ciclic;
var i,j,k:integer;
begin
writeln('Care este numele fisierului de intrare ?');
readln(nume);
assign(f,nume); reset(f);
readln(f,n);
for i:=1 to n do a[i]:=i;
while not seekeoln(f) do
begin
readln(f,i,j);
84
if a[i]=a[j] then
begin
writeln('Graful din fisierul ',nume,' contine cel putin un ciclu');
close(f); halt;
end;
for k:=1 to n do if a[k]=a[j] then a[k]:=a[i];
end;
writeln('Graful din fisierul ',nume,' nu contine cicluri');
close(f);
end;
begin
ciclic
end.
Implementare C:
#include <stdlib.h>
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
FILE *f;
int n,a[10];
void ciclic(){
int i,j,k;
fscanf(f,"%d",&n);
for(i=1;i<=n;i++)a[i]=i;
fscanf(f,"%d %d",&i,&j);
if (a[i]==a[j]) {
cout<<"contine ciclu";
fclose(f);
exit(1);
}
for (k=1; k<=n; k++){
if (a[k]==a[j]) a[k]=a[i];
}
cout<<"nu contine cicluri";
}
void main(void){
f=fopen("in.txt","r");clrscr();
ciclic();
}

85
R2.6.8. Se dă un graf şi un nod - considerat ca nodul nr. 1; se
cere să se determine toate nodurile accesibile din acest nod.
Rezolvare:
Implementare Pascal:
var
a: array[1..20,1..20] of integer; {matricea de adiacenta}
c: array[1..20] of integer;
{vectorul unde pastram nodurile accesibile}
n,i,j,k,p:integer; gasit:boolean;
begin
write('dati numarul de noduri; n=');readln(n);
writeln('dati matricea de adiacenta:');
for i:=1 to n do for j:=1 to n do read(a[i,j]);
c[1]:=1; k:=1; gasit:=true; i:=1;
while i<=k do begin
for j:=1 to n do
if a[i,j]=1 then begin
for p:=1 to k do
if c[p]=j then gasit:=false;
if gasit then begin k:=k+1; c[k]:=j end
end;
i:=i+1
end;
if k=n then writeln('Toate nodurile sunt accesibile.')
else for i:=1 to k do write(c[i],',');
end.
Exerciţiu:Să se implementeze aplicaţia de mai sus în limbajul C.

R2.6.9. Determinarea ciclului de lungime 3 dintr-un graf.


Rezolvare:
Implementarea Pascal:
var i,j,n,k:integer;
a:array[1..50,1..50] of integer;
f:text;
begin
assign(f,'c:\lucru\graf.txt');
reset(f);
read(f,n);
86
for i:=1 to n do for j:=1 to n do a[i,j]:=0;
while not eof(f) do begin
readln(f,i,j);
a[i,j]:=1; a[j,i]:=1
end;
for i:=1 to n-2 do for j:=i+1 to n-1 do
for k:=j+1 to n do
if (a[i,j]=1) and (a[j,k]=1) and (a[k,i]=1) then writeln(i,' ',j,' ',k);
end.
2.7. Arbori
Fie G un graf orientat. G este un arbore cu radacina r, dacă există în
G un vârf r din care oricare alt vârf poate fi ajuns printr-un drum unic.
Adâncimea unui vârf este lungimea drumului dintre rădăcina şi
acest vârf; înălţimea unui vârf este lungimea celui mai lung drum
dintre acest vârf şi un vârf terminal. Înălţimea arborelui este înalţimea
rădăcinii; nivelul unui vârf este înălţimea arborelui minus adâncimea
acestui vârf.
Reprezentarea unui arbore cu rădăcină se poate face prin adrese,
ca şi în cazul listelor înlănţuite. Fiecare vârf va fi memorat în trei
locaţii diferite, reprezentând informaţia propriu-zisă a vârfului (valoa-
rea vârfului), adresa celui mai vârstnic fiu şi adresa următorului frate.
Păstrând analogia cu listele înlănţuite, dacă se cunoaşte de la început
numărul maxim de vârfuri, atunci implementarea arborilor cu rădăcina
se poate face prin tablouri paralele.
Dacă fiecare vârf al unui arbore cu rădacină are până la n fii,
arborele respectiv este n-ar.
Într-un arbore binar, numărul maxim de vârfuri de adâncime k
este 2k. Un arbore binar de înălţime i are cel mult 2i+1-1 vârfuri, iar
dacă are exact 2i+1-1 varfuri, se numeste arbore plin. Vârfurile unui
arbore plin se numerotează în ordinea adâncimii.
Un arbore binar cu n vârfuri şi de înălţime i este complet, dacă
se obţine din arborele binar plin de înălţime i, prin eliminarea, dacă
este cazul, a vârfurilor numerotate cu n+1, n+2, …, 2i+1-1. Acest tip de
arbore se poate reprezenta secvenţial folosind un tablou T, punând
vârfurile de adâncime k, de la stânga la dreapta, în poziţiile T[2k],
T[2k+1], …, T[2k+1-1] (cu posibila excepţie a nivelului 0, care poate fi
incomplet). Un arbore binar este un arbore în care fiecare vârf are cel
mult doi descendenţi, făcându-se distincţie între descendentul stâng şi
descendentul drept al fiecărui vârf.
87
Reprezentarea arborilor binari
Reprezentarea prin paranteze: se începe cu rădăcina arborelui, iar
fiecare vârf care are descendenţi este urmat de expresiile ataşate
subarborilor care au ca rădăcină descendenţii vârfului, despărţite prin
virgulă şi cuprinse între paranteze. Dacă lipseşte un descendent sub-
expresia corespunzătoare este cuvântul vid. Pentru arborele de mai
sus, reprezentarea este: 6(2(1,(4(3,5)), 8(7, 9(,10)))
Reprezentarea standard: În care pentru fiecare vârf i este precizat des-
cendentul său stâng ST(i) descendentul său drept DR(i) = i informaţia
asociată v`rfului INF(i):
♦ static:ST şi DR sunt două tablouri ST=(0, 1, 0, 3, 0, 2, 0,7, 0, 0)
DR=(0, 4, 0, 5, 0, 8, 0, 9,10,0) rad=6
♦ dinamic:

type arbore=^nod; typedef struct nod{


nod=record int inf;
inf:integer; struct nod *st;
st,dr:arbore; struct nod *dr;
end; }arbore;
si se declara arbore *rad;

Reprezentarea cu doi vectori DESC şi TATA: În vectorul DESC,


având valori –1, 1,0 se precizează pentru fiecare nod ce fel de
descendent este el pentru părintele său iar în vectorul TATA se indică
pentru fiecare vârf, nodul părinte. Pentru exemplul de mai sus:
TATA=(2,6,4,2,4,0,8,6,8,9) DESC=(-1,-1,-1,1,1,0,-1,1,1,1)

88
Parcurgerea arborilor binari
♦ preordine:se vizitează rădăcina, se traversează subarborele stâng
în preordine, se traversează subarborele drept în preordine;
♦ inordine: se traversează subarborele stâng în inordine, se
vizitează rădăcina, se traversează subarborele drept în inordine;
♦ postordine: se traversează subarborele stâng în postordine, se
traversează subarborele drept în postordine, se traversează
rădăcina.

Aplicaţia 2.7.1. Să se creeze un arbore binar şi apoi să se parcurgă în


preordine, să se caute un nod în arbore, să se afişeze cheile de pe un
anumit nivel dat, să se afişeze drumurile de la rădăcină la frunze şi să
se calculeze adâncimea arborelui creat.
Rezolvare:
Implementare Pascal:
program arb_binar;
type arbore=^nod;
nod =record st,dr:arbore; inf:integer end;
var p:arbore;k,x,y,max,z:integer;a:array[1..100] of integer;
{Procedura creare arbore binar}
procedure creare(var p:arbore);
var x:integer;
begin
read(x);
if x=0 then p:=nil
else begin
new(p); p^.inf:=x;
write('Dati stanga lui ',x); creare(p^.st);
write('Dati dreapta lui ',x); creare(p^.dr);
end;
end;
{Procedura parcurgere arbore binar - prin preordine}
procedure preordine(p:arbore);
begin
if p<>nil then begin
write(p^.inf,' ');
preordine(p^.st); preordine(p^.dr);
end;
end;
89
{Cautarea unui nod din arbore}
function cautare(p:arbore; x:integer):boolean;
begin
if p=nil then cautare:=false
else if x=p^.inf then cautare:=true
else cautare:= cautare(p^.st,x) or cautare(p^.dr,x);
end;
{Afisarea tuturor cheilor de pe nivelul dat}
procedure nivel(p:arbore;k,x:integer);
begin
if p<>nil then begin
if k=x then write(p^.inf,' ');
nivel(p^.st,k+1,x);
nivel(p^.dr,k+1,x);
end;
end;
{Drumurile de la radacina la frunze}
procedure drum_frunze(p:arbore;k:integer);
var i:integer;
begin
if (p^.st=nil)and(p^.dr=nil) then begin
a[k]:=p^.inf;
for i:=1 to k do write(a[i],' ');
writeln
end
else if p<>nil then begin
a[k]:=p^.inf;
drum_frunze(p^.st,k+1);
drum_frunze(p^.dr,k+1);
end;
end;
{Afisarea numarului maxim de nivele}
procedure adancime(p:arbore;k:integer; var max:integer);
begin
if p<>nil then begin
if k>max then max:=k;
adancime(p^.st,k+1,max);
adancime(p^.dr,k+1,max);
end
end;

90
begin
creare(p);
preordine(p);
{Pentru procedura cautare}
read(x);
writeln(cautare(p,x));
{Pentru procedura nivel}
write('Nivelul= ');read(y);nivel(p,0,y);
{Pentru procedura drum_frunze}
drum_frunze(p,1);
{Pentru procedura adancime}
adancime(p,0,max);
writeln('Numarul de nivele este: ',max);
end.
Implementare în C++ :
#include <stdio.h>
#include <alloc.h>
#include <iostream.h>
#include <conio.h>
typedef struct nod{
int inf;
struct nod *st, *dr;
}arbore;
arbore *rad;
int a[10], max=0;
arbore *creare(){
int x;arbore *p;
cout<<"inf"; cin>>x;
if (x==0) return NULL;
else { (p)=(arbore*) malloc(sizeof(arbore)); (p)->inf=x;
cout<<"inf st. a lui"<<x; p->st=creare();
cout<<"inf dreapta a lui"<<x; p->dr=creare();
}
return p;
}
void preordine(arbore *p){
if (p){
printf("%d ",p->inf); preordine(p->st); preordine(p->dr);
}
}
91
void inordine(arbore *p){
if (p){
inordine(p->st); printf("%d",p->inf); inordine(p->dr);
}
}
void postordine(arbore *p){
if (p){
postordine(p->st); postordine(p->dr); printf("%d",p->inf);
}
}
int cautare(arbore *p, int x){
if(p){
if (p->inf==x) return 1;
return cautare(p->st,x)||cautare(p->dr,x);
}
else return 0;
}
void nivel(arbore*p, int k,int x){
if(p){
if (k==x) printf("%d ",p->inf);
nivel(p->st,k+1,x); nivel(p->dr,k+1,x);
}
}
void drum_frunze(arbore*p,int k){
int i;
if ((p->st)&&(p->dr)) {
a[k]=p->inf;
for (i=1;i<=k;i++) printf("%d ",a[i]);printf("\n");
}
else{
if(p) {
a[k]=p->inf;
drum_frunze(p->st,k+1); drum_frunze(p->dr,k+1);
}
}
}
void adancime(arbore *p, int k){
if (p) {
if (k>max) max=k; adancime(p->dr,k+1) ;adancime(p->st,k+1);
}}

92
void main(){
int x;
rad=creare();
preordine(rad);
printf("inf cautata:");scanf("%d", &x);
if (cautare(rad,x)) printf("exista");else printf("nu exista");
cout<<"nivelul";cin>>x;
cout<<"nodurile de pe nivelul "<<x<<":";nivel(rad,0,x);
cout<<"drumuri:";drum_frunze(rad,1);
adancime(rad,0);
printf("adancimea=%d",max);
}
Arbori binari de căutare
Arborele binar de căutare este un arbore binar în care pentru orice nod,
cheia din succesorul său stâng este mai mică decât cheia din nod, iar
cheia din succesorul său drept este mai mare decât cheia din nod. În
arborele binar de căutare informaţia din noduri este unică. Prin
parcurgerea în inordine a arborelui de căutare se obţine în ordine
crescătoare valorile din câmpurile cheie.
Crearea se realizează adăugând în arbore rând pe rând câte un
nod corespunzător fiecărei informaţii.
Adăugarea unu nod:
♦ dacă arborele nu are nici un nod se crează rădăcina;
♦ dacă arborele există se compară informaţia nodului nou cu cea din
nodul curent. Dacă este mai mare, se reia procesul pentru
subarborele din dreapta; dacă este mai mică, se reia pentru
subarborele din stânga, iar în caz de egalitate se afişează eroare.
Căutarea se realizează astfel: se caută în nodul curent; dacă
informaţia s-a găsit, algoritmul se încheie, astfel dacă informaţia este
mai mare decât informaţia din nodul curent, se caută în subarborele
drept al acestuia, altfel în subarborele stâng.
Aplicaţia 2.7.2. Program pentru crearea şi exploatarea unui arbore
binar de căutare.
Rezolvare:
Implementare Pascal:
type arbore=^nod;
nod = record inf:integer; st,dr:arbore end;
var p:arbore;x:integer;
93
procedure inserare(var p:arbore;x:integer);
var q:arbore;
begin
new(q);
q^.inf:=x;
q^.st:=nil;
q^.dr:=nil;
if p=nil then p:=q
else if p^.inf=x then write('Exista')
else if p^.inf<x then inserare(p^.dr,x)
else inserare(p^.st,x)
end;
procedure srd(p:arbore);
begin
if p<>nil then begin srd(p^.st); write(p^.inf,' '); srd(p^.dr) end
end;
procedure stergere(var p:arbore;x:integer);
var t:arbore;
begin
if p=nil then write('nu exista')
else if p^.inf<x then stergere(p^.dr,x)
else if p^.inf>x then stergere(p^.st,x)
else if p^.st=nil then begin
t:=p; p:=p^.dr; dispose(t)
end
else if p^.dr=nil then begin
t:=p;p:=p^.st;dispose(t);
end
else begin
t:=p^.st;
while t^.dr^.dr<>nil do t:=t^.dr;
p^.inf:=t^.dr^.inf; dispose(t^.dr);
t^.dr:=nil
end
end;
begin
read(x);
while x<>0 do begin inserare(p,x); read(x) end;
srd(p);read(x);stergere(p,x);srd(p);
end.

94
Implementare C++:
#include<iostream.h>
#include<conio.h>
#include<stdio.h>
typedef struct nod {int inf; nod *st,*dr;}arbore;
arbore *rad;
arbore *q;
void adaug(arbore* &p,int x){
if (p==NULL){
p=new nod;
p->inf=x;
p->st=p->dr=NULL;
}
else if (p->inf>x) adaug(p->st,x);
else if (p->inf<x) adaug(p->dr,x);
else cout <<"informatia exista";
}
void creare(arbore* &p){
int x,n,i;
cout <<"n=";cin>>n;
for (i=1;i<=n;i++){
cout<<"inf";cin>>x;
adaug(p,x);
}
}
void inordine(arbore *p){
if (p){
inordine(p->st); printf("%d",p->inf); inordine(p->dr);
}
}
arbore *cauta(arbore*p, int x){
if (p==NULL) return NULL;
else if (p->inf<x) {
q=p;p=p->st;cauta(p,x);
}
else if (p->inf>x){
q=p;p=p->st; cauta(p,x);
}
else return p;
}

95
void sterge(arbore *&r,int x){
arbore *t,*q1,*p;
int a;
t=cauta(r,x);
if(t==NULL){
cout<<"informatia nu se gaseste";
getch();
}
else if (t->dr==t->st){
if (t->inf<q->inf) q->st=NULL;
delete t;
}
else if (t->st==NULL&&t->st){
if (q->inf<t->inf) q->dr=t->st; else q->st=t->st;
delete t;
}
else if (t->st==NULL&&t->dr){
if(q->inf>t->inf) q->st=t->dr; else q->dr=t->dr;
delete t;
}
else {
p=t;
while (p->st!=NULL){q=p;p=p->st;}
}
q1=t;
t->inf=p->inf;
if (p->dr==p->st){
q->st=NULL;
delete p;
}
else {
q->st=p->dr;
delete p;
}
while (q1->st&&q1->inf<q1->st->inf){
a=q1->inf;
q1->inf=q1->st->inf; q1->st->inf=a;
q1=q1->st;
}
}

96
void main(){
int x;
arbore *rad=0;
creare(rad);
inordine(rad);
sterge(rad,2);
}

Probleme rezolvate în limbajul Pascal


R2.7.1. Să se scrie un subprogram pentru afişarea numărului cheilor
negative şi pozitive dintr-un arbore binar.
procedure p3(p:arbore);
begin
if p<>nil then begin
p3(p^.st);
if p^.inf<0 then nr1:=nr1+1;
if p^.inf>=0 then nr2:=nr2+1;
p3(p^.dr);
end;
end;
R2.7.2. Să se scrie un subprogram pentru afişarea cheilor impare
dintr-un arborele binar.
procedure p2(p:arbore);
begin
if p<>nil then begin
if p^.inf mod 2 =1 then write(p^.inf,' ');
p2(p^.st); p2(p^.dr)
end
end;
R2.7.3. Să se scrie un subprogram pentru aflarea produsului cheilor
pozitive dintr-un arborele binar.
function p4(p:arbore):longint;
begin
if p<>nil then if p^.inf >0 then p4:=p^.inf*p4(p^.st)*p4(p^.dr)
else p4:=p4(p^.st)*p4(p^.dr)
else p4:=1
end;
97
R2.7.4. Să se scrie un subprogram pentru aflarea numărului de
frunze dintr-un arborele binar .
function p5(p:arbore):integer;
begin
if p=nil then p5:=0 else if (p^.st=nil) and (p^.dr=nil) then p5:=1
else p5:=p5(p^.st)+p5(p^.dr)
end;
R2.7.5.. Să se scrie un subprogram pentru afişarea nodurilor care au
un succesor dintr-un arbore binar.
procedure p6(p:arbore);
begin
if p<>nil then begin
if ((p^.st=nil) and (p^.dr<>nil)) or ((p^.st<>nil) and (p^.dr=nil))
then write(p^.inf,' ');
p6(p^.st); p6(p^.dr)
end
end;
R2.7.6. Să se scrie un subprogram pentru aflarea numărului de
noduri de pe un nivel dat dintr-un arbore binar.
function p7(p:arbore;k:integer):integer;
begin
if p<>nil then if k=l then p7:=1+p7(p^.st,k+1)+p7(p^.dr,k+1)
else p7:=p7(p^.st,k+1)+p7(p^.dr,k+1)
else p7:=0
end;

Probleme propuse:

1. Se dă un graf orientat cu n noduri. Să se verifice dacă există un


nod având gradul interior n-1 şi gradul exterior 0.
2. Fie G = (X,U) un graf orientat şi fară circuite. Să se determine o
renumerotare a vârfurilor sale astfel încat dacă (u,v)∈U, atunci
numărul de ordine al lui u este mai mic decât numărul de ordine al
lui v (sortare topologică).
3. Fie G = (X,U) un graf orientat cu n vârfuri. Să se determine un alt
graf având aceleaşi vârfuri, aceeaşi matrice a drumurilor şi având
un număr minim de arce (se pot scoate, introduce noi arce).

98
4. La un turneu participă n jucători, fiecare jucător jucând pe rând
împotriva celorlalţi jucători. Ştiind că nu există jocuri egale, să se
construiască o listă care să cuprindă toţi jucătorii astfel încat doi
jucători i, j sunt alături dacă jucătorul i la învins pe jucătorul j.
5. La curtea regelui Artur s-au adunat 2n cavaleri şi fiecare din ei are
printre cei prezenţi cel mult n-1 duşmani. Să se arate că Merlin,
consilierul lui Artur, poate să-i aşeze în aşa fel pe cavaleri la o
masa rotundă încât nici unul dintre ei să nu stea alături de vreun
duşman.
6. Fie G=(X,U) un graf conex cu n noduri. Să se determine eficient
cel mai mic k astfel încât ştergând nodurile etichetate cu 1,2...k, în
această ordine să rezulte un graf ale cărui componente conexe au
toate cel mult n/2 noduri.
7. Să se determine, într-un graf conex, un ciclu care conţine două
noduri date, dar nu conţine un al treilea nod.
8. Numim transpusul unui graf G = (X,U), graful care are aceeaşi
mulţime de noduri, arcele sale fiind arcele grafului G, dar având
sens opus. Dându-se G prin matricea de adiacenţă sau prin liste de
vecini, să se determine în fiecare caz transpusul grafului dat.
9. Find date n persoane în care fiecare persoană se cunoaşte pe sine
şi eventual alte persoane. Să se formeze grupuri în care fiecare
persoană să cunoască toate celelalte persoane din grup (o persoană
aparţine unui singur grup). Relaţia “x cunoaşte pe y” nu este în
mod normal nici simetrică, nici tranzitivă.
Indicaţie: Se asociază problemei date un graf orientat cu n noduri şi se
construieşte matricea de adiacenţă (a[i,j]=1 daca i cunoaste pe j =i 0
dacă i nu cunoaşte pe j). Pentru fiecare persoană neataşată la un
moment dat unui grup se va construi grupul din care face parte
aceasta. Soluţia nu este unică.
10. Să se determine, într-un graf turneu, un drum elementar care trece
prin toate vârfurile. Un graf turneu este un graf orientat cu
proprietatea că între oricare două vârfuri distincte există un arc şi
numai unul.
Indicaţie: Se adaugă arcul format din nodurile 1 şi 2 într-o listă liniară.
Fiecare din vârfurile următoare se adaugă la drumul creat anterior fie
în faţă, fie la sfârşit, fie intercalat în listă.
11. Fie G=(X,U) un graf ponderat. Să se calculeze diametrul grafului.
Se numeşte diametrul unui graf, notat d(G), d(G)=max{l[i]/ i∈X},
unde l[i] este lungimea drumului maxim care are ca extremitate
iniţială vârful i.
99
12. Fie G un graf orientat în care fiecare arc are asociat un cost pozi-
tiv. Să se determine un circuit elementar care să treacă prin toate
vârfurile grafului care să aibă cost minim.
13. Se consideră un grup de n persoane. Fiecare persoana are cel puţin
n/2 prieteni şi cel mult k duşmani în grup. Una din persoane are o
carte pe care fiecare doreşte să o citească. Să se determine o mo-
dalitate prin care cartea să circule pe la fiecare persoană o singură
dată, transmiterea ei facându-se numai între doi prieteni, iar în
final cartea să ajungă din nou la proprietarul cărţii.
14. Pentru un graf dat în numerotare aciclică (orice arc u = (x,y) din U
satisface condiţia x<y), determinaţi distanţa minimă de la vârful 1 la
toate celelate vârfuri z pentru care există drumuri de la 1 la z, precum şi
câte un drum de lungime minimă. Sunt date arcele şi lungimile lor.
15. Pătratul unui graf orientat G se determină astfel: există arc de la x
la y în pătratul unui graf dacă există în G un drum de lungime 2 de
la x la y. Să se determine pătratul unui graf când graful este repre-
zentat fie prin liste de adiacenţă, fie prin matricea de adiacenţă.
16. Se dă un graf orientat aciclic şi se cere aranjarea vârfurilor sale
astfel încât orice arc are prima extremitate înaintea celei de-a doua
extremităţi în aranjare.
17. Se dă un graf orientat aciclic, în care numărul maxim de arce în
orice drum este k. Să se determine o partiţie a mulţimii vârfurilor
grafului în cel mult k submulţimi, astfel încât pentru orice două
noduri x,y din aceeaşi submulţime să nu existe drum de la x la y şi
nici de la y la x.
Indicaţie: Graful este aciclic, deci există noduri cu gradul 0 şi se
formează prima submulţime cu aceste noduri şi apoi se vor elimina
aceste noduri din graf împreuna cu arcele ce pleacă din ele obţinându-
se un graf orientat aciclic pentru care se reia procedeul.
18. Un graf orientat se numeşte semi-conex dacă pentru pentru orice
pereche de vârfuri diferite x şi y există drum de la x la y sau drum
de la y la x. Să se verifice dacă G este semi-conex.
19. Să se realizeze un program care să deseneze un graf (planar)şi apoi să
se marcheze succesiv pe desen drumul de lungime minimă între două
noduri date.
20. Se consideră o clasă de n ( n <= 40 ) cursanţi între care există relaţii de
simpatie, nu neaparat reciproce. Să se formeze grupuri de cursanţi între
care există relaţii de prietenie reciprocă. Un cursant nu poate să
aparţină mai multor grupuri.

100
3. Metode pentru rezolvarea problemelor
3.1. Metoda Divide et Impera
3.1.1. Prezentarea metodei
Metoda generală de programare cunoscută sub numele de divide et
impera (“dezbină şi stapâneşte”) constă în împărţirea repetată a unei
probleme în subprobleme de acelaşi tip, dar de dimensiune mai mică,
urmată de combinarea soluţiilor subproblemelor rezolvate pentru a
obţine soluţia problemei iniţiale. Fiecare subproblemă se rezolvă
direct dacă dimensiunea ei este suficient de mică încât să poată fi
rezolvată imediat cu un procedeu specific, altfel este împărţită în
subprobleme mai mici folosind acelaşi procedeu prin care a fost
descompusă problema iniţială. Procedeul se reia până când, în urma
descompunerilor repetate, se ajunge la probleme care admit rezolvare
imediată.
Algoritmul fiind de natură repetitivă şi deoarece subproblemele
au aceeaşi formă cu cea a problemei iniţiale, metoda “Divide et
impera” poate fi implementată elegant folosind o funcţie recursivă.
În continuare este dată funcţia generală care implementează
algoritmul.

function divimp(X: problema)


if (X este suficient de mica) then y ← rezolvă(X)
else{
descompune problema x în subproblemele X1, X2,…, Xk
for i ← 1 to k do
yi ← divimp(Xi)
/* combină y1, y2, …, yk pentru a obţine y soluţia problemei X */
y ← combină(y1, y2, …, yk)
return y
}
Uneori recursivitatea se poate înlocui cu un ciclu iterativ. Versiunea
iterativă poate fi mai rapidă şi foloseşte mai puţină memorie
comparativ cu varianta recursivă care utilizează o stivă pentru
memorarea apelurilor.
3.1.2. Probleme rezolvate
R3.1.1. [Cel mai mare divizor comun] Fie n numere naturale nenule
x1, x2,…,xn. Să se calculeze cmmdc pentru numere date.

101
Rezolvare:
program divizor_comun;
var x:array[1..25] of integer; n, i:integer;
function Divizor(a,b:integer):integer;
begin
while a<>b do if a>b then a:=a-b else b:=b-a;
Divizor := a
end;
function cmmdc(lo,hi:integer):integer;
var m:integer;
begin
if (hi-lo<=1) then cmmdc := Divizor(x[lo],x[hi])
else begin
m:=(lo+hi) div 2;
cmmdc:= Divizor(cmmdc(lo,m), cmmdc(m+1,hi));
end;
end;
begin
write('n = '); readln(n); writeln('Introduceti numerele');
for i:=1 to n do begin write('x[',i,'] = '); readln(x[i]) end;
writeln('cmmdc:= ', cmmdc(1,n));
end.
R3.1.2. [Căutare binară] Fie x1, x2,…,xn un şir de numere întregi
ordonate crescător şi x un număr întreg. Să se verifice dacă x se află
printre elementele şirului şi dacă da, să se afişeze poziţia acestuia.
Rezolvare:
program cautare;
uses crt;
var x:array[1..100] of integer; nr:integer;
function cauta(lo,hi:integer):integer;
var m:integer;
begin
if (lo<=hi) then begin m:=(lo+hi) div 2;
if nr=x[m] then cauta:=m
else if nr<x[m] then cauta:=cauta(lo,m-1)
else cauta:=cauta(m+1,hi)
end
else cauta:=0
end;

102
var n,i,pos:integer;
ch:char;
begin
write('n = ');readln(n);
writeln('Introduceti numerele');
for i:=1 to n do begin write('x[',i,'] = '); readln(x[i]) end;
repeat
writeln;
write('Numarul cautat = ');
readln(nr);
pos := cauta(1,n);
if pos <> 0 then
writeln(nr,' se afla in sir la pozitia ', pos)
else writeln(nr,' nu se afla in sir!');
write('Continuati (y/n) ?[y] ');
ch:=UpCase(readkey);
until ch = 'N';
end.
R3.1.3. [Sortare rapidă] Fie n ∈ N* şi numerele x1, x2,…,xn. Să se scrie
o procedură recursivă de sortare (quicksort) în ordine crescătoare a
numerelor date.
Rezolvare:
#include <stdio.h>
void QSort (int *table, int left,int right){
int leftp,rightp,aux;
unsigned type = 1;
leftp =left;
rightp=right;
do{
if ( table[leftp]>table[rightp] ){
type ^= 1;
aux = table[leftp];
table[leftp] = table[rightp];
table[rightp] = aux;
}
else type ? rightp--: leftp++;
}while (leftp < rightp);
if ( leftp-left > 1) QSort(table,left,leftp-1);
if ( right-rightp >1) QSort(table,rightp+1,right);
}
103
void main(){
int n,i;
int x[100];
printf(“n = ”); scanf(“%d”,&d);
for (i=0;i<n;i++) scanf(“%d”,&x[i]);
Qsort(x,0,n-1);
for (i=0;i<n;i++) printf(“%8d”,x[i]);
}
R3.1.4. [Problema turnurilor din Hanoi] Se dau 3 tije numerotate cu 1,
2, 3 şi n discuri perforate, cu diametre diferite. Iniţial toate discurile
se află pe tija 1 în ordinea descrescatoare a diametrelor lor, în sensul
de la bază la vârful tijei. Se pune problema de a muta toate cele n
discuri pe tija 2 (utilizând şi tija 3) şi respectând urmatoarele reguli:
• a fiecare pas se mută un singur disc;
• pe fiecare tijă deasupra unui disc pot apare numai discuri
cu diametru mai mic.
Rezolvare:
#include<stdio.h>
void Hanoi(int n, int a, int b){
if (n==1) printf("\%d - %d\n",a,b);
else {
Hanoi(n-1, a, 6-a-b);
printf("\%d - %d\n",a,b);
Hanoi(n-1, 6-a-b,b);
}
}
void main(){
int n;
printf("Numarul de discuri = "); scanf("%d",&n);
Hanoi(n,1,2);
}
R3.1.5. [Sortare prin interclasare] Se consideră un vector ne numere
intregi de lungime n. Descrieţi un algoritm de ordonare a numerelor
prin metoda de sortare prin interclasare.
Rezolvare:
#include <stdio.h>
#define MAX 100

104
void MSort(int tabel[],int temp[], int lo, int hi){
int mid, k, t_lo, t_hi;
if (lo >= hi) return;
mid = (lo+hi) / 2;
MSort(tabel,temp, lo, mid);
MSort(tabel,temp, mid+1, hi);
t_lo = lo;
t_hi = mid+1;
for (k = lo; k <= hi; k++)
if ( (t_hi > hi || tabel[t_lo] < tabel[t_hi]) && (t_lo <= mid) )
temp[k] = tabel[t_lo++];
else temp[k] = tabel[t_hi++];
for (k = lo; k <= hi; k++)
tabel[k] = temp[k];
}
void main(){
int n, i;
int tabel[MAX];
int temp[MAX];
printf("n = ");
scanf("%d",&n);
printf("Introduceti numerele:\n");
for(i=0;i<n;i++){
printf("tabel[%d] = ",i);
scanf("%d",&tabel[i]);
}
MSort(tabel, temp, 0, n-1);
for(i=0;i<n;i++) printf("%d ",tabel[i]);
}
3.1.3. Probleme propuse
1. Fie x1, x2,…,xn un şir de numere reale. Să se determine max{x1,
x2,…,xn}.
2. Se dă x1, x2,…,xn (cu n∈N) un şir de numere, descrieţi un program
“Divide et Impera” care să determine al k-lea cel mai mic element
din şir (k ∈N, k<n).
3. Se consideră un vector de x cu n componente numere intregi. Să
se sorteze componentele vectorului folosind metoda de sortare
prin interclasare.

105
4. Se dă o placă de tablă cu lungimea x şi lăţimea y având n găuri
date prin coordonatele lor întregi. Se cere să se decupeze o bucată
dreptunghiulară de arie maximă şi fără găuri ştiind că sunt permise
numai tăieturi orizontale şi verticale.
5. Se consideră un vector de lungime n. Se numeşte plierea
vectorului operaţia de suprapunere a unei jumătăţi (donatoare)
peste cealaltă jumătate (receptoare). Dacă n este impar elementul
din mijloc se elimină. Elementele rezultate după pliere vor avea
numeroatarea jumătăţii receptoare. Plierea se poate repeta până
când se obţine un singur element (final). Scrieţi un program care
să determine toate elementele finale posibile şi să afişeze
succesiunile de plieri corespunzătoare. Rezolvaţi aceiaşi problemă
ţinând cont că la pliere fiecare element receptor se dublează şi din
acesta se scade elementul donator corespunzător.
6. Rezolvaţi problema turnurilor din Hanoi folosint două tije de
manevră. Comparaţi numărul de mutări efectuate.
7. Fie P(x) un polinom de grag n cu coficienţi reali. Să se evalueze
polinomul în punctul x0 folosind metoda “Divide et Imera”.
8. Scrieţi o procedură “Divide et Impera” pentru inversarea unui şir
de caractere.
9. Se dă un dreptunghi prin dimensiunile sale numere naturale.
Dreptunghiul trebuie descompus în pătrate cu laturi numere
naturale, paralele cu laturile dreptunghiului iniţial. Se cere
numărul minim de pătrate în care se poate descompune
dreptunghiul.
10. Se dau un pătrat şi un cerc. Se cere să se calculeze aria lor comună
cu precizie de o zecimală. Coordonatele se citesc de la tastatură şi
sunt numere reale. Aria se va afişa pe ecran.
11. Un arbore cartezian al unui vector este un arbore binar definit
recursiv astfel: rădăcina arborelui este elementul cel mai mic din
vector; subarborele stâng este arborele cartezian al subvectorului
stâng (faţă de poziţia elementului din rădăcină); subarborele drept
este arborele cartezian al subvectorului drept. Se dă un vector de
dimensiune n. Să se afişeze arborele său cartezian.

106
3.2. Metoda programării dinamice
3.2.1. Principii fundamentale ale programării dinamice
Programarea dinamică, ca şi metoda divide et impera, rezolvă
problemele "combinând" soluţiile subproblemelor. Un algoritm bazat
pe programare dinamică rezolvă fiecare subproblemă o singură dată şi,
apoi, memorează soluţia într-un tablou, prin aceasta evitând
recalcularea soluţiei dacă subproblema mai apare din nou şi
subprobemele care apar în descompunere nu sunt independente.
Metoda programării dinamice se aplică problemelor de
optimizare. În problemele de optim metoda constă în determinarea
soluţiei pe baza unui şir de decizii d1, d2, d3....dn, unde di transformă
problema din starea si-1 în starea si.
Paşii pentru rezolvarea unei probleme folosind programarea
dinamică:
1. Caracterizarea structurii unei soluţii optime;
2. Definirea recursivă a valorii unei soluţii optime;
3. Calculul valorii unei soluţii optime
4. Construirea unei soluţii optime din informaţia calculată.
Principii fundamentale ale programării dinamice:
1. Dacă d1, d2, ... dn este un şir optim de decizii care duc un
sistem din starea iniţială în starea finală, atunci pentru orice i
(1≤i≤n) d1, d2, ... di este un şir optim de decizii.
2. Dacă d1, d2, ... dn este un şir optim de decizii care duc un
sistem din starea iniţială în starea finală, atunci pentru orice i
(1≤i≤n) di, di+1, ... dn este un şir optim de decizii.
3. Dacă d1, d2, ... , di, di+1 ... dn este un şir optim de decizii care
duc un sistem din starea iniţială în starea finală, atunci pentru
orice i (1≤i≤n) d1, d2,... , di şi di, di+1, ... dn sunt două şiruri
optime de decizii.

3.2.2. Probleme rezolvate


R3.2.1. [Subşir crescător maximal] Se consideră un şir de n numere
întregi. Să se determine cel mai lung subşir crescător din acesta.
Exemplu: n=8
7 1 8 2 11 4 12 3
Subşirul crescător maximal: 7 8 11 12 cu lungimea 4
Rezolvare: Se construieşte tabloul L reprezentând lungimea maximă a
unui subşir maximal care îl conţine ca prim element pe a[i] astfel
107
L[n]=1 şi L[i]=1+max{L[j]/ j>i şi a[j]>a[i]} pentru i de la n-1 la 1
(metoda înainte - deoarece soluţia unei subprobleme se află pe baza
subproblemelor din urmă).
Implementare Pascal:
var i,j,n,poz,max:integer;
l,a:array[1..30] of integer;
begin
write('n=');read(n);
for i:=1 to n do begin write('a[',i,']='); read(a[i]) end;
for i:=1 to n do l[i]:=0;
l[n]:=1;
for i:=n-1 downto 1 do
begin
max:=0;
for j:=1+i to n do
if (a[j]>a[i]) and (max<l[j]) then max:=l[j];
l[i]:=1+max
end;
max:=0;
for i:=1 to n do if max<l[i] then begin
max:=l[i];
poz:=i
end;
write(a[poz],' '); i:=poz;
while max>1 do begin
if (a[i]>a[poz]) and (l[i]=max-1) then begin
poz:=i;
write(a[poz],' ');
max:=max-1
end;
i:=i+1
end
end.
Implementare C:
#include<stdio.h>
void main(){
int n,i,j,poz,max,a[100],l[100];
printf("n=");scanf("%d",&n);
for (i=1;i<=n;i++) scanf("%d",&a[i]);
108
for(i=1;i<=n;i++) l[i]=0;
l[n]=1;
for (i=n-1;i>0;i--){
max=0;
for (j=i+1;j<=n;j++)
if ((max<l[j])&&(a[j]>a[i])) max=l[j];
max=max++;
l[i]=max;
}
max=0;
for(i=1;i<=n;i++) if (l[i]>max) {max=l[i];poz=i;}
printf(" %d ",a[poz]); i=poz;
while (max>1){
if ((a[i]>a[poz]) &&(l[i]==max-1)) {
poz=i;
printf("%d ",a[poz]);
max=max-1;
}
i++;
}
}
Exerciţiu: Să se rezolve această problemă folosind varianta “înapoi”
de programare dinamică (soluţia unei subprobleme se află pe baza
subproblemelor dinainte).
Indicaţie: Tabloul L se construieşte astfe L[1]=1 şi L[i]=1+max{L[j] /
j<i şi a[j]<a[i]} pentru i de la 2 la n.
R3.2.2. [Datorii] Domnul Tudor s-a împrumutat de n ori de la o bancă
cu diferite sume de bani. El trebuie să restituie banii băncii, dar după
prima restituire ştie că nu mai poate restitui toate sumele împrumutate
la rând ci doar acelea care nu au fost împrumutate în etape succesive.
Să se determine suma maximă pe care o poate recupera banca şi care
sunt împrumuturile restituite.
Rezolvare: Elementele tabloului L se calculează după formula
L[i]=a[i]+max{L[j] / j>i+1]}
Implementare Pascal:
var a,l:array[1..100]of integer;
max,n,i,j,poz:integer;
begin
read(n);
109
for i:=1 to n do read(a[i]);
for i:=n downto 1 do begin
max:=0;
for j:=i+2 to n do if (max<l[j]) then max:=l[j];
max:=max+a[i];
l[i]:=max
end;
max:=l[1];poz:=1;
for i:=1 to n do if l[i]>max then begin
max:=l[i];
poz:=i
end;
writeln('suma maxima este: ',l[1]);
max:=l[1];poz:=1;write('sirul: ');
repeat
write(a[poz],' '); i:=poz+2;
while (i<n ) and (l[i]<>max-a[poz]) do i:=i+1;
max:=max-a[poz];
poz:=i;
until max=0
end.
Implementare C:
#include<stdio.h>
void main(){
int n,i,j,poz,max,a[100],l[100];
printf("n=");scanf("%d",&n);
for (i=1;i<=n;i++) scanf("%d",&a[i]);
for (i=n;i>0;i--){
max=0;
for (j=i+2;j<=n;j++) if (max<l[j]) max=l[j];
max=max+a[i];
l[i]=max;
}
max=l[1];poz=1;
for(i=1;i<=n;i++) if (l[i]>max) {max=l[i];poz=i;}
printf("suma maxima %d\n",l[1]);
max=l[1];poz=1;
while (max>0){
printf("%d ",a[poz]);
i=poz+2;
110
while ((i<n) && (l[i]!=max-a[poz])) i++ ;
max=max-a[poz];
poz=i;
}
}
R3.2.3. [Subşiruri comune] Dându-se două şiruri comune să se deter-
mine cel mai lung subşir comun.
Rezolvare:
Implementarea Pascal (varianta mersului “înapoi”): Se construieşte
tabloul c care reţine lungimea maximă a celui mai lung subşir comun a
celor două şiruri până la i respectiv j.
var a,b,d:string;
i,j,n,m,max,t,v:integer;
c:array[1..50,1..50] of integer;
begin
write('primul sir:');readln(a);write('sirul doi:');readln(b);
n:=length(a);m:=length(b);
for i:=1 to m do if a[1]=b[i] then c[1,i]:=1 else c[1,i]:=0;
for i:=1 to n do if a[i]=b[1] then c[i,1]:=1 else c[i,1]:=0;
for i:=2 to n do for j:=2 to m do
if a[i]=b[j] then c[i,j]:=c[i-1,j-1]+1 else c[i,j]:=0;
max:=0;
for i:=1 to n do for j:=1 to m do
if max<c[i,j] then begin max:=c[i,j]; t:=i; v:=j end;
writeln('secventa maxima are lungimea :',max);
d:=a[t];
while max>1 do begin
dec(max); dec(t);
d:=a[t]+d
end;
writeln(d);
end.
Exerciţiu: Să se implementeze în C aplicaţia de mai sus.
R3.2.4. [Partiţii] Să se determine numărul de moduri în care se poate
descompune un număr natural în suma de numere.
Exemplu: n=4, numărul de moduri = 5
Rezolvare: Se construieşte tabloul a unde a[n][0] reprezintă numărul
de moduri în care se poate descompune:

111
a[n,n]=1;
a[n,k]=a[n-k,0] pentru n≤2k;
a[n,k]=Σa[n-k,i] pentru i de la 1 la k.
Implementare C:
#include <stdio.h>
void main(){
int n,i,k,j,p,a[100][100];
printf("n=");scanf("%d",&n);
a[1][0]=1;a[1][1]=1;
for( i=2;i<=n;i++){
a[i][0]=0;
for(k=1;k<=i;k++){
if(k==i) a[i][k]=1;
else if(i<2*k) a[i][k]=a[i-k][0];
else {
a[i][k]=a[i-k][k];
for(int l=1;l<=k-1;l++)
a[i][k]=a[i][k]+a[i-k][l];
}
a[i][0]=a[i][0]+a[i][k];
}
}
printf("nr de moduri=%d",a[n][0]);
}
Exerciţiu: Să se implementeze în Pascal aplicaţia de mai sus.
R3.2.5. [Drum] Dându-se un tablou bidimensional cu elemente
numere naturale. Să se determine un traseu de la poziţia 1,1 la poziţia
n,m, deplasarea făcându-se numai în direcţiile S şi E, astfel încât
suma elementelor de pe traseu să fie maximă.
Rezolvare: Implementarea Pascal este:
var i,j,n,m,max,p,q:integer;
a,l:array[1..10,1..10]of integer;
begin
read(m,n);
for i:=1 to n do for j:=1 to m do read(a[i,j]);
l[1,1]:=a[1,1];
for i:=2 to m do l[1,i]:=l[1,i-1]+a[1,i];
for i:=2 to n do l[i,1]:=l[i-1,1]+a[i,1];
for i:=2 to n do for j:=2 to m do

112
if l[i-1,j]>l[i,j-1] then l[i,j]:=l[i-1,j]+a[i,j]
else l[i,j]:=l[i,j-1]+a[i,j];
writeln(l[n,m]); max:=l[n,m];p:=n;q:=m; write(a[n,m],' ');
while (p<>1) and (q<>1) do begin
if l[p-1,q] = l[p,q]-a[p,q] then p:=p-1 else q:=q-1;
write(a[p,q],' ');
end;
write(a[1,1]);
end.
Exerciţiu: Să se implementeze aplicaţia de mai sus în C.
3.2.3. Probleme propuse
1. [Triunghi] Se consideră un triunghi de numere. Să se calculeze cea
mai mare dintre sumele numerelor ce apar pe drumurile ce pleacă din
vârf şi ajung la bază şi să se reconstituie drumul de sumă maximă. În
punctul (i,j) se poate ajunge numai din (i-1,j) sau (i-1,j-1).
Exemplu:
N=3
6
3 8
8 4 1
suma maximă este 18 şi drumul 6 8 4
Indicaţie: Se construieşte tabloul c astfel c[i,j]=max{a[i,j]+ c[i+1,j],
a[i,j]+ c[i+1,j+1]} (varianta mersului “înainte”). c[1,1] este suma
maximă cerută.
2. [Înmulţirea optimă a unui şir de matrice] Se dau n matrice.
Asociativitatea înmulţirii a două matrice ne oferă mai multe
posibilităţi de a calcula produsul şi care au un număr diferit de
operaţii. Să se determine o posibilitate de asociere a înmulţirii
matricelor astfel încât numărul de operaţii să fie minim.
Indicaţie: Se construieşte tabloul bidimensional L, unde l[i,j] repre-
zintă numărul minim de înmulţiri pentru a efectua produsul AiAi+1.....Aj.
3. [Triangularizarea optimă a poligoanelor] Se dă un poligon convex şi
o funcţie de pondere p definită pe triunghiurile formate de laturile şi
diagonalele poligonului. Să se găsească o triangulare care minimizea-
ză suma ponderilor triunghiurilor din triangularizare.
Indicaţie Există o corespondenţă între triangularizarea unui poligon şi
parantezarea unei expresii cum ar fi produsul unui şir de matrici.
4.[ Tipărirea uniformă] Textul de intrare este o succesiune de n cuvin-
te de lungimi l1, l2,...ln, lungimea fiind măsurată în caractere. Se doreş-
113
te tipărirea uniformă a paragrafului pe un anumit număr de linii, fieca-
re linie având cel mult m caractere astfel: dacă o linie conţine cuvinte-
le de la cuvântul i la cuvântul j, cu i ≤j iar între cuvinte se lasă exact
un spaţiu. Să se minimizeze suma cuburilor numărului de spaţii supli-
mentare de la sfârşitul liniilor, în raport cu toate liniile, mai puţin ultima.
5. [Problema patronului] Un patron a cumpărat un calculator şi doreşte
să înveţe să lucreze cu el. Pentru aceasta va umple un raft de cărţi din
colecţia “Informatica în lecţii de 9 minute şi 60 secunde”. Raftul are
lungimea L cm (L număr natural). Seria dispune de n titluri 1, 2, …,n
având grosimile g1, g2, ..., gn cm (numere naturale). Să se selecteze
titlurile pe care le poate cumpăra patronul, astfel încât raftul să fie
umplut complet (suma grosimilor cărţilor cumpărate să fie egală cu
lungimea raftului) şi numărul cărţilor achiziţionate să fie maxim.
Olimpiada Naţională de Informatică 1996
Indicaţie: Se construieşte tabloul bidimensional a unde a[i,j] reprezin-
tă numărul maxim de cărţi dintre primele i (în ordinea dată la intrare)
cu lungimea totală j sau, 0 dacă nu există nici o submulţime a mulţimii
primelor i cărţi cu lungimea totală j.
6. [Planificarea unei recepţii] Corporaţia AMC are o structură ierarhi-
că, relaţiile de subordonare formează un arbore în care rădăcina este
preşedintele. Fiecare angajat are un coeficient de convieţuire (număr
real). Preşedintele doreşte să organizeze o petrecere astfel încât nici un
angajat să nu se întâlnească cu seful său direct. Informaticianul Tom
trebuie să alcătuiască lista invitaţilor astfel încât suma coeficienţilor de
convieţuire a invitaţilor să se maximizeze şi preşedintele să participe
la propia petrecere.
7. [Mouse] Un experiment urmăreşte comportarea unui şoricel pus
într-o cutie dreptunghiulară, împărţită în mxn cămăruţe egale de formă
pătrată. Fiecare cămăruţă conţine o anumită cantitate de hrană. Şorice-
lul trebuie să pornească din colţul (1,1) al cutiei şi să ajungă în colţul
opus, mâncând cât mai multă hrană. El poate trece dintr-o cameră în
una alăturată (două camere sunt alăturate dacă au un perete comun),
mănâncă toată hrana din cămăruţă atunci când intră şi nu intră
niciodată într-o cameră fără hrană. Stabiliţi care este cantitatea maxi-
mă de hrană pe care o poate mânca şi traseul pe care îl poate urma
pentru a culege această cantitate maximă. Se dă n, m şi cantitatea de
hrană existentă în fiecare cămeruţă (numere între 1 şi 100).
Olimpiada judeţeană de informatică, 2002

114
Exemplu:
24
1263
3412
Ieşire:
7 21
1 1 --> 2 1 --> 2 2 --> 1 2 --> 1 3 --> 1 4 -->2 4
8. [Semne] Pentru n număr natural, să se găsească o combinaţie de semne
+ şi - (adică un vector x = (x(1), x(2), ..., x(k)), x(i) se află în <-1, 1>) şi
un număr k natural nenul astfel încât:
n = x(1)*12+x(2)*22+...+x(k)*k2.
Datele se citesc dintr-un fişier text ce conţine pe fiecare linie câte un
număr ce reprezintă valorile lui n pentru care se doresc reprezentări ca
mai sus. Datele de ieşire vor fi introduse într-un fişier text pe câte o
linie combinaţia de semne corespunzătoare.
Exemplu:
Intrare:
2
4
8
5
Ieşire:
---+
--+
--++--+
++--+
Indicaţie: Pentru k începând de la 1, se găsesc într-o listă numerele care
se pot obţine cu k semne; lista pentru k se obţine din cea pentru k-1.
Pentru unele numere k este foarte mare şi programul nu ar intra în
timp. O altă soluţie ar fi folosirea observaţiei:
(n+3)2-(n+2)2-(n+1)2+n2=(n+3+n+2)*1-(n+1+n)*1=2n+5-2n-1=4
Prin adăugarea secvenţei + - - + se ajunge de la un numar n la n+4.
Dacă am obţine secvenţele pentru n = 0, 1, 2 şi 3, am rezolva
problema. Acestea sunt:
0 secvenţa vidă
1+
2 - - - + (dată în exemplu)
3 - (pentru -1), apoi + - - +

115
9. [Şah] a) Dându-se două poziţii pe o tablă de şah de dimensiune
pătratică nxn, să se determine cel mai scurt drum dintre acestea
utilizând mutările calului.
b) Determinaţi numărul minim de cai care pot fi amplasaţi pe o tablă
de şah de dimensiune nxm astfel încât să nu se atace.
10. [Linii navale] Pe malul de nord şi cel de sud al unui fluviu există n
oraşe. Fiecare oraş are un unic oraş prieten pe celălalt mal; nu există
două oraşe pe un mal având acelaşi priten pe celălalt mal. Intenţia este
ca oraşele prietene să se lege printr-o linie navală. Se impune însă
restricţia ca să nu existe linii navale care să se intersecteze. Să se
determine numărul maxim de linii navale care pot fi înfiinţate, cu
respectarea condiţiei de mai sus. Pe prima linie a fişierului de intrare
apare numărul n de perechi de oraşe prietene, iar pe următoarele n linii
apar perechile de oraşe prietene (primul pe malul de nord, iar al doilea
pe malul de sud), fiecare oraş fiind specificat prin distanţa sa (cel mult
egală cu 6000) faţă de kilometrul 0 al fluviului.
Indicaţie: Se ordonează posibilele linii navale după primul argument
şi se determină cel mai lung subşir crescător format din coordonatele
celui de-al doilea argument. (vezi problema rezolvată 1).
11. [Drumuri minime] Se consideră un graf orientat, fiecare arc având
un cost ataşat. Se cere să se determine pentru fiecare pereche de
vârfuri ( i, j) şi să se tipărească lungimea drumului minim de la i la j.
12. [Submulţimi de sumă dată] Să se determine pentru un vector dat
dacă un număr k se poate scrie ca sumă de numere din vector, un
element din vector se poate folosi o dată.
Indicaţie: Se construieşte un tablou a cu valori logice, unde a[i] este
true dacă i se poate scrie ca sumă de elemente din vectorul dat şi false
în caz contrar.

3.3. Metoda Greedy


3.3.1. Prezentarea metodei
Algoritmii de tip Greedy sunt folosiţi la probleme de optimizare şi
sunt în general simpli şi eficienţi. Un astfel de algoritm determină
soluţia optimă în urma unei succesiuni de alegeri. La fiecare moment
decizional, din algoritm se va alege opţiunea care pare a fi cea mai
potrivită pe baza unui criteriu de optim local, în ideea că la final se va
obţine o soluţie optimă globală.

116
În general, datele de intrare pentru un algoritm Greedy se prezintă sub
forma unei mulţimi finite, iar soluţia este o submulţime sau o
permutare a datelor de intrare în aşa fel încât să fie îndeplinite anumite
condiţii de optim. Condiţiile de optim cer determinarea unei soluţii
care maximizează sau minimizează o funcţie dată, numită funcţie
obiectiv.
Dacă un element este inclus în soluţie el va rămâne în această
mulţime, nu se face nici o revenire asupra unei decizii luate.
Selectarea unui element la un moment dat poate depinde de
elementele alese până la acel moment dar nu depinde de cele care
urmează să fie alese ulterior.
Dacă un element a fost selectat, dar nu poate face parte din
soluţie atunci acest element nu va mai fi luat în calcul la alegerile
următoare. În acest fel se poate ajunge rapid la o soluţie, dar nu se
asigură (pentru toate problemele) optimalitatea globală. Există şi
probleme pentru care o astfel de metodă nu conduce la soluţie, deşi
aceasta există. Problemele care se pot rezolva folosind algoritmi de tip
Greedy trebuie să verifice două proprietăţi: proprietatea alegerii
Greedy şi substructura optimală.
Proprietatea alegerii Greedy spune că se poate ajunge la o
soluţie de optim global dacă la fiecare pas de decizie se selectează un
element care îndeplineşte criteriul de optim local. Proprietatea de
structură optimală spune că soluţia optimală a problemei conţine
soluţiile optimale ale subproblemelor.
O problemă care se va rezolva folosind o strategie Greedy este
dată de:
• O mulţime finită de “candidaţi” care reprezintă datele de
intrare;
• O funcţie care verifică dacă o anumită mulţime de elemente
constituie o soluţie posibilă, nu neaparat optimă, a
problemei;
• O funcţie care verifică dacă o submulţime a datelor de
intrare este o soluţie parţială a problemei;
• O funcţie de selecţie care alege la orice moment cel mai
potrivit candidat dintre elementele nefolosite încă;
• O funcţie obiectiv care trebuie minimizată sau maximizată.

Un algoritm Greedy nu furnizează decât o singură soluţie. În


contiunare este prezentată forma generală a metodei.

117
function greedy(C) {C este mulţimea datelor de intrare }
S←∅ {S este multimea care va conţine soluţia}
while not Soluţie(S) and C ≠ ∅ do
begin
x ← select(C)
C ← C \ {x}
if Posibil(S ∪ {x}) then S ← S ∪ {x}
end
if Solutie(S) then return S
else return write(‘nu exista soluţie’)
Această metodă îşi găseşte multe aplicaţii în teoria grafurilor la
determinarea unui arbore parţial de cost minim (Kruskal, Prim), la
determinarea drumurilor de cost minim de la un vârf la celelalte
vârfuri ale unui graf (Dijkstra), compresia datelor (arbori Huffman)
ş.a.
3.3.2. Probleme rezolvate
R3.3.1. [Problema continuă a rucsacului] Avem la dispoziţie un
rucsac cu care se poate transporta o greutate maximă G. Trebuie să
alegem din n obiecte pentru care se cunoaşte greutatea gi şi profitul pi
care se obţine prin transportul obiectului i cu rucsacul. Să se
determine obiectele care trebuiesc alese pentru transport astfel încât
profitul obţinut să fie maxim, iar greutatea totală a acestora să nu
depăşească G ştiind că putem lua orice parte dintr-un obiect.
Rezolvare: Programul în C este:
#include <stdio.h>
#define MAX 25
void main(){
int p[MAX]; int g[MAX];
int n,i,j,aux,nr[MAX];
float G, gr,profit,sol[MAX];
printf("Greutatea maxima G = ");scanf("%f",&G);
printf("Numarul de obiecte: ");
scanf("%d", &n);
for (i=0; i<n; i++){
printf("Obiectul nr. %d:\n",i+1);
printf("Greutatea = "); scanf("%d",&g[i]);
printf("Profitul = "); scanf("%d",&p[i]);
sol[i] = 0.0; nr[i] = i;
}
118
/*
ordonare descrescatoare dupa raportul p/g
*/
for (i=0;i<n-1;i++) for (j=i+1;j<n;j++)
if(p[i]/g[i] < p[j]/g[j]){
aux=p[i]; p[i]=p[j]; p[j]=aux;
aux=g[i]; g[i]=g[j]; g[j]=aux;
aux=nr[i]; nr[i]=nr[j]; nr[j]=aux;
}
/*alegere */
gr = G;
for (i=0; i<n && g[i]<gr; i++){
sol[i] = 1.0; //100%
gr -= g[i];
}
if (i < n) sol[i] = gr/g[i];
/*afisare zezultate */
profit = 0.0;
for (i=0;i<n;i++)
if(sol[i] > 0){
printf("Obiect %d - %5.2f%%\n", nr[i]+1, sol[i] *100);
profit += sol[i] * p[i];
}
printf("Profit = %5.2f", profit);
}
R3.3.2.[Planificarea activităţilor] Într-o sală, într-o zi trebuie plani-
ficate n activităţi, pentru fiecare activitate se cunoaşte intervalul în
care se desfăşoară [s, f). Se cere să se planifice un număr maxim de
activităţi astfel încât să nu se suprapună.
Rezolvare:Programul Pascal este:
program Activitati;
uses crt;
const max=100;
var n,i,j,aux:integer;
s,f:array[1..max] of integer;
sol:array[1..max] of 0..1;
nsol, finish:integer;
begin
{ citire date }

119
write('Nr de activitati = '); readln(n);
for i:=1 to n do
begin
writeln('Activitatea nr. ',i,':');
write('Start = '); readln(s[i]);
write('Finish = '); readln(f[i]);
sol[i]:=0;
end;
{ordonare după termenul final}
for i := 1 to n-1 do
for j := i + 1 to n do
if ( f[i] > f[j]) then
begin
aux:=s[i]; s[i]:=s[j]; s[j]:=aux;
aux:=f[i]; f[i]:=f[j]; f[j]:=aux;
end;
{calcul}
finish := 0;
for i := 1 to n do
if (s[i] >= finish ) then
begin
sol[i]:=1;
finish := f[i];
end;
{afisare rezultate}
writeln('Solutie:'); j := 1;
for i := 1 to n do
if sol[i]=1 then
begin
writeln('Activitatea nr. ',j,': ',
s[i], ' - ' ,f[i]);
j := j + 1;
end;
end.
R3.3.3. [Schimbarea banilor] Presupunem că a avem la dispoziţie k
tipuri de monede cu valorile c0, c1,… ck-1 unde c∈N*, să se scrie un
program care să determine o modalitate de schimb a unei sume S
folosind un număr minim de monede (numărul de monede din fiecare
tip fiind nelimitat).
Rezolvare: Programul C este:

120
#include<stdio.h>
#include<conio.h>
void main(){
int i,j,k,c,S; int sol[100]; int x,xmax;
/*citire date */
printf("k = "); scanf("%d",&k); printf("c = "); scanf("%d",&c);
printf("S = "); scanf("%d",&S);
/*calcul */
xmax=1; for(i=1; i<k; i++) xmax*=c;
x = xmax;
for(i=0; S>0; i++){ sol[i] = S / x; S %= x; x /= c; }
/*afisare */
x = xmax;
for(j=0; j<i; j++,x/=c)
if(sol[j]>0) printf("%d monede cu valoarea %d\n", sol[j], x);
getch();
}
R3.3.4. [Numere frumoase] Numerele frumoase sunt acele numere
care au în descompunerea lor numai factori primi de 2, 3 şi 5. Fiind
dat un număr natural n<1000, să se afişeze pe ecran primele n
numere frumoase.
Rezolvare: Programul Pascal este:
program nrFrumoase;
const max = 1000;
function min(a,b:integer):integer;
begin if a <= b then min:=a else min:=b end;
var
t,n2,n3,n5:integer;
f:array[1..max] of longint;
i,j,k,l,n:integer;
begin
write('n = '); readln(n);
n2:=2; i:=1; n3:=3; j:=1; n5:=5; k:=1; t:=1; l:=1; f[1]:=1;
while(l<=n) do
begin
t:=min(n2,min(n3,n5));
l:=l+1; f[l]:=t;
if t = n2 then begin inc(i); n2 := 2*f[i] end;
if t = n3 then begin inc(j); n3 := 3*f[j] end;

121
if t = n5 then begin inc(k); n5 := 5*f[k] end
end;
for i:=1 to n do write(f[i],' ');
readln;
end.
R3.3.5. [Copaci şi ciori] Pe n copaci aşezaţi în cerc se află n-1 ciori,
maxim una pe fiecare copac. Dându-se o configuraţie iniţială şi una
finală a ciorilor şi ştiind că la un moment dat o singură cioară poate
zbura de pe copacul ei pe cel liber, să se scrie un program care să de-
termine o secvenţă de zboruri pentru a ajunge la configuraţia finală.
Rezolvare: Programul C este:
#include <stdio.h>
#define MAX 10
int initial[MAX]; int final[MAX]; int n;
int CopacLiber(){
int i=0; while(initial[i]!=0) i++; return i; }
int Copac(int c){
int i=0; while(initial[i] != c) i++; return i; }
void Zboara(int s,int d){
printf("Cioara %d zboara de pe copacul %d pe %d !\n", \
initial[s], s+1,d+1);
initial[d] = initial[s]; initial[s] = 0;
}
void main(){ int i, k;
printf("n = "); scanf("%d",&n);
printf("Introduceti configuratia initiala (0 pt. liber):\n");
for(i=0;i<n;i++)
{printf("initial[%d] = ",i+1); scanf("%d",&initial[i]);}
printf("Introduceti configuratia finala (0 pt. liber):\n");
for(i=0;i<n;i++)
{printf("final[%d] = ",i+1);scanf("%d",&final[i]);}
for(i=0;i<n;i++)
if (final[i] && initial[i] != final[i]){
if(initial[i]) {
k = CopacLiber(); Zboara(i,k);
}
k = Copac(final[i]); Zboara(k,i);
}
}

122
R3.3.6. [Problema comis-voiajorului] Un comis-voiajor trebuie să
viziteze un număr de n oraşe şi să revină în oraşul de plecare.
Cunoscând legăturile dintre oricare două oraşe se cere să se afişeze
cel mai scurt traseu posibil pe care poate să-l parcurgă comis-
voiajorul ştiind că nu trebuie să treacă de două ori prin acelaşi oraş.
Rezolvare: Programul Pascal este:
program comis;
const max = 25;
var harta:array[1..max,1..max] of integer;
traseu:array[1..max] of integer;
v:array[1..max] of 0..1;
i,j,k,min,n,lungime,pos:integer;
begin
write('n = '); readln(n);
for i:=1 to n-1 do for j:=i+1 to n do
begin
write('distanta[',i,', ', j,'] = '); readln(harta[i,j]);
harta[j,i]:=harta[i,j];
end;
write('Orasul de pornire = '); readln(k);
lungime:=0; traseu[1]:=k; v[k]:=1;
for i:=2 to n do
begin
min:=maxint;
for j:=1 to n do
if v[j] = 0 then
if harta[k, j] < min then
begin
min := harta[k, j];
pos := j
end;
inc(lungime,min); traseu[i]:=pos; v[pos]:=1; k:=pos
end;
inc(lungime,harta[k,1]);
for i:=1 to n do write(traseu[i]:3);
writeln(traseu[1]:3);
writeln('lungimea = ', lungime);
readln;
end.

123
3.3.3. Probleme propuse
1. [Problema planificării optime a lucrărilor] Se presupune că avem de
executat un număr de n lucrări, cunoscându-se pentru fiecare lucrare i
termenul de finalizare ti şi penalizarea pi plătită în cazul în care
lucrarea nu este predată la termen. Ştiind că fiecare lucrare este
executată într-o unitate de timp şi că nu se pot executa mai multe
lucrări simultan, să se planifice ordinea de execuţie a lucrărilor astfel
încât penalizarea totală să fie minimă.
2. [Amplasarea canalelor] În jurul unui oraş avem N bazine cu apă ce
trebuiesc unite între ele prin canale. Fiecare bazin poate fi unit prin
câte un canal cu mai multe bazine. Perechile de bazine ce vor fi unite
sunt date într-o listă. Un canal poate trece doar prin interiorul sau
exteriorul cercului format de bazine. Se cere modul de amplasare al
fiecărui canal.
3. Se dau numerele întregi nenule a1, a2, …, an şi b1, b2, …, bm cu n ≥
m. Să se determine o submulţime {x1, x2, …, xm} a mulţimii {a1, a2,
…, an} care să maximizeze expresia E = b1x1 + b2x2 + … + bmxm.
4. Într-un depozit al monetăriei statului sosesc n saci cu monezi. Se
cunoaşte numărul de monezi din fiecare sac şi se doreşte mutarea
monezilor dintr-un sac în altul în aşa fel încât în fiecare sac să apară
acelaşi număr de monezi. Scrieţi un program care rezolvă problema
efectuând un număr minim de mutări.
5. Fie n şiruri ordonate crescător S1, S2, …, Sn cu lungimile L1, L2, …,
Ln. Să se interclaseze cele n şiruri obţinându-se un singur şir crescător
de lungime L1 + L2 + … + Ln cu un număr minim de comparaţii între
elementele şirurilor.
6. Fie X = {x1, x2, …,xn} o mulţime de numere reale. Să se detmine o
mulţime de cardinal minim de intervale de lungime 1 a căror reuniune
să includă mulţimea X.
7. Se cunosc poziţiile oraşelor unei ţări date prin coordonatele lor. Să
se determine configuraţia unei reţele telefonice astfel încât toate
oraşele să fie conectate la reţea şi costul reţelei să fie minim.
8. Se dau n şi k, naturale, k≤n. Să se construiască un tablou n×n care
îndeplineşte simultan condiţiile:
♦ conţine toate numerele de la 1 la n2 o singură dată;
♦ pe fiecare linie numerele sunt aşezate în ordine crescătoare, de la
stânga la dreapta;
♦ suma elementelor de pe coloana k să fie minimă.
9. Un instructor de schi trebuie să distribuie n perechi de schiuri la n
elevi începători. Descrieţi un algoritm care să facă distribuirea în aşa
124
fel încât suma diferenţelor absolute dintre înălţimea elevului şi
lungimea schiurilor să fie minimă.
10. Se dă un polinom P(X) cu coeficienţi întregi. Să se afle toate
rădăcinile raţionale ale polinomului.
11. Se dau n puncte în plan prin coordonatele lor numere întregi. Să se
construiască, dacă este posibil, un poligon convex care să aibă ca
vârfuri aceste puncte.
12. Fiind dată o hartă cu n ţări, descrieţi un algoritm euristic care să
determine o modalitate de colorare a hărţii astfel încât două ţări care
se învecinează să fie colorate diferit.
13. Descrieţi un algoritm euristic care să determine o modalitate prin
care un cal aflat în colţul stânga-sus să străbată întreaga tablă de şah
fără a trece de două ori prin aceeaşi poziţie.
14. Descrieţi un algoritm euristic care să schimbe o sumă S dată
folosind un număr minim de monete având valorile v1, v2, …, vn.
3.4. Metoda backtracking
3.4.1.Prezentarea metodei
Această tehnică se foloseşte la rezolvarea problemelor a căror soluţie
se poate reprezenta sub forma unui vector S = x1x2...xn cu x1 ∈ S1, x2
∈ S 2, …, xn ∈ S n, iar mulţimile S1, S2, …, Sn, care pot coincide, sunt
mulţimi finite ordonate liniar (pentru orice element, cu exceptia
ultimului este cunoscut succesorul). Pentru fiecare problemă concretă
sunt precizate relaţiile dintre componentele vectorului care memorează
soluţia, numite condiţii interne. Mulţimea S = S1 × … × S1 se numeşte
spaţiul soluţiilor, iar elementele produsului cartezian care satisfac şi
condiţiile interne se numesc soluţii rezultat. Metoda evită generarea
tuturor combinaţiilor completând pe rând componentele vectorului
soluţie cu valori ce îndeplinesc condiţiile de continuare, scurtându-se
astfel timpul de calcul.
Vectorul x este privit ca o stivă, lui xk nu i se atribuie o valoare
decât după ce x1, …, xk-1 au primit valori. La început xk este iniţializat
la o valoare (cu ajutorul unei funcţii init) al cărei succesor este primul
element din Sk. Cu ajutorul unei funcţii succesor xk ia valoarea
primului element din Sk. După ce xk a primit o valoare, nu se trece
direct la atribuirea unei valori lui xk+1 ci se verifică mai întâi anumite
condiţii de continuare referitoare la x1, …, xk cu ajutorul unei funcţii
valid de tip logic. Dacă aceste condiţii sunt satisfăcute se verifică dacă
s-a obţinut o soluţie caz în care se trece la afişarea acesteia.

125
Neîndeplinirea condiţiilor de continuare exprimă faptul că oricum am
alege xk+1, …, xn, nu vom ajunge la o soluţie. În acest caz se face o
nouă alegere pentru xk sau, dacă Sk a fost epuizat, se micşorează k cu o
unitate şi se încearca o nouă alegere pentru xk (k >0) din restul
elementelor lui Sk încă nealese folosind funcţia succesor. Ca urmare se
trece la atribuirea unei valori lui xk+1 doar dacă sunt îndeplinite
condiţiile de continuare.
Algoritmul se termină în momentul în care au fost luate în calcul
toate elementele mulţimii S1.
Metoda backtracking poate fi descrisă prin următoarea proce-
dură (în varianta iterativă):
k := 1; init(k, st);
while k > 0 do begin
repeat
succesor(as, st, k);
if as then valid(ev, st, k);
until (not as) or (as and ev);
if as if solutie(k) then tipar
else begin k := k + 1; init(k, st) end
else k := k – 1;
end;
Rezolvarea problemelor prin această metodă necesită un timp mare de
execuţie, motiv pentru care metoda este recomandată numai în
cazurile în care spaţiul soluţiilor este destul de redus, sau dacă nu se
cunoaşte un alt algoritm mai rapid.
Metoda admite şi o variantă recursivă în care stiva nu este
gestionată explicit de programator. În continuare este dată forma
generală a procedurii backtracking în variantă recursivă (care se va
apela cu Back(1)):
procedure Back(k);
var i:integer;
begin
if solutie(k) then tipar(x)
else begin
init(k,x);
while succesor(x,k) do
begin x[k]:=i; if valid(x,k) then Back(k+1) end;
end
end;

126
3.4.2. Probleme rezolvate
R3.4.1. [Permutări] Se dă un număr n ∈ N, se cere să se genereze
toate permutările mulţimii {1, 2, …,n}.
Rezolvare:
program permutari;
uses crt;
type stack = array[1..25] of integer;
var st: stack;
n:integer; k:integer;
as, ev: boolean;

procedure init(k:integer; var st: stack);


begin st[k]:=0 end;
procedure succesor(var as:boolean; var st: stack; k:integer);
begin
if st[k] < n then begin st[k] := st[k] + 1; as := true end
else as:=false;
end;
procedure valid(var ev:boolean; st: stack; k:integer);
var i:integer;
begin ev:=true; for i:=1 to k-1 do if st[k] = st[i] then ev:=false end;
function solutie(k:integer) :boolean;
begin solutie:=(k=n) end;
procedure tipar;
var i:integer;
begin for i:=1 to n do write(st[i],' '); writeln end;
begin
write ( ' n = ' ); readln(n) ;
k:=1; init(k,st);
while (k>0) do begin
repeat
succesor (as, st, k) ;
if as then valid(ev,st,k);
until (not as) or (as and ev) ;
if as then if solutie(k) then tipar
else begin k:=k+1; init(k,st) end
else k:=k-1
end;
end.

127
R3.4.2. [Descompunere ca sumă de numere prime] Să se descompună
un număr natural n în toate modurile posibile ca sumă de numere
prime.
Rezolvare:
program desc_prime;
var Prime, x :array[1..100] of integer;
n,k,i,p,S:integer;
function Prim(nr:integer):boolean;
var i:integer;
begin
Prim:=true; i:=2;
while i*i <= nr do
if nr mod i = 0 then
begin
Prim:=false; break;
end
else inc(i);
end;
procedure Tipar;
var i:integer;
begin for i:=1 to k do write(Prime[x[i]],' '); writeln end;

begin
write('n = '); readln(n);
p:=0;
for i:=2 to n do if Prim(i) then
begin inc(p); Prime[p]:=i end;
S:=0;
k:=1; x[k]:=0;
while k>0 do begin
inc(x[k]);
if (x[k] <= p) and (S + Prime[x[k]] <= n) then
if S + Prime[x[k]] = n then Tipar
else begin
inc(S,Prime[x[k]]); inc(k); x[k] := x[k-1]-1
end
else begin dec(k); Dec(S,Prime[x[k]]); end
end
end.

128
R3.4.3. [Problema damelor] Pe o tablă de şah trebuie aşezate opt
dame astfel încât să nu existe două dame pe aceeaşi linie, coloană sau
diagonală. Problema se generalizează imediat pentru n dame pe o
tablă cu n linii şi n coloane. Să se determine toate soluţiile.
Rezolvare :
#include <stdio.h>
#include <conio.h>
#include <math.h>
typedef int stack[25];
stack st;
int n;
void tipar(){
int j;
for(j = 0; j < n; j++) printf("%d ", st[j]);
printf("\n");
}
void dame(int k){
int i,j, valid;
if (k==n) tipar();
else for(i = st[k]+1; i <= n; i++) {
st[k] = i; valid = 1;
for(j = 0; j < k; j++)
if ((st[j]==st[k])||(abs(st[k]-st[j])==k-j)) valid = 0;
if (valid ==1) dame(k+1);
}
st[k] = 0;
}
void main(){
int i;
printf("n = "); scanf("%d",&n);
for(i = 0; i < n; i++) st[i]=0;
dame(0);
getch();
}
R3.4.4. [Problema colorării hărţilor] Fiind dată o hartă cu n ţări, se
cer toate soluţiile de colorare a hărţii utilizănd un număr minim de
culori, astfel încât două tări cu frontieră comună să fie colorate
diferit. Menţionăm că este demonstrat faptul că sunt suficiente 4
culori pentru a colora orice hartă.

129
Rezolvare:
program Culori;
uses crt;
type stack = array[1..25] of integer;
var st: stack;
i,j,k,n:integer;
as, ev: boolean;
a:array[1..25,1..25] of integer;
procedure init(k:integer;var st: stack);
begin st[k]:=0 end;
procedure succesor(var as:boolean; var st: stack; k:integer);
begin
if st[k] < 4 then begin st[k] := st[k] + 1; as := true end
else as:=false;
end;
procedure valid(var ev:boolean; st: stack; k:integer);
var i:integer;
begin
ev:=true;
for i:=1 to k-1 do if (st[k]=st[i]) and (a[i,k]=1) then ev:= false;
end;
function solutie(k:integer) :boolean;
begin solutie:=(k=n) end;
procedure tipar;
var i:integer;
begin for i:=1 to n do write(st[i],' '); writeln end;
begin
write (' n = '); readln(n);
for i:=1 to n do for j:=1 to i-1 do
begin write('a[',i,',',j,']= '); readln(a[i,j]); a[j,i]:=a[i,j] end;
k:=1; init(k,st);
while (k>0) do
begin repeat
succesor (as, st, k) ; if as then valid(ev,st,k);
until (not as) or (as and ev) ;
if as then if solutie(k) then tipar
else begin k:=k+1; init(k,st) end
else k:=k-1;
end;
end.

130
R3.4.5. [Săritura calului] Se consideră o tablă de şah n x n şi un cal
plasat în colţul din stânga sus. Se cere să se afişeze toate posibilităţile
de mutare a calului astfel încât să treacă o singură dată prin fiecare
pătrat al tablei.
Rezolvare:
#include <stdio.h>
#include <conio.h>
typedef int tabla[25][25];
int linT[8] = {-1,+1,+2,+2,+1,-1,-2,-2};
int colT[8] = {+2,+2,+1,-1,-2,-2,-1,+1};
tabla tab;
int n;
void tipar(){
int i,j;
for(i = 0; i < n; i++){ printf("\n");
for(j = 0; j < n; j++) printf("%3d", tab[i][j]);
}
printf("\n"); getch();
}
void cal(int lin, int col, int k){
int i,lin2,col2;
if (tab[lin][col]) return;
k++;
tab[lin][col]=k;
if (k==n*n) tipar();
else for (int i = 0; i < 8; i++){
lin2 = lin + linT[i];
col2 = col + colT[i];
if (lin2>=0 && lin2<n && col2>=0 && col2<n)
cal(lin2,col2,k);
}
tab[lin][col] = 0;
}
void main(){
int i,j;
printf("n = "); scanf("%d",&n);
for(i = 0; i < n; i++) for(j = 0; j < n; j++) tab[i][j]=0;
cal(0,0,0);
printf("\n%d solutii",t);
}
131
3.4.3. Probleme propuse
1. Se dau numerele naturale n şi k, să se genereze toate
aranjamentele (funcţiile injective) de elemente din mulţimea {1, 2,
…, n} luate câte k.
2. Fie două numere naturale n şi k cu n ≥ k, se cere să se genereze
toate submulţimile (combinările) de k elemente din mulţimea {1,
2, …,n}.
3. Se dau mulţimile A1 = {1, 2, …, p1}, A2 = {1, 2, …, p2}, …, An =
{1, 2, …, pn} se cere să se genereze produsul cartezian al
mulţimilor A1, A2, …, An.
4. Să se descompună un număr natural n în toate modurile posibile
ca sumă de numere naturale.
5. Se consideră mulţimea {1, 2, …, n}. Se cer toate partiţiile acestei
mulţimi.
6. Se dă o sumă S şi n tipuri de monede având valorile v1, v2, …, vn
lei. Se cer toate modalităţile de plată a sumei S utilizând aceste
monede.
7. Considerându-se o tablă de şah de dimensiuni n × n, să se
determine toate modalităţile de amplasare a n ture pe această tablă
în aşa fel încât să nu se atace între ele.
8. Un întreprinzător ce dispune de un capital C trebuie să leagă
dintre n oferte la care trebuie avansate fonfurile fi şi care aduc
profiturile pi pe acelea pe care le poate onora cu capitalul de care
dispune şi care îi aduc profitul total maxim.
9. Să se determine toate delegaţiile de n persoane din care p femei
care se pot forma din t persoane din care v femei.
10. Avem la dispoziţie 6 culori: alb, negru, roşu, albastru, verde,
galben. Să se genereze toate steagurile ce se pot forma cu trei
culori diferite având la mijloc alb sau negru.
11. Un comis-voiajor trebuie să viziteze un număr de n oraşe şi să
revină în oraşul de plecare. Cunoscând legăturile dintre oraşe se
cere să se afişeze toate drumurile posibile pe care poate să le
parcurgă comis-voiajorul ştiind că nu trebuie să treacă de două ori
prin acelaşi oraş.
12. Într-un grup de persoane fiecare persoană se cunoaşte pe sine şi
cunoaşte eventual şi alte persoane din grup. Să se determine toate
modurile în care cele N persoane se pot repartiza în echipe astfel
încât orice persoană din grup să fie cunoscută de cel puţin un
membru al fiecărei echipe.

132
13. Cei doi fii ai unei familii moştenesc prin testament, în mod egal, o
avere formată din monede având ca valori numere întregi pozitive.
O condiţie din testament cere ca fraţii să împartă monezile în
două părţi având valori egale pentru a intra în posesia lor, altfel
suma va fi donată unei biserici. Scrieţi un program care să îi ajute
pe cei doi fraţi să intre în posesia moştenirii.
14. Pe malul unui râu se găsesc c canibali şi m misionari. Ei urmează
să treacă râul cu o barcă cu două locuri. Dacă pe unul dintre
maluri se vor afla mai mulţi canibali decât misionari aceştia din
urmă vor fi mâncaţi de canibali. Se cere un program care să
afişeze toate modalităţile de trecere a râului în care misionarii să
nu fie mâncaţi.
15. Pentru elaborarea unui test de aptitudini avem un set de n
întrebări, fiecare fiind cotată cu un număr de puncte. Să se
elaboreze toate chestionarele având între a şi b întrebări distincte,
fiecare chestionar totalizând între p şi q puncte. Întrebările sunt
date prin număr şi punctaj.
16. 2n + 1 persoane participă la discuţii (masă rotundă) care durează n
zile. Să se găsească variantele de aşezare la masă astfel ca o
persoană să nu aibă în două zile diferite acelaşi vecin.
17. Să se determine 5 numere de câte n cifre fiecare cifră putând fi 1
sau 2, astfel încât oricare dintre cele 5 numere să coincidă în m
poziţii şi să nu existe nici o pozitie care să conţină aceeaşi cifră în
toate cele 5 numere.
18. Să se genereze o secvenţă binară de lungime 2n+n+1 astfel încât
orice două secvente consecutive de n biti să fie diferite.
19. Se dau x1, x2, …, xn numere naturale cu cel mult 9 cifre şi n ≤ 100.
Fiecare număr xi este reprezentat în baza bi (2 ≤ bi ≤ 10) care nu
este cunoscută. Să se gasească baza bi pentru fiecare număr xi
astfel încât intervalul [a, b] în care sunt cuprinse cele n numere
(după transformarea în baza 10) să fie de lungime minimă.
20. Numărul 123456789 înmulţit cu 2, 4, 7 sau 8 dă ca rezultat tot un
număr de nouă cifre distincte (fără 0). Această proprietate nu
funcţionează pentru numerele 3, 6 sau 9. Există totuşi multe
numere de nouă cifre distincte (fără 0) care înmulţite cu 3 au
aceeaşi proprietate. Să se listeze toate aceste numere în care
ultima cifră este 9.
21. Să se dispună pe cele 12 muchii ale unui cub toate numerele de la
1 la 12, astfel încât suma numerelor aflate pe muchiile unei feţe să
fie aceeaşi pentru toate feţele.

133
22. Toate oraşele unei ţări sunt legate la o reţea de linii feroviare.
Două oraşe i şi j pot fi legate prin cel mult o legătură directă prin
care circulă un singur tren pe zi între orele p(i, j) şi s(i, j). Dacă se
dau două oraşe A şi B, să se afişeze traseul, staţiile de schimbare a
trenurilor şi timpii de aşteptare în aceste staţii astfel încât durata
totală a călătoriei să fie minimă.
23. Configuraţia unui teren este specificată printr-o grilă gen tablă de
şah de dimensiune n×n, fiecare careu având o anumită înălţime.
Într-un careu precizat al grilei se plasează o minge. Ştiind că
mingea se poate deplasa într-unul din cele maxim 8 careuri vecine
doar dacă acesta are o cotă strict mai mică, să se genereze toate
trasele pe care le poate urma mingea pentru a părăsi terenul.
24. Un cal şi un rege se află pe o tablă de şah. Unele câmpuri ale
tablei sunt "arse", poziţiile lor fiind cunoscute. Calul nu poate
călca pe câmpuri "arse", iar orice mişcare a calului face ca
respectivul câmp să devină "ars". Să se afle dacă există o
succesiune de mutări permise (cu restricţiile de mai sus), prin care
calul să poată ajunge la rege şi să revină la poziţia iniţială. Poziţia
iniţială a calului, precum şi poziţia regelui sunt considerate
"nearse".
25. Fie o matrice n x m cu valori de 0 şi 1. Dându-se o poziţie iniţială
(i,j) să se găsească toate ieşirile din matrice mergând doar pe
elemente cu valoarea 1.
26. O fotografie alb-negru este dată printr-o matrice cu valori 0 şi 1.
Fotografia prezintă unul sau mai multe obiecte. Punctele fiecărui
obiect sunt marcate cu 1. Se cere un program care să calculeze
numărul de obiecte din fotografie.
27. Se dă o matrice binară în care elementele cu valoarea 1
delimitează o suprafaţă marcată cu elemente de zero. Dându-se
coordonatele unui punct în cadrul matricei să se “coloreze”
suprafaţa în interiorul căreia cade punctul.
28. Se dau n puncte albe şi n puncte negre în plan prin perechi (i,j) de
numere întregi. Fiecare punct alb se va uni cu un singur punct
negru asttfel încăt segmentele formate să nu se intersecteze. Să se
găsească o soluţie ştiind ca oricare 3 puncte sunt necoliniare.
29. Se dau n puncte în plan prin perechi (i,j) de numere întregi. Să se
selecteze 4 puncte ce reprezintă colţurile pătratului care cuprinde
numărul maxim de puncte din cele n puncte date.

134
30. Se dă un cuvânt si o matrice de caractere de dimensiune m x n. Să
se găsească în matrice prefixul de lunginme maximă al cuvântului
dat.
31. Se dă un număr natural par n. Să se afişeze toate şirurile de n
paranteze care se închid corect.
32. Se consideră o bară de lungime n şi k repere de lungimi l1, l2,…,
lk. Din bară trebuiesc tăiate bucăţi de lungimea reperelor date,
astfel încât să avem cel puţin câte o bucată de lungime li cu i =
1..k şi pierderile să fie nule.
33. Se consideră un număr nelimitat de piese dreptunghiulare de
dimensiuni a × 2a. Piesele se vor folosi la pavarea fără fisuri a
unei zone rectangulare de dimensiuni ka × pa. Se cer toate
soluţiile (reprezentare grafică).

3.5. Metoda Branch and Bound


3.5.1. Prezentarea metodei
Metoda branch and bound foloseste la rezolvarea problemelor la care
domeniul în care se caută soluţia este foarte mare şi nu se cunoaşte un
alt algoritm care să conducă mai rapid la rezultat. Problemele care pot
fi abordate prin această metodă pot fi modelate într-un mod asemă-
nător celui folosit la metoda backtracking. Se pleacă de la o configu-
raţie iniţială şi se reţine şirul de operaţii prin care aceasta este transfor-
mată într-o configuraţie finală dacă aceasta este dată, în alte cazuri se
cere configuraţia finală ştiind că trebuie să verifice anumite condiţii de
optim.
Diferenţa dintre cele două metode constă în faptul că metoda
backtracking la fiecare etapă selectează un singur succesor după care
se face verificarea condiţiilor de continuare, iar metoda branch and
bound generează la fiecare pas toate configuraţiile posibile (toţi succe-
sorii) care rezultă din cea curentă şi le stochează într-o listă. Se alege
apoi un element din listă după un criteriu ce depinde de problemă şi
se reia procesul de expandare.
Alegerea optimă a unui element din această listă pentru
expandare se face cu ajutorul unei funcţii f = g + h în care g este o
funcţie care măsoară lungimea drumului parcurs de la configuraţia
iniţială până la nodul curent, iar h este o funcţie (euristică) care
estimează efortul necesar pană se ajunge la soluţie şi este specifică
fiecărei probleme. Alegerea funcţiei h este foarte importantă din punct
de vedere a vitezei de execuţie a programului.
135
Se lucrează cu două liste: lista open în care se reţin configuraţiile
neexpandate încă şi lista close care le memorează pe cele expandate.
Soluţia se construieşte folosind un arbore care se parcurge pe baza
legăturii tată.
Nodurile sunt înregistrări care cuprind următoarele informaţii:
1. configuraţia la care s-a ajuns, t;
2. valorile funcţiilor g si h,
3. adresa nodului 'tată';
4. adresa înregistrării următoare în lista open sau close;
5. adresa înregistrării următoare în lista succesorilor.
Algoritmul este următorul:
A. înregistrarea corespunzătoare configuraţiei iniţiale este încărcată
în open cu g=0 şi f=h;
B. atât timp cât nu s-a selectat spre expandare nodul corespunzător
configuraţiei finale şi lista open este nevidă, se execută
următoarele:
1. se selectează din lista open nodul t cu f minim;
2. se expandează acest nod obţinând o listă liniară simplu
înlănţuită cu succesorii săi;
3. pentru fiecare succesor din această listă se execută:
3.1. se ataşează noul g, obţinut ca sumă între valoarea lui g
a configuraţiei t şi costul expandării (de obicei 1);
3.2. se testează dacă acest succesor aparţine listei open sau
close şi în caz afirmativ, se verifică dacă valoarea lui g
este mai mică decât cea a configuraţiei găsite în listă:
3.2.1. în caz afirmativ, nodul găsit este direcţionat
către actualul părinte (prin fixarea legăturii tată)
şi se ataşează noul g, iar dacă acest nod se găseşte
în close, este trecut în open;
3.2.2. în caz că acest nod nu se găseşte în open sau
close, este introdus în lista open;
C. dacă s-a selectat pentru expandare nodul corespunzător configu-
raţiei finale atunci se trasează folosind o procedură recursivă
drumul de la configuraţia iniţială la cea finală utilizând legătura
'tată';
D. dacă ciclul se încheie deoarece lista open este vidă înseamnă că
problema nu are soluţie.
Observaţie. Algoritmul se poate implementa folosind o singură listă,
iar diferenţierea dintre nodurile expandate şi cele neexpandate se va
face după un câmp special. Apare însă dezavantajul că operaţiile de
136
căutare se fac într-o listă mai lungă. Acest dezavantaj se poate elimina
folosind o structură specială numită tabelă hash. În unele cazuri se
poate folosi un arbore binar.
3.5.2. Probleme rezolvate
R.3.5.1. [Problema discretă a rucsacului] Se dă un rucsac de capacita-
te M şi un număr de n obiecte, specificându-se masele obiectelor şi
profiturile ce se obţin prin transportul lor cu rucsacul. Se cere un pro-
gram care să determine obiectele alese astfel încât să se obţină un
profit maxim. Soluţia se va da sub forma unui vector definit astfel:
xk=0 dacă obiectul k nu este luat în rucsac, xp = 1 dacă obiectul p este
luat în rucsac.
Rezolvare:
În rezolvarea acestei probleme nu vom folosi două liste ci un arbore
binar. Algoritmul face, în prealabil, o ordonare descrescătoare a
obiectelor după raportul profit/masă. Se porneşte de la configuraţia
iniţială în care nici obiect nu a fost încă ales. Se parcurge iterativ lista
de obiecte. La fiecare iteraţie se decide includerea sau nu a obiectului
curent în rucsac şi se expandează configuraţia curentă în două
configuraţii corespunzătoare celor doi subarbori. Nodul corespunzător
fiului drept se obţine din nodul tată la care se adaugă obiectul curent,
iar cel corespunzător fiului stâng se obţine din nodul tată însă fără
adăugarea obiectului analizat. Analiza continuă numai pe una din cele
două ramuri ale arborelui binar, iar opţiunea pentru una din cele două
ramuri se face pe baza unei funcţii H care estimează profitul.

program RucsacBB;
uses crt;
const max = 25;
type TObiect = record masa, profit:integer; nr:integer end;
PNod= ^Nod;
Nod = record
nivel,luat:integer;
p_estimat,profit,masa:integer;
tata,drept,stang:PNod;
end;
var n: integer;
obiect: array[1..max] of TObiect;
G, profitmax,masatotala:integer;
solutie:array[1..max] of 0..1;
137
procedure Sort;
var i,j:integer; aux:TObiect;
begin
for i:=1 to n-1 do for j:=i+1 to n do
if
(obiect[i].profit/obiect[i].masa < obiect[j].profit/obiect[j].masa)
then begin aux:= obiect[i]; obiect[i]:=obiect[j]; obiect[j]:=aux;
end;
end;
function H(pn:PNod):integer;
var i,k:integer; p,m:integer;
begin
p := pn^.profit; m := pn^.masa; k := pn^.nivel;
if m <= G then begin
for i:= k + 1 to n do if m + obiect[i].masa <= G then
begin
p := p + obiect[i].profit; m := m + obiect[i].masa
end;
H := p
end
else H:=-1;
end;
procedure Branch;
var k:integer; nodc,stang,drept:PNod;
begin
k:=0; new(nodc); nodc^.tata:= nil; nodc^.nivel:=k;
nodc^.luat:=0; nodc^.masa:=0; nodc^.profit:=0;
repeat
inc(k); new(stang); new(drept);
nodc^.stang:=stang; nodc^.drept:=drept;
stang^.tata:=nodc; stang^.nivel:=k; stang^.luat:=0;
stang^.masa:=nodc^.masa; stang^.profit:=nodc^.profit;
stang^.p_estimat:= H(stang); drept^.tata:=nodc;
drept^.nivel:=k; drept^.luat:=1;
drept^.masa:=nodc^.masa + obiect[k].masa;
drept^.profit:=nodc^.profit + obiect[k].profit;
drept^.p_estimat:=H(drept);
if stang^.p_estimat > drept^.p_estimat
then nodc:=stang else nodc:=drept;
until k=n;

138
profitmax := nodc^.profit; masatotala := nodc^.masa;
while nodc^.tata<>nil do begin
solutie[nodc^.nivel]:=nodc^.luat; nodc:=nodc^.tata;
end
end;
var i:integer;
begin {main}
write('Capacitatea rucsacului = '); readln(G);
write('Numarul obiectelor = '); readln(n);
for i:= 1 to n do
begin
write('Masa[',i ,'] = '); readln(obiect[i].masa);
write('Profitul[',i ,'] = '); readln(obiect[i].profit);
obiect[i].nr := i;
end;
Sort;
Branch;
writeln('Solutie:');
for i:= 1 to n do if solutie[i]=1 then write(obiect[i].nr,' ');
writeln;
writeln('Profitul obtinut = ', profitmax);
writeln('Masa totala = ', masatotala);
readln;
end.
R3.5.2. [Jocul Perspico] Se consideră o tablă cu 4 x 4 căsuţe, fiecare
căsuţă conţine un număr între 1 si 15 si o căsută conţine zero (este
liberă). Ştiind că 0 îşi poate schimba poziţia cu orice numâr natural
aflat deasupra, la dreapta, la stânga sau jos, în raport cu poziţiâ în
care se află numărul 0, se cere să se precizeze şirul de mutări prin
care se poate ajunge de la o configuraţie iniţială dată la o confi-
guraţie finală. Se cere de asemenea ca acest sir să fie optim, în sensul
că trebuie să se ajungă la configuraţia finalâ într-un număr minim de
mutări.
Rezolvare:
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
#define MAX 4
#define maxop 4

139
typedef struct Nod{
char conf[MAX][MAX];
int h, g;
struct Nod *down, * up;
}Nod, *PNod;
PNod New(PNod p){
PNod pnod = (PNod) malloc( sizeof(Nod) );
if (pnod==NULL){
printf("\nNot enough memory !\n");
exit(0);
}
*pnod = *p;
return pnod;
}
void Insert(PNod * list, PNod pnod){
pnod->down = *list; *list = pnod;
}
void RemoveAll(PNod *list){
PNod p;
while(*list){
p = *list; *list = (*list)->down;free(p);
}
}
PNod Expand(PNod pnod){
int i, j, k, ii, jj;
static int lin[maxop] = {-1, 0, 1, 0};
static int col[maxop] = { 0, 1, 0,-1};
PNod list = NULL; PNod newNod = NULL;
for(i=0; i < MAX; i++) for(j=0; j < MAX; j++)
if (pnod->conf[i][j]==0) ii=i,jj=j;
for(k = 0; k < maxop; k++){
i = ii + lin[k]; j = jj + col[k];
if(i >= 0 && i < MAX && j >= 0 && j < MAX){
newNod = New(pnod);
newNod->conf[ii][jj] = newNod->conf[i][j];
newNod->conf[i][j] = 0; Insert(&list,newNod);
}
}
return list;
}

140
int Equals(PNod n1, PNod n2){
int i, j;
if (n1->h != n2->h) return 0;
for (i = 0; i < MAX; i++) for (j = 0; j < MAX; j++)
if (n1->conf[i][j] != n2->conf[i][j]) return 0;
return 1;
}
PNod Extract(PNod * list, PNod p){
PNod temp = *list, spy = *list;
if (p==NULL || p == *list){ *list = (*list)->down; return temp;}
while(temp != p){ spy = temp; temp = temp->down;}
spy->down = temp->down;
return temp;
}
void PrintNod(PNod pnod){
int i,j;
for(i = 0; i < MAX; i++){ printf("\n|");
for(j = 0; j < MAX; j++) printf("%3d", pnod->conf[i][j]);
printf(" |");
}
printf("\nApasati orice tasta ...”); getch();
}
void Print(PNod pnod){
if (pnod->up) Print(pnod->up); PrintNod(pnod);}
PNod Choice(PNod list){
int f, min = 32000; PNod pmin = NULL;
while (list){
f = list->h + list->g; if ( f < min ){pmin = list; min = f;}
list=list->down;}
return pmin;
}
void ComputeH(PNod t, PNod fin){
int i, j, k, r, s; t->h = 0;
for(i = 0; i < MAX; i++) for(j = 0; j < MAX; j++)
for(r = 0; r < MAX; r++) for(s = 0; s < MAX; s++)
if (t->conf[i][j] == fin->conf[r][s]) t->h += abs(r-i) + abs(s-j);
}
PNod Search(PNod list, PNod pnod){
while (list && !Equals(list,pnod)) list = list->down;
return list;}

141
void Branch(Nod *in, Nod *fin){
PNod Open = NULL; PNod Close = NULL;
PNod TempList = NULL; PNod theOne, temp, found;
PNod initial = New(in); Pnod final = New(fin);
int ok = 0; initial->g = 0;
ComputeH(initial,final); final->h = 0; Insert(&Open,initial);
while(Open){ theOne = Choice(Open); Extract(&Open,theOne);
Insert(&Close,theOne);
if (Equals(theOne, final)){ok = 1; break;}
TempList = Expand(theOne);
while(TempList){
temp = Extract(&TempList,NULL);
temp -> g = theOne->g + 1;
if( found = Search(Open, temp) ){
if(temp->g < found->g){
found->g = temp->g; found->up = theOne;}
free(temp);
}
else if( found = Search(Close, temp) ){
if(temp->g < found->g){
found-> g = temp->g; found->up = theOne;
Extract(&Close, found);
Insert(&Open, found);}
free(temp);
}
else{ temp->up = theOne; ComputeH(temp,final);
Insert(&Open,temp);}
}
}
if (ok){ printf("\nSolutie:\n"); Print(theOne);}
else printf("Nu am avem solutii !");
RemoveAll(Open); RemoveAll(Close);
}
void main(){
Nod in = {{ 5, 1, 3, 4, 9, 7, 8, 0, 6, 2, 10, 11,
13, 14, 15, 12}, 0, 0, NULL,NULL};
Nod fin ={{ 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,
13,14,15, 0},0,0, NULL,NULL};
clrscr(); Branch(&in,&fin); getch();
}

142
3.5.3. Probleme propuse
1. [Generalizare] Să se rezolve problema Perspico pentru o tablă de
dimensiuni n x n cu numere de la 1 la n2-2 şi cu două zerouri (locuri
libere).
2. [Diamante] Se dă o tablă sub formă de cruce pe care sunt aşejate 32
de piese de diamant (◊) aşa cum se poate observa în figura de mai jos.
Poziţia din centru este liberă. O piesă se poate muta doar sărind
peste o altă piesă într-un loc liber, numai pe orizontală sau pe verticală
iar piesa peste care se sare trebuie luată. Se cere să se gasească un şir
de mutări astfel încât (să luăm cât mai multe piese de diamant)
pornind de la configuraţia iniţială să se ajungă la o configuraţie finală
care să conţină numai o singură piesă şi aceasta să se afle în centru.

◊ ◊ ◊
◊ ◊ ◊
◊ ◊ ◊ ◊ ◊ ◊ ◊
◊ ◊ ◊ ◊ ◊ ◊ ◊
◊ ◊ ◊ ◊ ◊ ◊ ◊
◊ ◊ ◊
◊ ◊ ◊

Configuraţia iniţială Configuraţia finală

3. [Operaţii] Se dă un tablou m×n (1 ≤ m+n ≤15) cu elemente numere


întregi. Se numeşte "operaţie" în acest tablou înmulţirea unei linii sau
coloane cu -1. Să se determine cea mai mică secvenţă de operaţii care
aduce tabloul la o formă în care toate sumele de linii sau coloane dau
numere nenegative.
4. [Buldozerul] Un teren de formă dreptunghiulară de dimensiuni m×n
este împărţit în pătrate de dimensiuni egale (ca la şah). Unele pătrate
conţin grămezi de pamânt iar altele sunt netede. Un buldozer plasat
într-un pătrat neted trebuie să niveleze tot terenul. Să se găsească un
loc de unde poate porni buldozerul şi ordinea de nivelare a pătratelor
(în cazul în care există o soluţie) ştiind că acesta:
• se deplasează doar pe direcţiile N, S, E, V;
• nu se poate deplasa peste un loc neted;
143
• trece dintr-un loc neted peste pătratul cu pământ de lângă
el în alt pătrat neted netezind astfel pătratul peste care a
trecut.
5. [Obstacole şi cai] Pe o tablă de şah n × n se găsesc p cai şi k
obstacole amplasate într-o configuraţie iniţială dată. Mutarea calului
este cea cunoscută de la şah. Calul poate sări peste un obstacol sau
peste un alt cal, dar nu poate staţiona pe o poziţie ocupată de un cal
sau de un obstacol. Să se determine, dacă există, un şir de mutări ale
cailor astfel încât să se ajungă la o configuraţie finală dată.
6. [Inele] Fie două inele în care sunt aşezate echidistant 13 bile.
Inelele au două bile în comun. Deci în
total vor fi 24 de bile din care 11 negre,
11 roşii şi două gri (ca în figura de mai
jos). Fiecare inel se poate roti în sensul
acelor de ceasornic cel puţin o poziţie şi
cel mult 12, antrenând cu el cele 13 bile
din interiorul său. Problema constă în a
aduce o configuraţie iniţială dată, la
configuraţia (finală) din figura următoare printr-un număr minim de
rotiri.
7. [Joc] Se dă o tablă de joc cu 31 de pătrate care conţine opt piese de
‘O’ şi opt piese de ‘X’ ca în figura de mai jos.
O piesă poate fi mutată numai pe orizontală şi pe verticală într-
un loc liber învecinat, sau prin săritură peste o altă piesă alăturată dacă
se ajunge într-un loc liber. Jocul constă în a schimba între ele locurile
ocupate de piesele 'O’ şi 'X' într-un număr minim de mutări.

A B C D E F G
1
2 O O O
3 O O O
4 O O X X
5 X X X
6 X X X
7

144
4. Programare modulară în Turbo Pascal
4.1. Unităţi de translatare
Pentru un program realizat conform principiului modularizării, trebuie
avute în vedere atât consistenţa, cât şi cuplajul modulelor. Consistenţa
unui modul este o mărime ce caracterizează relaţiile dintre elementele
unui modul, iar cuplajul exprimă interconexiunile dintre module.
Orice modul are trei atribute fundamentale: funcţia, interfaţa şi
logica internă. Funcţia modulului descrie transformarea care are loc la
apelarea modulului, interfaţa indică modul de apelare a elementelor
modulului, iar logica internă indică fluxul în cadrul modulului.
Conform principiului consistenţei, elementele unui program care
sunt strâns legate între ele trebuie să facă parte din acelaşi modul, iar
elementele nelegate trebuie să fie introduse în module diferite (a se
vedea uniturile Turbo Pascal şi fişierele antet utilizate de mediile de
programare C sau C++).
Cuplajul modulelor caracterizează mecanismul de transmitere a
datelor şi a atributelor acestora între module. Dacă se urmăreşte
maximizarea independenţei modulelor atunci va creşte şi consistenţa
modulelor. Totuşi, de multe ori, programul este organizat în module,
nu neapărat independente, unele dintre acestea fiind legate între ele
printr-un graf de cuplare. Dintre două module cuplate, la un moment
dat, unul este modul apelant (client), iar celălalt este modul apelat
(furnizor de serviciu).
Există mai multe modalităţi de cuplare: cuplare prin conţinut,
cuplare prin date comune, cuplare prin fişiere de date, cuplare prin
indicatori de control şi cuplare prin structuri de date.
Cuplajul bazat pe conţinut apare când modulul apelant foloseşte
elemente ale modulului apelat: date declarate în modulul apelat care
nu au fost declarate globale sau externe, conţinutul unui modul este
fizic inclus în alt modul (încuibarea subprogramelor în Pascal) etc.
Cuplajul prin date comune este caracteristic acelor module care folo-
sesc o structură de date declarată global. Cuplarea prin fişiere de date
asigură independenţa modulelor de numărul şi ordinea fizică a datelor.
Indicatorii de control sunt date care transmise unui modul determină
modul de executare a acestuia: variabile logice, identificatori de
funcţii etc. Cuplarea prin structuri de date apare atunci când modulul
apelant transmite modulului apelat, ca parametru, o întreagă structură
de date.

145
Modulele Turbo Pascal se mai numesc şi unităţi de translatare (pot fi
compilate separat; vezi şi metodele de proiectare C, C++ şi Java) sau
unituri. Un modul Turbo Pascal este o colecţie de constante, tipuri
definite, variabile, proceduri şi funcţii care sunt compilate separat şi
pot fi folosite de alte module sau de programul principal. Modulele
Turbo Pascal, cele mai importante, sunt: SYSTEM - implementează
operaţiile de nivel coborât; DOS - implementează funcţiile sistemului
de operare MS-DOS; CRT - permite controlul tastaturii şi al moni-
torului; GRAPH - implementează funcţiile grafice uzuale; PRINTER -
permite controlul imprimantei; OVERLAY - permite segmentarea
programelor.
Un avantaj major al introducerii unităţilor de translatare constă
în facilitarea lucrului în echipă.
O unitate de translatare Turbo Pascal are structura:
unit <id_unit>;
interface
uses <lista_unităţilor_de_translatare_apelate>;
declaraţii globale
lista_funcţiilor_şi_procedurilor_publice;
implementation
declaraţii_locale
proceduri_şi_funcţii (definiţii ale subprogramelor publice şi locale)
begin
secvenţă_de_iniţializare
end.
şi are patru secţiuni: antet, interfaţă, implementare şi iniţializare.
Antetul specifică numele modulului <id_unit> care trebuie să
coincidă cu numele fişierului sursă.
Partea de interfaţă conţine declararea funcţiilor şi procedurilor
care sunt definite în modul şi pot fi folosite (apelate) de alte module.
Descrierea algoritmului fiecărui subprogram declarat în interfaţă este
realizată în secţiunea implementation.
Constantele, tipurile, variabilele, procedurile şi funcţiile decla-
rate în partea de implementare sunt obiecte interne modului, fiind
ascunse altor module.
Deoarece dimensiunea unui modul este în general limitată (iar
pentru Turbo Pascal, limita maximă este de 64KB), un proiect este
compus din mai multe module, produsul final putând avea o
dimensiune mult mai mare de 64KB.

146
4.2. Probleme rezolvate
R4.1. [LU] Se consideră sistemul de ecuaţii liniare Ax=b, unde A este
o matrice pătratică de dimensiune n, iar b este un vector coloană n -
dimensional. Să se determine soluţia x = A-1b (în ipoteza existenţei şi
unicităţii soluţiei), prin metoda descompunerii matricei A în produsul
a două matrice triunghiulare.
Rezolvare:
Un sistem de ecuaţii liniare, Ax=b, poate fi rezolvat astfel: Se descom-
pune matricea coeficienţilor în produsul a două matrice triunghiulare
şi apoi se rezolvă două sisteme triunghiulare.
Căutăm matricele L şi U astfel încât:
⎛ 1 0 M 0 0⎞ ⎛ u1,1 u1, 2 L u1,n ⎞
⎜ ⎟ ⎜ ⎟
L= ⎜ l 2 ,1 1 M 0 0 ⎟ ,U= ⎜ 0 u 2 , 2 L u 2 , n ⎟ şi A =LU.
⎜L L O 1 0 ⎟ ⎜ L L O L ⎟
⎜ ⎟ ⎜ ⎟
⎜l ⎟ ⎜ 0 0 0 u n ,n ⎟⎠
⎝ n ,1 l n , 2 L l n ,n −1 1 ⎠ ⎝
La pasul 1, calculăm u1,j şi li,1 cu ajutorul relaţiilor: u1,j = a1, j, 1≤j≤ n;
li,1 = ai,1 /u1,1, 1 ≤ i ≤ n. La paşii următori se determină ui,j şi li,j astfel:
♦ Dacă i ≤ j atunci relaţia:
ai,j = li,1u1,j + li,2u2,j + … + li,i-1ui-1,j + ui,j
conduce la:
i −1
ui,j = ai,j - ∑l
k =1
i ,k u k , j , 2 ≤ i ≤ j.
♦ Dacă i>j atunci relaţia:
ai,j = li,1u1,j + li,2u2,j + … +li,j-1uj-1,j + li,juj,j
conduce la:
j −1
li,j = (ai , j − ∑l
k =1
i ,k u k , j ) u j , j , i > j ≥ 2.
Soluţia sistemului considerat se obţine prin rezolvarea sistemelor
Ly=b (inferior triunghiular) şi Ux=y (superior riunghiular).
Observăm că, dacă există 1 ≤ j ≤ n astfel încât uj,j =0 atunci
procedeul nu mai poate continua. De asemenea, când uj,j este mic,
acurateţea rezultatelor scade deoarece erorile de rotunjire se înmulţesc
cu 1/uj,j. Este necesară astfel, interschimbarea ecuaţiilor sistemului
astfel încât la pasul j, valoarea uj,j să fie, în valoare absolută (modul),
cea mai mare. Se va obţine, astfel, descompunerea PA=LU unde P
este o matrice obţinută din matricea In=δi,j=(if i = j then 1 else 0), prin
147
interschimbarea liniilor conform interschimbării intervenite pentru
matricea A. Următorul pas constă în rezolvarea sistemelor Ly=Pb şi
Ux=y. Implementarea variantei stabile este realizată de procedurile
LUSTAB şi LUSISTAB din unitatea de translatare MATH şi vor fi
utilizate în cadrul aplicaţiei R4.4. Programul prezentat mai jos,
utilizează descompunerea LU fără interschimbare, implementată de
procedura LUDESC pentru rezolvarea sistemului de ecuaţii dat. În
plus se calculează determinantul matricei sistemului şi inversa acesteia
(când există din punct de vedere al reprezentării numerelor în virgulă
mobilă). Se folosesc procedurile suplimentare LUSISTEM şi LUDET.
Această metodă este utilă atunci când avem de rezolvat mai multe
sisteme cu aceeaşi matrice sau când dorim o soluţie cu precizie ridica-
tă. Textul sursă al unităţii MATH este descris în finalul secţiunii.
Programul Pascal este următorul:

program R4_1;
uses math;
var a:matrice; {tip de date definit in math}
b,x,y,c,e:vector; {tip de date definit in math}
i,j,k,n:integer;
det:real;
begin
writeln;
write('Dimensiunea sistemului');readln(n);
writeln('Matricea sistemului este :');
for i:=1 to n do begin
write('Linia',i:2,' '); for j:=1 to n do read(a[i,j]);
writeln
end;
writeln;
writeln('Termenul liber este :');
for i:=1 to n do read(b[i]); writeln;
ludesc(n,a);
if not succes {variabilă definită în math}
then writeln(' Sistemul este LUDESC incompatibil')
else begin
lusistem(n,a,b,x);
writeln;
writeln(' Solutia sistemului este :');writeln;
for i:=1 to n do writeln(x[i]:10:6); writeln;

148
ludet(n,a,det);
writeln('LU-Determinantul matricei sistemului este: ',det);
writeln;
writeln('Matricea inversa - pe coloane este:');
for j:=1 to n do begin
write(' Coloana ',j,' ');
for i:=1 to n do e[i]:=0; e[j]:=1;
lusistem(n,a,e,y);
for i:=1 to n do begin
write(y[i]:10:4,' ');
if i mod 6 = 0 then writeln
end;
writeln
end
end
end.

R4.2. [LLT] O matrice cu numere reale, simetrică este pozitiv defini-


tă, dacă se poate scrie ca produsul dintre matricea inferior triunghiu-
lară L şi transpusa acesteia. Să se scrie o procedură care să testeze
dacă o matrice este pozitiv definită şi în caz afirmativ, să obţină
descompunerea LLT, unde prin LT notăm transpusa matricei L. Să se
utilizeze această procedură pentru a rezolva un sistem de ecuaţii, cu
matrice pozitiv definită.
Rezolvare:
Dacă A este o matrice simetrică şi pozitiv definită, atunci există o ma-
trice inferior triunghiulară L, astfel încât A=LLT.
Elementele matricei L se obţin prin scrierea egalităţii :
⎛ 1,1 a1, 2 L a1,n ⎞ ⎛ l1,1 0 L 0 ⎞ ⎛ l1,1 l 2,1 L l n ,1 ⎞
a
⎜ ⎟ ⎜ ⎟⎜ ⎟
⎜ a 2,1 a 2, 2 L a 2,n ⎟ = ⎜ l 2,1 l 2, 2 L 0 ⎟ ⎜ 0 l 2, 2 L l n , 2 ⎟ ,
⎜ M M O M ⎟ ⎜ M M O M ⎟⎜ M M O M ⎟
⎜ ⎟ ⎜ ⎟⎜ ⎟
⎜a ⎟ ⎜ ⎟⎜ 0 L l n, n ⎟⎠
⎝ n,1 a n , 2 L a n ,n ⎠ ⎝ l n ,1 l n, 2 L l n, n ⎠ ⎝ 0
prin următoarele relaţii:
l1,1 = a1,1 ;
li ,1 = ai ,1 l1,1 ; i>1;

149
12
⎛ n

l j , j = ⎜ a j , j − ∑ l 2j ,k ⎟ , j>1;
⎝ k =1 ⎠
⎛ j −1

li , j = ⎜⎜ ai , j − ∑ l i ,k l j ,k ⎟⎟ l j , j , i>j>1.
⎝ k =1 ⎠
Procedeul se blochează când există 1 ≤ j ≤ n astfel încât rezulta-
tul expresiei de sub radical este negativ sau zero. În această situaţie
matricea nu este pozitiv definită.
Dacă matricea este simetrică şi pozitiv definită atunci pentru a
rezolva sistemul Ax = b se consideră cele doua sisteme Ly = b şi
LTx=y care se rezolva prin substitutie (eliminare) înainte, respectiv
înapoi.
Procedura LLTD verifică dacă matricea, primită ca argument,
este pozitiv definită şi obţine descompunerea LLT, în caz afirmativ.
Procedura LLSISTEM rezolvă cele două sisteme triunghiulare.
Procedurile sunt incluse în unitatea de translatare MATH. Ele
sunt folosite în programul de mai jos.

program r4_2;
uses math;
var a:matrice;n,i,j:integer;
b,x,y:vector;
begin
writeln;
write('Dimensiunea sistemului:');read(n);
for i:=1 to n do begin
write('Linia ',i); for j:=1 to n do read(a[i,j]); writeln
end;
writeln('Termenul liber :'); for i:=1 to n do read(b[i]); writeln;
lltd(n,a);
if not nenul then write('Eps este prea mare ')
else if not pozdef then write('Matricea nu este pozitiv definita')
else begin
writeln('Matricea sistemului este pozitiv definita');
llsistem(n,a,b,x); writeln('Solutia sistemului este :');
for i:=1 to n do writeln (x[i]:8:4);
end
end.
150
R4.3. [Iterativitate]. Să se utilizeze metodele iterative Iacobi şi Gauss-
Seidel pentru a rezolva un sistem de ecuaţii liniare (numărul ecuaţii-
lor fiind egal cu numărul necunoscutelor).
Rezolvare:
Considerăm sistemul Ax=b, unde A este o matrice pătratică, de di-
mensiune n, cu elemente reale. Presupunem că ai,i ≠ 0 pentru oricare
1 ≤ i ≤ n . Atunci sistemul
ai,1 x1 + ai,2 x2 + … + ai,n xn = bi , i=1,2, ... ,n;
se mai poate scrie astfel :
⎛ ⎞
xi = ⎜⎜ bi − ∑ ai , j x j ⎟⎟ ai ,i , i = 1, 2, …, n.
⎝ j ≠i ⎠
Dacă presupunem că x(0) este o aproximare iniţială a soluţiei sistemu-
lui, atunci formula de mai sus sugerează următoarea metodă iterativă
pentru determinarea soluţiei sistemului Ax=b:
⎛ ⎞
xi( m +1) = ⎜⎜ bi − ∑ ai , j x (jm ) ⎟⎟ ai ,i , i = 1, 2, …, n; m ≥ 0.
⎝ j ≠i ⎠
Aceasta este metoda aproximării simultane (metoda lui Iacobi). Dacă
în membrul drept al relaţiei anterioare, utilizăm componentele recent
calculate, obţinem metoda aproximării succesive (metoda Gauss-Seidel).
Iteraţia Gauss-Seidel este definită prin formula:
⎛ i −1 n ⎞
xi( m +1) = ⎜⎜ bi − ∑ ai , j x (jm +1) − ∑a i, j x (jm ) ⎟⎟ ai ,i , i=1, 2, .., n; m ≥ 0.
⎝ j =1 j =i +1 ⎠
Cele două metode converg în cazul unui sistem liniar cu matrice dia-
gonal dominantă:
a i ,i > ∑
ai , j , i =1, 2, …, n.
j ≠i
Funcţia Pascal diag_dom verifică dacă o matrice pătratică de
dimensiune n este diagonal dominantă.
Procedurile Iacobi şi Seidel implementează cele două formule
iterative. Acestea sunt folosite în programul următor, care determină
soluţia unui sistem de ecuaţii liniare cu matrice diagonal dominantă.
Algoritmul se opreşte după un număr de iteraţii specificat sau
când precizia eps a fost atinsă.

151
program r4_3;
uses math;
var a:matrice;
b,x:vector;
i,j,n,niter:integer;
begin
writeln;
write('Dimensiunea sistemului>');read(n);
writeln('Coloanele matricei --- b ');
for i:=1 to n do begin
for j:=1 to n do read(a[i,j]);
read (b[i]); writeln
end;
write('Numar-iteratii >');read(niter);
if diag_dom(n,a) then begin
for i:=1 to n do x[i]:=0; iacobi(n,a,b,niter,x);
writeln;
writeln('Solutia obtinuta prin metoda Iacobi :');
for i:=1 to n do writeln (x[i] );
for i:=1 to n do x[i]:=0; Seidel(n,a,b,niter,x);
writeln;
writeln('Solutia obtinuta prin metoda Gauss-Seidel:');
for i:=1 to n do writeln(x[i])
end
else write(' Algoritmii Iacobi şi Gauss-Seidel nu converg.')
end.
R4.4. [Prelucrarea datelor prin metoda celor mai mici pătrate] Fie
A⊆RxR, A = {(x1 ,y1), (x2 ,y2), ..., (xn ,yn)}. Presupunem că yi sunt
valorile în xi ale unei funcţii reale a cărei expresie analitică nu este
cunoscută. Se cere identificarea unui model f, care să minimizeze su-
ma pătratelor erorilor:
n
S = ∑ ( y i − f ( xi ) ) .
2

i =1

Rezolvare:
Pentru a determina un astfel de model, se foloseşte metoda celor mai
mici pătrate. Aceasta este aplicată frecvent pentru următoarele modele
(dintre care se va face alegerea ): y = a + b x (liniar); y = a xb (putere);
y = a bx (exponential); y = c1 +c2 x+...+ ck+1xk(polinomial); y = a + b/x

152
(hiperbolic) etc. şi implică rezolvarea unor sisteme de ecuatii,
necunoscutele fiind coeficienţii curbei.
Pentru modelul liniar sistemul (cu necunoscutele a şi b) ce tre-
buie rezolvat este următorul:
⎧ n n

⎪⎪ na + b ∑
i =1
x i = ∑
i =1
yi ;
⎨ n n n
⎪a ∑ xi + b∑ xi2 = ∑ xi y i .
⎪⎩ i =1 i =1 i =1
Determinarea parametrilor modelului putere se bazează pe rezol-
varea următorului sistem:
⎧ n n

⎪⎪ n ln a + b ∑
i =1
ln x i = ∑
i =1
ln y i ;
⎨ n n n
⎪ln a ∑ ln xi + b∑ ln 2 xi = ∑ ln xi ln y i
⎪⎩ i =1 i =1 i =1
cu necunoscutele ln a şi b.
Modelul exponenţial necesită rezolvarea următorului sistem:
⎧ n n

⎪⎪ n ln a + ln b ∑
i =1
x i = ∑
i =1
ln y i ;
⎨ n n n
⎪ln a ∑ xi + ln b∑ xi2 = ∑ xi ln y i
⎪⎩ i =1 i =1 i =1
cu necunoscutele ln a şi ln b.
În cazul modelului polinomial, trebuie rezolvat următorul sistem
cu k+1 ecuaţii şi k+1 necunoscute {c0 ,c1,...,ck}:
⎧ n n n
+ 1∑ i + + k∑ i = ∑
k
⎪ c 0 c x L c x yi ;
⎪ n
i =1
n
i =1
n
i =1
n
⎪⎪ c0 ∑ xi + c1 ∑ xi2 + L c k ∑ xik +1 = ∑ xi y i ;
⎨ i =1 i =1 i =1 i =1
⎪ L
⎪ n n n n
⎪c0 ∑ xik + c1 ∑ xik +1 + L + c k ∑ xi2 k = ∑ xik y i .
⎪⎩ i =1 i =1 i =1 i =1
Pentru determinarea parametrilor modelului hiperbolic, trebuie
rezolvat următorul sistem cu două ecuaţii şi necunoscutele a şi b:
153
⎧ n
1 n

⎪⎪ an + b ∑
i =1 x i
= ∑
i =1
yi ;
⎨ n 1 n n
y
⎪a ∑ + b ∑ 1 = ∑ i .
⎪⎩ i =1 xi 2
i =1 x i i =1 x i

Pentru rezolvarea acestor sisteme, programul r4_4, utilizează


procedurile LUSTAB şi LUSISTAB din unitatea MATH, anunţate la
problema R4.1. Datele de intrare sunt înregistrate în fişierul text 'reg.dat',
creat cu ajutorul unui editor de texte, sau este ieşirea unui alt program
de prelucrare. Valorile sunt înregistrate câte o pereche pe rând.
Programul cere numărul de perechi şi tipul modelului matematic al
datelor.
Pentru fiecare model se afişează suma pătratelor erorilor sau
mesaje adecvate privind inaplicabilitatea metodei pentru datele consi-
derate. Dacă datele sunt corecte din punctul de vedere al modelului
atunci se afişează şi parametrii acestuia.
Programul Pascal este următorul:
program r4_4;
uses math;
var a:matrice;
b,coef:vector;
sx,sy,sxy,slogx,slogy,s2x,s2logx,slogxy:real;
suma,ss,sxlogy,s1px,s1px2,sypx,x,y:real;
xnegativ:boolean;
ynegativ:boolean;
xzero:boolean;
c:char;
putere,s:vector;
i,j,k,l,n:integer;
begin
sx:=0; { suma valorilor x }
sy:=0; { suma valorilor y }
sxy:=0; { suma valorilor x*y }
slogx:=0; { suma valorilor log x }
slogy:=0; { suma valorilor log y }
s2x:=0; { suma patratelor valorilor x }
s2logx:=0; { suma patratelor valorilor log x }
slogxy:=0; { suma produselor valorilor logx,logy }
sxlogy:=0; { suma produselor x, logy }
154
s1px:=0; { suma inverselor }
s1px2:=0; { suma patratelor inverselor }
sypx:=0; { suma rapoartelor y/x }
k:=1;
writeln;
writeln('Prelucrarea datelor din fşsierul reg.dat');
writeln;
write('Număr observatii :'); read(n);
assign(input,'reg.dat');
reset(input);
for i:=1 to n do begin
readln(x,y);
xnegativ:=x <= 0;
ynegativ:=y <= 0;
xzero:= abs(x) < eps;
if not xnegativ then begin
slogx:=slogx+ln(x);
s2logx:=s2logx+ln(x)*ln(x)
end;
if not ynegativ then begin
slogy:=slogy+ln(y);
sxlogy:=sxlogy+x*ln(y)
end;
if not xzero then begin
s1px:=s1px+1/x;
s1px2:=s1px2+1/(x*x);
sypx:=sypx+y/x
end;
sx:=sx+x;
sy:=sy+y;
sxy:=sxy+x*y;
s2x:=s2x+x*x;
if (x>0) and (y>0) then slogxy:=slogxy+ln(x)*ln(y);
end;
close(input);
writeln('---------------------------------------------');
writeln;
writeln(' L - liniar H - hiperbolic');
writeln(' P - putere E - exponential');
writeln(' A - polinomial');

155
writeln;
assign(input,'con');
reset(input);
repeat
if eoln then readln;
read(c);
writeln('S-a selectat ',c);
until c in ['l','L','H','h','p','P','e','E','a','A'];
close(input);
repeat
k:=1; suma:=0;
if c in ['l','L'] then begin
a[1,1]:=n; a[1,2]:=sx;
a[2,1]:=sx; { incarca matricea sistemului }
a[2,2]:=s2x;
b[1]:=sy;
b[2]:=sxy; { incarca termenul liber }
lustab(2,a,b); { descompunere lu }
if succes then begin
lusistab(2,a,b,coef) ; { rezolva sistem }
assign(input,'reg.dat');
reset(input);
for i:=1 to n do begin
readln(x,y);
suma:=suma+sqr(y-coef[1]-coef[2]*x)
end;
close(input)
end
else writeln('Erori mari in date.',
'Sistem rău condiţionat');
end;
if c in ['h','H'] then
if xzero then writeln ('Modelul nu se aplica')
else begin
a[1,1]:=n; a[1,2]:=s1px;
a[2,1]:=s1px; a[2,2]:=s1px2 ;
b[1]:=sy; b[2]:=sypx;
lustab(2,a,b);
if succes then begin
lusistab(2,a,b,coef) ;

156
assign(input,'reg.dat'); reset(input);
for i:=1 to n do begin
readln(x,y);
suma:=suma+sqr(y-coef[1]-coef[2]/x)
end;
close(input);
end
else begin
suma:=-1;
writeln('Erori mari in date.',
'Sistem rău conditionat');
end;
end;
if c in ['p','P'] then
if xnegativ or ynegativ then writeln('Modelul nu se aplica')
else begin
a[1,1]:=n; a[1,2]:=slogx;
a[2,1]:=slogx; a[2,2]:=s2logx ;
b[1]:=slogy; b[2]:=slogxy;
lustab(2,a,b);
if succes then begin
lusistab(2,a,b,coef) ;
coef[1]:=exp(coef[1]);
assign(input,'reg.dat');
reset(input);
for i:=1 to n do begin
readln(x,y);
suma:=suma+sqr(y-coef[1]*exp(coef[2]*ln(x)))
end;
close(input);
end
else begin
suma:=-1;
writeln('Erori mari în date.',
'Sistem rău condiţionat');
end;
end;
if c in ['e','E'] then
if ynegativ then writeln('Modelul nu se aplica')
else begin

157
a[1,1]:=n; a[1,2]:=sx;
a[2,1]:=sx; a[2,2]:=s2x ;
b[1]:=slogy; b[2]:=sxlogy;
lustab(2,a,b);
if succes then begin
lusistab(2,a,b,coef) ;
coef[1]:=exp(coef[1]);
coef[2]:=exp(coef[2]);
if coef[2]<=0 then begin
suma:=-1;
writeln(' Erori în date ')
end
else begin
assign(input,'reg.dat');
reset(input);
for i:=1 to n do begin
readln(x,y);
suma:=suma+sqr(y-coef[1]*exp(x*ln(coef[2])))
end;
close(input)
end
end
else begin
suma:=-1 ;
writeln('Erori mari în date.',
'Sistem rău condiţionat');
end;
end;
if c in ['a','A'] then begin
write('Gradul polinomului:');
assign(input,'con');
reset(input);
read(k);
close(input);
writeln(' ... ',k);
for i:=1 to 2*k do s[i]:=0;
for i:=1 to k+1 do b[i]:=0;
assign(input,'reg.dat');
reset(input);

158
for i:=1 to n do begin
readln(x,y); putere[1]:=1;
for l:=2 to 2*k+1 do putere[l]:=putere[l-1]*x;
for l:=1 to 2*k do s[l]:=s[l]+putere[l+1];
for l:=1 to k+1 do b[l]:=b[l]+putere[l]*y
end;
a[1,1]:=n;
for i:=2 to k+1 do
a[1,i]:=s[i-1]; {încarcă matricea sistemului}
for j:=2 to k+1 do a[j,1]:=s[j-1];
for i:=2 to k+1 do
for j:=2 to k+1 do a[i,j]:= s[i+j-2];
lustab(k+1,a,b);
if succes then begin
lusistab(k+1,a,b,coef);
assign(input,'reg.dat');
reset(input);
for i:=1 to n do begin
readln(x,y);
putere[1]:=1;
for l:=2 to k+1 do putere[l]:=x*putere[l-1];
ss:=coef[1];
for l:=2 to k+1 do ss:=ss+coef[l]*putere[l];
suma:=suma+sqr(y-ss)
end;
close(input)
end
else begin
suma:=-1;
writeln('Erori mari in date. Sistem rău conditionat');
end;
end;
writeln;
writeln('Suma patratelor erorilor',suma);
writeln;
if suma <> -1 then begin
writeln('Parametrii modelului :');
for i:=1 to k+1 do write(coef[i]);
end;
writeln;

159
writeln(' L - liniar H - hiperbolic');
writeln(' P - putere E - exponential');
writeln(' A - polinomial O - Oprire');
writeln;
assign(input,'con');
reset(input);
if eoln then readln;
read(c) ;
writeln;
writeln('S-a selectat ',c);
close(input)
until c in ['o','O']
end.

R4.5. [Unitatea Math.tpu] Descrieţi un text sursă pentru unitatea


Math aetfel încât să ofere serviciile descrise în aplicaţiile anterioare.
Rezolvare:
unit math;{
1. Se salvează cu numele "math.pas";
2. Constante globale:
maxlin; numarul maxim de linii.
Maxcol; numarul maxim de coloane.
eps; precizia (cel mai mic numar pozitiv).
3. Tipuri globale:
matrice;
vector.
4. Variabile globale:
succes ;
pozdef ;
nenul.
5. Proceduri globale (publice):
vdif; prodmv; vadd;
ludesc; lusistem; ludet; lustab; lusistab ;
lltd; llsistem;
Iacobi; Seidel.
6. Funcţii globale:
diag_dom;
norma .
}

160
interface
const maxlin=30;
maxcol=30;
Eps=1E-10;
type matrice = array[1..maxlin,1..maxcol] of real;
vector = array[1..maxcol] of real;
var succes,pozdef,nenul:boolean;
procedure vdif(n:integer; x,y:vector; var z:vector );
procedure prodmv(n:integer; a:matrice; x:vector; var y:vector);
procedure vadd(n:integer; var x:vector; y:vector);
procedure ludesc(n:integer; var a:matrice);
procedure lusistem(n:integer; a:matrice; b:vector; var x:vector);
procedure ludet(n:integer; a:matrice; var det:real);
procedure lustab(n:integer; var a:matrice; var b:vector);
procedure lusistab(n:integer; a:matrice; var b,x:vector );
procedure lltd(n:integer; var a:matrice);
procedure llsistem(n:integer; a:matrice; b:vector; var x:vector);
function diag_dom(n:integer;a:matrice):boolean;
function norma(n:integer; x:vector):real;
procedure Iacobi(n:integer;a:matrice; b:vector;
niter:integer; var x:vector);
procedure Seidel(n:integer;a:matrice;b:vector;
niter:integer; var x:vector);
implementation
{--------------------------------------------------------}
procedure vdif(n:integer; x,y:vector; var z:vector);
var i:integer;
begin
for i:=1 to n do z[i]:=x[i]-y[i]
end;
{--------------------------------------------------------}
procedure prodmv(n:integer;a:matrice;x:vector;var y:vector);
var i,j:integer;
begin
writeln('Prodmv');
for i:=1 to n do begin
y[i]:=a[i,1]*x[1];
for j:=2 to n do y[i]:=y[i]+a[i,j]*x[j];
end
end;

161
{--------------------------------------------------------}
procedure vadd(n:integer;var x:vector;y:vector);
var i:integer;
begin
for i:=1 to n do x[i]:=x[i]+y[i]
end;
{--------------------------------------------------------}
procedure ludesc(n:integer;var a:matrice);
{
Apel: LUdesc(n,a);
Descriere parametri:
n - dimensiunea matricei;
a - matricea de descompus.
Funcţie:
Descompune matricea a in produsul a doua matrice
triunghiulare . Matricea a se distruge.
Metoda:
Algoritmul este prezentat la problema R4.1.
}
var i,k,j:integer;s:real;
begin
succes:=true; for i:=1 to n do a[i,1]:=a[i,1]/a[1,1];
i:=2;
while (i <= n) and succes do begin
s:=0; for k:=1 to i-1 do s:=s+a[i,k]*a[k,i];
a[i,i]:=a[i,i]-s; succes:=abs(a[i,i]) > eps;
if succes then begin
for j:=i+1 to n do begin
s:=0; for k:=1 to i-1 do s:=s+a[i,k]*a[k,j];
a[i,j]:=a[i,j]-s
end;
for j:=i+1 to n do begin
s:=0; for k:=1 to i-1 do s:=s+a[j,k]*a[k,i];
a[j,i]:=(a[j,i]-s)/a[i,i]
end
end;
i:=i+1
end
end;
{-----------------------------------------------------}

162
procedure lusistem(n:integer; a:matrice; b:vector;
var x:vector);
{
Apel: LUsistem(n,a,b,x);
Descriere parametri:
n - dimensiunea sistemului;
a - matricea sistemului;
b - termenul liber;
x - soluţia sistemului.
Funcţia:
Rezolvă sistemul LUx=b;
Metoda:
Se rezolvă două sisteme triunghiulare.
}
var i,j:integer;
b1,y:vector;
s:real;
begin
{
Rezolvare sistem inferior triunghiular;
}
y[1]:=b[1];
for i:=2 to n do begin
s:=0;
for j:=1 to i-1 do s:=s+a[i,j]*y[j];
y[i]:=b[i]-s
end;
writeln;
{
Rezolvare sistem superior triunghiular;
}
x[n]:=y[n]/a[n,n];
for i:=n-1 downto 1 do begin
s:=0;
for j:=i+1 to n do s:=s+a[i,j]*x[j];
x[i]:=(y[i]-s)/a[i,i]
end;
end;
{-----------------------------------------------------}

163
procedure ludet(n:integer;a:matrice; var det:real);
{
Apel: ludet(n,a,det);
Descriere parametri:
n - dimensiunea matricei;
a - descompunerea lu a matricei initiale;
det - valoarea determinantului.
Funcţia:
Calculeaza determinantul unei matrice.
}
var i:integer;
p:real;
begin
p:=1;
for i:=1 to n do p:=p*a[i,i];
det:=p
end;
{-----------------------------------------------------}
procedure lustab(n:integer; var a:matrice; var b:vector);
{
Apel: lustab(n,a,b);
Descriere parametri:
n - dimensiunea matricei;
a - matricea ce se va descompune;
p - termenul liber .
Funcţia:
Descompunerea LU a matricei a;
Metoda:
Algoritmul este prezentat la problema R4.1.
Proceduri locale:
max(i,k) :- k este indicele liniei pe care se află
elementul de modul maxim din coloana i;
perlin(i,k) :- permuta liniile i şi k.
}
var i,j,k:integer; s:real;
procedure max(i:integer; var k:integer);
var j:integer;
valmax:real;
begin
valmax:=abs(a[i,i]); k:=i;

164
for j:=i+1 to n do
if valmax < abs(a[j,i]) then begin
valmax:=abs(a[j,i]); k:=j
end
end; {MAX}
procedure perlin(i,k:integer);
var s:real;
j:integer;
begin
for j:=1 to n do begin s:=a[i,j]; a[i,j]:=a[k,j]; a[k,j]:=s end;
s:=b[i]; b[i]:=b[k]; b[k]:=s
end;{PERLIN}
begin
max(1,j);
if j <> 1 then perlin(1,j);
i:=2;
succes:=abs(a[1,1])>eps;
while (i <= n) and succes do begin
max(i,j);
if j <> i then perlin(i,j);
a[i,1]:=a[i,1]/a[1,1];
for j:=2 to i-1 do begin
s:=0;
for k:=1 to j-1 do s:=s+a[i,k]*a[k,j];
a[i,j]:=(a[i,j]-s)/a[j,j]
end;
for j:=i to n do begin
s:=0;
for k:=1 to i-1 do s:=s+a[i,k]*a[k,j];
a[i,j]:=a[i,j]-s
end;
succes:=abs(a[i,i]) > eps ;
i:=i+1
end;
end;
{----------------------------------------------------}
procedure lusistab(n:integer; a:matrice; var b,x:vector);
{
Apel: LUsistab(n,a,b,x,p);
Descriere parametri:

165
n - dimensiunea sistemului;
a - matricea sistemului;
b - termenul liber;
x - soluţia sistemului;
p - permutarea rezultată prin interschimbări.
Funcţia:
Rezolvă sistemul LUx=b;
Metoda:
Se rezolvă două sisteme triunghiulare.
}
var i,j:integer;
y:vector;
s:real;
begin
{
Rezolvare sistem inferior triunghiular;
}
y[1]:=b[1];
for i:=2 to n do begin
s:=0; for j:=1 to i-1 do s:=s+a[i,j]*y[j];
y[i]:=b[i]-s
end;
writeln;
{
Rezolvare sistem superior triunghiular;
}
x[n]:=y[n]/a[n,n];
for i:=n-1 downto 1 do begin
s:=0; for j:=i+1 to n do s:=s+a[i,j]*x[j];
x[i]:=(y[i]-s)/a[i,i]
end
end;
{-----------------------------------------------------}
procedure lltd(n:integer; var a:matrice);
{
Apel: LLtd(n,a);
Descrierea parametrilor:
n - dimensiunea matricei;
a - matrice patratica cu elemente reale.
Funcţia:

166
Obţine în matricea A o descompunerea în produsul unei matrice
inferior triunghiulare cu transpusa acesteia, dacă matricea A este
pozitiv definită.
Metoda:
Conform algoritmului prezentat la problema R4.2.
}
var i,j,k:integer; sim:boolean;
begin
pozdef:= true; i:=1;
while (i <= n) and pozdef do begin
j:=i+1;
while (j <= n) and pozdef do begin
pozdef :=a[i,j]=a[j,i]; j:=j+1
end;
i:=i+1;
end;
pozdef:= (a[1,1] > 0) and pozdef;
if pozdef then begin
a[1,1] :=sqrt(a[1,1]); nenul:= a[1,1] >= eps;
if nenul then begin
for j:=2 to n do begin
a[j,1]:=a[j,1]/a[1,1]; a[1,j]:= a[j,1]
end;
i:=2;
while (i <= n) and pozdef and nenul do begin
for k:=1 to i-1 do a[i,i]:=a[i,i]-sqr(a[i,k]);
pozdef:=a[i,i] > 0;
if pozdef then begin
a[i,i]:=sqrt(a[i,i]); nenul := a[i,i] >= eps;
if nenul then
for j:=i+1 to n do begin
for k:=1 to i-1 do a[j,i]:=a[j,i]-a[j,k]*a[i,k];
a[j,i]:=a[j,i]/a[i,i]; a[i,j]:=a[j,i]
end
end;
i:=i+1
end
end
end
end;

167
{-----------------------------------------------------}
procedure llsistem(n:integer;a:matrice; b:vector; var x:vector);
{
Apel: LLsistem(n,a,b,x);
Descrierea parametrilor:
n - dimensiunea sistemului;
a - matricea ce conţine descompunerea LLT
b - termenul liber;
x - soluţia sistemului.
Funcţia:
Rezolvă două sisteme triunghiulare conform celor prezentate la
problema R4.2.
}
var i,j,k:integer; y:vector;
begin
y[1]:=b[1]/a[1,1];
for i:=2 to n do begin
y[i]:=b[i];
for k:=1 to i-1 do y[i]:=y[i]-a[i,k]*y[k];
y[i]:=y[i]/a[i,i]
end;
x[n]:=y[n]/a[n,n];
for i:=n-1 downto 1 do begin
x[i]:=y[i];
for j:=i+1 to n do x[i]:=x[i]-a[i,j]*x[j];
x[i]:=x[i]/a[i,i]
end;
end;
{-----------------------------------------------------}
function diag_dom(n:integer; a:matrice):boolean;
{
Apel : diag_dom(n,a);
Descrierea parametrilor:
n - dimensiunea matricei;
a - matrice patratică cu elemente reale.
Funcţia: Verifică dacă o matrice este diagonal dominantă
}
var i,j:integer;
v:boolean;
s:real;

168
begin
v:=true; i:=1;
while (i <= n) and v do begin
s:=0;
for j:=1 to n do if j <> i then s:=s + abs(a[i,j]);
if s >= abs (a[i,i]) then v:=false else i:=i+1;
end;
diag_dom:=v
end;
{-----------------------------------------------------}
function norma(n:integer; x:vector):real;
{
Apel : norma(n,x)
Descrierea parametrilor:
n - dimensiunea vectorului;
x - vector cu n componente.
Functia: Calculeaza norma uniformă.
}
var v, p:real; i:integer;
begin
v:=abs(x[1]);
for i:=2 to n do begin p:=abs(x[i]); if v < p then v:=p end;
norma:=v
end;
{-----------------------------------------------------}
procedure Iacobi(n:integer; a:matrice; b:vector;
niter:integer; var x:vector);
{
Apel: Iacobi(n,a,b,niter,x)
Descrierea parametrilor:
n - dimensiunea sistemului;
a - matricea sistemului;
b - termenul liber;
niter - numărul de iteraţii;
x - soluţia sistemului.
Functia: Rezolvă un sistem cu n ecuaţii liniare şi n necunoscute
prin metoda Iacobi.
Metoda: Algoritmul prezentat la problema R4.3.
}
var i,j,k:integer; y,e:vector; s:real;

169
begin
for i:=1 to n do y[i]:=x[i]; k:=1;
while ( k<= niter) or (norma(n,e) > eps) do begin
for i:=1 to n do begin
s:=0; for j:=1 to n do if j <> i then s :=s+a[i,j]*y[j];
x[i]:=(b[i]-s)/a[i,i]
end;
vdif(n,x,y,e); for i:=1 to n do y[i]:=x[i]; k:=k+1
end
end;
{-----------------------------------------------------}
procedure Seidel(n:integer; a:matrice; b:vector;
niter:integer;var x:vector);
{
Apel : Seidel(n,a,b,niter,x)
Descrierea parametrilor:
n - dimensiunea sistemului liniar;
a - matricea sistemului de ecuaţii;
b - termenul liber al sistemului;
niter - numarul de iteraţii ;
x - soluţia sistemului.
Funcţia: Rezolvă un sistem liniar cu n ecuaţii şi n necunoscute
prin metoda Gauss-Seidel.
Metoda: Algoritmul prezentat la problema R4.3.
}
var y,e:vector;i,j,k:integer;
begin
for i:=1 to n do y[i]:=x[i]; k:=1;
while (k <= niter) or (norma(n,e) > eps) do begin
for i:=1 to n do begin
x[i]:=b[i]; for j:=1 to n do if i <> j then x[i]:=x[i]-a[i,j]*x[j];
x[i]:=x[i]/a[i,i];
end;
vdif(n,x,y,e); for i:=1 to n do y[i]:=x[i]; k:=k+1
end
end;
begin
Writeln; Writeln('Unitul Math - GA2003'); Writeln
end.

170
4.3. Probleme propuse

1. O bibliotecă de grafică bidimensională, trebuie să ofere programa-


torului posibilitatea lucrului în coordonate utilizator (numere reale).
Aceasta trebuie să conţină subprograme pentru:
♦ realizarea transformărilor geometrice (translaţie absolută şi
relativă, rotaţie absolută şi relativă, simetrie etc.);
♦ desenarea primitivelor grafice (linie, cerc, dreptunghi, elipsa etc.);
♦ realizarea transformării imaginii din spaţiul de coordonate
utilizator în spaţiul de coordonate ecran.
♦ operaţia de decupare (clipping).
Să se realizeze o unitate numită Desenare.tpu care să utilizeze unita-
tea graph şi care să ofere cât mai multe din facilităţile anunţate mai
sus.
2. Se consideră declaraţiile:
unit MatProg10;
interface
const max_lin=10;
max_col=10;
type
matrice= array[1..max_lin,1..max_col] of real;
vector_linie= array[1..max_col] of real;
vector_coloana=array[1..max_lin] of real;
procedure copy_lin_vector (a:matrice; i:integer; var x:vector_linie);
procedure copy_col_vector
(a:matrice; i:integer; var x:vector_coloana);
procedure copy_diag_vector(a:matrice; var x:vector_coloana);
procedure add_number(var a:matrice; x:real);
procedure subtract_number(var a:matrice; x:real);
procedure divide_by_number(var a:matrice; x:real);
procedure replace_diag_vector(var a:matrice; x:vector_coloana);
procedure mul_linie_add(var a:matrice; i:integer; x:real; j:integer);
procedure mul_col_add(var a:matrice; i:integer; x:real; j:integer);
procedure interschimba_lin(var a:matrice; i,j:integer);
procedure interschimba_col(var a:matrice; i,j:integer);
procedure sum_lin_vector(a:matrice; var x:vector_coloana);
procedure sum_col_vector(a:matrice; var x:vector_linie);
procedure generare(var a:matrice);
procedure tiparire(a:matrice);
a) Să se implementeze unitatea conform următoarelor specificaţii:
171
• Procedura copy_lin_vector copiază linia i a matricei a într-un
vector linie.
• Procedura copy_col_vector copiază coloana i a matricei a într-un
vector coloană.
• Procedura copy_diag scoate într-un vector elementele de pe
diagonala matricei a.
• Procedura add_number adună un scalar la toate elementele
matricei a.
• Procedura subtract_number scade un scalar din toate elementele
matricei a.
• Procedura divide_by_number împarte toate elementele matricei a
la un număr real nenul.
• Procedura replace_diag_vector înlocuieşte elementele de pe
diagonala matricei a cu cele specificate într-un vector coloană.
• Procedura mul_linie_add adună la linia j, linia i, ale cărei
elemente se înmulţesc cu un număr real.
• Procedura mul_col_add adună la coloana j, coloana i, ale cărie
elemente se înmulţesc cu un număr real.
• Procedura interschimba_lin realizează interschimbarea liniilor i şi
j.
• Procedura interschimba_col realizează interschimbarea coloanelor
i şi j.
• Procedura sum_lin_vector calculează într-un vector coloană suma
elementelor de pe liniile matricei a.
• Procedura sum_col_vector calculează într-un vector linie suma
elementelor de pe coloanele matricei a.
• Procedura generare realizează încărcarea unei matrice a cu valori
numerice.
• Procedura tiparire afişează la mediul standard de ieşire, valorile
elementelor matricei a.
b) Să se utilizeze unitatea pentru implementarea unor algoritmi pentru
calculul rangului unei matrice;
3. Elaboraţi o unitate de translatare Pascal pentru definirea tipului
complex şi a operaţiilor cu numere complexe. Se cer utilizarea formei
algebrice şi a formei trigonometrice.
4. Elaboraţi o unitate pentru lucrul cu numere mari. Trebuie imple-
mentate operaţii precum: adunare, scădere, împărţire întreagă, deter-
minarea restului împărţirii a două numere întregi, calculul rădăcinii
pătrate.

172
5. Introducere în programarea orientată obiect
folosind limbajul C++
5.1. Fundamente
La prima vedere orice program poate fi perceput ca o colecţie de date
şi de operaţii care se execută asupra datelor. În programarea procedu-
rală aceste două elemente sunt tratate separat, reprezentarea datelor se
realizează cu ajutorul tipurilor de date, iar reprezentarea operaţiilor se
face prin funcţii şi proceduri.
Folosirea funcţiilor nu este suficientă dacă se doreşte o descriere
şi implementare eficientă a unor algoritmi ce necesită structuri de date
complexe în rezolvarea problemelor. În plus, dacă se doreşte reutili-
zarea unor programe scrise anterior în rezolvarea unor noi probleme
va fi necesar un efort considerabil din partrea programatorului să
adapteze codul sursă reutilizat la noile nevoi, iar aceasta va duce la
apariţia a numeroase erori. Din acest motiv este mult îngreunat şi
lucrul în echipă, dacă un programator trebuie să implementeze o
funcţie, acesta va trebui să studieze şi celelalte module ale progra-
mului.
Un limbaj de programare potrivit acestor sarcini ar trebui să
permită atât încapsularea structurilor de date cât şi a funcţiilor care
operează cu aceste structuri ca o singură entitate, să permită
ascunderea detaliilor de implementare a operaţiilor şi să permită
reutilizarea şi extinderea unor module existente (chiar şi a celor
compilate – fără recompilare).
S-a impus astfel, nevoia unui model de programare capabil să
depăşească limitările programării structurate şi care să permită
realizarea unei abstractizări adecvate a datelor şi a operaţiilor în aşa fel
încât să permită o tratare unitară a acestora. Aşa s-a născut clasa
limbajelor de programare orientate pe obiecte din care face parte şi
limbajul C++.
Modelul de programare orientată pe obiecte rezolvă aceste
probleme prin următoarele principii importante: abstractizare,
încapsulare, modularizare, moştenire şi polimorfism.
Abstractizarea
Abstractizarea este un model în care un obiect este privit prin prisma
metodelor (operaţiilor) sale, ignorându-se pentru moment detaliile de
implementare a acestora. O bună abstractizare va defini în mod clar
graniţele conceptuale ale obiectului, va scoate în evidenţă doar
173
aspectele semnificative ale obiectului, acelea care fac ca acesta să se
diferenţieze de alte obiecte şi va estompa celelalte caracteristici.
Aşadar, în procesul de abstractizare atenţia este îndreptată spre
aspectul exterior al unui obiect, spre modul său de comportare şi nu
spre felul în care această comportare este implementată. Comportarea
unui obiect se caracterizează printr-un număr de servicii sau resurse pe
care acesta le pune la dispoziţia altor obiecte. Mulţimea operaţiilor
unui obiect împreună cu regulile lor de apelare constituie interfaţa
obiectului.
Programatorii utilizează abstractizarea pentru a simplifica ana-
liza, proiectarea şi implementarea programelor complexe. În C++
instrumentul de bază pentru abstractizare este clasa.
Încapsularea
Încapsularea este conceptul complementar abstractizării. Dacă
rezultatul operaţiei de abstractizare pentru un anumit obiect este
identificarea interfeţei, atunci încapsularea trebuie să definească repre-
zentarea (structura) internă a obiectului şi să selecteze o implementare
a interfeţei acestuia. Prin urmare, încapsularea este procesul în care
are loc separarea interfeţei de implementare şi ascunderea implemen-
tării faţă de exterior.
Separarea interfeţei de reprezentarea unui obiect şi de imple-
mentarea metodelor sale permite modificarea structurii obiectului şi a
metodelor fără a afecta în nici un fel programul care foloseşte obiec-
tul, întrucât acesta depinde doar de interfaţă. Încapsularea permite
modificarea programelor într-o manieră eficientă, cu un efort limitat şi
bine localizat.
Modularizarea
Clasele obţinute în urma abstractizării şi încapsulării trebuie grupate şi
apoi stocate într-o formă fizică, denumită modul. Modulele pot fi
privite ca fiind containerele fizice în care declarăm clasele şi obiectele
rezultate în urma proiectării la nivel logic. Modulele formează aşadar
arhitectura fizică a programului. Modularizarea constă în divizarea
programului într-un număr de module care vor fi compilate separat,
dar care sunt conectate între ele. Scopul descompunerii în module este
reducerea costurilor prin posibilitatea de a proiecta şi revizui părţi ale
programului într-un mod independent.
Concret, în C++ modulele nu sunt altceva decât fişierele ce pot
fi compilate separat. În practică se obişnuieşte ca interfaţa unui modul

174
să fie plasată într-un fişier header (cu extensia ".h"), în timp ce
implementarea acestuia se va regăsi într-un fişier sursă (cu extensia
".cpp"). Dependenţele dintre module vor fi exprimate utlizând directi-
vele "#include".
Referitor la modularizare, cititorul poate observa asemănări şi
deosebiri ale modularizării la nivelul C, C++, în raport cu modula-
rizarea permisă de implementarea Borland a limbajului Pascal.
Moştenirea
Nu de puţine ori, scriind programe în maniera clasică, eram puşi în
situaţia de a adapta sau rescrie funcţii scrise anterior. Această etapă de
implementare consuma mai mult timp decât era necesar şi, în plus,
exista riscul apariţiei a numeroase erori. O variantă mult îmbunătăţită
de reutilizare a codului este moştenirea. Moştenirea este unul dintre
cele mai importante concepte ale limbajelor de programare pe obiecte.
Aceasta permite extinderea obiectelor existente şi construirea de noi
obiecte într-un mod simplu. Astfel, o clasă poate moşteni toate carac-
teristicile uneia sau a mai multor clase create anterior la care poate
adăuga trăsături noi şi, în anumite condiţii, poate redefini unele din
metodele moştenite.
Polimorfismul
Polimorfismul este o facilitate a programării orientate obiect care
oferă instanţelor unor clase posibilitatea de a reacţiona într-un mod
specific la un mesaj (la un apel de funcţie). Spre exemplu, într-o
ierarhie de clase obţinută prin moştenire, care reprezintă forme
geometrice (puncte, linii, dreptunghiuri, cercuri) fiecare obiect are o
funcţie Draw(). Apelul acestei funcţii având o referinţă la un obiect
grafic generic trebuie să se comporte corespunzător obiectului referit.
Clasele
Conceptul fundamental din programarea orientată pe obiecte îl
reprezintă noţiunea de clasă ca tip de date definit de utilizator.
Cuvântul cheie class ne permite să intrăm în universul programării
orientate pe obiecte, cu ajutorul lui putem defini tipuri abstracte de
date. Variabilele declarate pe baza unui tip abstract se numesc obiecte.
Putem spune că:
Clasa = Date + Operaţii.
Spre deosebire de structurile cunoscute din limbajul C, clasele
conţin nu numai date membre ci şi funcţii membre, constructori şi cel
mult un destructor. În general o clasă se defineşte astfel:
175
class nume{
private:
<date membre private>
<constructori privati>
<metode private>
protected:
<date membre protejate>
<constructori protejati>
<metode protejate>
public:
<date membre publice>
<constructori publici>
<destructor public>
<metode publice>
};
Pentru exemplificare vom defini o serie de clase pentru reprezentarea
figurilor geometrice în plan. Se ştie că figurile geometrice se
contruiesc pornind de la unele forme simple cum sunt punctul, linia,
etc. Urmează un exemplu de declaraţie de clasă care introduce un tip
nou de date numit Punct. Următoarea declaraţie se va scrie într-un
fişier header cu un numele Point.h.
class Point {
protected:
int x, y; //date membre
public:
Point(); //constructor
Point(int, int); //consructor
~Point(); //destructor
void Print(); //funcţie membra
void Read(); //funcţie membra
};
Operaţii cu fluxuri standard
În limbajul C toate operaţiile de intrare-ieşire se realizează prin
utilizarea funcţiilor din biblioteca standard stdio. C++ vine cu o nouă
bibliotecă de I/O numită iostream. Biblioteca nu conţine o colecţie de
funcţii ci o colecţie de obiecte care permit accesul la ecran pentru
afişare şi la tastatură pentru citirea datelor introduse de utilizator.
Aceste obiecte au definite câte o metodă pentru lucrul cu fiecare
tip de date simple (int, long, float, double, char…), eliminând astfel
176
necesitatea precizării tipului şi a formatului datelor care urmează a fi
scrise sau citite.
Când un program C++ care include headerul iostream.h este
lansat în execuţie, atunci sunt create şi iniţializate automat
următoarele patru obiecte:
• cin gestionează intrarea standard (tastatura);
• cout gestionează ieşirea standard (ecranul);
• cerr gestionează ieşirea către dispozitivul standard de eroare
(ecranul), neutilizând bufere;
• clog gestionează ieşirea către dispozitivul standard de eroare
(ecranul), utilizând bufere.
Când este vorba se operaţii de intrare/ieşire trebuie să avem în vedere
o sursă emitentă a unui flux de date şi o destinaţie care recepţionează
aceste date. În afară de aceastea avem nevoie şi de un mijloc de
comunicare între sursă şi destinaţie. Aici intervin operatorii.
Să vedem, în continuare, cum se face afişarea şi citirea tipurilor
de date standard în limbajul C++ folosind obiectele de mai sus.

În limbajul C În limbajul C++


Int x = 33; float f = 1.5; int x = 33; float f = 1.5;
Printf("x = %d f = %f", x, f); cout << "x=" << x <<"f ="<<f ;
Printf("\n"); cout << endl;
Printf("%c", 'C'); cout << 'C';
Printf("%s", "Limbajul C\n"); cout<<"Limbajul C++"<< endl;

Int x; scanf("%d", &x); int x; cin >> x;


Float f; scanf("%f" &f); float f; cin >> f;
Char ch; scanf("%c", &ch); char ch; cin >> ch;
Char sir[25]; scanf("%s", sir); char sir[25]; cin >> sir;

Din tabelul de mai sus reiese că cout ţine locul lui stdout, cin
înlocuieşte stdin şi operatorii << şi >> a fost redefiniti în scopul
introducerii/extragerii datelor.
Se observă că nu mai este necesară precizarea tipului şi
formatului datelor deoarece operatorii care lucrează cu fluxuri sunt
capabili să depisteze tipul fiecărei variabile transferate. Mai mult,
orientarea operatorului ne indică sensul în care are loc transferul
datelor.

177
Formatarea datelor în C++
În exemplul de mai sus afişarea s-a produs utilizând de fiecare dată un
format implicit. Există posibilitatea modificării formatului implicit
prin precizarea alinierii, umplerii, a dimensiunii spaţiului ocupat, a
bazei de numeraţie şi a preciziei. O bază de numeraţie odată stabilită
se menţine până când se face o nouă precizare în acest sens.
Instrucţiunile următoare:
#include<iostream.h>
#include<iomanip.h>
void main(){
cout<<setw(8)<<setfill('*')<<12345<<endl;
cout<<setw(8)<<setprecision(3)<<6.0231<<endl;
cout<<setw(8)<<setfill('0')<<oct<<12345<<endl;
cout<<setw(8)<<setfill('0')<<dec<<123456<<endl;
cout<<setw(8)<<hex<<123456<<endl;
}
produc următorul rezultat:
***12345
6.023
00030071
00123456
0001e240
Modificatori de access
Încapsularea nu presupune numai simpla combinare a metodelor şi a
datelor ca o singură entitate ci şi o posibilitate de a limita accesul la
datele şi metodele membre unei clase mai ales în situaţii în care se
doreşte prevenirea modificării accidentale a datelor aparţinând unui
obiect. În cadrul declarării unei clase, prin folosirea unor modificatori
de acces, se poate preciza unul din cele trei niveluri de acces la datele
şi metodele clasei, astfel:
• private – este modul implicit, membrii de acest tip sunt accesibili
numai metodelor membre şi a funcţiilor de tip friend, aceste
elemente sunt considerate proprii, intime clasei în care au fost
declarate şi nu vor fi accesibile în clasele derivate;
• protected – este similar modului private, în plus membrii sunt
accesibili şi în metodele claselor derivate (în cazul moştenirii
publice), acest modificator este recomandat să se folosească în
clasele ce urmează să fie extinse;

178
• public – toate atributele şi metodele publice pot fi accesate sau
apelate din orice punct din program exterior clasei, unde este
cunoscută clasa.
Trebuie să reţinem că limbajul C++ nu impune o anumită ordine de
folosire a acestor modificatori, iar atributele şi metodele fără
modificator de acces sunt considerate private. Secţiunile unei clase pot
apărea de mai multe ori în cadrul declaraţiei unei clase. În cele mai
multe situaţii datele membre se vor plasa în secţiunile protejate pentru
a permite accesul acestora din cadrul claselor derivate. De asemenea
se recomandă evitarea plasării datelor în secţiunea publică. Se reco-
mandă folosirea funcţiilor membre pentru iniţializarea şi prelucrarea
datelor membre. Clasele pot avea mai mulţi constructori (de obicei
publici) şi cel mult un destructor public.
În cazul nostru metodele care urmează imediat după cuvântul
public sunt publice, cu alte cuvinte acestea pot fi accesate din exterior
oriunde în program unde clasa Point este accesibilă. Cu alte cuvinte
domeniul de vizibilitate al atributelor şi metodelor este acelaşi cu
domeniul de vizibilitate al clasei.
Declararea funcţiilor membre
Definirea unui nou tip de date este formată din două părţi: declaraţia şi
definiţia (implementarea) metodelor declarate ca aparţinând clasei.
Faptul că o metodă aparţine unei clase, este specificat prin plasarea
prototipului acesteia în cadrul declaraţiei clasei respective. În mod
normal corpul metodei va fi definit în afara declaraţiei clasei.
Implementarea metodelor în afara clasei va trebui să folosească
numele clasei pentru care se definesc metodele împreună cu operatorul
de apartenenţă "::", aşa cum se poate observa în exemplul ce urmează.
Se recomandă ca implementarea următoare să fie introdusă într-un
fişier cu extensia "cpp" (exemplu: Point.cpp).
Point::Point(){x=0; y=0;}
Point::Point(int x0, int y0){x=x0; y=y0;}
Point::~Point(){ }
void Point::Print(){
cout<<”Point: ”<<" x = "<< x; cout<<" y = "<<y<<endl;
}
void Point::Read(){
cout<<"x = "; cin >> x; cout<<"y = "; cin >> y;
}

179
Există situaţii în care unei funcţii membre a unui obiect trebuie să-i
transferăm ca parametru chiar o referinţă sau un pointer la obiectul
respectiv. Un alt caz este acela în care o funcţie trebuie să returneze un
pointer la obiectul din care face parte sau o copie a obiectului. Pentru
astfel de situaţii limbajul C++ a definit un pointer special numit this.
Acest pointer conţine întotdeauna adresa în memorie a obiectului
pentru care se apelează metoda, deci return this; întoarce un pointer
la obiectul curent iar return *this; intoarce o referinţă sau obiectul
însuşi. Acest pointer se poate folosi şi în interiorul metodelor pentru a
deosebi datele membre de parametrii formali, aşa cum se poate obser-
va în definiţia funcţiei membre Set(int, int) din exemplul de mai jos.
Uneori se doreşte ca o anumită metodă să fie definită chiar în
interiorul clasei aşa cum este metoda getX, aceasta devenind o funcţie
inline. Astfel de funcţii se pot defini şi în afara clasei, dar în acest caz
la declaraţie trebuie precedate de cuvântul cheie inline.
class Point{
int x,y;
public:

int getX(){ return x;}
int getY(){ return y;}
inline void Set(int, int);
inline float Area();
};
void Point::Set(int x, int y){
this->x=x;
this->y=y;
}
float Point::Area(){return 0.0;}
Datorită încapsulării se poate limita accesul la datele membre ale unei
clase. În astfel de situaţii inspectarea din exterior a datelor protejate se
va face prin funcţii de acces, declarate ca fiind publice, care vor
returna valorile datelor protejate şi vor asigura protecţia la modificări
nedorite. Se ştie că accesul la un element membru al unei structuri este
mult mai eficient în comparaţie cu apelul unei funcţii deoarece apelul
este mare consumator de timp.
Aici intervin funcţiile inline. Aceste funcţii reprezintă o
combinaţie între funcţii şi macrodefiniţii, iar această proprietate
specială face ca orice apel al unei funcţii inline să fie substituit de
corpul acesteia. Pentru a reuşi substituirea, compilatorul trebuie să
180
cunoască forma şi conţinutul exact al funcţiei în momentul compilării.
Acest lucru nu este valabil pentru funcţiile care conţin bucle iterative.
Membrii statici
Există un număr destul de mare de aplicaţii în care programatorul are
nevoie să folosească date membre care aparţin conceptual mai mult
claselor decât instanţelor acestora. Aceste date membre pot fi utile în
următoarele situaţii:
• când se urmăreşte numărul de instanţe ale unei clase;
• când se doreşte alocarea unui bloc special de memorie sau
deschidera unui fişier pentru diferite obiecte ale clasei;
• când toate instanţele partajează un tablou de structuri, folosit ca o
bază de date miniaturală;
• când un set de date membre trebuie să aibă aceleaşi valori
indiferent de instanţă, cum ar fi coordonatele mouse-ului sau
cursorului.
Pentru toate aceste cazuri, C++ oferă soluţia membrilor statici.
Folosirea membrilor statici în definirea unei clase implică respectarea
următoarelor reguli:
♦ declararea unui membru static se face prin specificarea cuvântului
cheie static în faţa tipului datei membre;
♦ accesarea membrilor statici ai unei clase din interiorul funcţiilor
membre se face la fel ca accesarea membrilor obişnuiţi (nestatici);
♦ este obligatorie iniţializarea membrilor statici în afara declaraţiei
clasei chiar dacă aceştia au fost declaraţi în secţiunile private sau
protected, iniţializarea se va face în fişierul în care este imple-
mentată clasa niciodată în headerul clasei şi nu va fi precedată de
cuvântul cheie static;
♦ membrii statici există separat de celelalte date membre ale unei
clase şi pot fi accesaţi chiar şi fără a declara un obiect de acel tip,
accesul facându-se pe baza numelui clasei ca în exemplul de mai
jos:
class Mouse{
public:
static int xPos, yPos;
Mouse(){};
void SetPos(int x, int y){
xPos = x; yPos = y;
}
};

181
Mouse::xPos = 0; Mouse::yPos = 0;
void main(){
Mouse m;
m.SetPos(10,15);
cout<<Mouse::xPos<<" "<< Mouse::yPos<<endl;
}
Funcţii membre statice
Funcţiile membre statice se declară la fel ca membrii statici prin
plasarea cuvântului cheie static în faţa declaraţiei funcţiei. Accesul la
o metodă statică se realizează în mod identic cu cel la un membru
static. Adică putem apela o metodă statică a oricărui obiect, dar o
putem apela şi fară să avem un obiect declarat, folosind în acest caz
numele clasei umat de operatorul "::". Funcţiile membre statice se
deosebesc de cele nestatice prin faptul că nu posedă pointerul this. Ca
urmare, nu poate întoarce pointerul this sau obiectul *this şi nu va
putea accesa în mod direct decât alte elemente (date şi funcţii) statice
ale clasei. Deci o funcţie statică nu poate să apeleze o funcţie membră
nestatică şi nu poate citi sau modifica datele nestatice ale clasei din
care face parte (decât dacă i se transferă ca parametru un obiect sau o
referinţă/pointer la acesta).
class Cursor{
static int xo, yo; //originea
int x, y;
public:
Cursor(int x1, int y1) {x = x1, y = y1;}
static void SetOrigin(int x1, int y1) {
xo = x1, yo = y1;
}
void GetPos(int & xp, int &yp) {
xp = xo + x; y = yo + y;
}
};//Sfârşit Cursor
Cursor::xo = 0; Cursor::yo = 0;
void main(){
Cursor::SetOrigin(10,10);
Cursor c(5,2);
int x,y; c.GetPos(x,y);
cout<<" x = "<<x<<" y = "<<y<<endl;
}

182
Constructorii
În limbajul C++ constructorii sunt funcţii membre speciale care
asigură crearea şi distrugerea corectă a obiectelor. Constructorul unei
clase este prima metodă care este apelată la declararea unui obiect,
imediat după alocarea memoriei necesare datelor membre.
Constructorul este apelat imediat ce o variabilă intră în domeniul ei de
vizibilitate, adică atunci când execuţia unui program "atinge"
instrucţiunea în care aceasta este declarată. Un constructor poate
indeplini o mare varietate de sarcini, ca de exemplu iniţializarea
variabilelor interne, alocări dinamice de memorie, deschidere de fişiere,
etc. Avem următoarele reguli privind constructorii:
♦ numele unui constructor trebuie să fie identic cu numele clasei;
♦ constructorii nu returnează nimic, nici măcar tipul void;
♦ constructorul poate apela alte funcţii membre sau nu;
♦ constructorul nu poate fi virtual;
♦ constructorul, ca orice altă metodă poate fi supraîncărcat, pot să
existe, deci, mai mulţi constructori într-o clasă, având acelaşi
nume cu numele clasei, dar deosebindu-se prin lista de parametrii
formali;
• o clasă poate avea oricâţi constructori, inclusiv nici unul, dacă nu
s-a declarat nici un constructor compilatorul generează un
constructor implicit, public, fără parametrii;
♦ constructorul implicit, folosit fără parametrii la iniţializarea
obiectelor, dacă este definit, trebuie să fie definit fără parametrii
sau să folosească valori prestabilite ale parametrilor formali;
♦ în mod normal constructorii sunt declaraţi ca fiind publici, în
cazuri speciale aceştia pot fi declaraţi private şi nu vor putea fi
apelaţi decât din cadrul altor funcţii membre sau de tip friend,
clasele din această categorie se numesc clase private;
♦ dacă o clasă conţine date membre constante sau referinţe la
obiecte, atunci constructorul trebuie scris în aşa fel încât să
iniţializeze referinţele înaintea celorlalte date membre;
♦ constructorul de copiere iniţializează un obiect printr-o operaţie de
copiere a datelor dintr-un obiect existent, având o referinţă la
acesta;
♦ dacă în cadrul unei clase nu este declarat un constructor de
copiere, compilatorul crează unul implicit;
♦ se recomandă definirea unui constructor de copiere în special când
se lucrează cu clase care manevrează structuri de date dinamice,
aceşti constructori realizează o copie profundă a obiectelor, care
183
implică şi datele alocate dinamic, în timp ce constructorul de
copiere introdus de compilator realizează o copie superficială a
obiectelor, copiind doar datele membre ale claselor bit cu bit.

Exemplu:
#include "Point.h"
#define pi 3.14159265
class Circle{
public:
Circle(){radius = 0;}
Circle(int r){radius = r;}
Circle(int x, int y, int r){ Set(x,y,r);}
Circle(Circle&);
float Area(){return pi * radius * radius;}
protected:
Point center; int radius;
public:
void Read(); void Print(); void Set(int, int, int);
};
Circle::Circle(Circle &a){ center = a.center; radius = a.radius;}
void Circle::Set(int x, int y, int r){ center.Set(x,y); radius = r;}
void Circle::Read(){center.Read(); cout<<"Raza = "; cin >> radius;}
void Circle::Print(){cout<<”Circle:”<<end;center.Print();
cout<<"Radius = "<<radius<<endl;}
Destructorul
Destructorul este entitatea care dezactivează toate funcţiile unui obiect
înainte de eliberarea memoriei ocupate de acesta. Destructorii ca şi
constructorii pot efectua orice fel de operaţii, dar de obicei sunt
utilizaţi în clase care manevrează structuri de date alocate dinamic sau
care operează cu fişiere sau cu fluxuri de comunicaţie. Destructorul
este apelat ori de câte ori se "atinge" punctul final al domeniului de
vizibilitate al unei variabile, ca de exemplu: întoarcerea dintr-o funcţie,
terminarea unei bucle (pentru variabilele automatice declarate în
interiorul unei bucle, etc). Perioada dintre momentul creării unei
variabile şi momentul distrugerii sale se numeşte "durată de viaţă".
Trebuie respectate următoarele reguli de folosire a destructorilor:
♦ numele destructorului trebuie să fie identic cu cel al clasei care îl
conţine şi trebuie să fie precedat de caracterul tilda "~";
♦ destructorul nu returnează nici un tip de date, nici măcar void;
184
♦ destructorul nu poate avea parametrii;
♦ o clasă nu poate avea mai mult de un destructor, dacă pentru o clasă
nu este precizat nici un destructor atunci compilatorul crează un
destructor pentru clasa respectivă;
♦ dacă un obiect conţine ca date membre alte obiecte atunci
destructorii obiectelor membre sunt apelaţi după ce destructorul
clasei a fost executat, ordinea în care sunt distruse componentele este
inversă faţă de cea în care au fost create (ordinea din declaraţia
clasei);
♦ în timpul execuţiei programului este apelat automat destructorul
obiectelor a căror durată de viaţa a expirat.
Astfel, durata de viaţă a obiectelor este:
• obiectele automatice: sunt locale unei funcţii şi de aceea sunt
păstrate (create şi utlizate) pe stivă, acestea există atâta timp cât
"există" funcţia, se crează în prologul funcţiei şi se distrug în
epilogul acesteia de către destructor;
• obiectele statice: sunt declarate în afara funcţiilor sau în interiorul
funcţiilor cu ajutorul cuvântului cheie static, acestea sunt create la
începutul programului şi există pe întreaga durată de execuţie a
programului, la terminarea programului se apelează destructorul
fiecărui obiect static;
• obiecte dinamice: sunt utilizate prin alocare de locaţii din heap – o
zona de memorie specială - sunt create cu operatorul new şi sunt
eliberate explicit cu operatorul delete, pot exista şi după terminarea
funcţiei în care au fost create cu operatorul new, nu sunt eliberate
automat;
• obiecte membre ale altor obiecte sunt create/distruse atunci când
obiectul căruia îi aparţin este creat/distrus.
Declaraţii de obiecte. Instanţieri
În limbajele de programare un loc central îl ocupă conceptul de tip de
date. Fiecare variabilă care este prelucrată într-un program este de un
"anumit tip" predefinit sau definit de programator.
În limbajele de programare orientate obiect tipurile de date
definite de programator, sau tipurile abstracte sunt mai importante.
Declararea unei variabile de un anumit tip abstract se numeşte
instanţiere, iar variabilele se numesc obiecte sau instanţe ale clasei.
Sintaxa este asemănatoare cu cea a declaraţiilor de variabile.

185
#include "Point.h"
#include "Circle.h"
void main(){
Point A, B(1,2);
Point C = Point(3,3);
A.Set(10,5);
A.Print();
A = C;
A.Print();
Circle C1,C2(10);
Circle C3 = Circle(10,10,10);
Circle C4(C3), C5 = C4;
C1.Read();
C1.Print();
C5.Print();
}
Deoarece declaraţia lui C1 nu specifică nici un argument compilatorul
apelează constructorul fără parametrii, C2 se va iniţializa cu ajutorul
constructorului cu doi parametrii, instanţa C4 va fi o copie lui C3 (primit
ca argument), deci pentru C4 se va utiliza constructorul de copiere.
O altă metodă de iniţializare foloseşte listele de instanţiere. Lista
membrilor clasei ce urmează a fi iniţilizaţi se plasează între lista de
parametrii a costructorului şi corpul acestuia. Lista este formată din
constructori cu parametrii specifici, câte unul pentru fiecare membru
ce trebuie iniţializat în acest mod. Această metodă este singura care se
poate folosi la iniţializarea clasei ce conţine date membre constante,
referinţe la obiecte sau obiecte fără constructor implicit public. Un
aspect important legat de listele de instanţiere este ordinea în care se
face iniţializarea în cadrul acesteia, mai ales dacă la iniţializarea unor
membrii se folosesc valorile altora.
Ordinea de instanţiere a datelor membre este dată întotdeauna de
ordinea în care datele apar în declaraţia clasei şi nu de ordinea
acestora în lista de instanţiere. Dacă un obiect membru conţine în
declaraţia clasei sale constructor implicit atunci acest obiect poate lipsi
din lista de instanţiere. În cazul în care un membru al unei clase este
un pointer la un obiect atunci nu putem iniţializa pointerul în cadrul
listei de instanţiere, constructorii nu pot fi apelaţi pentru pointeri la
obiecte (în această listă) ci numai pentru obiecte şi referinţe.

186
#include "Point.h"
class Rectangle{
protected:
Point A, B;
public:
Rectangle(){ }
Rectangle(int x1,int y1,int x2,int y2):
A(x1,y1),B(x2,y2){ }
Rectangle(Point &a, Point &b ):A(a), B(b){ }
Rectangle(Rectangle & r):A(r.A), B(r.B){ }
int Area(){return (B.x-A.x)*(B.y-A.y);};
};
Aşa cum construim tablouri sau matrici de structuri în limbajul C, tot
aşa putem construi tablouri sau masive de obiecte. Iată, de exemplu,
cum putem defini un triunghi:
Point triunghi[3] ;
Pentru fiecare element al tabloului se va apela constructorul implicit.
Triunghiul poate fi iniţializat aşa cum se iniţializează tablourile de
structuri dacă obiectele componente conţin doar membrii publici, nu
conţin metode virtuale şi nu provin din clase derivate.
Point triunghi[3] = {{0,0},{0,3},{4,0}};
Un alt mod de iniţializare a tabloului poate folosi lista de constructori
într-un mod asemănător cu utilizarea listei de instanţiere, iniţializarea
tabloului poate fi parţială deoarece pentru restul elementelor se
apelează constructorul implicit:
Point triunghi[10] = {Point(), Point(0,3), Point(4,0)};
Operatorii new şi delete
Pentru alocarea şi eliberarea dinamică a memoriei în limbajul C avem
la dispoziţie funcţiile malloc şi free. Limbajul C++ introduce doi
operatori noi pentru a facilita operaţiile cu memoria alocată dinamic în
contextul programării orientate obiect. Aceşti operatori sunt new –
pentru alocarea memoriei şi delete – pentru eliberarea acesteia.
Avantajele folosirii operatorului new în comparaţie cu utilizarea
funcţiei malloc sunt:
♦ operatorul new ştie ce cantitate de memorie trebuie alocată pentru
orice tip de date, în timp ce funcţiei malloc trebuie să i se specifice
această dimensiune;

187
♦ tipul pointerul returnat de operatorul new este acelaşi cu tipul
pointerului care va memora adresa zonei de memorie alocate de
operator, nu este nevoie de nici un fel de conversie;
♦ operatorul new poate apela un constructor sau poate iniţializa
variabila standard alocată, spre deosebire de malloc care nu are
această proprietate;
♦ funcţia malloc întoarce un pointer la void ceea ce ne obligă să
facem o conversie la tipul aşteptat;
♦ operatorul new poate fi utilizat în manevrarea de masive.
Varianta C:
int *p = (int *) malloc(sizeof(int));
int *m = (int *) malloc(1000* sizeof(int));
Varianta C++:
int *p = new int;
int *m = new int[1000] ;
int *pzero=new int(20);//alocă un int şi îl iniţializează cu 20
char *ch=new char('A');//alocă un char şi îl iniţializează cu ‘A’
Operatorul new a fost special introdus pentru construirea dinamică a
obiectelor, astfel următoarea declaraţie alocă memorie în heap pentru
a stoca un obiect din clasa Point şi apoi apelează constructorul cu doi
parametrii pentru iniţializarea obiectului.
Point *pPoint = new Point(10,10);
În mod asemănător se poate aloca şi iniţializa un tablou de obiecte, în
acest caz se va apela constructorul implicit (fără parametrii) pentru
fiecare obiect din tablou;
Point * t = new Point[1000];
Operatorul delete este similar cu funcţia free din limbajul C, având ca
scop eliberarea memoriei din heap alocate cu ajutorul operatorului
new. Operatorul new apelează un constructor pentru iniţializarea
memoriei alocate, în mod similar operatorul delete apelează destruc-
torul imediat înaintea eliberării memoriei ocupate de un obiect.
Atenţie, pentru eliberarea de masive acest operator are o sintaxă
diferită. Astfel, dacă alocăm un tablou prin
char *sir = new char[80];
atunci delete sir; nu va elibera întregul şir ci doar primul octet sir[0].
Pentru a elibera tot tabloul va trebui să scriem delete [] şir.

188
Exemplu:
#include <iostream.h>
#include <stdlib.h>
class String{
char *data;
int len;
void Init(char * str){
len = strlen(str);
data = new char[len+1];
strcpy(data,str);
}
public:
String(){data = NULL; len = 0;}
String(String & str){Init(str.data);}
String(char *str) {Init(str);}
~String(){ //destructor
if (data) {
cout<<"Destructor: "<<data<<endl;
delete[] data;
}
}
void Concat(String &str){ Concat(str.data); }
void Concat(char *str){
int len2 = strlen(str);
data = (char *)realloc(data, len + len2 +1);
data[len]=0;
strcat(data,str);
len += len2;
}
void Print(){ if (data) cout<<data<<endl; }
};
void main(){
String s1 = String("Clasa String in ");
s1.Concat("programarea OOP cu ");
String s2("Limbajul C++ ");
s1.Concat(s2);
s1.Print();
//alocare dinamica
String *s = new String();
s->Concat(s4);
189
s->Concat("cu alocare dinamica!");
s->Print();
delete s;
//alocarea unui tablou de obiecte
String *stab = new String[10];
stab[0].Concat(s2);
stab[0].Print();
//eliberarea tabloului de obiecte
delete [] stab;
//alocarea unui tablou de pointeri la obiecte
String* *ptab = new String*[10];
//alocarea fiecarui obiect din tablou
for (int i=0;i<10;i++) ptab[i] = new String("Sir de caractere!");
//afisarea sirurilor
for (int i=0;i<10;i++) if(ptab[i]) ptab[i]->Print();
//eliberarea sirurilor
for (int i=0;i<10;i++) if(ptab[i]) delete ptab[i];
//eliberarea tabloului de pointeri
delete [] ptab;
}
Funcţii şi clase prietene
Datele private şi protejate membre ale unei clase poat fi accesate doar
de funcţiile membre ale clasei respective. Există cazuri când dorim să
permitem accesul la datele membre (private şi protejate) şi altor
funcţii, care pot fi membre ale altor clase sau nu. Acste funcţii se
numesc funcţii prietene (sau de tip friend). Declararea unor funcţii
prietene se face în interiorul clasei şi este precedată de cuvântul cheie
friend. Definiţia funcţiei se face în exteriorul clasei şi nu trebuie să fie
precedată de friend şi nici de numele clasei căreia îi este prietenă, este
o funcţie obişnuită. Singura diferenţă dintre funcţiile membre şi cele
prietene este aceea că acestea din urmă nu pot întoarce o referinţă la
clasa prietenă. Funcţiile prietene pot facilita implementarea unor ope-
raţii care sunt dificil sau imposibil de îndeplinit prin intermediul
funcţiilor membre.
Un alt tip se funcţii prietene sunt funcţiile membre ale altor
clase, creându-se astfel o punte de legătură între clase. Există două
moduri de a preciza acest lucru. Prima variantă constă în precizarea
funcţiei membru ca fiind de tip friend împreună cu numele clasei din
care face parte, iar a două variantă constă în declararea unei clase ca
fiind prietenă, ceea ce spune că toate datele protejate pot fi accesate şi
190
modificate de toate funcţiile membre ale clasei prietene. În ambele
variante una dintre clase trebuie să fie predeclarată.
Exemplu:
#include <iostream.h>
class Complex;
class Scalar{
float nr;
public:
Scalar(){nr = 0.0;}
Scalar(float s){nr = s;}
friend class Complex;
};
class Complex{
float re, im;
public:
Complex(){ re = im = 0.0;}
Complex(float r, float i){
re = r, im = i;}
Complex(Complex & c){
re = c.re, im = c.im;}
Complex Add(Scalar & s);
Complex Mul(Scalar & s);
friend Complex Add(Complex& c1, Complex & c2);
friend Complex Mul(Complex& c1, Complex & c2);
friend void Print(Complex & c);
};
void Print(Complex &c){
cout<<c.re;
if (c.im > 0)
cout << "+"<<c.im<<"i"<<endl;
else
if (c.im < 0)
cout << c.im<<"i"<<endl;
}
Complex Add(Complex& c1, Complex & c2){
Complex rez;
rez.re = c1.re + c2.re;
rez.im = c1.im + c2.im;
return rez;
}
191
Complex Mul(Complex& c1, Complex & c2){
Complex rez;
rez.re = c1.re * c2.re - c1.im*c2.im;
rez.im = c1.re*c2.im + c1.im*c2.re;
return rez;
}
Complex Complex::Add(Scalar & s){
return Complex(re+s.nr, im);
}
Complex Complex::Mul(Scalar & s){
return Complex(re * s.nr, im * s.nr);
}
void main(){
Complex c1(1,-1); Print(c1);
Complex c2(c1), c3;
c3 = Add(c1,c2); Print(c3);
c3 = Mul(c1,c2); Print(c3);
Scalar sc(1.3);
c3 = c1.Add(sc); Print(c3);
c3 = c1.Mul(sc); Print(c3);
}
Moştenirea
Moştenirea este unul dintre cele mai importante concepte ale
limbajelor de programare pe obiecte. Acesta permite extinderea
claselor existente şi aduce posibilitatea construirii de noi clase într-un
mod simplu. Moştenirea permite transferul atributelor şi a metodelor
unei clase construite anterior, numită clasă de bază, către o nouă clasă
numită clasă derivată. Deci clasele derivate au toate caracteristicile
claselor de bază şi, în plus, pot introduce noi elemente structurale şi
funcţionale. Moştenirea în limbajul C++ este de două feluri: a) moşte-
nire simplă – prin care o clasă extinde o singură clasă de bază; b) moş-
tenire multiplă – prin moştenire multiplă trăsăturile a două sau mai
multe clase pot fi înglobate şi eventual exinse într-o clasă nouă.
La prima vedere moştenirea se aseamănă cu procesul de includere
a obiectelor în obiecte (refolosire), dar diferă de acesta prin urmă-
toarele:
♦ codul poate fi comun mai multor clase;
♦ clasele pot fi extinse fără a fi recompilate (extensibilitate);
♦ funcţiile care prelucrează obiecte din clasa de bază vor prelucra
automat şi obiecte din clasa derivată (abstractizare);
192
♦ sintaxa folosită la derivare este următoarea:
class clasaDerivata:[tipMostenire]clasaBaza
{
//noi membrii
};
unde tipMostenire este un modificator opţional şi poate fi unul din
următoarele: public, protected, private (implicit) sau virtual.
Exemplu de moştenire simplă publică:
#define pi 3.14159265
class Circle :public Point{
protected:
int radius;
public:
Circle() { radius = 0;}
Circle(int r){ radius = r;}
Circle(int x,int y,int r):Point(x,y){radius=r;}
Circle::Circle(Circle &c){
Set(c.x,c.y,c.radius);
}
float Area (){
return 2* pi* radius * radius;
}
void Circle::Set(int x, int y, int r){
//Point::trebuie precizat
//altfel incearcă să se autoapeleze
//şi se genereaza eroare: nr de parametrii
Point::Set(x,y);
radius = r;
}
void Print(){
cout<<"Circle: "<<endl;
Point::Print(); cout<<"radius = "<<radius<<endl;
}
}; //Clasa Circle
class Cylinder: public Circle{
protected:
int height;
public:
Cylinder(){ height =0;}
Cylinder(int r, int h):Circle(r){ height = h;}
193
Cylinder(int x, int y, int r, int h):
Circle(x,y,r){ height = h;}
Cylinder(Cylinder& c){
//mostenita de la Circle
Set(c.x, c.y,c.radius);
height=c.height;
}
//aria laterala
float LArea(){
return 2*pi*radius*height;
}
//aria totala
float Area(){
return 2*Circle::Area()+LArea();
}
void Print(){
cout<<"Cylinder:"<<endl;
Circle::Print();
cout<<"height = "<<height<<endl ;
}
};
void main(){
Point p; Circle c; Cylinder cy(1,1,10,10);
p = c = cy;
cy.Print();c.Print(); p.Print();
cout<< "Aria cilindrului = "<<cy.Area()<< endl;
cout<< "Aria cercului = "<<c.Area()<< endl;
}
În exemplu de mai sus se poate observa cum se poate extinde o clasă
de bază construind clase derivate în vederea obţinerii unor obiecte noi
cu funcţionalităţi sporite, cum elementele membre ale clasei de bază
devin date membre ale clasei derivate, se mai observă modul în care se
reutilizează (atenţie la sintaxa unui apel de funcţie moştenită) codul
unor funcţii preexistente fără să mai fie nevoie de recompilarea
acestora.
Din acest exemplu se mai poate desprinde un aspect foarte
interesant: obiectele claselor derivate sunt privite ca fiind obiecte ale
clasei de bază, ceea ce ne permite să atribuim obiectelor din clasa de
bază instanţe ale clasei derivate fără să fie necesară conversia de tip.
Această regulă este valabilă doar la obiectele care sunt legate prin
194
relaţia de moştenire şi se extinde şi asupra pointerilor şi referinţelor la
astfel de obiecte. Atribuirea este posibilă doar în acest sens, o
încercare de a atribui unui obiect din clasa derivată o instanţă a clasei
de bază fiind ilegală.
Exemplu:
Point p;
Circle c;
Cylinder cy;
Point & pr = p;
Circle & cr = c;
Cylinder & cyr=cy;
pr = cr = cyr;
Point * pp; Circle * pc;
Cylinder *pcy = new Cylinder(10,25);
pp = pc = pcy;
Ce rezultat va produce codul urmăror ?
Circle * c1 = new Cylinder(10,25);
c1->Print();
cout<<"Aria = "<< c1->Area()<<endl;
În exemplul de mai sus am discutat moştenirea de tip public, în acest
caz datele publice/protejate în clasa de bază ramân publice/protejate şi
în clasa derivată, cele private (intime clasei de bază) sunt inaccesibile
direct, aceste putând fi manipulate cu ajutorul funcţiilor de acces.
Mai trebuie menţionat faptul că la inţializarea obiectelor întâi
sunt apelaţi constructorii claselor de bază după care se execută corpul
constructorului clasei derivate. Constructorii nu pot fi moşteniţi, deci
nu putem folosi un constructor al clasei de bază pentru a iniţializa toţi
membrii clasei derivate. În plus, dacă toţi constructorii clasei de bază
necesită parametrii atunci clasa derivată trebuie să conţină un con-
structor care să apeleze (în lista de instanţiere) unul din constructorii
clasei de bază cu parametrii potriviţi. Deci, prezenţa constructorilor în
clasele derivate devine, în unele cazuri, obligatorie.
Destructorii claselor de bază sunt apelaţi în ordine inversă faţă
de ordinea de iniţializare, întâi destructorul clasei derivate apoi
destructorii claselor derivate. Destructorul fiind funcţie fără parame-
trii, prezenţa acestuia în clasele derivate este opţională.
În continuare urmează un tabel în care se poate observa modul în
care se moştenesc datele, pe cele trei niveluri de protecţie şi prin cele
trei tipuri de moşteniri mai importante.

195
Tipul moştenirii
Public Protected private
public în clasa protected în private în
Public
În clasa de bază

derivată clasa derivată clasa derivată

Protected în protected în private în


Protected
clasa derivata clasa derivată clasa derivată
ascuns în clasa ascuns în clasa ascuns în
Private
derivată derivată clasa derivată

Polimorfism şi funcţii virtuale


Deseori, prin moştenire clasa derivată primeşte un set de funcţii de la
clasa de bază care, din cauza modificărilor de structură a clasei
derivate, nu produc rezultatul dorit atunci când sunt apelate pentru
obiecte din clasa derivată. Un astfel de exemplu este funcţia Area()
din clasa Point care este moştenită de clasa Circle şi de la aceasta de
Cylinder. Apelul acestei metode pentru puncte întoarce întotdeauna
zero, ceea ce nu este corect şi pentru un cerc sau cilindru. De aceea
această funcţie a fost redefinită (supradefinită) în aceste două clase.
Ştim acum că obiectelor clasei de bază (Point) putem să le atribuim
obiecte din clasa derivată (Circle) fără a mai fi necesară o conversie
de tip. Obiectul rezultat după atribuire aparţine tot clasei de bază
(Point), în consecinţă pentru apelul metodei Area pentru acest obiect
se va executa varianta definită în clasa Point. Aceasta se datorează
faptului că apelul metodei este legat de corpul acesteia static în
momentul compilării, compilatorul putând identifica încă din această
fază care variantă trebuie asociată apelului şi executată:
Point p;
Circle c;
p = c;
cout<<p.Area()<<endl; //afişează zero
Atunci când discutăm despre pointeri şi referinţe la obiecte lucrurile
stau altfel. În cazul în care am declarat o referinţă sau un pointer la un
obiect din clasa de bază avem posibilitatea să le iniţializăm cu adresele
din memorie a unor obiecte aparţinând claselor derivate, ca în
exemplul de mai jos. După o astfel de iniţializare avem un singur

196
nume de metodă şi două posibilităţi, două forme ale funcţiei. În acest
moment discutăm de polimorfism.
Point *pp = new Circle(1,1,5);
cout<< pp->Area() << endl;
Noi am dori ca pentru apelul metodei Area() să fie aleasă pentru
executare varianta funcţiei definită în clasa Circle. Dar nu se întâmplă
aşa. Pointerul pp conţine adresa unui Circle, dar metoda apelată este
Point::Area(), ceea ce va duce la afişarea valorii zero. La rezolvarea
acestei probleme intervin funcţiile virtuale şi mecanismul de legare
dinamică. Pentru a fi apelată metodă corectă în funcţie de tipul
obiectului care se află la adresa stocată în pointer trebuie să declarăm
metoda Area() ca fiind virtuală. Pentru aceasta va trebui să plasăm
cuvântul cheie virtual în faţa declaraţiei funcţiei membre Area().
class Point{

virtual float Area(){ … }
};
Prin aceasta anunţăm compilatorul să nu aleagă în momentul compi-
lării care variantă a funcţiei Area() să fie asociată unui apel prin poin-
teri sau referinţe. În acest caz compilatorul nu ştie în momentul
compilării care este tipul obiectului la care se referă pointerul sau
referinţa, iar corpul metodei va fi ales în timpul rulării programului.
Această alegere şi asociere din momentul executării programului
foloseşte identificarea şi legarea dinamică a metodelor.
Exemplu:
void PrinArea(Point & p){
cout << p.Area()<<endl;
}
void main(){
Point p(3,3); Circle c(1,1,3);
PrinArea(p); PrinArea(c);
}
Regulile impuse de limbajul C++ cu privire la funcţiile virtuale sunt:
♦ o funcţie membră odată declarată virtuală va rămâne virtuală în
toate clasele derivate;
♦ funcţiile virtuale nu pot fi supraîncărcate;
♦ constructorii nu pot fi virtuali pentru că aceştia nu pot fi moşteniţi;
♦ destructorii pot, şi de cele mai multe ori, vor fi virtuali pentru a
asigura o distrugere corectă a obiectelor aparţinând claselor
derivate;
197
♦ funcţiile virtuale pot apela funcţii obişnuite (nonvirtuale) ;
♦ funcţiile nonvirtuale pot apela funcţii viruale;
♦ funcţiile virtuale pot apela orice altă funcţie virtuală;
♦ funcţiile virtuale pot apela funcţii redefinite.
Moştenire multiplă
Acest tip de moştenire permite unei clase să primească toate
“trăsăturile” (în afară de cele private) a două sau mai multe clase.
Sintaxa generală pentru moştenire multiplă este următoarea:
class clasaDerivata:[tipMostenire1]clasaBaza1,
[tipMostenire2]clasaBaza2,…
{ //noi membrii };
Tipul moştenirii (1 şi 2) poate fi omis (şi atunci este considerat
private), poate fi precizat o singură dată la începutul listei pentru
pentru toate clasele de bază din listă, sau poate fi precizat pentru
fiecare clasă de bază în parte (ca în exemplul de mai jos). Accesul la
membrii claselor de bază din funcţiile membre ale clasei derivate se
supune regulilor precizate în tabelul de mai sus pentru fiecare clasă de
bază.
În exemplul care urmează am definit o clasă Stack care imple-
mentează funcţiile Push şi Pop ale unei clase GenericStack, si
foloseşte o listă simplu înlănţuită pentru a memora elementele în stivă.
Se poate observa că am precizat clasei Node că GenericStack este o
clasă prietenă, pentru a facilita accesul direct la membrii privaţi ai
clasei Node din cadrul funcţiilor membre clasei GenericStack. Clasa
LinkedList este moştenită folosind tipul protejat pentru a împiedica
apelul metodelor publice InsertHead şi ExtractHead din exterior
asupra unui obiect de tip Stack, deoarece aceste metode îşi pierd
semnificaţia în noul context. Funcţia f( ) care operează cu stive
generice lucrează în mod corect deoarece metodele Push şi Pop sunt
declarate virtuale. Clasa Stack nu este obligată să redefinească metode
Pop şi Push, pentru a forţa aceasta cele două metode se vor declara ca
fiind metode abstracte (sau virtuale pure) şi clasa GenericStack va
deveni clasă abstractă.
class Node{
int inf; Node * next;
public:
Node(int x=0){inf = x;}
friend class LinkedList;
};

198
class GenericStack{
protected:
int count;
public:
GenericStack(){count=0;}
virtual void Push(int t){ }
virtual int Pop(){return 0; }
};
class LinkedList{
Node * head;
public:
LinkedList(){ head = NULL; }
void InsertHead(int t){
Node * p = new Node(t);
p->next=head; head=p;
};
int ExtractHead(){
int t = -1;
if (head) {
t= head->inf; Node * p = head;
head= head->next;
delete p;
}
return t;
};
};
class Stack:public GenericStack,protected LinkedList{
public:
Stack(){}
void Push(int t){ count++;InsertHead(t); }
int Pop(){count --; return ExtractHead();}
};
void f(GenericStack & s){
s.Push(2);
cout<<s.Pop()<<endl;
}
void main(){
Stack stack; stack.Push(1); cout<<stack.Pop()<<endl;
f(stack);
}

199
5.2. Exerciţii şi probleme propuse

1. Să se proiecteze o clasă pentru implementarea structurii de date:


listă liniară simplu înlănţuită.
2. Să se proiecteze o clasă pentru implementarea structurii de date:
listă liniară dublu înlănţuită.
3. Să se proiecteze o clasă pentru implementarea structurii de date:
listă circulară simplu înlănţuită.
4. Să se proiecteze o clasă pentru implementarea structurii de date:
arbore binar.
5. Să se proiecteze o clasă pentru implementarea structurii de date:
graf orientat.
6. Să se proiecteze o clasă pentru implementarea structurii de date:
graf neorientat.
7. Să se proiecteze o clasă pentru implementarea structurii de date:
vector de liste liniare simplu înlănţuite.
8. Să se proiecteze o clasă pentru implementarea structurii de date:
vector de liste liniare dublu înlănţuite.
9. Să se proiecteze o clasă pentru implementarea structurii de date:
vector de liste circulare simplu înlănţuite.
10. Să se proiecteze o clasă pentru implementarea structurii de date:
vector de arbori binari.
11. Să se proiecteze o clasă pentru implementarea structurii de date:
vector de grafuri orientate.
12. Să se proiecteze o clasă pentru implementarea structurii de date:
vector de grafuri neorientate.
13. Proiectaţi o clasă pentru manipularea matricelor pătratice. Inclu-
deţi metode pentru operaţiile de transpunere, schimbare a
semnului elementelor, înmulţirea unei matrice cu un număr etc.
14. Proiectaţi o clasă care să reprezinte noţiunea de şir de caractere.
Consideraţi metode pentru implementarea operaţiilor de intrare-
ieşire utilizând următoarele moduri: standard, fişier, în memorie;
metode pentru: afişarea codificării ASCII a şirului, determinarea
lungimii, concatenarea şirului cu un alt şir etc.
15. Proiectaţi o clasă pentru lucrul cu înregistrări. Includeţi metode de
intrare-ieşire, accesarea componentelor etc.

200
6. Teste pentru verificarea cunoştinţelor
6.1. Teoria şi practica programării
Test 1 (Bacalaureat , sesiunea iunie 1999; test adaptat)
I.1. Se consideră următoarea secvenţa în pseudocod:
Citeşte a,b;
a a-b;
b a+b;
a b-a
Scrie a,b
a) Ce se va afişa pentru valorile citite a = 5, b = 7?
b) Ce realizează secvenţa dată?
2. Ce metodă de programare este folosită de secvenţa următoare de
program? (Procedurile Sol şi Nucleu, respectiv tipul vector se
consideră declarate anterior în program);
procedure metoda(p,q:integer,a:vector);
var m:integer;
begin
if q-p<=2 then nucleu(p,q,a)
else begin
m:=(p+q) div 2;
metoda(p,m,a);
metoda(m+1,q,a);
sol(p,q,m,a);
end
end;
3. Rescrieţi secvenţa de program de mai jos realizată cu structura pentru,
utilizând:
a) o structură repetitivă cu test iniţial;
b) o structură repetitivă cu test final;
citeşte n
p 0; a 1;
pentru i<1.6 executa
[p p+i*a; a a*n]
stop
c)Explicaţi care sunt diferenţele dintre structura repetitivă cu test
iniţial şi cea cu test final.
201
II.1. Se consideră un vector cu n elemente reale.
a) Scrieţi o funcţie recursivă care verifică dacă vectorul conţine,
sau nu, cel puţin un element pozitiv.
b) Scrieţi o procedură de tipărire a elementelor vectorului.
2. Se consideră un fişier text cu numele “MULT.IN” care are două
linii. Fiecare linie conţine elementele (ce fac parte din mulţimea {‘a’
… ’z’, ’A’…’Z’, ’0’…’9’}) mulţimilor A şi B, separate între ele prin
câte un spaţiu. Să se scrie un program care:
a) Scrie pe prima linie din fişierul text “MULT.OUT” mesajul
‘DA’ dacă A este inclusă în B, respectiv ‘NU’ în caz contrar.
b) Scrie pe a doua linie din fişierul “MULT.OUT” elementele
mulţimii (A\B)∪(B\A), adică diferenţa simetrică.
Exemplu: Dacă fişierul “MULT.IN” conţine
oa b c x y
9c d e X g
atunci fişierul MULT.OUT va conţine
NU
a b c d e g o x X Y 9
III. Se citeşte de la tastatură un număr natural n (n<=20), şi un număr
natural v. Scrieţi un program comentat folosind metoda Backtraking
care afişează toate numerele de la 1 la n în toate modurile posibile,
astfel încât între oricare două numere afişate în poziţii învecinate,
diferenţa în modul să fie mai mare (>) decât valoarea dată v. Datele de
ieşire se vor scrie în fişierul “IESIRE.DAT”. În cazul în care nu există
soluţie , în fişierul de ieşire se va scrie: ’Nu există soluţie’.
Exemplu: Dacă n = 4 şi v = 1 rezultatul din fişierul de ieşire va fi
3 1 4 2
2 4 1 3
IV. Cunoscând valoarea n < 50, reprezentând numărul unor persoane
înscrise la un curs, scrieţi subprograme în Pascal sau în C pentru a
realiza următoarele cerinţe:
a) trebuie să creaţi o listă simplu înlanţuită ale cărei elemente
trebuie să conţină în partea de informaţie propriu-zisă numele şi nota
de absolvire a cursanţilor;
b) trebuie să ştergeţi primul element din lista creată;
c) trebuie să afişaţi lista dată.
BAREM DE NOTARE (10p din oficiu)
I: 1. a) 5p b) 2.5p; 2. 2.5p 3. a) 7.5p b)7.5p c)5p
II: 1. a)7.5p b)7.5p ⇒ 1.5; 2. 10p; III. 20p; IV. a)5p b)5p c)5p
202
Test 2 (Bacalureat –simulare- februarie 1999; text adaptat)
Subiectul I
1. Se consideră următoarea secvenţă de program:
citeşte N
Z←0
cât timp n<>0 execută
[ Z←Z*10+N mod 10; N←N div 10 ]
scrie Z
a) Ce valoare Z se va afişa pentru N=11204?
b) Modificaţi o singură instrucţiune în această secvenţă de program
astfel încât valoarea afişată pentru Z să reprezinte suma cifrelor numă-
rului N.
2. Care din următoarele afirmaţii sunt corecte şi care sunt false?
a) Un subprogram este recursiv dacă este apelat când subprogra-
mul este activ;
b) Recursivitatea este directă când apelul subprogramului apare
în instrucţiunea compusă a unui subprogram;
c) Prin utilizarea tehnicilor recursive se obţin întotdeauna soluţii
optime;
d) Autoapelul unui subprogram trebuie suspendat în momentul
îndeplinirii anumitor condiţii.
3. Scrie secvenţa de program de mai jos utilizând o structură repetitivă
cu număr cunoscut de paşi.
citeşte N
p ← 1; i ← 1
cât timp i<=N execută [p←p * i; i←i + 1]
scrie p
Subiectul II
1. Scrieţi un subprogram care returnează o valoare logică pentru a
determina dacă un număr este prim.
2. Scrieţi un subprogram recursiv pentru a aranja un şir de n numere
întregi distincte (n<=25) în toate modurile posibile.
Subiectul III
Un grup de n (n<=10) persoane numerotate de la 1 la n sunt aşezate pe
un rând de scaune, dar între oricare două persoane vecine s-au ivit
conflicte. Scrieţi un program care afişează toate modurile posibile de
reaşezare a persoanelor, astfel încât între oricare două persoane aflate
în conflict să stea una sau cel mult două persoane.

203
Exemplu: Dacă numărul persoanelor (introdus de la tastatură) este 4,
programul trebuie să afişeze:
3142
2413
Subiectul IV
1. Să se scrie un subprogram care concatenează o listă simplu
înlănţuită de tip stivă (referit de q) în continuarea altei liste simplu
înlănţuite de tip stivă (referită de p).
2. Să scrie un subprogram care caută un anumit număr dat, citit de la
tastatură în subprogram şi returnează o valoare logică corespunză-
toare. Lista este definită astfel:
Type lista = ^nod; Nod = record inf:integer; urm:lista end;
3. Se consideră două fişiere de tip text: f1 şi f2. Să se scrie un sub-
program care verifică dacă cele două fişiere sunt identice din punct de
vedere al conţinutului. În subprogram se vor scrie toate instrucţiunile
care testează fişierele.
Test 3 (Bacalaureat- Simulare 2000; text adaptat)
I.1. Încercuiţi litera corespunzătoare răspunsului corect.
Câte numere pot fi reprezentate folosind 8 biţi?
a) 128
b) 256
c) 8*8 (5 puncte)
2.Stabiliţi, încercuind litera A (adevărat) sau F (fals), valoarea de
adevăr a următoarelor afirmaţii care exprimă diferenţa dintre
variabilele locale şi cele globale:
A.F. Variabilele globale sunt declarate în afara oricărui bloc al progra-
mului, în timp ce variabilele locale sunt declarate în interiorul blocului
în care sunt folosite;
(3 puncte)
A.F. Variabilele locale sunt stocate în memorie, în timp ce variabilele
globale sunt stocate pe stiva; (3 puncte)
A.F. Variabilele globale există pe parcursul executării întregului
program, în timp ce variabilele locale nu există decât în timpul
executării blocului în interiorul căruia au fost declarate. (3 puncte)
3. În continuare aveţi prezentate posibile variante ale aceleiaşi
implementări sub formă de subprogram, în Pascal şi C, a algoritmului
ce determină numărul de cifre ale unui număr întreg pozitiv. Stabiliţi
corectitudinea acestor implementări pentru una din variabilele de mai
jos (Pascal sau C), la alegere. Justificaţi răspunsul.
204
Varianta Pascal:
function Varianta C:
numar_1(n:Longint):integer;
begin int numar_1 (long n){
numar_1:= return
trunc(ln(n)/ln(10))+1 floor (log(n)/log(10)+1;
end; }
(5 puncte) (5 puncte)
function int numar_2 (long n){
numar_2 (n:Longint):integer; char s[20];
var s: string[20] ltoa(n,s,10);
begin return strlen(s);
str(n,s);numar_2:=length(s); }
end; (5 puncte)
(5 puncte)
function int numar_3 (long n){
numar_3(n:Longint):Integer; int nr=0;
var nr:integer; while (n) {
begin nr++;
nr:=0; while n>0 do n/=10;
begin }
nr:=nr+1; n:=n div 10 return nr;
end; }
numar_3:=nr (5 puncte)
end; (5 puncte)

4. Definiţi notiunea de tablou pentru unul din limbajele Pascal sau C şi


exemplificaţi adecvat. (6 puncte)
II Scrieţi un subprogram recursiv care să returneze valoarea lui n!,
unde n∈N* este o valoare dată, iar n! este produsul primelor n numere
naturale nenule. (10 puncte)
III Scrieţi un program, care să afişeze toate modalităţile prin care se
poate plăti o sumă s folosind n bancnote de valori b1<b2<b3<…<bn. Se
presupune că avem la dispoziţie oricâte bancnote din fiecare tip.
Numerele S şi n precum şi valorile bancnotelor se citesc de la
tastatură, iar modalităţile de plată vor fi scrise în fişierul BANI.OUT.
(25 puncte)

205
IV Scrieţi un program care citeşte de la tastatură un număr n. Din
fişierul INTRARE.TXT se citesc n numere reale şi creează o listă
simplu înlănţuită pe care o scrie în fişierul IESIRE.TXT. (20 puncte)
Test 4 (Bacalaureat - Sesiunea iunie-iulie 2000; enunţ adaptat)
I. Scrieţi litera sau literele corespunzătoare răspunsului sau răspun-
surilor corecte, precizând la fiecare subpunct care dintre variantele de
limbaj a fost considerată.
1.Care dintre următoarele secvenţe afişează cel mai mare număr natural
care este mai mic sau egal cu valoarea variabilei reale pozitive x? (10p)
Varianta Pascal Varianta C++
a) m: =0; a) m=0;
while m<=x do m:=m+1; while (m<=x) m++;
writeln(m-1) cout« m-1;
b) m:=0; b) m=0;
repeat m:=m+1 until m>x; do {m+=1} while (m<=x);
writeln(m) cout« m;
c) m:=1; c) m=1;
repeat m:=m+1 until m>x; do {m+=1} while (m<=x);
writeln(m-1) cout« m-1;
d) for m:=0 to x do; d) for(m=0;m<x;m++);
writeln(m-1) cout« m-1;
2. Care dintre următoarele variante reprezintă declararea corectă a
două variabile de tip întreg? (5p)
Varianta Pascal Varianta C
a) var x,y:=integer; a) integer x,y;
b) var x:integer; y: longint; b) int x; long y;
c) type float=integer; c) typedef real int;
var x,y:float; real x,y;
d) var x:array[1. .2]of integer; d) int x[2];
3. Care este valoarea returnată de funcţia următoare la apelul f(4)? (5p)
Varianta Pascal Varianta C
function f(x:integer):integer; int f(int x)
begin {
if x<=0 then f:=3 if (x<=0) return 3;
else f:=f(x-1)*2 else return f(x-1)*2;
end. }
a)16 b)24 c)48 d)3

206
II. Se consideră următorul algoritm reprezentat prin programul
pseudocod:
Citeşte a,b {numere naturale}
dacă a>b atunci [ c←a a←b b←c ]
d←0
Pentru i= a, b, 1 execută
Dacă i este divizibil cu 2 atunci d←d+1;
Scrie m
1. Ce valoare se afişează pe ecran, conform algoritmului dat pentru
a=18 şi b = 33? (5p)
2. Precizaţi o valoare pentru variabila a şi o valoare pentru varlabila b
astfel încăt valoarea afişată ca urmare a executării programului dat să fie
0. (5p)
3. Scrieţi programul Pascal, C sau C++ corespunzător algoritmului.
(10p)
4. Scrieţi un program echivalent care să nu conţină nici o structură
repetitivă.
Cele două programe sunt echivalente dacă pentru orice valori naturale
citite pentru vanabilele a şi b, ele afişează aceeaşi valoare.(5p)
III. 1. In fişierul NUMERE.TXT se află mai multe numere naturale
din intervalul [0, 5000], scrise cu spaţii între ele. Să se creeze fişierul
PARE.TXT care să conţină, căte una pe linie, doar acele valori din
fişierul NUMERE.TXT care sunt numere pare.(10p)
2. Se consideră programul:
Varianta Pascal Varianta C
var v:array[1. .50] of integer; #include <stdio.h>
i:integer; int v[50],i;
function m(a,b:byte):longint; long m(int a,int b) {
begin
if a>b then m:=0 if (a>b) return 0;
else if a=b then m:=v[a] else if (a == b) then v[a]
else m:=m(a, (a+b) div 2)+ else return (
m((a+b)div 2+1,b) m(a,a+b)/2)+m((a+b)/2+1,b));
end; }
begin void main()
for i:=1 to 10 do v[i]:=i; {
writeln(m(3,8)) for (i=1;i<11;i++) v[i]=i;
end. printf("%ld\n",m(3,8));
}
Ce afişează programul anterior?(5p)
207
3. Scrieţi un program care citeşte de la tastatură cele 10 numere reale
ce compun vectorul a şi apoi cele 8 numere reale ce constituie compo-
nentele vectorului b şi afişează pe ecran câte dintre componentele
vectorului a sunt strict mai mici decât toate componentele vectorului b.
Exemplu: Dacă = (4,8,1,9,5,11,3,43,6,20) şi b = (9,9,6,9,9,8,6,9),
atunci numărul căutat este 4, deoarece valorile 4, 1, 5 şi 3 sunt mai
mici decât toate elementele lui b. (l0p)
IV. Sa se aflşeze toate numerele formate din cifre distincte cu proprie-
tatea că suma cifrelor este S. Valoarea variabilei S se citeşte de ia
tastatură. Soluţiile vor fi afişate pe ecran.
Exemplu: Pentru S =3, se afişează valorile 102, 12, 120, 201, 21, 210,
3, 30 (20p)
Test 5 (Bacalaureat - Sesiunea august 2000; enunţ adaptat)
I. Scrieţi litera sau literele corespunzătoare răspunsului sau
răspunsurilor corecte, precizând la fiecare subpunct care dintre
variantele de limbaj (Pascal sau C, la alegere) a fost considerată.
1. Care dintre următoarele secvenţe de instrucţiuni atribuie variabilei
întregi u valoarea ultimei cifre a numărului natural reprezentat de
variabila x? (5p)
Varianta Pascal Varianta C
a) u:=x div 10 a) u=x/10;

b) u:=x; b) u=x;
while u>=10 do u:=u mod 10 while (u>=10) u%=10;

c) u:=x mod 10 c) u=x%10;

d) while x>=10 do x:=x div 10; d) while (x>=10)x%=10;


u:=x u=x;
2. Care dintre următoarele variante reprezintă declararea corectă a
unei variabile structurate cu două componente, una de tip întreg şi una
de tip real? (5p)
Varianta Pascal Varianta C
a) integer x array[1..2] of real; a) int float x[2];
b) x:array[1. .2] of real; b) float x[2];
c) x:record a:real; b:integer end; c) struct (float a; int b;}x;
d) x:array[1. .2] of integer; d) int x[2];

208
e) type inreg = record e) typedef struct {
a: integer; int a;
b:real float b;
end; } INTREG;
var x:inreg; INREG x;
Care dintre următoarele secvenţe de instrucţiuni atribuie variabilei
întregi x valoarea 10 la puterea n, cu n număr natural, variabila
auxiliară i fiind de tip întreg? (10p)
Varianta Pascal Varianta C
a) x := 1; a) x = 1;
for i := 1 to n do X := X*n for(i = 1;i <= n;i ++) X *= n;
b) x := 1; b) x = 1;
for i := 1 to n do x := x*10 for(i=1;i<n;i++) x*=10;
c) x := 10; c) x = 10;
for i:=1 to n do x:=x*i for(i=1;i<n;i++) x*=i;
d) x:=1; i:=0; d) x=1; i=0;
while i<n do x:=x*10; i:=i+1 while (i<n) x*=10; i++;
II. Se consideră următoarea secvenţă de instrucţiuni în pseudocod:
citeşte n, x {numere naturale}
x = 0; pentru i = 1, n execută x := x+i*i
scrie x
1. Ce se va afişa pe ecran pentru n = 3 şi x = 8?(5p)
2. Scrie o secvenţă echivalentă, care să utilizeze structura repetitivă
cât timp.(5p)
3. Scneţi programul Pascal, C sau C++ corespunzător algontmului dat. (l0p)
III. 1. Scrieţi un program Pascal sau C care citeşte de la tastatură cele
20 de componente reale ale unui vector a şi afişează pe ecran câte
dintre valorile citite sunt mai mici decât media antmetică a
componentelor vectorului.
Exemplu: dacă valorile citite sunt 3.2, 9, -2, 0, 4, -4, -0.5, 7, 1, 0.3 ,14,
0, -7, 2, 2, -1, 3, 6, 0, 1, se va afişa pe ecran valoarea 11. (l0p)
2. În fişierul text BAC.TXT.se află mai multe numere naturale de cel
mult trei cifre fiecare, numere despărţite între ele prin câte un spaţiu.
Scrieţi un subprogram care creează o listă simplu înlănţuită folosind
alocarea dinamică a memoriei, listă care să conţină numerele din
BAC.TXT, în ordinea în care se află ele în fişier subprogramul trebuie
să retumeze adresa primului element al listei. Se vor defini tipurile de
date utilizate.(15p)

209
3. Se consideră funcţia definită recursiv:
Varianta Pascal Varianta C
function ce(i:word):integer; int ce(int i) {
begin if i=0 then ce:=0 if (i == 0) return 0;
else ce:=ce(i-1)+2*i else return ce(i-1)+2*i;
end; }
Ce valoare va returna ce(5)? Dar ce(55)? (10 p)
IV. Se citesc de la tastatură două numere naturale n şi m
(0<n<m<12). Să se afişeze toate şirurile formate din n litere distincte,
litere alese dintre primele m ale alfabetului englez. De exemplu,
pentru n=2 şi m=4 se afişează, nu neapărat în această ordine, şirurile:
AB, BA, AC, CA, AD, DA, BC, CB, BD, DB, CD, DC. (l5p)
Test 6 (Bacalaureat-Sesiunea specială iunie 2000; enunţ adaptat)
I. Scrieţi litera sau literele corespunzătoare răspunsului sau răspun-
surilor corecte, precizând la fiecare subpunct care dintre variantele de
limbaj a fost considerată.
1. Dacă x, a şi b reprezintă variabile reale şi a<b, ce expresie se
utilizează într-un program pentru a testa dacă valoarea variabilei x este
situată în intervalul închis [a,b]?(5p)
Varianta Pascal Varianta C
a) (x>a) and (x<b) a) x>a && x<b
b) (x >=a) or (x<=b) b) x>=a ║ x<=b
c) (x>=a) and (x<=b) c) x>=a && x<=b
d) a>=x<=b d) a>=x<=b
e) a<=x<=b e)a<=x<=b
2. Dacă a, b, c, x reprezintă variabile reale, ce instrucţiune se foloseşte
pentru a atribui variabilei x suma dintre media aritmetică a valorilor
variabilelor a şi b şi media aritmetică a valorilor variabilelor b şi c?(5p)
Varianta Pascal Varianta C
a) x:=(a+c)/2+b a) x = (a+c)/2+b;
b) x:=(a+b+c)/3 b) x = (a+b+c)/3;
c) x:=(a+b)/2+(b+c)/2 c) x = (a+b)/2+(b+c)/2;
d) x:=(a+b+c)/3 d) x = (a+b+c)/3;
e) x:=a+b/2+b+c/2 e) x = a+b/2+b+c/2;
3. Dacă valorile variabilelor a, b şi x sunt numere naturale, cum se
poate atribui variabilei x restul împărţirii valorii variabilei a la
valoarea variabilei b? (5p)

210
Varianta Pascal Varianta C
a) x:=a mod b a)x = a%b;
b) x:=a div b b)x = a/b;
c) x:=a/b c)x = a/b;
d) if a>=b then x:=a div b d)if (a>=b) x=a/b;
else x:=b div a else x= b/a;
e) if a>=b then x:=a mod b e)if (a>=b) x= a%b;
else x:=b mod a else x= b%a;
II. Se consideră algoritmul reprezentat prin program pseudocod:
b := 0; c := 0
citeşte n {număr natural}
pentru i = 1, n execută
citeşte a {număr întreg}
dacă a>0 atunci c←c+l ; b←b+a
dacă c=0 atunci scrie “Imposibil”
altfel scrie b/c (cu 2 zecimale)
1. Scrieţi programul Pascal, C sau C++ corespunzător algoritmului. (15p)
2. Ce se afişează pe ecran, conform algoritmului dat, dacă toate valo-
rile citite prin program sunt egale cu 5? (5p)
3. Scrieţi o valoare pentru variabila n şi apoi un şir de valori introduse
succesiv pentru variabila a astfel încăt programul dat să afişeze
valoarea 5.50. (5p)
III. În fişierul text SERII.TXT se afiă, câte una pe linie, seriile cărţilor
de identitate ale unor persoane, fiecare serie fiind formată din exact
două litere mari de tipar nedespărţite prin spaţii. În fişierul text
NUMERE.TXT se află, câte unul pe linie, numerele cărţilor de identi-
tate ale aceloraşi persoane, în aceeaşi ordine, fiecare număr fiind
format din exact 6 cifre nedespărţite prin spaţii. Se consideră că
formatul fiecărei serii şi fiecărui număr este respectat de datele din
fişier, nefiind necesare validări ale acestora.
Să se verifice dacă fişierul SERII.TXT conţine tot atătea linii cu
serii câte linii cu numere conţine fişierul NUMERE.TXT.
Dacă această condiţie este îndeplinită, se vor afişa pe ecran, una
sub alta, seriile şi numerele cărţilor de identitate ale persoanelor
respective, între serie şi număr fiind afişat un spaţiu.
Dacă fişierele nu conţin acelaşi număr de linii cu serii şi
respectiv cu numere, se va afişa pe ecran numai mesajul “Eroare”.

211
Exemplu:
Dacă fişierele de date au următorul conţinut,
SERII.TXT NUMERE.TXT
AH 126045
BR 312469
LL 600495
atunci se vor afişa pe ecran liniile
AH 126045
BR 312469
LL 600495
(15p)
2. Se consideră o listă simplu înlănţuită care reţine mai multe numere
naturale de câte două cifre. Să se scrie un subprogram care primeşte ca
parametru adresa de început a listei şi realizează transferul primului
element la sfârşitul listei. Se ştie că adresa transmisă nu poate fi
niciodată adresa nulă (nil/NULL).
Exemplu:
Dacă lista conţine iniţial elementele 2 51 4 7 14 25 69 (în
această ordine), la revenirea din subprogram, conţinutul listei este:
51 4 7 14 25 69 2 (în acestă ordine).
Se vor preciza:
a) tipurile de date şi variabilele globale necesare;
b) definiţia completă a subprogramului;
c) instrucţiunea ce conţine apelul subprogramului.(15p)
IV. Câţiva copii cu vârste între 2 şi 7 ani trebuie să fie vizitaţi de
Moş Crăciun. Scrieţi un program Pascal, C sau C++ care determină
toate modurile diferite în care pot ei să fie aşezaţi în lista lui Moş
Crăciun, astfel încât să fie vizitaţi toţi copiii şi vizitele să se facă în
ordinea crescătoare a vârstei lor.
Se citesc de la tastatură: n, numărul de copii (0<n<10), apoi
numele şi vârsta fiecăruia dintre cei n copii. Se scriu în fişierul text
CRACIUN.TXT, pe linii diferite, liste cu numele copiilor, în ordinea
în care vor fi vizitaţi de Moş Crăciun. O listă este formată din toate
cele n nume ale copiilor, într-o anumită ordine, orice două nume
succesive fiind despărţite prin spaţii.
Explicaţi metoda folosită prin comentarii incluse în program.
Exemplu: Pentru datele de intrare
n=4
şi

212
Dan 6
Cristina 4
Corina 2
Iulia 4
se scriu în fişierui CRACIUN.TXT următoarele soluţii:
Corina Iulia Cristina Dan
Corina Cristina Iulia Dan
(20p)
Test 7 (Bacalaureat-Sesiunea specială iunie 2001; enunţ adaptat)
I. Scrieţi litera corespunzătoare răspunsului corect precizând la fiecare
subpunct care dintre variantele de limbaj a fost considerată (Pascal sau C).
1. Care este cea mai mare valoare pe care o poate avea variabila
întreagă i pentru ca următoarea secvenţă să afişeze textul DA? (5p)
Varianta Pascal Varianta C
if i<5-3*i then writeln('DA') if (i<5-3*i) printf('DA');
else writeln ('NU') else printf('NU');
a)-2 b)-1 c)0 d)1 e)2 f)3 g)4 h)5
2. Se ştie că variabila x va fi utilizată într-un program pentru a
memora cu o precizie de 10 la (puterea) -3 şi pentru a utiliza în calcule
valoarea ‘numărului de aur” (√5-1)/2. Care dintre următoarele decla-
rări este corectă şi corespunde intenţiilor propuse? (5p)
Varianta Pascal Varianta C
a) var x:real; a) int x;
b) var x:integer; b) float x;
c) var x:string; c) char *x;
d) var x:array[1. .10,1. .3] of char; d) char x[10] [3];
3. Stabiliţi care dintre următoarele variante reprezintă un subprogram
corect care returnează programului apelant cea mai mare dintre două
valori reale transmise prin parametrii reali x şi y. (10p)
Varianta Pascal Varianta C
a) function max(x,y:real); a) float max(float x,float y)
begin {
if x>y then max:=x else max:=real max=x>y?x:y;
end; }
b)function max(var x,y:real):real; b) float max(float &x, float &y)
begin max:=x; {if (x>y) return x;
if x<y then max:=y end; else return y;}

213
c)function max(x,y:real):real; c) void max(float x, float y)
begin { int z=x; if (x<y) z=y;
if x>y then z:=x else z:=y return z;
end; }
d)function max(x,y) :real; d) void max(float&x, float&y)
begin max:=x; {
if max>y then max:=y return x>y?y:x;
end; }
II. Fie programul pseudocod alăturat:
citeşte n, m {numere naturale};
cat timp n>=m execută n←n-m
scrie n
1. Ce valoare finală va avea n dacă iniţial: n=38 şi m=4? (5 p)
2. Scrieţi programul Pascal, C corespunzător. (10p)
3.Pentru n = 2071, determinaţi un număr de două cifre care să
reprezinte valoarea variabilei m astfel încăt, în urma executării
programului, rezultatul afişat să fie 0. (5p)
III. 1. Scrieţi un program Pascal, C sau C++ care citeşte de pe prima
linie a fişierului text CUVINTE.TXT un număr natural n (n<20) şi
apoi, de pe următoarele n linii, câte un cuvânt alcătuit numai din litere
(cel mult 30 de litere). Ca urmare a executări programului se va afişa
pe ecran un şir de n+1 caractere format astfel: primul caracter din şir
este prima literă a primului cuvânt din fişier, al doilea caracter din şir
este a doua literă a celui de-al doilea cuvânt din fişier, al treilea
caracter al şirului este a treia literă a celui de-al treilea cuvânt din
fişier etc. Ultimul caracter va fi «.» (punct).
Dacă vreunul dintre cuvinte nu are suficiente litere, şirul rezultat
va conţine pe poziţia corespunzătoare un spaţiu. Concepeţi o prelu-
crare cât mai eficientă din punct de vedere al spaţiului de memorie
utilizat de program.
De exemplu, pentru datele de intrare:
5
ALMI
COCOR
MASA
DO
MARINA
se va afişa pe ecran şirul de caractere
AOS N.
(15 p)
214
2. Scrieţi declarările necesare pentru definirea unei liste simplu-înlăn-
ţuite, ştiind că un element al listei memorează un număr natural de cel
mult 4 cifre. Scrieţi un subprogram care efectuează concatenarea a
două astfel de liste după următorul procedeu: se adaugă lista cu mai
puţine elemente în continuarea listei cu mai multe elemente. Subpro-
gramul primeşte prin doi parametri adresele de început ale celor două
liste şi retumează printr-un al treilea parametru adresa de început a
listei obţinute prin concatenarea celor două liste. Dacă cele două liste
au tot atătea elemente, atunci la sfârşitul listei transmisă prin primul
parametru se va adăuga cea de-a doua listă. (15 p)
IV. Se citesc de la tastatură numerele naturale n şi k (0<n < 10000 şi
0<k < 10) reprezentate în baza 10. Să se afişeze în ordine crescătoare
toate numerele naturale de k cifre cu proprietatea că sunt formate
numai cu cifre ale numărului n. De exemplu, pentru n = 216 şi k = 2,
se vor afişa numerele: 11, 12, 16, 21, 22, 26, 61, 62, 66. (20 p)
Test 8 (Bacalaureat-Sesiunea iunie-iulie 2001; enunţ adaptat)
I. Scrieţi litera corespunzătoare răspunsului corect, precizând la
fiecare subpunct care dintre variantele de limbaj a fost considerată (Pascal
sau C).
1. Care trebuie să fie valoarea iniţială a variabilei întregi i pentru ca
următoarea secvenţă să afişeze şirul XXX?(5p)
Varianta Pascal Varianta C
repeat while (i!=3){
write('XX’); i - -;
i:=i-1 printf ("XX");
until i = 3 }
a)0 b)1 c)2 d)3 e)4 f)5 g)6 h)nu există nici o valoare
2. Ştiind că variabila x este utilizată într-un program pentru a memora
numele unui elev, stabiliţi care este declararea corespunzătoare a
variabilei x? (5p)
Varianta Pascal Varianta C
a) var x:byte; a) int x;
b) var x:array[16]of char; b) char x[0..16];
c) var x:string; c) char *x;
d) var x:record d) struct x{
nume ‚ prenume: boolean int nume, prenume;
end };
3. Stabiliţi care dintre următoarele variante reprezintă antetul corect al
unei funcţii reale cu un parametru întreg.(5p)
215
Varianta Pascal Varianta C
a)function f(x:real); a) void f(float x)
b)function f(x:integer):real; b) int f(f1oat* x)
c)function f(var x:real):real; c) float f(int x)
d)function f(x:real):integer; d) float f(float x)
4. Pentru a atribui variabilei reale x rezultatul expresiei (2ab-
c*c)/0.25‚ unde a, b şi c desemnează variabile reale, se utilizează
instrucţiunea de atribuire: (5p)
Varianta Pascal Varianta C
a) x:=(2*a*b)-(c*c)/0.25 a) x=(2*a*b)-(c*c)/0.25
b) x:=2*a*b-c*c/0.25 b) x=2*a*b-c*c/0.25
c) x:=(2*a*b)-(c*c)*4 c) x=(2*a*b)-(c*c)*4
d) x:=(2*a*b-c*c)*4 d) x=(2*a*b-c*c)*4
II. Se consideră următorul program pseudocod:
citeşte n {număr natural}; m←0
repeta
m←m+1 n←[n/2] (câtul împărţirii întregi a lui n la 2)
până când n < 0
scrie m
1. Ce se va afişa pentru n = 51?(5p)
2. Scrieţi programul Pascal, C sau C++ corespunzător algoritmului dat
(10p)
3. Scrieţi un program echivalent care să utilizeze o structură repetitivă
cu condiţie iniţială.(5p)
4. Câte numere naturale există astfel încât oricare dintre acestea,
introdus ca valoare pentru n, să determine afişarea rezultatului 3?(5p)
III. 1. Scrieţi un program Pascal, C sau C++ care citeşte din fişierul
text BAC.TXT, 100 000 de numere reale scrise pe prima linie a
fişierului, cu spaţii între ele şi, de la tastatură două numere întregi a şi
b. Programul trebuie să stabilească dacă toate elementele din fişier se
află în afara intervalului închis [a,b]. Programul va afişa pe ecran
mesajul DA, în cazul în care toate numerele din fişier se află în afara
intervalului inchis [a,b] sau mesajul NU, în cazul în care există cel
puţin un număr din fişier aflat în intervalul închis [a,b]. (15 p)
2. Construiţi un program care stabileşte în mod eficient de căte ori
apare o cifră nenulă c în scrierea tuturor numerelor naturale mai mici
sau egale cu un număr dat k. Cifra c şi valoarea k se citesc de la
tastatură. De exemplu, pentru c = 6, k = 128, se afişează valoarea 23
(0, 1, ..‚ 5, 6, 7, ...‚16, 26, ...‚ 36, ...‚ 46, ...‚ 56, ...‚ 60, ...‚ 66, ...‚ 69,
...76, ..‚ 86, ...‚ 96, ...‚ 106, 116, ...‚ 126, 127, 128). (10 p)
216
VI. Se citesc de la tastatură: două numere n (0<n<15) şi s (0<s<106)şi
apoi n valori întregi distincte, fiecare valoare aparţinând intervalului [-1000,
1000]. Să se determine toate mulţimile de numere dintre cele date,
fiecare mulţime având proprietatea că suma elementelor ei este egală
cu s. Fiecare mulţime se va afişa pe o linie, elementele ei fiind scrise
în ordine crescătoare, despărţite prin câte un spaţiu sau câte o virgulă.
De exemplu, pentru n = 7, s = 61 şi valorile 12, 61, 22, 57,10, 4, 23, se
vor afişa, pe linii distincte, următoarele mulţimi:
4,12, 22, 23
4, 57
61
(20 p)
Test 9(Bacalaureat-Sesiunea august 2001; enunţ adaptat)
I. Scrieţi litera corespunzătoare răspunsului corect, precizănd la
fiecare subpunct care dintre vanantele de limbaj a fost considerată (Pascal
sau C).
1. Care dintre variabilele întregi x, y şi z au valori egale la sfârşitul
executării următoarei secvenţe de instrucţiuni? (5p)
Varianta Pascal Varianta C
y: =x+1; y = x+l;
z:=y-1; z = y-1;
x:=z+1 x = z+1;
a) numai x şi y b) numai x şi z
c) numai y şi z d) x şi y şi z
e) toate au valori diferite
2. Ştiind că funcţia fmin returnează cea mai mică dintre valorile celor
doi parametri reali ai săi sau retumează valoarea comună în cazul în
care cei doi parametri au aceeaşi valoare, precizaţi care dintre
următoarele instrucţiuni afişează cea mai mică dintre valorile variabilelor
reale a, b şi c.(5p)
Varianta Pascal Varianta C
a) writeln(fmin(a,b,c)) a) printf(”%f”,fmin(a,b,c));
b) writeln(fmin(a,fmin(b,c))) b) printf(”%f”,fmin(a,fmin(b,c)));
c) writeln(fmin(fmin(a,b),c) c) printf(”%f”,fmin(fmin(a,b),c);
d) writeln(fmin(a,b),fmin(b,c)) d) printf(”%f”,fmin(a,b),fmin(b,c));
3. Stabiliţi care dintre următoarele variante reprezintă declararea
corectă a unei variabile de tip tablou cu exact 20 de componente numere
întregi. (5p)

217
Varianta Pascal Varianta C
a) var x:array[1.. 20] of real; a) float x[1. .20];
b) var x:array[0..20] of integer; b) int x[1. .20];
c) var x:array[0..19] of integer; c) int x[0. .19];
d) var x:array[20] of integer; d) int x[20];
e) var x:array[19] of integer; c) int x[19];
4. Ce valoare (în format exponenţial) se afişează ca urmare a executări
instrucţiunii următoare? (5p)
Varianta Pascal Varianta C
writeln(48.0—(32/(2/2)+1)) printf("%f",48.0—(32/(2/2)+1))
a) 0 b) 5 c) 8 d) 15 e) 17 f) 24 g) 32 h) 39 i) 41 j) altă valoare
II. Se consideră următorul program pseudocod:
citeşte n (număr naturai)
m←0
citeşte V1 (număr natural)
pentru i = 2, n execută
citeşte Vi (număr natural)
dacă Vi = Vi-1 atunci m←m+1
scrie m
1. Ce se va afişa pentru n=5 şi V1=5, V2=3, V3=3, V4=8, V6=8? (5p)
2. Scrieţi programul Pascal, C sau C++ corespunzător algoritmului dat
(10p)
3. Pentru n=4, determinaţi un set de valori introduse pentru V1, V2, V3
şi V4 astfel încât rezultatul afişat de algoritmul dat să fie 3. (5p)
4. Scrieţi un program pseudocod, Pascal, C sau C++ care să fie
echivalent cu programul dat şi care să nu utilizeze variabile structurate
(tablouri, liste, fişiere etc.) sau adrese de variabile structurate.(5p)
III.1. Fişierul text BAC1.TXT conţine 70000 de numere întregi de cel
mult 3 cifre fiecare scrise cu spaţii între ele. Scrieţi un program Pascal,
C sau C++ care creează fişierul text BAC2.TXT care să conţină numai
valorile strict pozitive din fişierul BAC1 .TXT, exact în aceeaşi ordine
în care se aflau acestea în fişier.
De exemplu, dacă fişierul BAC1 .TXT are următorul conţinut: 6
-9 11 8 -5 -7 8 4 11 0 2 3 -6 4 -8 .... -8 de 69 987 de
ori fişierul BAC2.TXT creat de program trebuie să aibă următorul
conţinut: 6 11 8 8 4 11 23 4. (l0p)
2. Construiţi un algoritm eficient care determină toate perechile de
numere naturale a, b, cu a < b, numerele ce formează o pereche având
proprietatea că nu au nici o cifră comună şi suma lor este egală cu S.
218
Valoarea S este un număr natural citit de la tastatură (S<100 000 000).
Fiecare pereche se va scrie pe un rănd al ecranului, cu un spaţiu între
elementele ce compun perechea.
De exemplu, pentru S16, se vor afişa (nu neapărat în această
ordine) perechile:
2 14 '\n' 6 10 '\n' 4 12 '\n'
5 11 '\n' 0 16 '\n' 7 9
(15 p)
3. Transformaţi următoarea funcţie într-o funcţie recursivă care să nu
utilizeze nici o structură repetitivă şi să retumeze acelaşi rezultat ca şi
funcţia dată pentru orice valoare nenulă a parametrului i. Scrieţi
versiunea modificată a acesteia.(5p)
Test 10 (Bacalaureat-Sesiunea iunie2002; enunţ adaptat)
I. Scrieţi litera corespunzătoare răspunsului corect, precizând la
fiecare subpunct care dintre variantele de limbaj a fost considerată
(Pascal sau C/C++).
1. Ştiind că valoarea iniţială a variabilei întregi i este mai mare decât
10, stabiliţi care este valoarea expresiei abs(3-i) la sfârşitul executării
următoarei instrucţiuni.(5p)
Varianta Pascal Varianta C
while i>4 do i:=i-1 while (i>4) i--;
a)-1 b) 0 c) 1 d) 2
e) o valoare mai mare decât 2
f) o valoare nedeterminată
2. Ştiind că variabila x este utilizată într-un program pentru a memora
şi utiliza în alte calcule rezultatul expresiei 1*2*3...*10, stabiliţi care
dintre următoarele declarări este corectă din punct de vedere sintactic
şi corespunde scopului propus. (5p)
Varianta Pascal Varianta C
a) var x:string; a) char *x;
b) var x:integer; b) int x;
c) var x:longint; c) long x;
d) var x:byte; d) char x;
3. Ştiind că formula de calcul pentru aria coroanei circulare este π(R12-
R22), unde R1 este variabilă reală strict pozitivă reprezentând raza
exterioară, iar R2 este variabila reală pozitivă reprezentând raza
interioară a coroanei, precizaţi care dintre următoarele instrucţiuni
atribuie variabilei reale S valoarea coroanei circulare.(5p.)
219
Varianta Pascal Varianta C
a) S:=pi*(sqr(R1)-sqr(r2)) a)S=m_pi*(pow(R1,2)+pow(R2,2));
b) S:=pi*(R1-R2)(R1+R2) b)S=m_pi*(R1-R2)(R1+R2);
c) S:=pi*R1*R1-R2*R2; c)S=m_pi*(R1*R1-R2*R2);
d) S:=pi*(R1*R1+R2*R2) d)S=m_pi*R1*R1-R2*R2;
4. Ştiind că x, y şi z reprezintă trei variabile întregi, stabiliţi care dintre
următoarele secvenţe de instrucţiuni este corectă din punct de vedere
sintactic.(5p.)
Varianta Pascal Varianta C
a)x:=14;y:=x-7;z:=x/y a) x=14;y=x-7;z=x/y;
b)x:=14;if(x<10) then y:=x-1 b) x=14;if x<10 y=x-1;
c)x:=y:=14;z:=x+y; c) x=y=14;y=x++y;
d)x:=14;if 0<x<100 then y:=x+1 d) x=14;if 0<x<100 y=x+1;
II. Se consideră programul pseudocod:
citeşte a,b (numere întregi)
dacă a<0 atunci a←-a
dacă b<0 atunci b←-b
h←0
cât timp a≥b execută
h←h+1
a←a-b
scrie h
1. Ce se afişează dacă valorile citite pentru a şi b sunt 6 şi, respectiv 2?
(5p.)
2. Scrieţi programul Pascal, C sau C++ corespunzător algoritmului dat
(10p.)
3. Determinaţi o valoare pentru variabila a şi o valoare pentru b, astfel
încât rezultatul afişat să fie 1.(5p.)
4. Scrieţi un algoritm echivalent cu cel dat (la aceleaşi date de intrare
să furnizeze acelaşi rezultat), algoritm care să nu utilizeze nici o structu-
ră repetitivă. (5p.)
III. 1. Se citesc de la tastatură trei numere naturale f, a şi b, fiecare
număr având cel mult trei cifre. Să se afişeze o "tablă" cu toate
înmulţirile de doi factori naturali dintre care unul este obligatoriu f şi
care dau ca rezultate numai numere cuprinse între a şi b inclusiv.
Înmulţirile vor fi afişate câte una pe linie, în ordinea crescătoare a
rezultatelor. De exemplu, pentru f=5, a=8 şi b=25 se va afişa “tabla
înmulţirilor cu 5”astfel: 2*5=10; 3*5=15; 4*5=20; 5*5=25 (5p.)

220
2. Scrieţi un program prin care se citeşte de la tastatură o valoare
naturală n (0<n<200) şi apoi se citesc cele n componente numere
întregi ale vectorului v, orice element al vectorului având cel mult 4
cifre. Să se realizeze sortarea crescătoare a elementelor pare ale
vectorului având grijă în plus ca fiecare element impar să rămână
exact pe poziţia pe care se afla iniţial.
Vectorul se va afişa pe ecran cu spaţii între elementele ce-l
formează. Pentru n=7 şi vectorul v=(10,2,5,11,6,5,8) se va afişa şirul
de valori: 2 6 5 11 8 5 10. Se observă că elementele impare şi-au
păstrat locul, în timp ce elementele pare (10,2,6,8) se află acum în ordine
crescătoare (2,6,8,10).
Alegeţi un algoritm de rezolvare care să utilizeze eficient memo-
ria internă.(10p.)
3. Scrieţi un subprogram care primeşte prin primul parametru a o
valoare naturală (1< a < 1000) şi returnează prin al doilea parametru b
valoarea reală reprezentând inversul numărului a cu “două zecimale
importante” exacte, următoarele zecimale fiind 0.
Numim “două zecimale importante” prima pereche de cifre zeci-
male succesive (pornind de la virgula zecimală către dreapta), astfel
încât prima cifră să fie nenulă.
De exemplu, pentru a = 2 se va returna b = 0.5 (cele două
zecimale importante fiind 5 şi 0); pentru a =14 se va returna b=0.071
(cele două zecimale importante fiind 8 şi 1), iar pentru a = 121 se va
returna b=0.0082 (cele două zecimale importante fiind 8 şi 2) (10p.)
IV. Se citesc de la tastatură un număr natural s(0 < s < 5000) reprezen-
tând o sumă de bani exprimată în mii de lei. Să se determine un mod
de plată a sumei s ştiind că avem la dispoziţie 9 monede de o mie, 8
monede de 5 mii, 5 de bacnote de 10 mii, 8 bacnote de 50 de mii şi 45
de bacnote de 100 de mii de lei. Alegeţi o metodă eficientă de rezolva-
re şi scrieţi programul Pascal, C sau C++ corespunzător. Se va afişa pe
ecran modalitatea de plată în formatul sugerat prin exemplul următor
sau mesajul IMPOSIBIL dacă nu este posibilă plata sumei cu
monedele disponibile.
Pentru s=3076 (3076 mii lei) se poate afişa soluţia:
3076=30x100+1x50+2x10+6x1
Se observă că există şi alte posibilităţi de plată, însă problema solicită
determinarea doar a uneia dintre posibilităţi. Explicaţi pe scurt şi
justificaţi algoritmul folosit (maximum 4 rânduri). (20p.)

221
6.2. Probleme pentru concursuri

1. [Număr deosebit - Problema E25 1 ] Se ştie că m este un număr


deosebit dacă există n număr natural nenul astfel ca m = n+s(n), unde
s(n) reprezintă suma cifrelor numărului n. O persoană doreşte să ştie
dacă vârsta sa (în zile) este un număr deosebit. Scrieţi un program
care solicită data naşterii, află vârsta în zile şi afişează răspunsul sub
forma: "Da" (resp. "Nu") dacă vârsta reprezintă un număr deosebit
(resp. vârsta nu este un număr deosebit).
2. [Manipulator - Olimpiada de Informatică, Faza pe municipiu, 5
III 1995, Bucureşti, Clasa a IX-a] Pe o platformă industrială se
află n depozite D1, D2, …, Dm (n: 2..100), fiecare depozit având
capacitatea m (m:1..50) şi un manipulator cu capacitatea maximă
p (p: 2..200). Modul de aşezare a pieselor în timpul transportului
nu este important. Ştiind că:
• Iniţial, în fiecare din cele n depozite se află m piese aranjate
oricum, fiecare piesă fiind marcată cu indicele depozitului
specializat în păstrarea sa.
• Manipulatorul se poate deplasa, înainte şi înapoi, numai între
depozite cu numere de ordine succesive (de la Di la Di+1 şi de
la Di+1 la Di, i = 1, 2, …, n-1.)
• Iniţial manipulatorul încarcă piese din depozitul D1, iar după
efectuarea tuturor mişcărilor rămâne în dreptul depozitului D1. )
• Deplasarea manipulatorului de la un depozit la altul necesită t
unităţi de timp (t număr natural nenul).
• Timpul necesar pentru încărcarea/descărcarea pieselor, nu se
ia în considerare.
Să se elaboreze un program care indică mişcările ce trebuie să le
efectueze manipulatorul pentru a realiza aranjarea tuturor pieselor
în timp minim.
3. [Secvenţă de numere prime] Scieţi un program care citeşte numerele
naturale n1, n2, …, np şi tipăreşte cea mai lungă secvenţă ns, ns+1, …,
nd (cu s şi d în domeniul 1..p, s≤d) care conţine numai numere prime.
Să se proiecteze aplicaţia astfel încăt să utilizeze subprograme.
4. [Becuri - ONI 2002, Brăila, Clasa a IX-a - enunţ parţial
modificat] Un panou publicitar, de formă dreptunghiulară conţine
becuri, unul lângă altul. Aliniate pe linii şi coloane. Fiecare linie şi

1
R. Niculescu, G. Albeanu, V. Domocoş; Programarea calculatoarelor.
Probleme rezolvate în limbajul Pascal, Ed. Tempus, 1992.
222
fiecare coloană are un comutator care schimbă starea tuturor
becurilor de pe aceea linie sau coloana, din starea curentă în starea
opusă. Iniţial panoul are toate becurile stinse. Să se realizeze un
program care, acţionând asupra unui număr minim de linii şi
coloane, aduce panoul din starea iniţială, la o configuraţie dată,
dacă acest lucru e posibil.
5. [Codificare - ONI 2002, Brăila, Clasa a IX-a - enunţ parţial
modificat] Se consideră cuvintele formate numai cu literele mici
ale alfabetului englez. Dintre toate aceste cuvinte se consideră
numai cele ale căror caractere sunt în ordine strict lexicografică şi
se realizează următorul sistem de codificare:
• Se ordonează cuvintele în ordinea crescătoare a lungimii
acestora.
• Cuvintele de aceeaşi lungime se ordonează lexicografic.
• Cuvintele sunt codificate prin numerotarea lor (în şirul obţinut
după ordonare): 1 - a; 2 - b; …, 26 - z; 27 - ab, …, 51 - az; 52
- bc, …, 83681 - vwxyz, ….
Dându-se un cuvânt, să se precizeze, prin intermediul unui
program Pascal, c sau C++, dacă poate fi codificat conform
sistemului descris. În caz afirmativ să se precizeze codul său.
6. [Structură arborescentă - Problema S261] Într-un graf G cu n
noduri şi A o mulţime de muchii, se alege un nod, numit nodul
central al grafului. Fiecare muchie are stabilit un anumit cost
(număr real strict pozitiv). Se cere determinarea unui subarbore al
grafului G astfel încât drumurile de la nodul central la toate
celelalte noduri să aibă lungime minimă.
7. [Pavaj - Problema S271] Se consideră un dreptunghi de dimensiuni
întregi m şi n. Având la dispoziţie dale dreptunghiulare de dimensiuni
întregi pxq să se paveze în întregime dreptunghiul dat astfel încât
laturile dalelor să nu formeze nici o linie paralelă şi egală cu una din
laturile dreptunghiului.
8. [Labirint - Problema S291] Se consideră un labirint de formă
dreptunghiulară, având pereţi verticali şi orizontali. Să se găsească
drumul cel mai scurt dintre două puncte date.
9. [Cititori prieteni - Problema S301] Se consideră un grup de n
persoane. Fiecare persoană are cel puţin n/2 prieteni în grup. Una
dintre persoane are o carte pe care fiecare doreşte să o citească. Se
cere să se determine o modalitate prin care cartea să circule pe la
fiecare persoană exact o dată, transmiterea ei efectuându-se numai
între doi prieteni, iar în final cartea să ajungă la proprietarul ei.

223
Bibliografie

1. Albeanu G., Luminţa Radu - Algoritmică şi programare în limbajul


Pascal, Editura FRM, Bucureşti, 2001.
2. Albeanu G. - Algoritmi şi limbaje de programare, Editura FRM,
Bucureşti, 2000.
3. Albeanu G. - Programarea în Pascal şi Turbo Pascal. Culegere de
probleme. Editura Tehnică, Bucureşti, 1994.
4. Atanasiu A. - Concursuri de informatică. Probleme propuse, Editura Petrion, 1995.
5. Cormen T. H., Charles E. Leiserson, Rivest R. R. - Introducere în
algoritmi, Ed. Computer Press Agora, 2000.
6. Cristea V., Irina Athanasiu, Eugenia Kalisz, Iorga V. - Tehnici de
Programare, Editura Teora, 1999
7. Eckel B. - Thinking in C++, Prentice Hall Inc., 2000
8. Ivaşc Cornelia, Mona Prună, Emanuela Mateescu - Bazele Infor-
maticii (Grafuri şi elemente de combinatorică), Editura Petrion, 1997.
9. Jamsa K., Klander L. – Totul despre C şi C++, Ed. Teora, 2000.
10. Mocanu M. , Ghoerghe Marian, Costin Bădică, Carmen Bădică – 333
probleme de programare, Editura Teora 1994.
11. Namir C. Shammas – Curs rapid de Borland C++ 4, Editura Teora,
Bucureşti, 1996.
12. Niculescu R., Albeanu G., Domocoş V. - Programarea calculatoarelor.
Probleme rezolvate în limbajul Pascal, Editura Tempus, Bucureşti, 1992.
13. Popovici D. M., Popovici I. M., Tănase I. – Tehnologia orientată pe
obiecte. Aplicaţii, Editura Teora, 1996.
14. Stroustrup B.– The C++ Programming Language, Third Edition,
Addison Wesley Longman, Inc., 1997.
15. Tudor S. – Tehnici de programare, Ed. L&S INFOMAT, 1996.
16. *** Gazeta de Informatică

224

You might also like