Zapisz jako PDF

Transkrypt

Zapisz jako PDF
Spis treści
1 Rekurencja
1.1 Przykład — obliczanie silni
1.1.1 Rekurencyjnie
1.1.2 Iteracyjnie
1.2 Przerywnik
1.3 Do czego rekurencja będzie wygodna?
1.4 Ciąg Fibonacciego
1.5 Wyszukiwanie binarne
1.5.1 Zadanie
1.6 Wieże Hanoi
1.6.1 Rozwiązanie rekurencyjne
1.6.2 Rozwiązanie iteracyjne
1.6.2.1 Algorytm iteracyjny
1.6.3 Zadanie
1.7 Zadania różne
1.8 Wyznacznik macierzy (obliczany z definicji)
1.9 Macierz odwrotna
Rekurencja
Rekurencja polega na rozwiązywaniu problemów przy następującym spostrzeżeniu: gdybyśmy mieli
rozwiązanie danego problemu dla mniejszej liczby danych, to rozwiązanie dla obecnej byłoby
banalne. Łatwo to zobaczyć na poniższym przykładzie z obliczaniem silni:
Przykład — obliczanie silni
Silnia przedstawia się następującą zależnością matematyczną:
Rekurencyjnie
Silnię można zapisać jako równanie rekurencyjne:
Jak wygląda to w praktyce? Zobaczmy, co dzieje się, gdy chcemy obliczyć
Rekurencyjne obliczanie silni dla n = 4:
b4
=
=
=
=
=
=
=
=
=
4 *
4 *
4 *
4 *
4 *
4 *
4 *
4 *
24
b3
3
3
3
3
3
3
6
*
*
*
*
*
*
b2
2 *
2 *
2 *
2 *
2
b1
1 * b
1 * 1
1
Jak zapiszemy to równanie rekurencyjne w Pythonie?
def silnia(n):
if n == :
return 1
else:
return n*silnia(n-1)
Iteracyjnie
Silnię można oczywiście obliczyć iteracyjnie, co jest daleko bardziej wydajne.
def silnia(n):
silnia = 1
for i in xrange(2, n+1):
silnia *= i
return silnia
Przerywnik
Uruchom teraz silnia(999) i silnia(998). Co zauważasz? W pythonie jest domyślnie ustawione
ograniczenie na głębokość stosu wykonania programu = 1000. Limit ten można zmienić w
następujący sposób:
import sys
sys.setrecursionlimit(1500)
Ale też nie w nieskończoność. Żeby uniknąć takich problemów, optymalizuje się programy rekurencji
tak, aby stosować tzw rekurencję ogonową:
def even(x):
if x == :
return True
else:
return odd(x - 1)
def odd(x):
if x == :
return False
else:
return even(x - 1)
def tail_rec(fun):
def tail(fun):
a = fun
while callable(a):
a = a()
return a
return (lambda x: tail(fun(x)))
def tail_even(x):
if x == :
return True
else:
return (lambda: tail_odd(x - 1))
def tail_odd(x):
if x == :
return False
else:
return (lambda: tail_even(x - 1))
even = tail_rec(tail_even)
odd = tail_rec(tail_odd)
Do czego rekurencja będzie wygodna?
<korzen litera="d">
<lewy litera="b">
<lewy litera="a">
</lewy>
<prawy litera="c">
</prawy>
</lewy>
<prawy litera="f">
<lewy litera="e">
</lewy>
<prawy litera="g">
</prawy>
</prawy>
</korzen>
Napisz program, który wczyta następujące drzewo xml i wypisze je w porządku infiksowym.
Napisz program, który z zadanego ciągu znaków utworzy analogiczne drzewo xml, tak, aby po
wypisaniu go w porzadku infiksowym otrzymać wyjściowy ciąg znaków.
Spróbuje oba problemu rozwiązać zarówno iteracyjnie, jak rekurencyjnie. Ile linii kodu zajęło
Ci rozwiązanie iteracyjne, a ile rekurencyjne? Ile razy szybciej udało Ci się napisać rozwiązanie
rekurencyjnie? Ile razy pomyliłeś się pisząc rozwiązanie iteracyjne, a ile rekurencyjne?
Ciąg Fibonacciego
def fibonacci(n):
if n == or n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
Ciągu Fibonacciego od liczby 4 — schemat
wywołań funkcji w pierwszej gałęzi.
Wyszukiwanie binarne
Szukanie za pomocą bisekcji jest jednym z najbardziej intuicyjnych sposobów szukania wartości w
uporządkowanym wektorze, czy sekwencji. Wyobraź sobie, że musisz znaleźć liczbę 54 w sekwencji
składającej się z
liczb od 1 do 90. Zgadywałbyś zapewne, że 54 jest to element . Jeżeli nie
trafisz, sprawdzisz, czy wybrany element jest większy, czy mniejszy od 54. Jeżeli jest mniejszy,
poszukasz go w połowie lewej części sekwencji. Jeżeli jest większy, poszukasz go w połowie prawej
części sekwencji. Tak jak na rysunku:
Jeżeli w którymś momencie lewa granica sekwencji okaże się większa niż prawa, podzieliłeś
sekwencję na 2 o raz za dużo i nie masz już w czym szukać. Zwracasz wtedy -1, na znak tego, że nie
znalazłeś.
Rozwiązanie rekurencyjne tego algorytmu jest intuicyjne i sprowadza się do zapisu w poleceniami
powyżej opisanej procedury. Poniżej kod w Pythonie.
def znajdz(lista, lewy, prawy, x):
if lewy>prawy:
raise IndexError
srodek = (lewy + prawy) / 2
if lista[srodek] == x:
return srodek
if lista[srodek] > x:
return znajdz(lista, lewy, srodek - 1, x)
else:
return znajdz(lista, srodek + 1, prawy, x)
Zadanie
Rozwiąż ten problem iteracyjnie.
Wieże Hanoi
Wieże Hanoi: Od lewej: słupek A z całą
wieżą, pusty słupek B pełniący rolę bufora i
pusty słupek docelowy C.
Wieże Hanoi są łamigłówką matematyczną przypominającą dziecięcą zabawkę. Tak, jak na rysunku
Figure 2 mamy trzy słupki, oznaczone A, B i C. Na słupku A znajduje się wieża — ułożone na sobie
dyski, o zmniejszającej się średnicy. Słupki B — pełniący rolę bufora; i C — będący słupkiem
docelowym; są puste. Zadanie polega na przeniesieniu całej wieży ze słupka A na słupek C,
zachowując zależność im wyższy dysk, tym mniejsza jego średnica, posługując się słupkiem B jako
buforem. Dodatkowo:
W każdym kroku można przenieść tylko jeden dysk.
Krok polega na przeniesieniu dysku z wybranego słupka na inny słupek i umieszczeniu go na
dyskach, które znajdują się na danym słupku.
Dysk o większej średnicy nigdy nie może znaleźć się nad dyskiem o mniejszej średnicy.
Rozwiązanie rekurencyjne
Jest banalnie proste.
Przenieść wieżę z
dysków ze słupka A na słupek B.
Przenieść jeden dysk na słupek C.
Przenieść wieżę z
dysków ze słupka B na słupek C.
Rozwiązanie iteracyjne
Wieże Hanoi: Animacja pokazująca
rozwiązanie łamigówki.
Rozwiązanie takiego problemu z wieżą o czterech dyskach można zobaczyć na animacji na rysunku
Figure 3.
Na początku należy przenieść najszerszy dysk — pomarańczowy, ze słupka A na słupek C. W
tym celu:
czerwony na B,
żółty na C,
czerwony na C,
niebieski na B,
czerwony na A,
żółty na B,
czerwony na B.
Uwolniony pomarańczowy jest na słupku A. Słupek C jest wolny. Pomarańczowy przechodzi na
C.
Teraz należy przenieść niebieski na C.
czerwony na C,
żółty na A,
czerwony na A.
Uwolniony niebieski jest na słupku B. Na słupku C jest wyłącznie pomarańczowy. Niebieski
przechodzi na C.
Teraz należy przenieść żółty na C.
czerwony na B.
Żółty jest wolny na A, można go przenieść na C.
Czerwony zostaje przeniesiony na C.
Łamigłówkę udało się rozwiązać.
Algorytm iteracyjny
1. W przypadku parzystej liczby krążków:
wykonaj dozwolone przeniesienie pomiędzy słupkami A i B,
wykonaj dozwolone przeniesienie pomiędzy słupkami A i C,
wykonaj dozwolone przeniesienie pomiędzy słupkami B i C,
powtarzaj, aż przeniesiesz wszystkie.
2. W przypadku nieparzystej liczby krążków:
wykonaj dozwolone przeniesienie pomiędzy słupkami A i C,
wykonaj dozwolone przeniesienie pomiędzy słupkami A i B,
wykonaj dozwolone przeniesienie pomiędzy słupkami B i C,
powtarzaj, aż przeniesiesz wszystkie.
Wykonano
ruchów.
Jak widać opis rekurencyjny jest prostszy niż iteracyjny.
Zadanie
Napisz program, który implementuje algorytm iteracyjny i rekurencyjny. Załóż, że słupki A, B i C są
listami. Lista A jest wypełniona "dyskami" — liczbami albo słowami, listy B i C są puste. Sprawdź
swój program na dwóch zestawach danych:
A1 =['duzy', 'sredni', 'maly']
A2 =['bardzo duzy', 'duzy', 'sredni', 'maly', 'bardzo maly']
Zadania różne
Rozwiąż następujące zadania
Wyznacznik macierzy (obliczany z definicji)
Napisz funkcję
Niepoprawny język.
Musisz wybrać język w następujący sposób: <source lang="html4strict">...</source>
Języki obsługiwane w podświetlaniu składni:
4cs, 6502acme, 6502kickass, 6502tasm, 68000devpac, abap, actionscript,
actionscript3, ada, algol68, apache, applescript, arm, asm, asp, asymptote,
autoconf, autohotkey, autoit, avisynth, awk, bascomavr, bash, basic4gl, bf,
bibtex, blitzbasic, bnf, boo, c, caddcl, cadlisp, cfdg, cfm, chaiscript, cil,
clojure, cmake, cobol, coffeescript, cpp, csharp, css, cuesheet, d, dcl, dcpu16,
dcs, delphi, diff, div, dos, dot, e, ecmascript, eiffel, email, epc, erlang,
euphoria, f1, falcon, fo, fortran, freebasic, freeswitch, fsharp, gambas, gdb,
genero, genie, gettext, glsl, gml, gnuplot, go, groovy, gwbasic, haskell, haxe,
hicest, hq9plus, html4strict, html5, icon, idl, ini, inno, intercal, io, j, java,
java5, javascript, jquery, kixtart, klonec, klonecpp, latex, lb, ldif, lisp, llvm,
locobasic, logtalk, lolcode, lotusformulas, lotusscript, lscript, lsl2, lua,
m68k, magiksf, make, mapbasic, matlab, mirc, mmix, modula2, modula3, mpasm, mxml,
mysql, nagios, netrexx, newlisp, nsis, oberon2, objc, objeck, ocaml, octave, oobas,
oorexx, oracle11, oracle8, oxygene, oz, parasail, parigp, pascal, pcre, per, perl,
perl6, pf, php, pic16, pike, pixelbender, pli, plsql, postgresql, povray,
powerbuilder, powershell, proftpd, progress, prolog, properties, providex,
purebasic, pycon, pys60, python, q, qbasic, rails, rebol, reg, rexx, robots,
rpmspec, rsplus, ruby, sas, scala, scheme, scilab, sdlbasic, smalltalk, smarty,
spark, sparql, sql, stonescript, systemverilog, tcl, teraterm, text, thinbasic,
tsql, typoscript, unicon, upc, urbi, uscript, vala, vb, vbnet, vedit, verilog, vhdl,
vim, visualfoxpro, visualprolog, whitespace, whois, winbatch, xbasic, xml, xpp,
yaml, z80, zxbasic
def wyznacznik_1(macierz):
"Oblicza wyznacznik korzystając z definicji."
która oblicza wyznacznik macierzy macierzy korzystając z rekurencyjnej definicji wyznacznika.
Przypomnienie: jeśli
gdzie jest dowolne, a
wiersza oraz kolumny .
jest macierzą kwadratową o wymiarze , to jej wyznacznik wynosi
oznacza macierz o wymiarze zmniejszonym o 1 przez wykasowanie
Sprawdź swoją funkcję na macierzy jednostkowej różnych rozmiarów, na macierzy wypełnionej
jedynkami lub zerami i na paru małych macierzach dla których jesteś w stanie samemu policzyć
wynik.
Uwaga o funkcjach rekurencyjnych
Funkcje mogą wywoływać same siebie. Funkcja f może też wywoływać inne funkcje, które wywołają
f jeszcze raz. Tak czy inaczej, w pewnym momencie, funkcja f jest wykonywana jednocześnie dwa
(lub więcej) razy. W momencie wywołania przez funkcję f funkcji potomnej, to wykonanie zostaje
zawieszone — stan zmiennych i miejsce wykonywania jest zapamiętane. Funkcja zostaje wznowiona,
kiedy funkcja potomna zakończy działanie.
Z rekurencyjnym wywoływaniem funkcji nie należy przesadzać. Np. obliczenie miliona liczb
Fibonnaciego z definicji — w sposób rekurencyjny — raczej nie jest dobrym pomysłem. Obliczanie ich
w pętli jest dużo, dużo szybsze. Niemniej, funkcje rekurencyjne są zazwyczaj bardzo czytelne i
podobne do definicji matematycznych.
Macierz odwrotna
Jeżeli wyznacznik macierzy M jest niezerowy (macierz jest niezdegenerowana), można pokusić się o
znalezienie macierzy odwrotnej.
Macierz
jest macierzą dołączoną do macierzy A.
Macierz dołączona powstaje po transponowaniu macierzy dopełnień algebraicznych.
Element
macierzy dopełnień algebraicznych to wyznacznik macierzy powstałej z
skreślenie jej -tego wiersza i -tej kolumny, pomnożony przez
.
Napisz program, który:
1.
2.
3.
4.
wczyta macierz;
sprawdzi, czy nie jest osobliwa;
jeżeli nie jest osobliwa, obliczy macierz odwrotną;
i sprawdzi, czy iloczyn macierzy i macierzy odwrotnej daje macierz .
poprzez

Podobne dokumenty