Broj listova i visina
Dato je stablo sa n čvorova (koren je 1) kao n − 1 grana. Ispiši broj listova i visinu stabla. (n ≥ 2)
Primer
Ulaz
8
1 2
1 3
1 4
2 5
2 6
4 7
7 8Izlaz
4 3Stablo je povezan graf bez ciklusa. Porodično stablo, struktura foldera na računaru i organizacija škole su stabla. Zbog posebnog oblika na stablima se mnogi zadaci rešavaju jednim obilaskom: dubine, veličine podstabala, prečnik.
Graf sa n čvorova je stablo ako važi bilo koje od ovih (sva su jednaka): povezan je i nema ciklus; povezan je i ima tačno n − 1 granu; između svaka dva čvora postoji tačno jedan put.
1 dubina 0
/ | \
2 3 4 dubina 1
/ \ \
5 6 7 dubina 2
\
8 dubina 3
| Pojam | Značenje | U primeru |
|---|---|---|
| koren | izabrani početni čvor | 1 |
| roditelj, dete | sused bliže korenu / dalje od korena | roditelj čvora 6 je 2; deca čvora 1 su 2, 3, 4 |
| list | čvor bez dece | 3, 5, 6, 8 |
| dubina | broj grana od korena do čvora | dubina čvora 8 je 3 |
| visina stabla | najveća dubina | 3 |
| podstablo čvora v | v i svi čvorovi ispod njega | podstablo čvora 2: {2, 5, 6} |
Stablo se zadaje kao n − 1 grana. Obilazak u dubinu (DFS) iz korena određuje roditelja i dubinu svakog čvora. Umesto niza posecen dovoljno je ne vraćati se u roditelja – u stablu nema drugih ciklusa.
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> graf;
vector<int> roditelj, dubina;
void dfs(int v, int r) { // r = roditelj čvora v
roditelj[v] = r;
for (int u : graf[v]) {
if (u == r) continue; // ne vraćamo se nazad
dubina[u] = dubina[v] + 1;
dfs(u, v);
}
}
int main() {
int n;
cin >> n;
graf.resize(n + 1);
roditelj.resize(n + 1);
dubina.assign(n + 1, 0);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
graf[u].push_back(v);
graf[v].push_back(u);
}
dfs(1, 0); // koren je 1, njegov "roditelj" je 0
for (int v = 1; v <= n; v++)
cout << v << ": roditelj " << roditelj[v] << ", dubina " << dubina[v] << endl;
return 0;
}
Veličina podstabla čvora v je 1 (sam v) plus zbir veličina podstabala njegove dece. Deca se obrađuju pre roditelja, pa veličinu računamo posle rekurzivnih poziva (obilazak „posle“, postorder).
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> graf;
vector<int> velicina;
void dfs(int v, int r) {
velicina[v] = 1; // sam čvor v
for (int u : graf[v]) {
if (u == r) continue;
dfs(u, v);
velicina[v] += velicina[u]; // dodaj podstablo deteta
}
}
int main() {
int n;
cin >> n;
graf.resize(n + 1);
velicina.assign(n + 1, 0);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
graf[u].push_back(v);
graf[v].push_back(u);
}
dfs(1, 0);
for (int v = 1; v <= n; v++) cout << velicina[v] << " ";
cout << endl;
return 0;
}
Prečnik je najveće rastojanje (broj grana) između dva čvora stabla. Postoji lep trik sa dva obilaska:
A;A nađi najudaljeniji čvor B;A do B je prečnik.#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int n;
vector<vector<int>> graf;
// BFS iz čvora s; vraća udaljenosti svih čvorova od s
vector<int> bfs(int s) {
vector<int> d(n + 1, -1);
queue<int> q;
d[s] = 0;
q.push(s);
while (!q.empty()) {
int v = q.front();
q.pop();
for (int u : graf[v])
if (d[u] == -1) {
d[u] = d[v] + 1;
q.push(u);
}
}
return d;
}
int main() {
cin >> n;
graf.resize(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
graf[u].push_back(v);
graf[v].push_back(u);
}
vector<int> d1 = bfs(1);
int A = 1;
for (int v = 1; v <= n; v++) if (d1[v] > d1[A]) A = v; // najdalji od 1
vector<int> d2 = bfs(A);
int B = A;
for (int v = 1; v <= n; v++) if (d2[v] > d2[B]) B = v; // najdalji od A
cout << "precnik = " << d2[B] << endl;
return 0;
}
U primeru je prečnik put 5 – 2 – 1 – 4 – 7 – 8 dužine 5. BFS je ovde korišćen umesto DFS-a da ne bi bilo duboke rekurzije.
Često se stablo zadaje drugačije: za svaki čvor osim korena dat je njegov roditelj (p[2], p[3], …, p[n]). Tada graf pravimo tako što svakom roditelju dodamo dete: deca[p[v]].push_back(v). Ako je uvek p[v] < v, veličine podstabala mogu se izračunati i bez rekurzije – prolaskom od n unazad do 2: vel[p[v]] += vel[v].
Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.
Dato je stablo sa n čvorova (koren je 1) kao n − 1 grana. Ispiši broj listova i visinu stabla. (n ≥ 2)
8
1 2
1 3
1 4
2 5
2 6
4 7
7 84 3Svaki čvor stabla ima vrednost (može biti i negativna). Koren je 1. Ispiši najveći zbir vrednosti nekog podstabla.
Ulaz: n, zatim n vrednosti, pa n − 1 grana.
5
3 -10 4 2 2
1 2
1 3
2 4
2 54Dato je stablo i dva čvora a i b. Ispiši broj grana na putu između njih i sam put.
Ulaz: n, n − 1 grana, zatim a b.
8
1 2
1 3
1 4
2 5
2 6
4 7
7 8
5 85
5 2 1 4 7 8n − 1.long long.1. Koliko grana ima stablo sa 50 čvorova?
2. Koliko puteva postoji između dva čvora stabla?
3. Kada se računa veličina podstabla čvora v?
4. Kako se nalazi prečnik stabla?