Sunteți pe pagina 1din 31

1. Structuri de date.

Clasificarea și caracteristica generală


2. Structura de date, tipul de date și tipul de date abstract
3. Etape de implementare a tipului de date abstract în limbajul C
4. Implementarea tipului de date abstract „Tablou de structuri” în limbajul C
5. Liste înlanțuite. Clasificarea și caracteristica generală
6. Lista simplu înlanțuită. Implementarea tipului de date abstract „Lista simplu înlanțuită” în limbajul C
7. Lista dublu înlanțuită. Implementarea tipului de date abstract „Lista dublu înlanțuită” în limbajul C
8. Coada. Implementarea tipului de date abstract "Coada" în limbajul C
9. Stiva. Implementarea tipului de date abstract "Stiva" în limbajul C
10. Arbori. Clasificarea și caracteristica generală
11. Diferite forme de reprezentare a unui arbore în memoria calculatorului
12. Arbore binar. Implementarea tipului de date abstract "Arbore binar" în limbajul C
13. Arbore binar. Parcurgeri pe niveluri și parcurgeri recursive
14. Arbore binar de căutare.Implementarea tipului de date abstract"Arbore binar de căutare" în limbajul C
15. Crearea arborelui binar de căutare. Algoritm pentru inserarea unui nod
16. Arbore binar de căutare. Algoritmi pentru căutarea unui nod și pentru determinarea nodului max/min
17. Arbore binar de căutare. Algoritm pentru ștergerea unui nod
18. Algoritmi de căutare secvențială și binară pentru tabloul unidimensional
19. Algoritmi de sortare. Clasificarea şi caracteristica generală
20. Algoritmi de sortare prin selecţie și prin selecție și interscimbări
21. Algoritmi de sortare ”bubble” și prin inserție
22. Algoritmi de sortare ”shaker” și Shell
23. Algoritm de sortare prin interclasare (mergesort)
24. Algoritm de sortare rapidă (quicksort)
25. Arbore binar heap şi coada de prioritate
26. Algoritmi de sortare utilizînd coada de prioritate și arbore binar heap
27. Funcţii standard qsort ( ) şi bsearch ( ) în limbajul C
28. Tehnici de programare. Clasificarea şi caracteristica generală
29. Tehnica de programare „Forța brută ” și tehnica de programare „ Divide et empera”. Exemple
30. Tehnica de programare Backtracking și programarea dinamică. Exemple

1
1. Structuri de date. Clasificarea și caracteristica generală
Structura de date este un set de date care pot fi considerate un lucru sau o entitate noua. Structuri de date (interne din memoria
calc) divizate in 2.
1) Structuri liniare – au proprietatea ca fiecare element component poate avea nu mai mult decit 2 vecini:
a) tablouri (unidimensionale sau siruri de caractere)
b) structuri inregistrate, record
c) liste inlantuite liniare (nu sunt obligatoriu sa fie 2 vecini)
 Liste simple liniare, unidirectionale – cind fiecare element legat numai cu urmatorul, are si precedent si urmator, dar poate
fi procedat numai intr-o directie, incepind cu 1 element si pina la sfirsit;
 Stiva (stack) – se permite de a sterge de la sfirsit numai. LIFO (LAST INPUT FIRST OUTPUT);
 Coada (quene) – 2 capete, de la o parte adaugam, din alta parte extragem. (FIFO)
 Liste dublu inlantuite – fiecare element are 2 legaturi cu urmatorul si precedentul, ce permite parcurgerea listei in ambele
directii.
2) Structuri neliniare(data are mai mult de 2 vecini):
a) Structuri lantuite neliniare sau multiinlantuite;
b) Grafuri – arbori;
 Binari – fiecare nod(parinte) este legat 2 feciori(copii);
a) arbori binari de cautare;
b) Heap – independenta care valori sunt stocate in noduri;
 De grad n>2 – are mai mult de 2 copiii ... 3 4
Dupa acces se divizeaza:
 Structuri cu acces direct;
 Structuri cu acces secvential.
Structuri omogene – elementele de acelasi tip, aceeasi structura, tablou (omogena), structuri (omogena sau neomogena), Liste
(omogene sau neomogene - complicat), arbore (omogen sau neomogen).
Structuri recursive – cind structura, orice parte a structurii de date are aceeasi structura, lista inlantuita, structura arbore binar:
structuri nerecursive.
2. Structura de date, tipul de date și tipul de date abstract
Limbajul C ofera programatorului posibilitatea de a grupa datele, date de tipuri diferite putind fi prelucrate atit individual cit si
impreuna. Daca tabloul contine numai elemente de acelasi tip, o structura este formata din mai multe elemente de tipuri diferite. La
rindul lor, structurile definite pot canstitui elemente componente pentru formarea de noi tipuri de date (tablouri, structuri, uniuni).
Elementele componente ale unei structuri se numesc membri sau cimpuri.
Sintaxa declararii unei structuri este urmatoarea:
Struct [nume_tip_structura]
{
tip_1 lista_cimpuri_tip_1;
tip_2 lista_cimpuri_tip_2;
tip_n lista_cimpuri_tip_n;
}[lista_variabile_structura];
In declaratie poate lipsi precizarea numelui pentru tipul structurii sau a listei de variabile, insa nu ambele simultan. In cazul in care
lipseste numele tipului, spunem ca variabilele au un tip anonim. Cimpurile componente ale unei structuri pot fi oricare din tipurile:
 Predefinite (intreg, caracter, real);
 Definite de utilizator: scalar (cimpuri de biti, enumerare) sau compuse (tablouri, structuri, uniuni).
Exemplul 1:
Definirea datelor de baza pentru o persoana fiind specificate numele si virsta:
Struct persoana
{
char nume[35];
int virsta; sau int an;
};
S-a definit o structura de tip persoana fara a fiind folosita nici o variabila de tipul definit. Topurile de date este une set de date si
operatiuni asupra lor. Tipul de date abstract este un element matematic. Tipuri de date se divizeaza in 2:
1) predefinite in limbaj;
2) create de programator;
a) create de programator utilizind specificator predefinit care este structura, enumerare;
b) definite de programator utilizind conceptia tipului de date abstract.
================================================================================================
3. Etape de implementare a tipului de date abstract în limbajul C
Exemplu: numar complex:
Implementarea tipului abstract de date, numarul complex:
Etapele:
1) de scris fisierul cu extensia ’h’, unde:
a) se determina proprietatile datele obiectului sau descrierea proprietatilor obiectului prin declararea datelor;
b) se declara functiile pentru prelucrarea datelor sau descrierea operatiunilor asupra obiectelor utilizind prototipurile functiilor;
2) de scris fisierul cu extensia ’c’ sau ’cpp’ cu codurile functiilor din punctul 1.
Primul fisier se numeste interfata tipului de date abstract, dar al II-lea se numeste implementarea functiilor tipurilor de date
abstract.

2
// fiserul antet complex.h
Typedef struct complex
{
float re; float im; }complex;
//tipurile functiilor care asigura operatiuni
complex add(cimplex c1, complex c2);
complex scadere(cimplex c1, complex c2);
complex add(cimplex c1, complex c2);
complex multime(cimplex c1, complex c2);
complex divizare(cimplex c1, complex c2);
float real(complex c); float imaginar(complex c);
complex conjugare(complex c);
float module(complex c);
void readcomplex(complex *c); // read schimba continutul
void writecomplex(complex c); // write nu schimba continutul

//fisierul 2: functiile (complex.cpp)


#include <stdio.h>
#include <math.h>
#include <conio.h>
#include ”complex.h”
complex add(complex c1, complex c2) { Complex r; re.r = c1.re+c2.re; im.r = c1.im+c2.im; }return r;
================================================================================================
4. Implementarea tipului de date abstract „Tablou de structuri” în limbajul C
Definirea structurii:
struct agentii_tursm
{
char denumire[40];
char raion[40];
int oferta;
char telefon[10];
float costbilet;
};
void afisarea_datelor(agentii_tursm a[], int n, int j)
{
int i;
printf(”\nAfisarea elementelor introduse:\n”);
for(i=j;i<n;i++)
{
printf(”\n\nDenumire agentie:%s”, a[i].denumire);
printf(”\nLocatia:%s”, a[i].raion);
printf(”\nNr. de oferte:%d”, a[i].oferta);
printf(”\nNr. de telefon:%s”, a[i].telefon);
printf(”\nCost bilet:%2f$”, a[i].costbilet);
}
}
// functia de sortare a datelor dupa denumirea agentiilor
void sortare_denumire(agentii_turism a[], int n)
{
int i,k;
agentii_turism t;
for(i=0;i<n;i++)
{
for(k=0;k<n-1;k++)
{
if(strcmp(a[k].denumire,a[k+1].denumire)>0)
{
t=a[k];
a[k]=a[k+1];
a[k+1]=t;
}
}
}
}
================================================================================================

3
5. Liste înlanțuite. Clasificarea și caracteristica generală
Avem urmatoarele clasificari:
1) Simplu inlantuite;
2) Lista circulara simplu inlantuita;
3) Liste dublu inlantuite.
1) O lista simplu inlantuita are nodul format din doua componente principale: o parte statica, ce contine informatia utila si o parte
dinamica, ce contine adresa nodului urmator din lista. Ultimul nod al listei are in partea dinamica valoarea NULL, deoarece dupa
acesta nu mai este inlantuit nici un alt nod.
2) Lista circulara simplu inlantuita este aproape similara cu lista liniara simplu inlantuita.Diferentele sunt: nodul tail (ultim) este
legat cu nodul head (prim), in acest caz lista nu mai are inceput sau sfirsit.
3) Listele dublu inlantuite sunt structuri de date dinamice omogene. Ele au aceleasi caracteristici de baza ca si listele simplu
inlantuite. Diferenta fata de acestea consta in faptul ca, pentru fiecare nod, se retine si adresa elementului anterior, ceea ce permite
traversarea listei in ambele directii.
Structura unui nod este următoarea:
typedef struct tip_nod {
int cheie; /* câmp neobligatoriu */
alte câmpuri de date utile;
struct tip_nod urm; /* legătura spre următorul nod */
} TIP_NOD;
Organigrama fiind modelul grafic al listei simplu înlănţuite este prezentat în fig. 2.1.
Fig. 2.1. Model de listă simplu înlănţuită

================================================================================================
6. Lista simplu înlanțuită. Implementarea tipului de date abstract „Lista simplu înlanțuită” în limbajul C
Lista simplu inlantuita este o structura de date care consta din elementele legate intre ele cu ajutorul adreselor elementelor in
memorie. Fiecare element al listei simple inlantuite are o singura adresa in memorie, unde se afla elementul urmator al listei.
Primul element reprezinta capul listei. Daca ultimul element este egal cu primul, lista e circulara. Pentru lista necirculara in ultimul
element trebuie de pus NULL.
#include<conio.h>
#include<alloc.h>
#define NEW (NOD*)malloc(sizeof(NOD));
struct lista { int info; lista *next; };
typedef struct lista NOD;
NOD* creare() { NOD *prim,*ultim,*q; int nr;
printf(" primul nr="); scanf("%i",&nr); // se citeşte primul număr
prim=(NOD*)malloc(sizeof(NOD)); prim->info=nr; // se creează primul nod
prim->next=NULL; ultim=prim; // se creează ultimul nod
printf("\n urmatorul nr sau tastati CTRL+Z pentru iesire) = "); scanf("%i",&nr); // se citeşte al doilea număr
while(!feof(stdin)) // atâta timp cât nu introducem CTRL+Z
{ q=(NOD*)malloc(sizeof(NOD)); q->info=nr; // se creează nodul următor
q->next=NULL; ultim->next=q; ultim=q; // se creează ultimul nod
printf("\n urmatorul nr sau tastati CTRL+Z pentru iesire)= "); scanf("%i",&nr); // se citeşte numărul următor
} return prim; }
void afis(NOD* prim) { printf("\n"); printf("\n Lista este");
while(prim!=NULL) // atâta timp cât p este diferit de NULL
{ printf("\t \n%i",prim->info); // se afişează nodul curent
prim=prim->next; // se trece la următorul nod
} }
void stergere (NOD* prim) { NOD *p;
while(prim) // atâta timp cât prim este diferit de NULL
{ p=prim; // p devine primul nod
prim=prim->next; // prim devine următorul nod
free(p); // se eliberează nodul curent
} }
void main() { NOD *prim; int a,b,c,d,e,f,g; clrscr();
// Creare
prim=creare(); afis(prim);
// Eliberare memorie
stergere(prim); getch();
}
================================================================================================
4
7. Lista dublu înlanțuită. Implementarea tipului de date abstract „Lista dublu înlanțuită” în limbajul C
O componenta a unei liste dublu inlantuite se declara ca o data structurala de tip inregistrare, formata din trei cimpuri:
informatia propriu-zisa (care poate fi orice tip: numric, caracter, pointer, tablou, inregistrare) si informatiile de legatura (adresa la care e
momorata urmatoarea componenta si adresa la care e memorata precedenta componenta). Ultima componenta va avea informatia de
legatura corespunzatoare urmatoarei adresa NULL (sau 0), cu semnificatia ca dupa ea nu mai urmeaza nimic (retine adresa ”nici o
adresa” a urmatoarei componente). La fel si in cazul primei componente pentru cimpul adresa precedenta.
Lista dublu lănţuită este o listă, în care fiecare element al listei conţine doi pointeri: unul la precedentul element, altul – la
succesorul element din listă. Lista dublu lănţuită în program se poate determina cu ajutorul următoarelor descrieri:
typedef struct ndd
{ float val; /* valoarea informaţională a componentei */
struct ndd * succesor; /* pointer la succesorul element al listei n*/
struct ndd *precedent; /* pointer la precedentul element al listei m*/
} NDD;
NDD * prim, * p, * r;
Interpretarea grafică a listei F=< 2,5,7,1 > ca listă dublu lănţuită este următoarea:

