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ţă ……………………………………………………………... 1. Operaţii de intrare/ieşire în limbajele Pascal şi C …………… 1.1. Fişiere în limbajul Pascal …………………………………... 1.2. Fişiere în limbajul C …………...…………………………... 2. Structuri de date ……………………………………………….. 2.1. Liste simplu înlănţuite ……………………………………... 2.2. Liste dublu înlănţuite ………………………………………. 2.3. Liste circulare ………………………………….…………... 2.4. Stive …………………………………...…………………… 2.5. Cozi …………………………………...……………………. 2.6. Grafuri …………………………………...…………………. 2.7. Arbori …………………………………...………………….. 3. Metode pentru rezolvarea problemelor ……………….……... 3.1. Metoda Divide et Impera 3.2. Metoda programării dinamice 3.3. Metoda Greedy 3.4. Metoda backtracking 3.5. Metoda Branch and Bound 4. Programare modulară în Turbo Pascal ……………….…….. 5. Introducere în programarea orientată obiect folosind limbajul C++ …………………………………………………… 6.Teste pentru verificarea cunoştinţelor ………………………... 6.1. Teoria şi practica programării ………………………….…... 6.2. Probleme pentru concursuri ………………………………... Bibliografie …………………………………...……………………

5 7 7 17 31 32 46 50 53 55 62 87 101 101 107 116 125 135 145 173 201 201 222 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ă specializă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 componente, numite înregistrări, de dimensiune fixă sau variabilă, sunt stocate (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 capacitatea 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 componentelor 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ă Efect Apelul Procedure assign (var fişier;nume:string); I se asociază variabilei de tip fişier din program, numele fişierului fizic de pe suportul extern . 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 închiderea 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ă Crina are calculator

Ieşire: Fişierul are 6 cuvinte.

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: linia 1: 10 20 30 linia 2: 40 50 Fişiere cu tip

Ieşire: din linia 1 s-au citit 3 numere din linia 2 s-au citit 2 numere.

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 OF IDENTIFICATOR DE TIP

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ă: 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 cod_mat:string[5]; den_m:string[20]; cod_mag:integer; cant:real; um :string[4]; pu:real;

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 gestionate 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 înregistră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 Returnează Int fseek(FILE *pf, long deplasament, int origine); 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 1 2 Deplasamentul se consideră de la începutul fişierului; Deplasamentul se consideră din poziţia curentă a capului de citire/scriere; 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 Returnează Long ftell(FILE *pf); O valoare de tip long care defineşte poziţia curentă a capului de citire/scriere, şi anume reprezintă deplasamentul î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 Pascal program medie; var x,s,i:integer; f,g:text; begin assign(f,'IN.TXT'); RESET(f); assign(g,'out.TXT'); REWRITE(g); while not eof(f) do begin s:=0; i:=0; repeat read(f,x); i:=i+1; s:=s+x; until eoln(f); writeln(g,s/i:4:2); writeln(s/i:4:2); readln(f); end; close(f); close(g); end.
Varianta C #include <stdio.h> #include <string.h> void main (void){FILE *fin, *fout; char sir[256],sn[256]; long int n,i,j1,j2,suma; float media; if (!(fin=fopen("IN.txt","r"))){ printf("\n Nu pot deschide sursa!"); exit(1);} if (!(fout=fopen("OUT.TXT","w"))){ printf("\n Nu pot crea fisierul destinatie!"); exit(1);} while (!feof(fin)){ fgets(sir,256,fin); n=0;i=0;j1=0;j2=0;suma=0; while(sir[i]){ if (sir[i]=' ') {sn=substr(sir,j1,j2); suma=suma+atoi(sn); j1=j2=i+1;sn=' ';i++; } else {i++; j2++;}} media=suma/n; fprintf(fout, "%10.2f\n",media); } 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 exit(1); (not eof(f)) } and if((f2=fopen("Input2.txt","r")) (not eof(g)) ==NULL) and (identic) { do printf("\n Nu pot deschide fis2!"); Begin exit(1); readln(f,s); } readln(g,s1); ind=0; If s<>s1 then identic:=false; 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; uses crt; var f,f1:text;i:integer; s,s1:string; begin clrscr; assign(f,'in.txt'); reset(f); assign(f1,'in1.txt'); rewrite(f1); while not eof(f) do begin readln(f,s); s1:=''; for i:=1 to length(s) do s1:=s1+upcase(s[i]); writeln(f1,s1); end; close(f);close(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; uses crt;type mat = array[1..10,1..10] of integer; 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

close(g); readln; end.

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 descompunerea 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 Ce faci… Unde esti O sa reusim… buna dimineata Alo, este ora sapte si treizeci de minute, fix. se va afisa Alo Este Ora Sapte 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

type lista

= ^nod nod=record inf:tip ; urm: lista; end;

Limbajul C(++) typedef struct nod { tip inf; struct nod *urm; }lista;

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

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, prelucră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: lista* invers(lista*p){ var p1,p2,p3:lista; lista *p1,*p2,*p3; begin 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

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

if a<>nil then c^.urm:=a; if b<>nil then c^.urm:=b;

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 type lista=^nod; nod=record inf:tip; urm, ant:lista; end; typedef struct nod{ inf tip; struct nod *urm; struct nod *ant; }lista;

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

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

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

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.

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

Rezolvare: Implementarea Pascal:

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 7 5 2 6 0 1 3 4

0 1 2 3 4 5 6 7 8 0 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 2 0 0 0 0 0 0 1 0 0 3 0 0 0 0 1 0 0 0 0 4 0 0 0 0 0 1 0 0 0 5 0 0 0 1 0 0 0 1 0 6 0 0 0 0 0 0 0 0 0 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 fundamentali 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 7 5 2 6 0 1 3 4

0 1 2 3 4 5 6 7 8

2 6 6 4 5 3

5

7

2

8

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 Cel mai cunoscut mod de imple3 mentare a parcurgerii DF se realizează cu ajutorul unei funcţii recursive, dar există şi cazuri în care este recomandată 4 o implementare nerecursivă. 5 Implementarea acestei metode se 6 face folosind o stivă. Aceasta este iniţia65

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 nodurilor 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

} 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

} 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;

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;

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 drumurilor 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 subgraful 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

