Sunteți pe pagina 1din 15

Ministerul Educaţiei și Cercetării al Republicii Moldova

Universitatea Tehnică a Moldovei


Facultatea Calculatoare, Informatică și Microelectronică

RAPORT
Lucrarea de laborator nr.2
la Analiza și Proiectarea Algoritmilor

A efectuat:
st. gr. TI-221 Bejan Kateryna

A verificat:
asist. univ. Andrievschi-Bagrin Veronica

Chişinău - 2023
Lucrare de laborator nr 1

Tema: Metoda divide et impera

Scopul lucrării:
1. Studierea metodei divide et impera.
2. Analiza și implementarea algoritmilor bazați pe metoda divide et impera.

Sarcina:
1. De efectuat analiza empirică a algoritmilor propuși
2. De determinat relația ce reprezintă complexitatea temporală pentru acești algoritmi
3. De determinat complexitatea asimptotică a algoritmilor

Rezumat succint la tema lucrării de laborator:


Tehnica divide et impera Divide et impera este o tehnica de elaborare a algoritmilor care
constă în:
 Descompunerea cazului ce trebuie rezolvat într-un număr de subcazuri mai mici ale
aceleiaşi probleme.
2. Rezolvarea succesivă şi independentă a fiecăruia din aceste subcazuri.
3. Recompunerea subsoluţiilor astfel obţinute pentru a găsi soluţia cazului iniţial.

Quick Sort și Merge Sort sunt doi algoritmi de sortare care folosesc tehnica "Divide et
Impera" pentru a eficientiza procesul de sortare.

Quick Sort:
Alege un element pivot și partitionează lista în două subliste: una cu elemente mai mici decât
pivotul și alta cu elemente mai mari.
Aplică recursiv Quick Sort pe cele două subliste.
Combinați subliste sortate pentru a obține lista finală sortată.
În cel mai nefavorabil caz, Quick Sort poate avea o complexitate pătratică, dar în medie are o
performanță foarte bună și este frecvent folosit în practică.

Merge Sort:
Divide lista în două părți aproximativ egale.
Aplică recursiv Merge Sort pe cele două părți pentru a le sorta.
Interclasează cele două părți sortate pentru a obține lista finală sortată.
Merge Sort are o performanță mai predictibilă și are întotdeauna o complexitate de timp de
O(n log n), făcându-l adecvat pentru liste mari.
Ambele algoritmi sunt exemple ale tehnicii "Divide et Impera" și au aplicații importante în
sortarea și procesarea datelor.
Codul programului in C
#include <stdio.h>
#include <stdlib.h>

int* create_vector(int vector_length);


void initialize_vector_with_random_numbers(int* vector, int
vector_length);
void bubble_sort(int* vector, int vector_length, int* iterations);
void reverse_sort(int* vector, int vector_length, int* iterations);
void copy_vector(int* dest, const int* src, int vector_length);
void display_vector(const int* vector, int vector_length);
int divide_vector_in_subvector(int* vector, int left, int right);
void quick_sort_recursive(int* vector, int left, int right, int*
iterations);
void merge(int arr[], int l, int m, int r);
void mergeSort(int arr[], int l, int r, int* iterations);

int* create_vector(int vector_length)


{
int* vector = malloc(vector_length * sizeof(int));
if (vector)
{
return vector;
}
printf("\nError: tabloul nu a fost alocat in memorie.\n");
return NULL;
}

void initialize_vector_with_random_numbers(int* vector, int


vector_length)
{
for (int i = 0; i < vector_length; i++)
{
vector[i] = rand() % 100; // Generează numere întregi între 0 și
99
}
}

void print_vector_elements(int* vector, int vector_length)


{
if (vector)
{
printf("Tabloul introdus este: ");
for (int i = 0; i < vector_length; i++)
{
printf("%d, ", vector[i]);
}
printf("\n");
}
else
{
printf("Tabloul este gol sau nu a fost initializat.\n");
}
}

