Programovanie (2) v Jave
1-INF-166, letný semester 2023/24

Prednášky · Pravidlá · Softvér · Testovač
· Vyučujúcich predmetu možno kontaktovať mailom na adresách uvedených na hlavnej stránke. Hromadná mailová adresa zo zimného semestra v letnom semestri nefunguje.
· JavaFX: cesta k adresáru lib je v počítačových učebniach /usr/share/openjfx/lib.


Prednáška 18: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
 
(24 medziľahlých úprav od 3 ďalších používateľov nie je zobrazených)
Riadok 1: Riadok 1:
==Rad pomocou spájaného zoznamu==
+
== 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
  
Pri implementácii radu pomocou spájaného zoznamu rozšírime spájané zoznamy zo [[Prednáška 14|14. prednášky]] o smerník <tt>last</tt> na posledný prvok zoznamu. Bude tak možné jednoducho vkladať prvky na koniec zoznamu, ako aj odoberať prvky zo začiatku zoznamu.
+
Konkrétne funkcie hlavičky funkcií
 
 
Výhodou oproti implementácii radu pomocou poľa bude, podobne ako pri zásobníkoch, eliminácia obmedzenia na maximálny počet prvkov v rade.
 
 
 
<syntaxhighlight lang="C++">
 
#include <cassert>
 
 
 
// ...
 
 
 
struct node {
 
    dataType data;
 
    node *next;
 
};
 
 
 
struct queue {
 
    node *first; // Smernik na prvy prvok radu (spajaneho zoznamu). Ak je rad prazdny, ma hodnotu NULL.
 
    node *last;  // Smernik na posledny prvok radu (spajaneho zoznamu). Ak je rad prazdny, ma hodnotu NULL.
 
};
 
 
 
/* Inicializuje prazdny rad */
 
void init(queue &q) {
 
    q.first = NULL;
 
    q.last = NULL;
 
}
 
 
 
/* Zisti, ci je rad prazdny */
 
bool isEmpty(queue &q) {
 
    return q.first == NULL;
 
}
 
 
 
/* Prida prvok item na koniec radu */
 
void enqueue(queue &q, dataType item) {
 
    node *tmp = new node;
 
    tmp->data = item;
 
    tmp->next = NULL;
 
    if (isEmpty(q)) {
 
        q.first = tmp;
 
        q.last = tmp;
 
    } else {
 
        q.last->next = tmp;
 
        q.last = tmp;
 
    }
 
}
 
 
 
/* Odoberie prvok zo zaciatku radu a vrati jeho hodnotu */
 
dataType dequeue(queue &q) {
 
    assert(!isEmpty(q));
 
    dataType result = q.first->data;
 
    node *tmp = q.first->next;
 
    delete q.first;
 
    if (tmp == NULL) {
 
        q.first = NULL;
 
        q.last = NULL;
 
    } else {
 
        q.first = tmp;
 
    }
 
    return result;
 
}
 
 
 
/* Vrati prvok zo zaciatku radu, ale necha ho v rade */
 
dataType peek(queue &q) {
 
    assert(!isEmpty(q));
 
    return q.first->data;
 
}       
 
 
 
/* Uvolni pamat */
 
void destroy(queue &q) {
 
    while (!isEmpty(q)) {
 
        dequeue(q);
 
    }
 
}
 
</syntaxhighlight>
 
 
 
== Použitie zásobníka a radu ==
 
Zásobník aj rad často uchovávajú dáta určené na spracovanie, zoznamy úloh, atď. Rad sa zvyčajne používa v prípadoch, keď je žiadúce zachovať ich poradie. Typicky môže ísť o situácie, keď jeden proces generuje úlohy spracúvané iným procesom, napríklad:
 
* Textový procesor pripravuje strany na tlač a vkladá ich do radu, z ktorého ich tlačiareň (resp. jej ovládač) postupne vyberá.
 
* Sekvenčne vykonávané výpočtové úlohy čakajú v rade na spustenie.
 
* Zákazníci čakajú na zákazníckej linke na voľného operátora.
 
* Pasažieri na standby čakajú na voľné miesto v lietadle.
 
 
 