end.

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;

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 adiacenţă 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ţimea 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

end.

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

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

end; begin ciclic end.

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);

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

end.

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);

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 (valoarea 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 subexpresia 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 descendentul 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; nod=record inf:integer; st,dr:arbore; end; typedef struct nod{ int inf; struct nod *st; struct nod *dr; }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 pozitiv. 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 modalitate 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 reprezentat 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ânduse 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

} 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;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++; }

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

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

}

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 determine 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] reprezintă 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 minimizează 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 cuvinte 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, fiecare linie având cel mult m caractere astfel: dacă o linie conţine cuvintele 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 suplimentare 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] reprezintă 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ă ierarhică, 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ă. Şoricelul 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 maximă 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 planificate 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

end.

if t = n5 then begin inc(k); n5 := 5*f[k] end end; for i:=1 to n do write(f[i],' '); readln;

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ă determine 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ă comisvoiajorul ş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 procedură (î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 configuraţie iniţială şi se reţine şirul de operaţii prin care aceasta este transformată î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 succesorii) 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 configuraţ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 capacitate 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 program 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

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 configuraţ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

end;

profitmax := nodc^.profit; masatotala := nodc^.masa; while nodc^.tata<>nil do begin solutie[nodc^.nivel]:=nodc^.luat; nodc:=nodc^.tata; end

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ă întrun 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 1 2 3 4 5 6 7 B O O O C O O O D O O X X X X X X X X E F G

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 folosesc 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 monitorului; 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 declarate î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 descompune 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: ⎛ u1,1 u1, 2 L u1,n ⎞ 0 M 0 0⎞ ⎛ 1 ⎜ ⎟ ⎟ ⎜ M 0 0 ⎟ , U = ⎜ 0 u 2, 2 L u 2,n ⎟ şi A =LU. ⎜ l 2,1 1 L= ⎜L L O L ⎟ ⎜L L O 1 0⎟ ⎜ ⎟ ⎟ ⎜ ⎜ 0 ⎟ ⎜l 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: ui,j = ai,j -

