Prezentace se nahrává, počkejte prosím

Prezentace se nahrává, počkejte prosím

České vysoké učení technické Fakulta elektrotechnická Procedurální programování, rekurze Jazyk JAVA A0B36PRI - PROGRAMOVÁN Í V 2.02.

Podobné prezentace


Prezentace na téma: "České vysoké učení technické Fakulta elektrotechnická Procedurální programování, rekurze Jazyk JAVA A0B36PRI - PROGRAMOVÁN Í V 2.02."— Transkript prezentace:

1 České vysoké učení technické Fakulta elektrotechnická Procedurální programování, rekurze Jazyk JAVA A0B36PRI - PROGRAMOVÁN Í V 2.02

2 A0B36PRI „PROGRAMOVÁNÍ“ 062 Procedurální programování - zásady Postupný návrh programu rozkladem řešeního problému na podproblémy zadaný problém rozložíme na podproblémy pro řešení podproblémů zavedeme abstraktní příkazy pomocí abstraktních příkazů sestavíme hrubé řešení abstraktní příkazy realizujeme pomocí metod (funkcí, procedur) Navržené metody propojíme pomocí předávání parametrů Rozklad problému na podproblémy ukážeme na jednoduchých příkladech

3 A0B36PRI „PROGRAMOVÁNÍ“ 063 Rozklad problému na podproblémy Vstup dat od uživatele metodaA( ) Výstupní Parametr(y) Dílčí výpočet 1 metodaB( ) Vstupní parametry Dílčí výpočet 2 metodaC( ) Výstup výsledku pro uživatele metodaD( ) Vstupní parametry Řešený problém main () Začátek programu Konec programu metodaA ( ) metodaB ( ) metodaC ( ) metodaD ( )

4 A0B36PRI „PROGRAMOVÁNÍ“ 064 Příklad 1 – Výpočet s volbou metody Navrhněte řešení programu, který vypočítá buď obsah obdélníku nebo kruhu. Druh výpočtu lze volit z klávesnice z nabídkového menu (volba čisly) Výpočet lze spouštět z klávesnice opakovaně pro různé parametry Výsledky výpočtu se zobrazují na obrazovce Dílčí podproblémy (pro identifikované části zvolíme název budouci metody): Nabídkové menu s volbou obsluhy (1) ctiPrikaz Vykonání příkazu obsluhy (2) cmdObdelnik (3) cmdKruh Vlastní matematický výpočet (4) obsahObdelniku (5) obsahKruhu Pomocné metody Zjistění parametrů pro výpočet (6) prectiKladnyDouble Tisk nápovědy, pokynů a výsledků (7) tiskNaObrazovku Řídicí program celého řešení,zapíšeme v: (8) main Identifikovali jsem 8 dílčích bloků, ty zapíšeme pomocí metod