int compare_descending(const void* a, const void* b) {


return (*(int*)b - *(int*)a);
}
int compare_ascending(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}

void free_vector(int* vector)


{
if (vector != NULL)
{
free(vector);
}
}

void bubble_sort(int* vector, int vector_length, int* iterations)


{
int swapped;
*iterations = 0;
for (int i = 0; i < vector_length - 1; i++)
{
swapped = 0;
for (int j = 0; j < vector_length - i - 1; j++)
{
(*iterations)++;
if (vector[j] > vector[j + 1])
{
// Schimbă elementele
int temp = vector[j];
vector[j] = vector[j + 1];
vector[j + 1] = temp;
swapped = 1;
}
}

// Dacă nu s-au făcut schimbări în bucla internă, vectorul este


deja sortat
if (swapped == 0)
break;
}
}

int divide_vector_in_subvector(int* vector, int left, int right)


{
int pivot = vector[right];
int i = left;

for (int j = left; j < right; j++)


{
if (vector[j] <= pivot)
{
if (i != j)
{
int temp = vector[i];
vector[i] = vector[j];
vector[j] = temp;
}
i++;
}
}

int temp = vector[i];


vector[i] = vector[right];
vector[right] = temp;

return i;
}

int find_median(int* vector, int left, int right) {


int mid = left + (right - left) / 2;
if (vector[left] > vector[mid]) {
if (vector[mid] > vector[right]) {
return mid;
} else {
return (vector[left] > vector[right]) ? right : left;
}
} else {
if (vector[left] > vector[right]) {
return left;
} else {
return (vector[mid] > vector[right]) ? right : mid;
}
}
}

void quick_sort_recursive(int* vector, int left, int right, int*


iterations) {
if (left < right) {
int pivot_index = find_median(vector, left, right);

// Schimbă valoarea pivotului cu prima poziție


int temp = vector[left];
vector[left] = vector[pivot_index];
vector[pivot_index] = temp;

int pivot_value = vector[left];


int i = left - 1;
int j = right + 1;

while (1) {
do {
i++;
} while (vector[i] < pivot_value);

do {
j--;
} while (vector[j] > pivot_value);

if (i >= j) {
break;
}

// Schimbă elementele la i și j
int temp = vector[i];
vector[i] = vector[j];
vector[j] = temp;

(*iterations)++;
}

// Continuă sortarea pentru subvectorii stânga și dreapta


quick_sort_recursive(vector, left, j, iterations);
quick_sort_recursive(vector, j + 1, right, iterations);
}
}

void merge(int arr[], int l, int m, int r)


{
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;

// Create temp arrays


int L[n1], R[n2];

// Copy data to temp arrays L[] and R[]


for (i = 0; i < n1; i++)
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];

// Merge the temp arrays back into arr[l..r


i = 0;
j = 0;
k = l;
while (i < n1 && j < n2)
{
if (L[i] <= R[j])
{
arr[k] = L[i];
i++;
}
else
{
arr[k] = R[j];
j++;
}
k++;
}

// Copy the remaining elements of L[], if there are any


while (i < n1)
{
arr[k] = L[i];
i++;
k++;
}

// Copy the remaining elements of R[], if there are any


while (j < n2)
{
arr[k] = R[j];
j++;
k++;
}
}

// l is for left index and r is right index of the sub-array of arr to be


sorted
void mergeSort(int arr[], int l, int r, int* iterations)
{
if (l < r)
{
int m = l + (r - l) / 2;

// Sort first and second halves


mergeSort(arr, l, m, iterations);
mergeSort(arr, m + 1, r, iterations);

merge(arr, l, m, r);
(*iterations) += (r - l + 1); // Incrementăm contorul cu numărul
de elemente sortate în această etapă
}
}
void reverse_sort(int* vector, int vector_length, int* iterations)
{
int temp;
for (int i = 0; i < vector_length / 2; i++)
{
(*iterations)++;
temp = vector[i];
vector[i] = vector[vector_length - i - 1];
vector[vector_length - i - 1] = temp;
}
}
void copy_vector(int* dest, const int* src, int vector_length)
{
for (int i = 0; i < vector_length; i++)
{
dest[i] = src[i];
}
}

