Algoritmi şi structuri de date

MINISTERUL EDUCAŢIEI ŞI CERCETĂRII UNIVERSITATEA “1 DECEMBRIE 1918”

Lect. dr. Corina Rotar

ALGORITMI ŞI STRUCTURI DE DATE

Alba Iulia 2008

1

CUPRINS

I. Algoritmi şi programare. Descrierea algoritmilor.......................................2 II. Elementele limbajului de programare C..................................................15 III. Functii. Transmiterea parametrilor. Recursivitate..................................34 IV. Tablouri. Tehnici de sortare...................................................................43 V. Structuri. Tipuri de date definite de utilizator.........................................60 VI. Lucrul cu fişiere......................................................................................70 VII. Alocarea dinamica a memoriei.............................................................78 VIII. Listă simplu înlănţuită.........................................................................81 IX. Lista dublu înlănţuită.............................................................................91 X. Liste circulare. Stive. Cozi.....................................................................100 XI. Arbori...................................................................................................110 XII. Elemente de Grafuri. Algoritmi..........................................................127 XIII. Metode de elaborare a algoritmilor. Divide et Impera......................132 XIV. Metode de elaborare a algoritmilor.Greedy.......................................143 XV. Metode de elaborare a algoritmilor. Backtracking. ...........................148 XVI. Metode de elaborare a algoritmilor.Programare dinamica................158

Algoritmi şi structuri de date

I.

ALGORITMI ŞI PROGRAMARE. DESCRIEREA ALGORITMILOR

1. Programare. Etapele programării. Programarea reprezintă activitatea complexă de elaborare a programelor. Programarea nu se referă strict la scrierea codului sursă (descrierea în limbaj de programare a rezolvării problemei); această activitate implică parcurgerea mai multor etape: a. Analiza problemei. Problemele reale cu care ne confruntăm nu sunt formulate întotdeauna într-un stil clar, precis. De cele mai multe ori, problemele sunt formulate incomplet, ambiguu, lăsând programatorului sarcina de a determina corect: ce se cunoaşte din problemă şi rezultatele cerute. Etapa de analiză a problemei se finalizează prin identificarea celor două elemente esenţiale ale formulării unei probleme: datele de intrare şi datele de ieşire. b. Proiectarea algoritmului poate fi considerată etapa de creativitate a programării, în care folosindu-se de cunoştinţele şi experienţa dobândite, programatorul va identifica metoda de rezolvare a problemei date şi va dezvolta algoritmul corespunzător. Finalitatea acestei etape o constituie un algoritm descris clar într-una dintre variantele de reprezentare algoritmică (scheme logice, pseudocod, sau chiar limbaj de programare – în cazul în care problema este de dificultate mică). c. Traducerea în limbaj de programare (implementarea): este etapa în care se va efectua o “traducere” în limbaj de programare a descrierii algoritmului rezultat în etapa de proiectare. Cunoaşterea în detaliu a regulilor sintactice ale limbajului ales, experienţa şi stilul programatorului sunt ingredientele necesare şi suficiente ale acestei etape. d. Traducerea în cod maşină, execuţia şi testarea. Traducerea în cod maşină se realizează automat cu ajutorul a două componente ale mediului de programare: compilatorului şi a editorul de legături. Testarea programului constă în execuţia sa repetată pentru o mulţime de date de intrare (date de test) şi verificarea corectitudinii rezultatelor oferite. Rezultatele incorecte ne semnalează o eroare logică de proiectare şi necesită o revizuire a algoritmului şi re-parcurgerea etapelor programării. e. Întreţinerea este ultima etapă a programării şi constă din acţiuni de actualizare a produsului final şi de asistenţă oferită beneficiarului. Dintre toate aceste etape, cea care este mai solicitantă este etapa de proiectare. Practic, parcurgerea corectă a acestei etape va face diferenţa între programatori şi amatori. Esenţa programării constă în capacitatea programatorului de a elabora şi descrie clar metoda de rezolvare a problemei. Orice eroare apărută la nivelul etapei 2

surprinzând proprietăţile obligatorii pe care aceştia trebuie să le îndeplinească. 300 î. dispuşi într-o ordine stabilită.acţiunile algoritmului trebuie să fie clare. Claritate . Generalitate . Regulile prin care algoritmul exprimă maniera de rezolvare a problemei sunt de 4 tipuri: 1. alcătuită dintr-o mulţime de paşi. Definirea algoritmilor Aşa cum am subliniat mai sus. proprietăţile pe care trebuie să le îndeplinească acesta şi modalităţile de reprezentare standardizată a algoritmilor fac subiectul paragrafelor următoare. Algoritmii informatici.Algoritmi şi structuri de date de proiectare a algoritmului va avea repercusiuni asupra produsului final. În limbajul uzual. Definiţie: Un Algoritm (informatic) reprezintă o secvenţă finită şi ordonată de reguli clare.algoritmul trebuie să fie eficient privind resursele utilizate. algoritmul de determinare a celui mai mare divizor comun a două numere (algoritmul lui Euclid) este considerat ca primul algoritm din istorie. 2. Finitudine . prin algoritm se înţelege o metodă de rezolvare a unei probleme.. etapa de proiectare a algoritmilor este cea mai complexă dintre cele etapele enumerate ale programării. reguli de intrare prin care se realizează introducerea datelor de intrare în memoria unui sistem de calcul virtual sau real 3 . sunt definiţi mult mai riguros.Hr. 3. Aceiaşi descriere se regăseşte în lucrarea Elementele lui Euclid. a căror parcurgere ne permite ca.un algoritm nu rezolvă o singură problemă. Proprietăţile algoritmilor: Orice algoritm informatic verifică următoarele proprietăţi: 1. Corectitudine . şi anume să utilizeze memorie minimă şi să se execute într-un timp minim. ci o clasă de probleme de acelaşi tip. pornind de la o mulţime de date de intrare. Noţiunea de algoritm. simple şi riguros specificate 4. generând erori logice.algoritmul trebuie să producă un rezultat corect (date de ieşire) pentru orice set de date de intrare valide 5. despre care vom discuta în acest capitol. De altfel. Eficienţă . ale căror parcurgere ne conduce la rezultatul dorit. 2. cca. când pentru prima dată se descrie o secvenţă ordonată de reguli clare pentru descrierea rezolvării unor probleme.acţiunile algoritmului trebuie să se termine după un număr finit de operaţii. Cuvântul algoritm provine din pronunţia fonetica a numelui matematicianului arab Al-Khwarizmi Muhammed ibs Musa (780-850). să obţinem în mod eficient rezultatele corecte ale problemei. aceasta pentru orice set de date valide 3. care se referă la reguli precise pentru descrierea proceselor de calcul din aritmetică.

