Wstęp do programowania / Ćwiczenia 2: Różnice pomiędzy wersjami
Poczatek programu |
Do przodu... |
||
Linia 3: | Linia 3: | ||
==Zadanie 1 (Labirynt)== | ==Zadanie 1 (Labirynt)== | ||
Czy istnieje | Czy istnieje ścieżka miedzy wskazanymi punktami (i1,j1) i (i2,j2) w labiryncie reprezentowanym przez prostokątną tablicę liczb całkowitych o rozmiarze M×N, zawierającą zera (ściana) i jedynki (droga)? Zakładamy, że nie można przechodzić z pola na pole po skosie (np. z (2,5) na (3,6)), a tylko w czterech podstawowych kierunkach (np. z (2,5) na (3,5), (2,4) itd.) | ||
tablicę liczb całkowitych | |||
(ściana) i jedynki (droga)? | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"> | ||
'''Wskazówka 1''' | '''Wskazówka 1''' | ||
<div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible-content" style="display:none"> | ||
Właściwą funkcję rekurencyjną szukającą drogi zamknij w funkcji-otoczce, która poza wywołaniem funkcji rekurencyjnej sprawdzi poprawność argumentów, przygotuje i/lub posprząta tablicę z labiryntem, zinterpretuje wynik funkcji rekurencyjnej itp. oraz będzie przechowywać dane wspólne dla wszystkich wywołań funkcji rekurencyjnej. | |||
</div> | </div> | ||
</div> | </div> | ||
Linia 34: | Linia 15: | ||
'''Rozwiązanie 1''' | '''Rozwiązanie 1''' | ||
<div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible-content" style="display:none"> | ||
function Labirynt( | '''function''' Labirynt(M,N:integer; '''var''' A:'''array'''[1..M,1..N] '''of''' integer; i1,j1,i2,j2:integer):boolean; | ||
// M,N >= 1 | |||
// A zawiera 0 (ściana) i 1 (droga) | |||
'''function''' szukaj(i,j:integer):boolean; | |||
begin | // 1 <= i <= M | ||
if ( | // 1 <= j <= N | ||
'''var''' jest:boolean; | |||
else begin | '''begin''' | ||
szukaj:=false | '''if''' (i=i2) '''and''' (j=j2) '''then''' | ||
jest:=true | |||
'''else''' '''begin''' | |||
jest:=false; | |||
'''if''' A[i,j]>0 '''then''' '''begin''' | |||
A[i,j]:=-1; | |||
'''if''' i>1 '''then''' jest:=szukaj(i-1,j); | |||
'''if''' '''not''' jest '''and''' (i<M) '''then''' jest:=szukaj(i+1,j); | |||
'''if''' '''not''' jest '''and''' (j>1) '''then''' jest:=szukaj(i,j-1); | |||
'''if''' '''not''' jest '''and''' (j<N) '''then''' jest:=szukaj(i,j+1); | |||
'''end''' | |||
'''end'''; | |||
szukaj:=jest | |||
'''end'''; // szukaj | |||
'''var''' i,j:integer; | |||
'''begin''' // Labirynt | |||
'''if''' '''not'''((1<=i1) '''and''' (i1<=M) '''and''' (1<=j1) '''and''' (j1<=N) '''and''' | |||
(1<=i2) '''and''' (i2<=M) '''and''' (1<=j2) '''and''' (j2<=N)) '''then''' | |||
Labirynt:=false | |||
'''else''' | |||
'''begin''' | |||
Labirynt:=szukaj(i1,j1); | |||
'''for''' i:=1 '''to''' M '''do''' | |||
'''for''' j:=1 '''to''' N '''do''' | |||
'''if''' A[i,j]=-1 '''then''' A[i,j]:=1 | |||
'''end''' | |||
'''end'''; // Labirynt | |||
''Koszt czasowy:'' liniowy względem rozmiaru A (czyli M×N) | |||
''Koszt pamięciowy:'' liniowy względem rozmiaru A | |||
''Opis:'' [[Przeszukujemy wgłąb]] tablicę zaznaczając odwiedzone pola poprzez ustawienie ich wartości na -1. Dzięki temu nie zapętlimy się, ani nie przetwarzamy wielokrotnie tych samych pól. | |||
''Dyskusja:'' Funkcja 'Labirynt' jest otoczką, która sprawdza poprawność parametrów, wywołuje właściwą funkcję rekurencyjną 'szukaj' oraz sprząta po niej zaznaczenia w tablicy A. | |||
Wewnętrzna funkcja rekurencyjna 'szukaj' ma za zadanie znaleźć ścieżkę z punktu (i,j) do punktu (i2,j2) po niezaznaczonych polach w tablicy A. Korzysta ona z kilku nazw zadeklarowanych przez otoczkę (M, N, A, i2, j2), dlatego nie są one parametrami wywołania funkcji 'szukaj'. | |||
Funkcja 'szukaj': | |||
# sprawdza czy punkt (i,j) nie jest poszukiwanym końcem (w tym przypadku zwraca true), | |||
# sprawdza czy punkt (i,j) nie jest ścianą lub punktem odwiedzonym wcześniej (w tym przypadku zwraca false), | |||
# zaznacza punkt (i,j) jako odwiedzony | |||
# próbuje szukać drogi z punktów sąsiednich dla punktu (i,j), o ile sąsiedzi Ci istnieją. | |||
Zauważ, że testy 1 i 2, mogłyby być wykonane przed każdym wywołaniem funkcji 'szukaj' w punkcie 4 (oraz w otoczce) zamiast na jej początku, jednak prowadziłoby to do wielokrotnego pisania bardzo podobnych fragmentów programu, co łatwo prowadzi do błędów. Oczywiście nie zmniejszyłoby to złożoności czasowej i pamięciowej. | |||
Podobnie, jak testy 1 i 2, testy istnienia sąsiadów (i>1), (i<M) itp. w punkcie 4 mogłyby być wykonane na początku funkcji 'szukaj' (wtedy nie zakładalibyśmy, że (i,j) jest poprawnym punktem w tablicy), ale nie mając wiedzy w którą stronę ostatnio poszliśmy, musielibyśmy dla sprawdzić pełną poprawność obu współrzędnych (i,j), czyli w sumie sprawdzalibyśmy 4 warunki. W aktualnej wersji sprawdzamy tylko 1 warunek. | |||
''Poprawność rozwiązania:'' Oczywiste jest, że jeśli funkcja 'Labirynt' da wynik true, to ścieżka z (i1,j1) do (i2,j2) istnieje. Mniej oczywiste jest, że jeśli funkcja da wynik false, to ścieżka na pewno nie istnieje. | |||
Aby przeprowadzić dowód przez sprzeczność, załóżmy, że funkcja 'szukaj' wywołana w funkcji 'Labirynt' dała wynik false, a ścieżka z (i1,j1) do (i2,j2) istnieje. W takim razie A[i1,j1]=-1 a A[i2,j2]=1. Wynika z tego, że ścieżka z (i1,j1) do (i2,j2) w którymś miejścu ''opuszcza'' zaznaczoną część tablicy, czyli istnieją dwa sąsiednie pola (i,j) i (i',j') na tej ścieżce, takie że A[i,j]=-1, A[i',j']=1. Z tego wynika, że funkcja szukaj została (w czasie działania programu) wywołana z parametrami (i,j), ale nie została wywołana z parametrami (i',j'). Jest to niemożliwe, bo pola te sąsiadują ze sobą i wywołanie dla (i,j) wywołałoby rekurencyjnie 'szukaj' dla (i',j'). | |||
</div> | |||
</div> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
'''Pytanko 1:''' | |||
<div class="mw-collapsible-content" style="display:none"> Ile razy maksymalnie może być wywołana funkcja 'szukaj' dla tego samego punktu? | |||
</div> | |||
</div> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
'''Pytanko 2:''' | |||
<div class="mw-collapsible-content" style="display:none"> Jak przerobić przedstawione rozwiązanie na program wypisujący na ekran ścieżkę pomiędzy punktami (i1,j1) i (i2,j2), o ile taka ścieżka istnieje. Czy wypisana ścieżka będzie najkrótsza z możliwych ? | |||
</div> | |||
</div> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
'''Pytanko 3:''' (trudniejsze) | |||
<div class="mw-collapsible-content" style="display:none"> | |||
Co by się stało, gdybyśmy usuwali zaznaczenie wychodząc z funkcji 'szukaj'? Czy program dalej by działał? Jeśli tak, to jaką by miał złożoność? | |||
</div> | |||
</div> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
'''Odpowiedź:''' | |||
<div class="mw-collapsible-content" style="display:none"> | |||
Program dalej by działał, ale miałby wykładniczą złożoność czasową zamiast liniowej (złożoność pamięciowa pozostałaby liniowa). W tej wersji program wielokrotnie przeszukiwałby sąsiadów pól, o których już z wcześniejszych obliczeń wiadomo, że nie da się z nich dojść do punktu końcowego. | |||
</div> | |||
</div> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
'''Dla ciekawskich:''' | |||
<div class="mw-collapsible-content" style="display:none"> | |||
Rozwiązanie przedstawione powyżej polega na [[przeszukiwaniu wgłąb]] grafu reprezentującego labirynt. Inne, równie dobre rozwiązanie polega na przeszukiwaniu tego grafu [[wszerz]], zwanego również [[metodą płonącego lasu]]. Wymaga ono użycia struktury danych -- [[kolejki]]. Rozwiązanie to łatwo przerobić na program wypisujący najkrótszą ścieżkę pomiędzy punktami (i1,j1) i (i2,j2), o ile taka ścieżka istnieje. | |||
</div> | </div> | ||
</div> | </div> | ||
Linia 53: | Linia 112: | ||
==Zadanie 2 (Z górki na pazurki)== | ==Zadanie 2 (Z górki na pazurki)== | ||
W tablicy liczb całkowitych o rozmiarze | W tablicy liczb całkowitych o rozmiarze M×N zapisana jest mapa | ||
gór (każdy punkt ma podaną dodatnią wysokość). Sprawdzić, czy da się | gór (każdy punkt ma podaną dodatnią wysokość). Sprawdzić, czy da się | ||
przejść z punktu startowego do docelowego idąc: | przejść z punktu startowego (i1,j1) do docelowego (i2,j2) idąc: | ||
* tylko w dół lub po płaskim | * tylko w dół lub po płaskim | ||
* tylko w dół | * tylko w dół | ||
Tak jak w poprzednim zadaniu poruszać się można tylko w czterech kierunkach podstawowych, nie po przekątnej. | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"> | ||
'''Wskazówka 1''' | '''Wskazówka 1''' | ||
<div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible-content" style="display:none"> | ||
Zadanie to należy rozwiązywać tak jak poprzednie. Jedyną różnicą jest to jak należy decydować czy z danego pola można przejść do sąsiedniego: oprócz zaznaczenia trzeba wziąć pod uwagę różnicę wysokości. | |||
</div> | |||
</div> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
'''Rozwiązanie 1''' | |||
<div class="mw-collapsible-content" style="display:none"> | |||
'''function''' Zjazd1(M,N:integer; '''var''' A:'''array'''[1..M,1..N] '''of''' integer; i1,j1,i2,j2:integer):boolean; | |||
// M,N >= 1 | |||
// A zawiera liczby > 0 | |||
'''function''' szukaj(h,i,j:integer):boolean; | |||
// 0 < h | |||
// 1 <= i <= M | |||
// 1 <= j <= N | |||
'''var''' jest:boolean; | |||
hpom:integer; | |||
'''begin''' | |||
jest:=false; | |||
'''if''' (A[i,j]>0) '''and''' (h>A[i,j]) '''begin''' // h>=A[i,j] w wariancie II | |||
'''if''' (i=i2) '''and''' (j=j2) '''then''' | |||
jest:=true | |||
'''else''' '''begin''' | |||
hpom:=A[i,j]; | |||
A[i,j]:=-A[i,j]; | |||
'''if''' i>1 '''then''' jest:=szukaj(hpom,i-1,j); | |||
'''if''' '''not''' jest '''and''' (i<M) '''then''' jest:=szukaj(hpom,i+1,j); | |||
'''if''' '''not''' jest '''and''' (j>1) '''then''' jest:=szukaj(hpom,i,j-1); | |||
'''if''' '''not''' jest '''and''' (j<N) '''then''' jest:=szukaj(hpom,i,j+1); | |||
'''end''' | |||
'''end'''; | |||
szukaj:=jest | |||
'''end'''; // szukaj | |||
'''var''' i,j:integer; | |||
'''begin''' // Zjazd1 | |||
'''if''' '''not'''((1<=i1) '''and''' (i1<=M) '''and''' (1<=j1) '''and''' (j1<=N) '''and''' | |||
(1<=i2) '''and''' (i2<=M) '''and''' (1<=j2) '''and''' (j2<=N)) '''then''' | |||
Zajzd:=false | |||
'''else''' | |||
'''begin''' | |||
Zjazd:=szukaj(A[i1,j1]+1,i1,j1); // wystarczy szukaj(A[i1,j1],i1,j1) w wariancie II | |||
'''for''' i:=1 '''to''' M '''do''' | |||
'''for''' j:=1 '''to''' N '''do''' | |||
'''if''' A[i,j]<0 '''then''' A[i,j]:=-A[i,j] | |||
'''end''' | |||
'''end'''; // Zjazd1 | |||
''Koszt czasowy:'' liniowy względem rozmiaru A (czyli M×N) | |||
''Koszt pamięciowy:'' liniowy względem rozmiaru A | |||
''Opis:'' Podobnie jak w poprzednim zadaniu, przeszukujemy tablicę [[wgłąb]], jednak przechodzimy tylko do tych pól sąsiednich, które mają mniejszą (odpowiednio: mniejszą lub równą) wysokość. | |||
''Dyskusja:'' Tak jak w rozwiązaniu poprzedniego zadania, istnienie sąsiada na planszy badamy przed wywołaniem rekurencyjnym, a poprawność przejścia na początku wywołania. Służy nam do tego dodatkowy parametr 'h', który oznacza wysokość poprzedniego pola. Dlatego pierwsze wywołanie funkcji 'szukaj' ma pierwszy parametr A[i1,j1]+1 (w wariancie I). '''Uwaga!''' Funkcja ta nie zadziała, jeśli A[i1,j1]=MaxInt (czyli maksymalnej możliwej wartości typu integer). Poniżej przedstawione jest rozwiązanie bez tego mankamentu. | |||
</div> | |||
</div> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
'''Rozwiązanie 2''' | |||
<div class="mw-collapsible-content" style="display:none"> | |||
'''function''' Zjazd2(M,N:integer; '''var''' A:'''array'''[1..M,1..N] '''of''' integer; i1,j1,i2,j2:integer):boolean; | |||
// M,N >= 1 | |||
// A zawiera liczby > 0 | |||
'''function''' wdol(h,i,j:integer):boolean; | |||
// sprawdza czy pole (i,j) nie jest zaznaczone i | |||
// czy przejscie z pola o wysokosci h na pole (i,j) jest rzeczywiscie w dół | |||
// 1 <= i <= M | |||
// 1 <= j <= N | |||
'''begin''' | |||
wdol:=(A[i,j]>0) '''and''' (h>A[i,j]) // h>=A[i,j] w wariancie II | |||
'''end'''; //wdol | |||
'''function''' szukaj(i,j:integer):boolean; | |||
// 1 <= i <= M | |||
// 1 <= j <= N | |||
'''var''' jest:boolean; | |||
hpom:integer; | |||
'''begin''' | |||
'''if''' (i=i2) '''and''' (j=j2) '''then''' | |||
jest:=true | |||
'''else''' '''begin''' | |||
jest:=false; | |||
hpom:=A[i,j]; | |||
A[i,j]:=-hpom; | |||
'''if''' i>1 '''then''' '''if''' wdol(hpom,i-1,j) '''then''' jest:=szukaj(i-1,j); | |||
'''if''' '''not''' jest '''and''' (i<M) '''then''' '''if''' wdol(hpom,i+1,j) '''then''' jest:=szukaj(i+1,j); | |||
'''if''' '''not''' jest '''and''' (j>1) '''then''' '''if''' wdol(hpom,i,j-1) '''then''' jest:=szukaj(i,j-1); | |||
'''if''' '''not''' jest '''and''' (j<N) '''then''' '''if''' wdol(hpom,i,j+1) '''then''' jest:=szukaj(i,j+1); | |||
'''end'''; | |||
szukaj:=jest | |||
'''end'''; // szukaj | |||
'''var''' i,j:integer; | |||
'''begin''' // Zjazd2 | |||
'''if''' '''not'''((1<=i1) '''and''' (i1<=M) '''and''' (1<=j1) '''and''' (j1<=N) '''and''' | |||
(1<=i2) '''and''' (i2<=M) '''and''' (1<=j2) '''and''' (j2<=N)) '''then''' | |||
Zajzd:=false | |||
'''else''' | |||
'''begin''' | |||
Zjazd:=szukaj(i1,j1); | |||
'''for''' i:=1 '''to''' M '''do''' | |||
'''for''' j:=1 '''to''' N '''do''' | |||
'''if''' A[i,j]<0 '''then''' A[i,j]:=-A[i,j] | |||
'''end''' | |||
'''end'''; // Zjazd2 | |||
''Koszt czasowy:'' liniowy względem rozmiaru A (czyli M×N) | |||
''Koszt pamięciowy:'' liniowy względem rozmiaru A | |||
''Opis:'' Rozwiązanie jest bardzo podobne do poprzedniego. Różnica polega na przeniesieniu kodu z początku funkcji 'szukaj' do osobnej funkcji 'wdol'. Funkcja ta jest używana jako test dopuszczający do rekurencyjnego wywołania funkcji 'szukaj'. | |||
</div> | </div> | ||
</div> | </div> | ||
Linia 68: | Linia 240: | ||
==Zadanie 3 (Wieże Hanoi z ograniczeniami)== | ==Zadanie 3 (Wieże Hanoi z ograniczeniami)== | ||
Na wykładzie były wieże Hanoi. Ciekawa modyfikacja tego zadania polega na zabronieniu ruchów pomiędzy niektórymi pałeczkami, np. z pierwszej na drugą. Zapisać procedurę realizującą to zadanie przy zabronionych niektórych ruchach. | Na wykładzie były [[wieże Hanoi]]. Ciekawa modyfikacja tego zadania polega na zabronieniu ruchów pomiędzy niektórymi pałeczkami, np. z pierwszej na drugą. Zapisać procedurę realizującą to zadanie przy zabronionych niektórych ruchach. | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"> | ||
'''Wskazówka 1''' | '''Wskazówka 1''' | ||
<div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible-content" style="display:none"> | ||
* Dozwolone ruchy najlepiej reprezentować w tablicy typu TDozwRuchow = array[1..3, 1..3] of boolean, (przekątna nie ma znaczenia). | |||
* Zastanów się, jaki warunek musi być spełniony przez tablicę dozwolonych ruchów, aby zadanie miało rozwiązanie. | |||
* Użyj funkcji-otoczki do wstępnego sprawdzenia czy istnieje rozwiązanie oraz do pamiętania tablicy dozwolonych ruchów w czasie działania funkcji rekurencyjnej. | |||
</div> | </div> | ||
</div> | </div> | ||
<!-- | |||
procedure Przenies(Skad, Dokad: Paleczki); | |||
begin | |||
writeln('Przenoszę krążek z ',Skad',' na ',Dokad); | |||
end; | |||
--> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"> | ||
'''Rozwiązanie 1''' | '''Rozwiązanie 1''' | ||
<div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible-content" style="display:none"> | ||
type Paleczki = 1..3; | '''type''' Paleczki = 1..3; | ||
'''procedure''' Hanoi(Ile: integer; Skad, Dokad: Paleczki; Dozw: TDozwRuchow); | |||
'''procedure''' Rob(Skad, Dokad, Pom: Paleczki; Ile: Integer); | |||
// Pom jest zawsze rowne 6-Skad-Dokad, ale z parametrem jest czytelniej | |||
procedure Rob(Skad, Dokad, Pom: Paleczki; Ile: Integer); | '''begin''' | ||
'''if''' Ile > 0 '''then''' | |||
'''if''' Dozw[Skad, Dokad] '''then''' '''begin''' | |||
if Ile > 0 then | |||
if Dozw[Skad, Dokad] then begin | |||
Rob(Skad, Pom, Dokad, Ile-1); | Rob(Skad, Pom, Dokad, Ile-1); | ||
Przenies(Skad,Dokad); | Przenies(Skad,Dokad); | ||
Rob(Pom, Dokad, Skad, Ile-1); | Rob(Pom, Dokad, Skad, Ile-1); | ||
end | '''end''' | ||
else begin | '''else''' '''begin''' | ||
Rob(Skad, Dokad, Pom, Ile-1); | Rob(Skad, Dokad, Pom, Ile-1); | ||
Przenies(Skad, Pom); | Przenies(Skad, Pom); | ||
Rob(Dokad, Skad, Ile-1); | Rob(Dokad, Skad, Pom, Ile-1); | ||
Przenies(Pom, Dokad); | Przenies(Pom, Dokad); | ||
Rob(Skad, Dokad, Ile-1); | Rob(Skad, Dokad, Pom, Ile-1); | ||
end | '''end''' | ||
end; | '''end'''; //Rob | ||
begin | |||
'''function''' CzyDaSie(Skad, Dokad, Pom: Paleczki):boolean | |||
end; { | '''begin''' | ||
'''if''' '''not''' Dozw[Skad, Dokad] '''and''' '''not''' (Dozw[Skad, Pom] '''and''' Dozw[Pom, Dokad]) '''then''' '''begin''' | |||
writeln('Nie da sie przełożyć krążka z ',Skad,' na ',Pom); | |||
CzyDaSie:=false | |||
'''end''' | |||
'''else''' | |||
CzyDaSie:=false | |||
'''end''' // CzyDaSie | |||
'''var''' ok:boolean | |||
'''begin''' // Hanoi | |||
ok:=true; | |||
'''for''' i:=1 '''to''' 2 '''do''' '''for''' j:=i+1 '''to''' 3 '''do''' | |||
ok:=ok '''and''' CzyDaSie(i,j,6-i-j) '''and''' CzyDaSie(j,i,6-i-j); | |||
'''if''' ok '''then''' Rob(Skad,Dokad,6-Skad-Dokad,Ile); | |||
'''end'''; // Hanoi | |||
''Koszt czasowy:'' wykładniczy ze względu na Ile | |||
''Koszt pamięciowy:'' liniowy ze względu na Ile | |||
''Omówienie:'' Zauważ, że dużo łatwiej jest rozwiązać to zadanie sformułowane w sposób ogólny, niż gdy powie się, że zabroniony jest konkretny ruch (np. 1 → 2). | |||
</div> | |||
</div> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
'''Pytanko 1''' | |||
<div class="mw-collapsible-content" style="display:none"> | |||
Jaki przypadek jest najgorszy i ile trzeba wykonać wtedy ruchów? | |||
</div> | |||
</div> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
'''Odpowiedź''' | |||
<div class="mw-collapsible-content" style="display:none"> | |||
Najgorszy przypadek, to taki, gdy przerwane jest połączenie Skąd-Dokąd w obie strony. Wtedy trzeba wykonać <math>3^{Ile}-1</math> ruchów) | |||
</div> | </div> | ||
</div> | </div> | ||
Linia 116: | Linia 326: | ||
'''Wskazówka 1''' | '''Wskazówka 1''' | ||
<div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible-content" style="display:none"> | ||
W tablicach logicznych pamiętamy zajętość kolumn i obu rodzajówprzekątnych, kolejnego hetmana ustawiamy w kolejnym wierszu jeśli sięda, jeśli nie wracamy. | |||
Rozwiązanie jest ładnie opisane w [[dużym Wirthcie]], str. 155-160. | |||
Oczywiście lepiej uogólnić na N hetmanów na szachownicy N×N. | |||
</div> | |||
</div> | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
'''Rozwiązanie 1''' | |||
<div class="mw-collapsible-content" style="display:none"> | |||
</div> | </div> | ||
</div> | </div> | ||
==Zadanie 5 (Mnożenie wielomianów | ==Zadanie 5 (Mnożenie wielomianów)== | ||
Dane są dwie tablice (array[0.. | Dane są dwie tablice (array[0..N-1] of real) reprezentujące dwa wielomiany stopnia N-1. Należy obliczyć iloczyn tych wielomianów metodą dziel-i-zwyciężaj. Zakładamy, że N jest potęgą dwójki. | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"> | ||
Linia 130: | Linia 349: | ||
Zamiast rozwiązania naiwnego wymajającego <math>n^2</math> mnożeń zróbmy trochę | Zamiast rozwiązania naiwnego wymajającego <math>n^2</math> mnożeń zróbmy trochę | ||
lepsze (aczkolwiek nie najlepsze) polegające na podzieleniu wielomianów na dwie części. Cały pomysł polega na spostrzeżeniu, że jeśli zadane wielomiany <math>p(x)</math> i <math>q(x)</math> podzielimy na dolna i górną część<br> | lepsze (aczkolwiek nie najlepsze) polegające na podzieleniu wielomianów na dwie części. Cały pomysł polega na spostrzeżeniu, że jeśli zadane wielomiany <math>p(x)</math> i <math>q(x)</math> podzielimy na dolna i górną część<br> | ||
<math>p(x) = p_l(x) + p_h(x)*x^{ | <math>p(x) = p_l(x) + p_h(x)*x^{N/2}</math> i analogicznie | ||
<math>q(x) = q_l(x) + q_h(x)*x^{ | <math>q(x) = q_l(x) + q_h(x)*x^{N/2}</math>, to ich iloczyn można zapisać tak:<br> | ||
<math>p(x)*q(x) = r_l(x)+(r_m(x)-r_l(x)-r_h(x))*x^{n/2}+r_h(x)*x^ | <math>p(x)*q(x) = r_l(x)+(r_m(x)-r_l(x)-r_h(x))*x^{n/2}+r_h(x)*x^N</math>, gdzie<br> | ||
<math>r_l(x) = p_l(x)*q_l(x)</math><br> | <math>r_l(x) = p_l(x)*q_l(x)</math><br> | ||
<math>r_h(x) = p_h(x)*q_h(x)</math><br> | <math>r_h(x) = p_h(x)*q_h(x)</math><br> | ||
<math>r_m(x) = (p_h(x)+p_l(x))*(q_h(x)+q_l(x))</math><br> | <math>r_m(x) = (p_h(x)+p_l(x))*(q_h(x)+q_l(x))</math><br> | ||
Czyli potrzebujemy 3 mnożeń o | Czyli potrzebujemy 3 mnożeń o połowę mniejszych wielomianów. W sumie uzyskamy liczbę mnożeń rzędu <math>n^{lg 3}</math>. Szczególy można znaleźć w [[Sedgewick Algorithms in C++]], str. 527-530. | ||
Uwagi ogólne: | Uwagi ogólne: | ||
Linia 145: | Linia 364: | ||
==Zadanie 6 (Parser)== | ==Zadanie 6 (Parser)== | ||
Dla zadanej poniżej gramatyki języka programowania (będącego uproszczoną wersją Pascala) napisz metodą zejść rekurencyjnych zestaw procedur weryfikujących poprawność programów zapisanych w tym języku. W przypadku napotkania błędu należy wypisać stosowny komunikat i zakończyć działanie. Należy założyć istnienie procedury GetSym(Symbol), która daje kolejny symbol (identyfikator, liczbę, operator (:=,<,>,<=,>=,=,<>), separator (;, ., (, ) ) z wejścia | Dla zadanej poniżej gramatyki języka programowania (będącego uproszczoną wersją Pascala) napisz metodą zejść rekurencyjnych zestaw procedur weryfikujących poprawność programów zapisanych w tym języku. Należy sprawdzić poprawność programu względem podanej gramatyki oraz czy wszystkie zmienne występujące w programie są zadeklarowane i czy wszystkie zadeklarowane zmienne są użyte. Można założyć, że w całym programie nie wystąpi więcej niż Z zmiennych. W przypadku napotkania błędu należy wypisać stosowny komunikat i zakończyć działanie. | ||
Należy założyć istnienie procedury GetSym(Symbol), która daje kolejny symbol (identyfikator, liczbę, operator (:=,<,>,<=,>=,=,<>), separator (;, ., (, ) ) z wejścia. | |||
Gramatyka: | Gramatyka: | ||
Linia 181: | Linia 401: | ||
| || || | | <zmienna> := <wyrażenie> | | || || | | <zmienna> := <wyrażenie> | ||
|- | |- | ||
| <warunek> || → || <składnik> { | | <warunek> || → || <składnik> {or <składnik>} | ||
|- | |- | ||
| <składnik> || → || <czynnik> { | | <składnik> || → || <czynnik> {and <czynnik>} | ||
|- | |- | ||
| <czynnik> || → || | true | false | not <czynnik> | <wyrażenie> <oprel> <wyrażenie> | ( <warunek> ) | | <czynnik> || → || | true | false | not <czynnik> | <wyrażenie> <oprel> <wyrażenie> | ( <warunek> ) | ||
Linia 190: | Linia 410: | ||
|- | |- | ||
| <oprel> || → || | < | <= | > | >= | <> | = | | <oprel> || → || | < | <= | > | >= | <> | = | ||
|- | |||
| <zmienna> || → || ''ciąg liter'' | |||
|- | |||
| <liczba> || → || ''ciąg cyfr'' | |||
|} | |} | ||
Wersja z 21:31, 17 lip 2006
To są ćwiczenia z rekurencji.
Zadanie 1 (Labirynt)
Czy istnieje ścieżka miedzy wskazanymi punktami (i1,j1) i (i2,j2) w labiryncie reprezentowanym przez prostokątną tablicę liczb całkowitych o rozmiarze M×N, zawierającą zera (ściana) i jedynki (droga)? Zakładamy, że nie można przechodzić z pola na pole po skosie (np. z (2,5) na (3,6)), a tylko w czterech podstawowych kierunkach (np. z (2,5) na (3,5), (2,4) itd.)
Wskazówka 1
Rozwiązanie 1
Pytanko 1:
Pytanko 2:
Pytanko 3: (trudniejsze)
Odpowiedź:
Dla ciekawskich:
Zadanie 2 (Z górki na pazurki)
W tablicy liczb całkowitych o rozmiarze M×N zapisana jest mapa gór (każdy punkt ma podaną dodatnią wysokość). Sprawdzić, czy da się przejść z punktu startowego (i1,j1) do docelowego (i2,j2) idąc:
- tylko w dół lub po płaskim
- tylko w dół
Tak jak w poprzednim zadaniu poruszać się można tylko w czterech kierunkach podstawowych, nie po przekątnej.
Wskazówka 1
Rozwiązanie 1
Rozwiązanie 2
Zadanie 3 (Wieże Hanoi z ograniczeniami)
Na wykładzie były wieże Hanoi. Ciekawa modyfikacja tego zadania polega na zabronieniu ruchów pomiędzy niektórymi pałeczkami, np. z pierwszej na drugą. Zapisać procedurę realizującą to zadanie przy zabronionych niektórych ruchach.
Wskazówka 1
Rozwiązanie 1
Pytanko 1
Odpowiedź
Zadanie 4 (Ustawianie hetmanów)
Napisz procedurę znajdująca wszystkie takie rozstawienia 8 hetmanów na szachownicy, by żadne dwa hetmany się nie atakowały.
Wskazówka 1
Rozwiązanie 1
Zadanie 5 (Mnożenie wielomianów)
Dane są dwie tablice (array[0..N-1] of real) reprezentujące dwa wielomiany stopnia N-1. Należy obliczyć iloczyn tych wielomianów metodą dziel-i-zwyciężaj. Zakładamy, że N jest potęgą dwójki.
Wskazówka 1
Zadanie 6 (Parser)
Dla zadanej poniżej gramatyki języka programowania (będącego uproszczoną wersją Pascala) napisz metodą zejść rekurencyjnych zestaw procedur weryfikujących poprawność programów zapisanych w tym języku. Należy sprawdzić poprawność programu względem podanej gramatyki oraz czy wszystkie zmienne występujące w programie są zadeklarowane i czy wszystkie zadeklarowane zmienne są użyte. Można założyć, że w całym programie nie wystąpi więcej niż Z zmiennych. W przypadku napotkania błędu należy wypisać stosowny komunikat i zakończyć działanie. Należy założyć istnienie procedury GetSym(Symbol), która daje kolejny symbol (identyfikator, liczbę, operator (:=,<,>,<=,>=,=,<>), separator (;, ., (, ) ) z wejścia.
Gramatyka: W opisie gramatyki występują następujące symbole:
<nieterminal> | |
terminal | |
{ coś } | wielokrotne (być może 0-krotne) wystąpienie cosia |
→ | rozdziela lewą część produkcji od prawej |
| | rozdziela poszczególne produkcje |
Jest to trochę rozszerzona (o nawiasy klamrowe { } ) gramatyka bezkontekstowa.
<program> | → | <deklaracja> begin <ciąg instrukcji> end . |
<ciąg instrukcji> | → | <instrukcja> { ; <instrukcja> } |
<deklaracja> | → | var <zmienna> { , <zmienna> } ; |
<instrukcja> | → | while <warunek> do <instrukcja> |
| if <warunek> then <instrukcja> else <instrukcja> | ||
| begin <ciąg instrukcji> end | ||
| <zmienna> := <wyrażenie> | ||
<warunek> | → | <składnik> {or <składnik>} |
<składnik> | → | <czynnik> {and <czynnik>} |
<czynnik> | → | true | false | not <czynnik> | <wyrażenie> <oprel> <wyrażenie> | ( <warunek> ) |
<wyrażenie> | → | <zmienna> | <liczba> |
<oprel> | → | < | <= | > | >= | <> | = |
<zmienna> | → | ciąg liter |
<liczba> | → | ciąg cyfr |
Wskazówka 1