Documente Academic
Documente Profesional
Documente Cultură
Alba Iulia
2008
1
CUPRINS
2
Algoritmi şi structuri de date
2. Definirea algoritmilor
Aşa cum am subliniat mai sus, etapa de proiectare a algoritmilor este cea mai
complexă dintre cele etapele enumerate ale programării. Noţiunea de algoritm,
proprietăţile pe care trebuie să le îndeplinească acesta şi modalităţile de
reprezentare standardizată a algoritmilor fac subiectul paragrafelor următoare.
Cuvântul algoritm provine din pronunţia fonetica a numelui matematicianului
arab Al-Khwarizmi Muhammed ibs Musa (780-850), care se referă la reguli
precise pentru descrierea proceselor de calcul din aritmetică. Aceiaşi descriere se
regăseşte în lucrarea Elementele lui Euclid, cca. 300 î.Hr., când pentru prima dată
se descrie o secvenţă ordonată de reguli clare pentru descrierea rezolvării unor
probleme. De altfel, algoritmul de determinare a celui mai mare divizor comun a
două numere (algoritmul lui Euclid) este considerat ca primul algoritm din istorie.
În limbajul uzual, prin algoritm se înţelege o metodă de rezolvare a unei
probleme, alcătuită dintr-o mulţime de paşi, dispuşi într-o ordine stabilită, ale căror
parcurgere ne conduce la rezultatul dorit. Algoritmii informatici, despre care vom
discuta în acest capitol, sunt definiţi mult mai riguros, surprinzând proprietăţile
obligatorii pe care aceştia trebuie să le îndeplinească.
3. Proprietăţile algoritmilor:
3
Algoritmi şi structuri de date
4
Algoritmi şi structuri de date
5
Algoritmi şi structuri de date
6
Algoritmi şi structuri de date
7
Algoritmi şi structuri de date
8
Algoritmi şi structuri de date
7. Pseudocod
9
Algoritmi şi structuri de date
Sfârşit NUME-ALGORITM
sau SfAlgoritm
Citeşte a1,a2,…,an
Tipăreşte a1,a2,…,an
10
Algoritmi şi structuri de date
Structurile
Schema logică Pseudocod Limbaj natural
fundamentale
Structura [Execută] S1 Execută secvenţial
secvenţială ... blocurile de calcul
[Execută] Sn S1, S2,…Sn
Structuri Dacă Cond atunci Dacă condiţia este
alternative Execută S1 adevărată atunci se execută
Altfel blocul S1, altfel se execută
Execută S2 blocul S2.
SfDacă
Dacă Cond atunci Dacă condiţia este
Execută S adevărată atunci se execută
Sf Dacă blocul S1.
11
Algoritmi şi structuri de date
Altfel
Tipăreşte “Imposibil de
rezolvat”
SfDacă
Altfel
Δ = b2-4ac
Dacă Δ≥0 atunci
x1 = ( −b − ∆ ) 2a
x 2 = ( −b + ∆) 2a
Tipăreşte x1,x2
Altfel
Tipăreşte “Rădăcini complexe“
SfDacă
SfDacă
Sfârşit EcuaţieGradII
12
Algoritmi şi structuri de date
Sfârşit Euclid
10. Subalgoritmi
Citeşte a,b,c,d
Cheamă(a,b; max1)
Cheamă(c,d; max2)
Cheamă(max1,max2 ; maxim)
Tipăreşte maxim
Sfârşit Maxim4
14
Algoritmi şi structuri de date
1. <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. <simboluriSpeciale>::=<simboluriSpecialeAfişabile>|
<simboluriSpecialeNeafişabile>
<simboluriSpecialeAfişabile>::=!|,|”|%|#|&|(|)|*|/|+|-|:|
;|<|=|>|?|[|]|\|~|{|}|||.|’|spaţiu|
<simboluriSpecialeNeafişabile>::=\n |\t |\b |\r |\f |\v |\a
B. Unităţile lexicale:
1. Identificatori
2. Cuvinte cheie (cuvinte rezervate)
3. Constante
4. Operatori
C. Unităţile sintactice:
1. Expresii
2. Instrucţiuni
15
Algoritmi şi structuri de date
Unităţile lexicale sunt elementele atomice ale unui program, formate cu ajutorul
simbolurilor precizate de alfabet.
B.1. Identificatorii: sunt secvenţe de litere şi cifre ale limbajului definit care
încep obligatoriu cu o literă sau caracterul ‘_’, având rolul de a identifica
conceptele utilizate în program. Identificatorii corespund unor nume de variabile,
nume de constante simbolice, nume de funcţii.
Exemple:
int a,b,c; // declararea a trei variabile simple de tip întreg
float var; //declararea unei variabile simple de tipul real, virgulă flotantă în
simplă precizie
char ch ; // declararea unei variabile de tip caracter
16
Algoritmi şi structuri de date
B.3. Constante
17
Algoritmi şi structuri de date
Constante caracter
Constanta caracter este un simbol care are ataşată valoarea codului ASCII al
caracterului corespondent.. Un caracter este reprezentat în memoria calculatorului
pe un octet (8 biţi).
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: ‘ ‘.
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, se specifică
utilizând caracterul backslash (\) şi apostroful (‘).
Exemple:
Întoarcerea cu un spaţiu (Backspace) ‘\b’ – codul ASCII 8
Retur car ‘\r’ – codul ASCII 13
Linie nouă ‘\n’ – codul ASCII 10
Constante numerice
Constantele numerice sunt în fapt numerele. În limbajul C se face o distincţie
între numere, în funcţie de tipul (întreg sau real), baza de numeraţie şi precizia
codificării.
Numerele întregi zecimale, exemple: -123, 5, +5.
Numerele octale întregi, încep cu cifra 0 şi sunt de forma: 0c…c, unde c
este cifră octală. Exemple: 0452 , unde 452 este numărul în baza 8
18
Algoritmi şi structuri de date
Constante simbolice
Definirea constantelor simbolice se face prin directiva de preprocesare define,
prin construcţia:
#define <NumeConstantă> <ValoareAsociată>
Ex: #define PI 3.1415
#define raza 124
B.4. Operatorii
Operatorii sunt reprezentaţi de simboluri sau secvenţe de simboluri care intră în
alcătuirea expresiilor.
În funcţie de tipul operaţiilor pe care le induc, operatorii limbajului C sunt
clasificaţi:
a. Operatori aritmetici
b. Operatori de incrementare şi decrementare
c. Operatori logici
d. Operatori relaţionali
e. Operatori de deplasare
f. Operatori la nivel de biţi
g. Operatori de asignare (atribuire)
h. Operatorul condiţional
i. Alţi operatori
După numărul operanzilor pe care îi implică, operatorii pot fi:
- unari
- binari
- ternari
Operatorii aritmetici
Operatori aritmetici unari:
<OperatoriAritmeticiUnari>::= - | +
Au rolul de a specifica semnul unei expresii. Se folosesc în construcţii de
următoarea formă:
{+/-}<expresie>
19
Algoritmi şi structuri de date
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, 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
Operatorii relaţionali:
<OperatorRelaţional>::= = = | != | < | <= | > | >=
Cu semnificaţia:
= = egal
!= diferit
<= mai mic sau egal
>= mai mare sau egal, etc.
20
Algoritmi şi structuri de date
Operatorii logici:
<OperatorLogic> ::= && | || | !
Cu semnificaţia:
&& - operatorul AND (ŞI) conjuncţia
|| - operatorul OR (SAU) disjuncţia
! - operatorul NOT (negaţia)
Exemple:
(a<b) && (b !=c) , 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;
Operatorii de deplasare corespund operaţiilor de deplasare pe biţi, 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.
Regulă: Prin aceste operaţii de deplasare, biţii deplasaţi în afara limitei de
reprezentare se pierd, 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
21
Algoritmi şi structuri de date
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
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,2,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ă.
Exemple: (A<B) ? A : B
(A= =0) ? “egal cu zero” : “diferit de zero”
22
Algoritmi şi structuri de date
Efectul operatorului cast este conversia explicită a unei expresii la un alt tip de
dată decât cel implicit. Utilizarea operatorului se face prin construcţii: (tip)
expresie, unde tip - reprezintă tipul de date spre care se face conversia.
Expresiile de forma var=expresie semnifică evaluarea prima dată a expresiei, şi
dacă tipul rezultatului obţinut este diferit de cel al variabilei, se realizează
conversia implicita la tipul variabilei. În conversiile explicite, operatorul cast apare
în expresii de forma: var = (tip) expresie şi prin această operaţie, variabila
primeşte valoarea la care a fost evaluată expresia convertita la tipul explicit tip.
C. Unităţile sintactice
C.1. Expresii
Expresiile sunt secvenţe de operatori şi operanzi. În funcţie de tipul operatorilor
utilizaţi, expresiile pot fi: expresii aritmetice, expresii logice, relaţionale, etc.
Operanzii care intră în alcătuirea expresiilor pot fi:
- variabile
- constante
- apel de funcţii
- alte expresii
C.2. Instrucţiuni
Instrucţiunile sunt propoziţii care respectă regulile limbajului de programare în
care sunt redactate. Instrucţiunile pot fi simple sau compuse.
23
Algoritmi şi structuri de date
C. Instrucţiunile de ramificare
- corespund structurilor alternative: decizie simplă şi decizie multiplă.
Instrucţiunea if
Sintaxa instrucţiunii:
if ( <expresie> )
<instrucţiune_1>
else
<instrucţiune_2>
sau:
if ( <expresie> )
<instrucţiune_1>
Efectul instrucţiunii:
<expresia> este evaluată şi dacă valoarea obţinută în urma evaluării este
diferită de 0 se execută instrucţiunea 1, altfel se execută instrucţiunea 2.
Cele două instrucţiuni 1 şi 2 se exclud reciproc.
Instrucţiunea IF simplă nu conţine partea else <instrucţiune 2>
Instrucţiunea IF completă conţine ambele ramuri.
Exemple:
Instrucţiunea switch
- reprezintă o generalizare a structurii alternative (if).
- se poate înlocui printr-o secvenţă de structuri alternative imbricate.
Î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. Aceasta
instrucţiune are sintaxa:
break;
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
Algoritmi şi structuri de date
switch (<expresie>)
{
case <val1> : <instrucţiune_1>; break;
case <val2> : <instrucţiune_2>; break;
…
case <valn> : <instrucţiune_n>; break;
[default : <instrucţiune_default>;] //opţional
};
Efectul instrucţiunii:
- Se evaluează expresia dintre paranteze la o valoare.
- Se “caută” secvenţial valoarea la care a fost evaluată expresia în lista
de valori val1, val2, …valn.
- Dacă nu se găseşte nici o valoare val1, val2,…valn egală cu valoarea la
care a fost evaluată expresia se va executa, dacă este precizată,
instrucţiunea default sau efectul instrucţiunii switch este nul
- 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ă.
switch (numar)
{
case 1: printf(\n Luni”);break;
case 2: printf(\n Marti”); break;
case 3: printf(\n Miercuri”); break;
case 4: printf(\n Joi”); break;
case 5: printf(\n Vineri”); break;
case 6: printf(\n Sambata”); break;
case 7: printf(\n Duminica”); break;
default:printf(\n nu este numar 1,2,3,4,5,6,7”);
}
D. Instrucţiunile de ciclare
În pseudocod am evidenţiat cele trei tipuri de structuri repetitive. 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
Algoritmi şi structuri de date
Efectul instrucţiunii:
În primul pas, se evaluează expresia dintre paranteze. 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. Dacă rezultatul unei evaluări este nul, se
întrerupe execuţia instrucţiunii while, şi programul se continuă cu următoarea
instrucţiune ce urmează instrucţiunii while.
Exemplu 1:Se tipăreşte valoarea unui contor până când acesta devine mai mare
decât 10. Valoarea iniţială a contorului este 0.
i=0;
while (i<=10)
{
printf (“%d”, i);
i++;
}
Exemplu 2: Se calculează suma primelor 10 numere naturale, respectiv: 1+2+3+…
+10
i=1;s=0; //iniţializarea
while (i<=10)
{
s=s+i;
i++;
}
Instrucţiunea do…while
Sintaxa:
do
<instrucţiune> //corpul
while (<expresie>);
26
Algoritmi şi structuri de date
suma=0;
do
{
scanf(“%d”, &nr); //citeste numărul curent
suma=suma+nr;
} while (nr>0) ;
#include <stdio.h>
void main()
{
char t; //caracterul curent
int contor=0;
printf("Introduceti textul: ");
do
{
scanf("%c",&t); //citire caracter cu caracter textul
if ((t=='a')||(t=='e')||(t=='i')||(t=='o')||(t=='u'))
contor++;
} while (t!=10); //10 – este codul ASCII pentru Enter
printf("Numarul de vocale: %d",contor);
}
Instrucţiunea for
Sintaxa:
for ( <expresie1> ; <expresie2> ; <expresie3> )
<instrucţiune>
27
Algoritmi şi structuri de date
Unde:
- cele trei expresii din antetul instrucţiunii sunt separate prin
caracterul ;
- <instrucţiune> poate fi simplă, compusă sau poate conţine o
altă instrucţiune repetitivă for.
- <expresie1> - reprezintă expresia de iniţializare. (iniţializarea
contorului)
- <expresie2> - reprezintă o expresia ce este evaluată şi de a
cărei valori de evaluare depinde repetarea corpului
instrucţiunii.
- <expresie3> - reprezintă expresia de actualizare (actualizarea
contorului)
Efect:
- În primul pas, se evaluează expresia1, 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, se încheie execuţia for.
fact=1;
for (i=1;i<=n;i++)
{
fact=fact*i;
}
Specificatorii de format
<specificator de format>::= % [-] [m.[n]] [l] <conversie>
<conversie>::= d|o|x|X|u|s|c|f|e|E|g|G
29
Algoritmi şi structuri de date
Exemple:
printf(“valoarea intreaga a= %d”,a);
printf(“realul a= %5.2f”,a);
printf(“%d + %d = %d”,a,b,a+b);
Exemple:
30
Algoritmi şi structuri de date
Redactarea unui program în limbajul C este liberă atâta timp cât se respectă
regulile sintactice, semnele de punctuaţie şi se folosesc construcţiile specifice în
mod corect. Programul C este constituit dintr-o secvenţă de instrucţiuni, într-o
ordine determinată, corespunzător algoritmului de rezolvare a problemei
considerate. Instrucţiunile programului pot fi instrucţiuni de declarare sau definire
a unor variabile, funcţii şi instrucţiuni care se regăsesc în corpul funcţiilor.
Structura unui program C este următoarea:
31
Algoritmi şi structuri de date
Exemplu de program C:
#include <stdio.h> //rezolvarea ec. de gradul II
#include <math.h>
void main(void)
{
double a,b,c,x1,x2,delta;
printf("\nDati coeficientii ecuatiei de gradul II: ");
scanf("%lf,%lf,%lf",&a,&b,&c);
if (a==0)
{
if (b==0)
printf("ecuatie imposibila!");
else
{
x1=-c/b;
printf("Solutia ecuatiei de grad I: %lf",x1);
}
}
else
{ delta=b*b-4*a*c;
if (delta>=0)
{
x1=(-b-sqrt(delta))/2*a;
x2=(-b+sqrt(delta))/2*a;
printf("Solutiile ec. gr. II: %lf, %lf",x1,x2);
}
else
printf("Ecuatia de gr. II nu are solutii reale!");
}
}
Probleme:
1. Să se citească un sir de n numere întregi de la tastatură şi să se calculeze suma
acestora.
2. Să se citească n numere întregi şi să se numere câte elemente sunt pozitive.
3. Să se scrie un program C pentru afişarea relaţiei dintre două numere: < , > sau =.
4. Scrieţi un program C pentru rezolvarea ecuaţiei de gradul II
32
Algoritmi şi structuri de date
Obs. 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.h
5. Să se scrie un program C care citeşte două numere reale şi un caracter: + , - , /
sau * şi afişează rezultatul operaţiei aritmetice corespunzătoare.
6. Să se scrie un program C care afişează toţi multiplii de k mai mici decât o
valoare dată n.
7. Să se scrie un program C care calculează şi afişează puterile lui 2 până la n,
adică 2, 22,23, …, 2n .
33
Algoritmi şi structuri de date
<tip>NumeFuncţie(<tip1><arg1>,…,<tipn><argn>)
{
<instrucţiuni de declarare de tip a variabilelor locale>
……………
<instrucţiuni>
……………
}
- 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.
- În antetul unei funcţii este precizat tipul de dată pe care îl returnează
<tip>. Acesta poate fi void, caz în care funcţia nu returnează nimic.
- Parantezele rotunde (….) ce urmează numelui funcţiei delimitează lista
argumentelor funcţiei.
- Fiecărui argument îi este precizat tipul şi numele sub care este referit în
cadrul funcţiei curente.
Prototipul funcţiei: este format de antetul funcţiei din care poate să lipsească
numele parametrilor formali:
<tip>NumeFuncţie(<tip1>,…,<tipn>) ; //declararea unei funcţii
La definirea unei funcţii, argumentele precizate în antetul acesteia se numesc
parametrii formali. La apelul funcţiei, parametrii formali sunt înlocuiţi de
parametrii actuali:
34
Algoritmi şi structuri de date
Categorii de funcţii:
void NumeFuncţie ()
{
… //corpul functiei
}
NumeFuncţie();
#include <stdio.h>
void Cifre()
{
int i; //declaraţie de variabilă locală
for(i=0;i<=9;i++) printf(“%d”,i);
}
void main(void)
{
Cifre(); //apelul functiei Cifre
}
2. 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. În caz contrar, funcţia
nu returnează valoare. Tipul funcţiei reprezintă în fapt tipul de dată al valorii pe
care o returnează. Instrucţiunea return, folosită în cadrul funcţiei cu tip definite,
are sintaxa:
return <expresie>;
şi are rolul de a reveni în programul apelant ş de a returna acestuia o valoare de
tipul precizat.
Funcţia poate fi apelată printr-o instrucţiune de asignare:
ValoareReturnată= NumeFuncţie(); //membrul stâng al instrucţiunii este o
variabilă de tipul returnat de funcţie
35
Algoritmi şi structuri de date
#include <stdio.h>
int CitesteValoare()
{
int numar; //declaraţie de variabilă locală
scanf(“%d”,&numar);
return numar;
}
3. Funcţii parametrizate
Funcţiile cu parametri corespund acelor subalgoritmi care prelucrează date de
intrare reprezentând rezultate intermediare ale algoritmului general. Intrările
funcţiei sunt descrise prin lista parametrilor formali ce conţine nume generice ale
datelor prelucrate în corpul funcţiilor. Parametrii efectivi sunt transmişi la apelul
funcţiilor, aceştia înlocuind corespunzător parametrii generici specificaţi. Î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.
Exemplu:
int minim(int a, int b)
{ return ( (a>b)?a:b) }
Fie funcţia:
tip NumeFuncţie(tip1 pf1, tip2 pf2, … , tipn pfn)
{…};
La apelul:
NumeFuncţie(pa1, pa2, … , pan);
se vor transmite prin valoare parametrii actuali şi fiecare parametru formal din
antetul funcţiei este înlocuit cu valoarea parametrului actual. Dacă parametrul
36
Algoritmi şi structuri de date
actual este o expresie, aceasta este evaluată la o valoare, ulterior este copiată în
parametrul formal corespunzător.
Observaţie: modificările aduse parametrilor formali în cadrul funcţiei
apelate nu afectează valorile parametrilor actuali. Exempul următor
evidenţiază acest aspect:
#include <stdio.h>
Î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. În exemplul
anterior efectul dorit este acela ca variabila n după apel să păstreze rezultatul
ridicării la pătrat. Transmiterea prin valoare nu permite acest lucru. În schimb,
transmiterea prin adresă realizată prin intermediul variabilelor de tip pointer
asigură modificarea valorilor parametrilor actuali.
Pentru a înţelege mecanismul transmiterii parametrilor prin adresă în limbajul
C, vom defini în continuare noţiunea de pointer.
Pointeri
37
Algoritmi şi structuri de date
-
-
Variabila pointer p:
Operatorul * se numeşte operator de indirectare
Operatorul & se numeşte operator adresă
Adresa
Expresia *&x are aceiaşi semnificaţie cu expresia x.
Exemplu:
#include <stdio.h>
void main(void)
{
int n=5;
printf(“ valoarea lui n inainte de apel este %d”,n);
// efectul: valoarea lui n inainte de apel este 5
putere(&n);
// efectul: valoarea lui n in functie este 25
printf(“ valoarea lui n dupa de apel este %d”,n);
//efectul: valoarea lui n dupa de apel este 25 (!s-a
modificat parametrul actual)
}
38
Algoritmi şi structuri de date
Pentru construirea funcţiei cerute este utilă transmiterea prin adresă, deoarece,
efectul interschimbării trebuie să fie vizibil şi în codul apelant. Prezentăm în
continuare două variante de implementare, prima dintre acestea foloseşte
transmiterea implicită prin valoare, fapt pentru care nu corespunde rezultatului
dorit. Cea de-a doua este varianta corectă, folosindu-ne de transmiterea
parametrilor prin adresă (prin pointeri).
//Apelul: //Apelul:
int x,y; int x,y;
x=1; x=1;
x=2; x=2;
interschimbare(x,y) interschimbare(&x,&y)
//Efectul //Efectul
Inainte de apel: Inainte de apel:
x=1 x=1
y=2 y=2
După apel: După apel:
x=1 x=2
y=2 y=1
Probleme:
39
Algoritmi şi structuri de date
RECURSIVITATE
<tip>NumeFuncţie(<tip1><arg1>,…,<tipn><argn>)
{
<instrucţiuni de declarare de tip a variabilelor locale>
……………
<instrucţiuni>
……………
NumeFuncţie(pa1, pa2, … , pan); //auto apelul funcţiei
……………
}
La fiecare apel al funcţiei recursive, pe stiva programului sunt depuse noul set
de variabile locale (parametrii). Chiar dacă variabile locale au acelaşi nume cu cele
existente înainte de apelarea funcţiei, valorile lor sunt distincte, şi nu există
conflicte de nume. Practic, ultimele variabile create sunt luate în considerare în
operaţiile conţinute de funcţie.
int factorial(int k)
{
if (k>1)
return (k*factorial(k-1));
else
return 1;
}
…
//apelul functiei
int p;
p=factorial(n);
40
Algoritmi şi structuri de date
Considerăm n=3
1. La apelul iniţial factorial(n) se transmite valoarea 3 parametrului formal k şi
se predă controlul funcţiei apelate
2. Din condiţia adevărată 3>=1 rezultă amânarea revenirii din funcţie şi un
nou apel factorial(2); la ieşirea din primul apel se va return 3*factorial(2).
3. Apelul factorial(2) va produce transmiterea valorii 2 la parametrul formal
k şi predarea controlului funcţiei apelate pentru valoarea curentă a
parametrului
4. Condiţia adevărată 2>=1 produce o nouă apelare factorial(1) cu amânarea
revenirii din apelul curent; la ieşirea din acest al doilea apel se va return
2*factorial(1) .
5. Apelul factorial(1) va produce transmiterea valorii 1 la parametrul formal
k şi predarea controlului funcţiei apelate pentru valoarea curentă a
parametrului
6. 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ă.
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.
factorial(n) n* factorial
n* (n-
Recursivitatea poate fi de mai multe feluri, în funcţie de numărul de apeluri
n
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. 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ă:
- primii doi termeni ai şirului sunt 0,1:
o Fibonacci(0)=0
o Fibonacci(1)=1
- oricare termen este format prin însumarea celor doi termeni
precedenţi:
41
Algoritmi şi structuri de date
o Fibonacci(k)=Fibonacci(k-1)+ Fibonacci(k-2)
Funcţia recursivă este:
int Fibonacci(int k)
{
if (k<=1)
return k;
else
return Fibonacci(k-2) + Fibonacci(k-1); //două
autoapeluri
}
42
Algoritmi şi structuri de date
Exemplu:
int T[100]; //tablou de 100 intregi
Numele tabloului, T - reprezintă adresa primului element al sau, respectiv,
adresa lui T[0].
43
Algoritmi şi structuri de date
Între tablouri şi variabilele pointer există o strânsă legătură: numele unui tablou
este un pointer, fiind de fapt adresa primului element al tabloului respectiv:
tab[0] tab[1]
Exemplu:
int tab[100];
//tab – este adresa elementului t[0]
Observaţie: Deoarece memoria alocată unui tablou este o zonă contiguă, respectiv,
tab
elementele tabloului sunt grupate, cunoscând adresa primului element, printr-o
operaţie elementară se poate accesa oricare alt element al tabloului.
Operaţii cu pointeri:
44
Algoritmi şi structuri de date
tab[0]
int tab[100]; //tablou de 100 întregi
int i; tab[1]
(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, respectiv este
adresa elementului tab[i]
*(tab+i) – reprezintă valoarea elementului tab[i]
tab tab+1
tab[j]. Diferenţa p-q este un număr întreg k, reprezintând numărul de elemente
care desparte cele două adrese. Această dimensiune se poate determina prin
calculul elementar: k=(j-i).
tab+
Între adresele p şi q se află un număr de (j-i) elemente, respectiv, (j-i)*dim
octeţi, unde dim - dimensiunea necesară reprezentării unui element al tabloului.
dim
tab[i]
Şiruri de caractere .......
Operaţiile cu şiruri de caractere se efectuează prin apelul unor funcţii de
bibliotecă specifice. Prototipurile funcţiilor care manipulează şiruri de caractere se
află în fişierul antet string.h.
45
Algoritmi şi structuri de date
46
Algoritmi şi structuri de date
strcpy(aux,a);
strcpy(a,b);
strcpy(b,aux);
}
Concatenarea a două şiruri de caractere
Se realizează prin apelul funcţiei strcat:
char * strcat(char *destinatie,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. La revenire, funcţia
returnează adresa destinaţie.
#include <stdio.h>
#include <string.h>
void main()
{
char pass[20],cuv[20];
int i,n;
pass[0]=NULL;
printf("dati cuv ");scanf("%s",&cuv);
i=(strlen(cuv)-1);
if (i%2) i--;
n=0;
while (i>=0)
{
pass[n]=cuv[i];
i-=2;
n++;
}
pass[n]=NULL;
printf("parola este %s",pass);
}
47
Algoritmi şi structuri de date
1. Parcurgerea tablourilor
48
Algoritmi şi structuri de date
2. Căutarea secvenţială
- operaţia de căutare presupune parcurgerea element cu element a vectorului, în
ordinea dată de indecşi
//Căutarea : determinarea poziţiei pe care se află valoarea căutată
int cauta_cheie(int t[], int n, int cheie)
{
int i;
for(i=0;i<n;i++)
if (t[i]==cheie)
return i; //poziţia valorii căutate
return -1;//dacă nu s-a găsit valoarea se întoarce -1
}
50
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.
Observaţie: operaţia de concatenarea diferă semnificativ de operaţia de
interclasare.
51
Algoritmi şi structuri de date
TABLOURI n-DIMENSIONALE
52
Algoritmi şi structuri de date
două numere pozitive (indecşi), care reprezintă cele două dimensiuni (linie şi
coloană).
int mat[10][10];
int i,j;
printf(“\nIntroduceti nr. de linii ”);scanf(“%d”,&m);
printf(“\nIntroduceti nr. de coloane ”);scanf(“%d”,&n);
53
Algoritmi şi structuri de date
#include <stdio.h>
#include <conio.h>
#define DimMax 10 //numărul maxim de elemente
//pe linii si coloane
54
Algoritmi şi structuri de date
for(j=0;j<p;j++)
{
s=0;
for(k=0;k<m;k++)
s=s+a[i][k]*b[k][j];
c[i][j]=s;
}
}
void main()
{
int n,m,p;
int a[DimMax][DimMax];
int b[DimMax][DimMax];
int c[DimMax][DimMax];
citire_matrice(&n,&m,a);
citire_matrice(&m,&p,b);
produs(n,m,p,a,b,c);
afisare_matrice(n,p,c);
suma(n,m,a,b,c);
afisare_matrice(n,m,c);
}
ALGORITMI DE SORTARE
xi=min
SfPentru
SfAlgoritm
Exemplu:
/*program care citeste un vector de numere intregi,
ordoneaza elementele vectorului prin metoda sortarii prin selectie
si tipareste vectorul ordonat */
#include <stdio.h>
void main()
{
int a[100],n;
citireSir(a,&n);
SSort(a,n);
56
Algoritmi şi structuri de date
tiparireSir(a,n);
}
57
Algoritmi şi structuri de date
//1.....i-1
k=i-1; aux=x[i];//salvez valoarea curenta de inserat în aux
while ((aux<x[k])&& (k>=0))
//primul element mai mic decat aux
{x[k+1]=x[k];
k--;
}
// k+1 reprezinta pozitia pe care se insereaza aux
x[k+1]=aux;
}
}
58
Algoritmi şi structuri de date
{
if (x[i]>x[i+1])
{
//Interschimb vecinii
aux=x[i];
x[i]=x[i+1];
x[i+1]=aux;
cod=1;
//am gasit vecini care nu respecta
//relatia de ordine
}
}
}while (cod!=0);
}
59
Algoritmi şi structuri de date
unde:
<instructiune de declarare de tip>::= <tip> <lista identificatori>
Exemple:
60
Algoritmi şi structuri de date
struct DataCalendaristică
{
int zi;
int luna;
int an;
} DataNasterii, DataAngajarii;
struct {
int zi;
int luna;
int an;
} DataNasterii, DataAngajarii;
struct DataCalendaristică{
int zi;
int luna;
int an;
}
…
struct DataCalendaristică DataNasterii, DataAngajarii;
struct NumeStructură
{
<instrucţiuni de declarare de tip>
}
struct NumeStructură TablouDeStructuri[N];
61
Algoritmi şi structuri de date
struct DatăCalendaristică SirDate[100];
Pointeri la structuri
62
Algoritmi şi structuri de date
În foarte multe situaţii este nevoie ca utilizatorul să-şi definească noi tipuri de
date pe care le prelucrează în program. Introducerea unui nou tip de date utilizator
se face prin construcţiile struct. Noul tip de date este referit prin: struct
NumeStructură iar datele se vor declara prin instrucţiunile de declarare:
struct NumeStructură data1, data2, …datan;
Pentru a utiliza în mod mai flexibil numele noului tip de date introdus,
respectiv, de a renunţa la cuvântul cheie struct în declaraţii de date se va utiliza
declaraţia de tip.
Limbajul C permite atribuirea unui nume pentru un tip predefinit sau pentru nou
tip definit de utilizator prin construcţia:
typedef TIP NumeTip;
În continuare cuvântul NumeTip poate fi utilizat în declaraţii de date ca şi
cuvintele cheie (rezervate limbajului C) care denumesc tipurile (int, float, etc.)
Exemplu: Redenumirea tipului int al limbajului C:
typedef int INTREG;
Declararea unei variabile X de tipul int se poate face astfel:
INTREG X; //identică cu declaraţia: int X;
63
Algoritmi şi structuri de date
#include <stdio.h>
#include <string.h>
typedef struct{
int zi;
int luna;
int an;
}TData;
typedef struct {
char cnp[13];
char nume[20];
char prenume[40];
TData datanasterii;
char adresa[40];
}TPersoana;
void tiparirePers(TPersoana p)
{
printf("\n%s %s %s %d.%d.%d ", p.cnp,
p.nume,p.prenume,p.datanasterii.zi,p.datanasterii.luna,
p.datanasterii.an);
64
Algoritmi şi structuri de date
}
void main()
{
TPersoana per[20]; //tablou de structuri
int nr;
printf("\n dati numarul de persoana:");
scanf("%d",&nr);
for(int i=0;i<nr;i++)
citirePers(&per[i]);
sortNUME(per,nr);
for(i=0;i<nr;i++)
tiparirePers(per[i]);
}
65
Algoritmi şi structuri de date
66
Algoritmi şi structuri de date
Fiecare operaţie din cele enumerate va fi definită prin intermediul unei funcţii C
corespunzătoare în modulul prezentat mai jos.
TAD RATIONAL
#include <stdio.h>
typedef struct{
int m;
int n;
}Rational;
67
Algoritmi şi structuri de date
a->m = m_nou;
}
int getm(Rational a)
{ return a.m;
}
int getn(Rational a)
{ return a.n;
}
void print(Rational a)
{ printf("\n %d/%d ", a.m, a.n);
}
68
Algoritmi şi structuri de date
reduce(c);
}
Rational invers(Rational a)
{ Rational aux;
aux.m=a.n;
aux.n=a.m;
return aux;
}
69
Algoritmi şi structuri de date
1. Creare fişier
2. Deschidere fişier
3. Citire din fişier
4. Adaugare – scriere în fişier
5. Actualizare fişier
6. Poziţionare în fişier
7. Ştergere fişier
Pentru a putea prelucra un fişier la nivel inferior acesta trebuie să fie creat în
prealabil.
1. Crearea unui fişier nou se realizează prin apelul funcţiei creat care are
prototipul:
int creat (const char* cale, int mod );
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. 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.h şi stat.h.
70
Algoritmi şi structuri de date
Observaţii:
Dacă nu este specificată complet calea fişierului, se consideră implicit calea
curentă.
Calea completă a unui fişier este specificată printr-un şir de caractere de forma:
Litera:\Dir1 \ Dir2 …. \Dir
Unde:
- Litera poate fi A,B,C … reprezintă litera de identificare a discului (ex:
C – harddisk, A – floppy disk)
- Dir1, DIr2, … sunt nume de subdirectoare
Dacă specificăm calea fişierului în apelul funcţiei open, toate caracterele \
trebuie dublate
71
Algoritmi şi structuri de date
Exemplu:
int df;
df = open (“C:\\Borlandc\\bin\\text.cpp”, O_RDWR);
Funcţia read returnează un întreg reprezentând numărul de octeţi citiţi din fişier
sau: –1 în caz de eroare, 0 la terminarea fişierului
Efectul: Prin apelul funcţiei read se citeşte din fişier înregistrarea curentă; la un
apel următor se va citi următoarea înregistrare, ş.a.m.d până la sfârşitul fişierului.
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, i;
char c;
df=open(“proba.txt”,O_RDONLY);
do{
i=read(df, c, sizeof(char) );
//sizeof(char) returnează numărul de octeţi
//necesari tipului specificat între paranteze.
printf(“%c”, c);
}while (i>0);
4. Scrierea în fişier
Funcţia write , prototipul acesteia se află în fişierul io.h:
int write(int df, void *mem, unsigned lung);
unde:
- df – descriptorul ataşat fişierului (returnat de funcţia open)
- mem – pointer spre zona de memorie din care se preia informaţia ce se va
scrie în fişier
- lung – lungimea înregistrării exprimată în număr de octeţi
72
Algoritmi şi structuri de date
5. Poziţionarea în fişier
Citirea şi scrierea în fişiere se face în mod secvenţial, 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. 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, long depl, int orig);
unde:
- df – descriptorul de fişier
- depl – deplasamentul exprimat în număr de octeţi
- 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ă
6. Închiderea fişierului
Funcţia close având prototipul:
int close (int df); //df- descriptorul de fişier
Utilizarea funcţiei close implică includerea fişierului bibliotecă io.h
Returnează:
0 – la închiderea normală a fişierului
-1 – în caz de eroare
73
Algoritmi şi structuri de date
2. Închiderea fişierului
Funcţia de închidere a unui fişier se numeşte fclose şi are prototipul:
int fclose(FILE *pf);
Argumentul funcţiei:
pf – pointer spre FILE, identifică fişierul ce se închide
Funcţia returnează 0 în caz de succes sau EOF (end of file) în caz de insucces.
În declaraţiile de tip vom avea nevoie de o secvenţă:
int fclose();
3. Scrierea în fişier
Funcţia de scriere în fişier: fprintf are prototipul:
int fprintf(FILE *pf, const char* sir_format, lista_arg);
Argumentul funcţiei:
pf – pointer spre FILE, identifică fişierul ]n care se efectuează scrierea
sir_format, lista_arg - 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.
Exemplu:
fprintf(pf, “Rezultatul este: %d”, rezultat);
ptr = fopen("c:\\test.txt","r");
75
Algoritmi şi structuri de date
printf("%c",c);
}
fclose(ptr);
}
Operaţiile de inserare sau ştergere a unei înregistrări din fişier sunt operaţii
complexe realizate în mai multe etape. Se folosesc funcţiile de bibliotecă standard
enumerate (deschidere fişier, creare fişier, scriere, citire, etc.) în implementarea
operaţiilor de inserare-ştergere, însă nu există funcţii standard corespunzătoare
celor două operaţii ca atare.
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.
76
Algoritmi şi structuri de date
Sfcâttimp
Închidere fişier
Copierea conţinutului unui fişier (fis1.dat) într-un alt fişier (fis2.dat). Etape:
Creează fișierul fis2.dat
Deschide fişierul fis1.dat
Câttimp (nu s-a ajuns la sfârşitul fişierului fis1.dat)
Citeşte din fişierul fis1.dat
Scrie în fişierul fis2.dat
Sfcâttimp
Închidere fişierele.
Probleme:
6. 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.dat.
7. Să se scrie un program C care citeşte din fişierul puncte.dat un
vector de structuri Punct3D şi afişează coordonatele punctelor pe
ecran.
8. Scrieţi un program care gestionează într-un fişier structuri de tipul
Persoana (CNP, nume, prenume, data nasterii, etc.). Operaţiile
evidenţiate sunt cele de adăugare înregistrare, ştergere înregistrare,
căutare persoană după valoarea câmpului CNP, ordonarea
alfabetică a persoanelor memorate în fişier.
77
Algoritmi şi structuri de date
Una dintre cele mai costisitoare resurse ale unui program este este memoria
utilizată. Programarea este o activitate în care, în afara corectitudinii programelor
elaborate, se urmăreşte şi eficienţa acestora, măsurată prin timpul de execuţie şi
memoria utilizată. În scopul economiei resurselor, limbajele de programare oferă
programatorului instrumentele necesare unei bune gestionări a memoriei.
Alocarea memoriei pentru variabilele locale declarate într-o funcţie se face în
etapa de execuţie a programului, pe stivă, şi nu este permanentă. La ieşirea din
funcţie, stiva se curăţă, fapt pentru care datele alocate sunt distruse. Acest
mecanism de alocare este eficient şi se denumeşte alocare dinamică. În schimb,
datele declarate global au un comportament diferit. Pentru variabilele globale,
memoria este alocată în faza premergătoare execuţiei şi zona de memorie
respectivă rămâne alocată acestora până la terminarea programului. Spre exemplu,
declararea globală a unui tablou unidimensional de lungime maximă 2000
(<TipElement> tablou[2000]; ), face ca o zonă de memorie considerabilă să fie
blocată pentru această structură, indiferent de numărul elementelor folosite efectiv,
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. 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.
Dezavantajele alocării dinamice a memoriei constau în:
1. sarcina suplimentară a programatorului de a asigura şi eliberarea
memoriei când nu mai are nevoie de datele respective
2. efectul fragmentării memoriei care poate produce imposibilitatea
alocării ulterioare a unei zone de memorie de dimensiune dorită.
Limbajul C/C++ oferă programatorului funcţii standard prin care se realizează
alocarea, respectiv, dealocarea (eliberarea) memoriei. Funcţiile malloc şi free au
prototipurile în fişierul antet: alloc.h şi gestionează o zonă de memorie specială,
denumită memoria heap .
Funcţia malloc:
void *malloc(size_t dimensiune)
Argumentul funcţiei este dimensiunea exprimată în octeţi, semnificând
dimensiunea zonei alocate. Funcţia returnează în caz de succes adresa de memorie
a zonei alocate. 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. În caz de insucces, funcţia malloc
returnează NULL.
Funcţia calloc:
void *calloc(size_t nrElemente, 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 caz de succes, funcţia returnează adresa
78
Algoritmi şi structuri de date
Exemplu:
tip *p;
p= (tip*)malloc(sizeof(tip)) ;
free(p);
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, vectori de date
simple, vectori de structuri, etc.
Programele următoare sunt exemple simple de gestionare eficientă a memoriei
prin alocarea dinamică a structurilor de date:
#include <stdio.h>
#include <stdlib.h>
79
Algoritmi şi structuri de date
{
printf("vector[%d]=", i+1);
scanf("%d", &vector[i]);// sau scanf("%d", vector+i);
}
return vector; //se returneaza adresa vectorului
}
void main(void)
{
int n;
int *vector;
vector=citire_vector(&n);
afisare_vector(vector, n);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(void)
{
char sir1[10];
char *sir2;
printf("introduceti un sir de caractere: \n");
scanf("%9s", sir1);
// se retin primele 10 caractere introduse
int dimCh=sizeof(char);
if ((sir2=(char *)malloc( (strlen(sir1)+1)* dimCh))==NULL)
{
printf("Insucces la alocarea dinamica");
exit(1);
}
strcpy(sir2, sir1);
printf("Sirul sursa: %s \n", sir1);
printf("Sirul destinatie: %s \n", sir2);
}
80
Algoritmi şi structuri de date
81
Algoritmi şi structuri de date
info
O structură care descrie compunerea unui element de acest gen este următoarea:
struct nod
adr
{
//declaraţii de date – câmpuri ale informaţiei
struct nod *adr; //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). Pentru a simplifica exemplele următoare, vom introduce un nou tip de
dată, tipul nodurilor, denumit TNod.
typedef struct nod
{
int cheie; //câmp cu valori unice pentru nodurile listei
//alte câmpuri
struct nod *urm; //adresa următorului nod
}Tnod;
Gestionarea unei liste de astfel de noduri necesită cunaşterea adresei primului şi
eventual al ultimului nod din listă. Reţinându-se doar adresa primului nod, celelalte
noduri pot fi parcurse, accesate, prin urmărirea legăturilor urm conţinute în
nodurile curente.
Adresa fiecărui nod este conţinută de nodul precedent, cu excepţia primului nod
al listei, pentru care nu există un nod precedent. Ultimul nod nu va referi niciun alt
nod următor, fapt care se marchează prin pointerul urm care devine Null. Figura
următoare sugerează organizarea unei liste simplu înlănţuite:
Parcurgerea listei
prim
Parcurgerea listei presupune accesarea sau prelucrarea elementelor listei în
ordinarea stabilită de legăturile conţinute de noduri. Cunoscând primul element
prim , acesta conţine adresa următorului element, care la rândul său conţine adresa
următorului, etc. În acest mod, prin urmărirea adreselor de legătură pot fi accesaţi
toţi membrii listei. Terminarea operaţiei de parcurgere a listei constă în accesarea
ultimului element, marcat prin adresă nulă a pointerului urm.
82
Algoritmi şi structuri de date
void tiparire_lista()
{
tnod *p; //p semnifică nodul curent
p=prim; //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 ",p->cheie);
// afişarea celorlalte câmpuri din nodul curent
p=p->urm; //trecere la următorul element
}
}
83
Algoritmi şi structuri de date
void adaugare_prim()
{
tnod *p;
p=incarca_nod();
if (prim= =0) //lista vida
{prim=p;
1
ultim=p;
}
ultim->urm=0;
return; prim
p->urm=prim;
prim=p;
}
84
Algoritmi şi structuri de date
void adaugare_ultim()
{ tnod *nou; nou=incarca_nod();
if (prim==0) //lista vida
{prim=nou;
ultim=nou;
ultim->urm=0;
return;
}
ultim->urm=nou;
ultim=nou; ultim->urm=0; ultim
}
85
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;
prev
printf("\ndati cheia de cautat:");scanf("%d",&valoare);
tnod *p,*prec, *nou; 2 curent
p=prim; //iniţializare nodul curent cu prim
while (p!=0)
{
if (p->numar!=valoare)
{//NU s-a gasit încă nodul
prec=p; //salvează adresa precedentului în prev
p=p->urm; //trece la următorul element
}
else
{ //s-a găsit nodul de cheie dată
nou
if (p==prim)
{//caz particular, nodul căutat este primul
adaugare_prim();return;
}
else
{
nou=incarca_nod();
nou->urm=p; //reface legăturile
prec->urm=nou;
return;
}
}
}
}//sfârşit funcţie
86
Algoritmi şi structuri de date
Considerând nodul de cheie dată găsit: p, etapele inserării noului nod sunt:
1. stabileşte adresa urm a nodului nou ca fiind adresa nodul următor al lui p:
nou->urm=p->urm (1)
2. stabile;te adresa urm a lui p ca fiind adresa lui nou: p->urm=nou. Prin
p 2 p->urm
această asignare s-a pierdut automat înlănţuirea veche între p şi p->urm
void adaugare_dupa()
{
int valoare; printf("\ndati cheia de
cautat:");scanf("%d",&valoare);
tnod *p,*nou;
p=prim;
while (p!=0)
{
if (p->numar!=valoare)
{//NU am gasit
p=p->urm;
else
}
nou
{ //caz particular
if (p==ultim)
{
adaugare_ultim();return;
}
else
{
//alocare memorie şi încărcare nod cu inf.
nou=incarca_nod();
nou->urm=p->urm; //stabilirea legăturilor
p->urm=nou;
return;
}
}
}
87
Algoritmi şi structuri de date
}
void stergere_prim()
{
tnod *primvechi;
primvechi=prim; //salvare adresa primului element
if (primvechi==0)
{//lista este vidă, nu se va şterge nimic
printf("\nlista este vida");
return;
}
prim=prim->urm; //actualizare prim
free(primvechi);//eliberarea memoriei adresata de prim
}
88
Algoritmi şi structuri de date
{
prec=p; //salvare precedent
p=p->urm; //trecere la următorul nod
}
//în acest punct prec este adresa penultimului nod
ultimvechi=p; //salvare vechiul nod ultim
ultim=prec; ultim->urm=0; //actualizare şi marcare ultim
free (ultimvechi); //eliberare memorie
}
void stergere_oarecare()
{
int valoare; printf("\ndati cheia de
cautat:");scanf("%d",&valoare);
tnod *p,*prev,*pvechi;
p=prim;
while(p!=0)
{
if (p->cheie==valoare)
prev
{//s-a găsit nodul şi acesta va fi şters
if (p==prim)
{
stergere_prim();
return;
} //caz particular
if (p==ultim)
{
stergere_ultim();
return;
} //caz particular
//cazul general
pvechi=p; //salvare adresă nod curent
prev->urm=p->urm; //refacere legături
89
Algoritmi şi structuri de date
free(pvechi); //eliberare memorie
return;
}
else
{//nu s-a găsit încă
prev=p; //salvarea adresei precedentului
p=p->urm; //trecere la următorul nod
}
}
}
Ştergerea completă a listei se poate realiza prin apelul repetat al funcţiilor deja
construit de ştergere a primului, respectiv, a ultimului nod până când lista devine
vidă (pointer-ul prim devine Null). Din punct de vedere al eficienţei, 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.
O variantă simplă dar greşită de ştergere a listei constă în distrugerea capetelor
listei prim şi ultim, fapt pentru care gestionarea ulterioară a listei ( accesarea şi
prelucrarea nodurilor sale ) devine imposibilă. Cu toate acestea, memoria rămâne
alocată nodurilor intermediare. Prin aceasta s-a distrus doar mecanismul de
accesare a nodurilor, nu s-au distrus efectiv nodurile listei.
Funcţia următoare este o variantă de ştergere a listei, prin ştergerea repetată a
nodurilor din capătul listei.
void stergere_lista()
{
tnod*p,*primvechi;
p=prim;
while(p!=0) //cât timp mai sunt noduri în listă
{
if (prim==0)
{
printf("\nlista e complet stearsa");
return;
}
else
{ //sterg primul nod şi actualizez prim
primvechi=prim;
prim=prim->urm;
free(primvechi);
}
}
}
90
Algoritmi şi structuri de date
info
Avantajul utilizării listelor dublu înlănţuite rezultă din posibilitatea parcurgerii
(traversării) listei în ambele sensuri: de la primul la ultimul, respectiv, de la ultimul
la primul nod. 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; //adresa următorului nod
struct nod *prec; //adresa nodului precedent
}
În exemplele următoare vom utiliza un tip de date utilizator prin care specificăm
tipul nodurilor listei:
91
Algoritmi şi structuri de date
…….
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. 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).
prim
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. Un caz particular al
procedurii de adăugare a noului nod este tratat distinct: în situaţia în care lista este
vidă, după adăugarea unui nod trebuie marcate nodurile prim şi ultim. Funcţia
incarca_nod este cea definită în capitolul dedicat listelor simplu înlănţuite.
void creare()
{
tnod *p;
int rasp;
//creare lista vida
prim=ultim=0;
printf("\nIntroduceti? (1/0)");scanf("%d",&rasp);
while (rasp==1)
{
p=incarca_nod(); //alocare memorie şi încărcare nod
if (prim==0)
{//creare primul nod
prim=p;ultim=p;
prim->pre=0; ultim->urm=0; //marcare capete listă
}
else
{
ultim->urm=p;
p->pre=ultim;
ultim=p;
ultim->urm=0;
}
printf("\nIntroduceti? (1/0)");scanf("%d",&rasp);
}
}
92
Algoritmi şi structuri de date
Observaţie: În cazul în care lista este înaintea adăugării unui nod nou, efectul
operaţiei constă în obţinerea unei liste cu un singur element, fapt pentru care
capetelor listei prim şi ultim sunt confundate şi este necesară tratarea acestui caz
particular.
void adaugare_prim()
{
prim
tnod *nou; p=incarca_nod();
93
3
Algoritmi şi structuri de date
if (prim==0)
{
prim=nou;ultim=nou;
prim->prec=0;ultim->urm=0;
}
else
{
nou->urm=prim; //pasul 1
prim->prec=nou; //pasul 2
prim=nou; //pasul 3
prim->prec=0; //pasul 4
}
}
void adaugare_ultim()
{ …………….
tnod *nou;nou=incarca_nod();
if (prim==0)
{
prim=nou;ultim=nou;
prim->prec=0;ultim->urm=0;
}
else
{
94
Algoritmi şi structuri de date
nou->prec=ultim; //(1)
ultim->urm=nou; //(2)
ultim=nou; //(3)
ultim->urm=0; //(4)
}
}
95 nou
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;
}
else
{
adaugare_prim();
return;
}
}
}
}//sfarsit functie
96
Algoritmi şi structuri de date
{
p=p->urm;
}
else
{
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;
}
}
}
}
97
Algoritmi şi structuri de date
free(primvechi); free(ultimvechi);
} }
else else
prim=ultim=0; prim=ultim=0;
} }
}//sfarsit functie }//sfarsit functie
Ş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. Ştergerea
capetelor listei nu asigură eliberarea memoriei ocupate de nodurile intermediare.
99
Algoritmi şi structuri de date
Lista circulară este o listă (simplu sau dublu) înlănţuită cu proprietatea că toate
nodurile sunt echivalente, respectiv, nu există noduri speciale care nu conţin adresa
nodurilor succesoare sau predecesoare. Aceste noduri speciale - denumite capetele
listei au fost utilizate în gestionarea listelor simplu şi dublu înlănţuite. O listă
circulară va fi gestionată prin alte mecanisme decât cele bazate pe menţinerea
adreselor speciale prim şi ultim.
Într-o listă circulară simplu înlănţuită toate nodurile conţin adresa următorului
nod. Structura nodului este similară celei prezentate la capitolul dedicat listelor
simplu înlănţuite:
typedef struct nod
{
int cheie; //câmp cu valori unice pentru nodurile listei
//alte câmpuri
struct nod *urm; //adresa următorului nod
}Tnod;
Organizarea unei liste circulare cu noduri de acest tip este sugerată de figura
alăturată.
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, ceea ce conduce la o structură de listă
circulară a cărei gestionare poate fi efectuată prin adresa pointer-ului prim, însă
fără ca acesta să semnifice adresa unui capăt al listei, ci doar adresa unui nod
oarecare.
Spre deosebire de listele simplu înlănţuite la care este suficientă cunoaşterea
adresei primului nod şi, eventual, pentru simplificarea prelucrărilor, şi a adresei
ultimului nod, într-o listă circulară, cunoaşterea adresei oricărui nod din
compunerea listei este suficientă pentru a putea gestiona această structură. Astfel,
gestionarea unei liste circulare se face prin unui pointer care referă oricare nod al
listei:
Tnod *pLC; //pointer la lista circulară
100
Algoritmi şi structuri de date
pLC
Funcţia următoare adaugă noi noduri la lista circulară gestionată prin pLC în
varianta 2.
void creare_LCSI()
{
Tnod *nou; int rasp;
pLC=0; //lista este initial vida
printf("\nIntroduceti? (1/0)");scanf("%d",&rasp);
while (rasp==1)
{
nou=incarca_nod();//alocare memorie şi încărcare nod
if (pLC==0)
101
Algoritmi şi structuri de date
{//creare primul nod
// nodul pLC contine adresa sa;
pLC=nou;
//lista devine circulara
pLC->urm=pLC;
}
else
{ //adaugare nou dupa pLC
nou->urm=pLC->urm;
pLC->urm=nou;
pLC=nou; //pLC va contine adresa noului nod
}
printf("\nIntroduceti? (1/0)");scanf("%d",&rasp);
}
}
p=pLC;
do
{
//prelucrare nod referit de p
p=p->urm; //trecere la urmatorul nod
}while (p!=pLC);
void Tiparire()
{
tnod *p; //pointer-ul auxiliar
p=pLC; //iniţializare p
if (p==0) //lista este vida
return; // nu are sens continuarea parcurgerii
do
{
printf(“\n %d”,p->cheie);
p=p->urm; //trecere la urmatorul nod
}while (p!=pLC);
}
102
Algoritmi şi structuri de date
1. Inserarea unui nou nod înaintea unui nod specificat prin valoarea cheii
presupune parcurgerea următoarelor etape:
- căutarea nodului de cheie dată
- 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, pentru a face posibilă refacerea legăturilor în
faza de inserare. În caz contrar ar fi necesară o reparcurgere a listei circulare pentru
a determina precedentul nodului înaintea căruia va fi inserat noul nod.
Cunoscând adresa nodului de cheie dată şi adresa precedentului său, inserarea
propriu-zisă a unui nou nod se reduce la: alocarea memoriei, î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:
103
Algoritmi şi structuri de date
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) //cautarea s-a incheiat cu Insucces
return;
if (p->cheie==valoare) //cautarea s-a incheiat cu Succes
{ //etapa de inserare
nou=incarca_nod(); //alocare memorie si incarcare nou
nou->urm=p;
prev->urm=nou;
}
}
Observaţii:
- nodul referit de pLC este ultimul nod verificat în etapa de căutare
- instrucţiunea decizională if (p->cheie==valoare) … este redundantă, dat
fiind faptul că o condiţie precedentă verificat situaţia opusă şi provoacă
revenirea din funcţie. 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.
104
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.
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
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ă.
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
Algoritmi şi structuri de date
Practic, stiva este o listă simplu înlănţuită pentru care operaţiile specifice se
limitează la următoarele:
- creare stivă vidă
- adăugare element (push)
- ştergere element (pop)
- şterge lista (clear)
- accesare – fără eliminare - a elementului din vârful stivei
de verificare:
- verifică dacă stiva este plină
info
În plus faţă de operaţiile enumerate anterior sunt posibile implementate operaţii
baza info
- baza stivei corespunde nodului prim şi vârful stivei corespunde
108
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, primul ieşit). Acest principiu arată că primul element
introdus în listă este şi primul care va fi şters. O structură de acest gen are două
capete, denumite sugestiv: cap şi coadă.
Operaţiile primare cu cozi sunt:
- creare stivă vidă
- adăugare element în coadă
- ştergere element din cap
- şterge lista (clear)
Spre deosebire de stivă, adăugarea şi ştergerea unui element se execută în
capetele diferite ale cozii.
Ca şi în cazul stivelor, organizarea unei cozi poate fi făcută în mod secvenţial
(static) – prin intermediul tablourilor unidimensionale sau dinamic – prin liste
simplu înlănţuite. Cea de-a doua variantă este de preferat din raţiuni economice.
prim
Coada este astfel o listă înlănţuită ale cărei capete referite prin prim şi ultim
semnifică capul şi coada structurii, ceea ce permite organizarea în două maniere:
- prim referă capul listei şi ultim referă coada listei
- ultim referă capul listei şi prim referă coada listei
Conform celor două abordări enumerate anterior, operaţiile de adăugare şi
scoatere elemente în/din lista FIFO se traduc prin:
- adăugare după nodul ultim şi ştergere nod prim
- adăugare înainte de prim şi ştergere nod ultim
Constatăm că spre deosebire de stive, ambele abordări sunt eficiente, astfel încât
alegerea oricărei variante este posibilă. Printr-o convenţie, adăugarea unui nod se
face după ultimul nod (coada) al listei, iar scoaterea din listă a unui nod este
implementată prin ştergerea nodului prim (cap).
109
Algoritmi şi structuri de date
XI. ARBORI
110
Algoritmi şi structuri de date
Dacă între fiii oricărui nod există o relaţie de ordine, spunem că arborele
respectiv este arbore ordonat.
7 111
8 9
Algoritmi şi structuri de date
Dacă numărul de fii ai fiecărui nod din compunerea unui arbore este 0,1 sau 2,
atunci arborele respectiv este numit arbore binar. Structura unui nod al arborelui
binare conţine:
- zona de informaţii
- legătură spre fiul stâng
- legătură spre fiul drept
Într-un arbore binar, există posibilitatea ca una sau ambele legături ale unui
părinte spre fiii săi să fie nule. Nodurile terminale au ambele legăturile nule.
Anumite noduri interne pot să aibă doar un fiu, astfel încât legătura spre celălalt fiu
este nulă.
Importanţa studierii arborilor binari este dată de multitudinea de aplicaţii
practice în care se face uz de această structură de date. În plus, un arbore poate fi
transformat şi reprezentat prin arbore binar. Transformarea presupune etapele
următoare:
1. se stabileşte legătură între fraţii de acelaşi părinte
2. se suprimă legăturile dinspre părinte, cu excepţia legăturii cu
primului fiu
Exemplu:
Fie arborele oarecare din figura următoare:
2
Figura 1 Arbore oarecare
5 6
112
Algoritmi şi structuri de date
5
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.
- Parcurgerea în lăţime (in breadth): se vizitează nodurile pe nivele,
pornindu-se de la nivelele cele mai de sus (nivelul 1 – rădăcina) spre
nivelele mai mari, şi pe fiecare nivel se vizitează nodurile într-o anumită
ordine, spre ex. de la stânga la dreapta. Parcurgerea în lăţime a arborelui
din figura 1 produce lista: 1 2 3 4 5 6 7.
ARBORI BINARI
113
Algoritmi şi structuri de date
Absenţa unui fiu (stâng sau drept) se marchează prin valoarea Null (0) a
pointer-ului corespunzător.
Gestionarea unui arbore binare este posibilă prin adresa nodului rădăcină:
Tnod *rad;
114
Algoritmi şi structuri de date
Exemplu:
115
Algoritmi şi structuri de date
printf(“\t%d”, p->cheie);
//parcurgere subarbore stâng
//autoapelare funcţie preordine
preordine(p->st);
//parcurgere subarbore stâng
//autoapelare funcţie preordine
preordine(p->dr);
}
}
116
Algoritmi şi structuri de date
Un caz particular de arbori binari sunt arborii binari de căutare. În plus faţă de
aspectele menţionate la prezentarea arborilor binari, 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:
- toate nodurile din stânga nodului respectiv au valorile cheilor
mai mici decât cheia nodului curent
- toate nodurile din dreapta nodului respectiv au valorile cheilor
mai mari decât cheia nodului curent
117
Algoritmi şi structuri de date
4
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.
În plus, operaţia de căutare a unui nod specificat de valoarea cheii este simplificată
(de aici şi denumirea arborilor de căutare).
Căutarea unui nod după o cheie dată nu necesită parcurgerea întregului arbore.
Datorită specificului acestor arbori, valoarea cheii este comparată cu cheia
conţinută în rădăcină şi în funcţie de rezultatul comparaţiei, căutarea se va continua
doar într-unul dintre cei doi subarbori, celălalt subarbore fiind exclus din căutare.
Procedeul se va continua în mod similar pentru subarborele curent: se compară
2
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 6
curent. 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ă.
Căutarea unei informaţii într-o structură de arbore binar de căutare este
eficientă, 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, în definirea unor
funcţii specifice care operează asupra arborilor de căutare se va ţine cont de
1 3
criteriul de ordine între cheile nodurilor ce formează arborele.
5
1. Creare arbore binar de căutare. Inserarea nodurilor.
Fiind dată o mulţime de informaţii ce trebuie organizate sub forma unui arbore
binar de căutare, 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.
Dacă rădăcina este vidă înainte de inserare, nodul de inserat va deveni rădăcina
arborelui:
rad=nou; rad->st=rad->dr=0;
118
Algoritmi şi structuri de date
119
Algoritmi şi structuri de date
120
Algoritmi şi structuri de date
c. nodul căutat nu este nod terminal, dar are doar un subarbore stâng
- leagă părintele de subarborele stâng al nodului de şters:
o dacă nodul este legat în stânga părintelui, atunci
parinte->st=p->st – cazul c.1
o dacă nodul este legat în dreapta parintelui, atunci
parinte->dr = p->st – cazul c.2
- eliberează nodul
d. nodul căutat p nu este nod terminal, dar are doar un subarbore drept
- leagă părintele de subarborele drept al nodului p:
o dacă nodul este legat în stânga părintelui, atunci
parinte->st=p->dr– cazul d.1
o dacă nodul este legat în dreapta parintelui, atunci
parinte->dr = p->dr – cazul d.2
- eliberează nodul
e. nodul căutat p nu este terminal şi are ambii subarbori (legăturile stânga şi
dreapta sunt nenule).
Cazul e este cel mai complex caz de ştergere a unui nod. Numim predecesor al
nodului p, cel mai din dreapta nod din subarborelui stâng al lui p. Numim succesor
al nodului p, cel mai din stânga nod din subarborelui drept al lui p. Atât
predecesorul cât şi succesorul unui nod oarecare, sunt noduri terminale în arborele
dat.
Exemplu:
Nodul Nodul
4 4
de sters 121 de sters
Algoritmi şi structuri de date
4 3
Nodul
Figura 3 Înainte de ştergerea nodului Figura 4 După ştergerea nodului
denodul
Observaţii: Dacă sters
p ce va fi şters este părintele direct al predecesorului
său, atunci predecesorul este în stânga părintelui său (în stânga nodului p).
Dacă nodul p ce va fi şters nu este părintele direct al predecesorului său, atunci,
predecesorul este legat de părintele său direct prin legătura dreaptă.
Exemplu: În figura anterioară, nodul 4 nu este părintele direct al predecesorului
2 2 6
(nodul 3), astfel încât, predecesorul este legat în dreapta părintelui său direct –
nodul 2.
Ne imaginăm următoarea situaţie:
1 31 5
Predecesor
În acest caz, pentru nodul de şters se va actualiza legătura stângă. După operaţia
de ştergere, arborele devine: 3
Nodul
de sters
122
Algoritmi şi structuri de date
2
Funcţia de mai jos este o variantă de implementare a algoritmului de ştergere a
unui nod dintr-un arbore de căutare. Se acordă atenţie nodului rădăcină, acesta fiind
un nod important prin care se gestionează arborele. Dacă acest nod trebuie şters,
tratarea cazului se face în mod diferit pentru protejarea adresei de acces la arbore.
123
Algoritmi şi structuri de date
if (p==rad) {rad=p->st;eliberare_nod(p);return;}
if (tatap->st==p) // cazul c.1
{
tatap->st=p->st;
eliberare_nod(p);
return;}
if (tatap->dr==p)// cazul c.2
{
tatap->dr=p->st;
eliberare_nod(p);
return;}
}//sfarsit caz c
//cazul d, p are doar subarbore drept
if (p->st==0)
{
if (p==rad) {rad=p->dr;eliberare_nod(p);return;}
if (tatap->st==p) // cazul d.1
{
tatap->st=p->dr;
eliberare_nod(p);
return;}
if (tatap->dr==p) // cazul d.1
{
tatap->dr=p->dr;
eliberare_nod(p);
return;}
}//sfarsit caz d
124
Algoritmi şi structuri de date
}//sfîrşit functie stergere
Un caz special de arbori binari ordonaţi (arbori de căutare) sunt arborii AVL
(descrişi de Adelson, Velski, Landis). Aceştia au în plus proprietatea de echilibru,
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.
Fiecărui nod într-un arbore AVL îi este asociată o mărime denumită factor de
echilibru. Factorul de echilibru se defineşte prin diferenţa dintre înălţimile
subarborelui drept şi înălţimea subarborelui stâng, şi pentru arborii echilibraţi poate
fi una dintre valorile -1, 0, +1. Dacă factorul de echilibru pentru un nod are altă
valoare decât cele enumerate, arborele nu este arbore AVL.
Operaţiile posibile asupra arborilor AVL sunt aceleaşi operaţii ca în cazul
arborilor binari ordonaţi simpli: creare, inserare, ştergere, parcurgere, căutare. În
schimb, î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.
Practica este următoarea: operaţiile definite pentru arborii de căutare se aplică şi
asupra arborilor AVL, însă, ulterior, este verificată îndeplinirea proprietăţii de
echilibrare. În cazul în care arborele a devenit dezechilibrat, prin operaţii
suplimentare se va reorganiza arborele pentru obţine un arbore echilibrat.
125
Algoritmi şi structuri de date
126
Algoritmi şi structuri de date
2
O muchie de la vârful x la vârful y este notata cu perechea ordonata (x, y), dacă
graful este orientat şi în mod uzual este folosit termenul de arc, si cu mulţimea
{x, y}, dacă graful este neorientat. În reprezentarea grafică, arcele (x,y) sunt
marcate prin săgeţi de la extremitatea iniţială x la cea finală y, iar muchiile prin
4
segmente.
Într-un graf orientat, existenţa unui arc de la vârful x la vârful y nu presupune şi
existenţa arcului de la y la x. În grafurile neorientate, dacă există muchie între x şi
y, atunci aceasta este şi muchie între vârfurile y şi x. Vârfurilor unui graf li se pot
ataşa informaţii numite uneori valori, iar muchiilor li se pot ataşa informaţii numite
costuri.
5
Următoarele noţiuni sunt specifice grafurilor:
Două vârfuri unite printr-o muchie se numesc adiacente.
Un drum este o succesiune de muchii de forma:
(x1, x2), (x2, x3), ..., (xn-1, xn) – în graf neorientat
3
sau de forma
{x1, x2}, {x2, x3}, ..., {xn-1, xn} – în graf neorientat
Un lanţ se defineşte ca o succesiune de vârfuri x1, x2, x3, … xn în care oricare
două vârfuri sunt adiacente.
Într-un drum simplu muchiile care îl compun sunt distincte.
Într-un drum elementar vârfurile care îl compun sunt distincte.
Lungimea drumului este egala cu numărul muchiilor care îl constituie.
Un lanţ elementar al grafului G care conţine toate vârfurile grafului se numeşte
lanţ hamiltonian. Determinarea unui lanţ hamiltonian al grafului este o problemă
foarte populară cunoscută ca Problema Comis Voiajorului rezolvată prin metoda
Greedy.
Un ciclu este un drum care este simplu şi care are drept capete un acelaşi vârf.
Un graf fără cicluri se numeşte graf aciclic.
Un subgraf al lui G este un graf G’=(X', Γ '), unde X' × X, iar Γ ' este formata
din muchiile din Γ care unesc vârfuri din X'.
127
Algoritmi şi structuri de date
Reprezentarea grafurilor
Parcurgerea grafurilor
128
Algoritmi şi structuri de date
Inserare(C,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, respectiv,
tab[i]=0 – vârful i este nevizitat
129
Algoritmi şi structuri de date
130
Algoritmi şi structuri de date
131
Algoritmi şi structuri de date
132
Algoritmi şi structuri de date
# P1 de dimensiunea n1,
# P2 de dimensiune n2,
...
# Pk de dimensiune nk
Cheamă DivideEtImpera(P1,n1,S1)
Cheamă DivideEtImpera(P2,n2,S2)
...
Cheamă DivideEtImpera(Pk,nk,Sk)
#combină soluţiile parţiale S1,S2,...,Sk şi obţine S_formal
SfDacă
SfSubalgoritm
Cheamă DivideEtImpera(P,n;S)
Analiza problemei:
Determinarea celui mai mare element dintr-un vector poate fi privită ca o
problemă de determinare a maximului dintre două valori intermediare,
reprezentând maximele celor două subşiruri obţinute prin împărţirea şirului iniţial
în două părţi egale:
Maxim
Maxim1 Maxim2
Maxim= maxim(Maxim1,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. Acest proces de împărţire se va încheia când un subşir de
elemente nu mai poate fi împărţit, respectiv când dimensiunea acestuia s-a redus la
1. În acest moment, subproblema devine elementară, deoarece maximul
elementelor unui vector format dintr-un singur element este însăşi elementul
respectiv.
133
Algoritmi şi structuri de date
134
Algoritmi şi structuri de date
else
return max2;
}
void main()
{
int a[NMAX],dim;
citire(a,&dim);
int max;
max=maxim(a,0,dim-1);
printf("Maximul este: %d",max);
}
Problema 2: Problema turnurilor din Hanoi. Se dau 3 tije simbolizate prin A,B,C.
Pe tija A se găsesc n discuri de diametre diferite, aşezate de jos în sus în ordine
crescătoare a diametrelor. Se cere sa se mute toate discurile de pe tija A pe tija B,
utilizând ca tija intermediara tija C, respectând următoarele reguli:
- la fiecare pas se muta un singur disc ;
- nu este permis sa se aşeze un disc cu diametrul mai mare peste un disc cu
diametrul mai mic.
A B C
Analiza problemei:
Cazul elementar al problemei constă în existenţa unui singur disc pe tija A,
ceea ce reduce rezolvarea la o mutare a discului de pe tija A pe tija B.
Pentru n=2 (două discuri), rezolvarea problemei constă în 3 mutări,
folosind tija intermediară C:
- mută discul mai mic de pe tija A pe tija C
- mută discul mai mare de pe tija A pe tija B
- mută discul mic de pe tija C pe tija B
135
Algoritmi şi structuri de date
Program:
#include <stdio.h>
void Hanoi (int n, int A, int B, int C)
{if (n>0)
{
Hanoi (n - 1, A, C, B);
printf("\nMuta discul de pe tija %c pe tija %c",A,C);
Hanoi( n-1, B, A, C);
}
}//sfarsit Hanoi
void main ()
{
int n;
printf("\nDati numarul de discuri:"); scanf("%d",&n);
Hanoi (n, ‘A’, ‘B’, ‘C’); //apel hanoi
}
136
Algoritmi şi structuri de date
137
Algoritmi şi structuri de date
După parcurgerea paşilor 1-6 se consideră subşirul xinf, …,xsup împărţit în două
subşiruri: xi, …,xsup şi xinf, …,xj. Dacă subşirurile obţinute conţin mai mult de 1
element, acestea se vor supune aceluiaşi procedeu descris.
138
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,xj)
SfDacă
Pânăcând (i>j)
Dacă (i<sup) atunci
Cheamă QuickSort(xi, …,xsup)
SfDacă
Dacă (j>inf)
Cheamă QuickSort(xinf, …,xj)
SfDacă
SfSubalgoritm
Program C:
#include <stdio.h>
void citireSir(int x[10],int *n)
{
int i;
printf("\ndati n=");
scanf("%d",n);
for(i=0;i<*n;i++)
{printf("\nX[%d]=",i+1);
scanf("%d",&x[i]);
}
}
void tiparireSir(int x[10],int n)
{
int i;
for(i=0;i<n;i++)
printf("%d ",x[i]);
}
void SQuick(int x[10],int st, int dr)
{
int i,j,k,M;
i=st; j=dr;M=x[(st+dr)/2];
do{
//avans la stanga
while ((i<dr) && (x[i]<M)) i++;
//avans spre dreapta
while ((j>st) && (x[j]>M)) j--;
if (i<j)
139
Algoritmi şi structuri de date
{//interschimb x[i] cu x[j]
k=x[i];
x[i]=x[j];
x[j]=k;
}
if (i<=j) {i++; j--;}
}while(i<=j);
if (i<dr) SQuick(x,i,dr); //daca subsirul 1 mai are cel putin
1 elem ..
if (j>st) SQuick(x,st,j); //daca subsirul 2 mai are cel putin
1 elem ..
}
void main()
{
int a[10],n;
citireSir(a,&n);
SQuick(a,0,n-1);
tiparireSir(a,n);
}
140
Algoritmi şi structuri de date
141
Algoritmi şi structuri de date
#include <stdio.h>
void citireSir(int x[10],int *n)
{int i;
printf("\ndati n=");scanf("%d",n);
for(i=1;i<=*n;i++)
{printf("\nX[%d]=",i+1);scanf("%d",&x[i]);}
}
void tiparireSir(int x[10],int n)
{int i;
for(i=1;i<=n;i++) printf("%d ",x[i]);
}
void MergeSort(int x[], int inf, int sup)
{int mij,k,i,j;
int y[20];
if(inf<sup)
{
mij=(inf+sup)/2;
mergesort(x,inf,mij);
mergesort(x,mij+1,sup);
//merge(x,inf,sup,mij);
i=inf;
j=mij+1;
k=inf;
while((i<=mij)&&(j<=sup))
{
if(x[i]<x[j])
{ y[k]=x[i];
k++;
i++;}
else
{ y[k]=x[j];
k++;
j++;}
}
while(i<=mij)
{ y[k]=x[i]; k++; i++; }
while(j<=sup)
{ y[k]=x[j]; k++; j++; }
for(i=inf;i<k;i++)
x[i]=y[i];
}}
void main(void)
{int a[10],n;
citireSir( a,&n);
MergeSort(a,1,n);
tiparireSir(a,n);
}
142
Algoritmi şi structuri de date
143
Algoritmi şi structuri de date
SfAlgoritm
Analiza problemei:
Din problema formulată mai sus putem deduce mulţimea de intrare A ca fiind
mulţimea monedelor de valori 1,k1,k2,...,kn-1, considerând că numărul de monede de
aceiaşi valoare este nelimitat. Soluţia problemei este reprezentată dintr-o
submulţime B de monede, nu neapărat de valori diferite, astfel încât dimensiunea
submulţimii este minimă şi suma valorilor monedelor conţinute de B nu depăşeşte
suma dată S.
Rezolvarea logică a problemei constă în parcurgerea paşilor:
- iniţializează mulţimea B cu mulţimea vidă
- se alege cea mai mare unitate monetară kj care este mai mică valoric decât
suma S
- Cât timp suma S rămâne pozitivă adaugă moneda kj la mulţimea B şi scade
valoarea monedei din suma S
- alege monede de valoare imediat mai mică decât kj, respectiv, kj-1
- 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
- alege monede de valoare imediat mai mică decât kj-1, respectiv, kj-2
- ...
- Procesul se continuă până când nu mai există valori monetare mai mici
care ar putea fi adăugate mulţimii B
144
Algoritmi şi structuri de date
valorile monetare sunt mai mari, cu atât numărul monedelor prin care se exprima S
este mai mic).
145
Algoritmi şi structuri de date
146
Algoritmi şi structuri de date
s4 t4
s3 t3
s2 t2
s1 t1
Contraexemplu 2.
s4 t4
s3 t3
s2 t2
s1 t1
148
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, respectiv, eliminare ultim element adăugat. Aceste
operaţii corespund unor operaţii cunoscute: push şi pop; astfel, vectorul soluţie
funcţionează pe principiul unei stive.
O problemă se identifică ca o problemă rezolvabilă prin metoda Backtracking
dacă putem identifica următoarele aspecte din specificaţiile sale:
1. spaţiul soluţiilor este un produs cartezian S = A1 × A2 ×.. × An
2. soluţia probleme poate fi reprezentată ca un vector
x = { x1 , x 2 ,..., x n } ∈ S
3. există un set de condiţii prin care putem decide dacă o soluţie parţială
dată de vectorul { x1 , x 2 ,..., x k } , k ≤ n este validă → condiţiile de
validitate
4. există un set de condiţii prin care putem decide dacă o soluţie parţială
este finală → condiţiile de finalitate
5. soluţia (vectorul) se poate construi pas cu pas, astfel încât dacă
{ x1 , x 2 ,..., x k } este valid, are sens completarea vectorului cu un
element pe poziţia k+1.
Considerăm soluţia parţială { x1 , x 2 ,..., x k } reprezentată ca stivă în imaginile
alăturate:
Cazul 1
… … … …
Nivelul k+1 xk+1 Nivelul k+1 xk+1
Nivelul k xk Nivelul k xk
Nivelul k-1 xk-1 Nivelul k-1 xk-1
… … … …
Nivelul 2 x2 Nivelul 2 x2
Nivelul 1 x1 Nivelul 1 x1
Cazl 2.1
… … … …
Nivelul k+1 xk+1 Nivelul k+1 xk+1
Nivelul k xk Nivelul k x k*
Nivelul k-1 xk-1 Nivelul k-1 xk-1
… … … …
Nivelul 2 x2 Nivelul 2 x2
Nivelul 1 x1 Nivelul 1 x1
149
Algoritmi şi structuri de date
Cazul 2.2
… … … …
Nivelul k+1 xk+1 Nivelul k+1 xk+1
Nivelul k xk Nivelul k xk
Nivelul k-1 xk-1 Nivelul k-1 xk-1
… … … …
Nivelul 2 x2 Nivelul 2 x2
Nivelul 1 x1 Nivelul 1 x1
Procesul este unul repetitiv, pentru fiecare nivel curent al stivei se va proceda
similar. Descrierea algoritmului se poate face în două variante: iterativ şi recursiv,
ş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 , x 2 ,..., x k } .
Varianta Iterativă:
Varianta recursivă:
151
Algoritmi şi structuri de date
152
Algoritmi şi structuri de date
SfDacă
SfFuncţie
#include <stdio.h>
#include <math.h>
int final(int k)
{
if (k==n)
return 1;
else
return 0;
}
void tipareste()
{
int i;
printf("\n");
for(i=1;i<=n;i++)
printf("%d ",stiva[i]);
}
void main()
{
nrsolutii=0;
int i;
int gasit;
printf("dati n:"); scanf("%d",&n);
int k=1; //incepem de la primul nivel;
while(k>=1)
{
gasit=0;
for(i=stiva[k]+1;(i<=n) ;i++)
{
stiva[k]=i;
153
Algoritmi şi structuri de date
if (valid(k)==1) {gasit=1;break;}
}
if (gasit==0) //nu s-au mai gasit
{stiva[k]=0;
k=k-1;
}
else
if (final(k)==1)
{tipareste();
nrsolutii=nrsolutii+1;}
else
k=k+1;
}
printf("\n Numarul de solutii=%d",nrsolutii);
}
#include <stdio.h>
#include <math.h>
int stiva[100]; //solutia se construieste in stiva
int nrsolutii;
int n;
int valid(int k)
{
int i;
int cod=1;
for(i=1;i<=k-1;i++)
if ((stiva[k]==stiva[i])
|| (abs(k-i)==abs(stiva[k]-stiva[i]) ) )
cod=0;
return cod;
}
int final(int k)
{
if (k==n)
return 1;
else
return 0;
}
void tipareste()
{
int i;
printf("\n");
for(i=1;i<=n;i++)
printf("%d ",stiva[i]);
}
154
Algoritmi şi structuri de date
void dame(int k)
{
for(int i=1;i<=n;i++)
{
stiva[k]=i;
if (valid(k)==1)
if (final(k)==1)
tipareste();
else
dame(k+1);
}
}
void main()
{
printf("\n dati n:");scanf("%d",&n);
dame(1);
}
Analiza problemei:
Fie L – matricea de reprezentare a labirintului:
a( 1,1 ) a( 2,1 ) a( n,1 )
a( 2,1) a( 2,2 )
L= , a( i, j) ∈{0,1}
a( m,1 ) a( m,2 ) a( m, n )
O soluţie finală a problemei poate fi reprezentată printr-un şir al mişcărilor
efectuate în labirint, însă configuraţia stivei în care sunt salvate camerele traversate
este uşor modificată faţă de problema damelor. Astfel, 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. 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.
Acest gen de probleme pentru care fiecare nivel al stivei reţine mai multe
elemente se rezolvă cu un algoritm Backtracking generalizat. Principul aplicat este
acelaşi ca şi la Backtracking-ul simplu: soluţia/soluţiile se construiesc treptat.
Soluţiile finale ale problemei labirintului sunt vectori de perechi (i,j) cu
semnificaţia poziţiei camerelor prin care a trecut persoana pentru a ieşi din labirint.
Lungimile acestor vectori soluţie sunt diferite. Anumite drumuri parcurse pot fi mai
155
Algoritmi şi structuri de date
scurte decât altele. Condiţia ca un vector de perechi (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ă, sau, pe prima sau ultima linie a matricei
de reprezentare.
Condiţiile de validitate se deduc din specificaţiile problemei. Drumul persoanei
poate continua într-o nouă poziţie (i,j) dacă valoarea elementului de pe linia i şi
coloana j din matricea L este 0 (drum liber). Mai mult, pentru a evita parcurgerea
aceloraşi camere de mai multe ori, se impune restricţia ca poziţiile prin care trece
persoana să fie distincte. Astfel, persoana poate trece într-o cameră vecină dacă: nu
este zid (cameră liberă) şi nu a mai fost traversată.
De asemenea, 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,
dreapta, sus şi jos
În continuare este descris algoritmul backtracking de rezolvare a problemei
labirintului (varianta recursivă), punând în evidenţă condiţiile de validitate şi
finalitate prin subalgoritmi corespunzători:
Funcţie Valid(k)
// k-nivelul stivei
Fie (i,j) –poziţia memorată în Stiva[k]
Dacă (a(i,j)=0) atunci
//cameră liberă
Valid=Adevărat
Pentru l de la 1 la k-1
Dacă (i, j ) = Stiva [l ] atunci
//camera a mai fost traversată în drumul memorat în stivă
Valid=Fals
SfDacă
SfPentru
Altfel
//camera ocupată - zid
Valid=Fals
SfDacă
SfFuncţie
156
Algoritmi şi structuri de date
#include <stdio.h>
int n, stiva[200];
void TiparesteSolutia() {
printf("\n");
for (int i = 1; i <= n; i++)
printf("%d",stiva[i]);
}
int valid(int k) {
for (int i = 1; i < k; i++)
if(stiva[i] == stiva[k])
return 0;
return 1;
}
void permutari(int k) {
for (int i = 1; i <= n; i++)
{
stiva[k] = i;
if (valid(k))
if (k == n)
TiparesteSolutia();
else
permutari(k+1);
}
}
void main() {
printf("dati n");scanf("%d",&n);
permutari(1);
}
157
Algoritmi şi structuri de date
Varianta 2: Dacă şirul de decizii D1, D2 , …, Dn-1, Dn duce sistemul din starea
iniţială S0 în starea finală Sn în mod optim, atunci:
158
Algoritmi şi structuri de date
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ă.
159
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ă. Rezolvarea aceasta este corectă, însă neeficientă prin necesitatea de a
construi şi reţine toate subşirurile lui şirului dat.
O soluţie ingenioasă are fi să se calculeze şi să se reţină într-o tabelă doar
lungimile tuturor subşirurilor crescătoare, fără a le genera şi pe acestea.
În exemplul anterior, subşirurile crescătoare şi lungimile acestora sunt:
Subşiru Lungimea
l
7 1
123 3
45 2
23 2
5 1
3 1
Se poate observa că dacă subşirul 3 are lungime 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).
Astfel: Dacă subşirul 1 2 3 este subşirul optimal crescător de lungime maximă
(3), care începe cu 1, 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
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. Relaţia de recurenţă este
următoarea:
160
Algoritmi şi structuri de date
Exemplu:
Pentru următorul caz:
161
Algoritmi şi structuri de date
2 0 7 1
4 0 3 0
0 5 1 0
2 4 1 2
Din observaţiile anterioare, 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, j , S i , j −1 ) + aij , ∀i, i ∈{1,..., m} şi ∀j , j ∈{1,..., n}
162
Algoritmi şi structuri de date
163
Algoritmi şi structuri de date
Tipăreşte Vector(i)
SfPentru
SfAlgoritm
Probleme:
164
Algoritmi şi structuri de date
BIBLIOGRAFIE:
165