int main() {
int vector_length;
printf("Introduceti numarul de elemente ale tabloului: ");
scanf("%d", &vector_length);

// Cazul favorabil
printf("Cazul Favorabil:\n");

int *vector_favorabil = create_vector(vector_length);


initialize_vector_with_random_numbers(vector_favorabil,
vector_length);

// Bubble Sort
int iterations_bubble_favorabil = 0;
int *bubble_favorabil = malloc(vector_length * sizeof(int));
copy_vector(bubble_favorabil, vector_favorabil, vector_length);
bubble_sort(bubble_favorabil, vector_length,
&iterations_bubble_favorabil);
printf("Bubble Sort: %d iteratii\n", iterations_bubble_favorabil);
free(bubble_favorabil);

// Quick Sort
int iterations_quick_favorabil = 0;
int *quick_favorabil = malloc(vector_length * sizeof(int));
copy_vector(quick_favorabil, vector_favorabil, vector_length);
quick_sort_recursive(quick_favorabil, 0, vector_length - 1,
&iterations_quick_favorabil);
printf("Quick Sort: %d iteratii\n", iterations_quick_favorabil);
free(quick_favorabil);

// Merge Sort
int iterations_merge_favorabil = 0;
int *merge_favorabil = malloc(vector_length * sizeof(int));
copy_vector(merge_favorabil, vector_favorabil, vector_length);
mergeSort(merge_favorabil, 0, vector_length - 1,
&iterations_merge_favorabil);
printf("Merge Sort: %d iteratii\n", iterations_merge_favorabil);
free(merge_favorabil);

free(vector_favorabil);

// Cazul defavorabil
int iterations_reverse_sort = 0;
printf("\nCazul Defavorabil:\n");

int *vector_defavorabil = create_vector(vector_length);


initialize_vector_with_random_numbers(vector_defavorabil,
vector_length);
reverse_sort(vector_defavorabil,
vector_length,&iterations_reverse_sort ); // Sortează descrescător

// Bubble Sort
int iterations_bubble_defavorabil = 0;
int *bubble_defavorabil = malloc(vector_length * sizeof(int));
copy_vector(bubble_defavorabil, vector_defavorabil, vector_length);
bubble_sort(bubble_defavorabil, vector_length,
&iterations_bubble_defavorabil);
printf("Bubble Sort: %d iteratii\n", iterations_bubble_defavorabil);
free(bubble_defavorabil);

// Quick Sort
int iterations_quick_defavorabil = 0;
int *quick_defavorabil = malloc(vector_length * sizeof(int));
copy_vector(quick_defavorabil, vector_defavorabil, vector_length);
quick_sort_recursive(quick_defavorabil, 0, vector_length - 1,
&iterations_quick_defavorabil);
printf("Quick Sort: %d iteratii\n", iterations_quick_defavorabil);
free(quick_defavorabil);

// Merge Sort
int iterations_merge_defavorabil = 0;
int *merge_defavorabil = malloc(vector_length * sizeof(int));
copy_vector(merge_defavorabil, vector_defavorabil, vector_length);
mergeSort(merge_defavorabil, 0, vector_length - 1,
&iterations_merge_defavorabil);
printf("Merge Sort: %d iteratii\n", iterations_merge_defavorabil);
free(merge_defavorabil);
free(vector_defavorabil);

// Cazul mediului
printf("\nCazul Mediului:\n");

int *vector_mediu = create_vector(vector_length);


initialize_vector_with_random_numbers(vector_mediu, vector_length);

// Bubble Sort
int iterations_bubble_mediu = 0;
int *bubble_mediu = malloc(vector_length * sizeof(int));
copy_vector(bubble_mediu, vector_mediu, vector_length);
bubble_sort(bubble_mediu, vector_length, &iterations_bubble_mediu);
printf("Bubble Sort: %d iteratii\n", iterations_bubble_mediu);
free(bubble_mediu);

// Quick Sort
int iterations_quick_mediu = 0;
int *quick_mediu = malloc(vector_length * sizeof(int));
copy_vector(quick_mediu, vector_mediu, vector_length);
quick_sort_recursive(quick_mediu, 0, vector_length - 1,
&iterations_quick_mediu);
printf("Quick Sort: %d iteratii\n", iterations_quick_mediu);
free(quick_mediu);

// Merge Sort
int iterations_merge_mediu = 0;
int *merge_mediu = malloc(vector_length * sizeof(int));
copy_vector(merge_mediu, vector_mediu, vector_length);
mergeSort(merge_mediu, 0, vector_length - 1,
&iterations_merge_mediu);
printf("Merge Sort: %d iteratii\n", iterations_merge_mediu);
free(merge_mediu);

free(vector_mediu);

return 0;
}
Analiza empirică a algoritmilor

