Ce vei învăța astăzi?
După această lecție vei ști să adaptezi căutarea binară pentru aplicații practice: prima și ultima apariție a unei valori și numărarea elementelor mai mici decât o valoare.
Prima apariție a unei valori
Într-un tablou sortat, valorile egale cu x sunt alăturate (așezate una lângă alta, fără alte valori între ele). Pentru a găsi prima apariție, căutăm cea mai mică poziție cu a[poz] >= x: când întâlnim un element egal cu x, reținem poziția și continuăm spre stânga.
#include <iostream>
using namespace std;
int main() {
int n, a[100005], x, i, st, dr, mij, poz = 0;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> x;
st = 1;
dr = n;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] >= x) {
poz = mij;
dr = mij - 1;
} else {
st = mij + 1;
}
}
if (poz != 0 && a[poz] == x)
cout << poz;
else
cout << -1;
return 0;
}
Observă: condiția a[mij] >= x ne duce mereu spre stânga, astfel încât poz reține prima poziție cu valoare >= x. Verificăm apoi dacă pe acea poziție chiar se află x.
Ultima apariție a unei valori
Pentru ultima apariție, căutăm cea mai mare poziție cu a[poz] <= x: când întâlnim un element egal cu x, reținem poziția și continuăm spre dreapta.
#include <iostream>
using namespace std;
int main() {
int n, a[100005], x, i, st, dr, mij, poz = 0;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> x;
st = 1;
dr = n;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] <= x) {
poz = mij;
st = mij + 1;
} else {
dr = mij - 1;
}
}
if (poz != 0 && a[poz] == x)
cout << poz;
else
cout << -1;
return 0;
}
Observă: condiția a[mij] <= x ne duce mereu spre dreapta, astfel încât poz reține ultima poziție cu valoare <= x.
Câte elemente sunt mai mici decât x
Dacă poz este prima poziție cu valoarea >= x, atunci toate elementele de la 1 la poz - 1 sunt strict mai mici decât x. Numărul lor este poz - 1.
#include <iostream>
using namespace std;
int main() {
int n, a[100005], x, i, st, dr, mij, poz;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> x;
st = 1;
dr = n;
poz = n + 1;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] >= x) {
poz = mij;
dr = mij - 1;
} else {
st = mij + 1;
}
}
cout << poz - 1;
return 0;
}
Observă: dacă toate elementele sunt mai mici decât x, poz rămâne n + 1, iar poz - 1 = n, ceea ce este corect.
Exercițiu de completare
Completează enunțul cu cuvintele potrivite:
Pentru a găsi prima apariție a lui x, când găsim valoarea continuăm căutarea în partea ______, mutând dr pe ______.
Bancă de cuvinte:
Exercițiu de completare 2
Completează afirmațiile despre ultima apariție și numărare:
Pentru ultima apariție continuăm în dreapta, punând st pe ______. Dacă poz este prima poziție cu valoarea >= x, atunci numărul de elemente strict mai mici decât x este ______.
Bancă de cuvinte:
Test de înțelegere
Test rapid - aplicațiile căutării binare:
1. Când căutăm prima apariție și am găsit un element egal cu x:
2. Când căutăm ultima apariție și am găsit un element egal cu x:
3. Dacă poz este prima poziție cu valoarea >= x, atunci numărul de elemente strict mai mici decât x este:
x, nu neapărat prima.Aprofundare
De ce valorile egale cu x sunt alăturate într-un tablou sortat? Această proprietate este esențială: dacă a[i] == a[j] == x cu i < j, atunci orice element dintre ele este tot x (altfel tabloul nu ar fi sortat). Așadar, toate aparițiile lui x formează un bloc continuu (un grup de valori egale, așezate una lângă alta). Numărul de apariții se calculează deci ca diferența dintre ultima și prima poziție plus unu: ultima - prima + 1.
Inițializarea lui poz pentru numărarea elementelor mai mici: dacă toate elementele sunt mai mici decât x, prima poziție cu a[poz] >= x nu există, deci poz trebuie inițializat cu n + 1 pentru ca poz - 1 să dea răspunsul corect (n). Această inițializare „la marginea din dreapta" este un truc standard în problemele de tip „câte elemente sunt mai mici decât x".
Generalizarea celor patru tipuri de căutare: cu ajutorul a două funcții, prima >= x și prima > x, putem răspunde la toate întrebările: elemente < x, elemente <= x, prima apariție, ultima apariție, numărul de apariții. De exemplu, prima poziție cu valoarea > x se obține căutând prima poziție cu a[poz] > x (condiția a[mij] > x). Această idee apare frecvent la olimpiadă și în problemele de tip „punct de frontieră" (probleme în care se caută exact poziția la care datele își schimbă o proprietate).
Aplicație reală - granița dintre promovat și nepromovat: într-un catalog cu notele ordonate crescător, prima poziție cu notă >= 5 ne spune instant de unde încep elevii promovați, fără să parcurgem tot catalogul. Același raționament se aplică la determinarea numărului de produse dintr-un magazin cu preț sub un buget dat.
PbInfo - Prima și ultima apariție
Enunț:
Cerință: Se dă un tablou cu n numere naturale, sortat crescător, și o valoare x. Să se afișeze poziția primei apariții și poziția ultimei apariții a lui x, separate printr-un spațiu. Dacă x nu apare, să se afișeze -1.
Date de intrare:
Programul citește de la tastatură numărul n, apoi cele n valori sortate, apoi valoarea x.
Date de ieșire:
Programul va afișa prima și ultima poziție a lui x, separate printr-un spațiu, sau -1 dacă valoarea nu există.
Restricții și precizări:
1 <= n <= 100000, valorile din tablou sunt numere naturale sortate crescător.
Exemplu:
Intrare:
8 1 2 3 5 5 5 7 9 5
Ieșire:
4 6
Cum gândim soluția:
- Căutăm prima apariție, continuând spre stânga când găsim valoarea
- Căutăm ultima apariție, continuând spre dreapta când găsim valoarea
- Dacă valoarea nu există, afișăm
-1
Scrie soluția în C++:
Soluție corectă:
#include <iostream>
using namespace std;
int main() {
int n, a[100005], x, i, st, dr, mij, prima = 0, ultima = 0;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> x;
st = 1;
dr = n;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] >= x) {
prima = mij;
dr = mij - 1;
} else {
st = mij + 1;
}
}
st = 1;
dr = n;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] <= x) {
ultima = mij;
st = mij + 1;
} else {
dr = mij - 1;
}
}
if (prima != 0 && a[prima] == x)
cout << prima << " " << ultima;
else
cout << -1;
return 0;
}
Ce învățăm: prima și ultima apariție se găsesc cu două căutări binare distincte, una orientată spre stânga, cealaltă spre dreapta.
PbInfo - Elemente mai mici decât x
Enunț:
Cerință: Se dă un tablou cu n numere naturale, sortat crescător, și o valoare x. Să se afișeze câte elemente din tablou sunt strict mai mici decât x.
Date de intrare:
Programul citește de la tastatură numărul n, apoi cele n valori sortate, apoi valoarea x.
Date de ieșire:
Programul va afișa numărul de elemente strict mai mici decât x.
Restricții și precizări:
1 <= n <= 100000, valorile din tablou sunt numere naturale sortate crescător.
Exemplu:
Intrare:
6 1 3 5 7 9 11 7
Ieșire:
3
Cum gândim soluția:
- Căutăm prima poziție cu
a[mij] >= x(lower bound) - Inițializăm
poz = n + 1 - Afișăm
poz - 1, numărul de elemente mai mici decât x
Scrie soluția în C++:
Soluție corectă:
#include <iostream>
using namespace std;
int main() {
int n, a[100005], x, i, st, dr, mij, poz;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> x;
st = 1;
dr = n;
poz = n + 1;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] >= x) {
poz = mij;
dr = mij - 1;
} else {
st = mij + 1;
}
}
cout << poz - 1;
return 0;
}
Ce învățăm: poziția primului element >= x ne spune exact câte elemente sunt mai mici decât x, fără a parcurge tabloul.
Exercițiul Practic: Aplicații ale căutării binare
Sarcini de lucru
Completează următoarele sarcini pentru a-ți consolida aplicațiile căutării binare
Sarcina 1: Numără aparițiile lui x
Cerință: Scrie un program C++ care citește un tablou sortat, valoarea x și afișează de câte ori apare x în tablou, folosind prima și ultima apariție.
Soluție pentru Sarcina 1:
#include <iostream>
using namespace std;
int main() {
int n, a[100005], x, i, st, dr, mij, prima = 0, ultima = 0;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> x;
st = 1;
dr = n;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] >= x) {
prima = mij;
dr = mij - 1;
} else {
st = mij + 1;
}
}
st = 1;
dr = n;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] <= x) {
ultima = mij;
st = mij + 1;
} else {
dr = mij - 1;
}
}
if (prima != 0 && a[prima] == x)
cout << ultima - prima + 1;
else
cout << 0;
return 0;
}
Verifică: numărul de apariții este ultima - prima + 1, deoarece valorile egale sunt alăturate în tabloul sortat.
Sarcina 2: Prima poziție cu valoare strict mai mare decât x
Cerință: Scrie un program C++ care citește un tablou sortat, valoarea x și afișează poziția primului element cu valoarea strict mai mare decât x, sau -1 dacă nu există.
Soluție pentru Sarcina 2:
#include <iostream>
using namespace std;
int main() {
int n, a[100005], x, i, st, dr, mij, poz;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> x;
st = 1;
dr = n;
poz = n + 1;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] > x) {
poz = mij;
dr = mij - 1;
} else {
st = mij + 1;
}
}
if (poz == n + 1)
cout << -1;
else
cout << poz;
return 0;
}
Verifică: folosim condiția strictă a[mij] > x pentru a găsi primul element mai mare decât x, nu mai mare sau egal.
Scrie pe caiet - De reținut
Titlul lecției
Ora 5: Căutarea binară - aplicații
Definiția lecției
Într-un tablou sortat, toate valorile egale cu x sunt alăturate, formând un bloc continuu; de aceea putem găsi prima și ultima apariție tot cu căutare binară. Prima apariție se obține căutând prima poziție cu a[poz] >= x: când găsim un element egal, reținem poziția și continuăm spre stânga, mutând dr = mij - 1. Ultima apariție se obține simetric, căutând ultima poziție cu a[poz] <= x și continuând spre dreapta cu st = mij + 1. După ieșirea din buclă verificăm dacă pe poziția reținută chiar se află x, altfel afișăm -1. Dacă poz este prima poziție cu valoarea >= x, atunci numărul de elemente strict mai mici decât x este poz - 1, cu poz inițializat cu n + 1 pentru cazul în care toate elementele sunt mai mici. Numărul de apariții se calculează cu formula ultima - prima + 1.
Definiții esențiale
- Într-un tablou sortat, valorile egale formează un bloc continuu de poziții alăturate.
- Prima apariție: prima poziție cu
a[poz] >= x; când găsim un element egal, continuăm spre stânga (dr = mij - 1). - Ultima apariție: ultima poziție cu
a[poz] <= x; când găsim un element egal, continuăm spre dreapta (st = mij + 1). - Numărul de elemente mai mici decât x:
poz - 1, undepozeste prima poziție cua[poz] >= x(inițializat cun + 1).
Algoritmul cheie - pseudocod
Algoritmul cheie - cod C++
st = 1;
dr = n;
poz = 0;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] >= x) {
poz = mij;
dr = mij - 1;
} else {
st = mij + 1;
}
}
if (poz != 0 && a[poz] == x)
cout << poz;
else
cout << -1;
Greșeli comune de evitare
- Oprirea căutării la primul element egal: la prima apariție trebuie să continuăm spre stânga.
- Inițializarea lui
pozcu 0 la numărarea elementelor mai mici: dacă toate elementele sunt mai mici, răspunsul devine greșit; foloseșten + 1. - Verificarea lipsă
a[poz] == x: fără ea putem afișa o poziție pe care nu se află valoarea căutată.
Exemplu practic
Într-un catalog de note ordonate crescător, profesorul vrea să afle câți elevi au luat exact nota x la test. Valorile egale sunt alăturate, deci numărul lor este ultima - prima + 1. Programul de mai jos găsește prima și ultima apariție a notei x, apoi afișează numărul de apariții.
#include <iostream>
using namespace std;
int main() {
int n, a[100005], x, i, st, dr, mij, prima = 0, ultima = 0;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
cin >> x;
st = 1; dr = n;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] >= x) {
prima = mij;
dr = mij - 1;
} else {
st = mij + 1;
}
}
st = 1; dr = n;
while (st <= dr) {
mij = (st + dr) / 2;
if (a[mij] <= x) {
ultima = mij;
st = mij + 1;
} else {
dr = mij - 1;
}
}
if (prima != 0 && a[prima] == x)
cout << ultima - prima + 1;
else
cout << 0;
return 0;
}
Pentru notele 4 5 5 5 7 9 și x = 5, programul afișează 3 - exact numărul de elevi care au luat nota 5.
Sfaturi practice și trucuri
- Numărul de apariții:
ultima - prima + 1- două căutări binare, răspuns instant. - Pentru „elemente
<= x", caută prima poziție cu valoarea> xși scade 1. - Testează întotdeauna cazul în care
xnu apare deloc în tablou.