∑l
k =1

i −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: li,j = (ai , j −

∑l
k =1

j −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 ridicată. 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

end.

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

R4.2. [LLT] O matrice cu numere reale, simetrică este pozitiv definită, dacă se poate scrie ca produsul dintre matricea inferior triunghiulară 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 matrice inferior triunghiulară L, astfel încât A=LLT. Elementele matricei L se obţin prin scrierea egalităţii : a1,1 a1, 2 L a1,n ⎞ ⎛ l1,1 0 L 0 ⎞ ⎛ l1,1 l 2,1 L l n ,1 ⎞ ⎛ ⎟ ⎜ ⎟ ⎜ ⎟⎜ ⎜ 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 O M ⎟ ⎜ M M O M ⎟⎜ 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

n ⎛ ⎞ l j , j = ⎜ a j , j − ∑ l 2,k ⎟ , j>1; j k =1 ⎝ ⎠ j −1 ⎞ ⎛ li , j = ⎜ ai , j − ∑ l i ,k l j ,k ⎟ l j , j , i>j>1. ⎟ ⎜ k =1 ⎠ ⎝

12

Procedeul se blochează când există 1 ≤ j ≤ n astfel încât rezultatul 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 GaussSeidel pentru a rezolva un sistem de ecuaţii liniare (numărul ecuaţiilor fiind egal cu numărul necunoscutelor). Rezolvare: Considerăm sistemul Ax=b, unde A este o matrice pătratică, de dimensiune 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 sistemului, 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 ⎛ xi( m +1) = ⎜ bi − ∑ ai , j x (jm +1) − ⎜ j =1 ⎝

j =i +1

∑a

n

i, j

⎞ x (jm ) ⎟ ai ,i , i=1, 2, .., n; m ≥ 0. ⎟ ⎠

Cele două metode converg în cazul unui sistem liniar cu matrice diagonal 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 suma pătratelor erorilor:

S = ∑ ( y i − f ( xi ) ) .
2 i =1

n

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 trebuie rezolvat este următorul:
n n ⎧ na + b∑ xi = ∑ y i ; ⎪ ⎪ i =1 i =1 ⎨ n n n 2 ⎪a ∑ x i + b ∑ x i = ∑ x i y i . ⎪ i =1 i =1 i =1 ⎩

Determinarea parametrilor modelului putere se bazează pe rezolvarea următorului sistem:
n n ⎧ n ln a + b∑ ln xi = ∑ ln y i ; ⎪ ⎪ i =1 i =1 ⎨ n n n 2 ⎪ln a ∑ ln xi + b∑ ln 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∑ xi = ∑ ln y i ; ⎪ ⎪ i =1 i =1 ⎨ 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 ⎧ c0 + c1 ∑ xi + L + c k ∑ xik = ∑ y i ; ⎪ i =1 i =1 i =1 ⎪ n n n n k +1 2 ⎪ ⎪ c0 ∑ xi + c1 ∑ xi + L c k ∑ xi = ∑ 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 n 1 ⎧ an + b∑ = ∑ y i ; ⎪ ⎪ i =1 x i i =1 ⎨ n 1 n n y ⎪a ∑ + b ∑ 1 = ∑ i . 2 ⎪ i =1 xi 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 considerate. 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 programatorului 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 unitatea 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 implementate operaţii precum: adunare, scădere, împărţire întreagă, determinarea 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 procedurală 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 reutilizarea 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 programului. 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 analiza, 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ă reprezentarea (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 implementării faţă de exterior. Separarea interfeţei de reprezentarea unui obiect şi de implementarea metodelor sale permite modificarea structurii obiectului şi a metodelor fără a afecta în nici un fel programul care foloseşte obiectul, î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 directivele "#include". Referitor la modularizare, cititorul poate observa asemănări şi deosebiri ale modularizării la nivelul C, C++, în raport cu modularizarea 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 caracteristicile 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 Int x = 33; float f = 1.5; Printf("x = %d f = %f", x, f); Printf("\n"); Printf("%c", 'C'); Printf("%s", "Limbajul C\n"); Int x; scanf("%d", &x); Float f; scanf("%f" &f); Char ch; scanf("%c", &ch); Char sir[25]; scanf("%s", sir); În limbajul C++ int x = 33; float f = 1.5; cout << "x=" << x <<"f ="<<f ; cout << endl; cout << 'C'; cout<<"Limbajul C++"<< endl; int x; cin >> x; float f; cin >> f; char ch; cin >> ch; 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 recomandă 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 observa î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 implementată 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ă destructorul 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 operaţ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ştenire 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 constructor 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ă parametrii, 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

Public În clasa de bază Public Protected Private public în clasa derivată Protected în clasa derivata ascuns în clasa derivată

Tipul moştenirii Protected protected în clasa derivată protected în clasa derivată ascuns în clasa derivată

private private în clasa derivată private în clasa derivată ascuns în 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 compilării care variantă a funcţiei Area() să fie asociată unui apel prin pointeri 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 implementează 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. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.

15.

Să se proiecteze o clasă pentru implementarea structurii de date: listă liniară simplu înlănţuită. Să se proiecteze o clasă pentru implementarea structurii de date: listă liniară dublu înlănţuită. Să se proiecteze o clasă pentru implementarea structurii de date: listă circulară simplu înlănţuită. Să se proiecteze o clasă pentru implementarea structurii de date: arbore binar. Să se proiecteze o clasă pentru implementarea structurii de date: graf orientat. Să se proiecteze o clasă pentru implementarea structurii de date: graf neorientat. Să se proiecteze o clasă pentru implementarea structurii de date: vector de liste liniare simplu înlănţuite. Să se proiecteze o clasă pentru implementarea structurii de date: vector de liste liniare dublu înlănţuite. Să se proiecteze o clasă pentru implementarea structurii de date: vector de liste circulare simplu înlănţuite. Să se proiecteze o clasă pentru implementarea structurii de date: vector de arbori binari. Să se proiecteze o clasă pentru implementarea structurii de date: vector de grafuri orientate. Să se proiecteze o clasă pentru implementarea structurii de date: vector de grafuri neorientate. Proiectaţi o clasă pentru manipularea matricelor pătratice. Includeţi metode pentru operaţiile de transpunere, schimbare a semnului elementelor, înmulţirea unei matrice cu un număr etc. Proiectaţi o clasă care să reprezinte noţiunea de şir de caractere. Consideraţi metode pentru implementarea operaţiilor de intrareieş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. 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 subprogramul 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 subprogram 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 programului, î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 numar_1(n:Longint):integer; begin numar_1:= trunc(ln(n)/ln(10))+1 end; (5 puncte) function numar_2 (n:Longint):integer; var s: string[20] begin str(n,s);numar_2:=length(s); end; (5 puncte) function numar_3(n:Longint):Integer; var nr:integer; begin nr:=0; while n>0 do begin nr:=nr+1; n:=n div 10 end; numar_3:=nr end; (5 puncte)

Varianta C: int numar_1 (long n){ return floor (log(n)/log(10)+1; } (5 puncte) int numar_2 (long n){ char s[20]; ltoa(n,s,10); return strlen(s); } (5 puncte) int numar_3 (long n){ int nr=0; while (n) { nr++; n/=10; } return nr; } (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ăspunsurilor 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 componentele 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 proprietatea 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ăspunsurilor 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 valorile 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 identitate 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 declară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 prelucrare 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. Subprogramul 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 (2abc*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 π(R12R22), 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 structură 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 memoria 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 zecimale 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) reprezentâ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 rezolvare ş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

5.

6.

7.

8. 9.

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. [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. [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ă. [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. [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. [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. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.

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

224

Sign up to vote on this title
UsefulNot useful