Sunteți pe pagina 1din 90

e

ar
m
Lucrarea nr. 1

ra
Determinarea experimentala a timpului de

og
execuþie al unui program

pr
de
1. Scopul lucrãrii - lucrarea prezintã aspecte legate de diferite

ci
modalitaþi de determinare experimentalã a timpilor de execuþie pentru

ni
diferite secvenþe de program.

eh
it
2. Aspecte teoretice

is
2.1. Determinarea experimentalã a timpului de execuþie al unui

itm
program
Determinarea timpului de execuþie al unui program prin metode

or
analitice este foarte dificilã în practicã. Aºa ca mai exista si o alta

lg
-A metoda experimentalã, mult mai uºoarã, ºi cu rezultate la fel de
concludente în condiþiile în care se porneºte de la anumite premize
da

iniþiale. Ideea acestei metode constã din determinarea timpului efectiv


(în secunde, milisecunde, etc.) necesar rulãrii unui algoritm, folosindu-
an

ne de ceasul calculatorului. Se prezintã mai jos schema bloc a acestei


ar

metode.
m
.Z
D

Ideea acestui algoritm este aceea de a determina, folosindu-se ceasul


calculatorului, douã momente de timp : dinaintea inceputului
algoritmului ºi respectiv imediat de dupa terminarea acestuia.
Rezultatul, ca fiind diferenþa între cele douã momente, este considerat
ca fiind timpul de execuþie efectiv al algoritmului respectiv.

5
e
ar
m
Este cunoscut faptul cã, în general, timpul de execuþie al unui program

ra
depinde de urmãtorii factori :

og
-volumul datelor de intrare

pr
-calitatea codului generat de compilator

de
-natura si viteza de execuþie a instrucþiunilor programului (dependentã
ºi de performanþele calculatorului pe care acesta ruleazã)

ci
ni
-complexitatea algoritmului care stã la baza programului.

eh
In condiþiile în care algoritmii sunt rulaþi pe calculatoare cu performanþe

it
similare, utilizîndu-se acelaºi compilator, practic timpul de execuþie

is
devine funcþie doar de volumul datelor de intrare ºi complexitatea

itm
algoritmului. In aceste condiþii simplificate, evaluarea experimentalã a
performanþelor unui algoritm este cu adevarat relevantã.

or
In C++, funcþia prin care se poate determina ora curentã a calculatorului

lg
este gettime, ºi se gãseºte în bibioteca <dos.h> .
-A
2.2. Exemplu de determinarea experimentala a timpului de executie
da

al unui program
an

Se prezintã în continuare modul de evaluare a timpului de execuþie a


ar

procedurii de sortare a elementelor unui tablou de dimensiune N


m

utilizînd algoritmul bubblesort, prezentat mai jos:


.Z
D

typedef int tablou[n];


void bubble_sort(tablou a)
{
int i,j,temp
(1) for (i=0;i<n;i++)
(2) for (j=n;j>=i+1;j--)
(3) if (a[j-1]>a[j])
{
(4) temp=a[j-1];
(5) a[j-1]=a[j];
(6) a[j]=temp
}
}

Dacã dorim sã folosim metoda experimentalã, ºi sã utilizãm funcþia


gettime, programul se va modifica astfel:

typedef int tablou[n];


void bubble_sort(tablou a)
{
int i,j,temp

6
e
ar
m
for (i=0;i<n;i++)

ra
for (j=n;j>=i+1;j--)

og
if (a[j-1]>a[j])

pr
{
temp=a[j-1];

de
a[j-1]=a[j];

ci
a[j]=temp

ni
}

eh
}

it
is
void main()
{

itm
...

or
(1) struct time t1,t2;
int durata;

lg
-A (2) gettime(&t1);
(3) bubble_sort(a);
(4) gettime(&t2);
da

(5) durata=(t2.ti_sec*100+t2.ti_hund)
an

-(t1.ti_sec*100+t1.ti_hund);
ar

printf(“Durata de executie=%dms”,durata);
m

...
.Z

}
D

Trebuie þinut cont însã de faptul cã funcþia gettime are nevoie ca ºi


parametri de variabile de tip structurã de timp, deci în cazul nostru vom
avea nevoie de douã variabile t1 ºi t2 în care sã se stocheze ora
calculatorului (1). Se stocheazã ora calculatorului în t1, exact înainte
de a rula algoritmul (2). Se ruleazã algoritmul (3) dupa care, imediat se
stocheazã în t2 ora calculatorului (4). Pentru obþinerea diferenþei se va
realiza un mic calcul matematic pentru diferenþa între cei doi timpi t2
ºi t1 (5), iar rezultatul final obþinut va fi durata de execuþie în sutimi de
secunda.
Utilizînd aceasta metodã experimentalã de determinare a timpului de
execuþie, rezultatul se obþine mult mai uºor, fiindu-ne oferit chiar de
calculator, în comparaþie cu metoda analiticã, unde tot calculul trebuie
realizat manual de catre programator, ºi care implicã, în anumite cazuri,
cunoºtinþe aprofundate de matematicã. Este adevãrat cã metoda
analiticã este mai generalã.
O discuþie asemãnãtoare cu cea referitoare la timpul de execuþie se
poate face relativ la memoria utilizatã de un program. De obicei, dacã
nu este vorba de o programare defectuoasã, micºorarea spaþiului de

7
e
ar
m
memorie utilizat se face pe seama timpului de execuþie. Un exemplu

ra
tipic în acest sens îl constituie reprezentarea compactã a matricilor:

og
economia de memorie obþinutã în acest fel este însã în detrimentul

pr
timpului de execuþie, deoarece accesul la elementele matricii este mult

de
mai complicat.
În privinþa aspectelor referitoare la portabilitate, reutilizabilitate sau

ci
mentenanþã, estimarea performanþelor este mult mai puþin precisã.

ni
Evident, utilizarea în programare doar a construcþiilor standard va

eh
permite transferul uºor al programelor dintr-un mediu în altul, dar

it
aceasta vine în contradicþie cu cerinþele referioare la timp ºi memorie

is
ocupatã. De asemenea, pentru a permite reutilizarea modulelor este

itm
necesar ca interfaþa oferitã de acestea sã fie cît mai clarã ºi sã ofere cît
mai multe servicii posibile, astfel încît sã nu fie necesar nici un acces

or
care sã ocoleascã acestã interfaþã: satisfacerea acestor cerinþe intrã din

lg
-A
nou în contradicþie cu cele referitoare la eficienþã. În concluzie,
programatorul, atunci cînd elaboreazã un algoritm pentru o anumitã
da

problemã trebuie sã aibã în vedere toate aceste aspecte, ºi sã realizeze,


funcþie de cerinþele aplicaþiei în sine, un compromis între acestea.
an
ar

2.3. Problemã rezolvatã


m

Sã se realizeze un program care calculeazã n!. Pentru calculul


.Z

factorialului se va scrie o funcþie fact(int n). Sã se calculeze


D

timpii de execuþie al acestei functii pentru diferite valori ale lui n.

#include <conio.h>
#include <stdio.h>
#include <dos.h>

int nr;
struct time timp1,timp2;
unsigned long int fact(int n)
{unsigned long int f=1;
for (long int i=1;i<=n;i++)
{f*=i;
//fortam o intarziere pentru punere în
//evidenþã a timpului de executie
delay(100);
}
return f;
}

8
e
ar
m
void main()

ra
{ clrscr();

og
do

pr
{ //citire numar

de
printf("Dati numarul (1..30) = ");
scanf("%d",&nr);

ci
}

ni
while ((nr<1)||(nr>30));

eh
it
//get time start

is
gettime(&timp1);
//calculam si afisam factorialul

itm
printf("%d!=%lu",nr,fact(nr));

or
//get time final

lg
gettime(&timp2);
-A
//convertim timpii in ms
da

unsigned long int ms1=timp1.ti_hund*10 +


(timp1.ti_sec*1000) + (timp1.ti_min*60000) +
an

(timp1.ti_hour*3600000);
ar

unsigned long int ms2=timp2.ti_hund*10 +


m

(timp2.ti_sec*1000) + (timp2.ti_min*60000) +
.Z

(timp2.ti_hour*3600000);
D

//calculam diferenta dupa care o afisam


unsigned long int dif=ms2-ms1;
printf("\nTimpul de executie=%lu ms",dif);
getch();
}

3. Probleme propuse

1. Sã se realizeze un program care genereazã un set de n numere


aleatoare, care vor fi stocate într-un tablou a[n]. Se va folosi o
funcþie gener(int n). Sã se calculeze timpii de execuþie pentru
diferite valori ale lui n.

2. Sã se scrie o funcþie wait, avand sintaxa wait(int s). Scopul


acestei funcþii va fi de a simula o pauzã de rulare lungã de aproximativ s
secunde, folosindu-se doar funcþia gettime.

9
e
ar
m
Lucrarea nr. 2

ra
Tehnici de cãutare în tablouri

og
pr
1. Scopul lucrãrii - în foarte multe programe, se ajunge la un moment

de
dat în situaþia de a fi necesarã cãutarea unei anumite informaþii, dupã

ci
anumite criterii, dintr-un set de informaþii. Lucrarea curentã prezintã

ni
unele metode de implementare a unor astfel de algoritmi de cautare.

eh
it
2. Aspecte teoretice

is
2.1. Tehnica fanionului.

itm
Vom considera cã dorim sã cãutam dacã existã un anumit numãr dat,
stocat într-o variabilã X ,într-un set de n numere stocate într-un tablou

or
dat A. Tehnica fanionului urmãreºte îmbunãtãþirea performanþei

lg
algoritmului de cãutare standard, prin simplificarea condiþiei de cãutare.
-A
Principiul acestuia este foarte simplu : se mai adauga la sfarsitul
tabloului înca un element (fanion) ºi care se asigneazã cu valoarea
da

cautata X. Astfel, la conditia de cãutare nu vom mai avea nevoie de


an

condiþia de oprire i<n (care avea grijã ca toatã cãutarea sã se limiteze la


ar

numãrul de elemente din tablou), fiindcã indiferent dacã acest element


m

existã sau nu în tabloul nostru, elementul în cauzã tot va fi gãsit ºi


.Z

cãutarea se va opri, fie în interiorul tabloului (dacã exista) fie pe fanion


(dacã elementul nu existã în tabloul iniþial).
D

Programul va arãta astfel:

int a[100],n,x,i;
void main()
{
clrscr() ;
(1) printf(”Dati valoarea lui n=”);
(2) scanf(“%d”,&n) ;
for (i=0 ;i<n ;i++)
{
(3) printf(“Dati a[%d]=”,i);
(4) scanf(“%d”,&a[i]) ;
}
(5) printf(“Dati valoarea de cautat (x)=”);
(6) scanf(“%d”,&x);
(7) i=0;
(8) a[n]=x;
(9) while (a[i]!=x) i++;
(10) if (i==n)
(11) printf(“Nu a fost gasit in tablou.”);

10
e
ar
m
else

ra
(12) printf(“A fost gasit pe pozitia %d.”,i);

og
}

pr
de
Se poate observa cã singurele diferenþe fata de algoritmul clasic de
cãutare se gãsesc în liniile 8 si 9. Linia 8 pozitioneazã fanionul , iar linia

ci
9 face cãutarea propriu zisaã, cãutare care se va derula pîna cînd se va

ni
gãsi o valoare în tablou egalã cu. Deci, toatã condiþia de continuare al

eh
algoritmului de cãutare se rezuma la o singura expresie logica a[i]!=x.

it
Ideal : cînd elementul cãutat e primul element (prima poziþie) din

is
tablou, acesta fiind gãsit încã de la primul pas

itm
Dezavantajos : cînd elementul nu exista în tablou , deci cãutarea se va
opri pe elementul fanion (ultimul).

or
lg
-A 2.2. Cãutarea binarã
Cãutarea într-un tablou poate fi acceleratã în mod considerabil dacã
elementele unui tablou sînt în prealabil ordonate (sortate). Necesitatea
da

ca tabloul sã fie ordonat implicã faptul cã elementele sale au o


an

componentã (cheie) ce aparþine unui tip scalar, iar cãutarea se face dupã
ar

aceastã componentã. În acest caz principiul cel mai des utilizat este cel
m

al înjumãtãþirii repetate a intervalului în care elementul dorit trebuie


.Z

cãutat, rezultînd aºa numita tehnicã a cãutãrii binare.


D

Ca principiul acestui algoritm sã fie mai bine înþeles, sa ne reamintim


de un mic joc din copilãrie. Regula acestuia era simplã : trebuia sã se
gãseascã un numãr secret dintr-un interval de la 1 la 100, folosind cît
mai putine încercãri. Principiul era simplu : este numãrul acela mai mic
decat 50 (jumatate)? Daca da, înseamnã cã numarul se aflã între 1 si 50.
Apoi din nou : este acel numãr mai mic decît 25 (jumatate) ? Dacã nu,
înseamnã ca se afla între 25 si 50. ªi aºa mai departe, prin injumãtãþiri
repetate, intervalul este redus pînã se gasea (sau nu!) numãrul în cauzã.
Acesta este ºi principiul cãutarii binare.
Pseudocodul algoritmului de cãutare binarã este urmãtorul:

1. Citire tablou (trebuie obligatoriu sa fie sortat)


2. Citire element de cautat x
3. stanga=0;dreapta=n-1;
4. repeta
5. . calculeaza mijlocul : mij=(stanga+dreapta)/2;
6. . daca (a[mij]>x) x se afla in partea stanga
7. => dreapta=mij-1
8. . altfel x se afla in partea dreapta => stanga=mij+1

11
e
ar
m
9. atata timp cat [(a[mij]!=x) si (stanga<=dreapta)]

ra
10. daca (a[mij]=x) elemental a fost gasit pe pozitia mij

og
11. altfel elemental nu a fost gasit

pr
de
La fiecare repetiþie, intervalul inspectat, situat între indicii stanga ºi
dreata este înjumãtãþit. Numãrul necesar de comparaþii este cel mult

ci
egal cu log2(N), N reprezentînd numãrul de elemente ale tabloului în

ni
care se face cãutarea.

eh
Ideal : elementul cãutat este exact la mijlocul tabloului nostru, rezultînd

it
cã va fi gãsit încã de la primul pas

is
Dezavantajos : elementul nu existã în tablou

itm
or
2.3. Cautarea binarã performantã
Bazatã pe cãutara binarã prezentatã anterior, aceastã tehnicã foloseºte

lg
-A
un alt mod de calcul a extremitãþii dreapte a intervalului curent, astfel
încît se simplificã condiþia de ciclare.
da

Citire tablou (trebuie obligatoriu sa fie sortat)


an

1.
Citire element de cautat x
ar

2.
stanga=0;dreapta=n;
m

3.
repeta
.Z

4.
calculeaza mijlocul : mij=(stanga+dreapta)/2;
D

5. .
6. . daca (a[mij]>x) x se afla in partea stanga => dreapta=mij
7. . altfel x se afla in partea dreapta => stanga=mij+1
8. atata timp cat (stanga<dreapta)
9. daca (a[mij]=x) elemental a fost gasit pe pozitia mij
10. altfel elemental nu a fost gasit

Se poate vedea cã nici acest algoritm nu diferã cu mult de cel anterior


doar cã, prin modificarea modului de calcul a limitei dreapta, condiþia
de oprire se reduce la o singurã expresie logicã, mai simplã decît cazul
anterior.
Ideal : elementul cãutat este exact la mijlocul tabloului nostru
Dezavantajos : elementul nu existã în tablou

12
e
ar
m
2.4. Cãutarea prin interpolare

ra
Este similarã cu cãutarea binarã, dar foloseºte o altã formulã pentru

og
calculul mijlocului, ºi anume:

pr
mij=stanga+(x-a[stanga])*(dreapta-stanga)

de
div (a[dreapta]-a[stanga])
ceea ce conduce la o delimitare mai rapidã a zonei din tablou în care

ci
s-ar putea gãsi x. Ca principiu, metoda este inspiratã dupã procedeul

ni
c ãutarii într-o carte de telefon.

eh
Pseudocodul unui program care foloseste aceastã metodã este

it
urmãtorul :

is
itm
1. Citire tablou (trebuie obligatoriu sa fie sortat)
2. Citire element de cautat x

or
3. stanga=0;dreapta=n-1;

lg
-A 4. repeta
5. . calculeaza mijlocul : mij= …….
da

6. . daca (a[mij]>x) x se afla in partea stanga => dreapta=mij


7. altfel x se afla in partea dreapta => stanga=mij+1
an

.
8. atata timp cat (a[mij]!=x) si (stanga<dreapta) si
ar

(a[stanga]!=a[dreapta]) si (x>a[stanga]) si (x<a[dreapta])


m

9. daca (a[mij]=x) elemental a fost gasit pe pozitia mij


.Z

10. altfel elemental nu a fost gasit


D

În continuare vom explica pe larg metoda prin care s-a ajuns la formula
de calcul al mijlocului. Principiul este urmãtorul : unde ar trebui sa fie
acel numar, în cazul în care distribuþia numerelor în tablou ar fi
uniformã.
Presupunem urmatoarea situaþie :

3 6 10 12 20 31
| |
stanga dreapta

Se poate observã cã numerele din tablou nu sunt distribuite uniform


(adica pasul, diferenþa între douã elemente vecine nu este tot timpul
acelaºi). Pentru a calcula cum ar trebui sã arate elementele în tablou
astfel incît sã fie realizatã o distribuþie uniformã, trebuie prima datã sã
calculãm pasul:

Pasul = (a[dreapta]-a[stanga]) / (dreapta-stanga) (1)


În cazul nostru:

13
e
ar
m
Pasul = (31-3) / (5-0) = 28/5 = 5,6.

ra
Deci , tabloul nostru ar arãta astfel dacã ar exista distribuþie uniformã:

og
pr
3 8,6 14,2 19,8 25,4 31

de
| |
stanga dreapta

ci
ni
Deci, dacã ar fi sã cãutam elementul cu valoarea 16, acesta ar fi undeva

eh
în zona lui 14,2 , adicã pe pozitia a 3-a relativ la stînga. Cum se

it
calculeaza asta? Astfel :

is
-presupunem ca avem o distribuþie uniformã, cu primul element 1 ºi

itm
pasul 5.

or
lg
1 6 11 16 21 26
| |-A
stanga dreapta
da

-dacã ar fi sã calculãm pe ce poziþie e valoarea x=16, am aplica simplu


an

urmãtoarea formulã :
ar
m

pozitie=stanga + (x-a[stanga])/pasul (2)


.Z
D

Adica pozitie=0+(16-1)/5=15/5=3 , ceea ce este corect.


Combinînd formulele (1) si (2) , ºi modificînd numele variabilei
poziþie cu mij , va rezulta tocmai formula ce trebuia demonstratã.
Aceastã metodã este eficientã în cazul în care numãrul de elemente din
tablou, N, este foarte mare, ºi valorile elementelor tabloului au o
distribuþie uniformã în intervalul a[1],...,a[N]. Numãrul de cãutari în
acest caz este de ordinul lg (lg N). Aplicarea cãutarii prin interpolare
necesitã însã ca elementul de cãutat x sã se afle în interiorul intervalului
a[1],..,a[N], altfel apare riscul ca valoarea calculatã a lui mij sã
depãºeascã N.
Ideal : Elementul cãutat sã fie în aceeaºi poziþie ºi în varianta
distribuitã uniform a tabloului nostru, ca ºi în tabloul dat iniþial,
rezultînd în acest caz gasirea acestuia încã de la primul pas
Dezavantajos : Tabloul nostru diferã foarte mult de varianta distribuitã
uniform (ex: 1 – 50000 – 50001 – 50002 - 50003 – 50004 )

14
e
ar
m
2.5. Problemã rezolvatã

ra
Sã se realizeze un program în C++ cu ajutorul cãruia se va cãuta

og
existenþa unui anumit student într-o listã de n studenti. Pentru cãutare se

pr
va folosi tehnica cãutarii binare. Fiecare student va fi caracterizat prin

de
numele ºi grupa din care face parte.

ci
#include <conio.h>

ni
#include <stdio.h>

eh
#include <string.h>

it
is
struct st

itm
{
char nume[20];

or
char grupa[6];

lg
}student[51];
-A
int nr_studenti;
da

char nume_cautat[20];
an

void citeste_studenti()
ar

{ do
m

{
.Z

printf("Dati numarul de studenti (5..50)=");


D

scanf("%d",&nr_studenti);
}
while ((nr_studenti<5)||(nr_studenti>50));
for (int i=1;i<=nr_studenti;i++)
{
printf("\nDati numele studentului %d=",i);
scanf("%s",student[i].nume);
printf("Dati grupa studentului %d=",i);
scanf("%s",student[i].grupa);
}
}

void sorteaza_studenti()
{st temp;
int gasit;
do
{gasit=0;
for (int i=1;i<=nr_studenti-1;i++)
if
(strcmp(student[i].nume,student[i+1].nume)>0)

15
e
ar
m
{

ra
gasit=1;

og
temp=student[i];

pr
student[i]=student[i+1];
student[i+1]=temp;

de
}

ci
}

ni
while (gasit==1);

eh
}

