Marius L.
Tomescu
Algoritmi i structuri
de date n C#
Curs pentru nvmnt la distan
Algoritmi i structuri de date n C#
Algoritmi i structuri de date n C#
Cuprins
Introducere 2
Obiectivele cursului 3
1. Recursivitatea 4
1.1 Tipuri de algoritmi recursivi 4
1.2 Tipuri de funcii recursive. Eliminarea recursivitii 5
1.3 Test de autoevaluare No. 1 8
1.4 Metoda Divide et impera 9
1.5 Lucrare de verificare Nr.1 9
1.6 Bibliografie 12
2. Sortare intern 13
2.1 Sortare prin metoda bulelor (Bubble sort) 13
2.2 Sortarea prin inserie (Insertion sort) 14
2.3 Sortarea prin interclasare (Merge sort) 15
2.4 Sortarea rapid (Quicksort) 17
2.5 Testarea eficienei 20
2.6 Test de autoevaluare Nr. 2 20
2.7 Metoda Backtracking 21
2.8 Test de autoevaluare No. 3 28
2.9 Backtracking n plan 39
2.10 Lucrare de verificare Nr. 2 43
2.11 Bibliografie 44
3. Programare dinamic. Metoda Greedy 45
3.1 Metoda programrii dinamice. Prezentare general 45
3.2 Metoda Greedy 50
3.3 Test de autoevaluare No. 4 55
3.4 Liste. Liste implementate ca i tablouri 57
3.5 Liste nlnuite 60
3.6 Liste dublu nlnuite 63
3.7 Lucrare de verificare Nr. 3 67
3.8 Bibliografie 67
4. Stive i cozi 68
4.1 Stive 69
4.2 Cozi 69
4.3 Lucrare de verificare Nr. 4 75
4.4 Arbori. Noiuni generale 76
4.5 Arbori binari de cutare 77
4.6 Lucrare de verificare Nr. 5 83
4.7 Bibliografie 83
1
Algoritmi i structuri de date n C#
Introducere
Mini glosar de termeni informatici
algoritm = o secven finit de pai aranjat ntr-o ordine logic specific cu
proprietatea c, atunci cnd este executat, produce o soluie corect la o
problema dat. Exemplu: reeta culinar
algoritm calculator = un algoritm pentru care secvena de pai este executata de
un calculator
limbaj algoritmic = un limbaj cu care sunt descrii algoritmii
n vederea rezolvrii unei probleme, va trebui s detectm algoritmul de
rezolvare a ei.
n dicionarul de informatic, algoritmul este prezentat ca un concept folosit n
mod intuitiv pentru a desemna o mulime finit de operaii (instruciuni, comenzi)
cunoscute, care executate ntr-o ordine bine stabilit, pornind de la un set de
valori (intrri), produc n timp finit, un alt set de valori (ieiri).
Din aceast definiie se deduc proprietile pe care trebuie s le aib un
algoritm:
generalitate : algoritmul trebuie s se refere la o clas de probleme de un anumit
tip i s funcioneze pentru o varietate de date de intrare, nu doar pentru o
problem singular.
Ex: Algoritmul lui Euclid de determinare a c.m.m.d.c. a dou numere
funcioneaz pentru oricare dou numere naturale.
claritate : n orice moment al execuiei se cunoate cu exactitate urmtoarea
operaiune ce trebuie executat, fr ambiguiti sau neclariti.
realizabilitate : fiecare din operaiunile elementare prezente n algoritm trebuie
s poat fi executat ntr-un timp finit.
finitudine : operaiunile trebuiesc astfel concepute astfel nct algoritmul s se
termine ntr-un numr finit de pai.
Algoritmii sunt elemente de baz n domeniul informaticii, fr ei multe lucru
nefiind posibil de realizat. n prima parte a crii vom studia cteva clase de baz
de algoritmi.
n partea dou a crii vom face o scurt introducere n structurile de date, un
alt element esenial i necesar pentru a ne organiza informaiile cu care lucrm n
mod optim. Vom vedea c putem avea structuri de date liniare (ex: tablouri, liste)
sau structuri de date neliniare (ex: arbori).
2
Algoritmi i structuri de date n C#
Obiectivele cursului
Acest curs este axat pe:
- studierea conceptului de tip abstract de date i a celor mai frecvent utilizate
tipuri abstracte de date folosite n dezvoltarea aplicaiilor;
- studierea structurilor de date cu care se pot implementa aceste tipuri abstracte
de date (tablouri, liste nlnuite, arbori binari, tabele de dispersie, etc.);
- formarea deprinderilor de a proiecta i realiza aplicaii pornind de la utilizarea
tipurilor abstracte de date;
- formarea deprinderilor de a prelucra date stocate n diverse structuri de date:
tablouri, articole, string-uri, liste nlnuite, stive, cozi, tabele de dispersie,
arbori i grafuri;
- formarea deprinderilor de a compara costul alocrii statice i celei dinamice n
cazul diverselor structuri de date;
- formarea priceperilor i capacitilor de a alege structura adecvat unei
aplicaii;
- formarea abilitilor n proiectarea i implementarea algoritmilor care
prelucreaz aceste structuri de date;
- consolidarea deprinderilor de a evalua complexitatea algoritmilor.
Durata medie de studiu individual - 2 ore
3
Algoritmi i structuri de date n C#
1. Recursivitatea
Obiective:
Dup studiul acestui capitol, studentul va avea cunotine
suficiente pentru a fi capabil s neleag noiunea de
recursivitate.
Un obiect sau un fenomen este definit n mod recursiv dac n definiia sa se
face referire la el nsui.
O funcie (metod) este recursiv atunci cnd executarea ei implic cel puin
nc un apel ctre ea nsi. Pentru o funcionare corect (din punct de vedere
logic), apelul recursiv trebuie s fie condiionat de o decizie numit condiie de
oprire care, la un moment dat n cursul execuiei, s mpiedice continuarea
apelurilor recursive la infinit i s permit astfel revenirea din irul de apeluri.
Recursivitatea a fost introdus n programare n 1960, n limbajul Algol.
Dei uneori permit rezolvarea elegant a unei varieti de probleme,
algoritmii recursivi, deoarece realizeaz la fiecare autoapel salvri pe stiv,
necesit mai mult spaiu de memorie i, implicit, timp de execuie mai ndelungat.
Dac numrul de autoapeluri este mare, spaiul de memorie alocat stivei poate fi
insuficient, iar compilatorul transmite, n aceste situaii, mesajul stack overflow
(depire n stiv) i programul nu poate fi executat.
De cele mai multe ori totui, forma nerecursiv a unui algoritm este de
preferat formei recursive, fiind mai eficient din punct de vedere al timpului de
execuie i al memoriei ocupate.
n alegerea cii recursive sau nerecursive de rezolvare a unei probleme,
programatorul trebuie s stabileasc prioritile n realizarea programului,
analiznd complexitatea problemei, naturaleea exprimrii, uurina proiectrii i
testrii programului, eficiena n execuie.
1.1 Tipuri de algoritmi recursivi
Algoritmi de traversare i inversare a unei structuri
Traversarea i inversarea unei structuri nseamn efectuarea unor operaii
oarecare asupra tuturor elementelor unei structuri n ordine direct, respectiv n
ordine invers.
Dei mai uzuale sunt variantele iterative, caz n care inversarea
echivaleaz cu dou traversri directe (o salvare n stiv urmat de parcurgerea
stivei), variantele recursive sunt mai elegante i concise. Se pot aplica structurilor
de tip tablou, list, fiier i pot fi o soluie pentru diverse probleme (transformarea
unui ntreg dintr-o baz n alta, inversarea unui ir, etc).
Algoritmi care implementeaz definiii recursive
4
Algoritmi i structuri de date n C#
O definiie recursiv este cea n care un obiect se definete prin el nsui.
Definiia conine o condiie de terminare, indicnd modul de prsire a definiiei i
o parte ce precizeaz definirea recursiv propriu-zis.
Ca exemple: algoritmul lui Euclid de aflare a c.m.m.d.c., factorialul,
ridicarea la o putere ntrega (prin nmuliri repetate), definirea recursiv a unei
expresii aritmetice, curbele recursive, un mod de a privi permutrile, etc.
Algoritmi de divizare
Tehnica divizrii (Divide et impera), fundamental n elaborarea
algoritmilor, const n descompunerea unei probleme complexe n mai multe
subprobleme a cror rezolvare e mai simpl i din soluiile crora se poate
determina soluia problemei iniiale.
Exemple: gsirea minimului i maximului valorilor elementelor unui
tablou, cautarea binar, sortare Quicksort, turnurile din Hanoi, etc.
Algoritmi cu revenire (Backtracking)
Metoda se aplic problemelor n care soluia se poate reprezenta sub forma
unui vector x=(x1,x2,...xn) S=S1 x S2 x...x Sn, unde mulimile Si sunt finite, S
numindu-se spaiul soluiilor posibile. n particular, Si sunt identice avnd acelai
numr de elemente. Pentru fiecare problem concret sunt date anumite relaii
ntre componentele vectorului x, numite condiii interne.
Determinarea tuturor soluiilor rezultat se poate face genernd toate
soluiile posibile i verificnd apoi care satisfac condiiile interne, dar timpul de
calcul ar fi foarte mare.
Pe acest metod se bazeaz rezolvarea unor probleme clasice ca:
problema celor "opt regine", a "relaiilor stabile", colorarea unei hri, tierea unui
fir de lungime l n pri de lungimi date, etc. O alta gam de probleme ar fi:
"sritura calului", ieirea dintr-un labirint, etc.
Algoritmi "nlnuie i limiteaz" (Branch and Bound)
Sunt nrudii cu cei Backtracking, mai numindu-se i backtracking cu
constrngeri.
1.2 Tipuri de funcii recursive. Eliminarea recursivitii
O metod f() apeleaz direct metoda g() dac blocul care defineste f() include
un apel al lui g().
Exemplu:
f() {
g();
5
Algoritmi i structuri de date n C#
O metod f() apeleaz indirect metoda g() dac f() apeleaz direct o metod
h() i h() apeleaz direct sau indirect g().
Exemplu:
f() {
h();
}
h() {
g();
}
O metod f() este definit recursiv daca se autoapeleaz direct sau indirect.
Recursivitate liniar
Se caracterizeaz prin faptul c dou apeluri recursive ale lui f() pot apare
numai n ramificaii diferite ale aceleiai alternative.
Exemplu:
int f(int x)
{
if (x >= 3)
return 2 + f(x - 2);
else if (x == 2)
return 1 + f(x - 1);
else return 1;
}
Recursivitatea liniar repetitiv
Este un exemplu de recursivitate liniar. Apelul unei funcii de numete
simplu, dac este ultima aciune din corpul unei funcii. O funcie sau un sistem de
funcii avnd numai apeluri simple, este liniar recursiv repetitiv. Exemplu:
factorialul.
int Factorial(int nr)
{
if (nr <= 1) return nr;
else return nr * Factorial(nr - 1);
}
Recursivitate neliniar
n funciile recursiv neliniare, dou sau mai multe apeluri recursive pot
apare n aceeai ramificaie a unei alternative, iar unul din argumentele funciei
recursive este chiar funcia nsi. Acest tip de funcii recursive au tendina de
crete foarte rapid ca numr de apeluri recursive i deasemenea s aib multe
calcule care se repet (redundante). Exemplu: funcia Ackermann.
int ackerman(int m, int n)
6
Algoritmi i structuri de date n C#
{
if (m == 0) return(n+1);
else if (n == 0) return(ackerman(m-1,1));
else return(ackerman(m-1,ackerman(m,n-1)));
}
Recursivitate cascadat
n corpul funciei f() pot apare alte apeluri ale lui f(), rezultatele acestor
apeluri fiind legate de operatori. i n cadrul acestor funcii pot aprea calcule
redundante, ceea ce duce la ineficien. Exemplu: calcularea numerelor lui
Fibonacci.
int fibonacci(int n) {
if (n<=1) return n;
else return fibonacci(n-2) + fibonacci(n-1);
}
Eliminarea recursivitii liniare
Adesea, forma nerecursiv a unui algoritm este de preferat formei
recursive, fiind mai eficient din punct de vedere al timpului de execuie i al
memoriei ocupate.
Dac problema e de complexitate redus, ns se cere eficien maxim, se
va alege varianta nerecursiv. Varianta recursiv este preferat acolo unde
nlocuirea complexitii presupune tehnici de programare speciale, algoritmul
pierzndu-i naturaleea.
Algoritmul general de eliminare a recursivitii liniare :
i. se declar o stiv, care se iniializeaz ca fiind vid. Pe acest stiv
urmeaz s se salveze parametrii formali, i variabilele locale funciei
recursive.
ii. Ct timp condiia de continuare a recursivitatii e ndeplinit, se efectueaz
urmatoarele:
iii. Se salveaz pe stiv valorile actuale pentru argumentele funciei
recursive i variabilele locale
iv. Se executa instruciunile funciei recursive
v. Se modific valorile argumentelor funciei recursive
vi. Cnd condiia de continuare nu mai e ndeplinit, dac stiva nu e goal, se
aduce un set de variabile de pe stiv i se calculeaz valoarea dorit (dup
apelul funciei recursive) eventual se execut instruciunile de dup
apelul funciei recursive, apoi se trece la pasul ii.
Pseudocod: (B este condiia de continuare a recursivitii)
Stack s;
int v = 0; // vrful stivei
E1: if (B(..)) then
push valori variabile argument i variabile locale
7
Algoritmi i structuri de date n C#
executa instr inainte de apel recursiv
goto E1
E2: pop valori de pe stiva
Instructiuni dupa apel recursiv
Endif
If (sunt valori pe stiva) then goto E2;
Observaie: Stiva va fi studiat mai pe larg n capitolul 7 al acestei cri.
Test de autoevaluare No. 1
Realizai urmtoarele programe:
1. Calculul factorialului unui numr natural: n!=1*2**n.
2. irul lui Fibonacci.
3. Conversie binar: Transformarea unui numr ntreg din baza 10 n baza 2.
4. Calculai recursiv suma cifrelor unui numr natural dat cu maximum 9 cifre.
5. S se determine recursiv c.m.m.d.c. a dou numere naturale.
6. Generarea permutrilor.
7. Inversarea unui ir de caractere.
8. Funcia Ackermann.
9. S se afieze primele n puteri ale unui numr natural x dat.
10. S se calculeze valoarea radicalului unui numr real pozitiv S folosind metoda
de calcul a lui Heron, care utilizeaz iruri recursive.
8
Algoritmi i structuri de date n C#
1.4 Metoda Divide et impera
Expresia Divide et impera este atribuit n general lui Iulius Cezar, potrivit
creia adversarii (politici, militari, etc) trebuie mprii i divizai de luptele dintre
ei, fiind astfel mult mai uor de nvins sau controlat. Puterea este mparit ntre
grupuri care astfel sunt, individual, mai slabe dect unite sub aceeai strategie.
Aceast strategie de a conduce a fost atribuit de-a lungul timpului i altor
suverani, de la Louis al XI-lea pn la dinastia Habsburg. Recent, reprezint i o
strategie adaptat n economie n contextul unei piee competitive cu mai muli
juctori.
n domeniul programrii, ideea a fost introdus de Anatolii Karatsuba n
anii 1960.
Metoda Divide et impera - lat. (Divide and conquer engl.; Dezbin i
stpnete rom.) este o metod general de elaborare a algoritmilor i const n
mprirea repetat a unei probleme n dou sau mai multe subprobleme de acelai
tip n mod recursiv, pn cnd problemele sunt suficient de simple pentru a
permite o rezolvare imediat, iar soluia problemei iniiale se obine prin
combinarea soluiilor subproblemelor.
Etapele de rezolvare ale unei probleme folosind aceasta tehnica sunt:
1) divide: problema iniial este descompus ntr-un numr de k subprobleme de
aceeai natur ns de dimensiuni mai mici. Procedeul continu recursiv pn
cnd se ajunge la probleme de dimensiuni elementare (n = 1, 2).
2) impera: problemele de dimensiuni elementare sunt rezolvate direct, iar soluiile
lor sunt combinate pentru a obine soluiile subproblemelor de dimensiuni mai
mari inclusiv problema iniial.
Observaie: Prin modalitatea de descompunere i rezolvare a subproblemelor,
tehnica este adaptat pentru execuie multi-procesor sau multi-main ntruct
subproblemele distincte pot fi rezolvate distribuit i independent, soluiile lor fiind
apoi combinate.
2.2 Lucrare de verificare Nr.1
Realizai urmtoarele programe:
1. Turnurile din Hanoi
Acesta este un vechi joc, foarte popular, care se preteaz perfect pentru a
exemplifica recursivitatea i metoda Divide et impera.
Fie trei tije verticale notate A, B, C. Pe tija A se gsesc aezate n discuri de
diametre diferite, n ordinea cresctoare a diametrelor, privind de sus n jos.
Iniial, tijele B i C sunt goale. S se afieze toate mutrile prin care discurile de
9
Algoritmi i structuri de date n C#
pe tija A se mut pe tija B, n aceeai ordine, folosind ca tij de manevra C i
respectnd urmtoarele reguli:
la fiecare pas se mut un singur disc;
un disc se poate aeza numai peste un disc cu diametrul mai mare.
Modul recursiv de rezolvare a acestei probleme (respectnd cerinele de mai
sus) este urmtorul:
- Se mut n-1 discuri pe tija A pe tija C, folosind tija B ca tij de manevra
- Se mut un disc de pe tija A pe tija B
- Se mut n-1 discuri pe tija C pe tija B, folosind tija A ca tij de manevra
A C B
Program pseudocod
metoda mut(n, a, b, c)
// a = sursa
// b = destinatia
// c = intermediar
if (n = 1) then print(a, ->, b);
else
mut(n-1, a, c, b);
print(a, ->, b);
mut(n-1, c, b, a);
end
Program
using System;
namespace HanoiTowers
{
class HanoiTowers
{
public static void Hanoi(int numDisks, int start, int
temp, int end)
{
if (numDisks == 1)
{
[Link]("muta disc de pe " + start + "
pe " + end);
}
else
{
//muta n-1 discuri de pe start pe temp folosindu-
se de end
Hanoi(n - 1, start, end, temp);
//muta 1 disc de pe start pe end
10
Algoritmi i structuri de date n C#
[Link]("muta disc de pe " + start + "
pe " + end);
//muta n-1 discuri de pe temp pe end folosindu-se
de start
Hanoi(n - 1, temp, start, end);
}
}
static void Main()
{
int nr_discuri;
[Link]("Dati numarul de discuri: ");
nr_discuri = [Link]([Link]());
Hanoi(nr_discuri, 1, 2, 3);
}
}
}
2. Aflarea valorii minime/maxime dintr-un ir de n valori.
Rezolvarea este similar aflrii c.m.m.d.c. dintr-un ir de n valori i este propus
ca i exerciiu.
3. Cutarea binar a unei valori ntr-un ir sortat
Alturi de sortare, cutarea unui element este i ea o operaie de baz.
Cutarea unui element x ntr-un ir de n elemente poate fi fcut n timp liniar
parcurgnd fiecare element din ir i comparndu-l cu valoarea cautat. Cazul cel
mai defavorbil pentru acest algoritm are loc atunci cnd elementul x nu este gsit,
fiind necesare n comparaii cu fiecare element din ir.
Cunoscnd faptul c valorile irului sunt deja sortate n ordine cresctoare,
putem aplica principiul metodei Divide et Impera pentru a obine un algoritm mai
rapid.
S efectuam de exemplu prima comparaie ntre elementul x pe care l cutam
i valoarea aflat la mijlocul irului a: a[(n-1)/2]. Dac x < a[(n-1)/2] atunci este
evident c nu mai este necesar s comparam pe x cu elementele aflate n ir dup
poziia (n-1)/2 ntruct toate aceste elemente vor fi mai mari dect x. Lum astfel
decizia de a cuta valoarea x n prima jumtate a irului a, eliminnd astfel
jumatate din comparaii pe care primul algoritm le-ar fi efectuat. Dac x ar fi fost
mai mare dect valoarea de la mijlocul irului a[(n-1)/2], atunci am fi luat decizia
cutarii lui x n a doua jumtate a irului a.
Oricare jumtate am alege-o, repetm acelai principiu: comparm elementul
x cu valoarea aflat la mijlocul subirului. Reducem astfel problema (etapa
divide) la subprobleme de dimensiuni mai mici (jumtate din dimensiunea
problemei iniiale) pn cnd:
1) gsim condiia de egalitate dintre x i valoarea de la mijlocul subirului curent
pe care l procesm (etapa impera)
2) nu mai putem divide irul n continuare ntruct am ajuns la subproblema n = 1.
Program
class Program
11
Algoritmi i structuri de date n C#
{
static int cautareBinara(int[] vect, int val, int stanga, int
dreapta)
{
int mijloc;
if (stanga < dreapta)
{
mijloc = (stanga + dreapta) / 2;
if (vect[mijloc] == val)
return mijloc;
else if (val > vect[mijloc])
return cautareBinara(vect, val, mijloc + 1,
dreapta);
else
return cautareBinara(vect, val, stanga, mijloc);
}
return -1;
}
static void Main(string[] args)
{
int[] a = { 1, 3, 5, 7, 9, 11, 13, 15, 16, 18, 20};
[Link]("Dati valoarea de cautat: ");
int x = [Link]([Link]());
int poz = cautareBinara(a, x, 0, [Link]);
if (poz >= 0)
[Link]("Valoarea {0} a fost gasita pe poz
{1}", x, poz+1);
else
[Link]("Valoarea {0} nu a fost gasita", x);
}
}
2.3 Bibliografie
1. T.H. Cormen, [Link], R.R. Rivest Introducere n algoritmi, Mit Press
1990, trad. Computer Libris Agora.
2. V. Cretu Structuri de date i algoritmi, vol. 1, ed. Orizonturi Universitare,
2000.
3. D. Lucanu, M. Craus; Proiectarea algoritmilor, Ed. Polirom, 2008.
4. C. Giumale, L. Negreanu, S. Calinoiu Proiectarea i analiza algoritmilor.
Algoritmi de sortare, 1996.
12
Algoritmi i structuri de date n C#
2. Sortare intern
Obiective:
Dup studiul acestui capitol, studentul va avea cunotine
suficiente pentru a fi capabil s neleag urmtoarele metode
de sortare intern: metoda bulelor, sortarea prin inserie,
sortarea prin interclasare i sortarea rapid.
Sortarea nseamn aranjarea unei liste de obiecte dup o relaie de ordine
dat (ex.: pentru numere, ordine lexicografic pentru iruri, etc.). Sortarea
reprezint una din clasele cele mai fundamentale i studiate de algoritmi [D.
Knuth - Tratat de programare a calculatoarelor. Vol. 3: Sortare i cutare].
Sortarea exist peste tot n lumea real: ordinea cuvintelor n dicionar,
ordinea numelor n cartea de telefon, etc.
Exist dou tipuri distincte de sortare i anume: sortare intern (n memorie)
sau extern (folosind fiiere). Acest capitol va trata doar sortarea intern.
n general, principalele operaii la sortare sunt: compararea i
interschimbarea.
Chiar dac problema sortrii unei liste de elemente pare trivial, ea a fost i
este cercetat n mod foarte serios. Ca rezultat al acestei cercetri s-au elaborat
mai muli algoritmi de sortare. Primii dintre ei dateaz de prin anii '50 (Bubble
sort 1956), iar cei mai receni au aprut cu puini ani n urm (Library sort
2004). O asemenea activitate de cercetare este pe deplin justificat deoarece pe de
o parte rezolvarea n mod eficient a problemei sortrii nu este deloc simpl, iar pe
de alt parte sortarea este o operaie foarte des folosit i este necesar ca ea s se
efectueze ntr-un mod ct se poate de eficient.
2.1 Sortare prin metoda bulelor (Bubble sort)
Aceasta metod este printre cele mai ncete, ns este n acelai timp printre
cele mai uor de neles i implementat. Denumirea metodei vine de la modul de
funcionare al ei, i anume, (pentru o sortare cresctoare) la fiecare parcurgere a
irului, cea mai mare valoare va fi dus pe ultima poziie, procedeul putnd fi
comparat cu nite bule care se ridic la suprafa.
Algoritmul de sortare prin metoda bulelor functioneaz conform urmtorului
principiu:
- avem irul a care trebuie sortat de la [0.. n]
- vom parcurge irul de la capt spre nceput cu ajutorul indicelui i
- pentru fiecare i, vom parcurge cu ajutorul indicelui j elementele [1..i],
comparnd pentru fiecare j, a[j-1] cu a[j] i dac a[j-1] > a[j], elementele
se schimb ntre ele. Astfel, la finalul parcurgerii secvenei, cea mai
mare valoare se va afla pe ultima poziie
- dup ce a fost parcurs ntreg irul, acesta va fi sortat cresctor
13
Algoritmi i structuri de date n C#
Exemplu: Se d spre ordonare irul: 38 27 43 3 9 82 10
38 27 43 3 9 82 10
27 38 3 9 43 10 82
27 3 9 38 10 43 82
3 9 27 10 38 43 82
3 9 10 27 38 43 82
Aceast metod cunoate i o variant mbuntit, numit Shake Sort, care
parcurge irul dinspre ambele capete, astfel c la o parcurgere a unui ir de valori,
cea mai mic valoare este adus pe prima poziie i cea mai mare pe ultima
poziie.
Exist de asemenea o metod foarte asemntoare lui Bubble sort, numit
Selection sort care const n parcurgerea irului de la nceput spre sfrit, i la
fiecare parcurgere, valoarea minim este adus pe prima poziie.
Program
public static void bubbleSort(int[] numbers)
{
int i, j, temp;
int array_size = [Link];
for (i = (array_size - 1); i >= 1; i--)
for (j = 1; j <= i; j++)
if (numbers[j - 1] > numbers[j])
{
temp = numbers[j - 1];
numbers[j - 1] = numbers[j];
numbers[j] = temp;
}
}
2.2 Sortarea prin inserie (Insertion sort)
Algoritmul de sortare prin insertie functioneaz conform urmtorului
principiu:
- avem irul a care trebuie sortat de la [0..n]
- subirul alctuit dintr-un singur element a[0] care se consider sortat. Cu
ajutorul lui i vom parcurge irul de la [1..n], iar cu ajutorul lui j vom
parcurge irul de la [i..0]
- presupunnd c elementele subirului a de la [0..j-1] sunt deja sortate n
ordine cresctoare, urmatorul element a[j] va fi inserat la poziia corect
n subsirul [0..j-1] astfel nct n final elementele subirului a[0..j] s fie
de asemenea sortate cresctor
- inserarea elementului a[j] se realizeaz cutnd locul su n subirul
sortat i deplasnd la dreapta cu o poziie toate elementele mai mari
dect el.
14
Algoritmi i structuri de date n C#
- dup ce toate valorile au fost plasate la locul corespunztor, irul va fi
sortat cresctor
Exemplu: Se d spre sortare irul: 5 2 4 6 1 3
5 2 4 6 1 3
2 5 4 6 1 3
2 4 5 6 1 3
2 4 5 6 1 3
1 2 4 5 6 3
1 2 3 4 5 6
Program
public static void insertionSort(int[] numbers)
{
int i, j, aux;
int array_size = [Link];
for (i = 1; i < array_size; i++)
{
aux = numbers[i];
j = i;
while ((j > 0) && (numbers[j - 1] > aux))
{
numbers[j] = numbers[j - 1];
j = j - 1;
}
numbers[j] = aux;
}
}
2.3 Sortarea prin interclasare (Merge sort)
Aceast metod de sortare este mai complex i se folosete de tehnica
Divide et impera.
Se d spre sortare un ir de n valori. Algortimul sortarii prin interclasare
este:
- pasul divide:
mparte irul de n elemente care urmeaz a fi sortat n 2 subiruri
de n/2 elemente pn cnd se vor obine subiruri cu 1 element
- pasul impera:
sorteaz recursiv cele 2 subiruri utiliznd sortarea prin
interclasare
- pasul combina:
interclaseaz cele 2 subiruri sortate pentru a produce rezultatul
final
15
Algoritmi i structuri de date n C#
Exemplu: Se d spre ordonare irul: 38 27 43 3 9 82 10
38 27 43 3 9 82 10
38 27 43 3 9 82 10
38 27 43 3 9 82 10
38 27 43 3 9 82 10
38 27 43 3 9 10 82
27 38 43 3 9 10 82
3 9 10 27 38 43 82
Algoritmul n pseudocod
{
Sorteaza_prin_interclasare(A,p,r)
daca p<r atunci q = partea intreaga din (p+r)/2
sorteaza_prin_interclasare(A,p,q)
sorteaza_prin_interclasare(A,q+1,r)
interclaseaza(A,p,r)
}
Algoritmul de interclasare a 2 vectori sortai
Pentru a interclasa 2 vectori deja sortati crescator a i b i vom parcurge n
acelai timp folosind indicii i pentru a i j pentru b. Vectorul c (rezultatul) este
iniial gol. Algoritmul de completare al vectorului c este urmatorul:
- dac a[i] < b[j] atunci l vom adauga pe a[i] n vectorul c i vom
incrementa i
- daca a[i] >= b[j] atunci l vom adauga pe b[j] n vectorul c i vom
incrementa j
- dac se depun toate elementele din a n c, iar b nc mai are elemente,
acestea se copiaz ca atare la sfritul lui c
- dac se depun toate elementele din b n c, iar a nc mai are elemente,
acestea se copiaz ca atare la sfritul lui c
- la final, vectorul c va conine toate elementele vectorilor a i b, ordonate
cresctor
n cadrul sortrii prin interclasare, se vor interclasa dou buci din vectorul
a (poriunile [st..m] i [m+1..dr]), iar rezultatul se depune n vectorul b, dup care,
la final, vectorul b este copiat napoi n a, peste poziiile [st..dr].
Exemplu:
A (n=4): 1,3,7,9 i
B (m=3): 2,7,10 j
C (n+m=7): 1,2,3,7,7,9,10
16
Algoritmi i structuri de date n C#
Program
class SortareInterclasare
{
static int[] a = { 26, 5, 37, 1, 61, 11, 59, 15, 48, 19 };
static void interclas(int st, int m, int dr)
{
int[] b = new int[100];
int x = st;
int k = 1;
int y = m + 1;
while (x <= m && y <= dr)
if (a[x] < a[y])
b[k++] = a[x++];
else
b[k++] = a[y++];
while (x <= m)
b[k++] = a[x++];
while (y <= dr)
b[k++] = a[y++];
int t = st;
for (k = 1; k <= (dr - st) + 1; k++)
a[t++] = b[k];
}
static void sortInterclasare(int st, int dr)
{
if (st < dr)
{
int m = (st + dr) / 2;
sortInterclasare (st, m);
sortInterclasare (m + 1, dr);
interclas(st, m, dr);
}
}
static void Main(string[] args)
{
sortInterclasare (0, [Link]-1);
for (int i = 0; i < [Link]; i++)
[Link]("{0} ",a[i]);
}
}
3.4 Sortarea rapid (Quicksort)
Dintre algoritmii prezentai aici, acesta este cel mai rapid n special pentru
volume mari de date, ns n aceeai msur este i cel mai complex dintre ei,
folosindu-se i el de tehnica Divide et impera.
Algoritmul sortrii rapide, varianta recursiv este:
17
Algoritmi i structuri de date n C#
1. Se alege o valoare pivot. Se ia valoarea elementului din mijloc ca valoare
pivot, dar poate fi orice alt valoare, care este n intervalul valorilor sortate, chiar
dac nu este prezent n tablou.
2. Partiionare. Se rearanjeaz elementele n aa fel nct toate elementele care
sunt mai mici dect pivotul merg n partea stng a pivotului i toate elementele
care sunt mai mari dect pivotul merg n partea dreapt. Valorile egale cu pivotul
pot sta n orice parte a tabloului. n plus, tabloul poate fi mprit n prti care nu
au aceeai dimensiune (nu sunt egale).
3. Se sorteaz amndou prile. Se aplic recursiv algoritmul de sortare rapid
n partea stng i n partea dreapt.
Algoritmul de partiie n detaliu
- Exist 2 indici i i j, iar la inceputul algoritmului de partiionare i indic primul
element din tablou, iar j indic ultimul element din tablou.
- La pasul urmtor algoritmul mut i nainte, pn cnd un element cu o valoare
mai mare sau egal cu pivotul este gsit. Indicele j este mutat napoi, pn cnd
un element cu o valoare mai mic sau egal cu pivotul este gsit. Daca i <= j
atunci i merge pe pozitia (i + 1), iar j merge pe pozitia (j - 1). Cu alte cuvinte, se
compar elementul i cu elementul j i dac nu este necesar interschimbarea, j
merge pe pozitia (j - 1), repetndu-se procesul. Dac apare o interschimbare, i
merge pe pozitia (i + 1) i se continu compararea mrind i pn la apariia unei
alte interschimbri. Apoi, se micoreaz din nou j, continundu-se n acelai mod.
- Algoritmul se opreste cand i > j.
- Dupa partiie, toate valorile dinaintea celui de-al i-lea element sunt mai mici
sau egale cu pivotul i toate valorile de dup cel de-al j-lea element sunt mai mari
sau egale cu pivotul.
Exemplu: S sortam irul 1, 12, 5, 26, 7, 14, 3, 7, 2
1, 12, 5, 26, 7, 14, 3, 7, 2 - nesortat
1, 12, 5, 26, 7 , 14, 3, 7, 2 - valoarea pivot = 7
1, 12, 5, 26, 7 , 14, 3, 7, 2 - 12 >= 7 >= 2, interschimbam 12 cu 2
1, 2, 5, 26, 7 , 14, 3, 7, 12 - 26 >= 7 >= 7 , interschimbam 26 cu 7
1, 2, 5, 7, 7 , 14, 3, 26, 12 - 7 >= 7 >= 3, interschimbam 7 cu 3
1, 2, 5, 7, 3, 14, 7 , 26, 12 - i > j, se opreste partitionarea
se aplic din nou algoritmul pentru 1, 2, 5, 7, 3 si 14, 7, 26, 12
Se obine: 1, 2, 5, 7, 7, 12, 14, 26 - sortat
Program
public static void q_sort(int[] numbers, int left, int right)
{
int pivot, l_hold, r_hold;
l_hold = left;
r_hold = right;
18
Algoritmi i structuri de date n C#
pivot = numbers[left];
while (left < right)
{
while ((numbers[right] >= pivot) && (left < right))
right--;
if (left != right)
{
numbers[left] = numbers[right];
left++;
}
while ((numbers[left] <= pivot) && (left < right))
left++;
if (left != right)
{
numbers[right] = numbers[left];
right--;
}
}
numbers[left] = pivot;
pivot = left;
left = l_hold;
right = r_hold;
if (left < pivot)
q_sort(numbers, left, pivot - 1);
if (right > pivot)
q_sort(numbers, pivot + 1, right);
}
public static void quickSort(int[] numbers)
{
q_sort(numbers, 0, [Link] - 1);
}
Varianta 2:
void quickSort(int numbers[], int left, int right) {
int i = left, j = right;
int tmp;
int pivot = numbers[(left + right) / 2];
while (i <= j) {
while (numbers[i] < pivot)
i++;
while (numbers[j] > pivot)
j--;
if (i <= j) {
tmp = numbers[i];
numbers[i] =numbers[j];
numbers[j] = tmp;
i++;
j--;
}
}
if (left < j)
quickSort(numbers, left, j);
if (i < right)
19
Algoritmi i structuri de date n C#
quickSort(numbers, i, right);
}
2.5 Testarea eficienei
Cu urmtorul cod se pot compara diversele metode de sortare intern, prin
msurarea timpului necesar sortrii unei mulimi suficient de mari de valori,
pentru ca rezultatul s fie ct mai elocvent. Se declar un vector de 25000 de
valori ntregi cuprinse ntre 0-1000, generate aleator cruia i se aplic pe rnd
diverse metode de sortare.
static void Main(string[] args)
{
Random rnd = new Random();
int[] x = new int[25000];
for (int i = 0; i < [Link]; i++)
x[i] = [Link](0, 1000);
DateTime initial = [Link];
//bubbleSort(x);
//insertionSort(x);
//sortInterclasare(0,[Link]-1);
quickSort(x);
DateTime final = [Link];
[Link](final - initial);
}
2.6 Test de autoevaluare Nr. 2
1. S se testeze eficiena fiecrui algoritm de sortare pe o mulime de 1000
cuvinte generate automat.
20
Algoritmi i structuri de date n C#
2.7 Metoda Backtracking
Aceast tehnic se folosete n rezolvarea problemelor care ndeplinesc
simultan urmtoarele condiii:
- soluia lor poate fi pus sub forma unui vector S=x1,x2, ...,xn, cu x1 A1,
x2 A2 ,,xn An
- mulimile A1, A2 , ., An sunt mulimi finite, iar elementele lor se
consider c se afl ntr-o relaie de ordine bine stabilit;
- nu se dispune de o alt metod de rezolvare mai rapid
- x1 x2 , xn pot fi la rndul lor vectori;
- A1, A2 , An pot coincide.
La ntlnirea unei astfel de probleme, dac nu cunoatem aceast tehnic,
suntem tentai s generm toate elementele produsului cartezian A1xA2xxAn i
fiecare element s fie testat dac este soluie. Rezolvnd problema n acest mod,
timpul de execuie este att de mare, nct poate fi considerat infinit, algoritmul
neavnd nici o valoare practic.
De exemplu, dac dorim s generm toate permutrile unei mulimi finite A,
nu are rost s generm produsul cartezian AxAx.....xA, pentru ca apoi s testm,
pentru fiecare element al acestuia, dac este sau nu permutare (nu are rost de
exemplu s generm 1,1,1,...,1, pentru ca apoi s constatm c nu am obinut o
permutare, cnd de la a doua cifr 1 ne puteam da seama c cifrele nu sunt
distincte).
Tehnica Backtracking are la baz un principiu extrem de simplu:
- se construiete soluia pas cu pas: x1, x2 ,xn
- dac se constat c, pentru o valoare aleas, nu avem cum s ajungem la
soluie, se renun la acea valoare i se reia cutarea din punctul n care am rmas.
Paii care se parcurg pentru obinerea soluiilor unei probleme sunt:
- se alege primul element x1, ce aparine lui A1;
- presupunnd generate elementele x1,x2 ,xk , aparinnd mulimilor A1,
A2 ,Ak , se alege (dac exist) xk+1 , primul element disponibil din mulimea
Ak+1. Aici apar dou posibiliti :
1) Nu s-a gsit un astfel de element, caz n care caz n care se reia cutarea
considernd generate elementele x1,x2 ,xk-1, i se ncearc urmtorul element al
mulimii Ak rmas netestat;
2) A fost gsit, caz n care se testeaz dac acesta ndeplinete anumite
condiii de continuare aprnd astfel dou posibiliti:
ndeplinete, caz n care se testeaz dac s-a ajuns la soluie i apar din
nou dou posibiliti:
21
Algoritmi i structuri de date n C#
- s-a ajuns la soluie, se tiprete soluia i se reia algoritmul considernd
generate elementele x1,x2 ,xk i se caut n continuare un alt element al mulimii
Ak+1 rmas netestat;
- nu s-a ajuns la soluie, caz n care se reia algoritmul considernd
generate elementele x1,x2,,xk+1 i se caut un prim element xk+2 Ak+2.
nu le ndeplinete, caz n care se reia algoritmul considernd generate
elementele x1,x2,,xk, iar elementul xk+1 se caut ntre elementele mulimii Ak+1,
rmase netestate.
Algoritmul se termin atunci cnd nu exist nici un element x1 A1
netestat.
Observaie: Tehnica Backtracking are ca rezultat obinerea tuturor soluiilor
problemei. n cazul n care se cere o sigur soluie se poate fora oprirea, atunci
cnd a fost gsit.
Stiva este acea form de organizare a datelor (structur de date) cu
proprietatea c operaiile de introducere i scoatere a datelor se fac numai n
vrful ei.
Stivele se pot simula utiliznd vectori i le vom trata mai pe larg n capitolul
7 al acestei cri.
Exemplificm n continuare modul de lucru cu stiva:
A n stiva iniial vid se introduce litera A, vrful stivei va fi la nivelul 1
(k=1);
B
A introducem n stiv litera B, deci k va lua valoarea 2;
scoatem din stiv pe B (A nu poate fi scos); k=1
A
scoatem din stiv pe A; stiva rmne vid (k=0)
Practic la scoaterea unei variabile din stiv, scade cu 1 valoarea variabilei ce
indic vrful stivei, iar atunci cnd scriem ceva n stiv, acea valoare crete cu 1.
Pe un anumit nivel se reine, de regul, o singur informaie (liter sau
cifr), ns este posibil, aa cum va rezulta din exemplele prezentate n continuare,
s avem mai multe informaii, caz n care avem de a face cu stive duble, triple,
etc.
Ca observaie, ntreaga teorie a recursivitii se bazeaz pe structura de tip
stiv.
Vom considera c generarea soluiilor unei probleme folosind tehnica
Backtracking se face ntr-o stiv. Astfel, x1 A1, se va gsi pe primul nivel al
stivei, x2 A2 se va gsi pe al doilea nivel al stivei,..., xk Ak se va gsi pe
nivelul k al stivei. n acest fel, stiva (notat ST) va arta astfel:
22
Algoritmi i structuri de date n C#
...
xk
ST
x2
x1
Implementarea propriu-zis a tehnicii Backtracking se poate ntr-o
multitudine de moduri, att nerecursive ct i recursive. Vom prezenta n cele ce
urmeaz o variant care folosete cte o metod separat pentru fiecare din paii
de baz care se parcurg n cutarea soluiilor.
Nivelul k+1 al stivei trebuie iniializat (pentru a alege, n ordine, elementele
mulimii k+1). Iniializarea trebuie fcut cu o valoare aflat (n relaia de ordine
considerat, pentru mulimea Ak+1) naintea tuturor valorilor posibile din mulime.
De exemplu, pentru generarea permutrilor mulimii {1,2.....n}, orice nivel al
stivei va lua valori de la 1 la n, iar iniializarea unui nivel (oarecare) se face cu
valoarea 0. Metoda de iniializare o vom numi INIT i va avea un parametru: k -
nivelul care trebuie iniializat.
Gsirea urmtorului element al mulimii Ak (element care a fost netestat) se
face cu ajutorul metodei SUCCESOR (ST,K) de tip boolean. n situaia n care
am gsit elementul, acesta este pus n stiv i metoda returneaz TRUE, contrar
(nu a rmas un element netestat) metoda returneaz FALSE.
Odat ales un element, trebuie vzut dac acesta ndeplinete condiiile de
continuare (altfel spus, dac elementul este valid). Acest test se face cu ajutorul
metodei de tip boolean VALID(ST,K).
Testul dac s-a ajuns sau nu la o soluie se face cu ajutorul metodei de tip
boolean SOLUIE(K), iar o soluie se tiprete cu ajutorul metodei TIPAR().
n varianta puin simplificat, se poate renuna la metodele INIT i
SOLUIE(K), ele putnd fi scrise direct ca i linii de cod n rutina Backtracking.
Paii enunai mai sus i metodele vor fi pue mpreun n urmtorul mod:
Rutina Backtracking
K=1;
INIT(K);
while K>0
while SUCCESOR (ST, K)
if VALID(ST,K)
if SOLUIE(K) then TIPAR()
else
K:=K+l;
INIT (K);
K=K-1
23
Algoritmi i structuri de date n C#
S explicm n cuvinte cele de mai sus:
- iniial nivelul curent K din stiv este setat pe 1 i se iniializeaz.
- atta timp ct K>0
atta timp ct mai sunt elemente netestate pe nivelul K
dac a fost gsit un element netestat i valid
- se adaug acest element la soluia parial i: dac
se ajunge la o soluie a problemei este tiprit soluia, altfel,
K crete i se iniializeaz noul nivel curent.
cnd nu mai exist valori netestate pe nivelul K, se scade K cu 1
- cnd K=0 algoritmul se ncheie.
Observaii:
1) Problemele rezolvate prin aceast metod necesit un timp ndelungat de
execuie. Din acest motiv este bine s utilizm metoda atunci numai atunci cnd
nu mai avem la dispoziie un alt algoritm mai eficient.
2) Rezolvarea iterativ ncalc principiul de baz al stivei atunci cnd
verificm condiiile de continuare, sau atunci cnd tiprim soluia gsit, pentru c
accesm orice nivel al stivei.
Exemplu detaliat de rezolvare a unei probleme folosind Backtracking:
Problema celor n dame. Fiind dat o tabl de ah de dimensiune nn se cer toate
soluiile de aranjare a n dame, astfel nct s nu se afle dou dame pe aceeai linie,
coloan sau diagonal (damele s nu se atace reciproc).
Exemplu: Presupunnd c dispunem de o tabl de dimensiune 4x4 i ncercm s
generm o soluie a problemei:
Plasm prima dam pe linia 1, coloana 1. Nu am ajuns nc la soluie.
D
Trecem la linia 2 i constatm c, coloanele 1 i 2 nu sunt valide, deoarece
damele s-ar ataca. Astfel, a doua dam nu poate fi aezat dect n coloana 3. Nu
am ajuns nc la soluie.
D
D
Trecem la linia 3 i constatm c niciuna din cele 4 coloane nu este valid,
deoarece damele s-ar ataca.
Revenim la linia 2 i plasm dama n coloana valid 4. Nu am ajuns nc la
soluie.
24
Algoritmi i structuri de date n C#
D
D
Trecem la linia 3 i plasm dama n coloana valid 2. Nu am ajuns nc la
soluie.
D
D
D
Trecem la linia 4 i constatm c niciuna din cele 4 coloane nu este valid.
n aceast situaie dama a patra nu mai poate fi aezat.
Revenim la linia 3 unde nu mai avem poziii valide.
Revenim la linia 2 unde deasemenea nu mai avem poziii valide netestate.
Revenim la linia 1 i plasm dama n coloana 2. Nu am ajuns nc la soluie.
D
Trecem la linia 2 unde constatm c dama nu poate fi aezat dect n
coloana 4. Nu am ajuns nc la soluie.
D
D
Trecem la linia 3 unde constatm c dama nu poate fi aezat dect n
coloana 1. Nu am ajuns nc la soluie.
D
D
D
Trecem la linia 4 unde constatm c dama nu poate fi aezat dect n
coloana 3. Am obinut o soluie i o tiprim.
25
Algoritmi i structuri de date n C#
D
D
D
D
Algoritmul continu n acest mod pn cnd trebuie scoas de pe tabl
prima dam, nemai avnd poziii valide netestate pentru ea.
Pentru reprezentarea unei soluii putem folosi un vector cu n componente
(avnd n vedere c pe fiecare linie se gsete o singur dam).
Exemplu pentru soluia gsit avem vectorul ST ce poate fi asimilat unei
stive. Nivelul stivei va reprezenta linia, iar valoarea din stiv va reprezenta
coloana pe care se afl o dam.
Dou dame se gsesc pe aceeai diagonal dac i numai dac este
ndeplinit condiia:
|st(i)-st(j)|=|i-j| (diferena, n modul, ntre linii i coloane este aceeai).
3
ST(4)
1
ST(3) ST(i)=k semnific faptul c pe linia i dama ocup poziia k
4
ST(2)
2
ST(1)
Exemplu de dame care se atac: n tabla 4 x4 avem situaia:
D ST(1)= 1 i = 1
ST(3)= 3 j = 3
D |ST(1) - ST(3)| = |1 3| = 2
|i j| = |1 3| = 2
sau situaia
D ST(1) = 3 i = 1
ST(3) = 1 j = 3
D |ST(i) - ST(j)| = |3 1| = 2
|i j| = |1 3| = 2
ntruct dou dame nu se pot gsi n aceeai coloan, rezult c o soluie
este sub form de permutare. O prim idee ne conduce la generarea tuturor
permutrilor i la extragerea soluiilor pentru problema ca dou dame s nu fie
plasate n aceeai diagonal. A proceda astfel, nseamn c lucrm conform
strategiei Backtracking. Aceasta presupune ca imediat ce am gsit dou dame
care se atac, s relum cutarea.
Semnificaia metodelor utilizate este urmtoarea:
26
Algoritmi i structuri de date n C#
INIT - nivelul k al stivei este iniializat cu 0;
SUCCESOR - mrete cu 1 valoarea aflat pe nivelul k al stivei n situaia
n care aceasta este mai mic dect n i returneaz valoarea TRUE, n caz contrar,
returneaz valoarea FALSE;
VALID - valideaz valoarea pus pe nivelul k al stivei, verificnd dac nu
avem dou dame pe aceeai linie (ST(k)=ST(i)), sau dac nu avem dou dame pe
aceeai diagonal
(ST(k)-ST(i)=|k-i|) caz n care metoda va returna valoarea FALSE; n caz contrar,
metoda va returna valoarea TRUE;
SOLUIE - verific dac stiva a fost completat pn la nivelul n inclusiv;
TIPAR - tiprete o soluie.
Program
class Dame
{
static int[] ST;
static int n;
static void INIT(int k)
{
ST[k] = 0;
}
static bool SUCCESOR(int[] ST, int k)
{
if (ST[k] < n)
{
ST[k]++;
return true;
}
return false;
}
static bool VALID(int[] ST, int k)
{
int i;
for (i = 1; i < k; i++)
if ((ST[i]==ST[k])||([Link](i-
k)==[Link](ST[i]-ST[k])))
return false;
return true;
}
static void TIPAR()
{
int i, j;
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
if (j != ST[i])
[Link]("+");
else
[Link]("D");
[Link]("\n");
}
[Link]("\n");
}
static bool SOLUTIE(int k)
{
27
Algoritmi i structuri de date n C#
if (k == n) return true;
else return false;
}
static void Main(string[] args)
{
int k = 1;
[Link]("Dati n:");
n = [Link]([Link]());
ST = new int[n + 1];
INIT(k);
while (k > 0)
{
while (SUCCESOR(ST, k))
if (VALID(ST, k))
if (SOLUTIE(k)) TIPAR();
else ST[++k] = 0;
k--;
}
}
}
2.8 Test de autoevaluare No. 3
Realizai urmtoarele programe:
1. Generarea permutrilor. Se citete un numr natural n. S se genereze toate
permutrile mulimii {1, 2, 3, ,n}.
Generarea permutrilor se va face innd cont c orice permutare va fi
alctuit din elemente distincte ale mulimii A. Din acest motiv, la generarea unei
permutri, vom urmri ca numerele s fie distincte.
Prezentm algoritmul corespunztor cazului n=3:
1 2 3
1 2 2 2 2
1 1 1 1 1 1
1 2 3
3 3 3 3 1
1 1 1 1 2 2
1 2 3 1
1 1 1 2 3 3
2 2 2 2 2 2
se ncarc n stiv pe nivelul 1 valoarea 1. Nu am ajuns la soluie;
ncrcarea valorii 1 pe nivelul al 2-lea nu este posibil, ntruct aceast
valoare se gsete i pe nivelul 1 al stivei;
ncrcarea valorii 2 pe nivelul al 2-lea este posibil, deoarece aceast
valoare nu mai este ntlnit. Nu am ajuns la soluie;
valoarea 1 din nivelul al 3-lea se regsete pe nivelul 1;
28
Algoritmi i structuri de date n C#
valoarea 2 din nivelul al 3-lea se regsete pe nivelul al 2-lea;
valoarea 3 pe nivelul al 3-lea nu e ntlnit pe nivelurile anterioare;
ntruct nivelul 3 este completat corect. Am gsit o soluie. Tiprim: 1
23
Algoritmul continu pn cnd stiva devine vid.
Program
class Permutari
{
static int[] ST;
static int n;
static void INIT(int k)
{
ST[k] = 0;
}
static bool SUCCESOR(int[] ST, int k)
{
if (ST[k] < n)
{
ST[k]++;
return true;
}
return false;
}
static bool VALID(int[] ST, int k)
{
int i;
for (i = 1; i < k; i++)
if (ST[i] == ST[k])
return false;
return true;
}
static void TIPAR()
{
int i;
for (i = 1; i <= n; i++)
[Link]("{0} ", ST[i]);
[Link]("\n");
}
static void Main(string[] args)
{
int k = 1;
[Link]("Dati n:");
n = [Link]([Link]());
ST = new int[n + 1];
29
Algoritmi i structuri de date n C#
ST[k] = 0;
while (k > 0)
{
while (SUCCESOR(ST, k))
if (VALID(ST, k))
if (k==n) TIPAR();
else ST[++k] = 0;
k--;
}
}
}
2. Generarea aranjamentelor. Se citesc n i p. S se genereze toate
aranjamentele de n luate cte p.
Din analiza problemei rezult urmtoarele:
stiva are nlimea p;
fiecare nivel ia valori ntre 1 i n;
elementele plasate pe diverse niveluri trebuie s fie distincte.
Algoritmul este asemntor cu cel de la permutri, cu deosebirea c aici
stiva are nlime p.
Program
class Aranjamente
{
static int[] ST;
static int n, p;
static void INIT(int k)
{
ST[k] = 0;
}
static bool SUCCESOR(int[] ST, int k)
{
if (ST[k] < n)
{
ST[k]++;
return true;
}
return false;
}
static bool VALID(int[] ST, int k)
{
int i;
for (i = 1; i < k; i++)
if (ST[i] == ST[k])
return false;
return true;
}
static void TIPAR()
30
Algoritmi i structuri de date n C#
{
int i;
for (i = 1; i <= p; i++)
[Link]("{0} ", ST[i]);
[Link]("\n");
}
static void Main(string[] args)
{
int k = 1;
[Link]("Dati n:");
n = [Link]([Link]());
[Link]("Dati p:");
p = [Link]([Link]());
ST = new int[n + 1];
ST[k] = 0;
while (k > 0)
{
while (SUCCESOR(ST, k))
if (VALID(ST, k))
if (k==p) TIPAR();
else ST[++k] = 0;
k--;
}
}
}
3. Generarea combinrilor. Se citesc n i p numere naturale, np. Se cere s se
genereze toate submulimile cu p elemente ale mulimii {1, 2, 3, , n}.
Pentru rezolvarea problemei trebuie inut cont de urmtoarele:
stiva are nlimea p;
elementele aflate pe niveluri diferite ale stivei trebuie s fie distincte;
pentru a evita repetiia elementele se aeaz n ordine cresctoare: pe
nivelul k se va afla o valoare mai mare dect pe nivelul k-1 i mai mic
sau egal cu n-p+k.
Program
class Combinari
{
static int[] ST;
static int n, p;
static void INIT(int k)
{
ST[k] = 0;
}
static bool SUCCESOR(int[] ST, int k)
{
if (ST[k] < n)
31
Algoritmi i structuri de date n C#
{
ST[k]++;
return true;
}
return false;
}
static bool VALID(int[] ST, int k)
{
int i;
for (i = 1; i < k; i++)
if (ST[i] >= ST[k])
return false;
return true;
}
static void TIPAR()
{
int i;
for (i = 1; i <= p; i++)
[Link]("{0} ", ST[i]);
[Link]("\n");
}
static void Main(string[] args)
{
int k = 1;
[Link]("Dati n:");
n = [Link]([Link]());
[Link]("Dati p:");
p = [Link]([Link]());
ST = new int[n + 1];
ST[k] = 0;
while (k > 0)
{
while (SUCCESOR(ST, k))
if (VALID(ST, k))
if (k==p) TIPAR();
else ST[++k] = 0;
k--;
}
}
}
Se poate observa c cele 3 probleme de mai sus au rezolvri aproape identice,
diferind puin la condiiile de validitate i de soluie. Deasemenea, ele sunt 3
probleme de baz care pot ajuta la rezolvarea unei game mult mai mari de
probleme care pot fi reduse la una din cele trei.
32
Algoritmi i structuri de date n C#
4. Problema comis-voiajorului. Aceasta este o problem foarte celebr i
cunoate diverse enunuri. Vom prezenta n cele ce urmeaz unul din ele: Un
comis voiajor trebuie s viziteze un numr n de orae. Iniial, el se afl ntr-unul
dintre ele, notat 1. Comis voiajorul dorete s nu treac de dou ori prin acelai
ora, iar la ntoarcere s revin n oraul 1. Cunoscnd legturile existente ntre
orae, se cere s se tipreasc toate drumurile posibile pe care le poate efectua
comis voiajorul.
O alt variant a problemei este cea n care fiecare drum ntre dou orae
are alocat un cost, iar comis voiajorul trebuie s viziteze toate oraele cu un cost
minim.
Exemplu: n figura alturat sunt simbolizate cele 6 orae, precum i
drumurile existente ntre ele.
2 3
1 4
6 5
Comis voiajorul are urmtoarele posibiliti de parcurgere:
1, 2, 3, 4, 5, 6, 1;
1, 2, 5, 4, 3, 6, 1;
1, 6, 3, 4, 5, 2, 1;
1, 6, 5, 4, 3, 2, 1;
Legturile existente ntre orae sunt date n matricea An,n. Elementele
matricei A pot fi 0 sau 1 (matricea este binar).
1, dac exist drum ntre oraele i i j;
A(i,j) =
0 , altfel
Se observ c A(i,j) = A(j,i), oricare ar fi i,j {1, 2, 3, , n} matricea
este simetric.
Pentru rezolvarea problemei folosim stiva st. la baza stivei (nivelul 1) se
ncarc numrul 1. Prezentm n continuare modul de rezolvare a problemei.
2
De la oraul 1 la oraul 2 exist drum, deci se va urca n stiv;
1
2
2 Oraul 2 se mai gsete n stiv, deci nu este acceptat;
1
33
Algoritmi i structuri de date n C#
3
De la oraul 2 la oraul 3 se gsete drum; prin oraul 3 nu s-a mai trecut, deci oraul 3
2
acceptat.
1
Algoritmul continu n acest mod pn se ajunge din nou la nivelul 1, caz
n care algoritmul se ncheie.
Un succesor, ntre 2 i n, aflat pe nivelul k al stivei, este considerat valid
dac sunt ndeplinite urmtoarele condiii:
nu s-a mai trecut prin oraul simbolizat de succesor, deci acesta nu se
regsete n stiv;
exist drum ntre oraul aflat la nivelul k-1 i cel aflat la nivelul k;
dac succesorul se gsete la nivelul n, s existe drum de la el la oraul
1.
Program
class ComisVoiajor
{
static int[] ST;
static int n;
static bool SUCCESOR(int[] ST, int k)
{
if (ST[k] < n)
{
ST[k]++;
return true;
}
return false;
}
static bool VALID(int[] ST, int[,] a, int k)
{
int i;
for (i = 1; i < k; i++)
if (ST[i] == ST[k]) return false;
if (a[ST[k], ST[k - 1]] == 1)
return true;
else return false;
}
static void TIPAR()
{
int i;
for (i = 1; i <= n; i++)
[Link]("{0} ", ST[i]);
[Link]("1");
}
static void Main(string[] args)
{
int k = 2;
34
Algoritmi i structuri de date n C#
[Link]("Dati numarul de orase:");
n = [Link]([Link]());
ST = new int[n + 1];
int[,] a = new int[n + 1, n + 1];
[Link]("Dati matricea de adiacenta:");
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
{
[Link]("a[{0},{1}]=", i, j);
a[i, j] = [Link]([Link]());
a[j, i] = a[i, j];
}
ST[1] = 1; //se porneste din orasul 1
ST[k] = 0;
while (k > 1) //primul oras nu va fi scos din stiva
{
while (SUCCESOR(ST, k))
if (VALID(ST, a, k))
if (k == n) //a trecut prin toate orasele
{
if (a[ST[k], 1] == 1) //este drum de la
ultimul oras
//vizitat la primul
TIPAR();
}
else ST[++k] = 0;
k--;
}
[Link]();
}
}
5. Produsul cartezian a n mulimi. Se dau mulimile de mai jos i se cere
produsul cartezian al lor.
A1 = {1, 2, 3, , k1}
A2 = {1, 2, 3, , k2}
An = {1, 2, 3, , kn}
Exemplu: A1 = {1, 2}
A2 = {1, 2, 3}
A3 = {1, 2, 3}
A1 A2 A3 = {(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 1), (1, 2, 2), (1, 2, 3), (1, 3,
1), (1, 3, 2),
(1, 3, 3), (2, 1, 1), (2, 1, 2), (2, 1, 3), (2, 2, 1), (2, 2, 2), (2, 2, 3), (2, 3, 1), (2, 3, 2),
(2, 3, 3)}.
35
Algoritmi i structuri de date n C#
Pentru rezolvare, se folosesc stiva ST i un vector A ce reine numerele k1, k2,
kn. Utilizm metoda backtracking, uor modificat din urmtoarele motive:
a) Orice element aflat la nivelul k al stivei este valid, motiv pentru care
procedura valid nu face altceva dect s atribuie variabilei ev valoarea
TRUE.
b) Limita superioar pe nivelul k al stivei este dat de A(k).
Modul de concepere a algoritmului rezult din cele ce urmeaz:
1 2 3 1
1 1 1 2 2
1 1 1 1 1 1
2 3 1 2 3
2 2 3 3 3 3
1 1 1 1 1 1
Program
class ProdusCartezian
{
static int[] ST, a;
static int n;
static bool SUCCESOR(int[] ST, int k)
{
if (ST[k] < a[k])
{
ST[k]++;
return true;
}
return false;
}
static void TIPAR()
{
int i;
for (i = 1; i <= n; i++)
[Link]("{0} ", ST[i]);
[Link]();
}
static void Main(string[] args)
{
int k = 1;
[Link]("Dati numarul de multimi: ");
n = [Link]([Link]());
36
Algoritmi i structuri de date n C#
ST = new int[n + 1];
a = new int[n + 1];
[Link]("Dati dimensiunile multimilor:");
for (int i = 1; i <= n; i++)
{
[Link]("a[{0}]=", i);
a[i] = [Link]([Link]());
}
ST[k] = 0;
while (k > 0)
{
while (SUCCESOR(ST, k))
if (k == n)
TIPAR();
else ST[++k] = 0;
k--;
}
}
}
Se poate observa c din acest program lipsete metoda VALID, deoarece
orice valoare aleas la un moment dat dintr-o mulime este valid.
6. Plata unei sume de bani cu monede de valori date. Se cere s se plteasc o
sum S de bani avnd un numr nelimitat de monede de T valori diferite. S
se gseasc toate modalitile de plat a sumei S folosind monedele date.
Exemplu:
S=25
3 tipuri de monede de valoare: 3, 5, 10
Suma S poate fi platit astfel:
1 moned de 5 + 2 monede de 10
3 monede de 5 + 1 moned de 10
5 monede de 5
5 monede de 3 + 1 moned de 10
5 monede de 3 + 2 monede de 5
Program
class Monede
{
static bool VALID(int[] ST, int[] m, int k, int s)
{
int i, suma_temp = 0;
for (i = 1; i <= k; i++)
suma_temp += ST[i] * m[i];
if (suma_temp <= s)
return true;
else return false;
}
37
Algoritmi i structuri de date n C#
static bool SOLUTIE(int[] ST, int[] m, int k, int s)
{
int suma_temp = 0;
for (int i = 1; i <= k; i++)
suma_temp += ST[i] * m[i];
if (suma_temp == s)
return true;
else return false;
}
static void TIPAR(int[] ST, int[] m, int k)
{
int i;
for (i = 1; i <= k; i++)
if (ST[i] > 0)
[Link]("{0} monede de
{1}={2}",ST[i],m[i],ST[i]*m[i]);
[Link]();
}
static void Main(string[] args)
{
int k = 1, s, t;
[Link]("Suma de platit:");
s = [Link]([Link]());
[Link]("Cate tipuri de monede sunt? ");
t = [Link]([Link]());
int[] ST = new int[t + 1];
int[] m = new int[t + 1];
for (int i = 1; i <= t; i++)
{
[Link]("Valoarea monezii {0}: ", i);
m[i] = [Link]([Link]());
}
[Link]("Suma {0} poate fi platita astfel:",
s);
ST[k] = -1;
while (k > 0)
{
ST[k]++;
if (VALID(ST, m, k, s))
{
if (SOLUTIE(ST, m, k, s))
TIPAR(ST, m, k);
else if (k < t) ST[++k] = -1;
}
else k--;
}
[Link]();
}
}
38
Algoritmi i structuri de date n C#
Se observ c n acest program nu exist metoda SUCCESOR deoarece avem
la dispoziie un numr nelimitat de monede din fiecare tip. Tot din acest motiv, n
rutina Backtracking generarea unei noi valori pe un nivel al stivei nu se mai face
ntr-o instruciune repetitiv. Metoda VALID testeaz dac suma existent pn la
un momentdat este mai mic dect suma de pltit. Metoda SOLUTIE testeaz
dac suma existent pn la un momentdat este egal cu suma de pltit. Metoda
TIPAR afieaz numrul i valoarea monedelor folosite pentru a plti suma.
2.9 Backtracking n plan
Tipul problemelor care se rezolv folosind Backtracking n plan necesit mai
mult de o coloan n stiv, cum ar fi de exemplu dou coloane care reprezint
coordonate n plan. Problemele clasice care se rezolva astfel sunt cele de tip
labirint.
1. Sritura calului. Se d o tabl de ah de dimensiune nxn i un punct de
plecare a calului (linia, coloana). Se cere ca prin cele 8 posibile srituri ale calului
s se acopere ntreaga tabl, fr a trece de mai multe ori printr-o csu.
Rezolvare: Se vor codifica cele 8 posibile srituri ale calului pornind dintr-un
punct dat folosind doi vectori: a pentru deplasarea pe linie i b pentru deplasarea
pe coloan. Perechea de elemente de pe aceeai poziie din a i b formeaz o
sritur (ex: (-1, -2) nseamn o linie n sus i dou coloane la stnga). Variabila k
va reine a cta sritur se efectueaz.
Pentru aceast problem vom varia puin i vom introduce rutina
Backtracking ntr-o metod recursiv, iar celelalte metode din rezolvrile
anterioare (SUCCESOR, VALID, TIPAR) le vom include direct n metoda
Backtrackingcare va consta n testarea tuturor celor 8 posibile srituri dintr-un
punct, cu condiiile s nu se ajung ntr-o csu deja vizitat sau s se ias de pe
tabl. Cnd nivelul stivei va fi n*n nseamn c a fost traversat ntreaga tabl,
deci s-a gsit o soluie.
Stiva de data aceasta este imaginea tablei, avnd dimensiunea n*n.
Program
class SarituraCalului
{
static int[] a = { -1, -2, -2, -1, 1, 2, 2, 1 };
static int[] b = { -2, -1, 1, 2, 2, 1, -1, -2 };
static int[,] ST;
static int n;
static void back(int k, int i, int j)
{
int t, r, p;
ST[i, j] = k; //INIT
if (k == n * n) //SOLUTIE
{
[Link]();
for (t = 0; t < n; t++) //TIPAR
39
Algoritmi i structuri de date n C#
{
for (r = 0; r < n; r++)
[Link]("{0} ", ST[t, r]);
[Link]();
}
[Link]("Apasati o tasta...");
[Link]();
}
else
/* se incearca una din cele 8 posibile sarituri */
for (t = 0; t < 8; t++) //SUCCESOR
{
r = i + a[t];
p = j + b[t]; //VALID
if ((r >= 0) && (r<n) && (p >= 0) && (p<n) &&
(ST[r, p] == 0))
back(k + 1, r, p);
}
ST[i, j] = 0; /* reface pozitia alterata pentru a o
refolosi*/
}
static void Main(string[] args)
{
int l, c;
[Link]("Dati dimensiunea tablei:");
n = [Link]([Link]());
[Link]("Linia de pornire:");
l = [Link]([Link]());
[Link]("Coloana de pornire:");
c = [Link]([Link]());
ST = new int[n, n];
back(1, l - 1, c - 1);
}
2. Ieirea din labirint. Se d un labirint care conine culoare i perei.
Pornind de la o locaie din labirint, se cere s se gseasc toate ieirile posibile din
labirint.
Exist multe variante de a rezolva aceast problem. Mai jos este una dintre
ele:
Rezolvare: Labirintul va fi codificat printr-o matrice, astfel c dac la o anumit
coordonat (i,j) este perete, n matrice se va pune valoarea 0, iar dac este culoar
se va pune valoarea 1. Vom considera o matrice ptratic de dimensiune n*n
(pentru simplitate, dei nu este neaprat necesar). Aceast matrice va avea o
bordur completat cu valoarea 1 (liniiile/coloanele 0 i n+1). Acest fapt ne va
ajuta s putem detecta ieirea din labirint n urma unei micri.
Fiind ntr-un punct n labirint vor exista patru posibiliti de deplasare: n sus,
n jos, la stnga, la dreapta. Rezolvarea ncepe acum s semene cu cea de la
problema anterioar.
40
Algoritmi i structuri de date n C#
Stiva ST, ca i la problema anterioar va fi imaginea labirintului, avnd
aceleai dimensiuni cu el. Valorile (i,j) din stiv vor avea urmtoarea semnificaie:
0, dac nu s-a trecut prin punctul (i,j) sau o valoare pozitiv k care reprezint
faptul ca punctul (i,j) este cea de-a k mutare din traseul de ieire din labirint.
Noile poziii (i,j) se vor stabili n urma efecturii unei din cele 4 mutri
posibile, iar validitatea lor se va stabili dac: i,jn+1,
0 a[i,j]=1 (nu am ajuns la
un perete) i ST[i,j]=0 (nu ajungem ntr-o locaie n care deja am fost).
Dup ce epuizm toate variantele de deplasare dintr-un punct (i,j),
ST[i,j]=0 i coborm n stiv la un pas anterior.
Se ajunge la o soluie cnd se iese din labirint, adic linia sau coloana este
0 sau n+1.
Tiprirea soluiei se poate face i ea n mai mult emoduri. n programul de
mai jos, se va afia matricea labirintului, iar n locaiile unde ST[i,j]0, se afieaz
valoarea ST[i,j].
Exemplu:
n=5
Matricea labirintului:
0 0 1 0 0
0 0 1 1 1
0 1 1 0 0
1 1 0 0 0
0 0 0 0 0
Coordonatele de pornire: 3, 3
Soluii:
0 0 M3 0 0
0 0 M2 1 1
0 1 M1 0 0
1 1 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 M2 M3 M4
0 1 M1 0 0
1 1 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 1 1 1
0 M2 M1 0 0
M4 M3 0 0 0
0 0 0 0 0
41
Algoritmi i structuri de date n C#
Program
class Labirint
{
static int[] vert = { -1, 1, 0, 0 };
static int[] oriz = { 0, 0, -1, 1 };
static int[,] ST;
static int[,] a;
static int n;
static void back(int k, int i, int j)
{
int t, r, p;
ST[i, j] = k; //INIT
if ((i == 0) || (i == n + 1) || (j == 0) || (j == n + 1))
//SOLUTIE
{
[Link]();
for (int l = 1; l <= n; l++) //TIPAR
{
for (int c = 1; c <= n; c++)
if (ST[l, c] != 0)
[Link]("M{0} ", ST[l, c]);
else
[Link]("{0} ", a[l, c]);
[Link]();
}
[Link]("Apasati o tasta...");
[Link]();
}
else /* se incearca una din cele 4 posibile mutari */
for (t = 0; t < 4; t++) //SUCCESOR
{
r = i + vert[t];
p = j + oriz[t]; //VALID
if ((r >= 0) && (r <= n + 1) && (p >= 0) && (p <=
n + 1) &&
(a[r, p] == 1) && (ST[r, p] == 0))
back(k + 1, r, p);
}
ST[i, j] = 0; /* reface pozitia alterata */
}
static void Main(string[] args)
{
int l, c, i, j;
[Link]("Dati dimensiunea labirintului:");
n = [Link]([Link]());
ST = new int[n + 2, n + 2];
a = new int[n + 2, n + 2];
[Link]("Dati matricea labirintului (0/1):");
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
{
42
Algoritmi i structuri de date n C#
[Link]("a[{0},{1}]=", i, j);
a[i, j] = [Link]([Link]());
}
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
[Link]("{0} ", a[i, j]);
[Link]();
}
for (i = 0; i <= n + 1; i++) //bordarea labirintului
{
a[0, i] = 1;
a[n + 1, i] = 1;
a[i, 0] = 1;
a[i, n + 1] = 1;
}
do
{
[Link]("Linia de pornire:");
l = [Link]([Link]());
[Link]("Coloana de pornire:");
c = [Link]([Link]());
} while (a[l, c] == 0); //test sa nu pornim din perete
back(1, l, c);
[Link]();
}
}
2.10 Lucrare de verificare Nr. 2
Realizai urmtoarele programe:
1. Problema colorrii hrilor. Fiind dat o hart cu n ri i tiindu-se
care ri au granie comune, s se coloreze harta cu maxim 4 culori diferite astfel
nct rile vecine s nu aib aceeai culoare.
Indicaii: Este nevoie de o matrice de adiacen pentru a ti rile care se
nvecineaz i de o stiv simpl (n linii, o coloan). Linia k n stiv va reprezenta
ara k. Pe linia k se va putea pune una din valorile 1..4, cu condiia ca ara k s nu
aib aceeai culoare cu o tar deja plasat n stiv (pentru i=1..k-1, dac a[k,i]=1
atunci ST[k]ST[i]).
2. Problema bilei. Aceast problem este similar cu problema labirintului.
Se d o tabl de dimensiune n*n, iar fiecare locaie (i,j) are asociat o cot
numeric. Dat fiind o bil ntr-o locaie iniial, se cere s se scoat bila de pe
tabl mergnd doar pe locaii cu cote mai mici dect anterioarele (altfel spus, bila
nu poate s urce, dect s coboare).
Indicaii: La fel ca i la problema labirintului, se va citi matricea asociat tablei,
att c de data aceasta nu va conine valori 0 sau 1, ci diverse valori ntregi care
reprezint cotele fiecrei locaii.
43
Algoritmi i structuri de date n C#
3. Exemenul. Un student primete la un examen n subiecte, numerotate de
la 1 la n (1n10). Pentru fiecare subiect este cunoscut punctajul care se obine
rezolvnd subiectul respectiv (nu se acord punctaje pariale), precum i
dificultatea subiectului. Studentul vrea s obin cel puin un punctaj total P, dar
nu poate s rezolve subiecte cu dificultate > D. Determinai toate variantele n
care studentul poate obine un punctaj satisfctor.
Indicaii: Aceast problem se aseamn cu problema plii unei sume de bani S
cu n tipuri de monede.
4. Se cer toate soluiile de aezare n linie a m cini i n pisici astfel nct s
nu existe o pisic ntre doi cini.
5. La o mas rotund sunt n persoane de diverse naionaliti, pentru fiecare
persoan precizndu-se dou limbi strine cunoscute de ea. Se cere s ajutai
organizatorii mesei rotunde s aranjeze persoanele astfel nct fiecare s poat
conversa att cu cea din stnga ct i cu cea din dreapta.
2.11 Bibliografie
5. T.H. Cormen, [Link], R.R. Rivest Introducere n algoritmi, Mit Press
1990, trad. Computer Libris Agora.
6. V. Cretu Structuri de date i algoritmi, vol. 1, ed. Orizonturi Universitare,
2000.
7. D. Lucanu, M. Craus; Proiectarea algoritmilor, Ed. Polirom, 2008.
8. C. Giumale, L. Negreanu, S. Calinoiu Proiectarea i analiza algoritmilor.
Algoritmi de sortare, 1996.
44
Algoritmi i structuri de date n C#
3. Programare dinamic. Metoda Greedy
Obiective:
Dup studiul acestui capitol, studentul va avea cunotine
suficiente pentru a fi capabil s neleag cum se folosete
metoda Greedy.
3.1 Metoda programrii dinamice. Prezentare general
Programarea dinamic este o tehnic considerat ca fiind inversa
recursivitii (care pornete de la problema iniial pe care o mparte n
subprobleme mai mici pe care le rezolv i din care apoi se construiete soluia
problemei iniiale). Chiar dac soluiile recursive ale unor probleme sunt adesea
elegante, ele sunt i ineficiente. De exemplu, compilatorul C#, precum i
compilatoarele altor limbaje de programare, nu transpun eficient codul recursiv n
cod main, rezultnd un cod dei elegant, ineficient. Programarea dinamic n
schimb pornete de jos, rezolvnd probleme ct mai mici i combinndu-le spre a
obine soluia unei probleme mai mari. Adesea, soluiile subproblemelor sunt
stocate n vectori, pentru a putea fi accesate la nevoie.
Programarea dinamic este o tehnic ce poate fi aplicat pentru rezolvarea
acelor probleme pentru care se cere o soluie optim, metoda conducnd la
obinerea unui timp de calcul de ordin polinomial.
Termenul de programare dinamic a fost introdus de catre Richard Bellman
n anul 1953.
Programarea dinamic are la baz principiul optimalitii: problema poate fi
descompus n subprobleme asemntoare de dimensiuni mai mici iar soluiile
(optime) ale acestor subprobleme contribuie la obinerea soluiei optime pentru
problema iniial. Cu alte cuvinte, optimul general implic optimul parial.
Rezolvarea unei probleme folosind metoda programrii dinamice presupune
urmtoarele etape:
a) divizarea problemei n subprobleme de dimensiuni mai mici
b) rezolvarea tuturor subproblemelor de manier optim folosind etapele a, b, c
c) reunirea soluiilor subproblemelor pentru a obine soluia optim a problemei
iniiale
Exemplu simplu de programare dinamic: Aflarea celui de-al n-lea termen al
irului lui Fibonacci.
Formula recurent: Fibonacci(n)= Fibonacci(n-1)+ Fibonacci(n-2)
iar Fibonacci(1)= Fibonacci(2)=1
Aceast problem a fost expus i n Capitolul 1 al acestei cri subliniind
tocmai faptul c o rezolvare recursiv este foarte ineficient. n varianta de
programare dinamic nu vom mai porni de la Fibonacci(n) n jos pn la
Fibonacci(1), ci invers.
45
Algoritmi i structuri de date n C#
Rezolvare:
class Fibonacci
{
static long iterFib(int n)
{
long[] val = new long[n];
if ((n == 1) || (n == 2))
return 1;
else
{
val[1] = 1;
val[2] = 2;
for (int i = 3; i <= n - 1; i++)
val[i] = val[i - 1] + val[i - 2];
}
return val[n - 1];
}
static void Main(string[] args)
{
int num;
[Link]("Dati n: ");
num = [Link]([Link]());
[Link]("Fibonacci({0})={1}", num, iterFib(num));
[Link]();
}
}
Problem rezolvat. Subsecvena crescatoare de lungime maxim (LIS
Longest Increasing Subsequence): Fie un ir a de numere ntregi de dimensiune
n. O subsecven a irului a reprezint o submulime de elemente din ir, nu
neaparat pe poziii consecutive. Lungimea unei subsecvene este dat de numrul
de elemente care o alctuiesc. O subsecven se numete cresctoare dac
elementele sale sunt n ordine cresctoare.
Fiind dat un ir de numere ntregi a de dimensiune n, se cere s se gseasca
subsecvena cresctoare de lungime maxim din irul a. n cazul n care exist mai
multe soluii de lungime maxim, va fi afiat una dintre ele la alegere.
Cea mai lung subsecvena crescatoare are lungimea 6:
7 1 5 2 8 2 4 1 8 7 10
Soluia nu este unic, putem avea de exemplu:
7 1 5 2 8 2 4 1 8 7 10
Pentru a rezolva problema, vom calcula pentru fiecare element de pe
poziia i n irul de numere ntregi a, lungimea celei mai lungi subsecvene
cresctoare care poate fi format ncepnd cu elementul a[i] (lungime pe care o
reinem ntr-un vector suplimentar lung[i]). n final, vom afia valoarea maxim
din vectorul lung, ce va corespunde lungimii celei mai mari subsecvene
cresctoare.
Algoritmul pleac de la urmtoarea idee: presupunem deja calculate
valorile lung[j] pentru toate poziiile j mai mari dect i, j = i+1,..,n1. Vom
construi cea mai lung subsecven cresctoare care ncepe cu elementul a[i],
46
Algoritmi i structuri de date n C#
alipindu-l celei mai lungi subsecvene cresctoare formate cu elemente aflate n ir
dup poziia i (j = i+1,..,n1) cu condiia de a respecta proprietatea de subsecven
cresctoare pentru noua subsecven format, i anume: vom aduga elementul
a[i] ca prim element la subsecvena care ncepe cu a[j] numai dac a[i] <= a[j].
Astfel, vom obine o subsecven care ncepe cu a[i], continu cu a[j] i are
lungimea total 1 + lung[j]. ntruct dorim s gsim acea subsecven de lungime
maxim, vom alege acel j pentru care valoarea lung[j] este maxim. Dac nu
exist nici un element a[j] >= a[i] (care s respecte condiia de subsecven
cresctoare), atunci subsecvena ce ncepe cu elementul a[i] va avea lungimea
lung[i] = 1 (nu exist nici un element mai mare dect a[i] poziionat dup indexul
i).
Se pleac de la condiia iniial lung[n-1] = 1 (cea mai lung subsecven
cresctoare format din ultimul element al irului are lungimea 1). n final,
lungimea celui mai lung subir cresctor va fi data de max{lung[i], i = 0,..,n 1}.
Pentru a lista subsecvena de lungime maxim din ir, pornim de la elementul
aflat pe poziia ce corespunde lungimii maxime n irul lung, pe care l tiparim.
Apoi, trebuie s cutam urmtorul element din ir care s respecte urmtoarele
condiii: s fie mai mare dect elementul pe care tocmai l-am tiparit (condiia de
subsecven cresctoare), iar lungimea celei mai lungi subsecvene care ncepe cu
acest element s fie cu 1 mai mic dect lungimea subsecvenei care ncepe cu
elementul deja tiparit. Odat gsit, l vom tipari i continum algoritmul pn cnd
am scris elementul cu care ncepe o subsecvena de lungime 1 (ultimul element
dintr-o subsecven cresctoare).
Acest algoritm ne va lista o singur soluie, chiar dac exist mai multe
subsecvene de aceeai lungime maxim.
Program
class LungimeMaxima
{
static void Main(string[] args)
{
int n, max, poz, i;
[Link]("Cate elemente are vectorul a ? ");
n = [Link]([Link]());
int[] a = new int[n]; ;
int[] lung = new int[n];
[Link]("Dati elementele vectorului a:");
for (i = 0; i < n; i++)
{
[Link]("a[{0}]=", i + 1);
a[i] = [Link]([Link]());
}
[Link]("Sirul este: ");
for (i = 0; i < n; i++)
[Link](a[i] + " ");
//creare vector lung
lung[n - 1] = 1;
47
Algoritmi i structuri de date n C#
for (i = n - 2; i >= 0; i--)
{
max = 0;
for (int j = i + 1; j <= n - 1; j++)
if (a[i] <= a[j])
if (max < lung[j])
max = lung[j];
lung[i] = max + 1;
}
//determinare lungime maxima
max = lung[0];
poz = 0;
for (i=1; i<=n-1; i++)
if (max < lung[i])
{
max = lung[i];
poz = i;
}
//afiseaza subsecventa maxima
[Link]("\nLungimea maxima este: {0}", max);
[Link]("Subsecventa crescatoare maxima este: ");
[Link](a[poz]+" ");
for (i=poz+1; i<=n-1; i++)
if ((lung[i] == max - 1) && (a[i] >= a[poz]))
{
[Link](a[i] + " ");
poz = i;
max = max - 1;
}
}
}
Problem rezolvat. Cel mai lung subir comun a dou iruri de caractere.
Fiind date dou iruri de caractere diferite, s se gseasc cel mai lung subir (de
litere consecutive) comun al lor.
Exemplu:
sir1=informatician
sir2=matematica
cel mai lung subir al lor este matic coninnd 5 caractere.
Pentru a rezolva problema se folosesc cteva elemente ajuttoare i anume:
doi vectori de care conin caracterele celor dou iruri i o matrice de numere
ntregi cu ajutorul creia va fi determinat cel mai lung subir comun.
Dup citirea celor dou iruri de caractere cuv1 i cuv2, se construiesc
vectorii carray1 i carray2 de tip char, folosind metoda predefinit
ToCharArray().
Matricea larray va avea attea linii ct este dimensiunea cuv1 i attea
coloane ct este dimensiunea cuv2. Construirea propriu-zis a matricii se face n
metoda LCSubstring() astfel:
- iniial, elementele matricii sunt egale cu 0
48
Algoritmi i structuri de date n C#
- vectorii carr1 i carr2 conin caracterele irurilor cuv1 i cuv2
- se parcurge vectorul carr1 cu un contor i i vectorul carr2 cu un contor j
i se compar carr1[i] cu carr2[j]. Dac este gsit egalitate atunci arr[i, j] = 1 +
arr[i + 1, j + 1]. Cu alte cuvinte, dac carr1[i] = carr2[j] atunci arr[i, j] = 1, iar
dac exist egalitate ntre mai multe caractere consecutive (carr1[i+1] =
carr2[j+1]) atunci arr[i, j] se calculeaz ca fiind 1+arr[i+1, j+1]. Astfel, dac de
exemplu 5 caractere din irul cuv1, ncepnd cu poziia i sunt egale cu 5 caractere
din irul cuv2, ncepnd cu poziia j, atunci arr[i, j] = 5
- metoda LCSubstring() returneaz lungimea celui mai lung subir comun
gsit
- metoda MaxString() caut n matricea larray valoarea maxim gsit
anterior, reinnd poziia acesteia n linmax i colmax. Altfel spus, cel mai lung
subir comun ncepe n cuv1 la poziia linmax i are a[linmax, colmax] caractere.
Metoda returneaz subirul maxim. n parcurgerea matricii, pentru aflarea
numrului de linii i de coloane s-a folosit metoda predefinit GetUpperBound()
care primete ca i parametru dimeniunea din tablou pentru care se dorete aflarea
numrului de elemente. Astfel, liniile reprezint dimensiunea 0, iar coloanele
dimensiunea 1 n matrice.
- dac irurile iniiale nu au nici mcar o liter n comun, se va afia la
final mesajul c nu exist nici un subir comun.
Program
class SubsirMAxim
{
static int LCSubstring(string cuv1, string cuv2, char[]
carr1,
char[] carr2, int[,] arr)
{
int len1, len2;
len1 = [Link];
len2 = [Link];
int max = 0;
for (int i = len1 - 1; i >= 0; i--)
for (int j = len2 - 1; j >= 0; j--)
if (carr1[i] == carr2[j])
{
arr[i, j] = 1 + arr[i + 1, j + 1];
if (max < arr[i, j]) max = arr[i, j];
}
else
arr[i, j] = 0;
return max;
}
static string MaxString(int[,] arr, char[] carr, int lungmax)
{
string substr = "";
int linmax = 0, colmax = 0;
for (int i = 0; i <= [Link](0); i++)
for (int j = 0; j <= [Link](1); j++)
if (arr[i, j] == lungmax)
{
49
Algoritmi i structuri de date n C#
linmax = i;
colmax = j;
}
for (int i = linmax; i < linmax + arr[linmax, colmax];
i++)
substr += carr[i];
return substr;
}
static void Main()
{
string cuv1, cuv2;
[Link]("Dati primul sir: ");
cuv1 = [Link]();
[Link]("Dati al doilea sir: ");
cuv2 = [Link]();
char[] carray1 = new char[[Link]];
char[] carray2 = new char[[Link]];
carray1 = [Link]();
carray2 = [Link]();
int[,] larray = new int[[Link] + 1, [Link] +
1];
int lungmax = LCSubstring(cuv1, cuv2, carray1, carray2,
larray);
string substr;
substr = MaxString(larray, carray1, lungmax);
[Link]("Cele doua siruri sunt:\n" + cuv1 +
"\n" + cuv2);
if ([Link] > 0)
[Link]("Cel mai lung subsir comun este: " +
substr);
else
[Link]("Nu exista subsir comun");
}
}
3.2 Metoda Greedy
Un algoritm de tip Greedy este unul care caut caut soluii bune n drumul
spre gsirea soluiei problemei. Aceste soluii bune se mai numesc i optim
local i sunt utilizate spre a ajunge la soluia problemei mari, numit optim global.
Termenul greedy (lacom) vine de la faptul c acest tip de algoritmi iau ca optim
local ceea ce arat cel mai bine la un moment dat. Acest tip de algoritmi nu
garanteaz ntotdeauna gsirea celei mai bune soluii, ns sunt folosii cnd este
aproape imposibil de a aplica o alt metod pentru a gsi o soluie optim, din
considerente de complexitate de timp i/sau spaiu.
Problem rezolvat. Poate una din cele mai celebre probleme care se rezolv
folosind tehnica Greedy este problema rucsacului. Se consider un rucsac care
are un volum maxim i o mulime de obiecte care au un volum i o valoare
50
Algoritmi i structuri de date n C#
alocate. Problema const n a gsi acea mulime de obiecte care s fie puse n
rucsac i care s aib o valoare maxim.
Desigur s-ar putea lua n considerare o rezolvare de tip Backtracking care ns
n cutarea soluiei optime ar putea genera un numr foarte mare de ncercri i un
timp de procesare ndelungat.
Rezolvarea n stil Greedy este mult mai simpl, chiar dac nu garanteaz cea
mai bun soluie i se poate aplica dac se accept o soluie care s fie cel puin
optim, chiar dac poate nu este cea mai bun.
Varianta 1: Aceasta este probabil cea mai lacom variant, dar i cea mai
simpl i const n a ordona descresctor produsele dup pre i introducerea n
rucsac a celor mai valoroase, pn cnd se umple rucsacul. Pentru a-l umple exact,
dac ultimul produs nu intr n ntregime, se poate lua o fraciune din acesta.
Exemplu:
Volumul rucsacului = 40
Numrul de obiecte = 6
Pret 50 40 18 60 50 60
Volum 25 10 6 30 10 20
Dup sortarea descresctoare dup pre:
Pret 60 60 50 50 40 18
Volum 30 20 25 10 10 6
n rucsac va fi depus obiectul 1 i 0.5 din obiectul 2, valoarea rucsacului fiind n
acest caz 90.
Program:
class Rucsac1
{
static void Main(string[] args)
{
int n, i, j;
float vol, aux, r, s;
float[] p, v, x;
[Link]("Volum rucsac: ");
vol = [Link]([Link]());
[Link]("Numar obiecte: ");
n = [Link]([Link]());
[Link]("Dati pretul si volumul obiectelor :");
p = new float[n];
v = new float[n];
x = new float[n];
for (i = 0; i < n; i++)
{
[Link]("P({0})=", i + 1);
p[i] = [Link]([Link]());
[Link]("V({0})=", i + 1);
v[i] = [Link]([Link]());
51
Algoritmi i structuri de date n C#
//sortare decrescatoare p si v - Metoda "bulelor"
for (i = n - 1; i >= 0; i--)
for (j = 1; j <= i; j++)
if (p[j - 1] < p[j])
{
aux = p[j - 1]; p[j - 1] = p[j]; p[j] = aux;
aux = v[j - 1]; v[j - 1] = v[j]; v[j] = aux;
}
[Link]("Obiectele sortate descrescator dupa
pret:");
for (i = 0; i < n; i++)
[Link]("P({0})={1}, V({2})={3}",i +
1,p[i],i + 1,v[i]);
//Greedy
r = vol;
j = 0;
while (r > 0)
{
if (v[j] <= r)
{
x[j] = 1;
r = r - v[j];
}
else
{
x[j] = r / v[j];
r = 0;
}
j++;
}
[Link]("Continutul rucsacului:obiect/fractiune
din obiect");
for (i = 0; i < j; i++)
[Link]("Obiect {0}/Fractiune {1}", i + 1,
x[i]);
s = 0; //calcul valoare rucsac
for (i = 0; i < j; i++)
s = s + p[i] * x[i];
[Link]("Valoarea rucsacului: {0}", s);
}
}
Varianta 2: Varianta de mai sus, conform principiului tehnicii Greedy, nu duce
neaprat la cea mai bun soluie, ci la una aa numit optim. Exist ns o
modalitate de a mbunti puin varianta de mai sus, aducnd o mic modificare
n algoritm i anume a nu mai sorta obiectele descresctor dup pre, deci neinnd
cont de volumul lor, ci dup raportul pre/volum. n acest fel vom evita a plasa n
rucsac obiecte valoroase ns n acelai timp foarte voluminoase, n situaia n care
putem avea obiecte de valoare asemntoare ns mult mai mici.
52
Algoritmi i structuri de date n C#
Testnd aceleai valori introduse la Varianta 1, vom observa o mbuntire
semnificativ. Desigur, nu pentru orice set de valori vor avea loc mbuntiri
semnificative, ns oricum principiul Variantei 2 este preferabil de utilizat.
Exemplu:
Volumul rucsacului = 40
Numrul de obiecte = 6
Pret 50 40 18 60 50 60
Volum 25 10 6 30 10 20
Dup sortarea descresctoare dup pre/volum:
Pret 50 40 60 18 60 50
Volum 10 10 20 6 30 25
n rucsac va fi depuse obiectele 1, 2 i 3, valoarea rucsacului fiind n acest caz
150.
Program:
class Rucsac2
{
static void Main(string[] args)
{
int n, i, j;
float vol, aux, r, s;
float[] p, v, x;
[Link]("Volum rucsac: ");
vol = [Link]([Link]());
[Link]("Numar obiecte: ");
n = [Link]([Link]());
[Link]("Dati pretul si volumul obiectelor :");
p = new float[n];
v = new float[n];
x = new float[n];
for (i = 0; i < n; i++)
{
[Link]("P({0})=", i + 1);
p[i] = [Link]([Link]());
[Link]("V({0})=", i + 1);
v[i] = [Link]([Link]());
}
//sortare decrescatoare p si v dupa raportul p/v
for (i = n - 1; i >= 0; i--)
for (j = 1; j <= i; j++)
if (p[j - 1] / v[j - 1] < p[j] / v[j])
{
aux = p[j - 1]; p[j - 1] = p[j]; p[j] = aux;
aux = v[j - 1]; v[j - 1] = v[j]; v[j] = aux;
}
[Link]("Obiectele sortate descrescator dupa
pret/volum:");
for (i = 0; i < n; i++)
[Link]("P({0})={1}, V({2})={3}",i +
1,p[i],i + 1,v[i]);
53
Algoritmi i structuri de date n C#
//Greedy
r = vol;
j = 0;
while (r > 0)
{
if (v[j] <= r)
{
x[j] = 1;
r = r - v[j];
}
else
{
x[j] = r / v[j];
r = 0;
}
j++;
}
[Link]("Continutul rucsacului:obiect/fractiune
din obiect");
for (i = 0; i < j; i++)
[Link]("Obiect {0}/Fractiune {1}", i + 1,
x[i]);
s = 0; //calcul valoare rucsac
for (i = 0; i < j; i++)
s = s + p[i] * x[i];
[Link]("Valoarea rucsacului: {0}", s);
}
}
Problem rezolvat. O alt problem care se rezolv optim folosind tehnica
Greedy este problema programrii spectacolelor. Fiind dat o sal de
spectacole i un numr de n spectacole care au loc ntr-o zi, s se planifice un
numr maxim de spectacole, cunoscndu-se ora de nceput i de sfrit a lor.
Pentru a rezolva problema, spectacolele vor fi sortate cresctor dup ora de
terminare, iar planificarea spectacolelor se va face parcurgndu-le de la cel care
ncepe cel mai devreme i avnd grij ca urmtorul spectacol s nu nceap mai
devreme dect ora de terminare a celui planificat nainte sa.
Observaie: Pentru simplitate, orele de nceput i sfrit se vor da ca i numere
ntregi.
Exemplu:
Se dorete planificarea a 5 spectacole.
ncepe 19 20 16 17 18
Se termin 21 21 18 18 19
Dup sortarea cresctoare dup ora de sfrit:
ncepe 16 17 18 19 20
Se termin 18 18 19 21 21
Se vor planifica spectacolele 1, 3 i 4, aceasta nefiind unica soluie.
54
Algoritmi i structuri de date n C#
Program
class Spectacole
{
static void Main()
{
int n, i, j,aux;
int[] s, t;
[Link]("Cate spectacole sunt in total ? ");
n = [Link]([Link]());
s = new int[n];
t = new int[n];
for (i = 0; i < n; i++)
{
[Link]("Spectacolul {0}", i + 1);
do
{
[Link]("Ora de inceput :");
s[i] = [Link]([Link]());
[Link]("Ora de sfarsit :");
t[i] = [Link]([Link]());
} while (s[i] > t[i]);
}
//sortare cresc. a spectacolelor dupa ora de sfarsit -
Metoda "bulelor"
for (i = n - 1; i >= 0; i--)
for (j = 1; j <= i; j++)
if (t[j - 1] > t[j])
{
aux = s[j - 1]; s[j - 1] = s[j]; s[j] = aux;
aux = t[j - 1]; t[j - 1] = t[j]; t[j] = aux;
}
[Link]("Spectacolele sortate dupa ora de
sfarsit :");
for (i = 0; i < n; i++)
[Link]("Spectacol {0}-Incepe:{1}, Se
termina:{2}",
i+1, s[i], t[i]);
//Greedy
[Link]("Planificarea spectacolelor:");
[Link]("Spectacolul 1"); //din oficiu
j = 0;
for (i = 1; i < n; i++)
if (s[i] >= t[j]) //noul spectacol i incepe dupa ce
se termina
//anteriorul j
{
[Link]("Spectacolul {0}", i + 1);
j = i; //memoram spectacolul curent
}
}
}
55
Algoritmi i structuri de date n C#
3.3 Test de autoevaluare Nr. 4
Realizai urmtoarele programe:
1. Problema plii unei sume cu bancnote (monezi) de valoare dat
S se efectueze plata unei sume S utiliznd un numr minim de monezi. Valorile
monezilor se cunosc.
Metoda Greedy se poate aplica astfel:
1. Fie suma care a mai rmas de pltit X=S
2. Se alege moneda de valoare maxim Mi (astfel nct Mi<=X)
3. Se calculeaz nr. maxim de monezi Mi ce pot fi date (n = X div Mi )
4. Repetm pn cnd restul sumei de plat e zero
2. Problema determinrii drumului minim
Se d un graf G i se cere s se determine drumul minim ntre dou noduri ale
sale.
Metoda Greedy se poate aplica astfel:
1. Pornim din nodul de start x.
2. Alegem cel mai scurt drum spre nodul urmtor.
3. Repetm pn cnd se ajunge n nodul destinaie
3. Determinarea arborelui parial de cost minim. Se d un graf G. Se cere
determinarea arborelui parial de cost minim.
Algoritmul de rezolvare pe care l folosim se bazeaz pe tehnica Greedy:
1. La nceput nu avem un arbore (avem mai muli arbori, fiecare dintre acetia
fiind format dintr-un singur nod), sortm muchiile n ordine cresctoare a
costurilor
2. Se alege o muchie x (muchiile se aleg de la cea mai mic la cea mai mare)
3. Verificm dac muchia poate fi adugat (dac nu se produc cicluri)
4. Continum pn cnd avem n-1 muchii (n = numrul de noduri)
56
Algoritmi i structuri de date n C#
Listele sunt structuri de date de tip liniar i vin ca i o alternativ la tablouri
(arrays), care au diverse dezavantaje cum ar fi: dimensiune fix prestabilit,
operaii de inserare i tergere de elemente destul de ineficiente.
Listele de diverse tipuri sunt defapt parte din viaa oricruia dintre noi cum ar
fi: listele de cumprturi, listele de activiti, topurile, etc.
3.4 Liste. Liste implementate ca i tablouri
Una din clasele predefinite n C# pentru lucrul cu liste este ArrayList care
face parte din spaiul de nume [Link]. Cel mai mare avantaj al
acesteia fa de tablouri este dimensiunea variabil a sa. Astfel, ea are o capacitate
iniial de 16 elemente, care dac este atins, va crete cu nc 16 elemente.
Observaie: Tipul implicit al elementelor memorate n ArrayList este
Object.
Metode uzuale ale clasei ArrayList:
- Add() adaug un element n list (la capt)
- AddRange() adaug elementele unei colecii la captul listei
- BinarySearch() cautare optimizat a unui element dat ntr-o list
ordonat
- Capacity reine capacitatea acutal a listei
- Clear() terge toate elementele din list
- Contains() determin dac un anumit element se afl n list
- CopyTo() copiaz ntreaga list sau doar o poriune din ea ntr-un tablou
- Count returneaz numrul de elemente din list
- GetEnumerator() returneaz un enumerator cu ajutorul cruia se poate
parcurge lista
- GetRange() extrage o parte din list i o depune ntr-un alt obiect
ArrayList
- IndexOf() returneaz poziia din list la care este gsit prima dat un
element
- Insert() insereaz un element n list la o poziie specificat
- InsertRange() insereaz elementele unei colecii n list, ncepnd cu o
poziie dat
- Remove() terge din list prima apariie a unui element dat
- RemoveAt() terge din list elementul de pe o poziie dat
- Reverse() inverseaz elementele listei
- Sort() sorteaz alfabetic elementele listei
- ToArray() Copiaz toate elementele listei ntr-un tablou (array)
57
Algoritmi i structuri de date n C#
- TrimToSize() seteaz capacitatea listei s fie egal cu numrul curent de
elemente din list
Exemple de utilizare ale clasei ArrayList:
Exemplu 1:
using [Link];
namespace ConsoleApplication1
{
class TestLista1
{
static void PrintList(ArrayList al)
{
foreach (Object o in al)
[Link](o);
[Link]("-----------");
}
static void Main(string[] args)
{
ArrayList temp = new ArrayList();
int poz;
[Link](10);
[Link](15);
[Link](-5);
[Link](15);
[Link]("Elementele listei:");
PrintList(temp);
[Link]("Lista are {0} elemente",
[Link]);
if ([Link](-5))
[Link]("Valoarea -5 a fost gasita in
lista");
else
[Link]("Valoarea -5 nu a fost gasita in
lista");
poz = [Link](15);
if (poz >= 0)
[Link]("Prima aparitie a valorii 15 in
lista este la pozitia {0}", poz+1);
else
[Link]("Valoarea 15 nu a fost gasita in
lista");
[Link]();
[Link]("Lista inversata:");
PrintList(temp);
[Link]();
[Link]("Lista sortata:");
PrintList(temp);
poz=[Link](10);
if (poz >= 0)
[Link]("Valorea 10 este la pozitia {0}
in lista", poz+1);
58
Algoritmi i structuri de date n C#
else
[Link]("Valoarea 10 nu a fost gasita in
lista");
[Link]();
}
}
}
Exemplu2:
using [Link];
namespace ConsoleApplication1
{
class TestLista2
{
static void PrintList(ArrayList al)
{
foreach (Object o in al)
[Link](o);
[Link]("-----------");
}
static void Main(string[] args)
{
ArrayList nume = new ArrayList();
ArrayList nume2 = new ArrayList();
ArrayList nume3 = new ArrayList();
[Link]("Maria");
[Link]("Florin");
[Link]("Alina");
[Link]("Diana");
[Link]("Claudiu");
[Link]("Raluca");
[Link]("Elementele listei:");
PrintList(nume);
[Link]("Diana");
[Link]("Am sters <Diana>. Elementele
listei:");
PrintList(nume);
[Link](1);
[Link]("Am sters elementul de pe pozitia 1.
Elementele listei:");
PrintList(nume);
[Link](3, "Marius");
[Link]("Am inserat <Marius> pe pozitia 3.
Elementele listei:");
PrintList(nume);
[Link]("Sorin");
[Link]("Laura");
59
Algoritmi i structuri de date n C#
[Link](2, nume2);
[Link]("Am inserat mai multe elem. incepand
cu pozitia 2. Elementele listei:");
PrintList(nume);
[Link]("Ioana");
[Link]("Catalin");
[Link](nume3);
[Link]("Am adaugat mai multe elemente la
sfarsit. Elementele listei:");
PrintList(nume);
string[] arrstr = new string[[Link]];
[Link](2, arrstr, 0, 3);
[Link]("Am copiat 3 elemente din lista de
la pozitia 2, in tablou. Elementele tabloului:");
foreach (string s in arrstr)
[Link](s);
[Link]();
}
}
}
3.5 Liste nlnuite
Listele nlnuite (linked lists) aduc o mbuntire suplimentar fa de listele
implementate ca i tablouri (ArrayList), astfel c pe lng faptul c au o
dimensiune variabil, operaiile de inserare sau tergere de elemente din interiorul
listei se fac dup alt principiu, mult mbuntit.
Dezavantajul listelor nlnuite fa de listele implementate ca i tablouri este
faptul c accesul la elemente nu mai este direct, ci secvenial, disprnd indecii.
n aceast situaie, va trebui gsit cel mai bun echilibru n alegerea
implementrii celei mai potrivite pentru o problem concret. Ca i principiu,
listele implementate ca i tablouri vor fi utile atunci cnd avem nevoie de acces
rapid la elemente, iar operaiile de inserare sau tergere de elemente din interiorul
listei nu vor fi numeroase, iar listele nlnuite vor fi utile n cazul contrar.
List nlnuit este constituit din noduri. Fiecare nod este nlnuit de cel
care urmeaz dup el printr-o referin. Astfel, un nod va fi ntotdeauna format
din: informaie util i referina spre nodul succesor.
Din cauz c n definirea unui nod apare referina spre un alt nod, structura de
tip list nlnuit se mai numete structur cu autoreferire.
Pentru a accesa un element vor trebui parcurse toate elementele de la
nceput pn la el, urmnd referinele de la un nod la altul. Deasemenea,
eliminndu-se ideea de indeci, ne vom referi la un nod n funcie de poziia sa
fa de alte noduri din list. Ultimul nod va avea referina null.
Intuitiv, o list nlnuit va arta astfel:
60
Algoritmi i structuri de date n C#
Lapte Pine Ou Carne null
Dup cum spuneam mai sus, operaia de inserare a unui nod n interiorul listei
nu mai presupune deplasare de elemente ca i n cazul tablourilor, ci implic doar
dou noduri: cel de dinaintea i de dup locaia inserrii.
Lapte Pine Ou Carne null
Brnz
n mod similar se face i tergerea unui nod din list, modificnd doar
referina nodului anterior celui de ters s indice spre nodul urmtor celui de ters.
Lapte Pine Ou Carne null
Crearea unui nod: Deoarece un nod este compus din dou pri, vom crea o clas
din care vom crea apoi obiecte de tip nod.
Primul nod din list este uneori considerat ca nefcnd parte propriu-zis din
list, el semnalnd doar nceputul listei (se mai numete nod fictiv).
public class Nod
{
public Object Info;
public Nod Ref; //referinta spre nodul urmator
public Nod() //constructor fara parametri
{
Info = null;
Ref = null;
}
public Nod(Object theInfo) //constructor cu parametri
{
Info = theInfo;
Ref = null;
}
}
Informaia util este codificat la modul general, considerndu-se c este de
tip Object (cea mai general clas). n acest fel, se vor putea introduce orice tip de
informaii n list.
Constructorul fr parametrii doar creaz un nod vid, iar cel cu parametru
creaz nodul i iniializeaz partea de informaie util. La creare, nodul nu va
referi nici un alt nod, deci el nc nu face parte din lista nlnuit. Va fi nevoie
deci de operaii suplimentare pentru a-l aduga n list.
Urmeaz crearea clasei LinkedList n care vom implementa operaiile de
baz asupra unei liste: afiare, cutare, inserare, tergere.
public class LinkedList
{
protected Nod prim;
61
Algoritmi i structuri de date n C#
public LinkedList()
{
prim = new Nod("prim");
}
public void PrintList() //afisarea listei
{
Nod curent = new Nod();
curent = prim;
while ([Link] != null) //parcurgerea listei
{
[Link]([Link]);
curent = [Link]; //trecerea la urmatorul nod
}
[Link]("------------");
}
private Nod Find(Object item) //cautarea unui nod
{
Nod curent = new Nod();
curent = prim;
while ([Link] != item) //se parcurge lista
curent = [Link];
return curent;
}
public void Insert(Object newItem, Object after)
{
Nod curent = new Nod();
Nod newNod = new Nod(newItem); //nodul de inserat
curent = Find(after); //cautarea nodului dupa care se
face inserarea
[Link] = [Link]; //referinta noului
nod=referinta nodului anterior lui
[Link] = newNod; //referinta nodului anterior=noul
nod
}
private Nod FindPrevious(Object item) //cautarea nodului
anterior unui nod dat
{
Nod curent = prim;
while (([Link] != null) && ([Link] != item))
curent = [Link];
return curent;
}
public void Remove(Object item) //stergerea unui nod dat
{
Nod p = FindPrevious(item); //cautarea nodului anterior
if ([Link] != null)
[Link] = [Link]; //legarea nodului anterior de
nodul urmator celui de sters
}
}
62
Algoritmi i structuri de date n C#
Testarea claselor Nod i LinkedList:
class TestListe
{
static void Main(string[] args)
{
LinkedList lst = new LinkedList();
[Link]("Lapte", "prim");
[Link]("Paine", "Lapte");
[Link]("Oua", "Paine");
[Link]("Carne", "Oua");
[Link]();
[Link]("Branza", "Oua");
[Link]();
[Link]("Oua");
[Link]();
}
}
3.6 Liste dublu nlnuite
Listele dublu nlnuite aduc n plus fa de listele simplu nlnuite faptul c
fiecare nod are de data aceasta dou referine, una spre nodul urmtor lui, iar una
spre nodul anterior lui. Chiar dac o referin suplimentar ncarc puin procesul
de codificare, aceasta are i avantajul de a permite parcurgerea listei n ambele
direcii.
Intuitiv, o list dublu nlnuit va arta astfel:
Lapte Pine Ou Carne null
Inserarea unui nod:
Pine Ou
Brnz
tergerea unui nod:
Lapte Pine Ou Carne null
Crearea unui nod al unei liste dublu nlnuite:
public class DNod
{
public Object Info;
public DNod Urm; //referinta spre nod urmator
public DNod Ant; //referinta spre nod anterior
public DNod() //constructor fara parametri
63
Algoritmi i structuri de date n C#
{
Info = null;
Urm = null;
Ant = null;
}
public DNod(Object theInfo) //constructor cu parametri
{
Info = theInfo;
Urm = null;
Ant = null;
}
}
Clasa DoubleLinkedList:
public class DoubleLinkedList
{
protected DNod prim;
public DoubleLinkedList()
{
prim = new DNod("prim");
}
private DNod Find(Object item)
{
DNod curent = new DNod();
curent = prim;
while ((curent!=null)&&([Link] != item))
curent = [Link];
return curent;
}
public void PrintList()
{
DNod curent = new DNod();
curent = prim;
while ([Link] != null)
{
[Link]([Link]);
curent = [Link];
}
[Link]("------------");
}
public void Insert(Object newItem, Object after)
{
DNod curent = new DNod();
DNod newNod = new DNod(newItem);
curent = Find(after);
[Link] = [Link];
[Link] = curent;
[Link] = newNod;
}
public void Remove(Object item)
{
DNod p = Find(item); //p este nodul de sters
if ([Link] != null) //daca nu este ultimul nod
64
Algoritmi i structuri de date n C#
{
[Link] = [Link]; //modificare legaturi
[Link] = [Link];
[Link] = null;
[Link] = null;
}
else
{
[Link] = null;
[Link] = null;
[Link] = null;
}
}
private DNod FindLast() //cauta ultimul nod din lista
{
DNod curent = new DNod();
curent = prim;
while ([Link] != null)
curent = [Link];
return curent;
}
public void PrintReverse() //afiseaza lista de la capat la
inceput
{
DNod curent = new DNod();
curent = FindLast();
while ([Link] != null) //se deplaseaza in lista spre
stanga
{
[Link]([Link]);
curent = [Link];
}
[Link]("------------");
}
}
Testarea claselor DNod i DoubleLinkedList:
class TestListeDuble
{
static void Main(string[] args)
{
DoubleLinkedList lst = new DoubleLinkedList();
[Link]("Lapte", "prim");
[Link]("Paine", "Lapte");
[Link]("Oua", "Paine");
[Link]("Carne", "Oua");
[Link]();
[Link]("Carne");
[Link]();
[Link]();
}
}
65
Algoritmi i structuri de date n C#
Observaii:
1) Un alt tip de liste particulare utilizate uneori sunt cele circulare, unde
ultimul nod indic tot timpul spre primul nod din list.
2) Pentru a nu vizita fiecare nod, n unele situaii cnd se poate dovedi util,
listele nlnuite se pot parcurge de exemplu din dou n dou noduri.
n spaiul de nume [Link] exist predefinite dou clase
generice pentru lucrul cu liste:
- LinkedListNode<>, care modeleaz un nod al listei, oferind cmpurile: de
informaie util (Value), referina spre nodul anterior (Previous) i
referina spre nodul urmtor (Next).
- LinkedList<>, care implemeteaz o list dublu nlnuit i are definite metode
pentru operaiile de baz asupra unei liste: inserare, tergere, cutare, cum ar fi:
AddAfter() inserare dup un nod dat
AddBefore() inserare nainte de un nod dat
AddFirst() inserare nod n faa listei
AddLast() adugare nod la sfritul listei
Clear() terge toate nodurile din list
Contains() verific dac lista conine un anumit nod
Find() caut prima apariie a unui nod n list, iar rezultatul este depus
ntr-un obiect de tip LinkedListNode
FindLast() caut ultima apariie a unui nod n list, iar rezultatul este
depus ntr-un obiect de tip LinkedListNode
First returneaz primul nod din list ntr-un obiect de tip
LinkedListNode
Last returneaz ultimul nod din list ntr-un obiect de tip
LinkedListNode
Remove() terge un nod dat din list
RemoveFirst() terge primul nod din list
RemoveLast() terge ultimul nod din list
Spre deosebire de clasele noastre de mai sus, clasele generice restricioneaz
tipul elementelor care pot exista n list la unul singur care va aprea ntre <..>
Exemplu de utilizare a claselor generice pentru lucrul cu liste:
using System;
using [Link];
class Program
{
static void Main(string[] args)
{
LinkedListNode<string> node = new
LinkedListNode<string>("Mike");
LinkedList<string> names = new LinkedList<string>();
[Link](node);
66
Algoritmi i structuri de date n C#
LinkedListNode<string> node1 = new
LinkedListNode<string>("David");
[Link](node, node1);
LinkedListNode<string> node2 = new
LinkedListNode<string>("Raymond");
[Link](node1, node2);
LinkedListNode<string> aNode = [Link];
while (aNode != null)
{
[Link]([Link]);
aNode = [Link];
}
aNode = [Link]("David");
[Link]();
}
}
3.7 Lucrare de verificare Nr. 3
1. Care este clasa predefinit n C# pentru lucrul cu liste ?
2. Ce sunt listele nlnuite (linked lists) i cum se realizeaz acestea n C# ?
3. Ce sunt listele dublu nlnuite i cum se realizeaz acestea n C# ?
3.8 Bibliografie
9. T.H. Cormen, [Link], R.R. Rivest Introducere n algoritmi, Mit Press
1990, trad. Computer Libris Agora.
10. V. Cretu Structuri de date i algoritmi, vol. 1, ed. Orizonturi Universitare,
2000.
11. D. Lucanu, M. Craus; Proiectarea algoritmilor, Ed. Polirom, 2008.
12. C. Giumale, L. Negreanu, S. Calinoiu Proiectarea i analiza algoritmilor.
Algoritmi de sortare, 1996.
67
Algoritmi i structuri de date n C#
4. Stive i cozi
Obiective:
Dup studiul acestui capitol, studentul va avea cunotine
suficiente pentru a fi capabil s lucreze cu structura de date
de tip stiv i respectiv coad.
Stivele i cozile sunt cazuri particulare ale listelor i sunt adesea folosite n
practic.
4.1 Stive
Particularitatea stivelor se refer la faptul c operaiile de adugare i tergere
de elemente se pot face doar la un capt al lor, astfel c nu avem acces la
elementele din interiorul structurii.
Stivele sunt folosite n cadrul limbajelor de programare, in diverse scopuri
cum ar fi: memorarea variabilelor i evaluarea expresiilor.
Stivele sunt cunoscute ca fiind structuri de tip LIFO (Last In First Out), sau
mai concret spus, ultimul intrat-primul ieit, referindu-se la modul n care se fac
inserrile i tergerile din stiv.
Operaia de adugare poart numele particular de Push, iar tergerea se
numete Pop. Vizualizarea elementului din vrful stivei (fr a-l extrage) se
numete Peek.
Implementarea propriu-zis a stivelor se poate face att folosind tablouri
(operaiile de adugare i tergere se fac doar la sfrit), ct i liste nlnuite.
Observaie: Stivele se reprezint deobicei pe vertical.
4
3 Elementul din vrful stivei este 4.
El va fi primul care se va terge.
2
O nou valoare se va aduga deasupra lui 4.
1
Stivele pot fi implementate n C# prin clase proprii, ns este mai simplu s
folosim clasa predefinit Stack. Cteva din metodele uzuale ale clasei sunt:
- Clear() : terge toate elementele din stiv
- Contains() : verific dac un anumit element se afl n stiv
- Count : returneaz numrul de elemente din stiv
- Peek() : returneaz elementul din vrful stivei (fr a-l terge)
- Pop() : terge elementul din vrful stivei
- Push() : adaug un element n stiv
68
Algoritmi i structuri de date n C#
Modaliti de creare a unui obiect de tip stiv:
1) Stack st = new Stack(); //constructor fr parametri
2) Stack<Tip> st = new Stack<Tip>(); //tipuri generice
3) Stack st = new Stack(ICollection col); //creare stiv dintr-o colecie
existent
Ex: string[] s={abd, def, ghij};
Stack st = new Stack(s);
4) Stack st = new Stack(int capacitInitiala); //setare capacitate iniial
n C#, stiva este implementat ca un buffer circular, care permite alocarea
dinamic a spaiului necesar pentru noile elemente introduse n stiv.
Exemplu: Conversia unui numr zecimal n alt baz (<=9).
using System;
using [Link];
namespace Stiva
{
class Baze
{
static void SchimbaBaza(int n, int b)
{
Stack Cifre = new Stack();
do
{
[Link](n % b);
n /= b;
} while (n != 0);
while ([Link] > 0)
[Link]([Link]());
}
static void Main(string[] args)
{
int num, baseNum;
[Link]("Dati un numar in baza 10: ");
num = [Link]([Link]());
[Link]("Dati o baza: ");
baseNum = [Link]([Link]());
[Link](num + " devine ");
SchimbaBaza(num, baseNum);
[Link](" in baza " + baseNum);
}
}
}
tim c pentru a transforma un numr din baza 10 n alt baz trebuie s
mparim n mod repetat numrul la baz i s reinem resturile obinute, iar la
final, s afim resturile n ordinea invers obinerii lor. Programul de mai sus
face acest lucru folosindu-se de o stiv, ntroducnd n ea toate resturile i la final
extrgndu-le i afindu-le exact n ordinea invers n care au fost obinute.
69
Algoritmi i structuri de date n C#
4.2 Cozi
Particularitatea cozilor se refer la faptul c operaiile de adugare i tergere
de elemente se pot face doar la un capetele lor, adugarea la un capt, iar tergerea
de la captul opus, neavnd acces la elementele din interiorul structurii.
Cozile sunt cunoscute ca fiind structuri de tip FIFO (First In First Out), sau
mai concret spus, primul intrat-primul ieit, referindu-se la modul n care se fac
inserrile i tergerile din coad.
La modul cel mai simplu, cozile pot fi comparate cu ceea ce nseamn n
viaa real o coad de persoane care ateapt de exemplu la un ghieu. Persoanele
vor veni doar la un capt al cozii (adugare) i vor pleca doar de la captul opus
(tergere).
Cozile sunt i ele folosite n domeniul calculatoarelor, cum ar pentru
planificarea job-urilor la un procesor sau la o imprimant.
Operaia de adugare poart numele particular de Enqueue, iar tergerea se
numete Dequeue.
Exemplu: Mai sus, primul element ters va fi A, iar un nou element va veni dup
D.
Dequeue Enqueue
A B C D
Cozile pot fi implementate n C# prin clase proprii, ns este mai simplu s
folosim clasa predefinit Queue. Cteva din metodele uzuale ale clasei sunt:
- Clear() : terge toate elementele din coad
- Contains() : verific dac un anumit element se afl n coad
- CopyTo() : copiaz toate elementele cozii ntr-un Array
- Count : returneaz numrul de elemente din coad
- Dequeue() : terge elementul de la nceputul cozii
- Enqueue() : adaug un element la sfritul cozii
- Peek() : returneaz elementul de la nceputul cozii, fr a-l terge
- TrimToSize() : seteaz capacitatea cozii s fie egal cu numrul curent de
elemente coninute.
Modaliti de creare a unui obiect de tip coad:
1) Queue q = new Queue(); //constructor fr parametri
2) Queue<Tip> q = new Queue<Tip>(); //tipuri generice
3) Queue q = new Queue(ICollection col); //creare coad dintr-o colecie
existent
Ex: string[] s={abd, def, ghij};
Queue q = new Queue (s);
4) Queue q = new Queue (int capacitInitiala); //setare capacitate iniial
5) Queue q = new Queue (int capacitInitiala, float factorCretere);
Implicit, capacitatea iniial a unei cozi este de 32 de elemente i de
fiecare dat cnd va fi atins va crete cu nc 32 (factor de cretere 2).
70
Algoritmi i structuri de date n C#
Problem rezolvat: Fie un ring de dans n care pot ncpea cel mult 4 perechi de
dansatori la un momentdat i un grup de femei i unul de brbai care urmeaz s
formeze perechi de dans. Se cere s se formeze dou cozi de ateptare, una pentru
femei i una pentru brbai dup care: s se afieze primele 4 perechi care vor
intra n ring i urmtoarele care vor fi n ateptare. Perechile se vor forma lund pe
rnd o femeie i un brbat din cozile formate, n ordinea n care sunt plasai n
coad. Dac sunt mai multe femei dect brbai sau invers se va afia un mesaj ca
exist persoane care sunt n ateptare pentru parteneri de dans.
Exemplu:
Coada de femei: Ana, Bianca, Claudia, Diana, Florina
Coada de brbai: Sorin, Marcel, Laurentiu, Flavius, Dan, Cosmin, Bogdan
Primele 4 perechi vor fi:
Ana i Sorin
Bianca i Marcel
Claudia i Laureniu
Diana i Flavius
Perechile n ateptare vor fi:
Florina i Dan
Cosmin atept o partener de dans
Program:
using System;
using [Link];
namespace RingDans
{
public struct Dansator
{
public string nume;
public void GetName(string n)
{
nume = n;
}
}
class Perechi
{
static void perecheAsteptare(Queue barbati, Queue femei)
{
Dansator b, f;
b = new Dansator();
f = new Dansator();
if ([Link] > 0 && [Link] > 0)
{
[Link]([Link]().ToString());
[Link]([Link]().ToString());
[Link]("Urmatorii la rand: "+[Link]+"
si "+[Link]);
}
// perechi incomplete
else if (([Link] > 0) && ([Link] == 0))
71
Algoritmi i structuri de date n C#
{
[Link]([Link]().ToString());
[Link]("Urmatorul la rand este: " +
[Link]);
[Link]("Se asteapta o partenera de
dans...");
}
else if (([Link] > 0) && ([Link] == 0))
{
[Link]([Link]().ToString());
[Link]("Urmatoarea la rand este: " +
[Link]);
[Link]("Se asteapta un partener de
dans...");
}
}
static void startDans(Queue barbati, Queue femei)
{
Dansator b, f;
b = new Dansator();
f = new Dansator();
[Link]("Primii parteneri de dans sunt: ");
for (int count = 0; count <= 3; count++) //primele
perechi
{
[Link]([Link]().ToString());
[Link]([Link]().ToString());
[Link]([Link] + " si " + [Link]);
}
[Link]();
}
static void afisareCoada(string[] str) {
foreach (string nume in str)
[Link](nume);
}
static void Main()
{
string[] f = { "Ana", "Bianca", "Claudia", "Diana",
"Florina" };
string[] b = { "Sorin", "Marcel", "Laurentiu",
"Flavius", "Dan", "Cosmin", "Bogdan" };
Queue barbati = new Queue(b);
Queue femei = new Queue(f);
[Link]("Femeile care astepta sunt:");
afisareCoada(f);
[Link]();
[Link]("Barbatii care astepta sunt:");
afisareCoada(b);
[Link]();
startDans(barbati, femei);
// atata timp cat mai sunt barbati si femei in asteptare
while ([Link] > 0 && [Link] > 0)
{
perecheAsteptare(barbati, femei);
}
//se testeaza daca a ramas vreo pereche incompleta
72
Algoritmi i structuri de date n C#
perecheAsteptare(barbati, femei);
}
}
}
Problem rezolvat: Vom prezenta n continuare un algoritm de sortare mai
vechi, cu ajutorul cruia erau sortate cartele perforate, nu neaprat cel mai rapid,
ns uor de folosit. Vom exemplifica sortarea unei mulimi de valori din
intervalul 0-99. Astfel, fiind dat un ir de numere se cere s se sorteze cresctor
dup urmtorul principiu:
Pas 1: Folosind 10 cozi numerotate de la 0 la 9, vom plasa fiecare valoare din ir
n coada corespunztoare cifrei unitilor (ex: numarul 57 va fi plasa n coada 7).
Pas 2: Refacem irul de numere reunind valorile din cele 10 cozi.
Pas 3: Folosind din nou cele 10 cozi, vom plasa fiecare valoare din ir n coada
corespunztoare cifrei zecilor (ex: numarul 57 va fi plasa n coada 5).
Pas 4: Refacem irul de numere reunind valorile din cele 10 cozi. Vom oberva c
valorile vor fi ordonate cresctor.
Exemplu:
Fie irul: 91, 46, 85, 15, 92, 35, 31, 22
Pas 1:
Coada 0:
Coada 1: 91 31
Coada 2: 92 22
Coada 3:
Coada 4:
Coada 5: 85 15 35
Coada 6: 46
Coada 7:
Coada 8:
Coada 9:
Pas 2: irul devine: 91, 31, 92, 22, 85, 15, 35, 46
Pas 3:
Coada 0:
Coada 1: 15
Coada 2: 22
Coada 3: 31 35
Coada 4: 46
Coada 5:
Coada 6:
Coada 7:
Coada 8: 85
Coada 9: 91 92
Pas 4: irul devine: 15, 22, 31, 35, 46, 85, 91, 92
Program:
using System;
73
Algoritmi i structuri de date n C#
using [Link];
namespace SortareCuCozi
{
class Sortare
{
enum TipCifra { unitati = 1, zeci = 10 }
static void DisplayArray(int[] n)
{
for (int x = 0; x < [Link]; x++)
[Link](n[x] + " ");
}
static void PlasareInCozi(Queue[] que, int[] n, TipCifra
cifra)
{
int snum;
for (int x = 0; x < [Link]; x++)
{
if (cifra == [Link])
snum = n[x] % 10;
else
snum = n[x] / 10;
que[snum].Enqueue(n[x]);
}
}
static void RefacereSir(Queue[] que, int[] n)
{
int y = 0;
for (int x = 0; x <= 9; x++)
while (que[x].Count > 0)
{
n[y] = [Link](que[x].Dequeue().ToString());
y++;
}
}
static void Main(string[] args)
{
Queue[] numQueue = new Queue[10]; //vector de cozi
int[] nums = new int[] { 91, 46, 85, 15, 92, 35, 31,
22 };
[Link]("Sirul initial: ");
DisplayArray(nums);
for (int i = 0; i < 10; i++) //crearea celor 10 cozi
numQueue[i] = new Queue();
PlasareInCozi(numQueue, nums, [Link]);
RefacereSir(numQueue, nums);
[Link]();
[Link]("Sirul dupa prima reorganizare: ");
DisplayArray(nums);
PlasareInCozi(numQueue, nums, [Link]);
RefacereSir(numQueue, nums);
[Link]();
74
Algoritmi i structuri de date n C#
[Link]("Sirul dupa a doua reorganizare: ");
DisplayArray(nums);
}
}
}
Un caz particular de cozi sunt cozile de prioriti, care asociaz fiecrui
element din coad o anumit prioritate (valoare numeric). Astfel, principiul FIFO
se modific, iar primul element care va fi scos din coad va fi cel cu prioritatea
cea mai mare.
Acest tip de cozi are o larg rspndire att n viaa real (lista de ateptare la
o intervenie chirugical n funcie de gravitatea cazurilor) ct i n domeniul
calculatoarelor (procesele au asociate prioriti i sunt executate n funcie de
acestea).
4.3 Lucrare de verificare Nr. 4
1. S se scrie funcia de inversare a unui ir folosind o stiv alocat dinamic.
2. S se scrie funcia de adugare pentru o stiv alocat ca vector.
3. S se scrie funcia de extragere a unui element dintr-o stiv alocat ca vector.
4. S se descrie modul de organizare a unei cozi alocate ca vector i s se scrie
funcia de adugare a unui element.
5. S se descrie modul de organizare a unei cozi alocate ca vector i s se scrie
funcia de tergere a unui element.
6. S se scrie funcia de concatenare a dou stive alocate dinamic S1 i S2
folosind doar operaiile de baz (adugare, extragere, testare stiv vid).
Rezultatul trebuie s conin elementele din cele dou stive n ordinea iniial.
Indicaie: Se va folosi o stiv temporar.
7. S se scrie funcia de conversie a unei stive n list simplu nlnuit.
8. Se consider un ir de numere ntregi. S se scrie funcia care construiete dou
stive (una cu numerele negative i una cu cele pozitive) ce conin numerele n
ordinea iniial folosind doar structuri de tip stiv. Indicaie: Se adaug toate
elementele ntr-o stiv temporar dup care se extrag elementele din aceasta i se
introduc n stiva corespunztoare.
9. Scriei funcia recursiv pentru tergerea tuturor elementelor dintr-o stiv
alocat dinamic.
10. Scriei funcia pentru tergerea tuturor elementelor dintr-o coad alocat
dinamic.
75
Algoritmi i structuri de date n C#
4.4 Arbori. Noiuni generale
Arborii fac parte din grupa structurilor de date de tip neliniar permind
memorarea informaiilor ntr-o manier ierarhic.
Arborii sunt formai din noduri, iar referinele dintre ele se numesc laturi. Un
arbore are un singur nod numit rdcin. Un nod poate referi mai multe noduri,
situaie n care el se va numi nod printe, iar nodurile referite se vor numi noduri
copil/fii/descendeni. Un nod care nu refer nici un alt nod se numete nod frunz.
Exemplu de arbore:
7 ............................... nivel 0
4 9 6 ............ nivel 1
3 1 ... nivel 2
2
Rdcin: nodul 7
Copiii (fii) nodului 7: 4, 9, 6
Frunze: 2, 9, 3, 1
Arborele este organizat pe nivele, astfel c rdcina se afl pe nivelul 0, fii si
se afl pe nivelul 1, fii fiilor si pe nivelul 2, .a.m.d.
Orice nod de pe un anumit nivel este considerat a fi subarbore care l are pe
el ca i rdcin i tot ce se afl sub el sunt membrii acelui subarbore.
Exemplu: n figura de mai sus, nodul 6 este rdcina subarborelui care are ca i
membrii nodurile 3 i 1.
Traseul care poate fi urmat pentru a ajunge de la un nod la altul n arbore se
numete drum. Vizitarea tuturor nodurilor unui arbore se numete traversarea
arborelui.
Structura de tip arbore este i ea una cu autoreferire, deoarece fiecare nod
conine o parte de informaie util i referinele spre nodurile fii.
Un arbore n care fiecare nod are cel mult doi fii se numete arbore binar.
Acest tip de arbori este foarte rspndit n practic, iar n cele ce urmeaz ne vom
ocupa de cteva tipuri de arbori binari.
Deoarece fiecare nod poate avea doar maxim doi fii, acetia vor fi numii fiu
stng i fiu drept.
76
Algoritmi i structuri de date n C#
Traversarea arborilor binari:
Exist trei modaliti de a parcurge integral un arbore binar i vom vedea c
ele seamn ntre ele, diferind n funcie de momentul cnd se viziteaz rdcina.
1. Parcurgerea n preordine (RSD): Presupune vizitarea rdcinii, a fiului
stng i apoi a fiului drept. Deoarece fii pot fi la rndul lor subarbori, procedeul se
aplic recursiv, pn cnd se ajunge la noduri frunz.
2. Parcurgerea n postordine (SDR): Presupune pornind de la rdcina
arborelui, vizitarea fiului stng, a fiului drept i apoi rdcinii. Deoarece fii pot fi
la rndul lor subarbori, procedeul se aplic recursiv, pn cnd se ajunge la noduri
frunz.
3. Parcurgerea n inordine (SRD): Presupune pornind de la rdcina
arborelui, vizitarea fiului stng, a rdcinii i apoi a fiului drept. Deoarece fii pot
fi la rndul lor subarbori, procedeul se aplic recursiv, pn cnd se ajunge la
noduri frunz.
4.5 Arbori binari de cutare
Particularitatea arborilor binari de cautare const n faptul c la orice nivel
al arborelui, toate informaiile din stnga rdcinii sunt mai mici sau egale cu
informaia din rdcin, iar toate informaiile din dreapta rdcinii sunt mai mari
sau egale cu informaia din rdcin.
Exemplu:
6
4 8
7 9
2
Contraexemplu:
6
4 8
5 9
2
77
Algoritmi i structuri de date n C#
Acesta nu este un arbore binar de cutare deoarece nodul 5, fiind n dreapta
nodului 6, nu ndeplinete condiia de a fi mai mare dect el. La nivel local ns,
subarborele de rdcin 8 este arbore de cutare.
Motivul pentru care aceti arbori se numesc de cutare este, dup cum le
spune i numele, faptul c sunt foarte utili de folosit n operaii de regsire rapid
a informaiilor datorit particularitii de construire a lor. Astfel, dac avem de
cutat o informaie n arbore, o vom compara-o prima dat cu informaia din
rdcin. Dac este mai mic, vom elimina partea dreapt a arborelui i vom cuta
doar n stnga. Dac este mai mare, vom elimina partea stng a arborelui i vom
cuta doar n dreapta. Procedeul se va aplica recursiv pn cnd se gsete
informaia sau se iese din arbore.
Exemplu 1: Cutm informaia 7 n arborele de mai sus.
Comparm 7 cu informaia din rdcin i vedem c este mai mare. Trecem la
cutarea n subarborele drept.
Comparm 7 cu 8 i vedem c este mai mic. Trecem la cutarea n subarborele
stng.
Comparm 7 cu 7 i gsim egalitate. Cutarea se ncheie cu succes.
Exemplu 2: Cutm informaia 3.
Comparm 3 cu informaia din rdcin i vedem c este mai mic. Trecem la
cutarea n subarborele stng.
Comparm 3 cu 4 i vedem c este mai mic. Trecem la cutarea n subarborele
stng.
Comparm 3 cu 2 i vedem c este mai mare. Trecem la cutarea n subarborele
drept, ns n dreapta nu mai avem nici un nod, deci se iese din arbore. Cutarea se
ncheie, fr a fi gsit n arbore informaia cutat.
Inserarea unui nou nod: Adugarea unui nod n arbore va fi ntotdeauna pe o
poziie frunz i se face conform urmtorului principiu: Se compar valoarea
nodului de inserat cu cea din rdcin. Dac este mai mic, se va compara cu fiul
stng al rdcinii, iar dac este mai mare se va compara cu fiul drept al rdcinii.
Procedeul se repet recursiv pn cnd se ajunge la compararea cu un nod frunz,
astfel c noul nod se va aduga n stnga sau n dreapta unei frunze din arbore.
tergerea unui nod: Eliminarea unui nod din arbore este puin mai dificil i
trebuie inut cont de faptul c pot exista 3 situaii distincte:
a) se terge o frunz
b) se terge un nod cu un singur fiu
c) se terge un nod cu 2 fii
tergerea unui frunze sau a unui nod cu un singur fiu se face relativ uor,
ns eliminarea unui nod care are att fiu stng, ct i fiu drept presupune ceva
mai multe operaii, pentru a ne asigura c dup eliminarea nodului, arborele
complet ndeplinete n continuare condiiile de arbore de cutare. Din aceste
considerente, n programul de mai jos este implementat doar tergerea unui
frunze.
78
Algoritmi i structuri de date n C#
Pentru a terge un nod frunz trebuie parcurs arborele de la rdcin, pentru a
gsi nodul printe al frunzei i a-i seta referina stng sau dreapt a sa (n funcie
de poziia nodului de ters) pe null.
Program:
using System;
namespace ArboriBinariDeCautare
{
public class Node
{
public int Data;
public Node Stanga;
public Node Dreapta;
public void DisplayNode()
{
[Link](Data + " ");
}
}
public class BinarySearchTree
{
public Node radacina;
public BinarySearchTree()
{
radacina = null;
}
public void Insert(int i)
{
Node newNode = new Node();
[Link] = i;
if (radacina == null)
radacina = newNode;
else
{
Node curent = radacina;
Node parinte;
while (true)
{
parinte = curent;
if (i < [Link])
{
curent = [Link];
if (curent == null)
{
[Link] = newNode;
break;
}
}
else
{
curent = [Link];
if (curent == null)
79
Algoritmi i structuri de date n C#
{
[Link] = newNode;
break;
}
}
}
}
}
public bool Delete(int key)
{
Node curent = radacina;
Node parinte = radacina;
bool esteFiuStang = true;
while ([Link] != key)
{
parinte = curent;
if (key < [Link])
{
esteFiuStang = true;
curent = [Link];
}
else
{
esteFiuStang = false;
curent = [Link];
}
if (curent == null)
return false;
}
if (([Link] == null) && ([Link] ==
null))
if (curent == radacina)
radacina = null;
else if (esteFiuStang)
[Link] = null;
else
[Link] = null;
return true;
}
public void InOrder(Node rad)
{
if (rad != null)
{
InOrder([Link]);
[Link]();
InOrder([Link]);
}
}
public void PreOrder(Node rad)
{
if (rad != null)
{
80
Algoritmi i structuri de date n C#
[Link]();
PreOrder([Link]);
PreOrder([Link]);
}
}
public void PostOrder(Node rad)
{
if (rad != null)
{
PostOrder([Link]);
PostOrder([Link]);
[Link]();
}
}
}
class TestArbore
{
static void Main(string[] args)
{
BinarySearchTree nums = new BinarySearchTree();
[Link](23);
[Link](45);
[Link](16);
[Link](37);
[Link](3);
[Link](99);
[Link](22);
[Link]("Adaugam pe rand nodurile:
23,45,16,37,3,99,22");
[Link]("Traversarea in inordine: ");
[Link]([Link]);
[Link]("\nTraversarea in preordine: ");
[Link]([Link]);
[Link]("\nTraversarea in postordine: ");
[Link]([Link]);
[Link]("\nStergem nodul cu informatia 37");
[Link](37);
[Link]("Traversarea in inordine: ");
[Link]([Link]);
}
}
}
Conform ordinii n care au fost introduse nodurile, arborele va arta astfel:
23
16 45
3 22 37 99
81
Algoritmi i structuri de date n C#
Exist o multitudine de alte tipuri de arbori utilizai n practic. Dintre ei vom
aminti:
Arbori echilibrai (AVL) : Numele acestor arbori vine de la cei care i-au
descoperit i anume G.M. Adelson-Velskii i E. M. Landis, n 1962.
Caracteristica principal a unui arbore AVL const n faptul c pentru orice nod,
diferena de nlime dintre subarborele stng i subarborele drept este cel mult unu. n
acest fel, se spune c arborele este echilibrat.
Arbori rou-negru : Acest tip de arbori se numete astfel deoarece fiecare nod
are asignat una din cele dou culori, conform unui set de reguli:
1) Rdcina arborelui este neagr
2) Dac un nod este rou atunci fii acelui nod trebuie s fie negri
3) Fiecare drum de la un nod pn la o referin nul (dup un nod frunz)
trebuie s conin acelai numr de noduri negre
Avantajul acestui tip de arbori const n faptul c sunt foarte bine balansai,
iar cutarea se poate face destul de eficient.
Exemplu:
Arbori splay : Acest tip de arbori echilibrai au n plus caracteristica de a
plasa, printr-un mecanism prestabilit, nodurile mai recent cutate ct mai
aproape de rdcin, pentru o regsire mai rapid.
82
Algoritmi i structuri de date n C#
4.6 Lucrare de verificare Nr. 5
1. S se scrie un program care citete de la tastatura un sir de caractere ce
reprezint o expresie logica, ii genereaz arborele expresiei i stocheaz intr-o
lista variabilele identificate. Pentru verificarea programului, se va scrie o funcie
care afieaz arborele folosind parcurgerea in nordine: expresia asociata
subarborelui stng, apoi operatorul din rdcina, apoi expresia asociata
subarborelui drept.
2. Sa se scrie o funcie recursiva care, primind ca parametri o referin la arborele
(sau subarborele) unei expresii i o referin la o lista de perechi (variabila,
valoare), returneaz rezultatul evalurii expresiei respective. Sa se completeze
programul scris la punctul 1, astfel nct acesta sa citeasc apoi valori pentru
fiecare variabila detectata si sa evalueze expresia logica folosind acele valori.
3. Se citete de la dispozitivul de intrare o expresie matematic n form prefixata,
sub forma unui ir de caractere. S se construiasc arborele corespunztor acestei
expresii, fiecare nod coninnd un operator sau un operand. S se evalueze aceast
expresie.
4. S se scrie proceduri nerecursive pentru traversarea arborilor binari.
5. S se scrie o funcie care calculeaz numrul de frunze ale unui arbore binar.
6. S se gseasc o reprezentare a arborilor multici i s se scrie funcii primitive
pentru arborii multici.
4.7 Bibliografie
13. T.H. Cormen, [Link], R.R. Rivest Introducere n algoritmi, Mit Press
1990, trad. Computer Libris Agora.
14. V. Cretu Structuri de date i algoritmi, vol. 1, ed. Orizonturi Universitare,
2000.
15. D. Lucanu, M. Craus; Proiectarea algoritmilor, Ed. Polirom, 2008.
16. C. Giumale, L. Negreanu, S. Calinoiu Proiectarea i analiza algoritmilor.
Algoritmi de sortare, 1996.
83