Programovanie (1) v C/C++
1-INF-127, ZS 2024/25
Prednáška 18: Rozdiel medzi revíziami
(44 medziľahlých úprav od 3 ďalších používateľov nie je zobrazených) | |||
Riadok 1: | Riadok 1: | ||
− | == | + | == Oznamy == |
− | + | Budúci utorok bude na cvičeniach rozcvička na papieri. | |
+ | * Bude pokrývať učivo po prednášku 17. | ||
+ | * Dôležitý tréning na semestrálny test. | ||
− | + | Blíži sa '''semestrálny test'''. | |
+ | * Bude sa konať v stredu '''11.12. o 18:10''' v posluchárňach F1 a F2 v trvaní 90 minút. | ||
+ | * Viac informácií na stránke [[Zimný semester, semestrálny test]]. | ||
− | + | Od pondelka 2.12. 19:00 sa môžete hlásiť na termín skúšky pri počítači. | |
− | + | * Ak vidíte konflikt niektorého termínu s hromadnou skúškou alebo písomkou z iného predmetu, dajte nám vedieť čím skôr. | |
− | + | * Kapacita termínov bude obmedzená, prihláste sa teda radšej skôr, neskôr to môžete zmeniť. | |
− | + | * Prihlásiť a odhlásiť sa dá najviac deň vopred. | |
− | + | * Skúšku 20.12. môžete robiť iba ak spravíte test 11.12. aspoň na 50% bodov. | |
− | + | * Decembrový termín odporúčame hlavne študentom, ktorým programovanie nerobí problémy. | |
− | + | * Viac informácií o skúške na prednáške v stredu 11.12. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | == Opakovanie: zásobník a rad == | |
− | + | * '''Zásobník''' (angl. '''stack''') a '''rad''' alebo '''front''' (angl. '''queue''') sú abstraktné dátové typy, ktoré udržiavajú postupnosť nejakých prvkov. | |
− | + | * Obidva typy podporujú vloženie prvku a výber prvky | |
− | + | * Zo zásobníka sa vyberá prvok, ktorý v ňom pobudol najkratšie, z radu prvok, ktorý v ňom bol najdlhšie | |
+ | * Zásobník tak pripomína stĺpec čistých tanierov v reštaurácii, rad pripomína rad pri pokladni | ||
− | + | Konkrétne funkcie hlavičky funkcií | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<pre> | <pre> | ||
− | () | + | void init(stack &s); |
− | + | bool isEmpty(stack &s); | |
− | + | void push(stack &s, dataType item); // vlozenie prvku | |
− | + | dataType pop(stack &s); // vyber prvku | |
− | + | dataType peek(stack &s); | |
− | + | void destroy(stack &s); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | () | ||
− | |||
− | |||
− | ( | ||
− | |||
− | (( | + | void init(queue &q); |
− | + | bool isEmpty(queue &q); | |
+ | void enqueue(queue &q, dataType item); // vlozenie prvku | ||
+ | dataType dequeue(queue &q); // vyber prvku | ||
+ | dataType peek(queue &q); | ||
+ | void destroy(queue &q); | ||
</pre> | </pre> | ||
− | + | * Pre obidva typy sme videli implementáciu v poli aj v spájanom zozname | |
+ | * Hlavné funkcie vkladania a vyberania sú v obidvoch implementáciách rýchle a jednoduché | ||
− | + | Videli sme tiež, že obidve štruktúry sa dajú použiť na ukladanie dát alebo úloh, ktoré ešte treba vyriešiť | |
− | + | * Dnes uvidíme ďalšie príklady | |
− | |||
− | |||
− | |||
− | + | == Použitie zásobníka a radu: nerekurzívny Quick Sort == | |
− | + | Pripomeňme si triedenie Quick Sort z [[Prednáška 11#Quick_Sort|11. prednášky]]: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | Pripomeňme si triedenie | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
Riadok 188: | Riadok 58: | ||
} | } | ||
− | int partition(int a[], int | + | int partition(int a[], int left, int right) { |
− | int pivot = a[ | + | int pivot = a[left]; |
− | int lastSmaller = | + | int lastSmaller = left; |
− | for (int unknown = | + | for (int unknown = left + 1; unknown <= right; unknown++) { |
if (a[unknown] < pivot) { | if (a[unknown] < pivot) { | ||
lastSmaller++; | lastSmaller++; | ||
Riadok 198: | Riadok 68: | ||
} | } | ||
} | } | ||
− | swap(a[ | + | swap(a[left],a[lastSmaller]); |
return lastSmaller; | return lastSmaller; | ||
} | } | ||
− | void quicksort(int a[], int | + | void quicksort(int a[], int left, int right) { |
− | if ( | + | if (left >= right) { |
− | return; | + | return; |
} | } | ||
− | int | + | int middle = partition(a, left, right); |
− | quicksort(a, | + | quicksort(a, left, middle-1); |
− | quicksort(a, | + | quicksort(a, middle+1, right); |
} | } | ||
− | int main( | + | int main() { |
// ... | // ... | ||
quicksort(a, 0, N-1); | quicksort(a, 0, N-1); | ||
Riadok 224: | Riadok 94: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
struct usek { | struct usek { | ||
− | int | + | int left; |
− | int | + | int right; |
}; | }; | ||
Riadok 239: | Riadok 109: | ||
usek u; | usek u; | ||
− | u. | + | u.left = 0; |
− | u. | + | u.right = n-1; |
push(s,u); | push(s,u); | ||
while (!isEmpty(s)) { | while (!isEmpty(s)) { | ||
u = pop(s); | u = pop(s); | ||
− | if (u. | + | // vynechame useky dlzky 0 a 1 |
+ | if (u.left >= u.right) { | ||
continue; | continue; | ||
} | } | ||
− | int | + | int middle = partition(a, u.left, u.right); |
usek u1; | usek u1; | ||
− | u1. | + | u1.left = u.left; |
− | u1. | + | u1.right = middle - 1; |
usek u2; | usek u2; | ||
− | u2. | + | u2.left = middle + 1; |
− | u2. | + | u2.right = u.right; |
push(s,u2); | push(s,u2); | ||
push(s,u1); | push(s,u1); | ||
Riadok 264: | Riadok 135: | ||
} | } | ||
− | int main( | + | int main() { |
// ... | // ... | ||
quicksort(a, N); | quicksort(a, N); | ||
Riadok 271: | Riadok 142: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Tento program triedi úseky v rovnakom poradí, ako rekurzívny | + | * Tento program triedi úseky v rovnakom poradí, ako rekurzívny Quick Sort, lebo po rozdelení poľa na dve časti dá na vrch zásobníka úsek zodpovedajúci jeho ľavej časti. Až keď sa táto ľavá časť a všetky podúlohy, ktoré z nej vzniknú, spracuje, dôjde na spracovanie pravej časti poľa. |
− | + | * Pri triedení Quick Sort však na tomto poradí nezáleží, takže by sme mohli jednotlivé úseky vkladať na zásobník aj v opačnom poradí. | |
+ | * Alebo by sme namiesto zásobníka mohli použiť rad. Potom by najskôr rozdelil ľavú aj pravú časť na ďalšie podčasti a potom by delil každú z týchto podčastí atď. | ||
− | ''Na zamyslenie'' | + | '''Na zamyslenie:''' ako by mohla vyzerať nerekurzívna verzia triedenia [[Prednáška_11#Triedenie_zlu.C4.8Dovan.C3.ADm_.28Merge_Sort.29|Merge Sort]]? Prečo sa nedá použiť rovnaký prístup ako pri triedení Quick Sort? |
==Vyfarbovanie súvislých oblastí== | ==Vyfarbovanie súvislých oblastí== | ||
− | Uvažujme | + | * Uvažujme obrázok pozostávajúci z <tt>m</tt> krát <tt>n</tt> štvorčekov (pixelov). |
+ | * Farbu každého pixelu zapíšeme do jedného políčka dvojrozmerného poľa s <tt>m</tt> riadkami a <tt>n</tt> stĺpcami. | ||
+ | * V našom jednoduchom príklade budeme pracovať iba s piatimi farbami, ktoré budeme reprezentovať číslami <tt>0,...,4</tt> podľa nasledujúceho poľa (napríklad číslo <tt>0</tt> teda reprezentuje bielu farbu): | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
const char *farby[5] = {"white", "blue", "black", "yellow", "red"}; | const char *farby[5] = {"white", "blue", "black", "yellow", "red"}; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Napríklad | + | Napríklad obrázok |
[[Súbor:Matica1.png|400px]] | [[Súbor:Matica1.png|400px]] | ||
Riadok 303: | Riadok 177: | ||
</pre> | </pre> | ||
− | Zameriame sa teraz na nasledujúci problém: používateľ zvolí (zadá na konzolu) súradnice niektorého | + | Zameriame sa teraz na nasledujúci problém: používateľ zvolí (zadá na konzolu) súradnice niektorého štvorčeka a cieľom je ofarbiť nejakou farbou (napríklad červenou) celú súvislú oblasť rovnakej farby obsahujúcu daný štvorček. Napríklad pre obrazec vyššie a vstupné súradnice <tt>(2,1)</tt> (teda štvorček v treťom riadku a druhom stĺpci, keďže matica sa bude indexovať od nuly) by mal byť výstupom nasledujúci obrazec: |
[[Súbor:Matica2.png|400px]] | [[Súbor:Matica2.png|400px]] | ||
− | Podobný problém je napríklad často potrebné riešiť v rôznych nástrojoch na prácu s grafikou ( | + | Podobný problém je napríklad často potrebné riešiť v rôznych nástrojoch na prácu s grafikou a podobne. |
+ | |||
+ | * Jednoduché útvary, napr. obdĺžnik, by sa dali vyfarbiť jednoduchými cyklami, naša súvislá oblasť ale môže mať veľmi zložitý tvar, s mnohými zákrutami, vetveniami a dierami a podobne. | ||
+ | * Pre každé políčko, ktoré prefarbíme, nesmieme zabudnúť skontrolovať všetkých jeho susedov a ak majú rovnakú farbu ako malo pôvodné políčko, tak prefarbiť aj ich, ich susedov, susedov ich susedov atď. | ||
+ | * Toto vieme ľahko zapísať rekurzívne: zafarbíme jedno políčko a potom rekurzívne zafarbujeme celú dosiahnuteľnú oblasť pre každého suseda rovnakej farby, akú malo pôvodne prefarbené políčko. | ||
+ | * Táto podúloha je menšia, lebo aspoň jedno políčko z nej ubudlo prvým prefarbením. | ||
+ | |||
+ | ===Vyfarbovanie pomocou rekurzívnej funkcie=== | ||
+ | |||
+ | Nasledujúca rekurzívna funkcia <tt>vyfarbi</tt> prefarbí políčko so súradnicami <tt>(riadok, stlpec)</tt> na cieľovú farbu <tt>farba</tt> a následne sa rekurzívne zavolá pre všetkých susedov tohto políčka, ktoré sú zafarbené pôvodnou farbou prefarbovanej oblasti. | ||
+ | |||
+ | <syntaxhighlight lang="C++"> | ||
+ | /* Prefarbi súvislú jednofarebnú oblasť obsahujúcu | ||
+ | * pozíciu (riadok,stlpec) na farbu s číslom farba. */ | ||
+ | void vyfarbi(int **a, int m, int n, | ||
+ | int riadok, int stlpec, int farba) { | ||
+ | int staraFarba = a[riadok][stlpec]; | ||
+ | if (staraFarba == farba) { | ||
+ | return; | ||
+ | } | ||
+ | a[riadok][stlpec] = farba; | ||
+ | if (riadok - 1 >= 0 | ||
+ | && a[riadok - 1][stlpec] == staraFarba) { | ||
+ | vyfarbi(a, m, n, riadok - 1, stlpec, farba); | ||
+ | } | ||
+ | if (riadok + 1 < m | ||
+ | && a[riadok + 1][stlpec] == staraFarba) { | ||
+ | vyfarbi(a, m, n, riadok + 1, stlpec, farba); | ||
+ | } | ||
+ | if (stlpec - 1 >= 0 | ||
+ | && a[riadok][stlpec - 1] == staraFarba) { | ||
+ | vyfarbi(a, m, n, riadok, stlpec - 1, farba); | ||
+ | } | ||
+ | if (stlpec + 1 < n | ||
+ | && a[riadok][stlpec + 1] == staraFarba) { | ||
+ | vyfarbi(a, m, n, riadok, stlpec + 1, farba); | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
− | + | Proces vyfarbovania môžeme animovať pomocou našej knižnice SVGdraw | |
+ | * [[:Media:Animacia1.svg|Výsledná animácia]] | ||
− | + | V animácii okrem zmeny farby štvorčeka meníme aj farbu jeho rámčeka: pri zavolaní rekurzie sa zmení na hnedú a po skončení spracovania štvorčeka aj jeho susedov sa zmení na sivú. Hnedé sú teda vždy rámčeky štvorčekov uložené na zásobníku volaní. | |
+ | |||
+ | ===Program s animáciou=== | ||
+ | |||
+ | * Program na vyfarbovanie súvislých oblastí obsahuje funkcie na inicializáciu matice, jej načítanie zo súboru, vykresľovanie jednotlivých štvorčekov a celej matice, ako aj uvoľnenie pamäte. Všetky tieto funkcie pracujú podobne ako pri príklade s výškovou mapou z [[Prednáška 13|prednášky 13]]. | ||
+ | * Funkcia <tt>main</tt> načíta maticu zo súboru <tt>vstup.txt</tt> a animáciu uloží do súboru <tt>matica.svg</tt>. | ||
+ | * Do rekurzívnej funkcie pridáme volania funkcie <tt>vykresliStvorcek</tt>, ktoré aktuálny štvorček v obrázku prekreslia novou farbou a menia aj farbu rámčeka. Za ňou vždy zavoláme funkciu <tt>drawing.wait</tt>, ktorá animáciu pozdrží, aby sme zmeny stihli na obrázku sledovať. | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
Riadok 322: | Riadok 241: | ||
const int stvorcek = 40; // velkost stvorceka v pixeloch | const int stvorcek = 40; // velkost stvorceka v pixeloch | ||
const int hrubkaCiary = 2; // hrubka ciary v pixeloch | const int hrubkaCiary = 2; // hrubka ciary v pixeloch | ||
+ | const double pauza = 0.3; // pauza po kazdom kroku vyfarbovania v sekundach | ||
/* Vytvori maticu s n riadkami a m stlpcami. */ | /* Vytvori maticu s n riadkami a m stlpcami. */ | ||
Riadok 327: | Riadok 247: | ||
int **a; | int **a; | ||
a = new int *[m]; | a = new int *[m]; | ||
− | for (int i = 0; i < | + | for (int i = 0; i < m; i++) { |
a[i] = new int[n]; | a[i] = new int[n]; | ||
} | } | ||
Riadok 334: | Riadok 254: | ||
/* Uvolni pamat matice a s n riadkami a m stlpcami. */ | /* Uvolni pamat matice a s n riadkami a m stlpcami. */ | ||
− | void zmazMaticu( | + | void zmazMaticu(int **a, int m) { |
− | for (int i = 0; i < | + | for (int i = 0; i < m; i++) { |
delete[] a[i]; | delete[] a[i]; | ||
} | } | ||
Riadok 341: | Riadok 261: | ||
} | } | ||
− | /* Vykresli stvorcek v riadku i a stlpci j s farbou vyplne farba a farbou ciary farbaCiary. */ | + | /* Vykresli stvorcek v riadku i a stlpci j |
− | void vykresliStvorcek(int i, int j, const char *farba, const char *farbaCiary, SVGdraw &drawing) { | + | s farbou vyplne farba a farbou ciary farbaCiary. */ |
+ | void vykresliStvorcek(int i, int j, | ||
+ | const char * farba, | ||
+ | const char * farbaCiary, | ||
+ | SVGdraw &drawing) { | ||
drawing.setLineColor(farbaCiary); | drawing.setLineColor(farbaCiary); | ||
drawing.setLineWidth(hrubkaCiary); | drawing.setLineWidth(hrubkaCiary); | ||
drawing.setFillColor(farba); | drawing.setFillColor(farba); | ||
− | drawing.drawRectangle(j * stvorcek, i * stvorcek, stvorcek, stvorcek); | + | drawing.drawRectangle(j * stvorcek, i * stvorcek, |
+ | stvorcek, stvorcek); | ||
} | } | ||
/* Vykresli maticu a s n riadkami a m stlpcami. */ | /* Vykresli maticu a s n riadkami a m stlpcami. */ | ||
− | void vykresliMaticu(int m, int n | + | void vykresliMaticu(int **a, int m, int n, SVGdraw &drawing) { |
− | for (int i = 0; i < | + | for (int i = 0; i < m; i++) { |
− | for (int j = 0; j < | + | for (int j = 0; j < n; j++) { |
vykresliStvorcek(i, j, farby[a[i][j]], "lightgray", drawing); | vykresliStvorcek(i, j, farby[a[i][j]], "lightgray", drawing); | ||
} | } | ||
Riadok 358: | Riadok 283: | ||
} | } | ||
− | /* Nacita z textoveho suboru, na ktory ukazuje fr, prvky matice a s n riadkami a m stlpcami. */ | + | /* Nacita z textoveho suboru, na ktory ukazuje fr, |
− | void nacitajMaticu(FILE *fr, int m, int n | + | prvky matice a s n riadkami a m stlpcami. */ |
+ | void nacitajMaticu(FILE *fr, int **a, int m, int n) { | ||
assert(fr != NULL); | assert(fr != NULL); | ||
− | for (int i = 0; i < | + | for (int i = 0; i < m; i++) { |
− | for (int j = 0; j < | + | for (int j = 0; j < n; j++) { |
fscanf(fr, "%d", &a[i][j]); | fscanf(fr, "%d", &a[i][j]); | ||
} | } | ||
Riadok 368: | Riadok 294: | ||
} | } | ||
− | + | /* Prefarbi suvislu jednofarebnu oblast obsahujucu | |
− | + | * poziciu (riadok,stlpec) na farbu s cislom farba. */ | |
− | + | void vyfarbi(int **a, int m, int n, | |
− | + | int riadok, int stlpec, int farba, | |
− | + | SVGdraw &drawing) { | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | /* Prefarbi suvislu jednofarebnu oblast obsahujucu poziciu (riadok,stlpec) na farbu s cislom farba. */ | ||
− | void vyfarbi(int m, int n, | ||
int staraFarba = a[riadok][stlpec]; | int staraFarba = a[riadok][stlpec]; | ||
if (staraFarba == farba) { | if (staraFarba == farba) { | ||
Riadok 404: | Riadok 304: | ||
} | } | ||
a[riadok][stlpec] = farba; | a[riadok][stlpec] = farba; | ||
− | vykresliStvorcek(riadok, stlpec, farby[farba], "brown", drawing); | + | vykresliStvorcek(riadok, stlpec, farby[farba], |
+ | "brown", drawing); | ||
drawing.wait(pauza); | drawing.wait(pauza); | ||
if (riadok - 1 >= 0 && a[riadok - 1][stlpec] == staraFarba) { | if (riadok - 1 >= 0 && a[riadok - 1][stlpec] == staraFarba) { | ||
− | vyfarbi(m, n | + | vyfarbi(a, m, n, riadok - 1, stlpec, farba, drawing); |
} | } | ||
− | if (riadok + 1 < | + | if (riadok + 1 < m && a[riadok + 1][stlpec] == staraFarba) { |
− | vyfarbi(m, n | + | vyfarbi(a, m, n, riadok + 1, stlpec, farba, drawing); |
} | } | ||
if (stlpec - 1 >= 0 && a[riadok][stlpec - 1] == staraFarba) { | if (stlpec - 1 >= 0 && a[riadok][stlpec - 1] == staraFarba) { | ||
− | vyfarbi(m, n | + | vyfarbi(a, m, n, riadok, stlpec - 1, farba, drawing); |
} | } | ||
− | if (stlpec + 1 < | + | if (stlpec + 1 < n && a[riadok][stlpec + 1] == staraFarba) { |
− | vyfarbi(m, n | + | vyfarbi(a, m, n, riadok, stlpec + 1, farba, drawing); |
} | } | ||
− | vykresliStvorcek(riadok, stlpec, farby[farba], "lightgray", drawing); | + | vykresliStvorcek(riadok, stlpec, farby[farba], |
+ | "lightgray", drawing); | ||
drawing.wait(pauza); | drawing.wait(pauza); | ||
} | } | ||
− | |||
− | + | int main() { | |
− | |||
− | int main( | ||
FILE *fr = fopen("vstup.txt", "r"); | FILE *fr = fopen("vstup.txt", "r"); | ||
assert(fr != NULL); | assert(fr != NULL); | ||
+ | |||
+ | // nacitanie rozmerov matice | ||
int m, n; | int m, n; | ||
− | fscanf(fr, "%d", &m); | + | fscanf(fr, "%d %d", &m, &n); |
− | + | // vytvorenie matice a nacitanie jej prvkov | |
int **a = vytvorMaticu(m, n); | int **a = vytvorMaticu(m, n); | ||
− | nacitajMaticu(fr, m, n | + | nacitajMaticu(fr, a, m, n); |
fclose(fr); | fclose(fr); | ||
SVGdraw drawing(n * stvorcek, m * stvorcek, "matica.svg"); | SVGdraw drawing(n * stvorcek, m * stvorcek, "matica.svg"); | ||
− | vykresliMaticu(m, n | + | vykresliMaticu(a, m, n, drawing); |
+ | // nacitanie suradnic pociatocneho stvorceka | ||
int riadok, stlpec; | int riadok, stlpec; | ||
scanf("%d", &riadok); | scanf("%d", &riadok); | ||
scanf("%d", &stlpec); | scanf("%d", &stlpec); | ||
− | vyfarbi(m, n | + | vyfarbi(a, m, n, riadok, stlpec, 4, drawing); |
drawing.finish(); | drawing.finish(); | ||
− | zmazMaticu(m | + | zmazMaticu(a, m); |
− | |||
− | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ==Počítanie ostrovov== | |
− | + | Obrázok, s ktorým sme pracovali vyššie, môže reprezentovať napríklad jednoduchú mapu súostrovia, kde more je znázornené modrou farbou a pevnina je znázornená žltou farbou. Úlohou môže byť zistiť počet ostrovov. Ten môžeme zistiť napríklad takto: | |
− | |||
− | |||
* Prechádzame postupne všetky políčka mapy. | * Prechádzame postupne všetky políčka mapy. | ||
* Ak narazíme na pevninu (t. j. žlté políčko), zvýšime doposiaľ nájdený počet ostrovov o 1 a ofarbíme celý ostrov (napríklad) na červeno. | * Ak narazíme na pevninu (t. j. žlté políčko), zvýšime doposiaľ nájdený počet ostrovov o 1 a ofarbíme celý ostrov (napríklad) na červeno. | ||
Riadok 460: | Riadok 358: | ||
* Toto robíme, až kým prejdeme cez všetky políčka mapy. | * Toto robíme, až kým prejdeme cez všetky políčka mapy. | ||
− | + | Príklad mapy a jej zobrazenie pred začiatkom hľadania ostrovov, po nájdení prvých troch ostrovov a po nájdení všetkých ostrovov: | |
<pre> | <pre> | ||
11 17 | 11 17 | ||
Riadok 481: | Riadok 379: | ||
</gallery> | </gallery> | ||
− | Do programu | + | Do programu vyššie teda dorobíme funkciu |
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | int najdiOstrovy(int m, int n | + | int najdiOstrovy(int **a, int m, int n, SVGdraw &drawing) { |
int ostrovov = 0; | int ostrovov = 0; | ||
− | for (int i = 0; i < | + | for (int i = 0; i < m; i++) { |
− | for (int j = 0; j < | + | for (int j = 0; j < n; j++) { |
if (a[i][j] == 3) { | if (a[i][j] == 3) { | ||
ostrovov++; | ostrovov++; | ||
− | vyfarbi(m, n | + | vyfarbi(a, m, n, i, j, 4, drawing); |
} | } | ||
} | } | ||
Riadok 499: | Riadok 397: | ||
a funkciu <tt>main</tt> môžeme zmeniť napríklad takto: | a funkciu <tt>main</tt> môžeme zmeniť napríklad takto: | ||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
− | int main( | + | int main() { |
FILE *fr = fopen("ostrovy.txt", "r"); | FILE *fr = fopen("ostrovy.txt", "r"); | ||
assert(fr != NULL); | assert(fr != NULL); | ||
int m, n; | int m, n; | ||
− | fscanf(fr, "%d", &m | + | fscanf(fr, "%d %d", &m, &n); |
− | |||
int **a = vytvorMaticu(m, n); | int **a = vytvorMaticu(m, n); | ||
− | nacitajMaticu(fr, m, n | + | nacitajMaticu(fr, a, m, n); |
fclose(fr); | fclose(fr); | ||
SVGdraw drawing(n * stvorcek, m * stvorcek, "mapa.svg"); | SVGdraw drawing(n * stvorcek, m * stvorcek, "mapa.svg"); | ||
− | vykresliMaticu(m, n | + | vykresliMaticu(a, m, n, drawing); |
− | int pocetOstrovov = najdiOstrovy(m, n | + | int pocetOstrovov = najdiOstrovy(a, m, n, drawing); |
printf("Pocet ostrovov je %d.\n", pocetOstrovov); | printf("Pocet ostrovov je %d.\n", pocetOstrovov); | ||
drawing.finish(); | drawing.finish(); | ||
− | zmazMaticu(m | + | zmazMaticu(a, m); |
− | |||
− | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | * [[: | + | * [[:Media:Animacia2.svg|Výsledná animácia]] |
− | ''Cvičenie:'' upravte | + | ''Cvičenie:'' upravte program tak, aby ešte navyše zistil, či má niektorý z ostrovov jazero. |
+ | |||
+ | ==Nerekurzívne vyfarbovanie== | ||
===Vyfarbovanie s použitím zásobníka=== | ===Vyfarbovanie s použitím zásobníka=== | ||
− | S použitím niektorej implementácie zásobníka z [[ | + | S použitím niektorej implementácie zásobníka z [[Prednáška 17|minulej prednášky]] môžeme napísať aj nerekurzívnu verziu funkcie <tt>vyfarbi</tt>. Tá zakaždým vyberie zo zásobníka niektoré políčko, skontroluje všetkých jeho susedov a ak majú pôvodnú farbu, ofarbí ich a vloží ich na zásobník, aby sme ďalej skontrolovali aj ich susedov. |
− | + | Súradnice jednotlivých susedov budeme počítať s použitím cyklu <tt>for</tt> a polí <tt>deltaStlpec</tt> a <tt>deltaRiadok</tt>, ktoré pre <tt>i = 0,1,2,3</tt> obsahujú ''posuny'' jednotlivých súradníc <tt>i</tt>-teho suseda oproti práve spracúvanému políčku (dalo by sa použiť aj v rekurzívnej verzii). | |
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
struct policko { | struct policko { | ||
Riadok 539: | Riadok 436: | ||
− | /* Sem pride definicia struktury pre zasobnik a funkcii poskytovanych zasobnikom. */ | + | /* Sem pride definicia struktury pre zasobnik |
+ | * a funkcii poskytovanych zasobnikom. */ | ||
Riadok 545: | Riadok 443: | ||
const int deltaStlpec[4] = {1, -1, 0, 0}; | const int deltaStlpec[4] = {1, -1, 0, 0}; | ||
− | /* Prefarbi suvislu jednofarebnu oblast obsahujucu poziciu (riadok,stlpec) na farbu s cislom farba. */ | + | /* Prefarbi suvislu jednofarebnu oblast obsahujucu |
− | void vyfarbi(int m, int n, | + | * poziciu (riadok,stlpec) na farbu s cislom farba. */ |
+ | void vyfarbi(int **a, int m, int n, | ||
+ | int riadok, int stlpec, int farba, | ||
+ | SVGdraw &drawing) { | ||
int staraFarba = a[riadok][stlpec]; | int staraFarba = a[riadok][stlpec]; | ||
if (staraFarba == farba) { | if (staraFarba == farba) { | ||
return; | return; | ||
} | } | ||
− | + | a[riadok][stlpec] = farba; | |
+ | vykresliStvorcek(riadok, stlpec, farby[farba], | ||
+ | "lightgrey", drawing); | ||
stack s; | stack s; | ||
init(s); | init(s); | ||
Riadok 562: | Riadok 465: | ||
while (!isEmpty(s)) { | while (!isEmpty(s)) { | ||
p = pop(s); | p = pop(s); | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
for (int i = 0; i <= 3; i++) { | for (int i = 0; i <= 3; i++) { | ||
policko sused; | policko sused; | ||
sused.riadok = p.riadok + deltaRiadok[i]; | sused.riadok = p.riadok + deltaRiadok[i]; | ||
sused.stlpec = p.stlpec + deltaStlpec[i]; | sused.stlpec = p.stlpec + deltaStlpec[i]; | ||
− | if (sused.riadok >= 0 && sused.riadok < | + | if (sused.riadok >= 0 && sused.riadok < m |
− | + | && sused.stlpec >= 0 && sused.stlpec < n | |
+ | && a[sused.riadok][sused.stlpec] == staraFarba) { | ||
+ | a[sused.riadok][sused.stlpec] = farba; | ||
+ | vykresliStvorcek(p.riadok, p.stlpec, farby[farba], | ||
+ | "lightgrey", drawing); | ||
+ | drawing.wait(pauza); | ||
push(s, sused); | push(s, sused); | ||
} | } | ||
Riadok 582: | Riadok 484: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | * [[: | + | * [[:Media:Animacia3.svg|Výsledná animácia]] |
+ | * Poradie sa trochu zmenilo oproti rekurzívnej verzii. Prečo? | ||
===Vyfarbovanie s použitím radu=== | ===Vyfarbovanie s použitím radu=== | ||
− | Namiesto zásobníka môžeme použiť aj rad | + | Namiesto zásobníka môžeme použiť aj rad. Obrazec sa potom bude vyfarbovať v poradí podľa vzdialenosti od počiatočného políčka. Tento algoritmus sa volá ''prehľadávanie do šírky'', kým rekurzívna verzia zodpovedá ''prehľadávaniu do hĺbky''. |
+ | |||
<syntaxhighlight lang="C++"> | <syntaxhighlight lang="C++"> | ||
struct policko { | struct policko { | ||
Riadok 595: | Riadok 499: | ||
− | /* Sem pride definicia struktury pre rad a funkcii poskytovanych radom. */ | + | /* Sem pride definicia struktury pre rad |
+ | * a funkcii poskytovanych radom. */ | ||
Riadok 601: | Riadok 506: | ||
const int deltaStlpec[4] = {1, -1, 0, 0}; | const int deltaStlpec[4] = {1, -1, 0, 0}; | ||
− | /* Prefarbi suvislu jednofarebnu oblast obsahujucu poziciu (riadok,stlpec) na farbu s cislom farba. */ | + | /* Prefarbi suvislu jednofarebnu oblast obsahujucu |
− | void vyfarbi(int m, int n, | + | * poziciu (riadok,stlpec) na farbu s cislom farba. */ |
+ | void vyfarbi(int **a, int m, int n, | ||
+ | int riadok, int stlpec, int farba, | ||
+ | SVGdraw &drawing) { | ||
int staraFarba = a[riadok][stlpec]; | int staraFarba = a[riadok][stlpec]; | ||
if (staraFarba == farba) { | if (staraFarba == farba) { | ||
return; | return; | ||
} | } | ||
+ | a[riadok][stlpec] = farba; | ||
+ | vykresliStvorcek(riadok, stlpec, farby[farba], | ||
+ | "lightgrey", drawing); | ||
queue q; | queue q; | ||
Riadok 618: | Riadok 529: | ||
while (!isEmpty(q)) { | while (!isEmpty(q)) { | ||
p = dequeue(q); | p = dequeue(q); | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
for (int i = 0; i <= 3; i++) { | for (int i = 0; i <= 3; i++) { | ||
policko sused; | policko sused; | ||
sused.riadok = p.riadok + deltaRiadok[i]; | sused.riadok = p.riadok + deltaRiadok[i]; | ||
sused.stlpec = p.stlpec + deltaStlpec[i]; | sused.stlpec = p.stlpec + deltaStlpec[i]; | ||
− | if (sused.riadok >= 0 && sused.riadok < | + | if (sused.riadok >= 0 && sused.riadok < m |
− | + | && sused.stlpec >= 0 && sused.stlpec < n | |
+ | && a[sused.riadok][sused.stlpec] == staraFarba) { | ||
+ | a[sused.riadok][sused.stlpec] = farba; | ||
+ | vykresliStvorcek(sused.riadok, sused.stlpec, farby[farba], | ||
+ | "lightgrey", drawing); | ||
+ | drawing.wait(pauza); | ||
enqueue(q, sused); | enqueue(q, sused); | ||
} | } | ||
Riadok 638: | Riadok 548: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | * [[: | + | * [[:Media:Animacia4.svg|Výsledná animácia]] |
Program potom môžeme upraviť aj tak, aby do každého ofarbeného políčka vypísal jeho vzdialenosť od počiatočného políčka: | Program potom môžeme upraviť aj tak, aby do každého ofarbeného políčka vypísal jeho vzdialenosť od počiatočného políčka: | ||
Riadok 649: | Riadok 559: | ||
− | /* Sem pride definicia struktury pre rad a funkcii poskytovanych radom. */ | + | /* Sem pride definicia struktury pre rad |
+ | * a funkcii poskytovanych radom. */ | ||
− | + | void vypisVzdialenost(policko p, SVGdraw &drawing) { | |
− | void vypisVzdialenost( | + | drawing.setLineColor("white"); |
− | drawing.setLineColor( | ||
drawing.setFontSize(20); | drawing.setFontSize(20); | ||
char text[15]; | char text[15]; | ||
− | sprintf(text, "%d", vzd); | + | sprintf(text, "%d", p.vzd); |
− | drawing.drawText(( | + | drawing.drawText((p.stlpec + 0.5) * stvorcek, |
+ | (p.riadok + 0.5) * stvorcek, text); | ||
} | } | ||
Riadok 663: | Riadok 574: | ||
const int deltaStlpec[4] = {1, -1, 0, 0}; | const int deltaStlpec[4] = {1, -1, 0, 0}; | ||
− | /* Prefarbi suvislu jednofarebnu oblast obsahujucu poziciu (riadok,stlpec) na farbu s cislom farba. */ | + | /* Prefarbi suvislu jednofarebnu oblast obsahujucu |
− | void vyfarbi(int m, int n, | + | * poziciu (riadok,stlpec) na farbu s cislom farba. */ |
+ | void vyfarbi(int **a, int m, int n, | ||
+ | int riadok, int stlpec, int farba, | ||
+ | SVGdraw &drawing) { | ||
int staraFarba = a[riadok][stlpec]; | int staraFarba = a[riadok][stlpec]; | ||
if (staraFarba == farba) { | if (staraFarba == farba) { | ||
Riadok 678: | Riadok 592: | ||
p.vzd = 0; | p.vzd = 0; | ||
enqueue(q, p); | enqueue(q, p); | ||
+ | a[riadok][stlpec] = farba; | ||
+ | vykresliStvorcek(riadok, stlpec, farby[farba], | ||
+ | "lightgrey", drawing); | ||
+ | vypisVzdialenost(p, drawing); | ||
while (!isEmpty(q)) { | while (!isEmpty(q)) { | ||
p = dequeue(q); | p = dequeue(q); | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
for (int i = 0; i <= 3; i++) { | for (int i = 0; i <= 3; i++) { | ||
policko sused; | policko sused; | ||
sused.riadok = p.riadok + deltaRiadok[i]; | sused.riadok = p.riadok + deltaRiadok[i]; | ||
sused.stlpec = p.stlpec + deltaStlpec[i]; | sused.stlpec = p.stlpec + deltaStlpec[i]; | ||
− | + | if (sused.riadok >= 0 && sused.riadok < m | |
− | if (sused.riadok >= 0 && sused.riadok < | + | && sused.stlpec >= 0 && sused.stlpec < n |
− | + | && a[sused.riadok][sused.stlpec] == staraFarba) { | |
+ | sused.vzd = p.vzd + 1; | ||
+ | a[sused.riadok][sused.stlpec] = farba; | ||
+ | vykresliStvorcek(sused.riadok, sused.stlpec, farby[farba], | ||
+ | "lightgrey", drawing); | ||
+ | vypisVzdialenost(sused, drawing); | ||
+ | drawing.wait(pauza); | ||
enqueue(q, sused); | enqueue(q, sused); | ||
} | } | ||
Riadok 703: | Riadok 620: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | * [[: | + | * [[:Media:Animacia5.svg|Výsledná animácia]] |
+ | |||
+ | ==Zhrnutie== | ||
+ | * Vyfarbovanie súvislých oblastí v matici sa dá využiť v počítačovej grafike ale na iné úlohy, napríklad počítanie ostrovov na mape. | ||
+ | * Dá sa jednoducho napísať rekurzívne. | ||
+ | * Rekurziu vieme odstrániť použitím zásobníka alebo radu na ukladanie políčok, ktoré ešte treba spracovať. | ||
+ | * Pri využití radu vieme spočítať aj vzdialenosť od počiatočného bodu | ||
+ | * Budúci semester uvidíte na Programovaní (2) prehľadávanie grafov, ktoré funguje veľmi podobne, ale je trochu všeobecnejšie. |
Aktuálna revízia z 09:13, 27. november 2024
Obsah
Oznamy
Budúci utorok bude na cvičeniach rozcvička na papieri.
- Bude pokrývať učivo po prednášku 17.
- Dôležitý tréning na semestrálny test.
Blíži sa semestrálny test.
- Bude sa konať v stredu 11.12. o 18:10 v posluchárňach F1 a F2 v trvaní 90 minút.
- Viac informácií na stránke Zimný semester, semestrálny test.
Od pondelka 2.12. 19:00 sa môžete hlásiť na termín skúšky pri počítači.
- Ak vidíte konflikt niektorého termínu s hromadnou skúškou alebo písomkou z iného predmetu, dajte nám vedieť čím skôr.
- Kapacita termínov bude obmedzená, prihláste sa teda radšej skôr, neskôr to môžete zmeniť.
- Prihlásiť a odhlásiť sa dá najviac deň vopred.
- Skúšku 20.12. môžete robiť iba ak spravíte test 11.12. aspoň na 50% bodov.
- Decembrový termín odporúčame hlavne študentom, ktorým programovanie nerobí problémy.
- Viac informácií o skúške na prednáške v stredu 11.12.
Opakovanie: zásobník a rad
- Zásobník (angl. stack) a rad alebo front (angl. queue) sú abstraktné dátové typy, ktoré udržiavajú postupnosť nejakých prvkov.
- Obidva typy podporujú vloženie prvku a výber prvky
- Zo zásobníka sa vyberá prvok, ktorý v ňom pobudol najkratšie, z radu prvok, ktorý v ňom bol najdlhšie
- Zásobník tak pripomína stĺpec čistých tanierov v reštaurácii, rad pripomína rad pri pokladni
Konkrétne funkcie hlavičky funkcií
void init(stack &s); bool isEmpty(stack &s); void push(stack &s, dataType item); // vlozenie prvku dataType pop(stack &s); // vyber prvku dataType peek(stack &s); void destroy(stack &s); void init(queue &q); bool isEmpty(queue &q); void enqueue(queue &q, dataType item); // vlozenie prvku dataType dequeue(queue &q); // vyber prvku dataType peek(queue &q); void destroy(queue &q);
- Pre obidva typy sme videli implementáciu v poli aj v spájanom zozname
- Hlavné funkcie vkladania a vyberania sú v obidvoch implementáciách rýchle a jednoduché
Videli sme tiež, že obidve štruktúry sa dajú použiť na ukladanie dát alebo úloh, ktoré ešte treba vyriešiť
- Dnes uvidíme ďalšie príklady
Použitie zásobníka a radu: nerekurzívny Quick Sort
Pripomeňme si triedenie Quick Sort z 11. prednášky:
void swap (int &x, int &y) {
int tmp = x;
x = y;
y = tmp;
}
int partition(int a[], int left, int right) {
int pivot = a[left];
int lastSmaller = left;
for (int unknown = left + 1; unknown <= right; unknown++) {
if (a[unknown] < pivot) {
lastSmaller++;
swap(a[unknown], a[lastSmaller]);
}
}
swap(a[left],a[lastSmaller]);
return lastSmaller;
}
void quicksort(int a[], int left, int right) {
if (left >= right) {
return;
}
int middle = partition(a, left, right);
quicksort(a, left, middle-1);
quicksort(a, middle+1, right);
}
int main() {
// ...
quicksort(a, 0, N-1);
// ...
}
Namiesto rekurzie môžeme použiť aj zásobník úsekov, ktoré ešte treba dotriediť.
struct usek {
int left;
int right;
};
typedef usek dataType;
/* Sem pride definicia struktury stack a vsetkych potrebnych funkcii. */
/* Sem pridu funkcie swap a partition rovnake ako vyssie. */
void quicksort(int a[], int n) {
stack s;
init(s);
usek u;
u.left = 0;
u.right = n-1;
push(s,u);
while (!isEmpty(s)) {
u = pop(s);
// vynechame useky dlzky 0 a 1
if (u.left >= u.right) {
continue;
}
int middle = partition(a, u.left, u.right);
usek u1;
u1.left = u.left;
u1.right = middle - 1;
usek u2;
u2.left = middle + 1;
u2.right = u.right;
push(s,u2);
push(s,u1);
}
destroy(s);
}
int main() {
// ...
quicksort(a, N);
// ...
}
- Tento program triedi úseky v rovnakom poradí, ako rekurzívny Quick Sort, lebo po rozdelení poľa na dve časti dá na vrch zásobníka úsek zodpovedajúci jeho ľavej časti. Až keď sa táto ľavá časť a všetky podúlohy, ktoré z nej vzniknú, spracuje, dôjde na spracovanie pravej časti poľa.
- Pri triedení Quick Sort však na tomto poradí nezáleží, takže by sme mohli jednotlivé úseky vkladať na zásobník aj v opačnom poradí.
- Alebo by sme namiesto zásobníka mohli použiť rad. Potom by najskôr rozdelil ľavú aj pravú časť na ďalšie podčasti a potom by delil každú z týchto podčastí atď.
Na zamyslenie: ako by mohla vyzerať nerekurzívna verzia triedenia Merge Sort? Prečo sa nedá použiť rovnaký prístup ako pri triedení Quick Sort?
Vyfarbovanie súvislých oblastí
- Uvažujme obrázok pozostávajúci z m krát n štvorčekov (pixelov).
- Farbu každého pixelu zapíšeme do jedného políčka dvojrozmerného poľa s m riadkami a n stĺpcami.
- V našom jednoduchom príklade budeme pracovať iba s piatimi farbami, ktoré budeme reprezentovať číslami 0,...,4 podľa nasledujúceho poľa (napríklad číslo 0 teda reprezentuje bielu farbu):
const char *farby[5] = {"white", "blue", "black", "yellow", "red"};
Napríklad obrázok
tak môže byť reprezentovaný nasledujúcim textovým súborom obsahujúcim najprv rozmery matice (čísla m a n) a za nimi samotné prvky matice:
11 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 2 2 1 2 2 2 2 0 0 0 0 1 0 0 0 0 0 2 0 1 0 0 0 2 0 0 2 2 2 2 2 2 2 0 2 2 1 2 2 2 2 0 0 2 0 1 0 0 0 2 0 0 0 1 0 0 0 0 0 0 2 0 1 0 0 0 2 0 0 0 1 0 1 1 1 1 0 2 0 1 1 1 1 2 1 1 1 1 0 1 0 0 1 0 2 0 0 0 0 0 2 0 0 0 0 0 1 1 1 1 0 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0 0
Zameriame sa teraz na nasledujúci problém: používateľ zvolí (zadá na konzolu) súradnice niektorého štvorčeka a cieľom je ofarbiť nejakou farbou (napríklad červenou) celú súvislú oblasť rovnakej farby obsahujúcu daný štvorček. Napríklad pre obrazec vyššie a vstupné súradnice (2,1) (teda štvorček v treťom riadku a druhom stĺpci, keďže matica sa bude indexovať od nuly) by mal byť výstupom nasledujúci obrazec:
Podobný problém je napríklad často potrebné riešiť v rôznych nástrojoch na prácu s grafikou a podobne.
- Jednoduché útvary, napr. obdĺžnik, by sa dali vyfarbiť jednoduchými cyklami, naša súvislá oblasť ale môže mať veľmi zložitý tvar, s mnohými zákrutami, vetveniami a dierami a podobne.
- Pre každé políčko, ktoré prefarbíme, nesmieme zabudnúť skontrolovať všetkých jeho susedov a ak majú rovnakú farbu ako malo pôvodné políčko, tak prefarbiť aj ich, ich susedov, susedov ich susedov atď.
- Toto vieme ľahko zapísať rekurzívne: zafarbíme jedno políčko a potom rekurzívne zafarbujeme celú dosiahnuteľnú oblasť pre každého suseda rovnakej farby, akú malo pôvodne prefarbené políčko.
- Táto podúloha je menšia, lebo aspoň jedno políčko z nej ubudlo prvým prefarbením.
Vyfarbovanie pomocou rekurzívnej funkcie
Nasledujúca rekurzívna funkcia vyfarbi prefarbí políčko so súradnicami (riadok, stlpec) na cieľovú farbu farba a následne sa rekurzívne zavolá pre všetkých susedov tohto políčka, ktoré sú zafarbené pôvodnou farbou prefarbovanej oblasti.
/* Prefarbi súvislú jednofarebnú oblasť obsahujúcu
* pozíciu (riadok,stlpec) na farbu s číslom farba. */
void vyfarbi(int **a, int m, int n,
int riadok, int stlpec, int farba) {
int staraFarba = a[riadok][stlpec];
if (staraFarba == farba) {
return;
}
a[riadok][stlpec] = farba;
if (riadok - 1 >= 0
&& a[riadok - 1][stlpec] == staraFarba) {
vyfarbi(a, m, n, riadok - 1, stlpec, farba);
}
if (riadok + 1 < m
&& a[riadok + 1][stlpec] == staraFarba) {
vyfarbi(a, m, n, riadok + 1, stlpec, farba);
}
if (stlpec - 1 >= 0
&& a[riadok][stlpec - 1] == staraFarba) {
vyfarbi(a, m, n, riadok, stlpec - 1, farba);
}
if (stlpec + 1 < n
&& a[riadok][stlpec + 1] == staraFarba) {
vyfarbi(a, m, n, riadok, stlpec + 1, farba);
}
}
Proces vyfarbovania môžeme animovať pomocou našej knižnice SVGdraw
V animácii okrem zmeny farby štvorčeka meníme aj farbu jeho rámčeka: pri zavolaní rekurzie sa zmení na hnedú a po skončení spracovania štvorčeka aj jeho susedov sa zmení na sivú. Hnedé sú teda vždy rámčeky štvorčekov uložené na zásobníku volaní.
Program s animáciou
- Program na vyfarbovanie súvislých oblastí obsahuje funkcie na inicializáciu matice, jej načítanie zo súboru, vykresľovanie jednotlivých štvorčekov a celej matice, ako aj uvoľnenie pamäte. Všetky tieto funkcie pracujú podobne ako pri príklade s výškovou mapou z prednášky 13.
- Funkcia main načíta maticu zo súboru vstup.txt a animáciu uloží do súboru matica.svg.
- Do rekurzívnej funkcie pridáme volania funkcie vykresliStvorcek, ktoré aktuálny štvorček v obrázku prekreslia novou farbou a menia aj farbu rámčeka. Za ňou vždy zavoláme funkciu drawing.wait, ktorá animáciu pozdrží, aby sme zmeny stihli na obrázku sledovať.
#include "SVGdraw.h"
#include <cstdio>
#include <cassert>
const char *farby[5] = {"white", "blue", "black", "yellow", "red"};
const int stvorcek = 40; // velkost stvorceka v pixeloch
const int hrubkaCiary = 2; // hrubka ciary v pixeloch
const double pauza = 0.3; // pauza po kazdom kroku vyfarbovania v sekundach
/* Vytvori maticu s n riadkami a m stlpcami. */
int **vytvorMaticu(int m, int n) {
int **a;
a = new int *[m];
for (int i = 0; i < m; i++) {
a[i] = new int[n];
}
return a;
}
/* Uvolni pamat matice a s n riadkami a m stlpcami. */
void zmazMaticu(int **a, int m) {
for (int i = 0; i < m; i++) {
delete[] a[i];
}
delete[] a;
}
/* Vykresli stvorcek v riadku i a stlpci j
s farbou vyplne farba a farbou ciary farbaCiary. */
void vykresliStvorcek(int i, int j,
const char * farba,
const char * farbaCiary,
SVGdraw &drawing) {
drawing.setLineColor(farbaCiary);
drawing.setLineWidth(hrubkaCiary);
drawing.setFillColor(farba);
drawing.drawRectangle(j * stvorcek, i * stvorcek,
stvorcek, stvorcek);
}
/* Vykresli maticu a s n riadkami a m stlpcami. */
void vykresliMaticu(int **a, int m, int n, SVGdraw &drawing) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
vykresliStvorcek(i, j, farby[a[i][j]], "lightgray", drawing);
}
}
}
/* Nacita z textoveho suboru, na ktory ukazuje fr,
prvky matice a s n riadkami a m stlpcami. */
void nacitajMaticu(FILE *fr, int **a, int m, int n) {
assert(fr != NULL);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
fscanf(fr, "%d", &a[i][j]);
}
}
}
/* Prefarbi suvislu jednofarebnu oblast obsahujucu
* poziciu (riadok,stlpec) na farbu s cislom farba. */
void vyfarbi(int **a, int m, int n,
int riadok, int stlpec, int farba,
SVGdraw &drawing) {
int staraFarba = a[riadok][stlpec];
if (staraFarba == farba) {
return;
}
a[riadok][stlpec] = farba;
vykresliStvorcek(riadok, stlpec, farby[farba],
"brown", drawing);
drawing.wait(pauza);
if (riadok - 1 >= 0 && a[riadok - 1][stlpec] == staraFarba) {
vyfarbi(a, m, n, riadok - 1, stlpec, farba, drawing);
}
if (riadok + 1 < m && a[riadok + 1][stlpec] == staraFarba) {
vyfarbi(a, m, n, riadok + 1, stlpec, farba, drawing);
}
if (stlpec - 1 >= 0 && a[riadok][stlpec - 1] == staraFarba) {
vyfarbi(a, m, n, riadok, stlpec - 1, farba, drawing);
}
if (stlpec + 1 < n && a[riadok][stlpec + 1] == staraFarba) {
vyfarbi(a, m, n, riadok, stlpec + 1, farba, drawing);
}
vykresliStvorcek(riadok, stlpec, farby[farba],
"lightgray", drawing);
drawing.wait(pauza);
}
int main() {
FILE *fr = fopen("vstup.txt", "r");
assert(fr != NULL);
// nacitanie rozmerov matice
int m, n;
fscanf(fr, "%d %d", &m, &n);
// vytvorenie matice a nacitanie jej prvkov
int **a = vytvorMaticu(m, n);
nacitajMaticu(fr, a, m, n);
fclose(fr);
SVGdraw drawing(n * stvorcek, m * stvorcek, "matica.svg");
vykresliMaticu(a, m, n, drawing);
// nacitanie suradnic pociatocneho stvorceka
int riadok, stlpec;
scanf("%d", &riadok);
scanf("%d", &stlpec);
vyfarbi(a, m, n, riadok, stlpec, 4, drawing);
drawing.finish();
zmazMaticu(a, m);
}
Počítanie ostrovov
Obrázok, s ktorým sme pracovali vyššie, môže reprezentovať napríklad jednoduchú mapu súostrovia, kde more je znázornené modrou farbou a pevnina je znázornená žltou farbou. Úlohou môže byť zistiť počet ostrovov. Ten môžeme zistiť napríklad takto:
- Prechádzame postupne všetky políčka mapy.
- Ak narazíme na pevninu (t. j. žlté políčko), zvýšime doposiaľ nájdený počet ostrovov o 1 a ofarbíme celý ostrov (napríklad) na červeno.
- Ak narazíme na ďalšie žlté políčko, opäť urobíme to isté.
- Toto robíme, až kým prejdeme cez všetky políčka mapy.
Príklad mapy a jej zobrazenie pred začiatkom hľadania ostrovov, po nájdení prvých troch ostrovov a po nájdení všetkých ostrovov:
11 17 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 1 1 3 1 1 1 1 3 3 1 1 1 1 3 3 1 1 1 1 1 1 1 1 1 3 3 1 1 1 3 3 1 1 1 1 1 3 3 1 3 3 3 3 1 1 1 1 3 3 1 1 1 1 3 1 1 3 3 3 3 1 1 3 3 3 3 1 3 3 1 3 3 1 3 3 3 3 1 1 1 3 3 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 1 1 3 3 3 1 1 1 1 1 1 1 1 1 3 3 3 1 1 3 1 3 1 1 1 1 3 3 3 3 1 3 1 1 1 1 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Do programu vyššie teda dorobíme funkciu
int najdiOstrovy(int **a, int m, int n, SVGdraw &drawing) {
int ostrovov = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (a[i][j] == 3) {
ostrovov++;
vyfarbi(a, m, n, i, j, 4, drawing);
}
}
}
return ostrovov;
}
a funkciu main môžeme zmeniť napríklad takto:
int main() {
FILE *fr = fopen("ostrovy.txt", "r");
assert(fr != NULL);
int m, n;
fscanf(fr, "%d %d", &m, &n);
int **a = vytvorMaticu(m, n);
nacitajMaticu(fr, a, m, n);
fclose(fr);
SVGdraw drawing(n * stvorcek, m * stvorcek, "mapa.svg");
vykresliMaticu(a, m, n, drawing);
int pocetOstrovov = najdiOstrovy(a, m, n, drawing);
printf("Pocet ostrovov je %d.\n", pocetOstrovov);
drawing.finish();
zmazMaticu(a, m);
}
Cvičenie: upravte program tak, aby ešte navyše zistil, či má niektorý z ostrovov jazero.
Nerekurzívne vyfarbovanie
Vyfarbovanie s použitím zásobníka
S použitím niektorej implementácie zásobníka z minulej prednášky môžeme napísať aj nerekurzívnu verziu funkcie vyfarbi. Tá zakaždým vyberie zo zásobníka niektoré políčko, skontroluje všetkých jeho susedov a ak majú pôvodnú farbu, ofarbí ich a vloží ich na zásobník, aby sme ďalej skontrolovali aj ich susedov.
Súradnice jednotlivých susedov budeme počítať s použitím cyklu for a polí deltaStlpec a deltaRiadok, ktoré pre i = 0,1,2,3 obsahujú posuny jednotlivých súradníc i-teho suseda oproti práve spracúvanému políčku (dalo by sa použiť aj v rekurzívnej verzii).
struct policko {
int riadok, stlpec;
};
typedef policko dataType;
/* Sem pride definicia struktury pre zasobnik
* a funkcii poskytovanych zasobnikom. */
const int deltaRiadok[4] = {0, 0, 1, -1};
const int deltaStlpec[4] = {1, -1, 0, 0};
/* Prefarbi suvislu jednofarebnu oblast obsahujucu
* poziciu (riadok,stlpec) na farbu s cislom farba. */
void vyfarbi(int **a, int m, int n,
int riadok, int stlpec, int farba,
SVGdraw &drawing) {
int staraFarba = a[riadok][stlpec];
if (staraFarba == farba) {
return;
}
a[riadok][stlpec] = farba;
vykresliStvorcek(riadok, stlpec, farby[farba],
"lightgrey", drawing);
stack s;
init(s);
policko p;
p.riadok = riadok;
p.stlpec = stlpec;
push(s, p);
while (!isEmpty(s)) {
p = pop(s);
for (int i = 0; i <= 3; i++) {
policko sused;
sused.riadok = p.riadok + deltaRiadok[i];
sused.stlpec = p.stlpec + deltaStlpec[i];
if (sused.riadok >= 0 && sused.riadok < m
&& sused.stlpec >= 0 && sused.stlpec < n
&& a[sused.riadok][sused.stlpec] == staraFarba) {
a[sused.riadok][sused.stlpec] = farba;
vykresliStvorcek(p.riadok, p.stlpec, farby[farba],
"lightgrey", drawing);
drawing.wait(pauza);
push(s, sused);
}
}
}
destroy(s);
}
- Výsledná animácia
- Poradie sa trochu zmenilo oproti rekurzívnej verzii. Prečo?
Vyfarbovanie s použitím radu
Namiesto zásobníka môžeme použiť aj rad. Obrazec sa potom bude vyfarbovať v poradí podľa vzdialenosti od počiatočného políčka. Tento algoritmus sa volá prehľadávanie do šírky, kým rekurzívna verzia zodpovedá prehľadávaniu do hĺbky.
struct policko {
int riadok, stlpec;
};
typedef policko dataType;
/* Sem pride definicia struktury pre rad
* a funkcii poskytovanych radom. */
const int deltaRiadok[4] = {0, 0, 1, -1};
const int deltaStlpec[4] = {1, -1, 0, 0};
/* Prefarbi suvislu jednofarebnu oblast obsahujucu
* poziciu (riadok,stlpec) na farbu s cislom farba. */
void vyfarbi(int **a, int m, int n,
int riadok, int stlpec, int farba,
SVGdraw &drawing) {
int staraFarba = a[riadok][stlpec];
if (staraFarba == farba) {
return;
}
a[riadok][stlpec] = farba;
vykresliStvorcek(riadok, stlpec, farby[farba],
"lightgrey", drawing);
queue q;
init(q);
policko p;
p.riadok = riadok;
p.stlpec = stlpec;
enqueue(q, p);
while (!isEmpty(q)) {
p = dequeue(q);
for (int i = 0; i <= 3; i++) {
policko sused;
sused.riadok = p.riadok + deltaRiadok[i];
sused.stlpec = p.stlpec + deltaStlpec[i];
if (sused.riadok >= 0 && sused.riadok < m
&& sused.stlpec >= 0 && sused.stlpec < n
&& a[sused.riadok][sused.stlpec] == staraFarba) {
a[sused.riadok][sused.stlpec] = farba;
vykresliStvorcek(sused.riadok, sused.stlpec, farby[farba],
"lightgrey", drawing);
drawing.wait(pauza);
enqueue(q, sused);
}
}
}
destroy(q);
}
Program potom môžeme upraviť aj tak, aby do každého ofarbeného políčka vypísal jeho vzdialenosť od počiatočného políčka:
struct policko {
int riadok, stlpec, vzd;
};
typedef policko dataType;
/* Sem pride definicia struktury pre rad
* a funkcii poskytovanych radom. */
void vypisVzdialenost(policko p, SVGdraw &drawing) {
drawing.setLineColor("white");
drawing.setFontSize(20);
char text[15];
sprintf(text, "%d", p.vzd);
drawing.drawText((p.stlpec + 0.5) * stvorcek,
(p.riadok + 0.5) * stvorcek, text);
}
const int deltaRiadok[4] = {0, 0, 1, -1};
const int deltaStlpec[4] = {1, -1, 0, 0};
/* Prefarbi suvislu jednofarebnu oblast obsahujucu
* poziciu (riadok,stlpec) na farbu s cislom farba. */
void vyfarbi(int **a, int m, int n,
int riadok, int stlpec, int farba,
SVGdraw &drawing) {
int staraFarba = a[riadok][stlpec];
if (staraFarba == farba) {
return;
}
queue q;
init(q);
policko p;
p.riadok = riadok;
p.stlpec = stlpec;
p.vzd = 0;
enqueue(q, p);
a[riadok][stlpec] = farba;
vykresliStvorcek(riadok, stlpec, farby[farba],
"lightgrey", drawing);
vypisVzdialenost(p, drawing);
while (!isEmpty(q)) {
p = dequeue(q);
for (int i = 0; i <= 3; i++) {
policko sused;
sused.riadok = p.riadok + deltaRiadok[i];
sused.stlpec = p.stlpec + deltaStlpec[i];
if (sused.riadok >= 0 && sused.riadok < m
&& sused.stlpec >= 0 && sused.stlpec < n
&& a[sused.riadok][sused.stlpec] == staraFarba) {
sused.vzd = p.vzd + 1;
a[sused.riadok][sused.stlpec] = farba;
vykresliStvorcek(sused.riadok, sused.stlpec, farby[farba],
"lightgrey", drawing);
vypisVzdialenost(sused, drawing);
drawing.wait(pauza);
enqueue(q, sused);
}
}
}
destroy(q);
}
Zhrnutie
- Vyfarbovanie súvislých oblastí v matici sa dá využiť v počítačovej grafike ale na iné úlohy, napríklad počítanie ostrovov na mape.
- Dá sa jednoducho napísať rekurzívne.
- Rekurziu vieme odstrániť použitím zásobníka alebo radu na ukladanie políčok, ktoré ešte treba spracovať.
- Pri využití radu vieme spočítať aj vzdialenosť od počiatočného bodu
- Budúci semester uvidíte na Programovaní (2) prehľadávanie grafov, ktoré funguje veľmi podobne, ale je trochu všeobecnejšie.