Școala Româno-Britanică
Modulul 3 Clasa a X-a | C++

Ora 7: Interclasarea - aplicații

Ce vei învăța astăzi: Fuzionarea a două șiruri sortate, verificarea unei liste sortate

Rezultat practic: Analizezi și aplici interclasarea în probleme practice, inclusiv fuzionarea (interclasarea care elimină valorile repetate) cu eliminarea dublurilor (valorile care apar în ambele șiruri) și verificarea sortării (Competența 4.2).

Competența 4.2 Săptămâna 15

Înainte de a începe

De ce contează? Fuzionarea cu eliminarea dublurilor este varianta cerută de enunțurile cu „valori distincte" - prima distincție pe care o verifică corectorul la barem, pentru că o interclasare simplă ar păstra fiecare valoare de două ori.

Ce știi deja?

La ora 6 ai interclasat două liste cu indicii i, j și k. Acum schimbi doar tratarea cazului de egalitate și adaugi verificarea sortării - o adaptare mică, dar cu consecințe mari asupra rezultatului.

Ideea-cheie: La egalitate (a[i] == b[j]) copiem valoarea o singură dată și avansăm ambii indici; în toate celelalte cazuri fuzionarea funcționează exact ca interclasarea de la ora 6.
SINTEZĂ TEORETICĂ ȘI EXEMPLE GHIDATE

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 șirurilor cu eliminarea dublurilor
Verificarea dacă o listă este sortată
Analiza eficienței interclasării
Probleme practice cu interclasarea

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.

Algoritmul FuzionareFărăDubluri(a, b, n, m): // pasul 1: inițializarea indicilor i <- 1, j <- 1, k <- 0 // pasul 2: parcurgem ambele liste simultan cât timp i <= n și j <= m execută dacă a[i] = b[j] atunci // pasul 3: valoare comună - copiem o singură dată k <- k + 1 c[k] <- a[i] i <- i + 1 j <- j + 1 altfel dacă a[i] < b[j] atunci k <- k + 1 c[k] <- a[i] i <- i + 1 altfel k <- k + 1 c[k] <- b[j] j <- j + 1 sfârșit dacă sfârșit cât timp // pasul 4: copiem restul din listele rămase cât timp i <= n execută k <- k + 1 c[k] <- a[i] i <- i + 1 sfârșit cât timp cât timp j <= m execută k <- k + 1 c[k] <- b[j] j <- j + 1 sfârșit cât timp // pasul 5: afișăm rezultatul pentru i = 1, k execută scrie c[i], spațiu sfârșit pentru
Program complet: fuzionarea lui a și b, fără dubluri
#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ă.

Program complet: verificarea sortării
#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ă.

Program complet: numărul de elemente distincte din fuzionare
#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:
dublurile ambii unul elementele

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:
mai mic sau egal sortată mai mare aleatoare

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:

Ai grijă la: tratarea egalității ca un caz obișnuit de interclasare: dacă la 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.

APLICARE ȘI REZOLVARE DE PROBLEME

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:
  1. Parcurgem șirul de la 1 la n - 1
  2. Dacă găsim a[i] > a[i + 1], șirul nu este sortat
  3. Folosim o variabilă booleană pentru a marca rezultatul
  4. Afișăm DA sau NU
Scrie soluția în C++:
Soluție corectă:
Soluția completă
#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:
  1. Interclasăm cele două șiruri, dar copiem fiecare valoare o singură dată
  2. La egalitate, copiem o dată și avansăm ambii indici
  3. La valori diferite, copiem valoarea mai mică și avansăm indicele corespunzător
  4. Afișăm tabloul rezultat
Scrie soluția în C++:
Soluție corectă:
Soluția completă
#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ȚII PRACTICE

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 orice i de la 1 la n - 1.
  • Verificarea sortării are complexitatea O(n) - o singură parcurgere.
  • Interclasarea și fuzionarea au complexitatea O(n + m).

Algoritmul cheie - pseudocod

Algoritmul FuzionareFărăDubluri(a, b, n, m): i <- 1, j <- 1, k <- 0 cât timp i <= n și j <= m execută dacă a[i] = b[j] atunci k <- k + 1 c[k] <- a[i] i <- i + 1 j <- j + 1 altfel dacă a[i] < b[j] atunci k <- k + 1 c[k] <- a[i] i <- i + 1 altfel k <- k + 1 c[k] <- b[j] j <- j + 1 sfârșit dacă sfârșit cât timp cât timp i <= n execută k <- k + 1 c[k] <- a[i] i <- i + 1 sfârșit cât timp cât timp j <= m execută k <- k + 1 c[k] <- b[j] j <- j + 1 sfârșit cât timp

Algoritmul cheie - cod C++

Fuzionarea fără dubluri
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.

Fuzionarea a două liste, fără dubluri
#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 k fără a construi tabloul c.
  • O listă cu un singur element este întotdeauna sortată - tratează cazul limită.
  • Citește enunțul: „elementele celor două șiruri" = interclasare, „elementele distincte" = fuzionare.