#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
typedef struct lista //definim tipul de date LISTA care este o lista dublu lantuita
{ char x; //fiecare element contine cate un element de tip char
struct lista *next,*anti; // si legaturile la urmatorul si la precedentul elemnte
}LISTA; //tipul de date care este creat acuma
LISTA *r,*p; // doi pointeri care ne vor ajuta la crearea si parcurgerea listei
int nr; LISTA *prim; // pointer la primul element al listei
void add(char y) { if(!prim) //daca nu exista primul element
{ prim=new LISTA; //atunci el se creaza
if(!prim)printf("\a\n Memorie insuficienta !!!"); // daca nu s-a putut crea atunci mesaj de eroare
prim->x=y; //se initializeaza campul x din Lista cu valoarea variabilei y transmisa ca parametru
prim->next=NULL; // nu exista urmatrul elemnt
prim->anti=NULL; // nu exista elemntul anterior
} else { r=new LISTA; // daca exista elemente in lista atunci se creaza altul, r
if(!r)printf("\a\n Memorie insuficienta !!!"); // daca nu s-a alocat memorie atunci va fi afisat un mesaj de erare
r->x=y; // se initializeaza campul x din pointerul r, creat recent
p=prim; // de la inceputul listei
while(p->next) p=p->next; // parcurgem pana la ultimul
r->next=NULL; // de la elementul creat acuma, r nu exista elemente dupa el
r->anti=p; // se face legatura dintre elementul r cu ultimul element
p->next=r; // se realizeaza legatura inversa, dintre ultimul element cu r, cel care va deveni ultimul
} }
void print_invers() // functie care afiseaza lista in forma inversa
{ p=prim; // de la primul element
printf("\n\nLista contine:\n"); while(p) // cat timp nu s-a ajuns la ultimul element
{r=p; // se duce la
p=p->next; // ultimul element
} while(r!=NULL) // si parcurgand in sens invers, spre primul element
{ if(r) printf("%c",r->x); // si afiseaza caracterele in forma inversa
r=r->anti; // se deplaseaza spre inceputul listei
} getch(); // se asteapta introducerea unul caracter de la tastatura
}
void main() { int i; char c; // caracterul citit de la tatstatura si clrscr(); // se curata ecranul
clrscr(); printf("\nIntroduceti sirul de caractere terminandu-se cu \'$\' :\n");
do{ c=getche(); // se preia un caracter de la tastatura si se afiseaza, si apoi se atribuie lui c
if(c=='$') break; // daca c este egal cu $ atunci se iese din ciclu
else add(c); // daca nu se iese din ciclu atunci se adauga in lista
}while(c!='$'); // se citeste cate un caracter pana cand c este diferit de $
print(); // se afiseaza lista in ordinea introdusa
print_invers(); // se afiseaza in ordinea inversa
getch(); // se asteapta introducerea unul caracter de la tastatura
}
================================================================================================

5
8. Coada. Implementarea tipului de date abstract "Coada" în limbajul C
Aceasta structura de date este un caz particular de lista care functioneaza pe principal FIFO (first in – first out, primul
intrat – este primul servit, iesit). Prin urmare principalele prelucrari care se refera la aceasta structura de date vor fi:
- crearea cozii;
- listare coada(parcurgere);
- adaugare la sfirsit;
- stergere prim element;
- prelucrare prim element.
Specific acestei structure de date este faptul ca adaugarea se va face intotdeauna la sfirsit, in timp ce prelucrarea (stergerea) se
va face la celalalt capat. Pentru a prelucra o coada vor fi necesari doi pointeri: unul il vom numi virful cozii (primul nod creat),
in timp ce la capatul opus ne vom referi la ultimul element.
Functia pune() – creaza coada cind aceasta este vida sau adauga un nou element la sfirsit in caz contrar.
Functia scoate() – elimina elemental din virful cozii.
Cozi Coada este o listă simplu înlănţuită, bazată pe algoritmul FIFO (First In First Out), adică primul element introdus este primul
scos. Modelul cozii care va fi avut în vedere în consideraţiile
următoare, este prezentat în fig.1.7.1.
Fig.1.7.1. Model de coadă

Se introduce

Deci coada are două capete, pe la unul se introduce un element, un element


iar de la celalalt capăt se scoate un element.
Operaţiile importante sunt:
nou prin inserarea după ultimul nod,
- introducerea unui element în coadă - funcţia se realizează
- scoaterea unui element din coadă – funcţia se realizează prin ştergerea primului nod;
- ştergerea cozii.
Coada este o listă liniară, în care elementele listei se elimină din capul listei, şi elementele noi se includ prin coada listei.
Coadă de tip vagon este o listă liniară, în care includerea şi eliminarea elementelor din listă se efectuează din ambele capete (vîrful
şi sfîrşitul) ale listei.
Stiva şi coada se organizează atît static prin intermediul tabloului, cît şi dinamic – prin listă (simplu sau dublu lănţuită).
Vom cerceta cum se utilizează lista în formă de stivă pentru implementarea calculării expresiei aritmetice în formă inversă
poloneză. În astfel de mod de prezentare a expresiei, operaţiile se înregistrează în ordinea executării lor, iar operanzii se află nemijlocit
în faţa operaţiei. De exemplu, expresia (6+8)*5-6/2
în forma inversă poloneză are forma: 6 8 + 5 * 6 2 / -
Utilizînd noţiunea de stivă, expresia aritmetică în formă inversă poloneză se execută print-o singură trecere de examinare a
expresiei. Fiecare număr se introduce în stivă, iar operaţia se execută asupra următoarelor două elemente din vîrful stivei, înlocuindu-le
cu rezultatul operaţiei efectuate. Dinamica schimbărilor din stivă va fi următoarea:
S = < >; <6>; <6,8>; <14>; <14,5>; <70>; <70,6>; <70,6,2>; <70,3>; <67>.
#include<iostream.h>
#include<conio.h>
struct nod { int info; nod *next; };
nod *p,*u ; // acceseaza primul respective ultimul nod
int n; //numarul de noduri
void cre_ad() //functia de creare si adaugare a unui nou element
{ nod *c; if(!p) //daca lista este vida (p==0) se aloca primul nod
{p=new nod; cout<<"valoare primului nod "; cin>>p->info;
u=p; //la creare primul si ultimul nod vor fi identici
} else //altfel se adauga un nou element la sfarsit
{ c=new nod; //se aloca un nou nod
cout<<"informatia utila :";cin>>c->info; //se completeaza campul informatie utila
u->next=c; //se adauga dupa ultimul nod
u=c; //se stabileste noul nod c ca fiind ultimul
} u->next=0; //campul adresa urmatoare a ultimului nod este 0
}
void afis() //functia de afisare parcurge elementele cu afisare
{nod *c; c=p; //se porneste de la primul nod din lista
while(c) //cat timp c retine o adresa nenula
{cout<<c->info<<" ";//se afiseza campul informatie utila
c=c->next;} //se avanseaza la urmatoarea adresa, la urmatorul nod
cout<<endl; }
main(){ clrscr(); int b; cout<<"n="; cin>>n;
for(int i=1;i<=n;i++) cre_ad( );
cout<<endl; afis(); getch(); }
============================================================================

6
9. Stiva. Implementarea tipului de date abstract "Stiva" în limbajul C
STIVA este o structura de date cu proprietatea ca ultimul element inserat – este primul element extras. Inserarile si extragerile
se fac doar prin virful stivei.
Operatii premise sunt:
 adaugarea unui element in stiva; (PUSH)
 eliminarea sau extragerea unui element din stiva; (POP)
Definirea tipului abstract stiva. În funcţie de metoda de acces la elementele listei liniare pot fi cercetate următoarele tipuri de liste
liniare: stive, cozi şi cozi de tip vagon. Stiva este o consecutivitate de elemente de acelaşi tip – variabile scalare, tablouri, structuri sau
uniuni. Stiva reprezintă o structură dinamică, numărul de elemente a căreia variază. Dacă stiva n-are elemente, ea este vidă. Stiva este
un tip special de lista în care toate insertiile si suprimarile de noduri au loc la un singur capat. Acest capat se numeste vârful stivei.
Tipul abstract stiva pe care îl definim contine urmatorii operatori:
Initializarea stivei.
Verificarea faptului ca stiva e plina.
Verificarea faptului ca stiva e goala.
Introducerea unui element în vârful stivei.
Eliminarea elementului din vârful stivei.
Furnizarea elementului din vârful stivei fara a-l elimina.
Stiva este o listă simplu înlănţuită bazată pe algoritmul LIFO (Last In First Out), adică ultimul nod introdus este primul scos.
Modelul stivei, care va fi avut în vedere în continuare, este prezentat în fig.1.6.1.

Fig. 1.6.1. Model de stivă

Fiind o structură particulară a unei liste simplu înlănţuite, operaţiile principale asupra unei stive sunt:
- push - pune un element pe stivă; funcţia se realizează conform paragrafului 2.3.a., adică prin inserarea unui nod înaintea primului;
- pop - scoate elementul din vârful stivei; funcţia se realizează conform paragrafului 2.4.a., adică prin ştergerea primului
nod;
- clear - ştergerea stivei; funcţia se realizează conform paragrafului 2.5.
În concluzie, accesul la o stivă se face numai pe la un capăt al său.
//prelucrari stiva : (LIFO)
#include<iostream.h>
#include<conio.h>
struct nod{int info;
nod *back;};
nod *varf;
void push(nod* &v,int x)
{nod *c;
if(!v)
{v=new nod;
v->info=x;
v->back=0;}
else
{c=new nod;
c->back=v;
c->info=x;
v=c;}
}
void afisare(nod *v)
{nod *c;
c=v;
while(c) {cout<<c->info<<" "; c=c->back;}
}
void main()
{int n,a;
cout<<"numarul initial de noduri ";
cin>>n;
for(int i=1;i<=n;i++)
{cout<<"valoarea de adaugat in stiva ";
cin>>a; push(varf,a);
}
cout<<endl; afisare(varf);
}
=============================================================================

7
10. Arbori. Clasificarea și caracteristica generală
Un arbore binar e o multime de n>=0 noduri, care daca nu este vida, contine un nod numit radacina, restul nodurilor formind
doi arbori disjunctivi numiti subarbore sting si subarbore drept.
Arbori binari
Arbori cu mai multe noduri caracteristici nr de noduri, inaltimea, adincimea si modul de parcurgere.
Structura arborescentă este o structură de date de tip arbore datele fiind înmagazinate în noduri, nodul de baza e radacina. Un
arbore reprezinta un "graf" particular, în care substructurile legate la orice nod sunt disjuncte şi în care exista un nod numit radacina
din care e accesibil orice nod prin traversarea unui numar finit de arce. În teoria grafurilor, un arbore este un graf neorientat, conex şi
fără cicluri. Arborii reprezintă grafurile cele mai simple ca structură din clasa grafurilor conexe, ei fiind cel mai frecvent utilizaţi în
practică. Daca nodul A conţine o referinţă la un nod B, atunci A e ascendentul lui B sau B e descendentul lui A. Considerând
nodul radacina situat la nivelul 0 descendenţii nodului radacina vor fi situaţi pe nivelul 1, iar descendenţii acestora pe nivelul 2
ş.a.m.d. Numărul de arce transversat de la nodul radacină până la un nod K defineşte lungimea căii acelui nod. Ca structură de date
recursivă un arbore se defineşte recursiv astfel: el poate fi vid sau poate consta dintr-un nod conţinând referinţe la arborele disadjuncti.
ARBORI BINARI. Un arbore binar e un arbore orientat, în care fiecare vârf are maximum 2 descendenţi. Un arbore binar în
care orice vârf are 1 sau 2 descendenţi se numeşte arbore binar complet.
Arbore binar echilibrat e acel pentru fiecare nod a căruia numărul de noduri din subarborele stâng diferă de cel din subarborele
drept cu cel mult 1. Ca exemplu poate servi arborele genealogic.
Un mod de reprezentare a arborilor e acel a expresiilor cu paranteze incluse, ce se construieste astfel: expresia începe cu
radacina arborelui, iar fiecare vârf, ce are descendenti e urmat de expresiile atasate subarborilor, ce au ca radacini descendentii
vârfurilor, despartite între ele prin virgula şi cuprinse în paranteze.
A(B(C,D(H)),K(L(N),M(E)))
.A
/\
B./ \
/\.C \.K
./ \ /\
/\ / \.M
H./ L./ \
/ \.E
N./
Exista 3 metode principale de parcurgere a unui arbore şi anume:
1) preordine ;
2) inordine ;
3) postordine.
Transversarea în preordine e acea, în care mai întii se prelucreaza informatia din nod şi apoi se parcurge prin arbore.
Transversarea în inordine e acea, în care secventa actiunilor e urmatoarea: parcurgerea subarborelui stâng, vizitarea nodului
radacina şi apoi parcurgerea subarborelui drept.
Transversarea în postordine cu parcurgerea subarborelui stâng, subarborelui drept şi apoi vizitînd radacina.
Structura arborescenta tree - structura arborescenta de date utilizata frecvent în sistemele, ce implica memorarea unei mari
cantitati de date sortate pe baza unor chei compuse în vederea unor operatii de cautare.
11. Diferite forme de reprezentare a unui arbore în memoria calculatorului
Graful, asemenea arborelui, este o structura în care relatia dintre nodul parinte si nodul fiu este una ierarhica, dar care este
mai putin restrictiva în sensul ca un nod are mai multi succesori, dar si mai multi predecesori. El este definit ca o colectie de date
reunite în doua multimi: multimea N = { N1, N2, …, Nn | n – numarul de noduri al grafului}ce contine toate nodurile grafului si
multimea A = { ( Ni, Nj ) = Aij | Ni, Nj N si i,j = 1,n cu i j}care contine arcele dintre doua noduri vecine.
Reprezentarea in memoria calculatorului a grafurilor presupune memorarea numarului de noduri si a legaturilor dintre acestea. In
practica cele mai folosite moduri de reprezentare presupun utilizarea variabilelor de tip tablou sau a structurilor de date dinamice.
a) Reprezentarea grafurilor folosind variabile de tip tablou:
1. Reprezentarea tata-fii:
Se utilizeaza un vector V care pentru oricare nod i ce apartine grafului pastreaza toate nodurile j cu care acesta este adiacent
(daca graful este neorientat atunci trebuie respectata conditia j > i, deoarece se repeta muchii ). Prin conventie se separa
nodurile tata si adiacentii lor cu valoarea 0.
,0, Nodul i, fiu1i, fiu 2i, fiu 3i,, 0,
c). Matricea de adiacenta:
Fie G un graf cu n noduri. Pentru memorarea acestuia in forma numerica se poate folosi o matrice A (n x n) elemente unde

