Interclasarea a doi vectori sortați
Am învățat să sortăm un vector și să căutăm eficient într-unul sortat. Acum vom vedea cum combinăm doi vectori sortați într-un singur vector, tot sortat.
Această operație se numește interclasare.
Ce este interclasarea?
Având doi vectori sortați crescător, construim un al treilea vector care conține toate elementele din ambii, tot în ordine crescătoare.
Exemplu:
a: 2 5 8 12
b: 3 7 9 15 20
Rezultat: 2 3 5 7 8 9 12 15 20
Ideea algoritmului
Ne imaginăm că avem două cozi de numere, fiecare sortată. La fiecare pas:
- Ne uităm la primul element din fiecare coadă.
- Îl luăm pe cel mai mic și îl punem în rezultat.
- Avansăm în coada din care am luat.
- Repetăm până se termină ambele cozi.
Este ca și cum am amesteca două pachete de cărți deja ordonate - mereu alegem cartea mai mică dintre cele două de deasupra.
Exemplu detaliat pas cu pas
a: 2 5 8 12 (n = 4)
b: 3 7 9 15 20 (m = 5)
c: ? (rezultat, va avea n + m = 9 elemente)
Folosim trei indici: i pentru a,
j pentru b, k pentru
c.
| Pas | i | j | a[i] | b[j] | Alegem | c devine |
|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 2 | 3 | 2 (din a) | 2 |
| 2 | 2 | 1 | 5 | 3 | 3 (din b) | 2 3 |
| 3 | 2 | 2 | 5 | 7 | 5 (din a) | 2 3 5 |
| 4 | 3 | 2 | 8 | 7 | 7 (din b) | 2 3 5 7 |
| 5 | 3 | 3 | 8 | 9 | 8 (din a) | 2 3 5 7 8 |
| 6 | 4 | 3 | 12 | 9 | 9 (din b) | 2 3 5 7 8 9 |
| 7 | 4 | 4 | 12 | 15 | 12 (din a) | 2 3 5 7 8 9 12 |
| - | 5 | 4 | - | 15 | a s-a terminat! | |
| 8 | - | 4 | - | 15 | 15 (din b) | 2 3 5 7 8 9 12 15 |
| 9 | - | 5 | - | 20 | 20 (din b) | 2 3 5 7 8 9 12 15 20 |
Când un vector se termină, copiem ce a rămas din celălalt.
Codul complet
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int a[100001], b[100001], c[200001];
int n, m;
int main()
{
fin >> n;
for (int i = 1; i <= n; i++) {
fin >> a[i];
}
fin >> m;
for (int j = 1; j <= m; j++) {
fin >> b[j];
}
int i = 1, j = 1, k = 0;
while (i <= n && j <= m) {
if (a[i] <= b[j]) {
k++;
c[k] = a[i];
i++;
} else {
k++;
c[k] = b[j];
j++;
}
}
// Copiem ce a rămas din a
while (i <= n) {
k++;
c[k] = a[i];
i++;
}
// Copiem ce a rămas din b
while (j <= m) {
k++;
c[k] = b[j];
j++;
}
// Afișăm rezultatul
for (int p = 1; p <= k; p++) {
fout << c[p] << " ";
}
return 0;
}date.in:
4
2 5 8 12
5
3 7 9 15 20date.out:
2 3 5 7 8 9 12 15 20Structura algoritmului
Algoritmul are trei etape:
Etapa 1: bucla principală
while (i <= n && j <= m) {Cât timp ambii vectori mai au elemente, comparăm și alegem pe cel mai mic.
Etapa 2: restul din
a
while (i <= n) {Dacă b s-a terminat dar a mai are
elemente, le copiem pe toate.
Etapa 3: restul din
b
while (j <= m) {Dacă a s-a terminat dar b mai are
elemente, le copiem pe toate.
Doar una dintre etapele 2 și 3 se execută - cea care mai are elemente rămase. Cealaltă nu face nimic (condiția e falsă de la început).
De ce este eficientă?
Interclasarea parcurge fiecare element o singură
dată. Dacă a are n elemente
și b are m elemente, algoritmul face
exact n + m pași.
| n | m | Pași interclasare | Pași dacă am pune totul într-un vector și am sorta |
|---|---|---|---|
| 100 | 100 | 200 | ~40.000 (sortare) |
| 10.000 | 10.000 | 20.000 | ~400.000.000 (sortare) |
Interclasarea este mult mai rapidă decât sortarea, pentru că profită de faptul că vectorii sunt deja sortați.
Interclasare fără vector suplimentar
Dacă nu avem nevoie de vectorul c și vrem doar
să afișăm rezultatul, putem scrie direct:
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int a[100001], b[100001];
int n, m;
int main()
{
fin >> n;
for (int i = 1; i <= n; i++) {
fin >> a[i];
}
fin >> m;
for (int j = 1; j <= m; j++) {
fin >> b[j];
}
int i = 1, j = 1;
while (i <= n && j <= m) {
if (a[i] <= b[j]) {
fout << a[i] << " ";
i++;
} else {
fout << b[j] << " ";
j++;
}
}
while (i <= n) {
fout << a[i] << " ";
i++;
}
while (j <= m) {
fout << b[j] << " ";
j++;
}
return 0;
}Interclasare cu eliminarea duplicatelor
Dacă vrem ca rezultatul să conțină fiecare valoare o singură dată:
int i = 1, j = 1, k = 0;
while (i <= n && j <= m) {
if (a[i] < b[j]) {
k++;
c[k] = a[i];
i++;
} else if (a[i] > b[j]) {
k++;
c[k] = b[j];
j++;
} else {
// Sunt egale - luăm o singură dată
k++;
c[k] = a[i];
i++;
j++;
}
}
while (i <= n) {
k++;
c[k] = a[i];
i++;
}
while (j <= m) {
k++;
c[k] = b[j];
j++;
}Exemplu:
a: 1 3 5 7
b: 2 3 5 8
Rezultat (fără duplicate): 1 2 3 5 7 8
Când a[i] == b[j], luăm valoarea o singură dată
și avansăm în ambii vectori.
Reuniunea și intersecția
Interclasarea stă la baza a două operații importante:
Reuniunea (toate elementele, fără duplicate)
Exact varianta de mai sus - luăm fiecare valoare o singură dată.
Intersecția (doar elementele comune)
Afișăm valoarea doar când a[i] == b[j]:
int i = 1, j = 1;
while (i <= n && j <= m) {
if (a[i] < b[j]) {
i++;
} else if (a[i] > b[j]) {
j++;
} else {
fout << a[i] << " ";
i++;
j++;
}
}Exemplu:
a: 1 3 5 7 9
b: 2 3 5 8 9
Intersecție: 3 5 9
De ce funcționează intersecția?
Dacă a[i] < b[j], atunci a[i] nu
poate fi egal cu niciun element rămas din b (pentru
că b e sortat crescător, deci toate elementele
rămase din b sunt
>= b[j] > a[i]).
Deci putem sări peste a[i] fără să pierdem
nimic.
Aceeași logică în oglindă pentru
b[j] < a[i].
Greșeli frecvente
1. Uitarea copierii restului
while (i <= n && j <= m) {
// ... comparare și copiere
}
// GREȘIT: lipsesc buclele pentru restul din a și b!Dacă un vector se termină înaintea celuilalt, elementele rămase trebuie copiate.
2. Condiția <=
vs < la comparare
if (a[i] <= b[j]) { // CORECT: elementele egale merg la a (sau b, nu contează)
if (a[i] < b[j]) { // OK și asta, dar trebuie tratat cazul de egalitateCu <=, elementele egale din a
vin primele. Cu <, vin cele din b.
Ambele sunt corecte.
3. Vectorii nu sunt sortați
Interclasarea funcționează doar dacă ambii vectori sunt sortați. Dacă nu sunt, trebuie sortați mai întâi.
4. Vectorul c prea
mic
Vectorul rezultat are n + m elemente. Dacă
n și m pot fi câte
100.000, atunci c trebuie declarat cu
200.001 poziții.
Ce să reții
- Interclasarea combină doi vectori sortați într-unul singur, tot sortat.
- Parcurgem ambii vectori simultan, alegând mereu elementul mai mic.
- Când un vector se termină, copiem restul din celălalt.
- Algoritmul face exact
n + mpași - foarte eficient. - Variante utile: fără duplicat (reuniune), doar comune (intersecție).
- Funcționează doar pe vectori sortați.