0% found this document useful (0 votes)
72 views74 pages

Book

Uploaded by

kobucotokocurek
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
72 views74 pages

Book

Uploaded by

kobucotokocurek
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Bartłomiej Przybylski

Zbiór zadań z programowania


Część I. Wprowadzenie do algorytmiki

Wydanie I
Polecana literatura

• Maciej Sysło, Algorytmy, Wydawnictwo Helion, 2016.


• Maciej Sysło, Piramidy, szyszki i inne konstrukcje algorytmiczne, Wydawnictwo
Helion, 2015.

• Niklaus Wirth, Algorytmy + struktury danych = programy, Wydawnictwa


Naukowo-Techniczne, 2004.
• Sanjoy Dasgupta et al., Algorytmy, Wydawnictwo Naukowe PWN, 2010.
• Thomas H. Cormen, Algorytmy bez tajemnic, Wydawnictwo Helion, 2013.
• Thomas H. Cormen et al., Wprowadzenie do algorytmów, Wydawnictwo
Naukowe PWN, 2018.
• Alexander A. Stepanov, Daniel E. Rose, Od matematyki do programowania
uogólnionego, Wydawnictwo Helion, 2015.

1
Zestaw 1.

Zanim zaczniemy

Zadanie 1.1. Policz, ile trójkątów można wyodrębnić z poniższego diagramu. Jaki
obrałeś/aś sposób liczenia tych figur?

Zadanie 1.2. W jaki sposób szybko policzyć, ile trójkątów można wyodrębnić
z poniższgo diagramu? Ile ich jest?

Zadanie 1.3. Uzupłnij poniższą tabelę wstawiając w każde wolne pole jeden z czte-
rech symboli (kwadrat, krzyż, koło lub trójkąt) tak, aby w każdej kolumnie i w każ-
dym wierszu znajdowały się cztery różne symbole. Czy można rozwiązać to zadanie
na kilka różnych sposobów?

2
3

Zadanie 1.4. Spójrz na poniższą tabelę. W porównaniu do tej z poprzedniego


zadania, jedno dodatkowe pole zostało już uzupełnione symbolem. Jak ta zmiana
wpłynęła na liczbę możliwych rozwiązań zadania?

Zadanie 1.5. Zastanów się, w jaki sposób można by usystematyzować rozwiązy-


wanie zadań takich jak powyższe. Przedyskutuj swoje pomysły z nauczycielem,
koleżankami i kolegami.

Zadanie 1.6. Sudoku to francusko-japońska łamigłówka, która polega na takim


uzupełnieniu cyframi od 1 do 9 tabeli o wymiarach 9 × 9, aby jedna cyfra nie
występowała kilkukrotnie w żadnym wierszu tabeli, w żadnej kolumnie tabeli, ani
w żadnym module o wymiarach 3 × 3. Rozwiąż poniższe sudoku.

6 5 4 9
1 6 4 2
7 8 9
7 5 8 1
5 3 4 6
4 2
3 4 1
9 8 5
4 3 7

Zadanie 1.7. Mamy do dyspozycji dwa sznurki (lonty), z których każdy pali się
przez dokładnie godzinę. Jak odmierzyć za ich pomocą kwadrans?
4