A(i, j)=
Exemplu pentru graful din figura 1.3.a) matricea de adiacenta este:
1 2 3 4 5
1 0 1 1 1 1
2 0 0 1 0 1
A: 3 0 0 0 1 0
4 0 0 1 0 0
5 1 0 1 1 0
In cazul grafurilor neorientate matricea de adiacenta este simetrica si deci se va folosi efectiv, doar jumatate din spatiul
matricei. De aceea in unele situatii este recomandat ca in cealalta jumatate sa fie memorate alte informatii.
Matricea de adiacenta se va folosi in general pentru grafuri cu numar mic de noduri (aproximativ 250 de noduri).
8
c).Matricea de incidenta a muchiilor:
Fie un graf cu n noduri si muchii. Pentru memorarea lui se foloseste un tablou bidimensional cu (n x m) elemente unde orice
element din matrice poate lua urmatoarele valori:

a(i , j)=
d) Matricea costurilor:
In practica exista situatii cand se cunoaste costul legaturii a doua noduri din graf(exemplu: costul cablului telefonic necesar
conectarii a doua posturi telefonice, costul asfaltarii unei portiuni de sosea dintre doua puncte, etc.) si in aceste situatii graful se
reprezinta in memoria calculatorului folosind matricea costurilor. Matricea costurilor se noteaza cu C, este o matrice de dimensiune n *
n, unde n reprezinta numarul de noduri, iar fiecare element al ei poate lua urmatoarele valori:

C(i, j)=
e) Matricea muchiilor:
Atunci cand sunt cunoscute costurile muchiilor pentru memorarea unui graf se mai poate utiliza si un alt mod de reprezentare
interna care foloseste o matrice cu trei linii si un numar de coloane egal cu numarul de muchii (numarul maxim de muchii pentru un
graf neorientat este unde n reprezinta numarul de noduri). O linie din aceasta matrice are forma:
Linia 1: i
Linia 2: j
Linia 3: costul muchiei (i , j)
unde:
i, j sunt doua noduri adiacente din graf.
Exemplu: Pentru graful ponderat reprezentat in fig. 1.6 matricea muchiilor este:

M=
================================================================================================
12. Arbore binar. Implementarea tipului de date abstract "Arbore binar" în limbajul C
Arborii binari sunt o structura de date menita sa ocupe un spatiu de memorie cit mai mic posibil. Cele mai multe reprezentari
concise si complexe ale arborilor binary pot indica nu numai spatial ocupar de acestia ci si operatiile directe ce se pot aplica acestora
atita timp cit acestia sunt pastrati intr-o forma concisa.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <conio.h>
struct node // declararea unei structuri
{
char data[15]; // cimp de tip char
int num; // cimp de tip int
struct node *left, *right;
// declarare 2 pointeri sting si drept
};
int repet=1; // variabila de tip int
void insert(struct node *r, struct node *p)
// adaugare unui nod in arbore
{
if((r->right==NULL)&&(strcmp(p->data, r->data)>0))
r->right=p;
if((r->right!=NULL)&&(strcmp(p->data, r->data)>0))
insert(r->right, p);
if((r->left==NULL)&&(strcmp(p->data, r->data)< 0))
r->left=p;
if((r->left!=NULL)&&(strcmp(p->data, r->data)< 0))
insert(r->left, p);
p->num=repet;
if(strcmp(p->data, r->data)==0)
r->num++;
}

void tree(struct node *r, int c)


// functia de eliberare a memoriei
9
{
if(r!=NULL) // daca r este diferit de NULL
{
if(c!=0) // daca c diferit de 0
{
if(c == 1) // daca c egal cu 1
{
printf("%s",r->data); // afiseaza informatia
printf("_%d\n",r->num); // informatia
}
tree(r->left,c); // eliberare memorie
if(c == 2) // daca c egal cu 2
{
printf("%s",r->data); //informatia
printf("_%d\n",r->num); // informatia
}
tree(r->right,c);
if(c == 3)
{
printf("%s",r->data);
printf("_%d\n",r->num);
}
}
}
}
char *tab=(char*) malloc(sizeof(char)*15);
// alocare memorie
int main()
{
int optiune, c;
struct node *s, *root, *r, *q, *p;
// variabile pointer de tip structura
root = NULL;
// creare meniu
do
{
//clrscr();
printf("\n1 - Introducere");
printf("\n2 - Afisare");
printf("\n0 - Parasire");
printf("\nOptiune:");
scanf("%d",&optiune);
switch(optiune)
{
case 1:
s=(node*)malloc(sizeof(struct node));
s->left=NULL;
s->right=NULL;
printf("\nIntroduceti:");
scanf("%s",&s->data);
if(root==NULL) root=s;
else
insert(root,s);
break;
case 2:
do
{
printf("\n1 - Preordine\n2 - Inordine\n3 - Posordine\n0 - Inapoi");
printf("\nOptiune:");
scanf("%d",&c);
if(root!=NULL)
tree(root,c); getch();
}
while(c!=0);
break;
} }
while(optiune!=0);
}
===============================================================================
10
13. Arbore binar. Parcurgeri pe niveluri și parcurgeri recursive
Operatii curente:
 selectia câmpului de date dintr-un nod si selectia descendentilor;
 inserarea unui nod;
 stergera unui nod.
Traversarea arborilor binari (A.B.) Traversarea consta în "vizitarea" tuturor nodurilor unui arbore într-un scop anume, de
exemplu, listare, testarea unei conditii pentru fiecare nod, sau alta prelucrare. O traversare realizeaza o ordonare a nodurilor arborelui
(un nod se prelucreaza o singura data).
Strategii de traversare:
 traversare în preordine: prelucrare în ordinea: rad, SAS, SAD;
 traversare în inordine: prelucrare în ordinea: SAS, rad, SAD;
 traversare în postordine: prelucrare în ordinea: SAS, SAD, rad.
rad
/ \
SAS SAD
Exemplu de traversare: a
/ \
b c
/ \ \
d e f
/ \
g h
 preordine : A B D F G H C F
 inordine : D B G E H A C F
 postordine : D G H E B F C A
Functii de parcurgere (in pseudocod)
Facem notatiile:
p pointer la un nod
lchild(p)  pointer la succesorul stâng (p  stg)
rchild(p)  pointer la succesorul drept (p  drt)
data(p)  informatia memorata în nodul respectiv (p  data)
În C/C++ avem:
struct Nod{
Atom data;
Nod* stg;
Nod* dr;
};
Nod* p;
Procedurile de parcurgere sunt:
preorder(t)
{
if(t==0) return
else |¯ print (data(t)) // vizitarea uni nod
| preorder (lchild(t)) // parcurgerea subarborilor
|_ preorder (rchild(t))
}
inorder(t)
{
if(t  0) |¯ inorder (lchild(t))
| print (data(t))
|_ inorder (rchild(t))
}
postorder(t)
{
if(t  0) |¯ postorder (lchild(t))
| postorder (rchild(t))
|_ print(data(t))
}
Parcurgerea pe nivle, in largime:
1) stinga dreapta
2) dreapta stinga
Parcurgerea recursive:
1) StingRadacinaDrept – parcurgerea in inordine in directive SRD
2) SDR – postordine in directive SD
3) RSD – preordine in DS adincime
4) RDS – preordine in DS adincime
5) DRS – inordine DS
6) DSR – postordine in DS.

11
14. Arbore binar de căutare. Implementarea tipului de date abstract"Arbore binar de căutare" în limb. C
Arborele binar de cautare (BST). Arborele binar de cautare reprezinta o solutie eficienta de implementare a structurii
de date numite "dictionar". Vom considera o multime "atomi". Pentru fiecare element din aceasta multime avem:  a  atomi, este
definita o functie numita cheie de cautare: key(a)  k cu proprietatea ca doi atomi distincti au chei diferite de cautare: a1  a2 
key(a1)  key(a2).
Exemplu: (abreviere, definitie) ("BST","Binary Search Tree")
("LIFO","Last In First Out") key(a) = a.abreviere
Un dictionar este o colectie S de atomi pentru care se definesc operatiile:
 insert(S,a)  insereaza atomul a în S daca nu exista deja;
 delete(S,k)  sterge atomul cu cheia k din S daca exista;
 search(S,k)  cauta atomul cu cheia k în S si-l returneaza sau determina daca nu este.
================================================================================================
15. Crearea arborelui binar de căutare. Algoritm pentru inserarea unui nod
Structura unui nod al unui arbore binar de căutare este următoarea:
typedef struct tip_nod { tip cheie;
informaţii_utile;
struct tip_nod *stg, *dr;
} TIP_NOD;
În cele ce urmează, rădăcina arborelui se consideră ca o variabilă globală:
TIP_NOD *rad;
Inserarea într-un arbore binar de căutare.
Construcţia unui arbore binar de căutare se face prin inserarea a câte unui nod de cheie key. Algoritmul de inserare este următorul:
a) Dacă arborele este vid, se creează un nou nod care este rădăcina, cheia având valoarea key, iar subarborii stâng şi drept fiind
vizi.
b) Dacă cheia rădăcinii este egală cu key atunci inserarea nu se poate face întrucât există deja un nod cu această cheie.
c) Dacă cheia key este mai mică decât cheia rădăcinii, se reia algoritmul pentru subarborele stâng (pasul a).
d) Dacă cheia key este mai mare decât cheia rădăcinii, se reia algoritmul pentru subarborele drept (pasul a).
Funcţia nerecursivă de inserare va avea următorul algoritm:
void inserare_nerecursivă (int key)
{TIP_NOD *p, *q;
int n;
/* construcţie nod p*/
n=sizeof (TIP_NOD);
p=(TIP_NOD*)malloc(n);
p->cheie=key;
/* introducere informaţie utilă în nodul p */
p->stg=0; p->dr=0; /* este nod funză */
if (rad==0) { /* arborele este vid */
rad=p;
return;
}
/* arborele nefiind vid se caută nodul tată pentru nodul p */
q=rad; /* rad este rădăcina arborelui variabilă globală */
for ( ; ; ) { if (key<q->cheie)
{ /* căutarea se face în subarborele stâng */
if (q->stg==0) { /* inserare */
q->stg=p;
return;
}
else q=q->stg;
}
else if (key>q->cheie)
{ /* căutarea se face în subarborele drept */
if (q->dr==0) { /* inserare */
q->dr=p;
return;
}
else q=q->dr;
}
else { /* cheie dublă */
/* scriere mesaj */
free (p);
return;
} } }
================================================================================================

12
16. Arbore binar de căutare. Algoritmi pentru căutarea unui nod și pentru determinarea nodului max/min
Căutarea unui nod de cheie dată key într-un arbore binar de căutare.
Căutarea într-un arbore binar de căutare a unui nod de cheie dată se face după un algoritm asemănător cu cel de inserare.
Numărul de căutări optim ar fi dacă arborele de căutare ar fi total echilibrat (numărul de comparaţii maxim ar fi log 2 n – unde n
este numărul total de noduri).
Cazul cel mai defavorabil în ceea ce priveşte căutarea este atunci când inserarea se face pentru nodurile având cheile ordonate
crescător sau descrescător. În acest caz, arborele degenerează într-o listă.
Algoritmul de căutare este redat prin funcţia următoare:
TIP_NOD *cautare (TIP_NOD *rad, int key)
/* funcţia returnează adresa nodului în caz de găsire sau 0 în caz că nu există un nod de cheia key */
{ TIP_NOD *p;
if (rad==0) return 0; /* arborele este vid */
/* dacă arborele nu este vid, căutarea începe din rădăcina rad */
p=rad;
while (p!=0)
{ if (p->cheie==key) return p; /* s-a găsit */
else if (key<p->cheie) p=p->stg; /*căutarea se face în subarb.stâng */
else p=p->dr; /* căutarea se face în subarborele drept */
}
return 0; /* nu există nod de cheie key */
}
Apelul de căutare este:
p=cautare (rad, key);
rad fiind pointerul spre rădăcina arborelui.
Valoarea maximă dintr-un arbore binar de căutare se află în nodul din extremitatea dreaptă şi se determină prin coborârea pe
subarborele drept, iar valoarea minimă se află în nodul din extremitatea stângă.

Minimul şi maximul
Determinarea elementului având cheia minimă dintr-un arbore binar de căutare se realizează întotdeauna
urmând pointerii fiu stânga începând cu rădăcina şi terminând când se întâlneşte NULL.
Procedura următoare întoarce un pointer la elementul minim din subarborele a cărui rădăcină este nodul x:
ARBORE-MINIM (x) ARBORE-MAXIM (x)
1. cât timp stânga[x] ≠ NULL execută 1. cât timp dreapta[x] ≠ NULL execută
2. x <- stânga[x] 2. x <- dreapta[x]
3. returnează x 3. returnează x
Pseudocodul pentru procedura ARBORE-MAXIM este simetric.
================================================================================================
17. Arbore binar de căutare. Algoritm pentru ștergerea unui nod
În cazul ştergerii unui nod, arborele trebuie să-şi păstreze structura de arbore de căutare.
La ştergerea unui nod de cheie dată intervin următoarele cazuri:
a) Nodul de şters este un nod frunză. În acest caz, în nodul tată, adresa nodului
fiu de şters (stâng sau drept) devine zero.
b) Nodul de şters este un nod cu un singur descendent. În acest caz, în nodul tată,
adresa nodului fiu de şters se înlocuieşte cu adresa descendentului nodului fiu de şters.
c) Nodul de şters este un nod cu doi descendenţi. În acest caz, nodul de şters se înlocuieşte cu nodul cel mai din stânga al
subarborelui drept sau cu nodul cel mai din dreapta al subarborelui stâng.
Algoritmul de ştergere a unui nod conţine următoarele etape:
 căutarea nodului de cheie key şi a nodului tată corespunzător;
 determinarea cazului în care se situează nodul de şters.
