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

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Zadania na rekurencje wpisane jakoś
Poprawienie formatowania
Linia 1: Linia 1:
To są ćwiczenia z rekurencji.
To są ćwiczenia z rekurencji.


==Zadanie 1 (Labirynt)==
==Zadanie 1 (Labirynt)==
Linia 44: Linia 43:
'''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
W obu wariantach potrzebne jest markowanie (przez negację).  W pierwszym zapobiega zapętleniu, w drugim czasowi wykładniczemu. Znów potrzebna jest funkcja otoczka.
potrzebna jest funkcja otoczka.
</div>
</div>
</div>
</div>
Linia 51: Linia 49:
==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ą. Zapisac 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">
'''Wskazówka 1'''
<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).
# Kiedy to zadanie ma jeszcze rozwiązanie (gdy na każdą wieżę i z każdej wieży istnieje co najmniej jeden dozwolony ruch).
# Jak reprezentować dozwolone ruchy (TDozwRuchow = array[1..3, 1..3] of boolean, przekątna nie ma znaczenia).
# 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 class="mw-collapsible mw-made=collapsible mw-collapsed">
'''Rozwiązanie 1'''
<div class="mw-collapsible-content" style="display:none">
type Paleczki = 1..3;
procedure Hanoi(Ile: integer; Skad, Dokad: Paleczki; Dozw: TDozwRuchow);
    {...}
  procedure Rob(Skad, Dokad, Pom: Paleczki; Ile: Integer);
  begin
    {Pom oczywiscie jest rowne 6 - skad - dokad, ale chyba z parametrem jest czytelniej}
    if Ile > 0 then
      if Dozw[Skad, Dokad] then begin
        Rob(Skad, Pom, Dokad, Ile-1);
        Przenies(Skad,Dokad);
        Rob(Pom, Dokad, Skad, Ile-1);
      end
      else begin
        Rob(Skad, Dokad, Pom, Ile-1);
        Przenies(Skad, Pom);
        Rob(Dokad, Skad, Ile-1);
        Przenies(Pom, Dokad);
        Rob(Skad, Dokad, Ile-1);
      end
  end; {Rob}
begin {Hanoi}
  ...
end; {Hanoi}
</div>
</div>


Uwagi:
==Zadanie 4 (Ustawianie hetmanów)==
1) Duzo latwiej rozwiazac to zadanie przy powyzszym sformułowaniu, niz
Napisz procedurę znajdująca wszystkie takie rozstawienia 8 hetmanów na szachownicy, by żadne dwa hetmany się nie atakowały.
gdy powie
    sie ze zabroniony jest konkretny ruch (np. 1-> 2).
2) Kiedy to zadanie ma jeszcze rozwiazanie (gdy na kazda wieze i z
kazdej wiezy
    istnieje co najmniej jeden dozwolony ruch).
3) Jak reprezentowac dozwolone ruchy (TDozwRuchow = array[1..3, 1..3] of
boolean,
    przekatna nie ma znaczenia).
4) Znow konieczna jest otoczka (dostarcza srodowisko pamietajace
dozwolone ruchy,
    sprawdza czy w ogole istnieje rozwiazanie).
5) Algorytm:


    type Paleczki = 1..3;
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
'''Wskazówka 1'''
<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.  


    procedure Hanoi(Ile: integer; Skad, Dokad: Paleczki; Dozw:
Oczywiście lepiej uogólnić na N hetmanów na szachownicy N&times;N.
TDozwRuchow);
</div>
      {...}
</div>
      procedure Rob(Skad, Dokad, Pom: Paleczki; Ile: Integer);
      begin
        {Pom oczywiscie jest rowne 6 - skad - dokad, ale chyba z
parametrem jest czytelniej}
        if Ile > 0 then
        begin
          if Dozw[Skad, Dokad]
          then
            begin
                Rob(Skad, Pom, Dokad, Ile-1);
                Przenies(Skad,Dokad);
                Rob(Pom, Dokad, Skad, Ile-1);
            end
          else
            begin
                Rob(Skad, Dokad, Pom, Ile-1);
                Przenies(Skad, Pom);
                Rob(Dokad, Skad, Ile-1);
                Przenies(Pom, Dokad);
                Rob(Skad, Dokad, Ile-1);
            end
      end;
  begin {Otoczka}
    ...
  end;


6) Jaki przypadek jest najgorszy i ile trzeba wykonac ruchow (gdy
==Zadanie 5 (Mnożenie wielomianów stopnia n-1)==
przerwane jest polaczenie
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.
    Skad-Dokad w obie strony, 3^ile-1)


==Zadanie 4 (Nawroty)==
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
Napisz procedure znajdujaca wszystkie takie rozstawienia 8 hetmanow na
'''Wskazówka 1'''
szachownicy, by
<div class="mw-collapsible-content" style="display:none">
zadne dwa hetmany sie nie atakowaly.
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>
(rozwiazanie jest ladnie opisane w duzym Wirthcie 155-160, pamitamy w
<math>p(x) = p_l(x) + p_h(x)*x^{n/2}</math> i analogicznie  
tablicach logicznych
<math>q(x) = q_l(x) + q_h(x)*x^{n/2}</math>, to ich iloczyn można zapisać tak:<br>
zajetosc kolumn i obu rodzai przekatnych, kolejnego hetmana ustawiamy w
<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>
kolejnym
<math>r_l(x) = p_l(x)*q_l(x)</math><br>
wierszu jesli sie da, jesli nie wracamy). Oczywiscie lepiej uogolnic na
<math>r_h(x) = p_h(x)*q_h(x)</math><br>
N hetmanow na szachownicy
<math>r_m(x) = (p_h(x)+p_l(x))*(q_h(x)+q_l(x))</math><br>
N*N.
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.
 
 
==Zadanie 5 (Mnozenie wielomianow stopnia n-1)==
Dane sa dwie tablice (array[0..n-1] of real) reprezentujace dwa
wielomiany. Nalezy obliczyc iloczyn tych
wielomianow metoda dziel-i-zwyciezaj. Zakladamy, ze N jest potega
dwojki.
 
Rozw.
Zamiast rozwiazania naiwnego wymagajacego n^2 mnozen zrobmy troche
lepsze
(aczkolwiek nie najlepsze) polegajace na podzieleniu wielomianow na
dwie czesci. Caly pomysl polega na spostrzezeniu, ze jesli zadane
wielomiany p(x) i q(x) podzielimy na dolna i gorna czesc (p(x) =
p_l(x) + p_h(x)*x^(n/2) i analogicznie q(x)), to ich iloczyn mozna
zapisac tak:
r_l(x)+(r_m(x)-r_l(x)-r_h(x))*x^(n/2)+r_h(x)*x^n, gdzie
r_l(x) = p_l(x)*q_l(x)
r_h(x) = p_h(x)*q_h(x)
r_m(x) = (p_h(x)+p_l(x))*(q_h(x)+q_l(x))
Czyli potrzebujemy 3 mnozen o polowe mniejszych wielomianow. W sumie
uzyskamy liczbe mnozen rzedu n^lg3. Szczegoly mozna znalezc w Sedgewick
 
Algorithms in C++ 527-530 (lub tegoz autora Algorithms in *)
 
 
 
Uwagi ogolne:
- zwrocmy uwage na kolejnosc wyliczania wyrazen logicznych: standard
Pascala jej nie definiuje,
  zatem AND moze byc liczony tak, ze najpierw liczy sie _oba_ argumenty
a dopiero potem podaje wynik
  (nie mozna zakladac krotkiego liczenia od lewej do prawej). Przy
wywolaniach rekurencyjnych
  to wazne szczegolnie, bo nie chodzi tylko (czy az) o poprawnosc (if
(i>1) and (tab[i]<>x) jest bledem)
  ale o efektywnosc (if droga(i-1,j) or droga(i+1,j) or ... jest
nieefektywne).
- podkreslmy wage procedur otoczek: pozwalaja uzytkownikowi i nam
widziec nasze procedury z takimi parametrami
  jakie sa potrzebne.


Uwagi ogólne:
* zwróćmy uwagę na kolejność wyliczania wyrażeń logicznych: standard Pascala jej nie definiuje, zatem AND może być liczony tak, ze najpierw liczy sie _oba_ argumenty a dopiero potem podaje wynik (nie można zakładać krótkiego liczenia od lewej do prawej). Przy wywołaniach rekurencyjnych to ważne szczególnie, bo nie chodzi tylko (czy aż) o poprawność (if (i>1) and (tab[i]<>x) jest błędem) ale o efektywność (if droga(i-1,j) or droga(i+1,j) or ... jest nieefektywne).
* podkreślmy wagę procedur otoczek: pozwalają użytkownikowi i nam widzieć nasze procedury z takimi parametrami jakie są potrzebne.
</div>
</div>


==Zadanie 6 (Parser)==
==Zadanie 6 (Parser)==
Dla zadanej poniżej gramatyki języka programowania (będącego uproszczoną  
Dla zadanej poniżej gramatyki języka programowania (będącego
wersją Pascala) napisz metodą zejść rekurencyjnych zestaw procedur weryfikujących
uproszczoną  
wersją Pascala) napisz metodą zejść rekurencyjnych zestaw procedur
weryfikujących
poprawność programów zapisanych w tym języku. W przypadku napotkania
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ć
błędu należy wypisać stosowny komunikat i zakończyć działanie. Należy
istnienie procedury GetSym(Symbol), która daje kolejny symbol (identyfikator,
założyć
liczbę, operator (:=,<,>,<=,>=,=,<>),  separator (;, ., (, ) ) z wejścia. Należy także  
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.
zaprojektować odpowiedni typ danych do pamiętania symboli wejściowych.


Linia 175: Linia 146:
   ->                                              rozdziela lewą część produkcji od prawej
   ->                                              rozdziela lewą część produkcji od prawej
   |                                                rozdziela poszczególne produkcje
   |                                                rozdziela poszczególne produkcje
Jest to trochę roszerzona (o {}) gramatyka bezkontekstowa.
Jest to trochę rozszerzona (o {}) gramatyka bezkontekstowa.


<program> -> <dekl> begin <ciąg instrukcji> end .
<program> -> <dekl> begin <ciąg instrukcji> end .
Linia 187: Linia 158:
<warunek> -> <składnik> {and <składnik>}
<warunek> -> <składnik> {and <składnik>}
<składnik> -> <czynnik> {or <czynnik>}
<składnik> -> <czynnik> {or <czynnik>}
<czynnik> -> true | false | not <czynnik> | <wyrażenie> <oprel> <wyrażenie> | ( <warunek> )
<czynnik> -> true | false | not <czynnik> | <wyrażenie> <oprel>
<wyrażenie> | ( <warunek> )
<wyrażenie> -> <zmienna> | <liczba>
<wyrażenie> -> <zmienna> | <liczba>
<oprel> -> < | <= | > | >= | <> | =
<oprel> -> < | <= | > | >= | <> | =

Wersja z 17:31, 11 lip 2006

To są ćwiczenia z rekurencji.

Zadanie 1 (Labirynt)

Czy istnieje droga miedzy wskazanymi punktami (x1,y1) i (x2,y2) w labiryncie reprezentowanym przez prostokątną tablicę liczb całkowitych A o rozmiarze n×m, zawierająca zera (droga) i jedynki (ściana)?

Wskazówka 1

Zadanie 2 (Z górki na pazurki)

W tablicy liczb całkowitych o rozmiarze n×m zapisana jest mapa gór (każdy punkt ma podaną dodatnią wysokość). Sprawdzić, czy da się przejść z punktu startowego do docelowego idąc:

  • tylko w dół lub po płaskim
  • tylko w dół

Wskazówka 1

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

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

Zadanie 5 (Mnożenie wielomianów stopnia n-1)

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.

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. 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.

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 {}) gramatyka bezkontekstowa.

<program> -> <dekl> 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> {and <składnik>} <składnik> -> <czynnik> {or <czynnik>} <czynnik> -> true | false | not <czynnik> | <wyrażenie> <oprel> <wyrażenie> | ( <warunek> ) <wyrażenie> -> <zmienna> | <liczba> <oprel> -> < | <= | > | >= | <> | =


Rozw.

W tym zadaniu chodzi o przećwiczenie metody zejść rekurencyjnych (była na wykładzie). Niestety
na razie nie mamy dostatecznie ciekawych typów danych, żeby móc zapamiętać strukturę danych
programu (i np. po tem go wykonywać). Tym nie mniej zadanie jest pouczające. Typ danych który
ma być wymyślony to rekord z wariantami. Można uatrakcyjnić to zadanie każąc sprawdzać, czy każda
użyta zmienna była wcześniej zadeklarowana i czy nie była deklarowana kilkakrotnie (tablica
napisów).