Wstęp do programowania / Ćwiczenia 2: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Daria (dyskusja | edycje)
Poczatek programu
Do przodu...
Linia 3: Linia 3:
==Zadanie 1 (Labirynt)==
==Zadanie 1 (Labirynt)==


Czy istnieje droga miedzy wskazanymi punktami <math>(x_1,y_1)</math> i
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.)
<math>(x_2,y_2)</math> w labiryncie reprezentowanym przez prostokątną
tablicę liczb całkowitych A o rozmiarze m&times;n, zawierająca zera
(ś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 rozwiązaniu pojawia się funkcja-otoczka, która jako parametry ma
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.
(przekazywane przez zmienną) tablicę i 4 liczby opisujące początkowy i
końcowy punkt drogi. Dzięki temu, że otoczka ma jako parametr tablicę,
właściwa funkcja rekurencyjna już go nie potrzebuje, nie potrzebuje
też parametrów określających punkt docelowy. Otoczka ponadto sprząta
po funkcji rekurencyjnej (usuwa markowanie z tablicy), sprawdza czy
nie startujemy/kończymy na ścianie lub poza tablicą.
 
Zapis rozwiązania bardzo się upraszcza jeśli założymy, że mamy ścianę dookoła labiryntu, jest to pewna forma strażnika.
 
Należy się też zastanowić, co by było, gdyby zamiast usuwać markowanie
na końcu w otoczce, usuwać je lokalnie, wychodząc z rekurencji (tzn.
gdy każde wywołanie startując markuje jedno pole i kończąc to pole
odmarkowuje) (dalej by działało, ale z wykładniczym czasem). Należy
wspomnieć o liniowym (wgl. wielkości danych, kwadratowym wzgl długości
boku labiryntu) czasie rozwiązania i o innych możliwych rozwiązaniach
(przeszukiwanie wszerz z kolejką).
</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(m,n:integer; A:array[1..n,1..m] of integer; x1,y1,x2,y2:integer):boolean);
  '''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(x,y:integer):boolean
  '''function''' szukaj(i,j:integer):boolean;
  begin
  // 1 <= i <= M
   if (x=x1) and (y=y1) then  
  // 1 <= j <= N
     szukaj:=true
  '''var''' jest:boolean;
   else begin
  '''begin'''
     szukaj:=false
    '''if''' (i=i2) '''and''' (j=j2) '''then'''
   
      jest:=true
  end
    '''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>


begin // Labirynt
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
 
'''Pytanko 3:''' (trudniejsze)
end;
<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 n&times;m zapisana jest mapa
W tablicy liczb całkowitych o rozmiarze M&times;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">
W obu wariantach potrzebne jest markowanie (przez negację).  W pierwszym zapobiega zapętleniu, w drugim czasowi wykładniczemu. Znów potrzebna jest funkcja otoczka.
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">
# Dużo łatwiej rozwiązać to zadanie przy powyższym sformułowaniu, niż gdy powie sie ze zabroniony jest konkretny ruch (np. 1-> 2).
* Dozwolone ruchy najlepiej reprezentować w tablicy typu TDozwRuchow = array[1..3, 1..3] of boolean, (przekątna nie ma znaczenia).
# Kiedy to zadanie ma jeszcze rozwiązanie (gdy na każdą wieżę i z każdej wieży istnieje co najmniej jeden dozwolony ruch).
* Zastanów się, jaki warunek musi być spełniony przez tablicę dozwolonych ruchów, aby zadanie miało rozwiązanie.
# Jak reprezentować dozwolone ruchy (TDozwRuchow = array[1..3, 1..3] of boolean, przekątna nie ma znaczenia).
* 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.
# Znów konieczna jest otoczka (dostarcza środowisko pamiętające dozwolone ruchy, czy w ogóle istnieje rozwiązanie).
# Jaki przypadek jest najgorszy i ile trzeba wykonac ruchów (gdy przerwane jest połączenie Skąd-Dokąd w obie strony: 3^ile-1)
</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 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'''
   begin
     '''if''' Ile > 0 '''then'''
    {Pom oczywiscie jest rowne 6 - skad - dokad, ale chyba z parametrem jest czytelniej}
       '''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; {Rob}
   '''end'''; //Rob
  begin {Hanoi}
   
   ...
  '''function''' CzyDaSie(Skad, Dokad, Pom: Paleczki):boolean
  end; {Hanoi}
  '''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">
Rozwiązanie jest ładnie opisane w dużym Wirthcie 155-160. W tablicach logicznych pamiętamy zajętość kolumn i obu rodzajów przekątnych, kolejnego hetmana ustawiamy w kolejnym wierszu jeśli się da, jeśli nie wracamy.  
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">


Oczywiście lepiej uogólnić na N hetmanów na szachownicy N&times;N.
</div>
</div>
</div>
</div>


==Zadanie 5 (Mnożenie wielomianów stopnia n-1)==
==Zadanie 5 (Mnożenie wielomianów)==
Dane są dwie tablice (array[0..n-1] of real) reprezentujące dwa wielomiany. Należy obliczyć iloczyn tych wielomianów metodą dziel-i-zwyciężaj. Zakładamy, że N jest potęgą dwójki.
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^{n/2}</math> i analogicznie  
<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^{n/2}</math>, to ich iloczyn można zapisać tak:<br>
<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^n</math>, gdzie<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^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 polowe mniejszych wielomianów. W sumie uzyskamy liczbę mnożeń rzędu <math>n^{lg 3}</math>. Szczególny można znaleźć w Sedgewick Algorithms in C++ 527-530.
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. Należy także  zaprojektować odpowiedni typ danych do pamiętania symboli wejściowych.
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> || &rarr; || <składnik> {and <składnik>}
| <warunek> || &rarr; || <składnik> {or <składnik>}
|-
|-
| <składnik> || &rarr; || <czynnik> {or <czynnik>}
| <składnik> || &rarr; || <czynnik> {and <czynnik>}
|-
|-
| <czynnik> || &rarr; || | true | false | not <czynnik> | <wyrażenie> <oprel> <wyrażenie> | ( <warunek> )
| <czynnik> || &rarr; || | true | false | not <czynnik> | <wyrażenie> <oprel> <wyrażenie> | ( <warunek> )
Linia 190: Linia 410:
|-
|-
| <oprel> || &rarr; || | < | <= | > | >= | <> | =
| <oprel> || &rarr; || | < | <= | > | >= | <> | =
|-
| <zmienna> || &rarr; || ''ciąg liter''
|-
| <liczba> || &rarr; || ''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