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
Ř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 Tisk nápovědy, pokynů a výsledků Řídicí program celého řešení,zapíšeme v: (8) main Identifikovali jsme 8 dílčích bloků, ty zapíšeme pomocí funkcí (6) prectikladnydouble (7) tisknaobrazovku A0B36PRI A8B14ADP PROGRAMOVÁNÍ Jazyk C - procedurální 06 programovaní 44
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
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
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
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
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
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
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
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
Definice ze slovníku (pozor vtip) Rekurze viz Rekurze.nekonečná rekurze, lépe: Rekurze 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
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
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
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 2 cti x 4 if() cti x 6 if() cti x if() 8 2 4 6 8 0 cti x 0 8 6 4 2 if() cti x if() 6 8 4 piš x piš x 2 piš x piš x 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); // načtení if(x!=0) obrat(); // otočení zbytku printf(("%d ", x); // vypsani cisla A8B14ADP Jazyk C - procedurální programovaní 26
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
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 2 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
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 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.. 12 144.. 50 20 365 011 074 (20 miliard) 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
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
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á