reguli de ieşire prin care se permite furnizarea rezultatelor finale ale algoritmului În funcţie de tipurile de reguli pe care le conţine. reguli condiţionale prin care se va decide continuarea sau nu a algoritmului printr-o secvenţă de reguli 4.constante . caracter) şi tipuri structurate. În anumite situaţii.Algoritmi repetitivi (ciclici) – o secvenţă de reguli se repetă de un număr finit de ori. Introducerea şi utilizarea unei variabile în descrierea algoritmului corespunde identificării şi manipulării unei date de un tip de dată presupus. reguli de calcul care permit efectuarea operaţiilor elementare aritmetice 3. datele de ieşire (rezultatele finale ale problemei) şi datele intermediare (acele date care corespund unor rezultate parţiale ale problemei ce intervin în procese ulterioare pentru determinarea rezultatelor finale). 4. Prin această noţiune se denumesc date de tipuri diferite.4 . Cele două categorii de operaţii formează operaţiile principale ale algoritmilor informatici: . real.variabile Constanta – reprezintă o mărime a cărei valoare nu se modifică în timpul execuţiei (parcurgerii) algoritmului. iar datele de tip structural sunt alcătuite prin asamblarea datelor elementare. Variabila are ataşat un nume. Printr-un tip de dată se înţelege în general atât domeniul de valori posibile ale datelor. Fişierul reprezintă o mulţime structurată şi omogenă de date şi articolul este unitatea atomică a fişierului. Datele elementare sunt unităţi indivizibile de informaţie.Algoritmi şi structuri de date 2. Cuvântul variabilă este unul fundamental în programare.Operaţii de intrare-ieşire – permit introducerea în memoria sistemului de calcul (real sau virtual) a datelor de intrare şi respectiv. o valoare curentă care aparţine domeniului tipului specificat şi implică cunoaşterea mulţimii de operaţii posibile la care ia parte. redarea rezultatelor obţinute 4 .Algoritm ramificat – conţine cel puţin o regulă de tipul 3 . În algoritmii informatici. un algoritm poate fi: . acţiunea algoritmilor este îndreptată asupra: fişierelor şi a articolelor.2. Secvenţa de paşi descrisă de algoritm este parcursă într-o anumită ordine dictată de operaţiile de control. Obiecte si operaţii ale algoritmilor Algoritmii informatici operează cu următoarele obiecte fundamentale: . cât şi operaţiile specifice datelor de acel tip. Tipurile de date sunt clasificate ca tipuri elementare (întreg.Algoritm liniar – conţine doar reguli de tipul 1. Obiectele pe care le utilizează un algoritm sunt supuse unor operaţii de manipulare. variabilele sunt toate datele de intrare. Variabila – reprezintă o mărime a cărei valoare este modificabilă în timpul execuţiei algoritmului.

b. Scheme logice Reprezentarea algoritmilor poate fi făcută în două maniere: . 5 . Descrierea algoritmilor. Simboluri de intrare-ieşire – corespunzătoare operaţiilor de intrare-ieşire prin care se realizează comunicarea cu exteriorul sistemului de calcul virtual. unei variabile i se atribuie valoarea unei alte variabile.grafic – prin intermediul schemelor logice . 5.textual – prin intermediul unor limbaje standardizate (limbaj pseudocod. corespunde momentelor de start şi stop a algoritmului descris corespunzător.Algoritmi şi structuri de date - Operaţii de atribuire – prin intermediul acestor operaţii. Simbolurile delimitatoare: având rolul de a marca începutul şi sfârşitul schemei logice. a unei constante sau rezultatul evaluării unei expresii Operaţii de calcul – sunt operaţiile executate cu ajutorul expresiilor Operaţii de decizie – condiţionează execuţia unui operaţii sau grupe de operaţii în urma evaluării unor expresii la valorile logice – adevărat. fals. limbaj de programare) Schemele logice sunt reprezentări grafice ale algoritmilor informatici construite pe baza unei mulţimi de simboluri grafice conectate prin intermediul săgeţilor. Operaţii de apel – permit apelul unei instrucţiuni sau a unui grup de instrucţiuni în vederea executării acesteia de un număr finit de ori Operaţii de salt – permit continuarea algoritmului dintr-un anumit punct al acestuia Operaţii auxiliare – operaţii specifice lucrului cu baze de date sau fişiere. Mulţimea de simboluri grafice şi semnificaţia acestora este prezentată în continuare: a.

e. Detaliile referitoare la operaţii sunt înscrise în interiorul blocului. Simbolul de decizie – utilizate pentru reprezentarea operaţiilor de decizie. d.Algoritmi şi structuri de date c. Simboluri de atribuire . Utilizarea corectă a simbolurilor grafice permite descrierea oricărui algoritm informatic. Simbolul de procedură – se folosesc pentru reprezentarea operaţiilor de apel. Simboluri auxiliare: Conectorii şi săgeţile – asigură consistenţa schemei logice. f. Se evidenţiază trei structuri fundamentale corespunzătoare: parcurgerii secvenţiale a unei succesiuni de paşi. Sunt simboluri cu o singură intrare şi cel puţin două ieşiri. ramificării algoritmilor şi execuţiei repetate a 6 . Sunt apelate proceduri sau module externe (care sunt descrise în altă parte).calcul – pentru reprezentarea operaţiilor de calcul şi atribuire.

structura precondiţionată presupune iniţial verificarea condiţiei şi apoi execuţia secvenţei . structura repetitivă. …Sn Execută secvenţa S1 dacă condiţia Cond este adevărată SAU execută secvenţa S2 dacă Cond este falsă Execută secvenţa S dacă condiţia Cond este adevărată SAU nu execută secvenţa S dacă Cond este falsă. structura alternativă. 6. Structura repetitivă Structura repetitivă precondiţionată: Cât timp condiţia Cond rămâne adevărată repetă execuţia secvenţei S Structura repetitivă postcondiţionată: Repetă execuţia secvenţei S cât timp condiţia Cond rămâne adevărată Observaţie: Diferenţa dintre structurile repetitive precondiţionată şi postcondiţionată constă în poziţia simbolului de decizie corespunzător etapei de verificare a condiţiei de continuare a execuţiei blocului: .structura postcondiţionată presupune execuţia secvenţei şi apoi verificarea condiţiei Această diferenţă va genera un număr minim de repetări a secvenţei S diferit pentru cele două structuri: minim 0 execuţii pentru structura precondiţionată şi minim 1 execuţie a secvenţei în structura postcondiţionată. în situaţia în care de la prima evaluare a condiţiei aceasta este falsă. Cele trei structuri fundamentale sunt: structura secvenţială.Algoritmi şi structuri de date unei secvenţe de operaţii. Exemple de scheme logice 7 . şi sunt descrise prin blocuri de simboluri logice astfel: Denumire structură Structura secvenţială Structura alternativă Reprezentare grafică Semnificaţie Execuţia secvenţială a operaţiilor descrise în simbolurile de calcul: S1..

Algoritmi şi structuri de date

Exemplul 1. Algoritmul de determinare a soluţiilor ecuaţiei de gradul 2: ax 2 + bx + c = 0 . Rezolvarea problemei presupune tratarea celor 4 cazurilor identificate în funcţie de datele de intrare furnizate: 1. rădăcinile ecuaţiei sunt reale 2. ecuaţia de gradul II nu are rădăcini reale 3. ecuaţia este de gradul I 4. ecuaţia este imposibilă

Exemplul 2. Algoritmul de determinare a produsului primelor n numere naturale.

8

Algoritmi şi structuri de date

Exemplul 3. Algoritmul de determinare a celui mai mare divizor comun a două numere:

7. Pseudocod Pseudocodul este un limbaj standardizat intermediar între limbajul natural şi limbajul de programare. Descrierea algoritmilor cu ajutorul limbajului pseudocod înlătură din neajunsurile utilizării schemelor logice, fiind mult mai flexibil şi natural decât acestea. În plus, descrierea în pseudocod permite convertirea cu uşurinţă a algoritmilor astfel exprimaţi într-un limbaj de programare. 9

Algoritmi şi structuri de date

Limbajul pseudocod foloseşte două tipuri de propoziţii: 1. Propoziţiile standard: proprii limbajului pseudocod 2. Propoziţiile ne-standard: acele propoziţii ce descriu în limbaj uzual operaţii ce urmează a fi detaliate, rafinate ulterior. Aceste propoziţii sunt marcate prin simbolul #. Există o concordanţă între simbolurile grafice ale schemelor logice şi propoziţiile limbajului pseudocod. De asemenea, structurile fundamentale pot fi descrise prin propoziţii proprii limbajului pseudocod. Tabelul următor prezintă concordanţa dintre descrierea grafică şi propoziţiile pseudocod pentru operaţii specifice algoritmilor: Schema logică Pseudocod Algoritmul NUME-ALGORITM este: Sfârşit NUME-ALGORITM sau SfAlgoritm Citeşte a1,a2,…,an Tipăreşte a1,a2,…,an Limbajul pseudocod, pentru a uşura traducerea ulterioară a algoritmului în limbaj de programare, permite folosirea unei propoziţii standard echivalentă uneia dintre instrucţiunile populare ale limbajelor de programare. Aceasta propoziţie corespunde unei structuri repetitive cu număr cunoscut de repetări şi se descrie astfel: Schema logică Pseudocod Pentru var de la I la F cu pasul pas Execută S Sfârşit Pentru - structura ciclică cu număr cunoscut de repetări

10

S2. 9. Problema 3.Algoritmi şi structuri de date Tabelul următor prezintă modul de descriere a structurilor fundamentale în pseudocod: Structurile fundamentale Structura secvenţială Structuri alternative Schema logică Pseudocod [Execută] S1 . Să se rezolve sistemul de 2 ecuaţii liniare cu 2 necunoscute. b. Ec. Să se citească un număr de n valori şi să se determine cea mai mică valoare citită. altfel se execută blocul S2. Probleme propuse spre rezolvare Să se descrie grafic şi textual (pseudocod) algoritmul de rezolvare a problemei următoare: Problema 1.. Problema 2.. Algoritmul de determinare a soluţiilor ecuaţiei de gradul 2: decizionale ax 2 + bx + c = 0 . Să se citească n valori şi să se calculeze media aritmetică a acestora. imbricate Algoritm EcuaţieGradII este: Citeşte a. Câttimp condiţia Cond este adevărată se repetă blocul S.…Sn Dacă condiţia este adevărată atunci se execută blocul S1. c Dacă a ≠ 0 atunci Dacă b ≠ 0 atunci x = -c / b Tipăreşte “Sol.x 11 . Dacă condiţia este adevărată atunci se execută blocul S1. Se repetă blocul S până când condiţia Cond devine adevărată. grad I”. Exemple de algoritmi descrişi în pseudocod Structuri Exemplul 1. [Execută] Sn Dacă Cond atunci Execută S1 Altfel Execută S2 SfDacă Dacă Cond atunci Execută S Sf Dacă Câttimp Cond Execută S SfCâttimp Repetă Execută S Pânăcând Cond Limbaj natural Execută secvenţial blocurile de calcul S1. Structuri repetitive 8.

Algoritm Factorial este: Citeşte n P:=1 Structură repetitivă cu număr cunoscut de repetări Pentru i de la 1 la n cu pasul 1 Execută P:=P * i Sfârşit Pentru Tipăreşte P Sfârşit Factorial Exemplul 3. Algoritmul de determinare a celui mai mare divizor comun a două numere. Algoritmul de determinare a produsului primelor n numere naturale.Algoritmi şi structuri de date Altfel Tipăreşte rezolvat” SfDacă Altfel Δ = b2-4ac Dacă Δ≥0 atunci x1 = ( −b − ∆ ) 2a x 2 = ( −b + ∆) 2a Tipăreşte x1.b rest = a modulo b Câttimp (rest ≠ 0) a=b b = rest rest = a modulo b Sfârşit câttimp Tipăreşte b 12 Structură repetitivă precondiţionată .x2 “Imposibil de Altfel Tipăreşte “Rădăcini complexe“ SfDacă SfDacă Sfârşit EcuaţieGradII Exemplul 2. Algoritm Euclid este: Citeşte a.

Caracterul de generalitate va fi asigurat prin parametrizarea subalgoritmilor. DE: Max) Dacă x>y atunci Max=x Altfel Max=y SfDacă Sfârşit Maxim2 Algoritm Maxim4 este: Apelul repetat al subalgoritmului. Lista parametrilor actuali conţine numele datelor concrete care se doresc a fi transmise subalgoritmului şi numele sub care se reţin rezultatelor parţiale ale subalgoritmului. Subalgoritmi O problemă de dificultate sporită necesită uneori o împărţire în subprobleme de dificultate mai mică în baza principiului divide et impera. Ieşirile subalgoritmilor sunt date pe care algoritmul general le va prelucra ulterior. Subalgorimii sunt practic algoritmi care rezolvă subprobleme ale unei probleme date. corectitudine ale algoritmilor informatici. În aceste cazuri este utilă construirea unui subalgoritm corespunzător secvenţei de operaţii şi apelarea acestuia în algoritmul global ori de câte ori este nevoie. La apelul unui subalgoritm parametrii formali (generici) din listă se vor înlocui cu parametrii concreţi (actuali). Deseori se poate întâlni situaţia ca o anumită secvenţă de operaţii să fie executată de mai multe ori pentru date de intrare diferite. pentru 13 diferiţi parametri actuali . Subalgoritm Maxim2(DI: x . claritate. evidenţiind subalgoritmul de determinare a maximului dintre oricare două valori. Algoritmii de rezolvare a subproblemelor rezultate sunt mai uşor de descris şi vor constitui subalgoritmi ai algoritmului global de rezolvare a problemei considerate. Aceştia vor primi la intrare nu datele de intrare ale problemei ci date intermediare ale problemei globale sau rezultate parţiale din procesele de calcul. Comunicarea dintre subalgoritm şi algoritmul “părinte” se realizează prin intermediul parametrilor. // operaţii asupra parametrilor din lista Sfârşit NumeSubalgoritm Apelarea unui subalgoritm din algoritmul apelant se face prin propoziţia: Cheamă NumeSubalgoritm (listă parametrii actuali) Lista parametrilor formali va conţine nume generice ale datelor de intrare şi datelor de ieşire ale subalgoritmului. Acest lucru indică faptul că orice subalgoritm verifică proprietăţile de generalitate. Exemplu: Să se descrie un algoritm de determinare a maximului dintre 4 valori date. În pseudocod sintaxa generală a unui subalgoritm este următoarea: Subalgoritm NumeSubalgoritm ( lista parametrii formali) ….Algoritmi şi structuri de date Sfârşit Euclid 10. finitudine.y.

b. claritate. De aceea. 14 .max2 . maxim) Tipăreşte maxim Sfârşit Maxim4 Rezolvarea problemelor cu ajutorul calculatorului presupune: 1. finitudine.c.d. Calitatea implementării software este dependentă de calitatea algoritmului elaborat. faza de dezvoltare a algoritmului de rezolvare a unei clase de probleme trebuie abordată cu o deosebită atenţie. În faza de elaborare a algoritmului trebuie să se ţină seama de proprietăţile fundamentale ale algoritmilor: generalitate. max2) Cheamă(max1.d Cheamă(a.Algoritmi şi structuri de date Citeşte a. Modelarea matematică a problemei 2. Alegerea unei metode de rezolvare Modelarea matematică şi alegerea metodei de rezolvare se îmbină cu conceperea algoritmului rezultând ceea ce numim proiectarea programului. corectitudine şi eficienţă.b. max1) Cheamă(c.

Unităţile sintactice: 1.|’|spaţiu| <simboluriSpecialeNeafişabile>::=\n |\t |\b |\r |\f |\v |\a B.oferă facilităţi de manevrare a biţilor. Identificatori Cuvinte cheie (cuvinte rezervate) Constante Operatori C. 2.permite utilizarea principiilor programării structurate . Expresii 2. <literă>::=A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Z|Y|a|b| c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|z|y|_ 2. <cifră> ::= 0|1|2|3|4|5|6|7|8|9 3. 4.programele scrise în C sunt portabile eficiente . Popularitatea acestui limbaj a fost câştigată datorită avantajelor pe care le prezintă: . Instrucţiuni D.|<|=|>|?|[|]|\|~|{|}|||. ELEMENTELE LIMBAJULUI DE PROGRAMARE C Limbajul de programare C a fost dezvoltat la începutul anilor ’70 de către Ken Thompson şi Dennis M. A.Algoritmi şi structuri de date II.este un limbaj dedicat paradigmei programării procedurale Prin limbaj de programare înţelegem totalitatea regulilor sintactice şi semantice prin care se pot comunica unui computer algoritmii de rezolvare a problemelor. unităţile sintactice şi comentarii. <simboluriSpeciale>::=<simboluriSpecialeAfişabile>| <simboluriSpecialeNeafişabile> <simboluriSpecialeAfişabile>::=!|. Ritchie. octeţilor şi adreselor de memorie . Unităţile lexicale: 1. Alfabetul limbajului C este format din: 1.|”|%|#|&|(|)|*|/|+|-|:| . Comentariile sunt marcate în programul sursă prin // sau /*…*/ Exemple: //variabila i are semnificaţia unui contor /* acest program determină soluţiile ecuaţiei de gradul II*/ 15 . unităţile lexicale. Comentarii: au rolul de a justifica textul sursă redactat şi de a oferi o mai bună înţelegere a acestuia. Elementele unui limbaj de programare sunt: alfabetul. 3.

conţinutul acestei locaţii de memorie se poate schimba . formate cu ajutorul simbolurilor precizate de alfabet. Mulţimea cuvintelor cheie ale limbajului C este prezentată în tabelul următor: Cuvintele cheie ale limbajului C auto defaul float registe switch t r break do for return typedef case double go to short union char else if signed unsigned const enum int sizeof void 16 .c. //declararea unei variabile simple de tipul real. care exprimă natura şi dimensiunea valorilor pe care le ia Regulă: într-un program C.1.are un anumit tip de date. Orice cuvânt rezervat nu poate avea altă utilizare într-un program decât cea predefinită.b. nume de funcţii. B. Unităţile lexicale ale limbajului C Unităţile lexicale sunt elementele atomice ale unui program. Exemple: int a. Identificatorii: sunt secvenţe de litere şi cifre ale limbajului definit care încep obligatoriu cu o literă sau caracterul ‘_’.are un nume care o identifică (acest nume este exprimat printr-un identificator) . Declararea datelor se face prin instrucţiuni de declarare de tip de date: <instrucţiune de declarare de tip>::= <tip><listă de identificatori>. // declararea unei variabile de tip caracter B. având rolul de a identifica conceptele utilizate în program.are asociată o locaţie de memorie în care se regăseşte valoare curentă. un tip definit de programator sau tipul special void (tip nedefinit). Variabile. nume de constante simbolice. tip de date Variabilă: .2. Tipul datei ne spune domeniul valorilor datei cât şi lungimea de reprezentare internă. // declararea a trei variabile simple de tip întreg float var. toate datele (variabile) trebuie declarate pentru a putea fi folosite. virgulă flotantă în simplă precizie char ch . unde: <tip> poate fi un tip predefinit (vezi lista tipurilor predefinite în C). Identificatorii corespund unor nume de variabile. Cuvintele rezervate (cuvinte cheie) sunt identificatori care au o semnificaţie specială.Algoritmi şi structuri de date B. Prin declararea unei date se înţelege precizarea tipului datei respective şi stabilirea identificatorului.

reprezentare internă cu semn.valorile minime cuprinse în intervalul [. caracter. reprezentat în virgulă flotantă. în cod complementar faşă de 2 întreg reprezentat intern pe 32 biţi.32767] [-32768. dublă precizie – pe 80 biţi unsigned char signed char long double B. reprezentat în virgulă flotantă. Tipurile predefinite ale limbajului corespund tipurilor elementare de date: întreg.3. 1.4294967295] [0.4 ⋅10 −4932 .4 ⋅10 38 ] . 1. în cod complementar faşă de 2 întreg fără semn reprezentat pe 16 biţi întreg fără semn reprezentat pe 32 biţi caracter reprezentat intern prin codul ASCII corespunzător pe 8 biţi Real.127] .Algoritmi şi structuri de date continue extern long static struct volatile while Lista cuvintelor rezervate conţine şi setul de tipuri predefinite ale limbajului C.valorile minime cuprinse în intervalul [.3.3.7 ⋅10 308 . echivalent implicit pentru char Caracter. simplă precizie – pe 32 biţi Domeniul de valori [-32768.valoarea minimă absolută reprezentabilă: 3.255] [-128. real. pe 8 biţi Real.4 ⋅10 38 . reprezentat în virgulă flotantă.valoarea maximă absolută reprezentabilă: 1. 3.7 ⋅10 −308 ] se confundă cu 0 [0. pe 8 biţi.7 ⋅10 −308 .2147483647] [0. în cod complementar faşă de 2 întreg reprezentat intern pe 16 biţi.32767] [-2147483648.4 ⋅10 −38 ] se confundă cu 0 [. dublă precizie – pe 64 biţi Caracter.1 ⋅10 +4932 float double Real. 3.1.255] [.7 ⋅10 308 ] . Constante 17 . reprezentare internă fără semn.1.4 ⋅10 −38 . Aceste tipuri sunt introduse prin cuvintele rezervate: Tipul predefinit int short long unsigned unsigned long char Semnificaţie întreg reprezentat intern pe 16 biţi.65535] [0.

Sfârşitul secvenţei de coduri ale caracterelor dintr-un şir este marcată în memorie de un octet special cu valoarea NULL.Constante “şir de caracter” . Astfel. ultimul fiind cel ce marchează sfârşitul NULL. exemple: -123. Constantele se clasifică în patru categorii: . Există două categorii de caractere: caractere afişabile (imprimabile) şi caractere neafişabile: <constanta caracter>::= ‘ <caracter> ‘ <caracter>::=<caracter afişabil (ASCII)> |<caracter neafişabil> O constantă caracter grafică (afişabilă) se specifică incluzând caracterul între simbolurile apostrof: ‘ ‘. Observaţie: reprezentarea în memorie a lui ‘k’ este diferită de reprezentarea lui “k”.Constante simbolice Constante caracter Constanta caracter este un simbol care are ataşată valoarea codului ASCII al caracterului corespondent. Numerele întregi zecimale. încep cu cifra 0 şi sunt de forma: 0c…c. 5. şirului “k” îi este alocat un spaţiu de 2 octeţi. unde 452 este numărul în baza 8 18 . corespunzător caracterului ‘\0’.Algoritmi şi structuri de date Constantele sunt mărimi ale căror valoare nu se modifică pe parcursul execuţiei unui program. Un caracter este reprezentat în memoria calculatorului pe un octet (8 biţi).Constante numerice . Exemple: 0452 . Exemple: ‘A’ – codul ASCII 65 ‘a’ – codul ASCII 97 ‘+’ – codul ASCII 77 ‘2’ – codul ASCII 50 O constantă caracter non-grafică (neafişabilă) au notaţii speciale.Constante caracter . prin codul ASCII corespunzător. Numerele octale întregi. Exemple: Întoarcerea cu un spaţiu (Backspace) ‘\b’ – codul ASCII 8 Retur car ‘\r’ – codul ASCII 13 Linie nouă ‘\n’ – codul ASCII 10 Constante şir de caractere Sunt formate dintr-o succesiune de caractere care se delimitează prin ghilimele: “exemplu”. +5. Fiecare caracter al şirului se reprezintă în memoria calculatorului pe un octet. Constante numerice Constantele numerice sunt în fapt numerele. se specifică utilizând caracterul backslash (\) şi apostroful (‘). în funcţie de tipul (întreg sau real). unde c este cifră octală. În limbajul C se face o distincţie între numere.. baza de numeraţie şi precizia codificării.

Operatori de deplasare f.4567 Numerele flotante de tip real în dublă precizie [{+/-}]dd…d . Operatorul condiţional i.667 . operatorii pot fi: . Operatori de incrementare şi decrementare c. Se folosesc în construcţii de următoarea formă: {+/-}<expresie> 19 . dd…d Pot să conţină semnul (+ sau -) specificat la începutul scrierii urmat de partea întreagă. Numerele flotante de tip real în simplă precizie [{+/-}]dd…d .ternari Operatorii aritmetici Operatori aritmetici unari: <OperatoriAritmeticiUnari>::= .| + Au rolul de a specifica semnul unei expresii. Operatori de asignare (atribuire) h.binari . Operatorii Operatorii sunt reprezentaţi de simboluri sau secvenţe de simboluri care intră în alcătuirea expresiilor. .0e12 (=22*1012) Constante simbolice Definirea constantelor simbolice se face prin directiva de preprocesare define.1415 #define raza 124 B. Operatori logici d. Operatori la nivel de biţi g. -. În funcţie de tipul operaţiilor pe care le induc. Operatori relaţionali e. Exemple: +23. Exemplu: 0xabbd. prin construcţia: #define <NumeConstantă> <ValoareAsociată> Ex: #define PI 3.unari .Algoritmi şi structuri de date Numerele hexazecimale întregi: încep cu 0x sau 0X şi sunt de forma: 0xc…c unde c sunt cifre hexazecimale.345E2 (corespunzător valorii 0. punctul zecimal şi partea fracţionară.012*10-3) 22. 012e-3 (-0.567 .<parte fracţionară> {E/e} [{+/-}] <exponent> Exemple: 0. Operatori aritmetici b. dd…d {E / e} [{+/-}] dd sau: [{+/-}] <parte întreagă>.345*102) -0. Alţi operatori După numărul operanzilor pe care îi implică.4. operatorii limbajului C sunt clasificaţi: a.

+(a-2). A:=B++ si B are valoarea iniţială 4. Operatorii relaţionali: <OperatorRelaţional>::= = = | != | < | <= | > | >= Cu semnificaţia: = = egal != diferit <= mai mic sau egal >= mai mare sau egal. 2 pot fi reprezentate de constante. etc. variabile. unde prin simbolul “:=“ se înţelege “i se atribuie” <OperatorulDeDecrementare>::= -Se foloseşte în construcţii de forma: --<expresie> (predecrementare) <expresie>-(postdecrementare) Semnificaţia acestuia este următoarea: --<expresie> este echivalent cu <expresie> := <expresie>-1 Diferenţa dintre preincrementare şi postincrementare: Fie următoarele două expresii: 1.b) % c . scădere. etc. Operatori aritmetici binari: <OperatorAritmeticBinar>::= +| . împărţire.Algoritmi şi structuri de date Dacă o expresie nu este precedată de semn se consideră implicit semnul + (pozitiv).| * | / | % Au semnificaţia operaţiilor aritmetice: adunare. etc. în urma efectuării calculelor B va avea valoarea 5 şi A va avea valoarea 5 dat fiind faptul că prima acţiune este aceea de incrementare urmată de atribuire. 20 . Exemple: +12.34. În al doilea caz. dat fiind faptul că atribuirea se face înaintea incrementării. nume de funcţii sau alte expresii aritmetice. (a+b) / 2. -0. suma(a. Exemple: a+b. Pentru prima expresie. modulo şi se utilizează în structuri de forma: <expresie1><OperatorAritmeticBinar><expresie2> Expresiile 1.56. A:=++B 2. Operatorii de incrementare-decrementare <OperatorulDeIncrementare>::= ++ Se foloseşte în construcţii de forma: ++<expresie> (preincrementare) <expresie>++ (postincrementare) Semnificaţia acestuia este următoarea: ++<expresie> este echivalent cu <expresie> := <expresie>+1. B are valoarea 5 iar A=4. inmulţire.

Algoritmi şi structuri de date Se folosesc în construcţii de forma: <expresie1><OperatorRelaţional><expresie2> O structură de forma anterioară este denumită expresie relaţională şi este evaluată la una dintre valorile logice de adevăr True (1) sau False (0) Exemplu: Fie expresia 2<3 .SAU EXCLUSIV 21 . efectul acestora fiind acela de a provoca deplasarea biţilor din reprezentarea binară a întregului dat de expresia 1 la stânga (<<) sau la dreapta (>>) cu un număr de poziţii specificat de valoarea întreagă a expresiei 2. biţii deplasaţi în afara limitei de reprezentare se pierd.operatorul OR (SAU) disjuncţia ! . Negaţie.operatorul AND (ŞI) conjuncţia || .Negaţia ^ .ŞI | . Regulă: Prin aceste operaţii de deplasare. SAU EXCLUSIV <OperatoriBoolean>::= & | | | ~ | ^ & .SAU ~ . a || (!a) Operatori de deplasare: <OperatorDepalsare>::= << | >> Sunt operatori binari utilizaţi în construcţii de forma: <expresie1><OperatorDeplasare><expresie2> unde: expresiile sunt evaluate în prealabil la valori întregi. în urma comparării operanzilor (constante numerice în acest caz) se constată că valoarea logică este “adevăr” ceea ce conduce la evaluarea expresiei 2<3 la valoarea 1 (Adevărat).operatorul NOT (negaţia) Exemple: (a<b) && (b !=c) . Operatorii logici: <OperatorLogic> ::= && | || | ! Cu semnificaţia: && . SAU. Operatorii de deplasare corespund operaţiilor de deplasare pe biţi. iar biţii eliberaţi sunt completaţi cu 0 sau 1 în funcţie de semnul întregului respectiv: pozitiv sau negativ: A<<B are semnificaţia de deplasare a biţilor din reprezentarea binară a lui A cu B poziţii la stânga A>>B are semnificaţia de deplasare a biţilor din reprezentarea binară a lui A cu B poziţii la dreapta Operatorii la nivel de biţi: Aceştia corespund operatorilor logici (boolean): ŞI.

Algoritmi şi structuri de date Operatorii la nivel de biţi se utilizează în manevrarea biţilor din reprezentările binare ale operanzilor întregi sau caracter. structură este echivalentă cu: <variabilă>=<variabilă> <operator> <expresie> Operatorul condiţional: Este un operator ternar format din două simboluri “?” şi “:” şi folosit în construcţii de forma: <expresie1>? <expresie2>:<expresie3> Semnificaţie: expresiile 1.”: Se foloseşte în construcţii de forma: <expr1> . <expr2> . când avem asignări de forma: <variabilă> <operator> = <expresie> unde: <operator>::=+|-|*|/|%|>>|<<|&|!|^ . Exemple: (A<B) ? A : B (A= =0) ? “egal cu zero” : “diferit de zero” Operatorul secvenţial “. ….3 sunt evaluate şi valoarea de evaluare a expresiei globale este valoarea expresiei 2 dacă expresia 1 este adevărată altfel valoarea expresiei 3 dacă expresia 1 este falsă.2. <exprn> Expresia globală de mai sus se evaluază de la stânga la dreapta. Operatorul de conversie explicită ( cast) 22 . În cazul general. <expr3> . . valoarea finală este cea rezultată în urma evaluării ultimei sub-expresii exprn. Exemplu: Fie A şi B două numere întregi ale căror reprezentări pe 16 biţi sunt: A= 0001 0010 1010 1011 şi B= 1101 1111 0000 0101 Atunci: A&B= 0001 0010 0000 0001 A|B = 1101 1111 1010 1111 Operatori de asignare (atribuire): <OperatorAsignare>::= = | += | -= | *= | /= | %= | >>= | <<= | &= | != | ^= Cea mai simplă operaţie de asignare este cea formată prin construcţia: <variabilă> = <expresie> având semnificaţia următoare: expresia din partea dreaptă a operatorului de asignare este evaluată iar rezultatul evaluării (o valoare) este atribuit variabilei din partea stângă a operatorului =.

Utilizarea operatorului se face prin construcţii: (tip) expresie.1. Expresii Expresiile sunt secvenţe de operatori şi operanzi. devine instrucţiune. variabila primeşte valoarea la care a fost evaluată expresia convertita la tipul explicit tip. unde tip .2. C. Instrucţiunea bloc: este formată din simbolurile { } în interiorul cărora sunt scrise alte instrucţiuni. se realizează conversia implicita la tipul variabilei. Unităţile sintactice Unităţile sintactice sunt elemente complexe alcătuite prin combinarea a două sau mai multe unităţi lexicale. În funcţie de tipul operatorilor utilizaţi. şi dacă tipul rezultatului obţinut este diferit de cel al variabilei. operatorul cast apare în expresii de forma: var = (tip) expresie şi prin această operaţie. Instrucţiunea expresie: orice expresie urmată de caracterul . Ordinea de execuţie a instrucţiunilor grupate este ordinea apariţiei acestora în bloc (instrucţiunile din bloc se execută secvenţial): Sintaxa: { <instrucțiune_1> <instrucțiune_2> … <instrucțiune_n> } Rolul acestei instrucţiuni este de a grupa mai multe instrucţiuni simple. expresiile pot fi: expresii aritmetice. În conversiile explicite. Sintaxa instrucţiuni expresie este următoarea: <expresie>. Operanzii care intră în alcătuirea expresiilor pot fi: . expresii logice. B. C.constante . Instrucţiunile pot fi simple sau compuse. Exemplu: a=b+c.variabile . relaţionale. Instrucţiuni Instrucţiunile sunt propoziţii care respectă regulile limbajului de programare în care sunt redactate.apel de funcţii . A.reprezintă tipul de date spre care se face conversia. compuse sau alte instrucţiuni bloc. etc. 23 .alte expresii C. Expresiile de forma var=expresie semnifică evaluarea prima dată a expresiei.Algoritmi şi structuri de date Efectul operatorului cast este conversia explicită a unei expresii la un alt tip de dată decât cel implicit.

reprezintă o generalizare a structurii alternative (if). x2=(-b+sqrt(delta))/2*a. Exemple: if (a!=0) b=1/a. instrucţiunile 1 sau 2 pot fi de asemenea instrucţiuni compuse sau bloc de instrucţiuni. Instrucţiunea IF simplă nu conţine partea else <instrucţiune 2> Instrucţiunea IF completă conţine ambele ramuri. Instrucţiunea switch . Instrucţiunile de ramificare corespund structurilor alternative: decizie simplă şi decizie multiplă. Efectul instrucţiunii: <expresia> este evaluată şi dacă valoarea obţinută în urma evaluării este diferită de 0 se execută instrucţiunea 1. If else (a>=b) maxim=a.Algoritmi şi structuri de date C. . Aceasta instrucţiune are sintaxa: break. } Sintaxa limbajului C permite ca în cadrul instrucţiunilor compuse ce constituie cele două ramuri ale structurii alternative IF să existe alte structuri alternative IF. O astfel de construcţie se denumeşte structură alternativă imbricată sau instrucţiuni IF imbricate. if (delta>=0) { x1=(-b-sqrt(delta))/2*a. altfel se execută instrucţiunea 2. În structura instrucţiunii switch este regăsită o instrucţiune simplă de a cărei prezenţă depinde execuţia corectă a instrucţiunii alternative switch.se poate înlocui printr-o secvenţă de structuri alternative imbricate. Rolul instrucţiunii break este de a întrerupe execuţia instrucţiunii curente şi de a trece la execuţia următoarei instrucţiuni ce urmează instrucţiunii switch. Sintaxa generală: 24 . maxim=b. Instrucţiunea if Sintaxa instrucţiunii: if ( <expresie> ) <instrucţiune_1> else <instrucţiune_2> sau: if ( <expresie> ) <instrucţiune_1> Unde. Cele două instrucţiuni 1 şi 2 se exclud reciproc.

break. [default : <instrucţiune_default>.. case 4: printf(\n Joi”). default:printf(\n nu este numar 1. Acestora le corespund trei instrucţiuni de ciclare în limbajului C: Structura repetitivă precondiţionată -> instrucţiunea while Structura repetitivă postcondiţionată -> instrucţiunea do … while Structura repetitivă cu număr prefixat de repetări -> instrucţiunea for Instrucţiunea while Sintaxa: while (<expresie>) //antetul instrucţiunii <instrucţiune> //corpul instrucţiunii 25 .Se “caută” secvenţial valoarea la care a fost evaluată expresia în lista de valori val1. val2.Dacă s-a găsit o valoare egală cu valoarea expresiei se va executa instrucţiunea corespunzătoare şi se va părăsi structura alternativă. . instrucţiunea default sau efectul instrucţiunii switch este nul .2. … case <valn> : <instrucţiune_n>.Dacă nu se găseşte nici o valoare val1. dacă este precizată.3. break. break.7”). break.. val2.] //opţional }. case 5: printf(\n Vineri”).4.7: switch (numar) { case 1: printf(\n Luni”). Efectul instrucţiunii: . Instrucţiunile de ciclare În pseudocod am evidenţiat cele trei tipuri de structuri repetitive. break.5.2. case 7: printf(\n Duminica”). …valn.Algoritmi şi structuri de date switch (<expresie>) { case <val1> : <instrucţiune_1>.. case 3: printf(\n Miercuri”).6.Se evaluează expresia dintre paranteze la o valoare. case 6: printf(\n Sambata”). break. break.…valn egală cu valoarea la care a fost evaluată expresia se va executa. } D. case <val2> : <instrucţiune_2>. break. . Exemplu: Afişarea numelui zilei din săptămână căreia îi corespunde un număr 1.break. break.. case 2: printf(\n Marti”).

Algoritmi şi structuri de date unde: <instrucţiune> poate fi o instrucţiune simplă. Dacă rezultatul unei evaluări este nul. } Instrucţiunea do…while Sintaxa: do <instrucţiune> while (<expresie>). } Exemplu 2: Se calculează suma primelor 10 numere naturale. Valoarea iniţială a contorului este 0. şi poate conţine alte instrucţiuni do…while. şi poate conţine alte instrucţiuni while. Dacă rezultatul evaluării este o valoare non-nulă (adevărat) se execută instrucţiunea din corpul while şi procesul se reia prin reevaluarea expresiei. o instrucţiune while are semnificaţie: “Cât timp expresia este adevărată repetă corpul de instrucţiuni” Observaţie: corpul instrucţiunii while este posibil să nu se execute nici o dată. //corpul <instrucţiune>: poate fi o instrucţiune simplă. i). Simplificat. Efectul instrucţiunii do…while: . compusă. i++. //iniţializarea while (i<=10) { s=s+i. while (i<=10) { printf (“%d”. i=0. în situaţia în care expresia evaluată este de la început falsă. Exemplu 1:Se tipăreşte valoarea unui contor până când acesta devine mai mare decât 10.Se evaluează expresia dintre paranteze 26 . şi programul se continuă cu următoarea instrucţiune ce urmează instrucţiunii while. caz în care spunem că avem o instrucţiune while imbricată Efectul instrucţiunii: În primul pas. respectiv: 1+2+3+… +10 i=1. i++.în primul pas se execută corpul instrucţiunii do…while . se întrerupe execuţia instrucţiunii while. se evaluează expresia dintre paranteze. compusă. caz în care spunem că avem o instrucţiune repetitivă imbricată.s=0.

&caracter). //10 – este codul ASCII pentru Enter printf("Numarul de vocale: %d". do { scanf(“%c”. do { scanf("%c". <expresie3> ) <instrucţiune> 27 .Algoritmi şi structuri de date . } while (t!=10). Calculează suma unor numere întregi citite de la tastatură până la introducerea unui număr negativ sau zero: suma=0. (chiar dacă expresia este falsă de la prima evaluare). //caracterul curent int contor=0. Exemplu 1. #include <stdio. } Instrucţiunea for Sintaxa: for ( <expresie1> . suma=suma+nr. &nr). Citeşte un caracter de la tastatură până când se introduce caracterul ‘0’. printf("Introduceti textul: ").dacă rezultatul evaluării expresiei este adevărat se reia procesul prin execuţia corpului instrucţiunii.h> void main() { char t. Exemplu 2. //citeste numărul curent Exemplu 3: Programul următor citeşte un text (terminat prin Enter) şi afişează numărul de vocale din compunerea textului. în caz contrar se încheie execuţia instrucţiunii do…while Semnificaţie: “Execută blocul de instrucţiuni până când expresia devine falsă” Observaţie: corpul unei instrucţiuni repetitive postcondiţionate se execută cel puţin o dată. } while (nr>0) . //citire caracter cu caracter textul if ((t=='a')||(t=='e')||(t=='i')||(t=='o')||(t=='u')) contor++. <expresie2> .contor). } while (caracter<>’0’).&t). do { scanf(“%d”.

i++) printf(“\n %d”.reprezintă o expresia ce este evaluată şi de a cărei valori de evaluare depinde repetarea corpului instrucţiunii. Funcţiile printf. Apelul funcţiilor de intrare . scanf .printf şi scanf sunt funcţii predefinite de intrare–ieşire definite în biblioteca stdio Pentru a putea folosi aceste funcţii într-un program C este necesară în prealabil specificarea prin directivă de preprocesare a fişierului antet (header) ce conţine prototipurile acestora: #include <stdio. <expresie3> . respectiv se execută secvenţa de iniţializare Se evaluează expresia2 (această expresie are acelaşi rol ca <expresie> din structura while sau do…while ) Dacă rezultatul evaluării anterioare este adevărat se execută corpul instrucţiunii for şi apoi se execută secvenţa de actualizare specificată prin expresie3 Dacă rezultatul este fals.h> //la începutul programului sursă 28 . compusă sau poate conţine o altă instrucţiune repetitivă for. se evaluează expresia1.i<=n.reprezintă expresia de iniţializare. } E. se încheie execuţia for.ieşire Operaţiile de intrare-ieşire (citirea datelor de intrare şi afişarea rezultatelor) nu se realizează prin instrucţiuni ale limbajului C. Exemplu 2: Calcularea lui n! fact=1. <expresie1> . definite în bibliotecile de funcţii ale limbajului C.i++) { fact=fact*i.reprezintă expresia de actualizare (actualizarea contorului) - În primul pas. <instrucţiune> poate fi simplă. for (i=1.Algoritmi şi structuri de date Unde: Efect: cele trei expresii din antetul instrucţiunii sunt separate prin caracterul . (iniţializarea contorului) <expresie2> . for ( i=1. i). ci prin apelarea unor funcţii speciale. i<=10. Exemplu 1: Afişarea primelor 10 numere naturale.

Secvenţe escape Secvenţele escape sau comenzi de control pot fi de două tipuri: secvenţe negrafice şi secvenţe grafice sau afişabile. expr2. valorilor unor variabile sau rezultate finale sau intermediare ale programului.afişare backslash \xCC – afişare caracterul al cărui cod ASCII în hexazecimal este dat prin secvenţa CC de cifre în baza 16. Sir de formatare poate conţine doar text şi lista de expresii poate fi vidă.Algoritmi şi structuri de date Prin apelul funcţiei printf se realizează afişarea pe ecran a mesajelor. Sintaxa apelului funcţiei printf: printf(“sir de formatare”.expr1. Numărul specificatorilor de format este egal cu numărul de expresii separate prin virgulă. etc. Exemple: printf(“exemplu de program!”) printf(“\n acest text este afisat pe ecran”) În general sir de formatare poate conţine: . constante sau orice expresie evaluabilă (aritmetică. Specificatorii de format <specificator de format>::= % [-] [m. caz în care prin apelul funcţiei printf se tipăreşte pe ecran textul specificat în acest cîmp.) . Fiecare specificator de format se referă la modul de redare a valorii expresiei de pe poziţia corespondentă: Secvenţele escape negrafice: \n – trecere la linie nouă \t – deplasare la dreapta cu un tab \b – deplasare la stânga cu o poziţie \r – revenire la începutul rândului \f – trecere la pagina următoare \a – semnal sonor Secvenţele escape grafice: \’ – afişare apostrof \” – afişare ghilimele \\ . logică. expr1. …) unde: . expr2. … sunt expresii separate prin virgulă şi pot reprezenta variabile.sir de formatare este încadrat de caracterele ghilimele “ ” şi reprezintă forma sub care vor fi tipărite pe ecran valorile expresiilor. Efect: prin apelul funcţiei printf se realizează afişarea pe ecran a datelor într-un anumit format.Text .Specificatori de format .[n]] [l] <conversie> <conversie>::= d|o|x|X|u|s|c|f|e|E|g|G 29 . datelor. relaţională.

adr2. Astfel var este numele unei variabile.a.E .…F) u – întreg fără semn (unsigned) c – din codul ASCII al reprezentării binare interne -> caracterul s – din şirul de coduri ASCII -> şirul de caractere f – conversie din float sau double în formatul extern zecimal e. Exemple: 30 . printf(“%d + %d = %d”. iar &var este adresa de memorie la care se stochează valoarea variabilei. …) Efect: prin apelul funcţiei scanf se realizează citirea de la tastatură datelor întrun anumit format.semnifică în cazul datelor numerice numărul de cifre după punctul zecimal l .4f” .…f respectiv A.a). Excepţie: nu se utilizează operatorul adresă în cazul în care variabila este o variabilă specială pointer. O adresă se specifică prin precedarea numelui variabilei de operatorul unar adresă notat &.G – conversiile de la f şi E sau f şi e dar alegerea se face pentru numărul minim de poziţii Exemple: printf(“valoarea intreaga a= %d”.a).conversie din float sau double în formatul cu mantisă şi exponent g. Şirul de adrese: adr1.b. printf(“realul a= %5. suma).Algoritmi şi structuri de date - - caracterul – dacă apare semnifică cadrarea la stânga a rezultatului corespunzător specificatorului curent m .a+b).n . adr2.…adrn se asociază specificatorilor de format păstrând ordinea.b.semnifică conversie din formatul intern long în formatul extern.B.specifică numărul minim de caractere ale datei ce va fi afişate . Sintaxa apelului funcţiei scanf : printf(“sir de formatare”. respectiv când numele variabilei este o adresă de memorie. X – întreg în hexazecimal (litere mici sau MARI pentru cifrele a. printf(“suma este %4.2f”. adr1. Conversie specifică tipul de conversie din formatul intern (reprezentarea internă) în cel extern (cum va fi afişat): d – întreg int cu semn o – întreg în octal x. Şirul de formatare are aceeaşi semnificaţie ca şi la funcţia printf.

&b. Anumite variabile sunt folosite în cadrul mai multor funcţii. Exemplu: /* includerea bibliotecii standard de funcţii de intrare-ieşire */ #include <stdio. corespunzător algoritmului de rezolvare a problemei considerate. Instrucţiunile programului pot fi instrucţiuni de declarare sau definire a unor variabile. într-o ordine determinată. fapt pentru care aceste variabile sunt declarate în secvenţa de declarare a variabilelor cu caracter global (în afara oricăror funcţii ale programului).h> Instrucţiuni de declarare a variabilelor globale: opţional.&x). Programul C este constituit dintr-o secvenţă de instrucţiuni. &varsta). Funcţia principală (main) Un program C conţine obligatoriu funcţia principală main. Structura unui program C Redactarea unui program în limbajul C este liberă atâta timp cât se respectă regulile sintactice. Structura unui program C este următoarea: <program>::= [<directive de procesare>] [<instrucţiuni de declarare a variabilelor globale>] <funcţii> Directivele de preprocesare: sunt numite şi comenzi de preprocesare şi apariţie lor în program nu este obligatorie. nume. scanf(“%d %d %d”. Putem avea mai multe funcţii utilizator declarate şi definite sau alte funcţii standard ce sunt apelate dar în prealabil au fost incluse. &x[0]. scanf (“%d %d %f”.. &x). semnele de punctuaţie şi se folosesc construcţiile specifice în mod corect.Algoritmi şi structuri de date scanf (“%d”. Funcţii standard – au nume prestabilit. având o utilizare globală. bibliotecile din care fac parte. 31 .&a. Funcţii utilizator – au nume ales de către utilizator şi sunt construite de către acesta 3. prin directive de preprocesare. &x[1]. Clasificare funcţii: 1. scanf(“%s %s %d”. modul) al unui algoritm de rezolvare a unei probleme date. sunt constituite în biblioteci de funcţii standard 2. Funcţii: sunt unităţi independente ale unui program prin care se reprezintă un subalgoritm (procedura. prenume. &x[2]). Sunt marcate prin simbolul # (diez) care le precede. funcţii şi instrucţiuni care se regăsesc în corpul funcţiilor.

&c). Să se scrie un program C pentru afişarea relaţiei dintre două numere: < .b. > sau =.h> void main(void) { double a. II nu are solutii reale!"). scanf("%lf. printf("Solutiile ec. else { x1=-c/b. if (delta>=0) { x1=(-b-sqrt(delta))/2*a.%lf. Să se citească un sir de n numere întregi de la tastatură şi să se calculeze suma acestora.x1.delta.&a. de gradul II #include <math.&b.x2. 3. printf("Solutia ecuatiei de grad I: %lf". 2.h> //rezolvarea ec. if (a==0) { if (b==0) printf("ecuatie imposibila!"). } else printf("Ecuatia de gr. printf("\nDati coeficientii ecuatiei de gradul II: ").x1. II: %lf.x2).x1). 4. Scrieţi un program C pentru rezolvarea ecuaţiei de gradul II 32 .Algoritmi şi structuri de date Sintaxa funcţiei main: void main (void) { //declaraţii locale //instrucţiuni … } Exemplu de program C: #include <stdio. gr. Să se citească n numere întregi şi să se numere câte elemente sunt pozitive.c. } } else { delta=b*b-4*a*c. } } Probleme: 1. %lf". x2=(-b+sqrt(delta))/2*a.%lf".

/ sau * şi afişează rezultatul operaţiei aritmetice corespunzătoare. Să se scrie un program C care calculează şi afişează puterile lui 2 până la n.Algoritmi şi structuri de date Obs.. 7. 33 . 22. adică 2. Pentru scrierea unui program care rezolvă ecuaţia de gradul 2 este necesară cunoașterea funcţiei de extragere a radicalului: sqr(<parametru>) al cărei prototip se află în biblioteca math. Să se scrie un program C care citeşte două numere reale şi un caracter: + .h 5. . 6. …. 2n . Să se scrie un program C care afişează toţi multiplii de k mai mici decât o valoare dată n.23.

Programele C permit o traducere a descrierilor algoritmice bazate pe subalgoritmi prin posibilitatea definirii unor funcţii utilizator care tratează aspecte parţiale ale rezolvării. parametrii formali sunt înlocuiţi de parametrii actuali: Apelul unei funcţii se face prin construcţia: 34 .Algoritmi şi structuri de date III. Prototipul funcţiei: este format de antetul funcţiei din care poate să lipsească numele parametrilor formali: <tip>NumeFuncţie(<tip1>. .<tipn>) .În antetul unei funcţii este precizat tipul de dată pe care îl returnează <tip>. La apelul funcţiei. RECURSIVITATE.Parantezele rotunde (…. Aceste funcţii de biblioteci pot fi apelate în programele dezvoltate dacă în prealabil s-a inclus biblioteca corespunzătoare prin directive de preprocesare #include. Limbajul C este suplimentat prin biblioteci care conţin funcţii înrudite prin intermediul cărora se realizează diferite operaţii. Un program C poate conţine funcţia principală main dar şi alte funcţii definite de către programator sau funcţii predefinite din bibliotecile standard.<tipn><argn>) { <instrucţiuni de declarare de tip a variabilelor locale> …………… <instrucţiuni> …………… } .…. prin intermediul căreia se descrie un subalgoritm de rezolvare a unei subprobleme.Corpul funcţiei cuprinde între acolade {} conţine partea de declarare a variabilele locale funcţiei respective şi partea de prelucrare a datelor: secvenţă de instrucţiuni prin care se prelucrează variabilele locale sau altele cu caracter global. . FUNCTII. TRANSMITEREA PARAMETRILOR. caz în care funcţia nu returnează nimic. argumentele precizate în antetul acesteia se numesc parametrii formali. (spre exemplu funcţiile standard scanf şi printf ale bibliotecii stdio) Funcţiile definite de utilizator au următoarea sintaxă: <tip>NumeFuncţie(<tip1><arg1>.) ce urmează numelui funcţiei delimitează lista argumentelor funcţiei. Acesta poate fi void.…. .Fiecărui argument îi este precizat tipul şi numele sub care este referit în cadrul funcţiei curente. Într-un capitol precedent am subliniat importanţa subalgoritmilor în descrierea unor metode de rezolvare a problemelor. //declararea unei funcţii La definirea unei funcţii. Funcţia reprezintă o unitate de sine stătătoare a unui programului C.

folosită în cadrul funcţiei cu tip definite. În caz contrar. Tipul funcţiei reprezintă în fapt tipul de dată al valorii pe care o returnează. //declaraţie de variabilă locală for(i=0.Algoritmi şi structuri de date NumeFuncţie(pa1. Funcţie fără tip şi fără parametrii Există posibilitatea definirii de către utilizator a unor funcţii care nu returnează nimic şi lista parametriolor acestora este vidă: void NumeFuncţie () { … //corpul functiei } Apelul funcţiei se face printr-o construcţie de forma: NumeFuncţie(). şi are rolul de a reveni în programul apelant ş de a returna acestuia o valoare de tipul precizat. funcţia nu returnează valoare. pa2. are sintaxa: return <expresie>. Funcţia poate fi apelată printr-o instrucţiune de asignare: ValoareReturnată= NumeFuncţie().i<=9.i++) printf(“%d”. //membrul stâng al instrucţiunii este o variabilă de tipul returnat de funcţie 35 . Funcţii cu tip Dacă unei funcţii îi este precizat tipul (diferit de void) acest lucru ne spune că funcţia returnează codului apelant o valoare de acest tip. Categorii de funcţii: 1.h> void Cifre() { int i. //apelul functiei } Cifre 2. Funcţia va fi apelată în funcţia main. Exemplu: Se defineşte o funcţie Cifre care tipăreşte cifrele arabe. #include <stdio. } void main(void) { Cifre(). pan). Instrucţiunea return.i). … .

CitesteValoare()). Intrările funcţiei sunt descrise prin lista parametrilor formali ce conţine nume generice ale datelor prelucrate în corpul funcţiilor. se vor transmite prin valoare parametrii actuali şi fiecare parametru formal din antetul funcţiei este înlocuit cu valoarea parametrului actual.//apel printf(“valoarea este: %d”. … . nr2=6. … . care citeşte o valoare numerică întreagă de la tastatură şi returnează această valoare funcţiei main apelantă. tipn pfn) {…}. //declaraţie de variabilă locală scanf(“%d”. pan). Funcţii parametrizate Funcţiile cu parametri corespund acelor subalgoritmi care prelucrează date de intrare reprezentând rezultate intermediare ale algoritmului general. } void main(void) { int valoare. pa2. pe care aceasta o va afişa pe ecran. min. } 3. min=minim(nr1.nr2). return numar. #include <stdio.Algoritmi şi structuri de date Exemplu: Programul C conţine o funcţie CitireValoare. nr1=7. // la apel a va primi valoarea 6 şi b – valoarea 7 Fie funcţia: tip NumeFuncţie(tip1 pf1. Exemplu: int minim(int a. valoare).nr2. La apelul: NumeFuncţie(pa1. Parametrii efectivi sunt transmişi la apelul funcţiilor. // la apel a va primi valoarea 7 şi b – valoarea 6 min=minim(nr2. tip2 pf2.&numar). valoare=CitesteValoare(). În limbajul C transmiterea parametrilor actuali funcţiilor apelate se face prin valoare: înţelegând prin aceasta că valorile curente ale parametrilor actuali sunt atribuite parametrilor generici ai funcţiilor.h> int CitesteValoare() { int numar. Dacă parametrul 36 . } void main(void) //varianta compacta { printf(“valoarea este: %d”.nr1). aceştia înlocuind corespunzător parametrii generici specificaţi. int b) { return ( (a>b)?a:b) } //apelul din functia main int nr1.

Algoritmi şi structuri de date actual este o expresie. // variabilă pointer spre tip Prin această declaraţie s-a introdus o variabilă ale cărei valoare este adresa unei zone de memorie ce poate reţine o dată de tipul tip. 37 . În schimb. şi p un pointer (variabilă de tip pointer) care are ca valoare adresa variabilei x. În exemplul anterior efectul dorit este acela ca variabila n după apel să păstreze rezultatul ridicării la pătrat.n). Exempul următor evidenţiază acest aspect: #include <stdio. Sintaxa de declarare a unei variabile pointer: tip * p.n). vom defini în continuare noţiunea de pointer. Observaţie: modificările aduse parametrilor formali în cadrul funcţiei apelate nu afectează valorile parametrilor actuali. Pointeri Pointer = variabilă care are ca valori adrese de memorie. aceasta este evaluată la o valoare. // efectul: valoarea lui n in functie este 25 printf(“ valoarea lui n dupa de apel este %d”. Fie x o variabilă simplă de tipul tip: tip x. //efectul: valoarea lui n dupa de apel este 5 (!nu s-a modificat parametrul actual) } În multe situaţii este necesar ca efectul operaţiilor din corpul unei funcţii apelate asupra parametrilor de intrare să fie vizibil şi în corpul apelant. transmiterea prin adresă realizată prin intermediul variabilelor de tip pointer asigură modificarea valorilor parametrilor actuali. printf(“ valoarea lui n inainte de apel este %d”. //n este 25 } void main(void) { int n=5.h> void putere(int n) //ridică la pătrat valoarea n şi afişează rezultatul { n=n*n. Pentru a înţelege mecanismul transmiterii parametrilor prin adresă în limbajul C. Transmiterea prin valoare nu permite acest lucru.n). // semnificaţia: lui p i se atribuie adresa variabilei x. // efectul: valoarea lui n inainte de apel este 5 putere(n). Pentru a face o atribuire unei variabile de tip pointer p se foloseşte construcţia: p=&x. ulterior este copiată în parametrul formal corespunzător. printf(“ valoarea lui n in functie este %d”.

2. Transmiterea prin prin adresa este realizată astfel: 1. tip NumeFuncţie(tip1 *p1. Variabila pointer p: Transmiterea parametrilor prin adresă Adresa Transmiterea prin adresă se realizează prin variabile de tip pointer şi ne asigură de modificarea valorii parametrilor actuali.Algoritmi şi structuri de date Prin construcţia &x ne referim la adresa variabilei x. Parametrii formali ai funcţiei se declară ca fiind de tip pointer. Prin construcţia *p ne referim la valoarea variabilei x. argumentele funcţiei sunt adresele parametrilor actuali.n). … . printf(“ valoarea lui n inainte de apel este %d”. //n este 25 } void main(void) { int n=5. … .n). // efectul: valoarea lui n inainte de apel este 5 putere(&n).//ridică la pătrat valoarea referită de p: *p printf(“ valoarea lui n in functie este %d”.h> void putere(int *p) //ridică la pătrat valoarea n şi afişează rezultatul { *p=*p* *p.*p). // efectul: valoarea lui n in functie este 25 printf(“ valoarea lui n dupa de apel este %d”. //efectul: valoarea lui n dupa de apel este 25 modificat parametrul actual) } (!s-a 38 . La apel. NumeFuncţie(&p1. tipn *pn) {…}. - Operatorul * se numeşte operator de indirectare Operatorul & se numeşte operator adresă Expresia *&x are aceiaşi semnificaţie cu expresia x. tip2 *p2. &p2. &pn) Exemplu: #include <stdio.

folosindu-ne de transmiterea parametrilor prin adresă (prin pointeri). b=auxiliar. int *b) { int auxiliar.Algoritmi şi structuri de date Exerciţiu: Să se scrie o funcţie care interschimbă valorile a două variabile transmise ca parametrii. } //Apelul: int x. Prezentăm în continuare două variante de implementare. x=1.prin adresă. punând în evidenţă funcţia care determină minimul dintre două valori. Pentru construirea funcţiei cerute este utilă transmiterea prin adresă. ecuaţia are soluţii complexe . prima dintre acestea foloseşte transmiterea implicită prin valoare. interschimbare(x.y.0 . x=1. *a=*b. void interschimbare(int b) { int auxiliar.x2). a=b. } a. x=2.1. ultimii doi .&y) //Efectul Inainte de apel: x=1 y=2 După apel: x=2 y=1 1. int void interschimbare(int *a. Cea de-a doua este varianta corectă. deoarece. Funcţia returnează un număr întreg cu semnificaţia: . ecuaţia nu este de gradul II . auxiliar=*a. 39 . ecuaţia are soluţii reale Primii trei parametrii se transmit prin valoare. Funcţia va avea 5 argumente: coeficienţii ecuaţiei (a.y. auxiliar=a. Scrieţi o funcţie care rezolvă ecuaţia de gradul II. *b=auxiliar.b.c) şi posibilele soluţii (x1.y) //Efectul Inainte de apel: x=1 y=2 După apel: x=1 y=2 Probleme: //Apelul: int x. interschimbare(&x. fapt pentru care nu corespunde rezultatului dorit. efectul interschimbării trebuie să fie vizibil şi în codul apelant.-1. 2. Dezvoltaţi un program C care determină minimul dintr-un şir de numere citite de la tastatură (fără a utiliza tablouri). x=2.

condiţia de terminare a recursivităţii se deduce din observaţia că 1! are valoarea 1. P(k-1)=P(k-2)*(k-1). Apelul iniţial al funcţiei factorial se va face pentru parametrul actual nreprezentând data de intrare a programului. pan). Practic. Execuţia pas cu pas: 40 . Analiza problemei: Pornind de la observaţia că produsul P(n)=1*2*…(n-1)*n se mai poate formula ca: P(n)=P(n-1) * n. Chiar dacă variabile locale au acelaşi nume cu cele existente înainte de apelarea funcţiei. În problema calculului n!.<tipn><argn>) { <instrucţiuni de declarare de tip a variabilelor locale> …………… <instrucţiuni> …………… NumeFuncţie(pa1. } … //apelul functiei int p. ceea ce nu mai necesită un calcul suplimentar. else return 1. şi nu există conflicte de nume. int factorial(int k) { if (k>1) return (k*factorial(k-1)). ultimele variabile create sunt luate în considerare în operaţiile conţinute de funcţie.Algoritmi şi structuri de date RECURSIVITATE Recursivitatea se obţine prin instrucţiunea de apel a unei funcţii în corpul definirii funcţiei respective: <tip>NumeFuncţie(<tip1><arg1>. //auto apelul funcţiei …………… } La fiecare apel al funcţiei recursive. vom defini o funcţie factorial care tratează problema determinării lui P(k) pe baza formulei anterioare. p=factorial(n). … . Funcţia returnează la ieşirea din apel rezultatul dorit n!. pe stiva programului sunt depuse noul set de variabile locale (parametrii). pa2. Funcţia autoapelantă trebuie să conţină o condiţie de terminare a recursivităţii. presupunând că P(k-1) este calculabil după acelaşi procedeu.…. valorile lor sunt distincte. Problemă 1: Să se calculeze P(n)=n! printr-o funcţie recursivă.

Apelul factorial(2) va produce transmiterea valorii 2 la parametrul formal k şi predarea controlului funcţiei apelate pentru valoarea curentă a parametrului 4. factorial(n) n* factorial n* (nRecursivitatea poate fi de mai multe feluri. Din condiţia falsă 1>1 rezultă că la ieşirea din acest apel se va return 1 şi funcţia nu se mai apelează.Algoritmi şi structuri de date Considerăm n=3 1. în funcţie de numărul de apeluri conţinute de funcţia (subalgoritmul) recursivă: 1. recursivitate liniară: funcţia conţine cel mult un autoapel exemplu funcţia factorial descrisă anterior 2. Din condiţia adevărată 3>=1 rezultă amânarea revenirii din funcţie şi un nou apel factorial(2).oricare termen este format prin însumarea celor doi termeni precedenţi: 41 n .primii doi termeni ai şirului sunt 0. 7. Revenirea din ultimul apel este urmată de revenirile în cascadă din apelurile precedente în ordinea inversă. ceea ce conduce la rezultatul 3*2*1 = 6. Apelul factorial(1) va produce transmiterea valorii 1 la parametrul formal k şi predarea controlului funcţiei apelate pentru valoarea curentă a parametrului 6. 5. la ieşirea din primul apel se va return 3*factorial(2). 3.1: o Fibonacci(0)=0 o Fibonacci(1)=1 . la ieşirea din acest al doilea apel se va return 2*factorial(1) . Condiţia adevărată 2>=1 produce o nouă apelare factorial(1) cu amânarea revenirii din apelul curent. La apelul iniţial factorial(n) se transmite valoarea 3 parametrului formal k şi se predă controlul funcţiei apelate 2. recursivitate neliniară: funcţia conţine două sau mai multe apeluri recursive – exemplu fibonacci descris în continuare Problema 2: Se cere determinarea celui de-al n-lea termen din şirul lui Fibonacci. Analiza problemei: Se cunoaşte că: .

int n) { if(n==0) return m. Operaţiile posibile cu lista folosită sunt: adăugarea unui nou element în capătul listei şi extragerea unui element din capătul listei(vezi conceptul de stivă) 2. apoi se trece la pasul 2. return cmmdc(n. Câttimp condiţia de continuare a recursivităţii este adevărată execută: .Execută instrucţiunile funcţiei recursive . dacă b = 0 2.b) poate fi: 1. dacă b≠0 Prin a mod b se înţelege restul împărţirii întregi a lui a la b Pe baza observaţiei precedente. Există diferite metode de eliminare a recursivităţii din programele C. Descriem în continuare procedura de eliminare a recursivităţii liniare: 1.m%n). } //autoapel Recursivitatea este o tehnică costisitoare datorită spaţiului de memorie blocat pentru reţinerea variabilelor locale create la fiecare apel. În această listă vor fi salvaţi parametrii formali şi variabilele locale ale funcţiei recursive. Determinarea celui mai mare divizor comun a două numere a şi b. Se utilizează o listă care se iniţializează ca fiind vidă.Algoritmi şi structuri de date o Funcţia recursivă este: Fibonacci(k)=Fibonacci(k-1)+ Fibonacci(k-2) int Fibonacci(int k) { if (k<=1) return k.Adaugă (salvează) în listă valorile actuale pentru parametrii funcţiei recursive si variabilele locale . funcţia recursivă cmmdc se construieşte astfel: int cmmdc(int m. pentru a preîntâmpina acest neajuns. cmmdc(b. Dacă lista este vidă se termină execuţia algoritmului. se calculează valoarea dorită şi se executa instrucţiunile ce apar după apelul funcţiei recursive. (prin funcţie recursivă) Analiza problemei: cmmdc(a. //două autoapeluri } Problema 3. else return Fibonacci(k-2) + Fibonacci(k-1). a. se extrage mulţimea de variabile din capătul acesteia. 42 . Dacă la terminarea pasului 2 lista nu este vidă. a mod b).Modifica valorile argumentelor funcţiei recursive (actualizarea parametrilor) 3.

reprezintă adresa primului element al sau. tablouri unidimensionale (vectori) sau multidimensionale.Referirea unui element al tabloului multidimensional se face prin identificatorul tabloului (numele) şi specificarea poziţiei pe fiecare dimensiune: . Unde N1. . Unde <tip> este tipul de date al elementelor tabloului. N2 … Nn reprezintă numărul de elemente pe fiecare dimensiune. Tablourile în limbajul C sunt variabile cu indici. Fiecare poziţie i din cadrul tabloului conţine elementul xi. adresa lui T[0]. T .Referirea unui element al tabloului se foloseşte numele tabloului şi indicele corespunzător. TEHNICI DE SORTARE. //tablou de 100 intregi Numele tabloului. Spre exemplu: int x[100]. . Declararea unui tablou unidimensional în limbajul C se face astfel: <tip> NumeTablou [NumarElemente].Algoritmi şi structuri de date IV. Pot fi de două categorii.NumeTablou [i1] [i2] …[in]. Exemplu: int A[20] [30]. NumeTablou este un identificator prin care este denumit vectorul.Observaţie: numele tabloului (spre ex.Tablourilor li se asociază locaţii de memorie consecutive. La transmiterea vectorilor ca parametrii ai unor funcţii se va ţine cont de acest aspect. TABLOURI. respectiv o valoare de tipul specificat. La nivel abstract.Pentru a referi elementul de pe poziţia i vom scrie x[i-1]. .x2. . Observaţie: Numele unui tablou în C este de fapt un pointer. . Exemplu: int T[100]. Declararea tablourilor n-dimensionale: <tip> NumeTablou [N1] [N2] …[Nn]. iar numele tabloului conţine adresa primului element memorat. avînd ca valoare adresa primului sau element. . //declararea unei matrice (tablou bi-dimensional) de 20 linii si 30 coloane. respectiv. NumarElemente este un număr natural sau o constantă cu valoare întreagă pozitivă prin care se precizează numărul elementelor din tablou. … . de aceeaşi lungime în care sunt stocate valorile fiecărui element. 43 .xn. un tablou unidimensional se poate defini ca o listă ordonată de n valori de acelaşi tip: x1.Primul element al tabloului bidimensional declarat mai sus este A[0] [0]. dat fiind faptul că primul element al şirului este x[0]. x) este o variabilă a cărei valoare este adresa de memorie a primului element din şir (pointer).

//p se măreşte cu 2.Algoritmi şi structuri de date Tablourile sunt structuri de date: .incrementare/decrementare . printr-o operaţie elementară se poate accesa oricare alt element al tabloului. p++. Operaţii cu pointeri: tab Asupra pointerilor se pot efectua următoarele operaţii: . elementele tabloului sunt grupate. int *p. Pointeri şi tablouri.compuse: sunt formate din mai multe elemente (de acelaşi fel) . //p este un pointer la tipul <tip> Efectul operaţiilor de incrementare/decrementare este următorul: p++ şi ++p echivalent cu: p=p+dim p-. respectiv. //tab – este adresa elementului t[0] Observaţie: Deoarece memoria alocată unui tablou este o zonă contiguă. Operaţii cu pointeri Între tablouri şi variabilele pointer există o strânsă legătură: numele unui tablou este un pointer.ordonate: există o relaţie de ordine dată de poziţia elementelor în tablou .şi --p echivalent cu: p=p-dim unde: dim reprezintă dimensiunea exprimată în octeţi a unei variabile de tipul <tip> Exemplu. p-k sunt expresii valide în limbajul C. având semnificaţiile următoare: 44 . cunoscând adresa primului element.adunarea/scădere cu un întreg . deoarece o variabilă de tipul int se memorează pe 2 octeţi Adunarea şi scăderea unui întreg Fie: <tip> *p. Expresiile: p+k.omogene: toate elementele unui tablou au acelaşi tip Tablourile unidimensionale vor fi denumite în continuare vectori. int k. fiind de fapt adresa primului element al tabloului respectiv: tab[0] tab[1] Exemplu: int tab[100].diferenţa a doi pointeri Fie declaraţia următoare: <tip> *p.

tab tab+1 dim tab+ Şiruri de caractere tab[i] .... valoarea p+k este egală cu valoarea lui p mărită cu k*dim . //tablou de 100 întregi int i. unde dim. Această dimensiune se poate determina prin calculul elementar: k=(j-i).Algoritmi şi structuri de date p+k . respectiv. (tab) – adresa primului element tab[0] (tab+1) – adresa celui de-al doilea element tab[1] (tab+i) – reprezintă adresa celui de-al (i-1) -lea element al tabloului. Fie tabloul tab şi doi pointeri p şi q care adresează elementele tab[i] şi tab[j]. unde dim .h.dimensiunea exprimată în octeţi a tipului <tip> Accesarea elementelor unui tablou poate fi făcută şi prin operaţii cu variabile pointer: dim octeti tab[0] dim tab[1] Exemplu: int tab[100]. unde dim. Prototipurile funcţiilor care manipulează şiruri de caractere se află în fişierul antet string. Între adresele p şi q se află un număr de (j-i) elemente. Diferenţa p-q este un număr întreg k.. (j-i)*dim octeţi. reprezintând numărul de elemente care desparte cele două adrese..reprezintă o adresă de memorie.are valoarea p micşorată cu k*dim .. respectiv este adresa elementului tab[i] *(tab+i) – reprezintă valoarea elementului tab[i] Diferenţa a doi pointeri: Două variabile de tip pointer prin care se referă elementele aceluiaşi tablou pot fi scăzute.dimensiunea exprimată în octeţi a tipului <tip> p-k .dimensiunea necesară reprezentării unui element al tabloului. Operaţiile cu şiruri de caractere se efectuează prin apelul unor funcţii de bibliotecă specifice. 45 .

h pune la dispoziţie funcţii speciale dedicate operaţiilor specifice.codul numeric al celui de-al doilea caracter din sir sir sau psir – adresa primului caracter sir+1 sau psir+1 – adresa celui de-al doilea caracter din sir Pentru manipularea şirurilor de caractere. excluzând marcatorul NULL) Copierea unui şir de caractere - este operaţia prin care un şir de caractere sursă este copiat într-o altă zonă de memorie ataşată unui alt şir de caractere destinaţie funcţia specifică este: strcpy char * strcpy(char *destinatie. Ex: char sir[20]. Lungime unui şir de caractere Lungimea unui şir de caractere este definită prin numărul caracterelor care intră în compunerea şirului. (inclusiv caracterul NULL) Funţia strcpy returnează la revenire adresa de început a zonei în care s-a copiat şirul (pointer-ul destinatie) Exemplu: Interschimbarea a două şiruri de caractere: void schimb(char a[20]. pentru reţinerea codului numeric corespunzător (reprezentarea internă).Algoritmi şi structuri de date Orice şir de caractere se va păstra într-o zonă de memorie contiguă. Funcţia standard strlen este utilă în determinarea lungimii unui şir de caractere: unsigned strlen(const char *s) Exemplu: char const *psir = “text” unsigned l. Accesarea caracterelor din compunerea şirului va fi posibilă prin variabile indexate sau prin operaţii cu pointeri: sir[0] sau *psir – codul numeric (ASCII) al primului caracter din sir sir[1] sau *(psir) . Ne reamintim că unei variabile de tipul char îi este necesară o zonă de memorie de 1 octet. l= strlen(psir). Un şir de caractere. 46 . putem utiliza în programe declaraţii de forma: char *psir. const char *sursa) Funcţia are ca efect copierea tuturor caracterelor în zona de memorie referită de destinatie din zona de memorie referită de sursa. biblioteca standard string. declarat ca tablou unidimensional de tip char va ocupa o zonă de n octeţi terminată printr-un octet suplimentar cu valoarea '\0’ – caracterul nul.char b[20]) { char aux[20]. Folosind relaţia dintre tablouri şi pointeri. // lui l i se va atribui 4 //numărul de caractere. prin care s-a marcat terminarea şirului.

if (i%2) i--. char * strcmp(const char *sir1.aux). while (i>=0) { pass[n]=cuv[i]. i=(strlen(cuv)-1).h> void main() { char pass[20]. dacă sir1>sir2 Exemplu: Program de generare a parolelor.n. strcpy(b. dac ă sir1=sir2 . n=0.const char *sursa) Apelul funcţiei are ca efect copierea şirului de la adresa sursa în zona de memorie imediat următoare şirului de la adresa destinatie. dacă sir1<sir2 . pass[0]=NULL. printf("dati cuv "). printf("parola este %s". Compararea şirurilor de caractere este o operaţie utilă în probleme care cer ordonarea lexicografică a unor secvenţe de text. funcţia returnează adresa destinaţie.pass).Algoritmi şi structuri de date strcpy(aux.h> #include <string. La revenire. n++.a). } pass[n]=NULL.valoare negativă. int i.&cuv).const char *sir2) Funcţia strcmp returnează: .scanf("%s". Pentru un cuvânt dat. strcpy(a.0. #include <stdio. } Concatenarea a două şiruri de caractere Se realizează prin apelul funcţiei strcat: char * strcat(char *destinatie. parola va fi obţinută prin scrierea de la dreapta la stânga a caracterelor de pe poziţiile impare. Compararea a două şiruri de caractere Operaţia de comparare a două şiruri presupune verificarea codurilor ASCII ale caracterelor din compunerea şirurilor. } 47 .b). i-=2.cuv[20].valoare pozitivă.

citire_tablou(tablou.Algoritmi şi structuri de date Operaţiile specifice cu vectori sunt: 1. for(i=0. scanf(“%d”. sortarea a. pentru accesarea şi/sau modificarea elementelor (citirea şi afişarea) b. minimul/maximul dintr-un vector 4.i++) printf(“%d”. for(i=0. sortarea prin selecţie b. printf(“dati dimensiunea tabloului:”). &t[i]).i<n. &dim_tablou). sortarea prin metoda bulelor (prin interschimbare) Alte variante de sortare vor fi descrise în capitolele dedicate metodelor de programare (ex. sortarea rapidă şi sortarea prin interclasare) 1. interclasarea a doi vectori 7. t[i]). pentru numărarea elementelor ce verifică o condiţie 2. } … int tablou[30]. Oferim o altă variantă de funcţie care citeşte 48 . int *n) { int i.n). //apelul funcţiei de citire tablou afisare_tablou(tablou. sortarea prin numărare c. //apelul funcţiei de afişare tablou Legătura dintre pointeri şi tablouri influenţează maniera de transmitere a tablourilor ca parametri unei funcţii. int dim_tablou10. căutarea secvenţială 3.i++) scanf(“%d”. inserarea şi ştergerea unui element pe o poziţie dată 5. parcurgerea a. } void afisare_tablou(int t[]. dim_tablou).i<*n. concatenarea a doi vectori 6. int n) { int i. Parcurgerea tablourilor //Parcurgerea tablourilor –citirea şi afişarea unui vector void citire_tablou(int t[].

operaţia de căutare presupune parcurgerea element cu element a vectorului.i<*n. până la ultima). contor=0.i++) { scanf(“%d”. for(i=0. int *n) { int i. int cheie) { int i. for(i=0. iar parametrul formal este declarat ca pointer: void citire_tablou_II(int *pt.dacă elementul curent este mai mic decât minimul presupus. printf(“dati dimensiunea tabloului:”). în ordinea dată de indecşi //Căutarea : determinarea poziţiei pe care se află valoarea căutată int cauta_cheie(int t[]. comparându-se minimul presupus cu valoarea curentă din vector .i++) if (t[i]>0) contor++. for(i=0. return contor. } } //Parcurgerea tablourilor –numărarea elementelor ce verifică o condiţie dată int numara_pozitive(int t[].se presupune că elementul de pe prima poziţie din vector este minimul . } 2.i<n.n).i++) if (t[i]==cheie) return i. int n) { int i. în care se folosesc expresii cu pointeri.//dacă nu s-a găsit valoarea se întoarce -1 } 3. scanf(“%d”. &valoare).i<n. int n. *(p+i)=valoare. valoare. Căutarea secvenţială . Determinarea minimului/maximului dintr-un vector Principiul este următorul: .Algoritmi şi structuri de date un tablou unidimensional.se parcurge vectorul element cu element (de la a 2-a poziţie. //poziţia valorii căutate return -1. contor. se va actualiza valoarea minimului. în caz contrar se trece la următorul element fără a altera minimul presupus 49 .

.inserarea propriu-zisă a noului element pe poziţia k.i++) if (x[i]<min0) min=x[i].micşorarea dimensiunii vectorului cu 1 5. dimensiunea n Fie min=x1 Pentru i de la 2 la n //se parcurge vectorul Dacă xi<min atunci min=xi SfDacă SfPentru Tipăreşte min SfAlgoritm // codul sursă C corespunzător algoritmului descris //se citesc în prealabil dimensiunea şi elementele vectorului x int x[100]..Algoritmi şi structuri de date Pentru determinarea maximului procedeul este similar... respectiv n2. printf(”Minimul este:%d”. 4.. Algoritmul descris în pseudocod pentru determinarea minimului dintr-un vector x1.x2.xn este următorul: Algoritm Minim este Citeşte: x1. . Concatenarea este 50 . Inserarea/ştergerea unui element Inserarea unui element nou pe o poziţie k în vector presupune efectuarea următoarelor operaţii: . n //vectorul x de n elemente. modificându-se doar operatorul logic utilizat în condiţia de actualizare a maximului presupus..mărirea dimensiunii vectorului cu 1.. int i. Concatenarea a doi vectori Rezultatul concatenării a doi vectori de dimensiune n1. for(i=0.x2. format prin copierea elementelor primului vector urmată de copierea elementelor celui de-al doilea vector. min.mutarea la stânga cu o poziţie a tuturor elementelor situate la dreapta poziţiei k . min=t[0].xn. n. citire_tablou(x. min).&n).. Ştergerea elementului de pe poziţia k presupune operaţiile următoare: . este un al treilea vector de dimensiune n1+n2.mutarea cu o poziţie la dreapta a tuturor elementelor situate pe poziţiile mai mari decât k .i<n.

.. prin copierea elementelor vectorilor X şi Y păstrând relaţia de ordine.Z presupune utilizarea a trei variabile cursor cu semnificaţia poziţiei curente în vectorul corespunzător: Fie i – cursorul care indică poziţia curentă în vectorul X Fie j – cursorul care indică poziţia curentă în vectorul Y Fie k – cursorul care indică poziţia curentă în vectorul Z Algoritmul Interclasare este: Citeşte n. se vor copia în Z Parcurgerea vectorilor X..z[2]. Principiul este de a completa element cu element vectorul rezultat Z. respectiv m: x[1]... Operaţia de interclasare se aplică asupra a doi vectori sortaţi rezultând un al treilea vector cu proprietăţile: .Algoritmi şi structuri de date o operaţie simplă fapt pentru care vor lăsa ca exerciţiu definirea unei funcţii C care realizează această operaţie.. şi cel mai mic dintre acestea se copiază în vectorul Z 2... Observaţie: operaţia de concatenarea diferă semnificativ de operaţia de interclasare.x[2]. Dacă au mai rămas elemente neparcurse în vectorul Y.este ordonat Fie X şi Y doi vectori ordonaţi crescător... 6.conţine toate elementele vectorilor iniţiali . Dacă au mai rămas elemente neparcurse în vectorul X...x[2]. se vor copia în Z 3. pornind de la două secvenţe ordonate de elemente se formează o secvenţă care conţine toate elementele primelor două şi este ordonată.. Interclasarea a doi vectori Interclasarea este procedeul prin care. aceştia se parcurg secvenţial: a.x[n] y[1]. x[1].y[m] Fie i=1.j=1 //poziţiile de start în vectorii de intrare Fie k=1 //poziţia de start în vectorul rezultat Câttimp(i<=n şi j<=m) Dacă x[i]<y[j] atunci z[k]=x[i] 51 ... format din toate elementele celor doi vectori de intrare.y[2]. Cât timp mai există elemente de parcurs în ambii vectori: X.. de dimensiune n.x[n] Citeşte m.. Se compară elementele curente ale celor doi vectori X şi Y..Y.Y. y[1].y[2]...y[m] Se doreşte obţinerea vectorului ordonat z: z[1].z[p]. Interclasarea se realizează prin executarea următoarelor etape: 1.

. Poziţia este precizată prin 52 . ..Algoritmi şi structuri de date k=k+1 //trecere la următoarea poziţie în vectorul Z i=i+1//trecere la următoarea poziţie în vectorul X Altfel z[k]=y[j] k=k+1 //trecere la următoarea poziţie în vectorul Z j=j+1//trecere la următoarea poziţie în vectorul Y SfDacă SfCâttimp Dacă i<n atunci //au mai rămas elemente în vectorul X Pentru w de la i la n //se copiază elementele rămase în X z[k]=x[w] k=k+1 SfPentru SfDacă Dacă j<m atunci //au mai rămas elemente în vectorul Y Pentru w de la j la m //se copiază elementele rămase în Y z[k]=y[w] k=k+1 SfPentru SfDacă p=k-1 // sau p=n+m Tipăreşte z[1].linii şi coloane – specifică dimensiunea memoriei alocate tabloului identificat prin NumeTablou Un tablou bidimensional este reprezentat într-o succesiune de locaţii de memorie referite prin acelaşi identificator (NumeTablou). . Un caz particular este cel al tablourilor bidimensionale...tipElement este tipul de dat[ al elementelor .. ...   x mn   Toate elementele unei matrice sunt de acelaşi tip şi dimensiunile matricei se referă la numărul de linii (m) şi de coloane (n). Fiecare element al tabloului este referit prin poziţia sa în cadrul şirului.... x1n   x2n  ..z[p] SfAlgoritm //Interclasare TABLOURI n-DIMENSIONALE Adesea. Structura de matrice este reprezentată în matematică prin:  x11  x X =  21 .. cunoscute sub denumirea de matrice...  x  m1 x12 x 22 . declararea unei structuri de tablou bi-dimensional se face prin construcţia: tipElement NumeTablou [linii] [coloane]. xm2 . În limbajul C...z[2].. unde: . este necesară prelucrarea datelor structurate în tablouri multidimensionale..

astfel încît în prelucrările tablourilor.j. care reprezintă cele două dimensiuni (linie şi coloană).&n). scanf(“%d”.scanf(“%d”. j<n. int i. Numele tabloului este adresa primului element memorat. printf(“\nIntroduceti nr. i<m. în mod uzual se consideră numerotarea liniilor şi a coloanelor începând de la 0. j++) printf(“%d”. i++) for(j=0. necesari memorării elementelor acestei structuri de date. afişarea unei matrice. Funcţiile respective vor avea ca parametri atât dimensiunile tabloului (numărul de linii şi coloane) dar şi adresa primului element (numele tabloului). Codul C de mai jos are ca efect citirea unei matrice de numere întregi: int mat[10][10]. Prin declaraţia tipElement NumeTablou [linii] [coloane] s-au alocat în memorie un număr de octeţi egal cu linii*coloane*sizeof(tipElement). coloana j se face prin construcţia NumeTablou[i][j]. } În programele de prelucrare a matricelor este utilă implementarea funcţiilor corespunzătoare operaţiilor de intrare –ieşire cu aceste structuri: citirea unei matrice. for(i=0. accesat prin construcţia: NumeTablou[0][0].&m). mat[i][j]).i. j++) { printf(“\n Matrice[%d][%d] = : ”. de linii ”).Algoritmi şi structuri de date două numere pozitive (indecşi). Zonă de memorie rezervată tabloului este contiguă.j). de coloane ”). Într-o manieră similară. Citirea elementelor unei matrice este posibilă prin citirea pe rînd a fiecărui element din compunerea sa. printf(“\nIntroduceti nr. } Afişarea valorilor unei matrici (în formatul uzual) este descrisă prin secvenţa următoare : printf(“\n Matricea este: ”). Parcurgerea în cadrul funcţiilor a tablourilor cu ajutorul 53 . i++) { for(j=0. i<m.scanf(“%d”. printf(“\n”). Ne reamintim că sintaxa NumeTablou[i] este echivalentă unei operaţii cu pointeri exprimată prin expresia: NumeTablou+i. expresia NumeTablou[i][j] poate fi exprimată printr-o operaţie cu pointeri: NumeTablou+i+j.&mat[i][j]). for(i=0. În acest sens se vor parcurge mulţimile indecşilor de linie şi coloană prin două instrucţiuni repetitive (ciclice) imbricate. Accesarea elementului de pe linia i. j<n.

de coloane m=").n). printf("\nIntroduceti nr. } printf("\n"). scanf("%d".i++) for(j=0. int s.j<m.i.int c[DimMax][DimMax]) { int i.int a[DimMax][DimMax]) { int i. } void produs(int n. printf("\n").i++) 54 . } } void citire_matrice(int *n.i<n.int m.j. int coloane) Exemplu: Programul următor determină suma şi produsul a două matrici: #include <stdio. de linii n=").&a[i][j]).j. int a[DimMax][DimMax].h> #define DimMax 10 //numărul maxim de elemente //pe linii si coloane void afisare_matrice(int n.i++) { for(j=0.scanf("%d".Algoritmi şi structuri de date indecşilor ascunde în fapt un calcul de adrese: cunoaşterea adresei de început a zonei de memorie alocate tabloului permite accesarea tuturor elementelor sale.j).int p.a[i][j]). antetul funcţiei de citire poate fi exprimat: void citire (tipElement NumeTablou[Mmaxim][Nmaxim].%d]=". int linii. for (i=0. for(i=0.i<*n. int c[DimMax][DimMax]) { int i.k.j<m.i++) for(j=0.int m. printf("\nIntroduceti elementele matricei\n").int a[DimMax][DimMax]) { int i.j<*m.j++) printf("%d ".int *m. for(i=0. } void suma(int n.i<n.j. int a[DimMax][DimMax]. int b[DimMax][DimMax]. printf("\nIntroduceti nr.i<n.scanf("%d".j.h> #include <conio.int m. În privinţa parametrilor formali.j++) { printf("a[%d. for(i=0.j++) c[i][j]=a[i][j]+b[i][j].m). int b[DimMax][DimMax].

p.Algoritmi şi structuri de date for(j=0.c).….c).m.&m.…. c[i][j]=s.k++) s=s+a[i][k]*b[k][j]. afisare_matrice(n. citire_matrice(&m..m.. int a[DimMax][DimMax]..a). } } void main() { int n.k<m.a.xn pozmin=i min=xi Pentru j de la i+1 la n Dacă xj <min atunci pozmin=j min=xj SfDacă sfPentru //interschimbare xi cu min xpozmin=xi 55 . produs(n. Sortarea prin selecţie Fie x1.. Principiul este acela de a considera subvectorul xi.a. afisare_matrice(n.xn şi de a determina minimul din această secvenţă.&p. suma(n. x1. urmând ca minimul rezultat să se interschimbe cu elementul xi...p.p.. } ALGORITMI DE SORTARE Sortarea reprezintă procedeul prin care o mulţime de elemente este aranjată după o relaţie de ordine dată..b).m.c).b.xn un vector de n elemente. for(k=0.j++) { s=0. Algoritm SortareSelecţie este: Citeşte n.b.n-1.x2. Procedeul se va repeta pentru oricare i=1.c). int b[DimMax][DimMax].….m. citire_matrice(&n.j<p.x2. int c[DimMax][DimMax].xn Pentru i de la 1 la n-1 //determină poziţia şi valoarea minimului subvectorului xi.

} //interschimb cu x[i] x[poz]=x[i].i<*n. SSort(a.poz. printf("\ndati n="). for(i=0. scanf("%d".n).n.int *n) { int i.i<n.&x[i]).h> void citireSir(int x[]. citireSir(a. for(i=0.j<n. x[i]=aux.i+1).i++) {printf("\nX[%d]=".. 56 .Algoritmi şi structuri de date xi=min SfPentru SfAlgoritm Exemplu: /*program care citeste un vector de numere intregi. n elementul minim aux=x[i]. } void SSort(int x[].x[i]). for(i=0.i++) printf("%d ". ordoneaza elementele vectorului prin metoda sortarii prin selectie si tipareste vectorul ordonat */ #include <stdio..i++) { //caut in sirul i .int n) { int i.aux.. scanf("%d".j++) if (x[j]<aux) {aux=x[j].poz=i.n). } } void tiparireSir(int x[]. } } void main() { int a[100].int n) { int i. for(j=i+1. poz=j.j.i<n.&n).