5 A0B36PRI „PROGRAMOVÁNÍ“ 065 Příklad 1 – Výpočet s volbou metody Hrubé řešení (zapsané pseudojazykem – ten si volíme) Opakuj { switch (ctiPrikaz) pocitat-obdelnik: cmdObdelnik tiskNaObrazovku // napoveda prectiKladnyDouble // cti parametry vypoctu obsahObdelniku // vlastni vypocet tiskNaObrazovku // zobraz vysledek break pocitat-kruh: cmdKruh tiskNaObrazovku // napoveda prectiKladnyDouble // cti parametry vypoctu obsahKruhu // vlastni vypocet tiskNaObrazovku // zobraz vysledek break konec-programu: return }

6 A0B36PRI „PROGRAMOVÁNÍ“ 066 Příklad 1 – Vstup a vyhodnocení příkazu static int ctiPrikaz ( ) { Scanner sc = new Scanner(System.in); final int CMD_KRUH=1,CMD_OBDELNIK=2,CMD_KONEC=3;int cmd; while (true) { tiskNaObrazovku ("\nObsah-obdelniku=1,Obsah-kruhu=2,Konec=3","\n"); tiskNaObrazovku("Prikaz = ",""); switch(cmd = sc.nextInt()) { case CMD_KRUH:…; case CMD_OBDELNIK:..; case CMD_KONEC: return (cmd); default: tiskNaObrazovku("==Neznamy prikaz==","\n");break; }

7 A0B36PRI „PROGRAMOVÁNÍ“ 067 Příklad 1 – Příkaz pro výpočet obsahu obdélníku static void cmdObdelnik ( ) { // Akce "Obdelnik" tiskNaObrazovku("Vypocet obsahu OBDELNIKU","\n"); // Vstup dat double a = prectiKladnyDouble("a ="); double b = prectiKladnyDouble("b ="); // Vypocet double p = obsahObdelniku(a, b); // Vystup dat tiskNaObrazovku("Obsah Obdelniku = ","",p); }

8 A0B36PRI „PROGRAMOVÁNÍ“ 068 Příklad 1 – Příkaz pro výpočet obsahu kruhu static void cmdKruh ( ) { // Akce "Kruh" tiskNaObrazovku("Vypocet obsahu KRUHU","\n"); // Vstup dat double r = prectiKladnyDouble("r ="); // Vypocet double p = obsahKruhu(r); // Vystup dat tiskNaObrazovku("Obsah KRUHU = ","",p); }

9 A0B36PRI „PROGRAMOVÁNÍ“ 069 Příklad 1 – Vlastní výpočet obsahů obrazců static double obsahObdelniku (double a, double b) { // Vypocet double obsahObdelniku = a * b; return (obsahObdelniku); } static double obsahKruhu (double r) { // Vypocet double obsahKruhu = 2 * Math.PI * r; return (obsahKruhu); }

10 A0B36PRI „PROGRAMOVÁNÍ“ 0610 Příklad 1 – Zjištění parametrů výpočtu static double prectiKladnyDouble (String napis) { // Vstup dat (kladne racionalni cislo) Scanner sc = new Scanner(System.in); while (true) { tiskNaObrazovku("Zadej kladne cislo","\n"); tiskNaObrazovku(napis,""); double d = sc.nextDouble(); if (d >= 0) return(d); tiskNaObrazovku("Chyba","\n"); }

11 A0B36PRI „PROGRAMOVÁNÍ“ 0611 Příklad 1 – Tisk nápovědy a výsledků static void tiskNaObrazovku (String s,String lf,double d){ System.out.printf("%s%3.2f %s", s, d, lf); } static void tiskNaObrazovku (String s,String lf){ System.out.printf("%s %s", s, lf); } Přetížené metody

12 A0B36PRI „PROGRAMOVÁNÍ“ 0612 Příklad 1 – Řídicí program celého řešení public class Main { static {Locale.setDefault(Locale.US);} public static void main(String[] args) { final int CMD_KRUH=1,CMD_OBDELNIK=2,CMD_KONEC=3; while (true) { switch ( ctiPrikaz() ) { case CMD_KRUH: cmdKruh();break; case CMD_OBDELNIK: cmdObdelnik();break; case CMD_KONEC: tiskNaObrazovku("KONEC","\n");return; }

13 A0B36PRI „PROGRAMOVÁNÍ“ 0613 Příklad 1 – Připomenutí „Scanner“,“printf“ Před použitím metod „Scanneru“ (čtení klávesnice) je nutné: importovat knihovní soubor java.util.* pokud chceme zadávat z klávesnice desetinou tečku (ne čárku) zapíšeme deklaraci: static {Locale.setDefault(Locale.US);}. Založit proměnnou sc (viz níže), proč bude vysvětleno v lekci o objektech. Před použitím „printf“ není nutné importovat žádnou knihovnu. import java.util.*; public class Main { static {Locale.setDefault(Locale.US);} metody ( ) { Scanner sc = new Scanner(System.in); int x = sc.nextIn t(); System.out.printf("%s%3.2f %s", s, d, lf); } } // class END

14 A0B36PRI „PROGRAMOVÁNÍ“ 0614 Příklad 2 – Hra NIM Navrhněte řešení programu pro hru NIM „počítač-člověk“ Pravidla hry NIM: hráč zadá počet zápalek (např. od 15 do 35) pak se střídá se strojem v odebírání; odebrat lze 1, 2 nebo 3 zápalky, prohraje ten, kdo odebere poslední zápalku. Dílčí podproblémy: zadání počtu zápalek odebrání zápalek hráčem odebrání zápalek strojem

15 A0B36PRI „PROGRAMOVÁNÍ“ 0615 Příklad 2 – Hra NIM Pravidla pro odebírání zápalek strojem, která vedou k vítězství (je-li to možné): počet zápalek nevýhodných pro protihráče je 1, 5, 9, atd., obecně 4n+1, kde n >0, stroj musí z počtu p zápalek odebrat x zápalek tak, aby platilo p – x = 4n + 1 z tohoto vztahu po úpravě a s ohledem na omezení pro x dostaneme x = (p – 1) mod 4 vyjde-li x=0, znamená to, že okamžitý počet zápalek je pro stroj nevýhodný a bude-li protihráč postupovat správně, stroj prohraje.

16 A0B36PRI „PROGRAMOVÁNÍ“ 0616 Příklad 2 – Hra NIM Hrubé řešení: int pocet; boolean stroj = false; // zadání počtu zápalek do{ if (stroj) “odebrání zápalek strojem“ else “odebrání zápalek hráčem“ stroj = !stroj; }while (pocet > 0); if (stroj) “vyhrál stroj“ else “vyhrál hráč“ Podproblémy „zadání počtu zápalek“, „odebrání zápalek strojem“ a „odebrání zápalek hráčem“ budeme realizovat metodami, proměnné pocet a stroj pro ně budou statickými (čili nelokálními) proměnnými

17 A0B36PRI „PROGRAMOVÁNÍ“ 0617 Příklad 2 – Hra NIM public class Nim { static int pocet; // aktuální počet zápalek static boolean stroj; // =true znamená, že bere počítač public static void main(String[] args) { zadaniPoctu(); stroj = false; // zacina hrac do { if (stroj) bereStroj(); else bereHrac(); stroj = !stroj; } while (pocet>0); if (stroj) System.out.println("vyhrál jsem"); else System.out.println ("vyhrál jste, gratuluji"); } static void zadaniPoctu() { … } static void bereHrac() { … } static void bereStroj() { … } }

18 A0B36PRI „PROGRAMOVÁNÍ“ 0618 Příklad 2 – Hra NIM static void zadaniPoctu() { Scanner sc = new Scanner(System.in); do{ System.out.println("zadejte počet zápalek od15do 35)"); pocet = sc.nextInt(); }while (pocet 30); }

19 Příklad 2 – Hra NIM static void bereHrac() { Scanner sc = new Scanner(System.in); int x; boolean chyba; do{ chyba = false; System.out.println( "počet zápalek " + pocet ); System.out.println( "kolik odeberete" ); x = sc.nextInt(); if(x < 1) { System.out.println( "prilis malo" ); chyba = true;} else if (x>3 || x>pocet) { System.out.println( "prilis mnoho");chyba = true;} }while(chyba); pocet -= x; } A0B36PRI „PROGRAMOVÁNÍ“ 0619

20 Příklad 2 – Hra NIM static void bereStroj() { System.out.println( "počet zápalek " +pocet); int x = (pocet-1) % 4; if (x==0) x = 1; System.out.println( "odebírám " +x); pocet -= x; } A0B36PRI „PROGRAMOVÁNÍ“ 0620

21 A0B36PR Rekurze Definice ze slovníku (pozor vtip) Rekurze viz „Rekurze“ ….nekonečná rekurze, lépe: pokud neznáte význam tohoto pojmu, pokračujte pojmem „Rekurze“ Rekurze - algoritmus, který volá v průběhu svého běhu sama sebe Příklad: výpočet faktoriálu: n! 0! = 1, 1! = 1, pro záporné číslo x budiž x! = 1 pro n>1, n! = n(n-1)!

22 A0B36PRI „PROGRAMOVÁNÍ“ 0622 Rekurze Rekurzivní funkce (procedury) jsou přímou realizací rekurzivních algoritmů Rekurzivní algoritmus předepisuje výpočet „shora dolů“ v závislosti na velikosti (složitosti) vstupních dat: pro nejmenší (nejjednodušší) data je výpočet předepsán přímo pro obecná data je výpočet předepsán s využitím téhož algoritmu pro menší (jednodušší) data Výhodou rekurzivních funkcí (procedur) je jednoduchost a přehlednost Nevýhodou může být časová náročnost způsobená např. zbytečným opakováním výpočtu Řadu rekurzívních algoritmů lze nahradit iteračními, které počítají výsledek „zdola nahoru“, tj, od menších (jednodušších) dat k větším (složitějším) Pokud algoritmus výpočtu „zdola nahoru“ nenajdeme (např. při řešení problému Hanojských věží), lze rekurzivitu odstranit pomocí tzv. zásobníku

23 A0B36PRI „PROGRAMOVÁNÍ“ 0623 Rekurzivní algorimus Rekurzivní algoritmus v některém kroku volá sám sebe Rekurzivní metoda v některém příkazu volá sama sebe ( i nepřímo ) Příklad – faktoriál : Rekurse n! = 1 pro n  1 n! = n*(n-1)!pro n>1 Iterace n! = n*(n-1)*(n-2)*…*2*1

24 A0B36PR Faktoriál pomocí rekurze a iterace Rekurze n! = 1 pro n  1 n! = n*(n-1)!pro n>1 Iterace n! = n*(n-1)*(n-2)*…*2*1 static int fakt(int n) { if (n<=1) return 1; else return n*fakt(n-1); } static int fakt(int n) { return n<=1?1:n*fakt(n-1); // ternární operátor } static int fakt(int n) { if (n<=1) return 1; return n*fakt(n-1); } static int fakt(int n) { int f = 1; while (n>1){ f *= n; n--; } return f; }

25 A0B36PRI „PROGRAMOVÁNÍ“ 0625 Rekurze a rozklad problému na podproblémy Program, který přečte posloupnost čísel zakončenou nulou a vypíše ji obráceně Rozklad problému: –zavedeme abstraktní příkaz přečti,vypiš a obrať posloupnost –příkaz rozložíme do tří kroků: „obrať posloupnost“: –přečti číslo (a ulož ho) –if (přečtené číslo není nula) „obrať posloupnost“ (zbytek) –vypiš číslo (uložené)

26 A0B36PRI „PROGRAMOVÁNÍ“ 0626 Příklad 3 - Rekurze „Obrať posloupnost“ „obrať posloupnost“ –přečti číslo –if (přečtené číslo není nula) „obrať posloupnost, tj. zbytek“ –vypiš číslo cti x if()… piš x cti x if()… piš x cti x piš x cti x if()… piš x cti x if()… piš x if()…

27 A0B36PRI „PROGRAMOVÁNÍ“ 0627 Příklad 3 - Rekurze – obrat() posloupnost Řešení: public class Obrat { static Scanner sc = new Scanner(System.in); public static void main(String[] args) { System.out.println("zadejte …zakončenou nulou"); obrat(); } static void obrat() { int x = sc.nextInt(); // načtení if(x!=0) obrat(); // otočení zbytku System.out.print(x + " "); // výpis uloženého } }

28 A0B36PRI „PROGRAMOVÁNÍ“ 0628 Příklad 4 - Rekurze - Hanojské věže Zavedeme abstraktní příkaz přenes_věž(n,1,2,3) který interpretujeme jako "přenes n disků z jehly 1 na jehlu 2 s použitím jehly 3". Pro n>0 lze příkaz rozložit na tři jednodušší příkazy přenes_věž(n-1,1,3,2) "přenes disk z jehly 1 na jehlu 2", přenes_věž(n-1,3,2,1) 123 složitější

29 A0B36PRI „PROGRAMOVÁNÍ“ 0629 Příklad 4 - Rekurze - Hanojské věže public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("zadejte výšku věže"); int pocetDisku = sc.nextInt(); prenesVez(pocetDisku, 1, 2, 3); } static void prenesVez (int vyska,int odkud,int kam,int pomoci){ if(vyska>0) { prenesVez(vyska-1, odkud, pomoci, kam); System.out.println("přenes disk z "+odkud+" na "+kam); prenesVez(vyska-1, pomoci, kam, odkud); } } 3 přenes disk z 1 na 2 přenes disk z 1 na 3 přenes disk z 2 na 3 přenes disk z 1 na 2 přenes disk z 3 na 1 přenes disk z 3 na 2 přenes disk z 1 na 2 složitější

30 A0B36PR Příklad rekurze - Hanojské věže prenesVez(3, 1, 2, 3); prenesVez(2, 1, 3, 2); (1, 2); prenesVez(2, 3, 2, 1); prenesVez(1, 1, 2, 3); (1, 3); prenesVez(1, 2, 3, 1); | prenesVez(1, 3, 1, 2); (3, 2); prenesVez(1, 1, 2, 3); (1, 2); (2, 3); (3, 1); (1, 2); prenesVez(int vyska, int odkud, int kam, int pomoci) { if (vyska>0) { prenesVez(vyska-1, odkud, pomoci, kam); System.out.println("přenes disk z "+odkud+" na "+kam); prenesVez(vyska-1, pomoci, kam, odkud); } 3 přenes disk z 1 na 2 přenes disk z 1 na 3 přenes disk z 2 na 3 přenes disk z 1 na 2 přenes disk z 3 na 1 přenes disk z 3 na 2 přenes disk z 1 na 2 složitější

31 A0B36PRI „PROGRAMOVÁNÍ“ 0631 Obecně k rekurzivitě Rekurzivní metody jsou přímou realizací rekurzivních algoritmů Rekurzivní algoritmus předepisuje výpočet „shora dolů“ v závislosti na velikosti (složitosti) vstupních dat: pro nejmenší ( nejjednodušší ) data je výpočet předepsán přímo pro obecná data je výpočet předepsán s využitím téhož algoritmu pro menší ( jednodušší ) data Výhodou rekurzivních metod je jednoduchost a přehlednost Nevýhodou může být časová náročnost způsobená např. zbytečným opakováním výpočtu

32 A0B36PR Fibonacciho posloupnost - historie Maatraameru (Chhandah-shāstra, the Art of Prosody, 450 or 200 BC) Leonardo Pisano (Leonardo z Pisy), známý také jako Fibonacci (cca 1175–1250) - králíci Henry E. Dudeney ( ) - krávy „Jestliže každá kráva vyprodukuje své první tele (jalovici) ve věku dvou let a poté každý rok jednu další jalovici, kolik budete mít krav za 12 let, jestliže Vám žádná nezemře?“ Rok - počet krav (jalovic) Počet krav = počet krav vloni + počet narozených (odpovídá počtu krav předloni) f n = f n-1 + f n-2 … … (20 miliard)

33 A0B36PR Fibonacciho posloupnost - rekurzivně Platí: f 0 = 1 f 1 = 1 f n = f n-1 + f n-2 pro n > 1 Rekurzivní funkce: static int fib(int i) { if (i<2) return 1; return fib(i-1)+fib(i-2); } Rekurze je hezká - zápis „odpovídá“ rekurentní definici. Je ale i efektivní?

34 A0B36PR Složitost výpočtu Fibonacciho čísla - rekurzivně Příklad pro fib(10): static int fib(int i) { if (i<2) return 1; return fib(i-1)+fib(i-2); } fib(10) fib(9) + fib(8) + fib(7) + fib(6) fib(7) + fib(6) + fib(5) fib(6) + fib(5) + fib(4) f (20 miliard) Složitost je exponenciální!!!!

35 A0B36PR Platí: f 0 = 1 f 1 = 1 f n = f n-1 + f n-2,pro n > 1 Iteračně: static int fib(int n) { int i, fibNMinus2=1; int fibNMinus1=1, fibN=1; for (i=2; i<=n; i++) { fibNMinus2 = fibNMinus1; fibNMinus1 = fibN; fibN = fibNMinus1 + fibNMinus2; } return fibN; } Fibonacciho posloupnost - iteračně fibNMinus2fibNMinu1fibN i Složitost: 3*n

36 A0B36PR Složitost výpočtu Fibonacciho čísla 2 Iterační metoda: 3*n Rekurzivní výpočet ~ 2 n Podíl dvou po sobě následujících členů konverguje k hodnotě „zlatého řezu“ (Johannes Kepler - golden ratio): φ ≈1, Zlatý řez byl pokládán za středověku za ideální proporci mezi různými délkami Zlatý řez vznikne rozdělením úsečky na dvě části tak, že poměr větší části k menší je stejný jako poměr celé úsečky k větší části Pro zájemce

37 A0B36PR Další příklady rekurze - fraktály Pro zájemce

38 A0B36PR Příklad rekurze, základní schéma – součin public static void main(String[] args) { int x,y; ……; System.out.println(" " + souI(x, y) + souR(x,y)); } static int souI(int s, int t){ int souI=0; for (int i = 0; i < s; i++) souI=souI+t; return souI; } static int souR(int s, int t) { int souR; if (s > 0)souR=souR(s - 1,t)+t; else souR = 0; return souR; }

39 Shrnutí a perspektiva Naivní styl Procedurální styl Objektový přístup, malý úvod bude Návrhové vzory, další studium A0B36PRI „PROGRAMOVÁNÍ“ 0639

40 A0B36PRI „PROGRAMOVÁNÍ“ 0640 Programovací styly Na příkladu programu simulujícího čítač si ukážeme programovací styly: naivní procedurální objektově orientovaný (viz přednáška „Úvod do objektově orientovaného programování") Příklad komunikace programu: Hodnota = 10 0) Konec 1) Zvětši 2) Zmenši 3) Nastav Vase volba: 1 Hodnota = 11 0) Konec 1) Zvětši 2) Zmenši 3) Nastav...

41 A0B36PRI „PROGRAMOVÁNÍ“ 0641 Naivní styl Jednoduché úlohy lze řešit přímočaře : import java.util.*; public class Citac1 { final static int pocHodn = 0; static int hodn, volba; public static void main(String[] args) { Scanner sc = new Scanner(System.in); hodn = pocHodn; do{ System.out.prinln( "Hodnota = " + hodn ); System.out.println ( "0) Konec\n1) Zvětši\n2) Zmenši\n3) Nastav" ); System.out.print( "Vaše volba: " ); volba = sc.nextInt(); switch ( volba ) { case 0: break; case 1: hodn++; break; case 2: hodn--; break; case 3: hodn = pocHodn; break; default: System.out.println( "nedovolená volba" ); } }while (volba > 0); System.out.println( "Konec" ); }

42 A0B36PRI „PROGRAMOVÁNÍ“ 0642 Procedurální styl Připomeňme hlavní zásady: Zadaný problém se snažíme rozložit na podproblémy Pro řešení podproblémů zavádíme abstraktní příkazy, které nejprve specifikujeme a pak realizujeme pomocí metod Aplikováno na "čítač" identifikujeme tyto dílčí podproblémy: komunikace s uživatelem, jejímž výsledkem je kód požadované operace provedení požadované operace s čítačem Řešení: public class Citac2 { final static int POC_HODN = 0; // třídní konstanta static int hodn; // třídní proměnná static void operace(int op) { // pro všechny 3 operace switch (op) { case 1: hodn++; break; case 2: hodn--; break; case 3: hodn = POC_HODN; break; }

43 A0B36PRI „PROGRAMOVÁNÍ“ 0643 Procedurální styl static int menu(){ Scanner sc = new Scanner(System.in); int volba; do { System.out.println( "0. Konec" ); System.out.println( "1. Zvětši" ); System.out.println( "2. Zmenši" ); System.out.println( "3. Nastav" ); System.out.print( "Vaše volba: " ); volba = sc.nextInt(); if (volba 3){ System.out.println( "nedovolená volba" ); volba = -1; } }while (volba < 0); return volba; }

44 A0B36PRI „PROGRAMOVÁNÍ“ 0644 Procedurální styl public static void main(String[] args) { int volba; hodn = POC_HODN; do{ System.out.println("hodnota = " +hodn); volba = menu(); if (volba>0) operace(volba); } while (volba > 0); System.out.println("Konec"); } Poznámky: procedurální řešení čítače (jediná procedura pro všechny operace, třídní proměnná pro hodnotu, třídní konstanta udávající počáteční hodnotu) je nedokonalé (proč?) čítač je typ, pro který jsou definovány určité operace (realizované metodami) a jehož implementace (proměnná pro hodnotu) by neměla být volně přístupná tedy pro realizaci čítače je vhodnější objektově orientovaný styl (nebo lokální statická proměnná – viz úvod do jazyka "C").

46 České vysoké učení technické Fakulta elektrotechnická Procedurální programování a rekurze Jazyk JAVA Konec A0B36PRI - PROGRAMOVÁN Í


Stáhnout ppt "České vysoké učení technické Fakulta elektrotechnická Procedurální programování, rekurze Jazyk JAVA A0B36PRI - PROGRAMOVÁN Í V 2.02."

Podobné prezentace


Reklamy Google