it
is
void cautare_binara()
{ int stanga=1,dreapta=nr_studenti,mijloc;

itm
do

or
{
mijloc=(stanga+dreapta)/2;

lg
if -A
(strcmp(student[mijloc].nume,nume_cautat)>0)
dreapta=mijloc-1;
da

else
an

stanga=mijloc+1;
ar

}
m

while
.Z

((strcmp(student[mijloc].nume,nume_cautat)!=0)
&& (stanga<=dreapta));
D

if
(strcmp(student[mijloc].nume,nume_cautat)==0)
printf("\n\nStudentul a fost gasit in
grupa %s.",student[mijloc].grupa);
else
printf("\nStudentul NU a fost gasit!");
}

void main()
{ clrscr();
citeste_studenti();
//pentru a aplica cautarea binara, studentii vor trebui sortati
sorteaza_studenti();
//afisam studentii gata sortati
clrscr();
printf("Studentii sortati alfabetic sunt:");
for (int f=1;f<=nr_studenti;f++)
printf("\nNume:%20s\tGrupa:%5s",student[f].nume,
student[f].grupa);

16
e
ar
m
//citeste numele studentului care dorim sa-l cautam

ra
printf("\n\nDati numele studentului de

og
cautat = ");

pr
scanf("%s",nume_cautat);
//lansam cautarea binara

de
cautare_binara();

ci
getch();

ni
}

eh
it
is
3. Probleme propuse

itm
1. Se considerã o matrice de numere de dimensiune MxN. Sã se citeascã

or
de la consolã un numãr, ºi sã se caute existenþa acestui numãr în

lg
matrice, folosind tehnica fanionului. Evaluaþi experimental timpul de
-A execuþie al algoritmului de cãutare, pentru diferite valori ale lui M ºi N,
folosind metoda descrisã în lucrarea 1.
da
an

2. Se considerã un numãr de n societãþi comerciale, caracterizate prin


nume, adresa ºi rulaj. Dîndu-se un anumit rulaj, citit de la tastaturã, sã
ar
m

se scrie programul care cautã societatea a cãrui rulaj este cel mai
.Z

apropiat de valoarea introdusã, datã. Se va folosi în acest scop cãutarea


prin interpolare.
D

17
e
ar
m
ra
Lucrarea nr. 3

og
Tehnici de sortare directe ale tablourilor

pr
de
1. Scopul lucrãrii – îl reprezintã prezentarea celor mai cunoscute

ci
metode de sortare directe a tablourilor.

ni
eh
2. Aspecte teoretice

it
Metodele directe de sortare, cele mai cunoscute, ºi care realizeazã

is
sortarea in situ sunt urmãtoarele:

itm
- sortarea prin inserþie
- sortarea prin selecþie

or
- sortarea prin interschimbare

lg
Performanþa acestora este O(N2). -A
2.1.Tehnica sortãrii prin inserþie
da

Cele N elementele de sortat sînt în mod conceptual divizate într-o


an

secvenþã destinaþie a1 .... ai-1 ºi respectiv într-o secvenþã sursã ai ... an. La
ar

fiecare pas, începînd cu i=2 ºi incremetînd pe i cu 1, cel de-al i-lea


m

element al secvenþei sursã ai este luat ºi transferat în secvenþa destinaþie


.Z

prin inserarea sa la locul potrivit. Selectarea locului unde trebuie inserat


D

elementul se face parcurgînd ºirul a1 ..... ai-1 de la dreapta la stînga,


oprirea realizîndu-se pe primul element aj care are valoarea mai micã
sau egalã cu a i sau, dacã un astfel de element nu existã ,pe a1.

void sortare_insertie()
{
int i,j,x;
for (i=2;i<=n;i++)
{
x=a[i];
a[0]=x;
j=i-1;
while (a[j]>x)
{
a[j+1]=a[j];
j=j-1;
}
a[j+1]=x;
}
}

18
e
ar
m
ra
Algoritmul de sortare prin inserþie poate fi îmbunãtãþit pornind de la

og
observaþia cã secvenþa destinaþie este deja ordonatã. În acest caz

pr
cãutarea locului de inserare se poate face mai rapid utilizînd cãutarea

de
binarã, prin împãrþiri succesive a intervalului din secvenþa destinaþie.
Aceastã metodã, (o variantã a metodei clasice de inserþie), poartã

ci
denumirea de inserþie binarã.

ni
eh
void insertie_binara()

it
{

is
int i,j,s,d,m,x;

itm
{
for (i=1;i<=n;i++)

or
{

lg
-A x=a[i];
s=0;
d=i-1;
da

while (s<=d)
an

{
ar

y=div((s+d),2);
m=y.quot;
m

if (a[m]>x) d=m-1; else s=m+1;


.Z

}
D

for (j=i-1;j>=s;j--) a[j+1]=a[j];


a[s]=x;
}
}
}

2.2 Tehnica sortãrii prin selecþie


Acest algoritm foloseºte procedeul de a cãuta elementul cu valoarea
minimã din cele N elemente de sortat, ºi de a schimba între ele poziþiile
acestui element cu primul element. Se repetã apoi procedeul cu cele N-1
elemente rãmase, apoi cu cele N-2, terminînd cu ultimele 2 elemente.

void sortare_selectie()
{
int i,j,k,x;
for (i=1;i<=n-1;i++)
{
k=i;
x=a[i];
for (j=i+1;j<=n;j++)

19
e
ar
m
if (a[j]<x)

ra
{

og
x=a[j];

pr
k=j;
}

de
a[k]=a[i];

ci
a[i]=x;

ni
}

eh
}

it
2.3. Tehnica sortãrii prin interschimbare (bubble-sort)

is
Principiul acestei metode este urmãtorul: se comparã ºi se interschimbã
perechile de elemente alãturate, parcurgînd tabloul de la stînga la

itm
dreapta, pînã cînd toate elementele devin sortate. La fiecare trecere prin

or
tablou se deplaseazã cel mai mic element spre capãtul din stînga al

lg
tabloului. Dacã considerãm tabloul vertical ºi vom asimila elementele
-A
sale cu niºte bule de aer în interiorul unui lichid, fiecare bulã avînd o
“greutate” proporþionalã cu valoarea cheii, atunci fiecare trecere prin
da

tablou se soldeazã cu ascensiunea unei bile la nivelul specific de


an

greutate. Din acest motiv, aceastã metodã se mai numeºte ºi sortare


ar

prin metoda bulelor (bubble-sort).


m
.Z

void sortare_bubblesort()
{
D

int i,j,x;
for (i=1;i<=n;i++)
{
for (j=n;j>=i;j--)
if (a[j-1]>a[j])
{
x=a[j-1];
a[j-1]=a[j];
a[j]=x;
}
}
}

Acestui algoritm i se pot aduce o serie de îmbunãtãþiri ºi anume:


- se observã din exemplul dat cã ultimele trei treceri prin tablou sunt
fãrã efect, elementele fiind deja ordonate. Deci, se poate memora dacã a
avut loc o schimbare în ordonarea elementelor ºi dacã nu, algoritmul nu
se mai continuã. ªi în acest caz însã este necesarã o ultimã trecere fãrã
modificãri.

20
e
ar
m
- la o analizã atentã se poate observa o asimetrie particularã: astfel un

ra
singur element "uºor" plasat la capãtul "greu" este readus la locul sãu

og
într-o singurã trecere, pe cînd un element "greu" plasat la capãtul "uºor"

pr
va fi readus pe locul sãu doar cu cîte o poziþie la fiecare trecere. Aceastã

de
asimetrie sugereazã ca îmbunãtãþire alternarea sensurilor de parcurgere
ale trecerilor consecutive. Algoritmul care conþine aceste îmbunãtãþiri

ci
se numeºte shakersort sau sortare prin amestecare.

ni
eh
void shakersort()

it
{

is
int j,k,l,r,x;

itm
l=2;r=n;k=n;
do

or
{

lg
-A for (j=r;j>=l;j--)
if (a[j-1]>a[j])
{
da

x=a[j-1];a[j-1]=a[j];a[j]=x;
an

k=j;
ar

}
l=k+1;
m

for (j=l;j<=r;j++)
.Z

if (a[j-1]>a[j])
D

{
x=a[j-1];a[j-1]=a[j];a[j]=x;
k=j;
}
r=k-1;
} while (l<=r);
}

O a treia îmbunãtãþire care se vede în implementarea acestui algoritm


este aceea cã, în variabila k nu se memoreazã dacã a avut loc o
interschimbare ci indicele (indexul j) al ultimei interschimbãri. Este
evident cã toate perechile de elemente situate sub acest indice sunt deja
ordonate, deci trecerile urmãtoare pot fi terminate la acest indice, în loc
sã fie terminate la indicele obiºnuit (capãtul tabloului).

2.4. Problemã rezolvatã


Se considerã un set de n beþiºoare, fiecare avînd o culoare ºi o lungime.
Sã se sorteze crescãtor sau descrescãtor beþisoarele dupã lungimea lor,
folosindu-se sortarea prin insertie, si sã se afiseze rezultatele sub forma
« culoare » à « lungime »

21
D
.Z
m

22
ar
an
da
-A
lg
or
itm
is
it
eh
ni
ci
de
pr
og
ra
m
ar
e
e
ar
m
#include <stdio.h>

ra
#include <conio.h>

og
pr
struct bat_struct
{ int lungime;

de
char culoare[15];

ci
}bat[51];

ni
eh
int nr_bete;

it
is
void afis_bete()
{

itm
for (int i=1;i<=nr_bete;i++)

or
printf("\n%s ->
%d",bat[i].culoare,bat[i].lungime);

lg
-A }

void sort_insertie()
da

{
an

int i,j;
ar

bat_struct x;
m

for (i=2;i<=nr_bete;i++)
.Z

{
x=bat[i];
D

j=i-1;
while (bat[j].lungime>x.lungime)
{
bat[j+1]=bat[j];
j--;
}
bat[j+1]=x;
}
}

void main()
{
clrscr();
//citim numarul de bete
do
{
printf("Dati numarul de bete (5..50) = ");
scanf("%d",&nr_bete);
}
while ((nr_bete<5)||(nr_bete>51));

23
e
ar
m
for (int i=1;i<=nr_bete;i++)

ra
{

og
printf("\nDati lungimea batului nr.

pr
%d = ",i);
scanf("%d",&bat[i].lungime);

de
printf("Dati culoarea batului nr. %d = ",i);

ci
scanf("%s",bat[i].culoare);

ni
}

eh
it
//afisam varianta initiala inainte de sortare

is
clrscr();
printf("Betisoarele nesortate sunt:");

itm
afis_bete();

or
//sortam betisoarele

lg
sort_insertie(); -A
//afisa varianta de dupa sortare
da

printf("\n\nBetisoarele sortate sunt:");


an

afis_bete();
ar

getch();
m

}
.Z
D

3. Probleme propuse

1. Sã se realizeze un program interactiv care realizeaza sortarea


betisoarelor din problema rezolvata de la punctul 2.4. prin toate
metodele de sortare directe prezentate în aceastã lucrare. Programul va
prezenta urmãtorul meniu:
- T – iniþializeazã tabloul de betisoare cu valori generate aleator
- A - afiseaza tabloul
- R - reiniþializeazã tabloul: aducerea tabloului la forma iniþialã dupa ce
acesta a fost sortat, în vederea aplicãrii unei alte sortãri
- I - sortare prin inserþie
- B - sortare prin inserþie binarã
- S - sortare prin selecþie
- M - sortare prin interschimbare
- H - sortare prin metoda Shakersort
- X - pãrãsirea programului.
Pentru acelaºi tablou iniþial se vor evalua ºi compara timpii de execuþie
a diferitelor metode de sortare.

24
e
ar
m
ra
2. Se considerã un set de 49 numere. Calculatorul va extrage aleator 6

og
numere din setul de mai sus, ºi va afisa aceste 6 numere în ordine

pr
crescãtoare. Se va folosi sortarea prin selecþie.

de
3. Se considerã un set de n planete, caracterizate prin nume ºi cele 3

ci
coordonate în spatiu : X,Y,Z. Sã se afiºeze cea mai apropiatã pereche de

ni
planete, precum ºi cele mai îndepãrtate douã planete.

eh
it
is
itm
or
lg
-A
da
an
ar
m
.Z
D

25
e
ar
m
Lucrarea nr. 4

ra
Tehnici de sortare avansate ale tablourilor

og
pr
1. Scopul lucrãrii – îl reprezintã prezentarea celor mai cunoscute

de
metode de sortare avansate a tablourilor.

ci
ni
2. Aspecte teoretice

eh
2.1 Tehnica sortãrii prin inserþie cu diminuarea incrementului.

it
Secvenþa de program care urmeazã implementeazã algoritmul pentru o

is
secvenþã de incremenþi descrescãtori formatã din t elemente, h1,h2,......ht

itm
ºi care îndeplinesc condiþia ht=1 ºi hi+1>hi, pentru i>=1.
Fiecare sortare-h este programatã ca o sortare prin inserþie (poate fi

or
folosita si o alta sortare directa la nivelul acestor elemente) utilizînd

lg
tehnica fanionului în vederea simplificãrii condiþiei de terminare a
-A
procesului de cãutare. Deoarece fiecare sortare necesitã propriul sãu
fanion, pentru o cãutare cît mai simplã, tabloul iniþial (A) se extinde în
da

stînga nu numai cu o componentã a[0] ci cu h1 componente.


an

În limbajul C a algoritmului shellsort apare o problemã legatã de faptul


ar

c ã limbajul C nu permite existenþa unor indici negativi de tablou. Din


m

acest motiv, tabloul este “deplasat” înspre indici pozitivi, cu un


.Z

deplasament egal cu h1 (incrementul maxim, care reprezintã în


D

algoritmul anterior indicele negativ maxim). Astfel, poziþiile 0..h1 din


tablou vor fi utilizate pentru memorarea fanioanelor (corespunzãtoare
pozi þiilor -h1..0 în varianta Pascal) iar cele de la h1..h1+n pentru
memorarea tabloului propriu-zis. Din acest motiv, citirea ºi afiºarea
tabloului este cea prezentatã în programul principal, care utilizeazã
funcþia shellsort astfel modificatã.

# define n 8
# define h1 9
# define t 4

int a[n+h1+1];
int i;

void shellsort()
{
int i,j,k,s,m,x;
int h[t+1];
h[1]=h1;h[2]=5;h[3]=3;h[4]=1;
for (m=1;m<=t;m++)

26
e
ar
m
{

ra
k=h[m];

og
s=h1-k;

pr
for (i=k+h1+1;i<=n+h1;i++)
{

de
x=a[i];

ci
j=i-k;

ni
if (s==h1) s=h1-k;

eh
s=s+1;

it
a[s]=x;

is
while (x<a[j])
{

itm
a[j+k]=a[j];j=j-k;

or
}
a[j+k]=x;

lg
-A }
}
}
da
an

void main()
ar

{
m

for(i=h1+1;i<=n+h1;i++)
.Z

{
printf("elementul %d = ",i);
D

scanf("%d",&a[i]);
}
shellsort();
for(i=h1+1;i<=n+h1;i++)
{
printf("%d",a[i]);
printf("\n");
}
}

2.2 Tehnica sortãrii arborilor prin metoda ansamblelor (heapsort)


Metoda sortãrii prin selecþie clasicã se bazeazã pe selecþia repetatã a
celui mai mic dintre cele N elemente, apoi a celei mai mic elemente
dintre cele N-1 elemente, etc.
Astfel, un ansamblu (heap) este definit ca o secvenþã de valori :
h s,hs+1,.....hd
astfel încît hi<h2i ºi hi<h2i+1pentru toti i= s...d/2.
În particular, elementul h1 este cel mai mic al unui ansamblu.
În principiu metoda de construcþie a unui ansamblu este urmãtoarea:
evident, într-un tablou h1....hn, elementele hn/2....hn formeazã un

27
e
ar
m
ansamblu deoarece nu existã nici o pereche de indici i ºi j care sã

ra
satisfacã relaþia j=2i (sau j=2i+1). În procesul de construire al unui

og
ansamblu in situ, se ia fiecare element de la jumãtate în jos ºi se

pr
deplaseazã în tablou pînã la locul sãu (se comparã elementul i cu

de
elementele 2i respectiv 2i+1 , adicã cu cel mai mic dintre ele, ºi dacã
elementul i este mai mare decît vreunul dintre acestea, atunci se

ci
schimbã între ele cele douã elemente). Operaþia se repetã pînã cînd nu

ni
se mai face nici o schimbare ºi apoi tot procesul se reia pentru

eh
elementul urmãtor. Deplasarea unui element în tablou poate fi descrisã

it
de functia deplasare prezentatã în continuare:

is
itm
#define TRUE 1

or
#define FALSE 0

lg
void deplasare(int s, int d) -A
{
da

int i,j,ret,x;
an

i=s;j=2*i;x=a[i];ret=FALSE;
ar

while ((j<=d) && (ret==FALSE))


m

{
.Z

if (j<d)
D

if (a[j]>a[j+1]) j=j+1;
if (x>a[j])
{
a[i]=a[j];i=j;j=2*i;
}
else
ret=TRUE;
}
a[i]=x;
}

Utilizînd procedura de deplasare al unui element în tablou descrisã mai


sus, procesul de generare in situ al unui ansamblu poate fi descris
astfel:

s=(n/2)+1;
while (s>1)
{
s=s-1;
deplasare(s,n);

28
e
ar
m
}

ra
og
Dupã ce ansamblul a fost generat, în vederea sortãrii elementelor se

pr
executã N paºi de deplasare, dupã fiecare pas selectîndu-se vîrful

de
ansamblului. La fiecare pas se ia ultima componentã a ansamblului ºi se
memoreazã vîrful ansamblului în locul ei. Componenta se lasã apoi sã

ci
alunece în ansamblu, la locul ei (procedura deplasare).

ni
eh
d=n;

it
while (d>1)

is
{

itm
x=a[1];a[1]=a[d];a[d]=x;
d=d-1;

or
y=div(d,2);

lg
-A s=d/2+1;
while (s>1)
{
da

s=s-1;
an

deplasare(s,d);
ar

}
}
m

}
.Z
D

Elementele se obþin sortate în ordine inversã, lucru care se poate uºor


remedia modificînd sensul relaþiilor de comparaþie din procedura
deplasare.

2.3. Tehnica sortãrii prin partiþionare (quicksort)


C.A.R.Hoare a conceput o metodã de sortare cu performanþe spectacu-
loase pe care a denumit-o Quicksort. Ideea de bazã este urmãtoarea: fie
x un element oarecare al tabloului de sortat a1,....an. Se parcurge tabloul
de la stînga la dreapta pînã se gãseºte primul element ai>x. Apoi se
parcurge tabloul de la dreapta la stînga pînã cînd se gãseºte primul
element aj<x. Se schimbã între ele elementele ai ºi aj, dupã care
parcurgerea continuã. Procedeul se terminã cînd cele douã parcurgeri
se "întîlnesc" undeva în interiorul tabloului. Efectul final este cã acum
tabloul iniþial este partiþionat într-o parte stîngã cu chei mai mici decît x
ºi o parte dreapta cu chei mai mari decît x.
Dupã o primã partiþionare a secvenþei de elemente se aplicã aceeaºi
procedurã celor douã partiþii rezultate, apoi celor patru partiþii ale
acestora, º.a.m.d, pînã cînd fiecare partiþie se reduce la un singur

29
e
ar
m
element. Tehnica sortãrii pe bazã de partiþionare este ilustratã de

ra
procedura sortare, care se apeleazã recursiv pe ea însãºi.

og
pr
void sortare(int s,int d)

de
{
int i,j;

ci
element x,w;

ni
i=s;j=d;

eh
x=a[(i+j)/2];

it
do

is
{

itm
while (a[i].cheie<x.cheie) i=i+1;
while (x.cheie<a[j].cheie) j=j-1;

or
if (i<=j)

lg
{
w=a[i];a[i]=a[j];a[j]=w; -A
i=i+1;j=j-1;
da

}
} while (i<=j);
an

if (s<j) sortare(s,j);
ar

if (i<d) sortare(i,d);
m

}
.Z
D

void quicksort()
{
int m=1;
int p=n;
sortare(1,n);
}

Este de reþinut însã cã efortul de calcul este proporþional cu N1,2 în


cazul tehnicii shellsort ºi cn N log2N în cazul tehnicii heapsort ºi
quicksort.

2.4 Problemã rezolvatã


Se va citi de la consola o propozitie. Se vor extrage toate cuvintele
distincte din aceasta propozitie, si vor fi afisate in ordine alfabetica.

Pentru sortare va fi folosit algoritmul shellsort .

#include <stdio.h>
#include <conio.h>

30
D
.Z
m
ar
an
da
-A
lg
or
itm
is
it
eh
ni
ci
de
pr
og
ra
m
ar
e
#include <ctype.h>
#include <string.h>

31
e
ar
m
#define t 4

ra
#define h1 9

og
typedef char cuvant[20];

pr
int h[t+1],nr_cuvinte=0,i;
char propozitia[200];

de
cuvant cuv[21],cuv_work[21+h1];

ci
ni
int exista_deja(char c[20])

eh
it
{for (int i=1;i<=nr_cuvinte-1;i++)

is
if (strcmp(cuv[i],c)==0) return 1;
return 0;

itm
}

or
void get_cuvinte()

lg
{ int cuvant=0,f; -A
char temp[20];
strcpy(temp,"");
da

for (f=0;f<strlen(propozitia);f++)
an

propozitia[f]=toupper(propozitia[f]);
ar

for (f=0;f<strlen(propozitia);f++)
m

{if
.Z

(((propozitia[f]>='A')&&(propozitia[f]<='Z'))||
D

((propozitia[f]>='0')&&(propozitia[f]<='9')))
{if (cuvant==0)
{strcpy(temp,"");
cuvant=1;
}
char lit[2];
lit[0]=propozitia[f];
lit[1]='\0';
strcat(temp,lit);
}
else
if (cuvant)
{cuvant=0;
if (!exista_deja(temp))
{nr_cuvinte++;
strcpy(cuv[nr_cuvinte],temp);
}
}
}
}

32
e
ar
m
void sort_shell()

ra
{ int f,m,k,s,i,j;

og
char x[20];

pr
for (f=1;f<=nr_cuvinte;f++)
strcpy(cuv_work[f+h1],cuv[f]);

de
for (m=1;m<=t;m++)

ci
{ k=h[m];

ni
s=h1-k;

eh
for (i=k+h1+1;i<=nr_cuvinte+h1;i++)

it
{ strcpy(x,cuv_work[i]);

is
j=i-k;
if (s==h1) s=h1-k;

itm
s++;

or
strcpy(cuv_work[s],x);
while (strcmp(x,cuv_work[j])<0)

lg
-A {strcpy(cuv_work[j+k],cuv_work[j]);
j-=k;
}
da

strcpy(cuv_work[j+k],x);
an

}
ar

}
m

for (f=1;f<=nr_cuvinte;f++)
.Z

strcpy(cuv[f],cuv_work[f+h1]);
}
D

void main()
{h[1]=9;h[2]=5;h[3]=3;h[4]=1;
clrscr();
//citim propozitia
printf("Dati propozitia=");
gets(propozitia);
strcat(propozitia," ");
//scoatem cuvintele distincte
get_cuvinte();
//afisam cuvintele nesortate
printf("\n\nCuvintele distincte
nesortate sunt:");
for (i=1;i<=nr_cuvinte;i++)
printf("\n%s",cuv[i]);

//sortam cuvintele respective


sort_shell();
//afisam cuvintele sortate
printf("\n\n\Cuvintele distincte

33
e
ar
m
sortate sunt:");

ra
for (i=1;i<=nr_cuvinte;i++)

og
printf("\n%s",cuv[i]);

pr
getch();
}

de
ci
3. Probleme propuse

ni
eh
1. Sã se realizeze un program interactiv care implementeazã metodele

it
de sortare avansate prezentate în aceastã lucrare. Programul va prezenta

is
urmãtorul meniu:

itm
- C - iniþializeazã tabloul de sortat cu valori generate aleator
- V - vizualizeazã tablou

or
- R - reiniþializeazã tabloul: vezi lucrarea 3.

lg
- S - sortare shellsort -A
- H - sortare heapsort
- Q- sortare quicksort
da

- X - parasirea programului.
an

Pentru acelaºi tablou iniþial se vor evalua ºi compara timpii de execuþie


ar

a diferitelor metode de sortare.


m
.Z

2. Se considerã un vector de n numere. Sã se sorteze în ordine


D

crescãtoare elementele de pe pozitiile pare, folosind algoritmul


shellsort, ºi elementele de pe poziþiile impare în ordine descrescãtoare
folosind algorimul heapsort. Sã se afiºeze vectorul în noua formã.

3. Se considerã un set de n abonati la un serviciu de telefonie. Fiecare


abonat va fi caracterizat prin nume, adresa, numãr telefon ºi valoare
facturã. Sã se sorteze descrescator abonaþii dupã valoarea facturii de
telefon folosind algoritmul de sortare quicksort, ºi apoi sã se afiºeze
primii trei abonati cu cele mai mari facturi.

34
e
ar
m
Lucrarea nr. 5

ra
Sortarea fiºierelor secvenþiale (externã)

og
pr
1. Scopul lucrãrii – îl reprezintã prezentarea celor mai cunoscute

de
metode de sortare a fiºierelor.

ci
ni
2. Aspecte teoretice

eh
Metodele aplicate la sortarea tablourilor nu pot fi aplicate pentru date

it
care nu încap în memoria calculatorului (internã), dar care pot fi de

is
exemplu memorate pe dispozitive periferice secvenþiale. În acest caz

itm
datele pot fi descrise cu ajutorul unei structuri tip fiºier, avînd drept
caracteristicã esenþialã faptul cã, în fiecare moment este accesibilã doar

or
o singurã componentã. Aceastã restricþie face ca metodele de sortare a

lg
-A fiºierelor sã fie total diferite de cele ale tablourilor.

2.1. Tehnica sortãrii prin interclasare


da

Interclasarea presupune combinarea a douã sau mai multe secvenþe


an

ordonate într-o singurã secvenþã, prin selecþii repetate ale


ar

componentelor curent accesibile. O metodã bazatã pe interclasare ar fi


m

urmãtoarea:
.Z

1. Se împarte secvenþa a în douã jumãtãþi b ºi c.


2. Se interclaseazã b cu c combinînd cîte un element din
D

fiecare în perechi ordonate, obþinîndu-se o nouã secvenþã.


3. Se repetã cu secvenþa interclasatã a paºii 1 ºi 2, de data
aceasta combinînd perechile ordonate în quadruplele ordonate
4. Se repetã paºii iniþiali intercalînd quadruplele în 8-uple,
º.a.m.d., de fiecare datã dublînd lungimea subsecvenþelor de
interclasare, pînã la sortarea întregii secvenþe.
Fiecare operaþie care trateazã întregul set de date se numeºte fazã iar
subprocesele prin repetarea cãrora se realizeazã procesul de interclasare
se numesc treceri.

#define FALSE 0
#define TRUE 0
#define n 10

int x;
FILE *a, *b, *c;
int p,k,i;
int temp;

35
e
ar
m
void injumatatire(int *nr)

ra
{

og
a=fopen(“…\a.dat”,”r”);

pr
b=fopen(“…\b.dat”,”w”);
c=fopen(“…\c.dat”,”w”);

de
if (!feof(a)) fscanf(a,”%d”,&x);

ci
while (!feof(a))

ni
{

eh
i=0;

it
while ((i<(*nr))&&(!feof(a)))

is
{
fprintf(b,”%d\n”,x);

itm
fscanf(a,”%d”, &x);

or
i++;
}

lg
i=0; -A
while ((i<(*nr))&&(!feof(a)))
{
da

fprintf(c,”%d\n”,x);
an

fscanf(a,”%d”, &x);
ar

i++;
m

}
.Z

}
fclose(a); fclose(b); fclose(c);
D

void interclasare(int *nr)


{
int i,j,x,y,termb, termc;
b=fopen(“…\b.dat”,”r”);
c=fopen(“…\c.dat”,”r”);
a=fopen(“…\c.dat”,”w”);
termb=feof(b);
termc=feof(c);
if (termb==FALSE)
{fscanf(b,”%d”, &x); termb=feof(b);}
if (termc==FALSE)
{fscanf(c,”%d”, &y); termc=feof(c);}
do
{
i=0 ; j=0 ;
while ((i<*nr)&&(j<*nr)&&(termc==FALSE) &&
(termb==FALSE))
{
if (x<y)

36
e
ar
m
{

ra
fprintf(a,”%d\n”, x); i++;

og
if feof(b) termb=TRUE;

pr
else
{

de
fscanf(b,”%d”,&x); termb=feof(b);

ci
}

ni
}

eh
else

it
{

is
fprintf(a,”%d\n”, y); j++;
if feof(c) termc=TRUE;

itm
else

or
{
fscanf(c,”%d”,&y); termc=feof(c);

lg
-A }
}
}
da

while ((i<*nr)&&(termb==FALSE))
an

{
ar

fprintf(a,”%d\n”,x); i++;
m

if (feof(b) termb=TRUE;
.Z

else
{
D

fscanf(b,”%d”,&x); termb=feof(b);
}
}
while ((j<*nr)&&(termc==FALSE))
{
fprintf(a,”%d\n”,y); j++;
if (feof(c) termc=TRUE;
else
{
fscanf(c,”%d”,&y); termc=feof(c);
}
}
if ((termb==FALSE)||(termc==FALSE)) k++;
} while ((termb==FALSE)||(termc==FALSE));
fclose(a); fclose(b); fclose(c);
}
void main()
{
a=fopen(“…\a.dat”,”w”);
for (i=1;i<=n;i++)

37
e
ar
m
{

ra
scanf(“%d”, &temp);

og
fprintf(a, ”%d\n”, temp);

pr
}
fclose(a); //crearea fisierului

de
a=fopen(“…\a.dat”,”r”);

ci
for (i=1;i<=n;i++)

ni
{

eh
fscanf(a,“%d”, &temp);

it
printf(”%d\n”, temp);

is
}
fclose(a); //tiparirea fisierului creat

itm
//incepe inteclasarea

or
p=1;

lg
do
{ -A
injumatatire(&p);
da

k=0;
interclasare(&p);
an

p=2*p;
ar

}while (k!=0);
m

a=fopen(“…\a.dat”,”r”);
.Z

for (i=1;i<=n;i++)
D

{
fscanf(a,“%d”, &temp);
printf(”%d\n”, temp);
}
fclose(a); } //tiparirea fisierului sortat

2.2. Tehnica sortãrii prin interclasare naturalã.


Tehnica sortãrii prin interclasare prezentatã anterior nu ia în
considerare faptul cã datele pot fi parþial sortate, lungimea
subsecvenþelor fiind nedeterminatã. De fapt, oricare douã secvenþe
sortate de lungime M ºi respectiv N pot fi interclasate într-o singurã
secvenþã de lungime M+N. Tehnica de interclasare, care de fiecare datã
combinã cele mai lungi douã secvenþe, se numeºte sortare prin
interclasare naturalã. În cadrul acestei tehnici, un rol central îl joacã
noþiunea de monotorie. Se înþelege prin monotorie orice secvenþã
parþialã ai...aj, care satisface urmãtoarele condiþii:
1. 1<i<j<n;
2. ak < ak+1 pentru orice i <k<j-1;
3. ai-1>ai sau i=1;
4. aj>aj+1 sau j=n;

38
e
ar
m
ra
Practic, diferenþa dintre sortarea echilibratã ºi cea naturalã este aceea cã

og
lungimea subssecvenþelor de interclasat nu este constantã (1,2,4,8,

pr
º.a.m.d.) ci variazã, fiind egalã cu lungimea unei monotonii. Deci,

de
sortarea naturalã interclaseazã monotorii. Fiecare trecere constã în douã
faze alternative: defalcare ºi interclasare. În faza de defalcare

ci
monotoriile fiºierului c se defalcã alternativ pe fiºierele a ºi b. În faza

ni
de interclasare se recombinã în c monotoriile de pe fiºierele a ºi b.

eh
Sortarea se terminã în momentul în care numãrul monotoriilor din

it
fiºierul c este 1. Pentru numãrarea monotoriilor se foloseºte variabila

is
l.

itm
Prin aceastã metodã la distribuire se depune fie un numãr egal de

or
monotorii pe fiºierele a respectiv b, fie cu o monotorie mai mult pe

lg
fiºierul a. Dupã interclasarea monotoniilor perechi, monotoria
-A nepereche trebuie recopiatã în c.
da

2.3 Problemã rezolvatã


Se considerã un fiºier « sursa.txt » . Acest fiºier conþine ca ºi text un
an

proverb. Sã se sorteze caracterele în ordine alfabeticã din acest fiºier


ar

într-un alt fisier , numit « dest.txt »


m
.Z

#include <conio.h>
D

#include <stdio.h>

FILE *sursa,*dest,*a,*b;
char ch;
int file_size=0,a_size=0,b_size=0;

void transfera_sursa_in_destinatie()
{ sursa=fopen("sursa.txt","rt");
dest=fopen("dest.txt","wt");
while (!feof(sursa))
{ ch=fgetc(sursa);
if (!feof(sursa))
{fputc(ch,dest);
file_size++;
}
}
fclose(sursa);
fclose(dest);
}

39
e
ar
m
void split_destinatie(int nr)

ra
{ int ch_scrise=0;

og
dest=fopen("dest.txt","rt");

pr
a=fopen("a.tmp","wt");
b=fopen("b.tmp","wt");

de
a_size=0;b_size=0;

ci
while (ch_scrise<file_size)

ni
{ int i;

eh
for

it
(i=1;((i<=nr)&&(ch_scrise<file_size));i++)

is
{ ch=fgetc(dest);
fputc(ch,a);

itm
a_size++;

or
ch_scrise++;
}

lg
for -A
(i=1;((i<=nr)&&(ch_scrise<file_size));i++)
{ch=fgetc(dest);
da

fputc(ch,b);
an

b_size++;
ar

ch_scrise++;
m

}
.Z

}
fclose(dest);
D

fclose(a);
fclose(b);
}

void interclaseaza(int nr)


{a=fopen("a.tmp","rt");
b=fopen("b.tmp","rt");
dest=fopen("dest.txt","wt");
int na=1,nb=1,ch_scrise=0,read_next_a=1;
read_next_b=1,a_citit=0,b_citit=0,gata=0;
char ca,cb;
while (ch_scrise<file_size)
{ do
{if (read_next_a)
{ca=fgetc(a);
read_next_a=0;
a_citit++;
if (a_citit>a_size) {gata=1;na=nr+1;}
}

if (read_next_b)
40
e
ar
m
{cb=fgetc(b);

ra
read_next_b=0;

og
b_citit++;

pr
if (b_citit>b_size) {gata=1;nb=nr+1;}
}

de
ci
if (gata==0)

ni
if (ca<cb)

eh
{

it
fputc(ca,dest);

is
ch_scrise++;read_next_a=1;na++;
}

itm
else

or
{
fputc(cb,dest);

lg
-A ch_scrise++;read_next_b=1;nb++;
}
}
da

while ((na<=nr)&&(nb<=nr));
an
ar

if (nb<=nr)
m

{ fputc(cb,dest);ch_scrise++;
.Z

for
(int i=nb;((i<nr)&&(b_citit<b_size));i++)
D

{ cb=fgetc(b);b_citit++;
fputc(cb,dest);ch_scrise++;
}
}
else
{ fputc(ca,dest);ch_scrise++;
for
(int i=na;((i<nr)&&(a_citit<a_size));i++)
{ ca=fgetc(a);a_citit++;
fputc(ca,dest);ch_scrise++;
}
}
na=1;read_next_a=1;
nb=1;read_next_b=1;
}
fclose(a);
fclose(b);
fclose(dest);
}

41
e
ar
m
void sorteaza_destinatie()

ra
{

og
int n=1;

pr
while (n<=file_size)
{ split_destinatie(n);

de
interclaseaza(n);

ci
n*=2;

ni
}

eh
}

it
is
void main()
{ clrscr();

itm
transfera_sursa_in_destinatie();

or
sorteaza_destinatie();
}

lg
-A
3. Probleme propuse
da
an

1. Se considerã un fiºier « an.txt » în care sunt stocate informaþiile a N


ar

studenþi : nume, prenume, grupa. Sã se sorteze aceºti studenþi în ordine


m

alfabeticã, dupã nume, într-un al doilea fisier « an_sort.txt »


.Z
D

2. Sã se modifice problema rezolvatã din cadrul acestei lucrãri, prin


utilizarea sortãrii naturale în locul sortãrii echilibrate.

42
e
ar
m
Lucrarea nr. 6

ra
Algoritmi recursivi

og
pr
1. Scopul lucrãrii – îl reprezintã prezentarea conceptului de

de
recursivitate precum ºi a cîtorva categorii tipice de algoritmi recursivi.

ci
ni
2.Aspecte teoretice

eh
2.1. Definirea recursivitãþii

it
Recursivitatea presupune de asemenea execuþia repetatã a unei

is
porþiuni de program. În contrast cu iteraþia însã, în cadrul recursivitãþii

itm
condiþia este verificatã în decursul execuþiei programului (nu la sfîrºitul
ei ca la iteraþie) ºi, în caz de rezultat satisfãcãtor, întreaga porþiune de

or
program este apelatã din nou ca subprogram a ei însãºi, în particular ca

lg
-A un subprogram a porþiunii de program originale care însã nu ºi-a
terminat execuþia. În momentul satisfacerii condiþiei de revenire, se reia
execuþia programului apelant exact din punctul din care s-a apelat pe el
da

însuºi. Acest lucru este valabil pentru toate apelurile anterioare


an

satisfacerii condiþiei.
ar

Structurile de program necesare ºi suficiente în exprimarea recursivitãþii


m

sunt subrutinele care pot fi apelate prin nume. Dacã o subrutinã P


.Z

conþine o referinþã directã la ea însãºi se spune cã este direct recursivã;


D

dacã P conþine o referinþã cu o altã subrutinã Q, care la rîndul ei conþine


o referinþã (directã sau indirectã) la P, se spune cã P este indirect
recursivã.
De regulã, unei subrutine i se asocieazã un set de obiecte ale subrutinei
(variabile, constante, tipuri, funcþii ºi proceduri), care sunt definite local
în subrutinã ºi care nu existã sau nu au înþeles în afara acesteia. De
fiecare datã cînd o astfel de subrutinã este apelatã recursiv, se creeazã
un nou set de astfel de obiecte locale, specifice apelului. Deºi aceste
obiecte au acelaºi nume ca ºi cele corespunzãtoare lor din instanþa
anterioarã a subrutinei (în calitate de program apelant), ele au valori
distincte ºi orice conflict de nume este evitat prin regulile care stabilesc
domeniul identificatorilor: identificatorii se referã întotdeauna la setul
cel cel mai recent creat de obiecte. Aceleaºi reguli sunt valabile ºi în
cazul parametrilor subrutinei, asociaþi prin definiþie cu setul de
variabile.
Ca ºi în cazul structurilor repetitive, procedurile recursive necesitã
evaluarea unei condiþii de terminare, fãrã de care un program recursiv
duce la o buclã de program infinitã. Aplicaþiile practice au demonstrat
cã, deºi teoretic recursivitatea poate fi infinitã, practic ea nu numai cã
este finitã, dar adîncimea sa este relativ micã. Motivul este cã, fiecare

43
e
ar
m
apel recursiv al unei subrutine necesitã alocarea unui volum de memorie

ra
(in stiva) destinat obiectelor (variabilelor) sale curente. În plus, alãturi

og
de acestea mai trebuie memoratã ºi starea curentã a programului, cu

pr
scopul de a fi refãcutã atunci cînd noua activitate a subrutinei se terminã

de
ºi urmeazã ca cea veche sã fie reluatã.
Algoritmii recursivi sînt potriviþi a fi utilizaþi atunci cînd problema care

ci
trebuie rezolvatã sau datele care trebuiesc prelucrate sunt definite în

ni
termeni recursivi (algoritmii implementeazã în acest caz definiþia

eh
recursivã). Utilizarea recursivitãþii trebuie însã evitatã ori de cîte ori stã

it
la dispoziþie o rezolvare bazatã pe interaþie. De fapt, implementarea

is
recursivitãþii pe elemente nerecursive dovedeºte faptul cã, practic, orice

itm
algoritm recursiv poate fi transformat într-unul pur interativ. La
transformare putem distinge douã cazuri:

or
a). Cazul în care apelul recursiv al procedurii apare la sfîrºitul ei, drept

lg
-A
ultima instrucþiune a procedurii (tail recursion). În aceastã situaþie,
recursivitatea poate fi înlocuitã cu o buclã simplã de iteraþie. Acest
da

lucru este posibil deoarece, în acest caz, revenirea dintr-un apel încuibat
presupune ºi terminarea instanþei respective a subrutinei, motiv pentru
an

care contextul apelului nu trebuie salvat. Astfel, dacã o procedurã P(x)


ar

conþine ca ºi ultim pas al sãu un apel recursiv la ea însãºi P(y), atunci


m

acest apel poate fi înlocuit cu instrucþiunea de atribuire x:=y, urmatã de


.Z

un salt la începutul codului lui P. Dacã P are mai mulþi parametri, ei


D

pot fi trataþi fiecare în parte ca x ºi y. Aici y poate fi o expresie dar x


trebuie sã fie o variabilã transmisibilã prin adresã, astfel încît valoarea
sa sã fie memoratã într-o locaþie specificã apelului.
b). Cazul în care apelurile recursive se realizeazã în interiorul
procedurii, varianta iterativã a acestei situaþii presupune tratarea
explicitã de cãtre programator a stivei apelurilor recursive în care se
salveazã contextul fiecãrei instanþã de apel. Eliminarea recursivitãþii
poate fi fãcutã, teoretic, în orice situaþie. Eliminarea recursivitãþii poate
duce la creºterea performanþelor însã, în cele mai multe cazuri,
algoritmul devine mult mai complicat ºi mai greu de înþeles.
În general, atunci cînd nu avem “tail recursion”, pentru eliminarea
recursivitãþii se foloseºte o structurã de date de tip stivã, definitã de
utilizator. Un nod al acestei stive va conþine urmãtoarele elemente:
- valorile curente ale parametrilor procedurii
- valorile curente ale tuturor variabilelor locale ale procedurii
- o indicaþie referitoare la adresa de retur, adicã referitoare la locul în
care revine controlul execuþiei în momentul în care apelul curent al
instanþei procedurii se terminã.

44
e
ar
m
2.2. Tehnica divizãrii (divide and conquer)

ra
Una dintre metodele fundamentale de proiectare a algoritmilor se

og
bazeazã pe tehnica divizãrii (divide and conquer). Principiul de bazã

pr
este acela de a descompune o problemã în mai multe subprobleme a

de
cãror rezolvare este mai simplã ºi din soluþiile cãrora se poate determina
soluþia problemei iniþiale. Acest mod de lucru se repetã recursiv pînã

ci
cînd subproblemele devin banale iar soluþiile lor, evidente. O aplicaþie

ni
tipicã a tehnicii divizãrii are urmãtoarea structurã:

eh
it
procedure rezoiva (x)

is
begin

itm
if x este divizibil in subprobleme then
begin

or
divide pe x in doua sau mai multe parti: x1.....xk;

lg
-A combina cele k solutii partiale intr-o solutie pentru x
end
da

else
rezolva pe x direct
an

end;
ar
m

Dacã recombinarea soluþiilor parþiale este substanþial mai simplã decît


.Z

rezolvarea întregii probleme, aceastã tehnicã duce la proiectarea unor


D

algoritmi extrem de eficienþi.

2.3. Algoritmi recursivi pentru determinarea tuturor soluþiilor unor


probleme.

A. Algoritm pentru evidenþierea tuturor posibilitãþilor de împãrþire a


unei cantitãþi de valoare întreagã datã (N) în pãrþi de valoare l1 sau l2
(cu posibilitate de generalizare pentru l1,.l2,….lk).
Se bazeazã pe urmãtoarea tehnicã de lucru:
- pentru N >min(l1, l1) existã douã posibilitãþi
1). Când la început se ia o parte de valoare l1 ºi restul cantitãþii
de N- l1 se imparte în toate modurile posibile (apel recursiv la funcþia de
impãrþire, cu parametru N- l1);
2). Când la început se ia o parte de valoare l2 ºi restul cantitãþii
N- l2 se imparte în toate modurile posibile posibile (apel recursiv la
funcþia de impãrþire, cu parametru N- l1);
- pentru N= min(l1, l1) avem cazul banal de o tãieturã de lungime egala
cu minimul dintre l1 ºi l2;
- pentru N< min(l1, l1) nu existã nici o posibilitate.

45
e
ar
m
ra
B. Algoritm pentru determinarea tuturor soluþiilor de ieºire

og
dintr-un labirint

pr
Algoritmul urmãtor presupune un labirint descris cu ajutorul unui

de
tablou tridimensional de caractere de dimensiuni [N+1] x [N+1], în care
cu ajutorul caracterul ‘*’ sunt reprezentaþi pereþii iar cu ajutorul

ci
caracterului ' ' (blanc) culoarele; punctul de start este centrul

ni
labirintului (dar nu este obligatoriu). Cãutarea se executã astfel: dacã

eh
valoarea caracterului din poziþia curentã este ' ', se intrã pe o linie

it
posibilã de ieºire ºi se macheazã poziþia cu caracterul '+'. Dacã s-a ajuns

is
la ieºire, se executã tipãrirea tabloului (labirintul ºi drumul gãsit). În caz

itm
contrar se apeleazã recursiv procedura P pentru cele patru poziþii din

or
vecinãtatea imediatã a poziþiei curente.
Este important sã subliniem cã marcajul de drum se ºterge, de îndatã ce

lg
-A
s-a ajuns la o fundãturã. ªtergerea se executã prin generarea unui
caracter ' ' (blanc) pe poziþia curentã înainte de pãrãsirea procedurii.
da

#include <stdio.h>
an

#include <stdlib.h>
ar

#include <conio.h>
m

#define n 5
.Z
D

char m[n+1][n+1];
int i,j;
div_t a,b;

void tipar()
{
for (i=0;i<=n;i++)
{
printf(" ");
for (j=0;j<=n;j++) printf("%c",m[i][j]);
printf("\n");
}
printf("\n");
}
void p(int x,int y)
{
if (m[x][y]==' ')
{
m[x][y]='+';
a=div(x,n);
b=div(y,n);

46
e
ar
m
if ((a.rem==0) || (b.rem==0))

ra
tipar();

og
else

pr
{
p(x+1,y); p(x,y+1); p(x-1,y); p(x,y-1);

de
}

ci
m[x][y]=' ';

ni
}

eh
}

it
void main()

is
{
for (i=0;i<=n;i++)

itm
for (j=0;j<=n;j++)

or
m[i][j]='*';
for (j=2;j<=5;j++) m[1][j]=' ';

lg
-A m[2][1]=' ';
m[2][2]=' ';
m[2][4]=' ';
da

m[3][2]=' ';
an

m[3][3]=' ';
ar

m[3][4]=' ';
m

m[4][2]=' ';
.Z

m[5][2]=' ';
tipar();
D

a=div(n,2);
p(a.quot,a.quot);
}

În exemplul de mai sus, structura iniþialã a labirintului a fost furnizatã


prin setarea elementelor tabloului m din programul principal. Acest
lucru însã se poate realiza ºi într-o altã manierã, de exemplu prin citire
de la tastaturã sau dintr-un fiºier.

2.4 Problemã rezolvatã


Se considera un set de N cuvinte. Sa se afiºeze toate permutãrile
posibile care se pot realiza între aceste cuvinte.

#include <conio.h>
#include <stdio.h>

int a[20],n,i;
int pr[20];

void perm(int i)

47
e
ar
m
{ int j,f;

ra
if (i==n+1)

og
{ printf("\n");

pr
for (f=1;f<=n;f++) printf("%d ",pr[f]);
}

de
else

ci
for (j=1;j<=n;j++)

ni
if (pr[j]==-1)

eh
{pr[j]=a[i];

it
perm(i+1);

is
pr[j]=-1;
}

itm
}

or
void main()

lg
{clrscr(); -A
printf("Dati numarul de elemente = ");
scanf("%d",&n);
da

for (i=1;i<=n;i++)
an

{printf("\n\nDati valoarea
ar

elementului %d = ",i);
m

scanf("%d",&a[i]);
.Z

pr[i]=-1;
}
D

perm(1);
getch(); }
3. Probleme propuse

1. Pentru problema de determinare a tuturor soluþiilor de ieºire dintr-


un labirint, prezentatã în cadrul acestei lucrãri:
a). sã se scrie programul de aflare a optimului (drumul cel mai scurt
de iesire);
b). sã se scrie un program pentru determinarea tuturor soluþiilor ºi a
optimului de ieºire dintr-un labirint tridimensional.

2. Se considerã un set de N caractere. Sã se afiºeze toate combinaþiile de


N luate cîte M ale acestor caractere.

3. Se considerã un set de N studenþi. Sã se grupeze studenþii în grupuri


de cîte 5, ºi sã se afiºeze pentru fiecare grupã toate permutãrile posibile.

4. Sã se scrie cîte o funcþie recursivã pentru:


a). transformarea unui întreg din baza 10 în altã bazã data

48
e
ar
m
b). tipãrirea nodurilor unei liste în ordine inversã

ra
c). analog cu b), dar pentru elementele unui tablou

