Ce vei învăța astăzi?
După această lecție vei aplica interclasarea în probleme practice: fuzionarea a două șiruri cu eliminarea dublurilor și verificarea dacă o listă este sortată.
Fuzionarea a două șiruri sortate
Definiție: Fuzionarea este interclasarea care elimină dublurile: când cele două elemente curente sunt egale, copiem valoarea o singură dată și avansăm ambii indici.
#include <iostream>
using namespace std;
int main() {
int n, m, a[100005], b[100005], c[200005], i, j, k = 0;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> m;
for (j = 1; j <= m; j++)
cin >> b[j];
i = 1;
j = 1;
while (i <= n && j <= m) {
if (a[i] == b[j]) {
k++;
c[k] = a[i];
i++;
j++;
} else if (a[i] < b[j]) {
k++;
c[k] = a[i];
i++;
} else {
k++;
c[k] = b[j];
j++;
}
}
while (i <= n) {
k++;
c[k] = a[i];
i++;
}
while (j <= m) {
k++;
c[k] = b[j];
j++;
}
for (i = 1; i <= k; i++)
cout << c[i] << " ";
return 0;
}
Observă: la egalitate avansăm ambii indici și copiem o singură dată, eliminând astfel dublurile din rezultat.
Verificarea unei liste sortate
O listă este sortată crescător (ordonată de la cel mai mic la cel mai mare element) dacă fiecare element este mai mic sau egal cu următorul: a[i] <= a[i+1] pentru orice i. Dacă găsim a[i] > a[i+1], lista nu este sortată.
#include <iostream>
using namespace std;
int main() {
int n, a[100005], i;
bool sortat = true;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
for (i = 1; i < n; i++)
if (a[i] > a[i + 1])
sortat = false;
if (sortat)
cout << "DA";
else
cout << "NU";
return 0;
}
Observă: parcurgem o singură dată lista, deci complexitatea verificării este O(n). O listă cu un singur element este mereu sortată.
Analiza eficienței interclasării
| Algoritm | Date de intrare | Complexitate | Când îl folosim |
|---|---|---|---|
| Sortare prin selecție | o listă oarecare | O(n^2) | lista nu este sortată |
| Interclasare | două liste sortate | O(n + m) | liste deja sortate |
| Fuzionare | două liste sortate | O(n + m) | avem nevoie de elemente distincte |
De ce este interclasarea eficientă?
Dacă am avea două liste sortate cu câte 100000 de elemente, sortarea lor din nou ar necesita miliarde de operații. Interclasarea le combină în doar 200000 de pași, păstrând ordinea sortată.
#include <iostream>
using namespace std;
int main() {
int n, m, a[100005], b[100005], i, j, k = 0;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> m;
for (j = 1; j <= m; j++)
cin >> b[j];
i = 1;
j = 1;
while (i <= n && j <= m) {
if (a[i] == b[j]) {
k++;
i++;
j++;
} else if (a[i] < b[j]) {
k++;
i++;
} else {
k++;
j++;
}
}
while (i <= n) {
k++;
i++;
}
while (j <= m) {
k++;
j++;
}
cout << k;
return 0;
}
Exercițiu de completare
Completează enunțul cu cuvintele potrivite:
Fuzionarea elimină ______: când a[i] == b[j], copiem o singură dată și avansăm ______ indici.
Bancă de cuvinte:
Exercițiu de completare 2
Completează afirmațiile despre verificarea sortării:
O listă este sortată crescător dacă fiecare element este ______ cu următorul. Dacă întâlnim a[i] > a[i+1], atunci lista nu este ______.
Bancă de cuvinte:
Test de înțelegere
Test rapid - aplicații ale interclasării:
1. O listă cu un singur element este:
2. La fuzionare, când a[i] == b[j]:
3. Verificarea sortării unei liste cu n elemente are complexitatea:
a[i] == b[j] copiezi și avansezi un singur indice, valoarea apare de două ori în rezultat, iar enunțul cu „distincte" nu mai este respectat.Aprofundare
Fuzionare vs. interclasare - diferența cheie: la interclasare, elementele egale din cele două liste apar de două ori în rezultat (o dată din fiecare listă), pentru că ele sunt elemente distincte ale celor două mulțimi. La fuzionare, tratăm valorile ca elemente ale unei mulțimi, deci o valoare comună apare o singură dată. Alegerea între cele două se face citind cu atenție enunțul: „elementele celor două șiruri" înseamnă interclasare, „elementele distincte" înseamnă fuzionare.
Verificarea sortării cu un singur pas și complexitate O(n): este suficient să comparăm fiecare element cu următorul (a[i] <= a[i + 1]), nu cu toate elementele de după el. O greșeală frecventă este compararea fiecărui element cu toate celelalte (O(n^2)), inutilă și lentă. O listă cu 0 sau 1 element este întotdeauna sortată - verifică acest caz limită la problemele care nu impun n >= 2.
Numărarea elementelor distincte fără tablou rezultat: dacă problema cere doar câte valori distincte există în fuziunea a două liste, putem număra direct cu k, fără să construim tabloul c. Fiecare „copiere" devine un simplu k++. Astfel economisim memorie, iar codul rămâne identic ca logică - truc folosit des în problemele de eficiență.
Aplicație reală - unirea listelor de membri: două cluburi de lectură publică fiecare lista membrilor, sortată alfabetic. Pentru a afla cine apare în ambele liste (intersecție - elementele comune celor două liste) sau câți membri unici există în total (reuniune - toate elementele distincte ale ambelor liste), fuzionarea rezolvă problema într-o singură parcurgere a ambelor liste - exact logica din spatele uneltelor moderne de sincronizare a datelor.
PbInfo - Verificare ordonare
Enunț:
Cerință: Se dă un șir de n numere naturale. Să se verifice dacă șirul este sortat crescător și să se afișeze DA sau NU.
Date de intrare:
Programul citește de la tastatură numărul n, apoi cele n valori.
Date de ieșire:
Programul va afișa pe ecran DA dacă șirul este sortat crescător, sau NU în caz contrar.
Restricții și precizări:
1 <= n <= 100000, valorile sunt numere naturale.
Exemplu:
Intrare:
5 1 3 3 7 9
Ieșire:
DA
Cum gândim soluția:
- Parcurgem șirul de la 1 la n - 1
- Dacă găsim
a[i] > a[i + 1], șirul nu este sortat - Folosim o variabilă booleană pentru a marca rezultatul
- Afișăm
DAsauNU
Scrie soluția în C++:
Soluție corectă:
#include <iostream>
using namespace std;
int main() {
int n, a[100005], i;
bool sortat = true;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
for (i = 1; i < n; i++)
if (a[i] > a[i + 1])
sortat = false;
if (sortat)
cout << "DA";
else
cout << "NU";
return 0;
}
Ce învățăm: verificarea sortării presupune o singură parcurgere; elementele egale sunt permise, deci folosim condiția a[i] > a[i + 1].
PbInfo - Fuzionare
Enunț:
Cerință: Se dau două șiruri de numere naturale, fiecare sortat crescător, care pot conține valori repetate. Să se afișeze, în ordine crescătoare, valorile distincte care apar în cel puțin unul dintre șiruri (fuzionarea fără dubluri).
Date de intrare:
Programul citește de la tastatură numărul n, apoi cele n valori ale primului șir, numărul m, apoi cele m valori ale celui de-al doilea șir.
Date de ieșire:
Programul va afișa valorile distincte, în ordine crescătoare, separate prin spații.
Restricții și precizări:
1 <= n, m <= 100000, valorile sunt numere naturale.
Exemplu:
Intrare:
4 1 2 2 5 5 2 3 4 4 6
Ieșire:
1 2 3 4 5 6
Cum gândim soluția:
- Interclasăm cele două șiruri, dar copiem fiecare valoare o singură dată
- La egalitate, copiem o dată și avansăm ambii indici
- La valori diferite, copiem valoarea mai mică și avansăm indicele corespunzător
- Afișăm tabloul rezultat
Scrie soluția în C++:
Soluție corectă:
#include <iostream>
using namespace std;
int main() {
int n, m, a[100005], b[100005], i, j;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> m;
for (j = 1; j <= m; j++)
cin >> b[j];
i = 1;
j = 1;
while (i <= n && j <= m) {
if (a[i] == b[j]) {
cout << a[i] << " ";
i++;
j++;
} else if (a[i] < b[j]) {
cout << a[i] << " ";
i++;
} else {
cout << b[j] << " ";
j++;
}
}
while (i <= n) {
cout << a[i] << " ";
i++;
}
while (j <= m) {
cout << b[j] << " ";
j++;
}
return 0;
}
Ce învățăm: fuzionarea afișează fiecare valoare distinctă o singură dată, iar restul valorilor rămase sunt deja distincte în interiorul fiecărui șir.
Exercițiul Practic: Aplicații practice ale interclasării
Sarcini de lucru
Completează următoarele sarcini pentru a-ți consolida aplicațiile interclasării
Sarcina 1: Verifică ordonarea descrescătoare
Cerință: Scrie un program C++ care citește un șir de n numere și afișează DA dacă este sortat descrescător sau NU în caz contrar.
Soluție pentru Sarcina 1:
#include <iostream>
using namespace std;
int main() {
int n, a[100005], i;
bool sortat = true;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
for (i = 1; i < n; i++)
if (a[i] < a[i + 1])
sortat = false;
if (sortat)
cout << "DA";
else
cout << "NU";
return 0;
}
Verifică: pentru ordonarea descrescătoare, lista este sortată cât timp a[i] >= a[i + 1]; o încălcare are loc când a[i] < a[i + 1].
Sarcina 2: Numără elementele distincte din fuzionare
Cerință: Scrie un program C++ care citește două șiruri sortate și afișează câte valori distincte rezultă în urma fuzionării lor.
Soluție pentru Sarcina 2:
#include <iostream>
using namespace std;
int main() {
int n, m, a[100005], b[100005], i, j, k = 0;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> m;
for (j = 1; j <= m; j++)
cin >> b[j];
i = 1;
j = 1;
while (i <= n && j <= m) {
if (a[i] == b[j]) {
k++;
i++;
j++;
} else if (a[i] < b[j]) {
k++;
i++;
} else {
k++;
j++;
}
}
while (i <= n) {
k++;
i++;
}
while (j <= m) {
k++;
j++;
}
cout << k;
return 0;
}
Verifică: contorul k crește cu 1 pentru fiecare valoare distinctă, fie că vine din egalitate, fie din diferență.
Scrie pe caiet - De reținut
Titlul lecției
Ora 7: Interclasarea - aplicații
Definiția lecției
Fuzionarea este interclasarea care elimină dublurile: atunci când a[i] == b[j], copiem valoarea o singură dată în tabloul rezultat și avansăm ambii indici, i și j. Când a[i] < b[j], copiem din primul tablou, iar altfel copiem din al doilea; la final, copiem restul elementelor din lista rămasă. O listă este sortată crescător dacă fiecare element este mai mic sau egal cu următorul, adică a[i] <= a[i + 1] pentru orice i de la 1 la n - 1. Verificarea sortării se face într-o singură parcurgere și are complexitatea O(n). Atât interclasarea, cât și fuzionarea au complexitatea O(n + m). Alegerea dintre cele două se face după enunț: „elementele celor două șiruri" înseamnă interclasare, iar „elementele distincte" înseamnă fuzionare.
Definiții esențiale
- Fuzionarea este interclasarea care elimină dublurile: când
a[i] == b[j], copiem valoarea o singură dată și avansăm ambii indici. - O listă este sortată crescător dacă
a[i] <= a[i + 1]pentru oriceide la 1 lan - 1. - Verificarea sortării are complexitatea O(n) - o singură parcurgere.
- Interclasarea și fuzionarea au complexitatea O(n + m).
Algoritmul cheie - pseudocod
Algoritmul cheie - cod C++
i = 1;
j = 1;
k = 0;
while (i <= n && j <= m) {
k++;
if (a[i] == b[j]) {
c[k] = a[i];
i++;
j++;
} else if (a[i] < b[j]) {
c[k] = a[i];
i++;
} else {
c[k] = b[j];
j++;
}
}
while (i <= n) {
k++;
c[k] = a[i];
i++;
}
while (j <= m) {
k++;
c[k] = b[j];
j++;
}
Greșeli comune de evitare
- Tratarea fuzionării ca interclasare: la egalitate, valoarea ar apărea de două ori în rezultat.
- Verificarea sortării în O(n^2): inutil; o singură parcurgere este suficientă.
- Uitarea avansării ambilor indici la egalitate: provoacă buclă infinită.
Exemplu practic
Două cluburi de lectură publică fiecare lista membrilor, sortată alfabetic. Pentru a tipări lista completă a membrilor unici (o persoană care este în ambele cluburi apare o singură dată), se folosește fuzionarea. Programul de mai jos interclasează două liste sortate de numere de ordine, eliminând dublurile.
#include <iostream>
using namespace std;
int main() {
int n, m, a[100005], b[100005], c[200005], i, j, k = 0;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> m;
for (j = 1; j <= m; j++)
cin >> b[j];
i = 1; j = 1;
while (i <= n && j <= m) {
if (a[i] == b[j]) {
k++;
c[k] = a[i];
i++;
j++;
} else if (a[i] < b[j]) {
k++;
c[k] = a[i];
i++;
} else {
k++;
c[k] = b[j];
j++;
}
}
while (i <= n) {
k++;
c[k] = a[i];
i++;
}
while (j <= m) {
k++;
c[k] = b[j];
j++;
}
for (i = 1; i <= k; i++)
cout << c[i] << " ";
return 0;
}
Pentru listele 1 2 4 și 2 3 4, programul afișează 1 2 3 4 - valoarea 2, prezentă în ambele liste, apare o singură dată.
Sfaturi practice și trucuri
- Dacă se cere doar numărul de valori distincte, numără cu
kfără a construi tabloulc. - O listă cu un singur element este întotdeauna sortată - tratează cazul limită.
- Citește enunțul: „elementele celor două șiruri" = interclasare, „elementele distincte" = fuzionare.