Exemplu de stergere a unui cod:
void sterg(nod *&c,int k)
{nod *aux,*f;
if(c)
if(c->nr_o==k)
if(c->st==0&&c->dr==0) //daca e nod terminal
{delete c;
c=0;
}
else
if(c->st==0) //are numai subordonat drept
{aux=c->dr;
delete c;
c=aux;}
else
if(c->dr==0) //are numai subordonat drept
{aux=c->st;
13
delete c;
c=aux;}
else
cmd(c,c->st); //are ambii subordonati

else
if(c->nr_o<k)
sterg(c->dr,k);
else
sterg(c->st,k);

else
cout<<"valoarea de sters nu se gaseste in arbore ";
}
================================================================================================
18. Algoritmi de căutare secvențială și binară pentru tabloul unidimensional
Enuntarea problemei de cautare
Se cauta o valoare (citita de exemplu de la tastatura), intr-un tablou dimensional (citit anterior).
Cautarea secventiala
Presupune parcurgerea secventiala a unui tablou unidimansional si compararea fiecarui element din tablou cu elemental cautat pina cind
acesta este gasit sau ajunge la sfirsitul tabloului.
Cautarea binara
Presupune cautarea unei valori intr-un tablou, al carui elemente sunt ordonate. Valoarea cautata este comparata cu cea a elementului din
mijlocul listei. Daca e egala cu cea a acelui element, algoritmul se termina. Daca e mai mare decit acea valoare, algoritmul se reia, de la
mijlocul listei pina la sfirsit, iar daca e mai mica, algoritmulse reia pentru elementele de la inceput pina la mijloc.
Algoritm:
 Consideram un tablou v cu n elemente deja sortat, si trei variabile: i=inceput, s=sfirsit, m=mijloc. Metoda verifica de mai
multe ori daca mijlocul vectorului este egal cu elemental cautat:
 in cazul in care este egala, variabila m reprezinta pozitia elementului in vector.
 Daca nu se indeplineste conditia de egalitate se trece la verificarea pozitiei elementului cautat in vector astfel: daca
elemental cautat este mai mic decit elemental din mijlocul vectorului, variabila s ia valoarea lui m , iar daca nu variabila i ia
valoarea lui m.
 Totul se repeat cit timp i este mai mic decit s.
Exemplu:
m s
i 0 1 2 3 4 5 6 7 8 9
v 3 5 7 8 10 13 19 29 30 55
i=0; s=9; m=[(i+j)/2]=4 13> v[4] =10 deci se cauta in a doua jumatate a tabloului v
i 5 6 7 8 9
v 13 19 29 30 55
i=5; s=9; m=[(i+j)/2]=7 13<v[7]=29 deci se cauta in prima jumatate a tabloului v
i 5 6
v 13 19
i=5; s=6; m=[(i+j)/2]=5 13=v[5]

Program cautare secventiala:


#include <stdio.h>
int v[100], i, n, x=0; // v- tabloul cu maxim 100 elemente
// n- nr de elemente
main(){
printf(“n=”); scanf(“%d”,&n);
for(i=0;i<n;i+) scanf(“%d”,&v[i]); // citirea elementelor tabloului
printf(“n=”); scanf(“%d”,&x) ; // x – elemental cautat
i=0;
do{
if(v[i] == x) // se repeat cit timp elemental x nu este gasit
// sau pina la sfirsitul tabloului
printf(“Elementul %d a fost gasit pe pozitia:”,x,i+1);
}
while(v[i] != x || i<n);
if(i==n) printf(“Elementul %d n-a fost gasit!!!”,x);
}
Program cautare binara:
#include <stdio.h>
int v[100], i, n, s=0; // v- tabloul cu maxim 100 elemente
// n- nr de elemente
main(){
14
printf(“n=”); scanf(“%d”,&n);
for(i=0;i<n;i+) scanf(“%d”,&v[i]); // citirea elementelor tabloului
printf(“n=”); scanf(“%d”,&x) ; // x – elemental cautat
i=0; s=n-1;
do{
m=(i+s)/2;
if(v[m] == x)
printf(“Valoarea %d a fost gasit pe pozitia:”,x,m);
else
{
if(x<v[m])
s=m;
else
i=m;
}
}
while(i<s || x==v[m]);
if(i<s) printf(“Valoarea %d n-a fost gasit!!!”,x);
}
================================================================================================
19. Algoritmi de sortare. Clasificarea şi caracteristica generală
Sortarea este procesul prin care elementele setului sunt rearanjate astfel incit cheile lor sa se afle intr-o anumita ordine.
A. Notiuni teoretice
1.Cosideratii generale
Ce dificila ar fi consultarea unui dictionar ,daca nu ar avea cuvintele aranjate in ordine alfabetica.Analog,ordinea articolelor din
memoria calculatorului are implicatii majore asupra vitezei si simplitatii algoritmilor care le prelucreaza.
Nu vom insista asupra enormelor aplicatii ale sortarii dar mentionam ca frabricantii de calculatoare estimeaza ca peste 25% din timpul
de rulare al calculatoarelor este ocupat de sortare in conditiile in care toate solicitarile sunt luate in considerare.
2.Terminologie
Chiar daca dictionarele definesc sortarea ca pe un proces de separare si aranjare al lucrarilor dupa clase si fel, uzual programatorii
folosesc cuvantul sortare in sens de aranjare a lucrurilor intr-o ordine ascendenta sau descendenta.
Se poate utiliza si termenul de ordonare.
3.Definitie
Problema sortarii consta in rearanjarea unei colectii aflate in memoria interna astfel incat cheile articolelor sa fie ordonate
crescator ( eventual descrescator).
4.Clasificarea algoritmilor(strategii generale)
A. Algoritmi de sortare prin comparatii
Sortare prin interschimbare
Sortare prin insertie
Sortarea prin insertie directa si sortare prin insertie binara
Sortare prin metoda Shell
3.Sortare prin selectie
Selectie naiva
Selectie sistematica
B. Algoritmi de sortare prin distribuire
Sortarea cuvintelor
Distribuire prin segmentare
C.Algoritmi de sortare prin numarare
D.Sortare topologica
E.Algoritmi de sortare utilizand metoda “Divide et impera”
Sortarea prin interclasare
Sortarea rapida
A. Algoritmi de sortare prin comparatii
Au la baza determinarii permutarii, comparatii la fiecare moment intre doua elemente a[i] si a[j] din tabloul ce se sorteaza.Dupa scopul
compararii avem algoritmi definiti astfel:
Prin rearanjarea valorilor a[i] si a[j] in ordine(interschimbare) sortarea prin metoda bulelor(BubbleSort)
Prin inserarea uneia din valori intr-o subsecventa deja ordonata(insertie).
insertie directa
sortarea SHELL ( pentru vectori de mari dimensiuni)
Prin selectia unei valori ce se repartizeaza pe pozitia finala a ei (selectie)
selectia naiva mai rapida decat a bulelor,dar mai putin eficienta comparativ cu:
selectia sistematica ce presupune parcurgerea a doua etape:
- constructia proprietatii Heap(a) pentru o secventa data
-selectarea elementului maximal in mod repetat din secventa curenta ( refacand apoi Heap pentru secventa ramasa)
Ansamblul (Heap) reprezinta o modalitate frecventa de organizare a datelor utilizata foarte mult de calculul paralel.
B.Algoritmi de sortare prin distribuire

15
Algoritmii de sortare prin distribuire presupunand ca se cunosc informatii privind distributia acestor elemente (informatii suplimentare
despre elementele). Utilizam aceste informatii pentru a distribui elementele secventei de sortat in “pachete” ce se vor sorta in acelasi
mod prin alta metoda, iar apoi le combinam obtinand lista finala sortata.
Algoritmi de sortare a cuvintelor (RadixSort)
Sa presupunem ca avem n fise, iar fiecare contine un nume ce identifica in mod unic fisa.Sa sortam manual fisele.Le impartim in
pachete,fiecare pachet cuprizand toate fisele ce incep cu aceeasi litera.Apoi sortam fiecare pachet dupa a doua litera.. s.a.m.d. Dupa
sortarea pachetelor le recombinam obtinand o lista liniara sortata. Aceasta metoda poate fi formalizata intr-un algoritm de sortare a
sirurilor de cuvinte (caractere). Presupunem ca elementele secventei de sortat reprezinta sirul de lungime fixata m ce se definesc peste
un alfabet de k litere. Putem presupune ca elementele de sortat reprezinta nmere scrise in baza k. De aceea sortarea cuvintelor este
numita in limba engleza “radix sort”(radix=baze).
C.Algoritmul de sortare prin numarare
Consta in a numara pentru fiecare element a(i) cate elemenete ,strict mai mici decat el, exista. Numerele obtinute sunt memorate intr-un
vector k.Pe baza lui vom rearanja elementele lui a intr-un alt vector b.
D. Sortarea topologica
Consideram ca relatia de ordine este partiala. De ex a2<a1 ,a2<a3<a4. Problema consta in crearea unei liste liniare compatibila cu
relatia de ordine: daca Ai<Aj atunci Ai precede pe Aj in lista finala.In exemplul anterior lista finala poate fi (a2,a1,a3,a4) sau
(a2,a3,a1,a4) sau (a2,a3,a4,a1).
E.Sortare untilizand metoda “Divide et impera”
1.Sortarea rapida (Quick Sort) reprezentata in 1962. Pentru rezolvare este necesara o procedura care traseaza o portiune de vector
cuprinsa intre indicii dati de p si q. Rolul acestei proceduri este de a pozitiona prima componenta a[p] pe o pozitie k suprinsa intre p si q
astfel incat toate componentele vectorului curinse intre p si k-1 sa fie mai mici sau egale decat a[k] si toate componentele vectorului
cuprinse intre k+1 si q sa fie mai mari sau egale decat a[k].
2.Mergesor. Un alt algoritmcare se bazeaza pe metoda “Divide et Impera” este algoritmul de interclasare a doi vectori sortati.El produce
ca rezultat tot un vector sortat, care contine elementele celor doi vectori care se interclaseaza.DEci pentru a sorta elementele vectorului
a , il vom partitiona in doua vom sorta sirurile a[1]…a[m],a[m+1]…a[n] apoi vom interclasa subsirurile sortate.

20. Algoritmi de sortare prin selecţie și prin selecție și interscimbări


20.1) Algoritmul de sortare prin metoda selectiei:
Sortarea prin selecţia minimului (sau maximului) este metoda de ordonare prin selectarea unui element şi lasarea lui pe poziţia sa finală
direct în tabloul A. De exemplu, în caz de ordonare crescătoare, pornind de la primul element se caută valoarea minimă din tablou.
Aceasta se aşează pe prima poziţie printr-o interschimbare între
elementul de pe prima poziţie şi elementul minim de pe poziţia k. Apoi, se reia algoritmul, pornind de la a doua poziţie şi se caută
minimul între elementele a2, ..., an. Acesta se interschimbă cu al doilea dacă este cazul. Procedeul se continuă până la ultimul element.
Pseudocodul algoritmului de sortare prin selecţia minimului este:
Subalgoritm Selecţie(n,a)
1: pentru i=1,n-1 execută:
2: min = a[i]
3: pentru j=i+1,n execută:
4: dacă min > a[j] atunci
5: min = a[j]
6: k = j Sfd 4: Sfp 3:
7: dacă min <> a[i] atunci
8: aux = a[i]
9: a[i] = a[k]
10: a[k] = aux Sfd 7: Sfp 1:

Exemplu
Fie tabloul A = (5, 0, 8, 7, 3).
Pas Tabloul A Element minimPoziţia minimuluiNoul tablou A
i=1 (5, 0, 8, 7, 3) 0 2 (0, 5, 8, 7, 3)
i=2 (0, 5, 8, 7, 3) 3 5 (0, 3, 8, 7, 5)
i=3 (0, 3, 8, 7, 5) 5 5 (0, 3, 5, 7, 8)
i=4 (0, 3, 5, 7, 8) 7 4
Algoritmul se poate scrie şi prin determinarea valorilor maxime şi mutarea lor în tablou de la dreapta la stânga, astfel rezultând, de
asemenea, un şir ordonat crescător. Un astfel de algoritm este des utilizat pentru că este mai simplu de reţinut, în forma următoare:
Subalgoritm Selecţie(n,a)
1: pentru i=1,n-1 execută:
2: pentru j=i+1,n execută:
3: dacă a[i] > a[j] atunci
4: aux = a[i]
5: a[i] = a[j]
6: a[j] = aux sfd 3: sfp 2: sfp 1:

20.2) Algoritmul de sortare prin metoda selecției și interscimbări:


Descrierea generalã a metodei
Ideea de bazã constã în faptul cã, imaginãm o tabelã tinutã vertical care contine elemente (înregistrãri) nesortate. Înregistrãrile cu chei
mici sunt usoare si asemenea bulelor de aer urcã spre vârful tabelei. Sortarea bulelor se bazeazã pe cãutarea celui mai mare (mic)