og
d). copierea în ordine inversã a liniilor dintr-un fisier text, în altul.

pr
de
5. Sa se generalizeze programul TAIETURA (prezentat în curs) astfel
încît sã se gaseascã toate variantele, precum ºi cea optimã pentru

ci
ni
tãierea unui fir de lungime L în pãrþi de lungimi L1,L2,...,Ln date, în

eh
condiþiile:

it
a). nu existã nici un fel de restricþie în ce priveºte numãrul de bucãþi

is
din fiecare lungime;

itm
b). se taie cel mult o bucatã de fiecare lungime;
c). numãrul de bucãþi de fiecare lungime sã difere prin cel mult o

or
unitate.

lg
-A
da
an
ar
m
.Z
D

49
e
ar
m
Lucrarea nr. 7

ra
Algoritmi recursivi (continuare)

og
pr
1. Scopul lucrãrii – îl reprezintã prezentarea în continuare a altor

de
cîtorva clase tipice de algoritmi recursivi, respectiv algoritmii cu

ci
revenire (backtracking) ºi algoritmii de tipul “înlãþuie ºi limiteazã”

ni
(branch and bound algorithms).

eh
it
2.Aspecte teoretice

is
2.1. Algoritmi cu revenire (backtracking)

itm
Principiul acestei tehnici este aceea de descompunere a obiectivului în
obiective parþiale (task-uri parþiale). De regulã acestea sunt exprimate în