ceea ce produce un subvector format din doar elementul x1 şi subvectorul x2..xn.i++) { //caut pentru x[i] pozitia buna in subsirul din stanga 57 .. se va continua cu inserarea elementului xi+1 în x1... şi determinarea primul element xk care este mai mare decât xi.x2.xi+2. Inserarea unui element oarecare xi presupune determinarea poziţiei în care va fi depus. x1..x2.xi şi obţinerea subtabloului ordonat x1. de la dreapta la stânga... Se poate observa că primul subtablou este deja ordonat.. Aceasta se rezumă la parcurgerea subtabloului în care se va face inserarea... Se presupune că primul subtablou este deja ordonat şi se urmăreşte inserarea elementului xi în subtabloul ordonat astfel încât după efectuarea inserţiei subtabloul rezultat să rămână ordonat.xn...xi şi xi+1.i<n. însă în acest caz se va determina k – poziţia primului element xk care verifică condiţia că este mai mic decât xi.aux..xn Pentru i de la 2 la n //inserez elementul xi în subvectorul stâng x0=xi j=i-1 Câttimp (xj>x0) xj+1=xj j=j-1 SfCâttimp xj+1=x0 SfPentru SfAlgoritm Exemplu: Funcţia următoare realizează sortarea prin inserţie a unui vector.. În acel moment k devine poziţia pe care va fi depus xi..... Sortarea prin inserţie Fie x1..j..xn un vector de n elemente Principiul acestei tehnici este acela de a privi tabloul ca fiind împărţit în două subtablouri: x1. Parcurgerea subtabloului se poate face şi în sens invers.. Prima împărţire a tabloului este în punctul i=2.int n) { int i.. void SInsert(int x[]..Algoritmi şi structuri de date } tiparireSir(a..n).. de la stânga la dreapta... Acest procedeu se va continua privind tabloul iniţial ca două subtablouri : x1. Procedura se repetă până când nu mai sunt elemente de inserat în subtabloul stâng... Algoritm SortareInserţie este: Citeşte n.xn..k. Cunoscând că primul subtablou este deja ordonat (ne-am asigurat de acest lucru la operaţia de inserare precedentă).....xi-1 şi xi. for(i=1...

presupunerea s-a dovedit adevărată. //presupun vectorul ordonat for(i=0. k--.xn Repetă Ordonat=Adevărat Pentru i de la 1 la n-1 Dacă xi>xi+1 atunci Cheamă Interschimbare(xi.i<n-1.. x1. } // k+1 reprezinta pozitia pe care se insereaza aux x[k+1]=aux.Algoritmi şi structuri de date //1.i-1 k=i-1..k.. aux=x[i].. Algoritmul se va termina în condiţiile în care la o parcurgere anterioară.. void SBubble(int x[].//salvez valoarea curenta de inserat în aux while ((aux<x[k])&& (k>=0)) //primul element mai mic decat aux {x[k+1]=x[k].aux. presupunerea este anulată. //false sau adevarat int i.j.xi+1) Ordonat=fals SfDacă SfPentru Pânăcând (Ordonat=Adevărat) SfAlgoritm Exemplu: Funcţia următoare realizează sortarea prin interschimbare a unui vector.i++) 58 . completă a vectorului. însă la determinarea unei perechi de elemente consecutive care necesită interschimbare... Algoritm SortareInterschimbare este //ordonare crescătoare după valorile elementelor Citeşte n.x2.int n) { int cod. } } Sortarea prin interschimbare (BubbleSort ) Metoda sortării prin interschimbare constă în parcurgerea repetată a vectorului şi compararea pe rând a perechilor de elemente consecutive urmată de interschimbarea valorilor acestora în situaţia în care relaţia de ordine dorită nu este verificată.. La fiecare parcurgere se va presupune că vectorul este ordonat (marcarea acestei presupuneri se face printr-un cod). do { cod=0.

Să se determine numărul elementelor pozitive din compunerea matricei. Determinaţi cel mai mic număr pozitiv al unui vector de n numere întregi. Să se determine inversa unei matrice. 59 . 3.Algoritmi şi structuri de date { if (x[i]>x[i+1]) { //Interschimb vecinii aux=x[i]. ale cărei prime m coloane sunt copiate din matricea A şi ultima coloană este formată din valorile sumelor elementelor de pe fiecare linie a matricei A. Să se ordoneze crescător elementele de pe diagonala principală a matricei. Fiind dată o matrice A de n linii şi coloane. //am gasit vecini care nu respecta //relatia de ordine } } }while (cod!=0). Se dă o matrice cu numere întregi. 2. cod=1. formată din numere reale. 4. 5. Se dă un tablou bidimensional de numere întregi organizat în n linii şi n coloane (matrice pătratică). de n linii şi m coloane. să se construiască o matrice B de n linii şi m+1 coloane. x[i]=x[i+1]. x[i+1]=aux. } Probleme propuse spre rezolvare: 1.

Algoritmi şi structuri de date

V.

STRUCTURI. TIPURI DE DATE DEFINITE DE UTILIZATOR.

Datele pot fi clasificate în două categorii: - Date simple, izolate: tipurile acestora sunt predefinite în limbajul C (ex. int. double, float, char) - Date grupate, sau structurate: tablourile şi structurile, care permit prelucrarea globală dar şi individuală a fiecărui element al datei respective. Structurile: reprezintă o modalitate de grupare a datelor care nu sunt neapărat de acelaşi tip (spre deosebire de tablouri). Este necesară uneori referirea la o dată care cuprinde mai multe elemente, exemplu data calendaristică , având componentele: zi, luna, an. Prin structură în limbajul de programare, înţelegem o colecţie de date neomogene (de tipuri diferite), grupate sub un singur nume. Exista posibilitatea accesului la fiecare componentă a structurii , cât şi posibilitatea referirii în ansamblu a structurii definite. Sintaxa declarării unei structuri: struct NumeStructură { <instructiuni de declarare de tip> } nume1, nume2, …, numeN; unde: <instructiune de declarare de tip>::= <tip> <lista identificatori> Observaţie: Identificatorii: NumeStructură, nume1, …, numeN pot sa lipsească dar nu toţi odată. Astfel: - Dacă NumeStructură lipseşte, cel puţin nume1 trebuie să apară în construcţie - Dacă nume1, …, numeN lipsesc, atunci este obligatoriu ca NumeStructură se fie prezent. - În situaţia în care NumeStructură este prezent, prin acest lucru se defineşte un nou tip de dată denumit NumeStructură. -Dacă nume1, … numeN sunt prezente ele reprezintă date de tipul nou introdus NumeStructură. - Dacă una dintre datele din compunerea unei structuri este de tipul pointer la tipul de date introdus prin structura respectivă, numim structura recursivă: struct NumeStructură //structura recursivă { struct NumeStruct * data; //pointer la tipul de date struct NumeStructură <instructiuni de declarare de tip> } nume1, nume2, …, numeN; Exemple: 60

Algoritmi şi structuri de date struct DataCalendaristică { int zi; int luna; int an; } DataNasterii, DataAngajarii;

-prin această construcţie am introdus un tip nou (DataCalendaristică) şi am definit două variabile de tipul nou introdus (DataNasterii şi DataAngajarii)
struct { int zi; int luna; int an; } DataNasterii, DataAngajarii;

-prin această construcţie NU am introdus un tip nou ci doar am definit două variabile structurate (DataNasterii şi DataAngajarii).
struct DataCalendaristică{ int zi; int luna; int an; } … struct DataCalendaristică DataNasterii, DataAngajarii;

- prin această construcţie am definit un tip nou (DataCalendaristică) şi ulterior s-au declarat două variabile structurate (DataNasterii şi DataAngajarii) de tipul DataCalendaristică. Există posibilitatea declarării unor tablouri de date structurate. Spre exemplu, avem nevoie într-un program de un şir de 100 date calendaristice, pe care dorim ulterior să le prelucrăm. Cum se declară un tablou de date structurate? struct NumeStructură { <instrucţiuni de declarare de tip> } struct NumeStructură TablouDeStructuri[N]; - prin această construcţie am declarat un tablou unidimensional de N date structurate. Fiecare element al tabloului este reprezentat de o grupare de date. Exemplu:
struct DataCalendaristică { int zi; int luna;int an; }

61

Algoritmi şi structuri de date struct DatăCalendaristică SirDate[100];

-

fiecare element al tabloului SirDate are trei componente: zi, luna, an şi reprezintă o dată calendaristică.

Există posibilitatea ca în cadrul definirii unei structuri de date să apară o declaraţie a unei date de tip structurat. Spre exemplu, dorim să prelucram date grupate de tipul Persoană. Pentru fiecare dată de acest tip avem mai multe informaţii de identificare:nume, prenume, adresă, data naşterii. Se observă că data naşterii la rândul său este o dată structurată (zi, lună, an). Exemplu de structuri imbricate:
struct DataCalendaristică { int zi; int luna; int an; }; struct Persoana { char nume[10]; char prenume[10]; char adresa[25]; struct DataCalendaristica dataNasterii; }; //declaraţia unei date (Pers1) de tipul Persoana struct Persoana Pers1;

Accesul la componentele unei structuri Pentru a putea accesa componente ale unei date structurate se va utiliza operatorul punct “.”. Dacă avem o structură cu numele NumeStructură şi o componentă a sa cu numele NumeComponentă, referirea componentei respective se face prin construcţia: NumeStructură.NumeComponentă Pentru structuri imbricate se foloseşte operatorul “.” de câte ori este nevoie:Dacă dorim să accesăm anul naşterii persoanei Pers1 din exemplul precedent, acest lucru se face printr-o construcţie de forma: Pers1.dataNasterii.an Fie structura:
struct DataCalendaristică { int zi, luna, an; } struct DatăCalendaristică SirDate[100];

Pentru a accesa o componentă a unui element al unui tablou de date structurate se folosesc construcţii de forma : SirDate[24].zi , SirDate[1].luna , SirDate[k].an. Pointeri la structuri Declararea unui pointer la o structură NumeStructură se face prin construcţia: struct NumeStructură *p; În acest caz, p este o variabilă pointer, şi are ca valoare adresa unei date de tipul NumeStructură. 62

Introducerea unui nou tip de date utilizator se face prin construcţiile struct.luna=lunaN. dorim să citim o dată de tip structură printr-o funcţie. Noul tip de date este referit prin: struct NumeStructură iar datele se vor declara prin instrucţiunile de declarare: struct NumeStructură data1.În cazul în care avem o funcţie ce prelucrează o variabilă de tipul unei structuri. iar valorile citite în corpul funcţiei trebuie să fie vizibile în programul apelant. //Apelul functiei: Citire(&DC). (*p).lunaN. respectiv. …datan. data2.zi=ziuaN. de a renunţa la cuvântul cheie struct în declaraţii de date se va utiliza declaraţia de tip. Spre exemplu.anulN. Tipuri de date utilizator În foarte multe situaţii este nevoie ca utilizatorul să-şi definească noi tipuri de date pe care le prelucrează în program. Exemplu: void Citire(struct DataCalendaristică *p) { int ziuaN. etc.&lunaN).Algoritmi şi structuri de date Exemplu. scanf(“%d”. Avem nevoie de pointeri la structuri în următoarea situaţie: . transmisă ca parametru. scanf(“%d”. Declararea unei variabile X de tipul int se poate face astfel: INTREG X. float. Limbajul C permite atribuirea unui nume pentru un tip predefinit sau pentru nou tip definit de utilizator prin construcţia: typedef TIP NumeTip. //identică cu declaraţia: int X.) Exemplu: Redenumirea tipului int al limbajului C: typedef int INTREG. şi dorim ca efectul modificărilor din corpul funcţiei apelate să se regăsească şi în functia apelanta.&ziuaN).&anulN). construcţia (*p). } Struct DataCalendaristica DC. 63 . Pentru a utiliza în mod mai flexibil numele noului tip de date introdus.NumeComponentă se poate înlocui cu contrucţia: p->NumeComponentă. (*p). scanf(“%d”. struct DataCalendaristică *p.an=anulN. (*p). În continuare cuvântul NumeTip poate fi utilizat în declaraţii de date ca şi cuvintele cheie (rezervate limbajului C) care denumesc tipurile (int. Pentru simplificarea sintaxei codului sursă.

datanasterii. p. strcpy(p->cnp. putem asigna un nume noului tip printr-o construcţie de forma: typedef struct NumeStructură { <instructiuni de declarare de tip> } NumeTip. void citirePers(TPersoana *p) { char aux[40]. printf("\n Introduceti zi nastere:"). Declararea unor variabile de tipul anterior se face astfel: NumeTip data1.zi). TData datanasterii.%d ".h> typedef struct{ int zi.aux). scanf("%d". p.p.luna.nume. printf("\n Introduceti Prenume=").an). … datan.&p->datanasterii. printf("\n Introduceti CNP="). strcpy(p->prenume. printf("\n Introduceti an nastere:").p.%d.h> #include <string.aux). printf("\n Introduceti Nume="). int luna.aux). }TPersoana. }TData.datanasterii. 64 . scanf("%s".aux). introduse prin declaraţii de structură.prenume. char prenume[40]. int an. scanf("%s". char adresa[40].p. #include <stdio.cnp. scanf("%s". strcpy(p->nume. typedef struct { char cnp[13].Algoritmi şi structuri de date În cazul tipurilor definite de utilizator. } void tiparirePers(TPersoana p) { printf("\n%s %s %s %d. data2.&p->datanasterii.datanasterii.zi. scanf("%d". Exemplu1: Program care citeşte un şir de structuri de tipul Persoana şi afişează acest şir ordonat după câmpul nume.luna). scanf("%d".aux).aux).&p->datanasterii.an). char nume[20]. p. printf("\n Introduceti luna nastere:").

} persoana:").i.per[i+1].prenume)>0) { //interschimb per[i] cu per[i+1] aux=per[i].&nr). DenumireMarfa.Defineşte un tip de date Triunghi 65 . PretMarfa. Câmpurile structurii sunt: codMarfa.i++) citirePers(&per[i]). //tablou de structuri int nr. Se vor calcula valorile mărfurilor (valoare=pret*cantitate) şi tabloul se va ordona descrescător după valorile obţinute şi se va afişa pe ecran.i++) if(strcmp(per[i]. TPersoana aux. Scrieţi un program care citeşte într-un tablou un număr de k structuri de tipul utilizator Marfa. per[i+1]=aux.i++) tiparirePers(per[i]). aceasta urmînd a fi calculată după formula dată) 5.i<nr. for(i=0. 4.Algoritmi şi structuri de date } void sortNUME(TPersoana per[]. Probleme propuse spre rezolvare: 3. } }while (cod==0). Să se definească un nou tip de dată pentru entitatea Punct (3D). printf("\n dati numarul de scanf("%d".int nr) { int cod. per[i]=per[i+1]. } void main() { TPersoana per[20]. Valoare. cod=0.nume. Să se scrie un program care: .Defineşte un nou tip de date Punct2D (puncte în plan) .i<nr. (valoarea produselor nu se va citi.nr). să se denumească acest tip TipPunct şi să se declare 3 date de acest tip utilizator. sortNUME(per.i<=nr-2. for(int i=0. do{ cod=1. CantMarfa. for(i=0.

66 .Algoritmi şi structuri de date - Citeşte de la tastatură un tablou unidimensional (şir) de date de tipul triunghi.

Pentru tipurile de date definite de utilizator.Algoritmi şi structuri de date TIP ABSTRACT DE DATE Prin tip de date se înţelege domeniul valorilor şi operaţiile specificate datelor de acel tip. Tipurile de date se clasifică în tipuri elementare (ex. numeric real.Modificare unei componente m sau n (setm. neomogene .Adunarea a două numere raţionale .Înmulţirea a două numere raţionale . Identificăm următoarele operaţii cu numere raţionale: . TAD RATIONAL #include <stdio.Iniţializare: stabileşte valorile implicite pentru m=0.Testarea egalităţii a două numere raţionale .Reducerea unui raţional (prin împărţirea lui m şi n la cel mai mare divizor comun al celor doi întregi . . .Accesarea unei componente (getm. numeric întreg. Reprezentarea unui număr raţional se poate face printr-o structură cu două componente de tip întreg. Prezentăm ca exemplu un modul C care conţine definirea tipului de date abstract Rational. } void definire(Rational *a. getn) .h> typedef struct{ int m. a->n = 1.Definire: stabileşte valorile pentru componentele m şi n.Inversul unui raţional Fiecare operaţie din cele enumerate va fi definită prin intermediul unei funcţii C corespunzătoare în modulul prezentat mai jos. int n_nou) { a->n = n_nou.structuri). int n.Testarea relaţiei de ordine dintre două numere raţionale . Reamintim că un număr raţional este un număr care poate fi exprimat prin fracţia m/n. unde m şi n sunt numere întregi. Majoritatea limbajelor de programare oferă posibilitatea manipulării datelor de tip elementar dar şi introducerea de noi tipuri de date. caracter) şi structurate (omogene – tablouri. setn) . semnificând întregii m şi n ai fracţiei m/n. void initializare(Rational *a) { a->m = 0. int m_nou. programatorul îşi va construi module specifice de prelucrare a datelor de acel tip. }Rational. 67 .n=1.

m. a. Rational *c) { c->m = a.m . 68 . //cel mai mare divizor comun x = cmmdc(a->m. } int getm(Rational a) { return a. Rational b. a->n). } int getn(Rational a) { return a. else y = y -x. y = aux. } void inmultire(Rational a. a.n). int y) { int aux. void setm(Rational *a.Algoritmi şi structuri de date } a->m = m_nou. if (x < y) { aux = x.n * b. int m_nou) { a->m = m_nou. } void setn(Rational *a.m. a->n = a->n / x. } void reduce(Rational *a) { int x. } void print(Rational a) { printf("\n %d/%d ". a->m = a->m / x.n.m * b. c->n = a. return x. } int cmmdc(int x. x = y. } while ( x != y) if ( x > y) x = x -y. int n_nou) { a->n = n_nou.n.

n.n>0) &&(a. } Rational invers(Rational a) { Rational aux.n>=a. return 0.n=a. Rational *c) { c->m = (a. dezvoltând o bibliotecă de funcţii specifice operaţiilor cu numere complexe.m))) return 1. Rational b.n. } Problemă: Urmând exemplul prezentat anterior (TAD Rational) să se conceapă tipul abstract de date Complex.m*b.m * a.m)) return 1.n*b.n*b. void adunare(Rational a. c->n = a. aux.n + b. reduce(c).n<0) &&(a.n*b. } int egalitate(Rational a.n). 69 . Rational b) //verifica relatia de ordine a<b {if (((a. aux. Rational b) {if ((a. return 0.m * b.m*b.m*b.n<=a.n)==(a.m.n * b.m=a.m))|| ((a.Algoritmi şi structuri de date } reduce(c).n*b.n*b. } int maiMic(Rational a. return aux.

floppy disk. Există două nivele de tratare a fişierelor: 1. 2. 6. 3. 5. Crearea unui fişier nou se realizează prin apelul funcţiei creat care are prototipul: int creat (const char* cale. Nivelul inferior – face apel direct la sistemul de operare 2. Acest mod poate fi definit folosind constante simbolice:  S_IREAD – proprietarul poate citi fisierul  S_IWRITE – proprietarul poate scrie fisierul  S_IEXE – proprietarul poate executa programul conţinut de fişierul respectiv Utilizarea acestei funcţii implică includerea fişierelor: io. 4. Nivelul superior – face apel la proceduri specializate de prelucrare a fişierelor.h şi stat. 7.Algoritmi şi structuri de date VI. Unde: cale – este un pointer spre un şir de caractere prin care este specificat numele complet al fişierului (calea) mod – un număr întreg prin care se specifică modul de acces al proprietarului la fişier. int mod ). Fişierele sunt păstrate pe suporturi externe reutilizabile: hard disk. Prelucrarea fişierelor implică un număr de operaţii specifice acestora: 1. LUCRUL CU FIŞIERE Prin fişier se înţelege colecţie ordonată de elemente pe care le numim înregistrări. Creare fişier Deschidere fişier Citire din fişier Adaugare – scriere în fişier Actualizare fişier Poziţionare în fişier Ştergere fişier Operaţiile specifice fişierelor se realizează prin intermediul unor funcţii standard existente în biblioteca limbajului C. - 70 . Nivelul inferior de prelucrare a fişierelor Pentru a putea prelucra un fişier la nivel inferior acesta trebuie să fie creat în prealabil. care utilizează structuri speciale de tipul FILE. 1. etc.h.

toate caracterele \ trebuie dublate 71 . se consideră implicit calea curentă. A – floppy disk) .Algoritmi şi structuri de date Observaţie: Indicatorii de mod pot fi combinaţi.C … reprezintă litera de identificare a discului (ex: C – harddisk.h> - cale – pointer la un şir de caractere prin care se specifică calea completă a fişierului ce va fi deschis. DIr2. Astfel pentru a crea un fişier în care avem drepturi de scriere şi citire vom utiliza construcţia: S_IREAD| S_IWRITE.h şi fcntl. spre exemplu: O_RDONLY|O_BINARY Funcţia returnează un întreg nenegativ (descriptorul de fişier) sau –1 în caz de eroare. \Dir Unde: . Funcţia creat returnează un număr întreg care are semnificaţia descriptorului de fişier (dacă este pozitiv) sau eroare (dacă este -1).B. acces – întreg prin care se specifică modul de acces la fişier:  O_RDONLY – numai în citire  O_WRONLY– numai în scriere  O_RDWR – citire-scriere  O_APPEND – adăugare la sfârşitul fişierului  O_BINARY – prelucrare binară  O_TEXT – prelucrare text Valorile de acces pot fi combinate prin caracterul ‘|’ . Deschiderea unui fişier Deschiderea fişierului se realizează prin apelul funcţiei open al cărei prototip este: int open (const char* cale. df=open(“nume.txt”. Calea completă a unui fişier este specificată printr-un şir de caractere de forma: Litera:\Dir1 \ Dir2 …. Observaţii: Dacă nu este specificată complet calea fişierului. int acces ). … sunt nume de subdirectoare Dacă specificăm calea fişierului în apelul funcţiei open.Litera poate fi A.O_APPEND).h> #include <fcntl. Utilizarea acestei funcţii presupune includerea fişierelor io. 2.Dir1. Exemplu: int df. prin intermediul căruia se identifică un fişier în operaţiile specifice realizate asupra acestuia. Descriptorul de fişier este reprezintă un număr întreg pozitiv ataşat fişierului prelucrat.h: #include <io.

c). void *mem.d până la sfârşitul fişierului. text.txt”.O_RDONLY).h: int write(int df. df=open(“proba. char text[30] = “Acest text se scrie in fisier!”. O_RDWR). unde: .txt”. ş. i. 30).df – descriptorul ataşat fişierului (returnat de funcţia open) . Exemplu: scrierea într-un fişier a unei secvenţe de caractere … int df. prototipul acesteia se află în fişierul io. unde: . la un apel următor se va citi următoarea înregistrare. prototipul acesteia se află în fişierul io.cpp”. Citirea din fişier Se realizează prin funcţia read. sizeof(char) ).Algoritmi şi structuri de date Exemplu: int df.O_APPEND). do{ i=read(df. i.lung – lungimea înregistrării exprimată în număr de octeţi Funcţia read returnează un întreg reprezentând numărul de octeţi citiţi din fişier sau: –1 în caz de eroare.h: int read(int df.mem – pointer spre zona de memorie din care se preia informaţia ce se va scrie în fişier . 72 . 3. char c. unsigned lung).mem – pointer spre zona de memorie în care se va păstra înregistrarea citită din fişier . }while (i>0).a. Exemplu: citirea din fişier caracter cu caracter până la terminarea fişierului şi afişarea pe monitor a conţinutul acestuia: int df. Dacă cele două valori sunt diferite. c. df = open (“C:\\Borlandc\\bin\\text. write(df. //sizeof(char) returnează numărul de octeţi //necesari tipului specificat între paranteze. 4. în general numărul specificat de parametrul lung. Scrierea în fişier Funcţia write . void *mem.m. 0 la terminarea fişierului Efectul: Prin apelul funcţiei read se citeşte din fişier înregistrarea curentă. este semnalat prin acesta un caz de eroare.lung – lungimea înregistrării exprimată în număr de octeţi Funcţia write returnează un întreg reprezentând numărul de octeţi scrişi din fişier. unsigned lung). df=open(“proba. printf(“%c”.df – descriptorul ataşat fişierului (returnat de funcţia open) .

const char *mod). Dacă dorim ca accesul la înregistrări să se facă într-o ordine diferită decât cea implicită se poate utiliza funcţia lseek pentru poziţionarea în fişier: long lseek(int df. Poziţionarea în fişier Citirea şi scrierea în fişiere se face în mod secvenţial.descriptorul de fişier Utilizarea funcţiei close implică includerea fişierului bibliotecă io. Modurile valide de deschidere a fişierului sunt: “r” . fopen(). Închiderea fişierului Funcţia close având prototipul: int close (int df). În declaraţiile de tip vom avea nevoie de o secvenţă: FILE *pf. int orig).depl – deplasamentul exprimat în număr de octeţi . unde: .orig – este un număr întreg ale cărui valori semnifică: 0 – deplasamentul se consideră de la inceputul fişierului 1 – deplasamentul se consideră din poziţia curentă 2 – deplasamentul se consideră de la sfârşitul fişierului Funcţia returnează poziţia faţă de începutul fişierului. Deschiderea unui fişier Funcţia de deschidere a unui fişier se numeşte: fopen şi prototipul funcţiei este următorul: FILE *fopen(const char *nume.h Returnează: 0 – la închiderea normală a fişierului -1 – în caz de eroare Nivelul superior de prelucrare a fişierelor Funcţii de manipulare a fişierelor sunt definite în bibliotecile standard ale limbajului. long depl. Argumentele funcţiei: nume – şir de caractere prin care se identifică numele fişierului de pe disc pe care dorim să-l accesăm.Algoritmi şi structuri de date 5. Prototipurile funcţiilor se găsesc în fişierul antet stdio.h. 1. mod – şir de caractere prin care se specifică modul de acces la fişier. Fişierul care se prelucrează este identificat în mod unic printr-un pointer de fişier (uzual denumim acest pointer de fişier pf). ceea ce înseamnă că înregistrările se scriu una după alta pe suportul fişierului şi citirea se face în aceeiaşi ordine în care au fost scrise.deschidere pentru citire 73 . 6. În limbajul C există tipul de date FILE (fişier) prin care se defineşte structura datelor dintr-un fişier.df – descriptorul de fişier . În declaraţiile de tip vom avea nevoie de o secvenţă: FILE *pf. //df.

deschide sau creează fişier pentru actualizare (pierderea informaţiei) “a+” . “Rezultatul este: %d”.dat”.au aceiaşi semnificaţie ca şi în cazul funcţiei printf Funcţia returnează în caz de succes numărul de caractere scrise sau un număr negativ în caz de insucces. identifică fişierul ce se închide Funcţia returnează 0 în caz de succes sau EOF (end of file) în caz de insucces. Scrierea în fişier Funcţia de scriere în fişier: fprintf are prototipul: int fprintf(FILE *pf. Citirea din fişier Funcţia de citire din fişier: fscanf are prototipul: 74 . Exemplu: fprintf(pf.deschide pentru scriere (fără pierderea informaţiei din fişier) sau creează fişier “r+” . lista_arg).deschide pentru actualizare “w+” . 4. În declaraţiile de tip vom avea nevoie de o secvenţă: int fclose(). Argumentul funcţiei: pf – pointer spre FILE. 3. “r”). //iesire din program } 2. identifică fişierul ]n care se efectuează scrierea sir_format. // deschid fişierul fis1. Argumentul funcţiei: pf – pointer spre FILE.Algoritmi şi structuri de date “w” .dat pentru a putea citi informaţii if (pf==NULL) { printf(“\n Nu s-a putut deschide fisierul”). În caz de insucces. rezultat).deschide sau creează fişier pentru actualizare (fără pierderea informaţiei anterioare) Efectul apelării funcţiei: deschide fişierul specificat prin nume în modul de acces dorit şi returnează un pointer spre structura FILE (FILE *pf) care ulterior se utilizează pentru a identifica fişierul în operaţiile ce se efectuează asupra sa. Închiderea fişierului Funcţia de închidere a unui fişier se numeşte fclose şi are prototipul: int fclose(FILE *pf). const char* sir_format. //pointer spre FILE … pf=fopen(“fis1. exit(1). funcţia returnează NULL. Exemplu: FILE *pf.deschidere pentru scriere (informaţia din fişier se pierde la deschiderea în acest mod) “a” . lista_arg .

0 dacă nu s-a ajuns la sfârşitul fişierului şi -1 în caz de eroare. Argumentul funcţiei: pf – pointer spre FILE. Poziţionarea în fişier În mod firesc accesul la înregistrările unui fişier se produce în mod secvenţial. Funcţia de bibliotecă fseek este utilă deoarece are ca efect poziţionarea în fişier fără a fi necesară parcurgerea explicită a înregistrărilor anterioare celei căutate. În acest scop se va folosi funcţia: feof int feof(FILE *stream). Funcţia returnează 1 dacă s-a ajuns la sfârşitul fişierului. în mod frecvent este necesară verificarea dacă s-a ajuns sau nu la sfârşitul fişierului. int fputc(int c. “%d”. prin parcurgerea înregistrărilor de la începutul fişierului până la înregistrarea dorită. lista_arg). Exemplu: fscanf(pf. while ((c = fgetc(ptr)) != EOF) { 75 . Exemplu: Programul citeşte caracter cu caracter un fişier şi afişează conţinutul acestuia pe ecran: #include <stdio. Alte funcţii de prelucrare a fişierelor Funcţiile fgetc şi fputc au prototipueile int fgetc(FILE *pf).au aceiaşi semnificaţie ca şi în cazul funcţiei scanf Funcţia returnează în caz de succes numărul de caractere citite sau un număr negativ în caz de insucces. &x).txt".h> void main(void) { int c. const char* sir_format. 5. identifică fişierul din care se efectuează citirea sir_format.Algoritmi şi structuri de date int fscanf(FILE *pf. Funcţia fgetc – returnează caracterul citit. int mod) mod poate fi: 0 – dacă increment – indică numărul de octeţi faţă de începutul fişierului 1 – dacă increment – indică numărul de octeţi faţă de poziţia curentă 2 – dacă increment – indică numărul de octeţi faţă de sfârşitul fişierului În parcurgerea înregistrărilor dintr-un fişier. long increment. FILE *pf). lista_arg . Efectul: citirea/scrierea unui caracter din/în fişierul identificat prin pointerul pf. FILE *ptr. Prototipul funcţiei este: int fseek (FILE *pf."r"). ptr = fopen("c:\\test.

citirea înregistrării k fără a fi copiată în auxiliar 4. scriere. parcurgerea şi copierea înregistrărilor fişierului iniţial în fişierul auxiliar de la înregistrarea k+1 până la sfârşit.Algoritmi şi structuri de date printf("%c". FILE *stream). deschiderea fişierului iniţial în citire 2. parcurgerea secvenţială fişierului iniţial şi copierea înregistrărilor în fişierul auxiliar până la înregistrarea k 4. int fputs(const char *sir. creare fişier. Se folosesc funcţiile de bibliotecă standard enumerate (deschidere fişier. Afişarea conţinutului unui fişier. crearea fişierului auxiliar şi deschidere sa în modul scriere 2. etc. FILE *stream). Operaţiile de inserare sau ştergere a unei înregistrări din fişier sunt operaţii complexe realizate în mai multe etape. Etape: Deschide fişierul Câttimp nu s-a ajuns la sfârşitul fişierului Citeşte linia curenta Afişare pe ecran linia citita 76 . Inserarea pe o poziţie k a unei noi înregistrări presupune parcurgerea următoarelor etape: 1. crearea unu fişier auxiliar şi deschiderea sa în modul scriere 3. însă nu există funcţii standard corespunzătoare celor două operaţii ca atare.c). O variantă de inserare sau ştergere a unei înregistrări este aceea prin care se renunţă la folosirea unui fişier auxiliar şi se construieşte o structură liniară de tip tablou în care sunt salvate temporar înregistrările fişierului iniţial. Efectul: citirea/scrierea unui şir de caractere din-în fişierul identificat prin pointerul pf. } Funcţiile fgets şi fputs au prototipurile int *fgets(char *sir. redenumirea fişierului auxiliar cu numele fişierului iniţial Ştergerea înregistrării k dintr-un fişier presupune de asemenea folosirea unui fişier auxiliar şi parcurgerea etapelor următoare: 1. scrierea noii înregistrări în fişierul auxiliar 5. } fclose(ptr). int n.) în implementarea operaţiilor de inserare-ştergere. parcurgerea şi copierea înregistrărilor fişierului iniţial în fişierul auxiliar până la înregistrarea de pe poziţia k-1 3. citire. continuarea parcurgerii fişierului iniţial şi continuarea copierii înregistrărilor în fişierul auxiliar 6.

dat Deschide fişierul fis1. căutare persoană după valoarea câmpului CNP. Scrieţi un program care gestionează într-un fişier structuri de tipul Persoana (CNP.dat un vector de structuri Punct3D şi afişează coordonatele punctelor pe ecran. prenume.dat Scrie în fişierul fis2. Probleme: 6. 8.dat.).dat) Citeşte din fişierul fis1. Să se scrie un program C care citeşte de la tastatură un vector de structuri Punct3D şi le salvează într-un fişier puncte. nume.dat). ştergere înregistrare. etc.Algoritmi şi structuri de date Sfcâttimp Închidere fişier Copierea conţinutului unui fişier (fis1. Să se scrie un program C care citeşte din fişierul puncte.dat Câttimp (nu s-a ajuns la sfârşitul fişierului fis1. Operaţiile evidenţiate sunt cele de adăugare înregistrare. ordonarea alfabetică a persoanelor memorate în fişier. 7.dat) într-un alt fişier (fis2. 77 . Etape: Creează fișierul fis2.dat Sfcâttimp Închidere fişierele. data nasterii.

Funcţia calloc: void *calloc(size_t nrElemente. Dezavantajele alocării dinamice a memoriei constau în: 1.h şi gestionează o zonă de memorie specială. semnificând dimensiunea zonei alocate. ALOCAREA DINAMICA A MEMORIEI Una dintre cele mai costisitoare resurse ale unui program este este memoria utilizată. face ca o zonă de memorie considerabilă să fie blocată pentru această structură. datele declarate global au un comportament diferit. pe stivă. declararea globală a unui tablou unidimensional de lungime maximă 2000 (<TipElement> tablou[2000]. funcţia returnează adresa 78 . dealocarea (eliberarea) memoriei. Alocarea dinamică a memoriei poate fi simplificat definită prin totalitatea mecanismelor prin care datelor le sunt asignate zone specifice de memorie în faza de execuţie a programelor. Alocarea memoriei pentru variabilele locale declarate într-o funcţie se face în etapa de execuţie a programului. se urmăreşte şi eficienţa acestora. număr care în multe cazuri poate fi cu mult mai mic decât dimensiunea maximă declarată. În astfel de situaţii este preferabilă o manieră de alocare dinamică în vederea unei gestionări economice a memoriei. Funcţia returnează în caz de succes adresa de memorie a zonei alocate. funcţia malloc returnează NULL. în afara corectitudinii programelor elaborate. size_t dimensiune) Efectul funcţiei: se alocă un bloc de memorie de mărime nrElemente * dimensiune (această zonă nu trebuie să depăşească 64 Ko octeţi din heap) şi conţinutul blocului alocat este şters. În scopul economiei resurselor. măsurată prin timpul de execuţie şi memoria utilizată. fapt pentru care datele alocate sunt distruse.Algoritmi şi structuri de date VII. limbajele de programare oferă programatorului instrumentele necesare unei bune gestionări a memoriei. ). La ieşirea din funcţie. În caz de insucces. stiva se curăţă. Funcţia malloc: void *malloc(size_t dimensiune) Argumentul funcţiei este dimensiunea exprimată în octeţi. denumită memoria heap . În schimb. Limbajul C/C++ oferă programatorului funcţii standard prin care se realizează alocarea. respectiv. Acest mecanism de alocare este eficient şi se denumeşte alocare dinamică. Spre exemplu. Este posibil ca cererea de alocare să nu poată fi satisfăcută dacă nu mai există suficientă memorie în zona de heap sau nu există o zonă compactă de dimensiune cel puţin egală cu dimensiune. Programarea este o activitate în care. efectul fragmentării memoriei care poate produce imposibilitatea alocării ulterioare a unei zone de memorie de dimensiune dorită. Funcţiile malloc şi free au prototipurile în fişierul antet: alloc. sarcina suplimentară a programatorului de a asigura şi eliberarea memoriei când nu mai are nevoie de datele respective 2. Pentru variabilele globale. indiferent de numărul elementelor folosite efectiv. memoria este alocată în faza premergătoare execuţiei şi zona de memorie respectivă rămâne alocată acestora până la terminarea programului. În caz de succes. şi nu este permanentă.

vectori de date simple. etc. Dacă parametrul adrBloc este 0. Funcţia ajustează mărimea blocului alocat la Dimensiune şi returnează adresa blocului realocat sau 0 . } for (i=0. #include <stdio. returnează 0 dacă nu există spaţiu liber de mărimea solicitată. Exemplu: tip *p. Funcţia realloc: void *realloc(void* adrBloc. Parametrul adrBloc indică un bloc de memorie obţinut prin apelarea anterioară a funcţiei malloc. Funcţia free: void free(void * p) Argumentul funcţiei este adresa zonei ce va fi eliberată. int i.dacă nu se poate face realocarea. i++) 79 .h> int *citire_vector(int *pDim) { int * vector. free(p). if ((vector=(int *)calloc(*pDim. p= (tip*)malloc(sizeof(tip)) .scanf("%d". Efectul apelului funcţiei free este de a elibera memoria a cărei adresă este p. vectori de structuri. Observaţie: operatorul sizeof returnează dimensiunea în octeţi a expresiei: sizeof(expresie).sizeof(int)))==NULL ) { printf("Insucces la alocarea dinamica"). i<*pDim. printf("\nDati n:").h> #include <stdlib. calloc sau realloc. Programele următoare sunt exemple simple de gestionare eficientă a memoriei prin alocarea dinamică a structurilor de date: Program – Exemplu de alocarea dinamică a tablourilor. pDim). Dacă Dimensiune este 0 efectul apelului este identic cu cel al funcţiei free. Construcţia (Tip*) realizează o conversie explicită de tip: din tipul pointer nedefinit (void *) spre tipul pointer la Tip. size_t Dimensiune) Funcţia încearcă să mărească sau să micşoreze un bloc (alocat dinamic) la numărul de octeţi specificaţi de parametrul Dimensiune. Utilizarea celor două funcţii standard malloc şi free devine utilă şi în programe în care se vor manipula tablouri de elemente: şiruri de caractere. efectul este identic cu al funcţiei malloc. exit(1).Algoritmi şi structuri de date blocului de memorie alocat.

// sau scanf("%d". i++) { printf("\nvector[%d]=%d".h> #include <string. n). #include <stdio. } 80 . i+1.operaţii cu şiruri de caractere. int *vector. int n) { int i. } Program 2 . vector+i). } strcpy(sir2. &vector[i]). Programul ca aloca dinamic memorie pentru un şir de caractere sir2 şi va efectua o copiere a şirului sir1 citit de la tastatură în zona de memorie rezervată noului şir. afisare_vector(vector. if ((sir2=(char *)malloc( (strlen(sir1)+1)* dimCh))==NULL) { printf("Insucces la alocarea dinamica").h> void main(void) { char sir1[10]. printf("Sirul destinatie: %s \n". char *sir2. // se retin primele 10 caractere introduse int dimCh=sizeof(char). exit(1). vector[i]). printf("introduceti un sir de caractere: \n"). scanf("%9s". sir1). sir2). printf("Sirul sursa: %s \n".h> #include <stdlib. sir1). } } void main(void) { int n. vector=citire_vector(&n). i<n. i+1). sir1).Algoritmi şi structuri de date { printf("vector[%d]=". } return vector. for (i=0. scanf("%d". //se returneaza adresa vectorului } void afisare_vector(int *vector.

fiecare element conţine pe lângă informaţia propriu-zisă şi o legătură spre următorul element. abordarea structurilor de date de tip listă prin liste înlănţuite alocate dinamic este avantajoasă. structura unui nod al listei devine recursivă. Clasificarea listelor se face în funcţie de numărul legăturilor din compunerea unui element. Memorarea structurilor de date de tip listă se poate face în două maniere: 1. secvenţial . Pentru listele organizate static. Organizarea acestor structuri de date în C se face prin folosirea unor legături care nu sunt altceva decât pointeri (adrese) spre următoarele elemente din listă. Dinamica listei secvenţiale se realizează în această situaţie prin reţinerea unui parametru suplimentar cu semnificaţia dimensiunii curente a tabloului. Argumentul folosirii listelor înlănţuite rezidă din necesitatea economiei de memorie. ordinea elementelor este cea implicită în tablou. Elementele unei liste se denumesc în mod uzual noduri. adăugare nou element 5. a cărei dimensiune se modifică în timpul rulării programului. distrugere (ştergere) 4. sub formă înlănţuită. În spiritul economiei de memorie. Liste înlănţuite pot fi: simplu. creare 3. zona de informaţii. formată din elemente de acelaşi tip. punându-se în evidenţă cele două părţi ale nodului: 1. înlănţuit – elementele listei nu ocupă adrese consecutive. LISTĂ SIMPLU ÎNLĂNŢUITĂ Lista reprezintă o mulţime de dimensiune variabilă. În ciuda simplităţii acestei abordări se preferă varianta elementelor înlănţuite şi alocate dinamice. Înţelegem prin listă – o mulţime dinamică şi omogenă. Memorarea secvenţială se poate produce prin folosirea structurilor de date de tip tablou. ştergere element Lista simplu înlănţuită Structura nodului listei simplu înlănţuite: Figura următoare prezintă în mod grafic structura unui nod al listei simplu înlănţuite.elementele listei sunt memorate la adrese consecutive 2. Pentru listele organizate dinamic.Pointerii legătură din compunerea unui element al listei indică adresele unor alte elemente de acelaşi tip.Algoritmi şi structuri de date VIII. permite manevrarea elementelor sale dar în acelaşi timp produce o risipă inutilă a unui bloc memorie alocat şi neutilizat. dublu sau multiplu înlănţuite. ordinea elementelor este dată de pointeri. folosirea unui tablou de o dimensiune mult mai mică decât maximul declarat. Operaţiile principale cu liste sunt următoarele: 1. formată din unul sau mai multe câmpuri 81 . Prin folosirea pointer-ilor (legături). Folosirea tablourilor forţează o declararea a numărului maxim de elemente ceea ce rareori este cunoscut la momentul dezvoltării programului. parcurgere 2. În consecinţă. sub formă de tablou.

Reţinându-se doar adresa primului nod. Adresa fiecărui nod este conţinută de nodul precedent. //adresa următorului nod }Tnod. Gestionarea unei liste de astfel de noduri necesită cunaşterea adresei primului şi eventual al ultimului nod din listă. *ultim. etc. Ultimul nod nu va referi niciun alt nod următor. cu semnificaţia adreselor primului şi ultimului element din listă: Tnod *prim. Terminarea operaţiei de parcurgere a listei constă în accesarea ultimului element. accesate. Parcurgerea listei Parcurgerea listei presupune accesarea sau prelucrarea elementelor listei în ordinarea stabilită de legăturile conţinute de noduri. legătura spre alt nod de acelaşi fel O structură care descrie compunerea unui element de acest gen este următoarea: struct nod { //declaraţii de date – câmpuri ale informaţiei struct nod *adr. care la rândul său conţine adresa următorului. info adr prim . typedef struct nod { int cheie. prin urmărirea legăturilor urm conţinute în nodurile curente. celelalte noduri pot fi parcurse. acesta conţine adresa următorului element. Pentru a simplifica exemplele următoare. prin urmărirea adreselor de legătură pot fi accesaţi toţi membrii listei. marcat prin adresă nulă a pointerului urm. pentru care nu există un nod precedent. tipul nodurilor. În acest mod. 82 info adr ……. vom introduce un nou tip de dată. Figura următoare sugerează organizarea unei liste simplu înlănţuite: info adr Pentru implementarea operaţiilor specifice listelor înlănţuite este utilă declararea a doi pointeri la tipul Tnod.Algoritmi şi structuri de date 2. cu excepţia primului nod al listei. //adresa următorului nod } Convenim că informaţia din noduri conţine un câmp special (cheie) ale cărui valori sunt distincte pentru elementele aceleiaşi liste (cheie nu este un câmp obligatoriu). denumit TNod. fapt care se marchează prin pointerul urm care devine Null. //câmp cu valori unice pentru nodurile listei //alte câmpuri struct nod *urm. Cunoscând primul element prim .

prelucrare nodul referit de p (accesare. nodul nou trebuie alocat în memorie şi încărcat cu informaţie 2.p->cheie). trecerere la următorul nod p=p->urm Oferim în continuare o funcţie C prin care se parcurge lista simplu înlănţuită gestionată de prim şi ultim şi sunt afişate informaţiile nodurilor traversate. trecerea la nodul următor se obţine prin asignarea: p=p->urm. //trecere la următorul element } } Crearea listei vide Crearea unei liste înlănţuite care nu conţine niciun element presupune iniţializarea celor doi pointeri prim şi ultim cu valoarea 0: prim=ultim=0. Dacă nodul curent p este Null (p==0). //alocare memorie printf("\n dati cheia"). Crearea unei liste cu mai mult de 0 noduri presupune adăugarea în mod repetat a noilor noduri până la întâlnirea unei condiţii de terminare a procedeului.&p->cheie). În procesul de adăugarea a unui nou nod se ţine cont de două aspecte: 1. sau înaintea primului nod. //se porneşte traversarea listei de la primul nod while(p!=0) //câttimp nu s-a ajuns la sfîrşitul listei { printf("\n%d ". câttimp (pointerul curent este diferit de 0: p!=0 ) execută a. //p semnifică nodul curent p=prim. iniţializează pointer-ul curent cu adresa primului nod : p=prim 2. void tiparire_lista() { tnod *p.Algoritmi şi structuri de date Considerând p adresa nodului curent din listă. p=(tnod*)malloc(sizeof(tnod)). scanf("%d". anumite legături din listă trebuie refăcute pentru a asigura consistenţa organizării Prezentăm în continuare o funcţie C care are efectul alocării şi încărcării unui nou nod de tipul Tnod.adăugare noduri la listă: tnod * incarca_nod() { tnod *p. //citire cheie … //citire alte informaţii conţinute de nod return p. Paşii parcurgerii unei liste sunt următorii: 1. se încheie parcurgerea. modificare conţinut) b. //returnarea adresei nodului încărcat } 83 . // afişarea celorlalte câmpuri din nodul curent p=p->urm. Noile noduri pot fi adăugate sistematic după ultimul element al listei. Utilitatea acestei funcţii o vom înţelege în construirea funcţiilor de inserare .

nou->urm =0. efectul operaţiei constă în obţinerea unei liste cu un singur element. Înaintea primului nod Etapele introducerii unui nou nod înaintea primului nod al listei gestionate prin pointerii prim şi ultim sunt următoarele: 1. stabilirea faptului că acest ultimul nod va adresa ca următor element pe noul nod: ultim->urm=nou (1) 3. return. if (prim= =0) //lista vida {prim=p.Algoritmi şi structuri de date Inserarea unui nou element: 1. dacă lista este vidă în momentul încercării de a adăuga un nod nou. prim=p. alocarea şi încărcarea noului nod: 2. (2) 4. ultim->urm=0. alocarea şi încărcarea noului nod: 2. stabilirea faptului că acest nou nod va adresa ca următor element chiar pe nodul prim: nou->urm=prim 3. p=incarca_nod(). şi marcarea acestuia ca ultim nod din listă: ultim=nou. ultim=p. se va trata distinct acest caz nou 84 . stabilirea noului nod ca prim element al listei: prim=nou Observaţie: dacă lista este vidă în momentul încercării de a adăuga un nod nou. void adaugare_prim() { tnod *p. După ultimul nod Etapele introducerii unui nou nod după ultimul nod al listei gestionate prin pointerii prim şi ultim sunt următoarele: 1. fapt pentru care capetelor listei prim şi ultim sunt confundate şi este necesară tratarea acestui caz particular. } p->urm=prim. } 1 prim 2. stabilirea noului nod ca ultim element al listei.

Inserarea unui nod înaintea unui nod specificat Acest tip de operaţie se realizează în două etape: 1. if (prim==0) //lista vida {prim=nou. atribuire prin care vechea înlănţuirea a nodurilor prev şi p se pierde. Argumentul funcţiei este valoarea cheii căutate. inserarea propriu-zisă a noului nod Căutarea unui nod specificat prin cheie se realizează printr-un algoritm elementar de parcurgere sistematică a listei până la întâlnirea nodului ce conţine cheia căutată. Procedura de inserare ţine cont de cazul particular în care nodul specificat prin cheie este primul nod. Stabilirea legăturilor dintre noduri trebuie făcută corect pentru a nu genera pierderea unei sub-liste de noduri. ultim->urm=0. } ultim->urm=nou. căutarea nodului înaintea căruia se va insera un nou nod 2. cunoscându-se adresa prev (nodul precedent= şi adresa nodului curent p: 1.Algoritmi şi structuri de date void adaugare_ultim() { tnod *nou. şi 0 în caz de insucces (dacă nodul de cheie dată nu există în listă). În acest scop se poate construi o funcţie care returnează adresa nodului căutat. ultim->urm=0. ultim=nou. 85 . Căutarea nodului înaintea căruia se va insera noul nod poate fi realizată în aceiaşi funcţie în care se face inserarea. nodul nou va conţine adresa nodul curent: nou->urm=p. precedentul nod va conţine adresa noului nod prev->urm=nou. pentru ca ulterior noul nod să poate fi înlănţuit între nodurile precedent şi curent. } ultim 3. return. În acest sens este necesară reţinerea în permanenţă a adresei nodului precedent nodului curent. ultim=nou. nou=incarca_nod(). Imaginea următoare sugerează etapele inserării noului nod. 2.

86 . //iniţializare nodul curent cu prim while (p!=0) { if (p->numar!=valoare) {//NU s-a gasit încă nodul prec=p. tnod *p.*prec. *nou.Algoritmi şi structuri de date Funcţia următoare descrie operaţia de inserare înaintea unui nod căutat după valoarea cheii: void adaugare_inainte() { int valoare. nou->urm=p. nodul căutat este primul adaugare_prim(). } else { nou=incarca_nod().scanf("%d". p=prim. printf("\ndati cheia de cautat:"). fapt pentru care operaţia se reduce la adăugarea după ultim (funcţie deja construită). return. //reface legăturile prec->urm=nou.&valoare). Cazul particular al procedeului constă în găsirea ca nod de referinţă chiar ultimul nod al listei. //trece la următorul element } else { //s-a găsit nodul de cheie dată if (p==prim) {//caz particular. } } } }//sfârşit funcţie prev 2 curent nou 4.return. //salvează adresa precedentului în prev p=p->urm. După un nod stabilit de o cheie: Operaţia de inserare după un nod specificat este similară celei anterioare.

} } } p 2 p->urm nou 87 . stabile. return. adresa următorului nod al nodului găsit este cunoscută.&valoare). fiind memorată ca legătură chiar în nodul găsit: p>urm . Însă. reţinerea unei adresa a precedentului nodului curent nu mai este necesară.*nou. etapele inserării noului nod sunt: 1. nodul nou se va insera între nodul găsit (curent) şi următorul nod al nodului curent. Prin această asignare s-a pierdut automat înlănţuirea veche între p şi p->urm void adaugare_dupa() { int valoare.return. } else { //alocare memorie şi încărcare nod cu inf. printf("\ndati cheia de cautat:"). stabileşte adresa urm a nodului nou ca fiind adresa nodul următor al lui p: nou->urm=p->urm (1) 2.te adresa urm a lui p ca fiind adresa lui nou: p->urm=nou. nou=incarca_nod(). În fapt. } else { //caz particular if (p==ultim) { adaugare_ultim(). //stabilirea legăturilor p->urm=nou. nou->urm=p->urm. while (p!=0) { if (p->numar!=valoare) {//NU am gasit p=p->urm.Algoritmi şi structuri de date În plus.scanf("%d". p=prim. tnod *p. Considerând nodul de cheie dată găsit: p. prin maniera de înlănţuire.

adresa nodului anterior ultimului se determină printr-un procedeu suplimentar de traversare a listei. primvechi=prim. generând un cost suplimentar al algoritmului. În contradicţie cu operaţia de ştergere a primului nod. eliberarea unei zone de memorie şi actualizarea pointer-ului ultim (util în gestionarea listei).return. pentru a determina adresa precedentului nodului ultim. Ştergerea primului element al listei necesită eliberarea memoriei dar şi actualizarea pointer-ului prim necesar în gestionarea ulterioară a listei. } prim=prim->urm. Acest lucru este necesar pentru a realiza operaţia de actualizare a pointer-ului ultim.*prec. Etapele ştergerii ultimului element al unei liste sunt: a. actualizarea ultimului nod c. prin care următorul nodului prim (secundul) devine prim al listei void stergere_prim() { tnod *primvechi. Ştergerea ultimului presupune refacerea legăturilor. //salvare adresa primului element if (primvechi==0) {//lista este vidă. } while (p!=ultim) //traversarea listei şi reţinerea precedentului //curent nodului 88 .Algoritmi şi structuri de date } Ştergerea unui element 9. return.*p. nu se va şterge nimic printf("\nlista este vida").//eliberarea memoriei adresata de prim } 10. Adresa următorului nod după prim se poate determina în mod elementar prin câmpul urm. traversarea listei pentru a determina adresa penultimului element b. if (p==0) { printf("\nlista este vida"). însă. //actualizare prim free(primvechi). ştergerea ultimului nod al listei necesită o parcurgere în totalitate a listei. eliberarea memoriei void stergere_ultim() { tnod *ultimvechi. p=prim. Actualizarea prim-ului nod al listei se face prin asignarea prim=prim->urm.

return.scanf("%d". } //caz particular //cazul general pvechi=p. //trecere la următorul nod } //în acest punct prec este adresa penultimului nod ultimvechi=p. //salvare vechiul nod ultim ultim=prec. //salvare precedent p=p->urm. return. while(p!=0) { if (p->cheie==valoare) {//s-a găsit nodul şi acesta va fi şters if (p==prim) { stergere_prim(). //actualizare şi marcare ultim free (ultimvechi). //salvare adresă nod curent prev->urm=p->urm. printf("\ndati cheia de cautat:"). Ştergerea unui element precizat Ştergerea unui element precizat prin valoarea cheii se execută prin următorii paşi: .căutarea nodului p ce va fi şters şi reţinerea adresei precedentului acestuia: prev .&valoare).*prev. p=prim. void stergere_oarecare() { int valoare. ultim->urm=0. //eliberare memorie } 11. tnod *p. //refacere legături prev 89 .Algoritmi şi structuri de date { prec=p.eliberarea memoriei Cazurile particulare ale procedurii sunt tratate distinct prin procedurile specifice de ştergere a primului sau ultimului nod. } //caz particular if (p==ultim) { stergere_ultim().refacerea legăturilor: prev->urm= p->urm (1) .*pvechi.

Prin aceasta s-a distrus doar mecanismul de accesare a nodurilor. variante ştergerii listei prin apelul funcţiei ştergere_prim este preferată deoarece nu necesită traversarea listei pentru fiecare operaţie de ştergere a unui nod.Algoritmi şi structuri de date free(pvechi). nu s-au distrus efectiv nodurile listei. //salvarea adresei precedentului p=p->urm. //eliberare memorie return. while(p!=0) //cât timp mai sunt noduri în listă { if (prim==0) { printf("\nlista e complet stearsa"). //trecere la următorul nod } } } else 12. prim=prim->urm. fapt pentru care gestionarea ulterioară a listei ( accesarea şi prelucrarea nodurilor sale ) devine imposibilă. prin ştergerea repetată a nodurilor din capătul listei. } } } 90 . return. } {//nu s-a găsit încă prev=p. p=prim. Funcţia următoare este o variantă de ştergere a listei. O variantă simplă dar greşită de ştergere a listei constă în distrugerea capetelor listei prim şi ultim. } else { //sterg primul nod şi actualizez prim primvechi=prim. a ultimului nod până când lista devine vidă (pointer-ul prim devine Null). respectiv. Din punct de vedere al eficienţei. void stergere_lista() { tnod*p. free(primvechi).*primvechi. memoria rămâne alocată nodurilor intermediare. Ştergerea listei Ştergerea completă a listei se poate realiza prin apelul repetat al funcţiilor deja construit de ştergere a primului. Cu toate acestea.

Gestionarea unei liste dublu înlănţuite se face în maniera similară listelor simplu înlănţuite prin adresele nodurilor prim şi ultim. Figura următoare sugerează maniera de organizare a unei liste dublu înlănţuite (alocate dinamic): info 91 . Ca şi la lista simplu înlănţuită. În plus.//alte câmpuri struct nod *pre. LISTA DUBLU ÎNLĂNŢUITĂ Lista dublu înlănţuită este formată din noduri care conţin: .adresa precedentului nod Avantajul utilizării listelor dublu înlănţuite rezultă din posibilitatea parcurgerii (traversării) listei în ambele sensuri: de la primul la ultimul. . struct nod* urm. .adresa următorului nod .ştergerea listei. respectiv.Algoritmi şi structuri de date IX.crearea. //adresa nodului precedent } În exemplele următoare vom utiliza un tip de date utilizator prin care specificăm tipul nodurilor listei: typedef struct nod {int cheie. principalele operaţii sunt: .adăugarea unui nod..*ultim. nodul prim se marchează prin stabilirea adresei precedentului său la Null: prim->prec=0. .ştergerea unui nod.accesul la un nod. …. tnod *prim. Acest lucru permite o manipulare mai flexibilă a nodurilor listei Structura unui nod al listei dublu înlănţuite este următoarea: struct nod { //declaraţii de date – câmpuri ale informaţiei struct nod *urm. parcurgerea listei . //adresa următorului nod struct nod *prec.informaţie . de la ultimul la primul nod. }tnod.

ultim=p. după adăugarea unui nod trebuie marcate nodurile prim şi ultim.ultim=p. respectiv. } } Parcurgerea listei dublu înlănţuită Spre deosebire de listele simplu înlănţuite.scanf("%d". //creare lista vida prim=ultim=0. //marcare capete listă } else { ultim->urm=p. Prezentăm în continuare funcţii de afişare a informaţiei nodurilor parcurse în două variante: prin folosirea legăturii următor (urm). int rasp.scanf("%d". Funcţia următoare este un exemplu prin care se poate crea o listă dublu înlănţuită prin adăugarea noilor noduri după ultimul nod.Algoritmi şi structuri de date ……. prim->pre=0. Crearea listei dublu înlănţuită Crearea listei vide presupune iniţializarea celor doi pointer de control prim şi ultim cu valoarea 0 (Null): prim=ultim=0. while (rasp==1) { p=incarca_nod(). p->pre=ultim. Un caz particular al procedurii de adăugare a noului nod este tratat distinct: în situaţia în care lista este vidă. listele dublu înlănţuite pot fi parcurse în ambele sensuri. Funcţia incarca_nod este cea definită în capitolul dedicat listelor simplu înlănţuite.&rasp).&rasp). prim void creare() { tnod *p. //alocare memorie şi încărcare nod if (prim==0) {//creare primul nod prim=p. ultim->urm=0. Crearea unei liste cu mai mult de un nod se rezumă la apelul repetat al subrutinelor de adăugare a unui nou nod (înaintea primului sau după ultimul nod). } printf("\nIntroduceti? (1/0)"). printf("\nIntroduceti? (1/0)"). ultim->urm=0. succesor (prec). 92 .

//iniţializare adresă nod curent while(p!=0) { printf("\n %d". fapt pentru care capetelor listei prim şi ultim sunt confundate şi este necesară tratarea acestui caz particular. } p=ultim. alocarea şi încărcarea noului nod: 2.p->cheie). efectul operaţiei constă în obţinerea unei liste cu un singur element. stabilirea noului nod ca prim element al listei: prim=nou (3) 5. stabilirea faptului că nodul prim va referi ca precedent element pe nodul nou: prim->prec=nou (2) 4. prim 3 93 . marcarea nodului prim: prim->prec=0 (4) 1 Observaţie: În cazul în care lista este înaintea adăugării unui nod nou. p=p->prec. if (prim==0) {printf("\nLista e vida!"). return. //trece la următorul nod } } void tiparireInversa() { tnod *p. } p=prim. //iniţializare adresă nod curent while(p!=0) { printf("\n %d". return. void adaugare_prim() { tnod *nou.Algoritmi şi structuri de date void tiparireDirecta() { tnod *p. if (prim==0) {printf("\nLista e vida!"). Adăugare înaintea primului nod Adăugarea unui nou nod înaintea primului nod ale listei presupune efectuarea următoarelor operaţii: 1. p=incarca_nod(). p=p->urm.p->cheie). //trece la precedentul nod } } ADĂUGAREA UNUI NOU NOD Sunt tratate în continuare diferite modalităţi de adăugare a unui nou nod într-o listă dublu înlănţuită: 13. stabilirea faptului că acest nou nod va adresa ca următor element chiar pe nodul prim: nou->urm=prim (1) 3.

alocarea şi încărcarea noului nod: . if (prim==0) { prim=nou.ultim->urm=0.stabilirea noului nod ca ultim element al listei: ultim=nou (3) .marcarea nodului ultim: ultim->urm=0 (4) Cazul special al procedurii (lista este vidă) se tratează în mod diferit. } else { …………….ultim=nou.nou=incarca_nod().stabilirea faptului că acest nou nod va adresa ca precedent element chiar pe nodul ultim: nou->prec=ultim (1) .ultim=nou.Algoritmi şi structuri de date if (prim==0) { prim=nou.ultim->urm=0. prim->prec=0. } else { nou->urm=prim. //pasul 4 } } 14. prim->prec=0. void adaugare_ultim() { tnod *nou. Adăugare după ultimul nod Adăugarea unui nou nod după ultimul nod al listei presupune efectuarea următoarelor operaţii: . 94 . //pasul 2 prim=nou.stabilirea faptului că nodul ultim va referi ca următor element pe nodul nou: ultim->urm=nou (2) . //pasul 1 prim->prec=nou. //pasul 3 prim->prec=0.

*nou. adrese utile în accesarea vecinilor (nodurile anterioare). //(2) ultim=nou. if (p!=prim) { p->prec 2 95 nou . În cazul listelor dublu înlănţuite lucrurile sunt simplificate datorită adreselor precedent conţinute de fiecare nod. problema se reduce la un caz particular tratat prin funcţia adaugare_prim. //p contine adresa nodului curent in parcurgerea listei p=prim. //(4) } } 15. Adăugare înaintea uni nod specificat prin cheie Adăugarea unui nod înaintea unui nod specificat prin valoarea cheii. 3 void adaugare_inainte(int valoare) { tnod *p. //(3) ultim->urm=0.Algoritmi şi structuri de date nou->prec=ultim. nu se va adăuga un nou nod.inserarea propriu-zisă Reamintim că la operaţia similară pentru liste simplu înlănţuite era necesară determinarea nodului precedent nodului precizat de cheie li acest lucru se realiza printr-un pointer auxiliar în care se reţinea acea adresă. se realizează prin două etape: . de la primul spre ultimul nod if (p->cheie!=valoare) {//nu s-a gasit inca nodul de cheie data p=p->urm. while(p!=0) { //se parcurge direct lista. Dacă nodul căutat este chiar primul.căutarea nodului înaintea căruia se va face inserarea . //trecere la urmatorul nod } else { //am gasit nodul p inaintea caruia se insereaza nou. //(1) ultim->urm=nou. Dacă lista este vidă sau nodul căutat nu s-a găsit.

Algoritmi şi structuri de date nou=incarca_nod(); nou->urm=p; //(1) (p->pre)->urm=nou; //(2) nou->pre=p->pre; //(3) p->pre=nou; //(4) return; } { adaugare_prim(); return; } } } }//sfarsit functie

else

Observaţie: Pentru manevrarea legăturii următoare a nodului precedent celui curent (notăm nodul curent p) este valabilă construcţia: (p->pre)->urm, unde (p>pre) este adresa precedentului nodului p.

16. Adăugare după un nod specificat prin cheie
Procedura de adăugare a unui nou nod după un nod precizat prin valoarea cheii este simalră celei prezentate la punctul 3. Cazul particular este cel în care nodul căutat este ultimul nod al listei, în această situaţie se va apela funcţia adăugare_ultim. Dacă lista este vidă sau nu s-a găsit nodul căutat, nu are sens să se opereze adăugarea unui nou nod. Inserarea propriu-zisă a nodului nou înaintea nodului p presupune refacerea legăturilor în aşa fel încât consistenţa listei să fie asigurată (să nu se piardă secvenţe de noduri prin distrugerea accesului la ele ): - nodul nou va avea ca legătură urm pe următorul nod al nodului p: nou->urm=p->urm; (1) - nodul p va fi urmat de nou p->urm=nou; (2) - precedentul nodului nou va fi nodul p nou->pre=p; (3) - nodul precedent al nodului următorul lui p devine nou: (p->urm)->pre=nou; (4)
void adaugare_dupa(int valoare) { tnod *p,*nou; //caut p si inserez nod p=prim; while(p!=0) { if (p->cheie!=valoare)

96

Algoritmi şi structuri de date { p=p->urm; } { if (p==ultim) {adaugare_ultim();return;} else { nou=incarca_nod(); nou->urm=p->urm; p->urm=nou; nou->pre=p; (p->urm)->pre=nou; return; } }

else

} }

ŞTERGEREA UNUI NOD Ştergerea capetelor prim şi ultim ale unei liste dublu înlănţuite nu diferă prin costul de calcul precum la listele simplu înlănţuite. Am văzul că în cazul listelor simplu înlănţuite ştergerea ultimului nod necesita un efort computaţional mai mare. Prin legătura prec a nodurilor unei liste dublu înlănţuite putem accesa nodurile precedent, fapt pentru care, la ştergerea ultimului nod nu este necesară traversarea completă a listei. Ştergerea unui capăt al listei presupune: - salvarea temporară a adresei capătului respectiv într-un pointer auxiliar - actualizarea şi marcarea noului capăt - eliberarea memoriei Oferim în continuare două variante pentru funcţiile de ştergere a capetelor prim şi ultim:
/*ştergere prim nod*/ void stergere_prim() { tnod*primvechi; if (prim==0) //lista vidă return; else {//mai sunt noduri if(prim!=ultim) {//salvare prim primvechi=prim; //actualizare prim prim=prim->urm; //marcare prim prim->pre=0; //eliberare memorie /*ştergere ultim nod*/ void stergere_ultim() { tnod*ultimvechi; if (ultim==0) //lista vidă return; else { if (prim!=ultim) { //salvare ultim ultimvechi=ultim; //actualizare ultim ultim=ultim->pre; //marcare ultim ultim->urm=0; //eliberare memorie

97

Algoritmi şi structuri de date free(primvechi); } else prim=ultim=0; } }//sfarsit functie free(ultimvechi); } else prim=ultim=0; } }//sfarsit functie