În cazul favorabil în care tabloul care trebuie să fie sortat este deja sortat, observăm
rezultate interesante în urma analizei empirice a trei algoritmi de sortare: Bubble Sort,
Quick Sort și Merge Sort.

Tipul
sortarii/marime
a tabloului 50 60 100 200 300 400 500 900 3000 5000
bubble sort 49 59 99 199 299 399 499 899 2999 4999
quick sort 38 48 87 194 334 506 672 1476 7197 13587
merge sort 286 356 672 1544 2488 3488 4488 8876 34904 61808

Bubble Sort se comportă foarte bine în acest caz favorabil, deoarece este un algoritm de
sortare stabil și simplu, care funcționează bine pe liste care sunt deja parțial sau complet
sortate.
Quick Sort se comportă surprinzător de rău în acest caz, cu timpuri de execuție
semnificativ mai mari decât celelalte două algoritme. Acest lucru se datorează faptului că
Quick Sort este sensibil la alegerea pivotului și poate avea performanțe slabe pe liste care
sunt deja sortate.
Merge Sort oferă performanțe solide în acest caz, cu timpuri de execuție mai bune decât
Quick Sort, dar mai mari decât Bubble Sort. Merge Sort este cunoscut pentru performanța
sa constantă și, în general, funcționează bine pe liste de orice tip.

Caz favorabil
70000

60000

50000
nr de iteratii

40000

30000

20000

10000

0
0 1000 2000 3000 4000 5000 6000
nr de elemente ale tabloului

bubble sort quick sort merge sort

În cazul defavorabil, în care tabloul este sortat descrescător, observăm că algoritmii de


sortare se comportă diferit față de cazul favorabil.

caz
defavorabil 10 20 40 50 60 100 200 250 300 400
quick sort 45 189 780 1120 1734 4929 19885 31070 44070 79569
merge sort 10 22 54 80 96 186 420 558 699 1007
bubble sort 34 88 216 286 356 672 1544 1994 2488 3488
Comparând cele două cazuri:

Bubble Sort are timpuri de execuție semnificativ mai mari în cazul defavorabil, ceea ce
este de așteptat deoarece trebuie să facă mai multe comparații și interschimbări pentru a
sorta tabloul descrescător.
Quick Sort are timpuri de execuție similare în ambele cazuri, deoarece pivotul ales nu
depinde de ordinea elementelor inițiale.
Merge Sort oferă performanțe solide și consistente în ambele cazuri, deoarece utilizează
întotdeauna o strategie de împărțire echitabilă a listei, ceea ce asigură complexitatea sa de
timp constantă în orice situație.
În ambele cazuri, în special în cazul defavorabil, Merge Sort rămâne un algoritm eficient
și predictibil, în timp ce Bubble Sort are timpuri de execuție semnificativ mai mari în
cazul defavorabil. Quick Sort rămâne un algoritm eficient, dar fără îmbunătățiri
semnificative între cele două cazuri.
Cazuri defavorabile
90000
80000
70000
60000
50000
Iteratii