or
mod recursiv ºi constã în exploatarea unui numãr finit de sub-task-uri.

lg
Întregul proces poate fi privit ca un proces de încercare ºi cãutare care
-A
construieºte în mod gradat ºi parcurge în acelaºi timp un arbore de
subprobleme.
da

Obþinerea unor soluþii parþiale sau finale care nu satisfac provoacã


an

revenirea recursivã în cadrul procesului de calcul, pânã la obþinerea


ar

soluþiei dorite, motiv pentru care astfel de algoritmi se numesc


m

algoritmi cu revenire (backtracking algorithms)


.Z

O formã generalã a algoritmului este urmãtoarea :


D

function incearca;
begin
iniþializeazã selecþia candidatilor;
repeat
selecteazã urmãtorul candidat;
if acceptabil then
begin
înregistreazã-l;
if soluþie incompletã then
begin
încearcã pasul urmãtor;
if nu este reuºit then sterge inregistrarea
end
end
until (pas reuºit) or (nu mai sunt candidaþi)
end;
Acest model principial, general, poate fi însã concretizat în mai multe
forme. Astfel, dacã presupunem cã la fiecare pas numãrul candidatilor
de examinat este fix (M=8 în cazul problemei calului prezentatã la curs)

50
e
ar
m
precum ºi cã procedura este apelatã iniþial prin incearca(i), unde i

ra
reprezintã numãrul mutãrii urmãtoare, rezultã urmãtoarea formã

og
particularã a algoritmului:

pr
de
function incearca (i:integer);

ci
var k: integer;

ni
begin

eh
k:=0;

it
repeat
k:=k+1;{selecteaza al k-lea candidat}

is
if acceptabil then

itm
begin
înregistreazã-l;

or
lg
if i<n_total_solutie_completa then
-A begin
incearca(i+1);
da
if nereusita then
ºterge înregistrarea
an

end
ar

end
m

until reuºitã or (k=m)


.Z

end;
D

Forma de mai sus a algoritmului genereazã însã o singurã soluþie pentru


problema datã; pentru determinarea tuturor soluþiilor unor astfel de
probleme (dacã existã) este necesar ca generarea candidaþilor sã se facã
într-o manierã ordonatã, ceea ce garanteazã cã un candidat nu poate fi
generat decît o singurã datã. De îndatã ce o soluþie a fost gãsitã ºi
înregistrat ã (de exemplu, tipãritã), se trece la determinarea soluþiei
urm ãtoare pe baza unui proces de selecþie sistematicã, pînã la epuizarea
tuturor posibilitãþilor. Schema generalã derivatã din cea anterioarã (cu k
mergînd pînã la un m fix) care rezolvã aceastã problemã este
urm ãtoarea:

function incearca(i:integer);
var k:integer;
begin
for k:=1 to m do
begin
selecteazã cel de-al i-lea candidat;
if acceptabil then
begin

51
e
ar
m
înregistreazã-l;

ra
if i< n_total_solutie_completa

og
pr
then incearca(i+1)
else tipãreºte soluþia;

de
ºterge înregistrarea

ci
end

ni
end

eh
end;

it
Din cauza simplificãrii la un singur termen a condiþiei de terminare a

is
procesului de selecþie (k=M în acest caz), ciclul repeat a putut fi

itm
înlocuit cu unul for.

or
2.2. Algoritmi pentru realizarea selecþiei optime

lg
Se propune în continuare un algoritm pentru aflarea soluþiei optime din
-A
mai multe soluþii posibile. În acest scop, este necesar sã fie generate
toate soluþiile posibile, ºi pe parcursul procesului de generare, sã fie
da

re þinutã cea optimã, într-un anumit sens.


an

Astfel, variabila optim înregistreazã cea mai bunã soluþie întîlnitã pînã
ar

în momentul curent ºi ea este iniþializatã în mod corespunzãtor.


m

Problema generalã aflare a solþiei optime ar putea fi formulatã în felul


.Z

urmãtor: se cere sã se gãseascã maniera optima de selecþie a unei


D

submulþimi de obiecte dintr-o mulþime de bazã datã, þinînd cont de


anumite constrîngeri. Selecþionãrile care constituie soluþii acceptabile se
construiesc în mod gradat, examinînd obiectele individuale ale mulþimii
de baz ã. Examinarea fiecãrui obiect se poate termina în douã moduri:
fie cu includerea obiectului investigat în soluþia curentã, fie cu
excluderea sa. Un model general de algoritm ar fi urmãtorul:

function incearca(i:integer);
{încearcã includerea /excluderea celui de-al i-lea obiect}
begin
if incluziunea este acceptabilã then
begin
include cel de-al i-lea obiect;
if i< n_total_solutie_completa
then incearca (i+1)
else verificã optimalitatea;
eliminã cel de-al i-lea obiect;
end

52
e
ar
m
if excluzimea este acceptabilã then

ra
og
if i<n then incearca(i+1)
else verificã optimalitatea

pr
end;

de
Algoritmii care se bazeazã pe tehnica revenirii ºi care utilizeazã un

ci
factor de limitare care restrînge creºterea arborelui potenþial de cãutare

ni
se mai numesc ºi algorimi de tip “înlãþuie ºi limiteazã” (branch and

eh
bound algorithms).

it
is
2.3. Problemã rezolvatã

itm
Se considerã un lac îngheþat de dimensiune NxM. Vom avea o broscuþã

or
la poziþia (1,1) ºi o insectã la poziþia (N,M). Luînd în considerare ca

lg
broscuþa poate sãri doar în L , sã se afle drumul cel mai scurt pe care
-A trebuie broscuþa sa-l parcurgã de la poziþia initialã pînã la insectã.
OBS : în locul de unde a sãrit broscuþa, gheaþa se sparge, aºa cã în acel
da

loc broscuþa nu va mai putea sãri.


an
ar

#include <conio.h>
m

#include <stdio.h>
.Z

struct drum
D

{ int x,y;
} d[100],d_b[100];

int a[50][50],n,m,i,j;
int nr_pasi_b=-1,nr_pasi=0;

void afis_tabla()
{ clrscr();
int i,j;
for (i=3;i<=n+2;i++)
for (j=3;j<=m+2;j++)
{ gotoxy(i-2,j-2);
switch (a[i][j])
{ case -1:printf("°");break;
case 1:printf("ž");break;
case 2:printf("B");break;
case 3:printf("M");break;
}
}
}

53
e
ar
m
void tratare()

ra
{ if ((nr_pasi<nr_pasi_b)||(nr_pasi_b==-1))

og
{ nr_pasi_b=nr_pasi;

pr
for (int i=0;i<nr_pasi;i++)
{d_b[i].x=d[i].x;

de
d_b[i].y=d[i].y;

ci
}

ni
}

eh
}

it
is
void go(int x,int y)
{ if ((x==n+2)&&(y==m+2))

itm
{ d[nr_pasi].x=x;

or
d[nr_pasi].y=y;
nr_pasi++;

lg
tratare(); -A
nr_pasi--;
}
da

else
an

{ d[nr_pasi].x=x;
ar

d[nr_pasi].y=y;
m

nr_pasi++;
.Z

int temp=a[x][y];
a[x][y]=-1;
D

if (a[x-1][y-2]!=-1) go(x-1,y-2);
if (a[x-2][y-1]!=-1) go(x-2,y-1);
if (a[x-2][y+1]!=-1) go(x-2,y+1);
if (a[x-1][y+2]!=-1) go(x-1,y+2);
if (a[x+1][y+2]!=-1) go(x+1,y+2);
if (a[x+2][y+1]!=-1) go(x+2,y+1);
if (a[x+2][y-1]!=-1) go(x+2,y-1);
if (a[x+1][y-2]!=-1) go(x+1,y-2);
a[x][y]=temp;
nr_pasi--;
}
}

void main()
{ clrscr();
printf("Dati N=");scanf("%d",&n);
printf("Dati M=");scanf("%d",&m);
for (i=1;i<=n+4;i++)
for (j=1;j<=m+4;j++)
a[i][j]=-1;
for (i=3;i<=n+2;i++)
54
e
ar
m
for (j=3;j<=m+2;j++)

ra
a[i][j]=1;

og
a[n+2][m+2]=3;

pr
go(3,3);

de
ci
for (i=1;i<=n+4;i++)

ni
for (j=1;j<=m+4;j++)

eh
a[i][j]=-1;

it
for (i=3;i<=n+2;i++)

is
for (j=3;j<=m+2;j++)
a[i][j]=1;

itm
a[n+2][m+2]=3;

or
for (i=0;i<nr_pasi_b;i++)

lg
-A {a[d_b[i].x][d_b[i].y]=2;
afis_tabla();
getch();
da

a[d_b[i].x][d_b[i].y]=-1;
an

}
ar

}
m
.Z

3. Probleme propuse :
D

1. Se considerã un set de NxN piese, fiecare avînd 4 valori pe acestea.


Sã se aranjeze ºi sã se roteascã piesele în aºa fel încît valorile vecine
între acestea sã coincidã.
Exemplu :

55
e
ar
m
2. Se considerã un labirint tridimensional de dimensiune NxNxN. Sã se

ra
realizeze programul cu ajutorul cãruia se va calcula un anumit drum

og
prin acest labirint de la o anumitã poziþie de start la o anumitã

pr
destinaþie.

de
3. Numai 12 din cele 92 de soluþii ale problemei celor 8 regine diferã în

ci
mod esenþial, celelalte pot fi deduse din cele 12 prin considerente de

ni
eh
simetrie faþã de axe sau faþã de centru. Sã se scrie programul care
determinã cele 12 soluþii.

it
is
itm
or
lg
-A
da
an
ar
m
.Z
D

56
e
ar
m
Lucrarea nr. 8

ra
Structura de date lista

og
pr
1. Scopul lucrãrii – îl reprezintã prezentarea structurii de date tip listã

de
înlãnþuitã precum ºi a operaþiilor care se realizeazã asupra acesteia.

ci
ni
2.Aspecte teoretice

eh
2.1. Implementarea listelor cu ajutorul tipului pointer

it
În aceastã implementare, o listã liniarã este o structurã dinamicã ºi deci,

is
ea poate fi definitã în termeni recursivi utilizînd urmãtoarele structuri de

itm
date:

or
struct nod {

lg
-A int cheie;
struct nod *urm;
...info;
da

};
an

O astfel de structurã ar putea fi reprezentatã schematic în felul urmãtor:


ar
m
.Z

inceput
D

cheie 1 2 3 Í
urm … Íull
info … … … …

Figura 1: Exemplu de listã implementatã cu ajutorul tipului pointer


Se observã existenþa unei variabile pointer P care indicã începutul listei
(primul nod).

2.1.1. Crearea listelor înlãnþuite. Inserþia unui nod într-o listã


înlãnþuitã

q=(nod*)malloc(sizeof(nod));
q->urm=inceput;
inceput=q;

Trebuie menþionat cã aceastã secvenþã de program nu asigneazã ºi


valorile cîmpurilor cheie ºi info pentru noul nod (cel inserat).

57
e
ar
m
Inser þia unui nod la începutul listei se prezintã grafic astfel:

ra
og
… …

pr
inceput inceput

de
ci
q->urm=inceput q->urm=inceput

ni
eh
q

it
inceput=q

is
itm
Figura 2: Inserþia unui nod la începutul unei liste

or
lg
Inserþia unui nod la sfirºitul unei liste se poate realiza prin secvenþa
urmãtoare: -A
da

r =(nod*)malloc(sizeof(nod));
an

r->urm=NULL;
sfirsit->urm=r;
ar

sfirsit=r;
m
.Z

unde sfirsit este un pointer care indicã întotdeauna sfirºitul listei


D

(ultimul nod al acesteia).


Referitor la aceastã secvenþã trebuie observat cã aceasta nu poate insera
un nod într-o listã vidã, deoarece sfirsit->urm nu existã în acest
caz. Deci, dacã se doreºte crearea unei liste în ordine naturalã, primul
nod trebuie inserat printr-un alt procedeu, de exemplu printr-o inserþie
la început de listã. Schematic, inserþia la sfîrºitul unei liste se prezintã în
felul urmãtor:

sfirsit Null
sfirsit=r
sfirsit->urm=r

r
Null

Figura 3: Inserþia unei nod la sfirºitul unei liste

58
e
ar
m
Pe lîng ã cele douã situaþii prezentate anterior (inserþia unui nod la

ra
începutul unei liste ºi respectiv inserþia unui nod la sfirºitul unei liste),

og
mai rãmîne de arãtat modalitatea de a insera noduri într-o poziþie

pr
oarecare a unei liste. Aici putem avea douã cazuri ºi anume cînd se

de
doreºte ca inserþia noului nod sã aibã loc dupã un anumit nod, indicat de
un pointer p, sau înaintea acestuia.

ci
Astfel, fie p pointerul care indicã un nod al listei iar q o variabilã

ni
pointer ajutatoare, care va indica nodul care se insereazã. Inserþia

eh
acestuia dupã nodul indicat de pointerul p, p->, se realizeazã prin

it
urmatoarea secvenþã:

is
Grafic, inserþia se prezintã în felul urmãtor:

itm
or
lg
q 25 q->urm=p->urm
-A
da
p->urm=q
an


ar

10 20 30

m
.Z
D

Figura 4: Inserþia unui nod dupã nodul indicat de pointerul p


q =(nod*)malloc(sizeof(nod));
q->urm=p->urm;
p->urm=q;

Dacã se doreºte inserþia noului nod înaintea nodului indicat de p, *p,


apare o complicaþie generatã de imposibilitatea practicã de a afla simplu
adresa predecesorului lui *p. Aceastã problemã însã se poate rezolva
simplu printr-un artificiu ºi anume: se insereazã un nou nod dupã nodul
*p; în acest nod, se asigneazã toate cîmpurile, mai puþin cel de
înlãnþuire (urm), cu valorile nodului *p; în acest moment, nodul *p va
exista în dublu exemplar în listã, pe poziþia iniþialã ºi în poziþia imediat
urmãtoare; dupã care se asigneazã cu noile valori cîmpurile cheie ºi
info corespunzatoare vechiului nod *p. Acest lucru se poate ilustra
prin urmãtoarea diagramã:

59
e
ar
m
ra
og
q 20

pr
de
*q=*p

ci

ni
10 20 30

eh
it
is
itm
p

or
Figura 5a: Crearea unui nod ºi copierea vechiului nod peste acesta

lg
-A 20
da
an

p->urm=q
ar


m

10 15 30
.Z


D

Figura 5b: Inlãnþuirea în listã a noului nod

Secvenþa de program corespunzãtoare este urmãtoarea:

q =(nod*)malloc(sizeof(nod));
*q=*p;
p->urm=q;

2.1.2. Suprimarea nodurilor dintr-o listã înlãnþuitã


