Documente Academic
Documente Profesional
Documente Cultură
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
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
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>
return i;
}
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)++;
}
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");
// 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");
// 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");
// 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
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
Î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
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.