16
element. Comparãm elementele, dacã douã elemente adiacente nu sunt în ordine crescãtoare, le interschimbãm. Astfel elementul cu
cheia cea mai mare ajunge primul la locul potrivit, iar elementul cu cheia cea mai micã, urcã tot timpul spre vârf .
Descrierea algoritmului
Fiind dat o tabelã A cu N înregistrãri (Ri). Pentru sortarea tabelei este nevoie de N-1 pasuri. Parcurgem tabela: K1 cheia primului
element, comparãm cu K2 cheia celui de al doilea element si dacã nu sunt în ordine crescãtoare, R1 primul element interschimbãm cu
R2. Procesul se repetã pentru R2, R3,…..Dupã primul pas înregistrarea cu cheia cea mai mare va ajunge pe ultima pozitie N . Dupã
fiecare pas succesiv elementele cu chei mari vor ajunge pe pozitiile N-1, N-2, …, 2 rezultând o tabelã sortatã în ordine crescãtoare.
Pasii bubble sortului
În primul pas comparãm 6 cu 4, cu 5 si interschimbãm, apoi comparãm cu 10 nu interschimbãm; acum comparãm 10 cu 3, cu 1, cu 8, cu
9, cu 7, cu 2 si interschimbãm pe rând cu fiecare, astfel 10 ajunge pe ultima pozitie. Dupã pasul al doilea 9 va ajunge pe pozitia potrivitã
s.a.m.d. În pasul 8 comparãm 1 cu 2 si 2 cu 3. Dupã pasul 8 tabela este sortatã dar dacã nu avem o variabilã "flag" care sã opreascã
procesarea, algoritmul va mai face un pas în care comparã 1 cu 2 si numai dupã accea se opreste.
Algoritmul general:
1. Repetã de N-1 -ori pânã la pasul 4
2. Repetã pasul 3 pentru elementele nesortate din tabelã
3. Dacã cheia elmentului curent > cheia urmãtorului element atunci interschimbã
4. Dacã numai sunt necesare interschimbãri atunci stop
Altfel reduce mãrimea vectorului nesortat cu unu

Se parcurge vectorul (operand interschimbarii de elemente ,,vecine”)atat timp cat mai exista o pareche (a[i],a[a+i] ) cu a[i]>a[a+i].

repeta
modificat=fals
pentru i=1,N-1 executa
daca a[i]>a[i+1] atunci
schimba a[i],a[i+1]
modificat adevarat
sf daca
sf pentru
pana cand modificat=fals

Acest algoritm se mai numeste si “sortarea prin selectie si interschimbare” , “sortarea prin propagare” sau “metoda lenta de sortare”
datorita numarului mare de operatii care trebuie effectuate.Succesul algoritmului este asigurat de trecerea succesiva prin tablou,pana
cand acesta este sortat,cu specificatia ca , la fiecare trecere , elementele successive I si i+1 pentru care a[i]>a[i+1], vor fi interschimbate.
Metoda poate fi imbunatatita daca ,dupa fiecare trecere , se va retine ultima pozitie din tablou in care a avut loc o interschimbare, iar
trecerea urmatoare se va efectua doar pana la acea pozitie. In cazul in care la o trecere nu a avut oc nicio interschimbare algoritmul se
va incheia.
Pentru o si mai buna optimizare se poate inlocui trecerea prin tablou intr-un sens cu trecerea in dublu sens .In acest caz , daca la doua
treceri successive, intre doua elemente I si i+1 nu a avut loc o interschimbare atunci la trecerile urmatoare nu se vor inregistra
interschimbari.
Cu toate optimizarile aduse acestei sortari, ea se dovedeste a fi cu mult mai lenta decat sortarea prin insertie,fiind insa preferata de unii
datorita simplitatii, ce nu ridica problem de implementare. Pentru valori suficient de mari ale dimensiunii vectorului ce urmeaza a fi
sortat se recomanda utilizarea unor algoritmi ce au o complexitate mai redusa si, prin urmare, ofera un timp de calcul mai mic.

#include<stdio.h>
main( ){
int n,i,aux,terminat,a[50];
printf(”Introduceti dimensiunea vectorului : “);
scnf(“%d”, &n);
for(i=0;i<=n-1;i++){
printf(”a[%d]=”,i+1);
scanf(“%d”, &a[i]);
}
terminat=0;
while(!terminat){
terminat=1;
for(i=0;i<n-1;i++)
if(a[i]>a[i+1]){
aux-a[i];
a[i]=a[i+1];
a[i+1]=aux;
terminat=0;}
}
printf(”Vectorul ordonat este este : “);
for(i=0;i<+n-1;i++)
printf(”%d ”,a[i]);
}
================================================================================================

17
21. Algoritmi de sortare ”bubble” și prin inserție
21.1) Metoda bulelor (bubble-sort)
Descrierea generalã a metodei
Ideea de bazã constã în faptul cã, imaginãm o tabelã tinutã vertical care contine elemente (înregistrãri) nesortate. Înregistrãrile cu chei
mici sunt usoare si asemenea bulelor de aer urcã spre vârful tabelei. Sortarea bulelor se bazeazã pe cãutarea celui mai mare (mic)
element. Comparãm elementele, dacã douã elemente adiacente nu sunt în ordine crescãtoare, le interschimbãm. Astfel elementul cu
cheia cea mai mare ajunge primul la locul potrivit, iar elementul cu cheia cea mai micã, urcã tot timpul spre vârf .
Descrierea algoritmului
Fiind dat o tabelã A cu N înregistrãri (Ri). Pentru sortarea tabelei este nevoie de N-1 pasuri. Parcurgem tabela: K1 cheia primului
element, comparãm cu K2 cheia celui de al doilea element si dacã nu sunt în ordine crescãtoare, R1 primul element interschimbãm cu
R2. Procesul se repetã pentru R2, R3,…..Dupã primul pas înregistrarea cu cheia cea mai mare va ajunge pe ultima pozitie N . Dupã
fiecare pas succesiv elementele cu chei mari vor ajunge pe pozitiile N-1, N-2, …, 2 rezultând o tabelã sortatã în ordine crescãtoare.
Pasii bubble sortului
În primul pas comparãm 6 cu 4, cu 5 si interschimbãm, apoi comparãm cu 10 nu interschimbãm; acum comparãm 10 cu 3, cu 1, cu 8, cu
9, cu 7, cu 2 si interschimbãm pe rând cu fiecare, astfel 10 ajunge pe ultima pozitie. Dupã pasul al doilea 9 va ajunge pe pozitia potrivitã
s.a.m.d. În pasul 8 comparãm 1 cu 2 si 2 cu 3. Dupã pasul 8 tabela este sortatã dar dacã nu avem o variabilã "flag" care sã opreascã
procesarea, algoritmul va mai face un pas în care comparã 1 cu 2 si numai dupã accea se opreste.
Algoritmul general:
1. Repetã de N-1 -ori pânã la pasul 4
2. Repetã pasul 3 pentru elementele nesortate din tabelã
3. Dacã cheia elmentului curent > cheia urmãtorului element atunci interschimbã
4. Dacã numai sunt necesare interschimbãri atunci stop
Altfel reduce mãrimea vectorului nesortat cu unu

Se parcurge vectorul (operand interschimbarii de elemente ,,vecine”)atat timp cat mai exista o pareche (a[i],a[a+i] ) cu a[i]>a[a+i].

repeta
modificat=fals
pentru i=1,N-1 executa
daca a[i]>a[i+1] atunci
schimba a[i],a[i+1]
modificat adevarat
sf daca
sf pentru
pana cand modificat=fals

Acest algoritm se mai numeste si “sortarea prin selectie si interschimbare” , “sortarea prin propagare” sau “metoda lenta de sortare”
datorita numarului mare de operatii care trebuie effectuate.Succesul algoritmului este asigurat de trecerea succesiva prin tablou,pana
cand acesta este sortat,cu specificatia ca , la fiecare trecere , elementele successive I si i+1 pentru care a[i]>a[i+1], vor fi interschimbate.
Metoda poate fi imbunatatita daca ,dupa fiecare trecere , se va retine ultima pozitie din tablou in care a avut loc o interschimbare, iar
trecerea urmatoare se va efectua doar pana la acea pozitie. In cazul in care la o trecere nu a avut oc nicio interschimbare algoritmul se
va incheia.
Pentru o si mai buna optimizare se poate inlocui trecerea prin tablou intr-un sens cu trecerea in dublu sens .In acest caz , daca la doua
treceri successive, intre doua elemente I si i+1 nu a avut loc o interschimbare atunci la trecerile urmatoare nu se vor inregistra
interschimbari.
Cu toate optimizarile aduse acestei sortari, ea se dovedeste a fi cu mult mai lenta decat sortarea prin insertie,fiind insa preferata de unii
datorita simplitatii, ce nu ridica problem de implementare. Pentru valori suficient de mari ale dimensiunii vectorului ce urmeaza a fi
sortat se recomanda utilizarea unor algoritmi ce au o complexitate mai redusa si, prin urmare, ofera un timp de calcul mai mic.
#include<stdio.h>
main( ){
int n,i,aux,terminat,a[50];
printf(”Introduceti dimensiunea vectorului : “);
scnf(“%d”, &n);
for(i=0;i<=n-1;i++){
printf(”a[%d]=”,i+1);
scanf(“%d”, &a[i]);
}
terminat=0;
while(!terminat){
terminat=1;
for(i=0;i<n-1;i++)
if(a[i]>a[i+1]){
aux-a[i];
a[i]=a[i+1];
a[i+1]=aux;
terminat=0;
} }
printf(”Vectorul ordonat este este : “);
for(i=0;i<+n-1;i++) printf(”%d ”,a[i]); }
18
21.2) Metoda insertiei
Sortarea prin insertie se bazeaza pe aceleasi principii ca si cele aplicate de majoritatea jucatorilor de carti,adica dupa ridicarea uncei
carti de pe masa,aceasta se aseaza in pachetul din mana la locul potrivit.Cu alte cuvinte, consideram ca avem vectorul sortat a, iar la
ivirea unui nou element care se va adauga vectorului,el va fi pus pe locul potrivit printr-o insertie in interiorul vectorului.
Insertie directa.Este cea mai simpla implementare a algoritmului si se face in felul urmator: Se considera ca primele i elemente al
vectorului sunt deja sortate.Pentru elementul al (i+1)-lea,din tabloul initial,se va gasi pozitia in care trebuie inserat printre primele i
elemente.Toate elementele tabloului de la acasta pozitie si pana la i vor fi deplasate cu o pozitie mai la dreapta iar pozitia eliberata va fi
ocupata de elemntul i+1.
pentru j=1 ,N-1 executa
x=a[j]; i=j-1;
cat timp ((i>=0) && (x<a[i]))
a[i+1]=a[i]
i=i-1;
sf cat timp
a[i+1]=x
sf pentru
Inserare rapida.Deoarece vectorul destinatie este un vector ordonat crescator, cautarea pozitiei in care va fi inserat a[i] se poate
face nu secvential ( ca in cazul inserarii directe) ci prin algoritmul de cautare binara. Subvectorul destinatiei este impartit in doi
subvectori,se examineaza relatia de ordine dintre elementul de la mijlocul subvectorului si elementul a[j] si se stabileste daca elemntul
a[i] va fi inserat in prima jumatate sau in a doua jumatate.Operatia de divizare a subvectorului continua pana se gaseste pozitia in care
urmeaza sa fie inserat a[i].

#include<stdio.h>
main( )
{
int i,j,n,aux,a[50];
printf(“Iintroduceti dimensiunea sirului: ");
scanf(“%d”,&n);
printf("Dati elementele sirului:\n");
for(i=0;i<n;i++) {
printf("a[%d]=”,i+1); scanf(“%d”,&a[i]);
}
for(j=1;j<n;j++)
{ aux=a[j];
i=j-1;
while (aux<a[i] && i>=0)
{ a[i+1]=a[i];
i=i-1;
}
a[i+1]=aux;
}
printf("Sirul ordonat este:");
for(i=0;i<n;i++)
printf(“%d ”,a[i]);
}
================================================================================================
22. Algoritmi de sortare ”shaker” și Shell
22.1) Metoda de sortare “Shaker”
Sortare prin amestecare (Shaker Sort)La prima parcuregere cheia de valoare imediata inferioara celei maxime migreazaspre penultima
pozitie.La fiecare parcurgere a tabloului se memoreaza indicele ultimei interschimbari facute astfel incat la urmatoarea parcurgere un
capat al subtabloului va fi marcat de acest indice. Se schimba alternativ sensul de parcurgere al subtablourilor pentru doua treceri
succesive. Datorita acestor migrari,metoda a fost denumita astfel, deoarece elementele de valori mari se ridica la suprafata asemenea
unor bule.
Principiu: reprezinta o varianta a metodei bubblesort, avind urmatoarele imbunatatiri:
-la fiecare parcurgere a subtabloului a[i],...,a[N] se memoreaza indicele k al ultimei interschimbari efectuate, astfel incit la urmatoarea
trecere un capat al subtabloului va fi marcat de k (intre 1 si k tabloul este ordonat);
-se schimba alternativ sensul de parcurgere al subtablourilor pentru doua treceri consecutive.
Implementarea algoritmului in Pascal:
procedure ShakerSort;
VAR j,k,l,r : TipIndex; x : TipElement;
begin
l:=2; r:=N; k:=N;
repeat
for j:=r downto l do
if a[j-1].cheie>a[j].cheie then begin
x:=a[j-1];
a[j-1]:=a[j];
a[j]:=x;
19
k:=j
end;
l:=k+1;
for j:=l to r do
if a[j-1].cheie>a[j].cheie then begin
x:=a[j-1];
a[j-1]:=a[j];
a[j]:=x;
k:=j
end;
r:=k-1
until l > r
end; {ShakerSort}
La sortarile bubblesort si shakersort, M si C sint proportionali cu N*N, metodele fiind mai putin performante decit cele anterioare.

22.2) Metoda de sortare “Shell”


Descriere :
Algoritmul shell sort este o generalizare a algoritmului insertion sort. La algoritmul insertion sort, pentru a insera un nou element în
lista de elemente deja sortate, se deplasează fiecare element cu câte o poziţie spre dreapta atât timp cât avem elemente mai mari decât
el. Practic fiecare element înaintează spre poziţia sa finală cu câte o poziţie.
Algoritmul shell sort lucrează similar, doar că deplasează elementele spre poziţia finală cu mai mult de o poziţie. Se lucrează în iteraţii.
În prima iteraţie se aplică un insertion sort cu salt s1 mai mare decât 1. Asta înseamnă că fiecare element din şirul iniţial este deplasat
spre stânga cu câte s1 poziţii atât timp cât întâlneşte elemente mai mari decât el.
Se repetă asemenea iteraţii cu salturi din ce în ce mai mici s2, s3, s4, etc. Ultima iteraţie se face cu saltul 1. Această ultimă iteraţie este
practic un insetion sort clasic.
Principiul este că după fiecare iteraţie şirul devine din ce în ce “mai sortat”. Iar cum algoritmul insertion sort funcţionează cu atât mai
repede cu cât şirul este mai sortat, per ansamblu vom obţine o îmbunătăţire de viteză.