Sã presupunem cã avem un pointer p care indicã un nod al unei liste
liniare înlãnþuite, ºi se cere sã se suprime succesorul nodului indicat de
p, ºi anume nodul p->urm. Aceasta se poate face prin urmãtoarea
secvenþã:

r=p->urm;

60
e
ar
m
p->urm=r->urm;

ra
og
În secvenþa de program mai sus se poate evita utilizarea variabilei

pr
pointer r prin înlocuirea fragmentului de mai sus cu instrucþiunea:

de
p->urm=p->urm->urm;

ci
ni
Utilizarea variabilei r are însã avantajul cã, prin intermediul ei,

eh
programatorul poate avea acces ulterior la nodul suprimat, acces care

it
altfel se pierde.

is
Dacã programatorul nu mai are nevoie ulterior de nodul suprimat, se

itm
recomandã utilizarea procedurii standard dispose(r) pentru a
elibera zona de memorie ocupatã de *r.

or
Daca însã se doreºte suprimarea chiar a nodului indicat de pointerul p

lg
-A dintr-o listã, ºi anume chiar nodul *p, observãm cã ºi aici apare aceeaºi
dificultate, generatã de imposibilitatea de a afla simplu adreasa
da

predecesorului lui *p. Soluþia se bazeazã pe aceeaºi tehnicã: se aduce


an

succesorul în locul lui *p ºi apoi se suprimã vechiul succesor. Secvenþa


de program care realizeazã acest lucru este urmãtoarea:
ar
m
.Z

r=p->urm;
*p=*r;
D

free(r);

Se remarcã faptul cã ambele tehnici de suprimare se pot aplica numai


daca p nu este ultimul nod al listei, adicã în cazul în care
r->urm!=NULL.

2.1.3. Traversarea unei liste înlãnþuite


Se înþelege prin traversarea unei liste parcurgerea secvenþialã a acesteia
în scopul executãrii unei anumite operaþii asupra tuturor nodurilor listei.
Astfel, fie inceput pointerul care indicã începutul listei ºi q o
variabil ã pointer auxiliarã. Traversarea se va face astfel:

q=inceput;
while (q!=NULL)
{
prelucrare(*q);
q=q->urm;
}

61
e
ar
m
O opera þie frecvent utilizatã o reprezintã cãutarea unui nod dat într-o

ra
listã: secvenþa prin care se cautã nodul cu cheia x datã într-o listã

og
înlãnþuitã unde inceput este pointerul de început de listã este

pr
urm ãtoarea:

de
#define 0 FALSE

ci
#define 1 TRUE

ni
eh
b=FALSE;

it
q=inceput;

is
while ((q!=NULL) && (b==FALSE))

itm
if (q->cheie==x) b=TRUE;
else q=q->urm;

or
lg
-A
Dacã dupã terminarea secvenþei avem b=true atunci *q este nodul
c ãutat; în caz contrar, nodul nu s-a gãsit ºi avem q=NULL.
da
an

2.2. Problemã rezolvatã


ar

Se se realizeze un program cu ajutorul caruia se va putea prelucra o listã


m

simplu înlanþuitã, în care fiecare nod va contine informaþii specifice


.Z

unui student : nume, prenume, grupa. Programul va fi realizat folosind


un meniu, avînd urmãtoarele opþiuni : Adaugare student, ªtergere
D

student, Afiºare student (informaþiile referitoare la studentul cu un


anumit nume), Afiºare lista studenþi, Ieºire program.

#include <conio.h>
#include <stdio.h>
#include <string.h>

struct student_struct
{ char nume[20];
char prenume[20];
char grupa[6];
student_struct *urm;
};

student_struct *prim,*p,*t;
int o;

