Algorytm 6

advertisement
Tematy, które można by poruszyć
1. Algorytm Euklidesa
2. Algorytmy kompresji (np. algorytm kompresji filmów wideo MPEG-4)
3. Algorytmy sztucznej inteligencji
4. Algorytmy kryptograficzne
5. Algorytmy genetyczne
6. Algorytmy kwantowe
7. Algorytmy przeszukiwania drzew (rekurencja, możliwość graficznej wizualizacji)
8. Mistrz szachowy kontra maszyna (algorytm)
9. Co to są kodeki (od kompresor-dekompresor, ang. Codec)
10. Varia
ALGORYTMY WOKÓŁ NAS
Dlaczego na zajęciach z informatyki będziemy poznawać tajniki tworzenia
i działania algorytmów? Jakie znaczenie ma nauka o algorytmach dla informatyki?
Czy algorytmy występują tylko przy programowaniu komputerów?
Czy natrafiamy na nie w innych dziedzinach życia? Czy potrafimy je tam
dostrzegać?
Zapewne samo słowo algorytm jest Ci obce. Pochodzi ono od nazwiska
perskiego matematyka, Muhammada ibn Musa al-Chorezmi, który żył na
przełomie VIII i IX wieku i większość życia spędził w Bagdadzie. Jednak przykłady
algorytmów, zwłaszcza w matematyce,
sięgają czasów starożytnych. Jednym z najbardziej znanych jest
tzw. algorytm Euklidesa znajdowania największego wspólnego dzielnika
dwóch liczb naturalnych (o którym powiemy szerzej w podrozdz. 8.6). Pomyślisz
teraz - znowu ta matematyka... Otóż nie! Obiecujemy, że naszą
wędrówkę po ciekawym świecie algorytmów rozpoczniemy od... gotowania
zupy. Będziemy też zajmować się wyborami przewodniczącego klasy, a nawet
strzelaniem rzutów karnych podczas meczów pucharowych! Dopiero
pod koniec rozdziału - tylko dla miłośników matematyki - przygotowaliśmy
przykłady algorytmów w matematyce.
Czym jest ów tajemniczy algorytm - zapytasz? Najogólniej mówiąc, algorytm
to szczegółowy przepis opisujący działania, czynności, które powinny
być wykonane przez jakieś urządzenie lub przez człowieka, aby osiągnąć
zamierzony cel. Oczywiście, algorytmy występują w informatyce, gdyż
każdy program komputerowy działa według jakiegoś algorytmu. Języki
programowania są tak zbudowane, aby ułatwić zapisywanie algorytmów. Inną ważną
dziedziną, w której stykamy się z algorytmami, jest prawo. Często przepisy
prawa mają tak naprawdę charakter algorytmiczny, chociaż prawnicy zamiast
słowa algorytm używają określenia procedury prawne. Niektóre z algorytmów
w Kodeksie Cywilnym są niezwykle skomplikowane i tylko najlepsi
prawnicy potrafią je skutecznie wykorzystywać. Zauważ też, że wszelkie
ordynacje wyborcze (prezydencka, sejmowa, gminna itp.) można potraktować, jako
lepszy lub gorszy algorytm. Postaramy się przekonać Cię,
że algorytmy występują w naszym świecie nader często.
8.1 Algorytmiczne gotowanie zupy
Wyobraź sobie, że jesteś w kuchni i masz przygotować zupę. Gotowanie
zupy to procedura, w której ze składników, za pomocą różnych przyrządów,
wykonując określone czynności otrzymujesz końcowy wynik, czyli w naszym
przypadku zupę. Ale jest tu
jeszcze jeden niesłychanie ważny czynnik, potrzebny zwłaszcza dla tych,
którzy nie mają jeszcze doświadczenia w gotowaniu. To oczywiście przepis
na zupę z torebki. W naszym przykładzie przepis jest właśnie algorytmem gotowania
zupy.
Przyglądnijmy się dokładniej takiemu przykładowemu algorytmowi - przepisowi.
Algorytm 1
(1) Zawartość torebki wymieszać w 3/4 litra zimnej wody.
(2) Mieszając, doprowadzić do wrzenia.
(3) Zmniejszyć ogień.
(4) Gotować na małym ogniu ok. 10 minut.
(5) Odstawić i przecedzić mieszaninę.
(6) Stop - zupa jest gotowa.
Rys. 8.1
Widzimy zatem, że algorytm zadaje czynności, które składają się na cały
proces (w tym przypadku jest to proces gotowania zupy).
Rozważmy kolejny przykład. Przypuśćmy, że dysponujemy listą/tabelą zawierającą
dane o uczniach Twej szkoły. Wśród tych danych znajdują się m.in.
nazwisko, imię oraz wzrost (podany w centymetrach). Naszym zadaniem
jest znalezienie ucznia, który ma największy wzrost w szkole. Oczywiście,
jeśli uczęszczasz do niewielkiego gimnazjum, prawdopodobnie wystarczy
krótkie przeglądnięcie listy i będziesz znać odpowiedź. Jednakże w przypadku,
gdy lista będzie długa (np. będzie na niej tysiąc pozycji), musimy
zastosować ściśle określoną, systematyczną procedurę, aby uporać się z
problemem.
A oto jak można by słownie opisać algorytm, który będzie znajdował osobę
o największym wzroście w gimnazjum.
1
2
3
4
5

850
Adamczyk
Bogusławski
Czapska
Dobosz
Dudzik

Zwijacz
Marek
Roman
Anna
Jan
Jacek

Paulina
154
167
148
162
163