Zásobník sa, ako o niečo implementačne jednoduchší koncept, zvyčajne používa v situáciách, keď na poradí spracúvania nezáleží, alebo keď je žiadúce vstupné poradie obrátiť. Najvýznamnejší príklad situácie druhého typu je nasledujúci:
 
* Operačný systém ukladá lokálne premenné volaných funkcií na tzv. ''zásobníku volaní'' (angl. ''call stack''), čo umožňuje používanie rekurzie.
 
* Rekurzívne programy sa dajú prepísať na nerekurzívne pomocou &bdquo;ručne vytvoreného&rdquo; zásobníka (na budúcej prednáške si ako príklad ukážeme nerekurzívnu verziu triedenia ''Quick Sort'').
 
 
 
=== Príklad: kontrola uzátvorkovania ===
 
 
 
Ako jednoduchý príklad na použitie zásobníka uvažujme nasledujúcu situáciu: na vstupe je daný reťazec pozostávajúci (okrem prípadných ďalších znakov, ktoré možno ignorovať) zo zátvoriek <tt>(,),[,],{,}</tt>. Úlohou je zistiť, či je tento reťazec dobre uzátvorkovaný. To znamená, že:
 
* Pre každú uzatváraciu zátvorku musí byť posledná dosiaľ neuzavretá otváracia zátvorka rovnakého typu, pričom musí existovať aspoň jedna dosiaľ neuzavretá zátvorka.
 
* Každá otváracia zátvorka musí byť niekedy neskôr uzavretá.
 
 
 
Príklady očakávaného vstupu a výstupu:
 
 
<pre>
 
<pre>
()
+
void init(stack &s);
Retazec je dobre uzatvorkovany
+
bool isEmpty(stack &s);
 
+
void push(stack &s, dataType item); // vlozenie prvku
nejaky text bez zatvoriek
+
dataType pop(stack &s);    // vyber prvku
Retazec je dobre uzatvorkovany
+
dataType peek(stack &s);
 
+
void destroy(stack &s);
[((({}[])[]))]()
 
Retazec je dobre uzatvorkovany
 
 
 
