Algoritmizace a programování Procedurální programování Rekurze Jazyk C České vysoké učení technické Fakulta elektrotechnická Ver.1.10 J. Zděnek 2015 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 A8B14ADP Jazyk C - procedurální programovaní 2 1
Řešený problém Rozklad problému na podproblémy main() Začátek programu funkcea ( ) funkceb ( ) funkcec ( ) funkced ( ) Konec programu funkcea( ) Vstup dat od uživatele Výstupní Parametr(y) funkced( ) Vstupní parametry Výstup výsledku pro uživatele funkceb( ) funkcec( ) Vstupní parametry Dílčí výpočet 1 Dílčí výpočet 2 A8B14ADP Jazyk C - procedurální programovaní 3 Příklad 1 Výpočet s volbou metody (1) 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 čísly) 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 budoucí funkce): 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 Zjiště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 jsme 8 dílčích bloků, ty zapíšeme pomocí funkcí A0B36PRI A8B14ADP PROGRAMOVÁNÍ Jazyk C - procedurální 06 programovaní 44 2
Příklad 1 Výpočet s volbou metody (2) 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 A8B14ADP Jazyk C - procedurální programovaní 5 Příklad 1 Vstup a vyhodnocení příkazu (3) int ctiprikaz(void){ int cmd; while(true){ tisknaobrazovku ("\nobsah-obdelniku=1,obsah-kruhu=2,konec=3","\n,-1); tisknaobrazovku("prikaz = ",",-1); switch(scanf("%d", &cmd)){ case CMD_KRUH: ; case CMD_OBDELNIK:..; case CMD_KONEC: return (cmd); default: tisknaobrazovku("neznamy prikaz","\n,-1); break; A8B14ADP Jazyk C - procedurální programovaní 6 3
Příklad 1 Příkaz pro výpočet obsahu obdélníku (4) void cmdobdelnik(void){ // Akce "Obdelnik" double a,b,p; tisknaobrazovku("vypocet obsahu OBDELNIKU","\n,-1); // Vstup dat a = prectikladnydouble("a ="); b = prectikladnydouble("b ="); // Vypocet p = obsahobdelniku(a, b); // Vystup dat tisknaobrazovku("obsah Obdelniku = ","",p); A8B14ADP Jazyk C - procedurální programovaní 7 Příklad 1 Příkaz pro výpočet obsahu kruhu (5) void cmdkruh(void){ // Akce "Kruh double r,p; tisknaobrazovku("vypocet obsahu KRUHU","\n,-1); // Vstup dat r = prectikladnydouble("r ="); // Vypocet p = obsahkruhu(r); // Vystup dat tisknaobrazovku("obsah KRUHU = ","",p); A8B14ADP Jazyk C - procedurální programovaní 8 4
Příklad 1 Vlastní výpočet obsahů obrazců (6) double obsahobdelniku(double a, double b){ // Vypocet double obsahobdelniku; obsahobdelniku = a * b; return(obsahobdelniku); double obsahkruhu (double r) { // Vypocet double obsahkruhu; obsahkruhu = 2 * 3.14 * r; return (obsahkruhu); A8B14ADP Jazyk C - procedurální programovaní 9 Příklad 1 Zjištění parametrů výpočtu (7) double prectikladnydouble(char napis[]){ // Vstup dat (kladne racionalni cislo) double din; while(true){ tisknaobrazovku("zadej kladne cislo","\n,-1); tisknaobrazovku(napis,",-1); scanf("%d", &din); if (din >= 0) return(d); tisknaobrazovku("chyba","\n,-1); A8B14ADP Jazyk C - procedurální programovaní 10 5
Příklad 1 Tisk nápovědy a výsledků (8) void tisknaobrazovku(char s[],char lf[],double d){ if(d < 0){ printf("%s %s", s, lf); else{ printf("%s%3.2f %s", s, d, lf); A8B14ADP Jazyk C - procedurální programovaní 11 Příklad 1 Řídicí program celého řešení (9) #include <stdio.h> #include <stdlib.h> enum {CMD_KRUH,CMD_OBDELNIK,CMD_KONEC; int main(int argc, char** argv) { while(true){ switch(ctiprikaz()){ case CMD_KRUH: cmdkruh();break; case CMD_OBDELNIK: cmdobdelnik();break; case CMD_KONEC: tisknaobrazovku("konec","\n,-1);return; A8B14ADP Jazyk C - procedurální programovaní 1212 6
Příklad 2 Hra NIM (1) [1] 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 A8B14ADP Jazyk C - procedurální programovaní 13 Příklad 2 Hra NIM (2) 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. A8B14ADP Jazyk C - procedurální programovaní 14 7
Příklad 2 Hra NIM (3) Hrubé řešení: typedef unsigned Tboolean; int pocet; Tboolean 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 funkcemi, proměnné pocet a stroj pro ně budou statickými proměnnými A8B14ADP Jazyk C - procedurální programovaní 15 Příklad 2 Hra NIM (3) #define TRUE 1 #define FALSE 0 int main(int argc, char** argv){ int pocet, stroj=false; pocet = zadanipoctu(); do { if (stroj) pocet = berestroj(pocet); else pocet = berehrac(pocet); stroj =!stroj; while(pocet > 0); printf("vsechny zapalky odebrany \n"); if(!stroj) printf("vyhral jsem \n\n"); else printf("\nvyhral jste, gratuluji \n\n"); return (EXIT_SUCCESS); A8B14ADP Jazyk C - procedurální programovaní 16 8
Příklad 2 Hra NIM (4) int zadanipoctu(void){ int pocet = 0; do { printf("zadejte pocet zapalek (od 15 do 35) = "); scanf("%d", &pocet); while (pocet < 15 pocet > 30); return (pocet); A8B14ADP Jazyk C - procedurální programovaní 17 Příklad 2 Hra NIM (5) int berehrac(int pocet) { int x, chyba; do { chyba = FALSE; printf("pocet zapalek = %d \n\n", pocet); printf("kolik odeberete (1..3)? "); scanf("%d", &x); if (x < 1) { printf("prilis malo \n"); chyba = TRUE; else if (x > 3 x > pocet) { printf("prilis mnoho \n"); chyba = TRUE; while (chyba); pocet -= x; return (pocet); A8B14ADP Jazyk C - procedurální programovaní 18 9
Příklad 2 Hra NIM int berestroj(int pocet) { int x; printf("pocet zapalek = %d \n\n", pocet); x = (pocet-1) % 4; if(x == 0) x = 1; printf("odebiram %d \n", x); pocet -= x; return(pocet); A8B14ADP Jazyk C - procedurální programovaní 19 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)! A8B14ADP Jazyk C - procedurální programovaní 20 10
Rekurze [1] 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 A8B14ADP Jazyk C - procedurální programovaní 21 Rekurzivní algoritmus 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 A8B14ADP Jazyk C - procedurální programovaní 22 11
Rekurze n! = 1 pro n 1 n! = n*(n-1)! pro n>1 Iterace n! = n*(n-1)*(n-2)* *2*1 Faktoriál pomocí rekurze a iterace int fakt(int n) { int fakt(int n) { int fakt(int n) { if (n<=1) return 1; if (n<=1) return 1; int f = 1; return n*fakt(n-1); while (n>1){ else return n*fakt(n-1); f *= n; n--; int fakt(int return n) { f; return n<=1?1:n*fakt(n-1); // ternární operátor A8B14ADP Jazyk C - procedurální programovaní 23 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é) A8B14ADP Jazyk C - procedurální programovaní 24 12
Příklad 3 - Rekurze Obrať posloupnost [1] obrať posloupnost přečti číslo if (přečtené číslo není nula) obrať posloupnost, tj. zbytek vypiš číslo cti x if() 2 4 6 8 0 0 8 6 4 2 piš x 2 2 cti x if() piš x 4 4 cti x if() piš x 6 6 cti x if() piš x 8 8 cti x if() piš x 0 0 A8B14ADP Jazyk C - procedurální programovaní 25 Příklad 3 - Rekurze obrat() posloupnost Řešení: int main(int argc, char** argv){ printf( Zadejte posloupnost zakončenou nulou"); obrat(); return (EXIT_SUCCESS); static void obrat() { int x; scanf("%d", &x); if(x!=0) obrat(); printf(("%d ", x); // načtení // otočení zbytku // vypsani cisla A8B14ADP Jazyk C - procedurální programovaní 26 13
Příklad 4 - Rekurze - Hanojské věže [1] 1 2 3 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) A8B14ADP Jazyk C - procedurální programovaní 27 Příklad 4 - Rekurze - Hanojské věže void main(string[] args) { printf( Zadejte výšku věže"); int pocetdisku; scanf("%d", &pocetdisku); prenesvez(pocetdisku, 1, 2, 3); 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 void prenesvez(int vyska,int odkud,int kam,int pomoci){ if(vyska>0) { prenesvez(vyska-1,odkud,pomoci,kam); printf( Přenes disk z %d na %d,odkud,kam); prenesvez(vyska-1,pomoci,kam,odkud); A8B14ADP Jazyk C - procedurální programovaní 28 14
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); printf( Přenes disk z %d na %d,odkud,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 29 A8B14ADP Jazyk C - procedurální programovaní 29 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 A8B14ADP Jazyk C - procedurální programovaní 30 15
Fibonacciho posloupnost historie [1] 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 (1857-1930) - 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) 1 1 2 1 3 2 4 3 5 5 6 8.. 12 144.. 50 20 365 011 074 (20 miliard) 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 A8B14ADP Jazyk C - procedurální programovaní 31 Fibonacciho posloupnost - rekurzivně Platí: f 0 = 1 f 1 = 1 f n = f n-1 + f n-2 pro n > 1 Rekurzivní funkce: 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í? A8B14ADP Jazyk C - procedurální programovaní 32 16
Složitost výpočtu Fibonacciho čísla - rekurzivně Příklad pro fib(10): fib(10) fib(9) + fib(8) fib(8) + fib(7) fib(7) + fib(6) fib(7)+ fib(6) fib(6)+ fib(5) fib(6) + fib(5) fib(5)+ fib(4) f 50 20 365 011 074 (20 miliard) int fib(int i) { if (i < 2) return 1; return fib(i-1)+fib(i-2); Složitost je exponenciální!!!! A8B14ADP Jazyk C - procedurální programovaní 33 Fibonacciho posloupnost - iteračně Platí: f 0 = 1 f 1 = 1 f n = f n-1 + f n-2, pro n > 1 Iteračně: int fibonacci(int n){ int i,fn_2 = 1; int fn_1 = 1, fn = 1; for (i = 2;i <= n; i++){ fn_2 = fn_1; fn_1 = fn; fn = fn_1 + fn_2; return fn; fn_2 fn_1 fn 1 1 1 + 1 1 + 2 3 2 + 3 5 4 3 + 5 8 Složitost: 3*n 1 2 i 2 3 5 A8B14ADP Jazyk C - procedurální programovaní 34 17
Reference [1] Jelinek,I.- Zděnek, J.: Presentace k přednáškám A0B36PRI, 2012 A8B14ADP Jazyk C - procedurální programovaní 35 Algoritmizace a programování Procedurální programování Rekurze KONEC České vysoké učení technické Fakulta elektrotechnická 18