159
Algorytm 2
Dane: tabela zawierająca nazwiska (i imiona) oraz wzrost (w cm).
(1) Zapamiętaj „na boku" nazwisko i imię pierwszego ucznia z listy oraz jego
wzrost.
(2) Weź kolejnego ucznia z listy.
(3) Jeżeli jego wzrost jest większy od aktualnie zapamiętanego, to zapamiętaj
nowe nazwisko (i imię) oraz wzrost tego ucznia w miejsce dotychczas
pamiętanego.
(4) Jeżeli dalej na liście są nazwiska, to wróć do punktu (2).
(5) Podaj jako wynik ostatniego zapamiętanego ucznia i jego wzrost i zakończ
algorytm.
Ćwiczenie 8.1
Jak zachowa się algorytm 2, gdy w szkole jest kilku uczniów o największym
wzroście? Który z nich będzie wypisany (pierwszy, ostatni, a może któryś ze
środka)?
Zauważ, że modyfikacja algorytmu tak, aby podawał on wszystkich uczniów, którzy
mają ten największy wzrost nie będzie taka prosta. Wynika to stąd, że nie można
uczniów o największym wzroście wypisywać na bieżąco w trakcie przebiegania listy.
Aby znać ten największy wzrost musimy najpierw całą listę przeglądnąć do końca.
PODSUMOWANIE
Algorytm jest to precyzyjny i niezawodny przepis, który pozwala w
„mechaniczny" sposób osiągnąć określony cel. Poszczególne
instrukcje w tym przepisie muszą być ściśle określone, tak
aby wynik był zawsze jednoznaczny.
W życiu często stosujemy algorytmy, jednak przeważnie czynimy
to w sposób nieświadomy; mówimy wtedy o przepisach, czy też
procedurach postępowania.
Algorytmy mają ogromne znaczenie w informatyce, gdyż każdy
program komputerowy działa według jakiegoś algorytmu lub
zbioru algorytmów. Algorytmy odgrywają także ważną rolę w życiu
społecznym, regulując wiele dziedzin życia.
8.2 W jakiej kolejności wykonywać instrukcje?
Rys 8.2
Wiesz już, że algorytmy składają się ze starannie dobranych instrukcji
(poleceń). Dotychczas nie zastanawialiśmy się dokładniej nad kolejnością,
w jakiej są one wykonywane. Zasadniczo kolejność ta jest określona przez
zapis danego algorytmu w postaci kolejnych instrukcji. Niektóre z algorytmów
składają się z pewnej listy instrukcji, które mają być bezwarunkowo
wykonane w kolejności, w jakiej występują w zapisie danego algorytmu.
Żartobliwy przykład takiego algorytmu oraz efekt po zmianie kolejności
instrukcji przedstawiono na rys. 8.2. Takie algorytmy nazywamy algorytmami
liniowymi. Nazwa ma sugerować, że kolejne kroki tego algorytmu
następują jeden po drugim „w linii prostej" bez możliwości „zawracania" lub
„rozgałęziania”.
Zauważ, że nasz przepis gotowania zupy z torebki jest właśnie takim prostym
algorytmem liniowym.
Podać jakiś przykład
Ćwiczenie 8.2
Podaj przykłady algorytmów liniowych, jakie znasz z codziennego doświadczenia.
Algorytmy liniowe są bardzo proste i rzadko kiedy występują w praktyce
w postaci „czystej". Zauważ, że taki algorytm ma dwie podstawowe słabości:
• Nieco żartobliwie można powiedzieć, że jest on „bezmyślny", gdyż nie
może podejmować żadnych decyzji. Algorytm liniowy w sposób wręcz
nudny wykonuje czynność za czynnością i wszystkie kroki z góry można
przewidzieć.
• Nie jest odporny na błędne czy wyjątkowe sytuacje, które mogą się
pojawić w trakcie jego wykonywania (w przypadku obliczeń, na przykład,
czymś takim jest dzielenie przez zero).
Z doświadczenia wiesz zapewne, że kolejność wykonywania różnych czynności
nie jest zawsze taka sama i zależy od zachodzenia lub niezachodzenia
różnych warunków. Szczególnie widoczne jest to w niektórych regulacjach
prawnych (np. dotyczących dziedziczenia spadku w przypadku
braku testamentu). W naszym podręczniku, dla ilustracji, odwołamy się do
przepisów prawa cywilnego regulujących odpowiedzialność za sprzedaż
towaru z wadami (prawnicy mówią tu o odpowiedzialności z tytułu rękojmii
za wady). Oto najprostsza sytuacja w ujęciu algorytmicznym:
Algorytm 3 (odpowiedzialność za wady)
(1) W momencie, w którym sprzedano Ci towar z wadami, powstaje
odpowiedzialność sprzedawcy za wady.
(2) Jeżeli kupujący powiadomił sprzedawcę w ciągu miesiąca o pojawieniu
się wady, wówczas idź do punktu (5), w przeciwnym razie idź do (3).
(3) Jeżeli sprzedawca podstępnie/świadomie zataił wadę, idź do punktu (5), w
przeciwnym razie idź do (4).
(4) Wygasa odpowiedzialność sprzedawcy za wady - zakończ algorytm.
(5) Domagaj się przysługujących Ci uprawnień (wymiany towaru na towar
wolny od wad lub naprawy towaru, obniżenia ceny i zwrotu nadpłaty
itp.) - zakończ algorytm.
Zauważyłeś, że cechą charakterystyczną powyższego algorytmu jest występowanie
wielu zwrotów: jeżeli „coś zachodzi", to „zrób ..."
W algorytmie 3 jest pewien mechanizm sterujący kolejnością, według której
instrukcje będą się wykonywały. Są to tzw. instrukcje warunkowe, które
„popychają" algorytm w tym lub w innym kierunku w zależności od sytuacji,
czyli od tego, jakie warunki są spełnione, a jakie nie.
Przykład
Ćwiczenie 3
Czy potrafisz wskazać (sam bądź z pomocą nauczyciela) inne procedury
algorytmiczne występujące w prawie polskim (np. wynikający
z Konstytucji tryb uchwalania aktów prawnych przez parlament).
Czasami takie miejscach w algorytmie, gdzie występują warunki, określamy
jako rozgałęzienia warunkowe. Ogólnie rozgałęzienia warunkowe mają
jedną z dwóch postaci:
(1) jeśli W, to wykonaj czynność A
(2) jeśli W, to wykonaj czynność A, w przeciwnym razie wykonaj czynność B
(tutaj W oznacza pewien warunek).
Sytuację taką można bardzo obrazowo wyrazić w postaci następującego rysunku
W
A
B
Strzałki pokazują kierunek, w którym należy się poruszać wykonując algorytm.
Wyobraź sobie, że jesteś w miejscu oznaczonym górną strzałką i wchodzisz do bloku
z literą W oznaczającą warunek. Wejście jest jedno, ale wyjścia są dwa. Tak, należy
podjąć decyzję. Algorytm zaczyna jakby myśleć! W zależności od tego czy warunek
jest spełniony czy nie wejdziemy do bloku A lub B. Teraz też zaczynać już rozumieć
dlaczego nstrukcje warunkowe czasami nazywa się rozgałęzieniami.
W przepisach kucharskich natrafiamy często na tego typu zwroty:
sprawdź, czy makaron jest miękki, jeśli nie, to gotuj jeszcze chwilę
albo
wedle życzenia podawać z bitą śmietaną.
Oczywiście, są to instrukcje warunkowe, które sterują wykonywaniem algorytmu.
Ćwiczenie 4
Podaj kilka przykładów instrukcji warunkowych, które mogą się
pojawić w algorytmach.
Aby jeszcze dokładniej poznać instrukcje warunkowe, zajmijmy się algorytmem,
który ma za zadanie uporządkować trzy dowolne liczby. Jest to prosty
przypadek pewnego ogólniejszego zadania algorytmicznego, zwanego
w informatyce sortowaniem. Innym określeniem, którego można by tu
użyć, jest porządkowanie. Wyobraź sobie, że masz bardzo długą listę pracowników
(np. 5 tys. osób). Na liście tej obok imienia i nazwiska znajduje
się też informacja o zarobkach. W pewnych sytuacjach wygodnie byłoby
mieć tę listę uporządkowaną (posortowaną) według wysokości zarobków.
Trzeba więc będzie taką listę poddać procesowi sortowania. Zrealizuje to
oczywiście odpowiedni algorytm. Tak więc danymi wejściowymi zadania
sortowania jest nieuporządkowana lista elementów. Zadaniem algorytmu
sortowania jest przedstawienie tej listy w postaci uporządkowanej (posortowanej).
Do tematu sortowania w ogólnej wersji jeszcze będziemy wracać,
tutaj tylko sygnalizujemy sam problem.
Teraz zgodnie z zapowiedzią omówimy algorytm sortowania dwóch, a potem trzech
liczb.
Algorytm 4 (porządkowanie dwóch liczb)
Dane: a, b (dwie liczby).
(1) Jeżeli a<b, to wypisz (a, b),
w przeciwnym razie wypisz (b, a).
Ten algorytm jest naprawdę prosty. Ale robi to, czego chcemy – sortuje listę
dwuelementową Okazuje się jednak, że przypadek sortowania trzech liczb podobną
metodą daje algorytm znacznie bardziej skomplikowany.
Algorytm 4 (porządkowanie trzech liczb)
Dane: a, b, c (trzy liczby).
(1) Jeżeli a  b, to idź do (2),
w przeciwnym razie idź do (4).
(2) Jeżeli c  a, to wypisz (c,a,b) i zakończ algorytm,
w przeciwnym razie idź do (3).
(3) Jeżeli c  b, to wypisz (a,c,b) i zakończ algorytm,
w przeciwnym razie wypisz (a,b,c) i zakończ algorytm.
(4) Jeżeli c  b, to wypisz (c,b,a) i zakończ algorytm,
w przeciwnym razie idź do (5).
(5) Jeżeli a  c, to wypisz (b,a,c) i zakończ algorytm,
w przeciwnym razie wypisz (b,c,a).
(6) Zakończ algorytm.
Cóż, algorytm ten wydaje się nieco skomplikowany. Składa się on właściwie
z samych instrukcji warunkowych (jeżeli..., to.., w przeciwnym razie...). Rzeczywiście,
zawiłość tego algorytmu wynika właśnie z występowaniem wielu
rozgałęzień warunkowych, gdzie algorytm jakby podejmuje decyzję, co
robić dalej. Dlatego trudno jest śledzić taki algorytm, gdyż może on „pójść
różnymi ścieżkami" w zależności od tego jakie były liczby a, b, c. Niemniej jednak
algorytm ten nie jest trudny. Jego działanie
polega właściwie na systematycznym sprawdzaniu (porównywaniu)
relacji większości pomiędzy liczbami a, b, c.
Ćwiczenie 5
Aby lepiej zrozumieć ten algorytm, spróbuj go sam wykonać dla
przykładowych trójek liczb, np. (1,2,3), (3,1,2), (5,-1,0), czyli zachować
się jak komputer, który wykonuje program oparty na algorytmie 4.
Ćwiczenie 6
Postaraj się wymyślić jakiś algorytm porządkowania dwóch liczb.
Po zapoznaniu się z algorytmem porządkowania trzech liczb spróbuj
napisać ten algorytm „z pamięci". Oczywiście, nie musi to być
wierna kopia - masz pewną swobodę w jego zapisaniu.
PODSUMOWANIE
AIgorytm liniowy to bardzo prosty typ algorytmu, który ma
postać listy instrukcji, wykonywanych bezwarunkowo
zgodnie z kolejnością, w której występują.
Bardziej złożone algorytmy zawierają instrukcje warunkowe, które
sterują kolejnością wykonywania algorytmu. Innymi słowy, instrukcje
warunkowe mówią, co dalej wykonywać, a czego nie wykonywać.
Instrukcje warunkowe (rozgałęzienia warunkowe) mają postać:
jeżeli W, to wykonaj czynność A
lub
jeżeli W, to wykonaj czynność A, w przeciwnym razie wykonaj
czynność B.
Sortowanie (inaczej porządkowanie) to algorytm, który z listy
nieuporządkowanych danych tworzy listę uporządkowaną. Kryteria
porządkowania mogą być różne, np. porządek arytmetyczny (dla
liczb), alfabetyczny (dla nazw lub tekstów - jak w książce telefonicznej).
8.3 Czy algorytm, który się wykonuje bardzo długo, można
zapisać w kilku linijkach?
Gdy dokładnie przyjrzymy się algorytmom dotychczas omawianym, zauważymy, że
większość z nich zbudowane jest są one z dwóch podstawowych elementów:
• zwykłego następstwa kolejnych kroków,
• wyborów warunkowych.
Jak Ci się wydaje, jakie są istotne ograniczenia takich algorytmów? Zauważ,
że każdy fragment algorytmu zawierającego tylko zwykłe następstwo
instrukcji oraz wybory warunkowe może wykonać się nie więcej niż jeden
raz. A zatem taki algorytm nie może opisywać procesu, który będzie trwał
dowolnie długo, w zależności od „rozmiaru" danych wejściowych dla algorytmu.
Zastanówmy się nad następującym problemem. Komputer ma policzyć
sumę zarobków wszystkich pracowników danej firmy. Mamy daną listę płac
(imiona, nazwiska i zarobki w wierszach tabeli). Komputer musi zsumować wszystkie
zarobki. Oto przykładowa realizacja:
Algorytm 5
Dane: lista płac, liczba pracowników (oznaczona przez N).
(1) Zapamiętaj płacę pierwszej osoby z listy.
(2) Wykonuj N-1 razy poniższe dwie instrukcje:
(2.1) Weź następną płacę z listy.
(2.2) Dodaj pobraną płacę do zapamiętanej już wartości i wynik zapamiętaj.
(3) Wypisz zapamiętaną wartość.
Jak Ci się wydaje, co jest cechą charakterystyczną algorytmu 5? Zauważ,
że ten krótki algorytm realizuje obliczenia, które mogą trwać coraz dłużej.
Komputer, działający według programu opartego na algorytmie 5, może
pracować dowolnie bardzo długo, gdy lista płac będzie coraz większa. Lista może
się składać z 30 pozycji, jak i 30 tysięcy (czy nawet 30 milionów, gdyby
istniały takie przedsiębiorstwa - kolosy)! A zatem krótki algorytm może
opisywać bardzo długie procesy obliczeniowe.
Kolejny przykład algorytmu o podobnej budowie będzie związany z następującym
problemem: Jak znajdować w danym zbiorze pewien obiekt, który
ma określoną cechę? Oto konkretne przykłady tego ogólnego problemu:
• Czy wśród uczniów gimnazjum występuje, uczeń o nazwisku Skorupka?
• Czy w bibliotece szkolnej znajduje się określona książka (np. Krzyżacy
Henryka Sienkiewicza)?
Ćwiczenie 8.7
Podaj podobnego typu przykłady, zaczerpnięte z codziennego doświadczenia.
Jak zatem będzie wyglądał algorytm, który stanie się podstawą programu
komputerowego do wyszukiwania uczniów o podanym nazwisku (w naszym
przykładzie o nazwisku Skorupka)? Załóżmy, że komputer pamięta nazwiska
uczniów w postaci jakiejś listy lub tabeli.
Algorytm 6
Dane: lista nazwisk, liczba uczniów (oznaczona przez N).
(1) Ustaw się na początku listy.
(2) Wykonuj poniższe trzy instrukcje N razy.
(2.1) Pobierz aktualne nazwisko z listy.
(2.2) Jeżeli jest to nazwisko Skorupka, to wypisz: znalazłem Skorupkę.
(2.3) Wskaż na następne nazwisko na liście.
(3) Wypisz: „zakończyłem przegląd listy” i zakończ algorytm.
Podobnie jak poprzednio, charakterystyczną cechą tego algorytmu jest
instrukcja (2), która ogólnie mówi: wykonuj coś określoną ilość liczbę razy. Innymi
słowy, fragment ten zawiera instrukcję powtarzania pewnych kroków. Informatycy
używają tu terminu iteracja (od łacińskiego iteratio, czyli powtarzanie).
Powtórzmy więc, że iterowanie polega na wielokrotnym powtarzaniu
pewnych czynności. Dla algorytmu 6, podobnie jak algorytmu 5,
danymi wejściowymi jest liczba elementów do przetworzenia, sam algorytm
jest identyczny dla list o dowolnej długości. Jego zapis nie zależy od tego czy N=8
czy N=1000.
W algorytmach 5 i 6 występuje najprostsza forma iteracji. Dokonajmy w algorytmie
6 zmian, aby otrzymać nieco ogólniejszą postać iteracji.
Algorytm 7
Dane: lista nazwisk.
(1) Ustaw się na początku listy.
(2) Wykonuj poniższe trzy instrukcje aż do osiągnięcia końca listy:
(2.1) Pobierz aktualne nazwisko z listy.
(2.2) Jeżeli jest to nazwisko Skorupka, to wypisz: znalazłem Skorupkę.
(2.3) Wskaż na następne nazwisko na liście.
(3) Wypisz; zakończyłem przegląd listy i zakończ algorytm.
Czy dostrzegłeś różnicę pomiędzy algorytmem 6 i algorytmem 7? Oczywiście
chodzi o punkt (2), w którym pojawiło się wyrażenie: aż do końca
listy. Jest to przykład tak zwanej iteracji warunkowej, która ma postać:
wykonuj czynność Z, dopóki jest spełniony pewien warunek W
lub w odwrotnej kolejności:
dopóki jest spełniony pewien warunek W, wykonuj czynność Z.
Przykładowo w przepisach kulinarnych iteracja warunkowa ukryta jest
w poleceniach typu: ugniataj ciasto, aż będzie dostatecznie miękkie. Tak
więc, charakterystyczne jest tutaj występowanie specjalnych warunków,
które „mówią" algorytmowi, w jakiej sytuacji ma zakończyć działanie.
Kolejny przykład algorytmu z iteracja jest opisem bardzo prostej gry w odgadywanie
liczby. Załóżmy, że komputer (one to potrafią!) wylosował dowolną
liczbę naturalną od 1 do 1000. Twoim zadaniem jest odgadnięcie tej
liczby. Na każdą Twą propozycję komputer ma odpowiedzieć, czy zaproponowana liczba jest większa, czy też mniejsza od wylosowanej, i tak aż
do trafienia w szukaną liczbę. Oczywiście, dobrze by było odgadnąć tę
liczbę w możliwie jak najmniejszej ilości liczbie prób. Gdybyś grał z kolegą (kolega
wymyślałby przypadkową liczbę), po wyjaśnieniu mu zasad naszej zgadywanki
od razu można by przystąpić do zabawy. Natomiast komputer
będzie potrzebował bardziej konkretnego przepisu – czyli algorytmu.
Algorytm 8
(1) Wylosuj liczbę naturalną z przedziału 1...1000 i zapamiętaj ją.
(2) Wykonuj instrukcje (2.1) - (2.3), aż zgadujący trafi w wylosowaną liczbę:
(2.1) Poproś zgadującego o podanie liczby.
(2.2) Jeżeli proponowana liczba jest mniejsza od wylosowanej, to wypisz
komunikat: „szukana liczba jest większa od zaproponowanej”.
(2.3) Jeżeli proponowana liczba jest większa od wylosowanej, to wypisz
komunikat: „szukana liczba jest mniejsza od zaproponowanej”.
(3) Liczba została odgadnięta - koniec algorytmu.
Jak widzisz, jest to algorytm z iteracją warunkową. Warunkiem zakończenia
jest podanie właściwej liczby. Program komputerowy oparty na algorytmie 8
zawiera elementy dialogu z grającym. Prosi o podanie liczby oraz wypisuje
odpowiednie komunikaty. Szczegóły takich operacji nie są tu dla nas nazbyt
istotne, gdyż zależą od typu komputera, zainstalowanego na nim systemu
operacyjnego itp. Ważne jest natomiast to, że mamy możliwość komunikowania
się z komputerem. Na ogół dane będziemy podawali przy pomocy
klawiatury, a komunikaty odczytywali z ekranu monitora komputerowego.
Czy zauważyłeś zaskakującą cechę tego algorytmu? Otóż nie jest
wcale pewne, czy ten algorytm musi się kiedykolwiek zakończyć! Możemy
sobie bowiem wyobrazić, że (niezbyt inteligentna) osoba będzie cały czas
podawać (zgadywać) złe liczby, a wtedy instrukcja (2) będzie wykonywana
„w nieskończoność". Możemy temu zapobiec w prosty sposób, często stosowany
w podobnych sytuacjach. Aby ograniczyć liczbę wszystkich prób
(zgadywań), czyli w tym przypadku liczbę wykonań się instrukcji (2), informatycy
dodają tzw. licznik. Niech w naszym przypadku maksymalna liczba
prób wynosi 7. Przyjrzyjmy się zmienionemu nieco algorytmowi.
Algorytm 9
(1) Wylosuj liczbę naturalną z przedziału 1...1000 i zapamiętaj ją.
(2) Przyjmij, że na początku licznik prób wynosi 1 (licznik=1). 211
(3) Wykonuj instrukcje (3.1) - (3.4), aż zgadujący trafi w wylosowaną liczbę
lub licznik>7:
(3.1) Poproś zgadującego o podanie liczby.
(3.2) Jeżeli proponowana liczba jest mniejsza od wylosowanej, to wypisz
komunikat, że szukana liczba jest większa od zaproponowanej.
(3.3) Jeżeli proponowana liczba jest większa od wylosowanej, to wypisz
komunikat, że szukana liczba jest mniejsza od zaproponowanej.
(3.4) Jeżeli zgadujący nie trafił jeszcze w wylosowaną liczbę, to zwiększ
licznik o 1 (tzn. za licznik przyjmij licznik +1).
(4) Jeżeli licznik>7, to wypisz: wyczerpałeś liczbę prób i nie odgadłeś
liczby,
w przeciwnym razie wypisz: gratulacje! zgadłeś po k próbach (gdzie
k- licznik).
(5) Koniec.
Teraz już widzisz, że ten algorytm na pewno się zakończy. Nawet gdybyśmy
podawali nietrafne liczby, po siedmiu próbach iteracja z punktu (3)
zakończy się ze względu na warunek: licznik>7. Zwróć uwagę, że warunek,
kiedy zakończyć powtarzanie, jest bardziej złożony niż w poprzednich
algorytmach:
gdy zgadujący trafi w wylosowaną liczbę lub licznik przekroczy 7.
W niektórych algorytmach warunki takie mogą być jeszcze bardziej skomplikowane
i dlatego należy być ostrożnym przy ich formułowaniu, zwłaszcza
gdy do opisu algorytmu używamy języka potocznego. Na szczęście programy
komputerowe piszemy w specjalnych językach programowania (takich
jak Pascal czy język C), które zapewniają niezbędną precyzję. Ponadto
często w algorytmach pewne warunki są równoważne, mimo że nie jest to
widoczne od razu. Weźmy dla przykładu dwa sposoby ujęcia warunku
w punkcie (3) w algorytmie 9.
I sposób:
(3) Przerwij wykonywanie poniższych instrukcji, gdy zgadujący trafi w wylosowaną
liczbę lub licznik>7.
II sposób:
(3) Przerwij wykonywanie poniższych instrukcji, gdy zgadujący trafi w wylosowaną
liczbę lub gdy licznik= 8.
Gdy zastanowisz się przez chwilę, z pewnością zauważysz, że oba te
212 warunki spełniają identyczną rolę w algorytmie 9. Wynika to stąd, że za
każdym razem, gdy zgadujący nie trafi w wylosowaną liczbę w punkcie
(3.4) zwiększamy licznik o 1. Przyjmuje on zatem wartości 1,2,3, 4, 5, 6,
7, 8. Widać, że pierwsze przekroczenie liczby 7 daje po prostu 8!
Sytuację, w której chcemy uzyskać w informacji telefonicznej numer telefonu
interesującej nas osoby lub instytucji, można ująć algorytmicznie,
a warunek, kiedy zakończyć powtarzanie, komplikuje się jeszcze bardziej.
Uzyskanie połączenia z informacją telefoniczną za pierwszym razem jest
rzeczą trudną - wszyscy to znamy z własnego doświadczenia! Stąd też
w naszym algorytmie pojawia się iteracja.
Algorytm 10
(1) Podnosimy słuchawkę.
(2) Wykręcamy numer 913.
(3) Jeżeli nie uzyskaliśmy połączenia i nie znudziło się nam jeszcze, to powracamy
do punktu (2), w przeciwnym razie przechodzimy do punktu (4).
(4) Jeżeli uzyskaliśmy połączenie, to dowiadujemy się o numer telefonu
interesującej nas osoby lub instytucji.
(5) Koniec algorytmu.
Oczywiście, użyty w tym algorytmie warunek: jeżeli nie znudziło się nam
jeszcze jest wysoce nieprecyzyjny i może oznaczać dla każdego coś innego.
Uparciuchy będą próbować dzwonić do informacji telefonicznej nawet
dwadzieścia razy! Ktoś inny podda się już po dwóch próbach. Dokonajmy
zmian w algorytmie 10 w taki sposób, aby próby dodzwonienia się
do informacji telefonicznej ponawiane były najwyżej pięć razy:
Algorytm 11
(1) Podnosimy słuchawkę.
(2) Wykręcamy numer 913.
(3) Jeżeli nie uzyskaliśmy połączenia i nie była to piąta próba, to powracamy
do punktu (2), w przeciwnym razie przechodzimy do punktu (4).
(4) Jeżeli uzyskaliśmy połączenie, to dowiadujemy się o numer telefonu
interesującej nas osoby lub instytucji.
(5) Koniec algorytmu.
Teraz warunek, kiedy zakończyć powtarzanie, udało nam się wyrazić
znacznie precyzyjniej. Po prostu albo uda nam się dodzwonić na informację
telefoniczną (w co najwyżej pięciu próbach) i uzyskać numer telefonu, albo .
zniechęceni rezygnujemy po pięciu próbach.
Przy okazji, jaki jest Twój rekord nieskutecznych prób dodzwonienia się do
informacji telefonicznej?
Ćwiczenie 8
Podaj przykłady z życia codziennego, w których występuje iterecja
(powtarzanie) jakiejś czynności. Jeżeli dobrze się zastanowisz,
stwierdzisz, że wiele jest takich sytuacji.
PODSUMOWANIE
Algorytm iteracyjny zawiera instrukcje, które nakazują wielokrotne
powtarzanie pewnych czynności.
Iteracje występują w algorytmach w dwóch podstawowych
odmianach:
• iteracja z określoną liczbą powtórzeń: wykonuj czynność A
dokładnie N razy,
• iteracja warunkowa: wykonuj czynność A, dopóki jest spełniony
warunek W.
Algorytm iteracyjny może działać na danych o dowolnej długości
(wielkości).
Wokół nas - wszędzie tam, gdzie jakieś czynności powtarzane są
wielokrotnie - możesz doszukać się algorytmów z iteracjami.
8.4 Skończoność - ważna własność algorytmów
Niekiedy algorytm, który ma wykonać jakieś ważne zadanie, może zupełnie
pobłądzić. Innymi słowy, będzie działać nie tak, jak zamierzaliśmy. Dla
przykładu, pomimo poprawnych danych wejściowych nie będzie chciał się
zatrzymać! Zamiast tego uparcie będzie wykonywał jakieś dziwne czynności.
Oczywiście, algorytm mógł zostać przez nas niepoprawnie skonstruowany.
Czy zawsze jednak stanowi to wystarczające wytłumaczenie?
Ćwiczenie 8.9
Podaj przykłady algorytmów niepoprawnie skonstruowanych, które
będą działać nie tak, jak zamierzaliśmy.
Weźmy pod uwagę, dla przykładu, wykonywanie rzutów karnych, gdy w
regulaminowym czasie mecz pucharowy nie został rozstrzygnięty.
Algorytm 12
Dane wejściowe: dwie drużyny piłkarskie.
(1) Wylosuj drużynę, która rozpocznie wykonywanie rzutów karnych.
(2) Wykonuj rzuty karne na zmianę, aż któraś z drużyn uzyska przewagę
(nie do odrobienia w pierwszej serii pięciu rzutów karnych) lub liczba
prób wyniosła 5.
(3) Jeżeli wynik pierwszej serii pięciu rzutów nie jest rozstrzygający, to idź
do (4), w przeciwnym razie idź do punktu (5).
(4) Wykonuj na zmianę po jednym rzucie karnym, aż któraś z drużyn uzyska
przewagę, wtedy idź do punktu (5).
(5) Koniec - wynik meczu jest rozstrzygnięty.
Powyższy algorytm jest odbiciem regulaminu wykonywania rzutów karnych,
które (w pewnych sytuacjach) mają wyłaniać zwycięzcę w rozgrywkach
UEFA. Najpierw drużyny wykonują na zmianę pierwszą serię pięciu karnych.
Gdy ta seria nie wyłoni zwycięzcy, wówczas drużyny strzelają na
przemian po jednym rzucie karnym, aż któraś z nich uzyska przewagę. Czy
nic Cię nie zaniepokoiło w tym algorytmie (regulaminie)? Przecież teoretycznie
zawodnicy mogą strzelać rzuty karne bez końca! Wystarczy tylko,
by po nie rozstrzygniętej pierwszej serii pięciu rzutów karnych obie drużyny
uzyskiwały identyczne rezultaty: obaj zawodnicy wyznaczeni do kolejnego
rzutu karnego albo strzelali bramkę, albo nie. Ta potencjalna nieskończoność
algorytmu ukryta jest w punkcie (4). Oczywiście, w rzeczywistości
nie ma praktycznie szans, aby strzelanie rzutów karnych nie wyłoniło w końcu
zwycięzcy. Kibice piłkarscy mogą potwierdzić, że dotychczas w rozgrywkach
pucharowych taka sytuacja nigdy się nie zdarzyła!
Zauważ, że na skończoność algorytmów mają wpływ przede wszystkim
iteracje, zwłaszcza warunkowe instrukcje iteracyjne (dopóki warunek jest
spełniony, wykonuj coś). Instrukcje takie powinny zawierać prawidłowo
określony warunek zakończenia. Jak przekonasz się wkrótce, znane są
w informatyce zadziwiająco proste algorytmy, o których nie wiemy, czy (dla
dowolnie wybranych danych wejściowych) zawsze zatrzymają się po skończonej
liczbie kroków.
Kolejne ćwiczenie dotyczy zagadnień praktycznych. Po raz kolejny odwołujemy
się do procedur prawnych.
Podać jakies informacje ia temat wyborów, może adres internetowy?
Ćwiczenie 8.10
Dowiedz się, jak wygląda wybór burmistrza czy wójta w Twej
miejscowości. Jest to ściśle określona procedura prawna (o charakterze
algorytmicznym) opisana w Ustawie o Samorządzie Terytorialnym.
Zwróć uwagę, że algorytm ten rozpoczyna się od lokalnych
wyborów samorządowych (wybór rady gminy), następnie
odbywa się zgłaszanie kandydatów w radzie gminy oraz głosowania.
Czy może to trwać bez końca? Co się dzieje, gdy po kilku
miesiącach rada gminy nie zdoła wybrać burmistrza (wójta)?
Teraz nie trzeba Cię chyba przekonywać, że sprawne przeprowadzenie
wyborów zależy od precyzji regulaminu (algorytmu). Zajmijmy się więc
wyborem przewodniczącego klasy.
Algorytm 13 (a raczej szkic algorytmu)
Dane wejściowe: uczniowie Twojej klasy.
Dane wyjściowe: przewodniczący klasy.
(1) Zgłaszanie kandydatów.
(2) Głosowanie (tajne).
(3) Liczenie głosów.
(4) Ogłoszenie wyników.
(5) Jeżeli głosowanie nie wyłoniło zwycięzcy, powrót do (2).
(6) Koniec - ogłoszenie ostatecznego wyniku.
Zwróć uwagę, że jest to tylko szkic algorytmu. Wiele spraw należy jeszcze
szczegółowo ustalić. Należy zadbać przede wszystkim o to, by wybory nie
trwały bez końca. Będziecie musieli rozstrzygnąć, jak postępować w przypadku,
gdy kilka osób otrzyma tę samą liczbę głosów, jak długo powtarzać
głosowanie, kiedy głos jest ważny, na ilu kandydatów każdy uczeń może
głosować.
Następnym zadaniem, które stanie przed nami, jest przeprowadzenie rozgrywek
szkolnych (np. w siatkówce). Zauważ, że najprostszy sposób („każdy
z każdym") właściwie nie nadaje się już dla 10 drużyn. Należałoby wtedy
rozegrać (10*9 )/2 = 45 meczów! Podobne kłopoty mają organizatorzy
piłkarskich mistrzostw świata (w których uczestniczą 24 drużyny).
Algorytm 14 (szkic)
Dane wejściowe: pewna liczba drużyn.
Dane wyjściowe: mistrz i wicemistrz.
(1) Ustalenie liczby grup i sposobu przydziału drużyn do poszczególnych
grup (na ogół odbywa się to poprzez losowanie).
(2) Ustalenie, kto kwalifikuje się z danej grupy do następnego etapu rozgrywek.
(3) Opis rozgrywek w drugim etapie (w szczególności - kto z kim ma grać).
(4) Opis kolejnych etapów aż do rozgrywek półfinałowych i finałowych.
(5) Koniec - mistrz i wicemistrz są wyłonieni.
Ćwiczenie 8.11
Postaraj się doprecyzować szkic algorytmu 14, tak aby rzeczywiście
można było na jego podstawie przeprowadzić rozgrywki.
Czy wyłoniona w ten sposób drużyna wicemistrzowska wygrałaby
z każdym, z wyjątkiem mistrza? Uwzględnij w regulaminie sytuację,
w której dwie najlepsze drużyny znajdą się w wyniku
losowania w tej samej grupie eliminacyjnej.
Kolejny algorytm jest niezmiernie ciekawy. Choć wydaje się on niezbyt
skomplikowany, do dziś nie wiadomo, czy zawsze musi się zatrzymać po
skończonej liczbie kroków. (Mamy nadzieję, że potrafisz mnożyć i dzielić!).
Algorytm 15
Dane wejściowe: liczba naturalna N.
(1) Dopóki liczba N jest różna od 1, wykonuj:
(1.1) Wypisz komunikat: to ja, algorytm, wciąż żyję!!!
(1.2) Jeżeli liczba N jest parzysta, to podziel ją przez 2, wynik zapamiętaj
jako nową wartość N i wróć do punktu (1), w przeciwnym
razie idź do (1.3).
(1.3) Pomnóż N przez 3 i dodaj 1, a wynik zapamiętaj jako nową wartość
N i wróć do punktu (1).
(2) Koniec algorytmu.
Prześledźmy działanie tego algorytmu dla kilku przykładowych danych
początkowych.
Niech na początku A/=52. Wtedy kolejne wartości N będą następujące:
26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1. STOP.
Gdy na początku A/=9, wówczas kolejne wartości N będą następujące:
28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1. STOP.
Możesz spróbować samemu poeksperymentować z różnymi wartościami
początkowymi N. Zapewne zawsze dojdziesz do końca w punkcie (2) i algorytm
przestanie wypisywać radosny komunikat to ja algorytm, wciąż
żyję!!! Wydaje się, że w pewnym momencie algorytm „umrze". Na pewno?
To prawda, że do dzisiaj nikt nie natrafił na taką liczbę początkową N, dla
której ten algorytm wykonywałby się w nieskończoność. Z drugiej strony,
nikt też nie udowodnił, że ten prosty algorytm jest skończony.
PODSUMOWANIE:
Poprawnie zaprojektowany algorytm powinien zatrzymać
się po skończonej liczbie kroków (chyba że w jakichś celach
konstruujemy algorytm, który ma się nigdy nie skończyć).
Problemy ze skończonością algorytmów pojawiają się
przede wszystkim tam, gdzie występują instrukcje iteracyjne,
zwłaszcza w postaci warunku: dopóki W, wykonuj A.
W życiu społecznym (np. przy wyborze parlamentu, prezydenta)
niezmiernie ważne jest, by pewne procedury (algorytmiczne) zawsze
kończyły się po skończonej liczbie kroków, a nie trwały bez końca.
Czasami, mimo pozornej prostoty algorytmu, trudno jest udowodnić,
że dla wszystkich danych początkowych zatrzyma się po
skończonej liczbie kroków.
8.5 Magiczne sztuczki, czyli co to jest rekurencja
Słowo rekurencja zapewne nie jest Ci znane. Rekurencja jest pewną metodą
konstruowania algorytmów. Choć wydaje się trochę dziwna, tajemnicza,
często pozwala na przejrzyste i eleganckie rozwiązanie wielu problemów.
Na czym zatem polega ogólna idea algorytmu rekurencyjnego? W pewnym
momencie algorytm jest zatrzymywany i „proszony" o rozwiązanie takiego
samego zadania, ale dla innych danych. Mówimy też, że algorytm rekurencyjny
w trakcie wykonywania odwołuje się sam do siebie. Jest to trochę
tajemnicze - przyznajcie sami!
Rys. 8.3 Podobny rysunek rekurencyjny można znaleźć w znanej książce N. Wirtha
Algorytmy + struktury danych = programy
Kilka przykładów powinno jednak rozwiać Twoje wątpliwości. Oto pierwszy
z nich. Wyobraź sobie, że mały Piotruś zrobił ogromny bałagan w pokoju.
Mama poleciła mu, aby wszystkie porozrzucane zabawki powkładał do
dużego pudła, w którym są normalnie przechowywane. Zadanie zatem
brzmi: pozbieraj te zabawki i ułóż je w pudle. Jak sformułować algorytm,
który będzie „sterował" Piotrusiem w trakcie sprzątania? Oczywiście Piotruś
nie może wziąć wszystkich zabawek jednocześnie i sprawić, aby od razu
znalazły się w pudle. Może natomiast wykonać nieco inne polecenie: weź
jedną zabawkę i schowaj ją do pudła, a następnie posprzątaj to, co zastanie.
Aby lepiej zobaczyć rekurencyjną naturę takiego postępowania, zapiszmy
nasz algorytm.
Algorytm 16 PIOTRUŚ SPRZĄTA ZABAWKI - wersja rekurencyjną
Dane wejściowe: bałagan w pokoju.
Dane wyjściowe: porządek w pokoju.
(1) Jeśli jest bałagan, to weź jakąś zabawkę i włóż ją do pudła, w przeciwnym
razie idź do (3).
(2) PIOTRUŚ SPRZĄTA (pozostałe) ZABAWKI.
(3) Koniec sprzątania.
Zwróć uwagę, że algorytm 16 odwołuje się do siebie samego. Ma to miejsce
w punkcie (2) - instrukcja stwierdza, aby jeszcze raz zastosować algorytm
PIOTRUŚ SPRZĄTA ZABAWKI. Prześledźmy działanie algorytmu 16 w sytuacji,
gdy Piotruś ma do posprzątania trzy zabawki: A, B, C.
Krok 1. Instrukcja (1) Schowanie zabawki A do pudła.
Krok 2. Instrukcja (2) PIOTRUŚ SPRZĄTA (pozostałe) ZABAWKI.
Krok 3. Instrukcja (1) Schowanie zabawki B do pudła.
Krok 4. Instrukcja (2) PIOTRUŚ SPRZĄTA (pozostałe) ZABAWKI.
Krok 5. Instrukcja (1) Schowanie zabawki C do pudła.
Krok 6. Instrukcja (2) PIOTRUŚ SPRZĄTA (pozostałe) ZABAWKI.
Krok 7. Instrukcja (1) Nie ma bałaganu, więc idź do (3).
Krok 8. Instrukcja (3) Koniec.
Krok 9. Instrukcja (3) Koniec.
Krok 10. Instrukcja (3) Koniec.
Krok 11. Instrukcja (3) Koniec.
Czy zauważyłeś, że powyższe zadanie dałoby się opisać algorytmem iteracyjnym?
Algorytm 17 (Piotruś sprząta zabawki) - wersja iteracyjna
Dane wejściowe: bałagan w pokoju.
Dane wyjściowe: porządek w pokoju.
(1) Jak długo jest bałagan, wykonuj poniższe kroki:
(1.1) Weź zabawkę i włóż ją do pudła.
(2) Koniec - pokój jest posprzątany.
Mamy zatem rekurencyjny i iteracyjny algorytm. Oba rozwiązują to samo
zadanie. Wydaje się, że algorytm iteracyjny jest prostszy. Dlaczego zatem
wprowadzamy algorytmy rekurencyjne? Otóż okazuje się, że nie wszystkie
problemy mają proste rozwiązania w postaci algorytmów iteracyjnych. Mogą
natomiast mieć bardzo proste i eleganckie rozwiązania rekurencyjne.
Rozważmy kolejny przykład. Załóżmy, że komputer wczytuje „z zewnątrz"
(z klawiatury lub z pliku) jakiś ciąg znaków np., abcdxyz. Zadaniem programu
komputerowego jest wypisanie (np. na monitorze) tych znaków, ale
w odwrotnej kolejności, czyli zyxdcba. Oto algorytm rekurencyjny, który
realizuje to zadanie.
Algorytm 18 ODWRÓĆ CIĄG ZNAKÓW
Dane wejściowy: ciąg znaków.
Dane wyjściowe: odwrócony ciąg znaków.
(1) Jeżeli ciąg znaków jest niepusty, to weź ostatni jego znak i ustaw na
początku, w przeciwnym razie idź do (3).
(2) ODWRÓĆ (pozostały) CIĄG ZNAKÓW.
(3) Koniec.
Postarajmy się wykonać ten algorytm dla przykładowego ciągu: abc. Uruchamiamy
nasz algorytm ODWRÓĆ CIĄG ZNAKÓW dla ciągu abc.
Krok (1) Instrukcja (1) Ustaw c na początku.
Krok (2) Instrukcja (2) ODWRÓĆ (pozostały) CIĄG ZNAKÓW (tzn. ab).
Krok (3) Instrukcja (1) Ustaw b na początku.
Krok (4) Instrukcja (2) ODWRÓĆ (pozostały) CIĄG ZNAKÓW (składający
się już tylko z jednej litery: a).
Krok (5) Instrukcja (1) Ustaw a na początku.
Krok (6) Instrukcja (2) ODWRÓĆ (pozostały) CIĄG ZNAKÓW
(ale nie zawiera on już żadnej litery).
Krok (7) Instrukcja (1) Ciąg jest pusty, więc idź do (3).
Krok (8) Instrukcja (3) Koniec.
Krok (9) Instrukcja (3) Koniec.
Krok (10) Instrukcja (3) Koniec.
Krok (11) Instrukcja (3) Koniec.
W efekcie zastaną wypisane znaki: cba.
Ćwiczenie 8.12
Czy algorytm 18 można wykorzystać w następującej sytuacji:
mamy ustawionych w szeregu uczniów Twojej klasy; chcemy, by
byli ustawieni w odwrotnej kolejności?
Teraz nadszedł czas, abyś zapoznał się z trochę trudniejszym zadaniem chodzi o rozwiązanie pewnej łamigłówki. Wydaje się, że trudno byłoby ją
rozwiązać nawet mądremu człowiekowi, gdyby nie zastosowanie odpowiedniego
algorytmu rekurencyjnego. Z drugiej strony, zastosowanie algorytmu nie wymaga
właściwie żadnego myślenia i polega na mechanicznym
wykonaniu określonych czynności. Przykład ten znany jest jako problem
Wież z Hanoi. Mamy trzy paliki wbite w podłoże i pewną liczbę
krążków o różnych średnicach z otworami w środku. Krążki mogą być
nakładane na pręty, tworząc w ten sposób „wieże". Załóżmy, że na pierwszym
paliku mamy pewna ilość krążków ułożonych od największego do
najmniejszego.
Rys. 8.4 Patyczki i krążki tworzące „wieżę"
Naszym zadaniem jest przeniesienie tych krążków na trzeci patyczek C
przy pomocy patyczka B, ale musimy przestrzegać następujących reguł:
• wolno przenosić tylko po jednym krążku,
• nie wolno nigdy położyć większego krążka na mniejszym.
Łamigłówka ta pojawiła się w Europie XIX wieku. Odwołuje się do legendy,
pochodzącej z Tybetu, o mnichach przenoszących krążki w pewnym klasztorze,
zgodnie z regułami opisanymi powyżej (przy czym krążków tych ma
być dokładnie 64!). Z jednym krążkiem na początku nie ma problemu.
Wystarczy po prostu przenieść ten krążek z patyczka A na C.
Ćwiczenie 8.13
Rysując krążki na papierze (lub na tablicy), rozwiąż problem Wież
z Hanoi dla N=2 i N=3 (gdzie N oznacza liczbę krążków).
222
Ćwiczenie 8.13 nie powinno sprawić Ci kłopotów. Jednak dla A/=4, A/=5 itd.
(dla czterech, pięciu krążków) napotkasz już trudności. A jak postępowałbyś
z 64 krążkami? Okazuje się, że algorytm rekurencyjny jest tu bardzo
przydatny. Jak to w rekurencji bywa, rozwiązując zadanie dla N krążków,
opieramy się na rozwiązaniu dla N-1 krążków. Zanim jednak zapoznamy
się z ogólnym algorytmem rozwiązania, rozwiążemy go dla A/=4 krążków.
(Zauważmy, że z trzema krążkami potrafimy sobie poradzić). Dla czterech
krążków postępujemy następująco:
DLA DOCIEKLIWYCH
Algorytm 19 WIEŻE HANOI (dla A/=4)
(1) Wykorzystując algorytm WIEŻE HANOI dla A/=3, przenosimy 3
górne krążki z patyczka A na B przy pomocy C.
(2) Pozostały największy krążek z patyczka A przenosimy na patyczek
C.
(3) Wykorzystując algorytm WIEŻE HANOI dla A/=3, przenosimy 3 górne
krążki z patyczka B na patyczek C przy pomocy patyczka A.
Rys. 8.5 Kolejne etapy przenoszenia krążków
Oto algorytm, który będzie rozwiązywał problem Wież z Hanoi w ogólnym
przypadku. Składa się on z trzech głównych etapów i dwukrotnie odwołuje
się do rozwiązania dla przypadku, gdy jest o jeden krążek mniej.
Algorytm 20 WIEŻE HANOI (dla N krążków)
Dane wejściowe: Trzy patyczki A, B, C. Na patyczku A jest N krążków.
Dane wyjściowe: Trzy patyczki A, B, C. Na patyczku C jest N krążków.
(1) Jeżeli A/=1, to przenieś krążek z A na C i zakończ, w przeciwnym
razie idź do (2).
(2) Wykorzystując algorytm WIEŻE HANOI (dla N-1 krążków), przenieś
N-1 górnych krążków z patyczka A na B, używając C.
(3) Pozostały największy krążek przenieś z patyczka A na C. 223
(4) Wykorzystując algorytm WIEŻE HANOI (dla N-1 krążków), przenieś
N-1 krążków z patyczka B na C używając A.
Algorytm 20 radzi sobie z przeniesieniem N krążków z patyczka A na C
przy pomocy B. Jak on działa? Najpierw sprawdza, czy A/=1 i w tym przypadku
przenosi po prostu ten jeden krążek z patyczka A na C. Jeżeli
N>1, algorytm najpierw przenosi górne N-1 krążki z patyczka A na „pomocniczy"
B. Potem bierze jedyny krążek pozostawiony na patyczku
A (jest to największy krążek leżący na samym dole) i przenosi go na
docelowe miejsce przeznaczenia, czyli na patyczek C. Potem znowu
rekurencyjnie przenosi N-1 krążków (które chwilowo były „przechowywane"
na patyczku B) na patyczek C. Zauważ, że w kolejnych krokach
algorytmu rola patyczków A, B, C zmienia się! Oczywiście człowiekowi
jest czasami trudno śledzić poszczególne kroki, natomiast komputery
radzą sobie z tym wyśmienicie.
Teraz już chyba lepiej rozumiesz, czym jest algorytm rekurencyjny. Najważniejszą
cechą takiego algorytmu jest to, że w trakcie jego wykonywania
w pewnym momencie algorytm taki odwołuje się sam do siebie. Jak zwykli
mówić informatycy, algorytm rekurencyjny wywołuje samego siebie. Takie
wywołanie musi nastąpić dla nieco innych danych (przeważnie jest to
mniejszy zbiór danych), aby w końcu algorytm zatrzymał się. Tak samo jak
dla przykładowych algorytmów z podrozdz. 8.4 (Skończoność - ważna
cecha algorytmów) istotne jest, aby algorytm rekurencyjny się zatrzymał.
Nietrudno jest natomiast podać przykłady algorytmów rekurencyjnych, które
nigdy się nie kończą. Oto prosty przykład:
Algorytm 21 MASZERUJ (może IDŹ?)
(1) Zrób krok do przodu.
(2) MASZERUJ.
(3) Stop.
Oczywiście widzisz, że jest to typowy algorytm rekurencyjny. W punkcie (2)
mamy odwołanie się algorytmu MASZERUJ do siebie samego. Przyglądnij
się jednak dokładniej temu algorytmowi. Czy coś Cię nie niepokoi? Czy
algorytm ten zatrzyma się kiedykolwiek? Oczywiście, że nie. To nic, że
w punkcie (3) czeka upragniona instrukcja Stop, skoro algorytm MASZERUJ
nigdy do tej instrukcji nie dotrze. Będzie on w kółko robił „krok do
przodu" i wywoływał siebie samego w nieskończoność.
Bardzo ciekawym obszarem zastosowania algorytmów rekurencyjnych jest
grafika. Wszędzie tam, gdzie występują obrazy graficzne złożone z pewnych wielokrotnie
powtarzanych motywów, można dopatrzyć się rekurencji.
Spróbujmy wygenerować spiralę, taką jak na rys. 8.3, przy pomocy algorytmu
rekurencyjnego.
Rys. 8.6 Spirala
Aby łatwiej było nam opisać ten algorytm, wyobraź sobie, że mamy do
dyspozycji jakieś urządzenie kreślące, którym możemy sterować przy pomocy
kilku prostych poleceń. Wśród tych poleceń mamy:
(I) ruch (x) - oznacza przesunięcie pisaka o x cm do przodu lub do tyłu
w zależności od znaku liczby x,
(II) obrót (a) - oznacza zmianę kierunku ruchu pisaka o kąt a. Obrót jest
w kierunku przeciwnym do ruch wskazówek zegara, gdy kąt a jest dodatni.
W algorytmie rysującym spiralę wykorzystamy te dwie instrukcje: ruch
i obrót. Dodatkowo użyjemy zmiennej x, która na początku ma wartość
0,5 cm. Najwygodniej też będzie ustawić pisak na środku kartki oraz umówić
się, że pierwszy ruch nastąpi w prawo.
Algorytm 22 RYSUJ SPIRALĘ
Dane wejściowe: czysta kartka papieru.
Dane wyjściowe: rysunek spirali.
(1) Wykonaj: ruch (o aktualną wartość x).
(2) Wykonaj: obrót (o 90 stopni).
(3) Do zmiennej x dodaj 0,5 cm.
(4) Jeżeli x<5 cm, to RYSUJ SPIRALĘ.
(5) Koniec.
Postaraj się „wykonać" ten algorytm. Wystarczy coś do pisania i kartka
papieru. Ustaw pisak na środku, a potem wykonuj krok za krokiem polecenia
zawarte w algorytmie. Jeżeli będziesz uważny, algorytm bez trudu 225
posteruje Tobą tak, że narysujesz rozwijająca się spiralę. Jest to typowy
algorytm rekurencyjny - w punkcie (4) odwołuje się sam do siebie!
Zastanówmy się jeszcze nad możliwymi modyfikacjami algorytmu 22.
Oczywiście w zależności od tego, jaka będzie początkowa wartość zmiennej
x i jaka wartość ograniczająca wystąpi w punkcie (4), różne będą rozmiary
spirali (w algorytmie 22 mamy: Jeżeli bok<5 cm, to...). Tak samo
wpływ na narysowana spiralę będzie miała wartość „skoku", którą dodajemy
w punkcie (3) (w algorytmie 22 jest to 0,5 cm). Dalsze modyfikacje
mogą polegać na zmianie kąta, o który zmieniamy kierunek ruchu pisaka
(w algorytmie 22 jest to 90 stopni). Zauważ, że algorytm ten zatrzyma się,
gdyż warunek w punkcie (4) w pewnym momencie przestaje być prawdziwy,
więc przechodzimy ostatecznie do punktu (5).
Ćwiczenie 8.14
Zastanów się, ile razy wywołana zostanie instrukcja RYSUJ SPIRALĘ
w punkcie (4). Odpowiedz na to samo pytanie, gdy początkowa
wartość zmiennej x wynosi 2 cm, skok, o który ją zwiększamy
w punkcie (3), wynosi 3 cm, a wartość ograniczająca x
w punkcie (4) wynosi 15 cm.
Ostatnim przykładem, który ma proste i przejrzyste rozwiązanie rekurencyjne,
jest problem porządkowania, czyli sortowania (przypomnij sobie nasze
rozważania w podrozdz. 8.2) Pamiętasz zapewne, że sortowanie ciągu
liczb polega na ich uporządkowaniu (od najmniejszej do największej bądź
odwrotnie). Z kolei sortowanie nazwisk na liście uczniów będzie polegało
na uporządkowaniu alfabetycznym. W algorytmice skonstruowano wiele
algorytmów, które wykonują sortowanie. Jednym z najlepszych jest tak
zwany algorytm szybkiego sortowania. Idea jego działania jest niezmiernie
prosta. W pierwszym etapie wyjściowy zbiór danych dzielimy na dwa
mniejsze „worki", tak aby w pierwszym wszystkie liczby były mniejsze od
wszystkich z drugiego. Potem tym samym algorytmem sortujemy oba mniejsze
„worki". W ten sposób po połączeniu danych uporządkowanych z pierwszego
i drugiego zbioru otrzymamy uporządkowany cały wyjściowy zbiór.
Algorytm 23 SORTUJ
Dane wejściowe: ciąg liczb.
226 Dane wyjściowe: uporządkowany ciąg liczb (od najmniejszej do największej).
(1) Wybierz dowolną liczbę z ciągu.
(2) Podziel wyjściowy ciąg na dwie części - dwa podciągi: pierwszy podciąg
niech zawiera liczby mniejsze, a drugi podciąg - liczby większe bądź
równe od liczby wybranej w punkcie (1).
(3) SORTUJ pierwszy podciąg.
(4) SORTUJ drugi podciąg.
(5) Koniec - ciąg jest posortowany.
Jak widzisz, jest to algorytm rekurencyjny. W punkcie (3) i (4) algorytm
SORTUJ wywołuje siebie samego.
PODSUMOWANIE:
Algorytm rekurencyjny w trakcie wykonywania odwołuje
się do samego siebie.
Aby algorytm rekurencyjny mógł się zatrzymać, jego kolejne
odwołania do siebie samego muszą zależeć od pewnego
warunku, który zmienia się za każdy kolejnym odwołaniem.
Dzięki algorytmom rekurencyjnym trudne problemy udaje się
często rozwiązać w przejrzysty sposób.
8.6 Algorytmy w matematyce
Być może zauważyłeś już, że wielokrotnie na lekcjach matematyki pojawiały
się przykłady przepisów, które tak naprawdę można traktować jak
algorytmy. Oczywiście słowo to nie było używane, ale to niczego nie zmienia.
Na przykład przepis na tzw. pisemne dodawanie czy mnożenie liczb
jest to w gruncie rzeczy opis algorytmu, który zapewnia otrzymanie sumy
lub iloczynu danych liczb. Nieomal każdy potrafi stosować te algorytmy,
choć - przyznajmy - niewielu je rozumie. Czy zastanawiałeś się nad tym,
dlaczego pisemne dodawanie jest poprawnym przepisem?
Rzeczywiście, okazuje się, że matematyka jest niewyczerpanym źródłem
wielu algorytmów. Niektóre są dość banalne, a inne bardzo pomysłowe.
Warto tu przypomnieć, że pierwsze komputery powstały na potrzeby
matematycznych
obliczeń. Wiele zagadnień, którymi zajmują się inżynierowie
czy naukowcy, prowadzi bowiem do bardzo skomplikowanych, pracochłonnych
obliczeń. Właściwie żaden człowiek ani grupa ludzi nie byliby w stanie
ich wykonać. Nawet samo słowo komputer pochodzi od angielskiego czasownika
compute oznaczającego: obliczać, wyliczać, liczyć. Przedstawimy
teraz kilka algorytmów zaczerpniętych z matematyki.
Algorytm Euklidesa
Jest to jeden z najstarszych znanych algorytmów. Wielki matematyk starożytnej
Grecji, Euklides, zamieścił go w swoim słynnym dziele pod tytułem
Elementy. Było to ok. 300 roku p.n.e. Przypuszcza się, że algorytm ten był
znany jeszcze wcześniej. Czego dotyczy ten algorytm? Jest to przepis na
wyznaczanie tzw. największego wspólnego dzielnika dwóch liczb (w skrócie:
NWD). Przypomnijmy, że największy wspólny dzielnik liczb naturalnych
to największa liczba naturalna, przez którą można obie liczby podzielić bez
reszty. Inaczej mówiąc: mając dane dwie liczby naturalne a i b, szukamy
największej liczby naturalnej, która dzieli jednocześnie a i b. Nietrudno jest
wymyślić następujący algorytm znajdowania NWD:
Algorytm 24
Dane wejściowe: dwie liczby naturalne a i b.
Dane wyjściowa: NWD (a,b).
(1) Znajdź zbiór wszystkich dzielników liczby a.
(2) Znajdź zbiór wszystkich dzielników liczby b.
(3) Wybierz największą liczbę, która należy do obu zbiorów.
(4) Stop.
Oczywiście algorytm ten wynika wprost z definicji NWD. Jak widzisz, jest
nie jest on zbyt pomysłowy. Na przykład jeżeli jedna liczba ma niewiele
dzielników, a druga ma wiele dzielników, to wyznaczanie wszystkich dzielników
może być zbędne. Konkretny przykład a=4, 6=180. Wtedy w punkcie
(1) wyznaczamy: 1, 2, 4, a w punkcie (2): 2, 3, 5, 6, 9, 10, 12, 15, 18, 20,
30, 36, 45, 60, 90. Widać, że w (2) zupełnie niepotrzebnie wyznaczamy
dzielniki 5, 6, 9, 10, 12, 15, 18, 20, 30, 36, 45, 60, 90, które są większe od
a=4 i w związku z tym nie ma wśród nich NWD(a,ib). Można by starać się
usprawniać „naiwny" algorytm 24, ale zostawmy to i zajmijmy się algorytmem
Euklidesa. Zacznijmy od prześledzenia go na konkretnym przykładzie:
a=3654, b=1365. Wykonujemy następujące operacje (dzielenie i
branie reszty):
3654 dzielimy przez 1365, dostajemy resztę 924
1365 dzielimy przez 924, dostajemy resztę 441
924 dzielimy przez 441, dostajemy resztę 42
441 dzielimy przez 42, dostajemy resztę 21
42 dzielimy przez 21, dostajemy resztę 0
228 NWD(3654,1365)=21
Jak widzisz, idea tego algorytmu polega na dzieleniu przez kolejne reszty,
aż uzyskamy resztę zero. Wtedy ostatnia niezerowa reszta to właśnie
NWD. Zapiszmy ten algorytm bardziej formalnie:
Algorytm 25 (algorytm Euklidesa wyznaczania NWD)
Dane wejściowe: Dwie liczby naturalne a i b.
Dane wyjściowe: NWD (a,b).
(1) Niech m będzie mniejszą spośród liczb a i b oraz niech n będzie
większą.
(2) Jeśli m=0, to n jest szukanym NWD. Zakończ.
(3) Oblicz pomocniczą resztę: r = reszta z dzielenia n przez m.
(4) Za n podstaw m, a za m podstaw r. Wróć do (2).
Ćwiczenie 8.15
Aby dobrze zrozumieć ten algorytm, postaraj się wykonać go dla
kilku przykładowych par liczb naturalnych: a=48, 6=46; a=34,
b=55 itp.
Z pewnością zastanawiasz się, dlaczego ten algorytm rzeczywiście znajduje
NWD. Trafne pytanie! Na razie zajmowaliśmy się tylko opisem samego
algorytmu, a nie próbowaliśmy uzasadniać, że jest on poprawny, tzn. że
w rezultacie jego działania osiągniemy zamierzony cel. Pozostawmy jednak
dowód poprawności algorytmu Euklidesa nauczycielowi matematyki! Sprytny
wybieg, nieprawdaż?
Sprawdzanie, czy dana liczba jest liczbą pierwszą
Kolejne przykłady algorytmów matematycznych będą znów związane z arytmetyką
liczb naturalnych (tzn. nieujemnych liczb całkowitych). Podstawowym
pojęciem jest tutaj liczba pierwsza. Przypomnijmy najpierw podstawowe
definicje. Liczba naturalna jest liczbą pierwszą, gdy dzieli się tylko
przez 1 i przez samą siebie. Przy czym przyjmujemy, że liczba 1 nie jest
liczbą pierwszą. Jeżeli liczba naturalna ma dzielniki różne od 1 i od niej
samej, to nazywamy ją liczbą złożoną. Liczbami pierwszymi są na przykład:
2, 3, 5, 7, 11, a liczbami złożonymi 6=2*3,15=3*5, 18=2*3*3. To są
niezmiernie proste przykłady. Ale gdyby się ktoś zapytał, czy liczba
1243562713 jest liczbą pierwszą czy złożoną, wówczas trzeba się trochę
napracować nad odpowiedzią. Oczywiście czasami nawet dla dużej liczby
od razu widać, że jest złożona. Przykładowo, liczba 15423342 na pewno
nie jest pierwsza, gdyż jest parzysta (dzieli się przez 2). Podobnie 229
65365115 dzieli się przez 5, więc jest liczbą złożoną. W ogólnym przypadku
dla dużych liczb trzeba jednak stosować bardzo szybkie komputery i specjalne
algorytmy, aby stwierdzić, czy dana liczba jest pierwsza czy złożona.
Okazuje się, że liczby pierwsze mają duże znaczenie w kryptografii, czyli
nauce zajmującej się szyfrowaniem i kodowaniem informacji. Takie szyfrowanie
ma obecnie duże znaczenie w sieci Internet. Pewne dane, które
przesyłane są w sieci, nie powinny być dostępne dla każdego. Przykładowo,
mogą to być dane osobowe czy numery kart kredytowych. Okazuje się,
że do szyfrowania potrzebne są duże liczby pierwsze. Dlatego obecnie tak
wiele uwagi poświęca się znajdowaniu coraz większych liczb pierwszych.
Jak zatem najprościej sprawdzić, czy dana liczba jest liczbą pierwszą?
Oczywiście możemy brać wszystkie kolejne liczby począwszy od 2 do
danej i sprawdzać, czy jest to dzielnik czy nie. Oto prosty algorytm:
Algorytm 26
(najprostszy algorytm sprawdzania, czy liczba jest pierwsza)
Dane wejściowe: N liczba naturalna różna od 1.
(1) Niech k=2.
(2) Dopóki (N nie dzieli się przez k), wykonuj:
(2.1) Zwiększ k o 1 (czyli za k podstaw k+1).
(3) Jeżeli k=N, to N jest liczbą pierwszą, w przeciwnym razie N jest liczbą
złożoną.
(4) Koniec.
Ten algorytm naprawdę jest bardzo prosty. W instrukcji (2) wykonujemy
dzielenia kolejno przez 2, 3, 4 itd. Jeżeli natrafimy na dzielnik, to przerywamy.
Nasuwa się pytanie: Czy może się zdarzyć, że algorytm 26 nigdy się
nie zatrzyma? Inaczej mówiąc, czy warunek: N nie dzieli się przez k będzie
cały czas spełniony? Otóż, jak łatwo możesz zauważyć, algorytm zawsze
się zatrzyma. W najgorszym bowiem przypadku dojdziemy w końcu z k do
N (za każdym razem k jest zwiększane o 1, więc nie można „ominąć" liczby
N), a wtedy k będzie dzielić N. Jeżeli w instrukcji (2) zatrzymamy się na N,
to jest k będzie równe N, oznaczać to będzie, że wcześniej nie było żadnego
dzielnika, a zatem mamy do czynienia z liczbą pierwszą. W przeciwnym
razie N jest liczbą złożoną. Stąd na podstawie (3) otrzymujemy
poprawną odpowiedź.
Jak można by usprawnić ten algorytm? Pomyśl przez chwilę. Czy jest sens
sprawdzać wszystkie wartości k=2,3,...,A/w poszukiwaniu dzielnika? Chyba
jest Ci znany fakt, że liczba, która dzieli daną liczbę N, nie może być
większa od N/2. Na przykład jeżeli A/=1200, to wszystkie możliwe dzielniki
230 liczby 1200 różne od niej samej nie mogą być większe od A//2=600. Tak
więc proste usprawnienie naszego Algorytmu 26 może polegać na ograniczeniu
się w instrukcji (2) do sprawdzania warunku: dopóki (N nie dzieli się
przez k) i (kjest mniejsze bądź równe N/2). Taki nieco ulepszony algorytm
będzie wyglądał tak:
Algorytm 27
(udoskonalony algorytm sprawdzania, czy liczba jest pierwsza)
Dane wejściowe: N liczba naturalna różna od 1.
(1) Niech k=2.
(2) Dopóki (N nie dzieli się przez k) i (k<N/2), wykonuj:
(2.1) zwiększ k o 1 (czyli za k podstaw k+1).
(3) Jeżeli k>N/2, to N jest liczbą pierwszą, w przeciwnym razie N jest liczbą
złożoną.
(4) Koniec.
Chwili uwagi wymaga teraz tylko instrukcja (3), a w szczególności warunek:
Jeżeli k>N/2, to.... Najlepiej sprawdzić ten algorytm na kilku przykładowych
liczbach.
Ćwiczenie 8.16
Wykonaj algorytm 27 dla kilku przykładowych danych: N=8,
N=13, N=18.
Co możemy powiedzieć o przewadze algorytmu 27 nad algorytmem 26?
Widać, że w algorytmie 27 wykonujemy przeważnie o połowę mniej sprawdzeń
w punkcie (2). W jednym przypadku k zmienia się maksymalnie do
N, a w drugim do N/2. Można zatem powiedzieć, że algorytm 27 powinien
być przeważnie dwa razy szybszy. Ale czy zawsze tak będzie?
Ćwiczenie 8.17
Porównaj działanie algorytmu 26 i algorytmu 27 dla następujących
danych: N=17, N=12, N=77. Czy potrafisz wysnuć jakieś
ogólniejsze wnioski?
Zauważmy przynajmniej jedno: Jeżeli liczba N jest liczbą pierwszą, to rzeczywiście
instrukcja (2) w algorytmie 27 wykona się dwa razy szybciej niż 231
w algorytmie 26. W innych przypadkach, tzn. gdy N jest liczbą złożoną, tak
już nie będzie. W skrajnym przypadku, gdy N jest parzysta, obie pętle
wykonają się po jednym razie!
Trudno, nasze usprawnienie okazało się niezbyt efektowne. Na szczęście,
algorytm 27 można usprawnić o wiele bardziej, odwołując się do matematycznego
twierdzenia o dzielnikach liczby naturalnej:
Jeśli N jest liczbą złożoną, to ma dzielnik k spełniający warunek k*k< N.
Przede wszystkim zastanówmy się, jak możemy wykorzystać to twierdzenie?
Wynika z niego, że aby się przekonać, czy dana liczba jest złożona,
wystarczy sprawdzać tylko takie liczby k, dla których k*k<N. Jeżeli zatem
wśród tych liczb nie będzie dzielnika, mamy pewność, że dana liczba nie
ma ich wcale (poza oczywiście 1 i N). To zaś oznacza, że jest liczbą pierwszą.
Możemy zatem jeszcze raz ulepszyć algorytm sprawdzania, czy dana
liczba jest pierwsza.
Algorytm 28
(znacznie ulepszony algorytm sprawdzania, czy liczba jest pierwsza)
Dane wejściowe: N liczba naturalna różna od 1.
(1) Niech k=2.
(2) Dopóki (/V nie dzieli się przez k) i (k*k</V), wykonuj:
(2.1) zwiększ k o 1 (czyli za k podstaw k+1).
(3) Jeżeli k*k>N, to N jest liczbą pierwszą, w przeciwnym razie N jest liczbą
złożoną.
(4) Koniec.
Jak wygląda teraz porównanie tej wersji algorytmu z poprzednimi? Weźmy
dla przykładu liczbę pierwszą AM0007 (dziesięć tysięcy siedem). Wtedy
w algorytmie 26 instrukcja w punkcie (2) może wykonywać się około 10000
razy, a w algorytmie 28 tylko 100 razy (pierwiastek kwadratowy z 10000).
A zatem widzisz, że teraz jest to istotne ulepszenie.
Kolejny przykład będzie raczej ciekawostką. Nie dotyczy on jakiegoś problemu
o praktycznym znaczeniu, niemniej jednak daje możliwość łatwego
„eksperymentowania" z algorytmem i rodzi kilka interesujących pytań. Algorytm
ten generuje pewien ciąg liczb. W szczególności ciekawy jest problem,
czy algorytm w końcu zatrzyma się? Jest on podobny do algorytmu 15
z podrozdz. 8.4 {Skończoność - ważna własność algorytmów).
Algorytm 29
Dane wejściowe: liczba naturalna n.
Dane wyjściowe: pewien ciąg liczb naturalnych.
(1) Weź daną liczbę n i wypisz ją.
(2) Dopóki n>1 wykonuj poniższe instrukcje (2.1)-(2.2):
(2.1) Jeżeli n jest liczbą parzystą, to za nową wartość n przyjmij n/2,
w przeciwnym razie (czyli gdy n jest nieparzysta) za nową wartość
n przyjmij n+1.
(2.2) Wypisz aktualną wartość n.
(3) Koniec.
Jak widzisz, algorytm ten wypisuje pewne liczby naturalne, począwszy od
zadanej na początku liczby. Reguła tworzenia tych kolejnych liczb jest
zapisana w punkcie (2.1). W zależności od tego, czy aktualna wartość jest
parzysta czy nie, obliczamy n/2 lub n+1. Przykładowo, gdy początkowa
wartość n=11, to algorytm ten wygeneruje następującą listę wartości: 11,
12, 6, 3, 4, 2, 1, Stop. Instrukcja w punkcie (2) wykonuje się tak długo, jak
długo wartość n jest różna od 1.
Ćwiczenie 8.18
Postaraj się wykonać algorytm 29 dla kilku przykładowych danych
początkowych: n-23, n=17, n-32.
Czy zawsze na koniec otrzymasz 1?
UWAGA Można łatwo wykazać, że algorytm 29 jest skończony (przeciwnie
niż w przypadku algorytmu 15).
PODSUMOWANIE:
Matematyka dostarcza wielu ciekawych algorytmów. Jednym
z najstarszych jest algorytm Euklidesa.
Poprzez stopniowe udoskonalanie algorytmu osiągamy
zamierzony cel, wykonując coraz mniej obliczeń.
8.7 Czy wszystko jest algorytmiczne?
O algorytmach porozmawiamy z prof. Marianem Mrozkiem z Instytutu Informatyki
Uniwersytetu Jagiellońskiego. Wielokrotnie w swych dowodach matematycznych
korzystał on z pomocy komputerów. W ten sposób udowodnił
ważne twierdzenie (o istnieniu chaosu w równaniach Lorenza), które przez
lata było wyzwaniem dla matematyków. Nic dziwnego, że słynna Encyclopaedia
Britannica uznała ten rezultat za jedno z czterech najważniejszych
osiągnięć matematyki na świecie w 1995 r.
Zacznijmy od... kucharzy. Jeśli przepisy kulinarne mają charakter algorytmiczny,
dlaczego obok świetnych kucharzy jest cała rzesza partaczy kulinarnych?
Podejrzewam, że pierwsi z nich postępują zgodnie z procedurą, a drudzy mniej
dokładnie. Może się też zdarzyć, iż algorytm jest zapisany mało precyzyjnie, co
sprzyja odstępstwom od prawidłowych czynności. W ogóle algorytmika uczy nas
precyzji - np. zamiast wyrażenia szczypta soli powinno się podać konkretną liczbę
miligramów.
Algorytmy są wokół nas. Ale czy wszystko, czy procesy dokonujące się w przyrodzie
albo zachowania człowieka są algorytmiczne?
Zdecydowanie nie! Uczeń w drodze do szkoły często zachowuje się w sposób
niealgorytmiczny - zboczy z utartego szlaku, aby zobaczyć niezwykłego motyla,
przyśpieszy kroku, aby dogonić atrakcyjną koleżankę itd. Każdy z nas podejmuje
niekiedy ważne decyzje, np. wybiera kierunek studiów, decyduje się założyć
rodzinę. Nie wierzę, aby tego typu decyzje miały charakter algorytmiczny.
Czy algorytmy są ważne w matematyce?
Za każdym razem, kiedy uda się znaleźć algorytm, upraszcza się pewną cząstkę
rzeczywistości matematycznej. Algorytmy mogą być czasami banalne, czasami
bardzo trudne do wymyślenia. Zawsze jednak od momentu stworzenia algorytmu
ta cząstka rzeczywistości matematycznej przestaje być interesująca dla matematyków.
Paradoksalnie więc matematycy starają się coraz rozleglejsze obszary
matematyki uczynić mechanicznymi, wręcz nudnymi!
Ale całej matematyki nie da się chyba zalgorytmizować?
Na szczęście nie - świat matematyki jest nazbyt bogaty! Poza tym zbudowane
przez nas algorytmy prowokują nowe problemy, nowe zagadnienia logiczno-matematyczne.
Pytamy się na przykład, czy dany algorytm jest poprawny - czy w rezultacie
jego działania otrzymamy to, co zamierzaliśmy.
Przeprowadźcie dyskusję na poniższe tematy:
1. Jakie zachowania codziennego życia wydają się nie mieć algorytmicznego
charakteru?
2. Zachowania algorytmiczne a inwencja twórcza.
Download