Sunteți pe pagina 1din 11

Universitatea Tehnică a Moldovei

FCIM
Catedra Informatica Aplicativă

RAPORT
la lucrarea de laborator nr. 7

la SDA

Tema: Analiza complexităţii şi eficienţei algoritmilor ale METODELOR TEHNICII


PROGRAMĂRII

Varianta 28

A efectuat: st. gr. Ti-161 Trifan Iulian.

A verificat: Marin Ştefan

Chişinău – 2016
Sarcina şi obiectivele:
- de studiat şi însuşit materialul teoretic pentru evidenţierea esenţialului evaluării tehnicilor de
programare în elaborarea modelelor eficiente a soluţiei problemelor: esenţa metodelor (strategiilor
tehnicilor de apreciere) şi specificul realizării;
- să se analizeze complexitatea asimptotică şi notaţiile ei ca să se definească tehnica modelării
timpului de execuţie prin analiza empirică şi scenariile programării eficiente prin calculul
timpului de execuţie pentru diverse cazuri şi modele de structuri abstracte cu argumentări;
- să se efectueze analiza empirică a algoritmilor folosiţi, determinând relaţia de evaluare a
complexităţii temporale pentru aceşti algoritmi.
- să se analizeze modalitatea de evaluare a timpului de execuţie şi timpului de rulare din
exemplele propuse;
să se preia varianta problemei din Anexa pentru analiza empirică a complexităţii algoritmilor
şi calculul timpului de execuţie, implementând funcţiile de timp C, şi rularea programelor în limbajul C
 să se elaboreze scenariile succinte de modificare, incluzând pointeri, subprograme şi fişiere
cu teste de verificare şi vizualizări şi explicaţii la principalele subprograme prin analiza comparativă.
Folosiţi ceasul de timp real al sistemului pentru a estima performanţele
algoritmului.
- Faceţi o concluzie asupra eficienţei.

Consideraţii teoretice:
 Importanţa analizei şi implementării algoritmilor eficienţi
1.1 Eficienta algoritmilor. Ideal este ca, pentru o problema data, sa gasim mai multi algoritmi, iar apoi
sa-l alegem dintre aceştia pe cel optim. Care este insa criteriul de comparatie? Eficienta unui algoritm poate fi
exprimata in mai multe moduri. Putem analiza a posteriori (empiric) comportarea algoritmului dupa
implementare, prin rularea pe calculator a unor cazuri diferite. Sau, putem analiza a priori (teoretic)
algoritmul, inaintea programarii lui, prin determinarea cantitativa a resurselor (timp, memorie etc) necesare
ca o functie de marimea cazului considerat.
Marimea unui caz x, notata cu | x |, corespunde formal numarului de biti necesari pentru reprezentarea
lui x, folosind o codificare precis definita si rezonabil de compacta. Astfel, cand vom vorbi despre sortare,
| x | va fi numarul de elemente de sortat. La un algoritm numeric, | x | poate fi chiar valoarea numerica a
cazului x. Avantajul analizei teoretice este faptul ca ea nu depinde de calculatorul folosit, de limbajul de
programare ales, sau de indemanarea programatorului. Ea salveaza timpul pierdut cu programarea si rularea
unui algoritm care se dovedeste in final ineficient. Din motive practice, un algoritm nu poate fi testat pe
calculator pentru cazuri oricat de mari. Analiza teoretica ne permite insa studiul eficientei algoritmului pentru
cazuri de orice marime.
Este posibil sa analizam un algoritm si printr-o metoda hibrida. In acest caz, forma functiei care descrie
eficienta algoritmului este determinata teoretic, iar valorile numerice ale parametrilor sunt apoi determinate
empiric. Aceasta metoda permite o predictie asupra comportarii algoritmului pentru cazuri foarte mari, care
nu pot fi testate. O extrapolare doar pe baza testelor empirice este foarte imprecisa.
1.4 De ce avem nevoie de algoritmi eficienti?
Performantele hardware-ului se dubleaza la aproximativ doi ani. Mai are sens atunci sa investim in
obtinerea unor algoritmi eficienti? Nu este oare mai simplu sa asteptam urmatoarea generatie de calculatoare?