Implementare :
Code:
void shell(long a[],long n)
{
long i, j, k, h, v;
long cols[] = {1391376, 463792, 198768, 86961, 33936, 13776, 4592,
1968, 861, 336, 112, 48, 21, 7, 3, 1};
for (k = 0; k < 16; k++) //parcurgem fiecare limita
{
h = cols[k];
for (i = h; i < n; i++)//insertion sort
{
v = a[i];
j = i;
while (j >= h && a[j-h] > v) //crescator
{
a[j] = a[j-h];
j = j - h;
}
a[j] = v;
}
}
}

================================================================================================
23. Algoritm de sortare prin interclasare (mergesort)
Mulţi algoritmi utili au o structura recursiva: pentru a rezolva o problema data, aceştia sunt apelaţi de către ei înşişi o data sau de mai
multe ori pentru a rezolva subprobleme apropiate. Aceşti algoritmi folosesc de obicei o abordare de tipul divide şi stăpâneşte: ei rup
problema de rezolvat în mai multe probleme similare problemei iniţiale, dar de dimensiune mai mica, le rezolva în mod recursiv şi apoi
le combina pentru a crea o soluţie a problemei iniţiale. Pentru metoda de sortare prin interclasare principiul divide şi stăpâneşte poate fi
privit astfel:
Divide: Împarte şirul de n elemente care urmează a fi sortat în doua subşiruri de câte n/2 elemente.
Stăpâneşte: Sortează recursiv cele doua subşiruri utilizând sortarea prin interclasare.
Combina: Interclasează cele două subşiruri sortate pentru a produce rezultatul final.

20
Figura 1.12 Modul de operare al sortării prin interclasare asupra vectorului A = {5, 2, 4, 6, 1, 3, 2, 6}.Lungimile şirurilor sortate, în curs
de interclasare, cresc pe măsură ce algoritmul avansează de jos în sus. Să observăm că recursivitatea se opreşte când şirul de sortat are
lungimea 1, caz în care nu mai avem nimic de făcut, deoarece orice sir de lungime 1 este deja sortat. Operaţia principala a algoritmului
de sortare prin interclasare este interclasarea a două şiruri sortate, în pasul denumit mai sus “Combina”. Pentru aceasta vom utiliza o
procedura auxiliara, Interclaseaza(A, p, q, r), unde A este un vector şi p, q şi r sunt indici ai vectorului, astfel încât p ≤ q < r. Procedura
presupune că subvectorii A[p..q] şi A[q + 1..r] sunt sortaţi. Ea îi Interclasează pentru a forma un subvector sortat care înlocuieşte
subvectorul curent A[p..r]. O procedura de tip Interclaseaza are un de execuţie de ordinul Θ(n), în care n = r - p + 1 este numărul
elementelor interclasate. Revenind la exemplul nostru cu cărţile de joc, să presupunem că avem doua pachete de cărţi de joc aşezate pe
masa cu faţa în sus. Fiecare din cele doua pachete este sortat, cartea cu valoarea cea mai mica fiind deasupra. Dorim să amestecam cele
doua pachete într-un singur pachet sortat, care să rămână aşezat pe masa cu faţa în jos. Pasul principal este acela de a selecta cartea cu
valoarea cea mai mica dintre cele doua aflate deasupra pachetelor (fapt care va face ca o noua carte să fie deasupra pachetului respectiv)
şi de a o pune cu faţa în jos pe locul în care se va forma pachetul sortat final. Repetam acest procedeu până când unul din pachete este
epuizat. În aceasta faza, este suficient să luam pachetul rămas şi să-l punem peste pachetul deja sortat întorcând toate cărţile cu faţa în
jos.
1: daca p < r atunci
2: q ← [(p + r)/2]
3: Sorteaza-Prin-Interclasare(A, p, q)
4: Sorteaza-Prin-Interclasare(A, q + 1, r)
5: Interclaseaza(A, p, q, r)
i ← p;
j ← q + 1;
k←1;
cat timp i ≤ q şi j ≤ r executa
daca A[i] < A[j] atunci
C[k] ← A[i]
k←k+1
i←i+1
altfel
C[k] ← A[j]
k←k+1
j←j+1
cat timp i ≤ q executa
C[k] ← A[i]
k←k+1
i←i+1
cat timp j ≤ r executa
C[k] ← A[j]
k←k+1
j←j+1
k←1;
pentru i ← p, r executa
A[i] ← C[k]
k←k+1

Utilizam procedura Interclaseaza ca subrutina pentru algoritmul de sortare prin interclasare. Procedura Sorteaza-Prin-
Interclasare(A, p, r) sortează elementele dinsubvectorul A[p..r]. Daca p ¸ r, subvectorul are cel mult un element şi este, prin urmare, deja
sortat. Altfel, pasul de divizare este prezent aici prin simplul calcul al unui indice q care împarte A[p..r] în doi subvectori, A[p..q]
conţinând [n/2]+1elemente şi A[q + 1..r] conţinând [n/2]elemente.4 Pentru a sorta întregul sir A = {A[1],A[2], …A[n]}, vom apela
procedura Sorteaza-Prin-Interclasare sub forma Sorteaza-Prin-Interclasare(A, 1, lungime[A]) unde, din nou, lungime[A] = n. Daca
analizam modul de operare al procedurii, de jos în sus, când n este o putere a lui 2, algoritmul consta din interclasarea perechilor de
şiruri de lungime 1, pentru a forma şiruri sortate de lungime 2, interclasarea acestora în şiruri sortate de lungime 4, şi aşa mai departe,
până când doua şiruri sortate de lungime n/2 sunt interclasate pentru a forma şirul sortat final de dimensiune n. Figura 1.12 ilustrează
acest proces.
Deşi algoritmul Sorteaza-Prin-Interclasare funcţionează corect când numărul elementelor nu este par, analiza bazata pe recurenţa se
simplifica daca presupunem că dimensiunea problemei originale este o putere a lui 2. Fiecare pas de împărţire generează deci doua
subşiruri având dimensiunea exact n=2.
21
================================================================================================
24. Algoritm de sortare rapidă (quicksort)
Descriere
Metoda Divide et Impera este utilizată în sortarea rapidă.
Ideea algoritmului:
1. Se alege o valoare pivot. Se ia valoarea elementului din mijloc ca valoare pivot, dar poate fi oricare altă valoare, care este în
intervalul valorilor sortate, chiar dacă nu este prezentă în tablou.
2. Partiţionare. Se rearanjează elementele în aşa fel încât, toate elementele care sunt mai mari decât pivotul merg în partea dreaptă a
tabloului. Valorile egale cu pivotul pot sta în orice parte a tabloului. În plus, tabloul poate fi împărţit în părţi care nu au aceeaşi
dimensiune (nu sunt egale).
3. Se sortează amândouă părţile.se aplică recursiv algoritmul de sortare rapidă în partea stângă şi în partea dreaptă.
Algoritmul de partiţie în detaliu.
Există 2 indici i şi j, şi la începutul algoritmului de partiţionare i indică primul element al tabloului iar j indică ultimul element din
tablou. La pasul următor algoritmul mută i înainte, pâna când un element cu o valoare mai mare sau egală cu pivotul este găsită.
Indicele j este mutat înapoi, pâna când un element cu valoare mai mică sau egală cu pivotul este găsită. Dacă i<=j atunci i merge pe
poziţia i+1 iar j merge pe poziţia j-1. Algoritmul se opreşte, când i > j

Algoritm descris în pseudocod:


quickSort(V,st,dr);
pivot←v[(st+dr) div 2)];
cât timp i<=j execută
cât timp v[i] <pivot execută
i←i+1;
dacă i<=j atunci
aux←v[i];
v[i]←v[j];
v[j]←aux;
i←i+1;
j←j-1;
dacă st<j atunci
quikSort(v,st,j);
dacă i<dr atunci
quikSort(v,i,dr);
================================================================================================
25. Arbore binar heap şi coada de prioritate
25.1) Arbore binar heap
Situaţii practice impun în mod frecvent alegerea dintr-o mulţime dinamică de valori a uneia sau unora care îndeplinesc anumite
condiţii. Spre exemplu, o firmă are în vedere spre onorare, în primul rând comenzile cele mai rentabile. Este deci necesar, ca o structură
de date dinamică adecvată, să ofere cu "efort" minim de prelucrare, informaţia cerută de un anumit criteriu de optim. Constatăm că este
vorba despre selectarea unor informaţii dintr-un volum de date, organizate după un criteriu stabilit. O astfel de facilitate de prelucrare
este oferită de o categorie de arbori binari, cunoscuţi în literatura de specialitate sub numele de heap-uri.
Se numeşte ansamblu Heap un arbore binar care îndeplineşte următoarele condiţii:
 este un arbore aproape complet;
o rădăcina se notează cu 1
o dacă un nod i are descendenţi, aceştia sunt notaţi astfel: 2i descendentul dinstânga şi 2i+1 descendentul din dreapta;
o valoarea cheii oricărui nod este mai mare sau egală faţă de cea asociată descendenţilor săi (dacă aceştia există)
 în orice pereche de noduri tată-fiu cheile sunt într-o relaţie de ordine prestabilită.
Proprietatea care defineste structura unui arbore heap este urmatoarea:
 Valoarea cheii memorate in radacina este mai mare decât toate valorile cheilor conţinute în subarborii descendenţi.
Aceasta proprietate trebuie sa fie indeplinita pentru toti subarborii, de pe orice nivel in arborele heap.
În ansamblul heap în nodurile lui sunt memorate numere naturale, relaţia de ordine fiind: dacă în nodul tată este un număr impar, în
nodul fiu nu poate fi decât tot un număr impar. Numerele scrise în exteriorul nodurilor, reprezintă indici pentru numărarea nodurilor.
Terminologie:Ansamblul HEAP se mai numeşte şi arbore de selecţie sau arbore parţial ordonat. Definiţie Un max-
heap este un arbore binar complet în care valoarea memorată în orice nod al său este mai mare sau egală decât valorile memorate în
nodurile fii ai acestuia.
Corespunzător se poate defini min-heap-ul, ca un arbore binar complet în care valoarea memorată în orice nod este mai mică sau egală
decât valorile memorate în nodurile fii ai acestuia.
În cele ce urmează, un max-heap va fi numit heap, urmând ca atunci când este vorba despre un min-heap, aceasta să se precizeze în
mod explicit.
Un heap fiind un arbore binar complet, reprezentarea cea mai adecvată este reprezentarea secvenţială. Deci este suficient să
reţinem într-un vector informaţiile asociate nodurilor, relaţiile dintre noduri fiind descrise de formulele:

tata(x)=x/2 daca x>1


tata(x) nu exista daca x=1
unde x este un nod oarecare în arbore, iar n numărul de noduri din arbore.
Utilizând reprezentarea secvenţială, putem reformula definiţia unui heap astfel:
Definiţie

22
Tabloul H[1..n] formează un max-heap dacă
H[i] H[i div 2], i {2,3,...,n}.
Tabloul H[1..n] formează un min-heap dacă
H[i] H[i div 2], i {2,3,...,n}.
De exemplu, arborele binar complet din figura este un max-heap.

şi va fi reprezentat implicit :
1 2 3 4 5
9 7 5 5 7
Crearea unui heap
I. O primă soluţie este de a insera succesiv elementele în heap, plecând iniţial de la heap-ul format dintr-un singur
element:

void CreareHeap()
//organizează vectorul global H, de dimensiune n, ca un heap inserând succesiv elemente;
{ for i = 1;i<=n; i++)
InsertHeap(i-1, H[i]);
}
Inserarea unui nod într-un heap

Fie H[1..n] un heap cu n elemente şi x valoarea ce trebuie inserată. Valoarea x va fi memorată în heap pe poziţia n+1, apoi
se restaurează eventual heap-ul, “promovând” valoarea x spre rădăcină, până când proprietatea de heap este restabilită.
De exemplu,să inserăm valoarea 10 în heap-ul din figura:

Valoarea inserată va ocupa poziţia 6. Pentru a conserva proprietatea de heap, valoarea 10 trebuie promovată până în rădăcină, valorile
de pe drumul parcurs spre rădăcină fiind retrogradate succesiv, cu un nivel.
Rezultă arborele:

Modificările sunt aplicate de fapt vectorului ce constituie reprezentarea implicită :


1 2 3 4 5 6
10 7 9 5 7 5

void InsertHeap (int x);


{//inserează valoarea x în heap-ul H de dimensiune n
int fiu, tata;
fiu =++n;//fiu indică poziţia pe care trebuie plasată valoarea x
tata = n /2;
while (tata > 0) && (H[tata] < x)
{ // valoarea x trebuie “promovată”
H[fiu] = H[tata];
fiu = tata; tata = fiu / 2;
};
H[fiu] = x
}

23
25.2) Coada de prioritate
O coadă de priorități este o structură de date în care vom putea stoca informații alocate dinamic ca în orice altă structură de date, dar în
plus față de o coadă obișnuită, inserția datelor se face în funcție de un anumit număr ce reprezintă prioritatea acelei informații. Astfel,
atunci când scoatem informații din coadă, vom primi mereu informația cu cea mai mare prioritate la momentul respectiv.
Tipul de dată al fiecărui nod al cozii va trebui să fie o structură care va conține informația care se vrea ținută în coadă, o variabilă de tip
întreg (pozitiv) care va conține prioritatea informației și un pointer către următorul nod. Pentru ca să existe mai multă claritate la nivel
de cod ar fi bine să creem încă o structură care va conține un pointer către primul nod din coadă și o variabilă de tip întreg care va
conține numărul de noduri aflate la un moment dat în stivă. Ne interesează să știm dimensiunea pentru a putea limita dimenziunea
maximă a cozii.
//Definirea tipului nodurilor din coada
typedef struct coada{
TYPE val;
unsigned int priority;
struct coada *next;
}coada;

//Definirea tipului coada de prioritati


typedef struct pcoada {
unsigned int size;
coada *first;
}pcoada;

