Ministerul Educației al Republicii Moldova
Universitatea Tehnică a Moldovei
Departamentul ISA
RAPORT
Lucrarea de laborator nr.2
Analiza si proiectarea algoritmilor
A efectuat:
st. gr. TI-173 Roșca Florin
A verificat:
lect., univ. Andrievschi-Bagrin Veronica
Chisinau 2018
LUCRAREA DE LABORATOR NR.2
Tema: Metoda divide et impera
Scopul lucrării:
Studierea metodei divide et impera.
Analiza și implementarea algoritmilor bazați pe metoda divide et impera.
Note de curs
Divide et impera este o tehnica de elaborare a algoritmilor care constă în:
1. 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. Combinarea subsoluţiilor astfel obţinute pentru a găsi soluţia cazului iniţial.
Algoritmul formal al metodei divide et impera:
function divimp(x)
{returnează o soluţie pentru cazul x}
1: if x este suficient de mic
2: then return adhoc(x)
3: {descompune x în subcazurile x1, x2, …, xk}
4: for i 1 to k do yi divimp(xi)
5: {recompune y1, y2, …, yk în scopul obţinerii soluţiei y pentru x}
6: return y
unde adhoc este subalgoritmul de bază folosit pentru rezolvarea micilor subcazuri ale problemei în cauză.
Mergesort (sortarea prin interclasare)
Fie T[1 .. n] un tablou pe care dorim sa-l sortam crescător. Prin tehnica divide et impera putem proceda
astfel: separăm tabloul T în două părţi de mărimi cât mai apropiate, sortăm aceste părţi prin apeluri
recursive, apoi interclasăm soluţiile pentru fiecare parte, fiind atenţi să păstrăm ordonarea crescătoare a
elementelor. Obţinem următorul algoritm pentru rezolvarea unei subprobleme:
procedure
mergesort (T, p, r)
1: if p < r
2: then q p r/2
3: mergesort (T, p, q)
4: mergesort(T, q+1, r)
5: merge (T, p, q, r)
Algoritmul mergesort ilustrează perfect principiul divide et impera:
- divide problema în subprobleme;
- stăpâneşte subproblemele prin rezolvare;
- combină soluţiile subproblemelor.
In algoritmul mergesort, suma mărimilor subcazurilor este egală cu mărimea cazului iniţial. Această
proprietate nu este în mod necesar valabilă pentru algoritmii divide et impera. Este esenţial ca subcazurile
să fie de mărimi cât mai apropiate (sau, altfel spus, subcazurile să fie cât mai echilibrate).
Quicksort (sortarea rapida)
Algoritmul de sortare quicksort, inventat de Hoare în 1962, se bazează de asemenea pe principiul
divide et impera. Spre deosebire de mergesort, partea nerecursivă a algoritmului este dedicata construirii
subcazurilor şi nu combinării soluţiilor lor.
Ca prim pas, algoritmul alege un element pivot din tabloul care trebuie sortat. Tabloul este apoi
partiţionat în două subtablouri, alcătuite de-o parte şi de alta a acestui pivot în următorul mod: elementele
mai mari decât pivotul sunt mutate în dreapta pivotului, iar celelalte elemente sunt mutate în stânga
pivotului. Acest mod de partiţionare este numit pivotare. În continuare, cele două subtablouri sunt sortate
în mod independent prin apeluri recursive ale algoritmului. Rezultatul este tabloul complet sortat; nu mai
este necesară nici o interclasare. Pentru a echilibra mărimea celor două subtablouri care se obţin la fiecare
partiţionare, ar fi ideal să alegem ca pivot elementul median. Intuitiv, mediana unui tablou T este
elementul m din T, astfel încât numărul elementelor din T mai mici decât m este egal cu numărul celor
mai mari decât m. Din păcate, găsirea medianei necesita mai mult timp decât merită. De aceea, putem pur
şi simplu să folosim ca pivot primul element al tabloului. Iată cum arată acest algoritm:
procedure
quicksort (T, p, r )
1: if p < r
2: then q Partition (T, p, r )
3: quicksort (T, p, q)
4: quicksort t(T, q+1, r)
Intuitiv, ne dăm seama că algoritmul quicksort este ineficient, dacă se întâmpla în mod sistematic ca
subcazurile să fie puternic neechilibrate Dacă elementele lui T sunt distincte, cazul cel mai nefavorabil
este atunci când iniţial tabloul este ordonat crescător sau descrescător, fiecare partiţionare fiind total
neechilibrată.
Sarcina de baza:
1. Studiaţi noţiunile teoretice despre metoda divide et impera.
2. Implementaţi algoritmii Mergesort şi Quicksort.
3. Efectuaţi analiza empirică a algoritmilor Mergesort şi Quicksort.
4. Faceţi o concluzie asupra lucrării efectuate.
Codul programului in C++
#include<iostream>
#include<stdlib.h>
#include<vector>
#include<time.h>
using namespace std;
long num,num1, num2, num3, num4, optiune;
long n;
/*void OutVector(vector<long> vec) {
for (vector<long>::const_iterator i = [Link](); i != [Link](); ++i)
cout << *i << " ";
cout << endl;
}*/
void Insertion_Sort(vector<long> tab, long st, long dr)
{
num++;
for (int i = 1; i < n; i++)
{
num++;
long j = dr;
long key = tab[i];
j = i - 1;
for (j = i-1; j>=0 && tab[j] > key;j--)
{
tab[j + 1] = tab[j];
}
tab[j + 1] = key;
num++;
}
}
void QuickSort(vector<long> tab, long st, long dr) {
num3++;
long i = st, j = dr;
long mij = tab[(st + dr) / 2];
while (i <= j) {
while (tab[i] < mij)
i++;
while (tab[j] > mij)
j--;
if (i <= j) {
swap(tab[i], tab[j]);
i++;
j--;
}
};
num4++;
if (st < j)
QuickSort(tab, st, j);
if (i < dr)
QuickSort(tab, i, dr);
}
void Merge(vector<long> tab1, long st, long mij, long dr) {
num2++;
long i, j;
vector<long> tmp(100000);
for (i = mij + 1; i>st; i--)
tmp[i - 1] = tab1[i - 1];
for (j = mij; j<dr; j++)
tmp[dr + mij - j] = tab1[j + 1];
for (long k = st; k <= dr; k++)
if (tmp[j]<tmp[i])
tab1[k] = tmp[j--];
else
tab1[k] = tmp[i++];
}
void MergeSort(vector<long> tab2, long st, long dr) {
num1++;
if (dr <= st) return;
long mij = (dr + st) / 2;
MergeSort(tab2, st, mij);
MergeSort(tab2, mij + 1, dr);
Merge(tab2, st, mij, dr);
}
int main() {
clock_t start_1, finish_1;
clock_t start_2, finish_2;
clock_t start_3, finish_3;
cout << "Introduceti numarul de elemente a vectorului: ";
cin >> n;
while (1) {
vector<long>v1;
vector<long>v2;
vector<long>v3;
vector<long>v4;
if (![Link]()) [Link]();
if (![Link]()) [Link]();
if (![Link]()) [Link]();
if (![Link]()) [Link]();
cout << "[Link] favorabil" << endl
<< "[Link] mediu" << endl
<< "[Link] nefavorabil" << endl
<< "[Link]" << endl;
cout << "Optiune: ";
cin >> optiune;
switch (optiune) {
case 1: {
for (long i = 0; i<n; i++) {
v1.push_back(i+1);
v2.push_back(i+1);
v3.push_back(i+1);
v4.push_back(i + 1);
}
break;
}
case 2: {
for (long i = 0; i<n; i++) {
v1.push_back(rand() % 10001);
v2.push_back(v1[i]);
v3.push_back(v1[i]);
v4.push_back(v1[i]);
}
break;
}
case 3: {
for (long i = 0; i<n; i++) {
v1.push_back(n - i);
v2.push_back(n - i);
v3.push_back(n - i);
v4.push_back(n - i);
}
break;
}
case 0: {
return 0;
}
}
system("cls");
//cout << "\tVECTORUL INITIAL " << endl;
//OutVector(vrt);
cout << endl;
num = 0;
num1 = 0;
num2 = 0;
num3 = 0;
num4 = 0;
cout << endl << "\tINSERTION SORT " << endl;
start_1 = clock();
Insertion_Sort(v4, 0, [Link]() - 1);
finish_1 = clock() - start_1;
//OutVector(xrt);
cout << endl << "Numarul de iteratii: " << num << endl;
cout << "Timpul: " << (float)finish_1 / CLOCKS_PER_SEC << " secunde" <<
endl << endl;
cout <<endl<< "\tMERGE SORT "<<endl;
start_2 = clock();
MergeSort(v2, 0, [Link]() - 1);
finish_2 = clock() - start_2;
//OutVector(srt);
cout <<endl<< "Numarul de iteratii: " << num1+num2 << endl;
cout << "Timpul: " << (float)finish_2 / CLOCKS_PER_SEC << " secunde" <<
endl<<endl;
cout << endl << "\tQUICK SORT " << endl;
start_3 = clock();
QuickSort(v3, 0, [Link]() - 1);
finish_3 = clock() - start_3;
//OutVector(qrt);
cout <<endl<< "Numarul de iteratii: " << num3+num4 << endl;
cout << "Timpul: " << (float)finish_3 / CLOCKS_PER_SEC << " secunde" <<
endl;
[Link]();
[Link]();
system("cls");
}
return 0;}
Repere :
Merge Sort
Sortarea Merge împarte lista de sortare in doua jumătăţi egale, si le pune in
matrice diferite. Fiecare matrice e sortata recursive, mai apoi fiind unite înapoi formînd
lista finala sortata. Ca majoritatea listelor recursive, sortarea merge are un algoritm de
complexitate de O(n log n).
Quick Sort
Metoda quick sort este o metoda divide şi stăpîneşte, care foloseşte funcţii
recursive. Este o metoda mult mai rapida decît sortarea merge, dar metoda de sortare
quick e grea de scris in cod.
Algoritmul recursive consta din 4 paşi:
Daca in matrice are unu sau mai puţine elemente de sortat face return.
Alegem un element ca punct pivot. De obicei este utilizat elementul cel mai din stînga
împărţim matricea in doua-o parte cu elementele mai mari ca pivotul si alta mai mici.
Repetam recursive algoritmul pentru ambele jumătăţi.
Eficienta depinde de care element a fost ales ca pivot. In cel mai rău caz, eficienta quick
sort este de O(n2), cînd cel mai din stînga element a fost ales ca pivot. Pentru ca eficienta
sa fie mai mare alegem la întâmplare un număr numai in cazul cand elementele din
matrice nu sunt aranjate întîmplător. Cînd pivotul e ales la întîmplare, metoda quick sort
are o complexitate algoritmica de O(n log n).
Insertion Sort
Sortarea prin insertie este apoape asemanatoare cu sortarea prin selectie. Tabloul este
delimitata in doua parti - o parte sortata si o parte nesortata. La inceput, partea sortata contine
primul element al tabloului si partea nesortata contine restul tabloului. La fiecare iteratie,
algoritmul ia primul element din partea nesortata si il insereaza in locul potrivit al partii sortate.
Cand partea nesortata nu mai are nici un element, algoritmul se opreste.
Screens
Reprezentarea rezultatelor
Algoritmul
Date de intrare
Cel mai favorabil caz (nr iterații)
Cazul mediu
(nr iterații)
Cel mai nefavorabil caz(nr de iterații)
Insertion Sort
10
19
19
19
100
199
199
199
1000
1999
1999
1999
10000
19999
19999
19999
Mergesort
10
28
28
28
100
298
298
298
1000
2998
2998
2998
10000
29998
29998
29998
Quicksort
10
100
1000
10000
12
126
1022
11808
20
294
3766
17280
18
175
1521
16809
Algoritmul
Date de intrare
Cel mai favorabil caz (timpul)
Cazul mediu
(timpul)
Cel mai nefavorabil caz(timpul)
Insertion Sort
10
0
0
0
100
0
0.009
0.02
1000
0.003
0.337
0.632
10000
0.012
31.681
62.302
Mergesort
10
0.003
0.001
0.002
100
0.026
0.024
0.018
1000
0.127
0.104
0.105
10000
1.154
1.155
1.148
Quicksort
10
100
1000
10000
0.004
0.06
0.001
0.009
0.112
0.001
0.005
0.065
Numarul de itaratii pentru 10,100,1000,10000 elemente
Cazul Favorabil
29998
19999
Iteratii
11808
2998
1999
298
199
126 1022
28
12
19
10 100 1000 10000
Cazul Mediu
29998
19999
Iteratii
17280
2998
1999
1754
16
19
28 298
199
178
10 100 1000 10000
Cazul Nefavorabil
29998
19999
Iteratii
11810
2998
1999
1022
28
19
14 298
199
126
10 100 1000 10000
Timpul de executie pentru 10,100,1000,10000 elemente
Cazul Favorabil
1.15
Timpul
0.13
0.06
0 0.03
0 0 0.01
10 100 1000 10000
Cazul Mediu
31.68
Timpul
1.16
0 0.02
0.01
0 0.34
0.1
0.01 0.11
10 100 1000 10000
Cazul Nefavorabil
62.3
Timpul
0 0.02
0 0.63
0.11
0.01 1.15
0.07
10 100 1000 10000
Concluzie
Efectuind lucrarea data am studiat algoritmii QuickSort și MergeSort si Insertion Sort,
aplicindui în practica și observînd variațiile numărului de iterații la diferite mărimi a tablourilor
și în diferite situații precum tablouri deja sortate, cu numere aleatorii și sortate invers. După
datele obținute și o mulțime de rezultate obținute pot spune ca QuickSort sort este un algoritm
mai rapid decit celelalte. Am efectuat aceste 3 programe în C++ cu algoritmii de sortare quiqsort
şi mergesort si My_sort (Insertion) În urma datelor obtinute ne-am confirma că algoritmul
Quicksort este mai rapid decît Mergesort. Insa nu prea efectiv la numere mari.