Ştergerea unui nod oarecare Ştergerea unui nod oarecare precizat prin valoarea cheii presupune: - căutarea nodului - ştergerea propriu-zisă a nodului Căutarea nodului se face prin parcurgerea într-un sens a listei şi compararea valorii cheii nodurilor curente cu valoarea dată. Dacă se găseşte un nod care verifică condiţie, se va opera etapa de ştergere propriu-zisă a nodului prin: o salvarea adresei nodului de şters o refacerea legăturilor pentru a asigura consistenţa listei şi posibilitatea parcurgerii ulterioare în ambele sensuri o eliberarea memoriei alocate nodului de şters

1
Refacerea legăturilor constă din următoarele asignări: - precedentul următorului lui p devine precedentul lui p p->urm->pre=p->pre; (1) - următorul precedentului lui p devine următorul lui p: p->pre->urm=p->urm; (2) Cazurile particulare se tratează separat: lista este deja vidă sau lista devine vidă după ştergerea nodului.
void stergere_nod(int valoare) { tnod *p,*pvechi; if (prim==0) return; //lista este deja vida if (prim==ultim && prim->cheie==valoare) {//lista devine vida prim=ultim=0; return; } p=prim;

2

p->prec

p

98

//eliberare memoriei.} pvechi=p. void stergere_lista() { while(prim!=0) stergere_prim(). Ştergerea capetelor listei nu asigură eliberarea memoriei ocupate de nodurile intermediare. //trecere la următorul nod } } Ştergerea listei Ştergerea completă listei dublu înlănţuită se poate face cu acelaşi efort de calcul prin apelarea repetată a funcţiei stergere_prim sau stergere_ultim. } 99 . //salvare adresă nod curent p->urm->pre=p->pre.} if (p==ultim) {stergere_ultim().adresa pvechi return.Algoritmi şi structuri de date while(p!=0) { if (p->cheie==valoare) //gasit { if (p==prim) {stergere_prim().return. (2) free(pvechi). } else //nu s-a gasit încă p=p->urm.return. (1) p->pre->urm=p->urm. Exemplu:ştergerea listei prin apelul repetat al funcţiei de ştergere a primului nod.

ceea ce conduce la o structură de listă circulară a cărei gestionare poate fi efectuată prin adresa pointer-ului prim.denumite capetele listei au fost utilizate în gestionarea listelor simplu şi dublu înlănţuite. COZI Lista circulară este o listă (simplu sau dublu) înlănţuită cu proprietatea că toate nodurile sunt echivalente. LISTE CIRCULARE. Organizarea unei liste circulare cu noduri de acest tip este sugerată de figura alăturată. nu există noduri speciale care nu conţin adresa nodurilor succesoare sau predecesoare. însă fără ca acesta să semnifice adresa unui capăt al listei. Orice listă simplu înlănţuită gestionată prin pointer-ii prim şi ultim se poate transforma în listă circulară printr-o operaţie elementară de asignare: ultim->urm=prim Prin operaţia anterioară s-a stabilit faptul că ultimul nod al listei iniţiale va conţine adresa primului nod al listei. respectiv. Structura nodului este similară celei prezentate la capitolul dedicat listelor simplu înlănţuite: typedef struct nod { int cheie. eventual. şi a adresei ultimului nod. //pointer la lista circulară 100 . Aceste noduri speciale . O listă circulară va fi gestionată prin alte mecanisme decât cele bazate pe menţinerea adreselor speciale prim şi ultim. LISTA SIMPLU ÎNLĂNŢUITĂ CIRCULARĂ Într-o listă circulară simplu înlănţuită toate nodurile conţin adresa următorului nod. STIVE. într-o listă circulară. gestionarea unei liste circulare se face prin unui pointer care referă oricare nod al listei: Tnod *pLC. pentru simplificarea prelucrărilor. Spre deosebire de listele simplu înlănţuite la care este suficientă cunoaşterea adresei primului nod şi. ci doar adresa unui nod oarecare. Astfel. //adresa următorului nod }Tnod. cunoaşterea adresei oricărui nod din compunerea listei este suficientă pentru a putea gestiona această structură.Algoritmi şi structuri de date X. //câmp cu valori unice pentru nodurile listei //alte câmpuri struct nod *urm.

scanf("%d". adăugarea după nodul referit de pLC Adăugarea unui nou nod înaintea nodului pLC necesită un efort computaţional suplimentar prin parcurgerea listei circulare. adăugarea înaintea nodului referit de pLC 2.. //lista este initial vida printf("\nIntroduceti? (1/0)")..creare .parcurgere . Această parcurgere este necesară pentru a determina adresa nodului precedent al nodului pLC în vederea refacerii legăturilor şi asigurării consistenţei listei. Crearea unei liste circulare care conţine cel puţin un element presupune o operaţie repetitivă de adăugare a unui nou nod.//alocare memorie şi încărcare nod if (pLC==0) . respectiv.distrugere (ştergere) . pLC=0. int rasp. Adăugarea nodului nou se poate efectua în două maniere: 1.. inserare după un nod oarecare diferă semnificativ prin necesitatea parcurgerii complete a listei în primul caz. void creare_LCSI() { Tnod *nou. Acest aspect a fost evidenţiat în cazul listelor simplu înlănţuite.&rasp).ştergere element Crearea listei circulare simplu înlănţuite Crearea listei vide se realizează prin iniţializarea pointer-ului pLC cu valoarea Null: pLC=0. 101 .adăugare nou element . pentru care operaţiile de inserare înaintea unui nod oarecare. Pentru nodul care se adaugă se va aloca memorie în prealabil şi se acesta va încărca cu informaţii. Funcţia incarca_nod prezentată în capitolele precedente poate fi utilizată în acest sens.Algoritmi şi structuri de date pLC Operaţiile posibile cu listele circulare sunt aceleaşi ca cele specifice listelor simplu înlănţuite: . while (rasp==1) { nou=incarca_nod(). Funcţia următoare adaugă noi noduri la lista circulară gestionată prin pLC în varianta 2.

//pLC va contine adresa noului nod } printf("\nIntroduceti? (1/0)"). pLC=nou. Nodurile următoare se parcurg cât timp pointer-ul auxiliar nu va avea aceiaşi adresă de început: pLC (nu a revenit la poziţia iniţială): p=pLC. //lista devine circulara pLC->urm=pLC. } else { //adaugare nou dupa pLC nou->urm=pLC->urm. Dacă pentru listele gestionate prin prim şi ultim această condiţie era evidentă (pointer-ul auxiliar prin care se parcurge lista a ajuns la ultim). Iniţial.&rasp). } } Parcurgerea listei circulare simplu înlănţuite Parcurgerea listei circulare se va face prin urmărirea legăturilor (adreselor) conţinute de noduri. //iniţializare p if (p==0) //lista este vida return. pointer-ul conţine adresa cunoscută a unui nod oarecare: pLC. //pointer-ul auxiliar p=pLC. //trecere la urmatorul nod }while (p!=pLC). p=p->urm. în cazul listelor circulare condiţia se referă la revenirea în punctul de plecare.p->cheie). în aceiaşi manieră ca la listele simplu înlănţuite. printr-un pointer auxiliar. } 102 . pLC->urm=nou. pLC=nou.Algoritmi şi structuri de date {//creare primul nod // nodul pLC contine adresa sa. // nu are sens continuarea parcurgerii do { printf(“\n %d”.scanf("%d". do { //prelucrare nod referit de p p=p->urm. Specificul listelor circulare implică o altă condiţie de oprire a traversării listelor. Funcţia următoare afişează cheile nodurilor unei liste circulare: void Tiparire() { tnod *p. //trecere la urmatorul nod }while (p!=pLC).

//pointer care refera nodul curent if (pLC==0) return. //s-a gasit nodul cautat p=p->urm. //lista este vida. Cunoscând adresa nodului de cheie dată şi adresa precedentului său. Inserarea unui nou nod înaintea unui nod specificat prin valoarea cheii presupune parcurgerea următoarelor etape: . //retine precedentul nodului curent p=p->urm. //trecere la urmatorul nod }while (p!=pLC). //precedentul nodului curent tnod *p.inserarea propriu-zisă dacă etapa anterioară s-a încheiat cu succes Observaţie: În etapa de căutare a nodului de cheie dată se va reţine adresa nodului precedent a nodului curent. nu are sens continuare operatiei p=pLC. Funcţia prin care se realizează această operaţie va returna adresa nodului găsit sau 0 în caz de insucces: tnod* cauta(int valoare) { tnod *p.Algoritmi şi structuri de date Operaţia de căutare a unui nod specificat prin valoarea cheii presupune parcurgerea listei circulare şi verificarea dacă nodurile conţin pentru câmpul cheie valoarea dată. tnod *prev. do //cautarea nodului p { prev=p. pentru a face posibilă refacerea legăturilor în faza de inserare. //s-a incheiat cautarea si nodul nu s-a gasit } Inserarea nodurilor într-o listă circulară simplu înlănţuită 1. încărcarea nodului nou cu informaţie şi refacerea legăturilor într-o manieră similară celei prezentate în operaţia omonimă pentru liste simplu înlănţuite: void inserareInainte(int valoare) { tnod *nou. În caz contrar ar fi necesară o reparcurgere a listei circulare pentru a determina precedentul nodului înaintea căruia va fi inserat noul nod. inserarea propriu-zisă a unui nou nod se reduce la: alocarea memoriei. Inserarea înaintea unui nod specificat prin valoarea cheii 2. //lista este vida do { if (p->cheie==valoare) return p.căutarea nodului de cheie dată . //iniţializare p if (p==0) return 0. //trece la urmatorul nod 103 . return 0. //pointer-ul auxiliar p=pLC. Inserarea după un nod specificat prin valoarea cheii 1.

//lista este vida.inserarea propriu-zisă Dacă prima etapă s-a încheiat cu succes. nu are sens continuare operatiei p=pLC. dat fiind faptul că o condiţie precedentă verificat situaţia opusă şi provoacă revenirea din funcţie.căutarea nodului de cheie dată . prev->urm=nou. 2. dar şi adresa următorului nod (datorită legăturii urm) p->urm. //iesire din instructuinea repetitiva //p este adresa nodului gasit p=p->urm. se cunoaşte adresa nodului de cheie dată p. //dacă s-a ajuns în acest punct. Funcţia următoare este o posibilă implementare a operaţiei discutate: void inserareDupa(int valoare) { tnod *nou.Algoritmi şi structuri de date if (p->cheie==valoare) //s-a gasit nodul break. //pointer care refera nodul curent if (pLC==0) return. Nu mai este necesară determinare altei adrese decât cea a nodului căutat după valoarea cheii.instrucţiunea decizională if (p->cheie==valoare) … este redundantă. Din motive de lizibilitate şi nu de optimizare a codului am convenit să furnizăm o variantă explicită a funcţiei pentru o urmărire uşoară a etapelor descrise. if (p->cheie!=valoare) //cautarea s-a incheiat cu Insucces return. if (p->cheie!=valoare) //cautarea s-a incheiat cu Insucces return.nodul referit de pLC este ultimul nod verificat în etapa de căutare . Nodul nou va fi inserat între cele două noduri de adrese cunoscute. } } Observaţii: . if (p->cheie==valoare) //cautarea s-a incheiat cu Succes { //etapa de inserare nou=incarca_nod(). //trece la urmatorul nod }while(p!=pLC). tnod *p. Inserarea unui nod nou după un nod precizat de cheie presupune: . do //cautarea nodului p { if (p->cheie==valoare) //s-a gasit nodul break. cautarea s-a incheiat cu //Succes 104 . //alocare memorie si incarcare nou nou->urm=p. //iesire din instructuinea repetitiva //p este adresa nodului gasit }while(p!=pLC).

Algoritmi şi structuri de date //etapa de inserare nou=incarca_nod(); //alocare memorie si incarcare nou nou->urm=p->urm; p->urm=nou; }

Observaţie: nodul referit de pLC este primul nod verificat în etapa de căutare. Ştergerea unui nod precizat de valoarea cheii Operaţia de ştergere a nodului precizat printr-o cheie presupune: - căutarea nodului şi reţinerea adresei precedentului său () - ştergerea nodului: refacerea legăturilor şi eliberarea memoriei Cazurile particulare ale operaţiei se tratează diferit: a. lista este vidă înainte ştergerii b. lista devine vidă după ştergere c. nodul de şters este chiar pLC Convenim că în cazul particular c. (nodul ce se va şterge este chiar nodul referit de pointer-ul pLC şi lista nu devine vidă), pLC va referi nodul precedent celui şters. O funcţie C care descrie operaţia de ştergere este următoarea:
void steregereNod(int valoare) { tnod *p,*prev; //p - adresa nodului curent //prev - adresa precedentului nodului curent if (pLC==0) return; //lista este vida, cazul particular (a.) p=pLC; do //cautarea nodului p { prev=p; //retine precedentul nodului curent p=p->urm; //trece la urmatorul nod if (p->cheie==valoare) //s-a gasit nodul break; //iesire din instructuinea repetitiva, p este adresa

nodului gasit
}while(p!=pLC); if (p->cheie!=valoare) return; //nu s-a gasit nodul

//nodul gasit este referit de p, urmeaza etapa de stergere
if (p->urm==p) //lista are un singur nod - nodul care se va sterge (b.) { pLC=0; //lista devine vida free(p); //eliberare memorie } else

105

Algoritmi şi structuri de date { if (p==pLC) //nodul de sters este referit de pLC, cazul (c.) { pLC=prev; //actualizare pLC free(p); //eliberare memorie } else //cazul general { prev->urm=p->urm; //refacere legaturi free(p); //eliberare memorie } }

}//sfarsit functie steregereNod Observaţie: în situaţia în care informaţia din noduri conţine adrese alocate dinamic (prin apelul funcţiei malloc), eliberarea memoriei alocate unui nod p trebuie să ţină cont şi de acest aspect, fapt pentru care, apelul funcţiei free(p) nu este suficient. Din aceste considerente, o funcţie specială de eliberare a memoriei alocate unui nod poate fi concepută. Spre exemplu: typedef struct nod { int CNP; //câmp cu valori unice pentru nodurile listei char *nume struct nod *urm; //adresa următorului nod }Persoana; Alocarea memoriei pentru un nod de tipul Persoana (Persoana *p) necesită un apel malloc pentru câmpul nume. Eliberarea memoriei alocate nodului p se va executa corect prin funcţia următoare:
void eliberare_nod(Persoana *p) { free(p->nume); free(p); }

Ştergerea liste circulare simplu înlănţuite Operaţia de distrugere a unei liste circulare se realizează prin ştergerea tuturor nodurilor sale şi nu prin distrugerea adresei speciale pLC prin care se gestionează lista. Dacă nu este deja vidă, lista se parcurge şi noduri precedente nodului curent se şterg până când lista devine vidă. O funcţie de ştergere a listei circulare gestionate prin pLC este următoarea:
void stergere() { tnod *p; //nodul curent tnod *prev; //precedentul nodului curent if (pLC==0) return; //lista este deja vida

106

Algoritmi şi structuri de date p=pLC; do { prev=p; p=p->urm; eliberare_nod(prev); }while(p!=pLC) pLC=0; //marcare listă vidă }

Observaţie: primul nod eliberat este cel referit de pLC, fapt pentru care când condiţia p==pLC devine adevărată se indică revenirea în punctul de plecare a pointer-ului p ceea ce semnifică faptul că toate nodurile au fost şterse (inclusiv nodul referit de pointer-ul special pLC) şi lista este vidă. LISTA DUBLU ÎNLĂNŢUITĂ CIRCULARĂ Lista circulară dublu înlănţuită este gestionată printr-un pointer la un nod oarecare. Structura nodului este cea prezentată la listele dublu înlănţuite şi conţine: zona de informaţii, adresa precedentului şi adresa nodului următor. Operaţiile specifice: creare, inserare nod, ştergere nod, ştergere listă, parcurgere, căutare, sunt similare operaţiilor descrise cu liste circulare simplu înlănţuite. Diferenţele semnificative apar la procedurile de inserare înaintea unui nod precizat şi ştergerea unui nod oarecare, care se simplifică prin existenţa unei legături spre nodurile precedente. Transformarea unei liste dublu înlănţuite în listă circulară se realizează prin legarea capetelor prim şi ultim, în ambele sensuri: ultim->urm=prim; prim->prec=ultim; STIVE. COZI. Stiva reprezintă un caz special de lista liniara în care intrările si ieşirile se fac la un singur capăt al ei. Organizarea structurilor de date de tip stivă se poate face în două maniere: - secvenţial - elementele stivei sunt memorate la adrese consecutive - înlănţuit – elementele stivei nu ocupă adrese consecutive, fiecare element conţine o legătură spre următorul element. Prin organizarea secvenţială nu se poate face economie de memorie, fapt pentru care în general se practică organizarea înlănţuită cu alocare dinamică a stivelor. Structura de stivă se remarcă prin operaţiile specifice: push şi pop, corespunzătoare adăugării unui element, respectiv, ştergerii unui element în/din vârful stivei. Principiul de funcţionare al stivei este cel cunoscut sub denumirea de LIFO (Last In First Out – ultimul intrat, primul ieşit).

107

creare stivă vidă .adăugare a unui nou nod după ultim (adăugare în vârful stivei) .a elementului din vârful stivei În plus faţă de operaţiile enumerate anterior sunt posibile implementate operaţii de verificare: .ştergere ultim (ştergere din vârful stivei) Privitor la eficienţa operaţiilor descrise într-un capitol anterior.verifică dacă stiva este plină .adăugare a unui nou nod înainte de prim (adăugare în vârful stivei) . operaţiile push şi pop se traduc prin operaţiile de: .adăugare element (push) . adăugarea unui nou nod înaintea celui referit de prim este mai puţin costisitoare. În schimb. se practică o inversare a rolurilor celor două capete ale stivei.Algoritmi şi structuri de date vârful vârful info I Structura de date STIVĂ cu alocare statică II. Structura de date STIVĂ cu alocare dinamică Practic.verifică dacă stiva este goală Gestionarea stivei se face în mod similar listei înlănţuite prin capetele prim şi ultim. ceea ce convine unei asocieri a nodurilor referite de prim şi ultimi cu cele două elemente specifice: . La nivel abstract.baza stivei corespunde nodului prim şi vârful stivei corespunde nodului ultim În această abordare. stiva este o listă simplu înlănţuită pentru care operaţiile specifice se limitează la următoarele: . o stivă are o bază a sa şi un vârf.ştergere element (pop) .ştergere prim (ştergere din vârful stivei) info info baza baza info 108 .şterge lista (clear) .baza stivei corespunde nodului ultim şi vârful stivei corespunde nodului prim Astfel. operaţiile push şi pop se vor traduce prin: . pentru a obţine operaţii mai eficiente: . ne reamintim că operaţia de adăugare a unui nou element după cel referit de pointer-ul ultim necesita o parcurgere prealabilă a listei. Din aceste considerente.accesare – fără eliminare .

Algoritmi şi structuri de date Coada este un alt caz special de listă înlănţuită bazat pe principiul FIFO (First In First Out – primul intrat.ştergere element din cap . operaţiile de adăugare şi scoatere elemente în/din lista FIFO se traduc prin: .adăugare înainte de prim şi ştergere nod ultim Constatăm că spre deosebire de stive. adăugarea şi ştergerea unui element se execută în capetele diferite ale cozii.prim referă capul listei şi ultim referă coada listei . organizarea unei cozi poate fi făcută în mod secvenţial (static) – prin intermediul tablourilor unidimensionale sau dinamic – prin liste simplu înlănţuite. iar scoaterea din listă a unui nod este implementată prin ştergerea nodului prim (cap).creare stivă vidă .adăugare element în coadă .ultim referă capul listei şi prim referă coada listei Conform celor două abordări enumerate anterior. astfel încât alegerea oricărei variante este posibilă. adăugarea unui nod se face după ultimul nod (coada) al listei.şterge lista (clear) Spre deosebire de stivă. Ca şi în cazul stivelor. ceea ce permite organizarea în două maniere: . Cea de-a doua variantă este de preferat din raţiuni economice. 109 . ambele abordări sunt eficiente. Operaţiile primare cu cozi sunt: .adăugare după nodul ultim şi ştergere nod prim . Acest principiu arată că primul element introdus în listă este şi primul care va fi şters. primul ieşit). denumite sugestiv: cap şi coadă. Printr-o convenţie. O structură de acest gen are două capete. prim Coada este astfel o listă înlănţuită ale cărei capete referite prin prim şi ultim semnifică capul şi coada structurii.

Arborii sunt structuri de date de natură dinamică şi recursivă (ca şi listele). nodurile diferite de rădăcină formează submulţimi disjuncte – denumite subarbori. a n } . n > 0 Proprietăţile arborelui sunt: 1. pentru nodurile care urmează un nod părinte şi fraţi – pentru nodurile care au acelaşi părinte. ARBORI Un arbore reprezintă o mulţime nevidă şi finită de elemente de acelaşi fel. Reprezentarea grafică a unui arbore oarecare este ilustrată mai jos: Nodurile arborelui pot fi: . există un singur nod numit rădăcină 2. pe care le denumim noduri: A = { a 1 .noduri interne . fii.noduri terminale (frunze) Nodul rădăcină se distinge de celelalte noduri prin faptul că nu acesta nu are succesori (noduri care îl preced ). Nodurile interne sunt precedate de 1 singur nod şi pot fi urmate de 1 sau mai multe noduri.nodul rădăcină (fiecare arbore are un singur nod de acest tip) .a 2 . 3.. Nodurile unui arbore se caracterizează prin două mărimi: 110 .Algoritmi şi structuri de date XI. Terminologia specifică încorporează denumirile de părinte pentru nodul care preced alte noduri.. Nodurile terminale sunt precedate de 1 singur nod şi sunt urmate de 0 noduri... Fiecare subarbore respectă proprietăţile 1 şi 2.

mărime care reprezintă numărul de nivele pe care le conţine subarborele respectiv. Un nod al arborelui conţine: d. 7 111 8 9 . Ordinul nodului: este un număr natural şi reprezintă numărul de descendenţi direcţi (fii) pe care îi are nodul respectiv. spunem că arborele respectiv este arbore ordonat. Nivelele nodurilor în arbore Nodul Ordinul nodului 1 O(1)=2 2 O(2)=3 3 O(3)=0 4 O(4)=2 5 O(5)=0 6 O(6)=1 7 O(7)=0 8 O(8)=0 9 O(9)=0 Ordinul fiecărui nod din arborele prezentat ca exemplu 2 4 5 6 Fiecare subarbore al arborelui este caracterizat prin înălţimea sa. Nodurile terminale au ordinul 0. zona de date e.Algoritmi şi structuri de date - Nivelul nodului: reprezintă o valoarea naturală prin care se identifică numărul de strămoşi până la nodul rădăcină. 1 sau mai multe legături spre fii săi Dacă între fiii oricărui nod există o relaţie de ordine. o Nodul rădăcină este considerat pe nivelul 1 o Fiii nodului rădăcină sunt pe nivelul 2 o Fiii fiilor nodului rădăcină sunt pe nivelul 3 o etc.

atunci arborele respectiv este numit arbore binar. Anumite noduri interne pot să aibă doar un fiu.legătură spre fiul stâng . În plus. cu excepţia legăturii cu primului fiu Exemplu: Fie arborele oarecare din figura următoare: Figura 1 Arbore oarecare După transformare. Nodurile terminale au ambele legăturile nule. Importanţa studierii arborilor binari este dată de multitudinea de aplicaţii practice în care se face uz de această structură de date.1 sau 2. se suprimă legăturile dinspre părinte. există posibilitatea ca una sau ambele legături ale unui părinte spre fiii săi să fie nule.legătură spre fiul drept Într-un arbore binar. astfel încât legătura spre celălalt fiu este nulă. un arbore poate fi transformat şi reprezentat prin arbore binar. Transformarea presupune etapele următoare: 1.Algoritmi şi structuri de date Dacă numărul de fii ai fiecărui nod din compunerea unui arbore este 0. Structura unui nod al arborelui binare conţine: .zona de informaţii . arborele devine binar: 2 5 112 6 . se stabileşte legătură între fraţii de acelaşi părinte 2.

a cărei valoare este adresa fiului stâng a nodului curent dr – este un pointer. de la stânga la dreapta. //legătura spre subarborele stâng struct nod * dr. 5 ARBORI BINARI Structura unui nod al arborelui binar este următoarea: typedef struct nod { //informaţii struct nod * st.Algoritmi şi structuri de date 1 Figura 2 Arbore binar 2 Parcurgerea arborilor Parcurgerea (traversarea) arborilor presupune obţinerea unei liste a nodurilor arborelui. avem mai multe tipuri de traversare a arborilor. Parcurgerea în lăţime (in breadth): se vizitează nodurile pe nivele. în aceiaşi manieră. unde: st – este un pointer. Parcurgerea în adâncime a arborelui considerat ca şi exemplu în figura 1 produce următoarea listă a nodurilor: 7 5 6 2 3 4 1. spre ex. pornindu-se de la nivelele cele mai de sus (nivelul 1 – rădăcina) spre nivelele mai mari. Parcurgerea în lăţime a arborelui din figura 1 produce lista: 1 2 3 4 5 6 7. şi pe fiecare nivel se vizitează nodurile într-o anumită ordine. În funcţie de ordinea în care sunt considerate nodurile arborelui. a cărei valoare este adresa fiului drept a nodului curent 7 6 113 . - - Parcurgerea în adâncime (in depth): se vizitează subarborii descendenţi ai nodului rădăcină. apoi se vizitează nodul rădăcină. //legătura spre subarborele drept }Tnod.

în poziţia rădăcinii . ştergerea unui nod 4.stabilirea legăturilor în arbore În privinţa locaţiei în care poate fi inserat nodul. Tnod * pnod2).ca nod intern Ştergerea unui nod oarecare presupune: . Gestionarea unui arbore binare este posibilă prin adresa nodului rădăcină: Tnod *rad. Operaţiile de inserare şi accesare a nodurilor dintr-un arbore binar presupun existenţa unui criteriu care se desprinde din specificaţiile problemei concrete de rezolvat.alocarea de memorie şi încărcarea cu informaţii a nodului nou .eliberarea memoriei alocate nodului şters Ştergerea întregului arbore necesită ştergeri repetate ale nodurilor sale până când rădăcina devine vidă. Operaţiile specifice arborilor binari sunt: 1. accesarea unui nod (căutarea) 6.ca nod terminal .determinarea locului în care va fi inserat noul nod . Aceste criterii fiind variate. rezolvarea problemelor cu arbori binare se poate simplifica prin considerarea unei funcţii verifică care are doi parametri: pnod1 şi pnod2 – adresele a două noduri şi returnează valoare -1. crearea arborelui 2. parcurgerea unui arbore Operaţia de creare a arborelui binar necesită iniţializarea nodului rădăcină: rad=0 şi adăugarea (inserarea) ulterioară a noilor noduri după o anumită regulă. Parcurgerea unui arbore binare poate fi făcută în trei moduri: 114 . Prototipul acestei funcţii ajutătoare este: int verifica(Tnod *pnod1 . Inserarea unui nou nod presupune: .refacerea legăturilor în arbore .Algoritmi şi structuri de date Absenţa unui fiu (stâng sau drept) se marchează prin valoarea Null (0) a pointer-ului corespunzător. ştergerea arborelui 5. aceasta este dată de criteriul specificat de problema concretă şi se disting următoarele cazuri: .determinarea locaţiei sale .0 sau 1 cu semnificaţia: A) -1 dacă pnod2 este adresa unui nod care poate fi accesat sau inserat în stânga nodului adresat de pnod1 B) +1 dacă pnod2 este adresa unui nod care poate fi accesat sau inserat în dreapta nodului adresat de pnod1 C) 0 dacă pnod2 referă un nod care NU poate fi accesat sau inserat în subarborii stâng sau drept ai nodului referit de pnod2. inserarea unui nod 3.

R S D 2.Simplificat.Simplificat: ordinea de traversare a arborelui este S (stânga) R (rădăcină) D (dreapta). . parcurgerea în inordine: pentru fiecare nod curent se va parcurge subarborele stâng.Algoritmi şi structuri de date 1. (fiecare subarbore va fi parcurs în aceiaşi manieră). postordine. S (subarbore stâng). S (subarbore stâng). inordine) sunt date mai jos: void preordine(tnod *p) { if p!=0 { //vizitarea nodului curent (tipărirea cheii) 115 . subarborele drept şi în final se va accesa/prelucra informaţia conţinută în nodul curent (fiecare subarbore va fi parcurs în aceiaşi manieră). parcurgerea în postordine: pentru fiecare nod curent se va parcurge subarborele stâng. nodul curent şi în final subarborele drept (fiecare subarbore va fi parcurs în aceiaşi manieră). . parcurgerea în preordine a unui arbore presupune parcurgerea în ordinea: R (rădăcină). parcurgerea în preordine: pentru fiecare nod curent se va accesa/prelucra informaţia conţinută.Simplificat parcurgerea în postordine înseamnă traversarea arborelui în ordinea: S (stâng) D(drept) R (rădăcină) 3. subarborele stâng şi în final subarborele drept. . Exemplu: La parcurgerea în preordine a arborelui din figura anterioară se vor accesa nodurile în ordinea: J H E A B F I G C D La parcurgerea în postordine se vor accesa nodurile în ordinea: ABEFHCGDIJ La parcurgerea în inordine se vor accesa nodurile în ordinea: AEBHFJICGD Funcţiile de parcurgere a unui arbore binar în cele trei variante (preordine.

//parcurgere subarbore stâng //autoapelare funcţie preordine preordine(p->st). se consideră funcţia auxiliară prin care se verifică îndeplinirea sau neîndeplinirea criteriului de căutare şi continuare căutării într-unul din subarborii stâng sau drept ai nodului curent. În mod frecvent. sau se va continua căutarea în subarborele stâng sau drept a nodului adresat de pnod1. În cazul în care s-a găsit un nod care respectă criteriul. acest lucru trebuie semnalat.p->cheie). În scopul definirii unei funcţii de căutare în arborele binar. O funcţie de căutare se poate descrie ca mai jos. considerăm funcţia verificare descrisă anterior prin care se decide dacă un nod căutat pnod2 este găsit în arbore la adresa pnod1. } } Căutarea unui nod Fiind dat un criteriu de căutare. Dacă nu se găseşte niciun nod care verifică criteriul. } } void postordine(tnod *p) { if (p!=0) { postordine(p->st).Algoritmi şi structuri de date printf(“\t%d”. printf(" %d". postordine(p->dr). } } void inordine(tnod *p) { if (p!=0) { inordine(p->st).p->cheie). procesul de parcurgere a arborelui se va încheia.//vizitare nod inordine(p->dr). Pentru a generaliza. căutarea într-un arbore binar presupune parcurgerea nodurilor şi verificarea criteriului pentru fiecare nod vizitat. Funcţia returnează adresa nodului găsit sau 0 în cazul în care nu se găseşte un nod care verifică criteriul stabilit. tnod *rad. criteriul se referă la îndeplinirea condiţiei de egalitate între o valoare dată şi valoarea unui câmp din informaţia memorată în noduri. //parcurgere subarbore stâng //autoapelare funcţie preordine preordine(p->dr). //variabilă globală 116 . printf(" %d". p->cheie).

if rad==0 return 0. În plus faţă de aspectele menţionate la prezentarea arborilor binari. //s-a găsit nodul else if (verificare(p. //continuare cautare la stanga } return 0.toate nodurile din stânga nodului respectiv au valorile cheilor mai mici decât cheia nodului curent . ARBORI BINARI DE CĂUTARE Un caz particular de arbori binari sunt arborii binari de căutare.Algoritmi şi structuri de date tnod *cautare(tnod *pnod2) { // caută nodul din arbore care este echivalent cu pnod2 tnod *p. //continuare cautare la stanga else p= p->dr. În realizarea acestei operaţii ne putem folosi de funcţia auxiliară verifica sau o altă funcţie prin care se decide în care din subarbore va fi continuată căutarea posibilului părinte. respectiv. Pentru fiecare nod care urmează să fie inserat este necesară în prealabil determinarea poziţiei în care acesta va fi adăugat. //arborele este vid while (p!=0) { if (verificare(p.toate nodurile din dreapta nodului respectiv au valorile cheilor mai mari decât cheia nodului curent Exemplu: Figura alăturată ilustrează reprezentarea grafică a unui arbore binar de căutare (valoarea cheii este precizată în fiecare nod): 117 . Nodul părinte se va determina printr-o parcurgere parţială a arborelui. //nu s-a gasit nodul cautat } Crearea unui arbore binar Operaţia de creare a unui arbore binar presupune operaţii repetate de inserare a unui nou nod ca nod terminat. un arbore de căutare prezintă o caracteristică suplimentară: fiecare nod al său conţine o cheie (câmp cu valori unice pentru nodurile arborelui) şi: .pnod2)==-1) p= p->st.pnod2)==0) return p. părintele de care acesta se va lega.

În plus. Fiind dată o mulţime de informaţii ce trebuie organizate sub forma unui arbore binar de căutare. Inserarea nodurilor. celălalt subarbore fiind exclus din căutare. 118 .Algoritmi şi structuri de date Se poate observa o proprietate importantă a arborilor binari de căutare: la parcurgerea în inordine se obţine lista nodurilor ordonate după valoarea cheii. Dacă rădăcina este vidă înainte de inserare. Căutarea unei informaţii într-o structură de arbore binar de căutare este eficientă. 4 2 6 1 3 5 1. crearea arborelui se realizează în următoarele etape: creare arbore vid creare arbore cu un singur nod (rădăcină) inserare noduri cât timp mai există informaţie de organizat La fiecare pas de inserare unui nod în arborele deja format se va ţine cont de criteriul de ordonare şi arborele rezultat va fi tot un arbore binar de căutare. nodul de inserat va deveni rădăcina arborelui: rad=nou. căutarea se va continua doar într-unul dintre cei doi subarbori. Căutarea se va încheia în momentul în care un nod rădăcină a subarborelui curent conţine cheia căutată sau dacă nu mai sunt noduri de parcurs şi cheia nu a fost găsită. Procedeul se va continua în mod similar pentru subarborele curent: se compară valoarea cheii căutate cu cheia rădăcinii subarborelui şi în funcţie de rezultatul comparaţiei se va continua căutarea doar într-unul dintre subarborii subarborelui curent. Datorită specificului acestor arbori. Creare arbore binar de căutare. rad->st=rad->dr=0. valoarea cheii este comparată cu cheia conţinută în rădăcină şi în funcţie de rezultatul comparaţiei. în definirea unor funcţii specifice care operează asupra arborilor de căutare se va ţine cont de criteriul de ordine între cheile nodurilor ce formează arborele. operaţia de căutare a unui nod specificat de valoarea cheii este simplificată (de aici şi denumirea arborilor de căutare). deoarece numărul nodurilor accesate se reduce prin excluderea acelor subarbori a căror parcurgere ar fi inutilă. Operaţiile de inserare şi ştergere executate asupra unui arbore binar de căutare vor produce de asemenea un arbore binar de căutare. Astfel. Căutarea unui nod după o cheie dată nu necesită parcurgerea întregului arbore.

return 1. scanf("%d". if (rad==0) {//creare radacina rad=nou. părintele trebuie să aibă legătura dreapă vidă int inserare(tnod *nou) { tnod *p.nou->st=nou->dr=0. //arbore vid for (int i=0. while(1) { if (nou->cheie<p->cheie) if (p->st!=0) p=p->st.//eliberare memorie alocată pt. else {p->dr=nou.return 1. tnod *nou. atunci. pentru a reuşi legarea nodului nou în stânga sa (prin aceasta ne asigurăm că subarborele construit.i<n-1. if (nou!=0) //s-a reușit încărcarea { j=inserare(nou). if (j==0) //nu s-a reuşit inserarea eliberare_nod(nou). este ordonat) dacă cheia conţinută de părinte este mai mică decât cheia nodului nou.Algoritmi şi structuri de date Dacă există cel puţin un nod în arbore. } p=rad. else {p->st=nou. atunci. al cărei rădăcină este nodul părinte.&n).} } return 0. rad=0. rad->st=rad->dr=0.return 1. se va căuta un părinte al nodului de inserat.i++) { nou=incarca_nod(). //nu s-a realizat operaţia. cod de eroare } Arborele binar de căutare se poate construi printr-o secvenţă: printf("\ncate noduri introduceţi?"). Acest părinte îndeplinească condiţiile: dacă cheia conţinută de părinte este mai mare decât cheia nodului nou. nou->st=nou->dr=0.nou->st=nou->dr=0.} else if (p->dr!=0) p=p->dr. nou } } 119 . părintele trebuie să aibă legătura stângă vidă.

//nu s-a găsit nodul. În funcţie de poziţia nodului de şters: p şi a părintelui său în arbore: parinte. Prima etapă a operaţiei de ştergere constă în determinarea nodului de cheie precizată (nodul ce se va şterge). în caz de insucces tnod * cautare(int val) { tnod *p. sau .0. În acest scop este utilă o funcţie care caută cheia în arbore. este nevoie determinarea unui element suplimentar: adresa părintelui nodului ce va fi şters. întâlnim următoarele cazuri: a. } } return p. } if (p->cheie<val) {//continuare căutare în subarbore drept p=p->dr. va produce un arbore cu aceleaşi caracteristici (cheile nodurilor rămân ordonate după ştergere). nodul găsit este nod frunză (nod terminal) şi este situat în dreapta părintelui său: . Dacă nu se găseşte un astfel de nod. p=rad. nodul găsit este nod frunză (nod terminal) şi este situat în stânga părintelui său: . însă faţă de operaţia de căutare descrisă anterior. Ştergere nod de cheie precizată Ştergerea unui nod de cheie precizată.se eliberează nodul .se stabileşte legătura dreaptă a părintelui ca fiind nulă: parinte->dr=0 b.Algoritmi şi structuri de date 2. while(p!=0) //câttimp mai sunt noduri de vizitat { if (p->cheie==val) return p.se stabileşte legătura stângă a părintelui ca fiind nulă: parinte->st=0 120 . Construim mai jos o funcţie care primeşte ca parametru valoarea cheii căutate şi returnează: . //s-a gasit nodul if (val<p->cheie) { //continuare căutare în subarbore stâng p=p->st. Căutarea unui nod de cheie precizată Căutarea după valoarea cheii este operaţia specifică în arborele binar de căutare prin care se determină adresa nodului a cărei cheie este egală cu o valoare dată. operaţia se încheie cu insucces.se eliberează nodul .adresa nodului găsit. precum şi celelalte operaţii specifice arborilor de căutare. p este Null } 3.

Cazul e este cel mai complex caz de ştergere a unui nod.1 o dacă nodul este legat în dreapta parintelui.2 . nodul căutat p nu este terminal şi are ambii subarbori (legăturile stânga şi dreapta sunt nenule).d într-o singură tratare: părintele nodului p va conţine adresa subarborelui lui p (stâng sau drept). nodul căutat nu este nod terminal. atunci parinte->st=p->st – cazul c. atunci parinte->dr = p->dr – cazul d. dar are doar un subarbore drept .b.leagă părintele de subarborele stâng al nodului de şters: o dacă nodul este legat în stânga părintelui. Numim succesor al nodului p.eliberează nodul e. chiar dacă acest subarbore este vid (nodul p este terminal). Dacă ordinul nodului p este 2.eliberează nodul d. atunci parinte->dr = p->st – cazul c.2 . se va trata cazul e. Atât predecesorul cât şi succesorul unui nod oarecare.leagă părintele de subarborele drept al nodului p: o dacă nodul este legat în stânga părintelui. sunt noduri terminale în arborele dat. cel mai din dreapta nod din subarborelui stâng al lui p. atunci parinte->st=p->dr– cazul d.Algoritmi şi structuri de date c. cel mai din stânga nod din subarborelui drept al lui p. Exemplu: 7 Nodul de sters 121 Nodul de sters 4 4 . O altă manieră de stabilire a cazurilor de ştergere în arborele binar este dată de ordinul nodului ce va fi şters: Dacă nodul are ordinul 0 sau 1 (are maxim un subarbore) este util să grupăm cazurile descrise mai sus a.c. Numim predecesor al nodului p.1 o dacă nodul este legat în dreapta parintelui. nodul căutat p nu este nod terminal. dar are doar un subarbore stâng .

Exemplu: 7 Figura 3 Înainte de ştergerea nodului Nodul Figura 4 După ştergerea nodului de sters Observaţii: Dacă nodul p ce va fi şters este părintele direct al predecesorului 2 2 6 4 3 său. astfel încât. şi sunt noduri cu un singur subarbore. După operaţia În acest caz. Dacă nodul p ce va fi şters nu este părintele direct al predecesorului său. predecesorul este legat în dreapta părintelui său direct – nodul 2. atunci.Algoritmi şi structuri de date Nodurile predecesor şi succesor au proprietate că sunt nodurile de cheie imediat mai mică. predecesorul este legat de părintele său direct prin legătura dreaptă. nodul 4 nu este părintele direct al predecesorului (nodul 3).copierea informaţiei din predecesor/succesor în nodul p .ştergerea predecesorului/succesorului nodului p. urmată de ştergerea acestuia. Ne imaginăm următoarea situaţie: 1 de ştergere. atunci predecesorul este în stânga părintelui său (în stânga nodului p). fapt pentru care ştergerea nodului p se realizează prin: . imediat mai mare decât cheia nodului p. arborele devine: 31 5 Predecesor de şters se va actualiza legătura stângă. pentru nodul Nodul de sters 122 3 .cazul ştergerii unui nod cu un subarbore Practic. respectiv. ştergerea unui nod cu doi subarbori se transformă într-o căutare a altui nod care are maxim un subarbore. Exemplu: În figura anterioară.

void stergere()//stergere nod de cheie data { tnod *p.} } //cazul II________________________nod cu un subarbore 4 //cazul c. eliberare_nod(p).p=p->dr.&cheie). //arborele devine vid if (tatap->st==p) //cazul a {tatap->st=0. p are doar subarbore stang if (p->dr==0) { 123 .Algoritmi şi structuri de date 6 Funcţia de mai jos este o variantă de implementare a algoritmului de ştergere a unui nod dintr-un arbore de căutare. tratarea cazului se face în mod diferit pentru protejarea adresei de acces la arbore. printf("\ndati cheia"). return.} if (tatap->dr==p) //cazul b {tatap->dr=0.*tatap.} if (cheie>p->cheie) {tatap=p. //caut nodul p. return.return. Dacă acest nod trebuie şters. Se acordă atenţie nodului rădăcină.p=p->st.} 2 1 5 //cazul I________________________nod frunza if ((p->st==0)&&(p->dr==0)) { if (p==rad) rad=0. while(p!=0) { if (p->cheie==cheie) break. acesta fiind un nod important prin care se gestionează arborele.*predecesor. int cheie.scanf("%d". if (cheie<p->cheie) {tatap=p.} }//caut p si retine parintele sau if (p==0) {printf("\n Nu exista cheia cautata!!!"). p=rad. eliberare_nod(p).*tatapredecesor.

1 { tatap->st=p->st. return. return.} if (tatap->dr==p) // cazul d.Algoritmi şi structuri de date if (p==rad) {rad=p->st.} }//sfarsit caz d //cazul e________________________nod cu 2 subarbori //pas 1 caut predecesor si retin parintele predecesorului tatapredecesor=p. } if (tatapredecesor!=p) { tatapredecesor->dr=predecesor->st.2 { tatap->dr=p->st. eliberare_nod(p).} }//sfarsit caz c //cazul d. return. while(predecesor->dr!=0) { tatapredecesor=predecesor.1 { tatap->st=p->dr. }//retin parintele predecesorului if (tatapredecesor==p) {//nodul de sters este parintele predecesorului tatapredecesor->st=predecesor->st. return. eliberare_nod(predecesor).return. eliberare_nod(p).1 { tatap->dr=p->dr. predecesor=p->st. p->cheie=predecesor->cheie.} if (tatap->st==p) // cazul c.return. predecesor=predecesor->dr. eliberare_nod(p). return. eliberare_nod(p).eliberare_nod(p). p are doar subarbore drept if (p->st==0) { if (p==rad) {rad=p->dr.eliberare_nod(p). p->cheie=predecesor->cheie.} if (tatap->st==p) // cazul d.} if (tatap->dr==p)// cazul c. return. } 124 . eliberare_nod(predecesor).

şi pentru arborii echilibraţi poate fi una dintre valorile -1. Ultimul nod eliberat este nodul rădăcină. prin operaţii suplimentare se va reorganiza arborele pentru obţine un arbore echilibrat. În cazul în care arborele a devenit dezechilibrat. formulată prin: Pentru orice nod al arborelui diferenţa dintre înălţimea subarborelui stâng al nodului şi înălţimea subarborelui drept al nodului este maxim 1. } } Apelul funcţiei este: stergere_arbore(rad). eliberare_nod(p). parcurgere. în operaţiile asupra arborilor AVL trebuie ţinut cont de proprietatea de echilibru din moment ce o operaţie de inserare a unui nod nou sau de ştergere a unui nod poate conduce la arbori binari dezechilibraţi. Velski. ulterior. Aceştia au în plus proprietatea de echilibru. Fiecărui nod într-un arbore AVL îi este asociată o mărime denumită factor de echilibru. Dacă factorul de echilibru pentru un nod are altă valoare decât cele enumerate. Operaţiile posibile asupra arborilor AVL sunt aceleaşi operaţii ca în cazul arborilor binari ordonaţi simpli: creare. ARBORI BINARI ECHILIBRAŢI (AVL) Un caz special de arbori binari ordonaţi (arbori de căutare) sunt arborii AVL (descrişi de Adelson. În schimb. 0. arborele nu este arbore AVL. însă. Factorul de echilibru se defineşte prin diferenţa dintre înălţimile subarborelui drept şi înălţimea subarborelui stâng. +1. inserare. Probleme propuse spre rezolvare 125 . În acest scop se poate defini o funcţie recursivă prin care se parcurge arborele în post ordine şi vizitarea nodului constă în eliberarea sa. void stergere_arbore(tnod *p) { if (p!=0) { stergere_arbore(p->st). Landis). Ştergere arbore binar de căutare Ştergerea completă a arborelui binar constă în ştergerea tuturor nodurilor sale. este verificată îndeplinirea proprietăţii de echilibrare. stergere_arbore(p->dt).Algoritmi şi structuri de date }//sfîrşit functie stergere 4. ştergere. căutare. Practica este următoarea: operaţiile definite pentru arborii de căutare se aplică şi asupra arborilor AVL.

b]. Scrieţi o funcţie care calculează nivelul şi factorul de echilibrare pentru oricare nod al unui arbore binar. 2. Să se scrie o funcţie care numără într-un arbore binar ordonat câte noduri conţin chei cu valori cuprinse în intervalul [a. sub forma unui şir de caractere (operatorul este plasat în faţa operanzilor). Să se construiască arborele corespunzător acestei expresii. Se citeşte de la tastatură o expresie matematică în formă prefixata.Algoritmi şi structuri de date 1. Să se evalueze expresia. 3. Fiecare nod conţine un operator sau un operand. 126 .

x2}. . Vârfurilor unui graf li se pot ataşa informaţii numite uneori valori. iar Γ = X×X este o mulţime de muchii sau arce (pentru grafuri orientate). Un graf fără cicluri se numeşte graf aciclic.Algoritmi şi structuri de date XII.. Exemplu: 1 2 O muchie de la vârful x la vârful y este notata cu perechea ordonata (x.y) sunt marcate prin săgeţi de la extremitatea iniţială x la cea finală y. (x2.. y}. Definiţie: Graf este o pereche ordonată de mulţimi G =(X.. {xn-1. Un subgraf al lui G este un graf G’=(X'. Într-un drum simplu muchiile care îl compun sunt distincte. Determinarea unui lanţ hamiltonian al grafului este o problemă foarte populară cunoscută ca Problema Comis Voiajorului rezolvată prin metoda Greedy. Γ '). xn) – în graf neorientat sau de forma {x1. dacă graful este neorientat. ALGORITMI. . xn} – în graf neorientat Un lanţ se defineşte ca o succesiune de vârfuri x1. Într-un graf orientat. arcele (x.. iar muchiilor li se pot ataşa informaţii numite costuri. Un lanţ elementar al grafului G care conţine toate vârfurile grafului se numeşte lanţ hamiltonian. Următoarele noţiuni sunt specifice grafurilor: Două vârfuri unite printr-o muchie se numesc adiacente. x2). x3. x3). … xn în care oricare două vârfuri sunt adiacente. iar Γ ' este formata din muchiile din Γ care unesc vârfuri din X'.. unde X este o mulţime de vârfuri. existenţa unui arc de la vârful x la vârful y nu presupune şi existenţa arcului de la y la x.. {x2. În grafurile neorientate. Lungimea drumului este egala cu numărul muchiilor care îl constituie. atunci aceasta este şi muchie între vârfurile y şi x. x2. unde X' × X. x3}. si cu mulţimea {x. iar muchiile prin segmente. Γ ). dacă există muchie între x şi y. (xn-1. În reprezentarea grafică. 4 5 3 127 . Un drum este o succesiune de muchii de forma: (x1. y). ELEMENTE DE GRAFURI. Un ciclu este un drum care este simplu şi care are drept capete un acelaşi vârf. Într-un drum elementar vârfurile care îl compun sunt distincte. dacă graful este orientat şi în mod uzual este folosit termenul de arc.

iar C[i. apoi vecinii acestuia (vârfurile adiacente) apoi vecinii vecinilor lui xs. o singura dată fiecare. dacă între oricare doua vârfuri exista un drum. în care A[i. Un graf neorientat este conex. Parcurgerea în lăţime Breadth First: se vizitează vârful stabilit iniţial xs. aceasta noţiune este întărită: un graf orientat este tare conex.Depth First 1. 4. Γ "). unde Γ " ⊆ Γ . dacă între oricare două vârfuri x si y exista un drum de la x la y si un drum de la y la x.x) //inserare vârfului x în coada C Câttimp (C≠Φ) Scoate(C. iar A[i.Algoritmi şi structuri de date Un graf parţial este un graf (X. Prin lista de muchii. j] ≠0 este costul asociat muchiei. etc. Prin liste de adiacenta: fiecare vârf i are ataşată lista de vârfuri adiacente lui (pentru grafuri orientate. La parcurgerea în lăţime se foloseşte o structură de coadă pentru a putea vizita toţi vecinii unui nod dat înainte de a-l marca drept vizitat. Metoda parcurgerii în lăţime . adiacent lui y Dacă z este nevizitat atunci *Marchează z ca vizitat 128 . j] = 0 semnifică faptul că nu există muchie {i. etc. Parcurgerea grafurilor Parcurgerea unui graf presupune vizitarea intr-o anumita ordine nodurilor grafului. Procedura de parcurgere în lăţime funcţionează după următorul principiu: atunci când s-a ajuns într-un vârf oarecare x nevizitat.j}. Aceasta reprezentare este eficienta atunci când se doreşte examinarea tuturor muchiilor grafului. este necesar ca muchia sa plece din i). 2.y) //scoate din coadă vârful y Pentru fiecare vârf z. Subalgoritm BreadthFirst (x) este //x reprezintă vârful curent C=Φ // iniţializare coada vidă *Marchează x ca vizitat Inserare(C. În functie de ordinea de parcurgere a vârfurilor exista 2 metode de parcurgere: 1. 1. 3. j] = 1 dacă vârfurile i si j sunt adiacente. apoi toate vârfurile nevizitate adiacente vârfurilor adiacente lui x. îl marcăm si vizităm apoi toate vârfurile adiacente lui x rămase nevizitate.Breadth First Metoda parcurgerii în adâncime . Pentru grafuri orientate. 2. j] = 0 în caz contrar. Prin matricea costurilor (grafuri neorientate etichetate) C în care C[i. Reprezentarea grafurilor Prin matricea de adiacenta A.

adiacent lui xS şi se aplică aceiaşi procedură – recursiv. Dacă toate vârfurile adiacente lui x au fost marcate ca vizitate se termină vizitarea vârfului x.Algoritmi şi structuri de date Inserare(C. Subalgoritm DepthFirst (x) este: *Marchează x ca vizitat Pentru fiecare vârf y adiacent lui x Dacă y este nevizitat atunci Cheamă DepthFirst (y) SfDacă SfPentru SfSubalgoritm Observaţie: Varianta nerecursivă a subalgoritmului DepthFirst se realizează prin utilizarea unei structuri de date de tip stivă. dacă exista un alt vârf adiacent vârfului curent x. având ca punct de plecare vârful x. tab[i]=0 – vârful i este nevizitat 2.z) SfDacă SfPentru SfCâttimp SfSubalgoritm Observaţie: marcarea unui vârf ca fiind vizitat sau nevizitat se poate realiza prin folosirea unui tablou tab[1……n] ale cărui elemente sunt asociate vârfurilor grafului şi au valori binare cu semnificaţia: tab[i]=1 – vârful i este vizitat. Procedura de parcurgere în adâncime a vârfurilor unui graf se pretează la o implementare recursivă. Subalgoritmii BreadthFirst şi DepthFirst sunt apelaţi din algoritmul Parcurgere: Algoritm Parcurgere(G) este Pentru fiecare x din X *Marchează x ca nevizitat SfPentru Pentru fiecare x din X Dacă x este nevizitat atunci Cheamă BreadthFirst (x) sau Cheamă DepthFirst (x) SfDacă SfPentru SfAlgoritm 129 . Parcurgerea în adâncime presupune vizitarea vârfului iniţial xS şi marcarea sa ca fiind vizitat. care nu a fost vizitat. respectiv. apoi se alege un vârf x. La terminarea procedurii curente (la revenirea din apelul recursiv). apelam din nou procedura etc.

(datele de ieşire sunt muchiile care formează arborele parţial de cost minim) Rezolvare: Algoritmul lui Kruskal este un algoritm cunoscut de rezolvare a problemei enunţate şi este un algoritm de tip Greedy. Un graf parţial care este arbore se numeste arbore partial.Se cere determinarea arborelui parţial de cost minim a grafului dat.Γ ). Arborele parţial de cost minim (suma costurilor muchiilor este minimă) se determină printr-un algoritm de tip Greedy: Algoritmul lui Kruskal.Algoritmi şi structuri de date În cazul unui graf neconex.j} de cost minim din Γ Dacă Γ ’ ∪ {i.j} SfDacă SfCâttimp Date de ieşire: A=(X.algoritmul se termină când au fost alese n-1 muchii Algoritm Kruskal este: Date de intrare: G=(X.j} nu conţine cicluri Γ ’=Γ ’ ∪ {i. aciclic şi conex.j} Γ =Γ \ {i.se porneşte cu arborele vid: Γ ’=Φ . Problema APM: Se dă un graf G=(X. Algoritmul de determinare a Arborele parţial de cost minim (APM). M'). Într-un arbore exista exact un drum între oricare doua vârfuri. Γ ’) – arborele ce se determină (reprezentat prin lista de muchii) şi n – numărul de vârfuri n=|X|: . Maximalitatea se referă la faptul că nu exista lanţ în graful G care sa aibă o extremitate în X’ şi pe cealaltă în X\X’.în mod repetat se alege muchia de cost minim a grafului G care nu formează ciclu în arborele A şi se adaugă la Γ ’ .Γ ) Fie Γ ’=Φ Câttimp (|Γ ’|<n-1) *Alege {i. Un arbore parţial este un graf parţial fără cicluri.Γ ’) SfAlgoritm 130 . conex şi maximal.Γ ) cu muchiile etichetate prin costuri (datele de intarre sunt reprezentate prin matricea costurilor). Un arbore este un graf neorientat. A=(X. este un subgraf G’=(X'. M). se pune problema determinării componentelor sale conexe: O componenta conexa a grafului G=(X. Principiul acestui algoritm este următorul: Considerând graful G=(X.

Algoritmi şi structuri de date Observaţie: Mulţimea muchiilor grafului dat se poate ordona descrescător după costuri şi se va parcurge în mod secvenţial. 131 . fără a mai fi necesară procedura de alegere a muchiei de cost minim din graful G.

implementarea în limbaj de programare se face prin proceduri (funcţii) recursive. Metoda descrisă poate fi aplicată în condiţiile în care problemele admit o împărţire în subprobleme de acelaşi fel. S_formal) este: Dacă n_formal< n0 atunci #rezolvă imediat subproblema P_formal şi obţine soluţia S_formal Altfel #împarte problema P_formal de dimensiune n_formal în: 132 . Putem vorbi de un specific al problemelor rezolvabile cu Divide et Impera. Subalgoritm DivideEtImpera (P_formal. ceea ce permite ca soluţiile parţiale ale acestora să nu depindă unele de altele contextul problemei ne permite să identificăm condiţia ca o subproblemă să fie considerată elementară şi să nu mai fie supusă unei împărţiri ulterioare subproblemele obţinute prin împărţire admit aceiaşi metodă de rezolvare fiind probleme de acelaşi fel. Fie P – problema globală. Etapa de împărţirea se consideră încheiată când subproblemele devin elementare . Soluţiile subproblemelor se vor combina pentru a alcătui soluţia globală a problemei. - Exemple de probleme celebre rezolvabile cu metoda Divide et Impera sunt: problema Turnurilor din Hanoi. o problemă de complexitate mare se va împărţi în subprobleme de acelaşi tip însă de dimensiuni mai mici ale căror rezolvare se va dovedi mai simplă. n – dimensiunea ei şi S – soluţia dorită. Deoarece toate subproblemele sunt tratate prin aplicarea aceluiaşi algoritm. n_formal. De asemenea. DIVIDE ET IMPERA. descrierea potrivită a metodei este dată printr-un subalgoritm autoapelabil. METODE DE ELABORARE A ALGORITMILOR. precum şi datorită faptului că soluţiile obţinute sunt soluţii parţiale ale problemei globale. Acest specific este dat de următoarele afirmaţii: problema globală se poate împărţi în 2 sau mai multe subprobleme asemănătoare de dimensiuni mai mici subproblemele obţinute prin împărţire sunt independente. diferenţa făcându-se doar prin datele de intrare-ieşire şi dimensiunea subproblemelor.Algoritmi şi structuri de date XIII. În conformitate cu principiul amintit. subproblemele obţinute prin descompunerea problemei pot fi la rândul lor împărţite în subprobleme mai mici. etc. Sortarea rapidă. Metode Divide et Impera este o metodă de rezolvare a unor probleme şi este inspirată de principiul “Împarte şi stăpâneşte”.rezolvabile imediat. Fie n0 – dimensiunea minimă a unei probleme pentru care aceasta devine elementară şi admite o rezolvare imediată. De altfel.

deoarece maximul elementelor unui vector format dintr-un singur element este însăşi elementul respectiv. reprezentând maximele celor două subşiruri obţinute prin împărţirea şirului iniţial în două părţi egale: x1 . subproblema devine elementară.xn-1 .n2. Acest proces de împărţire se va încheia când un subşir de elemente nu mai poate fi împărţit.xn Maxim2 . apelul subalgoritmului se va face pentru parametrii actuali P. Analiza problemei: Determinarea celui mai mare element dintr-un vector poate fi privită ca o problemă de determinare a maximului dintre două valori intermediare. xk Maxim1 Maxim= maxim(Maxim1...xn Maxim x1 .S) Problema 1: Se cere determinarea maximului dintr-un vector de n valori numerice. Pentru a rezolva problema globală P.S1) Cheamă DivideEtImpera(P2.S2. 133 xk+1 .xn-2 .n. n şi S: Cheamă DivideEtImpera(P. …..nk.. ….Sk) #combină soluţiile parţiale S1.. . xk+1 .. # Pk de dimensiune nk Cheamă DivideEtImpera(P1.x2 . xk+1 .Sk şi obţine S_formal SfDacă SfSubalgoritm Propoziţiile nestandard din descrierea subalgoritmului nu pot fi detaliate decât în funcţie de problema rezolvată.Algoritmi şi structuri de date # P1 de dimensiunea n1. xk+1 .x3 . În acest moment.xn-2 .xn-1 ..S2) .x2 . respectiv când dimensiunea acestuia s-a redus la 1.….Maxim2) Fiecare subşir din împărţirea anterioară se va putea împărţi din nou în două părţi de dimensiuni apropiate.n1. # P2 de dimensiune n2. Cheamă DivideEtImpera(Pk.….. xk .x3 .

mij+1.h> #define NMAX 20 void citire (int t[NMAX].int *n) {int i. 134 .i). max1=maxim(t.Algoritmi şi structuri de date Am identificat în problema dată iniţial o problemă rezolvabilă prin Divide Et Impera.&t[i]).").Maxim2) Dacă Maxim1>Maxim2 atunci Maxim_formal=Maxim1 Altfel Maxim_formal=Maxim2 SfDacă SfDacă SfSubalgoritm Pentru rezolvarea problemei complete apelul subalgoritmului devine: Cheamă DivideEtImpera(x1 .sup). xsup . xmij.inf. … . max2=maxim(t.n). mij=(sup+inf)/2. ….max2. Subalgoritmul DivideEtImpera devine în acest caz: Subalgoritm DivideEtImpera _Maxim(xinf .Maxim) Program: #include <stdio. max1. Dimensiunea unei subprobleme este dată de numărul elementelor din subşirul manevrat şi dimensiunea unei subprobleme elementare este 1. scanf("%d".Maxim1) Cheamă DivideEtImpera_Maxim(xmij+1 .int sup) { int mij. for(i=0.i++) { printf("\nT[%d]=". …. if ((sup-inf)<=0) return t[inf]. scanf("%d". } } int maxim(int t[NMAX]. …. Combinarea soluţiilor parţiale se face printr-o comparaţie simplă. xsup. if (max1>max2) return max1.i<*n.Maxim_formal) este: Dacă sup-inf <= 0 atunci Maxim_formal= xinf Altfel mij= (sup+inf)/2 Cheamă DivideEtImpera_Maxim(xinf .xn . int inf. printf("dati dimensiunea tabloului.mij).

printf("Maximul este: %d". rezolvarea problemei se reduce la parcurgerea următoarelor etape: . int max.dim-1). pentru un număr oarecare n de discuri situate pe tija A.mută discul rămas de pe tija A pe tija B 135 .mută primele n-1 discuri de pe A pe C.max).nu este permis sa se aşeze un disc cu diametrul mai mare peste un disc cu diametrul mai mic.dim. folosind tija intermediară C: . utilizând ca tija intermediara tija C.C.mută discul mic de pe tija C pe tija B Generalizând. Se dau 3 tije simbolizate prin A. utilizând tija B ca intermediară . . respectând următoarele reguli: .Algoritmi şi structuri de date else } void main() { int a[NMAX].B. aşezate de jos în sus în ordine crescătoare a diametrelor. } return max2. citire(a. max=maxim(a. Pentru n=2 (două discuri). Problema 2: Problema turnurilor din Hanoi. A B C Analiza problemei: Cazul elementar al problemei constă în existenţa unui singur disc pe tija A. rezolvarea problemei constă în 3 mutări. ceea ce reduce rezolvarea la o mutare a discului de pe tija A pe tija B. Pe tija A se găsesc n discuri de diametre diferite.0.la fiecare pas se muta un singur disc .mută discul mai mare de pe tija A pe tija B .&dim). Se cere sa se mute toate discurile de pe tija A pe tija B.mută discul mai mic de pe tija A pe tija C .

//apel hanoi } Problema 3: Căutare Binară.Y. Analiza problemei: Căutarea unei valori cheie într-un vector ordonat de n valori se poate reduce la o problemă mai simplă (de dimensiune mai mică) dacă ne folosim de informaţia că vectorul este ordonat. int A.h> void Hanoi (int n. În cele din urmă. C).A.A.X) SfDacă SfSubalgoritm Apelul subalgoritmului pentru rezolvarea problemei globale este: Cheamă Hanoi(n.Algoritmi şi structuri de date - mută cele n-1 discuri de pe C pe B. de a efectua mutarea a n-1 discuri.C) Program: #include <stdio. scanf("%d". A. respectiv.X.X. să se determine poziţia pe care se regăseşte o valoare dată cheie. problemă elementară Altfel Cheamă Hanoi(k-1. ‘C’).B.Z. Subalgoritmul Hanoi va trata problema transferului a k discuri de pe tija X.Z. ş.d. ‘B’.Z) este: Dacă k=1 atunci Mută discul de pe tija X pe tija Y //rezolvare directă. A. pe tija Y. Aceiaşi manieră de abordare a problemei de dimensiune n-1 va reduce problema la mutarea a n-2 discuri.C).&n). } }//sfarsit Hanoi void main () { int n. C.Y. printf("\nMuta discul de pe tija %c pe tija %c".1. Hanoi( n-1. utilizând tija A ca intermediară Observăm că problema s-a redus la două probleme de dimensiuni mai mici. int B. Fiind dat un vector ordonat de n valori numerice. folosind tija rămasă Z ca intermediară: Subalgoritm Hanoi (k. B). ‘A’. pentru cazul elementar al unui singur disc ce trebuie mutat.a. Acest lucru ne permite să împărţim vectorul în două părţi 136 .m. Hanoi (n. B. printf("\nDati numarul de discuri:").Y) Mută discul rămas de pe X pe Y //problemă elementară Cheamă Hanoi(k-1. int C) {if (n>0) { Hanoi (n . se va aplica rezolvarea directă.

xn . ….rezultă că vom continua căutarea în prima parte a vectorului.cheie. ….cheie.cheie. 137 . cu cheia căutată. Împărţirea se termină în condiţiile în care cheia a fost găsită sau subvectorul pe care îl manevrăm nu poate fi împărţit (este format din maxim un element).cheie mai mică decât xmij . ignorând acea parte care nu poate conţine cheia căutată. Rezultatul comparaţiei poate fi următorul: . valoarea poz reprezintă poziţia pe care a fost găsită cheia căutată. ….cheie. xsup . între elementele situate la stânga poziţiei mij .poz) Altfel Cheamă CăutareBinară(xmij+1 .poz) este: Dacă sup-inf <= 0 atunci // vectorul curent conţine un singur element Dacă xinf= cheie atunci poz=inf Altfel poz= -1 // semnificaţia faptului că nu s-a găsit cheia SfDacă Altfel // vectorul curent conţine mai mult de un element mij= (sup+inf)/2 Dacă cheie=xmij atunci poz=mij //am găsit poziţia cheii Altfel Dacă cheie<xmij atunci Cheamă CăutareBinară(xinf . Subvectorul în care se continuă căutarea poate fi la rândul său împărţit în două părţi pentru a obţine probleme mai mici.poz) SfDacă SfDacă SfSubalgoritm Apelul Cheamă CăutareBinară(x1 .cheie egală cu valoarea elementului xmij – rezultă că am găsit poziţia mij a cheii . să comparăm cheia cu valoarea elementului de pe poziţia tăieturii şi să decidem continuarea căutării într-unul dintre subvectorii rezultaţi. în caz contrar.Algoritmi şi structuri de date relativ egale.poz) rezolvă problema globală. Dacă valoarea poz este egală cu -1 la terminarea apelului. Decizia continuării căutării într-unui dintre subvectorii rezultaţi printr-o împărţire precedentă se face pe baza unei comparaţii simple a elementului de pe poziţia tăieturii. cheia nu a fost găsită în vectorul dat.cheie mai mare decât xmij – rezultă că vom continua căutarea între elementele din dreapta poziţiei mij Subalgoritmul de rezolvare a problemei de căutare binară se descrie astfel: Subalgoritm CăutareBinară(xinf . xmij . notat xmij. xsup . ….

efectuează avans la dreapta al indicelui i 5. făcându-se în mod implicit. repetă paşii 3. În descrierea următoare. Împărţirea unui şir de elemente xinf. respectiv la stânga 6. Algoritmul de sortare rapidă este un exemplu tipic de algoritm Divide et Impera. Dacă subşirurile obţinute conţin mai mult de 1 element.xsup)este: Fie i=inf şi j=sup mij=(inf+sup)/2 //mij – reprezintă poziţia pivotului xmij Repetă Câttimp ( i<sup şi xi<xmij) //avans la dreapta i=i+1 138 . j – indicele de parcurgere de la dreapta la stânga. Împărţirea vectorului în două părţi care verifică proprietatea enunţată necesită operaţii de interschimbare a valorilor elementelor de pe poziţii diferite. …. fiind considerat ordonat. existând trei variante plauzibile: pivotul este elementul median al subvectorului manevrat. cei doi subvectori rezultaţi vor fi supuşi aceluiaşi procedeu de împărţire. Odată ce etapa de împărţire a fost parcursă. …. cât timp pivotul este mai mare decât elementele de pe poziţiile parcurse de la stânga la dreapta. respectiv. …. j=sup.xj. Fiind dat un vector oarecare de n elemente se cere ordonarea crescătoare a vectorului după valorile elementelor. Combinarea soluţiilor nu necesită o etapă distinctă. pivotul este elementul de pe prima poziţie. Alegerea pivotului rămâne în sarcina programatorului. stabileşte pivotul (elementul de pe poziţie mediană din şir) 3.xsup se face parcurgând paşii: 1. i=inf. pivotul este ales ca elementul de pe ultima poziţie din subvector. dacă i<j interschimbă valorile de pe poziţiile i şi j şi actualizează indicii prin avans la dreapta. iniţial.Algoritmi şi structuri de date Problema 4. 2. respectiv. Subalgoritmul corespunzător metodei descrise anterior este următorul: Subalgoritm QuickSort(xinf. pivotul este ales ca fiind elementul de pe poziţia mediană.xsup şi xinf. 4 şi 5 până când valoarea indicelui i devine mai mare decât valoarea lui j După parcurgerea paşilor 1-6 se consideră subşirul xinf. efectuează avans la dreapta al indicelui i 4. Sortare rapidă (QuickSort). prin interschimbările elementelor vectorului dat în apelurile recursive ale subalgoritmului.xsup împărţit în două subşiruri: xi. acestea se vor supune aceluiaşi procedeu descris. Ideea de bază a sortării rapide constă în împărţirea vectorului în doi subvectori cu proprietatea că toate elementele primului sunt mai mici decât un element pivot şi elementele celui de-al doilea subvector sunt mai mari decât pivotul. Problema devine elementară implicit dacă subvectorul corespunzător are dimensiunea 1. …. câttimp pivotul este mai mare decât elementele de pe poziţiile parcurse de la stânga la dreapta. …. iniţializează doi indici de parcurgere a subşirului în două sensuri: i – indicele de parcurgere a şirului de la stânga la dreapta.

for(i=0. int dr) { int i.xsup) SfDacă Dacă (j>inf) Cheamă QuickSort(xinf.xj) SfDacă Pânăcând (i>j) Dacă (i<sup) atunci Cheamă QuickSort(xi. printf("\ndati n="). …. j=dr.int st.int n) { int i.xn) Program C: #include <stdio. } } void tiparireSir(int x[10]. scanf("%d". } void SQuick(int x[10]. scanf("%d". for(i=0.&x[i]). ….i<n.n).i<*n.M=x[(st+dr)/2].k.Algoritmi şi structuri de date SfCâttimp Câttimp ( j>inf şi xj>xmij) //avans la stânga j=j-1 SfCâttimp Dacă i<j atunci Cheamă Interschimbare(xi.int *n) { int i.i++) {printf("\nX[%d]=".i+1).i++) printf("%d ".h> void citireSir(int x[10]. do{ //avans la stanga while ((i<dr) && (x[i]<M)) i++. if (i<j) 139 .j. …. i=st.M.x[i]). //avans spre dreapta while ((j>st) && (x[j]>M)) j--.xj) SfDacă SfSubalgoritm Pentru rezolvare problemei globale se va efectua apelul: Cheamă QuickSort (x1.

Subproblemele se consideră elementare dacă dimensiunea subvectorilor devine 1. de dimensiuni mai mari. //daca subsirul 1 mai are cel putin 1 elem . } void main() { int a[10].0. la rândul lor pot fi împărţiţi fiecare în două părţi. Fiecare dintre cei doi subvectori obţinuţi la o împărţire precedentă. tiparireSir(a. x[i]=x[j]. Această operaţie presupune parcurgerea secvenţială a celor doi vectori ordonaţi şi construirea celui de-al treilea vector prin copierea elementelor din cei doi vectori cu restricţia de a păstra relaţia de ordine între elementele vectorului rezultat. prin sortarea părţilor rezultate şi ulterior interclasarea celor doi subvectori ordonaţi. În algoritmul de sortare prin interclasare. vectorii ce vor fi interclasaţi sunt de fapt subvectori ai aceluiaşi vector. SQuick(a.j). citireSir(a.dr).. j--.n-1). if (i<dr) SQuick(x.n). urmând ca subvectorii de dimensiune mai mică să fie supuşi aceluiaşi mecanism de ordonare prin interclasarea şi să obţinem subvectori ordonaţi.&n).i.st. rezultând vectorul global ordonat. Sortarea prin interclasare este o tehnică de ordonare a unui vector.Algoritmi şi structuri de date {//interschimb x[i] cu x[j] k=x[i]. Operaţia de interclasare a doi vectori a fost discutată într-un capitol precedent.. } if (i<=j) {i++. } Problema 4. ceea ce ne permite o abordare prin metoda Divide et Impera. if (j>st) SQuick(x. ceea ce conduce la o procedură de interclasare uşor diferită faţă de cea prezentată în capitolul III. x[j]=k. Observăm că problema generală permite o împărţire în subprobleme independente mai mici rezolvabile prin aceiaşi tehnică. ceea ce este echivalent afirmaţiei “subvector ordonat”.} }while(i<=j).n. Subalgoritmul prin care se realizează sortarea prin interclasare este descris în continuare: 140 . //daca subsirul 2 mai are cel putin 1 elem . Principiul de bază este acela al împărţirii vectorului iniţial în două părţi egale (subvectori).

…. xmij j=mij+1 // j – indicele de parcurgere a subvectorului xmij+1 . xsup) este: Dacă sup-inf<1 atunci // nu se efectuează nimic. …. xsup) //interclasarea subvectorilor xinf . …. considerând că subvectorul curent de // dimensiune 1 este ordonat Altfel Fie mij=(inf+sup)/2 Cheamă MergeSort(xinf . …. xmij) Cheamă MergeSort(xmij+1 . xmij şi xmij+1 . se revine din apelul subalgoritmului (condiţia // de terminare a recursivităţii).Algoritmi şi structuri de date Subalgoritm MergeSort(xinf .…. xsup Pentru i de la 1 la k-1 xi+inf-1=yi SfPentru SfDacă SfSubalgoritm Apelul Cheamă MergeSort(x1 . …. xsup k=1 // k –parcurgerea vectorului rezultat y1 . xsup i=inf // i – indicele de parcurgere a subvectorului xinf . ydim Câttimp (i<=mij şi j<=sup) Dacă (xi<xj) atunci yk=xi // se copiază din primul subvector i=i+1 k=k+1 Altfel yk=xj // se copiază din al doilea subvector Interclasare j=j+1 k=k+1 SfDacă SfCâttimp Câttimp (i<=mij) //au mai rămas elemente în primul subcvector yk=xi // se copiază din primul subvector i=i+1 k=k+1 SfCâttimp Câttimp (j<=sup) //au mai rămas elemente în primul subcvector yk=xj // se copiază din al doilea subvector j=j+1 k=k+1 SfCâttimp // copierea vectorului y în vectorul xinf . …. xn) va produce ordonarea vectorului global 141 . …. …. ….

if(inf<sup) { mij=(inf+sup)/2. i++. printf("\ndati n="). citireSir( a.n). } for(i=inf. } void MergeSort(int x[].sup). int sup) {int mij.mij+1. k++. mergesort(x.} } void tiparireSir(int x[10].j. for(i=1.i++) {printf("\nX[%d]=".i.n).h> void citireSir(int x[10]. } while(j<=sup) { y[k]=x[j].} } while(i<=mij) { y[k]=x[i]. while((i<=mij)&&(j<=sup)) { if(x[i]<x[j]) { y[k]=x[i].int *n) {int i. } 142 . tiparireSir(a. int inf. #include <stdio. k++.sup.inf. j++.mij).x[i]).1.mij).i++) printf("%d ".i<=n.int n) {int i. j=mij+1.&x[i]).n. }} void main(void) {int a[10].i<k.Algoritmi şi structuri de date Program C: Ordonarea unui vector prin metoda sortării prin interclasare.scanf("%d".k. k++. k++. //merge(x.i++) x[i]=y[i].inf.n). mergesort(x. i=inf.i+1). j++.&n).scanf("%d". MergeSort(a. for(i=1. i++.} else { y[k]=x[j].i<=*n. k=inf. int y[20].

Dezavantajul major al metodei constă în incapacitatea determinării soluţiei optime globale pentru orice problemă.Procedeul se încheie în două situaţii: o S-a determinat mulţimea maximală B o Nu mai sunt elemente de parcurs în mulţimea A Există două variante ale algoritmului Greedy: . datele de ieşire sub forma unei submulţimi B a mulţimii de intrare A şi anumite condiţii specifice problemei care ne permit alegerea unei soluţii parţiale optimale: Fie A o mulţime de n elemente: A={a1. optimale şi irevocabile ale soluţiilor parţiale.GREEDY Metoda Greedy (greedy = (en. care ulterior va fi completată cu elemente ale mulţimii A .Algoritmi şi structuri de date XIV. şi se verifică pentru fiecare membru al mulţimii A condiţiile identificate din contextul problemei. elementul respectiv va fi „înghiţit” de mulţimea B .Se parcurge mulţimea A.an}. problema abordată trebuie reformulată într-o maniera şablon prin care se pot identifica datele de intrare sub forma unei mulţimi A. B ⊆ A astfel încât B este maximală şi verifică anumite condiţii.…. METODE DE ELABORARE A ALGORITMILOR. cu menţiunea că propoziţiile nestandard (introduse prin prefixarea cu simbolul*) nu pot fi rafinate decât pentru problema concretă de rezolvat: Algoritm Greedy este: //varianta fără ordonarea mulţimii A Date de intrare: A Date de ieşire: B Fie B={} Câttimp (A≠Φ) şi (*B nu este optimă) *Alege ai din A Dacă B ∪ {ai} *este soluţie posibilă atunci B = B ∪ {ai} sau * ai înlocuieşte un element din B A=A \ { ai } SfDacă SfCâttimp 143 . element cu element.a2. Dacă aceste condiţii sunt verificate. Pentru a aplica metoda Greedy. prin alegeri corecte.cu ordonarea în prealabil a mulţimii A .)lacom) este o tehnică de rezolvare problemelor de optimizare bazată pe principiul alegerii “lacome” a optimului local în vederea determinării optimului global.Se porneşte cu mulţimea vidă B={}.fără ordonarea mulţimii A Descrierea algoritmică a metodei generale este prezentată în continuare. Soluţia unei probleme abordată cu metoda Greedy se construieşte treptat. Se cere determinarea submulţimii B. Principiul de rezolvare: .

... respectiv.k2.... respectiv.k1. considerând că numărul de monede de aceiaşi valoare este nelimitat.. Lăcomia procedeului este dată de maniera de parcurgere a valorilor monetare. Se consideră o mulţime infinită de monede de valori 1.se alege cea mai mare unitate monetară kj care este mai mică valoric decât suma S ...alege monede de valoare imediat mai mică decât kj-1. astfel încât dimensiunea submulţimii este minimă şi suma valorilor monedelor conţinute de B nu depăşeşte suma dată S. .Cât timp suma S rămâne pozitivă adaugă moneda kj-1 la mulţimea B şi scade valoarea monedei kj-1din suma S . Problema monedelor.Algoritmi şi structuri de date SfAlgoritm Algoritm Greedy2 este: //varianta cu ordonarea mulţimii A după un criteriu Date de intrare: A Date de ieşire: B Fie B={} *Ordonează mulţimea A Pentru i de la 1 la n Dacă B ∪ {ai} este soluţie posibilă atunci B = B ∪ {ai} sau ai înlocuieşte un element din B SfDacă SfPentru SfAlgoritm Problema 1.iniţializează mulţimea B cu mulţimea vidă .kn-1. nu neapărat de valori diferite. Se cere determinarea unei scheme de exprimare a sumei S utilizând un număr optim (minim) de monede.k2.k1.kn-1 şi S o sumă exprimabilă prin aceste monede. Rezolvarea logică a problemei constă în parcurgerea paşilor: . kj-1 ...Procesul se continuă până când nu mai există valori monetare mai mici care ar putea fi adăugate mulţimii B Procedeul descris anterior corespunde unui algoritm Greedy: soluţia globală se construieşte treptat prin alegeri succesive şi irevocabile ale unităţilor monetare ce intră în alcătuirea schemei de exprimare a sumei S. de la cele mai mari înspre cele mai mici. kj-2 . ceea ce permite formarea unei mulţimi B de dimensiune minimă (cu cât 144 .alege monede de valoare imediat mai mică decât kj. Soluţia problemei este reprezentată dintr-o submulţime B de monede.Cât timp suma S rămâne pozitivă adaugă moneda kj la mulţimea B şi scade valoarea monedei din suma S . Analiza problemei: Din problema formulată mai sus putem deduce mulţimea de intrare A ca fiind mulţimea monedelor de valori 1.

mai mică SfCâttimp Datele de intrare ale algoritmului descris sunt suficiente pentru a defini mulţimea A: cunoscând k. ci – importanţa obiectului ai i Se cere determinarea unei scheme de încărcare optimă a unui rucsac de capacitate maxim permisă C cu obiecte din mulţimea A: suma importanţelor obiectelor selectate trebuie să fie maximă şi suma capacităţilor obiectelor selectate nu trebuie să depăşească capacitatea maximă C. Se consideră o mulţime A de n obiecte caracterizate fiecare prin capacitate şi importanţă: A= {a1. cu ordonarea elementelor mulţimii A.Algoritmi şi structuri de date valorile monetare sunt mai mari. Observaţie: Varianta fracţionară a problemei rucsacului permite ca anumite obiecte să fie încărcate în fracţiuni. Practic.k.a2. 145 . k2..kj ≥ 0) S=S..…. n şi faptul că numărul de monede de acelaşi tip este nelimitat.k . k..k.1. Pentru problema discretă a rucsacului (obiectele nu pot fi împărţite pe fracţiuni). wi – importanţa obiectului ai i ∀ =1. cu atât numărul monedelor prin care se exprima S este mai mic). mulţimea A se subînţelege ca fiind: A= {1.n Date de ieşire: B Fie B={} *Caută cel mai mare j pentru care kj<=S Câttimp (j≥0) Câttimp (S. Problema Rucsacului. pentru care raportul capacitate – importanţă este minim. metoda Greedy nu determină întotdeauna optimul global... soluţia finală oferită de metoda Greedy fiind optimul global. Se observă că mulţimea obiectelor A se parcurge în ordine crescătoare după raportul capacitate – importanţă.… . Faptul că asupra alegerilor efectuate nu se revine determină ca în multe situaţii soluţia optimă globală să nu poată fi găsită. Analiza problemei (varianta discretă): Strategia de încărcare a rucsacului (iniţial gol) este de a introduce treptat în rucsac acel obiect din mulţimea obiectelor disponibile.} Problema 2.kj B=B ∪ { kj } SfCâttimp j=j-1 //alege următoarea valoare monetară... Alegerea “lacomă” a obiectelor garantează obţinerea unei soluţii finale bune. n . la fiecare pas de construire a soluţiei vom prefera un obiect de importanţă mare şi capacitate mică.k2. ceea ce ne conduce la construirea unui algoritm Greedy în varianta a doua.… … kn1 n-1 .….. Algoritmul GreedyUnităţiMonetare este: Date de intrare: S. ceea ce corespunde construirii unei soluţii parţiale optime.. n .….an}: ∀ =1.

…. Ceea ce este dificil de stabilit. Se cere un orar al spectacolelor astfel încât numărul lor să fie maxim posibil şi să nu existe suprapuneri. n Date de intrare: C –capacitatea rucsacului Date de ieşire B //mulţimea obiectelor selectate în rucsac Ordonează A .an} i //pentru care se cunosc wi şi ci . varianta discretă.…. după timpul de start si . este descris în continuare: Algoritm Rucsac este: Date de intrare: A= {a1.….în ordine crescătoare. Pentru primele două cazuri vom demonstra ineficienţa prin contraexemple: Contraexemplu 1. se doreşte o selecţie a activităţilor disponibile astfel încât să fie derulate.. Se consideră o mulţime de n activităţi (spectacole) A= {a1. crescător după valorile rapoartelor wi/ci Fie CB=0 //suma capacităţilor obiectelor selectate Fie B={} Pentru i de la 1 la n Dacă ( CB + ci ≤ C ) atunci B=B ∪ {ai} CB =CB + ci SfDacă SfPentru SfAlgoritm Problema 3.an} Se cere B ⊆ A astfel încât B este maximală şi verifică anumite condiţii. 146 .Algoritmi şi structuri de date Algoritmul de rezolvare a problemei rucsacului. pentru a obţine în final o planificare optimă.în ordine crescătoare. Analiza problemei: Din analiza specificaţiilor problemei se deduce imediat formularea specifică unei probleme rezolvabile prin metoda Greedy: Se dă A= {a1. Într-o sală de spectacole.în ordine crescătoare.. doar cea de-a treia se dovedeşte plauzibilă.. Identificăm trei variante de alegere a următoarei activităţi pe care o planificăm : . într-o singură zi cât mai multe spectacole şi acestea să nu se intercaleze.. la fiecare pas. după timpul de terminare ti Dintre cele trei variante enunţate.a2.an}. după durata activităţii: di = ti-si . este maniera de alegere. ∀ =1. Condiţiile se referă la ne-suprapunerea oricăror două activităţi planificate. a următoarei activităţi din mulţimea activităţilor disponibile (neplanificate deja). fiecare activitate ai fiind caracterizată prin ora de debut si şi ora de terminare ti.a2.a2. Planificarea spectacolelor.

. Soluţia ar fi în acest caz B={a1} cu toate că există o soluţie mai bună: B={a2. Prin alegerea spectacolelor în ordinea crescătoare a timpului de debut. datorită suprapunerilor.…. Contraexemplu 2. prima activitate selectată este a1. Algoritmul ar furniza în acest caz soluţia B={a1} cu toate că există o soluţie mai bună: B={a2..Algoritmi şi structuri de date s4 s3 s2 s1 t2 t1 t3 t4 Considerăm cazul particular descris în imaginea alăturată. s4 s3 s2 s1 t2 t1 t3 t4 Prin alegerea în ordinea crescătoare a duratei activităţilor disponibile.. în cazul particular figurat în imaginea alăturată.. Algoritmul de rezolvare a problemei planificării spectacolelor implică ordonarea mulţimii de intrare. ∀ =1.a3}. după timpul de terminare a activităţilor: Algoritm GreedySpectacole este: Date de intrare: A= {a1.a2. n i Date de ieşire B Fie B={} *Ordonează crescător A după valorile ti Pentru i de la 1 la n Dacă *(nu există aj ∈ B. Fiecare activitate este reprezentată printr-un segment ale cărui capete marchează timpul de start şi de terminare. Această alegere se dovedeşte neinspirată deoarece nicio altă activitate nu va mai putea fi planificată ulterior. se va alege ca primă activitate planificată: a1 şi se constată imposibilitatea selectării unei alte activităţi fără să existe suprapuneri. 147 .an}// pentru care se cunosc si şi ti . astfel încât ai se suprapune lui aj) atunci B=B ∪ {ai} SfDacă SfPentru SfAlgoritm Observaţie: Două activităţi ai şi aj se consideră că nu se suprapun ddacă este verificată condiţia:si>tj sau sj>ti.a3}. crescător.

. × An . care satisfac anumite condiţii specifice problemei (denumite condiţii interne).. Metoda Backtracking (căutare cu revenire) este o metodă generală de rezolvare a unei clase de probleme de optimizare bazată pe principiul construirii soluţiilor. 148 ... fapt care conduce la obţinerea soluţiilor optime globale în orice situaţie. Dacă vectorul parţial { x1 . ceea ce produce o revenire la o decizie anterioară şi încercarea unui alt element pe poziţia anterioară k-1 din vector. metoda le determină pe toate. METODE DE ELABORARE A ALGORITMILOR. În plus. bazată pe acelaşi principiu. Metoda căutării cu revenire realizează o explorare sistematică a spaţiului soluţiilor posibile. Metoda evită generarea tuturor soluţiilor posibile. Un dezavantaj major al metodei este acela că este o metodă relativ costisitoare în privinţa timpului de execuţie. prin alegeri succesive ale soluţiilor parţiale. BACKTRACKING..Algoritmi şi structuri de date XV.. …. Problemele rezolvabile cu metoda Backtracking sunt probleme în care soluţia este reprezentată sub forma unui vector: x = { x1 . fără a genera însă toate soluţiilor posibile din care s-ar putea extrage doar acele soluţii ce îndeplinesc condiţii specifice problemei. se va continua cu augmentarea vectorului curent prin adăugarea unui nou element x k + din mulţimea corespunzătoare Ak + . şi Ai sunt mulţimi finite nu neapărat distincte. xi ∈ Ai i unde S = A1 × A2 ×. soluţiile finale se obţin prin alegerea succesivă de elemente din mulţimile A1 . dacă sunt mai multe soluţii.. An cu posibilitatea revenirii asupra unei alegeri dacă aceasta nu a condus la obţinerea unei soluţii finale. x k } verifică anumite condiţii de validare. Generarea tuturor soluţiilor implică memorie şi timp suplimentar. Abordarea aceasta este neeficientă. x 2 . Backtracking este o alternativă inspirată de rezolvare a problemelor de acest gen.. treptat pentru k=1.. deduse din condiţiile interne.. n ..…. Cerinţa problemei este determinarea tuturor soluţiilor posibile. Există posiblitatea ca Ak să nu mai conţină alte elemente care au rămas neverificate. × An . x n } ∈ S . construieşte soluţiile finale. A2 . O abordare simplistă ar genera toate soluţiile posibile . Ulterior acesta va fi mărit prin adăugarea elementelor x k din mulţimea corespunzătoare Ak . ∀ =1.. Dacă vectorul nu 1 1 îndeplineşte condiţiile de validare se încearcă un nou element din mulţimea Ak şi se reverifică condiţiile de validare. Algoritmii Backtracking pornesc cu o soluţie iniţială reprezentată de vectorul vid. în mod treptat.. Backtracking oferă posibilitatea revenirii asupra unor decizii anterioare. şi ulterior s-ar verifica aceste soluţii în vederea identificării acelora care verifică condiţiile interne..elementele produsului cartezian S = A1 × A2 ×. x 2 . Spre deosebire de metoda Greedy.2.

x 2 ..... astfel încât dacă { x1 .Algoritmi şi structuri de date Asupra vectorului soluţie se acţionează prin doar două operaţii: adăugare nou element după ultimul adăugat. soluţia (vectorul) se poate construi pas cu pas. vectorul soluţie funcţionează pe principiul unei stive. x 2 . respectiv. O problemă se identifică ca o problemă rezolvabilă prin metoda Backtracking dacă putem identifica următoarele aspecte din specificaţiile sale: 1.. k ≤ n este validă → condiţiile de validitate 4. Considerăm soluţia parţială { x1 . eliminare ultim element adăugat.. spaţiul soluţiilor este un produs cartezian S = A1 × A2 ×. x 2 . x n } ∈ S 3...... are sens completarea vectorului cu un element pe poziţia k+1. astfel.1 … Nivelul k+1 Nivelul k Nivelul k-1 … Nivelul 2 Nivelul 1 … xk+1 xk xk-1 … x2 x1 … Nivelul k+1 Nivelul k Nivelul k-1 … Nivelul 2 Nivelul 1 … xk+1 * xk … xk+1 xk xk-1 … x2 x1 … Nivelul k+1 Nivelul k Nivelul k-1 … Nivelul 2 Nivelul 1 … xk+1 xk xk-1 … x2 x1 xk-1 … x2 x1 149 . soluţia probleme poate fi reprezentată ca un vector x = { x1 .. x 2 . × An 2.. Aceste operaţii corespund unor operaţii cunoscute: push şi pop.. există un set de condiţii prin care putem decide dacă o soluţie parţială dată de vectorul { x1 .. există un set de condiţii prin care putem decide dacă o soluţie parţială este finală → condiţiile de finalitate 5. x k } este valid... x k } . x k } reprezentată ca stivă în imaginile alăturate: Cazul 1 … Nivelul k+1 Nivelul k Nivelul k-1 … Nivelul 2 Nivelul 1 Cazl 2.

. { x1 . atunci: .2 … Nivelul k+1 Nivelul k Nivelul k-1 … Nivelul 2 Nivelul 1 … xk+1 xk xk-1 … x2 x1 … Nivelul k+1 Nivelul k Nivelul k-1 … Nivelul 2 Nivelul 1 … xk+1 xk xk-1 … x2 x1 Tratarea nivelului k se face diferenţiat pentru cazurile următoare: Cazul 1. mai există elemente din Ak care nu au fost încercate... pentru fiecare nivel curent al stivei se va proceda similar.. { x1 . x k } este finală sau nu //întoarce Adevarat sau Fals în funcţie de rezultatul testului SfFuncţie Varianta Iterativă: Algoritm BackTrackingIterativ este: 150 . x k } NU verifică condiţiile de validitate şi: Cazul 2. x 2 ..1. x k } din stivă este validă sau nu //întoarce Adevarat sau Fals în funcţie de rezultatul testului SfFuncţie Funcţie Final(k) este: //verifică dacă soluţia parţială { x1 .. Descrierea algoritmului se poate face în două variante: iterativ şi recursiv.... Cazul 2.. x 2 . x k } . x k } verifică condiţiiile de validitate: în acest caz se va trece la completarea nivelului următor al stivei cu un element x k + din Ak + .2 Nu mai există elemente din Ak rămase neîncercate.. 1 1 (operaţie push)..Algoritmi şi structuri de date Cazul 2.. x 2 . Procesul este unul repetitiv.. atunci: * . Funcţie Valid(k) este: //verifică dacă soluţia parţială { x1 .. Dacă vectorul parţial verifică şi condiţiile de finalitate se va furniza soluţia.. şi în ambele situaţii vom considera subalgoritmii de tip funcţie prin care se verifică condiţiile de validitate respectiv de finalitate pentru o soluţie parţială oarecare { x1 ..se revine pe nivelul anterior k-1 (operaţie pop).. x 2 .se alege alt element x k neîncercat din Ak şi se reverifică condiţiile de validitate. Cazul 2. x 2 ...

Problema 1.. Pentru furnizarea soluţiilor..Algoritmi şi structuri de date k=1 //iniţializarea nivelului stivei Câttimp (k≥1) Dacă ( ∃ x k ∈ Ak “neîncercat” ) atunci Stiva [ k ] ← x k //operaţia push Dacă Valid(k)=Adevarat atunci Dacă Final(k)=Adevarat atunci Tipăreşte “Soluţie:” Stiva Stiva [1]. Problema damelor. Având n regine. operaţiile pop (revenirea pe un nivel inferior al stivei soluţie) se execută automat la revenirile din apelurile subalgoritmului. se cer toate soluţiile de aranjare a reginelor pe tabla de şah... fie pe aceiaşi coloană.. Analiza problemei: Pornind de la observaţia că două dame se atacă în condiţiile în care ambele se situează: fie pe aceiaşi linie. se va apela: Cheamă Backtracking (1). Stiva [k ] Altfel Cheamă Backtracking( k+1) //autoapel SfDacă SfDacă SfPentru SfSubalgoritm Observaţie: În varianta recursivă. Se dă o tablă de şah de n linii şi n coloane. fie pe o diagonală a tablei de 151 . Stiva [k ] Altfel k=k+1 //trece la alt nivel SfDacă SfDacă Altfel Stiva [ k ] = Null //operaţia pop k=k-1 //revine la nivelul precedent SfDacă SfCâttimp SfAlgoritm Varianta recursivă: Subalgoritm Backtracking( k) este: //tratează nivelul k Pentru fiecare x k ∈ Ak Stiva [ k ] ← x k //operaţia push Dacă Valid(k)=Adevarat atunci Dacă Final(k)=Adevarat atunci Tipăreşte “Soluţie:” Stiva Stiva [1].. astfel încât oricare două piese de acest tip să nu se atace.

.. dimensiunea vectorului construit este n (s-a completat nivelul n al stivei) Dacă pe tabla de şah sunt dispuse k-1 dame astfel încât acestea nu se atacă (soluţie parţială validă). Stiva [ k ]. × A . Considerăm astfel că dama k este blocată să ocupe coloana k a tablei de şah. reduc la a verifica dacă a k-a damă nu atacă oricare din damele poziţionate pe primele k-1 coloane. Soluţia este reprezentată printr-un vector (stivă). iar valoare de pe nivelul k din stivă coincide cu numărul liniei ocupate de dama k.2... oricare două dame sunt poziţionate pe coloane distincte şi soluţia finală poate fi reprezentată printr-un vector de n elemente Stiva [1]. 1.Algoritmi şi structuri de date şah. Astfel. Spaţiul soluţiilor posibile este produsul cartezian: A × A ×.k-1 Valid=Adevărat Pentru i de la 1 la k-1 Dacă ( Stiva [ k ] = Stiva [i ] ) SAU ( (k-i)=( Stiva [ k ] − Stiva [i ] ) ) atunci / damele k şi i sunt situate pe aceiaşi linie sau pe aceiaşi diagonală Valid=Fals SfDacă SfPentru SfFuncţie Funcţie Final(k) este: Dacă k=n atunci // am ajuns la nivelul n al stivei – s-au poziţionat toate cele n regine Final=Adevărat Altfel Final=Fals 152 . O soluţie este finală dacă s-a reuşit poziţionarea tuturor damelor. Pe baza consideraţiilor 1-5 problema 1 se poate rezolva printr-un algoritm Backtracking...n}. unde A={1...corespunde atât indicelui de coloană cât şi identificatorului damei. se poate trece la poziţionarea celei de-a k-a piesă. 5. 2.. putem deduce o variantă simplificată de reprezentare a unei soluţii prin împiedicarea oricăror dame de a fi situate pe aceiaşi coloană. Algoritmul general descris anterior nu se modifică. în schimb vor fi detaliate funcţiile Valid şi Final: Funcţie Valid(k) este //verifică dacă a k-a damă atacă damele 1. poziţia în stivă: k .…. Stiva [n ] astfel încât: k– reprezintă indicele coloanei pe care se află dama k Stiva[k] – elementul din stivă este indicele liniei ocupate de dama k Practic. 3..…. respectiv..2.. Condiţiile ca o soluţie parţială Stiva [1]. Stiva [k ] să fie validă se 4.

for(i=stiva[k]+1. while(k>=1) { gasit=0. //solutia se construieste in stiva int nrsolutii.(i<=n) .i++) printf("%d ". scanf("%d".i<=n. for(i=1. 153 .h> int stiva[100]. int valid(int k) { int i.Algoritmi şi structuri de date SfDacă SfFuncţie Rezolvare1: Programul C pentru rezolvarea problemei damelor (varianta nerecursivă) #include <stdio.h> #include <math. else return 0.i++) if ((stiva[k]==stiva[i]) || (abs(k-i)==abs(stiva[k]-stiva[i]) ) ) cod=0. for(i=1. printf("\n"). int n. int k=1.i++) { stiva[k]=i. } int final(int k) { if (k==n) return 1. //incepem de la primul nivel.&n).stiva[i]).int cod=1.i<=k-1. int i. int gasit. return cod. } void tipareste() { int i. } void main() { nrsolutii=0. printf("dati n:").

i++) if ((stiva[k]==stiva[i]) || (abs(k-i)==abs(stiva[k]-stiva[i]) ) ) cod=0. int valid(int k) { int i.stiva[i]). } int final(int k) { if (k==n) return 1.} else k=k+1.i<=k-1.} } if (gasit==0) //nu s-au mai gasit {stiva[k]=0. } Rezolvare2: Programul C pentru rezolvarea problemei damelor (varianta recursivă) #include <stdio.h> #include <math. int cod=1. printf("\n"). k=k-1. int n.break. for(i=1.i++) printf("%d ". //solutia se construieste in stiva int nrsolutii.h> int stiva[100]. return cod. } printf("\n Numarul de solutii=%d". for(i=1. } 154 .i<=n. } else if (final(k)==1) {tipareste(). } void tipareste() { int i.nrsolutii).Algoritmi şi structuri de date if (valid(k)==1) {gasit=1. nrsolutii=nrsolutii+1. else return 0.

Est şi Vest.i<=n. n )    O soluţie finală a problemei poate fi reprezentată printr-un şir al mişcărilor efectuate în labirint.Algoritmi şi structuri de date void dame(int k) { for(int i=1.2 )  L=  . j) ∈{0.1}    a( m.1 ) a( m.2 ) a( m. Analiza problemei: Fie L – matricea de reprezentare a labirintului: a( n. ale cărei elemente sunt valori 1 sau 0.scanf("%d". Sud.1) a( 2. însă configuraţia stivei în care sunt salvate camerele traversate este uşor modificată faţă de problema damelor. a( i.1 )   a( 1. Principul aplicat este acelaşi ca şi la Backtracking-ul simplu: soluţia/soluţiile se construiesc treptat. Lungimile acestor vectori soluţie sunt diferite. de coordonate Xstart si Zstart se afla o persoană. } } void main() { printf("\n dati n:").i++) { stiva[k]=i. Determinaţi toate drumurile prin care persoana va putea ieşi din labirint. Anumite drumuri parcurse pot fi mai 155 .1 )    a( 2.j) cu semnificaţia poziţiei camerelor prin care a trecut persoana pentru a ieşi din labirint. if (valid(k)==1) if (final(k)==1) tipareste(). Acest gen de probleme pentru care fiecare nivel al stivei reţine mai multe elemente se rezolvă cu un algoritm Backtracking generalizat.&n).1 ) a( 2. } Problema 2. Fiecare element al matricei reprezintă o camera a labirintului (1 pentru zid si 0 pentru drum liber). else dame(k+1). Astfel. Se dă un labirint reprezentat sub forma de matrice cu m linii si n coloane. Intr-una din camere.dacă aceasta va putea efectua doar mişcări spre Nord. Această observaţie ne este utilă în modul de construire a stivei: fiecare nivel al stivei necesită memorarea a două elemente (spre deosebire de rezolvarea problemei 1): linia şi coloana poziţiei în matrice. fiecare poziţie nouă pe care o încearcă persoana în labirint este identificată prin două elemente: linia şi coloana matricei prin care este reprezentat labirintul. dame(1). Soluţiile finale ale problemei labirintului sunt vectori de perechi (i.

dreapta. persoana poate trece într-o cameră vecină dacă: nu este zid (cameră liberă) şi nu a mai fost traversată. De asemenea. Drumul persoanei poate continua într-o nouă poziţie (i. Condiţiile de validitate se deduc din specificaţiile problemei.j)=0) atunci //cameră liberă Valid=Adevărat Pentru l de la 1 la k-1 Dacă (i.j) să fie soluţie finală a problemei este adevărată dacă ultima cameră (poziţie) parcursă se află la marginea labirintului: pe prima sau ultima coloană. pentru a evita parcurgerea aceloraşi camere de mai multe ori. Mai mult.zid Valid=Fals SfDacă SfFuncţie Funcţie Final (k) Fie (i.j) –poziţia memorată în Stiva[k] Dacă (a(i. pe prima sau ultima linie a matricei de reprezentare. sau.j) –poziţia memorată în Stiva[k] Dacă i=1 sau j=1 sau i=m sau j=n atunci Final=Adevarat Altfel Final=Fals SfDacă SfFuncţie Subalgoritm Labirint(k) este: Pentru fiecare vecin posibil (i. j ) = Stiva [l ] atunci //camera a mai fost traversată în drumul memorat în stivă Valid=Fals SfDacă SfPentru Altfel //camera ocupată . Condiţia ca un vector de perechi (i. tot specificaţiile problemei ne indică faptul că mutarea într-o nouă cameră se va face doar prin 4 mişcări posibile în poziţiile vecine: stânga.j) al camerei salvate în Stiva[k-1] Stiva[k]=(i. punând în evidenţă condiţiile de validitate şi finalitate prin subalgoritmi corespunzători: Funcţie Valid(k) // k-nivelul stivei Fie (i. sus şi jos În continuare este descris algoritmul backtracking de rezolvare a problemei labirintului (varianta recursivă).Algoritmi şi structuri de date scurte decât altele. se impune restricţia ca poziţiile prin care trece persoana să fie distincte. Astfel.j) //operaţia push 156 .j) dacă valoarea elementului de pe linia i şi coloana j din matricea L este 0 (drum liber).

} void permutari(int k) { for (int i = 1.. i++) if(stiva[i] == stiva[k]) return 0. i++) { stiva[k] = i. if (valid(k)) if (k == n) TiparesteSolutia(). i < k. permutari(1).Algoritmi şi structuri de date Dacă Valid(k)=Adevărat atunci Dacă Final(k)=Adevărat atunci Tipăreşte “Soluţie:” Stiva Stiva [1]. } } void main() { printf("dati n"). void TiparesteSolutia() { printf("\n"). #include <stdio. else permutari(k+1). Problemă rezolvată PERMUTĂRI: Programul următor geneează permutările de n prin metoda Backtracking . Ystart ) şi apelul:Cheamă Labirint(1). } 157 . } int valid(int k) { for (int i = 1. i <= n..stiva[i]). Stiva [k ] Altfel Cheamă Labirint(k+1) SfDacă SfDacă SfPentru SfSubalgoritm Rezolvarea problemei labirintului se face prin iniţializarea stivei cu poziţia de start: Stiva [0] = ( Xstart . stiva[200]. return 1.&n). i++) printf("%d".h> int n. for (int i = 1.. i <= n.scanf("%d".varianta recursivă.

Dn-1. METODE DE ELABORARE A ALGORITMILOR. Problema de optimizare rezolvabilă prin metoda programării dinamice este formulată astfel: Fie un sistem care se poate afla într-una din stările: S0. programarea dinamică presupune împărţirea problemei în subprobleme.. D2 . Ca şi metoda Divide et impera. În situaţia în care subproblemele obţinute prin descompunere conţin subprobleme comune.PROGRAMARE DINAMICA. → S n − 1   → S n Problema verifică unul dintre principiile optimalităţii: Varianta 1: Dacă şirul de decizii D1.2. Dn-1.. S1.soluţia este construită în manieră bottom-up (de jos în sus): pornind de la soluţiile celor mai mici subprobleme. însă la Programarea dinamică.. Sn-1.. D2 .. rezolvarea şi combinarea soluţiilor acestora pentru a obţine rezultatul final. Dn prin care sistemul este trecut prin stările date: D D Dn S 0  1 → S 1  2 → S 2 → . atunci: ∀ ∈{1. Denumirea metodei provine din maniera de completare dinamică a tabelei de informaţii intermediare. S2 . Tabelele sunt construite prin completarea unui element folosind elemente completate anterior. …. Dn-1. …. rezultatul final oferit de metoda programării dinamice este întotdeauna optimul global.sunt evitate calculele repetate ale aceluiaşi subcaz.. O particularitate a metodei constă în construirea si utilizarea unor tabele cu informaţii. …. Dn duce sistemul din starea k Sk-1 în starea finală Sn în mod optim. …. Dn duce sistemul din starea iniţială S0 în starea finală Sn în mod optim. În cazul metodei Greedy. Sn şi fie şirul de decizii D1.Algoritmi şi structuri de date XVI. prin combinarea acestora se va ajunge treptat la soluţia globală . este preferabilă aplicarea metodei Programării dinamice. D2 . Metoda Programării dinamice se poate considera ca o îmbunătăţire a tehnicii Greedy. Dn-1. Metoda programării dinamice se caracterizează prin principiile fundamentale: . rezultatele intermediare obţinute fiind memorate . secvenţa de decizii Dk . folosind-se de un criteriu mai puternic. alegerea unui element se face pe baza unui criteriu de admisibilitate. Dn duce sistemul din starea iniţială S0 în starea finală Sn în mod optim. acest criteriu este cel al optimalităţii. Varianta 2: Dacă şirul de decizii D1. …. n} . atunci: 158 .problema abordată respectă principiul optimalităţii Programarea dinamică este o metodă de rezolvare a problemelor de optimizare pentru care soluţia este rezultatul unui şir de decizii optime.

. se află optimul total 4.. Dk-1. de la început spre sfârşit (înainte) Metoda înapoi este duală metodei descrise anterior şi operează în următoarele 4 faze: 1.3 159 . ….Algoritmi şi structuri de date ∀ ∈{1. Metoda înainte operează în următoarele 4 faze: 1. în mod invers. X n } o mulţime (şir) neordonată de elemente. în mod directe.. se află optimul total 4. Dk duce sistemul din starea k iniţială S0 în starea Sk în mod optim. prin relaţiile de recurenţă se vor calcula optimele corespunzătoare stărilor îndepărtate de starea finală în funcţie de optimele corespunzătoare stărilor apropiate de starea finală (practic în această fază se completează tabela de informaţii intermediare) 2.2.. se determină deciziile care leagă optimele determinate în faza anterioară 3. Esenţa celor două variante (înainte sau înapoi) este aceiaşi şi constă în identificarea şi rezolvarea relaţiilor de recurenţă referitoare la criteriul de optimizat sau la valoarea ce se calculează.. şir ordonat crescător. se determină soluţia problemei reprezentată de un şir de decizii constituit prin compunerea deciziilor de la etapa 2. În funcţie de varianta principiului optimalităţii identificată din analiza problemei... se determină soluţia problemei reprezentată de un şir de decizii constituit prin compunerea deciziilor de la etapa 2. B ⊆ A . n} . de la sfârşit spre început (înapoi) Deşi pare o metodă generală de rezolvare a problemelor de optim. Problema 1 Subşir crescător de lungime maximă (metoda înainte) Se dă A = { X 1 . Metoda înapoi – aplicată în cazul verificării principiului optimalităţii în forma 2. metoda de abordare a poate fi: Metoda înainte – aplicată în cazul verificării principiului optimalităţii în forma 1. prin relaţiile de recurenţă se vor calcula optimele corespunzătoare stărilor apropiate de starea finală în funcţie de optimele corespunzătoare stărilor depărtate de starea finală 2. secvenţa de decizii D1 . de lungime maximă. Exemplu: în şirul 7 1 4 2 5 3.. aplicabilitatea programării dinamice este restrânsă doar la acele probleme pentru care se poate demonstra principiul optimalităţii. cel mai lung subşir crescător este 1.2. Se cere determinarea lui B. X 2 . se determină deciziile care leagă optimele determinate în faza anterioară 3.

…. pentru fiecare element al şirului iniţial lungimea celui mai mare subşir crescător care se poate forma începând de la el. n} } 160 . i={1. i k +1 .Algoritmi şi structuri de date Rezolvarea naivă a acestei probleme ar aceea prin care se vor determina toate subşirurile crescătoare urmând ca ulterior să se extragă acela care are lungime maximă.. n}} şi k ∈ {1.. subşirul care îl conţine are lungimea mai mare cu 1... ….. respectiv 2 (1+1) şi subşirul maximal 1 2 3 are lungime 3 (2+1). . O soluţie ingenioasă are fi să se calculeze şi să se reţină într-o tabelă doar lungimile tuturor subşirurilor crescătoare. i n este subşirul optimal crescător de lungime n care începe cu i1 : atunci: .i k . fapt pentru care se va aplica metoda înainte: Etapa_1&2: se vor calcula pe baza relaţiilor de recurenţă. subşirurile crescătoare şi lungimile acestora sunt: Subşiru l 7 123 45 23 5 3 Lungimea 1 3 2 2 1 1 Se poate observa că dacă subşirul 3 are lungime 1.. i n este subşirul optimal optimal de lungime n-1 care începe cu i2 . ∀ >1 Prin reformularea observaţiilor anterioare se identifică principiul optimalităţii în varianta 1.i 3 . atunci: 2 3 este subşirul optimal crescător de lungime maximă (2) care începe cu 2 şi 3 este subşirul optimal crescător de lungime maximă (1) care începe cu 3 Generalizând. . În exemplul anterior.... dacă i1 . care începe cu 1.i 2 . Relaţia de recurenţă este următoarea: L(k):={1+maxL(i) . însă neeficientă prin necesitatea de a construi şi reţine toate subşirurile lui şirului dat.. fără a le genera şi pe acestea. …. i n este optimal de lungime n-2 care începe cu i3 . i 2 . Astfel: Dacă subşirul 1 2 3 este subşirul optimal crescător de lungime maximă (3). astfel încât Xi >=Xk. Notăm L(k) lungimea celui mai mare subşir crescător care se poate forma începând elementul de pe poziţia k din şirul dat. 2.. Rezolvarea aceasta este corectă.. i n este subşirul optimal de lungime maximă care începe cu k ik.. i 3 .. 2....

L(imax) – reprezintă lungimea subşirului crescător de lungime maximă (lungimea soluţiei) .  a  m1 a12 a22 . prin construire. . Exemplu: Pentru următorul caz: 161 . .   amn   Fiecare căsuţă a tablei conţine obiecte de valori diferite (numere naturale): aij ∈N . reţinându-se doar elementele care verifică relaţia de ordine..... am 2 . În acest caz: .la dreapta cu o căsuţă: ( aij →ai .. Un robot situat în căsuţa de start a11 va traversa tabla pentru a ajunge la căsuţa finală amn realizând doar mişcările posibile: .. Fie acesta L(imax)..în jos cu o căsuţă: ( aij →a(i +1).Algoritmi şi structuri de date Algoritm Determinare_Lungimi este: L(n)=1 Pentru k=n-1 la 1 cu pas -1 L(k)=1 Caută i>k astfel încât i ≤ n şi X i ≥ X k Dacă s-a găsit (i) atunci L(k)=L(i)+1 SfDacă SfPentru SfAlgoritm Etapa3: Se determină maximul din tabela lungimilor L. Problema 2: Problema Robotului (metoda înapoi) Se dă o tablă împărţită în m linii şi n coloane:  a11  a A =  21 .. a1n   a2 n  ...( j +1) ) ... Să se determine secvenţa de mişcări (drumul) a robotului astfel încât suma valorilor colectate la ieşire să fie maximă.imax – reprezintă indicele din şirul iniţial de la care începe subşirul soluţie Etapa4: Se determină soluţia problemei (subşirul crescător de lungime maximă). pornind de la elementul de pe poziţia imax până la elementul de pe poziţia n. j ) şi colectează obiectele din căsuţele traversate. ...

. m} şi ∀. j k . efectuând ultima mutare jos (robotul ajunge în celula ai .4).j k− 11 k− 1 k Din observaţiile anterioare. aik −11 . maximă posibilă de la a11 la 1 Observaţie: Ultima formulare este chiar Principiul optimalităţii exprimat în varianta 2: Dacă secvenţa de celule parcurse de la celula : (1.. efectuând ultima mutare dreapta (robotul ajunge în celula ai .n k Atunci ∀ < n . Se observă că suma maximă cu care ajunge robotul în căsuţa finală poate fi obţinută fie prin mutarea anterioară de pe coloana din stânga (n-1). fie prin mutarea anterioară din celula de pe linia (m-1).1) la ( ik . n} i { j { 162 .2)-(4.Dacă în celula ai . ai . dacă suma finală a valorilor este maximă. j − robotul a ajuns cu sumă 1 1 maximă posibilă de la a11 la ai .Algoritmi şi structuri de date 2 4 0 2 0 0 5 4 7 3 1 1 1 0 0 2 Soluţia imediată este parcurgerea căsuţelor: (1. … . se poate deduce o relaţie de recurenţă prin care pot fi determinate sumele maxime cu care robotul poate ajunge în oricare celulă a tablei A: S ij = max ( S i −1.1)-(2.1) la celula (m. j . S i . …. înseamnă că şi drumurile parţiale efectuate până în celula am− . j robotul a ajuns cu sumă 1 1 ai −. j .2)-(4.. aik . Astfel. j − . Generalizând: . j din celula ai .m − sunt de sumă 1 1 maximă parţială.. j . j din celula ai −. ∀.n) este de transport maxim: a11 ..2)-(3. j −1 ) + aij . robotul colectează obiecte de valoare totală: 18 reprezentând suma maximă a sumelor colectate prin parcurgerea oricăror drumuri de la căsuţa iniţială la căsuţa finală.. am ..1)-(2.n sau an . i ∈ 1. j ∈ 1.. atunci prin scăderea valorii comune conţinută în amn . j robotul a ajuns cu transport maxim de la celula iniţială a11 .3)(4. secvenţa de celule parcurse de la (1. j k ) este de transport maxim: a11 .Dacă în celula ai . j − ) atunci în celula ai . 1 . j ) atunci în celula ai −. aik . ….. j robotul a ajuns cu transport maxim de la celula iniţială a11 . jk −1 ..

j − atunci 1 1 Vector(k)⇐ (i-1. Sm2 . k=2 Câttimp ((i.j-1)+ ai....1)) Dacă S i −.n) Fie i=m..j-1) j=j-1 k=k+1 SfDacă SfCâttimp //Vectorul de căsuţe parcurs invers este soluţia problemei.j) //Reţine căsuţa (i-1. j > S i .0):=0 Pentru i de la 1 la m Pentru j de la 1 la n Dacă S(i-1..n reprezintă chiar suma maximă a drumului complet al robotului. i=i-1 k=k+1 Altfel Vector(k)⇐ (i. S1n   S2n  .j SfDacă SfPentru SfPentru SfAlgoritm Se constată că ultima valoare determinată.  S  m1 S12 S 22 . ...j Altfel S(i..j-1) atunci S(i.j):= S(i. S m . Pentru i de la k la 1 cu pas -1 163 .j)> S(i.j)≠(1.. poate fi determinată printr-o parcurgerea inversă a matricei S : Algoritm RefacereDrum este: Vector(1)⇐ (m.   S mn   Algoritm DeterminareSumeMaxime este: S(0.j)+ ai.n) // Reţine căsuţa finală (m..j):= S(i-1.1):=0 S(1.j) traversate cu transport maxim..Algoritmi şi structuri de date Pe baza relaţiei de recurenţă se va construi o tabelă suplimentară care reţine valorile sumelor maxime pe care le poate colecta robotul până în fiecare celulă a tablei iniţiale:  S11  S S =  21 ..j-1) //Reţine căsuţa (i.. j=n.j).. . iar soluţia problemei–vectorul format din căsuţele (i. .

2.Algoritmi şi structuri de date Tipăreşte Vector(i) SfPentru SfAlgoritm Observaţie: Tehnica descrisă pentru rezolvarea problemei robotului este Programarea dinamică. Scrieţi programul de rezolvare a problemei 1: Subşir crescător de lungime maximă. Scrieţi un program care determină cel mai lung subşir comun al două şiruri date. Probleme: 1. varianta metodei înapoi. 164 . Scrieţi un program de rezolvare a problemei robotului. 3.

Donald E. Aplicaţii. 165 . Editura Teora. 2007. Goicea F. Algoritmi fundamentali în C++. 1996. Metode şi tehnici moderne. Bucureşti. 6. Iaşi. Alexandru Vancea. Petrovici V. Editura PROMEDIA. Pârv B.. Bazil Pârv. Fundamentele limbajelor de programare. Editura Polirom. Cluj-Napoca. Liviu Negrescu. 4. Limbajele C si C++ pentru incepatori Vol.. vol. Bucureşti. Knuth. 5. 2002. 1999. Arta programarii calculatoarelor: Algoritmi fundamentali.Algoritmi şi structuri de date BIBLIOGRAFIE: 1. 1994. Editura Tehnică. Doina Logofătu. 1993. I (p. Elaborarea programelor. Editura Albastră. Frenţiu M.1 si 2) Limbajul C . Editura Albastră. 2. Dennis Ritchie. Programare în limbajul C. Second Edition. 3.1 .. Cluj-Napoca. 7. 1988.. Prentice Hall. The C Programming Language. Brian Kernighan. Cluj Napoca.

Sign up to vote on this title
UsefulNot useful