void adauga_student()
{ char nume[20],prenume[20],grupa[6];
printf("\n\nAdaugare student");

62
e
ar
m
printf("\n\nDati numele studentului : ");

ra
scanf("%s",nume);

og
printf("Dati prenumele studentului : ");

pr
scanf("%s",prenume);
printf("Dati grupa studentului : ");

de
scanf("%s",grupa);

ci
if (prim==NULL)

ni
//daca nu avem o lista, se creazã primul nod

eh
{ prim=new student_struct;

it
strcpy(prim->nume,nume);

is
strcpy(prim->prenume,prenume);
strcpy(prim->grupa,grupa);

itm
prim->urm=NULL;

or
}
//daca avem deja o lista, adaugam la capat

lg
else
-A { p=prim;
while (p->urm!=NULL) p=p->urm;
da
t=new student_struct;
strcpy(t->nume,nume);
an

strcpy(t->prenume,prenume);
ar

strcpy(t->grupa,grupa);
m

t->urm=NULL;
.Z

p->urm=t;
}
D

void afis_lista()
{printf("\n\nAfisare lista studenti\n\n");
if (prim==NULL)
printf("Nu avem nici un student in lista");
else
{ p=prim;
while (p!=NULL)

{printf("\nNume:%s\nPrenume:%s\nGrupa:%s\n",p-
>nume,p->prenume,p->grupa);
p=p->urm;
}
}
getch();
}

void afis_student()
{char nume[20];

63
e
ar
m
printf("\n\nAfisare student");

ra
printf("\n\nDati numele studentului

og
cautat = ");

pr
scanf("%s",nume);
int gasit=0;

de
p=prim;

ci
while (p!=NULL)

ni
{if (strcmp(p->nume,nume)==0)

eh
{gasit=1;

it
is
printf("\nNume:%s\nPrenume:%s\nGrupa:%s\n",
p->nume,p->prenume,p->grupa);

itm
}

or
p=p->urm;
}

lg
if (!gasit) -A
printf("\nNu am gasit studentul %s.",nume);
getch();
da

}
an
ar

void sterge_student()
m

{ printf("\n\nStergere student");
.Z

char nume[20];
printf("\n\ndati numele studentului ce se
D

sterge : ");
scanf("%s",nume);
p=prim;int gasit=0;
while (p!=NULL)
{if (strcmp(p->nume,nume)==0)
{gasit=1;

printf("\nNume:%s\nPrenume:%s\nGrupa:%s\n",
p->nume,p->prenume,p->grupa);
break;
}
p=p->urm;
}
if (!gasit)
printf("Nu am gasit studentul %s.",nume);
else
{if (p==prim) //daca e tocmai primul din lista
prim=prim->urm;
else
if (p->urm==NULL) //daca e ultimul

64
e
ar
m
{t=prim;

ra
while (t->urm!=p) t=t->urm;

og
delete p;

pr
t->urm=NULL;
}

de
else //daca e undeva prin lista

ci
{t=prim;

ni
while (t->urm!=p) t=t->urm;

eh
t->urm=p->urm;

it
delete p;

is
}
}

itm
getch();

or
}
void main()

lg
-A {do
{clrscr();
printf("Meniu\n");
da

printf("1.Adaugare student\n");
an

printf("2.Stergere student\n");
ar

printf("3.Afisare student\n");
m

printf("4.Afisare lista studenti\n");


.Z

printf("5.Iesire\n\n");
printf("Alegeti optiunea : ");scanf("%d",&o);
D

switch (o)
{ case 1:adauga_student();break;
case 2:sterge_student();break;
case 3:afis_student();break;
case 4:afis_lista();break;
}
}
while (o!=5);
}

3. Probleme propuse

1. Se considerã o listã simplu inlanþuitã în care fiecare nod va conþine


un cuvînt. Sã se sorteze aceste cuvinte în ordine alfabeticã folosind
algoritmul de sortare Quicksort. Evaluaþi performanþele unui astfel de
algoritm de sortare, pentru diferite valori ale numãrului de cuvinte (10,
1000, 10000) ºi realizaþi o comparaþie faþa de utilizarea unui tablou în
locul unei liste. Comentaþi rezultatul.

65
e
ar
m
2. Se dã o listã înlãnþuitã L care conþine ca ºi elemente ºiruri de

ra
caractere (cuvinte), ºi o altã listã P, care conþine numere întregi în

og
ordine crescãtoare. Sã se scrie funcþia prin care se ºterg din lista L toate

pr
elementele care se aflã pe poziþiile specificate prin numerele din lista P,

de
ca o funcþie cu parametri de tipul sterge(L,P). Funcþia va trata prin
mesaje sugestive situaþiile de eroare.

ci
ni
3. Se considerã douã fraze oarecare, formate din cuvinte separate prin

eh
caracterul spaþiu, virgulã sau punct-virgulã, care se citesc de la

it
tastaturã. Frazele se terminã cu caracterul punct. Sã se scrie un program

is
care, utilizînd o structurã de listã simplu înlãnþuitã pentru memorarea

itm
frazelor, determinã cite cuvinte comune au cele douã fraze.

or
lg
4. Sã se scrie o funcþie care realizeazã operaþia de cut-and-paste a unei
-A
porþiuni dintr-o listã înlãnþuitã L1 în altã listã înlãnþuitã L2, conþinînd
ºiruri de caractere, astfel încît numãrul de mutãri de elemente sã fie cît
da

mai mic posibil. Funcþia va conþine ca ºi parametri : poziþia de început


respectiv de sfîrºit din prima lista L1, pentru care se face cut, ºi poziþia
an

din a doua lista L2 dupã care se face paste. Sã se scrie un program care
ar

utilizeazã funcþia astfel creatã, ºi care va afiºa cele douã liste în formã
m

iniþialã ºi apoi, dupã realizarea operaþiei cut-and-paste.


.Z
D

5. Problema Josephus : Se aºeazã un numãr de N copii, numerotaþi de la


1 la N, într-un cerc. Pornind de la copilul cu numãrul 1, se trece o
minge de la un copil la altul, ºi fiecare al M-lea copil la care ajunge
mingea este eliminat din cerc. Jocul continuã cu persoana care urmeazã
dupã copilul eliminat. Cîºtigãtorul este ultimul copil rãmas cu mingea.
Sã se scrie programul care rezolvã acest joc, pentru valori date ale lui M
respectiv N.

66
e
ar
m
ra
Lucrarea nr. 9

og
Liste ordonate. Utilizarea tehnicii fanionului

pr
în structura de listã

de
ci
1. Scopul lucrarii – îl reprezintã prezentarea modalitãþilor de creare a

ni
listelor ordonate precum ºi a utilizãrii tehnicii fanionului în cadrul

eh
structurii de listã înlãnþuitã

it
is
2.Aspecte teoretice

itm
2.1. Crearea unei liste ordonate utilizînd tehnica celor doi pointeri

or
Deosebirea fatã de variantele anterioare de construcþie a listelor
înlãnþuite, constã în aceea cã în acest caz inserþia unui nou nod nu se

lg
-A mai face întotdeauna la începutul listei, sau la sfirºitul ei, ci în acea
poziþie astfel încît lista sã se pãstreze ordonatã.
da

Crearea unei liste ordonate se realizeazã deosebit de simplu deoarece


înainte de inserþia unui nod, acesta trebuie oricum cãutat în listã. Dacã
an

lista este ordonatã (sã presupunem crescãtor), atunci cãutarea se va


ar

termina cu prima cheie mai mare decat cea cãutatã. Inserþia unui nod
m

într-o lista ordonatã reprezintã o aplicaþie a problemei inserþiei unui nod


.Z

înaintea celui indicat de un pointer, metodã care a fost descrisã în adrul


D

lucrarii 8.
O altã tehnicã de inserþie care poate fi utilizatã în acest caz o reprezintã
aºa numita tehnicã a celor doi pointeri. Aceasta se bazeazã pe
utilizarea a doi pointeri, de exemplu q1 ºi q2, care indicã tot timpul
douã noduri consecutive ale listei.
Inceput

Q2 Q1

Figura 6: Traversarea unei liste înlãnþuite utilizând tehnica celor doi


pointeri
Cei doi pointeri avanseazã simultan de-a lungul listei pînã cînd cheia lui
*q1 devine mai mare sau egala cu x (x reprezentînd cheia cãutatã în
list ã), lucru care se va întampla cu certitudine, în cel mai rãu caz în

67
e
ar
m
momentul în care *q1 devine egal cu fanionul (se utilizeazã tehnica

ra
fanionului ºi în acest caz). Dacã în acel moment cheia lui *q1 este strict

og
mai mare decat x sau q1=fanion, atunci înseamnã cã nodul nu existã

pr
de fapt în listã ºi deci trebuie inserat; inserþia noului nod se face

de
întotdeauna între nodurile indicate de cei doi pointeri q1 ºi q2. În caz
contrar, adicã în cazul în care cheia lui q1 este egalã cu x, înseamnã cã

ci
ni
nodul cu cheia x a fost gãsit în cadrul listei ºi atunci, funcþie de

eh
specificul aplicaþiei în care se utilizeazã lista, se va permite sau nu
inserarea de noduri cu aceeaºi cheie.

it
Funcþionarea corectã a algoritmului care utilizeazã tehnica celor doi

is
pointeri presupune existenþa iniþialã în listã a cel puþin douã noduri, deci

itm
cel puþin încã un nod în afara fanionului. Din acest motiv lista se va

or
iniþializa cu douã noduri fictive, astfel:

lg
-A
inceput=(nod *)malloc(sizeof(nod));
fanion=(nod *)malloc(sizeof(nod));
da

inceput->urm=fanion;
an

Ca principiu, *inceput va fi un aºa zis fanion de început , în timp ce


ar

*fanion va reprezenta nodul fanion obiºnuit, plasat la sfirºitul listei.


m

Funcþia de insertie a unui nod cu cheia x într-o listã ordonatã crescãtor ,


.Z

în care nu se admit inserþii multiple, va avea urmãtoarea formã :


D

void insert_ord(int x)
{
struct nod *q1, *q2, *q3;
q2=inceput;
q1=q2->urm;
fanion->cheie=x;
while (q1->cheie<x)
{
q2=q1;
q1=q2->urm;
}
if ((q1->cheie==x) && (q1!=fanion))
q1->numar++;
// contorizeaza numarul de aparitii ale aceleiasi chei
else
{
q3=(nod *)malloc(sizeof(nod));
q3->cheie=x;
q3->numar=1;

68
e
ar
m
q3->urm=q1;

ra
q2->urm=q3;

og
}

pr
}

de
2.2. Cãutarea în listã cu reordonare

ci
Cãutarea în listã cu reordonare reprezintã o metodã de cãutare ºi inserþie

ni
a nodurilor într-o listã înlãnþuitã care asigurã o performanþã mai bunã la

eh
cãutarea ulterioarã a unui nod. Ea constã în aceea cã, ori de cîte ori un

it
identificator se cautã ºi se gãseºte în listã, el se va muta la începutul

is
listei, astfel încît la proxima sa cãutare el va fi gãsit imediat. Cu alte

itm
cuvinte lista se reordoneazã dupã fiecare cãutare finalizatã cu gãsirea
nodului. Dacã un nod nu este gãsit în listã, el se va insera la începutul

or
acesteia.

lg
-A Utilizarea reordonãrii presupune existenþa unui nod fanion. Astfel, în
programul principal sunt necesare în acest caz urmãtoarele iniþializãri:
da

inceput=(nod *)malloc(sizeof(nod));
an

fanion=inceput;
ar

Funcþia de cãutare/inserþie în listã cu reordonare a unui nod cu cheia x


m

este urmãtoarea:
.Z
D

void caut_reord(int x)
{
struct nod *q1, *q2, *q3;
q1=inceput;
fanion->cheie=x;
if (q1==fanion)
{
inceput=(nod *)malloc(sizeof(nod));
inceput->cheie=x;
inceput->numar=1;
inceput->urm=fanion;
}
else
if (q1->cheie==x) q1->numar++;
else
{
do
{
q2=q1;
q1=q2->urm;
} while (q1->cheie!=x);

69
e
ar
m
if (q1==fanion)

ra
{

og
q2=inceput;

pr
inceput=(nod *)malloc(sizeof(nod));
inceput->cheie=x;

de
inceput->numar=1;

ci
inceput->urm=q2;

ni
}

eh
else

it
{

is
q1->numar++;
q2->urm=q1->urm;

itm
q1->urm=inceput;

or
inceput=q1;
}

lg
} -A
}
da

2.3. Problemã rezolvatã


an

Se considera o listã simplu înlanþuitã, în care fiecare nod conþine


ar

informaþiile specifice ale unui client la o banca:


m

nume,prenume,adresa,suma cont. Sã se realizeze un program cu ajutorul


.Z

cãruia se va citi un anumit numãr de clienþi, ºi va afiºa apoi lista de


D

clienþi în ordinea descrescãtoare a valorii contului. (Obs : Introducerea


unui client în lista afiºatã în final se va face astfel încît lista sã fie tot
timpul sortatã, aºa încat la afisarea listei nu va mai fi nevoie de înca o
sortare finalã.)

#include <conio.h>
#include <stdio.h>
#include <string.h>

struct client_struct
{ char nume[20];
char prenume[20];
char adresa[50];
long int suma_cont;
client_struct *urm;
};

client_struct *prim,*p1,*p2,*t;
int o;

70
e
ar
m
void afisare()

ra
{ if (prim==NULL)

og
printf("\n\nNu exita nici un client

pr
in lista");
else

de
{ p1=prim;

ci
while (p1!=NULL)

ni
{printf("\nNume : %s",p1->nume);

eh
printf("\nPrenume : %s",p1->prenume);

it
printf("\nAdresa : %s",p1->adresa);

is
printf("\nCont : %ld$\n",
p1->suma_cont);

itm
p1=p1->urm;

or
}
}

lg
-A getch();
}
da

void introducere()
an

{ char nume[20],prenume[20],adresa[50];
ar

long int suma_cont;


m

printf("\nDati numele : ");scanf("%s",nume);


.Z

printf("Dati prenumele : ");


scanf("%s",prenume);
D

fflush(stdin);
printf("Dati adresa : ");gets(adresa);
printf("Dati valoarea contului : ");
scanf("%ld",&suma_cont);
if (prim==NULL)
{ prim=new client_struct;
strcpy(prim->nume,nume);
strcpy(prim->prenume,prenume);
strcpy(prim->adresa,adresa);
prim->suma_cont=suma_cont;
prim->urm=NULL;
}
else
{t=new client_struct;
strcpy(t->nume,nume);
strcpy(t->prenume,prenume);
strcpy(t->adresa,adresa);
t->suma_cont=suma_cont;
p2=prim;
while((p2->suma_cont>suma_cont)&&(p2!=NULL))
p2=p2->urm;
71
e
ar
m
if (p2==prim)

ra
{t->urm=p2;

og
prim=t;

pr
}
else

de
{p1=prim;

ci
while (p1->urm!=p2) p1=p1->urm;

ni
t->urm=p2;

eh
p1->urm=t;

it
}

is
}
}

itm
or
void main()
{do

lg
{ clrscr(); -A
printf("Meniu\n");
printf("\n1.Introducere client");
da

printf("\n2.Afisare lista");
an

printf("\n3.Iesire");
ar

printf("\n\nAlegeti optiunea : ");


m

scanf("%d",&o);
.Z

switch (o)
{case 1:introducere();break;
D

case 2:afisare();break;
}
}
while (o!=3);
}

3. Probleme propuse

1. Se considera o lista simplu inlantuita cu 50 de noduri în care fiecare


nod va conþine o valoare de la 1 la 50. Se vor citi apoi un set de N
numere, ºi folosindu-se principiul cãutãrii cu reordonare, sã se afiseze
în final lista sub noua formã.

2. Se dau douã polinoame de forma :


P(x) = c 1xe1 + c2xe2 + ….+cnxen, unde e1>e2>…>en>=0
care se citesc de la tastaturã, sub forma exponent-coeficient. Se
utilizeazã structura de listã înlanþuitã ordonatã (descrescãtor, funcþie de
valoarea exponentului) pentru memorarea polinoamelor.

72
e
ar
m
Sã se scrie un program care determinã polinomul sumã ºi polinomul

ra
produs a celor douã polinoame citite iniþial. Polinoamele rezultate vor fi

og
afiºate în ordinea descrescãtoare a exponenþilor.

pr
de
3. Sã se realizeze un meniu cu urmãtoarele opþiuni : Adãugare nod,
Stergere nod, Afiºare listã. Sã se scrie un program care, utilizînd

ci
meniul de mai sus, va crea o listã înlãnþuitã cu chei intregi, cu

ni
proprietatea cã valorile de pe poziþiile pare vor fi sortate întotdeauna

eh
crescãtor, iar valorile de pe poziþiile impare vor fi sortate descrescãtor.

it
is
4. Informaþiile despre rezultatele concursului de admitere, la care au

itm
fost admiºi un numãr de N studenþi (N=NO1+NO2+NO3) sunt pãstrate
într-o listã odonatã descrescãtor funcþie de media cu care fiecare student

or
a fost admis. Pentru fiecare student se cunoaºte numele, media ºi

lg
-A secvenþa de opþiuni alese (O1 sau O2 sau O3).Aceastã listã se considerã
data de intrare. Sã se scrie programul care repartizeaza pe opþiuni cei N
da

studenþi admiºi, ºtiind cã existã NOi locuri disponibile la fiecare opþiune,


ºi cã repartiþia se face în ordinea descrescãtoare a mediilor ºi a
an

opþiunilor. Programul va afiºa lista iniþialã a studeþilor admiºi precum ºi


ar

listele (înlãnþuite !) obþinute în urma repartizãrii acestora pe opþiuni, în


m

ordine alfabeticã pentru fiecare opþiune.


.Z
D

73
e
ar
m
Lucrarea nr. 10

ra
Liste dublu înlãnþuite

og
pr
1. Scopul lucrãrii – îl reprezintã prezentarea particularitãþilor listelor

de
dublu înlãnþuite prin comparaþie cu cele simplu înlãnþuite.

ci
ni
2. Aspecte teoretice

eh
2.1. Caracteristicile listei dublu înlãnþuite

it
Structura de date tip listã dublu înlãþuitã se poate defini utilizînd

is
urmãtoarele tipuri de date:

itm
struct nod {

or
tipelement element;

lg
struct nod *anterior, *urmator;
-A
};
da

unde tipelement reprezintã tipul, definit de utilizator, elementelor listei.


an

Operaþiile de bazã care se executã asupra listelor dublu înlãnþuite sunt


ar

aceleaºi ca ºi pentru listele simplu înlãnþuite, diferenþa constînd în aceea


m

cã, la implementarea acestora trebuie þinut cont ºi de cel de-al doilea


.Z

cîmp de înlãnþuire (cîmpul anterior).


D

… …
… …

Figura 7: Listã dublu înlãnþuitã


Utilizarea listelor dublu înlãnþuite este recomandatã în aplicaþiile care
necesitã traversarea listei în ambele sensuri. Preþul care se plãteºte este
prezenþa unui cîmp suplimentar de tip pointer în fiecare nod ºi o
oarecare creºtere a lungimii unor funcþii care implementeazã operatorii
de bazã care prelucreazã astfel de liste.

2.2. Problemã rezolvatã


Sã se realizeze un program cu ajutorul cãruia se va putea prelucra o listã
dublu înlãnþuita , în care fiecare nod va conþine informaþii specifice unui
student : nume, prenume, grupa. Programul va fi realizat folosind un
meniu, avînd urmãtoarele opþiuni : Adaugare student, Stergere student,
Afisare student, Afisare lista student, Iesire program.

74
e
ar
m
#include <conio.h>

ra
#include <stdio.h>

og
#include <string.h>

pr
struct student_struct

de
{ char nume[20];

ci
char prenume[20];

ni
char grupa[6];

eh
student_struct *urm,*prec;

it
};

is
student_struct *prim,*p,*t;

itm
int o;

or
void adauga_student()

lg
-A { char nume[20],prenume[20],grupa[6];
printf("\n\nAdaugare student");
printf("\n\nDati numele studentului : ");
da

scanf("%s",nume);
an

printf("Dati prenumele studentului : ");


ar

scanf("%s",prenume);
m

printf("Dati grupa studentului : ");


.Z

scanf("%s",grupa);
if (prim==NULL) //daca nu avem lista,se creazã primul nod
D

{ prim=new student_struct;
strcpy(prim->nume,nume);
strcpy(prim->prenume,prenume);
strcpy(prim->grupa,grupa);
prim->urm=NULL;
prim->prec=NULL;
}
else
//daca avem deja o lista, adaugam la capat
{ p=prim;
while (p->urm!=NULL) p=p->urm;
t=new student_struct;
strcpy(t->nume,nume);
strcpy(t->prenume,prenume);
strcpy(t->grupa,grupa);
t->urm=NULL;
p->urm=t;
t->prec=p;
}
}

75
e
ar
m
ra
void afis_lista()

og
{printf("\n\nAfisare lista studenti\n\n");

pr
if (prim==NULL)
printf("Nu avem nici un student in lista");

de
else

ci
{ p=prim;

ni
while (p!=NULL)

eh
it
{printf("\nNume:%s\nPrenume:%s\nGrupa:%s\n",

is
p->nume,p->prenume,p->grupa);
p=p->urm;

itm
}

or
}
getch();

lg
} -A
void afis_student()
da

{char nume[20];
an

printf("\n\nAfisare student");
ar

printf("\n\nDati numele studentului


m

cautat = ");
.Z

scanf("%s",nume);
int gasit=0;
D

p=prim;
while (p!=NULL)
{if (strcmp(p->nume,nume)==0)
{gasit=1;

printf("\nNume:%s\nPrenume:%s\nGrupa:%s\n",
p->nume,p->prenume,p->grupa);
}
p=p->urm;
}
if (!gasit) printf("\nNu am gasit studentul
%s.",nume);
getch();
}

void sterge_student()
{ printf("\n\nStergere student");
char nume[20];
printf("\n\ndati numele studentului ce
se sterge : ");
scanf("%s",nume);
76
e
ar
m
p=prim;int gasit=0;

ra
while (p!=NULL)

og
{if (strcmp(p->nume,nume)==0)

pr
{gasit=1;
printf("\nSterg\nNume:%s\nPrenume:%s\n

de
Grupa:%s\n",p->nume,p->prenume,p->grupa);

ci
break;

ni
}

eh
p=p->urm;

it
}

is
if (!gasit)
printf("Nu am gasit studentul %s.",nume);

itm
else

or
{if (p==prim) //daca e tocmai primul din lista

lg
{prim=prim->urm;
-A prim->prec=NULL;
}
else
da

if (p->urm==NULL) //daca e ultimul


an

{t=p->prec;
ar

delete p;
m

t->urm=NULL;
.Z

}
else //daca e undeva prin lista
D

{t=p->prec;
t->urm=p->urm;
p->urm->prec=t;
delete p;
}
}
getch();
}

void main()
{do
{clrscr();
printf("Meniu\n");
printf("1.Adaugare student\n");

printf("2.Stergere student\n");
printf("3.Afisare student\n");
printf("4.Afisare lista studenti\n");
printf("5.Iesire\n\n");
printf("Alegeti optiunea : ");

77
e
ar
m
scanf("%d",&o);

ra
switch (o)

og
{ case 1:adauga_student();break;

pr
case 2:sterge_student();break;
case 3:afis_student();break;

de
case 4:afis_lista();break;

ci
}

ni
}

eh
while (o!=5);

it
}

is
itm
3. Probleme propuse

or
lg
1. a). Realizaþi implementarea operaþiilor de bazã pentru liste dublu
-A
înlãnþuite : inserþie, traversare în ambele sensuri, cãutare, ºtergere.
b). Scrieþi o funcþie care schimbã între ele douã elemente adiacente ale
da

unei liste dublu înlãnþuite. Poate fi realizatã aceastã funcþie numai prin
an

modificare înlãnþuirilor, fãrã a face mutãri de date ?


ar

2. Realizaþi implementarea operaþiilor de bazã pentru liste dublu


m

înlãnþuite circulare : inserþie, traversare în ambele sensuri, cãutare,


.Z

ºtergere.
D

3. Se considerã o listã dublu înlãnþuitã în care fiecare nod va conþine un


cuvant. Sã se sorteze aceste cuvinte în ordine alfabeticã folosind un
algoritm de sortare, la alegere.

78
e
ar
m
Lucrarea nr. 11

ra
Stive ºi cozi

og
pr
1. Scopul lucrãrii – îl reprezintã prezentarea structurilor de coadã ºi

de
stivã, ca structuri derivate din structura de listã. De asemenea, se

ci
prezintã ºi implementãri alternative ale acestora, în varianta de utilizare

ni
a tablourilor în locul listelor înlãnþuite.

eh
it
2. Aspecte teoretice

is
2.1. Stive

itm
O stivã este un tip special de listã în care toate inserþiile ºi suprimãrile
se executã la un singur capãt care se numeºte vîrful stivei. Din acest

or
motiv, stivele se mai numesc ºi structuri lista de tip LIFO

lg
-A (Last-In-First-Out).
Structura de stivã poate fi consideratã o listã cu un caracter mai special;
în consecinþã, toate implementãrile listelor descrise pînã în prezent sunt
da

valabile ºi în cazul stivei. Particularitatea stivei în implementarea cu


an

liste constã în aceea cã inserþiile ºi suprimãrile sunt permise la un singur


ar

capãt, vîrful stivei, conform principiului LIFO.


m

Pentru implementarea stivelor se poate însã utiliza ºi structura de


.Z

tablou, în cadrul cãruia se poate obþine o utilizare mai eficientã a


D

acestuia, dacã se þine cont de faptul cã inserþiile ºi suprimãrile se fac


numai la un singur capãt. Astfel, se poate considera drept bazã a stivei
sfîr ºitul tabloului (indexul maxim), stiva crescînd în sensul descreºterii
indexului de tablou. Un întreg numit virf indicã poziþia curentã al
primului element al stivei:

..
.
VIRF PRIMUL ELEMENT
AL DOILEA ELEMENT
..
.
LUNGIME_MAXIMÃ ULTIMUL ELEMENT

Figura 8: Implementarea stivei cu ajutorul tipului tablou


Structura de date utilizatã este urmãtoarea:

79
e
ar
m
#include <stdio.h>

ra
#define TRUE 1

og
#define FALSE 0

pr
#define lungime_maxima 10

de
typedef struct{

ci
int virf;

ni
int elemente[lungime_maxima];

eh
}stiva;

it
is
Implementarea celor cinci operaþii de bazã asupra stivelor este
urmãtoarea:

itm
or
int er;

lg
stiva s;
-A
void initilizare()
da

{
s.virf=lungime_maxima;
an

}
ar
m

int stivid(stiva s)
.Z

{
D

if(s.virf>lungime_maxima) return TRUE;


else return FALSE;
}

tipelement virfst(stiva s)
{
if (stivid(s)==TRUE)
{
er=TRUE;
printf("stiva este vida");
}
else
return s.elemente[s.virf];
}

tipelement pop()
{
if (stivid(s)==TRUE)
{
er=TRUE;
printf("stiva este vida");

80
e
ar
m
}

ra
else

og
{

pr
return s.elemente[s.virf++];
}

de
}

ci
ni
void push(tipelement x)

eh
{

it
if (s.virf==1)

is
{
er=TRUE;

itm
printf("stiva este plina");

or
}

lg
else
-A {
s.virf--;
s.elemente[s.virf]=x;
da

}
an

}
ar
m
.Z

2.2. Cozi
D

Cozile sunt o categorie specialã de liste în care elementele sunt


întotdeauna inserate la un capãt (spatele cozii) ºi sunt suprimate la
celãlalt capãt (faþa cozii). Din acest motiv, cozile se mai numesc ºi liste
FIFO (First-In-First-Out). Ca ºi în cazul stivelor, orice implementare al
listelor este valabilã ºi pentru cozi. Structurile de date care se utilizeazã
sunt urmatoarele:

#define TRUE 1
#define FALSE 0

struct nod {
tipelement element;
struct nod *urm;
};

Pe baza acestora se poate defini o structurã de tip coadã care constã din
doi pointeri indicînd faþa respectiv spatele unei liste înlãnþuite. Astfel,
se defineºte structura de coadã:

typedef struct{

81
e
ar
m
struct nod *fatza, *spate;

ra
}coada;

og
pr
Primul nod al cozii este un nod fictiv în care cîmpul element este

de
ignorat. Acestã convenþie permite o reprezentare ºi o manipulare mai
uºoarã a cozii vide.

ci
Secvenþele de program care implementeazã cele 5 operaþii de bazã care

ni
se efectueazã asupra cozilor sunt urmatoarele:

eh
it
coada c;

is
int er;

itm
void initializare()
{

or
c.fatza=(nod*)malloc(sizeof(nod));

lg
c.fatza->urm=NULL; -A
c.spate=c.fatza;
}
da
an

int vid(coada c)
{
ar

if (c.fatza==c.spate) return TRUE;


m

else return FALSE;


.Z

}
D

tipelement fatza(coada c)
{
if (vid(c)==1)
{
er=TRUE;
printf("coada este vida");
}
else
return c.fatza->urm->element;
}

void adauga(tipelement x)
{
c.spate->urm=(nod*)malloc(sizeof(nod));
c.spate=c.spate->urm;
c.spate->element=x;
c.spate->urm=NULL;
}

82
e
ar
m
void scoate()

ra
{

og
if (vid(c)==TRUE)

pr
{
er=TRUE;

de
printf("coada este vida");

ci
}

ni
else

eh
c.fatza=c.fatza->urm;

it
}

is
O altã variantã de implementare o reprezintã tablourile circulare. Un

itm
tablou circular este un tablou în care prima poziþie urmeazã ultimei

or
pozi þii, dupã cum aratã figura urmãtoare:

lg
-A LUNGIME MAXIMÃ 1
da
2
an
ar

.
.
m

.
.
.
.Z

.
.
.
D

.
.
.
.

C, SPATE C, FATZA
COADA

Figura 9: Implementarea cozii cu ajotorul tablourilor circulare

În aceastã implementare coada se gãseºte undeva în jurul cercului


(tabloului circular), în poziþii consecutive, avînd spatele undeva înaintea
feþei la parcurgerea cercului în sensul acelor de ceasornic. Înlãnþuirea
unui element presupune miºcarea pointerului c.spate cu o poziþie în
sensul acelor de ceasornic ºi introducerea elementului în aceastã poziþie
iar scoaterea presupune simpla mutare a pointerului c.fatza cu o
pozi þie în sensul acelor de ceasornic. Astfel, coada se roteºte în sensul
acelor de ceasornic dupã cum se adaugã sau se scot elemente din ea.

83
e
ar
m
Pentru sesizarea corectã a cozii vide (ºi respectiv pline), în tablou se vor

ra
introduce doar lungime_maxima-1 elemente (deºi existã

og
lungime_maxima poziþii). Astfel, testul de coadã plina conduce la o

pr
valoare adevaratã dacã c.spate devine egal cu c.fatza dupã douã

de
înaintãri succesive, iar testul de coadã vidã conduce la o valoare
adevãratã dacã c.spate devine egal cu c.fatza dupã înaintarea cu

ci
ni
o poziþie. Structura de date utilizatã în aceastã implementare este:

eh
#define TRUE 1

it
#define FALSE 1

is
#define lungime_maxima 10

itm
typedef struct{

or
tipelement elemente[lungime_maxima];

lg
int fatza, spate; -A
} coada;
da
an

Implementarea operaþiilor de bazã este urmãtoarea:


ar
m

coada c;
.Z

int er;
D

int avanseaza(int i)
{
div_t x;
x = div(i,lungime_maxima);
return x.rem;
}
int vid(coada c)
{
if (avanseaza(c.spate)==c.fatza) return TRUE;
else return FALSE;
}
tipelement fatza(coada c)
{
if (vid(c)==TRUE)
{
er=TRUE;
printf("coada este vida");
}
else
return c.elemente[c.fatza];

84
e
ar
m
}

ra
og
void adauga(tipelement x)

pr
{

de
if (avanseaza(avanseaza(c.spate))==c.fatza)
{

ci
er=TRUE;

ni
printf("coada este plina");

eh
}

it
else

is
{
c.spate=avanseaza(c.spate);

itm
c.elemente[c.spate]=x;

or
}

lg
}
-A
void scoate()
da
{
if (vid(c)==TRUE)
an

{
ar

er=TRUE;
m

printf("coada este vida");


.Z

}
else
D

c.fatza=avanseaza(c.fatza);
}

2.3. Problemã rezolvatã


Sa se realizeze un program cu ajutorul caruia se va putea simula
funcþionarea unei stive folosind o listã simplu inlãnþuitã. Ca ºi interfaþã,
se va considera un meniu cu urmãtoarele opþiuni : Adaugare stivã
(PUSH), Extragere stivã (POP), Golire stivã, Afiºare stiva.
#include <conio.h>
#include <stdio.h>

int o,i,stiva[100],sp=-1;
//sp=pozitia ultimului element din stiva

void afisare()
{ printf("\n");
if (sp==-1)
printf("Stiva este goala");
else

85
e
ar
m
{printf("Stiva : ");

ra
for (i=0;i<=sp;i++)

og
printf("%d ",stiva[i]);

pr
}
getch();

de
}

ci
ni
void adaugare()

eh
{ if (sp==99)

it
{printf("Stiva este deja plina !!!");

is
getch();
}

itm
else

or
{int valoare;
printf("\nDati numarul de introdus in stiva

lg
: "); -A
scanf("%d",&valoare);
stiva[++sp]=valoare;
da

}
an

}
ar
m

void scoate()
.Z

{if (sp==-1)
printf("Stiva este goala !!!");
D

else
printf("Elementul scos este %d",stiva[sp--
]);
getch();
}

void golire()
{
sp=-1;
}

void main()
{ do
{ clrscr();
printf("Meniu");
printf("\n1.Adaugare in stiva (PUSH)");
printf("\n2.Scoatere din stiva (POP)");
printf("\n3.Golire stiva");
printf("\n4.Afisare stiva");
printf("\n5.Iesire");
printf("\n\nAlegeti optiunea : ");
86
e
ar
m
scanf("%d",&o);

ra
switch (o)

og
{

pr
case 1:adaugare();break;
case 2:scoate();break;

de
case 3:golire();break;

ci
case 4:afisare();break;

ni
}

eh
}

it
while (o!=5);

is
}

itm
or
3. Probleme propuse

lg
-A 1. O listã dublu înlãnþuitã conþine o expresie aritmeticã în notatie
postfix, în care operanzii sunt reprezentaþi prin litere mici, iar operatorii
da

pot fi +, -, * sau / , nodurile avînd urmatoarea structurã:


an
ar

struct nod {
m

char oper; //operandul sau operatorul


.Z

int val;
//valoare operand sau 0 dacã e operator
D

nod *urm, *ant;


};
Sã se scrie o funcþie de evaluare a expresiei aritmetice, folosind o stivã
în care se depun valorile operanzilor ºi rezultatele parþiale (se va
implementa stiva printr-o lista sau printr-un tablou). Pe parcursul
evaluãrii, se vor face verificarile privind corectitudinea expresiei, o
eroare fiind semnalatã printr-un mesaj, oprind evaluarea expresiei.

2. Este posibil sã fie memorate douã structuri de date de tip stivã într-un
acelaºi tablou, una crescînd de la pozitia 1 spre sfirºit, cealaltã în sens
invers. Se cere sã se redacteze o funcþie generalã push(x,s) unde s
este una sau alta dintre stive. Funcþia va include toate testele de eroare
necesare, care vor fi semnalate prin mesaje corespunzãtoare.

3. Sã se realizeze un program care va conþine 2 funcþii A ºi B. Funcþia


A va gerera un numãr aleator, pe care-l va introduce într-o coadã, iar
functia B va extrage un numãr din aceasta coadã. (Obs: Aceast principiu

87
e
ar
m
este folosit în comunicarea dintre doua procese, coada fiind de fapt un

ra
buffer de date de la procesul A la procesul B).

og
Lucrarea nr. 12

pr
Tehnica dispersiei

de
ci
1. Scopul lucrãrii – îl reprezintã prezentarea principiului tehnicii

ni
dispersiei precum ºi evidenþierii eficacitãþii utilizãrii acestei tehnici în

eh
procesul de cãutare.

it
is
2. Aspecte teoretice

itm
2.1. Principiul tehnicii dispersiei
Tehnica dispersiei reprezintã o manierã specificã de organizare a unei

or
mulþimi de chei (elemente cu o cheie specificã) astfel încât regãsirea

lg
unei chei sã necesite cât mai puþin efort. Rezolvarea acestei probleme se
-A
reduce la gãsirea unei asocieri specifice H a mulþimii cheilor K cu
mulþimea adreselor A, adicã :
da
an

H:K->A
ar

Tehnica dispersiei se aplicã structurii de date tip tablou. Ideea pe care se


m

bazeazã aceastã metodã este urmãtoarea: se rezervã un volum constant


.Z

de memorie pentru aºa numitul “tablou dispersat”. Tabloul dispersat se


D

implementeazã cu ajutorul unei structuri de date de tip tablou. Notând


cu p numãrul elementelor din tabloul dispersat, indicele l care
precizeazã poziþia unui anumit nod poate lua valori între 0 ºi p-1. De
asemenea, se noteazã cu K mulþimea tuturor cheilor ºi cu k o cheie
oarecare. Numãrul cheilor va fi mai mare decât dimensiunea tabloului.
În acest caz o funcþie H care defineºte o aplicaþie a lui k pe mulþimea
tuturor indicilor L , astfel :
l=H(k)
unde k este o cheie din mulþimea tuturor cheilor ºi l un indice din
mulþimea tuturor indicilor.
Funcþia H de asociere se numeºte funcþie de dispersie ºi ea permite ca,
pornind de la o cheie k datã, sã se determine indicele asociat acesteia, l.
Din acest motiv, tehnica dispersiei se mai numeºte ºi tehnica
transformãrii cheilor, întrucât cheile se transformã, prin intermediul
funcþiei de dispersie, în indici de tablou.
Evident, funcþia H nu este o funcþie bijectivã deoarece numãrul cheilor
este mult mai mare decât numãrul indicilor. Practic, existã mai multe
chei cãrora le corespunde acelaºi indice.
Principiul metodei este urmãtorul: pentru introducerea unui nod în
structurã cu cheia k se determinã mai întâi indicele asociat l=H(k), dupã

88
e
ar
m
care se depune la nodul în tabloul dispersat în poziþia T(l) , unde T este

ra
tabloul de dispersie. Dacã în continuare apare o altã cheie k’ care are

og
acelaºi indice asociat l, adicã l=H(k’)=H(k), atunci s-a ajuns la aºa

pr
numita situaþie de coliziune, care se poate rezolva în mai multe moduri,

de
dupã cum se va ilustra ulterior.
Pentru a regãsi un nod cu o cheie k se procedeazã similar, adicã se

ci
calculeazã indicele asociat cheii k de cãutat ºi în tabloul de dispersie la

ni
indicele gãsit ca asociat cheii k se va cãuta cheia. Dacã cheia se va

eh
regãsi cãutarea se va termina, dar dacã nodul cãutat nu se va regãsi

it
atunci se ajunge într-o situaþie de coliziune, situaþie ce se va rezolva la

is
fel ca ºi la introducerea de noduri.

itm
Aplicarea în practicã a tehnicii dispersiei presupune deci rezolvarea a

or
douã probleme ºi anume: definirea funcþiei de dispersie H ºi tratarea
situaþiei de coliziune.

lg
-A
2.2. Determinarea funcþiei de dispersie. Tratarea situaþiei de
da

coliziune
Funcþia de dispersie trebuie sã repartizeze cât mai uniform mulþimea
an

cheilor pe mulþimea indicilor, deoarece astfel se minimalizeazã


ar

probabilitatea coliziunilor iar pe de altã parte calculele sunt mult mai


m

simple. De asemenea, funcþia de dispersie trebuie sã fie uºor calculabilã.


.Z

Una dintre funcþiile de dispersie cel mai des utilizate este urmãtoarea:
D

H(k) = ord(k) mod p


unde ord(p) reprezintã numãrul de ordine (întreg) ataºat cheii k ºi care
precizeazã a câta cheie este cheia k în mulþimea K a cheilor iar p este un
numãr reprezentând numãrul maxim de indici în tabloul dispersat
(0,p-1). În vederea repartizãrii cât mai uniforme a cheilor pe indici este
recomandabil ca p sã fie un numãr prim (dar nu obligatoriu).
In cazul cheilor ºiruri de caractere se pot utiliza ºi urmãtoarele funcþii
specifice:
length ( k )
H (k ) = ( ∑ i =1
ord (k[i ]) mod p

length ( k )
H (k ) = ( ∑ i =1
2 i ord (k[i ]) mod p

Tratarea situaþiei de coliziune presupune generarea unei noi poziþii


pentru noua cheie de inserat. O metodã în acest sens este aceea de a
înlãnþui într-o listã toate nodurile ale cãror indici primi sunt identici,
metoda numindu-se înlãnþuire directã. Elementele acestei liste pot sau

89
e
ar
m
nu sã aparþinã tabelului iniþial. În caz cã nu, memoria necesarã se alocã

ra
din aºa numita “zonã de depãºire”. Aceastã metodã, care este deosebit

og
de eficientã, are douã dezavantaje: necesitatea menþinerii unor liste

pr
secundare ºi prelungirea fiecãrui nod cu un spaþiu pentru pointerul

de
necesar înlãnþuirii. In aceastã variantã, algoritmul utilizeazã un tablou
de pointeri ce indicã fiecare spre o listã cu nodurile care sunt asociate

ci
aceleiaºi întrãri din tabloul dispersat.

ni
Algoritmul este urmãtorul:

eh
it
#include<stdio.h>

is
#include<conio.h>

itm
#include<alloc.h>
#include <stdlib.h>

or
lg
#define N 10 -A
typedef struct NOD
{
da

int cheie;
an

struct NOD *urm;


ar

};
m

int DISPERSIE(int x) //functia de dispersie


.Z

{
D

div_t y;
y=div(x,N);
y.rem;
return y.rem;
}
void TIPLIST (NOD *f) //tiparirei tablou dispersat
{ NOD *b;
b=f;
while (b!=NULL)
{
printf("%d\t",b->cheie);
b=b->urm;
}
printf(" \n ");
}
void main(void)
{
NOD *el; /* el = un element */
NOD a;
int x,i;
char c;

90
e
ar
m
NOD *t[N]; /* tabloul de dispersie*/

ra
for(i=0;i<N;i++) t[i]=NULL;

og
clrscr();

pr
printf(" Introduceti un element

de
intreg \n");
scanf("%d",&x);

ci
while (x!=0)

ni
{

eh
el=(NOD *)malloc(sizeof(NOD));

it
el->cheie=x;

is
i=DISPERSIE(x);
/*pozitia in tablou */

itm
el->urm=t[i];

or
t[i]=el;

lg
printf("Mai doriti sa
-A introduceti alte elmente?y/n");
c=getch();
da

if (c!='y')
{
an

x=0;
ar

}
m

else
.Z

{
printf(" \n
D

Introduceti un element intreg \n");


scanf("%d",&x);
};
}
if (c=='n')
{
for (i=0;i<N;i++)
{
printf("\nPe
pozitia %d avem elementele : ",i);
TIPLIST(t[i]);
};
};
}

O altã metodã, numitã adresare deschisã, realizeazã în cazul unei


coliziuni parcurgerea dupã o anumitã regulã a tabloului dispersat, pînã
la gãsirea primului loc liber, urmãtorul indice fiind:
- adresare deschisã: indicele consecutiv din tablou faþa de cel care a
provocat coliziunea, l+1, tabloul considerîndu-se circular.

91
e
ar
m
Algoritmul are tendinþa de a îngrãmãdi însã nodurile în continuarea

ra
celor existente, datoritã metodei de parcurgere folosite

og
- adresare patraticã: indicele l+r (l = indicele care a provocat

pr
coliziune; r = o valoare care se iniþializeazã pe 1 ºi se

de
incrementeazã cu 2 la fiecare nereuºitã). Un dezavantaj minor al
acestei metode este acela cã se poate întîmpla sã nu se gãseascã nici

ci
un element liber, ajungîndu-se la depãºirea tabloului, cu toate cã în

ni
tabel mai existã elemente libere. Totuºi, probabilitatea ca acest

eh
lucru sã se întîmple este foarte micã, ºi în general metoda este

it
foarte performantã

is
itm
or
2.3. Problemã rezolvatã
Sa se realizeze un program cu ajutorul cãruia se va face dispersia unei

lg
-A
mulþimi de numere date folosindu-se funcþia de dispersie :
k = (x mod n) + 1.
da
an

#include <conio.h>
#include <stdio.h>
ar
m

struct coloana_struct
.Z

{ int linie[100];
D

int nr_linii;
};

coloana_struct coloana[100];
int f,n,o,x;

void afisare_coloana(int c)
{ printf("\nColoana %d : ",c);
int f;
if (coloana[c].nr_linii==0) printf("Goala");
else
for (f=1;f<=coloana[c].nr_linii;f++)
printf("%d ",coloana[c].linie[f]);
}
void afis_coloana()
{ int c;
do
{printf("Dati coloana pe care doriti s-o
afisati [1..%d] = ",n);
scanf("%d",&c);
}

92
e
ar
m
while ((c<1)||(c>n));

ra
afisare_coloana(c);

og
getch();

pr
}

de
void adauga_element()

ci
{ printf("Dati elementul ce doriti sa-l

ni
introduceti : ");

eh
scanf("%d",&x);

it
int poz=(x%n)+1;

is
printf("Elementul %d a fost adaugat la
coloana %d.",x,poz);

itm
or
coloana[poz].linie[++coloana[poz].nr_linii]=x;
getch();

lg
-A }

void main()
da

{ for (f=0;f<100;f++) coloana[f].nr_linii=0;


an

clrscr();
ar

printf("Dati numarul de coloane de


m

dispersie : ");
.Z

scanf("%d",&n);
D

do
{ clrscr();
printf("Meniu pt dispersie cu %d
coloane",n);
printf("\n1.Adaugare numar");
printf("\n2.Afisare o anumita coloana");
printf("\n3.Afisare toate coloanele");
printf("\n4.Iesire");
printf("\n\nAlegeti optiunea : ");
scanf("%d",&o);
switch (o)
{case 1:adauga_element();break;
case 2:afis_coloana();break;
case 3:{for (f=1;f<=n;f++)
afisare_coloana(f);
getch();
} break;
}
}
while (o!=4);
}
93
e
ar
m
ra
3. Probleme propuse

og
pr
1. Se cere sã se construiascã o tabelã de dispersie conþinînd

de
identificatorii dintr-un text (aflat într-un fisier), fiecare identificator
avînd asociat ºi contorul de apariþii. Textul este format din propoziþii,

ci
separate prin caracterul punct. Cuvintele sunt la rindul lor separate cu

ni
caracterul spaþiu, virgulã sau punct-virgulã.

eh
it
2. Sa se realizeze un program prin care se va face dispersia unor

is
studenþi pe baza primei litere din nume (adica studenþii al cãror nume

itm
incep cu „A” vor fi introduºi incepind de la prima pozitie a tabloului

or
dispersat, cei cu „B” incepind de la a doua pozitie, º.a.m.d) . Datele
specifice unui student vor fi : nume, grupa, medie. Ca ºi interfaþã se va

lg
-A
folosi un meniu cu urmãtoarele opþiuni: adãugare student, ºtergere
student, cãutare student, afiºarea tuturor studenþilor al cãror nume care
da

începe cu o anumitã literã”, afiºarea integralã a tabelei de dispersie.


an

3. Principalul dezavantaj al tehnicii dispersiei este acela cã lungimea


ar

tabloului este fixã. Presupunînd cã existã un mecanism de alocare


m

dinamicã a memoriei prin care, daca tabloul de dispersie este plin, se


.Z

poate genera un alt tablou de dispersie, toate cheile din tabloul iniþial
D

copiindu-se in cel de-al doilea, dupã care zona ocupatã de tabloul iniþial
se elibereaza. Aceasta tehnica se numeste tehnica redispersiei
(re-hashing). Sã se scrie un program care implementeazã aceastã
tehnicã.

94

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