Programovanie (1) v C/C++
1-INF-127, ZS 2024/25
2015/16 Programovanie (2) v Jave: Rozdiel medzi revíziami
Riadok 1: | Riadok 1: | ||
− | * [[Letný semester, úvodné informácie]] | + | * [[#Letný semester, úvodné informácie]] |
− | * [[Letný semester, pravidlá]] | + | * [[#Letný semester, pravidlá]] |
− | * [[Letný semester, test a skúška]] | + | * [[#Letný semester, test a skúška]] |
− | * [[GraphGUI]] (knižnica ku skúške) | + | * [[#GraphGUI]] (knižnica ku skúške) |
− | * [[Letný semester, projekt]] | + | * [[#Letný semester, projekt]] |
{| | {| | ||
Riadok 9: | Riadok 9: | ||
|style="background:#f0f0f0" |'''Týždeň 15.-21.2.''' Úvod do Javy | |style="background:#f0f0f0" |'''Týždeň 15.-21.2.''' Úvod do Javy | ||
|- | |- | ||
− | | [[Prednáška 25]] '''·''' [[Cvičenia 14]] | + | | [[#Prednáška 25]] '''·''' [[#Cvičenia 14]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 22.2.-28.2.''' Úvod do objektovo-orientovaného programovania, JavaDoc | |style="background:#f0f0f0" |'''Týždeň 22.2.-28.2.''' Úvod do objektovo-orientovaného programovania, JavaDoc | ||
|- | |- | ||
− | | [[Prednáška 26]] '''·''' [[Cvičenia 15]] | + | | [[#Prednáška 26]] '''·''' [[#Cvičenia 15]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 29.2.-6.3.''' Dedenie, polymorfizmus, modifikátory, rozhrania | |style="background:#f0f0f0" |'''Týždeň 29.2.-6.3.''' Dedenie, polymorfizmus, modifikátory, rozhrania | ||
|- | |- | ||
− | | [[Prednáška 27]] '''·''' [[Cvičenia 16]] | + | | [[#Prednáška 27]] '''·''' [[#Cvičenia 16]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 7.-13.3.''' Výnimky, generické programovanie | |style="background:#f0f0f0" |'''Týždeň 7.-13.3.''' Výnimky, generické programovanie | ||
|- | |- | ||
− | | [[Prednáška 28]] '''·''' [[Cvičenia 17]] | + | | [[#Prednáška 28]] '''·''' [[#Cvičenia 17]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 14.-20.3.''' Collections, testovanie | |style="background:#f0f0f0" |'''Týždeň 14.-20.3.''' Collections, testovanie | ||
|- | |- | ||
− | | [[Prednáška 29]] '''·''' [[Cvičenia 18]] | + | | [[#Prednáška 29]] '''·''' [[#Cvičenia 18]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 21.-27.3.''' Opakovanie OOP, úvod k JavaFX | |style="background:#f0f0f0" |'''Týždeň 21.-27.3.''' Opakovanie OOP, úvod k JavaFX | ||
|- | |- | ||
− | | [[Prednáška 30]] '''·''' [[Cvičenia 19]] | + | | [[#Prednáška 30]] '''·''' [[#Cvičenia 19]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 28.3.-3.4.''' Veľká Noc | |style="background:#f0f0f0" |'''Týždeň 28.3.-3.4.''' Veľká Noc | ||
Riadok 36: | Riadok 36: | ||
|style="background:#f0f0f0" |'''Týždeň 4.-10.4.''' JavaFX (kreslenie) | |style="background:#f0f0f0" |'''Týždeň 4.-10.4.''' JavaFX (kreslenie) | ||
|- | |- | ||
− | | [[Prednáška 31]] '''·''' [[Cvičenia 20]] | + | | [[#Prednáška 31]] '''·''' [[#Cvičenia 20]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 11.-17.4.''' JavaFX (zložitejšie prvky, viacoknové aplikácie) | |style="background:#f0f0f0" |'''Týždeň 11.-17.4.''' JavaFX (zložitejšie prvky, viacoknové aplikácie) | ||
|- | |- | ||
− | | [[Prednáška 32]] '''·''' [[Cvičenia 21]] | + | | [[#Prednáška 32]] '''·''' [[#Cvičenia 21]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 18.-24.4.''' Reprezentácia grafov, prehľadávanie do hĺbky | |style="background:#f0f0f0" |'''Týždeň 18.-24.4.''' Reprezentácia grafov, prehľadávanie do hĺbky | ||
|- | |- | ||
− | | [[Prednáška 33]] '''·''' [[Cvičenia 22]] | + | | [[#Prednáška 33]] '''·''' [[#Cvičenia 22]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 25.4.-1.5.''' Prehľadávanie do šírky, ohodnotené grafy, najdlhšia cesta | |style="background:#f0f0f0" |'''Týždeň 25.4.-1.5.''' Prehľadávanie do šírky, ohodnotené grafy, najdlhšia cesta | ||
|- | |- | ||
− | | [[Prednáška 34]] '''·''' [[Cvičenia 23]] | + | | [[#Prednáška 34]] '''·''' [[#Cvičenia 23]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 2.-8.5.''' Maximálna klika, orientované grafy, topologické triedenie | |style="background:#f0f0f0" |'''Týždeň 2.-8.5.''' Maximálna klika, orientované grafy, topologické triedenie | ||
|- | |- | ||
− | | [[Prednáška 35]] '''·''' [[Cvičenia 24]] | + | | [[#Prednáška 35]] '''·''' [[#Cvičenia 24]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 9.-15.5.''' Zhrnutie, informácie k písomke a skúške, knižnica na skúšku | |style="background:#f0f0f0" |'''Týždeň 9.-15.5.''' Zhrnutie, informácie k písomke a skúške, knižnica na skúšku | ||
|- | |- | ||
− | | [[Prednáška 36]] '''·''' [[Cvičenia 25]] | + | | [[#Prednáška 36]] '''·''' [[#Cvičenia 25]] |
|- | |- | ||
|style="background:#f0f0f0" |'''Týždeň 16.-22.5.''' OOP v C++ | |style="background:#f0f0f0" |'''Týždeň 16.-22.5.''' OOP v C++ | ||
|- | |- | ||
− | | [[Prednáška 37]] '''·''' [[Cvičenia 26]] | + | | [[#Prednáška 37]] '''·''' [[#Cvičenia 26]] |
|- | |- | ||
|} | |} | ||
Riadok 139: | Riadok 139: | ||
* Projekt robia dvojice, výnimočne aj jednotlivci. | * Projekt robia dvojice, výnimočne aj jednotlivci. | ||
* Projekt sa bude odovzdávať v prvom týždni skúškového obdobia. | * Projekt sa bude odovzdávať v prvom týždni skúškového obdobia. | ||
− | * Témy projektov a podrobnejšie pravidlá nájdete na [[Letný semester, projekt|zvláštnej stránke]]. | + | * Témy projektov a podrobnejšie pravidlá nájdete na [[#Letný semester, projekt|zvláštnej stránke]]. |
* Na získanie bodov za projekt musí byť vaša práca dostatočne rozsiahla a kvalitná, v opačnom prípade získate 0 bodov. | * Na získanie bodov za projekt musí byť vaša práca dostatočne rozsiahla a kvalitná, v opačnom prípade získate 0 bodov. | ||
Riadok 177: | Riadok 177: | ||
* Navyše si môžete body z testu pre pokročilých nechať uznať ako body zo záverečného testu. Máte však aj možnosť písať záverečný test so spolužiakmi. | * Navyše si môžete body z testu pre pokročilých nechať uznať ako body zo záverečného testu. Máte však aj možnosť písať záverečný test so spolužiakmi. | ||
=Letný semester, test a skúška= | =Letný semester, test a skúška= | ||
− | Na túto stránku budeme postupne pridávať informácie týkajúce sa záverečného písomného testu a praktickej skúšky pri počítači v letnom semestri. Odporúčame tiež si preštudovať [[Letný semester, pravidlá|pravidlá predmetu]]. | + | Na túto stránku budeme postupne pridávať informácie týkajúce sa záverečného písomného testu a praktickej skúšky pri počítači v letnom semestri. Odporúčame tiež si preštudovať [[#Letný semester, pravidlá|pravidlá predmetu]]. |
==Organizácia skúšky== | ==Organizácia skúšky== | ||
Riadok 193: | Riadok 193: | ||
* Jeden príklad na JavaFX, jeden na grafy (väčšinou backtracking) | * Jeden príklad na JavaFX, jeden na grafy (väčšinou backtracking) | ||
− | * Obidva v [[GraphGUI]], pozrite si vopred pred skúškou | + | * Obidva v [[#GraphGUI]], pozrite si vopred pred skúškou |
* Zapíšte sa na termín v AIS, prihlasovanie/odhlasovanie do 14:00 deň pred skúškou | * Zapíšte sa na termín v AIS, prihlasovanie/odhlasovanie do 14:00 deň pred skúškou | ||
* Doneste si ťahák 1 list A4, ISIC, index, písacie potreby, papier | * Doneste si ťahák 1 list A4, ISIC, index, písacie potreby, papier | ||
Riadok 355: | Riadok 355: | ||
==Skúška pri počítači== | ==Skúška pri počítači== | ||
− | Na skúške pri počítači dostanete k dispozícii program [[GraphGUI]] | + | Na skúške pri počítači dostanete k dispozícii program [[#GraphGUI]] |
* Do programu budete pridávať riešenia dvoch úloh | * Do programu budete pridávať riešenia dvoch úloh | ||
* Tieto úlohy bude možné riešiť nezávisle na sebe a budú mať rovnakú váhu, nemusia však byť pre vás rovnako ťažké | * Tieto úlohy bude možné riešiť nezávisle na sebe a budú mať rovnakú váhu, nemusia však byť pre vás rovnako ťažké | ||
* V úlohe A bude treba doprogramovať niečo do grafického prostredia | * V úlohe A bude treba doprogramovať niečo do grafického prostredia | ||
− | ** Pozri napr. úlohu B na [[Cvičenia 26|cvičeniach 26]] | + | ** Pozri napr. úlohu B na [[#Cvičenia 26|cvičeniach 26]] |
* V úlohe B bude treba naprogramovať nejaký grafový algoritmus, väčšinou metódou prehľadávania s návratom | * V úlohe B bude treba naprogramovať nejaký grafový algoritmus, väčšinou metódou prehľadávania s návratom | ||
− | ** Pozri napr. úlohu D na [[Cvičenia 26|cvičeniach 26]], použité úlohy sa však budú menej podobať na algoritmy z prednášok. Viď napr. úlohu C o dominujúcej množine na [[Cvičenia 25|cvičeniach 25]] | + | ** Pozri napr. úlohu D na [[#Cvičenia 26|cvičeniach 26]], použité úlohy sa však budú menej podobať na algoritmy z prednášok. Viď napr. úlohu C o dominujúcej množine na [[#Cvičenia 25|cvičeniach 25]] |
* Odovzdávate iba súbory Editor.java (úloha A) a GraphAlgorithm.java (úloha B), celé vaše riešenie by teda malo byť v týchto súboroch. | * Odovzdávate iba súbory Editor.java (úloha A) a GraphAlgorithm.java (úloha B), celé vaše riešenie by teda malo byť v týchto súboroch. | ||
Riadok 2 512: | Riadok 2 512: | ||
==Stromy== | ==Stromy== | ||
− | Na [[Prednáška 26#Bin.C3.A1rny_vyh.C4.BEad.C3.A1vac.C3.AD_strom|prednáške 26]] je implementovaný binárny vyhľadávací strom pomocou tried <tt>Node</tt> a <tt>BinarySearchTree</tt>. Pridajte do triedy <tt>BinarySearchTree</tt> nasledujúce dve metódy, pričom podľa potreby pridajte aj metódy do triedy <tt>Node</tt>. Snažte sa čo najviac zachovať zapuzdrenie tried. | + | Na [[#Prednáška 26#Bin.C3.A1rny_vyh.C4.BEad.C3.A1vac.C3.AD_strom|prednáške 26]] je implementovaný binárny vyhľadávací strom pomocou tried <tt>Node</tt> a <tt>BinarySearchTree</tt>. Pridajte do triedy <tt>BinarySearchTree</tt> nasledujúce dve metódy, pričom podľa potreby pridajte aj metódy do triedy <tt>Node</tt>. Snažte sa čo najviac zachovať zapuzdrenie tried. |
* Metóda <tt>boolean contains(int data)</tt> zistí, či je v strome vrchol s hodnotou <tt>data</tt>. Inšpirujte sa metódou <tt>add</tt>. | * Metóda <tt>boolean contains(int data)</tt> zistí, či je v strome vrchol s hodnotou <tt>data</tt>. Inšpirujte sa metódou <tt>add</tt>. | ||
* Metóda <tt>int depth()</tt> vráti hĺbku stromu. Ak je strom prázdny, vráti -1. | * Metóda <tt>int depth()</tt> vráti hĺbku stromu. Ak je strom prázdny, vráti -1. | ||
Riadok 3 324: | Riadok 3 324: | ||
==Aritmetický strom== | ==Aritmetický strom== | ||
− | Do aritmetického stromu z [[Prednáška 27#Aritmetick.C3.BD_strom_s_vyu.C5.BEit.C3.ADm_dedenia|prednášky 27]] spravte nasledujúce zmeny: | + | Do aritmetického stromu z [[#Prednáška 27#Aritmetick.C3.BD_strom_s_vyu.C5.BEit.C3.ADm_dedenia|prednášky 27]] spravte nasledujúce zmeny: |
* Doprogramujte triedu <tt>Times</tt> pre násobenie a pridajte násobenie do testovacieho výrazu v metóde <tt>main</tt>. | * Doprogramujte triedu <tt>Times</tt> pre násobenie a pridajte násobenie do testovacieho výrazu v metóde <tt>main</tt>. | ||
* Pri vypisovaní (metóda <tt>toString</tt>) sa triedy <tt>Plus</tt> a <tt>Times</tt> správajú podobne, zmena je iba v znamienku. Naprogramujte teda <tt>toString</tt> už v triede <tt>BinaryNode</tt>, s tým, že bude volať pomocnú abstraktnú metódu <tt>operatorToString</tt>, ktorá vráti reťazcovú reprezentáciu príslušného znamienka (<tt>"+"</tt> alebo <tt>"*"</tt>) | * Pri vypisovaní (metóda <tt>toString</tt>) sa triedy <tt>Plus</tt> a <tt>Times</tt> správajú podobne, zmena je iba v znamienku. Naprogramujte teda <tt>toString</tt> už v triede <tt>BinaryNode</tt>, s tým, že bude volať pomocnú abstraktnú metódu <tt>operatorToString</tt>, ktorá vráti reťazcovú reprezentáciu príslušného znamienka (<tt>"+"</tt> alebo <tt>"*"</tt>) | ||
+ | <!-- | ||
== Riesenie - ArithmeticProgression == | == Riesenie - ArithmeticProgression == | ||
<pre> | <pre> | ||
Riadok 3 470: | Riadok 3 471: | ||
} | } | ||
</pre> | </pre> | ||
+ | --> | ||
=Prednáška 29= | =Prednáška 29= | ||
==Organizačné poznámky== | ==Organizačné poznámky== | ||
Riadok 4 639: | Riadok 4 641: | ||
+ | <!-- | ||
== Riešenia kazdyDruhy a prienik== | == Riešenia kazdyDruhy a prienik== | ||
<pre> | <pre> | ||
Riadok 4 783: | Riadok 4 786: | ||
} | } | ||
</pre> | </pre> | ||
+ | --> | ||
+ | |||
=Prednáška 31= | =Prednáška 31= | ||
== Organizačné poznámky == | == Organizačné poznámky == | ||
Riadok 5 804: | Riadok 5 809: | ||
* DÚ14 (JavaFX) je zverejnená, termín odovzdania streda 27.4. 22:00 | * DÚ14 (JavaFX) je zverejnená, termín odovzdania streda 27.4. 22:00 | ||
** Väčšia úloha, nenechávať si na poslednú chvíľu | ** Väčšia úloha, nenechávať si na poslednú chvíľu | ||
− | * Do pondelka 9.5. si môžete vybrať [[Letný_semester, | + | * Do pondelka 9.5. si môžete vybrať [[#Letný_semester, projekt|tému nepovinného projektu]] |
* V stredu 20.4. dekanské voľno kvôli študentskej vedeckej konferencii, odporúčame ísť si pozrieť aspoň niektoré prednášky ([http://compbio.fmph.uniba.sk/svk2016/program.php program]) | * V stredu 20.4. dekanské voľno kvôli študentskej vedeckej konferencii, odporúčame ísť si pozrieť aspoň niektoré prednášky ([http://compbio.fmph.uniba.sk/svk2016/program.php program]) | ||
** Študenti prezentujú výsledky svojho výskumu | ** Študenti prezentujú výsledky svojho výskumu | ||
Riadok 6 267: | Riadok 6 272: | ||
==Zdrojový kód programu, grafy== | ==Zdrojový kód programu, grafy== | ||
− | Program k prednáškam: [[Prednáška 33]] a [[Prednáška 34]] | + | Program k prednáškam: [[#Prednáška 33]] a [[#Prednáška 34]] |
* dve reprezentácie grafu, prehľadávanie do hĺbky a do šírky | * dve reprezentácie grafu, prehľadávanie do hĺbky a do šírky | ||
Riadok 7 411: | Riadok 7 416: | ||
==Programy== | ==Programy== | ||
Programy z tejto prednášky nájdete tu: | Programy z tejto prednášky nájdete tu: | ||
− | * Prehľadávanie do šírky: [[Prednáška 33#Zdrojový kód programu, grafy]] | + | * Prehľadávanie do šírky: [[#Prednáška 33#Zdrojový kód programu, grafy]] |
* [[#Zdrojový kód programu, prehľadávanie s návratom na neohodnotených grafoch]] | * [[#Zdrojový kód programu, prehľadávanie s návratom na neohodnotených grafoch]] | ||
* [[#Zdrojový kód programu, prehľadávanie s návratom na ohodnotených grafoch]] | * [[#Zdrojový kód programu, prehľadávanie s návratom na ohodnotených grafoch]] | ||
Riadok 8 063: | Riadok 8 068: | ||
* Nižšie je verzia prehľadávania do hĺbky, ktorá do poľa <tt>whenVisited</tt> čísluje vrcholy v poradí, v akom boli navštívené. Odsimulujte algoritmus na grafe nakreslenom vpravo a zistite, v akom poradí budú vrcholy navštívené, ak začneme prehľadávať vo vrchole 0. Predpokladajte, že <tt>adjVertices</tt> vracia susedov v poradí podľa čísla vrcholu. | * Nižšie je verzia prehľadávania do hĺbky, ktorá do poľa <tt>whenVisited</tt> čísluje vrcholy v poradí, v akom boli navštívené. Odsimulujte algoritmus na grafe nakreslenom vpravo a zistite, v akom poradí budú vrcholy navštívené, ak začneme prehľadávať vo vrchole 0. Predpokladajte, že <tt>adjVertices</tt> vracia susedov v poradí podľa čísla vrcholu. | ||
− | * Do reprezentácie grafu pomocou zoznamov susedov z [[Prednáška 33#Zdrojový kód programu, grafy|prednášky]] pridajte metódu <tt>addVertex</tt>, ktorá pridá do grafu nový vrchol. | + | * Do reprezentácie grafu pomocou zoznamov susedov z [[#Prednáška 33#Zdrojový kód programu, grafy|prednášky]] pridajte metódu <tt>addVertex</tt>, ktorá pridá do grafu nový vrchol. |
* Máme danú šachovnicu ''n x m'', na ktorej sú niektoré políčka obsadené figúrkami. Na políčku ''(i,j)'' stojí kôň. Na ktoré políčka šachovnice vie preskákať, pričom môže použiť aj viacero ťahov, ale môže chodiť iba po prázdnych políčkach? | * Máme danú šachovnicu ''n x m'', na ktorej sú niektoré políčka obsadené figúrkami. Na políčku ''(i,j)'' stojí kôň. Na ktoré políčka šachovnice vie preskákať, pričom môže použiť aj viacero ťahov, ale môže chodiť iba po prázdnych políčkach? | ||
Riadok 8 455: | Riadok 8 460: | ||
* V pondelok 9.5. cvičenia a prednáška | * V pondelok 9.5. cvičenia a prednáška | ||
* Prednáška: zhrnutie, príprava na test a skúšku, knižnica na skúšku | * Prednáška: zhrnutie, príprava na test a skúšku, knižnica na skúšku | ||
− | * Do budúceho pondelka si v prípade záujmu vyberte tému [[Letný semester, projekt|nepovinného projektu]]. | + | * Do budúceho pondelka si v prípade záujmu vyberte tému [[#Letný semester, projekt|nepovinného projektu]]. |
* Do stredy 11.5. DÚ16 | * Do stredy 11.5. DÚ16 | ||
Riadok 9 297: | Riadok 9 302: | ||
=Cvičenia 24= | =Cvičenia 24= | ||
Cieľom tohto cvičenia je precvičiť si prehľadávanie grafu do šírky a úlohy na backtracking na grafoch. | Cieľom tohto cvičenia je precvičiť si prehľadávanie grafu do šírky a úlohy na backtracking na grafoch. | ||
− | * Prehľadávanie do šírky nájdete v [[ | + | * Prehľadávanie do šírky nájdete v [[#Prednáška 33#Zdrojov.C3.BD_k.C3.B3d_programu.2C_grafy|programe pre prednášku 33]], trieda ShortestPaths |
− | * Program na hľadanie ciest dĺžky ''k'' nájdete v [[Prednáška 34#Zdrojov.C3.BD_k.C3.B3d_programu.2C_preh.C4.BEad.C3.A1vanie_s_n.C3.A1vratom_na_neohodnoten.C3.BDch_grafoch|programe pre prednášku 34]], trieda FixedLengthPaths, resp. trieda LongestPath pre najdlhšiu cestu | + | * Program na hľadanie ciest dĺžky ''k'' nájdete v [[#Prednáška 34#Zdrojov.C3.BD_k.C3.B3d_programu.2C_preh.C4.BEad.C3.A1vanie_s_n.C3.A1vratom_na_neohodnoten.C3.BDch_grafoch|programe pre prednášku 34]], trieda FixedLengthPaths, resp. trieda LongestPath pre najdlhšiu cestu |
− | [[Image:CV22-graf.png|thumb|200px|right]] '''Úloha A:''' Odsimulujte prácu [[Prednáška 34#Najkrat.C5.A1ie_cesty.2C_preh.C4.BEad.C3.A1vanie_do_.C5.A1.C3.ADrky_.28breath-first_search.2C_BFS.29|prehľadávania do šírky]] na grafe vpravo ak začneme prehľadávať vo vrchole 0. Predpokladajte, že adjVertices vracia susedov v poradí podľa čísla vrcholu. Uveďte, v akom poradí boli vrcholy vložené do queue a vypíšte výsledné polia dist a prev. | + | [[Image:CV22-graf.png|thumb|200px|right]] '''Úloha A:''' Odsimulujte prácu [[#Prednáška 34#Najkrat.C5.A1ie_cesty.2C_preh.C4.BEad.C3.A1vanie_do_.C5.A1.C3.ADrky_.28breath-first_search.2C_BFS.29|prehľadávania do šírky]] na grafe vpravo ak začneme prehľadávať vo vrchole 0. Predpokladajte, že adjVertices vracia susedov v poradí podľa čísla vrcholu. Uveďte, v akom poradí boli vrcholy vložené do queue a vypíšte výsledné polia dist a prev. |
'''Úloha B:''' Program na hľadanie najdlhšej cesty z ''u'' do ''v'' v neohodnotenom grafe zmeňte na hľadanie najdlhšieho cyklu, ktorý začína a končí v danom vrchole ''v'' | '''Úloha B:''' Program na hľadanie najdlhšej cesty z ''u'' do ''v'' v neohodnotenom grafe zmeňte na hľadanie najdlhšieho cyklu, ktorý začína a končí v danom vrchole ''v'' | ||
Riadok 9 387: | Riadok 9 392: | ||
Tento týždeň | Tento týždeň | ||
* Prednáška: zhrnutie, príprava na test a skúšku, knižnica na skúšku | * Prednáška: zhrnutie, príprava na test a skúšku, knižnica na skúšku | ||
− | * Do dnes 22:00 si v prípade záujmu vyberte tému [[Letný semester, projekt|nepovinného projektu]]. | + | * Do dnes 22:00 si v prípade záujmu vyberte tému [[#Letný semester, projekt|nepovinného projektu]]. |
* Do stredy 11.5. DÚ16 | * Do stredy 11.5. DÚ16 | ||
Riadok 9 428: | Riadok 9 433: | ||
==Informácie ku skúške== | ==Informácie ku skúške== | ||
− | * Potrebné informácie a cvičné príklady nájdete na stránke [[Letný semester, test a skúška]] | + | * Potrebné informácie a cvičné príklady nájdete na stránke [[#Letný semester, test a skúška]] |
− | * Na skúške budete dopisovať do hotového programu [[GraphGUI]] | + | * Na skúške budete dopisovať do hotového programu [[#GraphGUI]] |
=Cvičenia 25= | =Cvičenia 25= | ||
− | Cieľom dnešného cvičenia je precvičiť si topologické triedenie a prehľadávanie s návratom na grafoch z [[ | + | Cieľom dnešného cvičenia je precvičiť si topologické triedenie a prehľadávanie s návratom na grafoch z [[#Prednáška 35|prednášky 35]]. |
[[Image:PROG-C24-graf.png|thumb|150px|right]] '''Úloha A:''' Nájdite všetky topologické usporiadania orientovaného grafu na obrázku vpravo. | [[Image:PROG-C24-graf.png|thumb|150px|right]] '''Úloha A:''' Nájdite všetky topologické usporiadania orientovaného grafu na obrázku vpravo. | ||
− | '''Úloha B:''' Modifikujte [[Prednáška 34#Zdrojov.C3.BD_k.C3.B3d_programu.2C_preh.C4.BEad.C3.A1vanie_s_n.C3.A1vratom_na_neohodnoten.C3.BDch_grafoch|program na hľadanie maximálnej kliky]] (trieda MaximumClique) z prednášky tak, aby vypísal všetky maximálne kliky. Môžete začať buď z jednoduchšej alebo z rýchlejšej verzie programu. Namiesto jedného ArrayListu maxClique si spravte ArrayList ArrayListov, do ktorého budete ukladať všetky doteraz nájdené riešenia maximálnej veľkosti. Ak nájdete väčšiu kliku, ArrayList vyprázdnite a uložte tam iba najnovšie nájdenú. Pozor, ak používate rýchlejšiu verziu programu, treba tiež upraviť alebo zmazať podmienku so stupňom vrchola. | + | '''Úloha B:''' Modifikujte [[#Prednáška 34#Zdrojov.C3.BD_k.C3.B3d_programu.2C_preh.C4.BEad.C3.A1vanie_s_n.C3.A1vratom_na_neohodnoten.C3.BDch_grafoch|program na hľadanie maximálnej kliky]] (trieda MaximumClique) z prednášky tak, aby vypísal všetky maximálne kliky. Môžete začať buď z jednoduchšej alebo z rýchlejšej verzie programu. Namiesto jedného ArrayListu maxClique si spravte ArrayList ArrayListov, do ktorého budete ukladať všetky doteraz nájdené riešenia maximálnej veľkosti. Ak nájdete väčšiu kliku, ArrayList vyprázdnite a uložte tam iba najnovšie nájdenú. Pozor, ak používate rýchlejšiu verziu programu, treba tiež upraviť alebo zmazať podmienku so stupňom vrchola. |
'''Úloha C:''' Napíšte program, ktorý pomocou prehľadávania s návratom nájde najmenšiu dominujúcu množinu v grafe. | '''Úloha C:''' Napíšte program, ktorý pomocou prehľadávania s návratom nájde najmenšiu dominujúcu množinu v grafe. | ||
Riadok 9 448: | Riadok 9 453: | ||
* alebo nechceme vynechať vrchol, ak niektorý z jeho susedov nemá ešte suseda v ''X'' a ani nemá suseda s väčším číslom, ktorý ešte môže byť pridaný do ''X'' v ďalších rozhodnutiach | * alebo nechceme vynechať vrchol, ak niektorý z jeho susedov nemá ešte suseda v ''X'' a ani nemá suseda s väčším číslom, ktorý ešte môže byť pridaný do ''X'' v ďalších rozhodnutiach | ||
− | '''Úloha D:''' Napíšte program, ktorý dostane orientovaný graf a v prípade, že je v grafe cyklus, vypíše ho (ak je cyklov viac, vypíše hociktorý). Odporúčame začať z programu na [[ | + | '''Úloha D:''' Napíšte program, ktorý dostane orientovaný graf a v prípade, že je v grafe cyklus, vypíše ho (ak je cyklov viac, vypíše hociktorý). Odporúčame začať z programu na [[#Prednáška 35#Zdrojov.C3.BD_k.C3.B3d_programu.2C_topologick.C3.A9_triedenie_2|topologické triedenie pomocou prehľadávania do hĺbky]], pričom si pre každý vrchol do poľa uložte, z ktorého iného vrcholu ste ho objavili (podobne ako pole prev pri prehľadávaní do šírky). V momente, keď program nastavuje acyclic=false, by ste mali vedieť nájsť cyklus. |
Riadok 10 099: | Riadok 10 104: | ||
* http://pages.cs.wisc.edu/~hasti/cs368/CppTutorial/ | * http://pages.cs.wisc.edu/~hasti/cs368/CppTutorial/ | ||
* http://www.horstmann.com/ccj2/ccjapp3.html | * http://www.horstmann.com/ccj2/ccjapp3.html | ||
− | * Základy použitia STL knižnice (generické triedy) | + | * Základy použitia STL knižnice (generické triedy) (mali sme v zimnom semestri<!--, [[#Prednáška 24]]-->) |
Niektoré rozdiely medzi triedami v Jave a C++: | Niektoré rozdiely medzi triedami v Jave a C++: | ||
Riadok 10 145: | Riadok 10 150: | ||
* Väčšinu operátorov, ktoré majú aspoň jeden operand objekt, môžeme preťažiť, teda vymyslieť im vlastný význam | * Väčšinu operátorov, ktoré majú aspoň jeden operand objekt, môžeme preťažiť, teda vymyslieť im vlastný význam | ||
* V našom príklade preťažujeme < (využije sa pri triedení) a + (vytvára najmenší interval pokrývajúci oba operandy) | * V našom príklade preťažujeme < (využije sa pri triedení) a + (vytvára najmenší interval pokrývajúci oba operandy) | ||
− | * Viac [[Prednáška 24]] | + | * Viac [[#Prednáška 24]] |
===Const=== | ===Const=== | ||
Riadok 10 158: | Riadok 10 163: | ||
===Aritmetický strom v C++=== | ===Aritmetický strom v C++=== | ||
− | * verzia v Jave, pozri [[Prednáška 27]] | + | * verzia v Jave, pozri [[#Prednáška 27]] |
<pre> | <pre> | ||
class Node { | class Node { | ||
Riadok 10 375: | Riadok 10 380: | ||
=Cvičenia 26= | =Cvičenia 26= | ||
Úlohou tohto cvičenia je zoznámiť sa s knižnicou GraphGUI, ktorú budete používať na skúške. Táto knižnica zahŕňa programovanie vo JavaFX a prácu s grafmi. | Úlohou tohto cvičenia je zoznámiť sa s knižnicou GraphGUI, ktorú budete používať na skúške. Táto knižnica zahŕňa programovanie vo JavaFX a prácu s grafmi. | ||
− | * [[GraphGUI|popis knižnice]] | + | * [[#GraphGUI|popis knižnice]] |
'''Úloha A:''' Stiahnite si [http://compbio.fmph.uniba.sk/vyuka/prog-data/graphgui/graphgui.zip graphgui.zip] a rozzipujte ho. Potom si ho otvorte v Netbeans pomocou New project, ako typ projektu zvoľte Java Project with Existing Sources, na ďalšej obrazovke vyplňte Project Name graphgui a na ďalšej pomocou Add Folder pridajte adresár s rozzipovanými súbormi. Je dobré si tento postup vyskúšať v učebni, aby ste na skúške nemali problémy. Projekt skúste skompilovať, spustiť a pozrite si, čo program robí. | '''Úloha A:''' Stiahnite si [http://compbio.fmph.uniba.sk/vyuka/prog-data/graphgui/graphgui.zip graphgui.zip] a rozzipujte ho. Potom si ho otvorte v Netbeans pomocou New project, ako typ projektu zvoľte Java Project with Existing Sources, na ďalšej obrazovke vyplňte Project Name graphgui a na ďalšej pomocou Add Folder pridajte adresár s rozzipovanými súbormi. Je dobré si tento postup vyskúšať v učebni, aby ste na skúške nemali problémy. Projekt skúste skompilovať, spustiť a pozrite si, čo program robí. | ||
Riadok 10 384: | Riadok 10 389: | ||
* Odporúčané ovládacie prvky sú [https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/RadioButton.html RadioButton] (zaujímavé metódy setSelected, isSelected), pripadne [https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ListView.html ListView] (vhodný SelectionModel je SelectionMode.MULTIPLE a jeho metódy getSelectedIndices() resp. getSelectedItems()). | * Odporúčané ovládacie prvky sú [https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/RadioButton.html RadioButton] (zaujímavé metódy setSelected, isSelected), pripadne [https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ListView.html ListView] (vhodný SelectionModel je SelectionMode.MULTIPLE a jeho metódy getSelectedIndices() resp. getSelectedItems()). | ||
− | '''Úloha C:''' Do súboru GraphAlgorithm.java doprogramujte, aby po stlačení tlačidla Action program spustil algoritmus hľadania najväčšej kliky a vrcholy v tejto klike aby boli vyfarbené žltou farbou (nastavte im meno farby "yellow") a ostatné vrcholy mimo kliky bielou farbou (nastavte im meno farby "white"). Algoritmus upravte z [[Prednáška 34#Zdrojov.C3.BD_k.C3.B3d_programu.2C_preh.C4.BEad.C3.A1vanie_s_n.C3.A1vratom_na_neohodnoten.C3.BDch_grafoch|program na hľadanie maximálnej kliky]] (trieda MaximumClique). Metóda performAlgorithm vráti reťazec vo formáte "Pocet vrcholov kliky: 6". | + | '''Úloha C:''' Do súboru GraphAlgorithm.java doprogramujte, aby po stlačení tlačidla Action program spustil algoritmus hľadania najväčšej kliky a vrcholy v tejto klike aby boli vyfarbené žltou farbou (nastavte im meno farby "yellow") a ostatné vrcholy mimo kliky bielou farbou (nastavte im meno farby "white"). Algoritmus upravte z [[#Prednáška 34#Zdrojov.C3.BD_k.C3.B3d_programu.2C_preh.C4.BEad.C3.A1vanie_s_n.C3.A1vratom_na_neohodnoten.C3.BDch_grafoch|program na hľadanie maximálnej kliky]] (trieda MaximumClique). Metóda performAlgorithm vráti reťazec vo formáte "Pocet vrcholov kliky: 6". |
'''Úloha D:''' Riešenie úlohy C upravte tak, aby algoritmus nehľadal najväčšiu kliku v celom grafe, ale najväčšiu kliku, ktorá obsahuje vybraný vrchol, ktorý GraphAlgorithm dostane ako parameter. Ak hodnota tohto vrcholu je null, konštruktor vyhodí výnimku typu NoSuchElementException. | '''Úloha D:''' Riešenie úlohy C upravte tak, aby algoritmus nehľadal najväčšiu kliku v celom grafe, ale najväčšiu kliku, ktorá obsahuje vybraný vrchol, ktorý GraphAlgorithm dostane ako parameter. Ak hodnota tohto vrcholu je null, konštruktor vyhodí výnimku typu NoSuchElementException. |
Verzia zo dňa a času 16:15, 13. september 2016
- #Letný semester, úvodné informácie
- #Letný semester, pravidlá
- #Letný semester, test a skúška
- #GraphGUI (knižnica ku skúške)
- #Letný semester, projekt
Týždeň 15.-21.2. Úvod do Javy |
#Prednáška 25 · #Cvičenia 14 |
Týždeň 22.2.-28.2. Úvod do objektovo-orientovaného programovania, JavaDoc |
#Prednáška 26 · #Cvičenia 15 |
Týždeň 29.2.-6.3. Dedenie, polymorfizmus, modifikátory, rozhrania |
#Prednáška 27 · #Cvičenia 16 |
Týždeň 7.-13.3. Výnimky, generické programovanie |
#Prednáška 28 · #Cvičenia 17 |
Týždeň 14.-20.3. Collections, testovanie |
#Prednáška 29 · #Cvičenia 18 |
Týždeň 21.-27.3. Opakovanie OOP, úvod k JavaFX |
#Prednáška 30 · #Cvičenia 19 |
Týždeň 28.3.-3.4. Veľká Noc |
Týždeň 4.-10.4. JavaFX (kreslenie) |
#Prednáška 31 · #Cvičenia 20 |
Týždeň 11.-17.4. JavaFX (zložitejšie prvky, viacoknové aplikácie) |
#Prednáška 32 · #Cvičenia 21 |
Týždeň 18.-24.4. Reprezentácia grafov, prehľadávanie do hĺbky |
#Prednáška 33 · #Cvičenia 22 |
Týždeň 25.4.-1.5. Prehľadávanie do šírky, ohodnotené grafy, najdlhšia cesta |
#Prednáška 34 · #Cvičenia 23 |
Týždeň 2.-8.5. Maximálna klika, orientované grafy, topologické triedenie |
#Prednáška 35 · #Cvičenia 24 |
Týždeň 9.-15.5. Zhrnutie, informácie k písomke a skúške, knižnica na skúšku |
#Prednáška 36 · #Cvičenia 25 |
Týždeň 16.-22.5. OOP v C++ |
#Prednáška 37 · #Cvičenia 26 |
Obsah
- 1 Letný semester, úvodné informácie
- 2 Letný semester, pravidlá
- 3 Letný semester, test a skúška
- 4 GraphGUI
- 5 Letný semester, projekt
- 6 Prednáška 25
- 6.1 Úvod k predmetu (viac na ďalšej prednáške)
- 6.2 Hello world
- 6.3 Väčší program
- 6.4 Základy jazyka Java
- 7 Cvičenia 14
- 8 Cvičenia 14
- 9 Prednáška 26
- 9.1 Úvod do predmetu
- 9.2 Organizačné poznámky
- 9.3 Objektovo orientované programovanie (OOP)
- 9.4 Konštruktor a vznik objektov
- 9.5 Kontrola prístupu, modifikátory
- 9.6 Zapuzdrenie (encapsulation)
- 9.7 Ďalšie detaily
- 9.8 Nie všetko v Jave je objekt
- 9.9 Javadoc
- 9.10 Binárny vyhľadávací strom
- 9.11 Pomocné triedy
- 10 Cvičenia 15
- 11 Prednáška 27
- 12 Cvičenia 16
- 13 Prednáška 28
- 14 Cvičenia 17
- 15 Prednáška 29
- 16 Cvičenia 18
- 17 Prednáška 30
- 18 Cvičenia 19
- 19 Prednáška 31
- 20 Cvičenia 20
- 21 Prednáška 32
- 22 Cvičenia 21
- 23 Prednáška 33
- 24 Cvičenia 22
- 25 Prednáška 34
- 25.1 Oznamy
- 25.2 Opakovanie
- 25.3 Najkratšie cesty, prehľadávanie do šírky (breath-first search, BFS)
- 25.4 Opakovanie: prehľadávanie s návratom (backtracking)
- 25.5 Prehľadávanie s návratom na grafoch
- 25.6 Programy
- 25.7 Zdrojový kód programu, prehľadávanie s návratom na neohodnotených grafoch
- 25.8 Zdrojový kód programu, prehľadávanie s návratom na ohodnotených grafoch
- 26 Cvičenia 23
- 27 Prednáška 35
- 28 Cvičenia 24
- 29 Prednáška 36
- 30 Cvičenia 25
- 31 Prednáška 37
- 32 Cvičenia 26
Letný semester, úvodné informácie
Predmet 1-INF-166 Programovanie (2) v Jave nadväzuje na predmet 1-INF-127 Programovanie (1) v C/C++. Oba sú určené študentom prvého ročníka bakalárskeho študijného programu Informatika.
Rozvrh
- Prednášky: pondelok 9:50-11:20 v F1-247
- Cvičenia: pondelok 8:10-9:40 v H-6
Vyučujúci
- doc. Mgr. Broňa Brejová, PhD., miestnosť M-163,
- Mgr. Jaroslav Budiš, miestnosť M-25,
- Konzultácie po dohode e-mailom
Cvičiaci
- Mgr. Jaroslav Budiš, miestnosť M-25,
- Bc. Mária Vajdová, miestnosť M-25,
Ciele predmetu
- prehĺbiť a rozšíriť zručnosti v algoritmickom uvažovaní, písaní a ladení programov z predchádzajúceho semestra
- oboznámiť sa so základnými programovými a dátovými štruktúrami jazyka Java
- zvládnuť základy objektovo-orientovaného programovania a tvorby programov s grafickým užívateľským rozhraním
- oboznámiť sa so základnými algoritmami na prácu s grafmi
Literatúra
- Predmet sa nebude striktne riadiť žiadnou učebnicou. Prehľad preberaných tém a stručné poznámky nájdete na stránke predmetu, doporučujeme Vám si na prednáškach a cvičeniach robiť vlastné poznámky.
- Pri štúdiu Vám môžu pomôcť knihy o jazyku Java, o programovaní všeobecne a o algoritmoch preberaných na prednáške. Tu je výber z vhodných titulov, ktoré sú k dispozícii na prezenčné štúdium vo fakultnej knižnici:
- Bruce Eckel: Thinking in Java. 4th edition Prentice-Hall, 2006. Signatúra I-INF-E-2
- Staršie 2. vydanie v českom preklade v knižnici so signatúrou D-INF-E-1b, 3. vydanie v angličtine online, doporučujeme však radšej 4. vydanie
- Michael T. Goodrich, Roberto Tamassia: Data Structures and Algorithms in Java. John Wiley & Sons, 2006. Signatúra D-INF-G-5
- Robert Sedgewick: Algorithms in Java, Parts 1-4, Fundamentals, Data Structures, Sorting, Searching, Addison-Wesley, 2003. Signatúra D-INF-S-1/I-IVa
- Robert Sedgewick: Algorithms in Java, Part 5, Graph Algorithms Addison-Wesley, 2004. Signatúra D-INF-S-1/V (I)
- Bruce Eckel: Thinking in Java. 4th edition Prentice-Hall, 2006. Signatúra I-INF-E-2
- Tutoriál k jazyku Java a referenčná príručka k štandardným knižniciam
Priebeh semestra
- Na prednáškach budeme preberať obsah predmetu. Prednášky budú dve vyučovacie hodiny do týždňa.
- Cvičenia budú dve vyučovacie hodiny do týždňa v počítačovej učebni a ich cieľom je aktívne si precvičiť učivo. Na začiatku cvičenia bude krátka diskusia o prípadných nejasnostiach ohľadom materiálu z minulého cvičenia. Potom nasleduje rozcvička (krátky test) písaný na papieri. Ďalšou časťou cvičenia je precvičovanie príkladov k predchádzajúcim prednáškam (spoločne alebo individuálne).
- V prípade záujmu môžeme ponúknuť aj nepovinné cvičenia, kde s pomocou cvičiaceho môžete dokončovať príklady z predchádzajúcich cvičení, pýtať sa otázky k učivu, prípadne pracovať na domácej úlohe.
- Domáce úlohy navrhujeme tak, aby Vám ich riešenie pomohlo osvojiť si a precvičiť si učivo, čím sa okrem iného pripravujete aj na záverečnú skúšku. Okrem tohto sú za domáce úlohy body do záverečného hodnotenia. Najviac sa naučíte, ak sa Vám domácu úlohu podarí samostatne vyriešiť, ale ak sa vám to napriek vášmu úsiliu nedarí, neváhajte sa spýtať o pomoc prednášajúcich alebo cvičiacich. Možno s malou radou od nás sa Vám podarí úlohu spraviť. Treba však na domácej úlohe začať pracovať v predstihu, aby ste nás v prípade problémov stihli kontaktovať.
- Cieľom vyučujúcich tohto predmetu je vás čo najviac naučiť, ale musíte aj vy byť aktívni partneri. Ak Vám na prednáške alebo cvičení nie je niečo jasné, spýtajte sa. Môžete nám klásť tiež otázky počas našich konzultačných hodín alebo emailom. Ak sa dostanete do väčších problémov s plnením študijných povinností, poraďte sa s vyučujúcimi alebo s tútorom, ako tieto problémy riešiť.
- 40% známky dostávate za prácu cez semester, preto netreba nechávať štúdium učebnej látky až na skúškové obdobie.
Letný semester, pravidlá
Známkovanie
- 20% známky je na základe rozcvičiek, ktoré sa píšu na (takmer) každom cvičení
- 20% známky je za domáce úlohy
- 30% známky je za záverečný písomný test
- 30% známky je za praktickú skúšku pri počítači
- 10% bonusových percent je za nepovinný projekt
Pozor, body získavané za jednotlivé príklady nezodpovedajú priamo percentám záverečnej známky. Body za každú formu známkovania sa preváhujú tak, aby maximálny získateľný počet zodpovedal váham uvedených vyššie. Úlohy označené ako bonusové sa nerátajú do maximálneho počtu získateľných bodov v danej aktivite.
Stupnica
- Na úspešné absolvovanie predmetu je potrebné splniť nasledovné tri podmienky:
- Získať aspoň 50% bodov v celkovom hodnotení
- Získať aspoň 50% zo záverečného písomného testu
- Získať aspoň 50% zo skúšky
- Ak niektorú z týchto troch podmienok nesplníte, dostávate známku Fx.
- V prípade úspešného absolvovania predmetu získate známku podľa percent v celkovom hodnotení takto:
- A: 90% a viac, B:80...89%, C: 70...79%, D: 60...69%, E: 50...59%
Rozcvičky
- Rozcvičky sú krátke testy (cca 15 minút), ktoré sa píšu na začiatku (takmer) každého cvičenia. Za každú rozcvičku môžete získať najviac 5 bodov.
- Pri rozcvičke môžete použiť ľubovoľné písomné materiály (poznámky, knihy,...), nie však počítače a iné elektronické pomôcky. Počas rozcvičky nie je možné zdieľať materiály so spolužiakmi.
- Ak bude počas semestra celkovo N rozcvičiek, do výslednej známky sa vám zaráta iba N-2 najlepších, t.j. dve rozcvičky, na ktorých ste získali najmenej bodov (alebo ste sa ich ani nezúčastnili) sa vám škrtajú.
Domáce úlohy
- Domáce úlohy budú vypisované v priemere raz za dva týždne. Maximálny počet bodov za domácu úlohu bude uvedený v zadaní a bude sa pohybovať spravidla v rozsahu 10-20 bodov podľa náročnosti úlohy.
- Domáce úlohy treba odovzdať elektronicky pomocou odovzdávacieho systému do termínu určeného v zadaní. Neskoršie odovzdané úlohy nebudú akceptované.
- Program, ktorý odovzdáte ako domácu úlohu by mal byť skompilovateľný a spustiteľný v odovzdávacom systéme. Budeme kontrolovať správnosť celkovej myšlienky, správnosť implementácie, ale aj štýl.
- Odovzdávací systém skompiluje vašu úlohu a v niektorých prípadoch ju aj spustí vašu úlohu na niekoľkých vstupoch. Odporúčame vám ale vytvoriť si aj vlastné testovacie vstupy. Programy, ktoré nebudú správne bežať na testovacích vstupoch, nezískajú plný počet bodov, dajte preto pozor na všetky pokyny uvedené v zadaní (presný formát vstupu a výstupu, mená súborov a podobne).
Nepovinný projekt
- Za nepovinný projekt môžete získať 10% bonus k vašej výslednej známke (musíte však stále splniť všetky tri podmienky ukočenia predmetu).
- Projekt robia dvojice, výnimočne aj jednotlivci.
- Projekt sa bude odovzdávať v prvom týždni skúškového obdobia.
- Témy projektov a podrobnejšie pravidlá nájdete na zvláštnej stránke.
- Na získanie bodov za projekt musí byť vaša práca dostatočne rozsiahla a kvalitná, v opačnom prípade získate 0 bodov.
Záverečný písomný test
- Záverečný test bude trvať 90 minút a bude obsahovať úlohy podobné tým, ktoré sa riešili na cvičeniach.
- Riadny termín testu sa bude konať koncom semestra alebo začiatkom skúškového obdobia a cez skúškové obdobie bude aj opravný termín testu.
- Pri teste nemôžete používať žiadne pomocné materiály (písomné ani elektronické) okrem povoleného ťaháku v rozsahu jedného listu formátu A4 s ľubovoľným obsahom na oboch stranách.
Skúška
- Na skúške budete riešiť 2 úlohy pri počítači v celkovom trvaní 2-3 hodiny.
- Na skúške nemôžete používať žiadne pomocné materiály okrem povoleného ťaháku v rozsahu jedného listu formátu A4 s ľubovoľným obsahom na oboch stranách. Nebude k dispozícii ani internet. Budete používať rovnaké programátorské prostredie ako na cvičeniach.
- Po skončení skúšky sa koná krátky ústny pohovor s vyučujúcimi, počas ktorého sa prediskutujú programy, ktoré ste odovzdali a uzavrie sa vaša známka.
Opravné termíny
- Záverečný test má jeden opravný termín (je súčasťou priebežného hodnotenia)
- Ak sa zúčastníte opravného termínu, strácate body z predchádzajúceho termínu, aj keby ste na opravnom získali menej bodov.
- Opakovanie skúšky sa riadi študijným poriadkom fakulty. Máte nárok na dva opravné termíny (ale len v rámci termínov, ktoré sme určili).
- Ak po skúške pri počítači máte nárok na známu E alebo lepšiu, ale chceli by ste si známku ešte opraviť, musíte sa dohodnúť so skúšajúcimi pred zapísaním známky do indexu.
- Ak po skúške pri počítači ešte opravujete písomku, je potrebné prísť uzavrieť a zapísať známku v termíne určenom vyučujúcimi.
- Ak sa zo závažných dôvodov (napr. zdravotných, alebo konflikt s inou skúškou) nemôžete zúčastniť termínu skúšky alebo písomky, dajte o tom vyučujúcim vedieť čím skôr.
Opisovanie
- Máte povolené sa so spolužiakmi a ďalšími osobami rozprávať o domácich úlohách a stratégiách na ich riešenie. Kód, ktorý odovzdáte, musí však byť vaša samostatná práca. Je zakázané opisovať kód z literatúry alebo z internetu a ukazovať svoj kód spolužiakom. Domáce úlohy môžu byť kontrolované softvérom na detekciu plagiarizmu.
- Počas rozcvičiek, testov a skúšok môžete používať iba povolené pomôcky a nesmiete komunikovať s žiadnymi osobami okrem vyučujúcich.
- Ak nájdeme prípady opisovania alebo nepovolených pomôcok, všetci zúčastnení študenti získajú za príslušnú domácu úlohu alebo test nula bodov (t.j. aj tí, ktorí dali spolužiakom odpísať). Opakované alebo obzvlášť závažné prípady opisovania budú podstúpené na riešenie dekanovi fakulty.
Neprítomnosť
- Účasť na cvičeniach veľmi silne doporučujeme a v prípade neprítomnosti stratíte body za rocvičky.
- V prípade kratšieho ochorenia alebo iných problémov môžete využiť možnosť, že dve najhoršie rozcvičky sa škrtajú a stratené body za domáce úlohy je možné dohoniť riešením bonusových príkladov.
- V prípade dlhšieho ochorenia (aspoň dva týždne alebo opakovaná neprítomnosť) alebo iných závažných prekážok sa príďte poradiť s prednášajúcimi o možných riešeniach. Treba tak spraviť čím skôr, nie až spätne cez skúškové. Prineste si potvrdenku od lekára.
Test pre pokročilých
- V druhom týždni semestra sa bude konať nepovinný test pre pokročilých. Príklady na ňom budú podobné ako na záverečnom teste.
- Ak na test prídete a napíšete ho na menej ako 50%, nezískate žiadne výhody (ako keby ste na test ani neprišli).
- V opačnom prípade za každých celých získaných 10% získavate plný počet bodov z jednej rozcvičky. Napr. ak ste získali 59% z testu, dostanete plný počet bodov z prvých 5 rozcvičiek po opravení testu. Tieto body nie je možné presúvať na iné termíny rozcvičiek.
- Navyše si môžete body z testu pre pokročilých nechať uznať ako body zo záverečného testu. Máte však aj možnosť písať záverečný test so spolužiakmi.
Letný semester, test a skúška
Na túto stránku budeme postupne pridávať informácie týkajúce sa záverečného písomného testu a praktickej skúšky pri počítači v letnom semestri. Odporúčame tiež si preštudovať pravidlá predmetu.
Organizácia skúšky
Písomný test
- Termín: pondelok 16.5. o 14:00 v posluchárni F2.
- Opravný/náhradný termín: 2. týždeň semestra, dohodneme po teste
- Trvanie 90 minút
- Príklady zhruba ako na cvičeniach alebo rozcvičkách
- Dobre si rozvrhnite čas, niektoré úlohy sú ťažšie, iné ľahšie.
- Aby ste mali šancu úspešne ukončiť predmet, musíte získať aspoň polovicu bodov.
- Doneste si ISIC, písacie potreby, ťahák 1 list A4
- Zakázané sú ďalšie materiály, elektronické pomôcky, opisovanie
Skúška pri počítači
- Jeden príklad na JavaFX, jeden na grafy (väčšinou backtracking)
- Obidva v #GraphGUI, pozrite si vopred pred skúškou
- Zapíšte sa na termín v AIS, prihlasovanie/odhlasovanie do 14:00 deň pred skúškou
- Doneste si ťahák 1 list A4, ISIC, index, písacie potreby, papier
- Príďte na 8:50 pred príslušnú učebňu, kde sa dozviete pokyny a rozsadenie do miestností
- Doobeda: 2 a 3/4 hodiny práca pri počítačoch.
- Na väčších termínoch poobede opravíme uzavrieme známky bez vás, na menších termínoch vyhodnotenie s vašou prítomnosťou
- Oznámime termíny, kedy je možné prísť si zapísať známku, prípadne si pozrieť, kde ste spravili chybu. Prosím dostavte sa v týchto termínoch.
- Prostredie ako minulý semester, bude GraphGUI a dokumentácia k Jave
- Aby ste mali šancu úspešne ukončiť predmet, musíte získať aspoň polovicu bodov.
- Hodnotíme správnosť myšlienky, správnosť implementácie, štýl
- Nezáleží na rýchlosti programu
- Testovač nebude testovať správnosť, dostatočne si program otestujte sami.
Termíny skúšok
- štvrtok 26.5. 9:00 v H6 riadny termín (môžete využiť aj keď potrebujete opravovať test)
- štvrtok 9.6. 9:00 v H6 riadny termín (alebo 1. opravný)
- streda 22.6. 9:00 v H6 prvý opravný termín (alebo 2. opravný)
- v týždni od 27.6. druhý opravný termín
Písomný test
Na písomnom teste budú príklady na nasledujúce témy:
- Základné črty jazyka Java
- Objektovo-orientované programovanie (triedy, konštruktory, dedenie a polymorfizmus, rozhranie, ...)
- Výnimky
- Základy generického programovania, základy Collections (ArrayList, LinkedList, HashMap, Iterator)
- Testovacie vstupy (netreba JUnit, len tvorba vstupov ako takých)
- Grafy (reprezentácia, prehľadávanie grafu do hĺbky a šírky, topologické triedenie orientovaného grafu, úlohy na prehľadávanie s návratom)
- Všeobecné programátorské zručnosti, práca s poliami, zoznamami, stromami, rekurzia
Na písomnom teste nebudú príklady na JavaFX
Ukážkové príklady na písomný test
V texte nižšie je niekoľko príkladov, ktoré sa svojim charakterom a obtiažnosťou podobajú na príklady, aké budú na záverečnej písomke. Tieto ukážkové príklady sú prevažne vybrané z cvičení a prednášok, na skutočnej písomke však budú nové, zatiaľ nepoužité príklady.
- Príklad 1: Navrhnite triedu Polynomial, ktorá bude reprezentovať polynómy jednej premennej s celočíselnými koeficientami, ktorá bude mať nasledujúce metódy (napíšte kód celej triedy, vrátane premenných):
- Konštruktor bez parametrov, ktorý vytvorí nulový polynóm
- Konštruktor s dvoma celočíselnými parametrami a a i, ktorý vytvorí polynóm Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle ax^i}
- Konštruktor, ktorý dostane pole a vytvorí polynóm, ktorého koeficienty budú prvky tohto poľa
- Metódu getCoefficient(int i), ktorá vráti koeficient pri člene Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle x^i} .
- Metódu add(Polynomial p), ktorá vráti nový polynóm, ktorý bude súčtom tohto polynómu a polynómu p.
- Metódu evaluate(int value), ktorá vráti hodnotu polynómu, keď premenná bude mať hodnotu value.
- Príklad 2: Naprogramujte generickú triedu Matrix, ktorá reprezentuje obdĺžnikovú maticu prvkov nejakého neznámeho typu E.
- Napíšte konštruktor, ktorý vytvorí maticu zadaných rozmerov a vyplní ju zadaným prvkom typu E.
- Napíšte metódu get, ktorá vráti prvok matice nachádzajúci sa na zadanom mieste
- Napíšte metódu set, ktorá na zadané miesto v matici zapíše zadaný prvok typu E
- Ak metódam get alebo set užívateľ zadá súradnice mimo matice, hodia výnimku vašej vlastnej triedy MatrixIndexOutOfBoundsException (ktorú tiež naprogramujte)
- Výnimka tejto triedy by v metóde getMessage mala vrátiť reťazec obsahujúci obidve súradnice, ako aj obidva rozmery matice.
- Príklad 3: Napíšte generickú statickú metódu kazdyDruhy, ktorá dostane LinkedList a vyhodí z neho každý druhý prvok. K LinkedListu pristupujte iba cez iterátor a jeho metódu remove.
- Príklad 4: Uvažujme prehľadávanie grafu do hĺbky, ktoré do poľa whenVisited čísluje vrcholy v poradí, v akom boli navštívené. Odsimulujte algoritmus na grafe vpravo a zistite, v akom poradí budú vrcholy navštívené, ak začneme prehľadávať vo vrchole 0. Predpokladajte, že adjVertices vracia susedov v poradí podľa čísla vrcholu.
- Príklad 5: Odsimulujte prácu prehľadávania do šírky na grafe vyššie, ak začneme prehľadávať vo vrchole 0. Predpokladajte, že adjVertices vracia susedov v poradí podľa čísla vrcholu. Uveďte, v akom poradí boli vrcholy vložené do queue a vypíšte výsledné polia dist a prev.
- Príklad 6: Nájdite všetky topologické usporiadania orientovaného grafu na obrázku vpravo.
- Príklad 7: Napíšte funkciu static int countIsolated(Graph g), ktorá v grafe g spočíta počet izolovaných vrcholov, teda takých, ktoré nemajú žiadnych susedov. Graph je interface z prednášok s metódami getNumberOfVertices, getNumberOfEdges, addEdge, existsEdge, adjVertices.
- Príklad 8: Navrhnite 5 testov pre metódu s hlavičkou a špecifikáciou uvedenou nižšie. Pre každý test uveďte obsah vstupných parametrov a a x, ako aj aký výstup by metóda mala vrátiť a stručne slovne popíšte význam testu. Pokryte obvyklé aj okrajové prípady.
/** Z pola a vyhodi prvy vyskyt objektu rovneho x * pricom rovnost sa testuje metodou equals. * Vsetky dalsie prvky posunie o jedno dolava a na koniec * pola da null. * Vrati true, ak bolo pole modifikovane, inak false. * Ak a je null alebo x je null, vyhodi java.lang.NullPointerException */ public static boolean remove(Object[] a, Object x) {
- Príklad 9: Uveďte, čo vypíše nasledujúci program:
class E0 extends Exception {} public class Prog { static void f(int x) throws E0 { try { if(x==0) throw new E0(); System.out.print('A'); } catch (E0 e) { System.out.print('B'); throw new E0(); } } public static void main(String[] args) { for(int x=0; x<=1; x++) { try { System.out.print(x); f(x); System.out.print(x); } catch (E0 e) { System.out.print('C'); } } } }
- Príklad 10: Uveďte, čo vypíše nasledujúci program:
class A { private int x; public A() { x = 0; } public void print() { System.out.println(x); } public void inc() { x++; } public void work() { inc(); print(); } } class B extends A { private int y; public B() { super(); y=0; } @Override public void inc() { y++; } public static void main(String[] args) { A a = new B(); a.work(); a.work(); a = new A(); a.work(); a.work(); } }
- Príklad 11: Uveďte, čo vypíše nasledujúci program:
public class Prog { static void vypis(int[][] a) { if (a != null) { for (int i = 0; i < a.length; i++) { for (int j = 0; j < a[i].length; j++) { System.out.print(" " + a[i][j]); } System.out.println(); } } else { System.out.println("null"); } } static void vytvorMaticu(int[][] a) { if (a == null) { a = new int[2][]; } a[0] = new int[2]; a[1] = a[0]; a[0][0] = 1; // (*) } public static void main(String[] args) { int[][] a = new int[2][]; vytvorMaticu(a); int[][] b = null; vytvorMaticu(b); vypis(a); vypis(b); } }
Skúška pri počítači
Na skúške pri počítači dostanete k dispozícii program #GraphGUI
- Do programu budete pridávať riešenia dvoch úloh
- Tieto úlohy bude možné riešiť nezávisle na sebe a budú mať rovnakú váhu, nemusia však byť pre vás rovnako ťažké
- V úlohe A bude treba doprogramovať niečo do grafického prostredia
- Pozri napr. úlohu B na cvičeniach 26
- V úlohe B bude treba naprogramovať nejaký grafový algoritmus, väčšinou metódou prehľadávania s návratom
- Pozri napr. úlohu D na cvičeniach 26, použité úlohy sa však budú menej podobať na algoritmy z prednášok. Viď napr. úlohu C o dominujúcej množine na cvičeniach 25
- Odovzdávate iba súbory Editor.java (úloha A) a GraphAlgorithm.java (úloha B), celé vaše riešenie by teda malo byť v týchto súboroch.
- Ak kvôli ladeniu meníte iné časti programu, ubezpečte sa, že odovzdané súbory pracujú aj s pôvodnou verziou programu graphgui.
- Súbor ExamSpecifics.java sa môže na každom termíne mierne líšiť, dôležité zmeny budú vysvetlené v zadaní.
Na skúške budete mať prístup aj k dokumentácii k Jave, ktorá je stiahnuteľná tu, resp. prezerateľná tu a tu. K dispozícii bude len dokumentácia jednotlivých knižníc, ale nie tutoriály.
- Nebude možné v dokumentácii vyhľadávať, naučte sa v nej preto pred skúškou orientovať navigáciou z farebnej tabuľky na úvodnej stránke.
GraphGUI
Knižnica GraphGUI ku skúške
Na skúške pri počítači budete pracovať s knižnicou GraphGUI. Túto knižnicu aj jej dokumentáciou dostanete k dispozícii na skúške, doporučujeme vám ale sa s ňou oboznámiť vopred.
Do programu graphgui budete pridávať riešenia dvoch úloh
- Tieto úlohy bude možné riešiť nezávisle na sebe a budú mať rovnakú váhu, nemusia však byť pre vás rovnako ťažké
- V úlohe A bude treba doprogramovať niečo do grafického prostredia
- V úlohe B bude treba naprogramovať nejaký grafový algoritmus, väčšinou metódou prehľadávania s návratom
- Odovzdávate iba súbory Editor.java (úloha A) a GraphAlgorithm.java (úloha B), celé vaše riešenie by teda malo byť v týchto súboroch.
- Ak kvôli ladeniu meníte iné časti programu, ubezpečte sa, že odovzdané súbory pracujú aj s pôvodnou verziou programu graphgui.
- Súbor ExamSpecifics.java sa môže na každom termíne mierne líšiť, dôležité zmeny budú vysvetlené v zadaní.
Používanie knižnice
Po spustení GraphGui sa objaví grafický panel na editovanie grafu
- Pri kliknutí vzniká nový vrchol alebo je vrchol označený resp. odznačený
- Označený vrchol má červený rámik, ostatné vrcholy majú čierny rámik.
- Pre označený vrchol program vypíše vpravo zoznam susedov. Kliknutím na suseda v zozname označíme hranu, ktorá ich spája.
- Pri klinutí so Shiftom pridávame hranu z označeného vrchola na kliknutý vrchol.
- Označený vrchol vieme zmazať tlačidlom Delete vertex
- Označenú hranu vieme zmazať tlačidlom Delete edge
- Po stlačení tlačidla Edit sa spustí vaše riešenie úlohy A, po stlačení tlačidla Action sa spustí vaše riešenie úlohy B
Dôležité súčasti knižnice
Graph, Vertex, Edge
- Rozhranie pre prácu s neorientovaným grafom.
- Umožňuje pridávať aj mazať vrcholy aj hrany.
- Vrcholy a hrany sú objekty typu Vertex a Edge.
- Vrcholy aj hrany si pamätajú hodnotu (value) typu int, ktorá môže predstavovať napr. vzdialenosť dvoch miest, počet obyvateľom mesta a pod.
- Vrcholy sú číslované 0,...,N-1, kde N je počet vrcholov, a v algoritmoch je možné ku nim pristupovať buď cez tieto čísla (id) alebo cez samotné objekty typu Vertex. Po zmazaní vrchola sa id ostatných vrcholov môžu zmeniť.
- Vrcholy majú uložené aj údaje o svojich súradniciach, veľkosti a farbe.
- Pre každú reálnu hranu grafu {u,v} sú v grafe dve inštancie rozhrania Edge. Jedna ide z u do v a druhá naopak. Graf je však neorientovaný, takže predstavujú to isté a majú tú istú hodnotu (value). Jedna z nich je tzv primárna a je uložená v zozname všetkých hrán grafu.
- Vo vašich častiach programu pristupujte k grafu len pomocou metód z rozhraní Graph, Vertex a Edge.
interface Graph { int getNumberOfVertices(); Iterable<Vertex> getVertices(); int getNumberOfEdges(); Iterable<Edge> getEdges(); Vertex getVertex(int index); Iterable<Vertex> adjVertices(Vertex vertex); Iterable<Integer> adjVertexIds(int n); Iterable<Edge> adjEdges(Vertex vertex); Iterable<Edge> adjEdges(int n); Vertex addVertex(double x, double y); void removeVertex(Vertex vertex); Edge addEdge(Vertex v1, Vertex v2); Edge addEdge(int n1, int n2); void removeEdge(Edge e); void removeEdge(Vertex v1, Vertex v2); void removeEdge(int n1, int n2); Edge findEdge(Vertex v1, Vertex v2); Edge findEdge(int n1, int n2); void clear(); void print(PrintStream out, boolean full); void read(Scanner s) throws DataFormatException; } interface Edge { Vertex getOrigin(); int getOriginId(); Vertex getDestination(); int getDestinationId(); boolean isEquivalent(Edge e); // su e a this ta ista hrana, prip. opacne? Edge getReverse(); //opacna reprezentacia tej istej hrany Edge getPrimary(); int getValue(); void setValue(int value); } interface Vertex { int getId(); Iterable<Vertex> adjVertices(); Iterable<Integer> adjVertexIds(); Iterable<Edge> adjEdges(); Edge findEdge(Vertex v); int getValue(); void setValue(int value); String getColorName(); void setColorName(String color); double getX(); void setX(double X); double getY(); void setY(double Y); double getSize(); void setSize(double size); }
GraphImplementation, VertexImplementation, EdgeImplementation, ExtendedGraph
Sú pomocné triedy reprezentujúce graf a jeho súčasti
- Prvé tri triedy implementujú rozhrania popísané vyššie
- ExtendedGraph pridáva metódy potrebné na zobrazovanie a notifikuje grafické prvky o zmenách v grafe.
- Vo vašich častiach programu nevolajte priamo metódy týchto tried, používajte len rozhrania uvedené vyššie.
MyPanel, GraphGUI
Sú rozšírenia grafických kompomentov JavaFx, upravených pre naše grafové prostredie
- MyPanel je grafický panel pre zobrazovanie a editovanie samotného grafu
- GraphGUI obsahuje metódu main a stará sa o všetky zvyšné časti hlavného grafického okna, t.j. panel napravo so zoznamom susedov zvoleného vrchola, gombíky, menu
- Všetky zmeny v grafe sa automaticky zobrazujú v paneli a ostatných častiach hlavného okna
ExamSpecifics
ExamSpecifics je trieda, ktorá obsahuje niekoľko nastavení, ktoré sa môžu v závislosti od zadaní skúšky meniť. Konkrétne tam sú
- meno vstupného a výstupného súboru
- volanie triedy Editor, ktorú budete implementovať v úlohe A
- prípadne vám sem môžeme dať aj ďalšie hotové časti kódu
Všetky ostatné triedy budú na skúške úplne rovnaké, ako v súbore, ktorý si môžete stiahnuť už teraz (s výnimkou prípadných opráv chýb, ktoré sa pokúsime taktiež vopred zverejniť, nemali by však ovplyvniť vami používané rozhrania).
GraphAlgorithm
Trieda, ktorá má obsahovať implementáciu požadovaného grafového algoritmu z úlohy B.
- Používajte iba metódy rozhraní Graph, Vertex, Edge.
- Nemeňte hlavičky public metód ani konštruktora, môžete si však pridať do triedy ďalšie premenné, metódy, prípadne aj pomocné triedy.
Konštruktor dostane graf, ako aj informácie o tom, ktorý vrchol a hrana boli práve v programe vybrané (selected).
- Vybraný vrchol a hrana môžu byť aj null.
- V konštruktor tieto údaje uloží.
- Podľa potreby pridajte inicializáciu vašich ďalších premenných.
Metóda performAlgorithm má vykonať samotný algoritmus
- podľa pokynov v zadaní môže modifikovať graf
- napr. často je úlohou nejako prefarbiť vrcholy
- výsledok výpočtu vráti ako String
- použite formát požadovaný v zadaní
Ukážkový príklad tejto triedy, ktorý spustí prehľadávanie do hĺbky zo zvoleného vrchola a prefarbí všetky navštívené vrcholy na oranžovo. Ak je selectedVertex null, nerobí nič. Vráti správu s počtom vrcholov, ktoré prehľadávanie navštívilo.
package graphgui; /** Trieda, ktorá má obsahovať implementáciu požadovaného grafového * algoritmu z úlohy B. V tejto triede používajte iba metódy z * rozhraní Graph, Edge a Vertex. Nevolajte priamo žiadne metódy * z iných tried balíčka graphgui. Nemeňte hlavičku * konštruktora ani metódy performAlgorithm, môžete však * samozrejme zmeniť ich telo a pridávať do triedy vlastné metódy, * premenné a pomocné triedy. */ public class GraphAlgorithm { //PREMENNÉ TRIEDY, UPRAVTE SI PODĽA POTREBY /** Samotný graf, na ktorom spúšťame algoritmus */ private Graph graph; /** Vybraný vrchol, ktorý je parametrom algoritmu, * môže byť aj null. */ private Vertex selectedVertex; /** Vybraný vrchol, ktorý je parametrom algoritmu, * môže byť aj null. */ private Edge selectedEdge; // KONŠTRUKTOR: NEMEŇTE HLAVIČKU, TELO UPRAVTE PODĽA POTREBY /** Konštruktor triedy, ktorý dostane graf, vybraný vrchol a hranu * (tento vrchol a hrana môžu byť aj null). */ public GraphAlgorithm(Graph graph, Vertex selectedVertex, Edge selectedEdge) { // uloz vstupne udaje this.graph = graph; this.selectedVertex = selectedVertex; this.selectedEdge = selectedEdge; } // METÓDA performAlgorithm: NEMEŇTE HLAVIČKU, TELO UPRAVTE PODĽA POTREBY /** Metóda, ktorá spustí výpočet a upraví graf podľa špecifikácie * v zadaní úlohy. Vráti výsledok v textovej forme sformátovaný podľa * pokynov v zadaní. */ public String performAlgorithm() { // ukazkovy jednoduchy algoritmus: // ak selectedVertex nie je null, spustíme z neho // prehľadávanie do hĺbky, pričom počítame navštívené vrcholy // a nafarbíme ich na oranžovo int visitedVertices = 0; // ak bol nejaky vrchol vybrany if (selectedVertex != null) { // vytvor pole visited, vypln false int n = graph.getNumberOfVertices(); boolean[] visited = new boolean[n]; for(int i=0; i<n; i++) { visited[i] = false; } // rekurzivne prehladavanie search(visited, selectedVertex); // prefarbi a spocitaj navstivene vrcholy for(int i=0; i<n; i++) { if(visited[i]) { graph.getVertex(i).setColorName("orange"); visitedVertices++; } } } // v tejto ukazke vratime pocet navstivenych vrcholov return "Pocet navstivenych vrcholov: " + visitedVertices; } // POMOCNÉ METÓDY A TRIEDY, MEŇTE A PRIDÁVAJTE PODĽA POTREBY // v ukážkovom príklade metóda search rekurzívne prehľadáva graf do hĺbky private void search(boolean[] visited, Vertex vertex) { visited[vertex.getId()] = true; for(Vertex neighbour : vertex.adjVertices()) { if(!visited[neighbour.getId()]) { visited[neighbour.getId()] = true; search(visited, neighbour); } } } }
Editor
Trieda, ktorá má obsahovať implementáciu úlohy A na skúške.
Konštrukor dostane odkaz na graf (typu Graph)
Vašou úlohou bude modifikovať metódy tejto triedy. Metódy triedy sú volané v prípade stlačenia gombíka Edit podľa toho, ako to bude špecifikované v ExamSpecifics.java.
- V príklade nižšie, ak je vybratý vrchol, zavolá sa metóda editVertex a ak nie je vybratý vrchol, zavolá sa metóda edit
Odporúčame vyhadzovať aj nejaké výnimky, sú odchytávané a vypísané na System.err.
V tomto príklade sa v prípade, že je vybratý vrchol, zobrazí sa dialóg, ktorým sa dá meniť farba tohto vrchola podľa jej red, green a blue zložiek.
public class Editor { // POMOCNÁ TRIEDA PRE UKÁŽKOVÝ PRÍKLAD, MEŇTE PODĽA POTREBY class MyStage extends Stage { Vertex v; MyStage(Vertex vertex) { v=vertex; MyStage dialog=this; GridPane pan=new GridPane(); Scene sc = new Scene(pan); Color c = Color.web(vertex.getColorName()); TextField rText= new TextField((int)(c.getRed()*255)+""); TextField gText= new TextField((int)(c.getGreen()*255)+""); TextField bText= new TextField((int)(c.getBlue()*255)+""); Label rLabel=new Label("Red (0-255): "); Label gLabel=new Label("Green (0-255): "); Label bLabel=new Label("Blue (0-255): "); Button ok=new Button("OK"); ok.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { try { double r=(double)Integer.parseInt(rText.getText()); double g=(double)Integer.parseInt(gText.getText()); double b=(double)Integer.parseInt(bText.getText()); if (r>=0 && g>=0 && b>=0 && r<256 && g<256 && b<256) { vertex.setColorName(new Color(r/255,g/255,b/255,1).toString()); dialog.close(); } } catch (Exception e) { System.err.println("Problem s nastavenim farby"); } } }); pan.getColumnConstraints().add(new ColumnConstraints(100)); pan.setRowIndex(rLabel,0); pan.setColumnIndex(rLabel,0); pan.setRowIndex(rText,0); pan.setColumnIndex(rText,1); pan.setRowIndex(gLabel,1); pan.setColumnIndex(gLabel,0); pan.setRowIndex(gText,1); pan.setColumnIndex(gText,1); pan.setRowIndex(bLabel,2); pan.setColumnIndex(bLabel,0); pan.setRowIndex(bText,2); pan.setColumnIndex(bText,1); pan.setRowIndex(ok,3); pan.setColumnIndex(ok,1); pan.getChildren().addAll(rLabel,rText,gLabel,gText,bLabel,bText, ok); this.setScene(sc); } } // PREMENNÉ TRIEDY, UPRAVTE SI PODĽA POTREBY private Graph graph; // KONŠTRUKTOR: NEMEŇTE HLAVIČKU, TELO UPRAVTE PODĽA POTREBY /** Konštruktor triedy, ktorý dostane graf. */ public Editor(Graph graph) { this.graph = graph; } // METÓDA editVertex: NEMEŇTE HLAVIČKU, TELO UPRAVTE PODĽA POTREBY /** * Akcia, ktorá sa má vykonať v prípade, že je vybratý nejaký vrchol * @param vertex vybraný vrchol */ public void editVertex(Vertex vertex) { MyStage dialog=new MyStage(vertex); dialog.initStyle(StageStyle.UTILITY); dialog.initModality(Modality.APPLICATION_MODAL); dialog.show(); } // METÓDA editEdge: NEMEŇTE HLAVIČKU, TELO UPRAVTE PODĽA POTREBY /** * Akcia, ktorá sa má vykonať v prípade, že je vybratá nejaká hrana * @param edge vybraná hrana */ public void editEdge(Edge edge) { } // METÓDA edit: NEMEŇTE HLAVIČKU, TELO UPRAVTE PODĽA POTREBY /** * Akcia, ktorá sa má vykonať v prípade, že nie je vybratý žiadny vrchol */ public void edit() { } }
Letný semester, projekt
- Súčasťou bodovania je aj nepovinný projekt, za ktorý môžete dostať 10% bonus.
- Je to príležitosť vyskúšať si písanie väčšieho programu, pričom v projekte máte väčšiu voľnosť ako pri domácich úlohách
- Projekty, ktoré nebudú spĺňať podmienky uvedené nižšie, budú hodnotené 0 bodmi, nemá teda význam odovzdávať nedokončené programy
Požiadavky na projekt
- Projekt musí byť na jednu z tém nižšie. Témy však nepopisujú presné požiadavky, poskytujú len námety, z ktorých si môžete vybrať a prípadne ich ďalej rozšíriť.
- Projekt musí byť napísaný v Jave a spustiteľný v Netbeans v učebniach na fakulte a napísaný prehľadne s dostatkom komentárov.
- Používať môžete len štandardné javovské knižnice (a prípadne JUnit). Okrem toho samozrejme môžete používať kód poskytnutý v rámci predmetu. Zvyšok programu by mal byť z väčšej časti napísaný vami. Ak použijete nejaké úryvky kódu z internetu alebo iných zdrojov, v komentároch jasne uveďte zdroj.
- Program by mal poskytovať grafické užívateľské prostredie, mal by byť príjemne ovládateľný a mal by sa vyrovnať aj s neobvyklým správaním používateľa (nemal by padať na chyby, okrem naozaj závažných neobvyklých situácií ako nedostatok pamäte).
- Váš program by mal byť schopný načítavať spracovávané dáta zo súboru a po ich prípadnej zmene ich opäť do súboru uložiť. Formát súboru si môžete zvoliť, program by však nemal padať, ak dostane súbor v zlom formáte alebo s nezmyselnými dátami.
Projekt spĺňajúci tieto požiadavky získa aspoň 5% bonusových bodov, pričom je možné získať až 10% v závislosti od náročnosti a kvality vypracovania projektu.
Termíny a odovzdávanie
- V prípade, že chcete robiť projekt, treba si vybrať jednu z ponúkaných tém do pondelka 9.5.2015 22:00
- Doporučujeme však toto rozhodnutie spraviť čím skôr, aby ste na projekt mali dosť času, koncom semestra býva veľa práce na všetkých predmetoch.
- Do uvedeného termínu odovzdajte na testovači súbor .txt, ktorý bude obsahovať, ktorú tému ste si vybrali a meno jedného alebo dvoch študentov, ktorí budú na tejto téme robiť. Stačí, ak sa na projekt prihlási jeden z členov dvojice.
- Testovač vyhlási, že sa súbor nedá skompilovať, čo si nevšímajte.
- Samotný projekt je potrebné odovzdať klasicky ako domácu úlohu. Termín určíme neskôr, bude niekedy v prvom týždni skúškového.
- Odovzdajte v jednom zazipovanom adresári:
- podadresár obsahujúci zdrojový kód a ďalšie potrebné súbory, napr. bitmapy,
- podadresár obsahujúci niekoľko príkladov vstupných súborov,
- stručný popis projektu vo formáte txt, v ktorom vymenujte, aké možnosti váš program používateľovi poskytuje.
- Projekt je tiež potrebné prísť predviesť vyučujúcim v dopredu oznámených termínoch (tiež pravdepodobne počas 1.týždňa skúškového obdobia).
- Na tieto termíny sa bude zapisovať v AISe.
- Zverejníme odhadovaný časový rozvrh, aby ste vedeli, kedy približne prídete na radu.
- Vyučujúcim predvediete používanie vášho programu a hlavné časti zdrojového kódu, podobne ako na poobednajšej časti skúšky.
- Body oznámime prostredníctvom odovzdávača až po prezretí všetkých projektov.
Témy projektov
- Rozšírte DÚ11 (bludisko) o grafické rozhranie, ktoré zobrazí plochu, umožňuje používateľovi túto plochu editovať a prezerať a vyhľadávať počet ciest zo štartovacieho políčka. Pri editovaní plochy sa pokúste ponúknuť paletu viacerých nástrojov na vytváranie rôznych plôšok buď pevne daného tvaru alebo s náhodným prvkom. Môžete rozšíriť program aj o zobrazovanie ciest a podobne.
- Rozšírte DÚ12 (diamanty) o grafické rozhranie umožňujúce používateľovi vytvárať si hraciu plochu a modifikovať ju pomocou pridávania alebo úpravy kameňov. Tiež by ste mali umožniť režim pre hranie hry - umožnenie korektných ťahov a následnú likvidáciu vybuchnutých kameňov. Môžete tiež umožniť hru pre dvoch hráčov, ktorí sa budú striedať a aplikácia im bude počítať skóre.
- Rozšírte DÚ13 (roboty) o grafické rozhranie, ktoré zobrazí hernú plochu a dovolí používateľovi vytvoriť alebo editovať postupnosť robotov aj s ich nastaveniami (čas príchodu, typ, inicializačný parameter) a potom simuluje roboty a vypisuje aktuálne štatistiky úspešných a mŕtvych robotov. Môžete prípadne naprogramovať aj editor hernej plochy. Môžete tiež do hry pridať ďalšie typy robotov alebo políčok.
- Rozšírte DÚ15 (mnohouholníky) o ďalšie možnosti editovania mnohouholníkov. Používateľ bude môcť okrem pridávania a mazania vrcholov a hrán aj vrcholy posúvať (pomocou napr. ľavého tlačidla myši). Tiež bude možnosť práce s celým mnohouholníkom (prípadne s jeho časťou vybranou časťou), ktorú bude možné posúvať, škálovať a otáčať podobne, ako to robia grafické editory s obrázkom. Môžete prípadne naprogramovať aj editor vrcholov mnohouholníka - tvar, farby. Pri tejto téme dbajte o to, aby išlo o výrazné rozšírenie funkcionality požadovanej na DÚ.
Prednáška 25
Úvod k predmetu (viac na ďalšej prednáške)
Dôležité zmeny oproti zimnému semestru
- Budeme brať jazyk Java (používame verziu Java SE 8) a objektovo-orientované programovanie
- Iba jedna prednáška do týždňa
- Na rozcvičkách môžu byť aj témy z minulého semestra (práca s poľami, zoznamami, stromami, rekurzia,...), ale v Jave
- Opravné rozcvičky nebudú. V prípade záujmu o nepovinné cvičenia/konzultácie sa dohodnite s cvičiacimi
Začiatok semestra
- Prvá úloha už zverejnená
- Druhá úloha zverejnená budúci týždeň
- Prvá rozcvička v treťom týždni semestra
- Test pre pokročilých pondelok alebo utorok 22.-23.2.
- Prihlasujte sa cez Doodle do štvrtku 18.2.
Hello world
V Netbeans
Vytvorenie projektu:
- V menu zvolíme New Project
- Na prvej obrazovke zvolíme Categories: Java a Projects: Java Application
- Na ďalšej obrazovke Project name: hello a Create Main Class: hello.Hello
- Do súboru Hello.java napíšeme text:
package hello; public class Hello { public static void main(String[] args) { System.out.println("Hello World!"); } }
- Potom spúšťame podobne ako program v jazyku C++
V Linuxe na príkazovom riadku
Ak chcete Javu skúsiť bez použitia Netbeans:
- Vytvoríme adresár hello, v ňom súbor Hello.java s rovnakým obsahom ako vyššie
- Kompilácia javac hello/Hello.java (vznikne súbor hello/Hello.class)
- Spustenie java hello.Hello
- Pozor, meno adresára musí sedieť s menom balíčka (hello), meno súboru s menom triedy (Hello)
- Ak vynecháme riadok package hello, môžeme mať súbor Hello.java priamo v aktuálnom adresári.
Väčší program
- Ukážme si teraz väčší program, v ktorom bude aj načítanie vstupu, polia a rekurzia.
- Je to javová verzia C++ programu na generovanie variácií bez opakovania z minulého semestra.
- Jednotlivé jazykové konštrukty použité v programe rozoberieme nižšie v texte.
Najskôr v C++:
#include <iostream> using namespace std; void vypis(int a[], int k) { for (int i = 0; i < k; i++) { cout << a[i]; } cout << endl; } void generuj(int a[], bool bolo[], int i, int k, int n) { /* v poli a dlzky k mame prvych i cifier, * v poli bolo mame zaznamenane, ktore cifry su uz pouzite, * chceme vygenerovat vsetky moznosti * poslednych k-i cifier */ if (i == k) { vypis(a, k); } else { for (int x = 0; x < n; x++) { if (!bolo[x]) { a[i] = x; bolo[x] = true; generuj(a, bolo, i + 1, k, n); bolo[x] = false; } } } } int main(void) { int k, n; cin >> k >> n; int *a = new int[k]; bool *bolo = new bool[n]; for (int i = 0; i < n; i++) { bolo[i] = false; } generuj(a, bolo, 0, k, n); delete[] a; delete[] bolo; }
- A teraz v Jave:
package hello; import java.util.Scanner; public class Hello { static void vypis(int[] a) { for (int x : a) { System.out.print(" " + x); } System.out.println(); } static void generuj(int[] a, boolean[] bolo, int i, int n) { /* v poli a dlzky k mame prvych i cifier, * v poli bolo mame zaznamenane, ktore cifry su uz pouzite, * chceme vygenerovat vsetky moznosti * poslednych k-i cifier */ if (i == a.length) { vypis(a); } else { for (int x = 0; x < n; x++) { if (!bolo[x]) { a[i] = x; bolo[x] = true; generuj(a, bolo, i + 1, n); bolo[x] = false; } } } } public static void main(String[] args) { int k, n; Scanner s = new Scanner(System.in); k = s.nextInt(); n = s.nextInt(); int[] a = new int[k]; boolean[] bolo = new boolean[n]; for (int i = 0; i < n; i++) { bolo[i] = false; } generuj(a, bolo, 0, n); } }
Základy jazyka Java
Primitívne typy, polia a referencie
Primitívne typy (podobné na C/C++)
- int: 32-bitové číslo so znamienkom, hodnoty v rozsahu -2,147,483,648..2,147,483,647 (ďalšie celočíselné typy byte, short, long)
- double: 64-bitové desatinné číslo s pohyblivou desatinnou čiarkou (a 32-bitový float)
- boolean: hodnota true alebo false
- char: 16-bitový znak v kódovaní Unicode (podporuje teda napr. slovenskú diakritiku)
Lokálne premenné treba inicializovať, inak kompilátor vyhlási chybu:
int y; System.out.println(y); // variable y might not have been initialized
V poliach a v objektoch kompilátor inicializuje premenné na 0, null, resp. false.
Polia
- Polia v Jave vedia svoju dĺžku, nemusíme ju ukladať v dalšej premennej
- Pole musíme alokovať príkazom new:
double[] a; // deklarujeme premennu typu pole desatinnych cisel, zatial ma neinicializoavu hodnotu a = new double[3]; // alokujeme pole troch desatinnych cisel for (int i = 0; i < a.length; i++) { // do pola ulozime cisla 0..2 a[i] = i; }
- Alebo mu môžeme priradiť počiatočné hodnoty: double[] a = {0.0, 1.0, 2.0};
- Java kontroluje hranice polí, napr. System.out.println(a[3]); spôsobí chybu počas behu programu: Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
Referencie
- Každá premenná v Jave obsahuje buď hodnotu primitívneho typu alebo referenciu.
- Referencia, podobne ako smerník v C, predstavuje adresu v pamäti.
- Referencia môže ukazovať na pole alebo objekt, ale nie na primitívny typ.
- Nefunguje smerníková aritmetika.
- Referencie môžu mať hodnotu null, ak neukazujú na žiadnu pamäť.
- Na jedno pole alebo objekt môžeme mať viac referencií:
double[] a = {0.0, 1.0, 2.0}; double[] b = a; // skopiruje referenciu na to iste pole do b a[1]+=2; // zmenime pole, na ktore ukazuju a aj b System.out.println(b[1]); // vypise cislo 3.0 a = new double[2]; // a a b teraz ukazuju na rozne polia
- V Jave nemusíme polia odalokovať, program to spraví sám, keď už na nich nie je žiadna referencia (garbage collection)
Operátory, cykly, podmienky
- Operátory podobne ako C/C++, napr. aritmetické +, -, *, /, %, priradenie =, +=,..., ++, --, logické !, &&, ||, porovnávanie ==, !=, >=,...
- Pozor, pri referenciách operátor == testuje, či ukazujú na tú istú pamäť, nie či je v tej pamäti tá istá hodnota
- Podmienky if, else, switch rovnako ako v C
- Cykly for, while, do .. while podobne ako v C, podobne break a continue
Navyše Java má cyklus for, ktorý ide cez všetky hodnoty v poli aj bez indexovej premennej
- Tu vidíme dva spôsoby ako vypísať obsah poľa
double[] a = {0.0, 1.0, 2.0}; for (int i = 0; i < a.length; i++) { System.out.println(a[i]); } for (double x : a) { System.out.println(x); }
- Pozor, takýto cyklus sa nedá použiť na zmenenie hodnôt v poli:
for (double x : a) { x = 0; // nemeni pole, iba lokalnu premennu x }
Funkcie (statické metódy) a ich parametre
- Ak chceme písať menší program bez vlastných objektov, ako sme robili v C, použijeme statické metódy umiestnené v jednej triede
- Pred každé meno metódy okrem návratového typu píšeme slovo static
- Pred main píšeme aj slovo public, aby bola viditeľná aj mimo aktuálneho balíčku.
- Návratový typ funkcie main je void, argumenty sú v poli reťazcov (nie je tam meno programu ako v C)
package pocet; public class Pocet { public static void main(String[] args) { System.out.println("Pocet argumentov: " + args.length); } }
- Parametre sa odovzdávajú hodnotou
- Ak ide o primitívny typ, funkcii sa skopíruje jeho hodnota
- Ak ide o referenciu na pole alebo objekt, funkcii sa skopíruje táto referencia, funkcia môže teda meniť tento objekt alebo pole
- Nedá sa teda napísať funkcia swap, ktorá vymení obsah dvoch premenných
- Tu je ilustratívny príklad:
static void pokus(int[] a, int x) { a[1] = 5; // zmeni v poli na ktore ukazuje a aj b a = new int[3]; // a ukazuje na nove pole, b na stare System.out.println(a[1]); // vypise 0 x = 6; // zmeni v x, y ostava ta ista } public static void main(String[] args) { int[] b = {1, 2, 3}; int y = 4; pokus(b, y); System.out.println(b[1]); // vypise 5 System.out.println(y); // vypise 4 }
- Návratový typ môže byť void, primitívny typ alebo referencia
- Príkaz return ako v C
Práca s maticami
- V poli môžeme mať aj referencie na iné polia, dostávame tak viacrozmerné matice, podobne ako v C-čku.
- Deklarácia 3-rozmerného poľa: int[][][] a;
- Ak sú všetky rozmery známe, môžeme ho jedným príkazom alokovať, napr. a=new int[2][3][4];
- Môžeme však spraviť napr. trojuholníkovú maticu, v ktorej má každý riadok inú dĺžku:
package hello; public class Hello { static void vypis(int[][] a) { /* vypiseme cisla v matici a na konzolu */ for (int[] riadok : a) { for (int x : riadok) { System.out.print(" " + x); } System.out.println(); } } public static void main(String[] args) { int[][] a = new int[3][]; for (int i = 0; i < a.length; i++) { a[i] = new int[i+1]; for (int j = 0; j < a[i].length; j++) { a[i][j] = i * j; } } vypis(a); } }
Výstup:
0 0 1 0 2 4
- Podobne 3-rozmerné pole s rôzne veľkými podmaticami a riadkami:
static void vypis(int[][][] a) { /* vypiseme cisla v 3D poli a na konzolu */ for (int[][] matica : a) { for (int[] riadok : matica) { System.out.print("["); for (int x : riadok) { System.out.print(" " + x); } System.out.print(" ] "); } System.out.println(); } } public static void main(String[] args) { int[][][] a = new int[3][][]; for (int i = 0; i < a.length; i++) { a[i] = new int[i + 1][]; for (int j = 0; j < a[i].length; j++) { a[i][j] = new int[j + 1]; for (int k = 0; k < a[i][j].length; k++) { a[i][j][k] = i * j * k; } } } vypis(a); }
Výstup:
[ 0 ] [ 0 ] [ 0 1 ] [ 0 ] [ 0 2 ] [ 0 4 8 ]
Reťazce
- Objekt triedy String, po vytvorení sa nedá meniť
- Text medzi úvodzovkami je považovaný za String
- Inicializácia konštantným reťazcom: String greeting = "Hello world!";
- Operátor + konkatenuje (zliepa) reťazce. Ak je jeden operand reťazec, iné typy konvertuje na String:
int x=1; String str = "Hodnota x: " + x;
Prístup k reťazcu:
- dĺžka sa počíta metódou length() a i-ty znak metódou charAt(i)
String str = "Ahoj!"; int len = str.length(); // dlzka retazca for (int i = 0; i < len; i++) { System.out.println(i + ". znak: " + str.charAt(i)); }
Výstup:
0. znak: A 1. znak: h 2. znak: o 3. znak: j 4. znak: !
- Porovnanie reťazcov na rovnosť metódou equals (Pozor, porovnanie == testuje, či ide o to isté miesto v pamäti)
String str1 = "abc"; // retazec abc String str2 = str1; // referencia na ten isty retazec String str3 = str1 + ""; // vznikne novy retazec abc if (str1 == str2) { // true, lebo to iste miesto System.out.println("str1==str2"); } if (str1 == str3) { // false, lebo rozne miesta System.out.println("str1==str3"); } if (str1.equals(str3)) { // true, lebo zhodne retazce System.out.println("str1.equals(str3)"); }
- Veľa ďalších metód, pozri dokumentáciu
- Ak potrebujete reťazec meniť, napr. k nemu postupne pridávať, môžete použiť StringBuilder
- Rýchlejšie ako stále vyrábať nové reťazce pomocou operátora + (pre spájanie malého počtu častí stačí String)
- Napr. dva spôsoby ako vytvoriť reťazec abeceda obsahujúci písmená a..z:
// Pomocou String, postupne vytvorí 27 rôznych String-ov String abeceda = ""; for (char c = 'a'; c <= 'z'; c++) { abeceda = abeceda + c; // vytvori novy String, naplni ho novym obsahom }
// Pomocou StringBuilder, vytvorí jeden StringBuilder a jeden String StringBuilder buffer = new StringBuilder(); for (char c = 'a'; c <= 'z'; c++) { buffer.append(c); // modifikuje objekt buffer } String abeceda = buffer.toString(); // vytvori novy String
Vstup, výstup, súbory
- Java má rozsiahle knižnice, uvádzame len návod na základnú prácu s textovými súbormi.
- Vo väčšine prípadov potrebujeme triedy z balíčku java.io, takže si ich môžeme naimportovať všetky: import java.io.*;
- Trieda Scanner je v balíčku java.util, použijeme teda import java.util.Scanner;
- V prípade, že pri práci so súbormi nastane nečakaná chyba, Java použije mechanizmus výnimiek (exception)
- O výnimkách sa budeme učiť neskôr, nateraz len do metódy main (a prípadne ďalších metód) pridáme upozornenie, že výnimka môže nastať:
- public static void main(String[] args) throws java.io.IOException { ... }
Písanie na konzolu
- System.out.print(retazec)
- System.out.println(retazec) - pridá koniec riadku
- Reťazec môžeme vyskladať z viacerých častí rôznych typov pomocou +
- Formátovanie podobné na printf v C-čku: System.out.format("%.1f%n", 3.15); vypíše číslo na jedno desatinné miesto, t.j. 3.2 a koniec riadku podľa operačného systému.
- Viac detailov v dokumentácii alebo v tutoriáli
Čítanie z konzoly
- Objekt System.in je typu FileInputStream a podporuje iba čítanie jednotlivých bajtov resp. polí bajtov
- Lepšie sa pracuje, ak ho použijeme ako súčasť objektu, ktorý vie tieto bajty spracovať do riadkov, čísel a pod.
- Trieda BufferedReader umožňuje čítať celý riadok naraz ale aj znak po znaku. Tu je príklad jej použitia:
package hello; import java.io.*; // potrebujeme nejake triedy z balicka java.io public class Hello { public static void main(String[] args) throws java.io.IOException { // musime pridat oznam, ze moze vzniknut vynimka - chyba pri citani BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); while (true) { // nacitame riadok do retazca String line = in.readLine(); // skoncime, ked uzivatel zada prazdny riadok alebo ked prideme na koniec vstupu (null) if (line == null || line.equals("")) { break; } // vypiseme nacitany riadok System.out.println("Napisali ste riadok \"" + line + "\""); } System.out.println("Koncime..."); } }
Príklad behu programu:
Ahoj Napisali ste riadok "Ahoj" 1 2 3 Napisali ste riadok "1 2 3" Koncime...
- Metóda readLine() teda číta celý riadok (odstráni znak pre koniec riadku), metóda read() číta jeden znak (na konci súboru vráti -1)
- Trieda Scanner rozkladá vstup na slová oddelené bielymi znakmi (medzery, konce riadku a pod.) a prípadne ich premieňa na čísla.
- Príklad programu, ktorý vypisuje slová načítané od užívateľa, kým nezadá END alebo neskončí vstup
package hello; import java.util.Scanner; public class Hello { public static void main(String[] args) { Scanner s = new Scanner(System.in); // inicializujeme Scanner int num = 0; while (s.hasNext()) { // kym neskonci vstup String word = s.next(); // nacitame slovo if (word.equals("END")) { // skoncili sme ak najdeme END break; } System.out.println("Slovo " + num + ": " + word); // vypiseme slovo num++; } } }
Príklad behu programu:
Ahoj Slovo 0: Ahoj 1 2 3 END Slovo 1: 1 Slovo 2: 2 Slovo 3: 3
- Metóda nextInt() vráti ďalšie slovo konvertované na int (pozri program s rekurziou vyššie), overiť si, či nasleduje číslo, môžeme vopred metódou hasNextInt(). Podobne nextDouble().
Práca so súbormi
Čítanie zo súboru funguje podobne ako čítanie z konzoly, iba inak inicializujeme použitý objekt:
- Scanner vytvoríme príkazom Scanner s = new Scanner(new File("vstup.txt"));
- File reprezentuje súbor s určitým menom, potrebujeme pridať import java.io.File; alebo import java.io.*;
- BufferedReader vytvoríme príkazom BufferedReader in = new BufferedReader(new FileReader("vstup.txt"));
- Scanner aj BufferedReader umožňujú zavrieť súbor metódou close()
Písanie do súboru môžeme robiť napr. triedou PrintStream
- Otvorenie súboru: PrintStream out = new PrintStream("vystup.txt");
- Potom používame staré známe metódy print, println, format ako pri System.out (napr. out.println("Ahoj"))
- Na konci zavoláme out.close()
- Tento spôsob otvárania súborov existujúci obsah premaže
- Ak chceme pridávať na koniec súboru, použijeme PrintStream out = new PrintStream(new FileOutputStream("vystup.txt",true));
Matematika a pseudonáhodné čísla
- V triede Math nájdete rôzne matematické konštanty a funkcie
- Napr. Math.PI, Math.cos(x), Math.min(x,y) Math.pow(x,y),...
- Triedy na prácu s veľkými číslami a ďalšie matematické funkcie nájdete v balíčku java.math
Pseudonáhodné čísla
- Math.random() vygeneruje double z intervalu [0,1)
- Väčšie možnosti poskytuje trieda Random v balíčku java.util (generuje celé čísla, bity), umožňuje nastaviť počiatočnú hodnotu
Cvičenia 14
Cvičenia 14
Na tomto cvičení si vyskúšame spustiť program v Jave a mierne ho modifikovať. Ďalšie detaily k jazyku Java budú na prednáške.
Spúšťanie programu Hello world
V Netbeans
Vytvorenie projektu:
- V menu zvolíme New Project
- Na prvej obrazovke zvolíme Categories: Java a Projects: Java Application
- Na ďalšej obrazovke Project name: hello a Create Main Class: hello.Hello
- Do súboru Hello.java napíšeme text:
package hello; public class Hello { public static void main(String[] args) { System.out.println("Hello World!"); } }
- Potom spúšťame podobne ako program v jazyku C++
V Linuxe na príkazovom riadku
Ak chcete Javu skúsiť bez použitia Netbeans:
- Vytvoríme adresár hello, v ňom súbor Hello.java s rovnakým obsahom ako vyššie
- Kompilácia javac hello/Hello.java (vznikne súbor hello/Hello.class)
- Spustenie java hello.Hello
- Pozor, meno adresára musí sedieť s menom balíčka (hello), meno súboru s menom triedy (Hello)
- Ak vynecháme riadok package hello, môžeme mať súbor Hello.java priamo v aktuálnom adresári.
Väčší program
- Ukážme si teraz väčší program, v ktorom bude aj načítanie vstupu, polia a rekurzia.
- Je to javová verzia C++ programu na generovanie variácií bez opakovania z minulého semestra.
- Jednotlivé jazykové konštrukty použité v programe rozoberieme nižšie v texte.
- Jednotlivé jazykové konštrukty použité v programe rozoberieme na prednáške, pokúste sa ale z kontextu pochopiť, čo sa na jednotlivých riadkoch programu asi deje.
Najskôr v C++:
#include <iostream> using namespace std; void vypis(int a[], int k) { for (int i = 0; i < k; i++) { cout << a[i]; } cout << endl; } void generuj(int a[], bool bolo[], int i, int k, int n) { /* v poli a dlzky k mame prvych i cifier, * v poli bolo mame zaznamenane, ktore cifry su uz pouzite, * chceme vygenerovat vsetky moznosti * poslednych k-i cifier */ if (i == k) { vypis(a, k); } else { for (int x = 0; x < n; x++) { if (!bolo[x]) { a[i] = x; bolo[x] = true; generuj(a, bolo, i + 1, k, n); bolo[x] = false; } } } } int main(void) { int k, n; cin >> k >> n; int *a = new int[k]; bool *bolo = new bool[n]; for (int i = 0; i < n; i++) { bolo[i] = false; } generuj(a, bolo, 0, k, n); delete[] a; delete[] bolo; }
- A teraz v Jave:
package hello; import java.util.Scanner; public class Hello { static void vypis(int[] a) { for (int x : a) { System.out.print(" " + x); } System.out.println(); } static void generuj(int[] a, boolean[] bolo, int i, int n) { /* v poli a dlzky k mame prvych i cifier, * v poli bolo mame zaznamenane, ktore cifry su uz pouzite, * chceme vygenerovat vsetky moznosti * poslednych k-i cifier */ if (i == a.length) { vypis(a); } else { for (int x = 0; x < n; x++) { if (!bolo[x]) { a[i] = x; bolo[x] = true; generuj(a, bolo, i + 1, n); bolo[x] = false; } } } } public static void main(String[] args) { int k, n; Scanner s = new Scanner(System.in); k = s.nextInt(); n = s.nextInt(); int[] a = new int[k]; boolean[] bolo = new boolean[n]; for (int i = 0; i < n; i++) { bolo[i] = false; } generuj(a, bolo, 0, n); } }
Úlohy
- Skompilujte program, spustite ho a zadajte dve čísla, napr. 3 a 2. Mal by vypísať všetky variácie druhej triedy bez opakovania z množiny {0,1,2}, t.j. dvojice navzájom rôznych čísel.
- Zmeňte program tak, aby vypísal aj celkový počet týchto variácií.
- Zmeňte návratový typ metódy (funkcie) generuj z void na int a prepíšte ju tak, aby vrátila počet riešení, ktoré vypísala (vrátane tých vypísaných v rekurzívnych volaniach)
- V metóde main si výsledok uložte do premennej pocet typu int a vypíšte ho príkazom System.out.println("Pocet rieseni je " + pocet);
- Zmeňte program tak, aby vypisoval kombinácie, t.j. podmnožiny veľkosti k z množiny {0,1,...,n-1}. Aby sme každú kombináciu vypísali iba raz, budeme vyžadovať, že prvky sú v poli a rastúcom poradí, teda pre k=3, n=5 povolíme v poli a napr. 0,1,4, ale nepovolíme 0,4,1.
- V tomto programe môžete zrušiť pole bolo, ale zato musíte zmeniť cez aké hodnoty prechádza cyklus premennej x vo funkcii generuj.
Prednáška 26
Úvod do predmetu
Ciele predmetu
- prehĺbiť a rozšíriť zručnosti v algoritmickom uvažovaní, písaní a ladení programov z predchádzajúceho semestra
- oboznámiť sa so základnými programovými a dátovými štruktúrami jazyka Java
- zvládnuť základy objektovo-orientovaného programovania a tvorby programov s grafickým užívateľským rozhraním
- oboznámiť sa so základnými algoritmami na prácu s grafmi
Technické detaily
- Budeme používať verziu Java SE 8
- Na testovači zatiaľ beží Java SE 7, väčšina vecí by mala fungovať rovnako
- Budeme naďalej používať systém Netbeans, ale
- Pre Javu je lepšie prispôsobený ako pre C/C++
- Netbeans čisto pre Javu by mal byť jednoduchšie nainštalovateľný pod Windows
- Ak viete ako, môžete používať aj v iné prostredia, napr. Eclipse, prípadne textový editor. Pozor, na skúške len štandardné Linuxové prostredie v učebniach.
Literatúra
- Knihy na prezenčné štúdium vo fakultnej knižnici:
- B. Eckel: Thinking in Java. 4th edition Prentice-Hall, 2006. Signatúra I-INF-E-2
- Staršie 2. vydanie v českom preklade v knižnici so signatúrou D-INF-E-1b, 3. vydanie v angličtine online, doporučujeme však radšej 4. vydanie
- M. T. Goodrich, R. Tamassia: Data Structures and Algorithms in Java. John Wiley & Sons, 2006. Signatúra D-INF-G-5
- R. Sedgewick: Algorithms in Java, Parts 1-4, Fundamentals, Data Structures, Sorting, Searching, Addison-Wesley, 2003. Signatúra D-INF-S-1/I-IVa
- R. Sedgewick: Algorithms in Java, Part 5, Graph Algorithms Addison-Wesley, 2004. Signatúra D-INF-S-1/V (I)
- B. Eckel: Thinking in Java. 4th edition Prentice-Hall, 2006. Signatúra I-INF-E-2
- Dokumentácia k jazyku Java od firmy Oracle (používame verziu Java SE 8):
- Tutoriál jazyka Java
- referenčná príručka k štandardným knižniciam
Pravidlá na tento semester
- Podobné ako minulý semester
- Pozrite si pravidlá na stránke predmetu http://compbio.fmph.uniba.sk/vyuka/prog/
- Zmeny oproti minulému semestru:
- Iba jedna prednáška do týždňa
- Menej väčších úloh, vyžadujú priebežnú prácu
- Nebudú bonusy na DÚ
- Bude nepovinný bonusový projekt za 10%. Témy na projekt a bližšie informácie oznámime neskôr. Odporúčame robiť vo dvojiciach.
- Na rozcvičkách môžu byť aj témy z minulého semestra (práca s poľami, zoznamami, stromami, rekurzia,...), ale v Jave
- Opravné rozcvičky nebudú. V prípade záujmu o nepovinné cvičenia/konzultácie sa dohodnite s cvičiacimi
- Na skúške treba mať celkovo aspoň polovicu bodov, ale nemusí byť jeden príklad celý dobre
Odporúčania
- Neopisujte
- Pracujte na DÚ priebežne, nie tesne pred termínom
- Ak niečomu nerozumiete alebo potrebujete poradiť s DÚ, pýtajte sa
Organizačné poznámky
- Dnes ešte test pre pokročilých, stretnutie o 14:00 pred M-I.
- Budúci týždeň: cvičenia s rozcvičkou, prednáška (dedenie)
- DÚ11 je zverejnená, odovzdávajte do pondelka 29.2. 22:00, odporúčam spraviť čím skôr, aby ste sa zorientovali v Jave.
- DÚ12 bude zverejnená začiatkom tohto týždňa
Objektovo orientované programovanie (OOP)
- Java je objektovo-orientovaný jazyk a teda skoro všetko v Jave je objekt
- Základným pojmom OOP je trieda (class)
- Trieda je typ združujúci niekoľko hodnôt, podobne ako struct v C
- Navyše ale trieda obsahuje metódy (funkcie), ktoré s týmito hodnotami pracujú
- Objekty sú inštancie triedy
- Napríklad trieda Zlomok môže mať položky citatel a menovatel a konkrétnou inštanciou, objektom je napríklad zlomok z čitateľom 2 a menovateľom 3 vytvorený v programe
Napríklad v Cčku by jednoduchý zásobník int-ov implementovaný pomocou poľa a funkcia pop, ktorá z neho vyberie prvok, mohli vyzerať takto:
struct Stack { int *data; int pocet; }; int pop(Stack &s) { s.pocet--; return s.data[s.pocet]; }
Keď to prepíšeme ako triedu v Jave, vyzerá to podobne, ale:
- slovo struct sa nahradí slovom class
- metóda pop sa presunie do vnútra definície triedy
- metóda pop nedostane zásobník ako argument a k jeho položkám pristupuje priamo ich menami, t.j. napr. data a pocet
public class Stack { int data[]; int pocet; int pop() { pocet--; return data[pocet]; } }
Metódy sa potom volajú pre konkrétny zásobník, napr.
Stack s; // tu pridu prikazy na vytvorenie a naplnenie zasobnika int x = s.pop() // vyberie prvok zo zasobnika s
V Cčku by sme písali
Stack s; // tu pridu prikazy na vytvorenie a naplnenie zasobnika int x = pop(s);
Ak máme premennú s typu Stack, k jej premenným a metódam pristupujeme pomocou operátora .
- napr. s.pop(), s.pocet
- Java nemá operátor ->
- Ale pozor, premenná s typu Stack je referencia
- Po príkaze Stack t = s; premenné s a t ukazujú na to isté miesto v pamäti, na ten istý zásobnik
- Čo by podobný príkaz spravil v Cčku? V tomto prípade asi nie to, čo chceme...
Konštruktor a vznik objektov
V Cčku sme pre zásobník mali metódu init, ktorá inicializovala hodnoty pre prázdný zásobník, napr. takto:
void init(Stack &s) { s.data = new int[MAXN]; s.pocet = 0; }
Objekty sa inicializujú špeciálnou metódou, konštruktorom
- Názov konštruktora je názov triedy. Teda konštruktor triedy Stack bude metóda Stack()
- Konštruktor nemá v hlavičke návratovú hodnotu, môže však mať parametre.
public class Stack { Stack() { data = new int[MAXN]; pocet = 0; } ... }
Príkaz Stack s; vytvorí referenciu s, ktorá je však zatiaľ neinicializovaná, t.j. nikam neukazuje a Java nám ju nedovolí použiť.
- mohli by sme ju nastaviť na null
Na vytvorenie nového objektu použijeme príkaz new:
s=new Stack();
Príkaz new
- dynamicky alokuje pamäť pre objekt
- zavolá konštruktor objektu
- vráti referenciu - pamäťovú adresu objektu
Viac detailov neskôr
Kontrola prístupu, modifikátory
Trieda a jej súčasti môžu byť odniekiaľ prístupné a odinakiaľ nie. Na úpravu prístupových práv používame modifikátory.
- modifikátor private: premenná/metóda je prístupná iba z metód príslušnej triedy
- keď nepoužijeme modifikátor: trieda/premenná/metóda je prístupná z balíčka (package), kde sa nachádza
- modifikátor protected: podobne ako bez modifikátora, rozdiel uvidíme pri dedení
- modifikátor public: trieda/premenná/metóda je prístupná ľubovoľne
Mená súborov, main:
- public trieda musí byť v súbore nazvanom po tejto triede, ale môžu tam s ňou byť aj ďalšie (pomocné) triedy, ktoré nie sú public
- spustiteľná metóda main musí byť public a umiestnená v public triede
O ďalších modifikátoroch, napr. abstract, static, final, sa dozvieme neskôr
Zapuzdrenie (encapsulation)
- Jedným z hlavných princípov OOP je zapuzdrenie
- Dáta a k nim prislúchajúce metódy zabalíme do triedy
- Kód mimo triedy by k dátam objektu mal pristupovať iba pomocou poskytnutých metód
- Väčšinou teda premenným nastavíme modifikátor private alebo protected a pomocným metódam tiež
- public metódy triedy tvoria našu ponuku pre používateľov triedy
- ak zmeníme vnútornú implementáciu triedy, ale zanecháme rovnaké public metódy a ich správanie, používateľov triedy by to nemalo ovplyvniť
- napríklad v triede Stack sa môžeme rozhodnúť namiesto poľa použiť spájaný zoznam, čím potrebujeme preprogramovať triedu Stack, ale program, ktorý ju používa, sa meniť nemusí
- Zapuzdrenie umožňuje rozdeliť väčší projekt na pomerne nezávislé časti s dobre definovaným rozhraním
public class Stack { public static final int MAXN = 100; private int data[]; private int pocet; public Stack() { data = new int[MAXN]; pocet = 0; } public int pop() { pocet--; return data[pocet]; } public void push(int x) { data[pocet] = x; pocet++; } public boolean isEmpty() { return pocet==0; } }
Get a set metódy
Nakoľko premenné v triedach sú väčšinou private, niektoré triedy ponúkajú nepriamy prístup cez get a set metódy, napr.
class Contact { private String name; private String email; private String phone; public String getName() { return name; } public String getEmail() { return email; } public void setEmail(String newEmail) { email = newEmail; } public String getPhone() { return phone; } public void setPhone(String newPhone) { phone = newPhone; } }
- get a set metódy nerobíme mechanicky pre všetky premenné, iba pre tie, ktoré je rozumné sprístupniť mimo triedu
- ak poskytneme iba get metódu, premenná je zvonku v podstate read-only
- v set metódach môžeme kontrolovať, či je zadaná hodnota rozumná (napr. menovateľ zlomku nemá byť 0)
- get a set metódy nemusia presne korešpondovať s premennými a teda môže sa nám podariť ich zachovať aj po zmene vnútornej reprezentácie
- napr. ak getAngle a setAngle berú uhol v stupňoch, ale rozhodneme sa ho ukladať radšej v radiánoch, môžeme do týchto metód naprogramovať konverziu
class SomeGeometricObject { private double angle; // uhol v radianoch public double getAngle() { return angle*180.0/Math.PI; } public void setAngle(double x) { angle = x*Math.PI/180.0; } }
Ďalšie detaily
Premenná this
V rámci metód triedy premenná this je referencia na konkrétny objekt, na ktorom bola metóda zavolaná.
Napr. ak zavoláme s.pop(), tak vo vnútri metódy pop premenná this ukazuje na s.
- this.premenna je to isté ako premenna
- this.metoda(...) to isté ako metoda(...)
Jedno využitie this je poslanie objektu ako argumentu inej metóde, napr.
public static emptyStack(Stack s) { while(!s.empty()) { s.pop(); } }
V triede Stack potom môžeme mať napr. metódu
public empty() { emptyStack(this); }
Samozrejme logickejšie by bolo naprogramovať vyprázdnenie zásobníka priamo v triede a nie volať externé metódy.
Premenná this sa tiež hodí, ak sa argument metódy volá rovnako ako premenná triedy. Vtedy sa pomocou this vieme dostať k premennej a bez this k argumentu
class Contact { private String email; /** nastav novú emailovú adresu */ public void setEmail(String email) { this.email = email; } }
Viac metód s tým istým menom: overloading
Trieda môže mať niekoľko metód s tým istým menom, ale rôznymi typmi alebo počtom parametrov. Kompilátor vyberie tú, ktorá sa najlepšie hodí použitiu. Napr.
class Contact { private String email; public void setEmail(String email) { this.email = email; } public void setEmail(String username, String domain) { email = username + "@" + domain; } } Contact c = new Contact(); c.setEmail("jozkomrkvicka@gmail.com"); // prva metoda c.setEmail("jozkomrkvicka", "gmail.com"); // druha metoda
Overloading sa dá použiť aj na konštruktory:
class Node { private int data; private Node next; public Node() {} public Node(int data) { this.data = data; } public Node(Node next) { this.next = next; } public Node(int data, Node next) { this.data = data; this.next = next;} public int getData() { return data;} public void setData(int data) { this.data = data;} public Node getNext() { return next;} public void setNext(Node next) {this.next = next;} }
Detaily inicializácie objektov
- príkaz new najskôr inicializuje jednotlivé premenné (na 0, false, null) alebo na hodnotu, ktorú zadáme
class Node { private int data = -1; private Node next; // bude inicializovany na null }
- až potom spúšťa konštruktor
- prvý riadok konštruktora môže volať iný konštruktor tej istej triedy pomocou this(...) - často s menším alebo väčším počtom parametrov
class Node { private int data; private Node next; public Node(int data, Node next) { this.data = data; this.next = next;} public Node(int data) { this(data, null) } ... }
- V prípade, že nedefinujeme pre triedu žiaden konštruktor, bude automaticky vygenerovaný konštruktor bez parametrov
- tento inicializuje premenné na defaultné hodnoty
- defaultný konštruktor je vytvorený iba ak žiaden iný konštruktor neexistuje.
- Ďalšie detaily na prednáške o dedení
Nie všetko v Jave je objekt
Opakovanie:
- Ako sme videli na minulej prednáške, každá premenná obsahuje buď hodnotu primitívneho typu (int, double, bool, char a pod) alebo referenciu
- Referencia môže ukazovať na objekt alebo pole
- Pole môže obsahovať primitívne typy alebo referencie na iné objekty/polia
Wrapper
- Ku každému primitívnemu typu existuje aj zodpovedajúca trieda (wrapper), napr. Integer, Double, ... (viac pozri [1])
- Java medzi primitívnymi typmi a týmito triedami podľa potreby automaticky konvertuje (viac neskôr)
Polia
- Polia sú špeciálny typ objektov, viď napr. premennú a.length, ale aj ďalšie metódy (neskôr)
Javadoc
- Javadoc je systém na vytváranie dokumentácie
- Javadoc komentár začína /** a končí ako klasický komentár */ pričom každý riadok začína *
- Javadoc komentáre sa umiestnia pred triedu, premennú alebo metódu, ktorú chceme popísať
- Prvý riadok Javadoc komentára resp. po prvú bodku je stručný slovný popis. Ďalej pokračujú rôzne podrobnosti.
- Javadoc poskytuje rôzne tag-y [2]
Program Javadoc vie na základe kódu a Javadoc komentárov vygenerovať dokumentáciu (napr. v html formáte)
- dá sa spustiť cez Netbeans v časti Run, Generate Javadoc
- automaticky vytvára dokumentáciu iba k public položkám (keďže tie tvoria rozhranie, API k iným triedam)
- vo vlastnostiach aplikácie časť Documenting sa dá nastavovať
Viď príklad Javadocu v triede nižšie.
Binárny vyhľadávací strom
Príklad binárneho vyhľadávacieho stromu s pomocnou triedou Node a triedou BinarySearchTree.
- Trieda Node obsahuje pomocné metódy a rekurzívne funkcie
- Trieda BinarySearchTree skrýva tieto implementačné detaily pred používateľom, pričom ponúka možnosť pridať prvok a vypísať všetky prvky v utriedenom poradí
/** Trieda reprezentujúca jeden vrchol binárneho vyhľadávacieho stromu. * Každý vrchol v strome obsahuje dáta typu int a referenciu na ľavý a * pravý podstrom. Pre každý vrchol platí, že že všetky vrcholy v jeho * ľavom podstrome majú hodnotu menšiu ako on a všetky vrcholy v * pravom podstrome väčšiu. * * @author Jaba Katreniaková a Broňa Brejová */ class Node { /** Dáta typu int uložené vo vrchole */ private int data; /** Referencia na ľavé dieťa alebo null ak neexistuje */ private Node left; /** Referencia na pravé dieťa alebo null ak neexistuje */ private Node right; /** Konštruktor, ktorý vytvorí nový list * so zadanou hodnotou <code>data</code>. * @param data Dáta uložené v novom vrchole. */ public Node(int data) { this.data = data; } /** Metóda vráti dáta uložené vo vrchole. * @return dáta uložené vo vrchole */ public int getData() { return data; } /** Metóda, ktorá do stromu vloží nový vrchol <code>newNode</code>. * * @param newNode Nový vrchol vložený do stromu. Mal by byť listom. */ public void addNode(Node newNode) { if (newNode.data <= this.data) { if (left == null) { left = newNode; } else { left.addNode(newNode); } } else { if (right == null) { right = newNode; } else { right.addNode(newNode); } } } /** Metóda, ktorá vypíše hodnoty uložené vo vrcholoch podstromu * v inorder poradí, každý na jeden riadok. */ public void printInorder() { if (this.left != null) left.printInorder(); System.out.println(data); if (this.right != null) right.printInorder(); } } /** Trieda reprezentujúca binárny vyhľadávací strom, ktorý má v každom * vrchole dáta typu int. Strom umožňuje pridávať nové dáta a * vypísať dáta v utriedenom poradí. * * @author Jaba Katreniaková a Broňa Brejová */ public class BinarySearchTree { /** Premenná obsahujúca koreň stromu, alebo null, ak je strom prázdny. */ private Node root; /** Konštruktor vytvorí prázdny strom. */ public BinarySearchTree() { } /** Metóda do stromu pridá novú hodnotu <code>data</code>. * Malo by ísť o hodnotu, ktorá sa ešte v strome nenachádza. * @param data Nová hodnota pridaná do stromu. */ public void add(int data) { Node p = new Node(data); if (root == null) { root = p; } else { root.addNode(p); } } /** Metóda vypíše všetky hodnoty v strome v utriedenom poradí, * každú na jeden riadok. */ public void printSorted() { if (root != null) { root.printInorder(); } } /** Metóda je ukážkou použitia binárneho vyhľadávacieho stromu. * Do stromu vloží tri čísla a potom ich vypíše. */ public static void main(String args[]) { BinarySearchTree t = new BinarySearchTree(); t.add(2); t.add(3); t.add(1); t.printSorted(); } }
Pomocné triedy
Nakoniec dva typy pomocných tried, ktoré môžeme použiť na obídenie obmedzení javovských funkcií (metód).
Odovzdávanie parametrov hodnotou
- Všetky parametre sa v Jave odovzdávajú hodnotou - teda vytvorí sa lokálna kópia parametra a jej zmenou nedocielime zmenenie pôvodnej premennej
- Ak je ale parametrom referencia, nakopíruje sa adresa a môžeme teda meniť obsah pamäte, kam ukazuje
- Ak by sme teda parameter chceli meniť, podobne ako pri odovzdávaní premenných referenciou v C, môžeme si vytvoriť wrapper class, ktorý danú hodnotu obalí a umožní k nej pristúpiť cez referenciu
- Knižničné wrapper triedy ako Integer nemôžeme použiť, lebo tie tiež neumožňujú meniť hodnotu už vytvoreného objektu
class MyInteger { private int x; // data public MyInteger(int x) { this.x = x; } // konštruktor public int getValue() { return x; } // získanie hodnoty public void setValue(int x) { this.x = x;} // nastavenie hodnoty } static void swap(MyInteger rWrap, MyInteger sWrap) { // interchange values inside objects int t = rWrap.getValue(); rWrap.setValue(sWrap.getValue()); sWrap.setValue(t); }
Návratová hodnota
Návratová hodnota metódy je buď void, základný typ alebo referencia
- ak teda chceme vrátiť niekoľko hodnôt, musíme si spraviť triedu, ktorá ich spája do jedného celku
static class MyReturn { public int x; // data public char c; public MyReturn(int x, char c) { this.x = x; this.c = c; } } static MyReturn funkcia(int x, char c){ MyReturn temp=new MyReturn(x, c); return temp; } public static void main(String[] args) { MyReturn temp = funkcia(7,'a'); System.out.println(temp.x+" "+temp.c); }
V oboch prípadoch je ale lepšie skúsiť navrhnúť metódy tak, aby neboli takéto pomocné triedy potrebné.
Cvičenia 15
Referencie a polia v Jave
- Odsimulujte nasledovný program a nakreslite, ako budú vyzerať premenné v pamäti v oboch prípadoch, keď sa vykonal riadok označený (*). Zistite, čo program vypíše.
package prog; public class Prog { static void vypis(int[][] a) { if (a != null) { for (int i = 0; i < a.length; i++) { for (int j = 0; j < a[i].length; j++) { System.out.print(" " + a[i][j]); } System.out.println(); } } else { System.out.println("null"); } } static int[] vytvorRiadok() { return new int[2]; } static void vytvorMaticu(int[][] a) { if (a == null) { a = new int[2][]; } a[0] = vytvorRiadok(); a[1] = a[0]; a[0][0] = 1; // (*) } public static void main(String[] args) { int[][] a = new int[2][]; vytvorMaticu(a); int[][] b = null; vytvorMaticu(b); vypis(a); vypis(b); } }
Matice a súbory v Jave
- Napíšte funkciu static int[][] transpose(int[][] a), ktorá transponuje danú maticu a, teda vytvorí a vráti novú maticu, ktorá bude mať v stĺpcoch uložené riadky matice a. Môžete predpokladať, že vstupná matica má všetky riadky rovnako dlhé.
- Napíšte program, ktorý zo súboru matica.txt načíta najprv počet riadkov a stĺpcov matice a potom jej jednotlivé prvky (celé čísla). Do súboru matica2.txt vypíše transponovanú maticu k vstupnej matici v rovnakom formáte, teda najskôr počet riadkov a stĺpcov a potom jednotlivé riadky.
Odovzdávanie DÚ v Jave
Tento semester budete odovzdávať úlohy na tom istom testovači, ale namiesto jedného súboru budeme odovzdávať celý zozipovaný adresár. Ak nie ste príliš skúsení používatelia Linuxu, odporúčame Vám skúsiť si na cvičení odovzdať cvičný príklad na sčítanie čísel.
Náhodné čísla
- Pozrite si dokumentáciu k triede Random v balíčku java.util a napíšte program, ktorý odsimuluje 10 hodov kockou, teda vypíše 10 náhodných celých čísel od 1 po 6.
- Napíšte program, ktorý odsimuluje 10 hodov nevyváženou mincou, pri ktorej v každom hode s pravdepodobnosťou 80% padne hlava a s pravdepodobnosťou 20% prípadoch padne znak. Pomôcka: Ak sa chceme rozhodovať medzi dvoma vecami s určitou pravdepodobnosťou x, môžeme vygenerovať náhodné desatinné číslo z intervalu [0,1) a ak je toto náhodné číslo menej ako x, zvolíme jednu možnosť a ak viac ako x, zvolíme druhú.
Prednáška 27
Organizačné poznámky
- Domáca úloha 12 bola zverejnená, odovzdávajte do 14.3. 22:00.
Opakovanie: triedy a objekty
- Trieda (class) sa podobá na struct, ktorý sme videli v minulom semestri, môže teda obsahovať niekoľko premenných rôznych typov.
- Inštancia triedy alebo aj objekt triedy (instance, object) je konkrétna hodnota daného typu, t.j. kus pamäti s uloženými premennými triedy
- Navyše triedy zvyknú obsahovať aj metódy, ktoré s premennými v objekte niečo robia.
- Konštruktory sú špeciálne metódy na inicializáciu premenných v triede.
- Premenným a metódam triedy môžeme modifikátormi nastaviť, či ich má byť vidno mimo triedy.
- Zvyklosti v jazyku Java: mená tried väčšinou začínajú veľkým písmenom (napr. String), mená metód a premenných malým (napr. print), viacslovné názvy majú veľké písmená na začiatkoch slov, napr toString
- Zapuzdrenie (encapsulation): spojenie dát a súvisiaceho kódu.
- Trieda väčšinou navonok ukazuje iba vhodne zvolenú časť metód.
- Premenné a pomocné metódy sú skryté.
- Preto ich je možné meniť bez zmeny kódu využívajúceho triedu.
public class Stack { public static final int MAXN = 100; private int data[]; private int pocet; public Stack() { data = new int[MAXN]; pocet = 0; } public int pop() { pocet--; return data[pocet]; } public void push(int x) { data[pocet] = x; pocet++; } public boolean isEmpty() { return pocet==0; } public static void main(String[] args) { Stack s = new Stack(); Stack t = s; s.push(7); t.push(5); System.out.println(s.pop()); System.out.println(s.pop()); System.out.println(s.pop()); // co spravi tento prikaz? } }
Dnes pokračujeme ďalšími pojmami: dedenie (inheritance) a rozhranie (interface). K obom týmto oblastiam má Java pekné tutoriály:
Dedenie
- Trieda môže byť podtriedou inej triedy, napr. trieda Pes môže byť podtriedou všeobecnejšej triedy Zviera
- Toto vyjadrujeme kľúčovým slovom extends v definícii triedy class Pes extends Zviera { ... }
- Ak máme niekoľko podobných tried s podobnými metódami, vytvoríme nadtriedu a spoločné časti kódu tam presunieme
- vyhneme sa teda kopírovaniu podobného kódu
Príklad
Uvažujme triedy reprezentujúce rôzne geometrické útvary, ktoré môžeme posúvať v rovine. Takto by mohli vyzerať časti tried bez dedenia:
class Rectangle { int x, y; // suradnice laveho horneho rohu int width, height; // vyska a sirka void move(int deltaX, int deltaY) { x+=deltaX; y+=deltaY; } // plus dalsie metody pre obdlznik } class Circle { int x, y; // suradnice stredu int radius; // polomer void move(int deltaX, int deltaY) { x+=deltaX; y+=deltaY; } // plus dalsie metody pre kruh }
To isté s dedením: spoločné premenné a metódy presunieme do spoločnej triedy Shape a vynecháme z Circle a Rectangle, tie ich dedia:
class Shape { int x, y; // suradnice vyznacneho bodu utvaru (roh, stred a pod.) void move(int deltaX, int deltaY) { x+=deltaX; y+=deltaY; } } class Rectangle extends Shape { int width, height; // plus dalsie metody pre obdlznik } class Circle extends Shape { int radius; // plus dalsie metody pre kruh }
Trieda môže používať premenné a metódy nadtriedy, ako keby boli jej vlastné s výnimkou tých, ktoré sú private (o tom neskôr).
- Napr. v metódach triedy Circle môžeme používať premenné x, y, metódu move.
class Circle extends Shape { int radius; void print() { System.out.println("stred (" + x + "," + y + "), polomer " + radius); } }
Ak máme premennú Circle c, môžeme zavolať metódy c.move(1,1) alebo c.print() alebo použiť premenné c.x, c.y, c.radius
- (v praxi väčšinou chceme premenné v triede skryť)
Dedenie a typy
- Ak máme premennú typu Shape, môže obsahovať referenciu na objekt triedy Shape alebo jej ľubovoľnej podtriedy.
Circle c = new Circle(...); Shape s = c; // toto je dobre // c = s; //! toto neskompiluje - s nemusi byt kruh c = (Circle)s; // pretypujeme, skompiluje ale moze padnut, ak s nie je Circle alebo null if(s instanceof Circle) { // pre istotu kontrola, ci je s Circle alebo podtrieda c = (Circle)s; }
- Toto je výhodné, lebo môžeme rôzne typy útvarov spracovávať tým istým kódom. Napr. nasledujúca funkcia dostane pole útvarov (môžu v ňom byť útvary rôznych typov) a posunie každý z nich o daný vektor (deltaX, deltaY)
static void moveAll(Shape[] shapes, int deltaX, int deltaY) { for(Shape x : shapes) { x.move(deltaX, deltaY); } }
Čo vypíšu tieto riadky?
Shape[] shapes = new Shape[2]; shapes[0] = new Rectangle(0,0,1,2); shapes[1] = new Circle(0,0,1); for(Shape x : shapes) { if (x instanceof Circle) { Circle c = (Circle)x; System.out.println("Je to kruh s polomerom " + c.radius); } if (x instanceof Shape) { System.out.println("Je to utvar"); } System.out.println(); }
Konštruktory a dedenie
- Úlohou konštruktora je správne nainicializovať objekt
- Pri dedení si väčšinou každá trieda inicializuje "svoje" premenné
- Napr. Shape inicializuje x a y, Circle nechá inicializáciu x a y na Shape a inicializuje radius
- Prvý príkaz v konštruktore môže byť volanie konštruktoru predka, ktorý voláme kľúčovým slovom super (zo superclass, nadtrieda)
class Shape { int x, y; // suradnice vyznacneho bodu utvaru (roh, stred a pod.) Shape(int x, int y) { this.x = x; this.y = y; } // zvysok triedy Shape } class Circle extends Shape { int radius; Circle(int x, int y, int radius) { super(x,y); this.radius = radius; } // zvysok triedy Circle }
- Ak nezavoláme konštruktor predka ručne, automaticky sa zavolá konštruktor bez parametrov, t.j. super()
- Výnimka je, ak na prvom riadku voláme iný konštruktor tej istej triedy pomocou this(), vtedy sa volanie super nechá na zavolaný konštruktor
Polymorfizmus, prekrytie metód
Podtrieda môže prekryť (override) niektoré zdedené metódy, aby sa chovali inak ako v predkovi
- Napr. môžeme mať útvar Segment (úsečka), ktorý je zadaný dvoma koncovými bodmi a v metóde move treba posunúť oba.
- Metódu z predka môžeme zavolať pomocou super.move, ale nemusí to byť na prvom riadku a nemusí byť použitá vôbec
class Segment extends Shape { int x2, y2; Segment(int x, int y, int x2, int y2) { super(x,y); this.x2 = x2; this.y2 = y2; } @Override void move(int deltaX, int deltaY) { super.move(deltaX, deltaY); // volanie metody v predkovi x2 += deltaX; y2 += deltaY; } }
Značka (annotation) @Override je nepovinná, ale odporúčaná. Tým kompilátoru naznačíte, že sa snažíte prekryť zdedenú metódu a ak taká v predkovi nie je, vyhlási chybu.
Polymorfizmus v programovaní (hlavne pri OOP) je schopnosť funkcií chovať sa rôzne
- S určitou formou polymorfizmu sme sa už stretli, keď sme mali viacero metód s rovnakým menom ale s rôznymi typmi parametrov, ktoré sa mohli rôzne správať (tzv. overloading)
- Pri dedení navyše sa môže funkcia chovať rôzne v rôznych triedach aj keď sa volá rovnako a má rovnaké typy parametrov
- To, ktorá verzia sa zavolá, záleží od toho, akého typu je objekt, nie akého typu je definovaná referencia
Shape s = new Segment(0,0,1,-5); s.move(1,1); // zavola pretazenu metodu z triedy Segment s = new Circle(0,0,1); s.move(1,1); // zavola metodu z triedy Shape, lebo Circle ju nepretazil Shape[] shapes = new Shape[3]; // vypln pole shapes //... for(Shape x : shapes) { x.move(deltaX, deltaY); // kazdy prvok sa posuva svojou metodou move, ak ju ma }
Keď zavoláme metódu f() pre objekt o typu T (t.j. zavolám o.f() )
- Ak trieda T má f(), vykoná sa
- Ak T nemá f(), hľadáme f() v nadtriede a prípadne v jej nadtriede atď
Polymorfizus môže byť schovaný aj hlbšie:
- Neprekrytá metóda z predka môže vo svojom tele volať prekryté metódy a tým sa jej správanie mení v závislosti od typu objektu
class SuperClass { void doX() { System.out.println("doX in Super"); } void doXTwice() { doX(); doX(); } } class SubClass extends SuperClass { void doX() { System.out.println("doX in Sub"); } } // v metode main: SuperClass a = new SubClass(); a.doXTwice(); // vypise 2x doX in Sub
Zmysluplnejší príklad viď metóda printArea v príklade nižšie.
Abstraktné metódy a triedy
- Aby sa metóda chovala v určitej skupine tried polymorfne, musí byť definovaná v ich spoločnej nadtriede
- Pre túto nadtriedu však nemusí existovať zmysluplná implementácia
- Napr. môžeme mať metódu area(), ktorá zráta plochu geometrického útvaru
- vieme zrátať plochu kruhu alebo obdĺžnika, ale čo je plocha všeobecného útvaru?
- Vtedy môžeme označiť metódu v nadtriede ako abstraktnú (neimplementovanú)
- potom ale celú nadtriedu treba označiť ako abstraktnú, čo znamená, že nemôžeme tvoriť inštancie tejto triedy (také objekty by totiž nevedeli, čo robiť pri volaní area)
- podtriedy, ak nie sú absktraktné, musia abstraktné metódy z predka implementovať
Tu je príklad:
abstract class Shape { abstract double area(); // nepiseme telo metody void printArea() { // neabstraktne metody mozu volat abstraktne System.out.println("Plocha je " + area()); } // plus zvysok triedy } class Rectangle extends Shape { double area() { return width*height; } // plus zvysok triedy } class Circle extends Shape { double area() { return Math.PI*radius*radius; } // plus zvysok triedy } // v main: // Shape s = new Shape(10,20); //! neskompiluje Shape[] shapes = new Shape[2]; shapes[0] = new Rectangle(0,0,1,2); shapes[1] = new Circle(0,0,1); for(Shape x : shapes) { x.printArea(); }
Program vypíše
Plocha je 2.0 Plocha je 3.141592653589793
Hierarchia tried a trieda Object
- V Jave sa dedí iba od jednej triedy (na rozdiel od napr. C++)
- Dedenie je však možné viacúrovňovo
class Pes extends Zviera { } class Civava extends Pes { }
- Všetky triedy sú automaticky potomkami triedy Object
- Trieda Object obsahuje metódy (napr. toString()), ktoré môžeme prekryť.
class Blabol /* extends Object */ { @Override public String toString() { return "blabla"; } } // v metode main: Blabol b = new Blabol(); System.out.println(b.toString()); System.out.println(b); // spravi toString automaticky // PrintStream ma metody napr. println(Object x) aj println(String x)
Aritmetický strom s využitím dedenia
V minulom semestri sme mali problém s vrcholmi aritmetického stromu, že niektoré premenné sa používali len v niektorých vrcholoch stromu (napr. hodnota len v listoch, operátor len vo vnútorných vrcholoch). Tomuto sa vieme vyhnúť pomocou dedenia.
- Jednotlivé typy vrcholov budú podtriedy triedy Node
- Namiesto použitia príkazu switch na typ vrchola tu prekryjeme potrebné funkcie, napr. evaluate
abstract class Node { public abstract int evaluate(); } abstract class NularyNode extends Node { } abstract class UnaryNode extends Node { Node child; UnaryNode(Node child){ this.child=child; } } abstract class BinaryNode extends Node { Node left; Node right; BinaryNode(Node left, Node right) { this.left=left; this.right=right; } } class Constant extends NularyNode { int value; Constant(int value) { this.value=value;} @Override public int evaluate() { return value;} @Override public String toString() { return new Integer(value).toString(); } } class UnaryMinus extends UnaryNode { UnaryMinus(Node child){ super(child); } @Override public int evaluate() { return -child.evaluate(); } @Override public String toString() { return "(-"+child.toString()+")"; } } class Plus extends BinaryNode { Plus(Node left, Node right) { super(left,right); } @Override public int evaluate() { return left.evaluate() + right.evaluate(); } @Override public String toString() { return "("+left.toString()+"+"+right.toString()+")"; } } public class Expression { public static void main(String[] args) { Node expr=new Plus(new UnaryMinus(new Constant(2)), new Constant(3)); System.out.println(expr.toString()); System.out.println(expr.evaluate()); } }
Rozhranie (interface)
Interface sa podobá na abstraktnú triedu, ale
- neobsahuje premenné ani konštruktory, väčšinou ani implementácie metód
- ide teda predošetkým o zoznam abstraktných metód, ktoré treba implementovať
- slovo abstract netreba uvádzať, je tam implicitne
- trieda môže implementovať viacero rozhraní - pripomína "viacnásobnú dedičnosť"
Jeden interface môže rozširovať iný - dopĺňať ho o ďalšie požadované funkcie
- používame rovnako kľúčove slovo extends
interface Stack { void push(int item); int pop(); } interface Printable { void print(); } class LinkedStack implements Stack, Printable { static class Node { public int data; public Node next; public Node(int item, Node next_) {data=item; next=next_;} } private Node front; @Override public void push(int item) { Node p=new Node(item, front); front=p; } @Override public int pop() { if (front==null) return -1; int res=front.data; front=front.next; return res; } @Override public void print() { Node p=this.front; while (p!=null) { System.out.print(p.data+" "); p=p.next; } System.out.println(); } } class ArrayStack implements Stack, Printable { private int[] a; private int n; ArrayStack(){ a=new int[100]; n=0; } @Override public void push(int item){ a[n]=item; n++; } @Override public int pop(){ if (n<1) return -1; n--; return a[n]; } @Override public void print(){ for (int i=0; i<n; i++) System.out.print(a[i]+" "); System.out.println(); } } class Blabol implements Printable { @Override public void print() { System.out.println("Blabla"); } } public class InterfaceExample { static void fillStack(Stack stack) { stack.push(10); stack.push(20); } static void printTwice(Printable what) { what.print(); what.print(); } public static void main(String[] args) { LinkedStack s1=new LinkedStack(); Stack s2=new ArrayStack(); Blabol b=new Blabol(); fillStack(s1); fillStack(s2); printTwice(s1); //printTwice(s2); - s2 je Stack a nevie, že sa vie vypísať printTwice((ArrayStack)s2); printTwice(b); } }
Prehľad niektorých modifikátorov tried, premenných a metód
Modifikátory prístupu:
- public: triedy, rozhrania a ich súčasti prístupné odvšadiaľ
- prázdny: viditeľnosť len v rámci balíčka (package)
- protected: viditeľnosť v triede, jej podtriedach a v rámci balíčka
- private: viditeľnosť len v danej triede
Iné modifikátory
- abstract: neimplementovaná metóda alebo trieda s neimplementovanými metódami
- final:
- ak je trieda final, nedá sa z nej ďalej dediť
- ak je metóda final, nedá sa v podtriede prekryť
- ak je premenná alebo parameter final, ide o konštantu, ktorú nemožno meniť
- static:
- statické premenné a metódy sa týkajú celej triedy, nie konkrétnej inštancie
- statické triedy vo vnútri inej triedy nie sú viazané na jej konkrétnu inštanciu
Cvičenia 16
Cieľom tohto cvičenia je precvičiť si vytváranie a modifikovanie tried.
Polynómy
Navrhnite triedu Polynomial, ktorá bude reprezentovať polynómy jednej premennej s celočíselnými koeficientami. Rozmyslite si, aké potrebuje premenné a implementujte metódy popísané nižšie. Kvôli testovaniu nájdete na spodku tejto stránky kostru programu s metódou main. Odkomentuje vždy volania funkcií, ktoré ste už implementovali.
- Implementujte niekoľko konštruktorov:
- konštruktor bez parametrov, ktorý vytvorí nulový polynóm
- konštruktor s dvoma celočíselnými parametrami a a i, ktorý vytvorí polynóm Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle ax^i}
- konštruktor, ktorý dostane pole a vytvorí polynóm, ktorého koeficienty budú prvky tohto poľa
- Implementujte metódu public String toString() ktora zapíše koeficienty polynómu do reťazca vo vami vybranom formáte. Túto metódu volajú príkazy System.out.println("volaco: " + polynom) na konverziu polynómu na reťazec a preto sa vám zíde pri testovaní programu.
- Implementujte metódu getCoefficient(int i), ktorá vráti koeficient pri člene Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle x^i} . Metóda by mala sptrávne fungovať pre každé nezáporné i, pričom pre hodnoty väčšie ako stupeň polynómu bude vracať hodnotu 0.
- Implementujte metódu add(Polynomial p), ktorá vráti nový polynóm, ktorý bude súčtom tohto polynómu a polynómu p.
Ak vám na cvičení zostane čas, môžete navrhnúť a implementovať ďalšie funkcie vhodné na prácu s polynómami, napr. počítanie hodnoty polynómu pre určité x, načítanie polynómu zo vstupu, výpočet stupňa polynómu, ďalšie konštruktory a pod.
Stromy
Na prednáške 26 je implementovaný binárny vyhľadávací strom pomocou tried Node a BinarySearchTree. Pridajte do triedy BinarySearchTree nasledujúce dve metódy, pričom podľa potreby pridajte aj metódy do triedy Node. Snažte sa čo najviac zachovať zapuzdrenie tried.
- Metóda boolean contains(int data) zistí, či je v strome vrchol s hodnotou data. Inšpirujte sa metódou add.
- Metóda int depth() vráti hĺbku stromu. Ak je strom prázdny, vráti -1.
Kostra programu k cvičeniu s polynómami
package polynomial; public class Polynomial { // TODO: VASE METODY A PREMENNE SEM public static void main(String[] args) { int[] coeff = {1,2,3,-2}; // TODO: POSTUPNE ODKOMENTUJTE IMPLEMENTOVANE METODY // // test konstruktorov // Polynomial a = new Polynomial(); // Polynomial b = new Polynomial(2,3); // Polynomial c = new Polynomial(coeff); // // vypisanie polynomov // System.out.println("Polynom a: " + a); // System.out.println("Polynom b: " + b); // System.out.println("Polynom c: " + c); // // koeficent pri x^3 v c // System.out.println("Koeficent pri x^3 v c: " + c.getCoefficient(3)); // System.out.println("Koeficent pri x^5 v c: " + c.getCoefficient(5)); // // scitanie polynomov d = b+c; // Polynomial d = b.add(c); // System.out.println("Polynom b+c: " + d); } }
Prednáška 28
Organizačné poznámky
- DÚ12 odovzdávajte do pondelka 14.3. 22:00. Začnite na nej pracovať skôr, nenechávajte si ju na poslednú chvíľu.
Výnimky
- Počas behu programu môže dôjsť k rôznym chybám a neobvyklým situáciám, napr.
- neexistujúci súbor, zlý formát súboru
- málo pamäte pri alokovaní polí, objektov
- adresovanie mimo hraníc poľa, delenie nulou, ...
- Doteraz sme v našich cvičných programoch ignorovali chyby
- Programy určené pre užívateľov a kritické programy, ktorých zlyhanie by mohlo spôsobiť škody, by sa s takýmito situáciami mali vedieť rozumne vyrovnať
- Ošetrovanie chýb bez požitia výnimiek
- Do návratového kódu funkcie musíme okrem samotnej hodnoty zakomponovať aj ohlasovanie chýb
- Po každom príkaze, ktorý mohol spôsobiť chybu, musíme existenciu chyby otestovať a vyrovnať sa s tým
- Vedie to k neprehľadným programom
Malý príklad s načítaním poľa
Príklad: pseudokód funkcie, ktorá načíta zo súboru číslo n, naalokuje pole a načíta do poľa n čísel:
funkcia readArray { otvor subor vstup.txt if (nepodarilo sa otvorit) { return chybovy kod } nacitaj cislo n if (nepodarilo sa nacitat n) { zatvor subor return chybovy kod } alokuj pole a velkosti n if (nepodarilo sa alokovat pole) { zatvor subor return chybovy kod } for (int i=0; i<n; i++) { nacitaj cislo a uloz do a[i] if (nepodarilo sa nacitat) { zatvor subor odalokuj pole return chybovy kod } } zatvor subor return naalokovane pole, beh bez chyby
- Premiešané príkazy, ktoré niečo robia a ktoré ošetrujú chyby
- Ľahko môžeme zabudnúť odalokovať pamäť alebo zavrieť súbor
- Volajúca funkcia musí analyzovať chybový kód, môže potrebovať rozlišovať napr. problémy so súborom a s pamäťou
- Chyba môže nastať aj pri zatváraní súboru
Jednoduché použite výnimiek v Jave
- Prepíšme náš predchádzajúci príklad s výnimkami
static int[] readArray(String filename) { Scanner s = null; int[] a = null; try { s = new Scanner(new File(filename)); int n = s.nextInt(); a = new int[n]; for (int i = 0; i < n; i++) { a[i] = s.nextInt(); } s.close(); return a; } catch (Exception e) { if (s != null) { s.close(); } e.printStackTrace(); return null; } }
- Využívame konštrukty try a catch.
- Do try bloku dáme príkazy, z ktorých niektorý môže zlyhať.
- Ak niektorý zlyhá a vyhodí výnimku, okamžite sa ukončí vykonávanie bloku try a pokračuje sa blokom catch. V bloku catch túto výnimku spracujeme, v našom prípade len debugovacím výpisom.
- Ak sa podarilo čísla načítať do poľa, metóda vráti pole, inak vráti null
Ako všelijako môže zlyhať
Rôzne príklady, ako môže táto metóda zlyhať:
- Príkazu na inicializáciu Scannera pošleme meno neexistujúceho súboru:
java.io.FileNotFoundException: vstup.txt (No such file or directory) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:137) at java.util.Scanner.<init>(Scanner.java:653) at prog.Prog.readArray(Prog.java:17) at prog.Prog.main(Prog.java:10)
- V súbore sú nečíselné údaje:
java.util.InputMismatchException at java.util.Scanner.throwFor(Scanner.java:857) at java.util.Scanner.next(Scanner.java:1478) at java.util.Scanner.nextInt(Scanner.java:2108) at java.util.Scanner.nextInt(Scanner.java:2067) at prog.Prog.readArray(Prog.java:18) at prog.Prog.main(Prog.java:10)
- Ak nie je dosť pamäte na pole a (toto ani nie je Exception, ale Error, takže náš catch to nezachytil, pozri ďalej)
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at prog.Prog.readArray(Prog.java:19) at prog.Prog.main(Prog.java:10)
- Ak je číslo n v súbore záporné
java.lang.NegativeArraySizeException at prog.Prog.readArray(Prog.java:19) at prog.Prog.main(Prog.java:10)
- Súbor končí skôr ako sa načíta n čísel
java.util.NoSuchElementException at java.util.Scanner.throwFor(Scanner.java:855) at java.util.Scanner.next(Scanner.java:1478) at java.util.Scanner.nextInt(Scanner.java:2108) at java.util.Scanner.nextInt(Scanner.java:2067) at prog.Prog.readArray(Prog.java:21) at prog.Prog.main(Prog.java:10)
- A zrejme by sa dali vyrobiť aj ďalšie prípady (napr filename==null)
- V dokumentácii sa o každej metóde dočítame, aké výnimky produkuje za akých okolností
Rozpoznávanie typov výnimiek
- Možno by náš program mal rôzne reagovať na rôzne typy chýb, napr.:
- Chýbajúci súbor: vypýtať si od užívateľa nové meno súboru
- Zlý formát súboru: ukázať užívateľovi, kde nastala chyba, požiadať ho, aby ju opravil alebo zadal nové meno súboru
- Nedostatok pamäte: program vypíše, že operáciu nie je možné uskutočniť vzhľadom na málo pamäte
- Toto vieme spraviť, lebo výnimky patria do rôznych tried dedených z triedy Exception (prípadne z vyššej triedy Throwable)
- K jednému príkazu try môžeme mať viacero príkazov catch pre rôzne triedy výnimiek, každý chytá tú triedu a jej podtriedy
- Pri danej výnimke sa použije najvrchnejší catch, ktorý sa na ňu hodí
- Po blokoch try a catch môže nasledovať blok finally, ktorý sa vykoná vždy, bez ohľadu na to, či nastala výnimka a či sa nám ju podarilo odchytiť nejakým príkazom catch
- V tomto bloku môžeme napr. pozatvárať otvorené súbory a pod.
Jednoduchý príklad, ktorý vypíše rôzne hlášky pre rôzne typy chýb:
static int[] readArray(String filename) { Scanner s = null; int[] a = null; try { s = new Scanner(new File(filename)); int n = s.nextInt(); a = new int[n]; for (int i = 0; i < n; i++) { a[i] = s.nextInt(); } return a; } catch (FileNotFoundException e) { System.err.println("Subor nebol najdeny"); return null; } catch(java.util.NoSuchElementException e) { System.err.println("Zly format suboru"); return null; } catch(OutOfMemoryError e) { System.err.println("Nedostatok pamate"); return null; } catch(Throwable e) { System.err.println("Neocakavana chyba pocas behu programu"); return null; } finally { if (s != null) { s.close(); } } }
- catch pre java.util.NoSuchElementException chytí aj InputMismatchException, ktorá je jej podtriedou, takže zahŕňa prípady keď súbor nečakane končí, aj keď v ňom nie sú číslené dáta
- do tejto kategórie by sme chceli zaradiť aj prípad, kedy je n záporné, ale ten skončí na všeobecnej Throwable
- to vyriešime tým, že hodíme vlastnú výnimku (viď nižšie)
Prehľad tried z tohto príkladu, plus niektorých ďalších, ktoré sa často vyskytujú:
Object | |-- Throwable | |-- Error vážne systémové problémy | | | |-- VirtualMachineError | | | |-- OutOfMemoryError | |-- Exception | |-- IOException | | | |-- FileNotFoundException | |-- RuntimeException | |-- IndexOutofBoundsException | |-- NegativeArraySizeException | |-- NoSuchElementException | | | |-- InputMismatchException | |-- NullPointerException
Hádzanie výnimiek, vlastné triedy výnimiek
- Výnimku vyhodíme príkazom throw, pričom musíme vytvoriť objekt nejakej vhodnej triedy, ktorá podtriedou Throwable
- V našom príklade pre záporné n môžeme vyhodiť objekt triedy java.util.NoSuchElementException, ktorý sa spracuje rovnako ako iné chyby s formátom súboru
int n = s.nextInt(); if(n<0) { throw new java.util.NoSuchElementException(); }
- Nie je to však elegantné riešenie, lebo táto trieda reprezentuje iný typ udalosti
- Môžeme si vytvoriť aj vlastnú triedu, ktorá v premenných môže mať uložené podrobnejšie informácie o chybe, ktorá nastala.
- Väčšinou to bude podtrieda triedy Exception
static class WrongFormatException extends Exception { private String filename; public WrongFormatException(String filename) { this.filename = filename; } @Override public String getMessage() { return "Zly format suboru " + filename; } }
Propagácia a zreťazenie výnimiek
- Ak vznikne výnimka v príkaze, ktorý nie je vo vnútri try-catch bloku, alebo ak jej typ nie je zachytený žiadnym catch príkazom, hľadá sa ďalší try-catch blok, napr. vo volajúcej metóde
- Ak výnimku nikto nechytí, program skončí s chybovým výpisom zásobníka
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at prog.Prog.readArray(Prog.java:19) at prog.Prog.main(Prog.java:10)
- Pri spracovaní výnimky v bloku catch je možné hodiť novú výnimku (trebárs vhodnejšieho typu)
- Metóda musí deklarovať všetky výnimky, ktoré hádže, alebo ktoré v nej môžu vzniknúť a ich nechytá
- Neplatí pre výnimky triedy RuntimeException a jej podtried a pre Throwable, ktoré nie sú výnimka (ale napr. Error)
Nasledujúci program si pýta meno súboru, až kým nenájde súbor, ktorý vie načítať
- V metóde readArray spracuje chyby týkajúce sa formátu súboru a hodí novú výnimku typu WrongFormatException.
- V metóde main spracuje WrongFormatException a FileNotFoundException tak, že sa znovu pýta meno súboru.
- Iné nečakané udalosti, napr. málo pamäte, koniec vstupu od užívateľa a pod. spôsobia ukončenie programu s chybovou hláškou.
public static void main(String[] args) { boolean fileRead = false; Scanner s = new Scanner(System.in); int[] a = null; while (!fileRead) { try { System.out.println("Zadaj meno suboru: "); String filename = s.next(); a = readArray(filename); fileRead = true; System.out.println("Dlzka pola je " + a.length); } catch (WrongFormatException e) { System.out.println(e.getMessage()); } catch (FileNotFoundException e) { System.out.println("Subor nebol najdeny."); } catch(Throwable e) { System.out.println("Neocakavana chyba."); System.exit(1); } } } static int[] readArray(String filename) throws WrongFormatException, FileNotFoundException { Scanner s = null; int[] a = null; try { s = new Scanner(new File(filename)); int n = s.nextInt(); if (n < 0) { throw new WrongFormatException(filename); } a = new int[n]; for (int i = 0; i < n; i++) { a[i] = s.nextInt(); } return a; } catch (java.util.NoSuchElementException e) { throw new WrongFormatException(filename); } finally { if (s != null) { s.close(); } } }
Zhrnutie
- Keď vznikne neočakávaná udalosť, môžeme ju signalizovať vyhodením výnimky pomocou príkazu throw.
- Výnimka je objekt triedy, ktorá je podtriedou Throwable
- Pri ošetrovaní sa nájde a vykoná najbližší vyhovujúci try ... catch blok obkolesujúci príkaz throw, v tej istej alebo niektorej volajúcej metóde, ďalej sa pokračuje za týmto blokom
- Blok finally sa vykoná vždy, keď je aj keď nie je výnimka a aj ak sa výnimku nepodarilo chytiť. Slúži na zatváranie súborov a iné upratovacie práce.
- Niektoré typy neodchytených výnimiek treba deklarovať v hlavičke funkcie.
Ďalšie informácie
- Dokumentácia k triedam Throwable, Exception
- Kapitola z tutoriálu
Generické programovanie
- V minulom semestri sme videli rôzne abstraktné dátové typy a dátové štruktúry, napr. zásobník, rad, slovník, spájaný zoznam,...
- V každej sme museli zadefinovať, akého typu dáta bude obsahovať
- Ak teda potrebujeme zásobník intov aj zásobník reťazcov, museli sme všetky funkcie písať dvakrát, čo prináša problémy
Zásobník dát typu Object
- Dedenie nám prináša jedno riešenie tohto problému - zadefinovať typ dát ako Object a nakoľko všetky triedy sú podtriedami tejto triedy, môžeme ich v zásobníku skladovať
class Node { private Object data; private Node next; public Node(Object data, Node next) { this.data = data; this.next = next; } public Object getData() { return data; } public Node getNext() { return next; } } class Stack { private Node front; public void push(Object data) { Node p = new Node(data, front); front = p; } public Object pop() { Object res = front.getData(); front = front.getNext(); return res; } }
Teraz môžeme do zásobníka dávať rôzne veci:
Stack s = new Stack(); s.push(null); s.push("Hello world!"); // String je potomok Object s.push(new int[4]); // pole sa tiez vie tvarit ako objekt int x = 4; s.push(x); // kompilator vytvori objekt typu Integer
Ale pozor, keď vyberáme zo zásobníka, majú typ Object, musíme ich teda pretypovať:
int y = (Integer)s.pop(); // ok int z = (Integer)s.pop(); // java.lang.ClassCastException
Pre pretypovaní teda môže dôjsť k chybe počas behu programu, radšej sme keď chybu objaví kompilátor.
Zásobník ako generická trieda
- Zadefinujeme parametrický typ class Stack <T>, kde T je parameter reprezentujúci typ objektov, ktoré do zásobníka budeme dávať.
- V definícii triedy namiesto konkrétneho typu (napr. Object), použijeme parameter T
- Keď vytvárame nový zásobník, špecifikujeme typ T: Stack<Integer> s = new Stack<Integer>();
- Potom do neho môžeme vkladať objekty triedy Integer a jej podtried
class Node <T> { private T data; private Node <T> next; public Node(T data_, Node<T> next_) { data = data_; next = next_; } public T getData() { return data; } public Node <T> getNext() { return next; } } class Stack <T> { private Node<T> front; public void push(T data) { Node<T> p = new Node<T>(data, front); front = p; } public T pop() { T res = front.getData(); front = front.getNext(); return res; } }
Použitie zásobníka:
Stack<Integer> s = new Stack<Integer>(); s.push(new Integer(4)); s.push(5); Integer y = s.pop(); int z = s.pop();
V tom istom programe môžeme vytvoriť zásobníky veľa rôznych typov.
Skratka:
- namiesto Stack<Integer> s = new Stack<Integer>(); stačí písať Stack<Integer> s = new Stack<>();
- kompilátor z kontextu určí, že v <> má byť Integer
Generické metódy
Aj jednotlivé metódy môžu mať typový parameter, ktorý sa píše pred návratový typ.
Statická metóda, ktorá dostane zásobník s prvkami typu T a vyprázdni ho.
static <T> void emptyStack(Stack<T> s) { while(!s.isEmpty()) { s.pop(); } }
Stack<String> s = new Stack<String>(); s.push("abc"); Prog.<String>emptyStack(s); // alebo len emptyStack(s);
Statická metóda, ktorá dostane pole s prvkami typu E a naplní ho referenciami na prvok e.
static <E> void fillArray(E[] a, E e) { for(int i=0; i<a.length; i++) { a[i] = e; } }
Integer[] a = new Integer[3]; fillArray(a, 4); int[] b = new int[3]; //fillArray(b, 4); // E musí byť objekt, nie primitívny typ
Generické triedenie, rozhranie Comparable
Ukazovali sme si aj algoritmy na triedenie, aj tie pracovali s poľom konkrétneho typu.
- Triediacu funkciu môžeme spraviť generickú, potrebujeme však prvky porovnávať
- Operátory <, <= atď pracujú len s primitívnymi typmi
- Použijeme teda špeciálnu metódu compareTo špecifikovanú v rozhraní Comparable
- x.compareTo(y) vráti zápornú hodnotu, ak x<y, nulu ak x=y a kladnú hodnotu ak x>y
- potrebujeme, aby prvky poľa boli z triedy, ktorá implementuje toto rozhranie, čo zapíšeme ako <E extends Comparable>
Jednoduché generické triedenie vkladaním:
static <E extends Comparable> void sort(E[] a) { for (int i = 1; i < a.length; i++) { E prvok = a[i]; int kam = i; while (kam > 0 && prvok.compareTo(a[kam - 1]) < 0) { a[kam] = a[kam - 1]; kam--; } a[kam] = prvok; } } public static void main(String[] args) { Integer[] a = {3, 1, 2}; sort(a); for (int i = 0; i < a.length; i++) { System.out.println(a[i]); } }
Java Collections
- Java poskytuje štandardné triedy na mnohé často používané dátové štruktúry, používa generické programovanie
- Tutoriál
- Je dobré tieto triedy poznať a podľa potreby využívať
- Pochopením ich štruktúry si tiež môžeme precvičiť objektovo-orientované programovanie
- Na úvod si ukážeme malá ukážka, viac nabudúce
ArrayList
ArrayList sa podobá na vector z C++ (existuje aj trieda Vector)
- ide o štruktúru reprezentujúcu pole, ktoré rastie podľa potreby
- na koniec poľa pridávame metódou add(prvok), konkrétny prvok adresujeme metódou get(index), meníme cez set(index, hodnota), veľkosť poľa je size()
import java.util.ArrayList; ... ArrayList<Integer> a = new ArrayList<Integer>(); a.add(2); a.add(7); for (int i = 0; i < a.size(); i++) { System.out.println(a.get(i)); // vypiseme vsetky prvky pola a.set(i, -1); // a potom ich prepiseme na -1 }
LinkedList
LinkedList je obojsmerný spájaný zoznam, ktorý môžeme použiť napr. ako zásobník alebo rad
- Vie teda efektívne pridávať a uberať prvky z oboch koncov a tiež prejsť cez všetky prvky zoznamu pomocou iterátora.
- Hľadanie prvku na pozícii i niekde v strede zoznamu je pomalé.
LinkedList<Integer> a = new LinkedList<Integer>(); a.addFirst(2); // to iste ako push a.addLast(7); // to iste ako add for (ListIterator<Integer> it = a.listIterator(); it.hasNext(); ) { System.out.println(it.next()); } a.removeFirst(); // to iste ako pop a.removeLast();
Cvičenia 17
Cieľom tohto cvičenia je precvičiť si dedenie a polymorfizmus.
Progression
- Nižšie je uvedený kód abstraktnej triedy Progression, ktorá predstavuje celočíselnú postupnosť a dokáže jeden za druhým generovať jej členov, pričom si v premennej index pamätá index posledne vygenerovaného členu. Jediná public metóda tejto triedy je print, ktorá vypíše zadaných počet prvkov na obrazovku.
- Všimnite si, že premenná index je pre podtriedy tejto triedy "read-only", lebo jej hodnotu môžu zistiť pomocou metódy getIndex, ale nemôžu ju meniť.
- Napíšte triedu ArithmeticProgression, ktorá bude podtriedou Progression a bude reprezentovať aritmetickú postupnosť, ktorá je v konštruktore zadaná nultým prvkom a rozdielom medzi dvoma nasledujúcimi prvkami.
- Ak do main dáme Progression ap = new ArithmeticProgression(1, 3); ap.print(10);, program by mal vypísať 1 4 7 10 13 16 19 22 25 28
- Stačí implementovať konštruktor a currentValue()
- Napíšte triedu FibonacciProgression, ktorá bude reprezentovať Fibonacciho postupnosť, ktorá má pre účely tohto cvičenia nultý prvok 1, prvý prvok 1 a každý ďalší prvok je súčtom predchádzajúcich dvoch. Prvý prvok sa dá tiež reprezentovať ako súčet nultého a fiktívneho mínus prvého s hodnotou nula.
- Ak do main dáme Progression fp = new FibonacciProgression(); fp.print(10);, program by mal vypísať 1 1 2 3 5 8 13 21 34 55.
- Implementujte konštruktor, currentValue, firstvalue, nextValue
- Nižšie nájdete aj implementáciu triedy ProgressionSum, ktorá reprezentuje postupnosť, ktorá vznikla ako súčet dvoch postupností, ktoré dostane v konštruktore.
- Ak do main dáme Progression ps = new ProgressionSum(fp, fp); ps.print(10);, chceli by sme dostať dvojnásobok Fibonacciho postupnosti, teda 2 2 4 6 10 16 26 42 68 110. Nie je to však tak. Prečo? Ako prebieha volanie nextValue() pre premennú triedy ProgressionSum? Aké všetky metódy sa volajú a v akom poradí?
- Zmeňte riadok s vytvorením postupnosti ps tak, aby program mal požadované správanie.
Trieda Progression
package prog; /** Trieda reprezentujuca celociselnu postupnost. */ public abstract class Progression { /** Aktualny index prvku postupnosti. */ private int index; /** Konstruktor */ protected Progression() { index = 0; } /** Vrati aktualny index prvku postupnosti */ protected int getIndex() { return index; } /** Vrati hodnotu postupnosti pre aktualny index */ protected abstract int currentValue(); /** Restartuje index na 0 a vrati nulty prvok. */ protected int firstValue() { index = 0; return currentValue(); } /** Zvysi index o 1 a vrati aktualny prvok. */ protected int nextValue() { index++; return currentValue(); } /** Vypise prvych n prvkov postupnosti. */ public void print(int n) { System.out.print(firstValue()); for (int i = 1; i < n; i++) { System.out.print(" " + nextValue()); } System.out.println(); } public static void main(String[] args) { } }
Trieda ProgressionSum
class ProgressionSum extends Progression { Progression p1, p2; ProgressionSum(Progression p1, Progression p2) { this.p1 = p1; this.p2 = p2; } @Override protected int currentValue() { return p1.currentValue() + p2.currentValue(); } @Override protected int nextValue() { p1.nextValue(); p2.nextValue(); return super.nextValue(); } @Override protected int firstValue() { p1.firstValue(); p2.firstValue(); return super.firstValue(); } }
Aritmetický strom
Do aritmetického stromu z prednášky 27 spravte nasledujúce zmeny:
- Doprogramujte triedu Times pre násobenie a pridajte násobenie do testovacieho výrazu v metóde main.
- Pri vypisovaní (metóda toString) sa triedy Plus a Times správajú podobne, zmena je iba v znamienku. Naprogramujte teda toString už v triede BinaryNode, s tým, že bude volať pomocnú abstraktnú metódu operatorToString, ktorá vráti reťazcovú reprezentáciu príslušného znamienka ("+" alebo "*")
Prednáška 29
Organizačné poznámky
- DÚ12 do dnes
- DÚ13 bude zverejnená dnes alebo zajtra. Pomerne veľká úloha, začnite na nej pracovať skoro. Opäť dostanete dosť veľkú časť programu hotovú, treba doprogramovať niekoľko nových tried.
Opakovanie: generické programovanie
- V Jave môžeme definovať triedu alebo metódu, ktorá má špeciálny parameter určujúci typ dát, ktoré bude spracovávať, napr. zásobník s prvkami typu T.
class Stack <T> { private Node<T> front; public void push(T data) { Node<T> p = new Node<T>(data, front); front = p; } public T pop() { T res = front.getData(); front = front.getNext(); return res; } }
Použitie zásobníka:
Stack<Integer> s = new Stack<Integer>(); s.push(new Integer(4)); s.push(5); Integer y = s.pop(); int z = s.pop();
Výhody generickej verzie zásobníka:
- V tom istom programe môžeme vytvoriť zásobníky veľa rôznych typov.
- Kompilátor skontroluje, či vkladáme a vyberáme prvky správnych typov.
Java Collections
- Minulý semester sme programovali mnohé základné dátové štruktúry a algoritmy (zásobník, rad, slovník, triedenie...), vo väčšine moderných jazykov sú však tieto už k dispozícii v štandardných knižniciach.
- Java Collections sú štandardné triedy na mnohé často používané dátové štruktúry, používajú generické programovanie.
- Tutoriál.
- Je dobré tieto triedy poznať a podľa potreby využívať.
- Pochopením ich štruktúry si tiež môžeme precvičiť objektovo-orientované programovanie.
- Na úvod dve ukážky.
ArrayList
ArrayList sa podobá na vector z C++ (existuje aj trieda Vector)
- Ide o štruktúru reprezentujúcu pole, ktoré rastie podľa potreby.
- Na koniec poľa pridávame metódou add(prvok), konkrétny prvok adresujeme metódou get(index), meníme cez set(index, hodnota), veľkosť poľa je size()
import java.util.ArrayList; ... ArrayList<Integer> a = new ArrayList<Integer>(); a.add(2); a.add(7); for (int i = 0; i < a.size(); i++) { System.out.println(a.get(i)); // vypiseme vsetky prvky pola a.set(i, -1); // a potom ich prepiseme na -1 }
LinkedList
LinkedList je obojsmerný spájaný zoznam, ktorý môžeme použiť napr. ako zásobník alebo rad
- Vie teda efektívne pridávať a uberať prvky z oboch koncov a tiež prejsť cez všetky prvky zoznamu pomocou iterátora.
- Hľadanie prvku na pozícii i niekde v strede zoznamu je pomalé.
LinkedList<Integer> a = new LinkedList<Integer>(); a.addFirst(2); // to iste ako push a.addLast(7); // to iste ako add for (ListIterator<Integer> it = a.listIterator(); it.hasNext(); ) { System.out.println(it.next()); } a.removeFirst(); // to iste ako pop a.removeLast();
Prehľad Collections
- Dátové štruktúry a algoritmy na základnú prácu so skupinami dát.
- Generické typy - môžeme vytvárať dátové štruktúry pre dáta rôznych typov.
- Definované pomocou rozhraní, jedno rozhranie (interface) môže mať viacero implementácií.
Vybrané triedy:
Rozhranie | Význam | Implementácie |
Collection | skupina objektov | |
- Set | množina, skupina bez opakujúcich sa objektov | HashSet |
-- SortedSet | množina s definovaným usporiadaním prvkov | TreeSet |
- List | postupnosť objektov s určitým poradím | ArrayList, LinkedList |
Map | slovník, asociatívne pole, mapuje kľúče na hodnoty | HashMap |
- SortedMap | slovník s definovaným usporiadaním kľúčov | TreeMap |
V metódach je dobré argumenty definovať najvšeobecnejším vhodným rozhraním alebo triedou.
- Napr. chceme spočítať súčet viacerých Integer-ov:
// tato metoda sa da pouzit iba na ArrayList public static Integer sum(ArrayList<Integer> a) { ... } // tato metoda sa da pouzit na hocijaku Collection (LinkedList, HashSet...) public static Integer sum(Collection<Integer> a) { ... }
Základné operácie pre Collection:
public interface Collection<E> extends Iterable<E> { int size(); boolean isEmpty(); boolean contains(Object element); boolean add(E element); // optional boolean remove(Object element); // optional void clear(); // optional Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); // a dalsie... }
- Metódy add a remove vracajú true, ak sa Collection zmenila a false, ak sa nezmenila.
- Metódy, ktoré menia Collection, sú nepovinné v tom zmysle, že musia byť zadefinované, ale môžu hádzať UnsupportedOperationException
Prechádzanie cez prvky Collection
Použitie cyklu for-each:
public static Integer sum(Collection<Integer> a) { int sum = 0; for(Integer x : a) { sum += x; } return sum; }
- cyklus for-each sa dá použiť na ľubovoľný objekt triedy implementujúcej rozhranie Iterable, ktoré definuje jedinú metódu Iterator<T> iterator()
Použitie iterátora:
public static Integer sum(Collection<Integer> a) { int sum = 0; for (Iterator<Integer> it = a.iterator(); it.hasNext();) { sum += it.next(); } return sum; }
- a.iterator() vráti objekt it implementujúci rozhranie Iterator
- it.next() vráti ďalší prvok zo skupiny a, alebo hodí NoSuchElementException, ak už ďalší nie je
- it.hasNext() vráti, či ešte je ďalší prvok
- it.remove() zmaže prvok, ktorý sme naposledy dostali pomocou next
- Užitočná predstava je, že iterátor vždy ukazuje na "medzeru" medzi dvoma prvkami (prípadne pred prvým alebo za posledným prvkom)
- next() preskočí do ďalšej medzery a vráti preskočený prvok
- Poradie, v akom prvky navštívime, nie je pre všeobecnú Collection definované
- Iterátor pre SortedSet vracia prvky v utriedenom poradí od najmenšieho po najväčší.
- Iterátor pre List vracia prvky v poradí, v akom sú v postupnosti (poli, zozname)
- Vo verzii Java 1.8 pribudla metóda forEachRemaining, ale tá má aj základnú implementáciu hotovú.
Pomocou iterátora môžeme napr. vymazať všetky nuly (môže byť pomalé):
static void removeZeroes(Collection<Integer> a) { for (Iterator<Integer> it = a.iterator(); it.hasNext();) { if (it.next().equals(0)) { // vynimka ak it.next() je null! it.remove(); } } }
Rozhranie List definuje ListIterator, ktorý rozširuje základný iterátor.
- Umožňuje aj hýbať sa dozadu it.previous(),
- pridávať a meniť prvky it.add(x), it.set(x)
- zisťovať polohu it.nextIndex(), it.previousIndex()
Použitie Map
public interface Map<K,V> { V put(K key, V value); // klucu key prirad hodnotu value, vrati predch. hodnotu pre key V get(Object key); // hodnota pre kluc key alebo null V remove(Object key); // zmaz kluc key a jeho hodnotu boolean containsKey(Object key); // obsahuje kluc key? boolean containsValue(Object value); int size(); boolean isEmpty(); void putAll(Map<? extends K, ? extends V> m); void clear(); // Vrátia Set alebo Collection, cez ktorý môžeme iterovať public Set<K> keySet(); public Collection<V> values(); public Set<Map.Entry<K,V>> entrySet(); // Interface pre dvojice vo výsledku entrySet public interface Entry { K getKey(); V getValue(); V setValue(V value); } }
Príklad použitia Map:
- vstup z konzoly rozložíme Scannerom na slová (kým užívateľ nezadá END) a počítame počet výskytov každého slova
import java.util.*; public class Prog { public static void main(String[] args) { Map<String, Integer> map = new HashMap<String, Integer>(); Scanner s = new Scanner(System.in); // inicializujeme Scanner while (s.hasNext()) { // kym neskonci vstup String word = s.next(); // nacitame slovo if (word.equals("END")) { // skoncili sme ak najdeme END break; } Integer freq = map.get(word); if(freq == null) { map.put(word, 1); } else { map.put(word, freq+1); } } System.out.println("Pocet roznych slov: " + map.size()); System.out.println(map); } }
Príklad výstupu:
one two three one two two END Pocet roznych slov:3 {two=3, one=2, three=1}
HashMap vypisuje prvky v ľubovoľnom poradí. Ak typ zmeníme na TreeMap, dostaneme utriedené podľa kľúča:
{one=2, three=1, two=3}
Ak chceme vypísať zoznam slov a ich frekvencií v inom formáte, použijeme for-each alebo iterátor
for(Map.Entry<String, Integer> e : map.entrySet()) { System.out.println("Slovo " + e.getKey() + " sa vyskytuje " + e.getValue() + " krat"); }
for(Iterator<Map.Entry<String,Integer>> it=map.entrySet().iterator(); it.hasNext();) { Map.Entry<String,Integer> e = it.next(); System.out.println("Slovo " + e.getKey() + " sa vyskytuje " + e.getValue() + " krat"); }
Slovo two sa vyskytuje 3 krat Slovo one sa vyskytuje 2 krat Slovo three sa vyskytuje 1 krat
Porovnávanie objektov na rovnosť: equals
- Metódy z Collection contains(Object element), remove(Object element) a ďalšie potrebujú porovnávať objekty na rovnosť.
- Operátor == porovnáva referencie, t.j. či sú dva objekty na tej istej adrese v pamäti
- Collection používa namiesto toho metódu equals(Object obj) definovanú v triede Object
- Metóda equals() v triede Object tiež porovnáva len referencie, ostatné triedy ju môžu prekryť
- Napr. v triedach ako String, Integer,... definovaná na porovnávanie reťazcov, čísel,...
- Rôzne triedy implementujúce Collection tiež väčšinou vedia porovnávať na rovnosť spúšťaním equals na jednotlivé prvky
- Metódy nevieme spúšťať na null, napr. contains(Object o) vracia true práve vtedy, keď nejaký prvok e Collection spĺňa (o==null ? e==null : o.equals(e))
- Prekrytá metóda equals by sa mala správať "rozumne", t.j. byť symetrická, tranzitívna a pod.
Porovnávanie objektov na nerovnosť: Comparable
- SortedMap a SortedSet potrebujú vedieť porovnávať prvky podľa veľkosti
- Používajú metódu compareTo definovanú v rozhraní Comparable (videli sme na minulej prednáške)
- Ak naša trieda neimplementuje toto rozhranie alebo chceme použiť iné usporiadanie, môžeme použiť vlastný komparátor, objekt implementujúci rozhranie Comparator
import java.util.*; public class SetSortedByAbsoluteValue { /** Trieda AbsoluteValueComparator porovnava Integery * podla absolutnej hodnoty */ static class AbsoluteValueComparator implements Comparator<Integer> { public int compare(Integer o1, Integer o2) { Integer x1 = Math.abs(o1); Integer x2 = Math.abs(o2); return x1.compareTo(x2); } } public static void main(String[] args) { AbsoluteValueComparator comp = new AbsoluteValueComparator(); TreeSet<Integer> set = new TreeSet<>(comp); set.add(-3); set.add(0); set.add(7); set.add(-10); for(Integer x : set) { // vypise 0 -3 7 -10 System.out.print(" " + x); } System.out.println(); } }
Hešovacie funkcie: hashCode
- HashSet a HashMap potrebujú vedieť prekódovať ľubovoľný objekt do pozície v poli
- Používajú metódu int hashCode() definovanú v triede Object
- Object jednoducho použije svoju adresu v pamäti ako svoj hashCode
- Štandardné triedy prekrývajú hashCode
- Ak prekryjete equals, treba prekryť aj hashCode, lebo ak sa dva prvky rovnajú v equals, majú mať rovnaký hashCode
static class Name { String givenName; String lastName; @Override public int hashCode () { return givenName.hashCode() + 31*lastName.hashCode(); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Name other = (Name) obj; return this.givenName.equals(other.givenName) && this.lastName.equals(other.lastName); } }
Algoritmy
- Triedy Collections a Arrays obsahujú statické metódy na prácu s Collections a poliami
- Napr. sort, shuffle (náhodne preusporadaj), reverse, fill, copy, swap, binarySearch, min, max,...
Zhrnutie
- Collections sú obrovská knižnica a veľmi užitočná
- Neváhajte ich používať v programoch, nájdite si v dokumentácii metódy, ktoré sa vám hodia
- Mali by ste ovládať (s pomocou ťaháka) aspoň základy práce s ArrayList, LinkedList, HashMap a s iterátormi
- Pri spracovaní väčších dát pozor na to, že niektoré metódy sú pomalé
- Pre prácu s Collections môže byť potrebné prekryť niektoré metódy z Object (equals, hashCode)
- Ďalšie metódy z Object, ktoré sa často hodí prekryť sú clone a toString
Testovanie programov
- Cieľom testovania je nájsť chyby v programe, teda preukázať, že program nefunguje podľa špecifikácie (aby sme potom vedeli chybu nájsť a opraviť)
- Test pozostáva zo vstupu, správneho výstupu a popisu jeho významu
- Program sa spustí na vstupe a jeho výsledok sa porovná so správnou odpoveďou
- Tradičný prístup: najprv sa napíše kód, potom sa vytvárajú testy
- Test-driven development: najprv sa napíšu testy, potom sa programuje kód, ktorý ich dokáže splniť
Black-box testing
- Vytvorenie sady testov len na základe špecifikácie
- Snažíme sa zachytiť okrajové aj typické prípady
- Napr.:
/** Z pola a vyhodi prvy vyskyt objektu rovneho x * pricom rovnost sa testuje metodou equals. * Vsetky dalsie prvky posunie o jedno dolava a na koniec * pola da null. * Vrati true, ak bolo pole modifikovane, inak false. * Ak a je null alebo x je null, vyhodi java.lang.NullPointerException */ public static boolean remove(Object[] a, Object x) {
Príklady testov:
- prázdne pole a
- pole obsahujúce iba x
- pole obsahujúce x na začiatku
- pole obsahujúce x na konci
- pole obsahujúce x v strede
- pole obsahujúce viacero kópií x
- pole obsahujúce null prvky
- pole obsahujúce objekty rôznych typov
Podrobnejšie rozpísanie jedného testu:
- vstup: a = {1,2,3}, x = 1
- výstup: a = {2,3,null}, návratová hodnota true
- význam testu: testovanie prípadu, keď pole a obsahuje x na začiatku
White-box testing
Testy vytvárame na základe kódu, snažíme sa preveriť všetky vetvy výpočtu
- V cykle vyskúšame 0 iterácií, 1 iteráciu, maximálny počet iterácií
- V podmienke vyskúšame vetvu true aj false
- Avšak tým, že sa sústredíme na kód, môžeme vynechať prípady, na ktoré sa v ňom nemyslelo
Tento kód nespĺňa úplne špecifikáciu:
/** Z pola a vyhodi prvy vyskyt objektu rovneho x * pricom rovnost sa testuje metodou equals. * Vsetky dalsie prvky posunie o jedno dolava a na koniec * pola da null. * Vrati true, ak bolo pole modifikovane, inak false. * Ak a je null alebo x je null, hodi java.lang.NullPointerException */ public static boolean remove(Object[] a, Object x) { int i; for (i = 0; i < a.length; i++) { if (a[i].equals(x)) { break; } } if (i == a.length) { return false; } while (i < a.length - 1) { a[i] = a[i + 1]; i++; } a[i] = null; return true; }
JUnit
- Systém JUnit umožňuje vytvárať špeciálne triedy obsahujúce testy iných tried
- Sadu testov môžeme ľahko automaticky spustiť a vyhodnotiť, vidíme všetky výsledky
- Môžeme program testovať po každej zmene
- Dobrá podpora v Netbeans
- Krátky návod: [3]
Príklad niekoľkých testov pre funkciu remove vyššie:
package prog; import org.junit.Test; import static org.junit.Assert.*; public class ProgTest { @Test public void testEmpty() { // hladame x v poli dlzky nula Object[] working = new Object[0]; // vstupne pole Object[] correct = new Object[0]; // spravna odpoved Object x = new Object(); boolean result = Prog.remove(working, x); // spustime testovanu metodu assertEquals(result, false); // testujeme navratovu hodnotu assertTrue(java.util.Arrays.equals(working,correct)); // testujeme pole } @Test public void testXOnly() { // hladame x v poli obsahujucom iba x Object[] working = {7}; Object[] correct = {null}; Object x = 7; boolean result = Prog.remove(working, x); assertEquals(result, true); assertTrue(java.util.Arrays.equals(working,correct)); } @Test(expected = java.lang.NullPointerException.class) public void testANull() { // Testujeme, ci hodi vynimku ked je pole null Object[] working = null; Object x = 7; boolean result = Prog.remove(working, x); } }
Tento príklad je možné rôzne vylepšovať
- Opakujúce sa časti kódu môžeme dať do pomocných metód
- Môžeme pridať výpisy výsledkov, aby sme v prípade chyby videli, čo sa stalo.
- Môžeme pridať premenné triede ProgTest, konštruktor, ako aj špeciálne metódy, ktoré sa vykonajú pred každým testom, prípadne po každom teste.
Cvičenia 18
Jednoduché výnimky
Nižšie nájdete program z prednášky, ktorý načítava zo súboru číslo n a n čísel do poľa, pričom neexistenciu súboru a jeho zlý formát rieši výnimkami. Od užívateľa opakovanie vypýta meno súboru, až kým sa mu nepodarí súbor prečítať.
- Po načítaní čísel do poľa v metóde readArray overte metódou hasNext() triedy Scanner, že sa v súbore už nenachádzajú ďalšie čísla alebo iné nebiele znaky. Ak sa nachádzajú, hoďte tiež WrongFormatException.
- Zmeňte program tak, aby WrongFormatException v konštruktore dostala aj podrobnejší popis chyby formátu, ktorá bude napríklad "Nepodarilo sa načítať počet prvkov n", alebo "Nepodarilo sa načítať prvok i", kde namiesto znaku i dosadíte príslušné poradové číslo prvku, kde nastala chyba. V metóde getMessage potom túto podrobnejšiu správu vráťte.
- Návod: premennú i v metóde readArray zadefinujte už pred príkazom try a inicializujte na -1. V časti catch potom podľa aktuálnej hodnoty i viete zistiť, či sa for cyklus vôbec nezačal alebo na ktorom prvku zhavaroval.
Generická trieda Matrix
Naprogramujte generickú triedu Matrix, ktorá reprezentuje obdĺžnikovú maticu prvkov nejakého neznámeho typu E.
- Prvky matice si v tejto triede uložte do premennej typu ArrayList<ArrayList<E>>, potrebujete si ešte udržiavať aj aktuálny počet riadkov a stĺpcov.
- Napíšte konštruktor, ktorý vytvorí maticu zadaných rozmerov a vyplní ju zadaným prvkom typu E.
- Napíšte metódy, ktoré do matice pridajú nový stĺpec a nový riadok, vyplnený zadaným prvkom typu E.
- Napíšte metódu get, ktorá vráti prvok matice nachádzajúci sa na zadanom mieste
- Napíšte metódu set, ktorá na zadané miesto v matici zapíše zadaný prvok typu E
Výnimky v triede Matrix
- Čo sa stane, ak metódu get triedy Matrix zavoláte so súradnicami mimo rozsah matice?
Prepíšte metódy get a set tak, aby pri zlých súradniciach hádzali výnimku vašej vlastnej triedy MatrixIndexOutOfBoundsException.
- Výnimka tejto triedy by v metóde getMessage mala vrátiť reťazec obsahujúci obidve súradnice, ako aj obidva rozmery matice.
- Skúste dva spôsoby implementácie:
- v prvom odchytávajte vzniknuté výnimky a nahraďte ich svojou výnimkou
- v druhom otestujte vhodnosť indexov hneď na začiatku metódy a v prípade potreby vyhoďte vlastnú výnimku
Ešte Matrix, dedenie
Napíšte generickú triedu InfiniteMatrix, ktorá je podtriedou triedy Matrix a líši sa od nej v tom, že ak metóde get dáme súradnice mimo rozsah matice, vráti hodnotu null (a nevyhadzuje výnimku). Je to ako keby sme mali maticu nekonečnej veľkosti vyplnenú null-mi a v malom obdĺžniku s určitým počtom riadkov a stĺpcov máme nejaké uložené hodnoty, ktoré sa môžu líšiť od null.
Výnimky pre Scanner
Vytvorte triedu IntScanner, ktorá v konštruktore dostane meno súboru a okrem konštruktoru obsahuje public metódy hasNextInt, nextInt a close, ktoré robia zhruba to isté ako v triede Scanner. Avšak ak je v súbore nejaký reťazec, ktorý nie je možné interpretovať ako číslo, metóda nextInt vyhodí výnimku vašej novej triedy, ktorá v metóde getMessage vráti reťazec obsahujúci číslo riadku, na ktorom chyba nastala aj reťazec, ktorý nebolo možné ako číslo interpretovať. Trieda Scanner nám neumožňuje zistiť číslo riadku, preto budeme mať v tejto triede tri premenné:
- aktuálne číslo riadku
- premennú typu BufferedReader, v ktorej na začiatku otvoríme zadaný súbor a vždy keď treba, načítame z neho riadok, zvýšime počítadlo a vytvoríme novú inštanciu triedy Scanner, ktorá bude čítať čísla z toho riadku
- premennú triedy Scanner
Zdrojový kód pre prvý príklad
package prog; import java.io.*; import java.util.Scanner; class Prog { static class WrongFormatException extends Exception { private String filename; public WrongFormatException(String filename_) { filename = filename_; } @Override public String getMessage() { return "Zly format suboru " + filename; } } public static void main(String[] args) { boolean fileRead = false; Scanner s = new Scanner(System.in); int[] a = null; while (!fileRead) { try { System.out.print("Zadaj meno suboru: "); String filename = s.next(); a = readArray(filename); fileRead = true; System.out.println("Dlzka pola je " + a.length); } catch (WrongFormatException e) { System.out.println(e.getMessage()); } catch (FileNotFoundException e) { System.out.println("Subor nebol najdeny."); } catch (Throwable e) { System.out.println("Neocakavana chyba."); System.exit(1); } } } static int[] readArray(String filename) throws WrongFormatException, FileNotFoundException { Scanner s = null; int[] a = null; try { s = new Scanner(new File(filename)); int n = s.nextInt(); if (n < 0) { throw new WrongFormatException(filename); } a = new int[n]; for (int i = 0; i < n; i++) { a[i] = s.nextInt(); } return a; } catch (java.util.NoSuchElementException e) { throw new WrongFormatException(filename); } finally { if (s != null) { s.close(); } } } }
Prednáška 30
Opakovanie OOP
- Java je objektovo orientovaný jazyk - všetko sú objekty
- Program pozostáva z objektov, ktoré navzájom 'komunikujú' prostredníctvom svojich metód
Objekty, zapúzdrenie
- Trieda (class) definícia objektu
class MyClass { premenné konštruktory metódy }
- Konštruktor - vytváranie objektu
- používanie this(..) resp. super(..) na volanie iného konštruktora tej istej triedy, resp. nadtriedy
- Pes punto=new Pes("Punto");
- Úrovne prístupu - Modifikátory
- Zapuzdrenie - oddelenie rozhrania a implementácie
- Nemení sa to, čo je viditeľné zvonku
- Pozor na prístup k premenným
Dedenie objektov, polymorfizmus
- Trieda môže byť potomkom inej triedy - potom zdedí jej premenné a metódy
- Dediť môžeme iba od jednej triedy (ale môžeme viacúrovňovo)
- Triedy, ktoré nemajú predchodcu, dedia všeobecnú triedu Object
- Aj primitívne typy premenných majú svoje objektové varianty (Integer, Boolean atď)
- Metódy je možné preťažiť - potomok tým prepíše chovanie metódy
- Potomka je vždy možné použiť na mieste predka, keďže má všetky jeho 'vlastnosti' - spolu s preťaženými metódami to umožňuje polymorfizmus
Rozhranie (interface)
- Abstraktná trieda, ktorá nemá žiadnu metódu implementovanú
- Trieda môže implementovať interface - musí implementovať všetky jej metódy
- Umožňuje "viacnásobnú dedičnosť" a dobré zapuzdrenie
Generické programovanie
- Ako mať rôzne typy premenných vnútri nejakej štruktúry (napr. zásobník)
- Bude skladovať Object a keďže potomka môžeme použiť namiesto predka môžeme tam skladovať čokoľvek - musíme pretypovávať
- Generické programovanie: parametrický typ class Stack <T>, kde T je parameter reprezentujúci typ objektov, pri definícii povieme konkrétny typ
- Aj metódy môžu mať generické typy - typový parameter, ktorý sa píše pred návratový typ
static <E> void fillArray(E[] a, E e)
- Generické funkcie (napr. triedenie) s využitím interface
static <E extends Comparable> void sort(E[] a)
JavaFX
- Doteraz sme mali aplikácie, ktoré pracovali s textovým vstupom a výstupom - konzolové aplikácie
- Ukážeme si jednoduché aplikácie, ktoré majú grafické UI
- Grafické okno, ovládacie prvky, ...
- Udalosti, ktoré vedú k akciám
Program s jedným grafickým oknom
V klasickej java aplikácii (ako sme používali doteraz) aplikáciu urobiť nedokážeme, musíme dopredu povedať, že ideme robiť grafickú aplikáciu.
- File–>New Project vyberieme kategóriu JavaFX a projekt typu JavaFX Application
- Projekt si môžeme pre jednoduchosť nazvať "HelloWorld"
- Bude automaticky vygenerovaný kód, ktorý obsahuje
- hlavičku a niekoľko (veľa) importovaných knižníc
- triedu public class HelloWorld extends Application s metódou start (obsahuje nejaký kód, ktorý na začiatok zmažeme, neskôr si ho môžete nechať a upravovať) a metódou main, ktorá je skoro prázdna a tak aj ostane
Naša metóda start bude na začiatok obsahovať iba nasledovný kód
@Override public void start(Stage primaryStage) { primaryStage.setTitle("Hello World!"); primaryStage.show(); }
- Takto vytvorený program vytvorí grafické okno a zobrazí ho
- Hlavné okno je primaryStage, ktorému nastavíme nadpis (setTitle) a zobrazíme ho - zistíme však, že okno sa zobrazí prázdne alebo sa vôbec nezobrazí (v závislosti od OS), pretože nevie, čo v ňom zobraziť
- Chceme aspoň nejaký prázdny panel - pri starších grafických programoch existovali rôzne Form, ktoré nevyžadovali viac informácií, v JavaFX potrebujeme každému panelu nastaviť aj spôsob, akým má byť zobrazovaný a nezobrazujeme priamo panel ale toto zobrazenie
@Override public void start(Stage primaryStage) { Pane pane = new Pane(); //panel Scene scene = new Scene(pane, 300, 250); //jeho zobrazenie primaryStage.setTitle("Hello World!"); primaryStage.setScene(scene); //primarnemu oknu povieme, že má zobraziť zobrazenie panelu primaryStage.show(); }
Text v okienku
- Do takto vytvoreného okna si môžeme vytvárať grafické (ovládacie) prvky label, button, ...
- pochopiteľne pre tieto prvky má JavaFX vlastné triedy, ktorých inštancie vytvárame
- Pre jednoduchosť začneme textovým popiskom - label, ktorý je inštanciou Label
- Label je needitovateľný text, ktorý sa používateľovi objaví na paneli
Label label = new Label("Label"); pane.getChildren().add(label);
- Ďalej môžeme s týmto popiskom pracovať (napríklad mu zmeniť text)
- Môžeme tiež umiestniť label na iné miesto label.setLayoutX(100); label.setLayoutY(100);
Button a jeho stlačenie
- Doteraz sme síce mali grafické okno, ale nemali sme (okrem zavretia) žiadnu interakciu s používateľom
- Ukážeme si najjednoduchší ovládací prvok - Button a jednoduchú akciu - stlačenie gombíka
- Button pridáme podobne ako Label pomocou pane.getChildren().add()
- Stlačenie gombíka je akcia, ktorá sa môže alebo aj nemusí stať - záleží od používateľa - preto na ňu musíme čakať a v prípade, že nastane nejako ju spracovať
- Použijeme na to rozhranie EventHandler<ActionEvent>, ktoré implementujeme anonymnou triedou. Toto rozhranie má jedinú funkciu handle() s parametrom ActionEvent, ktorý hovorí parametre tej udalosti (na ktorom objekte nastala a nejaké bližšie informácie)
package helloworld; import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.layout.*; import javafx.stage.Stage; public class HelloWorld extends Application { @Override public void start(Stage primaryStage) { Button btn = new Button(); btn.setText("Say 'Hello World'"); btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); } }); Pane pane = new Pane(); pane.getChildren().add(btn); Scene scene = new Scene(pane, 300, 250); primaryStage.setTitle("Hello World!"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } }
- môžeme priamo pracovať s objektami grafického okna - nastavovať a meniť nápisy na gombíku ale aj iných grafických prvkoch
- problém môže byť, že kým sa to spracováva, tak grafické okno 'zatuhne' (čiže nedávať počítať gombíku prvočíselný rozklad)
btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { btn.setText("ha"); ((Button)event.getSource()).setText("he"); //zmení text na gombíku, na ktorom bol Event vyvolaný - tu je to btn } });
- Jeden EventHandler môžem nasadiť aj na viac objektov - napríklad niekoľko gombíkov
- Urobím si konkrétnu inštanciu triedy EventHandler<ActionEvent> a tú potom v oboch gombíkoch použijem
- Pozor gombíkom treba nastaviť súradnice, ináč sú cez seba a akciu vykoná vrchný z nich
EventHandler<ActionEvent> xxx = new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); ((Button)event.getSource()).setText("he"); } }; btn1.setOnAction(xxx); btn2.setOnAction(xxx);
Viac akcií
- Pre rôzne ovládacie prvky existujú rôzne akcie, ktoré sa môžu vykonať - v závislosti od konkrétneho ovládacieho prvku
- Už pre gombík môžeme okrem stlačenia pozorovať veľa akcií (môžete si ich pozrieť medzi medódami triedy Button [4] - začínajú 'setOn')
- V našom prípade som pridala akciu s myšou -- keď myš vojde do priestoru gombíku, gombík ju pozdraví
btn1.setOnMouseEntered(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { System.out.println("Hello Mouse!"); } });
- Takýmto spôsobom môžeme mať na jeden prvok zavesených viacerých, ktorí čakajú na nejakú svoju udalosť
- Na jednu akciu však môžeme mať iba jednu reakciu -- jeden EventHandler
- Treba si však uvedomiť, že udalosti sú rôznych typov a EventHandler k nemu prislúchajúci by mal mať vhodný typový parameter (pri udalosti Action ActionEvent, pri práci s myšou MouseEvent atď.)
Vlastná trieda EventHandlerov
- Doteraz sme akciu na ktorú má prvok reagovať zapisovali nasledovne:
btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); } });
Pozrime sa teraz poriadnejšie na to, čo to vlastne znamená
- Akciu, ktorá sa vykonala na gombíku (v tomto prípade) má niekto spracovať. Ten niekto je nejaký EventHandler, ktorý má typový parameter ActionEvent (teda vie spracovávať ActionEvent)
- Mohli by sme to teda zapísať nasledovne (až na to, že trieda EventHandler<> je abstraktná a teda nemá implementované niektoré svoje metódy)
EventHandler<ActionEvent> niekto= new EventHandler<ActionEvent>(); btn.setOnAction(niekto);
- Takže na to, aby sme mohli vytvoriť niekoho typu EventHandler<> , musíme doimplementovať niektoré metódy (v našom prípade metódu handle(ActionEvent event)
EventHandler<ActionEvent> niekto= new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { ... } }; btn2.setOnAction(niekto);
- Vytvorili sme tým vlastne anonymnú triedu a jej inštanciu.
- Ďalšia možnosť je, že túto triedu budeme používať častejšie - viacero prvkov bude mať podobnú reakciu, ale závislú napríklad od typu prvku alebo inej okolnosti. Potom môžeme vytvoriť triedu konkrétnu a použiť ju (či už jej anonymnú alebo pomenovanú inštanciu).
class PrintIt implements EventHandler<ActionEvent> { String s; PrintIt(String str) { s = str; } @Override public void handle(ActionEvent ev) { System.out.println(s); } } ... btn1.setOnAction(new PrintIt("Hello")); // anonymná inštancia PrintIt niekto=new PrintIt("Hello"); // pomenovaná inštancia btn2.setOnAction(niekto);
- Ako parameter môžeme konštruktoru triedy PrintIt dať aj napr. objekt, ktorý ho zavolá (alebo vhodnú nadtriedu, aby zahŕňala všetky potenciálne objekty), ktoré ju môžu skúšať volať (napr. trieda Node)
Event driven programming
- Občas (často) sa program dostáva do situácie, kedy musí čakať na niečo zvonku - kým používateľ zadá vstup, kým niekto niekam klikne ..
- V zásade sú dva možné prístupy, čo v takejto situácii robiť:
- Jednou možnosťou je aktívne čakanie na udalosť. Môžeme si to predstaviť ako cyklus while (!udalost) { skontrolujUdalost(); }
- Možno si spomínate na Oslíka v Shrekovi - presne takto sa chová takýto program.
- Je to však trochu mrhanie časom a výkonom, najmä u programov, kde sa očakáva časté a dlhé čakanie (napríklad rôzne GUI)
- Preto sa v takýchto situáciách používa Event driven programming (programovanie riadené udalosťami)
- Na rozdiel od predchádzajúceho povie program operačnému systému, že si ide pospať a keby sa náhodou niečo stalo, že ho má zobudiť.
- Spôsobí to však dosť odlišný spôsob programovania, ako sme boli zvyknutí. Program prestáva byť súvislou postupnosťou kódu, ale rozpadá sa na kúsky, ktoré sa (jeden alebo aj viac z nich) vykonajú, keď nastane nejaká udalosť.
- Viac o tomto spôsobe sa dočítate napr. tu
- Jednou možnosťou je aktívne čakanie na udalosť. Môžeme si to predstaviť ako cyklus while (!udalost) { skontrolujUdalost(); }
Ako vlastne fungujú Eventy
Event je vlastne akcia, ktorú niekto vyvolá a niekto iný zkonzumuje
- V prípade kliknutia na gombík tento gombík vyvolá MouseEvent
- Potrebujeme teda niekoho, kto tento Event spracuje -- obvykle EventHandler čakajúci na konkrétny typ Eventu
Ukážeme si jednoduchšiu (nie JavaFX) aplikáciu, kde Eventy využijeme
- Budeme mať vlastný nový typ Eventu, ktorý bude vedieť zdroj (kto ho vyvolal) a ešte String what
- Konštruktory objektu volajú všeobecný Event
- Okrem toho vie Event oznámiť aj String what
class myEv extends Event{ private String what; public myEv(Object source){ super(source,NULL_SOURCE_TARGET,new EventType("myEv")); } myEv(Object source, String s){ super(source,NULL_SOURCE_TARGET,new EventType("myEv")); what=s;} public String getWhat(){ return what; } }
- Na spracovávanie Eventov si vytvoríme triedu, ktorá implementuje všeobecný EventHandler na náš nový typ Eventov
- EventHandler potrebuje implementovať jedinú metódu handle s parametrom -- Event typu ktoré má spracovávať
- Vidíme, že môžeme využívať špecifické metódy myEv (getWhat) ako aj metódy podedené od Eventu (getSource)
class myHandler implements EventHandler<myEv> { @Override public void handle(myEv ev) { System.out.println("I handle event: "+ ev.getWhat()+ " from "+ ((mySender)ev.getSource()).getName()); } }
- Keď už máme Eventy aj niekoho, kto ich vie spracovať, potrebujeme ich ešte na niekom vytvárať (ako je napr. gombík) -- v našom prípade mySender
- Ten niekto by mal vedieť robiť setOnAction (aby sme vedeli pridať niekoho čakajúceho na akciu)
- Okrem toho musí vlastne aj spôsobovať akciu -- makeAction (toto v JavaFX robí za nás aplikácia resp. používateľ, ktorý kliká myšou, navyše je to spracovávanie výrazne zložitejšie)
- Okrem toho si samotný Sender bude pamätať tých, ktorým oznámi, keď je nejaká akcia, počas ktorej vznikne Event (tu makeAction)
class mySender { private String name; private ArrayList<EventHandler<myEv>> myActionListeners=new ArrayList<>(); mySender(String s){ name=s; } public String getName(){ return name; } void setOnAction(EventHandler<myEv> h){ myActionListeners.add(h); } void makeAction(){ myEv ev= new myEv(this, "Hello"); for (EventHandler<myEv> h: myActionListeners){ h.handle(ev); } } }
- Teraz môžeme celé spracovávanie Eventov spustiť
- V main si vytvoríme Sendera, pridáme mu niekoho, kto na neho čaká a vyvoláme na ňom akciu
- Prípadne môžeme Handlera vytvoriť aj ako anonymnú triedu, prípadne ich vytvoriť viac...
public static void main(String[] args) { mySender A=new mySender("A"); A.setOnAction(new myHandler()); A.setOnAction(new EventHandler<myEv>(){ @Override public void handle(myEv ev) { System.out.println("I also handle event: "+ ev.getWhat()); } }); A.makeAction(); }
- Vidíme, že sa to podobá na kód, ktorý pri vytvárame pri JavaFX aplikácii
- Button je vlastne náš Sender -- má svoje meno, vieme na ňom zavolať metódy setOn..., ktoré majú ako parameter niekoho, kto vie spracovať udalosť daného typu
- Jediný rozdiel je, že vyvolanie akcie musíme urobiť my
- Ešte sme nezapojili poslednú zaujímavú vlastnosť Eventov, ich konzumáciu
- Event má metódy consume() a isConsumed(), ktoré určujú, či ešte udalosť existuje a potrebené ju ďalej spracovávať
- V našom príklade si môžeme túto skutočnosť odsimulovať v metóde makeAction, kde cyklus ukončíme keď nám udalosť niekto zkonzumuje
- A zabezpečíme, že ju niekto zkonzumuje (myHandler v metóde handle zavolá ev.consume())
- Vidíme, že k druhému čakateľovi na udalosť sa už táto udalosť nedostane (opäť je to v JavaFX zložitejšie)
class myHandler implements EventHandler<myEv> { @Override public void handle(myEv ev) { System.out.println("I handle event: "+ ev.getWhat()+ " from "+ ((mySender)ev.getSource()).getName()); ev.consume(); } } class mySender { ... void makeAction(){ myEv ev= new myEv(this, "Hello"); for (EventHandler<myEv> h: myActionListeners){ h.handle(ev); //if (ev.isConsumed()) break; } } }
Posielanie Eventov v JavaFX aplikácii
http://docs.oracle.com/javafx/2/events/processing.htm
Cvičenia 19
Iterátory, testovanie
- Napíšte generickú statickú metódu kazdyDruhy, ktorá dostane List a vyhodí z neho každý druhý prvok. K Listu pristupujte iba cez iterátor a jeho metódu remove.
- Metódu otestujte napr. s použitím triedy LinkedList
- Na inicializovanie LinkListu s daným zoznamom prvkov môžete použiť metódu Arrays.asList, viď ukážkový kód pre metódu remove nižšie
Metódy equals, hashCode, compareTo
Napíšte triedu Zlomok, ktorá bude implementovať zlomok s celočíselným čitateľom a menovateľom.
- Triede spravte konštruktor a preťažte equals tak, aby správne testovala rovnosť zlomkov a hashCode tak, aby bol konzistentný s equals.
- Vaša trieda by tiež mala implementovať interface Comparable s bežným porovnaním zlomkov podľa veľkosti.
- Prekryte aj metódu toString aby vrátila reťazec typu "a/b"
- Skúšajte zlomky vkladať do TreeSet a HashSet a skontrolujte, že dostávate správne výsledky.
- Pre jednoduchosť môžete predpokladať, že čitateľ aj menovateľ sú kladné a že nedôjde k pretečeniu rozsahu čísla typu Integer pri aritmetických operáciách.
- Môže sa vám zísť Euklidov algoritmus na nájdenie najväčšieho spoločného deliteľa:
// ratame nsd(a,b) while(b != 0) { int x = a % b; a = b; b = x; } // vysledok je v premennej a
Metóda remove
Trieda ArrayList (ale aj LinkedList a iné triedy implementujúce List) má dve metódy s menom remove, ale s iným typom parametra
- remove(int index) zmaže prvok na pozícii index
- remove(Object o) zmaže prvý výskyt prvku o
Úloha:
- Zistite experimentovaním, ktorá z metód remove sa vykoná v nasledujúcom kóde.
- Zmeňte kód tak, aby sa zavolala opačná forma metódy remove.
Pozor, podobná zámena metód môže byť zdrojom zákernej chyby v programe.
import java.util.ArrayList; public class Prog { public static void main(String[] args) { Integer[] tmp = {3,2,1}; ArrayList<Integer> a = new ArrayList<Integer>(Arrays.asList(tmp)); System.out.println("Pred remove:" + a); a.remove(1); System.out.println("Po remove: " + a); } }
Generické statické metódy
- Napíšte generickú statickú metódu prienik, ktorá dostane dve SortedSet (s tým istým typom prvkov E) a vráti SortedSet obsahujúcu ich prienik, t.j. prvky, ktoré sa nachádzajú v oboch.
- Použite algoritmus podobný na merge, pričom sa posúvate dvoma iterátormi (iterátory pre SortedSet vracajú prvky v utriedenom poradí).
- Potrebujete si tiež pamätať aktuálny prvok z každej množiny, lebo po volaní next sa už späť k tomu istému prvku nedostanete.
- Vašu metódu prienik poriadne otestujte
- Vytvorte si niekoľko testov na bežné aj okrajové prípady.
- Buď použite JUnit, alebo jednoducho zavolajte metódu prienik z metódy main pre rôzne dvojice množín a skontrolujte, či sa vypočítaný výsledok zhoduje so správnou odpoveďou (na porovnanie dvoch množín môžete použiť metódu equals)
Prednáška 31
Organizačné poznámky
- DÚ14 je zverejnená, odovzdávajte do pondelka 11.4. 22:00 (iterátory, generické programovanie)
Grafické objekty na paneli
- V JavaFX síce existuje priamo Canvas, ktorý je určený čisto na vykresľovanie, ale ak by sme chceli s grafickými objektami pracovať (presúvať, prekresľovať..), museli by sme si pamätať ich vlastnosti a eventy na nich spracovávať sami.
- To je samozrejme možné (a uvidíme to neskôr na tejto prednáška), ale JavaFX ponúka ešte inú možnosť -- vykresliť grafické objekty môžeme na rôzne komponenty napríklad na rôzne typy Pane.
Vytvorenie grafického objektu
- Trieda Node (čiže objekty, ktoré je možné ukladať na panely) obsahuje aj podtriedu Shape -- geometrické útvary ako Arc, Circle, CubicCurve, Ellipse, Line, Path, Polygon, Polyline, QuadCurve, Rectangle, SVGPath, Text. Tieto teda jednoducho vložíme na panel a o ich vykreslenie sa nám stará aplikácia podobne ako o gombíky a podobné objekty.
@Override public void start(Stage primaryStage) { Pane pane = new Pane(); Rectangle square=new Rectangle(); square.setHeight(10); square.setWidth(10); square.setX(10); square.setY(10); pane.getChildren().add(square); Scene scene = new Scene(pane, 300, 250); primaryStage.setTitle("Drawing a square"); primaryStage.setScene(scene); primaryStage.show(); }
- Nič nám však nebráni urobiť si vlastný objekt, ktorý bude rozšírením niektorého už existujúceho objektu. Napríklad červený štvorček.
- Štvorčeky potom môžeme jednoducho vytvárať a umiestňovať na panel.
class MyRedSquare extends Rectangle { MyRedSquare(){ this(10); } MyRedSquare(int size){ super(); this.setFill(Color.RED); this.setHeight(size); this.setWidth(size); } MyRedSquare(int size, int x, int y){ this(size); this.setX(x); this.setY(y); } }
Práca s vykreslenými objektami
- Aby sme vedeli s vykreslenými objektami nejako pracovať potrebujeme im pridať akcie. Objekty typu Shape majú zdedené od Node niekoľko udalostí na prácu s myšou.
- Môžeme teda štvorčeku nastaviť, že ak na ňom klikneme myšou, zväčší sa
MyRedSquare square=new MyRedSquare(10,10,10); square.setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { square.setHeight(square.getHeight()+10); square.setWidth(square.getWidth()+10); } });
- Samozrejme takto môžeme štvorčeku nastaviť viacero reakcií na rôzne udalosti.
- Keďže už máme vlastnú triedu štvorček, môže sa nám hodiť, ak funkciu na zmenu štvorčeka po kliknutí urobíme jednou jeho vlastnou metódou -- napríklad changeSquare()
- Okrem toho sme štvorčeku pridali aj farbu, ktorú pri každom kliknutí meníme (striedame červenú a modrú)
- Potom pri odlišnej reakcii zmeníme iba metódu changeSquare
class MyRedSquare extends Rectangle { Color col=Color.RED; MyRedSquare(){ this(10); } MyRedSquare(int size){ super(); this.setFill(col); this.setHeight(size); this.setWidth(size); } MyRedSquare(int size, int x, int y){ this(size); this.setX(x); this.setY(y); } public void changeSquare(){ this.setHeight(this.getHeight()+10); this.setWidth(this.getWidth()+10); if (col==Color.RED) col=Color.BLUE; else col=Color.RED; this.setFill(col); } } ... MyRedSquare square=new MyRedSquare(10,10,10); square.setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { square.changeSquare(); } }); ...
- Skúsime pridať do programu ďalšiu udalosť -- ak klikneme na plochu mimo štvorčeka, štvorček sa presunie na danú pozíciu.
- Zjavne udalosť kliknutia by mala teda vedieť spracovávať aj Pane.
- Pridáme teda pane.setOnMouseClicked() s príslušnou reakciou na kliknutie na plochu
- Čo sa teraz stane, keď klikneme na plochu?
- A čo sa stane keď klikneme na štvorček? Vidíme, že sa štvorček aj posunul a teda, že udalosť sa dostala až k panelu. Potrebujeme udalosť zastaviť!
- Výsledná metóda start teda bude vyzerať nasledovne:
@Override public void start(Stage primaryStage) { Pane pane = new Pane(); MyRedSquare square=new MyRedSquare(10,10,10); square.setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { square.changeSquare(); event.consume(); } }); pane.setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { square.setX(event.getX()); square.setY(event.getY()); } }); pane.getChildren().add(square); Scene scene = new Scene(pane, 300, 250); primaryStage.setTitle("Hello World!"); primaryStage.setScene(scene); primaryStage.show(); }
Viacero vykreslených objektov
- Môžeme si vytvoriť celé pole červených štvorčekov a všetky ich vykresliť na panel.
- K tomu potrebujeme ArrayList<MyRedSquare> squares=new ArrayList<>();
- Každý štvorček vytvoriť for (int i=0; i<10; i++){ squares.add(new MyRedSquare(10,20*i,20*i));}
- Každý štvorček pridať na panel for (int i=0; i<10; i++){ pane.getChildren().add(squares.get(i));}
- Každému štvorčeku pridať spracovávanie udalosti. Alebo spracovávanie udalosti pridať priamo v konštruktore triedy MyRedSquare
- Samozrejme v prípade, že máme objektov viac ja potrebné pri akcii myši rozmýšľať, ktorého objektu sa akcia má týkať.
- Toto za nás rieši to, že na udalosť čaká každý z objektov
- Tiež však potrebujeme rozhodovať, ktorý objekt sa posunie, ak sa akcia vykoná na paneli.
- Jednoduchým rozšírením vieme napríklad dosiahnuť iba jeden (alebo žiadny) vybraný prvok, s ktorým môžeme potom niečo robiť (vypísať jeho vlastnosti, vymazať...
Posielanie Eventov v JavaFX
- Pozrieme sa, čo sa stane, ak na obrazovke máme viacero prvkov, ktoré môžu chcieť spracovať jednu udalosť.
- Na začiatok si vezmeme jeden obdlždnik na paneli. Oba sú schopné spracovať udalosť typu MouseEvent.
- Prvý udalosť dostane obdĺždnik a spracuje ju.
- Ak tento udalosť nezkonzumoval, dostane sa aj k panelu, ktorý ju tiež môže spracovať.
- Zaujímavejšie to začne byť, keď na paneli máme dva obdlždniky. Pokiaľ sa neprekrývajú nič sa nemení. Takže si vytvoríme dva, ktoré sa prekrývajú.
@Override public void start(Stage primaryStage) { Pane root = new Pane(); Scene scene = new Scene(root, 300, 250); Rectangle r1=new Rectangle(); r1.setX(0); r1.setY(0); r1.setWidth(100); r1.setHeight(100); r1.setFill(Color.BLUE); r1.setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { System.out.println("R1"); } }); Rectangle r2=new Rectangle(); r2.setX(50); r2.setY(50); r2.setWidth(100); r2.setHeight(100); r2.setFill(Color.RED); r2.setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { System.out.println("R2"); } }); root.getChildren().addAll(r1,r2); root.setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { System.out.println("Root"); } }); primaryStage.setScene(scene); primaryStage.show(); }
- Vidíme, že ak objekt je na jednej úrovni s niekým ďalším tak udalosť dostane iba ten, koho je vidno (to je aj vysvetlenie, prečo to tak fungovalo pre 2 gombíky cez seba).
- Špeciálnym prípadom je však panel. Keď na základaný panel (v našom prípade root) umiestnime ďalší prázdny panel typu Pane, zistíme, že jeho reakcia na udalosť sa nikdy nezavolá. Je to preto, že tento panel (keďže je prázdny) má nulovú veľkosť. Umiestnime naň teda obdĺždnik.
... Rectangle r3=new Rectangle(); r3.setX(70); r3.setY(70); r3.setWidth(50); r3.setHeight(50); r3.setFill(Color.YELLOW); r3.setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { System.out.println("R3"); } }); Pane panel=new Pane(); panel.getChildren().add(r3); panel.setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { System.out.println("Panel"); } }); root.getChildren().addAll(r1,r2,panel); ...
- Panel má teraz veľkosť po pravý dolný roh r3. Môžeme vyskúšať ktoré spracovanie udalosti kde nastane.
- Vyskúšajte si, čo sa stane, keď jednotlivé objekty udalosť zkonzumujú. A čo keď niekto udalosť spracovávať nebude?
Zhrnutie
Ako teda funguje spracovanie udalostí?
- Najprv sa zistí, komu bola asi udalosť určená. Bude to objekt, ktorý je na obrazovke reálne vidieť (t.j. je úplne navrchu v mieste, kde udalosť vznikla).
- Od tohto objektu sa zkonštruuje cesta, koho všetkého môže udalosť zaujímať. Prvý je samotný "zastihnutý" objekt O, potom objekt, medzi ktorého deti O patrí, atď.
- Po tejto ceste sa bude udalosť pohybovať a spracovávať (ak ju jednotlivé objekty na ceste chcú), kým nebude zkonzumovaná alebo nedosiahne primaryStage.
- Do tohto procesu môžu ešte vstupovať filtre, ale o tých sa v tomto krátkom prehľade o JaveFX venovať nebudeme.
Canvas a grafika
- Okrem klasického panelu, na ktorý umiestňujeme objekty typu, ktorý rozširuje Shape, môžeme grafický obsah tvoriť aj pomocou Canvasu.
- Canvas je plocha s grafickým obsahom. Vytvoríme ho pomocou Canvas canvas = new Canvas(200,200);
- Každé plátno má k sebe priradený GraphicsContext, ktorý sa stará o vykresľovanie a veci s tým súvisiace
- Keď teda chceme niečo nakresliť, musíme najskôr zistiť kto sa nám bude starať o kreslenie na nášom plátne napr. GraphicsContext gc = canvas.getGraphicsContext2D();
- Príkazy na konkrétne vykresľovanie alebo zmeny farieb a pod. budeme teda adresovať tomuto grafickému kontextu
@Override public void start(Stage primaryStage) { Canvas canvas = new Canvas(200,200); GraphicsContext gc = canvas.getGraphicsContext2D(); gc.setFill(Color.LIGHTGRAY); gc.fillRect(0,0,200,200); gc.setFill(Color.BLUE); gc.fillRect(10, 10, 100, 50); Pane root = new Pane(); root.getChildren().add(canvas); Scene scene = new Scene(root, 300, 250); primaryStage.setTitle("My Canvas Application"); primaryStage.setScene(scene); primaryStage.show(); }
- Viac príkladov, čo môžeme chcieť od grafického kontextu aby nám namaľoval napr. tu
- Grafické prvky na plátne však nie sú objekty, ale iba vyrenderované obrazce - nemôžu teda očakávať žiadne udalosti - o toto sa musí starať plátno.
- A nielen to, tieto grafické útvary po vykreslení ani žiadnym spôsobom nemôžeme ovládať - zmeniť im farbu, zistiť ich zoznam a pod.
- Nič nám však nebráni mať ich zoznam niekde uložený a udržiavať si ho tam. Takýto príklad uvidíme v nasledujúcom príklade.
- Pre jednoduché vykresľovanie sa teda plátno môže hodiť, ale musíme si premyslieť, čo už je nad jeho sily a radšej to urobíme pomocou klasického panelu na ktorý budeme umiestňovať vhodné objekty.
Štvorcová mriežka
- Chceme si vykresliť napríklad šachovnicu, v ktorej môžeme následne hrať nejakú hru.
- Aby sme neskôr (ak budeme chcieť program rozšíriť) nemuseli hľadať konštanty vyrobíme aplikácii 2 privátne premenné riadkov a stĺpcov
- Veľkosť plátna nastavíme na 20*riadkov x 20*stlpcov
- Potom môžeme vykresliť zvislé a vodorovné čiary a prípadne vyfarbiť vhodne políčka - vyfarbenie políčok robíme kreslením obdĺždnikov
public class CanvasApp extends Application { private int riadkov=8, stlpcov=8; @Override public void start(Stage primaryStage) { Canvas canvas = new Canvas(20*riadkov,20*stlpcov); ... nastav canvas, farby... for (int r=0; r<riadkov+1; r++) gc.strokeLine(0, r*20, stlpcov*20, r*20); for (int s=0; s<stlpcov+1; s++) gc.strokeLine(s*20, 0, s*20, riadkov*20); for (int r=0; r<riadkov; r++) for (int s=0; s<stlpcov; s++){ if ((r+s)%2==0) gc.setFill(Color.WHITE); else gc.setFill(Color.BLACK); gc.fillRect(s*20, r*20, 20, 20); // gc.fillRect(s*20+1, r*20+1, 18, 18); //ak chceme stvorceky mensie } ... panel, scena, atd. ... }
- Keby sme si v takejto mriežke chceli niečo hrať, museli by sme si už pamätať, čo v ktorom políčku je.
- Vytvoríme si hracie pole, ktoré bude opäť privátnou premennou aplikácie
- Kreslenie čiar hracieho poľa má zmysel vykonať iba raz - pri akejsi inicializácii plátna, ale štovrčeky (najmä ak budú niečo obsahovať alebo budú mať potenciálne rôzne farby budeme prekresľovať častejšie.
- Vytvoríme si dve privátne metódy - initCanvas() a redrawCanvas(), ktorú budeme volať pri zmene (toto napríklad SWING mal vlastnú metódu, ktorú bolo treba preťažovať)
Vypĺňanie plochy
- Keby sme chceli celú hraciu plochu mať farebnú a farby nejakým sposobom meniť podľa klikania na plochu opäť budeme potrebovať reprezentovať "hracie pole", teda vlastne NxM štvorčekov.
- Ak budeme meniť iba dve farby (najjednoduchšia varianta) stačí nám pole booleanov a[riadkov][stlpcov]
- Okrem toho však potrebujeme dať na samotný Canvas niekoho, kto spracuje kliknutie (a rozozná koho sa týkalo)
- toto môžeme ľahko urobiť v metóde initCanvas
gc.getCanvas().setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { int x=(int)event.getX()/20; int y=(int)event.getY()/20; zmen a[y][x] redrawCanvas(gc); } });
- Nemusíme však vždy prekresľovať celý Canvas, keď meníme farbu jednému štvorčeku. Stačí nakresliť vhodnou farbou konkréty štvorček.
gc.getCanvas().setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { int x=(int)event.getX()/20; int y=(int)event.getY()/20; zmen a[y][x] gc.setFill( ... ); //nejaká farba podľa a[y][x] gc.fillRect(x*20+1, y*20+1, 18, 18); } });
- Celý program #Program farebné štvorčeky
- Keby sme chceli meniť viac farieb mohli by sme namiesto poľa booleanov dať int (a číslo reprezentuje farbu) alebo priamo farby (v príklade sú to zakomentované časti s poľom b).
Program vykresľovanie
package javafxapplication1; import java.util.ArrayList; import javafx.application.Application; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; public class JavaFXApplication1 extends Application { MyRedSquare current=null; class MyRedSquare extends Rectangle { Color col=Color.RED; MyRedSquare(double size){ super(); this.setFill(col); this.setHeight(size); this.setWidth(size); MyRedSquare sq=this; setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { sq.setCurrent(); event.consume(); } }); } MyRedSquare(){ this(10); } MyRedSquare(double size, double x, double y){ this(size); this.setX(x); this.setY(y); } public void changeColor(Color c){ col=c; this.setFill(col); } public void setCurrent(){ if (current!=null) { current.changeColor(Color.RED); } if (current==this) { current=null; } else { this.changeColor(Color.BLUE); current=this; } } } @Override public void start(Stage primaryStage) { ArrayList<MyRedSquare> squares=new ArrayList<>(); Pane pane = new Pane(); pane.setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { if (current!=null){ current.setX(event.getX()); current.setY(event.getY()); } else { MyRedSquare newsq=new MyRedSquare(10,event.getX(),event.getY()); squares.add(newsq); pane.getChildren().add(newsq); newsq.setCurrent(); } } }); Scene scene = new Scene(pane, 300, 250); primaryStage.setTitle("My Squares"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } }
Program farebné štvorčeky
package canvasapp; import javafx.application.Application; import javafx.event.*; import javafx.scene.Scene; import javafx.scene.canvas.*; import javafx.scene.input.MouseEvent; import javafx.scene.layout.*; import javafx.scene.paint.*; import javafx.stage.Stage; public class CanvasApp extends Application { private int riadkov=8, stlpcov=8; private boolean a[][]=null; /*private Color b[][]=null;*/ private Color changeColor(Color c){ if (c==Color.WHITE) {return Color.BLACK;} return Color.WHITE; } private void initCanvas(GraphicsContext gc){ gc.getCanvas().setWidth(20*stlpcov); gc.getCanvas().setHeight(20*riadkov); gc.setFill(Color.LIGHTGRAY); gc.setStroke(Color.BLACK); gc.fillRect(0,0,20*riadkov,20*stlpcov); for (int r=0; r<riadkov+1; r++) gc.strokeLine(0, r*20, stlpcov*20, r*20); for (int s=0; s<stlpcov+1; s++) gc.strokeLine(s*20, 0, s*20, riadkov*20); gc.getCanvas().setOnMouseClicked(new EventHandler<MouseEvent>(){ @Override public void handle(MouseEvent event) { int x=(int)event.getX()/20; int y=(int)event.getY()/20; a[y][x]=!a[y][x]; if (!a[y][x]) gc.setFill(Color.WHITE); else gc.setFill(Color.BLACK); /* b[y][x]=changeColor(b[y][x]); gc.setFill(b[y][x]); */ gc.fillRect(x*20+1, y*20+1, 18, 18); } }); } private void redrawCanvas(GraphicsContext gc){ for (int r=0; r<riadkov; r++) for (int s=0; s<stlpcov; s++){ if (!a[r][s]) gc.setFill(Color.WHITE); else gc.setFill(Color.BLACK); /* gc.setFill(b[r][s]); */ gc.fillRect(s*20+1, r*20+1, 18, 18); } } @Override public void start(Stage primaryStage) { Canvas canvas = new Canvas(); GraphicsContext gc = canvas.getGraphicsContext2D(); a=new boolean[riadkov][stlpcov]; /* b=new Color[riadkov][stlpcov]; for (int r=0; r<riadkov; r++) for (int s=0; s<stlpcov; s++) b[r][s]=Color.WHITE; */ initCanvas(gc); redrawCanvas(gc); Pane root = new Pane(); root.getChildren().add(canvas); Scene scene = new Scene(root, 300, 250); primaryStage.setTitle("My Canvas Application"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } }
Cvičenia 20
Základy JavyFX
- Vytvorte aplikáciu, ktorá bude mať jeden gombík a bude do konzolového okna vypisovať, koľký krát bol gombík stlačený.
- Ako by ste rozlíšili rôzne typy stlačení - myšou/klávesnicou
- Vytvorte aplikáciu, ktorá bude mať tri gombíky s popiskami 1,2 a 3. Do konzolového okna bude vypisovať číslo gombíku, ktorý bol stlačený.
- Použite iba jeden EventHandler, ktorý čaká na ľubovoľnú udalosť stlačenia gombíka.
- Skúste urobiť aplikáciu všeobecne -- s N gombíkmi (kde N načítate z konzoly alebo zo súboru)
- Skúste, ako môžete meniť polohu a veľkosť prvkov v okne.
- Vytvorte aplikáciu, ktorá vytvorí v okne veľkosti 500x500 gombík veľkosti 20x20 v strede. Pomocou šípok meňte polohu gombíka.
Malé aplikácie
- Na tieto príklady odporúčam naštudovať ovládací prvok TextField ([5])
- Vytvorte aplikáciu, v ktorej zadáte do textového poľa uhol v stupňoch a vypíše do vedľajšieho textového poľa hodnotu v radiánoch.
- Vytvorte aplikáciu na prepočet CZK na EUR a naopak.
Prednáška 32
Organizačné poznámky
Zložitejšie prvky v JavaFX
- Pozrieme sa na niektoré vybrané prvky, ktoré sa zaujímavejšie alebo komplikovanejšie chovajú.
- Skúsime jednotlivé prvky použiť na jedno spoločné zadanie - výber farby z vymedzeného zoznamu (pôvodne čierna, možnosti červená, modrá, zelená, príp. žltá)
Pane root = new Pane(); Rectangle rect=new Rectangle(); rect.setFill(Color.BLACK);
TextField
- Na cvičeniach ste sa už stretli s prvkom TextField. Jedná sa o pole, do ktorého môžeme umiestniť text a nechať používateľa text upravovať, vyberať a pod.
- Asi najpoužívanejšie metódy tohto prvku sú getText(), setText(), getSelectedText()
- Najbežnejšia udalosť je zadanie textu ukončené klávesou Enter. Túto udalosť môžeme odchytiť pomocou metódy setOnAction()
- Takže ako môžeme vybrať farbu? Najjednoduchšie je niekam napísať zoznam farieb a očakávať číslo, podľa ktorého hodnotu nastavíme.
Label lab0=new Label("0 Black"); lab0.setLayoutY(0); Label lab1=new Label("1 Red"); lab1.setLayoutY(30); Label lab2=new Label("2 Green"); lab2.setLayoutY(60); Label lab3=new Label("3 Blue"); lab3.setLayoutY(90); TextField tf=new TextField("0"); tf.setLayoutY(120); tf.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { try { switch (Integer.parseInt(tf.getText())) { case 0: rect.setFill(Color.BLACK); break; case 1: rect.setFill(Color.RED); break; case 2: rect.setFill(Color.GREEN); break; case 3: rect.setFill(Color.BLUE); break; } } catch (Exception e){} } }); root.getChildren().addAll(rect,tf,lab0,lab1,lab2,lab3);
Menu
- Bežnou súčasťou GUI býva Menu. Či už vo forme hlavného menu, v hornej časti alebo vo forme vyskakovacieho menu.
- Každé Menu sa skladá z prvkov, ktoré sú buď priamo prvky menu (MenuItem) alebo ďalšie Menu.
- Na každý z prvkov pochopiteľne môžeme umiestniť EventHandler, ktorý čaká na stlačenie konkrétnej položky (pri stlačení Menu sa toto rozbalí)
- Okrem toho celé Menu môže byť umiestnené v MenuBar, čo je vlastne lišta obsahujúca niekoľko prvkov menu (klasicky v GUI býva File, Edit...)
- Skúsime si premyslieť, ako by naše menu mohlo vyzerať
Color (Menu)--- Red (MenuItem) | |- Green (MenuItem) | |- Blue (MenuItem) | -- Other (Menu) --- Black (MenuItem)
- Celé to ešte dáme do MenuBaru, ktorý umiestnime do ľavého horného rohu obrazovky
- Pri kliknutí na MenuItem sa zmení farba nášho štvorčeka podľa vybranej položky
MenuBar menuBar = new MenuBar(); Menu menu1 = new Menu("Color"); menuBar.getMenus().add(menu1); MenuItem menu11 = new MenuItem("Red"); menu11.setOnAction(new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { rect.setFill(Color.RED);} }); MenuItem menu12 = new MenuItem("Green"); menu12.setOnAction(new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { rect.setFill(Color.GREEN);} }); MenuItem menu13 = new MenuItem("Blue"); menu13.setOnAction(new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { rect.setFill(Color.BLUE);} }); Menu menu14=new Menu("Others"); MenuItem menu141 = new MenuItem("Black"); menu141.setOnAction(new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { rect.setFill(Color.BLACK);} }); menu14.getItems().add(menu141); menu1.getItems().addAll(menu11,menu12,menu13,menu14); root.getChildren().addAll(rect,menuBar);
- Miernou obmenou môžeme rovnaké menu vytvoriť aj pri kliknutí na plochu
- Pochopiteľne nepotrebujeme MenuBar a menu1 (Color) nebude typu Menu ale ContextMenu
- Jeho vytvorenie zabezpečíme v metóde handle pri kliknutí myšou na plochu pomocou metódy show
- menu1.show(root, event.getScreenX(), event.getScreenY());
Radio Buttony
- Bežný spôsob ako vybrať jednu z niekoľkých možností je stlačenie jedného z niekoľkých gombíkov
- My si tu ukážeme špeciálne RadioButton, ktorý má dva základné stavy - označený a neoznačený
- Urobíme teda niekoľko RadioButtonov, ktoré reprezentujú farby, ktoré môže štvorček mať
- Pri stlačení iného sa zmení farba štvorčeka
- Pozor, musíme zároveň odznačiť ten, čo bol označený
- Treba si premyslieť, čo sa stane, ak nebude označený žiaden - napríklad bude štvorček čierny (alebo odznačenie nedovolíme)
RadioButton rb1= new RadioButton("Red"); RadioButton rb2= new RadioButton("Blue"); RadioButton rb3= new RadioButton("Green"); rb1.setOnAction(new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { if (rb1.isSelected()) { rect.setFill(Color.RED); rb2.setSelected(false); rb3.setSelected(false); } else rect.setFill(Color.BLACK); } }); rb2.setLayoutX(100); rb2.setOnAction(new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { if (rb2.isSelected()) { rect.setFill(Color.BLUE); rb1.setSelected(false); rb3.setSelected(false); } else rect.setFill(Color.BLACK); } }); rb3.setLayoutX(200); rb3.setOnAction(new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { if (rb3.isSelected()) { rect.setFill(Color.GREEN); rb2.setSelected(false); rb1.setSelected(false); } else rect.setFill(Color.BLACK); } }); root.getChildren().addAll(rect, rb1, rb2, rb3);
- Vidíme, že takto spravovať viacero gombíkov môže byť vcelku otravné.
- Keď teda chceme, aby používateľ musel vždy mať vybratú práve jednu možnosť z gombíkov použijeme ToggleGroup
- final ToggleGroup group = new ToggleGroup();
- Ak potom všetky gombíky pridáme do tejto skupiny pomocou rb1.setToggleGroup(group); odklikávanie gombíkov manažuje systém
Zoznam a jeho selection model
- Asi najkomplikovanejším prvkom, ktorým môžeme vybrať jeden z prvkov je zoznam
- Samotný zoznam je objekt typu ListView, ktorý v konštruktore potrebuje vedieť čo bude zobrazovať (v podstate nejaký ArrayList)
- Mierny rozdiel je v tom, že ListView potrebuje taky ArrayList, ktorý umožní sledovať zmeny, ktoré nastanú
- Takýto ArrayList je pripravený v FXCollections (obdoba Collections pre JavaFX) a implementuje generické rozhranie ObservableList<>
- Konkrétna implementácia tohto rozhrania, ktorú budeme využívať je FXCollections.observableArrayList
- Má niekoľko konštruktorov, kde ako parameter môže byť nič, konkrétny zoznam alebo ArrayList toho istého typu
- Akákoľvek zmena, ktorá sa stane v ObservableList sa okamžite premietne aj do zobrazovaného zoznamu (čo môžeme využiť a počas behu napr. pridávať farby)
ObservableList<Color> data = FXCollections.observableArrayList(Color.BLACK, Color.RED, Color.GREEN, Color.BLUE); ListView listView = new ListView(data); listView.setMaxWidth(100); listView.setMaxHeight(150);
- Ďalšia špeciálna vlastnosť ListView je prístup k výberu - samotný ListView nevie, kto je vybratý
- Na tieto účely má každý ListView svoj SelectionModel, ktorý si od neho vieme vypýtať pomocou metódy getSelectionModel()
- SlectionModel (defaultne je nastavený tak, že vybraný môže byť jeden prvok, ale dá sa to zmeniť) vie pomocou svojich metód pracovať s výberom - upravovať resp. odovzdať
- Vieme teda pomocou ListView nastaviť farbu a po stlačení gombíka na túto farbu zmeniť náš štvorček
Button btn=new Button("Change Color"); btn.setLayoutY(200); btn.setOnAction(new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { Color col=(Color)listView.getSelectionModel().getSelectedItem(); rect.setFill(col); } }); root.getChildren().addAll(rect, listView, btn);
ChangeListenery
- Pri zozname typu ListView a vlastne aj pri prvkoch typu RadioButton sme narazili (alebo mohli naraziť) na situáciu, kedy sme chceli zistiť, či nastala nejaká udalosť v celom prvku (resp. zoskupení prvkov)
- napríklad sme chceli niečo urobiť, keď sa zmenil vybraný prvok zoznamu (resp. vybraný RadioButton)
- pri skupine RadioButtonov sme to vedeli obísť, keď sme na každý samotný gombík dali EventHandler, pri zozname nemáme veľmi na čo pri prvkoch čakať - posunitie na ďalší prvok nemusí vzniknúť kliknutím používateľa
- Na takéto situácie sa používajú tzv. Properties a ChangeListenery
- Property je vlastnosť, ktorá na nejakom prvku nastáva
- ChangeListener je niečo podobné ako EventHandler, len očakáva zmenenie vlastnosti, na ktorú je nasadený a zavolá svoju metódu changed()
group.selectedToggleProperty().addListener(new ChangeListener<Toggle>(){ @Override public void changed(ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) { if (group.getSelectedToggle()==null) rect.setFill(Color.BLACK); else { if (newValue.equals(rb1)) rect.setFill(Color.RED); if (newValue.equals(rb2)) rect.setFill(Color.GREEN); if (newValue.equals(rb3)) rect.setFill(Color.BLUE); } } });
- Ešte dôležitejšie je to pri ListView, kde sa k tomu, že nastala zmena dostávame komplikovanejšie (nemusíme to zistiť z Eventov)
- Vlastnosť, ktorej zmenu očakávame je vlastne vlastnostnosť SelectionModelu (pripomínam, že ten pracuje s výberom prvkov a nie sám ListView)
- Ide o vlastnosť selectedItemProperty()
listView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Color>(){ @Override public void changed(ObservableValue<? extends Color> observable, Color oldValue, Color newValue) { if (newValue==null) rect.setFill(Color.BLACK); else rect.setFill(newValue); } });
- Vlastnosť si môžete vhodne pripraviť nielen pre tieto prvky - môžete ju definovať aj pre svoju triedu
- Viac o Properties a ich spracovaní môžete pozrieť tu
- POZOR! v iných jazykoch (napríklad .NET) sú Properties niečo úplne iné
Dizajn aplikácie
- Keď chceme, aby aplikácia nemala iba napevno nastavenú veľkosť okna a na čomkoľvek inom vyzerala strašne, nemôžeme súradnice prvkov nastavovať napevno, ale musíme im dať nejaké pravidlá
- Na to slúžia rôzne panely, ktoré prvky umiestňujú
- Tieto panely je bežne vhodné kombinovať a až na túto "vrstvu" panelov umiestniť konkrétne ovládacie prvky
Layouty
- Pane - všeobecný panel, kde môžeme prvkom nastaviť súradnice
- BorderPane - bežne sa používa ako základný panel, prvky sa rozmiestňujú do 5 regiónov (center, top, bottom, left, right)
- HBox/VBox - umiestňuje prvky horizontálne resp. vertikálne (za seba resp. pod seba)
- StackPane - umiestňuje prvky na seba (vhodné, keď chceme vytvoriť štvorček s popisom s grafikou a ešte niečím)
- GridPane - umiestňuje prvky do mriežky (t.j. pri pridaní prvku povieme do ktorého políčka mriežky prvok pridať)
- FlowPane - umiestňuje prvky za seba tak, aby sa zmestili do priestoru vuhradenému panelu (prvky pri zmene veľkosti panelu môžu plávať)
- TilePane - niečo medzi FlowPane a GridPane - umiestňuje do mriežky ale postupne (môžeme nastaviť preferovaný počet riadkov/stĺpcov)
- AnchorPane - umožňuje prvky ukotviť na určitú pozíciu na paneli (napr. 10px od okraja a pod.)
@Override public void start(Stage primaryStage) { BorderPane root = new BorderPane(); HBox hpanel= new HBox(); hpanel.setStyle("-fx-background-color: black;"); VBox vpanel=new VBox(); vpanel.setStyle("-fx-background-color: green;"); GridPane gridpanel=new GridPane(); gridpanel.setStyle("-fx-background-color: blue;"); FlowPane flowpanel=new FlowPane(); flowpanel.setStyle("-fx-background-color: red;"); AnchorPane anchorpanel=new AnchorPane(); anchorpanel.setStyle("-fx-background-color: yellow;"); root.setCenter(flowpanel); root.setTop(hpanel); root.setLeft(vpanel); root.setRight(gridpanel); root.setBottom(anchorpanel); Button btn1=new Button("1"); btn1.setMinHeight(50); Button btn2=new Button("2"); Button btn3=new Button("3"); btn3.setMinHeight(40); btn3.setMinWidth(150); Button btn4=new Button("4"); Button btn5=new Button("5"); Button btn6=new Button("6"); Button btn7=new Button("7"); btn7.setMinSize(40,40); Button btn8=new Button("8"); btn8.setMinSize(50, 50); Button btn9=new Button("9"); btn9.setMinWidth(100); Button btn10=new Button("10"); btn10.setMinSize(300, 30); Button btn11=new Button("11"); hpanel.getChildren().addAll(btn1,btn2); vpanel.getChildren().addAll(btn3,btn4,btn5); gridpanel.add(btn6, 1,0); gridpanel.add(btn7, 0,1); flowpanel.getChildren().addAll(btn8,btn9,btn10); anchorpanel.getChildren().add(btn11); AnchorPane.setLeftAnchor(btn11, 10.0); AnchorPane.setTopAnchor(btn11, 10.0); Scene scene = new Scene(root); primaryStage.setTitle("Layouts"); primaryStage.setScene(scene); primaryStage.show(); }
Použitie statických metód v layoutoch (a inde)
- Pri používaní AnchorPane (ale je to možné aj v iných paneloch, ale nebolo to potrebné) sme použili zaujímavú vec
AnchorPane.setLeftAnchor(btn11, 10.0);
- V podstate, keď si to uvedomíme, my nepracujeme so žiadnym konkrétnym AnchorPane, ale napriek tomu nastavujeme gombíku nejakú jeho vlastnosť na nejakom paneli
- jedná sa o statickú metódu triedy AnchorPane, ktorá nastaví gombíku nejakú vlastnosť, ktorú pri umiestnení na AnchorPane bude využívať
- keby sme rovnakú vlastnosť nastavili gombíku, ktorý napokon neumiestnime na AnchorPane, nič sa nestane, lebo túto vlastnosť si nikto iný nevyžiada
- podobne pre GridPane existujú metódy, ktoré nastavia gombíku preferovaný stĺpec bez toho, aby sme vedeli, na ktorom konkrétnom paneli sa nachádza
- Statické metódy sme videli (okrem využívania na začiatku semestra, kedy sme ich používali aby sme sa vyhli objektom) napríklad pri parsovaní zo stringu
- Integer.parseInt(s) - vidíme, že sa jedná o metódu triedy Integer
Viacoknové aplikácie
- Často chceme v aplikácii zadávať niektoré údaje nie na hlavnom okne, ale bokom (v novom okne, ktoré nastaví konkrétnu hodnotu, ktorá nás trápi
- Druhá možnosť prečo chceme druhé okno je nejaký oznam používateľovi (niečo sa nepodarilo, podarilo, iné..)
- Máme niekoľko možností, ako takéto okno vytvoriť
- Pre špeciálne hodnoty, ktoré je bežné nastavovať existujú predpripravené dialógy (farba, súbor)
- Nájdeme si jednoduchý dialóg (informačný, warning, zadanie hodnoty), ktorý upravíme k obrayu svojmu
- Vytvoríme si niečo vlastné
Predpripravené dialógy
- ColorPicker - je v podstate mierne upravený ComboBox, ktorý vie okrem už pripravených veľa farieb pridať aj výber vlastnej farby
final Rectangle rect=new Rectangle(); ColorPicker btn1=new ColorPicker(Color.BLACK); btn1.setOnAction(new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { rect.setFill(btn1.getValue()); } });
- FileBrowser - je dialóg, ktorý ako výsledok odovzdá súbor, ktorý bol vybratý
FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Open Resource File"); String currentDir = System.getProperty("user.dir") + File.separator; fileChooser.setInitialDirectory(new File(currentDir)); File file = fileChooser.showOpenDialog(primaryStage); try { Scanner sc = new Scanner(file); ... //nacitanie farby rect.setFill(c); sc.close(); } catch (Exception e){ System.err.println("Neexistujuci alebo chybny subor!"); }
Jednoduché dialógy
- Pozrieme sa aké jednoduché dialógy existujú a upravíme si ich (viac ukážok tu)
- Ide o špeciálne triedy dialógov, ktoré sa očakáva, že sú jednoduchšie (a bežne používané)
- Čo si treba vždy premyslieť je, čo ak používateľ nič nezadá (vedieť spracovať) a ako veľmi chceme nedovoliť pracovať zvyšku aplikácie kým je otvorený dialóg (tzv. Modalita) - z časti ovplyvníme tým, ako dialóg ukážeme
- Keď ho máme vytvorený, ukážeme ho pomocou metódy showAndWait() alebo show() podľa toho, či vyžadujeme blokovanie zvyšku aplikácie
- Výsledok dialógu môžeme získať pomocou Optional<> result = dialog.showAndWait();
- Typ v tejto generickej metóde závisí od typu dialógu a od toho, čo z neho môžeme chcieť vrátiť
- Pre alert obvykle gombík (resp. typ gombíka), ktorý bol stlačený, pre textový vstup to bude pochopiteľne zadaný vstup a pod.
- K výsledkom dialógu sa dostávame pomocou metód result.isPresent() a result.get()
Alert (informačný, upozorňovací a pod.)
- Pri konštrukcii musíme zadať Alert.AlertType, ktorý hovorí o dizajne a prvkoch, ktoré sú defaultne na ňom
- Môžeme mu pridať aj vlastné gombíky a tým rozšíriť možnosti, čo používateľ môže odpovedať
Alert alert = new Alert(AlertType.CONFIRMATION); alert.setTitle("Alert Dialog"); alert.setHeaderText("What color do you want?"); alert.setContentText("Red?"); ButtonType buttonOK = new ButtonType("OK"); ButtonType buttonCancel = new ButtonType("Cancel"); alert.getButtonTypes().setAll(buttonOK, buttonCancel); Optional<ButtonType> result = alert.showAndWait(); if (result.isPresent()&&result.get() == buttonOK){ rect.setFill(Color.RED); } else {}
TextInputDialog
- zadávanie textovej hodnoty (v podstate TextField s potvrdzovacím tlačidlom na vlastnom dialógu)
- Pri konštrukcii môžeme zadať defaultnú hodnotu textového poľa na dialógu
TextInputDialog textin = new TextInputDialog(); textin.setTitle("TextInput"); textin.setHeaderText("Set the red value of the Rectangle Color:"); textin.setContentText("double value 0.0-1.0"); Optional<String> result = textin.showAndWait(); if (result.isPresent()){ try { String str=result.get(); ... //zisti farbu col zo str rect.setFill(col); } catch (Exception e){} }
ChoiceDialog<>
- výber z položiek zadaných ArrayListom
- Pri konštrukcii zadáme ArrayList a defaultnú hodnotu
List<Color> choices = new ArrayList<>(); ... //napln choices ChoiceDialog<Color> dialog = new ChoiceDialog<>(Color.BLACK, choices); dialog.setTitle("Choice Dialog"); dialog.setHeaderText("Look, a Choice Dialog"); dialog.setContentText("Choose your color:"); Optional<Color> result = dialog.showAndWait(); if (result.isPresent()){ rect.setFill(result.get()); }
Všeobecný dialóg
- dá sa aj všeobecný Dialog s niekoľkými výstupmi, ale už to asi nie je jednoduchý dialóg
Vlastné dialógy
- Pre zložité dialógy si môžeme vytvoriť vlastný Stage, na ktorý umiestnime scénu, panel a pracujeme s ním ako s úvodným oknom aplikácie
- Samozrejme si môžeme Stage aj rozšíriť a vytvoriť si vlastnú triedu, ktorá vie niečo naviac (napríklad má nejaké premenné navyše)
- Doležité je nezabudnúť nastaviť Modalitu a vedieť sa vyrovnať s tým, že používateľ mohol okno zavrieť bez zadania (korektného) výsledku
Stage dialog = new Stage(); dialog.initStyle(StageStyle.UTILITY); dialog.initModality(Modality.APPLICATION_MODAL); TilePane pan=new TilePane(); Scene sc = new Scene(pan); TextField rText= new TextField(); TextField gText= new TextField(); TextField bText= new TextField(); Label rLabel=new Label("Red 0.0-1.0: "); Label gLabel=new Label("Green 0.0-1.0: "); Label bLabel=new Label("Blue 0.0-1.0: "); Button ok=new Button("OK"); ok.setOnAction(new EventHandler<ActionEvent>(){ @Override public void handle(ActionEvent event) { try { double r =Double.parseDouble(rText.getText()); double g =Double.parseDouble(gText.getText()); double b =Double.parseDouble(bText.getText()); if (r>=0 && g>=0 && b>=0 && r<=1 && g<=1 && b<=1){ rect.setFill(new Color(r,g,b,1)); dialog.close(); } } catch (Exception e){ System.err.println("ERR:NekorektnaFarba"); } } }); pan.setPrefColumns(2); pan.getChildren().addAll(rLabel,rText,gLabel, gText,bLabel,bText,ok); dialog.setScene(sc); dialog.showAndWait();
Cvičenia 21
Grafické komponenty
Vytvorte JavaFX aplikáciu s panelom triedy Pane
- pri kliknutí ľavým tlačítkom na panel sa na tomto mieste zobrazí nový štvorček (trieda Rectangle)
- pri kliknutí pravým tlačítkom to bude kruh (trieda Ellipse).
- ak užívateľ klikne na už existujúci komponent, náhodne zmení jeho farbu
- v prípade, ak pri kliknutí myšou drží klávesové tlačítko Ctrl, komponent vymaže.
- umožnite užívateľovi posúvať posledne vytvorený alebo označený komponent pomocou šípok na klávesnici
- Obdobne umožnite zväčšovať komponenty pomocou kláves A, S, D, F do príslušných strán
Do panela pridajte komponent triedy TextField
- pri pohybe myšou nad štvorčekmi a kruhmi vypíšte do textového poľa typ tvaru, poradie, v ktorom bol vytvorený a veľkosť, napr. Rectangle 1 - [100, 150], Circle 2 - [200]
Doplňte do aplikácie nový tvar, trojuholník, rozšírením triedy Polygon
- umožnite jeho vytvorenie, presúvanie a zmenu veľkosti podobne, ako pri štvorčekoch a kruhoch
Canvas
Vytvorte obdobnú aplikáciu použitím komponentu Canvas.
Prednáška 33
Organizačné poznámky
- DÚ14 (JavaFX) je zverejnená, termín odovzdania streda 27.4. 22:00
- Väčšia úloha, nenechávať si na poslednú chvíľu
- Do pondelka 9.5. si môžete vybrať tému nepovinného projektu
- V stredu 20.4. dekanské voľno kvôli študentskej vedeckej konferencii, odporúčame ísť si pozrieť aspoň niektoré prednášky (program)
- Študenti prezentujú výsledky svojho výskumu
- Hlavne diplomovky a doktorandi, ale aj bakalárske práce prípadne iné projekty
- Na konferencii resp. na posteroch zistíte, čo robia školitelia, môže vám to pomôcť nájsť si školiteľa bakalárky, príp. aj ročníkového projektu
- O 14:00 plenárna prednáška Juraja Tótha: Kometárne a asteroidálne meteoroidy (v F1)
Úvod
- Zvyšok semestra sa budeme venovať práci s grafmi
- Grafy poznáte z predmetu Úvod do kombinatoriky a teórie grafov
- Ďalšie algoritmy pre grafy v druhom ročníku na predmete Tvorba efektívnych algoritmov
- Ďalšia teória grafov + nejaké algoritmy povinne voliteľný predmet pre tretí ročník Teória grafov
Príklady využitia grafov
- Mapa, cestná sieť: vrcholy sú križovatky, mestá, obce, hrany sú cesty (podobne železnice, letecká sieť, ulice v rámci mesta ...)
- Počítačové siete, elektrické obvody, siete potrubí
- Web: vrcholy sú webstránky, hrany sú odkazy (podobne napríklad vedecké články a citácie medzi nimi)
- Sociálne siete: vzťahy, kontakty medzi ľudmi, šírenie správ
- Závislosti medzi činnosťami: ak máme vykonať niekoľko činností, ale niektoré sa dajú vykonať iba ak sú iné už hotové, ako ich usporiadať
- Preferencie: napr. pri tvorbe rozvrhu sa určitý predmet môže konať len v určité časy, môžeme spájať hranou predmety a časy
Anglická terminológia:
- graf = graph, vrchol = vertex (mn.č. vertices), hrana = edge
Reprezentácia grafov
Na dnešnej prednáške budeme uvažovať neorientovaný graf s vrcholmi očíslovanými 0,1,...,n-1.
- Príklad: V={0,...,6}, E={{0,5},{1,2},{2,3},{1,3},{3,4}}
Do akých dátových štruktúr takýto graf uložíme?
- V našom prípade množinu vrcholov reprezentujeme jednoducho počtom vrcholov n, zostáva uložiť hrany
Zoznam hrán
- Najjednoduchšia reprezentácia je zoznam hrán
- Vytvoríme si pomocnú triedu pre hranu, ktorá obsahuje čísla jej koncových vrcholov
- Všetky hrany uložíme do poľa alebo spájaného zoznamu (ArrayList, LinkedList)
(0,5), (1,2), (2,3), (1,3), (3,4)
Matica susednosti (adjacency matrix)
- Matica nxn napr typu boolean
- Políčko a[i][j]=true ak {i,j} je hrana
- Pre neorientovaný graf symetrická matica
0 1 2 3 4 5 6 0 F F F F F T F 1 F F T T F F F 2 F T F T F F F 3 F T T F T F F 4 F F F T F F F 5 T F F F F F F 6 F F F F F F F
Zoznamy susedov (adjacency lists)
- Pre každý vrchol zoznam jeho susedov
- Uložíme ako pole potrebnej veľkosti alebo spájaný zoznam (ArrayList, LinkedList)
0: 5 1: 2,3 2: 1,3 3: 1,2,4 4: 3 5: 0 6:
Graf ako abstraktný dátový typ: Interface Graph
- Ukážeme si konkrétne implementácie pre maticu a zoznamy susedov, potrebujeme však vedieť, aké operácie by mal graf poskytovať.
- Tu je jednoduché rozhranie pre graf, do ktorého vieme pridávať hrany, testovať či hrana existuje a prechádzať cez susedov určitého vrcholu.
/** Interface reprezentujúci graf s n vrcholmi očíslovanými 0..n-1. */ interface Graph { /** Vráti počet vrcholov grafu n. */ int getNumberOfVertices(); /** Vráti počet hrán grafu. */ int getNumberOfEdges(); /** Do grafu pridá hranu z vrcholu from do vrcholu to, * vráti true ak sa ju podarilo pridať. */ boolean addEdge(int from, int to); /** Vráti true, ak existuje hrana z vrcholu from * do vrcholu to. */ boolean existsEdge(int from, int to); /** Vráti iterovateľnú skupinu susedov vrchola vertex. * V prípade orientovaného grafu vracia iba hrany vychádzajúce z vrchola. * Metóda remove iterátora nemusí byť podporovaná. */ Iterable<Integer> adjVertices(int vertex); }
- adjVertices vráti Iterable, čo je rozhranie (interface) s jedinou predpísanou metódou iterator(), ktorá vráti iterátor cez ten istý typ
- Objekty typu Iterable sa dajú použiť vo for-cykle typu for(int v : g.adjVertices(u))
Príklad použitia: vypísanie grafu v poradí počet vrcholov, počet hrán a potom zoznam hrán, každá daná koncovými vrcholmi:
/** Graph g vypíše do výstupného streamu */ static void printGraph(Graph g, PrintStream out) { int n = g.getNumberOfVertices(); out.println(n + " " + g.getNumberOfEdges()); for (int u = 0; u < n; u++) { for(int v : g.adjVertices(u)) { if (u < v) { // kvoli symetrii v neorientovaných grafoch out.println(u + " " + v); } } } }
Napr. pre graf vyššie
7 5 0 5 1 2 1 3 2 3 3 4
Implementácia pomocou zoznamov susedov: Trieda AdjListsGraph
- Pre každý vrchol si udržiavame zoznam susedných vrcholov v ArrayListe
- Ako iterátor cez susedov použijeme jednoducho iterátor z ArrayListu
/** Trieda reprezentujúca neorientovaný graf pomocou * zoznamov susedov uložených v poli dynamickej dĺžky (ArrayList). */ class AdjListsGraph implements Graph { /** Zoznam susedov pre každý vrchol */ private ArrayList<ArrayList<Integer>> adjLists; /** Počet hrán v grafe */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public AdjListsGraph(int numVertices) { adjLists = new ArrayList<ArrayList<Integer>>(numVertices); for (int i = 0; i < numVertices; i++) { adjLists.add(new ArrayList<Integer>()); } numEdges = 0; } @Override public int getNumberOfVertices() { return adjLists.size(); } @Override public int getNumberOfEdges() { return numEdges; } @Override public boolean addEdge(int from, int to) { adjLists.get(from).add(to); // pridaj hranu v oboch smeroch adjLists.get(to).add(from); numEdges++; return true; } @Override public boolean existsEdge(int from, int to) { return adjLists.get(from).contains(to); // pozri do zoznamu susedov pre from } @Override public Iterable<Integer> adjVertices(int vertex) { // vrati ArrayList obaleny, aby sa nedal menit return Collections.unmodifiableList(adjLists.get(vertex)); } }
Implementácia pomocou matice susednosti: Trieda AdjMatrixGraph
- V konštruktore vyrobíme maticu vyplnenú false
- O niečo zložitejšia metóda adjVertices
- Dal by sa spraviť aj iterátor vlastnou triedou bez použitia pomocného poľa
/** Trieda reprezentujúca neorientovaný graf pomocou matice susednosti. */ class AdjMatrixGraph implements Graph { /** Matica susednosti */ private boolean[][] matrix; /** Počet hrán grafu */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public AdjMatrixGraph(int numVertices) { matrix = new boolean[numVertices][numVertices]; for (int i = 0; i < numVertices; i++) { for (int j = 0; j < numVertices; j++) { matrix[i][j] = false; } } numEdges = 0; } @Override public int getNumberOfVertices() { return matrix.length; } @Override public int getNumberOfEdges() { return numEdges; } @Override public boolean addEdge(int from, int to) { if (existsEdge(from, to)) { // nepridava existujuce hrany return false; } matrix[from][to] = true; //prida hranu v oboch smeroch matrix[to][from] = true; numEdges++; return true; } @Override public boolean existsEdge(int from, int to) { return matrix[from][to]; } @Override public Iterable<Integer> adjVertices(int vertex) { // vytvori pomocne pole a, // vlozi do neho vsetkych susedov // a vrati ho ako Iterable obaleny aby sa nedal menit ArrayList<Integer> a = new ArrayList<Integer>(); for (int i = 0; i < matrix[vertex].length; i++) { if (matrix[vertex][i]) { a.add(i); } } return Collections.unmodifiableList(a); } }
Vytvorenie grafu
- Vytvoríme prázdny graf s určitým počtom vrcholov, po jednom pridávame hrany
/** Zo scannera načíta graf vo formáte: počet vrcholov, * počet hrán a zoznam hrán zadaných koncami vrcholov. * Ak je matrix true, uloží ho ako AdjMatrixGraph, * inak ako AdjListsGraph. */ static Graph readGraph(Scanner s, boolean matrix) { int n = s.nextInt(); int m = s.nextInt(); Graph g; if (matrix) { g = new AdjMatrixGraph(n); } else { g = new AdjListsGraph(n); } for (int i = 0; i < m; i++) { int u = s.nextInt(); int v = s.nextInt(); g.addEdge(u, v); } return g; }
Porovnanie reprezentácií
- Majme graf s n vrcholmi a m hranami
- Počet hrán m môže byť od 0 po n(n-1)/2
- Vyjadríme čas rôznych operácií v O-notácii:
- O(1): robíme len konštantný počet operácií bez ohľadu na veľkosť grafu
- O(n): čas operácie rastie v najhoršom prípade lineárne s počtom vrcholov grafu
- O(m): čas operácie rastie v najhoršom prípade lineárne s počtom hrán grafu
- Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle O(n^2)} : čas operácie rastie v najhoršom prípade kvadraticky s počtom vrcholov grafu
- Väčšinou máme viac hrán ako vrcholov, takže O(1) je lepšie ako O(n), to je lepšie ako O(m) a to je lepšie Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle O(n^2)}
Zoznam hrán | Matica susednosti | Zoznamy susedov | |
Pamäť | O(m) | Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle O(n^2)} | O(n+m) |
Vytvoriť graf bez hrán | O(1) | Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle O(n^2)} | O(n) |
addEdge | O(1) | O(1) | O(1) |
existsEdge | O(m) | O(1) | O(n) |
Prejdenie susedov vrchola | O(m) | O(n) | O(stupeň) |
Výpis grafu pomocou adjVertices | O(nm) | Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle O(n^2)} | O(n+m) |
- Matica susednosti
- Rýchla operácia existsEdge
- Ak je graf riedky (málo hrán), zaberá zbytočne veľa pamäte a dlho trvá prejdenie susedov vrchola
- Zoznamy susednosti
- Vhodný aj pre riedke grafy
- Dlho trvá nájdenie konkrétnej hrany, ale všetkých susedov vrchola vieme prejsť rýchlo
- Najvhodnejšia reprezentácia na väčšinu algoritmov, ktoré uvidíme
Poznámky a obmeny
Orientované grafy
- Ak v metóde addEdge pridáme hranu iba jedným smerom, dostaneme orientovaný graf
- adjIterator vráti iterátor cez vychádzajúce hrany
Grafy s násobnými hranami
- AdjMatrixGraph nepovoľuje násobné hrany
- Ak chceme prirobiť, museli by sme v matici mať int-y vyjadrujúce násobnosť hrany
- AdjListsGraph nekontroluje násobnosť hrany, dovoľuje opakovanie
- Kontrola násobnosti by dlho trvala - pri addEdge by sme museli pozrieť, či už v poli nie je
Ohodnotené grafy
- V grafe si pre jednotlivé vrcholy a hrany môžeme chcieť pamätať ďalšie dáta
- Napr. ak modelujeme cestnú sieť, hrana môže mať určitú dĺžku
- Vrcholy môžu mať mená, súradnice a pod.
- Nabudúce pridáme takéto dáta do implementácie
Dynamické grafy
- Náš graf možno meniť len pridaním hrán
- V grafe môžeme chcieť robiť aj iné zmeny
- Mazanie hrán by sa ľahko doplnilo, rýchle pre maticu, pomalšie pre zoznamy susedov
- Pridávanie a mazanie vrcholov ťažšie -- potrebujeme číslovanie 0..n-1
- Neskôr si ukážeme všeobecnejšiu štruktúru, ktorá to umožňuje
Súvislosť grafu, prehľadávanie do hĺbky (depth-first search, DFS)
- Zameriame sa na zisťovanie, či sú dva vrcholy spojené v grafe cestou, t.j. či sú v tom istom komponente súvislosti (v neorientovanom grafe)
- Napr. vrchol 1 je v komponente súvislosti s vrcholmi 2,3,4
- Použijeme prehľadávanie do hĺbky, podobné ako sme použili na vyfarbovanie súvislých oblastí v matici (ostrovy)
- Mapu ostrovov môžeme zapísať ako graf, pričom každé políčko ostrovu bude vrchol a dve políčka budú spojené hranou, ak spolu susedia
- Ostrovy sú potom komponenty súvislosti
Existuje cesta z u do v?
- Poďme teda zisťovať, či sú u a v v tom istom komponente
- Rekurzívne prehľadávame začínajúc od vrcholu u
- Vytvoríme si pole booleanov visited, v ktorom si značíme už navštívené vrcholy
- Z každého vrcholu rekurzívne prehľadáme zatiaľ nenavštívených susedov
/** Pomocná metóda pre metódu connected. * Dostane graf g, vrchol vertex * a pole s poznačenými navštívenými vrcholmi, pričom * visited[vertex] by malo byť false. * Rekurzívne prehľadá nenavštívené vrcholy, ktoré sa z * vrcholu vertex dajú dosiahnuť. */ static void search(Graph g, int vertex, boolean[] visited) { visited[vertex] = true; for(int neighbor : g.adjVertices(vertex)) { // prejdi cez susedov if (!visited[neighbor]) { search(g, neighbor, visited); // navstivime ho rekurzivne } } } /** Metóda, ktorá zistí, či sú vrcholy from a to v grafe g * spojené cestou. */ static boolean connected(Graph g, int from, int to) { // vytvor pole visited vyplnené false boolean[] visited = new boolean[g.getNumberOfVertices()]; for (int i = 0; i < visited.length; i++) { visited[i] = false; } search(g, from, visited); // zavolaj rekurziu return visited[to]; // dostali sme sa do vrchola to? }
Hľadanie komponentov súvislosti
- Ak by sme chceli testovať spojenie medzi veľa dvojicami vrcholov, oplatí sa nám nájsť komponenty súvislosti v celom grafe naraz.
- Komponenty očísľujeme 0,1,...,k-1. Pre každý vrchol máme v poli číslo jeho komponentu.
- Potom dva vrcholy sú spojené cestou práve vtedy, keď majú rovnaké číslo komponentu.
- Toto robí nasledujúca trieda.
/** Trieda obsahujúca rozdelenie vrcholov grafu do komponentov súvislosti */ class Components { /** Pre každý vrchol číslo jeho komponentu 0..počet komponentov-1 */ private int[] componentId; /** počet komponentov grafu */ private int numComponents; /** samotný graf */ private Graph g; /** Konštruktor, ktorý dostane graf a prehľadávaním do hĺbky * hľadá komponenty súvislosti */ public Components(Graph g) { this.g = g; // uloz graf numComponents = 0; // inicializuj pocet komponentov int n = g.getNumberOfVertices(); // pocet vrcholov grafu // vytvor pole cisel komponentov a inicializuj na -1 - nevyfarbene componentId = new int[n]; for (int i = 0; i < n; i++) { componentId[i] = -1; } // prechadzaj cez vrcholy a ak najdes nevyfarbeny, spusti prehladavanie for (int i = 0; i < n; i++) { if (componentId[i] == -1) { search(i, numComponents); // vyfarbi cislom numComponents numComponents++; // zvys numComponents } } } /** Pomocná rekurzívna metóda používaná v konštruktore na vyfarbenie * jedného komponentu číslom id. */ private void search(int vertex, int id) { componentId[vertex] = id; for (int neighbor : g.adjVertices(vertex)) { if (componentId[neighbor] == -1) { search(neighbor, id); // navstivime ho rekurzivne } } } /** Vráti true ak vrcholy from a to sú v tom istom komponente */ public boolean areConnected(int from, int to) { return componentId[from] == componentId[to]; } /** Vráti počet komponentov grafu */ public int getNumberOfComponents() { return numComponents; } }
- Čas výpočtu pri použití zoznamov susedov je O(n+m)
- Vedeli by sme triedu obohatiť aj o iterovanie cez vrcholy v jednotlivých komponentoch
Príklad použitia prehľadávania do hĺbky: valec lesom
- Ukážeme si trochu netradičný príklad, v ktorom sa dajú grafy a prehľadávanie použiť
- Máme daný obdĺžnikový pozemok obohnaný plotom, na ktorom rastú stromy, ktoré si predstavíme ako body rovine
- Pri západnom okraji pozemku stojí valec s polomerom r (t.j. v rovine si ho predstavíme ako kruh), pričom pri západnej strane plotu je dosť miesta, aby popri nej valec prešiel od severu na juh
- Cieľom je zistiť, či sa valec dá pretlačiť pomedzi stromy na východnú stranu pozemku
Na prvý pohľad geometrická úloha sa prevedie na grafovú takto:
- Vytvoríme vrcholy S a J reprezentujúce severnú a južnú stranu pozemku
- Vytvoríme tiež vrchol pre každý strom
- Vrcholy pre dva stromy spojíme hranou, ak sú bližšie ako 2r, t.j. valec sa medzi ne neprepchá
- Podobne vrchol pre strom spojíme hranou s vrcholom S alebo J, ak je k príslušnému okraju pozemku bližšie ako 2r.
- Ak S a J sú v tom istom komponente súvislosti, valec nie je možné cez les prepchať, lebo existuje lomená čiara spájajúca stromy na ceste z S do J, cez ktorú valec nevie prejsť
- Ak S a J nie sú v tom istom komponente súvislosti, valec môžeme posúvať po južnej strane komponentu obsahujúceho vrchol S
- Na odpoveď, či sa dá valec presunúť lesom, teda stačí spustiť connected(g,S,J).
Zhrnutie
- Grafy sa používajú na veľa problémov
- Reprezentujeme ich ako zoznamy susedov, prípadne ako maticu susednosti
- V neorientovanom grafe vieme prehľadávaním do hĺbky nájsť komponenty súvislosti
- Ako je to v orientovaných grafoch?
- Celý program (vrátane prehľadávania do šírky z ďalšej prednášky) pozri nižšie
Zdrojový kód programu, grafy
Program k prednáškam: #Prednáška 33 a #Prednáška 34
- dve reprezentácie grafu, prehľadávanie do hĺbky a do šírky
package prog; import java.io.*; import java.util.*; /** Interface reprezentujúci graf s n vrcholmi očíslovanými 0..n-1. */ interface Graph { /** Vráti počet vrcholov grafu n. */ int getNumberOfVertices(); /** Vráti počet hrán grafu. */ int getNumberOfEdges(); /** Do grafu pridá hranu z vrcholu from do vrcholu to, * vráti true ak sa ju podarilo pridať. */ boolean addEdge(int from, int to); /** Vráti true, ak existuje hrana z vrcholu from * do vrcholu to. */ boolean existsEdge(int from, int to); /** Vráti iterovateľnú skupinu susedov vrchola vertex. * V prípade orientovaného grafu vracia iba hrany vychádzajúce z vrchola. * Metóda remove iterátora nemusí byť podporovaná. */ Iterable<Integer> adjVertices(int vertex); } /** Trieda reprezentujúca neorientovaný graf pomocou * zoznamov susedov uložených v poli dynamickej dĺžky (ArrayList). */ class AdjListsGraph implements Graph { /** Zoznam susedov pre každý vrchol */ private ArrayList<ArrayList<Integer>> adjLists; /** Počet hrán v grafe */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public AdjListsGraph(int numVertices) { adjLists = new ArrayList<ArrayList<Integer>>(numVertices); for (int i = 0; i < numVertices; i++) { adjLists.add(new ArrayList<Integer>()); } numEdges = 0; } @Override public int getNumberOfVertices() { return adjLists.size(); } @Override public int getNumberOfEdges() { return numEdges; } @Override public boolean addEdge(int from, int to) { adjLists.get(from).add(to); // pridaj hranu v oboch smeroch adjLists.get(to).add(from); numEdges++; return true; } @Override public boolean existsEdge(int from, int to) { return adjLists.get(from).contains(to); // pozri do zoznamu susedov pre from } @Override public Iterable<Integer> adjVertices(int vertex) { // vrati ArrayList obaleny, aby sa nedal menit return Collections.unmodifiableList(adjLists.get(vertex)); } } /** Trieda reprezentujúca neorientovaný graf pomocou matice susednosti. */ class AdjMatrixGraph implements Graph { /** Matica susednosti */ private boolean[][] matrix; /** Počet hrán grafu */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public AdjMatrixGraph(int numVertices) { matrix = new boolean[numVertices][numVertices]; for (int i = 0; i < numVertices; i++) { for (int j = 0; j < numVertices; j++) { matrix[i][j] = false; } } numEdges = 0; } @Override public int getNumberOfVertices() { return matrix.length; } @Override public int getNumberOfEdges() { return numEdges; } @Override public boolean addEdge(int from, int to) { if (existsEdge(from, to)) { // nepridava existujuce hrany return false; } matrix[from][to] = true; //prida hranu v oboch smeroch matrix[to][from] = true; numEdges++; return true; } @Override public boolean existsEdge(int from, int to) { return matrix[from][to]; } @Override public Iterable<Integer> adjVertices(int vertex) { // vytvori pomocne pole a, // vlozi do neho vsetkych susedov // a vrati ho ako Iterable obaleny aby sa nedal menit ArrayList<Integer> a = new ArrayList<Integer>(); for (int i = 0; i < matrix[vertex].length; i++) { if (matrix[vertex][i]) { a.add(i); } } return Collections.unmodifiableList(a); } } /** Trieda obsahujúca rozdelenie vrcholov grafu do komponentov súvislosti */ class Components { /** Pre každý vrchol číslo jeho komponentu 0..počet komponentov-1 */ private int[] componentId; /** počet komponentov grafu */ private int numComponents; /** samotný graf */ private Graph g; /** Konštruktor, ktorý dostane graf a prehľadávaním do hĺbky * hľadá komponenty súvislosti */ public Components(Graph g) { this.g = g; // uloz graf numComponents = 0; // inicializuj pocet komponentov int n = g.getNumberOfVertices(); // pocet vrcholov grafu // vytvor pole cisel komponentov a inicializuj na -1 - nevyfarbene componentId = new int[n]; for (int i = 0; i < n; i++) { componentId[i] = -1; } // prechadzaj cez vrcholy a ak najdes nevyfarbeny, spusti prehladavanie for (int i = 0; i < n; i++) { if (componentId[i] == -1) { search(i, numComponents); // vyfarbi cislom numComponents numComponents++; // zvys numComponents } } } /** Pomocná rekurzívna metóda používaná v konštruktore na vyfarbenie * jedného komponentu číslom id. */ private void search(int vertex, int id) { componentId[vertex] = id; for (int neighbor : g.adjVertices(vertex)) { if (componentId[neighbor] == -1) { search(neighbor, id); // navstivime ho rekurzivne } } } /** Vráti true ak vrcholy from a to sú v tom istom komponente */ public boolean areConnected(int from, int to) { return componentId[from] == componentId[to]; } /** Vráti počet komponentov grafu */ public int getNumberOfComponents() { return numComponents; } } /** Trieda, ktorá reprezentuje najkratšie cesty a vzdialenosti * z jedného vrchola do ostatných. */ class ShortestPaths { /** Graf, v ktorom počítame cesty */ private Graph g; /** Štartovací vrchol, z ktorého rátame najkratšie cesty */ private int start; /** Pre každý vrchol v grafe vzdialenosť od startu * alebo -1 ak je v inom komponente. */ private int[] dist; /** Pre každý vrchol u predchádzajúci vrchol na ceste zo start do u */ private int[] prev; /** Konštruktor, ktorý dostane graf a štartovací vrchol * a nájde najkratšie cesty */ public ShortestPaths(Graph g, int start) { this.g = g; this.start = start; int n = g.getNumberOfVertices(); // inicializacia poli - vyplnime -1 dist = new int[n]; prev = new int[n]; for (int i = 0; i < n; i++) { dist[i] = -1; prev[i] = -1; } // prehladavanie do sirky // vytvorime rad a vlozime do neho startovaci vrchol LinkedList<Integer> queue = new LinkedList<Integer>(); queue.addLast(start); dist[start] = 0; // sam od seba ma vzdialenost 0 while (!queue.isEmpty()) { // vyberieme prvok na zaciatku radu int vertex = queue.removeFirst(); // prejdeme cez jeho susedov a ak este neboli navstiveni // urcime im vzdialenost a pridame ich do radu for (int neighbor : g.adjVertices(vertex)) { if (dist[neighbor] < 0) { dist[neighbor] = dist[vertex] + 1; prev[neighbor] = vertex; queue.addLast(neighbor); } } } } /** Je vrchol vertex spojený so štartovacím vrcholom? */ public boolean isConnected(int vertex) { return dist[vertex] >= 0; } /** Vráti vzdialenosť vrcholu vertex od štartovacieho vrcholu. * Ak sú v rôznych komponentoch, vráti -1. */ public int distance(int vertex) { return dist[vertex]; } /** Vráti najkratšiu cestu zo štartovacieho vrcholu * do vrcholu vertex (postupnosť vrcholov, cez ktoré cesta ide). * Ak sú v rôznych komponentoch, vráti null. */ public int[] shortestPath(int vertex) { if (!isConnected(vertex)) { // vybav rozne koponenty return null; } int[] path = new int[dist[vertex] + 1]; // alokujeme cestu int v = vertex; //posledny vrchol bude vertex path[dist[vertex]] = v; for (int i = dist[vertex] - 1; i >= 0; i--) { // odzadu pridavame vrcholy v = prev[v]; // posunieme sa na predchadzajuci vrchol na ceste path[i] = v; } return path; } } public class Prog { /** Zo scannera s načíta graf vo formáte: počet vrcholov, * počet hrán a zoznam hrán zadaných koncami vrcholov. * Ak je matrix true, uloží ho ako AdjMatrixGraph, * inak ako AdjListsGraph. */ static Graph readGraph(Scanner s, boolean matrix) { int n = s.nextInt(); int m = s.nextInt(); Graph g; if (matrix) { g = new AdjMatrixGraph(n); } else { g = new AdjListsGraph(n); } for (int i = 0; i < m; i++) { int u = s.nextInt(); int v = s.nextInt(); g.addEdge(u, v); } return g; } /** Graph g vypíše do výstupného streamu */ static void printGraph(Graph g, PrintStream out) { int n = g.getNumberOfVertices(); out.println(n + " " + g.getNumberOfEdges()); for (int u = 0; u < n; u++) { for (int v : g.adjVertices(u)) { if (u < v) { // kvoli symetrii v neorientovaných grafoch out.println(u + " " + v); } } } } /** Pomocná metóda pre metódu connected. * Dostane graf g, vrchol vertex * a pole s poznačenými navštívenými vrcholmi, pričom * visited[vertex] by malo byť false. * Rekurzívne prehľadá nenavštívené vrcholy, ktoré sa z * vrcholu vertex dajú dosiahnuť. */ static void search(Graph g, int vertex, boolean[] visited) { visited[vertex] = true; for (int neighbor : g.adjVertices(vertex)) { // prejdi cez susedov if (!visited[neighbor]) { search(g, neighbor, visited); // navstivime ho rekurzivne } } } /** Metóda, ktorá zistí, či sú vrcholy from a to v grafe g * spojené cestou. */ static boolean connected(Graph g, int from, int to) { // vytvor pole visited vyplnené false boolean[] visited = new boolean[g.getNumberOfVertices()]; for (int i = 0; i < visited.length; i++) { visited[i] = false; } search(g, from, visited); // zavolaj rekurziu return visited[to]; // dostali sme sa do vrchola to? } public static void main(String[] args) throws FileNotFoundException { Scanner s; Graph g; PrintStream out; // nacitame graph ako maticu susedov a vypiseme s = new Scanner(new File("graph-in.txt")); g = readGraph(s, true); s.close(); out = new PrintStream("graph-out1.txt"); printGraph(g, out); out.close(); // nacitame graf ako zoznamy susedov a vypiseme s = new Scanner(new File("graph-in.txt")); g = readGraph(s, false); s.close(); out = new PrintStream("graph-out2.txt"); printGraph(g, out); out.close(); // ratame pre dvojice vrcholov ci su spojene jednorazovou funkciou System.out.println("Are 1 and 4 connected? " + connected(g, 1, 4)); System.out.println("Are 0 and 1 connected? " + connected(g, 0, 1)); // predpocitame komponenty, potom zistime ich pocet a testujeme spojitost Components comp = new Components(g); System.out.println("The number of connected components: " + comp.getNumberOfComponents()); System.out.println("Are 1 and 4 connected? " + comp.areConnected(1, 4)); System.out.println("Are 0 and 1 connected? " + comp.areConnected(0, 1)); // najdeme najkratsie vzdialenosti z 1 do ostatnych vrcholov ShortestPaths paths = new ShortestPaths(g, 1); System.out.println("Are 1 and 4 connected? " + paths.isConnected(4)); System.out.println("Distance of 1 and 4: " + paths.distance(4)); System.out.println("Shortest path from 1 to 4: " + Arrays.toString(paths.shortestPath(4))); } }
Cvičenia 22
- Urobte v hlavnom okne 10 gombíkov, z ktorých každý bude vypisovať svoje číslo. Skúste urobiť v rôznych layoutoch (napr. Border, VBox, HBox, Flow, Grid), aby ste videli, ako sa gombíky umiestňujú.
- Vyskúšajte úlohu aj všeobecne (t.j. bez použitia 10 konkrétnych premenných typu Button).
- Na grafickom paneli (klasický panel alebo Canvas) vytvorte do stredu kruh s priemerom 100. Nastavte akciu, ktorá celý čas pohybu myšou po paneli vypisuje (do textového popisku pod obrázkom) súradnice myši alebo informáciu, že myš je v kruhu. (Dajte si záležať, aby rozloženie panelu a popisku bolo prirodzené a nepokazilo sa pri zmene veľkosti okna).
- K predchádzajúcej aplikácii si nechajte dopredu (dialógom, vhodným výberom z menu...) nastaviť, aký obrázok bude vykreslený (vždy v strede s veľkosťou cca 100x100)
- V jednoduchšej verzii pripravte dopredu prvky, ktoré ste schopní vykresliť a ponúknite ich
- V zložitejšej verzii si premyslite dialóg, kde dovolíte používateľovi vytvoriť pomocou niekoľkých bodov uzavretý útvar, ktorý potom preškálujete na veľkosť cca 100x100 a vykreslíte.
Prednáška 34
Oznamy
- Do stredy 22:00 odovzdávajte DÚ15
- DÚ16 bude zverejnená koncom týždňa, odovzdanie pondelok 9.5.
- Pondelok 16.5. o 14:00 v posluchárni F2 záverečná písomka.
- Prípadné konflikty mi dajte vedieť čím skôr
Opakovanie
Reprezentácia grafov
Neorientovaný graf s vrcholmi očíslovanými 0,1,...,n-1.
- Príklad: V={0,...,6}, E={{0,5},{1,2},{2,3},{1,3},{3,4}}
Interface Graph
/** Interface reprezentujúci graf s n vrcholmi očíslovanými 0..n-1. */ interface Graph { /** Vráti počet vrcholov grafu n. */ int getNumberOfVertices(); /** Vráti počet hrán grafu. */ int getNumberOfEdges(); /** Do grafu pridá hranu z vrcholu from do vrcholu to, * vráti true ak sa ju podarilo pridať. */ boolean addEdge(int from, int to); /** Vráti true, ak existuje hrana z vrcholu from * do vrcholu to. */ boolean existsEdge(int from, int to); /** Vráti iterovateľnú skupinu susedov vrchola vertex. * V prípade orientovaného grafu vracia iba hrany vychádzajúce z vrchola. * Metóda remove iterátora nemusí byť podporovaná. */ Iterable<Integer> adjVertices(int vertex); }
Matica susednosti (adjacency matrix), trieda AdjMatrixGraph
- Matica nxn napr typu boolean
- Políčko a[i][j]=true ak {i,j} je hrana
0 1 2 3 4 5 6 0 F F F F F T F 1 F F T T F F F 2 F T F T F F F 3 F T T F T F F 4 F F F T F F F 5 T F F F F F F 6 F F F F F F F
Zoznamy susedov (adjacency lists), trieda AdjListsGraph
- Pre každý vrchol zoznam jeho susedov
- Uložíme ako pole potrebnej veľkosti alebo spájaný zoznam (ArrayList, LinkedList)
- Lepšia reprezentácia pre väčšinu algoritmov, obzvlášť ak je graf riedky, t.j. má pomerne málo hrán
0: 5 1: 2,3 2: 1,3 3: 1,2,4 4: 3 5: 0 6:
Prehľadávanie do hĺbky, komponenty súvislosti
- Vrcholy neorientovaného grafu vieme rozdeliť na komponenty súvislosti.
- V grafe existuje cesta z vrcholu u do vrcholu v práve vtedy, keď sú v tom istom komponente súvislosti.
- Mnohé úlohy vieme formulovať ako zisťovanie komponentov súvislosti (videli sme valec lesom, šachového koňa, červíka v bludisku).
- Prehľadávanie do hĺbky začne z jedného vrcholu a rekurzívne prehľadá celý komponent.
- Môžeme teda postupným volaním prehľadávania očíslovať komponenty 0,1,...,k-1 a pre každý vrchol uložiť číslo jeho komponentu.
- Potom dva vrcholy sú spojené cestou práve vtedy, keď majú rovnaké číslo komponentu.
- Dáta potrebné počas rekurzie a výsledky si uložíme do pomocnej triedy.
/** Trieda obsahujúca rozdelenie vrcholov grafu do komponentov súvislosti */ class Components { /** Pre každý vrchol číslo jeho komponentu 0..počet komponentov-1 */ private int[] componentId; /** počet komponentov grafu */ private int numComponents; /** samotný graf */ private Graph g; /** Konštruktor, ktorý dostane graf a prehľadávaním do hĺbky * hľadá komponenty súvislosti */ public Components(Graph g) { this.g = g; // uloz graf numComponents = 0; // inicializuj pocet komponentov int n = g.getNumberOfVertices(); // pocet vrcholov grafu // vytvor pole cisel komponentov a inicializuj na -1 - nevyfarbene componentId = new int[n]; for (int i = 0; i < n; i++) { componentId[i] = -1; } // prechadzaj cez vrcholy a ak najdes nevyfarbeny, spusti prehladavanie for (int i = 0; i < n; i++) { if (componentId[i] == -1) { search(i, numComponents); // vyfarbi cislom numComponents numComponents++; // zvys numComponents } } } /** Pomocná rekurzívna metóda používaná v konštruktore na vyfarbenie * jedného komponentu číslom id. */ private void search(int vertex, int id) { componentId[vertex] = id; for (int neighbor : g.adjVertices(vertex)) { if (componentId[neighbor] == -1) { search(neighbor, id); // navstivime ho rekurzivne } } } /** Vráti true ak vrcholy from a to sú v tom istom komponente */ public boolean areConnected(int from, int to) { return componentId[from] == componentId[to]; } /** Vráti počet komponentov grafu */ public int getNumberOfComponents() { return numComponents; } }
Najkratšie cesty, prehľadávanie do šírky (breath-first search, BFS)
- Ak sú dva vrcholy v jednom komponente súvislosti, mohli by sme chcieť vypísať aj cestu, ktorá ich spája
- Najlepšie najkratšiu cestu, teda takú, ktorá obsahuje najmenej hrán
- To nám prehľadávanie do hĺbky nenájde, použijeme prehľadávanie do šírky
- Začneme v nejakom vrchole s
- V prehľadávaní do šírky najprv navštívime všetky vrcholy vo vzdialenosti 1 od s, potom vo vzdialenosti 2, ...
- Pre každý navštívený vrchol v spočítame jeho vzdialenosť od s aj predposledný vrchol na najkratšej ceste z s do v
- Hrany medzi v a pred[v] tvoria strom (kostru grafu, ak je súvislý)
- Ak chceme vypísať cestu, po strome sa pohybujeme smerom k jeho koreňu vo vrchole s
- Algoritmus používa rad (frontu, queue), do ktorej dáva vrcholy, ktoré už majú známu vzdialenosť, ale ešte sme nepozreli ich susedov
- V rade sú v každej chvíli vrcholy najviac dvoch vzdialeností od s: najprv nejaké vrcholy vo vzdialenosti d, potom nejaké vrcholy vo vzdialenosti d+1
- Zložitosť je tiež O(n+m), ak použijeme zoznamy susedov
/** Trieda, ktorá reprezentuje najkratšie cesty a vzdialenosti * z jedného vrchola do ostatných. */ class ShortestPaths { /** Graf, v ktorom rátame cesty */ private Graph g; /** Startovaci vrchol, z ktoreho ratame najkratsie cesty */ private int start; /** Pre kazdy vrchol v grafe vzdialenost od startu * alebo -1 ak je v inom komponente. */ private int[] dist; /** Pre kazdy vrchol u predchadzajuci vrchol na ceste zo start do u */ private int[] prev; /** Konstruktor, ktory dostane graf a startovaci vrchol a najde najkratsei cesty */ public ShortestPaths(Graph g, int start) { this.g = g; this.start = start; int n = g.getNumberOfVertices(); // inicializacia poli - vyplnime -1 dist = new int[n]; prev = new int[n]; for (int i = 0; i < n; i++) { dist[i] = -1; prev[i] = -1; } // prehladavanie do sirky // vytvorime rad a vlozime do neho startovaci vrchol LinkedList<Integer> queue = new LinkedList<Integer>(); queue.addLast(start); dist[start] = 0; // sam od seba ma vzdialenost 0 while (!queue.isEmpty()) { // vyberieme prvok na zaciatku radu int vertex = queue.removeFirst(); // prejdeme cez jeho susedov a ak este neboli navstiveni // urcime im vzdialenost a pridame ich do radu for (int neighbor : g.adjVertices(vertex)) { if (dist[neighbor] < 0) { dist[neighbor] = dist[vertex] + 1; prev[neighbor] = vertex; queue.addLast(neighbor); } } } } /** Je vrchol vertex spojeny so startovacim vrcholom? */ public boolean isConnected(int vertex) { return dist[vertex] >= 0; } /** Vrati vzdialenost vrcholu vertex od starovacieho vrcholu. * Ak su v roznych komponentoch, vrati -1. */ public int distance(int vertex) { return dist[vertex]; } /** Vrati najkratsiu cestu zo startovacieho vrcholu * do vrcholu vertex (postupnost vrcholov, cez ktore cesta ide). * Ak su v roznych komponentoch, vrati null. */ public int[] shortestPath(int vertex) { if (!isConnected(vertex)) { // vybav rozne koponenty return null; } int[] path = new int[dist[vertex] + 1]; // alokujeme cestu int v = vertex; //posledny vrchol bude vertex path[dist[vertex]] = v; for (int i = dist[vertex] - 1; i >= 0; i--) { // odzadu pridavame vrcholy v = prev[v]; // posunieme sa na predchadzajuci vrchol na ceste path[i] = v; } return path; } }
Opakovanie: prehľadávanie s návratom (backtracking)
- Prehľadávanie s návratom sme videli minulý semester napr. na problém 8 dám
- Riešenie úlohy je nejaká postupnosť, rekurzívne skúšame pridať všetky možnosti ďalšieho prvku postupnosti
- Prehľadávame exponenciálny počet možností, algoritmus môže byť veľmi pomalý
- Snažíme sa ukončiť prehľadávanie vždy, keď už nie je šanca dosiahnuť platné riešenie
Príklad: vypisujeme všetky variácie bez opakovania dĺžky k z prvkov {0..n-1}
package prog; import java.util.Scanner; public class Prog { static void vypis(int[] a) { for (int x : a) { System.out.print(" " + x); } System.out.println(); } static void generuj(int[] a, boolean[] bolo, int i, int n) { /* v poli a dlzky k mame prvych i cifier, * v poli bolo mame zaznamenane, ktore cifry su uz pouzite, * chceme vygenerovat vsetky moznosti * poslednych k-i cifier */ if (i == a.length) { vypis(a); } else { for (int x = 0; x < n; x++) { if (!bolo[x]) { a[i] = x; bolo[x] = true; generuj(a, bolo, i + 1, n); bolo[x] = false; } } } } public static void main(String[] args) { int k, n; Scanner s = new Scanner(System.in); k = s.nextInt(); n = s.nextInt(); int[] a = new int[k]; boolean[] bolo = new boolean[n]; for (int i = 0; i < n; i++) { bolo[i] = false; } generuj(a, bolo, 0, n); } }
Prehľadávanie s návratom na grafoch
- Veľa úloh na grafoch nevieme riešiť rýchlymi algoritmami, backtrackingom vieme spočítať odpoveď aspoň pre malé vstupy
Hľadanie ciest dĺžky k
- Cesta v grafe je postupnosť vrcholov Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle v_0,v_1,...,v_k} taká, že každé dva za sebou idúce vrcholy Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle v_i} a Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle v_{i+1}} sú spojené hranou a žiaden vrchol sa na ceste neopakuje. Dĺžka tejto cesty je k, t.j. počet hrán na ceste.
- Prehľadávaním do šírky vieme nájsť najkratšiu cestu z u do v.
- Čo ak chceme nájsť cestu z u do v dĺžky presne k?
Príklad použitia: vypisujeme cesty rôznych dĺžok medzi vrcholmi from=0 a to=3:
for (int length = 1; length < g.getNumberOfVertices(); length++) { System.out.println("Cesty dlzky " + length + ":"); FixedLengthPaths p = new FixedLengthPaths(g, 0, 3, length); }
Výstup pre graf s V={0,...,4} a E={{0,1},{0,2},{0,3},{1,2},{2,3},{2,4},{3,4}}
Cesty dlzky 1: 0 3 Cesty dlzky 2: 0 2 3 Cesty dlzky 3: 0 1 2 3 0 2 4 3 Cesty dlzky 4: 0 1 2 4 3
- Opäť použijeme pomocnú triedu na ukladanie všetkých premenných potrebných v rekurzii
- Pole path s práve vyrábanou cestou
- Pole visited, v ktorom máme poznačené, ktoré vrcholy sú použité na ceste
- Vstupné dáta: graf, 2 vrcholy, požadovaná dĺžka
- V rekurzii skúšame k ceste pridať ďalší vrchol, pričom vyberáme z vrcholov susedných s posledným vrcholom na ceste
- Keď nájdeme cestu požadovanej dĺžky, ktorá končí v cieľovom vrchole, cestu vypíšeme
/** Trieda, ktora umoznuje najst vsetky cesty zadanej * dlzky medzi dvoma danymi vrcholmi. */ class FixedLengthPaths { /** samotny graf */ private Graph g; /** pociatocny a koncovy vrchol */ private int from, to; /** pozadovana dlzka cesty */ private int length; /** tvorena cesta */ private int[] path; /** vrcholy, ktore su na ceste pouzite */ private boolean[] visited; /** Konstruktor, ktory dostane graf, pociatocny a koncovy vrchol * a pozadovanu dlzku cesty a rovno aj spusti rekurzivne vyhladavanie * a vypisuje vysledky. */ public FixedLengthPaths(Graph g, int from, int to, int length) { this.g = g; // uloz vstupne data this.from = from; this.to = to; this.length = length; int n = g.getNumberOfVertices(); // pocet vrcholov grafu visited = new boolean[n]; // vytvor a inicializuj polia visited a path for (int i = 0; i < n; i++) { visited[i] = false; } path = new int[length + 1]; // length hran, teda length+1 vrcholov search(0, from); // zavolaj rekurziu } /** Hlavna rekurzivna metoda volana z konstruktora. * Metoda ako i-ty vrchol cesty ulozi vertex a * potom skusa vsetky moznosti ako sa na length-i krokov * dostat do cieloveho vrcholu. */ private void search(int i, int vertex) { // ulozime vrchol do cesty a poznacime ho ako pouzity path[i] = vertex; visited[vertex] = true; // ak uz mame cestu dlzky length, pozrieme, // ci konci v spravnom vrchole a vypiseme if (i == length) { if (vertex == to) { printPath(); } } else { // ak este nemame cestu dlzky length, // pozrieme na susedov aktualneho vrcholu vertex for(int neighbor : g.adjVertices(vertex)) { // prejdi cez susedov if (!visited[neighbor]) { // ak este sused nebol navstiveny, preskumame ho rekurzivne search(i + 1, neighbor); } } } visited[vertex] = false; } private void printPath() { for (int i = 0; i < path.length; i++) { System.out.print(" " + path[i]); } System.out.println(); } }
Cvičenia:
- Namiesto vypisovania ciest iba spočítajte, koľko ich je
- Zistite, či cesta dĺžky k existuje, po nájdení prvej cesty je už možné ukončiť ďalšie prehľadávanie
- Navrhnite spôsoby, ako v niektorých prípadoch zistiť, že aktuálne rozrobenú cestu už nie je možné požadovaným spôsobom rozšíriť
Hľadanie najdlhšej cesty
- V grafe chceme nájsť najdlhšiu cestu z u do v
- Malé zmeny v predchádzajúcom programe
- Pamätáme si najdlhšiu nájdenú cestu
- Vždy keď prídeme do cieľového vrcholu, porovnáme dĺžku aktuálnej cesty s najdlhšou nájdenou doteraz
/** Trieda, ktora umoznuje najst najdlhsiu cestu medzi dvoma danymi vrcholmi. */ class LongestPath { /** samotny graf */ private Graph g; /** pociatocny a koncovy vrchol */ private int from, to; /** najvyssia najdena dlzka cesty */ private int maxLength; /** najlepsia najdena cesta */ private int[] longestPath; /** tvorena cesta */ private int[] path; /** vrcholy, ktore su na ceste pouzite */ private boolean[] visited; /** Konstruktor, ktory dostane graf, pociatocny a koncovy vrchol * a spusti rekurzivne vyhladavanie */ public LongestPath(Graph g, int from, int to) { this.g = g; // uloz vstupne data this.from = from; this.to = to; int n = g.getNumberOfVertices(); // pocet vrcholov grafu visited = new boolean[n]; // vytvor a inicializuj pole visited for (int i = 0; i < n; i++) { visited[i] = false; } path = new int[n]; maxLength = -1; search(0, from); // zavolaj rekurziu } /** Hlavna rekurzivna metoda volana z konstruktora. * Metoda ako i-ty vrchol cesty ulozi vertex a * potom skusa vsetky moznosti ako v ceste pokracovat dalej. */ private void search(int i, int vertex) { // ulozime vrchol do cesty a poznacime ho ako pouzity path[i] = vertex; visited[vertex] = true; // ak sme v cielovom vrchole, porovname cestu s maximom if (vertex == to) { if (i > maxLength) { // ak sme nasli lepsiu cestu, ulozime si ju longestPath = Arrays.copyOf(path, i + 1); // kopiruj prvych i+1 prvkov maxLength = i; } } else { // ak este nie sme vo vrchole to, // pozrieme na susedov aktualneho vrcholu vertex for(int neighbor : g.adjVertices(vertex)) { // prejdi cez susedov if (!visited[neighbor]) { // ak este sused nebol navstiveny, preskumame ho rekurzivne search(i + 1, neighbor); } } } visited[vertex] = false; } int[] longestPath() { return Arrays.copyOf(longestPath, maxLength + 1); } }
Použitie triedy:
LongestPath p = new LongestPath(g, 0, 3); System.out.println("Najdlhsia cesta: " + Arrays.toString(p.longestPath()));
Príklad výstupu na rovnakom grafe ako vyššie:
Najdlhsia cesta: [0, 1, 2, 4, 3]
Hľadanie najdlhšej cesty v grafe s ohodnotenými hranami
Mierna obmena predchádzajúceho problému
- Každá hrana v grafe má priradené číslo (váha alebo dĺžka hrany)
- Hľadáme cestu z u do v s najväčším súčtom váh hrán
Napr. ak v grafe vyššie majú všetky hrany dĺžku 1, ale hrany {0,2} a {2,3} majú dĺžku 10, dostávame:
Najdlhsia cesta: [0, 2, 3]
Pozor, na hľadanie najkratšej cesty v ohodnotenom grafe sa nedá použiť prehľadávanie do šírky, algoritmy uvidíte v treťom ročníku.
Interface pre graf s ohodnotenými hranami
Potrebujeme v prvom rade takýto graf reprezentovať. V abstraktnom dátovom type potrebujeme aspoň tieto zmeny:
- Možnosť pri vkladaní hrany uložiť aj jej dĺžku
- Možnosť zistiť dĺžku hrany medzi dvoma vrcholmi, ak existuje (nebudeme implementovať)
- Možnosť pri iterovaní cez susedov zistiť dĺžku hrany k susedovi
/** Pomocna trieda reprezentujuca dvojicu sused a * vaha hrany k tomuto susedovi. */ class WeightedNeighbor { private int vertex; // cislo suseda private double weight; // vaha hrany k susedovi /** Konstruktor dostane vrchol v a vahu w */ public WeightedNeighbor(int v, double w) { vertex = v; weight = w; } public int vertex() { return vertex; } double weight() { return weight; } } /** Interface pre graf s hranami ohodnotenymi * desatinnymi cislami. */ interface WeightedGraph extends Graph { /** Pridaj hranu s danou vahou */ boolean addEdge(int from, int to, double weight); /** Iterovatelny zoznam ovahovanych susedov */ Iterable <WeightedNeighbor> weightedAdjVertices(int vertex); }
Implementácia ohodnoteného grafu zoznamami susedov
- podobná na neváhovanú verziu
/** Trieda reprezentujúca neorientovaný graf s hranami ohodnotenymi * desatinnymi cislami. Graf je ulozeny pomocou * zoznamov susedov uložených v poli dynamickej dĺžky (ArrayList). */ class WeightedAdjListsGraph implements WeightedGraph { /** Zoznam susedov pre každý vrchol (dvojice vrchol, vaha hrany) */ private ArrayList<ArrayList<WeightedNeighbor>> adjLists; /** Počet hrán v grafe */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public WeightedAdjListsGraph(int numVertices) { adjLists = new ArrayList<ArrayList<WeightedNeighbor>>(numVertices); for (int i = 0; i < numVertices; i++) { adjLists.add(new ArrayList<WeightedNeighbor>()); } numEdges = 0; } public int getNumberOfVertices() { return adjLists.size(); } public int getNumberOfEdges() { return numEdges; } /** Pridanie hrany s vahou 0 */ public boolean addEdge(int from, int to) { return addEdge(from, to, 0); } /** Pridanie hrany s danou vahou */ public boolean addEdge(int from, int to, double weight) { adjLists.get(from).add(new WeightedNeighbor(to, weight)); // pridaj hranu v oboch smeroch adjLists.get(to).add(new WeightedNeighbor(from, weight)); numEdges++; return true; } /** Test na existenciu hrany */ public boolean existsEdge(int from, int to) { // prejdeme cez susedov from iteratorom, testujeme na rovnost s to for(WeightedNeighbor other : weightedAdjVertices(from)) { if (other.vertex() == to) { return true; } } return false; } /** iterator cez vahovanych susedov vrchola */ public Iterable <WeightedNeighbor> weightedAdjVertices(int vertex) { return adjLists.get(vertex); } /** iterator cez susedov vrchola */ public Iterable <Integer> adjVertices(int vertex) { ArrayList <Integer> result = new ArrayList<Integer>(); for(WeightedNeighbor other : weightedAdjVertices(vertex)) { result.add(other.vertex()); } return result; } }
Samotné prehľadávanie
- Okrem aktuálnej cesty si musíme pamätať aj jej dĺžku, t.j. súčet váh hrán na ceste
- Rekurzia riešená trochu inak: v cykle cez susedov rovno ukladáme suseda do všetkých potrebných polí (lebo vieme váhu hrany)
- Rekurzívne prehľadávanie sa väčšinou dá zapísať veľa rôznymi spôsobmi
- Treba si vždy dobre rozmyslieť, čo robí rekurzívna funkcia a čo by malo platiť o jednotlivýh premenných
/** Trieda, ktora umoznuje najst najdlhsiu cestu * medzi dvoma danymi vrcholmi v ovahovanom grafe. */ class LongestWeightedPath { /** samotny graf */ private WeightedGraph g; /** pociatocny a koncovy vrchol */ private int from, to; /** najvyssia najdena dlzka cesty */ private double maxLength; /** najlepsia najdena cesta */ private int[] longestPath; /** tvorena cesta */ private int[] path; /** dlzka tvorenej cesty */ private double length; /** vrcholy, ktore su na ceste pouzite */ private boolean[] visited; /** Konstruktor, ktory dostane graf, pociatocny a koncovy vrchol * a spusti rekurzivne vyhladavanie */ public LongestWeightedPath(WeightedGraph g, int from, int to) { this.g = g; // uloz vstupne data this.from = from; this.to = to; int n = g.getNumberOfVertices(); // pocet vrcholov grafu visited = new boolean[n]; // vytvor a inicializuj pole visited for (int i = 0; i < n; i++) { visited[i] = false; } path = new int[n]; // vytvor cestu dlzky 0 obsahujucu iba zac. vrchol path[0] = from; visited[from] = true; length = 0; maxLength = -1; // najlespia najdena cesta search(1); // zavolaj rekurziu } /** Hlavna rekurzivna metoda volana z konstruktora. * Metoda skusa pridat novy vrchol na poziciu i a * potom skusa vsetky moznosti ako v ceste pokracovat dalej. */ private void search(int i) { int vertex = path[i - 1]; // predchadzajuci vrchol na ceste // ak sme v cielovom vrchole, porovname cestu s maximom if (vertex == to) { if (length > maxLength) { // ak sme nasli lepsiu cestu, ulozime si ju longestPath = Arrays.copyOf(path, i); // kopiruj prvych i prvkov maxLength = length; } } else { // ak este nie sme v cielovom vrchole, // pozrieme na susedov predchadzajuceho vrcholu vertex for(WeightedNeighbor other : g.weightedAdjVertices(vertex)) { // prejdi cez susedov int neighbor = other.vertex(); // sused vrcholu vertex if (!visited[neighbor]) { // ak este sused nebol navstiveny, ulozime ho do cesty // a zavolame rekurziu path[i] = neighbor; visited[neighbor] = true; double weight = other.weight(); length += weight; search(i + 1); length -= weight; visited[neighbor] = false; } } } } int[] longestPath() { return Arrays.copyOf(longestPath, longestPath.length); } }
Programy
Programy z tejto prednášky nájdete tu:
- Prehľadávanie do šírky: #Prednáška 33#Zdrojový kód programu, grafy
- #Zdrojový kód programu, prehľadávanie s návratom na neohodnotených grafoch
- #Zdrojový kód programu, prehľadávanie s návratom na ohodnotených grafoch
Zdrojový kód programu, prehľadávanie s návratom na neohodnotených grafoch
- niekoľko prehľadávaní s návratom v neohodnotenom grafe (cesta zadanej dĺžky, najdlhšia cesta, najväčšia klika)
package prog; import java.io.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Scanner; /** Interface reprezentujúci graf s n vrcholmi očíslovanými 0..n-1. */ interface Graph { /** Vráti počet vrcholov grafu n. */ int getNumberOfVertices(); /** Vráti počet hrán grafu. */ int getNumberOfEdges(); /** Do grafu pridá hranu z vrcholu from do vrcholu to, * vráti true ak sa ju podarilo pridať. */ boolean addEdge(int from, int to); /** Vráti true, ak existuje hrana z vrcholu from * do vrcholu to. */ boolean existsEdge(int from, int to); /** Vráti iterovateľnú skupinu susedov vrchola vertex. * V prípade orientovaného grafu vracia iba hrany vychádzajúce z vrchola. * Metóda remove iterátora nemusí byť podporovaná. */ Iterable<Integer> adjVertices(int vertex); } /** Trieda reprezentujúca neorientovaný graf pomocou * zoznamov susedov uložených v poli dynamickej dĺžky (ArrayList). */ class AdjListsGraph implements Graph { /** Zoznam susedov pre každý vrchol */ private ArrayList<ArrayList<Integer>> adjLists; /** Počet hrán v grafe */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public AdjListsGraph(int numVertices) { adjLists = new ArrayList<ArrayList<Integer>>(numVertices); for (int i = 0; i < numVertices; i++) { adjLists.add(new ArrayList<Integer>()); } numEdges = 0; } @Override public int getNumberOfVertices() { return adjLists.size(); } @Override public int getNumberOfEdges() { return numEdges; } @Override public boolean addEdge(int from, int to) { adjLists.get(from).add(to); // pridaj hranu v oboch smeroch adjLists.get(to).add(from); numEdges++; return true; } @Override public boolean existsEdge(int from, int to) { return adjLists.get(from).contains(to); // pozri do zoznamu susedov pre from } @Override public Iterable<Integer> adjVertices(int vertex) { return adjLists.get(vertex); // vrati ArrayList } } /** Trieda, ktora umoznuje najst vsetky cesty zadanej * dlzky medzi dvoma danymi vrcholmi. */ class FixedLengthPaths { /** samotny graf */ private Graph g; /** pociatocny a koncovy vrchol */ private int from, to; /** pozadovana dlzka cesty */ private int length; /** tvorena cesta */ private int[] path; /** vrcholy, ktore su na ceste pouzite */ private boolean[] visited; /** Konstruktor, ktory dostane graf, pociatocny a koncovy vrchol * a pozadovanu dlzku cesty a rovno aj spusti rekurzivne vyhladavanie * a vypisuje vysledky. */ public FixedLengthPaths(Graph g, int from, int to, int length) { this.g = g; // uloz vstupne data this.from = from; this.to = to; this.length = length; int n = g.getNumberOfVertices(); // pocet vrcholov grafu visited = new boolean[n]; // vytvor a inicializuj polia visited a path for (int i = 0; i < n; i++) { visited[i] = false; } path = new int[length + 1]; // length hran, teda length+1 vrcholov search(0, from); // zavolaj rekurziu } /** Hlavna rekurzivna metoda volana z konstruktora. * Metoda ako i-ty vrchol cesty ulozi vertex a * potom skusa vsetky moznosti ako sa na length-i krokov * dostat do cieloveho vrcholu. */ private void search(int i, int vertex) { // ulozime vrchol do cesty a poznacime ho ako pouzity path[i] = vertex; visited[vertex] = true; // ak uz mame cestu dlzky length, pozrieme, // ci konci v spravnom vrchole a vypiseme if (i == length) { if (vertex == to) { printPath(); } } else { // ak este nemame cestu dlzky length, // pozrieme na susedov aktualneho vrcholu vertex for(int neighbor : g.adjVertices(vertex)) { // prejdi cez susedov if (!visited[neighbor]) { // ak este sused nebol navstiveny, preskumame ho rekurzivne search(i + 1, neighbor); } } } visited[vertex] = false; } private void printPath() { for (int i = 0; i < path.length; i++) { System.out.print(" " + path[i]); } System.out.println(); } } /** Trieda, ktora umoznuje najst najdlhsiu cestu medzi dvoma danymi vrcholmi. */ class LongestPath { /** samotny graf */ private Graph g; /** pociatocny a koncovy vrchol */ private int from, to; /** najvyssia najdena dlzka cesty */ private int maxLength; /** najlepsia najdena cesta */ private int[] longestPath; /** tvorena cesta */ private int[] path; /** vrcholy, ktore su na ceste pouzite */ private boolean[] visited; /** Konstruktor, ktory dostane graf, pociatocny a koncovy vrchol * a spusti rekurzivne vyhladavanie */ public LongestPath(Graph g, int from, int to) { this.g = g; // uloz vstupne data this.from = from; this.to = to; int n = g.getNumberOfVertices(); // pocet vrcholov grafu visited = new boolean[n]; // vytvor a inicializuj pole visited for (int i = 0; i < n; i++) { visited[i] = false; } path = new int[n]; maxLength = -1; search(0, from); // zavolaj rekurziu } /** Hlavna rekurzivna metoda volana z konstruktora. * Metoda ako i-ty vrchol cesty ulozi vertex a * potom skusa vsetky moznosti ako v ceste pokracovat dalej. */ private void search(int i, int vertex) { // ulozime vrchol do cesty a poznacime ho ako pouzity path[i] = vertex; visited[vertex] = true; // ak sme v cielovom vrchole, porovname cestu s maximom if (vertex == to) { if (i > maxLength) { // ak sme nasli lepsiu cestu, ulozime si ju longestPath = Arrays.copyOf(path, i + 1); // kopiruj prvych i+1 prvkov maxLength = i; } } else { // ak este nie sme vo vrchole to, // pozrieme na susedov aktualneho vrcholu vertex for(int neighbor : g.adjVertices(vertex)) { // prejdi cez susedov if (!visited[neighbor]) { // ak este sused nebol navstiveny, preskumame ho rekurzivne search(i + 1, neighbor); } } } visited[vertex] = false; } int[] longestPath() { return Arrays.copyOf(longestPath, maxLength + 1); } } /** Trieda, ktora umoznuje najst maximalnu kliku v danom grafe. */ class MaximumClique { /** samotny graf */ private Graph g; /** zoznam vrcholov v najvacsej doteraz najdenej klike */ private LinkedList<Integer> maxClique; /** zoznam vrcholov v aktualnej klike */ private LinkedList<Integer> clique; /** Konstruktor, ktory dostane graf a spusti rekurzivne vyhladavanie */ public MaximumClique(Graph g) { this.g = g; // uloz vstupny graf // vytvor dve prazdne kliky clique = new LinkedList<Integer>(); maxClique = new LinkedList<Integer>(); search(0); // zavolaj rekurziu } /** Hlavna rekurzivna metoda volana z konstruktora. * Metoda skusi pouzit aj vynechat vrchol vertex * potom skusa vsetky moznosti pre vrcholy vertex+1...n-1. */ private void search(int vertex) { // ak aktualna klika je vacsia ako doterajsie maximum, uloz ju if (clique.size() > maxClique.size()) { // konstruktor LinkedListu moze vytvorit kopiu inej Collection maxClique = new LinkedList<Integer>(clique); } // ak uz sme vycerpali vsetky vrcholy, nie je co skusat dalej if (vertex == g.getNumberOfVertices()) { return; } // otestuj, ci sa vrchol vertex da pridat do kliky // a ci ma sancu byt vo vacsej klike ako doteraz najdena if (isConnected(g, vertex, clique) && degree(g, vertex) + 1 > maxClique.size()) { // ak ano, pridaj ho do kliky a zavolaj rekurziu clique.addLast(vertex); search(vertex + 1); // odober vertex x kliky clique.removeLast(); } // preskoc vertex a zavolaj rekurziu na ostatne vrcholy search(vertex + 1); } /** pomocna metoda, ktora overi, ci vrchol vertex je v grafe g * spojeny s kazdym z vrcholov v mnozine vertexSet. */ private static boolean isConnected(Graph g, int vertex, Collection<Integer> vertexSet) { // iterujeme cez mnozinu vertexSet for(int v : vertexSet) { if (!g.existsEdge(vertex, v)) { // over hranu return false; // hrana nie je } } return true; // vsetky hrany najdene } /** pomocna metoda, ktora zisti stupen vrchola vertex v grafe g */ private static int degree(Graph g, int vertex) { // iterujeme cez susedov vrchola, zvysujeme pocitadlo result int result = 0; for(int x : g.adjVertices(vertex)) { result++; } return result; } /** vrati maximalnu kliku najdenu v grafe g */ public LinkedList<Integer> maxClique() { // vrat kopiu nasej najlepsej kliky return new LinkedList<Integer>(maxClique); } } public class Prog { /** Zo scannera s načíta graf vo formáte: počet vrcholov, * počet hrán a zoznam hrán zadaných koncami vrcholov. * Uloží ho ako AdjMatrixGraph. */ static Graph readGraph(Scanner s) { int n = s.nextInt(); int m = s.nextInt(); Graph g = new AdjListsGraph(n); for (int i = 0; i < m; i++) { int u = s.nextInt(); int v = s.nextInt(); g.addEdge(u, v); } return g; } public static void main(String[] args) throws FileNotFoundException { Scanner s = new Scanner(new File("graph.txt")); Graph g = readGraph(s); s.close(); for (int length = 1; length < g.getNumberOfVertices(); length++) { System.out.println("Cesty dlzky " + length + ":"); FixedLengthPaths p = new FixedLengthPaths(g, 0, 3, length); } LongestPath p = new LongestPath(g, 0, 3); System.out.println("Najdlhsia cesta: " + Arrays.toString(p.longestPath())); MaximumClique c = new MaximumClique(g); System.out.println( "Maximalna klika: " + c.maxClique().toString()); } } /* Priklad vstupu (ulozit do graph.txt) 5 7 0 1 0 2 0 3 1 2 2 3 2 4 3 4 */
Zdrojový kód programu, prehľadávanie s návratom na ohodnotených grafoch
- graf s ohodnotenými hranami a hľadanie najdlhšej cesty v ňom
package prog; import java.io.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Scanner; /** Interface reprezentujúci graf s n vrcholmi očíslovanými 0..n-1. */ interface Graph { /** Vráti počet vrcholov grafu n. */ int getNumberOfVertices(); /** Vráti počet hrán grafu. */ int getNumberOfEdges(); /** Do grafu pridá hranu z vrcholu from do vrcholu to, * vráti true ak sa ju podarilo pridať. */ boolean addEdge(int from, int to); /** Vráti true, ak existuje hrana z vrcholu from * do vrcholu to. */ boolean existsEdge(int from, int to); /** Vráti iterovateľnú skupinu susedov vrchola vertex. * V prípade orientovaného grafu vracia iba hrany vychádzajúce z vrchola. * Metóda remove iterátora nemusí byť podporovaná. */ Iterable<Integer> adjVertices(int vertex); } /** Trieda reprezentujúca neorientovaný graf pomocou * zoznamov susedov uložených v poli dynamickej dĺžky (ArrayList). */ class AdjListsGraph implements Graph { /** Zoznam susedov pre každý vrchol */ private ArrayList<ArrayList<Integer>> adjLists; /** Počet hrán v grafe */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public AdjListsGraph(int numVertices) { adjLists = new ArrayList<ArrayList<Integer>>(numVertices); for (int i = 0; i < numVertices; i++) { adjLists.add(new ArrayList<Integer>()); } numEdges = 0; } @Override public int getNumberOfVertices() { return adjLists.size(); } @Override public int getNumberOfEdges() { return numEdges; } @Override public boolean addEdge(int from, int to) { adjLists.get(from).add(to); // pridaj hranu v oboch smeroch adjLists.get(to).add(from); numEdges++; return true; } @Override public boolean existsEdge(int from, int to) { return adjLists.get(from).contains(to); // pozri do zoznamu susedov pre from } @Override public Iterable<Integer> adjVertices(int vertex) { return adjLists.get(vertex); // vrati ArrayList } } /** Pomocna trieda reprezentujuca dvojicu sused a * vaha hrany k tomuto susedovi. */ class WeightedNeighbor { private int vertex; // cislo suseda private double weight; // vaha hrany k susedovi /** Konstruktor dostane vrchol v a vahu w */ public WeightedNeighbor(int v, double w) { vertex = v; weight = w; } public int vertex() { return vertex; } double weight() { return weight; } } /** Interface pre graf s hranami ohodnotenymi * desatinnymi cislami. */ interface WeightedGraph extends Graph { /** Pridaj hranu s danou vahou */ boolean addEdge(int from, int to, double weight); /** Iterovatelny zoznam ovahovanych susedov */ Iterable <WeightedNeighbor> weightedAdjVertices(int vertex); } /** Trieda reprezentujúca neorientovaný graf s hranami ohodnotenymi * desatinnymi cislami. Graf je ulozeny pomocou * zoznamov susedov uložených v poli dynamickej dĺžky (ArrayList). */ class WeightedAdjListsGraph implements WeightedGraph { /** Zoznam susedov pre každý vrchol (dvojice vrchol, vaha hrany) */ private ArrayList<ArrayList<WeightedNeighbor>> adjLists; /** Počet hrán v grafe */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public WeightedAdjListsGraph(int numVertices) { adjLists = new ArrayList<ArrayList<WeightedNeighbor>>(numVertices); for (int i = 0; i < numVertices; i++) { adjLists.add(new ArrayList<WeightedNeighbor>()); } numEdges = 0; } public int getNumberOfVertices() { return adjLists.size(); } public int getNumberOfEdges() { return numEdges; } /** Pridanie hrany s vahou 0 */ public boolean addEdge(int from, int to) { return addEdge(from, to, 0); } /** Pridanie hrany s danou vahou */ public boolean addEdge(int from, int to, double weight) { adjLists.get(from).add(new WeightedNeighbor(to, weight)); // pridaj hranu v oboch smeroch adjLists.get(to).add(new WeightedNeighbor(from, weight)); numEdges++; return true; } /** Test na existenciu hrany */ public boolean existsEdge(int from, int to) { // prejdeme cez susedov from iteratorom, testujeme na rovnost s to for(WeightedNeighbor other : weightedAdjVertices(from)) { if (other.vertex() == to) { return true; } } return false; } /** iterator cez vahovanych susedov vrchola */ public Iterable <WeightedNeighbor> weightedAdjVertices(int vertex) { return adjLists.get(vertex); } /** iterator cez susedov vrchola */ public Iterable <Integer> adjVertices(int vertex) { ArrayList <Integer> result = new ArrayList<Integer>(); for(WeightedNeighbor other : weightedAdjVertices(vertex)) { result.add(other.vertex()); } return result; } } /** Trieda, ktora umoznuje najst najdlhsiu cestu * medzi dvoma danymi vrcholmi v ovahovanom grafe. */ class LongestWeightedPath { /** samotny graf */ private WeightedGraph g; /** pociatocny a koncovy vrchol */ private int from, to; /** najvyssia najdena dlzka cesty */ private double maxLength; /** najlepsia najdena cesta */ private int[] longestPath; /** tvorena cesta */ private int[] path; /** dlzka tvorenej cesty */ private double length; /** vrcholy, ktore su na ceste pouzite */ private boolean[] visited; /** Konstruktor, ktory dostane graf, pociatocny a koncovy vrchol * a spusti rekurzivne vyhladavanie */ public LongestWeightedPath(WeightedGraph g, int from, int to) { this.g = g; // uloz vstupne data this.from = from; this.to = to; int n = g.getNumberOfVertices(); // pocet vrcholov grafu visited = new boolean[n]; // vytvor a inicializuj pole visited for (int i = 0; i < n; i++) { visited[i] = false; } path = new int[n]; // vytvor cestu dlzky 0 obsahujucu iba zac. vrchol path[0] = from; visited[from] = true; length = 0; maxLength = -1; // najlespia najdena cesta search(1); // zavolaj rekurziu } /** Hlavna rekurzivna metoda volana z konstruktora. * Metoda skusa pridat novy vrchol na poziciu i a * potom skusa vsetky moznosti ako v ceste pokracovat dalej. */ private void search(int i) { int vertex = path[i - 1]; // predchadzajuci vrchol na ceste // ak sme v cielovom vrchole, porovname cestu s maximom if (vertex == to) { if (length > maxLength) { // ak sme nasli lepsiu cestu, ulozime si ju longestPath = Arrays.copyOf(path, i); // kopiruj prvych i prvkov maxLength = length; } } else { // ak este nie sme v cielovom vrchole, // pozrieme na susedov predchadzajuceho vrcholu vertex for(WeightedNeighbor other : g.weightedAdjVertices(vertex)) { // prejdi cez susedov int neighbor = other.vertex(); // sused vrcholu vertex if (!visited[neighbor]) { // ak este sused nebol navstiveny, ulozime ho do cesty // a zavolame rekurziu path[i] = neighbor; visited[neighbor] = true; double weight = other.weight(); length += weight; search(i + 1); length -= weight; visited[neighbor] = false; } } } } int[] longestPath() { return Arrays.copyOf(longestPath, longestPath.length); } } public class Prog { /** Zo scannera s načíta graf vo formáte: počet vrcholov, * počet hrán a zoznam hrán zadaných koncami vrcholov a vahami. */ static WeightedGraph readGraph(Scanner s) { int n = s.nextInt(); int m = s.nextInt(); WeightedGraph g = new WeightedAdjListsGraph(n); for (int i = 0; i < m; i++) { int u = s.nextInt(); int v = s.nextInt(); double weight = s.nextDouble(); g.addEdge(u, v, weight); } return g; } public static void main(String[] args) throws FileNotFoundException { Scanner s = new Scanner(new File("weightedGraph.txt")); WeightedGraph g = readGraph(s); s.close(); LongestWeightedPath p = new LongestWeightedPath(g, 0, 3); System.out.println("Najdlhsia cesta: " + Arrays.toString(p.longestPath())); } } /* Priklad vstupu (ulozit do weightedGraph.txt) 5 7 0 1 1 0 2 10 0 3 1 1 2 1 2 3 10 2 4 1 3 4 1 */
Cvičenia 23
- Nižšie je verzia prehľadávania do hĺbky, ktorá do poľa whenVisited čísluje vrcholy v poradí, v akom boli navštívené. Odsimulujte algoritmus na grafe nakreslenom vpravo a zistite, v akom poradí budú vrcholy navštívené, ak začneme prehľadávať vo vrchole 0. Predpokladajte, že adjVertices vracia susedov v poradí podľa čísla vrcholu.
- Do reprezentácie grafu pomocou zoznamov susedov z prednášky pridajte metódu addVertex, ktorá pridá do grafu nový vrchol.
- Máme danú šachovnicu n x m, na ktorej sú niektoré políčka obsadené figúrkami. Na políčku (i,j) stojí kôň. Na ktoré políčka šachovnice vie preskákať, pričom môže použiť aj viacero ťahov, ale môže chodiť iba po prázdnych políčkach?
- Použite prehľadávanie do hĺbky na grafe, v ktorom sú vrcholy jednotlivé políčka a hrany spájajú dvojice voľných políčok, medzi ktorými môže kôň skočiť. Môžete vytvoriť graf v niektorej reprezentácii, alebo to riešiť priamo v matici visited rozmerov n x m.
- Šachový kôň môže z pozície (i,j) skočiť na jednu z pozícií (i+2,j+1), (i+2,j-1), (i-2,j+1), (i-2,j-1), (i+1,j+2), (i+1,j-2), (i-1,j+2), (i-1,j-2)
- Reprezentáciu grafu pomocou matice susednosti z prednášky zmeňte tak, aby adjVertices vrátil objekt vašej vlastnej triedy implementujúcej Iterable<Integer>. Tá by zase vedela vrátiť váš vlastný iterátor triedy implementujúcej Iterator<Integer>. Tento objekt by si pamätal iba číslo vrchola, cez ktorého susedov iterujeme a pozíciu v príslušnom riadku matice susednosti. Pri každom volaní next hľadá ďalšiu hodnotu true v tomto riadku. Iterátoru tiež správne implementujte metódu remove, aby mazala hranu (obidve jej kópie). Kostru nájdete nižšie.
Prehľadávanie s číslovaním pre prvý príklad
/** Trieda obsahujuca prehladavanie do hlbky s cislovanim vrcholov */ class Search { /** Pre kazdy vrchol poradove cislo kedy bol navstiveny */ private int[] whenVisited; /** Pocet navstivenych vrcholov */ private int numVisited; /** samotny graf */ private Graph g; /** Konstruktor, ktory dostane graf a prehladava do hlbky * so zaciatkom vo vrchole start */ public Search(Graph g, int start) { this.g = g; // uloz graf numVisited = 0; // inicializuj pocet navstivenych vrcholov int n = g.getNumberOfVertices(); // pocet vrcholov grafu whenVisited = new int[n]; // vytvor a inicializuj pole for (int i = 0; i < n; i++) { whenVisited[i] = -1; } search(start); // prehladavaj rekurzive } /** Pomocna rekurzivna metoda pouzivana v konstruktore * na rekurzivne prehladavanie. */ private void search(int vertex) { whenVisited[vertex] = numVisited; // ocislujeme vrchol vertex numVisited++; for(int neighbor : g.adjVertices(vertex)) { // prejdi cez susedov if (whenVisited[neighbor] == -1) { search(neighbor); // navstivime ho rekurzivne } } } @Override public String toString() { String res = ""; for(int i=0; i<g.getNumberOfVertices(); i++) { res = res + i + ": " + whenVisited[i] + "\n"; } return res; } }
Kostra pre implementáciu vlastného iterátora
/** Trieda reprezentujúca neorientovaný graf pomocou matice susednosti. */ class AdjMatrixGraph implements Graph { /** Matica susednosti */ private boolean[][] matrix; /** Počet hrán grafu */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public AdjMatrixGraph(int numVertices) { matrix = new boolean[numVertices][numVertices]; for (int i = 0; i < numVertices; i++) { for (int j = 0; j < numVertices; j++) { matrix[i][j] = false; } } numEdges = 0; } @Override public int getNumberOfVertices() { return matrix.length; } @Override public int getNumberOfEdges() { return numEdges; } @Override public boolean addEdge(int from, int to) { if (existsEdge(from, to)) { // nepridava existujuce hrany return false; } matrix[from][to] = true; //prida hranu v oboch smeroch matrix[to][from] = true; numEdges++; return true; } @Override public boolean existsEdge(int from, int to) { return matrix[from][to]; } @Override public Iterable<Integer> adjVertices(int vertex) { // vytvori pomocne pole a, // vlozi do neho vsetkych susedov a vrati jeho iterator return new AdjIterable(vertex); } /** Vnútorná trieda pre Iterable cez susedov. */ private class AdjIterable implements Iterable<Integer> { int vertex; public AdjIterable(int vertex_) { vertex = vertex_; } @Override public Iterator<Integer> iterator() { return new AdjIterator(vertex); } } /** Vnútorná trieda pre Iterator cez susedov. * Vie pristupovať k premenným a metódam grafu, napr getNumberOfVertices(), matrix atd. */ private class AdjIterator implements Iterator<Integer> { int vertex; // vrchol, ktorého susedov hľadáme int pos = -1; // posledný sused doteraz vrátený pomocou next private AdjIterator(int vertex_) { vertex = vertex_; } @Override public boolean hasNext() { // VAS KOD TU } @Override public Integer next() { // VAS KOD TU } @Override public void remove() { // VAS KOD TU } } } public class Prog { /** Zo scannera s načíta graf vo formáte: počet vrcholov, * počet hrán a zoznam hrán zadaných koncami vrcholov. * Ak je matrix true, uloží ho ako AdjMatrixGraph, * inak ako AdjListsGraph. */ static Graph readGraph(Scanner s, boolean matrix) { int n = s.nextInt(); int m = s.nextInt(); Graph g; if (matrix) { g = new AdjMatrixGraph(n); } else { g = new AdjListsGraph(n); } for (int i = 0; i < m; i++) { int u = s.nextInt(); int v = s.nextInt(); g.addEdge(u, v); } return g; } /** Graph g vypíše do výstupného streamu */ static void printGraph(Graph g, PrintStream out) { int n = g.getNumberOfVertices(); out.println(n + " " + g.getNumberOfEdges()); for (int u = 0; u < n; u++) { for (int v : g.adjVertices(u)) { if (u < v) { // kvoli symetrii v neorientovaných grafoch out.println(u + " " + v); } } } } public static void main(String[] args) throws FileNotFoundException { Scanner s; Graph g; PrintStream out; // nacitame graf ako zoznamy susedov s = new Scanner(new File("graph-in.txt")); g = readGraph(s, true); s.close(); // zmazeme vsetky hrany for (int u = 0; u < g.getNumberOfVertices(); u++) { Iterator<Integer> it = g.adjVertices(u).iterator(); while (it.hasNext()) { it.next(); it.remove(); } } System.out.println("Number of edges: " + g.getNumberOfEdges()); // malo by byt 0 } }
Prednáška 35
Oznamy
Budúci týždeň
- V pondelok 9.5. cvičenia a prednáška
- Prednáška: zhrnutie, príprava na test a skúšku, knižnica na skúšku
- Do budúceho pondelka si v prípade záujmu vyberte tému nepovinného projektu.
- Do stredy 11.5. DÚ16
Posledný týždeň semestra
- Pondelok 16.5. cvičenia a prednáška
- Prednáška: objekty v C/C++ (nebude na skúške)
- Pondelok 16.5. o 14:00 v F2 záverečný test
Predbežné termíny skúšok (dajte nám čím skôr vedieť, ak je konflikt s nejakou inou dôležitou povinnosťou)
- štvrtok 26.5. riadny termín
- štvrtok 9.6. riadny termín
- streda 22.6. prvý opravný termín
- v týždni od 27.6. druhý opravný termín
Nepovinný projekt
- streda 25.5. ráno termín odovzdania projektu, poobede predvádzanie
Opakovanie backtracky na grafe
- Na minulej prednáške boli príklady na prehľadávanie grafu s návratom
- Hľadanie ciest dĺžky k
- Hľadanie najdlhšej cesty v neohodnotenom grafe
- Hľadanie najdlhšej cesty v ohodnotenom grafe
- Pri riešení sme postupne vytvárali cestu pridávaním potenciálnych vrcholov do nej a následným kontrolovaním situácie (hotové riešenie, slepá vetva)
Hľadanie maximálnej kliky
- Pozrime sa teraz na iný typ problému, nehľadáme cestu, ale množinu vrcholov
- Klika je taká množina vrcholov, v ktorej sú každé dva vrcholy spojené hranou
- Maximálna klika je klika s najväčším počtom vrcholov v danom grafe
Graf G=(V,E), kde V={0,...,4} a E={{0,1},{0,2},{0,3},{1,2},{2,3},{2,4},{3,4}} obsahuje niekoľko klík veľkosti 3, ale žiadnu kliku veľkosti 4
Maximalna klika: [0, 1, 2]
Po pridaní hrany {0,4} dostávame kliku veľkosti 4:
Maximalna klika: [0, 2, 3, 4]
Jednoduchšia verzia
Hľadanie maximálnej kliky:
- Prehľadávame všetky podmnožiny vrcholov
- Rekurzívne skúšame každý vrchol najprv pridať do podmnožiny, potom vynechať
- Keď prejdeme cez všetky vrcholy, skontrolujeme, že aktuálna podmnožina je klika
- Aktuálnu podmnožinu aj najväčšiu nájdenú kliku ukladáme do LinkedList-u.
/** Trieda, ktora umoznuje najst maximalnu kliku v danom grafe. */ class MaximumClique { /** samotny graf */ private Graph g; /** zoznam vrcholov v najvacsej doteraz najdenej klike */ private LinkedList<Integer> maxClique; /** zoznam vrcholov v aktualnej podmnozine */ private LinkedList<Integer> vertexSet; /** Konstruktor, ktory dostane graf a spusti rekurzivne vyhladavanie */ public MaximumClique(Graph g) { this.g = g; // uloz vstupny graf // vytvor dve prazdne mnoziny vrcholov vertexSet = new LinkedList<Integer>(); maxClique = new LinkedList<Integer>(); search(0); // zavolaj rekurziu } /** Hlavna rekurzivna metoda volana z konstruktora. * Metoda skusi pouzit aj vynechat vrchol vertex * potom skusa vsetky moznosti pre vrcholy vertex+1...n-1. */ private void search(int vertex) { // ak uz sme vycerpali vsetky vrcholy, nie je co skusat dalej if (vertex == g.getNumberOfVertices()) { // skontroluj, ci mame kliku a ak ano, porovnaj s najlepsou doteraz if(isClique(g, vertexSet) && vertexSet.size() > maxClique.size()) { // konstruktor LinkedListu moze vytvorit kopiu inej Collection maxClique = new LinkedList<Integer>(vertexSet); } return; } // pridaj vrchol vertex do mnoziny a zavolaj rekurziu vertexSet.addLast(vertex); search(vertex + 1); // odober vertex z mnoziny a zavolaj rekurziu na ostatne vrcholy vertexSet.removeLast(); search(vertex + 1); } /** pomocna metoda, ktora overi, ci je vertexSet klika. */ private static boolean isClique(Graph g, Collection<Integer> vertexSet) { // iterujeme cez vsetky dvojice v mnozine for(int u : vertexSet) { for(int v : vertexSet) { if (u!=v && !g.existsEdge(u, v)) { // over hranu return false; // hrana nie je } } } return true; // vsetky hrany najdene } /** vrati maximalnu kliku najdenu v grafe g */ public List<Integer> maxClique() { // vrat nemenitelnu kopiu nasej najlepsej kliky return Collections.unmodifiableList(maxClique); } }
Príklad použitia
MaximumClique c = new MaximumClique(g); System.out.println("Maximalna klika: " + c.maxClique().toString());
Rýchlejšia verzia
- Rekurzívne skúšame každý vrchol najprv pridať do kliky, potom vynechať
- Vrchol pridávame do kliky iba ak je spojený so všetkými vrcholmi, ktoré už sú v klike
- Vrchol v klike veľkosti k má stupeň (počet susedov) aspoň k-1
- Preto do kliky skúšame dať iba vrcholy, ktoré majú dosť veľký stupeň na to, aby mohli patriť do kliky väčšej ako zatiaľ najväčšia nájdená (to neznamená, že do takej kliky aj patria)
/** Trieda, ktora umoznuje najst maximalnu kliku v danom grafe. */ class MaximumClique { /** samotny graf */ private Graph g; /** zoznam vrcholov v najvacsej doteraz najdenej klike */ private LinkedList<Integer> maxClique; /** zoznam vrcholov v aktualnej klike */ private LinkedList<Integer> clique; /** Konstruktor, ktory dostane graf a spusti rekurzivne vyhladavanie */ public MaximumClique(Graph g) { this.g = g; // uloz vstupny graf // vytvor dve prazdne kliky clique = new LinkedList<Integer>(); maxClique = new LinkedList<Integer>(); search(0); // zavolaj rekurziu } /** Hlavna rekurzivna metoda volana z konstruktora. * Metoda skusi pouzit aj vynechat vrchol vertex * potom skusa vsetky moznosti pre vrcholy vertex+1...n-1. */ private void search(int vertex) { // ak aktualna klika je vacsia ako doterajsie maximum, uloz ju if (clique.size() > maxClique.size()) { // konstruktor LinkedListu moze vytvorit kopiu inej Collection maxClique = new LinkedList<Integer>(clique); } // ak uz sme vycerpali vsetky vrcholy, nie je co skusat dalej if (vertex == g.getNumberOfVertices()) { return; } // otestuj, ci sa vrchol vertex da pridat do kliky // a ci ma sancu byt vo vacsej klike ako doteraz najdena if (isConnected(g, vertex, clique) && degree(g, vertex) + 1 > maxClique.size()) { // ak ano, pridaj ho do kliky a zavolaj rekurziu clique.addLast(vertex); search(vertex + 1); // odober vertex x kliky clique.removeLast(); } // preskoc vertex a zavolaj rekurziu na ostatne vrcholy search(vertex + 1); } /** pomocna metoda, ktora overi, ci vrchol vertex je v grafe g * spojeny s kazdym z vrcholov v mnozine vertexSet. */ private static boolean isConnected(Graph g, int vertex, Collection<Integer> vertexSet) { // iterujeme cez mnozinu vertexSet for(int v : vertexSet) { if (!g.existsEdge(vertex, v)) { // over hranu return false; // hrana nie je } } return true; // vsetky hrany najdene } /** pomocna metoda, ktora zisti stupen vrchola vertex v grafe g */ private static int degree(Graph g, int vertex) { // iterujeme cez susedov vrchola, zvysujeme pocitadlo result int result = 0; for(int x : g.adjVertices(vertex)) { result++; } return result; } /** vrati maximalnu kliku najdenu v grafe g */ public List<Integer> maxClique() { // vrat nemenitelnu kopiu nasej najlepsej kliky return Collections.unmodifiableList(maxClique); } }
Cvičenia:
- Čo by program robil, ak by sme vynechali test na stupeň vrchola z rekurzívnej funkcie?
- Čo by program robil, ak by sme vynechali aj test isConnected?
- Čo program robí, ak ho spustíme na grafe bez hrán?
- Ktorá reprezentácia grafu je vhodnejšia pre tento algoritmus?
Orientované grafy
V orientovanom grafe má každá hrana smer (zobrazujeme ako šípku z jedného vrcholu do druhého)
- napríklad jednosmerné ulice, závislosti medzi úlohami
- v cestách a cykloch zväčša vyžadujeme, aby išli iba v smere šípky
Pre orientované grafy môžeme využiť existujúci interface Graph ako sme ho mali definovaný pri neorientovaných grafoch.
- Pri implementácii funkcie addEdge však vložíme hranu iba jedným smerom. Napr. pre hranu (0,1) vložíme 1 medzi susedov 0 ale nie 0 medzi susedov 1.
- Funkcia existsEdge nám potom pre parametre (0,1) odpovie true ale pre (1,0) odpovie false.
- Iterator adjIterator bude poskytovať iba vychádzajúce hrany
Prehľadávanie do hĺbky
Pri takto implementovanom grafe môžeme použiť prehľadávanie do hĺbky z minulých prednášok na zistenie existencie orientovanej cesty medzi dvoma vrcholmi.
- Treba si však uvedomiť, že podobne, ako existencia hrany (0,1) nehovorí nič o hrane (1,0) ani existencia cesty medzi dvomi vrcholmi nie je obojsmerná
- Keďže však máme k dispozícii iterátor cez výstupné hrany, vytvárame práve správne orientované cesty
void search(int vertex, boolean[] visited) { visited[vertex] = true; //prejdi cez vchadzajuce hrany for (int neighbor: g.adjVertices(vertex)){ // ak este sused nebol vobec navstiveny, navstiv do rekurzivne if (!visited[neighbor]) { search(neighbor,visited); // navstivime ho rekurzivne } } }
- Čo ak by sme chceli vypísať cestu z vrcholu u do vrcholu v?
- Viacnásobným použitím funkcie vieme získať zoznamy vrcholov, ktoré sú dosiahnuteľné z jednotlivých vrcholov.
Cvičenie:
- Zamyslite sa čo by robil algoritmus na hľadanie komponentov z prednášky 33 na orientovanom grafe - funguje? prečo?
Prehľadávanie do šírky a najkratšie cesty v neohodnotenom orientovanom grafe
Podobne ako prehľadávanie do hĺbky aj prehľadávanie do šírky je na orientovanom (neohodnotenom) grafe rovnako použiteľné.
- Získame najkratšie orientované cesty z vrchola do všetkých ostatných vrcholov
- Môžeme si hrany rozdeliť na stromové (ktoré objavili vrchol) a ostatné
Je orientovaný graf strom?
Orientovaný graf je strom práve vtedy, keď
- má práve jeden koreň, do ktorého nevchádza žiadna hrana
- z koreňa je možné dosiahnuť všetky ostatné vrcholy
- do každého vrcholu okrem koreňa vchádza práve jedna hrana
Ako by sme presne implementovali v našom interface Graph?
- napíšte metódu, ktorá do poľa pre každý vrchol orientovaného grafu spočíta, koľko hrán do neho vstupuje
Topologické triedenie, existencia cyklu
Motivačná úloha:
- Na úrade potrebujeme vybaviť niekoľko potvrdení. Ale číha tam na nás byrokracia: o niektorých dvojiciach potvrdení vieme, že na získanie potvrdenia B potrebujeme predložiť potvrdenie A.
- Úlohou je nájsť poradie (a zistiť, či také vôbec existuje) ako potvrdenia na úrade vybavovať.
- Úlohu reprezentujeme ako orientovaný graf, kde vrcholy sú potvrdenia a hrany závislosti medzi nimi.
Topologické usporiadanie orientovaného grafu je permutácia jeho vrcholov Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle v_1,v_2,\dots v_n} taká, že pre každú hranu Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle (v_i,v_j)} platí, že i<j
- t.j. všetky hrany idú v permutácii zľava doprava
- orientovaný graf môže mať aj viac ako jedno topologické usporiadanie
- Koľko najviac topologických usporiadaní môže mať orientovaný graf s n vrcholmi? Pre aký graf sa to stane?
- Môže sa však stať, že graf nemá žiadne topologické usporiadanie
- To sa stane práve vtedy, ak je v grafe orientovaný cyklus
- Zjavne ak je v grafe orientovaný cyklus, topologické usporiadanie neexistuje, lebo v topologickom usporiadaní idú hrany zľava doprava a cyklus sa nemá ako vrátiť späť
- Skúste si dokázať aj opačnú implikáciu
- Graf bez cyklu voláme acyklický
Samotné topologické triedenie bude pracovať nasledovne:
- ak máme vrchol, do ktorého nevchádza žiadna hrana, môžeme ho vypísať (potvrdenie, ktoré nemá žiadne závislosti)
- z tohto vrcholu vychádzajú hrany, môžeme ich odteraz ignorovať (splnené závislosti)
- pre každý vrchol si pamätáme počet zatiaľ nesplnených závislostí
- na začiatku to bude počet hrán vchádzajúcich do vrchola
- keď vypíšeme vrchol v, prejdeme všetky hrany z neho vychádzajúce a vrcholom na druhom konci znížime počet nesplnených závislostí
- udržujeme si tiež množinu vrcholov, ktoré už nemajú nesplnené závislosti a v každom kroku jeden vrchol z nej vyberieme a vypíšeme
/* Statická metóda, ktorá dostane orientovaný acyklický graf * a vráti zoznam jeho vrcholov * v topologickom usporiadaní */ public static ArrayList<Integer> TopologicalSort(Graph g) { int n = g.getNumberOfVertices(); // pocet vrcholov grafu // inicializuj pocet nesplnenych zavislosti, potom // prejdi vsetky hrany a zvysuj int[] numberOfPrerequisites = new int[n]; for (int vertex = 0; vertex < n; vertex++) { numberOfPrerequisites[vertex] = 0; } for (int vertex = 0; vertex < n; vertex++) { for (int neighbor : g.adjVertices(vertex)) { numberOfPrerequisites[neighbor]++; } } // vsetky vrcholy bez zavislosti pridaj do mnoziny ready LinkedList<Integer> ready = new LinkedList<Integer>(); for (int vertex = 0; vertex < n; vertex++) { if (numberOfPrerequisites[vertex] == 0) { ready.add(vertex); } } // inicializuj vysledok ArrayList<Integer> order = new ArrayList<Integer>(); // hlavny cyklus - vyberaj vrchol z mnoziny ready a pridavaj do order while (!ready.isEmpty()) { int vertex = ready.remove(); order.add(vertex); // pre susedov vypisaneho vrchola zniz pocet zavislosti for (int neighbor : g.adjVertices(vertex)) { numberOfPrerequisites[neighbor]--; // ak to bola posledna zavislost, vrchol je uz vypisatelny if (numberOfPrerequisites[neighbor] == 0) { ready.add(neighbor); } } } return order; }
Čo spraví tento program, ak mu dáme vstupný graf, v ktorom je orientovaný cyklus?
Ak sa nám do poľa order podarilo dať všetky vrcholy, máme topologické usporiadanie, graf je teda acyklický
- ak máme v poli order menej ako n vrcholov, každý vrchol má aspoň jednu nesplnenú závislosť
- topologické triedenie teda v tomto prípade nemôže existovať (žiaden vrchol nemôže ísť prvý)
- graf má teda cyklus
Cvičenie:
- Upravte program tak, aby v prípade, že graf má orientovaný cyklus, funkcia TopologicalSort vrátila null
- Napíšte program, ktorý v prípade, že graf nie je acyklický, v ňom nájde orientovaný cyklus.
Existencia cyklu a topologické triedenie pomocou prehľadávania do hĺbky
Trochu iný prístup založený na malej modifikácii prehľadávania do hĺbky, ale ťažšie vidieť, že funguje (dobré precvičenie)
Do prehľadávania do hĺbky pridáme pole finished
- keď začneme rekurziu pre vrchol v, nastavíme visited[v]=true (ako predtým)
- keď končíme rekurziu pre vrchol v, nastavíme finished[v]=true
V danom bode behu algoritmu máme vrcholy troch typov:
- ešte nenavštívené, visited[v] aj finished[v] je false
- už ukončené, visited[v] aj finished[v] je true
- rozrobené, visited[v] je true, ale finished[v] je false
Rozrobené vrcholy sú všetky na zásobníku a sú spojené orientovanou cestou
Topologické triedenie:
- Vždy keď nastavíme finished[v]=true, pridáme v do poľa order
- Po prehľadaní celého grafu otočíme poradie poľa order
Predstavme si, že kontrolujeme hrany vychádzajúce z vrchola v, ktorý je teste pred dokončením, t.j. po tom, ako sme pozreli jeho susedov, ale predtým ako sme ho dali do poľa order. Kam môžu ísť?
- Do ešte nenavštíveného vrchola u. To sa nestane, lebo by sme predtým zavolali rekurziu pre u.
- Do ukončeného vrchola u. Ten už je v poli order, ale v tam ešte nie je. Hrana (u,v) teda pôjde vo výsledku zľava doprava.
- Do rozrobeného vrchola u. Toto ale znamená existenciu cyklu v grafe, takže topologické triedenie neexistuje.
class TopologicalSort { /** Samotny graf */ private Graph g; /** Zoznam vrcholov v topologickom usporiadani */ private ArrayList<Integer> order; /** Indikator, ci je graf acyklicky */ private boolean acyclic; /** Pole indikujuce, ci sme uz vrchol v prehladavani navstivili */ private boolean[] visited; /** Pole indikujuce, ci sme uz ukoncili prehladavanie vrchola * a vsetkych jeho nasledovnikov */ private boolean[] finished; /** Konstruktor, ktory dostane graf a prehladavanim do hlbky * testuje acyklickost grafu a hlada topologicke usporiadanie */ public TopologicalSort(Graph g) { this.g = g; // uloz graf int n = g.getNumberOfVertices(); // pocet vrcholov grafu order = new ArrayList<Integer>(); // inicializuj vysledok acyclic = true; // zatial sme nevideli cyklus visited = new boolean[n]; finished = new boolean[n]; for (int i = 0; i < n; i++) { visited[i] = false; finished[i] = false; } // prechadzaj cez vrchol a ak najdes nevyfarbeny, // spusti prehladavanie for (int i = 0; i < n; i++) { if (!visited[i]) { search(i); } } Collections.reverse(order); //prevratime poradie vrcholov } /** Pomocna rekurzivna metoda pouzivana v konstruktore * na vyfarbenie vsetkych nasledovnikov vrchola vertex */ private void search(int vertex) { visited[vertex] = true; // uz sme ho navstivili //prejdi cez vychadzajuce hrany for (int neighbor: g.adjVertices(vertex)){ // ak uz sme suseda navstivili, ale este nie je // ukonceny, mame cyklus if (visited[neighbor] && !finished[neighbor]) { acyclic = false; } // ak este sused nebol vobec navstiveny, navstiv do rekurzivne if (!visited[neighbor]) { search(neighbor); // navstivime ho rekurzivne } } // ukoncili sme prehladavanie aktualneho vrcholu // poznac ako ukonceny a pridaj ho do zoznamu finished[vertex] = true; order.add(vertex); } ... }
Zdrojový kód programu, topologické triedenie 1
Pomocou počítania závislostí
package prog; import java.io.*; import java.util.ArrayList; import java.util.LinkedList; import java.util.Scanner; /** Interface reprezentujúci graf s n vrcholmi očíslovanými 0..n-1. */ interface Graph { /** Vráti počet vrcholov grafu n. */ int getNumberOfVertices(); /** Vráti počet hrán grafu. */ int getNumberOfEdges(); /** Do grafu pridá hranu z vrcholu from do vrcholu to, * vráti true ak sa ju podarilo pridať. */ boolean addEdge(int from, int to); /** Vráti true, ak existuje hrana z vrcholu from * do vrcholu to. */ boolean existsEdge(int from, int to); /** Vráti iterovateľnú skupinu susedov vrchola vertex. * V prípade orientovaného grafu vracia iba hrany vychádzajúce z vrchola. * Metóda remove iterátora nemusí byť podporovaná. */ Iterable<Integer> adjVertices(int vertex); } /** Trieda reprezentujúca orientovaný graf pomocou * zoznamov susedov uložených v poli dynamickej dĺžky (ArrayList). */ class AdjListsGraph implements Graph { /** Zoznam susedov pre každý vrchol */ private ArrayList<ArrayList<Integer>> adjLists; /** Počet hrán v grafe */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public AdjListsGraph(int numVertices) { adjLists = new ArrayList<ArrayList<Integer>>(numVertices); for (int i = 0; i < numVertices; i++) { adjLists.add(new ArrayList<Integer>()); } numEdges = 0; } @Override public int getNumberOfVertices() { return adjLists.size(); } @Override public int getNumberOfEdges() { return numEdges; } @Override public boolean addEdge(int from, int to) { adjLists.get(from).add(to); // pridaj hranu v jednom smere numEdges++; return true; } @Override public boolean existsEdge(int from, int to) { return adjLists.get(from).contains(to); // pozri do zoznamu susedov pre from } @Override public Iterable<Integer> adjVertices(int vertex) { return adjLists.get(vertex); // vrati ArrayList } } /** Trieda obsahujuca topologicke usporiadanie vrcholov orientovaneho grafu */ class TopologicalSort { /** samotny graf */ private Graph g; /** Zoznam vrcholov v topologickom usporiadani */ private ArrayList<Integer> order; /** Indikator, ci je graf acyklicky */ private boolean acyclic; /** Konstruktor, ktory dostane graf a prehladavanim do hlbky * testuje acyklickost grafu a hlada topologicke usporiadanie */ public TopologicalSort(Graph g) { this.g = g; // uloz graf int n = g.getNumberOfVertices(); // pocet vrcholov grafu // inicializuj pocet nesplnenych zavislosti, potom // prejdi vsetky hrany a zvysuj int[] numberOfPrerequisites = new int[n]; for (int vertex = 0; vertex < n; vertex++) { numberOfPrerequisites[vertex] = 0; } for (int vertex = 0; vertex < n; vertex++) { for (int neighbor: g.adjVertices(vertex)){ numberOfPrerequisites[neighbor]++; } } // vsetky vrcholy bez zavislosti pridaj do mnoziny ready LinkedList<Integer> ready = new LinkedList<Integer>(); for (int vertex = 0; vertex < n; vertex++) { if(numberOfPrerequisites[vertex] == 0) { ready.add(vertex); } } order = new ArrayList<Integer>(); // inicializuj vysledok // hlavny cyklus - vyberaj vrchol z mnoziny ready a pridavaj do order while (!ready.isEmpty()){ int vertex = ready.remove(); order.add(vertex); // pre susedov vypisaneho vrchola potrebujem znizit pocet zavislosti for (int neighbor : g.adjVertices(vertex)){ numberOfPrerequisites[neighbor]--; // ak to bola posledna zavislost, vrchol je uz vypisatelny if(numberOfPrerequisites[neighbor]==0) { ready.add(neighbor); } } } acyclic = (order.size()==n); } /** vratil, ci je vstupny graf acyklicky */ public boolean isAcyclic() { return acyclic; } /** ak je graf acyklicky, vrati topologicke usporiadanie vrcholov */ public ArrayList<Integer> order() { if (!acyclic) { return null; } return new ArrayList<Integer>(order); } } public class Prog { /** Zo scannera s načíta graf vo formáte: počet vrcholov, * počet hrán a zoznam hrán zadaných koncami vrcholov. * Uloží ho ako AdjMatrixGraph. */ static Graph readGraph(Scanner s) { int n = s.nextInt(); int m = s.nextInt(); Graph g = new AdjListsGraph(n); for (int i = 0; i < m; i++) { int u = s.nextInt(); int v = s.nextInt(); g.addEdge(u, v); } return g; } public static void main(String[] args) { Scanner s = new Scanner(System.in); Graph g = readGraph(s); s.close(); TopologicalSort sort = new TopologicalSort(g); if (sort.isAcyclic()) { System.out.println("Topologicke usporiadanie: " + sort.order()); } else { System.out.println("Graf ma cyklus"); } } } /** Priklad vstupu bez cyklu: 4 4 1 0 2 0 2 1 3 1 * s cyklom: 4 4 1 0 0 2 2 1 3 1 */
Zdrojový kód programu, topologické triedenie 2
Pomocou prehľadávania do hĺbky
package prog; import java.io.*; import java.util.ArrayList; import java.util.LinkedList; import java.util.Scanner; import java.util.Collections; /** Interface reprezentujúci graf s n vrcholmi očíslovanými 0..n-1. */ interface Graph { /** Vráti počet vrcholov grafu n. */ int getNumberOfVertices(); /** Vráti počet hrán grafu. */ int getNumberOfEdges(); /** Do grafu pridá hranu z vrcholu from do vrcholu to, * vráti true ak sa ju podarilo pridať. */ boolean addEdge(int from, int to); /** Vráti true, ak existuje hrana z vrcholu from * do vrcholu to. */ boolean existsEdge(int from, int to); /** Vráti iterovateľnú skupinu susedov vrchola vertex. * V prípade orientovaného grafu vracia iba hrany vychádzajúce z vrchola. * Metóda remove iterátora nemusí byť podporovaná. */ Iterable<Integer> adjVertices(int vertex); } /** Trieda reprezentujúca orientovaný graf pomocou * zoznamov susedov uložených v poli dynamickej dĺžky (ArrayList). */ class AdjListsGraph implements Graph { /** Zoznam susedov pre každý vrchol */ private ArrayList<ArrayList<Integer>> adjLists; /** Počet hrán v grafe */ private int numEdges; /** Konštruktor dostane počet vrcholov grafu */ public AdjListsGraph(int numVertices) { adjLists = new ArrayList<ArrayList<Integer>>(numVertices); for (int i = 0; i < numVertices; i++) { adjLists.add(new ArrayList<Integer>()); } numEdges = 0; } @Override public int getNumberOfVertices() { return adjLists.size(); } @Override public int getNumberOfEdges() { return numEdges; } @Override public boolean addEdge(int from, int to) { adjLists.get(from).add(to); // pridaj hranu v jednom smere numEdges++; return true; } @Override public boolean existsEdge(int from, int to) { return adjLists.get(from).contains(to); // pozri do zoznamu susedov pre from } @Override public Iterable<Integer> adjVertices(int vertex) { return adjLists.get(vertex); // vrati ArrayList } } /** Trieda obsahujuca topologicke usporiadanie vrcholov orientovaneho grafu */ class TopologicalSort { /** samotny graf */ private Graph g; /** Zoznam vrcholov v topologickom usporiadani */ private ArrayList<Integer> order; /** Indikator, ci je graf acyklicky */ private boolean acyclic; /** Pole indikujuce, ci sme uz vrchol v prehladavani navstivili */ private boolean[] visited; /** Pole indikujuce, ci sme uz ukoncili prehladavanie vrchola * a vsetkych jeho nasledovnikov */ private boolean[] finished; /** Konstruktor, ktory dostane graf a prehladavanim do hlbky * testuje acyklickost grafu a hlada topologicke usporiadanie */ public TopologicalSort(Graph g) { this.g = g; // uloz graf int n = g.getNumberOfVertices(); // pocet vrcholov grafu order = new ArrayList<Integer>(); // inicializuj vysledok acyclic = true; // zatial sme nevideli cyklus visited = new boolean[n]; finished = new boolean[n]; for (int i = 0; i < n; i++) { visited[i] = false; finished[i] = false; } // prechadzaj cez vrchol a ak najdes nevyfarbeny, // spusti prehladavanie for (int i = 0; i < n; i++) { if (!visited[i]) { search(i); } } Collections.reverse(order); //prevratime poradie vrcholov } /** Pomocna rekurzivna metoda pouzivana v konstruktore * na vyfarbenie vsetkych nasledovnikov vrchola vertex */ private void search(int vertex) { visited[vertex] = true; // uz sme ho navstivili //prejdi cez vychadzajuce hrany for (int neighbor: g.adjVertices(vertex)){ // ak uz sme suseda navstivili, ale este nie je // ukonceny, mame cyklus if (visited[neighbor] && !finished[neighbor]) { acyclic = false; } // ak este sused nebol vobec navstiveny, navstiv do rekurzivne if (!visited[neighbor]) { search(neighbor); // navstivime ho rekurzivne } } // ukoncili sme prehladavanie aktualneho vrcholu // poznac ako ukonceny a pridaj ho do zoznamu finished[vertex] = true; order.add(vertex); } /** vratil, ci je vstupny graf acyklicky */ public boolean isAcyclic() { return acyclic; } /** ak je graf acyklicky, vrati topologicke usporiadanie vrcholov */ public ArrayList<Integer> order() { if (!acyclic) { return null; } return new ArrayList<Integer>(order); } } public class Prog { /** Zo scannera s načíta graf vo formáte: počet vrcholov, * počet hrán a zoznam hrán zadaných koncami vrcholov. * Uloží ho ako AdjMatrixGraph. */ static Graph readGraph(Scanner s) { int n = s.nextInt(); int m = s.nextInt(); Graph g = new AdjListsGraph(n); for (int i = 0; i < m; i++) { int u = s.nextInt(); int v = s.nextInt(); g.addEdge(u, v); } return g; } public static void main(String[] args) { Scanner s = new Scanner(System.in); Graph g = readGraph(s); s.close(); TopologicalSort sort = new TopologicalSort(g); if (sort.isAcyclic()) { System.out.println("Topologicke usporiadanie: " + sort.order()); } else { System.out.println("Graf ma cyklus"); } } } /** Priklad vstupu bez cyklu: 4 4 1 0 2 0 2 1 3 1 * s cyklom: 4 4 1 0 0 2 2 1 3 1 */
Cvičenia 24
Cieľom tohto cvičenia je precvičiť si prehľadávanie grafu do šírky a úlohy na backtracking na grafoch.
- Prehľadávanie do šírky nájdete v programe pre prednášku 33, trieda ShortestPaths
- Program na hľadanie ciest dĺžky k nájdete v programe pre prednášku 34, trieda FixedLengthPaths, resp. trieda LongestPath pre najdlhšiu cestu
Úloha A: Odsimulujte prácu prehľadávania do šírky na grafe vpravo ak začneme prehľadávať vo vrchole 0. Predpokladajte, že adjVertices vracia susedov v poradí podľa čísla vrcholu. Uveďte, v akom poradí boli vrcholy vložené do queue a vypíšte výsledné polia dist a prev.
Úloha B: Program na hľadanie najdlhšej cesty z u do v v neohodnotenom grafe zmeňte na hľadanie najdlhšieho cyklu, ktorý začína a končí v danom vrchole v
- Cyklus je postupnosť vrcholov Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle v_0,v_1,...,v_k} taká, že každé dva za sebou idúce vrcholy Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle v_i} a Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle v_{i+1}} sú spojené hranou, Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle v_0=v_k} a žiaden iný vrchol sa v postupnosti neopakuje.
Úloha C: Vráťme sa k príkladu s koňom, ale namiesto prehľadávania do hĺbky použite prehľadávanie do šírky
- Máme danú šachovnicu n x m, na ktorej sú niektoré políčka obsadené figúrkami. Na políčku (i,j) stojí kôň, ktorý sa pohybuje obvyklým spôsobom, ale môže chodiť iba po prázdnych políčkach.
- Pre každé prázdne políčko v šachovnici spočítajte, na aký najmenší počet ťahov na neho vie kôň doskákať. Ak naňho vôbec nevie doskákať, vypíšte -1.
- Použite prehľadávanie do šírky na grafe, v ktorom sú vrcholy jednotlivé políčka a hrany spájajú dvojice voľných políčok, medzi ktorými môže kôň skočiť. Môžete vytvoriť graf v niektorej reprezentácii, alebo to riešiť priamo v matici dist n x m.
- Šachový kôň môže z pozície (i,j) skočiť na jednu z pozícií (i+2,j+1), (i+2,j-1), (i-2,j+1), (i-2,j-1), (i+1,j+2), (i+1,j-2), (i-1,j+2), (i-1,j-2)
Úloha D: Program na hľadanie všetkých ciest dĺžky k z u do v v neohodnotenom grafe zmeňte tak, aby si predpočítal najkratšiu cestu z každého vrcholu do cieľového vrcholu (prehľadávaním do šírky z v) a potom nepokračoval ďalej v prehľadávaní z daného vrchola x, ak už nám v ceste zostáva len nejakých q hrán a najkratšia cesta z x do cieľového vrchola v má dĺžku viac ako q.
Prednáška 36
Oznamy
Tento týždeň
- Prednáška: zhrnutie, príprava na test a skúšku, knižnica na skúšku
- Do dnes 22:00 si v prípade záujmu vyberte tému nepovinného projektu.
- Do stredy 11.5. DÚ16
Budúci týždeň (posledný týždeň semestra)
- Pondelok 16.5. cvičenia a prednáška
- Prednáška: objekty v C/C++ (nebude na skúške)
- Pondelok 16.5. o 14:00 v F2 záverečný test
Termíny skúšok (už sú v AIS)
- štvrtok 26.5. riadny termín (môžete využiť aj keď potrebujete opravovať test)
- štvrtok 9.6. riadny termín (alebo 1. opravný)
- streda 22.6. prvý opravný termín (alebo 2. opravný)
- v týždni od 27.6. druhý opravný termín
Nepovinný projekt
- streda 25.5. ráno termín odovzdania projektu, poobede predvádzanie
Opravný test
- pravdepodobne 2. týždeň skúškového
Iné
- Do pondelka 21.5. by sa mali na testovači objaviť všetky body z rozcvičiek a DÚ. Skontrolujte si ich a reklamácie nám dajte vedieť najneskôr deň pred skúškou.
- Ozvite sa aj ak máte ospravedlnenie na viac ako jednu zmeškanú rozcvičku
- Nezabudnite hlasovať v študentskej ankete
Zhrnutie
Čo by ste mali po dvoch semestroch vedieť
- Základy jazykov C/C++, Java: cykly, podmienky, premenné, funkcie, primitívne typy, polia, alokovanie pamäte, reťazce, súbory
- Základy OOP, triedy, dedenie, polymorfizmus, výnimky, generické programovanie
- Základy tvorby GUI v JavaFX
- Dátové štruktúry: spájaný zoznam, zásobník a rad, binárne stromy a ich využitie (vyhľadávacie, lexikografické, aritmetické, rozhodovacie,...), hešovacie tabuľky
- Základné algoritmy: triedenia, binárne vyhľadávanie, prehľadávanie grafov a stromov, prehľadávanie s návratom
- Vymyslieť jednoduchý algoritmus, vedieť ho napísať a odladiť, porozumieť hotovým programom
Nadväzujúce predmety
- Algoritmy a dátové štruktúry (2/Z) a Tvorba efektívnych algoritmov (2/L): viac algoritmov a dátových štruktúr, časová zložitosť
- Programovanie (3) (2/Z): viac programovania v Jave, návrhové vzory
- Ročníkový projekt (1) a (2): píšete väčší program na tému podľa vlastného výberu
- Rýchlostné programovanie: riešenie úloh z programátorských súťaží, precvičenie programovania, algoritmov, hľadania chýb
Informácie ku skúške
- Potrebné informácie a cvičné príklady nájdete na stránke #Letný semester, test a skúška
- Na skúške budete dopisovať do hotového programu #GraphGUI
Cvičenia 25
Cieľom dnešného cvičenia je precvičiť si topologické triedenie a prehľadávanie s návratom na grafoch z prednášky 35.
Úloha A: Nájdite všetky topologické usporiadania orientovaného grafu na obrázku vpravo.
Úloha B: Modifikujte program na hľadanie maximálnej kliky (trieda MaximumClique) z prednášky tak, aby vypísal všetky maximálne kliky. Môžete začať buď z jednoduchšej alebo z rýchlejšej verzie programu. Namiesto jedného ArrayListu maxClique si spravte ArrayList ArrayListov, do ktorého budete ukladať všetky doteraz nájdené riešenia maximálnej veľkosti. Ak nájdete väčšiu kliku, ArrayList vyprázdnite a uložte tam iba najnovšie nájdenú. Pozor, ak používate rýchlejšiu verziu programu, treba tiež upraviť alebo zmazať podmienku so stupňom vrchola.
Úloha C: Napíšte program, ktorý pomocou prehľadávania s návratom nájde najmenšiu dominujúcu množinu v grafe.
- Dominujúca množina je taká množina vrcholov X, že každý vrchol grafu je buď v X, alebo susedí s nejakým vrcholom v X.
- Napríklad hrany sú rovné chodby a vrcholy križovatky. Strážnik stojaci vo vrchole má pod kontrolou túto križovatku aj všetky s ňou susedné. Chceme s použitím čo najmešieho počtu strážnikov mať pod kontrolou všetky vrcholy grafu.
Algoritmus:
- V každom rekurzívnom volaní vyskúšajte pre jeden vrchol dve možnosti: patrí do dominujúcej množiny alebo nepatrí. Potom rekurzívne zavolajte prehľadávanie pre všetky možnosti ďalších vrcholov.
- Ak ste už spravili rozhodnutie pre každý vrchol, skontrolujte, či je výsledok naozaj dominujúca množina a porovnajte veľkosť s najlepšou zatiaľ nájdenou.
Viete kvôli zrýchleniu orezať nejaké neperspektívne vetvy výpočtu? Skúste napríklad niektorú z týchto stratégií:
- ak ste už našli dominujúcu množinu určitej veľkosti, nemá zmysel skúmať a rozširovať množiny, ktoré majú zaručene viac prvkov
- alebo nechceme pridať do X vrchol, ktorý nijako nepomôže, lebo aj on aj všetci jeho susedia už majú suseda v X
- alebo nechceme vynechať vrchol, ak niektorý z jeho susedov nemá ešte suseda v X a ani nemá suseda s väčším číslom, ktorý ešte môže byť pridaný do X v ďalších rozhodnutiach
Úloha D: Napíšte program, ktorý dostane orientovaný graf a v prípade, že je v grafe cyklus, vypíše ho (ak je cyklov viac, vypíše hociktorý). Odporúčame začať z programu na topologické triedenie pomocou prehľadávania do hĺbky, pričom si pre každý vrchol do poľa uložte, z ktorého iného vrcholu ste ho objavili (podobne ako pole prev pri prehľadávaní do šírky). V momente, keď program nastavuje acyclic=false, by ste mali vedieť nájsť cyklus.
Prednáška 37
Organizačné poznámky
- Test dnes o 14:00 v F2
- Body zajtra večer v testovači
- Pozeranie písomiek, reklamácie streda 11:15-11:45, prípadne nejaké neskoršie termíny
- Projekty do budúcej stredy 25.5. 11:00 doobeda
- Predvádzanie projektov poobede
- rozpíšeme v AIS na termíny, zapíšte sa na konkrétny čas
- konflikt so skúškou - predvádzanie aj v piatok 27.5. ?
- Termín skúšky štvrtok 26.5. o 9:00
- Výsledky ten istý deň večer testovač,AIS
- Zapisovanie známok piatok 27.5. doobeda?
- ak nemáte k bodov vážnejšie otázky k bodovaniu, zapísať známku si môžete aj na inom termíne skúšky
- reklamácie k tomuto termínu uvšak neskôr nebudú možné (iba úplne oficiálnou žiadosťou)
Úvod do OOP v C++
Dve doplnkové témy, nebudeme skúšať:
- Objektovo-orientované programovanie v C++
- Ilustračný príklad: použitie triedenia, spracovanie intervalov
Dva príklady na prácu s intervalmi
Máme danú množinu intervalov [a1,b1],[a2,b2],...
- Môžu predstavovať napríklad nejaké udalosti v kalendári (prednášky, rozpis služieb,...)
- Napríklad {[2,4],[1,3],[6,7],[5,8]}
Jednoduchá úloha: Pretínajú sa nejaké dva intervaly v množine?
- Kedy sa pretínajú intervaly [a,b] a [c,d]?
Jednoduchý algoritmus:
- porovnáme každé dva intervaly
- zložitosť Syntaktická analýza (parsing) neúspešná (MathML (experimentálne): Neplatná odpověď („Math extension cannot connect to Restbase.“) od serveru „https://wikimedia.org/api/rest_v1/“:): {\displaystyle O(n^2)}
Rýchlejší algoritmus:
- utriedime intervaly podľa začiatku {[1,3],[2,4],[5,8],[6,7]}
- porovnávame len susedné dvojice
- ak sa žiadne nepretínajú, nepretínajú sa ani žiadne iné
Časová zložitosť:
- Triedenie O(n log n), napr. MergeSort
- Prechod poľom O(n)
- Spolu O(n log n)
Triedenie nám často umožní spraviť rýchlejší alebo jednoduchší algoritmus
Výpočet dĺžky zjednotenia intervalov
- Chceme zrátať dĺžku zjednotenia množiny
- Napr, koľko hodín do týždňa prebiehajú na matfyze prednášky
- Príklad:
- množina intervalov {[2,4],[1,3],[6,7],[5,8]}
- zjednotenie intervalov {[1,4],[5,8]}
- dĺžka zjednotenia 6 (dva disjunktné intervaly dĺžky 3)
Ako na to:
- Utriedime intervaly podľa začiatku: {[1,3],[2,4],[5,8],[6,7]}
- Prechádzame zoznam intervalov a pamätáme si aktuálny interval zo zjednotenia [a,b]
- Ak sa práve prezeraný interval [c,d] zo vstupu pretína s intervalom [a,b], pridáme ho k nemu, t.j. do [a,b] uložíme zjednotenie [a,b] a [c,d]
- Ak sa nepretínajú, znamená to, že b<c, a teda ani žiadny ďalší interval zo vstupu sa nebude s [a,b] pretínať
- Dĺžku [a,b] teda pridáme k doteraz spočítanej dĺžke a do [a,b] priradíme [c,d]
- Zložitosť opäť O(n log n)
Implementácia v C++
#include <algorithm> #include <vector> #include <iostream> using namespace std; /** Trieda reprezentujuca uzavrety interval s celociselnymi * suradnicami oboch koncov. */ class Interval { public: /** Konstruktor so zadanymi suradnicami zaciatku a konca */ Interval(int newStart, int newEnd); /** Vrati lavy koniec intervalu */ int getStart() const; /** Vrati pravy koniec intervalu */ int getEnd() const; /** Vrati dlzku intervalu */ int length() const; /** Porovna intervaly najprv podla laveho konca, * pri rovnosti podla praveho. */ bool operator <(const Interval &other) const; /** Spocita najmensi interval obsahujuci zadane dva intervaly */ Interval operator +(const Interval &other) const; private: int start; int end; }; Interval::Interval(int newStart, int newEnd) : start(newStart), end(newEnd) { } int Interval::getStart() const { return start; } int Interval::getEnd() const { return end; } int Interval::length() const { return end - start; } bool Interval::operator<(const Interval& other) const { return start < other.start || (start == other.start && end < other.end); } Interval Interval::operator +(const Interval& other) const { return Interval(min(start, other.start), max(end, other.end)); } /** Pre vektor intervalov utriedenych podla zaciatku * spocita dlzku pokrytu ich zjednotenim. */ int unionLength(const vector <Interval> &a) { if (a.empty()) { return 0; } int length = 0; // doteraz namerana dlzka Interval cur = a[0]; // aktualny interval zo zjednotenia for (int i = 1; i < (int) a.size(); i++) { if (a[i].getStart() > cur.getEnd()) { // cur a a[i] sa nepretinaju length += cur.length(); cur = a[i]; } else { // cur a a[i] sa pretinaju cur = cur + a[i]; // do cur dame zjednenie s a[i] } } length += cur.length(); return length; } int main() { vector <Interval> a; Interval i1(2, 4), i2(1, 3); Interval *pi = new Interval(6, 7); a.push_back(i1); a.push_back(i2); a.push_back(*pi); a.push_back(Interval(5, 8)); sort(a.begin(), a.end()); cout << unionLength(a) << endl; }
Základy OOP v C++
Viac informácií nájdete napríklad v týchto dvoch tutoriáloch:
- http://pages.cs.wisc.edu/~hasti/cs368/CppTutorial/
- http://www.horstmann.com/ccj2/ccjapp3.html
- Základy použitia STL knižnice (generické triedy) (mali sme v zimnom semestri)
Niektoré rozdiely medzi triedami v Jave a C++:
- V C++ oddeľujeme deklaráciu triedy (premenné a hlavičky metód) od implementácie metód
- Implementáciu niektorých veľmi krátkych metód môžeme dať priamo do deklarácie
- V dlhšom programe dáme deklaráciu napr. do súboru Interval.h a implementácie metód do súboru Interval.cpp
- Ak chceme používať intervaly, dáme do hlavičky #include "Interval.h"
- Na rozdiel od Javy pomenovanie súborov nemusí sedieť s menani tried
- Deklarácia triedy rozdelená na časti public, private, protected, za deklaráciou bodkočiarka
- Pri implementácii musíme pred názov dať meno triedy napr. Interval::length
- Štruktúra struct je skoro to isté ako trieda, iba default prístup je public
Konštruktor
- premenné objektu môžeme inicializovať pred začiatkom tela konštruktora
Interval::Interval(int newStart, int newEnd) : start(newStart), end(newEnd) { }
- môžeme to však spraviť aj obyčajnými priradeniami
Interval::Interval(int newStart, int newEnd) { start = newStart; end = newEnd; }
- prvý spôsob vhodný ak premenná je objekt - do zátvorky dáme parametre konštruktora
class SomeClass { Interval myInterval; SomeClass(int start, int end); ... }; SomeClass::SomeClass(int start, int end) : myInterval(start, end) { }
- použitie konštruktora:
- inicializácia lokálnej premennej i1: Interval i1(2, 4)
- vytvorenie nového objektu pomocou new: Interval *pi = new Interval(2, 4);
- vytvorenie anonymnej dočasnej premennej, ktorá sa inicializuje a nakopíruje do vektora: a.push_back(Interval(5, 8));
Preťaženie operátorov
- Väčšinu operátorov, ktoré majú aspoň jeden operand objekt, môžeme preťažiť, teda vymyslieť im vlastný význam
- V našom príklade preťažujeme < (využije sa pri triedení) a + (vytvára najmenší interval pokrývajúci oba operandy)
- Viac #Prednáška 24
Const
- Niektoré metódy sú označené ako const, čo znamená, že nemenia objekt
- Naopak parametre metód môžu byť const referencie
- Nemusia sa kopírovať, ale máme zaručené, že nebudú zmenené
bool Interval::operator<(const Interval& other) const { return start < other.start || (start == other.start && end < other.end); }
Aritmetický strom v C++
- verzia v Jave, pozri #Prednáška 27
class Node { public: virtual int evaluate() const = 0; // abstraktna metoda virtual void print() const = 0; // abstraktna metoda virtual ~Node() { } // destruktor s prazdnym telom }; class BinaryNode : public Node { public : BinaryNode(Node *newLeft, Node *newRight); virtual ~BinaryNode(); protected: Node *left, *right; }; BinaryNode::BinaryNode(Node *newLeft, Node *newRight) : left(newLeft), right(newRight) { } BinaryNode::~BinaryNode() { delete left; delete right; } class ConstantNode : public Node { public : ConstantNode(int newValue); virtual int evaluate() const; virtual void print() const; private: int value; }; ConstantNode::ConstantNode(int newValue) : Node(), value(newValue) { } int ConstantNode::evaluate() const { return value; } void ConstantNode::print() const { cout << value; } class PlusNode : public BinaryNode { public: PlusNode(Node *newLeft, Node *newRight); virtual int evaluate() const; virtual void print() const; }; PlusNode::PlusNode(Node* newLeft, Node* newRight) : BinaryNode(newLeft, newRight) { } int PlusNode::evaluate() const { return left->evaluate()+right->evaluate(); } void PlusNode::print() const { cout << "("; left->print(); cout << "+"; right->print(); cout << ")"; } void treeTest() { Node * tree = new PlusNode(new PlusNode(new ConstantNode(2), new ConstantNode(1)), new ConstantNode(5)); tree->print(); cout << endl << tree->evaluate() << endl; delete tree; }
OOP v C++ (pokračovanie)
Dedenie
- BinaryNode je podtriedou Node:
class BinaryNode : public Node { public : BinaryNode(Node *newLeft, Node *newRight); virtual ~BinaryNode(); protected: Node *left, *right; };
- V hornej časti konštruktora môžeme zavolať konštruktor nadtriedy:
PlusNode::PlusNode(Node* newLeft, Node* newRight) : BinaryNode(newLeft, newRight) { }
- Pozor, ak chceme, aby sa metódy správali polymorfne, musia byť deklarované ako virtual
- Abstraktné metódy sa píšu takto: virtual int evaluate() const = 0;
Deštruktor
- Špeciálna metóda, opak konštruktora
- spúšťa sa automaticky pri zániku objektu (keď na objekt spustíme delete alebo ak objekt v lokálnej premennej a funkcia končí)
- zvyčajne sa používa na odalokovanie pamäte, prípadne ďalšie upratovanie
- deštruktory sú často virtuálne
- po skončení deštruktora sa zavolá deštruktor nadtriedy
BinaryNode::~BinaryNode() { delete left; // rekurzivne zavolá deštruktor na ľavý podstrom delete right; }
Uloženie v pamäti
V Jave:
- každá premenná buď primitívny typ alebo referencia na objekt/pole
- všetky objekty alokované cez new
- odalokované automaticky
- do premennej typu nejaká trieda môžeme priradiť aj referenciu na objekt z podtriedy
V C++:
- premenná môže priamo obsahovať objekt, alebo referenciu na objekt alebo smerník
- naopak smerník môže ukazovať aj na primitívny typ
- objekty alokované cez new treba zmazať
- do premennej typu nejaká trieda sa objekty kopírujú
- môžeme predefinovať operátor priradenia a copy konštruktor, aby fungovali ako treba
- nemôžeme priradiť objekt podtriedy, lebo by sa nemusel zmestiť
- smerníky fungujú podobne ako referencie v Jave, použijeme ak chceme využiť polymorfizmus
Dátové štruktúry zo štandardnej knižnice
- Generické programovanie sa v C++ nazýva template.
- Štandardné knižnice obsahuje viacero dátových štruktúr a algoritmov, podobne ako v Jave
- Stručný príklad použitia:
- Na vstupe máme dvojice meno, email, ukončené slovom END
- Chceme vypísať pre každé meno všetky emaily v abecednom poradí
- Príklad vstupu a výstupu:
mrkvicka staryEmail@nefunkcnyServer.com mrkvicka jozef.mrkvicka@gmail.com hrasko janko42@gmail.com mrkvicka mrkvicka73@uniba.sk hraskova zuzana@hraskova.org END
Meno: hrasko emaily: janko42@gmail.com Meno: hraskova emaily: zuzana@hraskova.org Meno: mrkvicka emaily: jozef.mrkvicka@gmail.com mrkvicka73@uniba.sk staryEmail@nefunkcnyServer.com
- Spravíme si map (binárny vyhľadávací strom) ktorý bude mať ako kľúče reťazce (mená) a ako hodnoty vektory reťazcov (zoznam emailov)
- Program nižšie vyžaduje rozšírenia z verzie jazyka C++11, môže byť treba zapnúť v kompilátore, napr. -std=gnu++11 v g++
#include <map> #include <vector> #include <string> #include <iostream> #include <algorithm> using namespace std; int main(void) { map<string, vector<string>> zoznam; while(true) { string meno, email; cin >> meno; if(meno=="END") { break; } cin >> email; zoznam[meno].push_back(email); // pridame email do zoznamu } // pre kazdeho utriedime zoznam emailov // namiesto auto& by mohlo byt pair<string, vector<string>> & for(auto& prvok : zoznam) { sort(prvok.second.begin(), prvok.second.end()); } // opat prejdeme cely map a vypisujeme for(auto const& prvok : zoznam) { cout << "Meno: " << prvok.first << " emaily:"; for(auto const &email : prvok.second) { cout << " " << email; } cout << endl; } }
Druhú časť programu (od načítania nižšie) môžeme prepísať aj pomocou iterátora:
- iterátor sa správa ako smerník na prvok
- na rozdiel od Javy sa k hodnote prvku môžeme dostať aj viackrát, bez toho, aby sme si ju ukladali do pomocnej premennej
for(auto it = zoznam.begin(); it!=zoznam.end(); it++) { sort(it->second.begin(), it->second.end()); cout << "Meno: " << it->first << " emaily:"; for(auto const &email : it->second) { cout << " " << email; } cout << endl; }
Viac napr. v týchto tutoriáloch:
- http://www.cprogramming.com/tutorial/stl/iterators.html
- http://www.cprogramming.com/tutorial/stl/stlmap.html
- http://www.cprogramming.com/c++11/c++11-ranged-for-loop.html
- http://www.cprogramming.com/c++11/what-is-c++0x.html
Ešte jedna úloha s intervalmi
Daná množina intervalov, nájdite bod, ktorý je pokrytý najväčším počtom intervalov
- napr. interval predstavuje čas, keď má niekto voľno, hľadáme vhodný čas na stretnutie
Cvičenia 26
Úlohou tohto cvičenia je zoznámiť sa s knižnicou GraphGUI, ktorú budete používať na skúške. Táto knižnica zahŕňa programovanie vo JavaFX a prácu s grafmi.
Úloha A: Stiahnite si graphgui.zip a rozzipujte ho. Potom si ho otvorte v Netbeans pomocou New project, ako typ projektu zvoľte Java Project with Existing Sources, na ďalšej obrazovke vyplňte Project Name graphgui a na ďalšej pomocou Add Folder pridajte adresár s rozzipovanými súbormi. Je dobré si tento postup vyskúšať v učebni, aby ste na skúške nemali problémy. Projekt skúste skompilovať, spustiť a pozrite si, čo program robí.
Úloha B: Do súboru Editor.java doprogramujte, aby sa po stlačení tlačidla Edit otvorilo editovacie okienko, v ktorom je pre každý vrchol jeden ovládací prvok s číslom vrcholu a používateľ môže pre každý vrchol nastaviť, aby sa jeho farba zmenila na zelenú. Ovládacie prvky majú byť umiestnené pod sebou a na spodku bude ovládací prvok Button s nápisom OK, ktorý po stlačení označené vrcholy prefarbí. Ak bude okno zavreté bez stlačenia OK, zmeny sa nevykonajú. Pomôcky:
- Na zmenu farby použite metódu setColorName("green") rozhrania Vertex.
- Ako vhodný Layout aplikácie odporúčame GridPane, VBox, alebo TilePane.
- Odporúčané ovládacie prvky sú RadioButton (zaujímavé metódy setSelected, isSelected), pripadne ListView (vhodný SelectionModel je SelectionMode.MULTIPLE a jeho metódy getSelectedIndices() resp. getSelectedItems()).
Úloha C: Do súboru GraphAlgorithm.java doprogramujte, aby po stlačení tlačidla Action program spustil algoritmus hľadania najväčšej kliky a vrcholy v tejto klike aby boli vyfarbené žltou farbou (nastavte im meno farby "yellow") a ostatné vrcholy mimo kliky bielou farbou (nastavte im meno farby "white"). Algoritmus upravte z program na hľadanie maximálnej kliky (trieda MaximumClique). Metóda performAlgorithm vráti reťazec vo formáte "Pocet vrcholov kliky: 6".
Úloha D: Riešenie úlohy C upravte tak, aby algoritmus nehľadal najväčšiu kliku v celom grafe, ale najväčšiu kliku, ktorá obsahuje vybraný vrchol, ktorý GraphAlgorithm dostane ako parameter. Ak hodnota tohto vrcholu je null, konštruktor vyhodí výnimku typu NoSuchElementException.