Figura 1.1 Algoritmi sau hardware?


Sa presupunem ca pentru rezolvarea unei anumite probleme avem un algoritm exponential si un
calculator pe care, pentru cazuri de marime n, timpul de rulare este de 10-4 × 2n secunde. Pentru n = 10, este
nevoie de 1/10 secunde. Pentru n = 20, sunt necesare aproape 2 minute. Pentru n = 30, o zi nu este de ajuns,
iar pentru n = 38, chiar si un an ar fi insuficient. Cumparam un calculator de 100 de ori mai rapid, cu timpul
de rulare de 10-6 × 2n secunde. Dar si acum, pentru n = 45, este nevoie de mai mult de un an! In general, daca
in cazul masinii vechi intr-un timp anumit se putea rezolva problema pentru cazul n, pe noul calculator, in
acest timp, se poate rezolva cazul n+7.
Timpul de execuţie al algoritmilor.
De multe ori, pentru rezolvarea unei probleme, trebuie ales un algoritm dintre mai mulţi posibili, două
criterii principale de alegere fiind contradictorii:
1) algoritmul să fie simplu de înţeles, de codificat şi de depanat;
2) algoritmul să folosească eficient resursele calculatorului, să aibă un timp de execuţie redus.
Dacă programul care se scrie trebuie rulat de un număr mic de ori, prima cerinţă este mai
importantă; în această situaţie, timpul de punere la punct a programului e mai important decât timpul lui
de rulare, deci trebuie aleasă varianta cea mai simplă a programului.
Daca programul urmează a fi rulat de un număr mare de ori, având şi un număr mare de date de
prelucrat, trebuie ales algoritmul care duce la o execuţie mai rapidă. Chiar în această situaţie, ar trebui
implementat mai înainte algoritmul mai simplu şi calculată reducerea de timp de execuţie pe care ar aduce-o
implementarea algoritmului complex.
Timpul de rulare al unui program depinde de următorii factori:
- datele de intrare;
- calitatea codului generat de compilator;
- natura şi viteza de execuţie a instrucţiunilor programului;
- complexitatea algoritmului care stă la baza programului.
Importanţa celui mai defavorabil caz. In aprecierea şi compararea algoritmilor interesează în special
cel mai defavorabil caz deoarece furnizează cel mai mare timp de execuţie relativ la orice date de intrare de
dimensiune fixă. Pe de altă parte pentru anumiţi algoritmi cazul cel mai defavorabil este relativ frecvent.
În ceea ce priveşte analiza celui mai favorabil caz, aceasta furnizează o margine inferioară a timpului de
execuţie şi poate fi utilă pentru a identifica algoritmi ineficienţi (dacă un algoritm are un cost mare în cel mai
favorabil caz, atunci el nu poate fi considerat o soluţie acceptabilă).
Timp mediu de execuţie. Uneori, cazurile extreme (cel mai defavorabil şi cel mai favorabil) se întâlnesc
rar, astfel ca analiza acestor cazuri nu furnizează suficientă informaţie despre algoritm.
In aceste situaţii este utilă o altă măsură a complexităţii algoritmilor şi anume timpul mediu de execuţie.
Acesta reprezintă o valoare medie a timpilor de execuţie calculată în raport cu distribuţia de probabilitate
corespunzătoare spaţiului datelor de intrare.
2.2. Ordin de creştere. Pentru a aprecia eficienţa unui algoritm nu este necesară cunoaşterea
expresiei detaliate a timpului de execuţie. Mai degrabă interesează modul în care timpul de
execuţie creşte o dată cu creşterea dimensiunii problemei. O măsură utilă în acest sens este
ordinul de creştere. Acesta este determinat de termenul dominant din expresia timpului de
execuţie. Când dimensiunea problemei este mare valoarea termenului dominant depăşeşte
semnificativ valorile celorlalţi termeni astfel că aceştia din urmă pot fi neglijaţi.
Întrucât problema eficienţei devine critică pentru probleme de dimensiuni mari se face analiza
complexităţii pentru cazul când n este mare (teoretic se consideră că n —> ∞), în felul acesta luându-se în
considerare doar comportarea termenului dominant. Acest tip de analiză se numeşte analiză asimptotică. In
cadrul analizei asimptotice se consideră că un algoritm este mai eficient decât altul dacă ordinul de creştere al
timpului de execuţie al primului este mai mic decât al celuilalt.
Există următoarele cazuri când ordinul de creştere a timpului de execuţie, nu e cel mai bun criteriu de
apreciere a performantelor unui algoritm:
- dacă un program se rulează de puţine ori, se alege algoritmul cel mai uşor de implementat;
- dacă întreţinerea trebuie făcută de o altă persoană decât cea care l-a scris, un algoritm simplu, chiar
mai puţin eficient, e de preferat unuia performant, dar foarte complex şi greu de înţeles;
 există algoritmi foarte eficienţi, dar care necesită un spaţiu de memorie foarte mare, astfel încât