[[#))
 
Retazec nie je dobre uzatvorkovany
 
 
 
())(
 
Retazec nie je dobre uzatvorkovany
 
 
 
((
 
Retazec nie je dobre uzatvorkovany
 
  
((cokolvek
+
void init(queue &q);
Retazec nie je dobre uzatvorkovany
+
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>
  
Túto úlohu rieši nasledujúci program, ktorý postupne prechádza cez vstupný reťazec, pričom pre každú otváraciu zátvorku si na zásobník pridá uzatváraciu zátvorku rovnakého typu. Ak narazí na uzatváraciu zátvorku, výraz môže byť dobre uzátvorkovaný len v prípade, že je na zásobníku aspoň jedna zátvorka, pričom zátvorka na vrchu zásobníka sa zhoduje so zátvorkou na vstupe. V prípade úspešného prechodu cez celý vstup je reťazec dobre uzátvorkovaný práve vtedy, keď na zásobníku nezostala žiadna zátvorka.
+
* 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é
  
<syntaxhighlight lang="C++">
+
Videli sme tiež, že obidve štruktúry sa dajú použiť na ukladanie dát alebo úloh, ktoré ešte treba vyriešiť
#include <iostream>
+
* Dnes uvidíme ďalšie príklady
#include <cassert>
 
using namespace std;
 
  
typedef char dataType;
 
  
/* Sem pride definicia struktury stack a vsetkych potrebnych funkcii. */
+
== Použitie zásobníka a radu: nerekurzívny Quick Sort ==
  
int main(void) {
+
Pripomeňme si triedenie Quick Sort z [[Prednáška 11#Quick_Sort|11. prednášky]]:
    char vyraz[100];
 
    cin.getline(vyraz, 100);
 
   
 
    stack s;
 
    init(s);
 
   
 
    bool dobre = true;
 
   
 
    for (int i = 0; vyraz[i] != 0; i++) {
 
        switch (vyraz[i]) {
 
            case '(':
 
                push(s, ')');
 
                break;
 
            case '[':
 
                push(s, ']');
 
                break;
 
            case '{':
 
                push(s, '}');
 
                break;
 
            case ')':
 
            case ']':
 
            case '}':
 
                if (isEmpty(s)) {
 
                    dobre = false;
 
                } else {
 
                    char c = pop(s);
 
                    if (c != vyraz[i]) {
 
                        dobre = false;
 
                    }
 
                }
 
                break;
 
        }
 
    }
 
   
 
    dobre = dobre && isEmpty(s);
 
       
 
    destroy(s);
 
   
 
    if (dobre) {
 
        cout << "Retazec je dobre uzatvorkovany." << endl;
 
    } else {
 
        cout << "Retazec nie je dobre uzatvorkovany." << endl;
 
    }
 
   
 
    return 0;
 
}
 
</syntaxhighlight>
 
 
 
''Cvičenie:'' Prepíšte program na kontrolu zátvoriek do rekurzívnej podoby. Použite pritom iba premenné typu <tt>char</tt>; špeciálne nepoužívajte žiadne polia. Reťazec načítavajte pomocou funkcií <tt>getc</tt> a <tt>ungetc</tt>. Môžete predpokladať, že je ukončený koncom riadku.
 
 
 
== Príklad na použitie zásobníka: nerekurzívny ''Quick Sort'' ==
 
 
 
Pripomeňme si triedenie ''Quick Sort'' z [[#Prednáška 11|11. prednášky]]:
 
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 188: Riadok 40:
 
}
 
}
  
int partition(int a[], int low, int high) {
+
int partition(int a[], int left, int right) {
     int pivot = a[low];
+
     int pivot = a[left];    
     int lastSmaller = low;
+
     int lastSmaller = left;
 
      
 
      
     for (int unknown = low + 1; unknown <= high; unknown++) {
+
     for (int unknown = left + 1; unknown <= right; unknown++) {
 
         if (a[unknown] < pivot) {
 
         if (a[unknown] < pivot) {
 
             lastSmaller++;
 
             lastSmaller++;
Riadok 198: Riadok 50:
 
         }
 
         }
 
     }   
 
     }   
     swap(a[low],a[lastSmaller]);  
+
     swap(a[left],a[lastSmaller]);  
 
     return lastSmaller;
 
     return lastSmaller;
 
}
 
}
  
void quicksort(int a[], int low, int high) {
+
void quicksort(int a[], int left, int right) {
     if (low >= high) {
+
     if (left >= right) {
         return;
+
         return;  
 
     }
 
     }
 
      
 
      
     int mid = partition(a, low, high);
+
     int middle = partition(a, left, right);
 
          
 
          
     quicksort(a, low, mid-1);
+
     quicksort(a, left, middle-1);
     quicksort(a, mid+1, high);   
+
     quicksort(a, middle+1, right);   
 
}
 
}
  
int main(void) {
+
int main() {
 
   // ...
 
   // ...
 
   quicksort(a, 0, N-1);
 
   quicksort(a, 0, N-1);
Riadok 224: Riadok 76:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
struct usek {
 
struct usek {
     int low;
+
     int left;
     int high;
+
     int right;
 
};
 
};
  
Riadok 239: Riadok 91:
 
      
 
      
 
     usek u;
 
     usek u;
     u.low = 0;
+
     u.left = 0;
     u.high = n-1;
+
     u.right = n-1;
 
     push(s,u);
 
     push(s,u);
 
      
 
      
 
     while (!isEmpty(s)) {
 
     while (!isEmpty(s)) {
 
         u = pop(s);
 
         u = pop(s);
         if (u.low >= u.high) {
+
        // vynechame useky dlzky 0 a 1
 +
         if (u.left >= u.right) {
 
             continue;
 
             continue;
 
         }
 
         }
 
          
 
          
         int mid = partition(a, u.low, u.high);
+
         int middle = partition(a, u.left, u.right);
 
          
 
          
 
         usek u1;
 
         usek u1;
         u1.low = u.low;
+
         u1.left = u.left;
         u1.high = mid-1;
+
         u1.right = middle - 1;
 
         usek u2;
 
         usek u2;
         u2.low = mid+1;
+
         u2.left = middle + 1;
         u2.high = u.high;
+
         u2.right = u.right;
 
         push(s,u2);
 
         push(s,u2);
 
         push(s,u1);
 
         push(s,u1);
Riadok 264: Riadok 117:
 
}
 
}
  
int main(void) {
+
int main() {
 
   // ...
 
   // ...
 
   quicksort(a, N);
 
   quicksort(a, N);
Riadok 271: Riadok 124:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Tento program triedi úseky v rovnakom poradí, ako rekurzívny Quicksort, 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í.
+
* 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.
<!--* 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ď-->
+
* 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''?
+
'''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í==
Riadok 311: Riadok 165:
 
===Základ programu===
 
===Základ programu===
  
Funkciu na vyfarbovanie súvislých oblastí budeme dorábať do nasledujúcej kostry programu, ktorá 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]]. Nasledujúca kostra tiež obsahuje funkciu <tt>main</tt>, ktorá načíta maticu zo súboru <tt>vstup.txt</tt> a následne zatiaľ len vykreslí ňou reprezentovaný obrazec do súboru <tt>matica.svg</tt>.
+
Funkciu na vyfarbovanie súvislých oblastí budeme dorábať do nasledujúcej kostry programu, ktorá 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]]. Nasledujúca kostra tiež obsahuje funkciu <tt>main</tt>, ktorá načíta maticu zo súboru <tt>vstup.txt</tt> a následne zatiaľ len vykreslí ňou reprezentovaný obrazec do súboru <tt>matica.svg</tt>.
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
Riadok 327: Riadok 181:
 
     int **a;
 
     int **a;
 
     a = new int *[m];
 
     a = new int *[m];
     for (int i = 0; i <= m - 1; i++) {
+
     for (int i = 0; i < m; i++) {
 
         a[i] = new int[n];
 
         a[i] = new int[n];
 
     }
 
     }
Riadok 334: Riadok 188:
  
 
/* Uvolni pamat matice a s n riadkami a m stlpcami. */
 
/* Uvolni pamat matice a s n riadkami a m stlpcami. */
void zmazMaticu(int m, int n, int **a) {
+
void zmazMaticu(int **a, int m) {
     for (int i = 0; i <= m - 1; i++) {
+
     for (int i = 0; i < m; i++) {
 
         delete[] a[i];
 
         delete[] a[i];
 
     }
 
     }
Riadok 350: Riadok 204:
  
 
/* Vykresli maticu a s n riadkami a m stlpcami. */
 
/* Vykresli maticu a s n riadkami a m stlpcami. */
void vykresliMaticu(int m, int n, int **a, SVGdraw &drawing) {
+
void vykresliMaticu(int **a, int m, int n, SVGdraw &drawing) {
     for (int i = 0; i <= m - 1; i++) {
+
     for (int i = 0; i < m; i++) {
         for (int j = 0; j <= n - 1; 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 359: Riadok 213:
  
 
/* 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, prvky matice a s n riadkami a m stlpcami. */
void nacitajMaticu(FILE *fr, int m, int n, int **a) {
+
void nacitajMaticu(FILE *fr, int **a, int m, int n) {
 
     assert(fr != NULL);
 
     assert(fr != NULL);
     for (int i = 0; i <= m - 1; i++) {
+
     for (int i = 0; i < m; i++) {
         for (int j = 0; j <= n - 1; j++) {
+
         for (int j = 0; j < n; j++) {
 
             fscanf(fr, "%d", &a[i][j]);
 
             fscanf(fr, "%d", &a[i][j]);
 
         }
 
         }
Riadok 368: Riadok 222:
 
}
 
}
  
int main(void) {
+
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);               // nacitaj rozmery matice
+
     fscanf(fr, "%d %d", &m, &n);
    fscanf(fr, "%d", &n);
+
    // vytvorenie matice a nacitanie jej prvkov 
 
     int **a = vytvorMaticu(m, n);
 
     int **a = vytvorMaticu(m, n);
     nacitajMaticu(fr, m, n, a);        // nacitaj jednotlive prvky matice
+
     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, a, drawing);
+
     vykresliMaticu(a, m, n, drawing);
  
 
     drawing.finish();
 
     drawing.finish();
     zmazMaticu(m, n, a);
+
     zmazMaticu(a, m);
   
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 395: Riadok 249:
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
const double pauza = 0.3;  // pauza po kazdom kroku vysvetlovania v sekundach
+
const double pauza = 0.3;  // pauza po kazdom kroku vyfarbovania v sekundach
  
/* Prefarbi suvislu jednofarebnu oblast obsahujucu poziciu (riadok,stlpec) na farbu s cislom farba. */
+
/* Prefarbi suvislu jednofarebnu oblast obsahujucu  
void vyfarbi(int m, int n, int **a, int riadok, int stlpec, int farba, SVGdraw &drawing) {
+
* 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 404: Riadok 260:
 
     }
 
     }
 
     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, a, riadok - 1, stlpec, farba, drawing);
+
         vyfarbi(a, m, n, riadok - 1, stlpec, farba, drawing);
 
     }
 
     }
     if (riadok + 1 <= m - 1 && a[riadok + 1][stlpec] == staraFarba) {
+
     if (riadok + 1 < m && a[riadok + 1][stlpec] == staraFarba) {
         vyfarbi(m, n, a, riadok + 1, stlpec, farba, drawing);
+
         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, a, riadok, stlpec - 1, farba, drawing);
+
         vyfarbi(a, m, n, riadok, stlpec - 1, farba, drawing);
 
     }
 
     }
     if (stlpec + 1 <= n - 1 && a[riadok][stlpec + 1] == staraFarba) {
+
     if (stlpec + 1 < n && a[riadok][stlpec + 1] == staraFarba) {
         vyfarbi(m, n, a, riadok, stlpec + 1, farba, drawing);
+
         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);
 
}  
 
}  
Riadok 425: Riadok 283:
 
Funkcia <tt>main</tt> potom môže vyzerať napríklad nasledovne:
 
Funkcia <tt>main</tt> potom môže vyzerať napríklad nasledovne:
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
int main(void) {
+
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);               // nacitaj rozmery matice
+
     fscanf(fr, "%d %d", &m, &n);
    fscanf(fr, "%d", &n);
+
    // vytvorenie matice a nacitanie jej prvkov 
 
     int **a = vytvorMaticu(m, n);
 
     int **a = vytvorMaticu(m, n);
     nacitajMaticu(fr, m, n, a);        // nacitaj jednotlive prvky matice
+
     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, a, drawing);
+
     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, a, riadok, stlpec, 4, drawing);
+
     vyfarbi(a, m, n, riadok, stlpec, 4, drawing);
  
 
     drawing.finish();
 
     drawing.finish();
     zmazMaticu(m, n, a);
+
     zmazMaticu(a, m);
   
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* [[:Image:Animacia1.svg|Výsledná animácia]]
+
* [[:Media:Animacia1.svg|Výsledná animácia]]
  
 
===Počítanie ostrovov===
 
===Počítanie ostrovov===
Riadok 481: Riadok 340:
 
</gallery>
 
</gallery>
  
Do programu z vyššia teda dorobíme funkciu  
+
Do programu vyššie teda dorobíme funkciu  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
int najdiOstrovy(int m, int n, int **a, SVGdraw &drawing) {
+
int najdiOstrovy(int **a, int m, int n, SVGdraw &drawing) {
 
     int ostrovov = 0;
 
     int ostrovov = 0;
     for (int i = 0; i <= m - 1; i++) {
+
     for (int i = 0; i < m; i++) {
         for (int j = 0; j <= n - 1; j++) {
+
         for (int j = 0; j < n; j++) {
 
             if (a[i][j] == 3) {
 
             if (a[i][j] == 3) {
 
                 ostrovov++;
 
                 ostrovov++;
                 vyfarbi(m, n, a, i, j, 4, drawing);
+
                 vyfarbi(a, m, n, i, j, 4, drawing);
 
             }
 
             }
 
         }
 
         }
Riadok 499: Riadok 358:
 
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(void) {
+
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);
    fscanf(fr, "%d", &n);
 
 
     int **a = vytvorMaticu(m, n);
 
     int **a = vytvorMaticu(m, n);
     nacitajMaticu(fr, m, n, a);
+
     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, a, drawing);
+
     vykresliMaticu(a, m, n, drawing);
  
     int pocetOstrovov = najdiOstrovy(m, n, a, drawing);
+
     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, n, a);
+
     zmazMaticu(a, m);
   
 
    return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* [[:Image:Animacia2.svg|Výsledná animácia]]
+
* [[:Media:Animacia2.svg|Výsledná animácia]]
  
 
''Cvičenie:'' upravte funkciu <tt>najdiOstrovy</tt> tak, aby ešte navyše zistila, či má niektorý z ostrovov jazero.
 
''Cvičenie:'' upravte funkciu <tt>najdiOstrovy</tt> tak, aby ešte navyše zistila, či má niektorý z ostrovov jazero.
Riadok 528: Riadok 384:
 
===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 [[#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. Ak ešte nebolo ofarbené, ofarbí ho a vloží na zásobník všetkých jeho susedov, ktorých je ešte potrebné ofarbiť.   
+
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. Ak ešte nebolo ofarbené, ofarbí ho a vloží na zásobník všetkých jeho susedov, ktorých je ešte potrebné ofarbiť.   
  
 
Drobnou zmenou bude, že 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.
 
Drobnou zmenou bude, že 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.
Riadok 539: Riadok 395:
  
  
/* Sem pride definicia struktury pre zasobnik a funkcii poskytovanych zasobnikom. */
+
/* Sem pride definicia struktury pre zasobnik  
 +
* a funkcii poskytovanych zasobnikom. */
  
  
Riadok 545: Riadok 402:
 
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, int **a, int riadok, int stlpec, int farba, SVGdraw &drawing) {
+
* 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 563: Riadok 422:
 
         p = pop(s);
 
         p = pop(s);
 
         if (a[p.riadok][p.stlpec] == farba) {
 
         if (a[p.riadok][p.stlpec] == farba) {
 +
            // uz sme medzitym policko prefarbili z ineho smeru
 
             continue;
 
             continue;
 
         }
 
         }
 
         a[p.riadok][p.stlpec] = farba;
 
         a[p.riadok][p.stlpec] = farba;
         vykresliStvorcek(p.riadok, p.stlpec, farby[farba], "lightgrey", drawing);
+
         vykresliStvorcek(p.riadok, p.stlpec, farby[farba],  
 +
                        "lightgrey", drawing);
 
         drawing.wait(pauza);
 
         drawing.wait(pauza);
 
         for (int i = 0; i <= 3; i++) {
 
         for (int i = 0; i <= 3; i++) {
Riadok 572: Riadok 433:
 
             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 - 1 && sused.stlpec >= 0 && sused.stlpec <= n - 1 &&
+
             if (sused.riadok >= 0 && sused.riadok < m
                    a[sused.riadok][sused.stlpec] == staraFarba) {
+
                && sused.stlpec >= 0 && sused.stlpec < n
 +
                && a[sused.riadok][sused.stlpec] == staraFarba) {
 
                 push(s, sused);     
 
                 push(s, sused);     
 
             }         
 
             }         
Riadok 582: Riadok 444:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* [[:Image:Animacia3.svg|Výsledná animácia]]
+
* [[:Media:Animacia3.svg|Výsledná animácia]]
  
 
===Vyfarbovanie s použitím radu===
 
===Vyfarbovanie s použitím radu===
  
Namiesto zásobníka môžeme použiť aj rad &ndash; obrazec sa potom bude vyfarbovať v poradí podľa vzdialenosti od počiatočného políčka. Pôjde o takzvané ''prehľadávanie do šírky'', kým rekurzívna verzia a verzia so zásobníkom zodpovedajú takzvanému ''prehľadávaniu do hĺbky''.
+
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. Pôjde o takzvané ''prehľadávanie do šírky'', kým rekurzívna verzia a verzia so zásobníkom zodpovedajú takzvanému ''prehľadávaniu do hĺbky''.
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
struct policko {
 
struct policko {
Riadok 595: Riadok 457:
  
  
/* Sem pride definicia struktury pre rad a funkcii poskytovanych radom. */
+
/* Sem pride definicia struktury pre rad  
 +
* a funkcii poskytovanych radom. */
  
  
Riadok 601: Riadok 464:
 
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, int **a, int riadok, int stlpec, int farba, SVGdraw &drawing) {
+
* 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 622: Riadok 487:
 
         }
 
         }
 
         a[p.riadok][p.stlpec] = farba;
 
         a[p.riadok][p.stlpec] = farba;
         vykresliStvorcek(p.riadok, p.stlpec, farby[farba], "lightgrey", drawing);
+
         vykresliStvorcek(p.riadok, p.stlpec, farby[farba],  
 +
                        "lightgrey", drawing);
 
         drawing.wait(pauza);
 
         drawing.wait(pauza);
 
         for (int i = 0; i <= 3; i++) {
 
         for (int i = 0; i <= 3; i++) {
Riadok 628: Riadok 494:
 
             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 - 1 && sused.stlpec >= 0 && sused.stlpec <= n - 1 &&
+
             if (sused.riadok >= 0 && sused.riadok < m
                    a[sused.riadok][sused.stlpec] == staraFarba) {
+
                && sused.stlpec >= 0 && sused.stlpec < n
 +
                && a[sused.riadok][sused.stlpec] == staraFarba) {
 
                 enqueue(q, sused);     
 
                 enqueue(q, sused);     
 
             }         
 
             }         
Riadok 638: Riadok 505:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* [[:Image:Animacia4.svg|Výsledná animácia]]
+
* [[: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 516:
  
  
/* Sem pride definicia struktury pre rad a funkcii poskytovanych radom. */
+
/* Sem pride definicia struktury pre rad  
 +
* a funkcii poskytovanych radom. */
  
  
void vypisVzdialenost(int i, int j, int vzd, const char *farbaTextu, SVGdraw &drawing) {
+
void vypisVzdialenost(int i, int j, int vzd,  
 +
                      const char *farbaTextu, SVGdraw &drawing) {
 
     drawing.setLineColor(farbaTextu);
 
     drawing.setLineColor(farbaTextu);
 
     drawing.setFontSize(20);
 
     drawing.setFontSize(20);
Riadok 663: Riadok 532:
 
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, int **a, int riadok, int stlpec, int farba, SVGdraw &drawing) {
+
* 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 685: Riadok 556:
 
         }
 
         }
 
         a[p.riadok][p.stlpec] = farba;
 
         a[p.riadok][p.stlpec] = farba;
         vykresliStvorcek(p.riadok, p.stlpec, farby[farba], "lightgrey", drawing);
+
         vykresliStvorcek(p.riadok, p.stlpec, farby[farba],  
         vypisVzdialenost(p.riadok, p.stlpec, p.vzd, "white", drawing);
+
                        "lightgrey", drawing);
 +
         vypisVzdialenost(p.riadok, p.stlpec, p.vzd,  
 +
                        "white", drawing);
 
         drawing.wait(pauza);
 
         drawing.wait(pauza);
 
         for (int i = 0; i <= 3; i++) {
 
         for (int i = 0; i <= 3; i++) {
Riadok 692: Riadok 565:
 
             sused.riadok = p.riadok + deltaRiadok[i];
 
             sused.riadok = p.riadok + deltaRiadok[i];
 
             sused.stlpec = p.stlpec + deltaStlpec[i];
 
             sused.stlpec = p.stlpec + deltaStlpec[i];
            sused.vzd = p.vzd + 1;
+
             if (sused.riadok >= 0 && sused.riadok < m
             if (sused.riadok >= 0 && sused.riadok <= m - 1 && sused.stlpec >= 0 && sused.stlpec <= n - 1 &&
+
                && sused.stlpec >= 0 && sused.stlpec < n
                    a[sused.riadok][sused.stlpec] == staraFarba) {
+
                && a[sused.riadok][sused.stlpec] == staraFarba) {
 +
                sused.vzd = p.vzd + 1;
 
                 enqueue(q, sused);     
 
                 enqueue(q, sused);     
 
             }         
 
             }         
Riadok 703: Riadok 577:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* [[:Image:Animacia5.svg|Výsledná animácia]]
+
* [[: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 15:42, 26. november 2023

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 obrazec daný obdĺžnikovou maticou o m riadkoch a n stĺpcoch. Obdĺžnikové plátno je v takom prípade rozdelené na m krát n „štvorčekov” určitej konštantnej veľkosti, pričom jednotlivé prvky matice zodpovedajú farbám jednotlivých týchto štvorčekov. 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 obrazec

Matica1.png

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) – to znamená pre „š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:

Matica2.png

Podobný problém je napríklad často potrebné riešiť v rôznych nástrojoch na prácu s grafikou (kde sa namiesto „štvorčekov” ofarbujú pixely) a podobne.

Základ programu

Funkciu na vyfarbovanie súvislých oblastí budeme dorábať do nasledujúcej kostry programu, ktorá 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. Nasledujúca kostra tiež obsahuje funkciu main, ktorá načíta maticu zo súboru vstup.txt a následne zatiaľ len vykreslí ňou reprezentovaný obrazec do súboru matica.svg.

#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

/* 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]);
        }
    }
}

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);

    drawing.finish();
    zmazMaticu(a, m);
}

Rekurzívne vyfarbovanie

Vyfarbovanie súvislých oblastí potom môžeme realizovať napríklad nasledujúcou rekurzívnou funkciou vyfarbi, ktorá vždy na cieľovú farbu farba prefarbí políčko so súradnicami (riadok, stlpec) a následne sa rekurzívne zavolá pre všetkých susedov tohto políčka, ktoré sú zafarbené pôvodnou farbou prefarbovanej oblasti.

Za každým vyfarbením „štvorčeka” navyše voláme funkciu drawing.wait s parametrom pauza, čo je konštanta, ktorú na úvod nastavíme na 0,3 sekundy. Výsledný SVG súbor tak bude obsahovať animáciu postupného vyfarbovania jednotlivých políčok. Farbou „rámika” okolo políčka budeme navyše rozlišovať, či už bolo dané políčko úplne spracované (t. j. či sa už ukončilo rekurzívne volanie funkcie vyfarbi pre toto políčko).

const double pauza = 0.3;   // pauza po kazdom kroku vyfarbovania v sekundach

/* 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);
}

Funkcia main potom môže vyzerať napríklad nasledovne:

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

Obrazec, 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 funkciu najdiOstrovy tak, aby ešte navyše zistila, či má niektorý z ostrovov jazero.

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. Ak ešte nebolo ofarbené, ofarbí ho a vloží na zásobník všetkých jeho susedov, ktorých je ešte potrebné ofarbiť.

Drobnou zmenou bude, že 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.

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;
    }
    
    stack s;
    init(s);
    
    policko p;
    p.riadok = riadok;
    p.stlpec = stlpec;
    push(s, p);
    
    while (!isEmpty(s)) {
        p = pop(s);
        if (a[p.riadok][p.stlpec] == farba) {
            // uz sme medzitym policko prefarbili z ineho smeru
            continue;
        }
        a[p.riadok][p.stlpec] = farba;
        vykresliStvorcek(p.riadok, p.stlpec, farby[farba], 
                         "lightgrey", drawing);
        drawing.wait(pauza);
        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) {
                push(s, sused);    
            }        
        }
    }
    destroy(s);
}

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. Pôjde o takzvané prehľadávanie do šírky, kým rekurzívna verzia a verzia so zásobníkom zodpovedajú takzvanému 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;
    }
    
    queue q;
    init(q);
    
    policko p;
    p.riadok = riadok;
    p.stlpec = stlpec;
    enqueue(q, p);
    
    while (!isEmpty(q)) {
        p = dequeue(q);
        if (a[p.riadok][p.stlpec] == farba) {
            continue;
        }
        a[p.riadok][p.stlpec] = farba;
        vykresliStvorcek(p.riadok, p.stlpec, farby[farba], 
                         "lightgrey", drawing);
        drawing.wait(pauza);
        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) {
                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(int i, int j, int vzd, 
                      const char *farbaTextu, SVGdraw &drawing) {
    drawing.setLineColor(farbaTextu);
    drawing.setFontSize(20);
    char text[15];
    sprintf(text, "%d", vzd);
    drawing.drawText((j + 0.5) * stvorcek, (i + 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);
    
    while (!isEmpty(q)) {
        p = dequeue(q);
        if (a[p.riadok][p.stlpec] == farba) {
            continue;
        }
        a[p.riadok][p.stlpec] = farba;
        vykresliStvorcek(p.riadok, p.stlpec, farby[farba], 
                         "lightgrey", drawing);
        vypisVzdialenost(p.riadok, p.stlpec, p.vzd, 
                         "white", drawing);
        drawing.wait(pauza);
        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;
                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.