|
|
Linia 1: |
Linia 1: |
| {{powrot|WDP_Wprowadzenie_do_programowania|do modułu Wprowadzenie do programowania}} | | {{powrot|WDP_Wprowadzenie_do_programowania|do modułu Wprowadzenie do programowania}} |
|
| |
|
| Ta strona zawiera podstawowe zadania na tablice oraz rekurencję. | | Ta strona zawiera podstawowe zadania na tablice. |
|
| |
|
| <div class="mw-collapsible mw-made=collapsible mw-collapsed"> | | <div class="mw-collapsible mw-made=collapsible mw-collapsed"> |
Linia 1040: |
Linia 1040: |
| </div> | | </div> |
| </div>}} | | </div>}} |
|
| |
|
| |
| ==Zadanie 14 (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.)
| |
|
| |
| {{wskazowka| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><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>}}
| |
|
| |
| {{rozwiazanie| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none">
| |
| '''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;
| |
| // 1 <= i <= M
| |
| // 1 <= j <= N
| |
| '''var''' jest:boolean;
| |
| '''begin'''
| |
| '''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>}}
| |
|
| |
| {{cwiczenie| 1|pytanko 1|<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none">
| |
| Ile razy maksymalnie może być wywołana funkcja 'szukaj' dla tego samego punktu?
| |
| </div>
| |
| </div>}}
| |
|
| |
| {{cwiczenie| 2|pytanko 2|<div class="mw-collapsible mw-made=collapsible mw-collapsed"><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>}}
| |
|
| |
| {{cwiczenie| 3|pytanko 3|<div class="mw-collapsible mw-made=collapsible mw-collapsed"><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>}}
| |
|
| |
| {{odpowiedz||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><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>
| |
|
| |
| ==Zadanie 15 (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.
| |
|
| |
| {{wskazowka| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><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>}}
| |
|
| |
| {{rozwiazanie| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><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>}}
| |
|
| |
| {{rozwiazanie| 2||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><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>}}
| |
|
| |
| ==Zadanie 16 (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.
| |
|
| |
| {{wskazowka| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><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>}}
| |
|
| |
| <!--
| |
| procedure Przenies(Skad, Dokad: Paleczki);
| |
| begin
| |
| writeln('Przenoszę krążek z ',Skad',' na ',Dokad);
| |
| end;
| |
| -->
| |
| {{rozwiazanie| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><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);
| |
| // Pom jest zawsze rowne 6-Skad-Dokad, ale z parametrem jest czytelniej
| |
| '''begin'''
| |
| '''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, Pom, Ile-1);
| |
| Przenies(Pom, Dokad);
| |
| Rob(Skad, Dokad, Pom, Ile-1);
| |
| '''end'''
| |
| '''end'''; //Rob
| |
|
| |
| '''function''' CzyDaSie(Skad, Dokad, Pom: Paleczki):boolean
| |
| '''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>}}
| |
|
| |
| {{cwiczenie| 1|pytanko 1|<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none">
| |
| Jaki przypadek jest najgorszy i ile trzeba wykonać wtedy ruchów?
| |
| </div>
| |
| </div>}}
| |
|
| |
| {{odpowiedz||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><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>}}
| |
|
| |
| ==Zadanie 17 (Ustawianie hetmanów)==
| |
| Napisz procedurę znajdująca wszystkie takie rozstawienia 8 hetmanów na szachownicy, by żadne dwa hetmany się nie atakowały.
| |
|
| |
| {{wskazowka| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none">
| |
| 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.
| |
|
| |
| 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>}}
| |
|
| |
| {{rozwiazanie| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none">
| |
| '''procedure''' Hetmany(N:integer);
| |
| // N >= 1
| |
| '''var'''
| |
| wiersze:'''array'''[1..N] '''of''' integer;
| |
| kolumny:'''array'''[1..N] '''of''' boolean;
| |
| przekgd:'''array'''[-(N-1)..N-1] '''of''' boolean; // przekątne \ o stałej różnicy
| |
| przekdg:'''array'''[2..2*N] '''of''' boolean; // przekątne / o stałej sumie
| |
|
| |
| '''procedure''' ustaw(k,i:integer);
| |
| // 1 <= k,i <= N
| |
| '''begin'''
| |
| wiersze[k]:=i;
| |
| kolumny[i]:=true;
| |
| przekgd[k-i]:=true;
| |
| przekdg[k+i]:=true;
| |
| '''end'''; // ustaw
| |
|
| |
| '''procedure''' odstaw(k,i:integer);
| |
| // 1 <= k,i <= N
| |
| '''begin'''
| |
| wiersze[k]:=0;
| |
| kolumny[i]:=false;
| |
| przekgd[k-i]:=false;
| |
| przekdg[k+i]:=false;
| |
| '''end'''; // odstaw
| |
|
| |
| '''function''' wolne(k,i:integer):boolean
| |
| // 1 <= k,i <= N
| |
| '''begin'''
| |
| wolne:='''not''' kolumny[i] & '''not''' przekgd[k-i] & '''not''' przekdg[k+i]
| |
| '''end'''; // wolne
| |
|
| |
| '''var''' jest:boolean;
| |
|
| |
| '''procedure''' wypisz;
| |
| '''var''' i:integer;
| |
| '''begin'''
| |
| jest:=true;
| |
| write('Ustawienie: ')
| |
| '''for''' i:=1 '''to''' N; '''do''' write(i,':',wiersze[i],' ')
| |
| writeln;
| |
| '''end'''; // wypisz
| |
|
| |
| '''procedure''' rozstaw(k:integer);
| |
| '''var''' i:integer;
| |
| '''begin'''
| |
| '''if''' k=N+1 '''then'''
| |
| wypisz
| |
| '''else'''
| |
| '''for''' i:=1 '''to''' N '''do'''
| |
| '''if''' wolne(k,i) '''then''' '''begin'''
| |
| ustaw(k,i);
| |
| rozstaw(k+1);
| |
| odstaw(k,i);
| |
| '''end'''
| |
| '''end'''; // rozstaw
| |
|
| |
| '''var''' i:integer;
| |
| '''begin''' // Hetmany
| |
| '''for''' i:=1 '''to''' N '''do''' wiersze[i]:=0;
| |
| '''for''' i:=1 '''to''' N '''do''' kolumny[i]:=false;
| |
| '''for''' i:=-(N-1) '''to''' N-1 '''do''' przekgd[i]:=false;
| |
| '''for''' i:=2 '''to''' 2*N '''do''' przekdg[i]:=false;
| |
| jest:=false;
| |
| rozstaw(i);
| |
| '''if''' '''not''' jest '''then''' writeln('Nie znaleziono żadnego ustawienia')
| |
| '''end''' // Hetmany
| |
|
| |
| ''Koszt czasowy:'' wykładniczy względem N
| |
|
| |
| ''Koszt pamięciowy:'' liniowy względem N
| |
|
| |
| ''Opis:'' Aby ustawić N hetmanów, każdy z nich musi stać w osobnym wierszu. Dlatego główna procedurą rekurencyjna 'rozstaw' próbuje ustawić k-tego hetmana w wierszu k w kolejnych kolumnach. Jeśli to się uda (wolne(k,i)=true) zaznacza zajętość odpowiedniej kolumny i przekątnych i wywołuje się rekurencyjnie aby rozstawić pozostałe hetmany. Po powrocie z wywołania rekurencyjnego, kolumna i przekątne są zwalniane i pętla for próbuje ustawić k-tego hetmana w kolejnej kolumnie.
| |
|
| |
| Jeśli nie ma już żadnych hetmanów do rozstawiania (k=N+1) oznacza to, że N jest już ustawionych - pozostaje ich wypisać. Przy okazji zmienna 'jest' rejestruje czy jakiekolwiek rozwiązanie zostało wypisane. Jeśli nie, główna procedura 'Hetmany' wypisuje stosowny komunikat.
| |
|
| |
| </div>
| |
| </div>}}
| |
|
| |
| ==Zadanie 18 (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.
| |
|
| |
| {{wskazowka| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none">
| |
| 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>
| |
| <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>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_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>
| |
| 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|Sedgewick Algorithms in C++]], str. 527-530.
| |
| </div>
| |
| </div>}}
| |
|
| |
| ==Zadanie 19 (Suma składników)==
| |
|
| |
| Napisz procedurę, która wypisze dla zadanej liczby n jej wszystkie rozkłady na sumy nieujemnych liczb naturalnych większych od 1 ustawionych w kolejności nierosnącej. Np. dla n = 3:<br>
| |
| 3 = 3<br>
| |
| 3 = 2+1<br>
| |
| 3 = 1+1+1
| |
|
| |
| {{wskazowka| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none">
| |
| Użyj dodatkowej tablicy do przechowywania początków rozkładów. Uważaj, aby nie kopiować tej tablicy przy każdym wywołaniu rekurencyjnym!
| |
| </div>
| |
| </div>}}
| |
|
| |
| {{wskazowka| 2||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none">
| |
| Funkcja rekurencyjna powinna mieć parametr wskazujący jak duże składniki mogą być użyte do rozkładu pozostałej liczby. </div>
| |
| </div>}}
| |
|
| |
| {{rozwiazanie| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none">
| |
| '''procedure''' Suma(n:integer);
| |
| // 1 <= n
| |
| '''var''' T:'''array'''[1..n] '''of''' integer;
| |
|
| |
| '''procedure''' rozklad('''var''' T:'''array'''[1..n] '''of''' integer; ti,k,m:integer)
| |
| // 0 <= ti <= n
| |
| // ti > 0 --> k=T[ti]
| |
| // 0 <= m
| |
| // m=0 --> ti > 0
| |
| '''var''' i:integer
| |
| '''begin'''
| |
| '''if''' m=0 '''then''' '''begin'''
| |
| write(n,' = ');
| |
| '''for''' i:=1 '''to''' ti-1 '''do''' write(T[i],'+');
| |
| writeln(T[ti])
| |
| '''end'''
| |
| '''else''' '''begin'''
| |
| ti:=ti+1;
| |
| '''for''' i:=min(k,m) '''downto''' 1 '''do''' '''begin'''
| |
| T[ti]:=i;
| |
| rozklad(T,ti,i,m-i);
| |
| '''end'''
| |
| '''end'''
| |
| '''end''' //rozklad
| |
|
| |
| '''begin'''
| |
| rozklad(T,0,n,n);
| |
| '''end'''
| |
|
| |
| ''Koszt czasowy:'' wykładniczy względem n
| |
|
| |
| ''Koszt pamięciowy:'' liniowy względem n
| |
|
| |
| [http://www.mimuw.edu.pl/~annan/modul2_19_1.html Wizualizacja]
| |
|
| |
| ''Opis:'' Funkcja rekurencyjna 'rozkład' przegląda wszystkie nierosnące rozkłady liczby m używające składników niewiększych niż k. Ponieważ w tablicy T[1..ti] jest już nierosnący ciąg składników o sumie n-m, wydłużenie go o dowolny taki rozkład m będzie dawać poprawny rozkład n.
| |
|
| |
| Istotnie, jeśli m=0, w tablicy jest już gotowy rozkład n, więc wypisujemy go. W przeciwnym razie zwiekszamy ti, wszystkie liczby i, które mogą znaleźć się w tym miejscu rozkładu umieszczamy kolejno w T[ti] i wywołujemy rekurencyjnie funkcję 'rozklad', aby do wydłużonego rozkładu dopisała wszystkie rozkłady m-i o pierwszym składniku nieprzekraczającym i.
| |
|
| |
| Zwróć uwagę, że tablica T przekazywana jest przez zmienną i dlatego jej zawartość nie jest kopiowana. Tablica T mogłaby też być zmienną globalną (dla procedury 'Suma'), ponieważ i tak wszystkie wywołania funkcji rozklad korzystają z tej samej tablicy zadeklarowanej w głównej części procedury 'Suma'.
| |
|
| |
| Zauważ, że można by też tak napisać procedurę 'rozklad', aby nigdy nie wywoływała się rekurencyjnie dla m=0. Wypisywanie pełnego rozkładu miałoby miejsce wtedy, gdy min(k,m)=m (czyli k>=m) i oczywiscie petla 'for' zaczynalaby sie w takim przypadku od m-1.
| |
|
| |
| Druga możliwa modyfikacja polega na zrezygnowaniu z parametru k, ponieważ k albo jest równe T[ti], albo m jeśli ti=0. Mozna by też użyć strażnika T[0]=n i zawsze mielibyśmy k=T[ti].
| |
|
| |
| Poza wspomnianymi modyfikacjami całą procedurę 'rozkład' można by napisać w sposób ''rosnący'', tzn. zamiast generować coraz mniejsze składniki - generować coraz większe, aż osiągniemy, bądź przekroczymy n. W takim wypadku składniki należałoby wypisywać w odwrotnej kolejności.
| |
| </div>
| |
| </div>}}
| |
|
| |
| <!-- to zadanie tu nie pasuje, wymaga zdecydowanie rekordow z wariantami...
| |
| ==Zadanie 7 (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:
| |
|
| |
| {| border="1" cellpadding="5" cellspacing="0"
| |
| |-
| |
| | <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.
| |
|
| |
| {| border="0" cellpadding="4" cellspacing="0"
| |
| |-
| |
| | <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''
| |
| |}
| |
|
| |
| <!-- to między || i pierwszą | to są parametry komórki, więc żeby wpisać w komórce kreskę, to trzeba zacząc komórkę od _spacja_kreska_ :) - - >
| |
|
| |
| {{wskazowka| 1||<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none">
| |
| 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).
| |
| </div>
| |
| </div>}}
| |
| -->
| |
<<< Powrót do modułu Wprowadzenie do programowania
Ta strona zawiera podstawowe zadania na tablice.
Ogladaj wskazówki i rozwiązania __SHOWALL__
Ukryj wskazówki i rozwiązania __HIDEALL__
Zadanie 1 (Flaga polska)
Tablica A typu array[1..N] of integer (N > 0) wypełniona zerami i jedynkami reprezentuje ciąg N urn w których znajdują się żetony białe (0) i czerwone (1). Podaj algorytm działania automatu przestawiającego żetony w urnach tak, by najpierw były żetony czerwone, potem białe. Robot może wykonywać dwa rodzaje operacji:
- Kol(i) - podaje kolor żetonu w i-tej urnie (1 ≤ i ≤ n)
- Z(i,j) - zamienia żetony z i-tej i j-tej urny (1 ≤ i,j ≤ n)
Uwagi:
- Operacja Kol jest bardzo kosztowna, więc zależy nam na tym by kolor każdego żetonu był sprawdzany co najwyżej raz.
- Robot potrafi zapamiętać tylko kilka wartości z przedziału 0..N+1.
- Nie można założyć, że występuje choć jeden żeton w każdym z kolorów.
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Wskazówka 2
{{{3}}}
Rozwiązanie 2
{{{3}}}
Wskazówka 3
{{{3}}}
Rozwiązanie 3
{{{3}}}
Wskazówka 4
{{{3}}}
Rozwiązanie 4
{{{3}}}
Zadanie 2 (Flaga trójkolorowa)
Dana jest tablica A typu array[1..N] of integer (N > 0). Należy tak poprzestawiać w niej elementy, żeby najpierw były elementy ujemne, potem równe zero, a na końcu dodatnie.
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Rozwiązanie 2
{{{3}}}
Zadanie 3 (Najdłuższe plateau)
Napisz program znajdujący w zadanej tablicy A typu array[1..N] of integer, N > 0, długość najdłuższego stałego segmentu (spójnego fragmentu tablicy).
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Wskazówka 2
{{{3}}}
Rozwiązanie 2
{{{3}}}
Inna wersja zadania
A co było gdyby tablica była posortowana ?
Wskazówka 3
{{{3}}}
Rozwiązanie 3
{{{3}}}
Zadanie 4 (Segment o maksymalnej sumie)
Napisz program znajdujący w zadanej tablicy A typu array[1..N] of integer, N > 0, maksymalną sumę segmentu (spójnego fragmentu tablicy). Przyjmujemy, że segment pusty ma sumę 0.
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Wskazówka 2
{{{3}}}
Rozwiązanie 2
{{{3}}}
Wskazówka 3
{{{3}}}
Rozwiązanie 3
{{{3}}}
Wskazówka 4
{{{3}}}
Rozwiązanie 4
{{{3}}}
Rozwiązanie 5
{{{3}}}
Zadanie 5 (Część wspólna zbiorów)
Dane są dwie tablice A i B typu array[1..N] of integer, N > 0. Obie są
posortowane rosnąco. Należy traktując A i B jako reprezentacje dwu
zbiorów wypisać (operacją write) cześć wspólną tych zbiorów.
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Zadanie 6 (Suma zbiorów)
Dane są dwie tablice A i B typu array[1..N] of integer, N > 0. Obie są
posortowane rosnąco. Należy traktując A i B jako reprezentacje dwu
zbiorów wypisać (operacją write) sumę tych zbiorów.
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Wskazówka 2
{{{3}}}
Rozwiązanie 2
{{{3}}}
Zadanie 7 (Podciąg)
Dane są dwie tablice A typu array[1..N] of integer i B typu array[1..M] of integer, N, M > 0. Napisz program sprawdzający, czy A jest podciągiem B (tzn. czy istnieje funkcja f, rosnąca, z 1..N w 1..M, t. ze A[i]=B[f(i)]).
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Zadanie 8 (Odwracanie tablicy)
Dana tablica A typu array[0..N-1] of integer, N > 1. Napisz program odwracający kolejność elementów w A.
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Wskazówka 2
{{{3}}}
Rozwiązanie 2
{{{3}}}
Zadanie 9 (Przesunięcie cykliczne)
Dana tablica A typu array[0..N-1] of integer, N > 1, i liczba naturalna k > 1. Napisz program realizujący przesunięcie cykliczne w prawo o k pól, czyli przesuwający zawartość pola A[i] na A[(i+k) mod N] dla każdego i < N.
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Wskazówka 2
{{{3}}}
Rozwiązanie 2
{{{3}}}
Wskazówka 3
{{{3}}}
Rozwiązanie 3
{{{3}}}
Zadanie 10 (Następna permutacja)
Tablica A typu array[1..N] of integer, N > 0, zawiera pewną permutację liczb 1.. N. Napisz program wpisujący do A następną leksykograficznie permutację. Zakładamy, że permutacja w A nie jest ostatnia leksykograficznie.
Przykład Dla N=3 kolejne permutacje w porządku leksykograficznym wyglądają nastepująco:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Zadanie 11 (Segment o danej sumie)
Tablica A typu array[1..N] of integer, N > 0, zawiera tylko liczby dodatnie. Napisz program który dla danego W typu integer sprawdza czy w A istnieje segment o sumie W (czyli czy istnieją l, p takie, że W).
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Wskazówka 2
{{{3}}}
Rozwiązanie 2
{{{3}}}
Zadanie 12 (Głosowanie większościowe)
Dana jest tablica A typu array[1..N] of integer, N > 0. Sprawdzić czy jest w niej element wystepujący więcej niż N/2 razy i jeśli tak wskaż go.
Wskazówka 1
{{{3}}}
Rozwiązanie 1
{{{3}}}
Wskazówka 2
{{{3}}}
Rozwiązanie 2
{{{3}}}
Zadanie 13 (Arytmetyka liczb wielocyfrowych)
Liczby wielocyfrowe będą reprezentowane w tablicach typu liczba=array[0..N-1] of integer, N > 1, w taki sposób, że najmniej znacząca cyfra jest pod indeksem 0. Rozpatrujemy liczby przy podstawie b > 1. Napisz procedury obliczające:
- sumę liczb A i B do C. Jeśli wynik nie zmieści się w C to wartość C nie ma znaczenia. Zmienna przepełnienie wskazuje czy do niego doszło czy nie.
- różnicę A i B do C. Jeśli wynik miałby byc liczbą ujemną to wartość C nie ma znaczenia. Zmienna ujemny wskazuje jaki jest znak wyniku.
- iloczyn A i B do C (C powinno być tablicą dwa razy dłuższą niż A i B, żeby móc pomieścić wynik).
Rozwiązanie 1
{{{3}}}