Inițializarea cozii
Înainte de fiecare utilizare, coada va trebui să fie inițializată. Astfel, câmpul first din structura de tip coadă va trebui inițializat cu NULL
iar câmpul size va trebui inițializat cu 0.
void initpcoada(pcoada **coad)
{
//Alocam memorie pentru tipul coada de prioritati
//si punem valorile initiale in campurile structurii.
(*coad) = (pcoada *) malloc(sizeof(pcoada));
(*coad)->first = NULL;
(*coad)->size = 0;
return;
}
Adăugarea în coadă
Fucția pentru adăugarea unui element va trebui să știe în ce coadă să adauge informația, ce informație să adauge și cu ce prioritate.
Dacă câmpul size din coadă a ajuns deja la dimensiunea maximă permisă (cea definită în header), nici nu are rost să mergem mai
departe, afișăm un mesaj de eroare și ieșim din funcție.
Este bine să alocăm memoria pentru elementul pe care vrem să-l introducem dinainte să ne dăm seama unde trebuie să-l plasăm, altfel
ar trebui să scriem mult cod pentru fiecare caz în parte. Știind că informația are o prioritate, elementul cu prioritatea cea mai mare va fi
mereu la început. Dacă coada este goală, evident, indiferent de prioritate, elementul se va adăuga la început. Altfel, trebuie să
parcurgem coada până întâlnim un element care are o prioritate mai mică decât prioritatea elementului pe care vrem să-l adăugăm și să-l
plasăm înaintea lui. Evident, nu putem reface legăturile dacă verificăm prioritatea în funcție de elementul curent, din acest motiv,
comparăm de fiecare dată cu elementul imediat următor. Dacă am ajuns la sfârșit și nu s-a adaugat încă elementul în coadă, îl vom
adăuga la sfârșit. Funcția de adăugare o puteți vizualiza în codul sursă aflat la sfârșitul articolului.
Preluarea elementelor din coadă
Funcția care preia elementele va returna efectiv informația primului element din coadă (adică cel de prioritatea cea mai mare), dar
inainte de asta, îl va elimina din coadă, transformând primul element de după el în primul element din coadă.
Funcția va afișa mesaj de eroare când se va încerca preluarea elementului când coada este goală.
TYPE getpcoada(pcoada **coad)
{
TYPE returnValue;
//Luam elemente din coada atata timp cat ea nu este goala.
if((*coad)->size>0)
{
returnValue = (*coad)->first->val;
(*coad)->first = (*coad)->first->next;
(*coad)->size--;
}
else
{
//Daca coada este goala afisam mesaj de eroare.
//Functia va returna totusi ce se gaseste in memorie la momentul
//in care se defineste variabila returnValue.
printf("\nQueue is empty.\n");
}
return returnValue; }

24
26. Algoritmi de sortare utilizînd coada de prioritate și arbore binar heap
26.1) Algoritmul de sortare coada de prioritate
Aplicatiile pricipale ale arborilor heap sint:
- implementarea tipului de date abstract "coada cu prioritate".
- agoritmul de sortare HeapSort.
O coada cu prioritate este o structura de date de tip "container" pentru care se definesc urmatoarele functii de acces:
INSERT(C, X) - pune in coada C atomul X;
REMOVE(C) - scoate din coada C si returneaza atomul cu valoarea cea mai mare a cheii. Arborii heap pot fi reprezentati explicit
(reprezentarea standard a arborilor binari) sau implicit. Reprezentarea implicita a arborilor heap este cea mai folosita, si ea va face
obiectul celor prezentate in continuare. Vom realiza in cotinuare operatiile INSERT si REMOVE pentru arbori heap in reprezentare
implicita. Arborii heap la care ne referim sint formati din k nivele complete iar pe nivelul k+1 nodurile sint grupate in partea stinga:

Operatia INSERT
Se adauga X la sfirsitul vectorului si apoi se promoveaza spre radacina pina ajunge in pozitia in care structura de heap a arborelui in
reprezentare implicita este respectata.
De exemplu, se insereaza valoarea 35 in heapul de mai sus:

Pentru a pastra structura arborelui vloarea 35 trebuie promovata pana pe nivelul 2. Valorile de pe portiunea din ramura parcursa vor fi
retrogradate cu un nivel. Rezulta arborele:

In algoritmul urmator promovarea valorii inserate se face "din aproape in aproape", aceasta urcind in arbore din nivel in nivel.

INSERT(A,N,X)// N trebuie sa fie pasat prin referinta


{
A[N+1] = X //adauga valoarea de inserat la sfirsitul vectorului
N = N+1
fiu = N
parinte = N / 2 // impartire intreaga
WHILE parinte>=1 DO // parcurge ramura spre radacina
│IF A[parinte]<A[fiu] THEN
│ │SCHIMBA(A[parinte], A[fiu]); //retrogradeaza A[parinte]
│ │fiu := parinte; │
│ │parinte := parinte div 2; │ // urca spre radacina
│ELSE parinte := 0; // pentru parasirea buclei
}

25
Operatia REMOVE
Operatia REMOVE sterge din heap valoarea cea mai mare, valoare care se afla intotdeauna in radacina arborelui heap, deci in prima
pozitie a heap-ului. Pentru a reface structura de heap se aduce in prima pozitie ultimul element din vector si apoi se retrogradeaza pina
se reface structura de heap. La retrogradarea cu un nivel valoarea din radacina este schimbata cu descendentul care are valoarea
maxima.

Iata algoritmul:
REMOVE(A,N) // N trebuie sa fie pasat prin referinta
{
IF N=0 THEN eroare
ELSE │ret_val = A[1]; //Valoarea de returnat
│A[1] = A[N]; //Aduce ultimul element in radacina
│N = N-1; //Micsoreaza heapul
│parinte = 1;
│fiu = 2;
│WHILE fiu<=N DO //parcurge o ramura in sens descendent
│ │IF (fiu+1<=N) and (A[fiu]<A[fiu+1]) THEN
│ │ fiu = fiu+1; // este ales fiu cel mai mare
│ │IF A[fiu]>A[parinte] THEN // daca parinte<max(fii)
│ │ │SCHIMBA(A[parinte],A[fiu]); // retrogradeaza
│ │ │parinte := fiu; │
│ │ │fiu := fiu*2; │ // coboara
│ │ELSE fiu=N+1; // pentru parasirea buclei
}

26.2) Algoritmul de sortare arbore binary heap


Sortarea cu ansamble face parte din clasa strategiilor de sortare prin selecţiei. Aceasta are la bază selectarea pe rând a
elementelor şi plasarea acestora pe locul lor final. Sortarea cu ansamble se bazează pe o structură de date de tip arbore binar,
reprezentată printr-un arbore binar.
Dacă un vector este organizat ca un ansamblu Heap, nodurile vor fi extrase în ordinea precizată prin relaţia de ordine asociată
arborelui. Această caracteristică a ansamblului Heap stă la baza algoritmului de sortare HeapSort.
Heapsort este un algoritm de sortare care nu necesita memorie suplimentara (sorteaza vectorul pe loc), care funcţioneaza în doua etape:
 transfomarea vectorului de sortat intr-un arbore heap;
 reducerea treptata a dimensiunii heap-ului prin scoaterea valorii din rădăcină.
Pentru un vector format din n elemente numere întregi putem să-i punem în corespondenţă toate componentele sale cu nodurile
unui arbore binar complet prin procedeul de creare a unui heap.
Primul pas este de a organiza vectorul ca un heap. Deoarece, conform proprietăţii de heap, elementul maxim din vector este plasat în
rădăcina heap-ului, deci pe poziţia 1, el poate fi plasat pe poziţia sa corectă, interschimbându-l cu elementul de pe poziţia n. Noul
element din rădăcina heap-ului nu respectă proprietatea de heap, dar subarborii rădăcinii rămân heap-uri. Deci trebuie restaurat heap-ul,
apelând CombHeap(1, n-1), elementul de pe poziţia n fiind deja pe poziţia sa corectă nu mai este inclus în heap. Procedeul se repetă
până când toate elementele vectorului sunt plasate pe poziţiile lor corecte.
etapa I
 organizeaza tabloul ca un max-heap
 iniţial tablou satisface proprietatea maxheap incepand cu n/2
 introduce în max-heap elementele de pepozitiile n/2-1, n/2 -1, …, 1, 0
etapa II
 selecteaza elementul maxim şi-l duce lalocul lui prin interschimbare cu ultimul
 micşoreaza cu 1 si apoi reface max-heapul
 repeta paşii de mai sus pana cand toateelementele ajung pe locul lor
Valorile rezultate in ordine descrescatoare sint plasate in vector pe spatiul eliberat, de la dreapta la stânga.
void HeapSort()
{//ordonează crescător vectorul global H, de dimensiune n, utilizând //heap-uri
int i;
CreareHeap();
for i( = n ;i>1;i--) //plasează corect un element pe poziţia i
{
26
H[1] H[i]; // schimbă elementul H[i] cu H[1]
CombHeap(1, i-1); //restaurează heap-ul
}
}
Construirea heap-ului se poate face urmând doua strategii
Construirea heap-ului de sus in jos:
Vectorul va fi parcurs de la stinga la dreapta, corespunzator unei deplasari in arbore de sus in jos.
Pentru adaugarea elementului din pozitia i in vector se presupune ca elementele A[1..i-1] formeaza un heap.

Adaugarea elementului A[i] este realizata prin intermediul operatiei INSERT

Construirea heap-ului prin aceasta metoda este un algoritm de complexitate O(n·log(n))


Construirea heap-ului de jos in sus:

Elementul adaugat, A[i], va fi retrogradat la fel cu modul de retrogradare folosit in procedura REMOVE.
Construirea heap-ului prin aceasta metoda este un algoritm de complexitate O(n*log n).
Heapsort este o metodă de sortare care se bazează pe proprietatea de heap.

================================================================================================
27. Funcţii standard qsort ( ) şi bsearch ( ) în limbajul C
27.1) Functii standart qsort()
27.2) Functii standart bsearch()

27
28. Tehnici de programare. Clasificarea şi caracteristica generala
O clasificare a cunoasterii importanta pentru programare în general se refera la continutul acesteia, existând urmatoarele trei
categorii: cunoastere procedurala, declarativa sitacita. Cunoasterea procedurala se refera la a sti cum sa faci ceva, adica a sti mijloacele
pentru a ajunge la un anume rezultat . Cunoasterea declarativa se refera la a sti daca un enunt este adevarat sau fals (o asemenea
cunoastere ar trebui sa se foloseasca în justitie!). Cunoasterea tacita (denumita uneori si neconditionata, datorata inconstientului) este
cunoasterea ce nu poate fi exprimata prin limbaj (un exemplu de acest fel este cunoasterea privind mersul pe bicicleta).
Tehnici de programare:
Clasa limbajelor procedurale se bazeaza pe notiunea de algoritm. Acesta este o metoda de a rezolva o problema într-un numar finit de
pasi, folosind o multime finita de operatii (instructiuni) cunoscute, care executate într-o ordine bine stabilita, pornind de la un set de
valori (intrarile), produc în timp finit un alt set de valori (iesirile). Limbajele imperative sau orientate pe instructiune (în engleza
”statement-oriented”) au ca o caracteristica esentiala aceea ca programul este un sir de instructiuni - comenzi, precizând calculatorului
operatiile pe care trebuie sa le execute la momente succesive de timp. Aceste limbaje au fost dezvoltate pentru a elibera programatorul
de lucrul în cod masina si în acest sens au fost
prevazute cu facilitati privind manipularea variabilelor, operatiile de atribuire si asigurarea repetarii unor secvente.
Programarea functionala este centrata pe notiunea de functie. Aici programul este vazut ca o functie, care se obtine din compunerea mai
multor functii simple, fie functii sistem, fie definite de utilizator.
O alta trasatura pe care au dorit sa o realizeze limbajele functionale este asa numita transparenta referentiala. Aceasta este o
caracteristica a limbajului folosit în matematica si care nu a fost respectata în limbajele imperative. Ea se refera la faptul ca întelesul
unui întreg este complet determinat de întelesul partilor, neputând apare efecte parazite datorita combinarii partilor sau unor relatii de
ordine dintre ele.

================================================================================================
29. Tehnica de programare „Forța brută” și tehnica de programare „ Divide et empera”. Exemple
29.1) Tehnica de programare „Forța brută”
Programarea Dinamică este cea mai flexibilă tehnica din programare. Cel mai ușor mod de a o înțelege presupune parcurgerea cât mai
multor exemple.
O problema clasică de Programare Dinamică este determinarea celui mai lung subșir strict crescător dintr-un șir de numere. Un subșir al
unui șir este format din caractere (nu neapărat consecutive) ale șirului respectiv, în ordinea în care acestea apar în șir.
Exemplu: pentru șirul 24 12 15 15 8 19 răspunsul este șirul 12 15 19
Se observa că dacă încercăm o abordare greedy nu putem stabili nici măcar elementul de început într-un mod corect. Totuși, problema
se poate rezolva ”muncitorește” (forța brută) folosind un algoritm care alege toate combinațiile de numere din șir, validează că șirul
obținut este strict crescător şi îl reține pe cel de lungime maximă, dar aceasta abordare are complexitatea temporală O(N!). Cu
optimizări, este posibil sa se ajungă la O(2N).
O metoda de rezolvare mai eficientă folosește Programarea Dinamică. Începem prin a stabili pentru fiecare element lungimea celui mai
lung subșir strict crescător care începe cu primul element și se termină în elementul respectiv. Numim aceasta valoare best i și aplicăm
formula recursivă besti = 1 + max(bestj), cu j<i și elemj < elemi.
Aplicând acest algoritm obținem:
elem 24 12 15 15 8 19 best 1 1 2 2 1 3
Pentru 24 sau 12 nu există nici un alt element în stânga lor strict mai mici decât ele, de aceea au best egal cu 1. Pentru elementele 15 se
poate găsi în stânga lor 12 strict mai mic decât ele. Pentru 19 se găsește elementul 15 strict mai mic decât el. Cum 15 deja este capăt
pentru un subșir soluție de 2 elemente, putem spune că 19 este capătul pentru un subșir soluție de 3 elemente.
Cum pentru fiecare element din mulțime trebuie să găsim un element mai mic decât el şi cu best maxim, avem o complexitate O(N)
pentru fiecare element. În total rezultă o complexitate O(N 2). Se pot obține şi rezolvări cu o complexitate mai mica folosind structuri de
date avansate. Atât soluția în O(N2), cât si o soluție în O(N log N) poate fi găsită la [5]. Tot acolo se poate găsi și o lista de probleme
mai dificile ce folosesc tehnica Programării Dinamice, adaptată în diferite forme.
Pentru a găsi care sunt elementele ce alcătuiesc subșirul strict crescător putem să reținem și o „cale de întoarcere”. Reconstrucția astfel
obținută are complexitatea O(N). Exemplu: subproblema care se termina în elementul 19 are subșirul de lungime maximă 3 şi a fost
calculată folosind subproblema care se termină cu elementul 15 (oricare din ele). Subșirul de lungime maximă care se termină în 15 a
fost calculat folosindu-ne de elementul 12. 12 marchează sfârșitul reconstrucției fiind cel mai mic element din subșir.