folosirea memoriei externe le diminuează foarte mult performanţele.
2.5. Analiza empirică a complexităţii algoritmilor
O alternativă la analiza matematică a complexităţii o reprezintă analiza empirică.
Aceasta poate fi utilă pentru:
(i) a obţine informaţii preliminare privind clasa de complexitate a unui algoritm;
(ii) pentru a compara eficienţa a doi (sau mai mulţi) algoritmi destinaţi rezolvării aceleiaşi probleme;
(iii) pentru a compara eficienţa mai multor implementări ale aceluiaşi algoritm;
(iv) pentru a obţine informaţii privind eficienţa implementării unui algoritm pe un anumit calculator.
Etapele analizei empirice. In analiza empirică a unui algoritm se parcurg de regulă următoarele etape:

1. Se stabileşte scopul analizei.


2. Se alege metrica de eficienţă ce va fi utilizată (număr de execuţii ale unei/unor operaţii sau timp de
execuţie a întregului algoritm sau a unei porţiuni din algoritm.
3. Se stabilesc proprietăţile datelor de intrare în raport cu care se face analiza (dimensiunea datelor sau
proprietăţi specifice).
4. Se implementează algoritmul într-un limbaj de programare.
5. Se generează mai multe seturi de date de intrare.
6. Se execută programul pentru fiecare set de date de intrare.
7. Se analizează datele obţinute.

Problemele de autocontrol:
APLICAŢIE Se consideră un triunghi format din n linii, fiecare linie i conţinând i numere întregi. Să se
determine cea mai mare sumă de numere aflate pe un drum între numărul de pe prima linie şi un număr
de pe ultima linie. Fiecare număr din acest drum este situat sub precedentul, la stânga sau la dreapta
acestuia.
8. Exemplul 4 Metoda Divide et Impera

#include <iostream>
using namespace std;
#include <stdio.h>
int n; int a[30][30];
void citire() { int i,j;
cout<<"n=";cin>>n;
for (i=1;i<=n;i++)
for (j=1;j<=i;j++) { cout<<"a["<<i<<","<<j<<"]="; cin>>a[i][j]; } }
void afisare() { int i,j;
for (i=1;i<=n;i++) { for (j=1;j<=i;j++) printf("%5d",a[i][j]); printf("\n"); } }
int suma (int i, int j) { int s,d,S; if (i<=n) { s=suma(i+1,j); d=suma(i+1,j+1);
if (s>d) S=a[i][j]+s; else S=a[i][j]+d; } else S=0; return S; }
int main()

{ citire(); cout<<endl; afisare(); cout<<endl; cout<<"\nSuma maxima="<<suma(1,1); }


Metoda Greedy
#include <iostream>
#include <stdio.h>
using namespace std;
int n,S; int a[50][50];
void citire() { int i,j; cout<<"n=";cin>>n;
for (i=1;i<=n;i++) for (j=1;j<=i;j++) { cout<<"a["<<i<<","<<j<<"]=";cin>>a[i][j];
} }
void afisare() { int i,j;
for (i=1;i<=n;i++) { for (j=1;j<=i;j++) printf("%5d",a[i][j]); printf("\n"); } }
void suma () { int i,j; S=a[1][1]; j=1;
for (i=2;i<=n;i++) { if (a[i][j]>a[i][j+1]) S+=a[i][j];
else { S+=a[i][j+1]; j++; } } }
int main()
{ citire(); cout<<endl; afisare(); cout<<endl; suma(); cout<<"\nsuma
maxima=="<<S; }
Metoda Backtracking
#include <iostream>
#include <stdio.h>
using namespace std;
#include <conio.h>
#include <math.h>
int n,S,Smax; int a[50][50],x[50];
void citire() { int i,j; cout<<"n=";cin>>n;
for (i=1;i<=n;i++) for (j=1;j<=i;j++) { cout<<"a["<<i<<","<<j<<"]="; cin>>a[i][j];
} }
void afisare() { int i,j;
for (i=1;i<=n;i++) { for (j=1;j<=i;j++) printf("%5d",a[i][j]); printf("\n"); } }
void afis() { cout<<endl; S=0;
for (int i=1;i<=n;i++) { cout<<a[i][x[i]]<<" "; S+=a[i][x[i]]; } cout<<S;
if (S>Smax) Smax=S; getch(); }
int valid(int k) { int cod; if (k>1) if (x[k]==x[k-1] || x[k]==x[k-1]+1) cod=1;
else cod=0; else cod=1; return cod; }
void back(int k) { for (int i=1;i<=k;i++) { x[k]=i; if (valid(k)) if (k==n) afis(); else
back(k+1); } }
int main() {
citire(); cout<<endl; afisare(); cout<<endl; Smax=0; back(1); cout<<"\nsuma
maxima=="<<Smax; }
Exemplul 8 Se dă o multime de numere pozitive, P şi un număr M. Se cere determinarea unui subset a lui P a cărui
sumă a elementelor să fie cel mult M.
#include <iostream>
#include <stdio.h>
using namespace std;
#include <conio.h>
#include <math.h>
int main() { int n, p[200], rez[200], k, M, i, j, sum, temp, x;
printf("introduceti n: "); scanf("%d", &n);
for (i=0; i<n; i++) { printf("p[%d]=", i);
scanf("%d", &p[i]); }
printf("Introduceti suma: "); scanf("%d", &M);
/* incepem abordarea greedy a problemei ordonam crescator multimea de numere p
*/
for (i=0; i<n-1; i++)
for (j=i+1; j<n; j++)
if (p[i]>p[j]) {
temp = p[i]; p[i] = p[j]; p[j] = temp; };
k = 0; sum = 0;
for (i=0; i<n; i++) { x = p[i]; /*
alege(A,i,x) */
if ( sum + x <= M ) {
rez[k++] = x;
sum += x; }
else break; }
printf("submultimea rezultat: ");
for (i=0; i<k; i++) printf(" %d", rez[i]); printf("\n"); }
Exemplul 7 Metoda programarii dinamice
#include <iostream.h>
#include <stdio.h>
int n; int a[50][50],s[50][50];
void citire() { int i,j; cout<<"n=";cin>>n;
for (i=1;i<=n;i++) for (j=1;j<=i;j++) { cout<<"a["<<i<<","<<j<<"]="; cin>>a[i][j]; } }
void afisare() { int i,j;
for (i=1;i<=n;i++) { for (j=1;j<=i;j++) printf("%5d",a[i][j]); printf("\n"); } }
void suma () { int i,j;
for (i=1;i<=n;i++) s[n][i]=a[n][i];
for (i=n-1;i>0;i--) for (j=1;j<=i;j++)
{ s[i][j]=a[i][j]+s[i+1][j]; if (s[i+1][j]<s[i+1][j+1]) s[i][j]=a[i][j]+s[i+1][j+1]; } }
void main() { citire(); cout<<endl; afisare(); cout<<endl; suma(); cout<<"\nsuma maxima=="<<s[1][1]; }

#include <iostream>
#include <stdio.h>
using namespace std;
#include <conio.h>
#include <math.h>
#include<cstdio>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define INT 'i'
#define STR 's'
struct data { char tag;
union { int i; char *s; } value; } *a; int counter = 0;
int main() { char word[128]; int n; int i; clock_t t1,t2; float t; system("cls");
t1=clock();
printf("Dati numarul de elemente pentru tablou :"); scanf("%d",&n);
a=(data *) malloc(n *sizeof(*a));
if(!a) { puts("Memoria nu a fost alocata :"); }
puts("Dati numarul sau cuvintul :"); for(counter=0; counter < n; counter++)
{ fflush(stdin); if( gets(word) == NULL ) break; if( isdigit((unsigned char) *word))
{ a[counter].value.i = atoi(word); a[counter].tag = INT; }
else { a[counter].value.s =(char *) malloc(strlen(word)+1); strcpy(a[counter].value.s,
word); a[counter].tag = STR; } }
t2=clock(); t=t2-t1/CLK_TCK;
puts("Rezultatele obtinute :"); printf("Timpul de executie este: %f\n",t);
for(i=0; i < counter; i++)
switch(a[i].tag)
{
case INT: printf("numarul %d\n", a[i].value.i); break;
case STR: printf("cuvintul %s\n", a[i].value.s);
free(a[i].value.s);
break;
}
}

Problema 28:
1. Sa se genereze toate permutarile de dimensiune n cu valori în 1..n cu proprietatea ca oricare ar fi
2<=i<=n exista un 1<=j<=i astfel incât |v(i)-v(j)|=1.
#include<stdio.h>
#include<string.h>
#define N 10

void afisare(int *num, int n)


{
int i;
for ( i = 0 ; i < n ; i++)
printf("%d ", num[i]);
printf("\n");
}
int main()
{
int num[N];
int *ptr;
int temp;
int i, n, j;
printf("\nDati numarul n de numere: ");
scanf("%d", &n);
printf("\nIntroduceti o lista de numere:\n");
for (i = 0 ; i < n; i++)
scanf("%d", &num[i]);
for (j = 1; 1<=j<=i ; j++)break; {
for (i = 0; i < n-1; i++) {
temp = num[i];
num[i] = num[i+1];
num[i+1] = temp;
afisare(num, n);
}
}
return 0;
}

9. Concluzie : Am analizat si am elaborat probleme cu divesre tehnici de programare (back


tracking , Geddy , programarea dinamica , Divide et Impera) care difera si se remarca prin
timpul de executie , numarul de iteratii . Astfel am determinat ca pentru orice caz particular
putem folosi tehnica de prelucrare care avantajeaza problema data.

Bibliografie
 Limbajul de programare C". Brian W.Kernighan. Dennis M.Ritchie.
- Liviu Negrescu. ”Limbajul de programare C şi C++” V.1-4. Buc. 1999
- Knuth, D. E. - "Arta programarii calculatoarelor, vol. 1: Algoritmi fundamentali", Ed. Teora, 1999.
- Knuth, D. E. - "Arta programarii calculatoarelor, vol. 2: Algoritmi seminumerici", Ed. Teora, 2000.
- Knuth, D. E. - "Arta programarii calculatoarelor, vol. 3: Sortare si cautare", Ed. Teora, 2001.
- Bacivarov, A.; Nastac, I. - "Limbajul C. Indrumar de laborator", Tipografia UPB, Bucuresti, 1997.
- Bates, J; Tompkins, T. - "Utilizare C++", Ed. Teora 2001.
- Andonie, R.; Gabarcea, I. - "Algoritmi fundamentali. O perspectiva C++", Ed. Libris, 1995.
- Help din Turbo C ++IDE ,versiunea 3.0. 1999
- CORMEN, T. - LEISERSON, CH. - RIVEST, R. : Introducere in algoritmi, Editura Computer Libris. Agora,
Cluj-Napoca, 2000.
- Conspectele la PC 2009 şi SDA-2010
- L.Negrescu.Limbajele C si C++ pentru incepatori.Vol 1 si 2.Ed.Microinformatica.Cluj-N.,1994 si reeditata in
1996 ,1998,2000.
- L.Livovschi,H.Georgescu.Analiza si sinteza algoritmilor. Ed.Enciclopedica, Bucuresti,, 1986.
- I.Ignat, C.Ignat. Structuri de date si algoritmi. Indrumator de laborator. U.T.Pres, 2001.
- Informatica. Îndrumar metodic pentru lucrările de laborator. Marin Şt. Chişinău 2003. UTM

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