Zadanie 1.8. Twój znajomy poprosił Cię o pomoc w otwarciu sejfu z zamkiem
elektronicznym. Otwarcie drzwiczek następuje po wprowadzeniu konkretnego
4-cyfrowego kodu, a otwierający ma aż 5 szans zanim zamek zablokuje się na zawsze.
Niestety, Twój znajomy był bardzo niecierpliwy i wykorzystał już cztery z pięciu prób.
Na szczęście elektroniczny zamek poinformował go o tym, ile z cyfr wprowadzo-
nych w każdej z czterech prób było prawidłowych i znajdowało się na odpowiednich
pozycjach ( ), a ile było prawidłowych, ale umieszczonych w złym miejscu (#).
Poniżej znajdziesz notatki, które sporządził Twój kolega. Jaki kod otwiera zamek?

1354
3542 ##
1357 #
1754 #

Zadanie 1.9 (Zagadka Einsteina). 5 ludzi różnych narodowości zamieszkuje 5 do-


mów w 5 różnych kolorach. Wszyscy palą papierosy 5 różnych marek i piją 5 różnych
napojów. Hodują zwierzęta 5 różnych gatunków. Który z nich trzyma w domu rybki?

1. Norweg zamieszkuje pierwszy dom


2. Anglik mieszka w czerwonym domu.
3. Zielony dom znajduje się bezpośrednio po lewej stronie domu białego.
4. Duńczyk pija herbatkę.
5. Palacz papierosów light mieszka obok hodowcy kotów.
6. Mieszkaniec żółtego domu pali cygara.
7. Niemiec pali fajkę.
8. Mieszkaniec środkowego domu pija mleko.
9. Palacz papierosów light ma sąsiada, który pija wodę.
10. Palacz papierosów bez filtra hoduje ptaki.
11. Szwed hoduje psy.
12. Norweg mieszka obok niebieskiego domu.
13. Hodowca koni mieszka obok żółtego domu.
14. Palacz mentolowych pija piwo.
15. W zielonym domu pija się kawę.

Zakłada się, że domy ustawione są w jednej linii (1-2-3-4-5), a określenie „po lewej
stronie” w punkcie 3. dotyczy lewej strony z perspektywy naprzeciw tych domów
(tj. dom o numerze n jest bezpośrednio po lewej stronie domu n + 1, a dom po lewej
od domu n to dom o numerze n − 1).

Zadanie 1.10. W stadninie przy torze wyścigowym jest 25 koni. Jaka jest minimalna
liczba wyścigów, które należy przeprowadzić, i jak należy je przeprowadzić, aby
wyłonić spośród wszystkich koni:
5

(a) najszybszego,
(b) dwa najszybsze,
(c) trzy najszybsze?

Zakładamy, że na torze wyścigowym może się jednocześnie ścigać pięć koni, a dany
koń potrzebuje za każdym razem tyle samo czasu, aby dobiec do mety. Nie masz do
dyspozycji zegarka.

Zadanie 1.11. Spójrz na poniższy labirynt. Znajdź taką drogę prowadzącą od wej-
ścia do wyjścia, która przechodzi na zmianę przez czarne i białe kropki.

Wejście Wyjście
Algorytmy

Słowo „algorytm” pochodzi od nazwiska perskiego matematyka, Muhammada


ibn-Musy al-Chuwarizmiego (nazywanego też Algorismusem). Żył on w latach
780–850 n.e. w Bagdadzie. To on jest uważany za pioniera metod obliczeniowych
w matematyce, które dziś nazwalibyśmy algorytmami.
Algorytm to uporządkowany, jasno zdefiniowany ciąg czynności, które pozwa-
lają nam wykonać ogólne zadanie (rozwiązać problem), na przykład związane z prze-
kształceniem jednego zbioru informacji w inny. Żebyśmy mogli mówić o algorytmie,
przygotowany ciąg czynności musi spełniać kilka wymagań. Oto one:

• Algorytm jest poprawny. Oczekujemy, że przygotowany przez nas ciąg czyn-


ności doprowadzi nas do rezultatu, którego w istocie oczekujemy. Na przykład,
jeśli opracujemy ogólną metodę (algorytm) mnożenia dwóch liczb, to powin-
na ona zawsze prowadzić do prawidłowego iloczynu wskazanych liczb.
• Algorytm jest jednoznaczny. Działanie algorytmu może zależeć od infor-
macji, które posiadamy na samym początku jego wykonywania, ale w takich
samym okolicznościach algorytm musi działać w taki sam sposób.1 Iloczyn
dwóch liczb nie zmienia się, jeśli liczby te pozostają te same — działanie
algorytmu też nie powinno.
• Algorytm jest skończony. Niezależnie od tego, z jakim zadaniem mamy do
czynienia, algorytm musi się zakończyć po wykonaniu skończonej liczby
czynności. Liczba ta może być bardzo duża i często zależy od samych danych
(potrzeba więcej czynności do tego, aby pomnożyć liczby 2673293 i 576328892
niż do tego, aby pomnożyć liczby 13 i 7), ale zawsze powinna być skończona.
• Algorytm jest sprawny. Ciąg czynności służący do rozwiązania postawione-
go zadania powinien być optymalny. Tę optymalność zwykle określa się na
podstawie (maksymalnej, minimalnej lub średniej) liczby czynności niezbęd-
nych do wykonania zadania o określonej wielkości. Jeśli można coś zrobić
lepiej, to dlaczego nie?

1
Nie zawsze musi tak być. Istnieje duża grupa tzw. algorytmów randomizowanych (losowych),
w których rezygnuje się z tego warunku. My jednak nie będziemy rozważać takich algorytmów.

6
Część I

Schematy blokowe

7
Schematy blokowe

Algorytmy można zapisywać z wykorzystaniem wielu technik. Najpopularniejsze


z nich to: lista kroków, schemat blokowy, pseudokod i implementacja w języku
programowania. W przypadku schematu blokowego, algorytm reprezentuje się za
pomocą połączonych ze sobą strzałkami figur geometrycznych, zwanych blokami.
Kształt bloku jest ściśle związany z rodzajem instrukcji, którą zawiera. Na przykład,
informacje o operacjach odczytania danych z wejścia i wypisania danych na wyjściu
umieszcza się wewnątrz równoległoboku. Z kolei prostokąt zawiera informacje
o operacjach modyfikujących dane wykorzystywane przez algorytm. Na następnej
stronie zebrano przykłady i opisy różnych rodzajów bloków wykorzystywanych do
konstruowania schematów. Z kolei na stronie 11. znajdziesz przykładowy algorytm
pracy z tymi materiałami, wykorzystujący omawianą notację.

9
10

Start Blok początku algorytmu. W schemacie blokowym wystę-


puje dokładnie jeden taki znacznik, a strzałka wychodząca
od niego prowadzi do pierwszej operacji.

Blok wejścia/wyjścia. Wykorzystuje się go do reprezento-


instrukcja wania operacji wyjścia/wyjścia, takich jak pobranie warto-
ści od użytkownika albo wypisanie wartości.

Blok operacji. Wykorzystuje się go do reprezentowania


instrukcja
wszelkich operacji wykonywanych na danych.

Blok decyzyjny. Wykorzystuje się go do sterowania prze-


warunek pływem algorytmu w zależności od tego, czy warunek jest
T prawdziwy (T) czy fałszywy (N).
N

Blok wywołania procedury. Stosowany w przypadkach,


wywołanie kiedy operacje na danych wykonuje się z wykorzystaniem
zdefiniowanej wcześniej procedury.

Blok komentarza. Wykorzystuje się go, aby wyjaśnić dzia-


komentarz
łanie algorytmu osobie, która się z nim zapoznaje.

Łącznik wewnętrzny. Wykorzystuje się go do przeskaki-


X wania w inne miejsce algorytmu, oznaczone taką samą
X etykietą X.

Blok końca algorytmu. W schemacie blokowym występu-


je dokładnie jeden taki znacznik, a strzałka wchodząca do
Stop niego może prowadzić od więcej niż jednej operacji.
11

Start
W
Znajdź pierwsze
nierozwiązane zadanie
Z
Dokładnie przeczytaj
treść zadania

Spróbuj rozwiązać
zadanie

Udało się?
T
Zapisz rozwiązanie
N w zeszycie

Zapisz pytania
w zeszycie

Jakieś
pytania?
T
Koniec
zestawu? Omów swoje
T N
rozwiązania
N z nauczycielem
X
Następny
zestaw? T
Zmęczony? W
T N
X
N Zrób sobie
dzień przerwy Koniec
książki?
T

Przejdź do kolejnego N Stop


nierozwiązanego
Poczekaj na
zadania
nowy zestaw

Z W
Zestaw 2.

Algorytmy dnia codziennego

Zadanie 2.1. Zaprojektuj listę kroków lub schemat blokowy algorytmu przemiesz-
czania się z Twojego domu do budynku szkoły. Upewnij się, że przygotowany przez
Ciebie algorytm będzie:

(a) poprawny, czyli że Twoja droga zawsze zakończy się w szkole,


(b) jednoznaczny, czyli że w identycznych okolicznościach zawsze poprowadzi
Cię taką samą drogą,
(c) skończony, czyli że dotrzesz do szkoły zanim skończy się wszechświat,
(d) sprawny, czyli że droga zajmie Ci tak mało czasu, jak to możliwe.

Uwzględnij przypadki losowe, takie jak opóźnione lub nieprzyjeżdżające autobusy


i tramwaje, albo awaria samochodu. Możesz założyć, że autobusy, tramwaje, rowery
i samochody nie psują się w trakcie jazdy.

Zadanie 2.2. Zaprojektuj listę kroków lub schemat blokowy algorytmu prania Two-
jej kolekcji białych T-shirtów. Załóż, że pralka ma wbudowany program koszulki, ale
temperatura prania i szybkość odwirowywania powinny zostać ustawione ręcznie
(na odpowiednio 40 stopni i 800 obrotów). Możesz przyjąć, że do prania wystar-
czy płyn. Postaraj się, aby Twój algorytm był poprawny, jednoznaczny, skończony
i sprawny.

Zadanie 2.3. Zaprojektuj listę kroków lub schemat blokowy algorytmu, który od-
powiada Twojemu sposobowi rozwiązywania testów wielokrotnego wyboru z jedną
prawidłową odpowiedzią. Przyjmij, że test składa się z 50 pytań ABCD. Czy roz-
wiązujesz wszyskie zadania po kolei? A może pomijasz zadanie i wracasz do niego
później, jeśli nie znajdziesz prawidłowej odpowiedzi w zadanym sobie czasie? Twój
algorytm powinien być poprawny, jednoznaczny, skończony i sprawny.

Zadanie 2.4. Twój znajomy poprosił Cię o posortowanie stosu faktur VAT według
daty ich wystawienia, rosnąco. Zaprojektuj listę kroków lub schemat blokowy al-
gorytmu, który pozwoli Ci przekształcić istniejący stos nieposortowanych faktur
w nowy stos faktur posortowanych według numeru. Twój algorytm powinien być
poprawny, jednoznaczny, skończony i sprawny.

12
13

★★ Zadanie 2.5. Przygotuj poster, na którym zaprezentujesz uproszczony schemat


blokowy algorytmu przejścia przez poszczególne etapy zdobywania wykszałcenia
w Polsce. Postaraj się, żeby Twój schemat był jak najbardziej uniwersalny — powinien
uwzględniać nie tylko różne możliwe ścieżki, ale także różne formy aktywności (takie
jak choćby studia podyplomowe). Poster powinien mieć rozmiar A1 i może być albo
przygotowany ręcznie, albo wydrukowany na podstawie projektu przygotowanego
na komputerze.
Zestaw 3.

Proste algorytmy liczbowe

Zadanie 3.1. Liczba parzysta to taka, która dzieli się bez reszty przez 2. Na poniż-
szym schemacie zaprezentowano algorytm, który powinien weryfikować parzystość
podanej liczby całkowitej (zapisanej w zmiennej o nazwie n). Czy rzeczywiście tak
jest? Czy ten algorytm jest poprawny, jednoznaczny, skończony i sprawny?

Start

Czytaj liczbę n

n=1
T
N Pisz „nieparzysta”

n=0
T
N Pisz „parzysta”

Zmniejsz n o dwa

Stop

14
15

Zadanie 3.2. Zmodyfikuj algorytm z poprzedniego zadania tak, aby działał prawi-
dłowo dla dowolnej liczby całkowitej n. Jedynymi operacjami, z których możesz
korzystać, są operacje zmniejszenia lub zwiększenia wartości zmiennej n o pewną
wartość liczbową. W blokach warunkowych możesz odtąd dodatkowo wykorzysty-
wać instrukcję porównania dwóch wartości liczbowych ze sobą (np. n < 100).
Zadanie 3.3. Zaprojektuj algorytm, który pobiera z wejścia dodatnie liczby natural-
ne k i l, a następnie wypisuje resztę z dzielenia liczby k przez l. Jedynymi operacjami,
które możesz wykorzystać, są operacje zwiększenia lub zmniejszenia wartości zmien-
nej o pewną wartość liczbową (stałą lub przechowywaną w zmiennej). Aktualną
wartość przechowywaną w zmiennej możesz wypisać na wyjściu korzystając z bloku
takiego jak poniżej:

Pisz n

Zadanie 3.4. Zaprojektuj algorytm, który wypisuje na wyjściu n początkowych


liczb naturalnych w porządku malejącym. Przyjmij, że podana na wejściu wartość n
jest zawsze dodatnią liczbą naturalną. Jedynymi operacjami, które możesz wykorzy-
stać, są operacje zwiększenia lub zmniejszenia wartości zmiennej o pewną wartość
liczbową.
Zadanie 3.5. Zaprojektuj algorytm, który wypisuje na wyjściu n początkowych liczb
naturalnych w porządku rosnącym. Przyjmij, że podana na wejściu wartość n jest
zawsze dodatnią liczbą naturalną. Rozwiązując to zadanie możesz skorzystać z jednej
zmiennej pomocnicznej o wybranej przez Ciebie nazwie. Zmienną pomocniczną
definiuje się, przypisując do niej wartość początkową, tak jak na poniższym rysunku.

p := 1

Jedynymi operacjami, które możesz wykorzystać, są operacje tworzenia zmiennej


oraz operacje zwiększenia lub zmniejszenia wartości zmiennej o pewną wartość
liczbową.
Zadanie 3.6. Zaprojektuj algorytm, który wypisuje na wyjściu wynik dzielenia
całkowitego dwóch pobranych z wejścia dodatnich liczb naturalnych, k i l. Rozwią-
zując to zadanie możesz skorzystać z jednej zmiennej pomocnicznej o wybranej
przez Ciebie nazwie. Jedynymi operacjami, które możesz wykorzystać, są operacje
tworzenia zmiennej oraz operacje zwiększenia lub zmniejszenia wartości zmiennej
o pewną wartość liczbową.
16

• Czy korzystając z powyższego algorytmu można szybko wyznaczyć iloraz


i resztę z dzielnia liczby k przez liczbę l? A może jedynie iloraz?

Zadanie 3.7. Zaprojektuj algorytm, który wypisuje na wyjściu iloczyn dwóch po-
branych z wejścia dodatnich liczb naturalnych, k i l. Rozwiązując to zadanie możesz
skorzystać z jednej zmiennej pomocnicznej o wybranej przez Ciebie nazwie. Je-
dynymi operacjami, które możesz wykorzystać, są operacje tworzenia zmiennej
oraz operacje zwiększenia lub zmniejszenia wartości zmiennej o pewną wartość
liczbową.

• Czy da się skonstruować poprawny algorytm opisany w tym zadaniu bez


wykorzystania zmiennej pomocniczej? Odpowiedź uzasadnij.

★ Zadanie 3.8. Zaprojektuj algorytm, który wypisuje na wyjściu wartość wyrażenia


ka , gdzie k i a są liczbami naturalnymi pobranymi z wejścia, takimi że k > 0 i a ≥ 0.
Rozwiązując to zadanie możesz skorzystać z trzech zmiennych pomocnicznych
o wybranych przez Ciebie nazwach. Jedynymi operacjami, które możesz wykorzystać,
są operacje tworzenia zmiennej oraz operacje zwiększenia lub zmniejszenia wartości
zmiennej o pewną wartość liczbową.

• Czy da się skonstruować poprawny algorytm opisany w tym zadaniu z wyko-


rzystaniem tylko dwóch zmiennych pomocniczych? Odpowiedź uzasadnij.
Wyrażenia arytmetyczne

Przy rozwiązywaniu zadań z poprzedniego zestawu korzystaliśmy z bardzo ograni-


czonego zbioru możliwych operacji na danych. Mogliśmy wczytać dane i zapisać je
w zmiennej, utworzyć zmienną pomocniczą przypisując do niej wartość początkową,
a także zwiększyć lub zmniejszyć wartość istniejącej zmiennej o pewną wartość licz-
bową. W końcu, mogliśmy wypisać na wyjściu wartość przechowywaną w zmiennej.
Przy tworzeniu algorytmów będziemy odtąd wykorzystywać szerszy zakres
elementarnych wyrażeń arytmetycznych zbudowanych z liczb. Wyrażenia takie
można budować korzystając z operatorów arytmetycznych, których listę znajdziesz
w Tabeli 1. Użyte tam symbole a i b oznaczają albo inne wyrażenia arytmetyczne,
albo stałe liczbowe. W praktyce, wyrażenia arytmetyczne mogą być wykorzystywane:

(a) w blokach operacji, do nadania nowej zmiennej wartości początkowej, np.

p := 3 ∙ 2

(b) w blokach operacji, do nadania nowej wartości istniejącej zmiennej, np.

p := 2 ∙ (p + 2)

(c) w blokach warunkowych, do porównywania dwóch wyrażeń arytmetycznych


ze sobą, np.

2∙p>5
T
N

17
18

Tabela 1: Elementarne wyrażenia arytmetyczne

Operator Przykład Znaczenie


+ a+b Operator dodawania dwóch liczb. Wartością tego wyra-
żenia jest suma liczb a i b.
− a−b Operator odejmowania dwóch liczb. Wartością tego wy-
rażenia jest różnica liczb a i b.
⋅ a⋅b Operator mnożenia dwóch liczb. Wartością tego wyra-
żenia jest iloczyn liczb a i b.
div a div b Operator dzielenia całkowitego dwóch liczb. Wartością
tego wyrażenia jest iloraz liczb a i b. Liczba b nie może
być zerem. Na przykład, 5 div 2 = 2.
mod a mod b Operator reszty z dzielenia całkowitego dwóch liczb.
Wartością tego wyrażenia jest reszta z dzielenia liczby a
przez liczbę b. Liczba b nie może być zerem. Na przykład,
5 mod 2 = 1.

(d) w blokach wyjścia, do wypisywania wartości na wyjściu, np.

Pisz p mod 3

Uwaga. Zauważ, że obie zaprezentowane poniżej operacje są równoważne. Od tego


momentu będziemy stosować zapis taki jak po prawej stronie.

Zmniejsz n o dwa n := n − 2
Wyrażenia logiczne

W blokach decyzyjnych umieszczamy wyrażenia logiczne, które przyjmują jedną


z dwóch wartości: prawda lub fałsz. W praktyce jednak blok warunkowy interpre-
tujemy jako pytanie, na które odpowiedź brzmi tak (T) lub nie (N). Na przykład,
blok

n>5
T
N

można utożsamiać z pytaniem: czy prawdą jest, że wartość przechowywana w zmien-


nej o nazwie n jest większa od 5?
Budując wyrażenia logiczne oparte na liczbach, możemy korzystać z różnych
operatorów porównań. W Tabeli 2. znajdziesz ich listę, przy czym użyte w niej
symbole a i b oznaczają albo wyrażenia arytmetyczne, albo stałe liczbowe.

Uwaga. Zwróć uwagę, że wartością wyrażenia arytmetycznego jest liczba, a warto-


ścią wyrażenia logicznego jest wartość logiczna (prawda lub fałsz). Oznacza to, że
nie możemy łączyć ze sobą operacji porównania, pisząc np. 1 ≤ n ≤ 10, ponieważ
nie potrafimy porównać wartości logicznej z liczbą. Takie warunki (1 ≤ n oraz
n ≤ 10) należy więc rozpatrywać osobno.

Tabela 2: Operatory porównań

Operator Przykład Przyjmuje wartość prawda wtedy i tylko wtedy, gdy...


< a<b ...wartość a jest mniejsza niż wartość b.
<= a <= b ...wartość a jest mniejsza lub równa wartości b.
> a>b ...wartość a jest większa niż wartość b.
>= a >= b ...wartość a jest większa lub równa wartości b.
= a=b ...wartość a jest taka sama jak wartość b.
!= a != b ...wartość a jest inna niż wartość b.

19
Zestaw 4.

Liczby i ciągi liczbowe

Zadanie 4.1. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia


dwie liczby całkowite, a i b, a następnie wypisuje je w kolejności rosnącej (najpierw
mniejszą, a potem większą). Nie wolno Ci korzystać ze zmiennych pomocniczych
i nie wolno Ci modyfikować wartości wczytanych zmiennych.

Zadanie 4.2. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia trzy
liczby całkowite, a, b i c, a następnie wypisuje je w kolejności malejącej. Nie wolno
Ci korzystać ze zmiennych pomocniczych i nie wolno Ci modyfikować wartości
wczytanych zmiennych.

★ Zadanie 4.3. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia


cztery liczby całkowite, a, b, c i d, a następnie wypisuje je w kolejności rosnącej.
Nie wolno Ci korzystać ze zmiennych pomocniczych i nie wolno Ci modyfikować
wartości wczytanych zmiennych.

• Ile operacji porównania musimy maksymalnie wykonać, żeby uporządkować


cztery liczby? Zastanów się, czy i jak można ograniczyć tę wartość.

★★ Zadanie 4.4. W podrozdziale 4.3. książki Macieja Sysło pt. Algorytmy opisany
jest interesujący algorytm porządkowania ciągu pięciu liczb. Wymaga on maksymal-
nie 7 porównań, niezależnie od tego, w jakiej kolejności podano liczby na wejściu.
Czy istnieje algorytm, który wymaga maksymalnie sześciu porównań? Przedyskutuj
swój punkt widzenia z nauczycielem.

Zadanie 4.5. Zaprojektuj schemat blokowy algorytmu, który dla pobranej z wejścia
dodatniej liczby naturalnej k wypisuje na wyjściu wartość k-tego wyrazu ciągu
liczbowego określonego wzorem:

1 dla k = 1,
ak = {
2 ⋅ ak−1 + 3 dla k > 1.

★ Zadanie 4.6 (Ciąg Fibonacciego). Zaprojektuj schemat blokowy algorytmu, który


dla pobranej z wejścia dodatniej liczby naturalnej k wypisuje na wyjściu wartość

20
21

k-tego wyrazu ciągu liczbowego określonego wzorem:

1 dla k ∈ {1, 2},


ak = {
ak−2 + ak−1 dla k > 2.

Zadanie 4.7. Zaprojektuj schemat blokowy algorytmu, który dla pobranych z wej-
ścia dodatnich liczb naturalnych n i k, wypisuje na wyjściu wartość wyrażenia:

n ⋅ (n + 1) ⋅ (n + 2) ⋅ ⋯ ⋅ (n + k − 1).

Zadanie 4.8. Zaprojektuj schemat blokowy algorytmu, który dla pobranej z wejścia
dodatniej liczby naturalnej n wypisuje na wyjściu wszystkie jej cyfry składowe, jedna
po drugiej, zaczynając od ostatniej. Na przykład dla n = 1582, algorytm powinien
wypisać kolejno 2, 8, 5 oraz 1.

★ Zadanie 4.9. Zaprojektuj schemat blokowy algorytmu, który dla pobranej z wej-
ścia dodatniej liczby naturalnej n wypisuje na wyjściu wszystkie jej cyfry składowe,
jedna po drugiej, zaczynając od pierwszej. Na przykład dla n = 1582, algorytm
powinien wypisać kolejno 1, 5, 8 oraz 2.
Zestaw 5.

Podstawowe algorytmy liczbowe

Uwaga. Od teraz, zanim rozpoczniesz pracę nad algorytmem, określ dane wejścio-
we i oczekiwany wynik działania algorytmu. Innymi słowy, stwórz specyfikację
problemu, który planujesz rozwiązać.

Zadanie 5.1. Liczbę naturalną n > 1 nazywamy pierwszą, jeśli dzieli się ona wy-
łącznie przez 1 i n. Oto kilka początkowych liczb pierwszych: 2, 3, 5, 7, 11, 13…
Jednym ze sposobów, aby zweryfikować czy liczba n jest pierwsza, jest sprawdzenie,
czy dzieli się przez którąkolwiek z liczb naturalnych z przedziału od 2 do n − 1.
Zaprojektuj schemat blokowy algorytmu, który dla pobranej z wejścia dodatniej
liczby naturalnej n wypisuje na wyjściu słowo tak, jeśli liczba n jest pierwsza i nie,
jeśli liczba n nie jest pierwsza.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

• Czy naprawdę musimy sprawdzać wszystkie potencjalne dzielniki pomiędzy


2 a n − 1? Zredukuj liczbę operacji wykonywanych przez algorytm tak bardzo,
jak tylko potrafisz.

Zadanie 5.2. Każda liczba naturalna większa od 1 może zostać jednoznacznie


zapisana jako iloczyn liczb pierwszych. Na przykład

1540 = 2 ⋅ 2 ⋅ 5 ⋅ 7 ⋅ 11.

Występujący po prawej stronie powyższej równości ciąg liczb pierwszych 2, 2, 5, 7


oraz 11 nazywamy rozkładem liczby 1540 na czynniki pierwsze. Zaprojektuj schemat
blokowy algorytmu, który dla pobranej z wejścia liczby naturalnej n > 1 wypisuje na
wyjściu jej rozkład na czynniki pierwsze. Na przykład dla n = 20, algorytm powinien
wypisać kolejno 2, 2 oraz 5.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

22
23

Zadanie 5.3 (Algorytm Euklidesa). Największy wspólny dzielnik (NWD) dwóch


liczb naturalnych, a i b, takich że a ≥ b, można wyznaczyć korzystając z następującej
własności matematycznej:

NWD(a, b) = NWD(b, a mod b).

Jako że a mod b jest zawsze wartością mniejszą od b (reszta z dzielenia a przez b


wynosi co nawyżej b − 1), powyższą własność można stosować wielokrotnie, aż do
chwili, kiedy odczytanie wartości NWD będzie bardzo proste. Ostatnia niezerowa
składowa jest bowiem największym wspólnym dzielnikiem dwóch wyjściowych
liczb. Na przykład, gdybyśmy chcieli odnaleźć największy wspólny dzielnik liczb
282 i 78, moglibyśmy przeprowadzić następujące obliczenia, z których wynika, że
NWD(282, 78) = 6.

NWD(282, 78) = NWD(78, 282 mod 78) =


= NWD(78, 48) = NWD(48, 78 mod 48) =
= NWD(48, 30) = NWD(30, 48 mod 30) =
= NWD(30, 18) = NWD(18, 30 mod 18) =
= NWD(18, 12) = NWD(12, 18 mod 12) =
= NWD(12, 6) = NWD(6, 12 mod 6) =
= NWD(6, 0) → (koniec algorytmu)
Korzystając z zaprezentowanej powyżej metody, zaprojektuj schemat blokowy
algorytmu, który dla pobranych z wejścia dwóch dodatnich liczb naturalnych, a i b,
takich że a ≥ b, wypisuje na wyjściu ich największy wspólny dzielnik. Na przykład
dla a = 12 i b = 8, algorytm powinien wypisać liczbę 4.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

• Co by się stało, gdyby zachodziła nierówność a < b? Czy wówczas powyższy


algorytm przestałby działać?
• ★ Czy można uogólnić algorytm z tego zadania tak, aby wyznaczał poprawnie
NWD dowolnych liczb całkowitych? Ile wynosi reszta z dzielenia przez liczbę
ujemną?
Tablice

Najprostszą spotykaną w praktyce strukturą danych jest tablica. Możemy ją rozumieć


jako zestaw wielu wartości, do których odwołujemy się za pomocą indeksów. Zwykle
przyjmuje się, że tablica przechowuje dane takiego samego rodzaju (np. wyłącz-
nie liczby), a kolejnym elementom tablicy odpowiadają indeksy będące kolejnymi
liczbami naturalnymi.
W projektowanych przez nas schematach blokowych, a później także w algoryt-
mach opisywanych pseudokodem, będziemy traktować każdą tablicę jak specjalną
zmienną, mogącą przechowywać dowolną liczbę wartości. Ze względu na ten spe-
cjalny charakter tablic, umawiamy się, że ich nazwy będziemy zawsze rozpoczynać
wielką literą, np. Tablica lub T.

Uwaga. Czasami liczba elementów tablicy jest określona z góry. W takich przypad-
kach stosuje się zapis T[1..n], który należy rozumieć jako tablica T zawierająca n
elementów indeksowanych od 1 do n.

Aby odwołać się do konkretnego elementu tablicy, umieszczamy indeks tego


elementu w nawiasach kwadratowych zaraz za nazwą tablicy, np. T[3]. Na poniższym
rysunku zwizualizowano tę ideę — poszczególnym elementom tablicy odpowiadają
konkretne odwołania.

...

T[1] T[2] T[3] T[4]

Odwołanie się do elementu tablicy jest traktowane jak odwołanie do zwykłej zmien-
nej. Oznacza to, że możemy odczytać wartość trzeciego elementu tablicy, pisząc T[3],
a także przypisać temu elementowi nową wartość, pisząc choćby T[3] ≔ T[2] + 7.
Przy odwoływaniu się do konkretnych elementów tablicy, możemy wykorzystywać
również wyrażenia arytmetyczne. Na przykład, odwołanie T[3 ⋅ p] dotyczy elementu
tablicy T o indeksie będącym bieżącą wartością wyrażenia 3 ⋅ p.

Uwaga. Od tej chwili będziemy przyjmować, że element tablicy, któremu nie przy-
pisano żadnej wartości, przechowuje specjalną wartość nil. I odwrotnie, przypisanie
elementowi tablicy wartości nil rozumiemy jako „usunięcie” elementu z tablicy bez
przemieszczania pozostałych elementów.

24
Zestaw 6.

Tablice liczbowe

Zadanie 6.1. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia


dodatnią liczbę naturalną n, a następnie pobiera z wejścia n wartości liczbowych,
które umieszcza w tablicy o nazwie T, na pozycjach o indeksach od 1 do n.

Uwaga. Od tego momentu możesz stosować skrótowy zapis algorytmu z Zadania 6.1,
taki jak poniżej. Po wykonaniu tej procedury, zmienna n będzie przechowywać liczbę
elementów w tablicy T.

Czytaj tablicę T[1..n]

Zadanie 6.2. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia


tablicę liczb T[1..n], a następnie zlicza, ile z liczb umieszczonych w tej tablicy jest
parzystych. Uzyskany wynik powinien zostać wypisany na wyjściu.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Zadanie 6.3. Zmodyfikuj rozwiązanie poprzedniego zadania tak, aby uwzględnić


ewentualne wartości nil występujące we wczytywanej tablicy. Wartości nil nie da się
podzielić, a więc nie można sprawdzić, jaką daje resztę w wyniku dzielenia przez 2.

Zadanie 6.4. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia


tablicę liczb T[1..n], a następnie oblicza i wypisuje na wyjściu sumę jej wszystkich
elementów.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

25
26

Zadanie 6.5. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia ta-
blicę liczb T[1..n], a następnie znajduje i wypisuje na wyjściu najmniejszy znajdujący
się w niej element.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Zadanie 6.6. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia tabli-
cę liczb T[1..n], a następnie znajduje i wypisuje na wyjściu najmniejszy i największy
spośród znajdujących się w niej elementów.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

★★ Zadanie 6.7. Spójrz na rozwiązania poprzednich dwóch zadań. Aby znaleźć


najmniejszy element w nieuporządkowanej tablicy wykonujemy n−1 porównań (czy
da się to zrobić szybciej?). Pokaż, że w przypadku, gdy poszukujemy jednocześnie
najmniejszego i największego elementu w tablicy zawierającej nieparzystą liczbę
elementów, to wystarczy wykonać co najwyżej ⌈3n/2⌉ − 2 porównań.

Zadanie 6.8. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia


tablicę liczb T[1..n] oraz wartość x, a następnie sprawdza, czy w tablicy T znajduje
się element równy x. W zależności od odpowiedzi na to pytanie, algorytm powinien
wypisywać na wyjściu słowo tak lub słowo nie.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

★ Zadanie 6.9. Spójrz na rozwiązanie poprzedniego zadania. Ile elementów tablicy


T należy w najgorszym przypadku sprawdzić w poszukiwaniu elementu x? Czy da
się zagwarantować lepszy wynik? A co, jeśli przyjmiemy, że elementy znajdujące
się w tablicy T są posortowane rosnąco? Dowiedz się, na czym polega algorytm
przeszukiwania binarnego.

★ Zadanie 6.10. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia


tablicę liczb T[1..n], a następnie znajduje i wypisuje na wyjściu dwa najmniejsze
elementy umieszczone w tablicy T, w kolejności malejącej. Przez tablicę T wolno Ci
przejść tylko jeden raz.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Zestaw 7.

Tablice liczbowe, cd.

Uwaga. Rozwiązując zadania z tego zestawu możesz przyjąć, że operacje wyko-


nywane są na wczytanej wcześniej tablicy liczbowej T[1..n]. W związku z tym nie
musisz jej już wczytywać — możesz przejść od razu do rzeczy.

Zadanie 7.1. Dana jest tablica liczbowa T[1..n], w której T[1] ≤ T[2] ≤ T[3] ≤
⋯ ≤ T[n]. Zaprojektuj schemat blokowy algorytmu, który wypisuje na wyjściu
liczbę różnych elementów znajdujących się w tej tablicy. Nie wolno Ci modyfikować
zawartości tablicy T.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Zadanie 7.2. Dana jest tablica liczbowa T[1..n]. Zaprojektuj schemat blokowy algo-
rytmu, który wypisuje na wyjściu liczbę najmniejszych elementów w tej tablicy. Na
przykład, jeśli T = [4, −2, 3, −2, 10, −2], to algorytm powinien wypisać na wyjściu
wartość 3 (bo w tablicy T znajdują się trzy wartości −2).

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Zadanie 7.3. Dana jest tablica liczbowa T[1..n]. Zaprojektuj schemat blokowy algo-
rytmu, który odwraca tę tablicę. Mówiąc inaczej, pierwszy element powinien zostać
zamieniony z ostatnim, drugi z przedostatnim itd. Algorytm nie powinien niczego
wypisywać na wyjściu.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Zadanie 7.4. Dana jest tablica liczbowa T[1..n] zawierająca liczby całkowite z prze-
działu od 1 do n. Zaprojektuj schemat blokowy algorytmu, który wypisuje na wyjściu

27
28

liczbę różnych elementów w tej tablicy. Może Ci się przydać tablica pomocnicza.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

★ Zadanie 7.5. Dana jest tablica liczbowa T[1..n], w której T[1] ≤ T[2] ≤ T[3] ≤
⋯ ≤ T[n]. Zaprojektuj schemat blokowy algorytmu, który wypisuje na wyjściu
dominantę elementów znajdujących się w tablicy T lub słowo „brak”, jeśli dominanta
taka nie istnieje. Nie wolno Ci modyfikować zawartości tablicy T.

Dane: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Wynik: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Zadanie 7.6. Obejrzyj film dostępny pod adresem https://www.youtube.com/


watch?v=4s44rXRdmhQ — autor prezentuje w nim metodę sortowania bąbelko-
wego tablicy liczbowej. Następnie zaprojektuj schemat blokowy algorytmu, który
sortuje rosnąco, z wykorzystaniem algorytmu sortowania bąbelkowego, tablicę licz-
bową T[1..n]. Algorytm nie powinien niczego wypisywać na wyjściu.
Ciągi znaków

Ciąg znaków to skończona tablica symboli, takich jak litery, cyfry, odstępy i zna-
ki specjalne. Taką tablicę możemy zdefiniować wprost, przypisując do zmiennej
tablicowej ciąg znaków otoczony podwójnymi cudzysłowami, tak jak poniżej:

S := „Algorytm”

Powyższa operacja spowoduje powiązanie ze zmienną tablicową S 8-elementowej


tablicy o zawartości takiej jak poniżej.

'A' 'l' 'g' 'o' 'r' 'y' 't' 'm'

S[1] S[2] S[3] S[4] S[5] S[6] S[7] S[8]

Zwróć uwagę, że elementami tablicy S są pojedyncze symbole, które w zapisie wprost


otaczamy pojedynczymi cudzysłowami. Ta metoda pozwala uniknąć nieporozumień
— różnica pomiędzy liczbą 9 a symbolem ‘9’ jest widoczna na pierwszy rzut oka.
Tablice znaków, podobnie jak tablice liczbowe, można modyfikować. Jednym ze
sposobów zmiany ciągu znaków jest wczytanie nowego ciągu znaków do zmiennej
tablicowej o takiej samej nazwie. Drugi polega na zamianie pojedynczych symboli
wchodzących w jego skład, tak jak poniżej.

S[3] := 'k'

W końcu, jako że ciągi znaków traktujemy jako całość, mogą być one — w prze-
ciwieństwie do tablic liczbowych — czytane i wypisywane wprost.

Czytaj S[1..n] Pisz S

29
Zestaw 8.

Ciągi znaków

Uwaga. Od tego momentu umieszczaj specyfikację problemu przy rozwiązaniu


zadania.
Zadanie 8.1. Zaprojektuj schemat blokowy algorytmu, który pobiera z wejścia dwa
ciągi znaków, A[1..n] i B[1..m], a następnie wypisuje na wyjściu słowo tak, jeśli oba
te ciągi są identyczne lub nie, jeśli się między sobą różnią.
Uwaga. Rozwiązując kolejne zadania z tego zestawu przyjmij, że operacje wykony-
wane są na już wczytanych ciągach znaków X i Y o nieznanej z góry długości. Końce
tych ciągów są wyznaczone przez pierwsze wartości nil, które występują w tablicach
znaków X i Y. Nie wolno Ci modyfikować zawartości ciągów X i Y.
Zadanie 8.2. Zaprojektuj schemat blokowy algorytmu, który wypisuje na wyjściu
długość ciągu X. Na przykład, jeśli X = „Algorytm”, to algorytm powinien wypisać
na wyjściu liczbę 8.
Zadanie 8.3. Zaprojektuj schemat blokowy algorytmu, który wypisuje na wyjściu
indeks pierwszej pozycji, na której ciągi X i Y się różnią lub 0, jeśli ciągi X i Y są
identyczne.
Zadanie 8.4. Zaprojektuj schemat blokowy algorytmu, który wypisuje na wyjściu
słowo tak, jeśli każdy znak z ciągu X występuje w ciągu Y. W przeciwnym przypadku
algorytm powinien wypisać na wyjściu pierwszy znak z ciągu X, który nie występuje
w ciągu Y.
★ Zadanie 8.5. Zaprojektuj schemat blokowy algorytmu, który wypisuje na wyjściu
długość najdłuższego początkowego podciągu ciągu X, który składa się wyłącznie
ze znaków nie występujących w ciągu Y.
★ Zadanie 8.6. Zaprojektuj schemat blokowy algorytmu, który wypisuje na wyjściu
indeks pierwszego znaku podciągu Y wewnątrz ciągu X. Jeśli ciąg Y nie występuje
w ciągu X, to algorytm powinien wypisać na wyjściu wartość 0. Na przykład, jeśli
X = „Proroctwo”, a Y = „ro”, to algorytm powinien wypisać na wyjściu wartość 2.
Podobnie, jeśli X = „Proroctwo”, a Y = „rororo”, to algorytm powinien wypisać na
wyjściu wartość 0.

30
Część II

Pseudokody

31
Pseudokod

Poza schematami blokowymi, do zapisywania algorymów można też wykorzystywać


pseudokod. Celem stosowania pseudokodu jest zwięzłe przekazanie idei algoryt-
micznej w formie, którą można niewielkim wysiłkiem przekształcić w implementację
w konkretnym języku programowania. Zwięzłość pseudokodu możemy uzyskać sto-
sując słowa kluczowe, które w wielu przypadkach zastępują skomplikowane konstruk-
cje algorytmiczne stosowane dotąd w schematach blokowych. Poniżej znajdziesz
zestawienie stosowanych przez nas konstrukcji blokowych i ich odpowiedników
zapisanych w pseudokodzie.

Obsługa wejścia i wyjścia

czytaj n
Czytaj n czytaj n ∈ ℕ
czytaj n ∈ ℤ

Pisz 2 ∙ n pisz 2 ⋅ n

Operacje na zmiennych

n := 12 ∙ k
n ≔ 12 ⋅ k
n ← 12 ⋅ k

33
34

Instrukcje warunkowe

warunek
T jeżeli warunek to

warunek jeżeli warunek to


T
N w przeciwnym przypadku
35

Pętle

warunek
T dopóki warunek wykonuj
N

wykonuj

warunek dopóki warunek


T
N

powtarzaj

warunek aż warunek
T
N

i := 1

dla i = 1, … , n wykonuj
i <= n
T
N

i := i + 1
Zestaw 9.

Algorytmy zapisane pseudokodem

Zadanie 9.1. Zapoznaj się z poniższym pseudokodem. Przekształć go do postaci


schematu blokowego, a następnie odpowiedz na pytanie, co robi ten algorytm.
czytaj a, b ∈ ℤ ⧵ {0}
dopóki b ≠ 0 wykonuj
c ← a mod b
a←b
b←c
pisz a

Zadanie 9.2. Zapoznaj się z poniższym pseudokodem. Przekształć go do postaci


schematu blokowego, a następnie odpowiedz na pytanie, co robi ten algorytm.
czytaj k, a ∈ ℕ+
w=1
dopóki a ≠ 0 wykonuj
w ←w⋅k
a←a−1
pisz w

Zadanie 9.3. Przeanalizuj poniższy pseudokod. Przekształć go do postaci schematu


blokowego, a następnie odpowiedz na pytanie, co robi ten algorytm.
czytaj k, a ∈ ℕ+
p←1
dopóki a ≠ 0 wykonuj
jeżeli a mod 2 ≠ 0 to
p←p⋅k
k ←k⋅k
a ← a div 2
pisz p

36
37

Zadanie 9.4. Przeanalizuj poniższy pseudokod. Ten algorytm powinien wypisywać


pobrane z wejścia liczby całkowite a, b i c w kolejności rosnącej. Uzupełnij pseudokod
tak, aby algorytm spełniał to zadanie.
czytaj a, b, c ∈ ℤ
jeżeli a < b to
jeżeli b < c to
pisz a, b, c
w przeciwnym przypadku jeżeli a < c to
pisz ..............
w przeciwnym przypadku
pisz c, a, b
w przeciwnym przypadku
jeżeli b > c to
pisz ..............
w przeciwnym przypadku jeżeli c > a to
pisz b, a, c
w przeciwnym przypadku
pisz b, c, a

Zadanie 9.5. Algorytm o znajdującym się poniżej pseudokodzie powinien wypi-


sywać wynik dzielenia całkowitego dodatnich liczb naturalnych a i b. Czy tak się
dzieje? Jeśli nie, to popraw ten algorytm tak, aby działał prawidłowo.
czytaj a, b ∈ ℕ+
k=0
dopóki a > 0 wykonuj
a←a−b
k ←k+1
pisz k
Zestaw 10.

Liczby i ciągi liczbowe

Zadanie 10.1. Napisz pseudokod algorytmu, który pobiera z wejścia liczby całko-
wite tak długo, aż natrafi na liczbę parzystą. Pierwsza odczytana liczba parzysta
powinna zostać zapisana w zmiennej o nazwie n.

Zadanie 10.2. Napisz pseudokod algorytmu, który czyta z wejścia dodatnią liczbę
naturalną n, a następnie wypisuje na wyjściu wszystkie liczby naturalne z przedziału
od 1 do n:

(a) w porządku rosnącym,


(b) w porządku malejącym.

Zadanie 10.3. Napisz pseudokod algorytmu, który czyta z wejścia dodatnią liczbę
naturalną n, a następnie wypisuje na wyjściu wszystkie te liczby z przedziału od 1
do n, które:

(a) dzielą się przez 3,


(b) dzielą się przez 2 i 5,
(c) dzielą się przez 4 lub dzielą się przez 2,
(d) dzielą się przez 5 lub dzielą się przez 3.

Zadanie 10.4. Napisz pseudokod algorytmu, który czyta z wejścia dodatnią liczbę
naturalną k, a następnie wypisuje na wyjściu wartość k-tego wyrazu ciągu liczbowego
zdefiniowanego wzorem:

3 dla k = 1,
(a) ak = {
2 ⋅ ak−1 − 3 dla k > 1.
5 dla k = 1,
(b) ak = {
ak−1 + 7 dla k > 1.
⎧1 dla k = 1,
{
(c) ak = 3 dla k = 2,

{a
⎩ k−2 + 6 dla k > 2.

38
39

⎧1 dla k = 1,
{
(d) ak = 3 dla k = 2,

{a
⎩ k−2 + ak−1 dla k > 2.
Zadanie 10.5. Napisz pseudokod algorytmu, który czyta z wejścia dodatnie liczby
naturalne k i n, takie że 1 ≤ k ≤ n, a następnie wypisuje na wyjściu wartość
wyrażenia:

(a) n ⋅ (n + 1) ⋅ (n + 2) ⋅ ⋯ ⋅ (n + k − 1),
(b) (n − k + 1) ⋅ (n − k + 2) ⋅ … n,
(c) (n − k)!.

Zadanie 10.6. Napisz pseudokod algorytmu, który czyta z wejścia dodatnie liczby
naturalne k i n, takie że 1 ≤ k ≤ n, a następnie wypisuje na wyjściu wartość
wyrażenia
n n!
( )= .
k (n − k)! ⋅ k!
Upewnij się, że wszyskie uzyskane przez Ciebie wyniki pośrednie będą liczbami
całkowitymi.
Zestaw 11.

Algorytmy liczbowe

Zadanie 11.1. Napisz pseudokod algorytmu, który czyta z wejścia dodatnią liczbę
naturalną n, a następnie weryfikuje, czy liczba ta jest pierwsza. Algorytm powinien
wypisywać słowo tak, jeśli liczba n jest pierwsza oraz nie, jeśli tak nie jest.

Zadanie 11.2 (Sito Eratostenesa). Dowiedz się, na czym polega metoda wyznacza-
nia liczb pierwszych znajdujących się w przedziale od 1 do n, zwana sitem Erato-
stenesa. Następnie napisz pseudokod algorytmu, który dla danej dodatniej liczby
naturalnej n wypisuje na wyjściu wszystkie liczby pierwsze mniejsze od n.

Zadanie 11.3. Napisz pseudokod algorytmu, który czyta z wejścia dodatnią liczbę
naturalną n, a następnie wypisuje na wyjściu rozkład tej liczby na czynniki pierwsze.

Zadanie 11.4. Liczbę naturalną n nazywamy liczbą półpierwszą, jeśli jest iloczynem
dokładnie dwóch — niekoniecznie różnych — liczb pierwszych. Na przykład, liczba
6 jest liczbą półpierwszą, ponieważ 6 = 2 ⋅ 3, a 7 i 8 nie są liczbami półpierwszymi.
Napisz pseudokod algorytmu, który czyta z wejścia dodatnią liczbę naturalną n, a
następnie weryfikuje, czy liczba ta jest półpierwsza. Algorytm powinien wypisywać
słowo tak, jeśli liczba n jest półpierwsza oraz nie, jeśli tak nie jest. Swoje rozwiązanie
oprzyj na rozwiązaniu zadania 11.3.

Zadanie 11.5. Dodatnią liczbę naturalną n nazywamy liczbą kwadratową, jeśli


jest kwadratem liczby naturalnej. Na przykład, liczba 25 jest liczbą kwadratową,
ponieważ 25 = 52. Napisz pseudokod algorytmu, który czyta z wejścia dodatnią
liczbę naturalną n, a następnie weryfikuje, czy liczba ta jest liczbą kwadratową.
Algorytm powinien wypisywać słowo tak, jeśli liczba n jest kwadratowa oraz nie,
jeśli tak nie jest. Swoje rozwiązanie możesz oprzeć na rozwiązaniu zadania 11.3.

★★ Zadanie 11.6. Spróbuj znaleźć lepszy sposób weryfikacji, czy dana liczba jest
kwadratowa. Czy można to zrobić adaptując odpowiednio algorytm wyszukiwania
binarnego z zadania 6.9?

Zadanie 11.7. Napisz pseudokod algorytmu, który czyta z wejścia dodatnie liczby
naturalne k i l, a następnie wypisuje na wyjściu ich najmniejszą wspólną wielokrot-
ność. Rozwiązując to zadanie, nie możesz obliczać wartości NWD tych liczb.

40
41

★★ Zadanie 11.8 (Rozszerzony algorytm Euklidesa). Niech dane będą dwie licz-
by całkowite, a i b. Dla dowolnych takich liczb możemy znaleźć dwie inne liczby
całkowite, x i y, takie że

NWD(a, b) = x ⋅ a + y ⋅ b.

Znalezienie odpowiednich wartości x i y umożliwia nam rozszerzony algorytm


Euklidesa. Znajdź o nim informacje i przygotuj pseudokod algorytmu, który dla
danych wartości a i b znajduje i wypisuje odpowiednie wartości x i y, takie że
NWD(a, b) = x ⋅ a + y ⋅ b.

Zadanie 11.9. Liczbę naturalną n nazywamy liczbą doskonałą, jeśli suma wszystkich
dzielników właściwych liczby n (mniejszych od tej liczby) jest równa n. Na przykład,
liczba 6 jest liczbą doskonałą, ponieważ dzieli się przez 1, 2 i 3, a 1 + 2 + 3 = 6. Napisz
pseudokod algorytmu, który czyta z wejścia dodatnią liczbę naturalną n, a następnie
weryfikuje, czy liczba ta jest doskonała. Algorytm powinien wypisywać słowo tak,
jeśli liczba n jest doskonała oraz nie, jeśli tak nie jest.

Zadanie 11.10. Liczby naturalne k i l nazywamy liczbami zaprzyjaźnionymi, jeśli su-


ma wszystkich dzielników właściwych liczby k jest równa l, a suma wszystkich dziel-
ników właściwych liczby l jest równa k. Na przykład, liczby 220 i 284 są zaprzyjaźnio-
ne, ponieważ suma dzielników właściwych liczby 220 wynosi 1+2+4+71+142 = 284,
a suma dzielników właściwych liczby 284 wynosi 1 + 2 + 4 + 5 + 10 + 11 + 20 + 22 + 44 +
55 + 110 = 220. Napisz pseudokod algorytmu, który czyta z wejścia dodatnie liczby
naturalne k i l, a następnie weryfikuje, czy liczba te są zaprzyjaźnione. Algorytm
powinien wypisywać słowo tak, jeśli liczby k i l są zaprzyjaźnione oraz nie, jeśli tak
nie jest.

Zadanie 11.11. Napisz pseudokod algorytmu, który czyta z wejścia dodatnią liczbę
naturalną n, a następnie:

(a) wypisuje na wyjściu wszystkie jej cyfry składowe, zaczynając od ostatniej,


(b) wypisuje na wyjściu wszystkie jej cyfry składowe, zaczynając od pierwszej,
(c) wypisuje na wyjściu reprezentację binarną liczby n.
Typy danych

W przygotowywanych przez nas algorytmach wykorzystywaliśmy dotąd jedynie


dwa typy danych: liczby całkowite i symbole. Na przykład, ciąg znaków zdefiniowa-
liśmy jako tablicę symboli. Od teraz będziemy stosować więcej typów danych, które
zaprezentowano w poniższej tabeli.

Typ danych Inne nazwy Komentarz


Liczby całkowite integer, int W każdym języku programowania istnieją
limity reprezentacyjne dla liczb całkowitych,
tzn. najmniejsza i największa liczba całko-
wita, jaką można zapisać. Tego typu ograni-
czeniami nie musimy się przejmować przy
tworzeniu pseudokodów, chociaż warto.
Liczby wymierne float, double Liczby wymierne to takie liczby, które moż-
na zapisać w postaci ułamka dwóch liczb cał-
kowitych. W większości języków programo-
wania istnieją ograniczenia dotyczące moż-
liwych wartości takich liczb. Na przykład,
stosując zapis pozycyjno-wagowy, nie da się
zapisać w systemie binarnym dokładnej war-
1
tości liczby 10 .
Watości logiczne bool, boolean Wartość logiczna to jedna z dwóch wartości:
prawda lub fałsz. Do tej pory spotykaliśmy
się z wartościami logicznymi w kontekście
bloków warunkowych.
Znaki char Pojedyncze znaki, wśród których znajdziemy
litery, cyfry i znaki odstępu.
Ciągi znaków char[], string Ciągi znaków to po prostu tablice znaków.

42
Operacje logiczne

Jak już ustaliliśmy, warunki wykorzystywane przez nas w blokach warunkowych


przyjmują jedną z dwóch wartości logicznych: prawda lub fałsz. Gdybyśmy chcieli
zweryfikować, czy kilka warunków zachodzi w pewnej relacji, moglibyśmy co prawda
sprawdzić je wszystkie, jeden po drugim (por. zadanie 9.4), ale w wielu sytuacjach
komplikuje to nie tylko proces tworzenia, ale i odczytywania pseudokodu.
Tak jak wyrażenia arytmetyczne mogliśmy łączyć ze sobą operatorami (dodawa-
nia, odejmowania itd.), uzyskując w ten sposób nowe wartości liczbowe o określonej
interpretacji (suma, różnica itd.), tak możemy łączyć ze sobą wyrażenia logiczne,
uzyskując w ten sposób nowe wartości logiczne. Na przykład, jeśli zachodzi waru-
nek p, a jednocześnie zachodzi warunek q, to możemy powiedzieć, że prawdą jest, iż
zachodzi warunek p i zachodzi warunek q. Wyróżniamy trzy podstawowe operatory
logiczne, zebrane w poniższej tabeli. W tej tabeli wartości p i q oznaczają wyrażenie
logiczne, w tym również wartości logiczne.

Tabela 3: Operatory logiczne

Operator Przykład Przyjmuje wartość prawda wtedy i tylko wtedy, gdy...


i piq ...p jest prawdziwe oraz q jest prawdziwe.
lub p lub q ...co najmniej jedno z wyrażeń, p lub q, jest prawdziwe.
nie nie p ...p jest fałszywe.

43
Zestaw 12.

Typy danych

Zadanie 12.1. Operator arytmetyczny / jest operatorem dzielenia w zbiorze liczb


wymiernych. Wiedząc to, napisz pseudokod algorytmu, który dla danej dodatniej
1
liczby naturalnej n wypisuje na wyjściu wartość wyrażenia n! .
Zadanie 12.2. Napisz pseudokod algorytmu, który dla danej dodatniej liczby na-
turalnej n wypisuje na wyjściu sumę kolejnych odwrotności silni, od 1 do n. Na
przykład, dla n = 3, wartość wypisana na wyjściu powinna być równa
1 1 1
+ + .
1! 2! 3!
Zadanie 12.3. Napisz pseudokod algorytmu, który dla danej dodatniej liczby wy-
miernej x i dodatniej liczby naturalnej n wypisuje na wyjściu sumę iloczynów liczby
x przez kolejne liczby parzyste, od 2 do n włącznie. Na przykład, dla x = 0,3 i n = 7,
wartość wypisna na wyjściu powinna być równa

0,3 ⋅ 2 + 0,3 ⋅ 4 + 0,3 ⋅ 6.

(a) Zastosuj podejście iteracyjne, dodając do wyniku pośredniego kolejne wyrazy


poszukiwanej sumy.
(b) Wyraź wartość poszukiwanej sumy wzorem zależnym od wartości n oraz x,
a następnie wykorzystaj go w swoim rozwiązaniu.
Zadanie 12.4. Napisz pseudokod algorytmu, ktory dla danej dodatniej liczby na-
turalnej n wypisuje na wyjściu wszystkie liczby naturalne z przedziału od 1 do n,
które:
(a) dzielą się przez 7,
(b) nie dzielą się przez 7,
(c) dzielą się przez co najmniej jedną z liczb: 3 i 5,
(d) dzielą się albo przez 3, albo przez 5.
★★ Zadanie 12.5. Jedną z metod wyznaczania przybliżonej wartości pierwiastka
kwadratowego z dodatniej liczby wymiernej x jest algorytm Newtona-Raphsona.
Algorytm ten stosuje podejście iteracyjne, w którym rozpoczynając od pewnego

44
45

łatwego do określenia przybliżenia p > 0 wartości √x, z każdą iteracją zbliża się do
prawidziwej wartości tego pierwiastka. Działanie algorytmu kończy się wtedy, gdy
liczba iteracji przekroczy liczbę μ lub gdy dokładność przybliżenia osiągnie wartość
lepszą niż ε. Pseudokod algorytmu Newtona-Raphsona prezentuje się następująco:
czytaj x, ε ∈ ℚ+ , μ ∈ ℕ+
y ← x/2
i←0
powtarzaj
p←y
y ← (p + x/p)/2
i←i+1
aż i > μ lub |p − y| < ε
pisz p

(a) Jaka jest interpretacja geometryczna tego algorytmu? Zauważ, że x może być
traktowane jako pole prostokąta o bokach długości p i x/p.
(b) Zaimplementuj ten algorytm w programie Microsoft Excel. Następnie wy-
znacz wartość pierwiastka z liczby 2 z dokładnością do 12 miejsc po przecinku.
(c) W powyższym algorytmie przyjmujemy, że początkowym przybliżeniem
wartości √x jest wartość x/2. Jak sądzisz, czy to dobre podejście?
(d) W jaki sposób można uogólnić zaprezentowany powyżej algorytm Newtona-
Raphsona do przypadku, w którym próbujemy odnaleźć pierwiastek trzeciego
stopnia z danej liczby x.

Zadanie 12.6. Załóżmy, że do zapisu wymiernej liczby dziesiętnej wykorzystujemy


system pozycyjno-wagowy z dokładnie sześcioma cyframi. Jeśli założymy, że cyfra
jedności znajduje się na ostatniej pozycji, to jesteśmy w stanie zapisać dowolną
liczbę całkowitą z przedziału od 0 do 999999. Przyjmijmy teraz, że cyfra jedności
znajduje się na trzeciej pozycji ciągu.

(a) Jaka jest najmniejsza dodatnia liczba dziesiętna, którą możemy zapisać w po-
staci XXX,XXX? A największa?
(b) Jaka jest różnica pomiędzy dwiema kolejnymi liczbami dziesiętnymi, które
podlegają takim ograniczeniom?
(c) Ile w takich okolicznościach wynosi wartość wyrażenia 0,001/3? Będzie to
0,001 czy może 0?

★ Zadanie 12.7. Dowiedz się, w jaki sposób zapisuje się liczby niecałkowite w pa-
mięci komputera. Przeanalizuj metodę zapisu stałoprzecinkowego i zmiennoprze-
cinkowego, a następnie zastanów się, jakie ograniczenia niesie za sobą każda z tych
metod. Czy rozważania z zadania 12.6 mają tu zastosowanie?
Zestaw 13.

Złożoność algorytmów

Zadanie 13.1. Poniżej zaprezentowano dwa algorytmy, które robią dokładnie to


samo. Mając dane dwie dodatnie liczby naturalne, a i b, wyznaczają i wypisują na
wyjściu ich największy wspólny dzielnik.
czytaj a, b ∈ ℕ+ czytaj a, b ∈ ℕ+
dopóki b ≠ 0 wykonuj dopóki a ≠ b wykonuj
c ← a mod b jeżeli a > b to
a←b a←a−b
b←c w przeciwnym przypadku
pisz a b←b−a
pisz a
(a) Ile operacji przypisania wartości do zmiennej zostanie wykonanych w obu
przypadach, jeśli a = 31 i b = 7? A ile, jeśli a = 600 i b = 6?
(b) W jakich przypadkach pierwszy z algorytmów działa błyskawicznie, a drugi
bardzo wolno? Czy kiedykolwiek zachodzi sytuacja odwrotna?
(c) Jak myślisz, dlaczego wystarczy, że policzymy operacje przypisania? A może
należałoby policzyć operacje arytmetyczne lub operacje logiczne? Jak się
ma liczba operacji arytmetycznych do liczby operacji przypisania? A liczba
wykonanych operacji logicznych do liczby wykonanych operacji przypisania?

Zadanie 13.2. Poniżej zaprezentowano dwa algorytmy, które robią dokładnie to


samo. Mając dane dwie dodatnie liczby naturalne, a i b, takie że a > b, wyznaczają
i wypisują na wyjściu ich największy wspólny dzielnik.
czytaj a, b ∈ ℕ+ czytaj a, b ∈ ℕ+
dopóki b ≠ 0 wykonuj q←1
c ← a mod b dla i ← 2, … , b wykonuj
a←b jeżeli a mod i = 0 i b mod i = 0 to
b←c q←i
pisz a pisz q
(a) Ile operacji arytmetycznych zostanie wykonanych w obu przypadkach, jeśli
a = 29 i b = 5? A ile, jeśli a = 300 i b = 6?

46
47

(b) ★ Ile maksymalnie operacji arytmetycznych będzie potrzebował pierwszy


i drugi algorytm, aby znaleźć NWD liczb a i b, jeśli wiadomo tylko tyle, że b
jest liczbą 3-cyfrową? A 10-cyfrową?
(c) ★ Sporządź wykres, który pokaże jak w przypadku obu algorytmów zmienia
się maksymalna liczba operacji arytmetycznych niezbędnych do znalezienia
NWD liczb a i b, w zależności od długości liczby b. Szczególną uwagę poświęć
analizie pierwszego algorytmu.
Zadanie 13.3. Poniżej zaprezentowano dwa algorytmy, które robią dokładnie to
samo. Mając daną dodatnią liczbę naturalną n, wyznaczają i wypisują na wyjściu
sumę liczb naturalnych od 1 do n.
czytaj n ∈ ℕ+ czytaj n ∈ ℕ+
s←0 s ← (n ⋅ (n + 1))/2
dla i ← 1, … , n wykonuj pisz s
s←s+i
pisz s
(a) Ile operacji arytmetycznych zostanie wykonanych w obu przypadach, jeśli
n = 300? A ile, jeśli n = 2?
(b) Sporządź wykres, który — dla każdego algorytmu z osobna — przedstawia
liczbę wykonanych operacji arytmetycznych i operacji przypisania w zależno-
ści od wartości liczby n.
Zadanie 13.4. Poniżej zaprezentowano dwa algorytmy, które robią dokładnie to
samo. Mając daną dodatnią liczbę naturalną k, wyznaczają i wypisują na wyjściu
k-ty wyraz ciągu Fibonacciego.
czytaj k ∈ ℕ+ czytaj k ∈ ℕ+
Fib[1] ← 1 a←1
Fib[2] ← 1 b←1
dla i ← 3, … , k wykonuj dla i ← 3, … , k wykonuj
Fib[i] ← Fib[i − 2] + Fib[i − 1] c ←a+b
pisz Fib[k] a←b
b←c
pisz b
(a) Ile operacji arytmetycznych zostanie wykonanych w obu przypadach, jeśli
k = 100? A ile, jeśli k = 1000?
(b) Ile wartości liczbowych musi przechować w pamięci każdy z algorytmów, jeśli
k = 2? Jak zmieni się ta wartość dla k = 4 lub k = 100?
(c) ★ Zmodyfikuj algorytm po prawej stronie tak, aby wykorzystanie zmiennej
pomocniczej c nie było konieczne.
Zadanie 13.5. Wybierz trzy dowolne algorytmy, które do tej pory przygotowa-
łaś/eś. Przeanalizuj, jak wiele operacji (arytmetycznych, logicznych lub przypisania)
wymagają one do rozwiązania zadania. Spróbuj je ulepszyć.
Funkcje i procedury

Stworzyliśmy już pseudokody wielu różnych algorytmów. Niektóre z nich mogłyby


nam posłużyć jako część rozwiązania większych problemów. Na przykład, algo-
rytm obliczania wartości wyrażenia ka mógłby stanowić ważną część algorytmu
obliczającego wartość zadanego wielomianu w konkretnym punkcie1 . Zauważ, że
pewne działania algorytmiczne (np. potęgowanie, wyznaczanie NWD dwóch liczb,
weryfikacja pierwszości liczby itp.) mogłyby być przez nas traktowane jak czarne
skrzynki. Takie czarne skrzynki nazywamy funkcjami lub procedurami. Spójrz na
poniższy przykład, aby zobaczyć, do czego zmierzamy.
jeżeli NWD(17, 2) = 1 to
pisz „Liczby są względnie pierwsze.”
w przeciwnym przypadku
pisz „Liczby nie są względnie pierwsze.”
Każde wywołanie funkcji wiąże się z uzyskaniem z powrotem jakiejś wartości
— mówimy, że funkcja zwraca pewną wartość. Z tego powodu każde wywołanie
funkcji może być utożsamiane z konkretną wartością logiczną lub arytmetyczną,
w zależności od rodzaju zwracanej wartości. Na przykład, w miejscu wywołania
funkcji NWD w powyższym algorytmie powinna się znaleźć liczba, która odpowiada
największemu wspólnemu dzielnikowi liczb 17 i 2. Taką wartość liczbową możemy
porównać do jedynki i podjąć odpowiednie działanie, w zależności od wyniku tego
porównania. Procedury, w przeciwieństwie do funkcji, nie zwracają żadnej wartości,
a zatem nie mogą stanowić części wyrażeń. Ale skąd wiadomo, co tak naprawdę
robi funkcja NWD? To po prostu inny algorytm!

Definiowanie funkcji i procedur

Funkcje i procedury mają nazwy oraz przyjmują argumenty, które wpływają na


ostateczny efekt ich działania. Na pewno zgodzisz się, że wywołania NWD(10, 5)
i NWD(12, 5) powinny zwrócić różne wartości (w pierwszym przypadku 5, a w dru-
gim 1). Każda funkcja i procedura jest więc ogólnym algorytmem, który możemy
zdefiniować jako jednolitą całość. Funkcje i procedury zapisujemy jako blok pseu-
kodu rozpoczynający się od słowa funkcja lub procedura, nazwy definiowanej
1
W praktyce tak nie jest, ponieważ istnieją o wiele szybsze metody obliczania wartości wielomianu
w punkcie niż sumowanie kolejnych potęg, por. schemat Hornera.

48
49

funkcji (procedury) i umieszczonej w nawiasach listy argumentów. Na przykład,


funkcję NWD moglibyśmy zdefiniować następująco:
funkcja NWD(a, b)
dopóki b ≠ 0 wykonuj
c ← a mod b
a←b
b←c
zwróć a
Wewnąrz funkcji musi wystąpić instrukcja zwróć — funkcja musi być tak zaprojek-
towana, aby zawsze zwracała wartość, niezależnie od wartości argumentów. W chwili,
w której należy wykonać instrukcję zwróć, działanie funkcji kończy się, ponieważ
zwracana przez nią wartość może już zostać jednoznacznie określona. Wykonywa-
nie procedury kończy się wtedy, gdy z jej przebiegu wynika, że nie ma już żadnej
instrukcji do wykonania.
Argumenty przekazane do funkcji (procedury) występują z jej wnętrzu jako
konkretne zmienne. Te zmienne nie są dostępne spoza funkcji (procedury), a w defi-
nicji funkcji (procedury) mają charakter abstrakcyjny — nazywamy je parametrami
formalnymi. Moglibyśmy powiedzieć więc, że:

Funkcja NWD implementuje algorytm wyznaczania największego wspól-


nego dzielnika dwóch liczb. Dla danych, ale nieznanych z góry dwóch
wartości, a i b, wyznacza i zwraca ich NWD. Tak zwrócona wartość
może być później wykorzystana w dowolny sposób.

Zwróć uwagę, że funkcja NWD nie wypisuje niczego na wyjściu. Gdybyśmy chcieli
wypisać na wyjściu największy wspólny dzielnik wskazanych liczb, moglibyśmy
napisać
pisz NWD(17, 3)

Uwaga. Zawsze definiuj funkcje w taki sposób, aby były jak najbardziej uniwersalne.
Funkcja, która wypisuje cokolwiek na wyjściu jest niepraktyczna, ponieważ trudno
ją wykorzystać w sytuacjach, w których wyznaczana przez nią wartość jest krokiem
pośrednim większego algorytmu.

Wywoływanie funkcji i procedur

Aby wywołać funkcję (procedurę), należy wskazać jej nazwę, a w nawiasach umieścić
w odpowiedniej kolejności argumenty, np.
NWD(31, 7)
Argumenty przekazane do funkcji (procedury) mogą być:

(a) stałymi wartościami (np. 3, prawda),


(b) wyrażeniami logicznymi (np. n < 3),
(c) wyrażeniami arytmetycznymi (np. 2 ⋅ n + 7 lub T[3]),
50

(d) nazwami tablic (np. tablic liczbowych i ciągów znaków).

W przypadku gdy argument jest wyrażeniem logicznym lub arytmetycznym, jego


wartość jest wyznaczana zanim funkcja (procedura) zostanie wywołana. W praktyce
oznacza to, że zmiana wartości argumentów liczbowych i logicznych wewnątrz
funkcji (procedury) nie ma żadnego wpływu na wartości zmiennych, które posłużyły
do ich wyznaczenia.
procedura Zwiększ(a)
a←a+1
k←3
Zwiększ(k) ▷ Wywoła Zwiększ(3), a nie Zwiększ(k).
pisz k ▷ Wypisze wartość 3.
Jeżeli argument jest tablicą (nazwą tablicy), to wszelkie modyfikacje tej tablicy prze-
prowadzone wewnątrz funkcji (procedury) mają wpływ na wartości przechowywane
w oryginalnej tablicy, np.
procedura Zmień(T, k)
T[k] = ’*’
S ← „Algorytm”
m←3
Zmień(S, m) ▷ Wywoła Zmień(S, 3)
pisz S ▷ Wypisze „Al*orytm”
Zestaw 14.

Proste funkcje

Zadanie 14.1. Czy moglibyśmy powiedzieć, że operator jest specyficznym rodzajem


funkcji? Dlaczego tak? Dlaczego nie? W jaki sposób można by określić kolejność,
w której wyznaczana jest wartość rozbudowanego wyrażenia arytmetycznego lub
logicznego, np.
a + b ⋅ c div d − e?
Czy taki sam problem mamy w przypadku zagnieżdżonych wywołań funkcji?

Zadanie 14.2. Zdefiniuj funkcję Max(x, y), która przyjmuje jako argumenty dwie
liczby wymierne, a następnie wyznacza i zwraca większą z nich.

Zadanie 14.3. Zdefiniuj funkcję Abs(x), która przyjmuje jako argument liczbę
wymierną, a następnie wyznacza i zwraca jej wartość bezwzględną.

Zadanie 14.4. Załóżmy, że istnieje funkcja Round(x), która zwraca liczbę będącą
efektem matematycznego zaokrąglenia liczby wymiernej x do najbliższej liczby
całkowitej.

(a) Zdefiniuj funkcję Floor(x), która dla danej liczby wymiernej x zwraca naj-
większą liczbę całkowitą mniejszą od x.
(b) Zdefiniuj funkcję Ceil(x), która dla danej liczby wymiernej x zwraca naj-
mniejszą liczbę całkowitą większą od x.

Zadanie 14.5. Zdefiniuj funkcję Distance(x, y), która przyjmuje jako argumenty
dwie liczby wymierne, x i y, a następnie wyznacza i zwraca wartość wyrażenia |x −y|.

Zadanie 14.6. Zdefiniuj funkcję FirstEven(T, n), która przyjmuje jako argumenty
tablicę liczb całkowitych oraz liczbę jej elementów, a następnie znajduje i zwraca
pierwszy parzysty element tej tablicy lub nil, jeśli w tablicy nie występuje żaden
parzysty element.

• Czy możemy zrezygnować z drugiego argumentu przekazywanego do funk-


cji, skoro koniec tablicy jest wyznaczony przez pierwszą występującą w niej
wartość nil?

51
52

Zadanie 14.7. Zdefiniuj funkcję Power(k, a), która przyjmuje jako argumenty
dwie dodatnie liczby naturalne, a następnie oblicza i zwraca wartość wyrażenia ka .

★ Zadanie 14.8. Spójrz na rozwiązanie poprzedniego zadania.

(a) Ile mnożeń musi wykonać naiwny algorytm, aby obliczyć wartość wyrażenia
k6 ? Zaproponuj usprawnienie, które pozwoli obliczyć szóstą potęgę dowolnej
liczby naturalnej z użyciem jedynie trzech mnożeń.
(b) Przyjmijmy, że znamy binarną reprezentację wykładnika potęgi liczby k. Czy
w takiej sytuacji możemy istotnie zredukować liczbę mnożeń dla ogólnego
przypadku?
Zestaw 15.

Funkcje operujące na tablicach

Zadanie 15.1. Zdefiniuj funkcję Min(T, n), która przyjmuje jako argumenty ta-
blicę liczb wymiernych oraz liczbę jej elementów, a następnie znajduje i zwraca
najmniejszy element występujący w tej tablicy.
Zadanie 15.2. Zdefiniuj funkcję MinP(T, k, l), która przyjmuje jako argumenty
tablicę liczb wymiernych oraz indeksy graniczne, a następnie znajduje i zwraca
najmniejszy spośród elementów T[k], T[k + 1], … , T[l]. Załóż, że 1 ≤ k ≤ l ≤ n,
gdzie n jest liczbą elementów w tablicy T.
Zadanie 15.3. Zdefiniuj funkcję MinI(T, k, l), która przyjmuje jako argumenty
tablicę liczb wymiernych oraz indeksy graniczne, a następnie znajduje i zwraca indeks
najmniejszego spośród elementów T[k], T[k + 1], … , T[l]. Załóż, że 1 ≤ k ≤ l ≤ n,
gdzie n jest liczbą elementów w tablicy T.
Zadanie 15.4. Zdefiniuj funkcję Swap(T, k, l), która przyjmuje jako argumenty
tablicę liczb wymiernych oraz dwa indeksy jej elementów, a następnie zamienia
miejscami elementy T[k] i T[l]. Załóż, że 1 ≤ k, l ≤ n, gdzie n jest liczbą elementów
w tablicy T.
• ★★ Zwykle zamiana wartości dwóch zmiennych między sobą wymaga użycia
zmiennej pomocniczej. Dowiedz się, w jaki sposób można zamienić wartości
dwóch zmiennych miejscami bez wykorzystania zmiennej pomocniczej. Jakie
warunki muszą być spełnione, aby było to możliwe?
Zadanie 15.5 (por. 7.4). Dana jest tablica liczbowa T[1..n] zawierająca liczby cał-
kowite z przedziału od 1 do n. Zdefiniuj funkcję Counter(T, n), która znajduje i
zwraca liczbę różnych elementów tej tablicy. Nie wolno Ci modyfikować zawartości
tablicy T.
★★ Zadanie 15.6. Rozszerz rozwiązanie poprzedniego zadania na przypadek,
w którym nie znamy z góry możliwych wartości elementów przechowywanych
w tablicy T. Jakie problemy należy rozwiązać w tym przypadku?
Zadanie 15.7 (por. 7.5). Dana jest tablica liczbowa T[1..n], w której T[1] ≤ T[2] ≤
T[3] ≤ ⋯ ≤ T[n]. Zdefiniuj funkcję Mode(T, n), która znajduje i zwraca dominantę

53
54

elementów znajdujących się w tablicy T lub wartość nil, jeśli dominanta taka nie
istnieje. Nie wolno Ci modyfikować zawartości tablicy T.

Zadanie 15.8. Zdefiniuj funkcję Evaluate(T, n, x), która przyjmuje jako argumen-
ty tablicę liczb wymiernych, liczbę jej elementów oraz dowolną liczbę wymierną,
a następnie oblicza i zwraca wartość wyrażenia

T[1] ⋅ xn−1 + T[2] ⋅ xn−2 + T[3] ⋅ xn−3 + ⋯ + T[n].

Wykorzystaj funkcję zdefiniowaną w zadaniu 14.7.

★ Zadanie 15.9 (Iteracyjny schemat Hornera). Zauważ, że dowolny wielomian


stopnia n − 1, taki jak w poprzednim zadaniu, można zapisać w postaci:

(… (((T[1] ⋅ x) + T[2]) ⋅ x + T[3]) ⋅ x + … ) ⋅ x + T[n].

Zdefiniuj funkcję EvaluateHorner(T, n, x), która przyjmuje jako argumenty ta-


blicę liczb wymiernych, liczbę jej elementów oraz dowolną liczbę wymierną, a następ-
nie oblicza i zwraca wartość wielomianu określonego tablicą T w oparciu o powyższą
własność.

Zadanie 15.10. Zdefiniuj procedurę SortMin(T, n), która przyjmuje jako argu-
menty tablicę liczb wymiernych oraz liczbę jej elementów, a następnie sortuje ro-
snąco zawartość wskazanej tablicy. Wykorzystaj to, że potrafisz odnaleźć indeks
najmniejszego elementu w podtablicy. Rozwiązując to zadanie możesz wykorzystać
operator ↔, który pozwala zamienić ze sobą miejscami wartości przechowywane
w zmiennych. Na przykład, instrukcja x ↔ y zamienia miejscami wartości przecho-
wywane w zmiennych x i y.

Zadanie 15.11. Zapoznaj się z pseudokodem poniższej procedury sortującej tablicę


T[1..n]. Spróbuj zwizualizować działanie tego algorytmu, a następnie wykonaj go
dla przykładowej 8-elementowej tablicy [1, 3, 5, 4, 2, 7, 6, 8].
procedura InsertionSort(T, n)
dla i ← 2, … , n wykonuj
k ← T[i]
j←i−1
dopóki j > 0 i T[j] > k wykonuj
T[j + 1] ← T[j]
j←j−1
T[j + 1] ← k

★ Zadanie 15.12. Zmodyfikuj procedurę zaprezentowaną w poprzednim zadaniu


tak, aby mogła służyć do sortowania podtablicy określonej indeksami granicznymi.
Tablice dwuwymiarowe

W wielu rozważanych algorytmach wykorzystywaliśmy już tablice zawierające liczby


lub symbole. Tablice, jako struktury danych, mogą przechowywać dane różnych
typów, w tym… inne tablice. Tablicę tablic nazywamy tablicą dwuwymiarową, a do
jej elementów odwołujemy się z wykorzystaniem dwóch indeksów. Pierwszy indeks
wskazuje na tablicę składową, a drugi na konkretny element w tej tablicy. Ideę tę
zaprezentowano na poniższej grafice.
[1] [2] [3] [4]
T[1] ...

T[2] ...

T[3] ...

... ... ... ...

Zwróć uwagę, że tablicę dwuwymiarową możemy traktować jak macierz, której


każdy element jest jednoznacznie określony przez numer wiersza (pierwszy indeks)
i numer kolumny (drugi indeks). Na przykład, odwołanie T[2][3] dotyczy elementu
w drugim wierszu i w trzeciej kolumnie macierzy T.
Jak zapewne pamiętasz, tablica może przechowywać potencjalnie nieskończenie
wiele elementów. Podobnie jest w przypadku tablic dwuwymiarowych — może ona
zawierać nieskończenie wiele tablic, z których każda może zawierać nieskończenie
wiele elementów. Istnieją macierze, w których poszczególne wiersze mają różną
długość. Możesz sobie tu wyobrazić choćby tablicę zbudowaną ze słów, np.
T[1] = „To”
T[2] = „jest”
T[3] = „tablica”
T[4] = „słów.”
Będziemy zakładać, że jeśli tablica dwuwymiarowa zawiera liczby, to jest prostokątna,
to znaczy że każda jej tablica składowa ma tyle samo elementów.
Uwaga. Czasami liczba elementów tablicy prostokątnej jest określona z góry. W ta-
kich przypadkach stosuje się zapis T[1..m][1..n], który należy rozumieć jako tablica T
zawierająca m tablic indeksowanych od 1 do m, z których każda zawiera n elementów,
indeksowanych od 1 do n.

55
Zestaw 16.

Dwuwymiarowe tablice liczb

Zadanie 16.1. Zaprojektuj funkcję Rows(T), która przyjmuje jako argument pro-
stokątną tablicę liczb T oraz zwraca liczbę jej wierszy,

Zadanie 16.2. Zaprojektuj funkcję Columns(T), która przyjmuje jako argument


prostokątną tablicę liczb T oraz zwraca liczbę jej wierszy,

Zadanie 16.3. Zaprojektuj procedurę Unit(T), która przyjmuje jako argument


kwadratową tablicę liczb T (tablica T ma tyle samo wierszy i kolumn), aby prze-
kształcić ją w macierz jednostkową. Macierz jednostkowa to taka macierz, której
wszystkie elementy są równe 0, z wyjątkiem elementów leżących na przekątnej, które
są równe 1.

Zadanie 16.4. Zaprojektuj funkcję Diagonal(T), która przyjmuje jako argument


kwadratową tablicę liczb T i zwraca sumę elementów leżących na jej przekątnej.

• ★ Jak należy interpretować przekątną macierzy niekwadratowej?

Zadanie 16.5. Zaprojektuj procedurę Lower(T), która przyjmuje jako argument


prostokątną tablicę liczb T, aby zmienić na 0 wszystkie te elementy tablicy T, które
leżą pod jej przekątną.

Zadanie 16.6. Zaprojektuj funkcję Min2D(T), która przyjmuje jako argument pro-
stokątną tablicę liczb T oraz zwraca najmniejszy element, który się w niej znajduje.

56
Zestaw 17.

Trochę grafiki

Uwaga. W tym zestawie będziemy traktować tablice jako dwuwymiarowe ekrany


zbudowane z pojedynczych pikseli: # lub .
Zadanie 17.1. Zdefiniuj procedurę Rectangle(T, a, b, c, d), która przyjmuje ja-
ko argumenty nieskończoną tablicę pikseli (ekran) T oraz cztery dodatnie liczby
naturalne, a następnie rysuje na tym ekranie
(a) wypełniony,
(b) niewypełniony
prostokąt określony przez przeciwległe wierzchołki o współrzednych (a, b) oraz
(c, d). Nie wiadomo nic o relacjach pomiędzy liczbami a, b, c oraz d.
★ Zadanie 17.2. Zdefiniuj procedurę Line(T, a, b, c, d), która przyjmuje jako argu-
menty nieskończoną tablicę pikseli (ekran) T oraz cztery dodatnie liczby naturalne,
a następnie rysuje na tym ekranie odcinek określony przez punkty końcowe o współ-
rzednych (a, b) oraz (c, d). Nie wiadomo nic o relacjach pomiędzy liczbami a, b, c
oraz d.
• Czy trafiłaś/eś na jakiś problem, którego nie umiesz rozwiązać? Co jeśli ryso-
wana linia nie będzie równoległa do żadnej krawędzi ekranu?
• Zastanów się jak można rozwiązać problemy, na które natrafiłaś/eś w czasie
tego zadania.
★★ Zadanie 17.3. Rysowanie linii na ekranie monochromatycznym jest intere-
sującym zagadnieniem. Istnieje kilka algorytmów, które można wykorzystać do
tego celu, ale najciekawszym z nich jest algorytm Bresenhama (zob. https://en.
wikipedia.org/wiki/Bresenham%27s_line_algorithm). Przygotuj prezenta-
cję, która pozwoli zrozumieć ten algorytm koleżankom i kolegom z klasy.
Zadanie 17.4. Przyrzyj się poniższym grafikom wygenerowanym dla wskazanych
wartości parametru n. Odgadnij zasadę, według której wygenerowano poszczególne
grafiki, a następnie napisz pseudokody algorytmów, które pozwolą wygenerować
analogiczne grafiki dla dowolnego parametru n ∈ ℕ+ . Tablicę pikseli musisz wyge-
nerować od zera, a każdy piksel wolno Ci ustawić tylko raz.

57
58

(a) n = 5 (e) ★ n = 3
####
### ######
## #####
# ####
#####
(b) n = 5 ####
#### ###
### ##
## ####
# ###
##
(c) n = 5 #
#### ####
### ### (f) ★ n = 3
## ##
# # #### ####
### ###
(d) n = 5 ## ##
#### #### ### ###
### ### ## ##
## ## # #
# # ## ##
# #
# #
## ##
### ### (g) ★ n = 2517
#### ####
## # # #

Zadanie 17.5. Obraz wyświetlany na ekranie komputera zbudowany jest z pikseli.


Każdy piksel zbudowany jest z trzech modułów, emitujących światło w kolorach
czerwonym (R), zielonym (G) i niebieskim (B), każde w jednym z 256 natężeń (od 0
do 255). Kolorowy obraz możemy więc opisać przez trzy dwuwymiarowe tablice war-
tości liczbowych od 0 do 255, odpowiadających natężeniom poszczególnych barw
składowych. Obraz w skali odcieni szarości możemy z kolei opisać jedną dwuwy-
miarową tablicą natężeń światła białego. Zaprojektuj procedurę Mono(R, G, B, W),
która mając dane trzy dwuwymiarowe tablice natężeń poszczególnych barw skła-
dowych, wypełnia dwuwymiarową tablicę W wartościami odpowiadających im
natężeń światła białego. Wykorzystaj wzór:

biały = (0.3 ⋅ czerwony) + (0.59 ⋅ zielony) + (0.11 ⋅ niebieski).


Rekurencja

Rozwiązania wielu problemów można sformułować jako algorytmy iteracyjne. Al-


gorytmy iteracyjne to takie, które prowadzą do właściwego rozwiązania poprzez
wielokrotne, przyrostowe wykonywanie tych samych lub zbliżonych operacji. Przy-
kładem algorytmu iteracyjnego mógłby być zaprezentowany poniżej algorytm obli-
czania silni z zadanej liczby naturalnej n.
funkcja Silnia(n)
s=1
dla i ← 2, … , n wykonuj
s←s⋅i
zwróć s
Zauważ, że działanie tego algorytmu iteracyjnego można by opisać słowami w na-
stępujący sposób:
Obliczam wartość wyrażenia 1! (czyli 1). Jeśli pomnożę tak uzyskaną
wartość przez 2, to uzyskam wartość wyrażenia 2!. Jeśli pomnożę war-
tość wyrażenia 2! przez 3, to uzyskam wartość 3!, itd. Jeśli, po pewnej
liczbie kroków, pomnożę wartość wyrażenia (n−1)! przez n, to dowiem
się, ile wynosi n!. W tym momencie mogę zakończyć algorytm.
Istnieje też cała grupa problemów, których rozwiązania można sformułować jako
algorytmy rekurencyjne (rekursywne). Algorytmy rekurencyjne to takie, w któ-
rych osadzamy wprost rozwiązanie elementarnej instancji problemu, a następnie
określamy zależność, jaka zachodzi pomiędzy rozwiązaniem wskazanego problemu
a rozwiązaniem problemu prostszego. Uzależnienie rozwiązania problemu od jego
prostszego wariantu powoduje, że algorytmy rekurencyjne odwołują się same do
siebie, stąd ich nazwa.
Na przykład wiadomo, że 1! = 1. Z kolei wartość wyrażenia n! bardzo łatwo
wyznaczyć, jeśli znamy już wartość wyrażenia (n−1)!. Wystarczy tę ostatnią wartość
pomnożyć przez liczbę n. Możemy więc wykorzystać fakt, że n! = (n − 1)! ⋅ n
i przeformułować algorytm wyznaczania silni tak, jak zaprezentowano to poniżej.
funkcja SilniaRec(n)
jeżeli n = 0 to
zwróć 1
w przeciwnym przypadku
zwróć SilniaRec(n − 1) ⋅ n

59
Zestaw 18.

Funkcje rekurencyjne

Zadanie 18.1. Zdefiniuj funkcję rekurencyjną, która przyjmuje jako argument


liczbę naturalną k, a następnie zwraca wartość k-tego wyrazu ciągu liczbowego
zdefiniowanego wzorem:

3 dla k = 1
(a) ak = {
3 ⋅ ak−1 dla k > 1
6 dla k = 1
(b) ak = {
2 ⋅ ak−1 + 7 dla k > 1
⎧1 dla k = 1
{
(c) ak = ak/2 + 3 dla parzystego k > 1

{a
⎩ k−1 − 1 dla nieparzystego k > 1
Zadanie 18.2. Zdefiniuj funkcję rekurencyjną, która przyjmuje jako argument
liczby naturalne k i n, takie że 1 ≤ k ≤ n, a następnie zwraca wartość wyrażenia (nk).
Wykorzystaj następującą zależność rekurencyjną:

n 1 dla k = 0 lub n = k
( ) = { n−1 n−1
k (k−1) + ( k ) dla 0 < k < n

Zadanie 18.3. Określ wzór rekurencyjny i zdefiniuj na jego podstawie rekuren-


cyjną funkcję NWDRec(k, l), która zwraca największy wspólny dzielnik dwóch
przekazanych do niej jako argumenty dodatnich liczb naturalnych k i l.

Zadanie 18.4. Określ wzór rekurencyjny i zdefiniuj na jego podstawie funkcję


Fib(n), która przyjmuje jako argument dodatnią liczbę naturalną n, a następnie
wyznacza rekurencyjnie wartość n-tego wyrazu ciągu Fibonacciego. Zastanów się,
czy takie podejście ma sens. Dlaczego tak lub dlaczego nie?

Zadanie 18.5. Zdefiniuj funkcję MinRec(T, k, l), która przyjmuje jako argumenty
tablicę liczb wymiernych oraz indeksy graniczne, a następnie rekurencyjnie znajduje
i zwraca najmniejszy spośród elementów T[k], T[k + 1], … , T[l].

60
61

• Czy lepiej porównywać rekurencyjnie obliczoną wartość minimum z konkret-


nym (np. pierwszym) elementem podtablicy, czy może lepiej podzielić tablicę
na pół i porównywać dwie uzyskane rekurencyjnie wartości?
• Czy podejście rekurencyjne do poszukiwania minimum w tablicy ma sens?
Dlaczego tak lub dlaczego nie?

★ Zadanie 18.6 (Rekurencyjny schemat Hornera). Przyjmijmy, że tablica liczb


wymiernych T[1..n] zawiera współczynniki wielomianu stopnia n − 1 tak, że wartość
tego wielomianu w punkcie x opisana jest wzorem:

Tn (x) = T[1] ⋅ xn−1 + T[2] ⋅ xn−2 + T[3] ⋅ xn−3 + ⋯ + T[n − 1] ⋅ x + T[n].

Zauważ, że zachodzi następująca własność rekurencyjna:

T[1] dla n = 1
Tn (x) = {
Tn−1 (x) ⋅ x + T[n] n>1

Zaprojektuj funkcje, które pozwolą Ci obliczyć rekurencyjnie wartość jaką przyjmuje


w punkcie x wielomian określony współczynnikami z tablicy T[1..n].
Zestaw 19.

Algorytmy sortowania tablic — Projekt

Algorytm 19.1 (Sortowanie przez wstawianie, por. 15.11). Zapoznaj się z pseudoko-
dem poniższej procedury sortującej tablicę T[1..n]. Następnie wykonaj tę procedurę
dla przykładowej 8-elementowej tablicy [1, 3, 5, 4, 2, 7, 6, 8].
procedura InsertionSort(T, n)
dla i ← 2, … , n wykonuj
k ← T[i]
j←i−1
dopóki j > 0 i T[j] > k wykonuj
T[j + 1] ← T[j]
j←j−1
T[j + 1] ← k

Algorytm 19.2 (Sortowanie bąbelkowe). Zapoznaj się z pseudokodem poniższej


procedury sortującej tablicę T[1..n]. Następnie wykonaj tę procedurę dla przykłado-
wej 8-elementowej tablicy [4, 8, 6, 3, 2, 8, 9, 0].
procedura BubbleSort(T, n)
dla i ← 1, … , n − 1 wykonuj
dla j ← 1, … , n − 1 wykonuj
jeżeli T[i] > T[i + 1] to
T[i] ↔ T[i + 1]

Algorytm 19.3 (Sortowanie przez wybór). Zapoznaj się z pseudokodem poniższej


procedury sortującej tablicę T[1..n]. Następnie wykonaj tę procedurę dla przykłado-
wej 8-elementowej tablicy [7, 4, 2, 8, 4, 7, 0, 2].
procedura SelectionSort(T, n)
dla i ← 1, … , n − 1 wykonuj
p←i
dla j ← i + 1, … , n wykonuj
jeżeli T[j] < T[p] to
p←j
T[i] ↔ T[p]

62
63

★ Algorytm 19.4 (Sortowanie przez scalanie). Zapoznaj się z pseudokodem po-


niższej procedury sortującej tablicę T[1..n]. Następnie wykonaj tę procedurę dla
przykładowej 8-elementowej tablicy [7, 4, 8, 2, 7, 9, 1, 3].
procedura Merge(T, p, q, r)
a=q−p+1
b=r−q
dla k = 1, … , a wykonuj
A[k] = T[p + k − 1]
A[a + 1] = ∞
dla k = 1, … , b wykonuj
B[k] = T[q + k]
A[b + 1] = ∞
i=1
j=1
dla k = p, … , r wykonuj
jeżeli A[i] < B[j] to
T[k] ← A[i]
i←i+1
w przeciwnym przypadku
T[k] ← B[j]
j←j+1
procedura MergeSortPart(T, p, r)
jeżeli p < r to
q ← (p + r) div 2
MergeSortPart(T, p, q)
MergeSortPart(T, q + 1, r)
Merge(T, p, q, r)
procedura MergeSort(T, n)
MergeSortPart(T, 1, n)

★ Algorytm 19.5 (Sortowanie szybkie). Zapoznaj się z pseudokodem poniższej


procedury sortującej tablicę T[1..n]. Następnie wykonaj tę procedurę dla przykłado-
wej 8-elementowej tablicy [3, 7, 2, 8, 6, 2, 0, 1].
procedura Divide(T, p, r)
q ← p + (r − p) div 2
x ← T[q]
T[q] ↔ T[r]
j←p
dla i ← p, … , r − 1 wykonuj
jeżeli T[i] < x to
T[i] ↔ T[j]
j←j+1
64

T[j] ↔ T[r]
zwróć j
procedura QuickSortPart(T, p, r)
jeżeli p < r to
q ← Divide(T, p, r)
QuickSortPart(T, p, q − 1)
QuickSortPart(T, q + 1, r)
procedura QuickSort(T, n)
QuickSortPart(T, 1, n)

Zadanie 19.1. W maksymalnie 3-osobowych grupiach przygotujcie kilkuminutowe


filmy, w którym zaprezentujecie jeden z powyższych algorytmów. Resztę instrukcji
otrzymacie od nauczyciela.
Programowanie dynamiczne

Jak już zauważyliśmy przy projektowaniu funkcji rekurencyjnych, rozwiązania wie-


lu problemów da się bardzo łatwo wywieść na podstawie rozwiązań problemów
odrobinę mniejszych. Na przykład, znając wartości dwóch następujących po sobie
wyrazów ciągu Fibonacciego, możemy łatwo wyznaczyć wartość trzeciego z kolei.
Jednym z problemów związanych ze stosowaniem funkcji rekurencyjnych jest to,
że w wielu sytuacjach wymagają one wielokrotnego obliczenia tych samych wartości.
Spójrz na poniższą, rekurencyjną definicję funkcji FibRec.
procedura FibRec(k)
jeżeli k = 1 lub k = 2 to
zwróć 1
zwróć FibRec(k − 2) + FibRec(k − 1)
Wywołanie funkcji FibRec z argumentem równym 5 spowoduje utworzenie zapre-
zentowanego poniżej drzewa wywołań funkcji. Zwróć uwagę, że niektóre wywołania

FibRec(5)

FibRec(3) FibRec(4)

FibRec(1) FibRec(2) FibRec(2) FibRec(3)

1 1 1 FibRec(1) FibRec(2)

1 1

(np. FibRec(3)) pojawiają się w tym drzewie wielokrotnie. Oznacza to, że niektóre
obliczenia będą wykonywane wielokrotnie, niezależnie od siebie. Aby zniwelować
ten problem, można zastosować technikę programowania dynamicznego.
Idea programowania dynamicznego leży w tym, aby rozwiązywać problem zde-
finiowany rekurencyjnie począwszy od przypadku elementarnego, zapamiętując
(w tablicy) wszystkie pośrednie wyniki. Oto dynamiczna wersja funkcji generującej
k-ty wyraz ciągu Fibonacciego.

65
66

procedura FibDyn(k)
T[1] = 1
T[2] = 1
dla i ← 3, … , k wykonuj
T[i] = T[i − 2] + T[i − 1]
zwróć T[k]
Rozważany problem powinien spełniać następujące warunki, aby zastosowanie
techniki programowania dynamicznego miało sens:

• Optymalne rozwiązanie problemu jest funkcją optymalnych rozwiązań jego


podproblemów. (Własność optymalnej podstruktury.)
• Istnieje rekurencyjna zależność pomiędzy wynikami uzyskanymi dla badane-
go problemu wynikami uzyskanymi dla problemów mniejszych.
• Zastosowanie standardowej techniki rekurencyjnej powoduje wielokrotne
wykonywanie tych samych obliczeń.
Zestaw 20.

Programowanie dynamiczne

Zadanie 20.1. Funkcja FibDyn zaprezentowana we wstępie teoretycznym potrze-


buje (asymtotycznie) tyle samo operacji, co jej poniższa wersja. Które z podejść jest
lepsze? Rozważ liczbę wartości przechowywanych w pamięci.
procedura FibIter(k)
a=1
b=1
dla i ← 3, … , k wykonuj
b←a+b
a←b−a
zwróć b

Zadanie 20.2 (por. zadanie 18.2). Wartość symbolu Newtona może zostać wyrażona
rekurencyjnie w następujący sposób:

n 1 dla k = 0 lub n = k
( ) = { n−1
k (k−1) + (n−1
k ) dla 0 < k < n

(a) Przypominij sobie funkcję przygotowaną w zadaniu 18.2.


(b) Narysuj drzewo wywołań tej funkcji dla n = 5 i k = 2. Policz, ile razy funkcja
wywoływana jest z tymi samymi argumentami.
(c) Przygotuj rozwiązanie wykorzystujące technikę programowania dynamiczne-
go. Wartości pośrednie będziesz musiał/a przechowywać w dwuwymiarowej
tablicy (jeden z indeksów będzie odpowiadał górnej wartości wewnątrz sym-
bolu, a drugi dolnej).

Zadanie 20.3 (Problem wydawania reszty). Przyjmijmy, że dysponujemy nieskoń-


czoną liczbą następujących nominałów: 1zł, 2zł, 5zł, 10zł, 20zł, 50zł, 100zł oraz
200zł. Chcemy odpowiedzieć na następujące pytanie: ile monet/banknotów potrzeba
najmniej, aby otrzymać kwotę równą dokładnie k?

(a) Zaprojektuj funkcję, która oblicza poszukiwaną wartość zgodnie z metodą


stosowaną przez sprzedawców: rozpocznij od największych nominałów, prze-
chodząc do coraz niższych dopiero wtedy, gdy to koniecznie.

67
68

(b) ★ Określ zależność rekurencyjną pomiędzy poszukiwaną liczbą monet/bank-


notów dla danej kwoty k a liczbą monet/banknotów dla kwot mniejszych.
Rekurencja ta będzie uwzględniać nie tylko kwotę, ale także maksymalny do-
stępny nominał. Następnie zaprojektuj rozwiązanie problemu wykorzystujące
technikę programowania dynamicznego.
(c) ★ Wskaż przykładową listę nominałów, dla których strategia przyjęta w punk-
cie (a) nie zadziała. Czy poprawność strategii programowania dynamicznego
z punktu (b) została zachwiana?
Zestaw 21.

Złożoność algorytmów

Zadanie 21.1. Spójrz na funkcję FirstEven(T, n) z zadania 14.6. Odpowiedz na


następujące pytanie:
(a) Ile operacji aytmetycznych i logicznych zostanie wykonanych, jeśli pierwszy
parzysty element znajduje się na pierwszym miejscu w tablicy? A ile, jeśli nie
ma takiego w ogóle?
(b) ★ Załóżmy, że tablica T[1..n] została wypełniona losowo liczbami naturalny-
mi od 1 do n. Jaka jest oczekiwana liczba operacji niezbędnych do odnalezienia
pierwszego parzystego elementu w takiej tablicy?
Zadanie 21.2. Spójrz na procedurę SortMin(T, n), którą przygotowałaś/eś roz-
wiązując zadanie 15.10. Ideą przygotowanego wówczas algorytmu sortującego było
wielokrotne poszukiwanie elementu najmniejszego w podtablicy, która z każdą
iteracją była coraz mniejsza. Przeanalizuj ten algorytm, a następnie odpowiedz na
pytania.
(a) Ile operacji arytmetycznych i logicznych zostanie wykonanych, jeśli przyjmie-
my najgorszy możliwy scenariusz, to znaczy że tablica T[1..n] jest posortowana
malejąco?
(b) Czy liczba operacji zmieni się, jeśli tablica T[1..n] będzie miała inną strukturę?
Zadanie 21.3. Narysuj drzewo wywołań funkcji Fib(n) zdefiniowanej jako rozwią-
zanie zadania 18.4.
(a) Ile wierzchołków ma to drzewo, w zależności od wartości n? Inaczej, ile razy
funkcja Fib zostanie wywołana dla zadanej wartości początkowej n? Jeśli
nie potrafisz podać dokładnej wartości, to znajdź jak najlepsze oszacowanie
górne.
(b) Porównaj uzyskany wynik z tym, który uzyskasz analizując algorytm iteracyj-
ny dla tego samego problemu.
★★ Zadanie 21.4. Zapoznaj się z porównaniem wydajności kilku różnych algoryt-
mów służących do wyznaczania wartości n-tego wyrazu ciągu Fibonacciego, dostęp-
nym na stronie https://www.nayuki.io/page/fast-fibonacci-algorithms.
Spróbuj napisać pseudokod algorytmu wyznaczającego te wartości z wykorzysta-
niem algorytmu macierzowego lub algorytmu szybkiego podwajania.

69
70

Zadanie 21.5. Spójrz na rozwiązania zadań 15.8, 15.9 oraz 18.6. Porównując te trzy
rozwiązania, odpowiedz na następujące pytania:

(a) Ile operacji dodawania należy wykonać w obu przypadkach, aby obliczyć
wartość wielomianu stopnia n − 1? A ile operacji mnożenia?
(b) Jak sądzisz, czy zmniejszenie liczby operacji mnożenia kosztem zwiększenia
liczby operacji dodawania ma sens? Dlaczego tak lub dlaczego nie? Przypo-
mnij sobie zagadnienia poruszane na zajęciach z urządzeń techniki kompute-
rowej.

Zadanie 21.6. Przyjrzyj się implementacjom procedur InsertionSort(T, n), Bub-


bleSort(T, n) oraz SelectionSort(T, n) zaprezentowanym w zestawie 19. Porów-
naj ich wydajność (w sensie liczby operacji porównania), jeśli założymy, że:

(a) Elementy tablicy T[1..n] są już w odpowiednim porządku.


(b) Elementy tablicy T[1..n] są w porządku odwrotnym do pożądanego.
(c) ★ Tablica T[1..n] została wypełniona liczbami naturalnymi od 1 do n w losowy
sposób.

★ Zadanie 21.7. W poprzednich zadaniach kilkukrotnie była mowa o tablicach


wypełnionych losowo liczbami z przedziału od 1 do n. Załóżmy, że istnieje funkcja
Random(n), która zwraca pseudolosową liczbę naturalną z przedziału od 1 do n.
Zaprojektuj procedurę FillRandomly(T, n), która wypełnia tablicę T liczbami
naturalnymi od 1 do n w sposób losowy. Przeanalizuj złożoność swojego algorytmu,
a nastepnie porównaj go z algorytmem Fishera-Yatesa (https://en.wikipedia.
org/wiki/Fisher%E2%80%93Yates_shuffle).
Spis rzeczy

Polecana literatura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

Zestaw 1. Zanim zaczniemy . . . . . . . . . . . . . . . . . . . . . . . . . 2

Algorytmy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

I Schematy blokowe 7
Schematy blokowe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

Zestaw 2. Algorytmy dnia codziennego . . . . . . . . . . . . . . . . . . . 12

Zestaw 3. Proste algorytmy liczbowe . . . . . . . . . . . . . . . . . . . . 14

Wyrażenia arytmetyczne . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

Wyrażenia logiczne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

Zestaw 4. Liczby i ciągi liczbowe . . . . . . . . . . . . . . . . . . . . . . 20

Zestaw 5. Podstawowe algorytmy liczbowe . . . . . . . . . . . . . . . . . 22

Tablice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

Zestaw 6. Tablice liczbowe . . . . . . . . . . . . . . . . . . . . . . . . . . 25

Zestaw 7. Tablice liczbowe, cd. . . . . . . . . . . . . . . . . . . . . . . . 27

Ciągi znaków . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

Zestaw 8. Ciągi znaków . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

71
72

II Pseudokody 31
Pseudokod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

Zestaw 9. Algorytmy zapisane pseudokodem . . . . . . . . . . . . . . . . 36

Zestaw 10. Liczby i ciągi liczbowe . . . . . . . . . . . . . . . . . . . . . . 38

Zestaw 11. Algorytmy liczbowe . . . . . . . . . . . . . . . . . . . . . . . . 40

Typy danych . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

Operacje logiczne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

Zestaw 12. Typy danych . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

Zestaw 13. Złożoność algorytmów . . . . . . . . . . . . . . . . . . . . . . 46

Funkcje i procedury . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

Zestaw 14. Proste funkcje . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

Zestaw 15. Funkcje operujące na tablicach . . . . . . . . . . . . . . . . . . 53

Tablice dwuwymiarowe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

Zestaw 16. Dwuwymiarowe tablice liczb . . . . . . . . . . . . . . . . . . . 56

Zestaw 17. Trochę grafiki . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

Rekurencja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

Zestaw 18. Funkcje rekurencyjne . . . . . . . . . . . . . . . . . . . . . . . 60

Zestaw 19. Algorytmy sortowania tablic — Projekt . . . . . . . . . . . . . 62

Programowanie dynamiczne . . . . . . . . . . . . . . . . . . . . . . . . . . 65

Zestaw 20. Programowanie dynamiczne . . . . . . . . . . . . . . . . . . . 67

Zestaw 21. Złożoność algorytmów . . . . . . . . . . . . . . . . . . . . . . 69

You might also like