Fill și algoritmul lui Lee (BFS pe matrice)
Multe probleme cer să explorăm o matrice pornind dintr-un punct: să umplem o zonă, să găsim drumul cel mai scurt într-un labirint, sau să numărăm regiunile conexe. Pentru asta folosim două tehnici fundamentale: Fill (umplere) și algoritmul lui Lee (BFS pe grilă).
Ambele se bazează pe structura de coadă (Queue - FIFO): adăugăm celulele descoperite la spate și le procesăm de la față. Aceasta garantează explorarea nivel cu nivel (cele mai apropiate celule sunt procesate primele). Varianta cu stivă (LIFO) ar produce DFS - explorare în adâncime, nu în lățime.
Flood Fill
Flood Fill umple o zonă conectată cu o valoare nouă - ca și cum ai turna vopsea dintr-un punct și ea se extinde în toate direcțiile posibile.
Problema
Avem o matrice cu 0 și 1. Pornind de la o poziție
(sx, sy) care conține 0, vrem să “umplem” toată
zona de 0-uri conectată (sus, jos, stânga, dreapta) cu valoarea
2.
Înainte: După fill din (2,2):
1 1 1 0 0 1 1 1 2 2
1 0 0 0 1 1 2 2 2 1
1 0 1 0 1 → 1 2 1 2 1
0 0 1 1 1 2 2 1 1 1
1 1 1 1 1 1 1 1 1 1
Implementare cu coadă (BFS)
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int a[101][101];
int n, m;
// Direcțiile: sus, jos, stânga, dreapta
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
// Coadă simplă
int qx[10001], qy[10001];
void fill(int sx, int sy, int culoare) {
int st = 0, dr = 0;
qx[dr] = sx;
qy[dr] = sy;
dr++;
a[sx][sy] = culoare;
while (st < dr) {
int x = qx[st];
int y = qy[st];
st++;
for (int d = 0; d < 4; d++) {
int nx = x + dx[d];
int ny = y + dy[d];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && a[nx][ny] == 0) {
a[nx][ny] = culoare;
qx[dr] = nx;
qy[dr] = ny;
dr++;
}
}
}
}
int main()
{
fin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
fin >> a[i][j];
int sx, sy;
fin >> sx >> sy;
fill(sx, sy, 2);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
fout << a[i][j] << " ";
fout << "\n";
}
return 0;
}Vectorii de direcție
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};Aceștia codifică cele 4 direcții:
| Direcție | dx | dy |
|---|---|---|
| Sus | -1 | 0 |
| Jos | +1 | 0 |
| Stânga | 0 | -1 |
| Dreapta | 0 | +1 |
Vecinul (nx, ny) = (x + dx[d], y + dy[d]) pentru
fiecare direcție d.
8 direcții: dacă problema cere și diagonale,
adăugăm: dx[] = {-1,-1,-1,0,0,1,1,1},
dy[] = {-1,0,1,-1,1,-1,0,1}.
Numărarea componentelor conexe
O componentă conexă este o regiune de celule cu aceeași valoare, conectate între ele.
Problema
Câte “insule” de 1 sunt în matrice?
0 0 1 0 0
0 1 1 0 0 → 3 insule
0 0 0 1 1
1 1 0 0 1
Ideea
Parcurgem matricea. Când găsim un 1 nevizitat, pornim un fill care marchează toată insula. Numărăm câte fill-uri am făcut.
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int a[101][101];
int n, m;
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
int qx[10001], qy[10001];
void fill(int sx, int sy) {
int st = 0, dr = 0;
qx[dr] = sx; qy[dr] = sy; dr++;
a[sx][sy] = 0;
while (st < dr) {
int x = qx[st], y = qy[st]; st++;
for (int d = 0; d < 4; d++) {
int nx = x + dx[d], ny = y + dy[d];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && a[nx][ny] == 1) {
a[nx][ny] = 0;
qx[dr] = nx; qy[dr] = ny; dr++;
}
}
}
}
int main()
{
fin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
fin >> a[i][j];
int insule = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i][j] == 1) {
insule++;
fill(i, j);
}
fout << insule;
return 0;
}date.in:
4 5
0 0 1 0 0
0 1 1 0 0
0 0 0 1 1
1 1 0 0 1date.out:
3Algoritmul lui Lee - distanța minimă
Algoritmul lui Lee este BFS pe matrice. Găsește distanța minimă (în număr de pași) de la un punct de start la toate celelalte celule accesibile.
Problema: labirint
Avem o matrice cu 0 (liber) și 1 (zid). Pornim de la
(sx, sy) și vrem distanța minimă până la
(fx, fy).
Labirint:
0 0 1 0 0
1 0 1 0 1
0 0 0 0 1
0 1 1 0 0
Start: (1,1), Final: (4,5)
Ideea
BFS explorează celulele nivel cu nivel - mai întâi cele la distanță 1, apoi la distanță 2, etc. Prima dată când ajungem la o celulă, avem distanța minimă.
Folosim o matrice dist[i][j] care reține
distanța de la start. Inițial -1 (nevizitat).
Codul complet
#include <fstream>
using namespace std;
ifstream fin("date.in");
ofstream fout("date.out");
int a[101][101];
int dist[101][101];
int n, m;
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
int qx[10001], qy[10001];
int main()
{
fin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
fin >> a[i][j];
dist[i][j] = -1;
}
int sx, sy, fx, fy;
fin >> sx >> sy >> fx >> fy;
// BFS (Lee)
int st = 0, dr = 0;
qx[dr] = sx; qy[dr] = sy; dr++;
dist[sx][sy] = 0;
while (st < dr) {
int x = qx[st], y = qy[st]; st++;
for (int d = 0; d < 4; d++) {
int nx = x + dx[d], ny = y + dy[d];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m
&& a[nx][ny] == 0 && dist[nx][ny] == -1) {
dist[nx][ny] = dist[x][y] + 1;
qx[dr] = nx; qy[dr] = ny; dr++;
}
}
}
fout << dist[fx][fy];
return 0;
}date.in:
4 5
0 0 1 0 0
1 0 1 0 1
0 0 0 0 1
0 1 1 0 0
1 1
4 5date.out:
8Pas cu pas
Distanțe calculate (Lee):
0 1 . . .
. 2 . . .
4 3 4 5 .
5 . . 6 7 → dist[4][5] = 8?
De fapt, drumul: (1,1)→(1,2)→(2,2)→(3,2)→(3,3)→(3,4)→(4,4)→(4,5) = 7 pași. Verificarea exactă depinde de labirint.
Matricea distanțelor
Lee calculează distanța de la start la toate celulele, nu doar la final. Putem afișa toată matricea distanțelor:
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (dist[i][j] == -1)
fout << "# ";
else
fout << dist[i][j] << " ";
}
fout << "\n";
}# = inaccesibil (zid sau celulă la care nu se
poate ajunge).
Lee cu mai multe surse
Dacă avem mai multe puncte de start, le punem pe toate în coadă la început:
// Toate celulele cu valoarea 2 sunt surse
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i][j] == 2) {
dist[i][j] = 0;
qx[dr] = i; qy[dr] = j; dr++;
}
// BFS normal de aici
while (st < dr) {
// ...
}Rezultatul: dist[i][j] = distanța minimă de la
(i,j) la cea mai apropiată sursă.
Structura generală BFS pe matrice
1. Inițializăm dist cu -1 (nevizitat)
2. Punem start-ul în coadă, dist[sx][sy] = 0
3. Cât timp coada nu e goală:
a. Scoatem (x, y) din coadă
b. Pentru fiecare vecin (nx, ny):
- Verificăm limite, ziduri, vizitat
- dist[nx][ny] = dist[x][y] + 1
- Adăugăm în coadă
Fill vs Lee
| Flood Fill | Lee (BFS) | |
|---|---|---|
| Ce face | Umple o regiune | Calculează distanțe |
| Rezultat | Marchează celulele | Matrice de distanțe |
| Complexitate | O(n*m) | O(n*m) |
| Folosim pentru | Componente conexe, umplere | Drum minim, distanțe |
Ambele sunt BFS pe matrice. Diferența e ce reținem: fill marchează, Lee calculează distanțe.
Greșeli frecvente
1. Coada prea mică
Coada trebuie să poată conține cel mult n * m
elemente (dacă toate celulele sunt accesibile).
int qx[100001], qy[100001]; // pentru n,m <= 3162. Nu verificăm limitele
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m // OBLIGATORIU
&& a[nx][ny] == 0 && dist[nx][ny] == -1)Fără verificarea limitelor, accesăm memorie în afara matricei.
3. Marcăm celula prea târziu
// GREȘIT - o adăugăm în coadă dar nu o marcăm imediat
qx[dr] = nx; qy[dr] = ny; dr++;
// ... alt vecin o adaugă din nou!
// CORECT - marcăm ÎNAINTE de a adăuga în coadă
dist[nx][ny] = dist[x][y] + 1; // marcăm
qx[dr] = nx; qy[dr] = ny; dr++;4. Confuzia fill recursiv cu BFS
Fill-ul se poate implementa și recursiv (DFS), dar pe matrici mari dă stack overflow. BFS cu coadă e mereu sigur.
Ce să reții
- Flood Fill: umple o zonă conectată pornind dintr-un punct.
- Lee (BFS): calculează distanța minimă de la start la toate celulele.
- Ambele folosesc o coadă și vectorii de
direcție
dx,dy. - Verificăm mereu: limite, ziduri, vizitat.
- Marcăm celula înainte de a o adăuga în coadă.
- Complexitate: O(n*m) - fiecare celulă e vizitată o singură dată.
- Coada trebuie dimensionată la
n * m.