40000
30000
20000
10000
0
0 50 100 150 200 250 300 350 400 450
Nr de elemente din tablou

quick sort merge sort bubble sort

În cazul median, am exclus algoritmul Bubble Sort din analiză pentru că are o
complexitate mare. Ne-am concentrat pe comparația între Quick Sort și Merge Sort, care
ambele folosesc paradigma "divide et impera" pentru a eficientiza procesul de sortare.
Tipul
sortarii/marim
ea tabloului 100000 400000 600000 800000 1100000 1500000 1600000 1800000
1640117 7699100 1250959 1716276 2639034 4256844 4356360 4981082
quick sort 7 2 66 91 50 80 94 68
2064602 9098287 1400641 1903637 2664766 3713473 3975296 4500590
merge sort 5 3 95 79 02 78 74 55

Quick Sort și Merge Sort sunt ambele algoritmi eficienți în cazul mediu.

Punctul de intersectare dintre algoritmii Merge Sort și Quick Sort este influențat și de
stabilitatea algoritmilor. Quick Sort, deși poate fi rapid pentru seturi de date mai mici și
poate avea o constantă mai mică în factorul său de timp, este cunoscut pentru
instabilitatea sa în anumite cazuri. Acesta poate să își modifice comportamentul în funcție
de distribuția inițială a datelor, devenind mai puțin eficient și chiar neperformant în cazul
unor distribuții specifice. Această instabilitate a Quick Sort poate duce la intersectarea sa
cu algoritmul Merge Sort în anumite condiții, în special în cazul seturilor de date mari,
unde Merge Sort oferă o performanță mai constantă și exacta decit Quick Sort.

Caz mediu
600000000

500000000

400000000
nr de iteratii

300000000

200000000

100000000

0
0 200000 400000 600000 800000 1000000 1200000 1400000 1600000 1800000 2000000
marimea tabloului

quick sort merge sort

Concluzie
În urma testării diferitelor cazuri pentru algoritmii de sortare Bubble Sort, Quick Sort și
Merge Sort, putem trage următoarele concluzii generale:
Bubble Sort:
 Bubble Sort este un algoritm simplu, dar ineficient pentru seturi de date mari sau chiar
moderate.
 Este adecvat pentru seturi de date mici sau atunci când setul de date este aproape
sortat.
 Nu se bazează pe paradigma "divide et impera" și, prin urmare, are o complexitate în
timp ridicată în cazurile nefavorabile.
 Este un algoritm instabil, ceea ce înseamnă că nu păstrează ordinea relativă a
elementelor cu aceeași valoare.
Quick Sort:
 Quick Sort este un algoritm eficient, în special pentru seturi de date mari și moderate.
 Poate avea o performanță bună în cazurile medii și favorabile.
 Este un algoritm instabil, iar timpul său de execuție poate varia în funcție de valorile și
distribuția datelor din tablou.
 Este important să se aleagă pivotul optim pentru a obține performanță maximă.
Merge Sort:
 Merge Sort este un algoritm eficient și stabil, indiferent de distribuția datelor.
 Are complexitatea în timp O(n log n) și este constant în ceea ce privește timpul de
execuție.
 Este o alegere excelentă pentru seturile de date mari și pentru situațiile în care
stabilitatea sortării este importantă.
 Se bazează pe paradigma "divide et impera" și garantează o performanță constantă.
În concluzie, alegerea algoritmului de sortare depinde de dimensiunea și caracteristicile
setului de date, precum și de importanța stabilității sortării. Pentru seturi de date mici sau
aproape sortate, Bubble Sort poate fi acceptabil. Pentru seturi de date mari și performanță
maximă, Merge Sort este o opțiune excelentă. Quick Sort oferă un echilibru între eficiență și
complexitate, dar poate varia în funcție de distribuția datelor.

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