Ce vei învăța astăzi?
După această lecție vei înțelege principiul sortării prin interclasare: împărțim vectorul în două jumătăți, sortăm fiecare jumătate recursiv (repetând operația pe jumătăți tot mai mici) și apoi interclasăm cele două jumătăți sortate.
Ideea metodei
Definiție: Sortarea prin interclasare (merge sort) descompune vectorul în două jumătăți aproximativ egale, sortează fiecare jumătate prin apel recursiv, apoi interclasează cele două jumătăți sortate, obținând un vector complet sortat. Un vector cu un singur element este deja sortat (cazul de bază).
Analogie: îmbinarea a două grămezi de cartonașe ordonate
Ai două grămezi de cartonașe, fiecare ordonată crescător. Le îmbini ținând câte un deget la începutul fiecărei grămezi: compari cele două cartonașe, îl iei pe cel mai mic și înaintezi degetul din grămada respectivă. Repetând, obții o singură grămadă ordonată.
- Divizare: vectorul se împarte la jumătăți până la un element
- Rezolvare: un singur element este deja sortat
- Combinare: interclasăm cele două jumătăți sortate
Exemplu: interclasarea a două secvențe
Secvențele sortate [1, 3, 5] și [2, 4] se interclasează astfel: comparăm 1 cu 2 (luăm 1), 3 cu 2 (luăm 2), 3 cu 4 (luăm 3), 5 cu 4 (luăm 4), apoi rămâne 5. Rezultatul: [1, 2, 3, 4, 5].
Funcția de interclasare
Funcția interclaseaza(st, mij, dr) folosește doi indici (i pentru prima jumătate, j pentru a doua) și un vector auxiliar temp. La fiecare pas copiem elementul mai mic, apoi copiem elementele rămase.
interclasarea secvențelor [1, 3, 5] și [2, 4]
i și j parcurg secvențeleObservă: după ce o secvență se epuizează, copiem direct elementele rămase din cealaltă. Rezultatul este întotdeauna o secvență sortată.
Programul complet: merge sort
#include <iostream>
using namespace std;
int v[1001], temp[1001];
void interclaseaza(int st, int mij, int dr) {
int i = st, j = mij + 1, k = st;
while (i <= mij && j <= dr) {
if (v[i] < v[j]) {
temp[k++] = v[i++];
} else {
temp[k++] = v[j++];
}
}
while (i <= mij) {
temp[k++] = v[i++];
}
while (j <= dr) {
temp[k++] = v[j++];
}
for (int p = st; p <= dr; p++) {
v[p] = temp[p];
}
}
void mergeSort(int st, int dr) {
if (st < dr) {
int mij = (st + dr) / 2;
mergeSort(st, mij);
mergeSort(mij + 1, dr);
interclaseaza(st, mij, dr);
}
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> v[i];
}
mergeSort(1, n);
for (int i = 1; i <= n; i++) {
cout << v[i] << " ";
}
return 0;
}
Ce învățăm: funcțiile se definesc înainte de main(); temp este vector auxiliar global; cazul de bază este st == dr (un element, deja sortat).
Exercițiu de completare
Completează descrierea sortării prin interclasare:
Sortarea prin interclasare împarte vectorul în două ______; sortează fiecare jumătate prin ______; apoi ______ cele două secvențe, obținând un vector ______.
Bancă de cuvinte:
Exercițiu de completare: codul interclasării
Completează descrierea funcției de interclasare:
În funcția de interclasare: indicele i pornește din st, iar j din mij + ______; copiem în ______ elementul mai ______ dintre v[i] și v[j].
Bancă de cuvinte:
Test de înțelegere
Test rapid - merge sort:
1. Un vector cu un singur element este considerat:
2. Interclasarea a două secvențe sortate are complexitatea:
3. Complexitatea totală a sortării prin interclasare este:
temp trebuie copiat înapoi în v pe tot intervalul [st, dr]; fără această copiere, sortarea „se pierde" între apelurile recursive.Aprofundare
De ce interclasarea este liniară: fiecare element este comparat de cel mult o dată pe fiecare nivel al recursivității, iar numărul de niveluri este log₂(n). De aici rezultă complexitatea totală O(n log n), cu mult mai bună decât sortarea prin selecție sau inserție, de complexitate O(n).
Stabilitatea algoritmului: interclasarea compară cu < și copiază mai întâi elementul din jumătatea stângă la egalitate, deci elementele egale își păstrează ordinea relativă. Un algoritm cu această proprietate se numește stabil, ceea ce contează la sortarea după mai multe criterii (de exemplu, mai întâi după medie, apoi după nume).
Memoria suplimentară: merge sort folosește un vector auxiliar temp de aceeași dimensiune cu v, deci complexitatea de spațiu este O(n). Sortarea clasică prin selecție sortează „în loc" (in-place), dar plătește cu timp O(n); este compromisul clasic timp-memorie.
Interclasarea fără vector auxiliar: există variante care interclasează prin rotații de subsecvențe, dar sunt mult mai complicate. La Bacalaureat se folosește întotdeauna vectorul auxiliar temp, urmat de copierea înapoi în v.
Exemplu din viața reală: o firmă de curierat primește dimineața 1000 de colete cu adrese, deja sortate alfabetic pe fiecare cartier. Pentru a obține lista completă, angajații interclasează două câte două listele vecine, exact ca în pasul de combinare al merge sort. Interclasarea a două liste deja sortate se face într-o singură trecere.
PbInfo - Sortare (merge sort)
Enunț:
Cerință: Se citește un număr natural n și apoi n numere întregi. Să se afișeze numerele în ordine crescătoare, folosind sortarea prin interclasare.
Date de intrare:
Programul citește de la tastatură numărul n, apoi cele n numere.
Date de ieșire:
Programul va afișa pe ecran numerele sortate crescător, separate prin spații.
Restricții și precizări:
1 ≤ n ≤ 1000; elementele sunt numere întregi; se cere obligatoriu sortare prin interclasare.
Exemplu:
Intrare:
6 5 2 8 1 9 3
Ieșire:
1 2 3 5 8 9
Cum gândim soluția:
- Declarăm vectorul
vși auxiliarultempglobale - Scriem
interclaseaza(st, mij, dr)cu cei doi indici - Scriem
mergeSort(st, dr): dacăst < dr, împărțim și apelăm recursiv - Afișăm vectorul sortat
Scrie soluția în C++:
Soluție corectă:
#include <iostream>
using namespace std;
int v[1001], temp[1001];
void interclaseaza(int st, int mij, int dr) {
int i = st, j = mij + 1, k = st;
while (i <= mij && j <= dr) {
if (v[i] < v[j]) {
temp[k++] = v[i++];
} else {
temp[k++] = v[j++];
}
}
while (i <= mij) {
temp[k++] = v[i++];
}
while (j <= dr) {
temp[k++] = v[j++];
}
for (int p = st; p <= dr; p++) {
v[p] = temp[p];
}
}
void mergeSort(int st, int dr) {
if (st < dr) {
int mij = (st + dr) / 2;
mergeSort(st, mij);
mergeSort(mij + 1, dr);
interclaseaza(st, mij, dr);
}
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> v[i];
}
mergeSort(1, n);
for (int i = 1; i <= n; i++) {
cout << v[i] << " ";
}
return 0;
}
Ce învățăm: interclasarea se face în vectorul auxiliar temp, care este apoi copiat înapoi în v pe intervalul [st, dr].
PbInfo - Interclasare
Enunț:
Cerință: Se citesc două șiruri de numere, fiecare ordonat crescător. Să se afișeze, în ordine crescătoare, toate elementele celor două șiruri, prin interclasare.
Date de intrare:
Programul citește n și cele n elemente ale primului șir, apoi m și cele m elemente ale celui de-al doilea șir.
Date de ieșire:
Programul va afișa pe ecran elementele interclasate, în ordine crescătoare, separate prin spații.
Restricții și precizări:
1 ≤ n, m ≤ 1000; ambele șiruri sunt sortate crescător.
Exemplu:
Intrare:
4 1 3 5 7 3 2 4 6
Ieșire:
1 2 3 4 5 6 7
Cum gândim soluția:
- Folosim doi indici
ișijpentru cele două șiruri - Copiem elementul mai mic în
cși înaintăm - Copiem elementele rămase dintr-un șir atunci când celălalt se epuizează
- Afișăm vectorul
c
Scrie soluția în C++:
Soluție corectă:
#include <iostream>
using namespace std;
int a[1001], b[1001], c[2002];
int main() {
int n, m;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> b[i];
}
int i = 1, j = 1, k = 0;
while (i <= n && j <= m) {
if (a[i] < b[j]) {
c[++k] = a[i++];
} else {
c[++k] = b[j++];
}
}
while (i <= n) {
c[++k] = a[i++];
}
while (j <= m) {
c[++k] = b[j++];
}
for (int p = 1; p <= k; p++) {
cout << c[p] << " ";
}
return 0;
}
Ce învățăm: interclasarea este operația de combinare din merge sort; aici o folosim direct pe două șiruri deja sortate.
Exercițiul Practic: variante de interclasare
Sarcini de lucru
Rezolvă sarcinile de mai jos pentru a exersa merge sort și interclasarea
Sarcina 1: Merge sort descrescător
Cerință: Modifică programul de sortare prin interclasare astfel încât vectorul să fie sortat descrescător. Scrie programul complet.
Soluție pentru Sarcina 1:
Sugestie: se modifică doar comparația din interclasare: copiem elementul mai mare în locul celui mai mic.
#include <iostream>
using namespace std;
int v[1001], temp[1001];
void interclaseaza(int st, int mij, int dr) {
int i = st, j = mij + 1, k = st;
while (i <= mij && j <= dr) {
if (v[i] > v[j]) {
temp[k++] = v[i++];
} else {
temp[k++] = v[j++];
}
}
while (i <= mij) {
temp[k++] = v[i++];
}
while (j <= dr) {
temp[k++] = v[j++];
}
for (int p = st; p <= dr; p++) {
v[p] = temp[p];
}
}
void mergeSort(int st, int dr) {
if (st < dr) {
int mij = (st + dr) / 2;
mergeSort(st, mij);
mergeSort(mij + 1, dr);
interclaseaza(st, mij, dr);
}
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> v[i];
}
mergeSort(1, n);
for (int i = 1; i <= n; i++) {
cout << v[i] << " ";
}
return 0;
}
Verifică: pentru v = 5 2 8 1, rezultatul descrescător este 8 5 2 1. Diferența față de varianta crescătoare este doar operatorul > din interclasare.
Sarcina 2: Interclasare fără duplicate
Cerință: Se citesc două șiruri sortate crescător (pot avea elemente comune). Afișează elementele interclasate fără valori duplicate.
Soluție pentru Sarcina 2:
#include <iostream>
using namespace std;
int a[1001], b[1001], c[2002];
int main() {
int n, m;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> b[i];
}
int i = 1, j = 1, k = 0;
while (i <= n && j <= m) {
if (a[i] < b[j]) {
c[++k] = a[i++];
} else {
if (a[i] > b[j]) {
c[++k] = b[j++];
} else {
c[++k] = a[i++];
j++;
}
}
}
while (i <= n) {
c[++k] = a[i++];
}
while (j <= m) {
c[++k] = b[j++];
}
for (int p = 1; p <= k; p++) {
cout << c[p] << " ";
}
return 0;
}
Verifică: pentru a = 1 2 3 și b = 2 4, rezultatul este 1 2 3 4; la egalitate copiem o singură dată valoarea și înaintăm ambii indici.
Scrie pe caiet - Sortarea prin interclasare (merge sort)
Titlul lecției
- Ora 3: Sortarea prin interclasare (merge sort)
Definiția lecției
Merge sort este un algoritm de sortare de tip Divide et impera, cu complexitatea O(n log n). Vectorul se împarte la jumătate până rămân elemente individuale (un singur element este deja sortat), apoi, la întoarcere, fiecare pereche de jumătăți sortate se interclasează într-o singură secvență ordonată. Interclasarea folosește doi indici (i în jumătatea stângă, j în cea dreaptă) și un vector auxiliar temp, copiind la fiecare pas elementul mai mic, apoi elementele rămase dintr-o jumătate epuizată. La final, temp se copiază înapoi în vectorul inițial. Din această lecție rămân în caiet funcția interclaseaza(st, mij, dr) completă, funcția mergeSort(st, dr) și apelul din main().
Definiții esențiale
- Sortarea prin interclasare (merge sort): algoritm de sortare de tip Divide et impera care împarte vectorul în două jumătăți, sortează recursiv fiecare jumătate, apoi le interclasează într-o singură secvență sortată.
- Interclasare: operația de îmbinare a două secvențe deja sortate, alegând la fiecare pas cel mai mic element dintre „capetele" celor două secvențe.
- Vector auxiliar: tabloul
tempîn care se construiește secvența interclasată, apoi se copiază înapoi înv. - Complexitate: timp
O(n log n), spațiuO(n).
Algoritmul cheie - pseudocod
mergeSort(st, dr): dacăst < dr, calculeazămij, apelează recursivmergeSort(st, mij)șimergeSort(mij + 1, dr), apoiinterclaseaza(st, mij, dr).interclaseaza(st, mij, dr): cu trei indici (iîn stânga,jîn dreapta,kîntemp), copiază elementul mai mic; la final copiază restul și readucetempînv.
Algoritmul cheie - cod C++
- Declară global
int v[1001], temp[1001];. - Implementează
void interclaseaza(int st, int mij, int dr)cu trei buclewhile(comparație, restul stâng, restul drept) și copierea finală cufor. - Implementează
void mergeSort(int st, int dr)cuif (st < dr)ca pas de divizare. - În
main(): citește, apeleazămergeSort(1, n)și afișează vectorul sortat.
Greșeli comune de evitat
- Condiția
st <= drîn loc dest < drla mergeSort provoacă recursivitate infinită. - Uitarea ultimelor două bucle
whilecare copiază elementele rămase într-o secvență epuizată. - Uitarea copierii finale din
tempînv: fără ea, vectorul nu rămâne sortat între niveluri. - Amestecarea indicilor (
kfolosit caisauj) în interclasare duce la valori greșite.
Sfaturi practice și trucuri
- Testează interclasarea separat, pe două secvențe deja sortate, înainte de a testa întreaga sortare.
- Urmărește pașii pe un vector cu 4 elemente și desenează arborele de apeluri pentru a înțelege ordinea.
- Reține: complexitatea
O(n log n)se justifică la oral spunând „n elemente pe fiecare dintre cele log₂(n) niveluri". - Pentru n mare (100000 de elemente), merge sort rămâne rapid, în timp ce sortările pătratice devin impracticabile.
Exemplu practic
Secretariatul școlii are două liste de elevi, fiecare sortată alfabetic, pe care trebuie să le îmbine într-o singură listă ordonată. Procedeul manual — compararea numelor de la începutul fiecărei liste și mutarea celui mai mic — este exact interclasarea, implementată în funcția de mai jos (cu tablourile globale v și temp):
void interclaseaza(int st, int mij, int dr) {
int i = st, j = mij + 1, k = st;
while (i <= mij && j <= dr) {
if (v[i] <= v[j]) {
temp[k++] = v[i++];
} else {
temp[k++] = v[j++];
}
}
while (i <= mij) {
temp[k++] = v[i++];
}
while (j <= dr) {
temp[k++] = v[j++];
}
for (int p = st; p <= dr; p++) {
v[p] = temp[p];
}
}