29.2) Tehnica de programare „Divide et empera”


Divide et impera este o tehnică specială prin care se pot rezolva anumite probleme. Ideea de bază este aceea de a descompune problema
în două sau mai multe subprobleme similare, dar mai simple, care se rezolvă, iar soluţiile lor se combină pentru a da rezultatul final. Se
presupune că subproblemele considerate se descompun la rândul lor în mod similar în subprobleme, până când se ajunge la probleme
ale căror soluţii sunt evidente.
Tehnica divide et impera se bazează pe ideea de recursivitate: la un anumit moment dat (nivel) avem două posibilităţi:
(1) subproblema curentă are o rezolvare imediată, caz în care recursia se opreşte şi revin la subproblema anterioară
28
(2) nu am ajuns la o rezolvare imediată şi deci descompun subproblema curentă în 2 sau mai multe subprobleme similare şi reiau
procedeul pentru fiecare dintre acestea.
Exemplu:
Maximul dintr-un vector
Se citește un vector cu n componente, numere naturale. Se cere să se tipărească valoarea maximă.
Funcția căutată va genera valoarea maximă dintre numerele reținute în vector pe o poziție dintre i și j (inițial, i=1, j=n). Pentru aceasta,
se procedează astfel:
 dacă i=j, valoarea maxima va fi v[i];
 în caz contrar, se imparte vectorul în doi subvectori - presupunem varianta pentru paralelizare pe 2 procesoare. Se calculează
mijlocul m al intervalului [i, j]: m = (i+j) div 2. Primul subvector va conține componentele de la i la m, al doilea va conține
componentele de la (m+1) la j; se rezolvă subproblemele (aflându-se astfel maximul pentru fiecare din subvectori), iar soluția
curentă va fi dată de valoarea maximă dintre rezultatele celor două subprobleme.

#include <stdio.h>
int v[10],n;
int max(int i, int j)
{
int a, b, m;
if (i==j) return v[i];
else
{
m = (i+j)/2;
a = max(i, m);
b = max(m+1, j);
if (a>b) return a;
else return b;
}
}

int main( )
{
printf("n="); scanf(“%d”,&n);
for (int i=1; i<=n; i++)
{
printf("v[%d]=",i); scanf(“%d”,&v[i]);
}
printf("max=%d",max(1,n));
return 0; }
================================================================================================
30. Tehnica de programare Backtracking și programarea dinamică. Exemple
30.1) Tehnica de programare Backtracking
Backtracking este o metodă de parcurgere sistematică a spaţiului soluţiilor posibile al unei probleme. Este o metodă generală
de programare, şi poate fi adaptă pentru orice problemă pentru care dorim să obţinem toate soluţiile posibile, sau să selectăm o soluţie
optimă, din mulţimea soluţiilor posibile. Backtracking este însă şi cea mai costisitoare metodă din punct de vedere al timpului de
execuţie.
Forma generală a unei funcţii backtracking Implementarea recursivă a algoritmului furnizat de metoda backtracking, este mai
naturală şi deci mai uşoară. Segmentul de stivă pus la dispoziţie prin apelul funcţiei este gestionat în mod automat de sistem. Revenirea
la pasul precedent se realizează în mod natural prin închiderea nivelului de stivă.
void BK(int k) //k-poziţia din vector care se completează
{int i;
for (i=1; i<=nr_elemente_Sk; i++) //parcurge elementele mulţimii Sk
{ v[k]=i; //selectează un element din mulţime
if (validare(k)==1) //validează condiţiile de continuare ale problemei
{ if (solutie(k)==1) //verifică dacă s-a obţinut o soluţie
afisare(k); //afişează soluţia
else
BK(k+1); //reapelează functia pentru poziţia k+1
}
} //dacă nu mai există nici un element neselectat în mulţimea Sk,
} //se închide nivelul de stivă şi astfel se revine pe poziţia k-1 a
//vectorului
//execuţia funcţiei se încheie, după ce s-au închis toate nivelurile stivei, înseamnă că în vectorul v nu
mai poate fi selectat nici un elemente din multimile Sk

Exemplu - Sa consideram problema submultimilor de suma data care consta in urmatoarele: Fie A = (a1, a2, ..., an) cu ai > 0, oricare ar
fi i. Fie M inclus in R. Se cauta toate submultimile B ale lui A pentru care suma elementelor este M.
Pentru a putea realiza problema prin metoda backtracking vom reprezenta solutia sub forma x = (x1, ..., xn) unde xi = 1 daca ai inclus in
B si xi = 0 in caz contrar. Sa ne situam in ipoteza ca n = 4.
29
Castigul obtinut prin introducerea conditiilor de continuare consta in faptul ca, daca intr-un varf ele nu mai sunt verificate, se va renunta
la parcurgerea subarborelui care are ca radacina acest varf.
Acest exemplu permite prezentarea unei variante a metodei backtracking. Intr-adevar, sa consideram drept solutie posibila o valoare k
<= n impreuna cu cuplul (x1, ...,xk) unde pentru i inclus in {1, ..., k}, xi reprezinta indicele elementului pe care il introducem in B.
Evident xi este diferit de xj pentru i diferit de j. Pentru a nu se repeta solutii, vom presupune x1 < x2 < ... < xn.
Diferite variante ale metodei backtracking nu schimba esenta ei care consta in faptul ca este reprezentabila pe un arbore care este
parcurs "coborand" in arbore numai daca exista sanse de a ajunge la o solutie rezultat.
In continuare, problemele care vor fi prezentate vor urma o schema generala si anume:
- se va testa daca am obtinut o solutie, situatie in care aceasta se va retine;
- daca nu am obtinut o solutie se incearca plasarea unui nou element in vectorul solutie cu respectarea conditiilor de continuare.
- daca nu se reuseste plasarea unui nou element si spatiul valorilor posibile de plasat s-a epuizat, se revine la pozitia anterioara si se
incearca sa se plaseze pe ea un alt element.
Faptul ca dupa plasarea unui element in vectorul solutie algoritmul presupune plasarea unui element pe pozitia imediat urmatoare, adica
de fapt reluarea algoritmului, conduce posibilitatea abordarii recursive a algoritmilor de tip backtracking. Acest lucru permite o scriere
mult mai scurta si mai simpla a algoritmilor si apoi a programelor care ii implementeaza. Astfel, general, un algoritm backtracking
poate fi prezentat astfel:

pentru ficare valoare i din multimea Sk executa


xk <-i
daca X respecta conditiile interne atunci
daca X este solutie atunci
afiseaza X
altfel
apeleaza back(k+1)
sfdaca
sfdaca
sfpentru

In functie de problema concreta, in algoritmul descris mai sus se vor modifica doar instructiunea pentru, conditiile interne si cele de
solutie, structura algoritmului pastrandu-se.

PRODUS CARTEZIAN
Se dau n mulţimi, ca cele de mai jos:
S1={1,2,3,...,w1}
S2={1,2,3,...,w2}
.........................
Sn={1,2,3,...,wn}
Se cere să se gnereze produsul lor cartezian.
Exemplu:
pemtru n=3 şi urmăroarele mulţimi
S1={1,2} w1=2
S2={1,2,3} w2=3
S3={1,2} w3=2
produsul cartezian este:
S1 xS2 xS3 ={ (1,1,1),(1,1,2),(1,2,1),(1,2,2),(1,3,1),(1,3,2),
(2,2,1),(2,1,2),(2,2,1),(2,2,2),(2,3,1),(2,3,2)}
Prin urmare o soluţie este un şir de n elemente, fiecare element iєSi, cu i=1,n
Să analizăm exemplul de mai sus:
1. La pasul k selectăm un element din mulţimea Sk ={1,2,3,...,wk}.
2. Elementele unei soluţii a produsului cartezian, pot să se repete, pot fi în orice ordine, iar valori străine nu pot apare, deoarece le
selectăm doar dintre elementele mulţimii Sk . Prin urmare nu trebuie să impunem
condiţii de continuare.
3. Obţinem o soluţie în momentul în care am completat n elemente în vectorul v.Vom memora numărul de elemente al fiecăerei mulţimi
Sk , într-un vector w. Soluţiile le vom construi pe rând în vectorul v.

#include <stdio.h> // PRODUS CARTEZIAN


#define const 50
int n,v[MAX],w[MAX]; //n-nr. de mulţimi, v-vectorul soluţie, w-conţine nr. de elemente di
//fiecare mulţime Sk
void BK(int k);
void citire();
void afisare(int k);
int solutie(int k);
void main()
{citire(); //citire date
BK(1); //apelăm funcţia BK pentru selectarea primului element în v
}
void BK(int k) //funcţia backtracking
{int i;
30
for (i=1;i<=w[k];i++) //parcurgem mulţimea Sk ={1,2,3,...,wk}
{v[k]=i; //selectăm elementul i din mulţimea Sk
//nu avem funcţie de validare- nu avem condiţii de continuare
if (solutie(k)) //verificăm dacă am obţinut o soluţie
afisare(k); //afişăm soluţia
else
BK(k+1); //reapelăm funcia BK pentru completarea poziţiei următoare în
// vectorul v
} //se închide un nivel de stivă si astfel se ajunge la poziţia k-1 în v
}
void citire() //citirea datelor
{int i;
Scanf(“%d”,&n); //se citeşte numărul de mulţimi
for(i=1;i<=n;i++)
scanf(“%d”,&w[i]); //se citeşte numărul de elemente al fiecărei mulţimi
f.close();
}
int solutie(int k) //funcţia soluţie determină momentul în care se ajunge la o soluţie
{if (k==n) //obţinem o soluţie dacă am dpus în vectorul v, n elemente
return 1;
return 0;
}
void afisare(int k) //afuşează o soluţie
{int i;
for (i=1;i<=k;i++)
cout<<v[i]<<" ";
cout<<endl;
}

30.2) Programarea dinamica


Optimizarea proceselor secvenţiale se obţine prin metodele unei teorii matematice relative recent constituite şi care se numeşte
programare dinamică. Creatorul acestei teorii este Richard Bellman, iar lucrarea sa fundamentală este Dynamic Programming apărută în
anul 1957. Programarea dinamică are un câmp larg de aplicaţie în cercetarea operaţională (organizarea producţiei, gestiunea stocurilor,
reînnoirea echipamentelor), precum şi în alte domenii (navigaţie cosmică, procese cu conexiune inversă etc.).
Programarea Dinamica poate fi descompusa in urmatoarea secventa de pasi:
1. Descoperirea structurii si "masurii" pe care o are o solutie optima.
2. Determinarea unei metode de calcul recursive pentru a afla valoarea fiecarei subprobleme.
3. Calcularea "de jos in sus" a acestei valori (de la subproblemele cele mai mici la cele mai mari)
4. Reconstructia solutiei optime pornind de la rezultatele obtinute anterior.
Exemple:
1) Problemˇa: Arbore binar de cˇautare optim
O mult¸ime de chei trebuie aranjate ˆıntr-un arbore binar de cˇautare (cheile din subarborele stˆang < cheia din nod < cheile din s. drept)
Fiecare cheie are o probabilitate de aparit¸ie. (tablou double p[N];) Care e arborele binar cu nr. minim (probabilistic) de pa¸si de
cˇautare ? Fie cik costul minim pentru arborele cu cheile i..k, ¸si rik rˇadˇacina sa:
cik =Pk
j=i
pj + mini≤j≤k(ci,j−1 + cj+1,k), cii = pi
, ci,i−1 = 0 (arb. vid)
(suma Pk
j=i
pj pt. cˇa fiecare nod e cu 1 mai jos decˆat ˆın subprobleme) Completarea tabloului: ˆın ordine crescˇatoare a diferent¸ei k−i (numˇarul
de noduri), ˆın paralel cu tabloul rik pentru rˇadˇacina arborelui.
2) Problema rucsacului pentru ˆıntregi
Se dau N (tipuri de) obiecte cu dimensiuni si ¸si valori vi ˆıntregi. Care e valoarea maximˇa a unor obiecte de dimensiune totalˇa datˇa D
? Problema rucsacului are multe variante !
Dacˇa se permit fragmente de obiecte, problema are solut¸ie greedy. Dacˇa dimensiunile sunt reale, problema e NP-completˇa
(exponent¸ialˇa). Varianta 1: numˇar nelimitat de obiecte de fiecare tip
for (d = 1; d <= D; ++d) /* succesiv pt. dimensiuni crescˇatoare */
for (c[d] = i = 0; i < n; ++i) /* ^ıncearcˇa fiecare obiect i */
if (s[i] < d && (m = c[d-s[i]] + v[i]) > c[i]) c[i] = m;
Varianta 2: obiecte unice; c[d][i]: cost max. pt. dim. d, obiecte 0..i
for (d = 1; d <= D; ++d) /* succesiv pt. dimensiuni crescˇatoare */
for (i = 0; i < n; ++i) /* ^ıncearcˇa includerea obiectului i */
if (s[i] < d) c[d][i] = max(c[d][i-1], c[d-s[i]]+v[i])

31

S-ar putea să vă placă și