129 42 6MB
Polish Pages 272 [270] Year 2008
Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Redakcja: Krzysztof Zemanek Projekt okładki: Maciej Pasek Fotografia na okładce została wykorzystana za zgodą iStockPhoto Inc. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie?calgor_p Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Dodatkowe materiały do książki można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/calgor.zip ISBN: 978-83-246-3255-8 Nr katalogowy: 6108 Copyright © Helion 2011 Printed in Poland.
Spis treści Wstęp Rozdział 1. Niezbędne informacje o algorytmach Czym jest algorytm? Ocena jakości algorytmu Planowanie pracy Sposoby opisu algorytmów Klasyfikacja algorytmów Podsumowanie
Rozdział 2. Algorytmy sekwencyjne. Kodowanie algorytmów Algorytm sekwencyjny Obliczanie wartości funkcji Kodowanie algorytmów Liczymy koszt rozmowy telefonicznej Uwagi końcowe Ćwiczenia do samodzielnego wykonania
Rozdział 3. Algorytmy z rozgałęzieniami. Sterowanie przepływem w algorytmie Algorytm z rozgałęzieniami Miejsce zerowe funkcji, rozwiązanie równania liniowego Obliczanie pierwiastków równania kwadratowego Uwagi końcowe Ćwiczenia do samodzielnego wykonania
5 7 7 9 9 11 22 24
27 27 28 29 45 55 57
59 59 61 68 86 88
4
Algorytmy • Ćwiczenia
Rozdział 4. Algorytmy iteracyjne. Przetwarzanie danych w pętli programowej Algorytm iteracyjny Rysowanie gwiazdek Co umożliwia iteracja? Uwagi końcowe Ćwiczenia do samodzielnego wykonania
Rozdział 5. Algorytmy rekurencyjne Algorytm rekurencyjny Funkcja silnia Obliczanie potęgi liczby rzeczywistej Uwagi końcowe Ćwiczenia do samodzielnego wykonania
Rozdział 6. Schemat Hornera. Obliczanie wartości wielomianu Schemat Hornera Uwagi końcowe Ćwiczenia do samodzielnego wykonania
Rozdział 7. Pozycyjne systemy liczbowe System liczbowy Obliczanie wartości liczby zapisanej w dowolnym systemie pozycyjnym Przedstawianie liczb w dowolnym pozycyjnym systemie liczbowym Uwagi końcowe Ćwiczenia do samodzielnego wykonania
Rozdział 8. Algorytmy sortowania danych Sortowanie zbioru danych Metody sortowania zbioru danych Uwagi końcowe Ćwiczenia do samodzielnego wykonania
91 91 94 102 110 111
115 115 116 127 134 137
139 139 165 167
169 169 174 194 214 216
217 217 220 265 266
Wstęp Wiek XXI to era informatyki. Komputery są wykorzystywane we wszystkich dziedzinach naszego życia, dlatego marzeniem wielu jest umiejętność ich programowania. Niestety początki bywają bardzo trudne. Brak bowiem wzorców postępowania pokazujących szczegółowo i w sposób zrozumiały wszystkie etapy prowadzące do rozwiązania problemu. Literatura omawiająca algorytmy przedstawia zagadnienia, zakładając, że odbiorca ma już dużą wiedzę matematyczną i informatyczną. Autorzy, bazując na takim założeniu, omawiają problem, podając teorię i wskazówki ogólne, a szczegóły pozostawiają do rozwiązania Czytelnikowi. Bywa, że przedstawiany jest algorytm, ale bez wnikliwej analizy i opisu albo tylko w jednej formie (np. schemat, program). Osoba dopiero rozpoczynająca przygodę z informatyką niewiele z tego rozumie i szybko się zniechęca. Niniejsza książka jest swoistym elementarzem dla przyszłego programisty. Uczy planowania pracy i pokazuje szczegółowo wszystkie fazy, które prowadzą do rozwiązania: analizę problemu i jego zdefiniowanie, projektowanie rozwiązania w formie algorytmu, kodowanie algorytmu, testowanie i usuwanie błędów oraz opracowanie dokumentacji. Każdy rozdział nie tylko przedstawia niezbędną dawkę teorii, ale również zawiera kilka rozbudowanych ćwiczeń wraz z kompletnym rozwiązaniem (analiza, wzory, wykresy, schemat blokowy, kod programu). To wszystko jest poparte obszernymi komentarzami, wskazówkami i wyjaśnieniami.
6
Algorytmy • Ćwiczenia
Każde ćwiczenie w książce jest zrealizowane w postaci algorytmu graficznego lub słownego oraz w formie programu w językach Turbo Pascal i Visual Basic for Application. Oba języki są powszechnie dostępne. Kolejne ćwiczenia zostały tak dobrane, by Czytelnik nieznający Turbo Pascala i VBA mógł zapoznać się z konstrukcją języków oraz nauczyć się podstaw programowania, przechodząc od zagadnień łatwiejszych do bardziej złożonych. Czytelnik nie musi znać tych języków, by rozpocząć korzystanie z książki. Zastosowanie dwóch różniących się języków programowania sprawia, że odbiorca dostrzeże podobieństwa i różnice oraz nabierze przekonania, że programować można w różnych językach. Z książki może korzystać każdy, kto zna zasady obsługi komputera. Nie są potrzebne jakieś szczególne wiadomości, a tylko umiejętności korzystania ze środowiska Windows i znajomość podstaw obsługi arkusza kalkulacyjnego Excel 2003. Wszystkie przykłady omówione w książce są zamieszczone na serwerze ftp wydawnictwa Helion pod adresem: ftp://ftp.helion.pl/przyklady/calgor.zip Przykłady w Turbo Pascalu znajdują się w katalogu TP, a przykłady w Excelu — w katalogu EX.
1 Niezbędne informacje o algorytmach Czym jest algorytm? Algorytm to przepis, który podaje wszelkie czynności, jakie należy wykonać, by osiągnąć rozwiązanie określonego problemu w skończonej liczbie kroków. Niezależnie od sposobu dojścia do rozwiązania, opis można uznać za algorytm tylko wtedy, gdy przy dobrze określonych danych wejściowych jest jednoznaczny, skończony i posiada precyzyjnie określony punkt końcowy. Słowo „algorytm” wywodzi się od nazwiska arabskiego matematyka Muhammada ibn-Musy al-Chuwarizmiego (w zlatynizowanej formie nazywanego Algorismusem), żyjącego w latach 780 – 850 n.e. na dworze królewskim w Bagdadzie. W jego pracach po raz pierwszy pojawiły się metody obliczeniowe w matematyce, a sam autor był propagatorem systemu dziesiętnego i stosowania cyfry zero. W matematyce i naukach wykorzystujących do obliczeń komputer algorytm oznacza procedurę, która pozwala rozwiązać bieżący problem w sposób optymalny, a więc najlepszy, najkorzystniejszy według przyjętego kryterium (optimum z łac. to zespół najbardziej sprzyjających
8
Algorytmy • Ćwiczenia
warunków, najkorzystniejsze stadium czegoś). Na przykład w przemyśle niezmiernie ważne jest obniżenie kosztów wytworzenia produktu i zużycia energii, w medycynie skuteczność leczenia i rehabilitacji, w szkolnictwie jakość kształcenia. Ć W I C Z E N I E
1.1
Optymalna droga przez labirynt
Wskaż najkrótszą drogę dojścia do skarbu w labiryntach przedstawionych na rysunku 1.1.
Rysunek 1.1. Labirynt — skarb oznaczony jest czarnym punktem
Rozwiązanie Na rysunku 1.1 a tylko jedna droga prowadząca do skarbu jest optymalna. Tor poruszania się jest łatwy do wskazania, ale tylko dlatego, że siatka ścian labiryntu widziana jest w rzucie z góry. O wiele trudniej byłoby rozwiązać ten problem, gdyby należało pokonać labirynt na własnych nogach. Rysunek 1.1 b przedstawia problem bardziej złożony. Analiza możliwych dróg przejścia pokazuje, że istnieją dwie równoważne ze względu na długość drogi, które różnią się jedynie torem.
Rozdział 1. • Niezbędne informacje o algorytmach
9
Ocena jakości algorytmu Obliczenia zajmują czas. Dlatego niezależnie od dziedziny życia, w której występuje analizowany problem, podstawowym kryterium oceny jakości algorytmu jest ilość czasu potrzebna do wykonania obliczeń. Im mniej czasu pochłaniają obliczenia dające wynik, tym algorytm jest szybszy. Czas w obliczeniach komputerowych jest kosztem, jaki trzeba ponieść, aby otrzymać wynik. Ilość czasu potrzebna do wykonania zadania zależy nie tylko od przyjętego sposobu postępowania, ale również od ilości danych wejściowych, które należy przetworzyć. Algorytmy można jednoznacznie porównywać tylko wtedy, gdy operują na tej samej liczbie danych wsadowych. Jakość algorytmu najlepiej jest określać poprzez podanie czasu potrzebnego do uzyskania wyniku. Zwiększanie liczby danych zazwyczaj powoduje wydłużenie czasu ich przetwarzania.
Planowanie pracy Urządzenia tworzące zestaw komputerowy (ang. hardware) są bezużyteczne bez oprogramowania (ang. software) sterującego ich działaniem, gdyż komputer wykona tylko te czynności, które zostaną mu podane w formie odpowiedniej instrukcji. Algorytm po przetłumaczeniu na język zrozumiały dla komputera staje się programem. Osoba dokonująca translacji to programista. Program komputerowy powinien działać bezbłędnie i bez zbędnych opóźnień. Łatwiej to uzyskać, gdy programista zaplanuje swoją pracę. Planowanie pozwala efektywnie wykorzystać czas przeznaczony na pracę nad programem. Pomaga stworzyć wolny od błędów algorytm oraz odpowiadający mu program dający oczekiwany wynik.
Planowanie pracy obejmuje: T analizę problemu i jego zdefiniowanie, T projektowanie rozwiązania problemu w formie algorytmu, T kodowanie algorytmu, T testowanie i usuwanie błędów, T opracowanie dokumentacji.
10
Algorytmy • Ćwiczenia
Analiza problemu i jego zdefiniowanie Poświęć czas, by dogłębnie zapoznać się z problemem, który należy rozwiązać. Przeanalizuj dostępne dane lub informacje wejściowe. Odszukaj wszelkie relacje między nimi, jak również między danymi wsadowymi i wyjściowymi. Upewnij się, że rozumiesz, co program ma robić i jakiego wyniku należy oczekiwać.
Projektowanie rozwiązania w formie algorytmu Wyszukaj logiczny ciąg kroków prowadzących do rozwiązania i zapisz je w formie algorytmu. Każdy szczegół ma znaczenie i musi być uwzględniony w opisie. Rozbicie problemu na elementarne zadania (kroki algorytmu) ułatwia znalezienie rozwiązania.
Kodowanie algorytmu Kodowanie to termin techniczny oznaczający przetłumaczenie algorytmu na wybrany język programowania.
Testowanie i usuwanie błędów Testowanie to proces wyszukiwania błędów w programie. Usuwanie błędów to odpluskwianie programu (ang. debugging). Błędy logiczne i związane z nieprawidłowo wpisanymi operacjami wyszukasz poprzez odpowiedni dobór danych wejściowych, takich, które generują znany wynik. Błędy związane z wykonaniem programu są zazwyczaj sygnalizowane przez komputer. Poprawa takich błędów polega na zastosowaniu prawidłowej składni lub pisowni oraz podaniu brakujących danych.
Opracowanie dokumentacji Dokumentacją są wszelkie materiały, które umożliwią zrozumienie oraz wykorzystanie algorytmu i programu przez inną osobę. Również sam autor ma ułatwione zadanie, gdy po kilku miesiącach zechce zapoznać się z programem i dokonać w nim zmian. Problem jasny i zrozumiały dziś może okazać się skomplikowany, gdy wrócisz do niego po kilku miesiącach. Pisząc program, korzystaj często z komentarzy, czyli linii w programie, które nie są wykonywane, a służą do opisu, co i w jaki sposób wykonują fragmenty aplikacji, jaki charakter mają dane itp. Dokumentacja
Rozdział 1. • Niezbędne informacje o algorytmach
11
to wzory, szkice rozwiązań, algorytmy graficzne, a w programach komercyjnych także opis sposobu korzystania z programu, na przykład instrukcja użytkowania (ang. manual).
Sposoby opisu algorytmów Każdy algorytm, którym chcemy się podzielić, musi być podany w formie zrozumiałej dla innych ludzi. Najczęściej stosuje się: T opis słowny algorytmu, T opis algorytmu za pomocą listy kroków, T opis w postaci schematu blokowego, T opis za pomocą drzewa, T opis za pomocą pseudokodu, T przedstawienie algorytmu w wybranym języku programowania.
Opis słowny Opis słowny polega na podaniu czynności, które należy podjąć, by uzyskać oczekiwany efekt. Opis musi być zrozumiały dla odbiorcy. Często przypomina dyskusję nad problemem lub metodę analizy zwaną burzą mózgów, a nie matematyczny opis metody rozwiązania. W trakcie rozważań notuje się wszelkie informacje, pomysły, definicje, wzory, wartości stałych itp., które mogą być przydatne w rozwiązaniu postawionego zadania. Opisy algorytmiczne spotykamy codziennie. Są to na przykład: przepis kulinarny, instrukcja obsługi kosiarki do trawy, metoda rozwiązania zadania, planowanie wakacyjnego wyjazdu, programowanie magnetowidu. Ć W I C Z E N I E
1.2
Algorytm wjazdu na skrzyżowanie
Podaj opis słowny algorytmu, jakim powinien posłużyć się kierowca sprawnego samochodu oczekujący na wjazd na skrzyżowanie, na którym ruch reguluje sygnalizacja świetlna.
12
Algorytmy • Ćwiczenia
Rozwiązanie Temat ćwiczenia zakłada, że pojazd stoi przed światłami. Sposób dalszego zachowania kierowcy zależy od koloru świecącej lampy w sygnalizatorze. Kolory, położenie i znaczenie świateł są znormalizowane. Najwyżej znajduje się lampa czerwona — polecenie stój, w środku żółta — przygotuj się, a najniżej zielona — jedź. Ustawienie kolorów jest zawsze takie samo. Dzięki temu osoby nierozróżniające barw mogą również określić rodzaj polecenia przekazywanego przez sygnalizator. Kierowca powinien obserwować zmieniające się światła. Oczekiwane zachowanie kierowcy: T świeci się lampa czerwona — bezwzględnie stój; T gaśnie lampa czerwona, a zapala się żółta — przygotuj się do ruszania, ale nadal stój; T gaśnie lampa żółta, a zapala się zielona — ruszaj. Z powyższego wynika, że kierowca powinien obserwować sygnalizator świetlny i wjechać na skrzyżowanie wtedy, gdy świecąca się lampa ma kolor zielony.
Lista kroków Listę kroków stanowi uporządkowany opis wszelkich czynności, jakie mają być wykonywane w ramach algorytmu. Krok to pojedyncza czynność realizowana w algorytmie. Ponieważ kolejność wykonywania ma znaczenie, poszczególne kroki w algorytmie są numerowane. Operacje w algorytmie wykonywane są zgodnie z rosnącą numeracją kroków, z wyjątkiem operacji wymuszających przejście do kroku o wyraźnie wskazanym numerze — mówimy wówczas o operacji warunkowego lub bezwarunkowego skoku. Ć W I C Z E N I E
1.3
Algorytm włączania komputera
Ułóż i zapisz w postaci listy kroków algorytm włączania komputera.
Rozdział 1. • Niezbędne informacje o algorytmach
13
Rozwiązanie Krok 1. Sprawdź, czy włączona jest listwa zasilająca. Jeżeli tak, przejdź do 3, jeżeli nie, przejdź do 2. Krok 2. Włącz listwę zasilającą. Krok 3. Włącz przycisk zasilania komputera. Krok 4. Wywołaj procedurę Czekaj — przejdź do 7. Krok 5. Zacznij korzystanie z programów. Krok 6. Przejdź do 9. Procedura Czekaj: Krok 7. Czy system został uruchomiony? Jeżeli tak, wróć do 5, jeżeli nie, przejdź do 8. Krok 8. Przejdź do 7. Krok 9. Koniec algorytmu Dokonajmy krótkiej analizy podanego algorytmu. Występują w nim dwa bardzo charakterystyczne dla algorytmiki działania: polecenie warunkowe oraz wywołanie procedury dodatkowej. Polecenia warunkowe są dwa. Pierwsze występuje w kroku 1, a drugie w kroku 7. Obydwa sprawdzają, czy został spełniony wymagany warunek, ale różnią się konstrukcją: T W kroku pierwszym po sprawdzeniu stanu wyłącznika w listwie zasilającej następuje albo przejście do kroku 2, albo ominięcie kroku 2 i przejście do kroku 3. Niezależnie od stanu wyłącznika listwy zasilającej, wykonywane są kolejne punkty i nie ma powrotu do kroku 1. T W kroku 7 odpowiedź twierdząca powoduje powrót procedury do kroku 5, a brak spełnienia warunku wywoła przejście do kroku 8. Znajduje się tu polecenie bezwarunkowego powrotu do kroku 7, gdzie znów sprawdzany jest warunek uruchomienia. Dopóki system nie zostanie uruchomiony, bieg algorytmu ogranicza się do krążenia pomiędzy punktami 7 i 8. Taki fragment algorytmu nazywamy pętlą, a polecenie przejścia do wskazanego miejsca w algorytmie przyjęło się nazywać skokiem.
14
Algorytmy • Ćwiczenia
Wywołanie procedury dodatkowej jest częstym działaniem programistycznym. Taka procedura najczęściej znajduje się poza głównym torem algorytmu (programu) i można do niej się dostać tylko poprzez polecenie skoku. W powyższym algorytmie występuje skok bezwarunkowy, gdyż następuje zawsze, gdy tylko dojdzie do wykonania kroku 4. Skok bezwarunkowy występuje też w krokach 6 i 8. Natomiast skok z kroku 1, to skok warunkowy — miejsce docelowe skoku zależy od tego, który warunek został spełniony. W tym ćwiczeniu zostały wprowadzone pojęcia, które zostaną jeszcze raz dokładnie wyjaśnione w kolejnych rozdziałach książki.
Schemat blokowy Schemat blokowy to plan algorytmu przedstawiony w formie graficznej struktury elementów zwanych blokami. Bloki, zwane również klatkami lub skrzynkami, są uporządkowane zależnie od ich zawartości i oczekiwań programisty. Każdy blok zawiera informację o operacji, która ma być w nim wykonana. Pomiędzy blokami znajdują się połączenia, inaczej linie przepływu (linia ciągła zakończona strzałką), określające kolejność wykonywania klatek schematu. Linia ze strzałką w kierunku bloku oznacza informację wejściową, linia ze strzałką skierowaną na zewnątrz bloku oznacza informację wyjściową (wynik). Przykładowy blok przedstawia rysunek 1.2.
Rysunek 1.2. Przykładowa klatka schematu blokowego — informacją wejściową jest a, informacją wyjściową b. W bloku realizowana jest instrukcja arytmetyczna b = a + 1 (np. jeżeli a = 2, to b = 3)
Rozdział 1. • Niezbędne informacje o algorytmach
15
Graficzne przedstawianie algorytmu ułatwia zaobserwowanie i zrozumienie jego głównych własności. Aby ułatwić odnajdywanie charakterystycznych typów operacji realizowanych w algorytmie, przyjęło się stosować różne kształty bloków. Najczęściej używane przedstawione są w tabeli 1.2. Tabela 1.2. Podstawowe symbole stosowane w schematach blokowych Symbol graficzny
Opis symbolu Linia przepływu (połączenie) — przedstawiana jest w postaci linii zakończonej strzałką, która wskazuje kierunek przemieszczania się po schemacie. Blok początku (startowy) — wskazuje miejsce rozpoczęcia algorytmu. Posiada jedno wyjście, a nie posiada wejścia. Na schemacie występuje tylko jedna klatka startowa. Schematy blokowe podprogramów, oprócz komendy START, mogą zawierać nazwę realizowanej funkcji lub procedury. Blok końcowy — wskazuje miejsce zakończenia algorytmu. Posiada przynajmniej jedno wejście, a nie posiada wyjścia. Aby ułatwić rysowanie, przyjęło się, że na schemacie może znajdować się kilka takich skrzynek. Schematy blokowe podprogramów, oprócz komendy STOP, mogą zawierać nazwę kończonej funkcji lub procedury. Blok wykonawczy (instrukcyjny, poleceń) — zawiera polecenie, które należy wykonać, wykorzystując dostarczone dane wejściowe. Uzyskany wynik jest wyprowadzany na zewnątrz klatki. Instrukcją (operacją) może być podstawienie, operacja arytmetyczna, wprowadzenie danej.
16
Algorytmy • Ćwiczenia
Tabela 1.2. Podstawowe symbole stosowane w schematach blokowych (ciąg dalszy) Symbol graficzny
Opis symbolu Blok warunkowy (porównujący, komparator) — sprawdza umieszczony w nim warunek i dokonuje wyboru tylko jednej drogi wyjściowej. Z bloku wychodzą dwa połączenia. Najczęściej oznaczane są: T — tak, co oznacza, że warunek został spełniony; N — nie, warunek nie został spełniony. Blok wejścia-wyjścia — poprzez ten blok wprowadzane są dane i wyprowadzane są wyniki. Blok posiada jedno połączenie wejściowe i jedno wyjściowe. Często zamiast tej skrzynki stosuje się klatkę instrukcyjną.
Bloki łącznikowe (stronicowe i międzystronicowe) — stosowane są wtedy, gdy zachodzi konieczność kontynuowania rysunku w innym miejscu kartki lub na kolejnej stronie. Urwany fragment schematu kończy się blokiem takim jak u góry, po lewej na rysunku obok. Miejsce kontynuacji przerwanego schematu oznacza się za pomocą bloku o kształcie takim jak u dołu, po prawej. Numer we wnętrzu koła oznacza, które miejsca we fragmentach należy łączyć. Blok komentarza — umieszcza się w nim opisy niezbędne do zrozumienia kolejnych poleceń w algorytmie. Ten typ skrzynek umieszcza się z boku, poza głównym torem algorytmu (rysunek 1.3). Treść komentarza (nawet nieprawidłowego) nie ma żadnego wpływu na wynik.
Rozdział 1. • Niezbędne informacje o algorytmach
17
Ć W I C Z E N I E
1.4
Liczba operacji w algorytmie
Podaj liczbę operacji logicznych i arytmetycznych realizowanych przez algorytm przedstawiony na schemacie z rysunku 1.3.
Rysunek 1.3. Przykładowy schemat blokowy
18
Algorytmy • Ćwiczenia
Rozwiązanie Są dwie operacje logiczne. Każda z nich realizowana jest w odrębnym bloku warunkowym. Operacji arytmetycznych jest trzy. Pierwsze dwie n = n + 100 oraz w = n * n są realizowane w jednym bloku wykonawczym. Trzecia instrukcja arytmetyczna w = w + n realizowana jest w osobnym bloku. Zapamiętaj! Dopuszcza się zapis kilku instrukcji w jednym bloku, o ile nie komplikuje to schematu blokowego.
Znaczenie symboli (:=) oraz (=) jest dokładnie omówione w kolejnym rozdziale. Ć W I C Z E N I E
1.5
Algorytm obliczający koszt rozmowy telefonicznej
Przedstaw w formie schematu blokowego algorytm, który pozwala obliczać koszt rozmowy z telefonu komórkowego. Załóż, że: T znany jest czas trwania rozmowy w sekundach, T operator pobiera 1 zł za każdą rozpoczętą minutę rozmowy.
Rozwiązanie W pierwszym bloku algorytmu (rysunek 1.4) następuje pobranie danej wsadowej, którą jest czas trwania rozmowy S w sekundach. Ponieważ opłata pobierana jest za każdą rozpoczętą minutę, konieczna jest zamiana czasu rozmowy w sekundach na czas w minutach. Dokonywane jest to w dwóch kolejnych blokach instrukcyjnych. Najpierw liczba sekund dzielona jest przez 60 (liczba sekund w minucie). Wyznaczona w ten sposób wartość musi zostać zaokrąglona w górę. Uzyskany wynik jest liczbą rozpoczętych minut rozmowy K, a zarazem jej kosztem. Jest tak dlatego, że zgodnie z założeniem każda minuta kosztuje 1 zł. Przetestujmy poprawność ułożonego algorytmu: T czas trwania rozmowy S = 144 sekundy; T dzielenie 144 przez 60 daje wartość 2,4;
Rozdział 1. • Niezbędne informacje o algorytmach
19
Rysunek 1.4. Algorytm obliczający koszt rozmowy telefonicznej
T zaokrąglenie liczby 2,4 w górę daje wartość K = 3; T wynik 3 zł.
Sposób przetwarzania danych jest zawsze ten sam. Obrazuje go rysunek 1.5.
Rysunek 1.5. Istota komputerowego przetwarzania danych
Algorytmy przedstawiane w formie schematu są łatwiejsze do analizy niż inne formy ich wizualizacji. Każdy kolejny krok w algorytmie jest doskonale widoczny, a kształt bloku od razu informuje o tym, jaki rodzaj procesu w nim zachodzi. Wadą schematów blokowych jest to, że bardziej złożone zagadnienia wymagają często wielu stron, by w pełni opisać analizowany problem.
20
Algorytmy • Ćwiczenia
Drzewo Drzewem nazywamy graficzny algorytm przedstawiony w postaci linii lub wektorów symbolizujących drogę, wzdłuż której wykonywane są operacje arytmetyczno-logiczne. Drogi mają wspólny początek, lecz inne wierzchołki końcowe. Miejsce rozpoczęcia algorytmu nazywane jest często korzeniem, a miejsce z zapisanym poleceniem nosi nazwę wierzchołka pośredniego. Łatwiej zrozumieć pojęcia „korzeń” i „wierzchołek”, jeżeli spojrzy się na schemat z rysunku 1.6 po obróceniu go do góry nogami. Rysunek 1.6. Przykład drzewa opisującego przebieg rozgrywek piłkarskich
Pseudokod Pseudokod (pseudojęzyk) to uproszczona wersja typowego języka programowania. Symbole geometryczne używane w schemacie blokowym zostały zastąpione zdaniami w ojczystym języku, które opisują kodowany problem. Często wtrącane są zwroty pochodzące z wybranego języka programowania. Pseudokod pozwala skupić się na istocie rozwiązywanego zagadnienia, a nie na zawiłościach języka programowania. Zapis w pseudojęzyku może być łatwo przetłumaczony na wybrany język programowania. Pseudokod jest zwarty i zajmuje mniej miejsca niż schemat blokowy.
Rozdział 1. • Niezbędne informacje o algorytmach
21
Ć W I C Z E N I E
1.6
Pseudokod opisujący obliczanie kosztu rozmowy
Przedstaw w postaci pseudokodu algorytm określający koszt rozmowy telefonicznej, opisany w ćwiczeniu 1.5.
Rozwiązanie Wczytaj czas trwania rozmowy S w sekundach.
(dane wejściowe)
Podziel K = S/60.
(przetwarzanie danych)
Zaokrąglij otrzymany wynik K do najbliższej większej liczby naturalnej.
(przetwarzanie danych)
Wyświetl koszt rozmowy K.
(wynik końcowy)
Realizacja programowa algorytmu Ten sposób opisu polega na zakodowaniu algorytmu w wybranym języku programowania. Powstaje program, który jest zrozumiały dla komputera. Czytając niektóre publikacje omawiające problematykę algorytmów i struktur danych, można odnieść wrażenie, że istnieją uprzywilejowane języki programowania i tylko one mogą sprostać wyzwaniu, jakim jest programowe odwzorowanie algorytmu. Również twórcy oprogramowania w każdej sytuacji starają się wykazać, że to właśnie ich język programowania jest najlepszy. Chcą w ten sposób rozreklamować swój produkt i zapewnić mu zbyt. Poszukiwanie najlepszego, uniwersalnego języka programowania jest z góry skazane na porażkę, a spory i dywagacje na ten temat to przysłowiowe przelewanie z pustego w próżne. Zapamiętaj! Nie ma najlepszego języka programowania.
22
Algorytmy • Ćwiczenia
W tej książce algorytmy będą implementowane za pomocą dwóch języków wysokiego poziomu. Pierwszym z nich jest Turbo Pascal firmy Borland, a drugim wbudowany do arkusza kalkulacyjnego Excel Visual Basic for Application firmy Microsoft.
Klasyfikacja algorytmów Podział ze względu na kolejność wykonywania działań: T algorytmy sekwencyjne (proste, liniowe) — kolejne kroki
algorytmu są zawsze wykonywane w tej samej kolejności, w jakiej zostały zapisane, niezależnie od wartości danych wsadowych i wyników pośrednich (cząstkowych); żaden krok nie może być pominięty ani powtórzony; T algorytmy z rozgałęzieniami — kolejność wykonywania
niektórych kroków algorytmu może ulegać zmianie w zależności od wartości danych wejściowych lub uzyskanych wyników pośrednich; istnieje w nim przynajmniej jeden blok warunkowy; T algorytmy cykliczne (z pętlą) — podczas wykonywania
następuje powtórzenie części operacji opisanych w algorytmie przy zmienionych danych wejściowych; pętla to ta część algorytmu, która jest powtarzana; T algorytmy mieszane (złożone) — są one kombinacją algorytmów
wymienionych w trzech powyższych punktach. Na rysunku 1.7 przedstawione są schematy blokowe ilustrujące powyższy podział.
Podział ze względu na sposób wykonywania operacji: T algorytmy sekwencyjne — operacje opisane w algorytmie
wykonywane są zawsze w kolejności, w jakiej zostały wprowadzone; T algorytmy iteracyjne — niektóre kroki w algorytmie są
powtarzane, aż do spełnienia wymaganego warunku;
Rozdział 1. • Niezbędne informacje o algorytmach
23
Rysunek 1.7. Rodzaje algorytmów: a) algorytm prosty, b) algorytm z rozgałęzieniem, c) algorytm cykliczny, d) algorytm mieszany. Dla uproszczenia pominięto bloki początku i końca algorytmu T algorytmy rekurencyjne (rekursyjne) — rozwiązywanie
problemu polega na utworzeniu formuły przetwarzającej dane i odwoływaniu się do niej samej. Kolejne wywołania formuły trwają, dopóki nie zostanie osiągnięty warunek zakończenia rekurencji.
24
Algorytmy • Ćwiczenia
Podział ze względu na przeznaczenie: T algorytmy numeryczne — wykonują obliczenia arytmetyczne; T algorytmy przeszukujące — badają zbiór w celu wyszukania
wyróżnionego elementu; T algorytmy porządkujące — ustawiają elementy zbioru
w wymaganej kolejności; T algorytmy rekurencyjne — rozwiązują problemy, które da się
rozbić na mniejsze części stanowiące kopię wzorca; T algorytmy szyfrujące — zmieniają dane tak, by ich odczyt nie
był możliwy bez znajomości klucza kodującego; T algorytmy kompresji danych — określają taki sposób zapisu
danych, by zmniejszyć objętość pliku kompresowanego.
Poprawność i efektywność działania algorytmów Każdy algorytm i odpowiadający mu program komputerowy musi być poprawny. Dlatego po ich utworzeniu konieczne jest sprawdzenie prawidłowości działania. Poprawność zaproponowanego algorytmu sprawdza się poprzez matematyczne dowodzenie użytych w algorytmie formuł i wzorów. Można również dokonać analizy uzyskiwanych wyników. Często łatwiejsze jest testowanie programu niż samego algorytmu, ponieważ język programowania jest zawsze opisem bardziej precyzyjnym niż przepis podany w formie algorytmu. Sprawdzanie poprawności programu przeprowadza się poprzez wykorzystanie i wprowadzenie do programu odpowiednio dobranych danych wsadowych, o których wiemy na pewno, jaki generują wynik. Obserwując uzyskiwane wartości pośrednie i końcowe, możemy stwierdzić, czy występują błędy, czy nie.
Podsumowanie W tym rozdziale Czytelnik poznał niezbędne wiadomości na temat algorytmów. Wprowadzone tutaj pojęcia będą używane w kolejnych rozdziałach. Charakter książki — ćwiczenia praktyczne — wymusił opis
Rozdział 1. • Niezbędne informacje o algorytmach
25
zwięzły i bez nadmiernych rozważań teoretycznych. Czytelników zainteresowanych poszerzeniem informacji w tym zakresie odsyłam do specjalistycznej literatury. Szczególnie polecam podręczniki: T Piotr Wróblewski, Algorytmy, struktury danych i techniki programowania, wyd. III, Helion 2003, T Niklaus Wirth, Algorytmy + struktury danych = programy, WNT 1989. Myślimy algorytmicznie codziennie: szukając kluczy, porządkując mieszkanie, planując comiesięczne wydatki, wybierając trasę przejazdu przez zatłoczone miasto itp. Podczas wykonywania powszednich czynności mózg analizuje problem, określa cel, który należy osiągnąć, i na tej podstawie wypracowuje ciąg decyzji wymuszających nasze zachowanie. W tym sensie swoistym algorytmem jest na przykład przepis kulinarny, instrukcja obsługi kosiarki do trawy, sposób wprowadzania nowego numeru do telefonu komórkowego, a także każdy prosty czy skomplikowany program komputerowy. Zaprezentowane w rozdziale przykłady odwoływały się do znanych czynności życia codziennego. Pamiętajmy jednak, że bezkrytyczne stosowanie pojęcia „algorytm” w odniesieniu do czynności, które nie są rozwiązywane przez zaprogramowany komputer, jest swoistym nadużyciem prowadzącym do błędów językowych. Dbajmy o czystość naszego rodzimego, pięknego języka. Niech przepis parzenia nadal pozostanie przepisem, a instrukcja obsługi urządzenia instrukcją. Gwarantuję, że na co dzień lepiej planować, co zrobimy jutro, niż układać algorytm. W dalszej części książki zajmiemy się tylko tymi algorytmami, które prowadzą do komputerowego rozwiązania problemu za pomocą metod matematycznych. Tego typu algorytmy są nazywane algorytmami informatycznymi. Pozwalają one rozwiązywać zagadnienia z różnych dziedzin nauki i techniki za pomocą metod stosowanych w informatyce i przy użyciu komputera.
26
Algorytmy • Ćwiczenia
2 Algorytmy sekwencyjne. Kodowanie algorytmów Algorytm sekwencyjny Algorytm sekwencyjny (prosty, liniowy) to ciąg następujących po sobie kroków, które są zawsze wykonywane w tej samej kolejności, w jakiej zostały zapisane. Sekwencja jest niezależna od wartości danych wsadowych i wyników pośrednich (cząstkowych). W algorytmach liniowych nie występują bloki warunkowe. Żaden krok się nie powtarza ani nie może być pominięty. Algorytmy liniowe stosowane są do wykonywania prostych operacji, na przykład wszelkich obliczeń arytmetycznych, podczas których nie zachodzi konieczność sprawdzania poprawności danych i weryfikacji wyników pośrednich. Z tego powodu zwane są również algorytmami prostymi. Nazwa ta dobrze oddaje stopień trudności realizowanych przez nie zadań. Istotę przetwarzania w algorytmie sekwencyjnym przedstawia rysunek 2.1. W praktyce taki sposób przetwarzania danych występuje w większości działań programistycznych, ponieważ w każdym algorytmie można wyróżnić fragment, w którym dane przetwarzane są sekwencyjnie.
28
Algorytmy • Ćwiczenia
Rysunek 2.1. Algorytm liniowy — kolejność wykonywania bloków jest zawsze taka sama
Obliczanie wartości funkcji Ć W I C Z E N I E
2.1
Obliczanie wartości funkcji. Lista kroków
Ułóż algorytm, który dla podanej wartości argumentu x∈ R, oblicza wartość funkcji:
f ( x) = 2 x 2 − 3 sin x 4 .
Rozwiązanie Dane: Wartość argumentu x∈R oraz wzór funkcji f(x). Oczekiwany wynik: Liczba będąca wartością funkcji. Analiza problemu: Z postaci funkcji f(x) wynika, że dziedziną funkcji jest zbiór liczb rzeczywistych. W tym zbiorze wszelkie operacje ze wzoru są wykonalne. We wzorze funkcji występują następujące działania: potęgowanie (do potęgi drugiej i czwartej), obliczenie wartości funkcji sinus, działania mnożenia i odejmowanie. Operacje arytmetyczne należy zawsze wykonać w kolejności zgodnej z hierarchią działań obowiązującą w matematyce.
Rozdział 2. • Algorytmy sekwencyjne. Kodowanie algorytmów
29
W ćwiczeniu będzie użyty algorytm liniowy, a podnoszenie do potęgi zostanie zrealizowane poprzez mnożenie liczby przez samą siebie: x2 = x * x oraz x4 = x * x * x * x. Łącznie w funkcji występuje osiem działań, w tym aż sześć operacji mnożenia. Warto rozważyć, czy nie uda się zmniejszyć liczby operacji arytmetycznych. Im mniej działań, tym szybszy algorytm i odpowiadający mu program. Skoro x4 = x2 * x2, to można najpierw wykonać podnoszenie do kwadratu (poprzez mnożenie x * x), a uzyskany wynik pośredni ponownie pomnożyć przez siebie. W algorytmie do przechowania wartości wyrażenia x2 zostanie użyta pomocnicza zmienna y. Dzięki tej modyfikacji łączna liczba operacji, które należy wykonać podczas obliczania wartości f(x) zmniejszy się z ośmiu do sześciu. Zmaleje aż o 25%! Zapamiętaj! Postępowanie zmniejszające liczbę operacji, a tym samym czas potrzebny do wykonania programu, nosi nazwę optymalizacji.
Algorytm w postaci sekwencji kroków Krok 1. Czytaj wartość x. Krok 2. Oblicz wartość pomocniczej zmiennej y = x * x. Krok 3. Oblicz wartość funkcji f(x) = 2 * y – 3 * sin(y * y). Krok 4. Wyświetl wartość f(x). Krok 5. Zakończ program.
Kodowanie algorytmów W dalszej części rozdziału zostanie omówione zagadnienie kodowania algorytmów w języku programowania wysokiego poziomu. Na potrzeby książki zostanie wykorzystany Turbo Pascal oraz arkusz kalkulacyjny Excel wraz z wbudowanymi funkcjami i językiem Visual Basic for Applications (VBA). Programy w książce będą opatrzone obszernym opisem i rysunkami. Dzięki temu osoby mające tylko podstawową znajomość komputera będą mogły poszerzyć wiadomości i dokładnie poznać proces realizacji algorytmów w postaci programu. Niemniej znajomość podstaw
30
Algorytmy • Ćwiczenia
Turbo Pascala i arkusza Excel jest wskazana. Podczas kodowania algorytmów Czytelnik pozna strukturę programu, metody wprowadzania danych i wyprowadzania wyników oraz specyficzne dla danego języka polecenia pozwalające na przetwarzanie danych i sterowanie ich przepływem (instrukcje warunkowe i skoki). Algorytmy występujące w książce będą kodowane na trzy sposoby: T za pomocą języka Turbo Pascal w wersji 5.5, T za pomocą arkusza kalkulacyjnego Excel — w formie tabeli
z użyciem formuł, T za pomocą arkusza kalkulacyjnego Excel — z wykorzystaniem
makropoleceń i kodu VBA.
Pierwsze programy Ć W I C Z E N I E
2.2
Obliczanie wartości funkcji. Program w Turbo Pascalu
Napisz program w Turbo Pascalu realizujący algorytm obliczania wartości funkcji z ćwiczenia 2.1.
Rozwiązanie 1. Uruchom Turbo Pascala. 2. Utwórz nowy plik, wybierając z paska menu polecenia File
(Alt+F), a następnie New (rysunek 2.2). Polecenia z głównego paska menu wywołuje się poprzez wciśnięcie Alt+[wyróżniona litera] w nazwie polecenia. W oknie edycyjnym Turbo Pascala wpisz kod z listingu 2.1. Możesz też oszczędzić czas i wczytać program cw2_2.pas z pliku pobranego z serwera ftp wydawnictwa Helion (ftp://ftp.helion.pl/przyklady/ ´calgor.zip). Aby wczytać program, wciśnij F3 albo z menu File wybierz opcję Load (Open w Turbo Pascalu 7.0) i podaj prawidłową ścieżkę dostępu do pliku (rysunek 2.3).
Rozdział 2. • Algorytmy sekwencyjne. Kodowanie algorytmów
Rysunek 2.2. Wywołanie polecenia New
Rysunek 2.3. Otwieranie istniejącego pliku — na pierwszym planie podana jest ścieżka dostępu do pliku Listing 2.1. Program w Turbo Pascalu realizujący algorytm z ćwiczenia 2.1 program cw2_2; { Program oblicza wartosc funkcji { f(x) = 2x*x - 3*sin(x*x*x*x) { dla podanej wartosci argumentu x. { Deklaracja zmiennych uzywanych w programie: { x - wartosc argumentu, y - pomocnicza zmienna. var x, y : Real;
} } } } }
31
32
Algorytmy • Ćwiczenia begin writeln; writeln(' Obliczanie wartosci funkcji '); writeln('-----------------------------'); writeln ('Podaj wartosc argumentu x i wcisnij ENTER.'); write ('x = '); readln (x); y := x*x; writeln ('y = ',y); writeln ('f(',x,') = ', 2*y - 3*(sin(y*y))); readln; end.
3. Zapisz plik na dysku w wybranym przez siebie katalogu
pod nazwą cw2_2.pas. W tym celu wciśnij F2 albo wybierz polecenie Save z menu File (rysunek 2.4).
Rysunek 2.4. Zapisywanie pliku poleceniem Save 4. Skompiluj program, wciskając Alt+F9 lub wybierając opcję
Compile z menu Compile. Jeżeli w programie wystąpił błąd, to proces kompilacji zostaje zatrzymany i wyświetlany jest komunikat o błędzie (rysunek 2.5). Należy wówczas poprawić błąd i ponownie skompilować program. Prawidłowo przeprowadzona kompilacja kończy się komunikatem podobnym jak na rysunku 2.6. Nagłówek okna informuje, że kompilacja została przeprowadzona do pamięci.
Rozdział 2. • Algorytmy sekwencyjne. Kodowanie algorytmów
33
Rysunek 2.5. Błąd w programie Error 85 — zasymulowany celowo, poprzez usunięcie średnika po słowie Real
Rysunek 2.6. Okno potwierdzające prawidłowość kompilacji w Turbo Pascalu
Aby uzyskać wersję wykonywalną programu *.exe, należy dokonać kompilacji na dysk. W tym celu trzeba wybrać z menu Compile podmenu Destination i zmienić ustawienie z Memory (pamięć) na Disc (dysk). Przedstawia to rysunek 2.7.
34
Algorytmy • Ćwiczenia
Rysunek 2.7. Zmiana ustawień kompilacji w menu Compile 5. Uruchom program, wybierając Run z menu Run (Ctrl+F9)
— rysunek 2.8. Program wykonywany jest w oknie użytkownika (User screen), zwanym również oknem działania programu. Jednak zaraz po zakończeniu działania następuje przełączenie do okna środowiska zintegrowanego, zwanego też oknem edycji.
Rysunek 2.8. Rozwinięte menu Run (wykonaj) 6. Aby obejrzeć wyniki, należy wcisnąć Alt+F5 lub wybrać
menu Run, a następnie User screen. 7. Poeksperymentuj z różnymi wartościami wejściowymi, a następnie zakończ pracę z programem, wybierając File/Quit albo wciskając Alt+X, jak na rysunku 2.9.
Rozdział 2. • Algorytmy sekwencyjne. Kodowanie algorytmów
35
Rysunek 2.9. Prawidłowe kończenie pracy z programem — poprzez podmenu Quit w menu File
Program cw2_2, którego kod podany jest w listingu 2.1, rozpoczyna się deklaracją, czyli określeniem typu zmiennych. Służy do tego słowo kluczowe var (ang. variable — zmienna). W Turbo Pascalu wszystkie zmienne muszą być zadeklarowane. W przeciwnym razie pojawi się komunikat o błędzie, gdyż kompilator tłumaczy kod źródłowy programu na postać wynikową i przed rozpoczęciem tego procesu musi znać typ zmiennej. Dzięki temu potrafi zarezerwować dla niej odpowiednią ilość miejsca w pamięci komputera. Zmienna przed użyciem musi zostać zainicjalizowana, tzn. trzeba nadać jej wartość. Typ zmiennej podaje liczbę zajmowanych przez nią bajtów, zakres dopuszczalnych wartości i dokładność reprezentacji. Dlatego w trakcie wykonywania programu typ nie może ulec zmianie. Próba przypisania zmiennej wyrażenia lub liczby innego typu niż zadeklarowana spowoduje błąd. Więcej na ten temat znajduje się w rozdziale 7. W programie użyte są dwie zmienne oznaczone analogicznie jak w algorytmie: x — wartość argumentu, dla którego należy wyznaczyć wartość funkcji, y — zmienna pomocnicza przechowująca kwadrat argumentu. Pomiędzy słowem kluczowym begin a poleceniem end jest umieszczony blok instrukcji, które mają być wykonane przez program. Pierwszym poleceniem jest instrukcja writeln, wyświetlająca na ekranie tekst żądający podania wartości argumentu x — liczba może być z częścią dziesiętną, która jest oddzielona od części całkowitej kropką. Po wpisaniu i zatwierdzeniu klawiszem Enter następuje przypisanie wartości
36
Algorytmy • Ćwiczenia
wprowadzonej przez użytkownika do zmiennej x. Instrukcja y := x*x; oblicza kwadrat argumentu i przyporządkowuje otrzymaną wartość do zmiennej y, która zostaje wyświetlona za pomocą polecenia writeln ('y = ',y). Tę linię można usunąć z programu, gdyż nie ma żadnego wpływu na wynik. Została dodana tylko po to, by użytkownik programu miał podgląd wartości zmiennej y. Najbardziej złożoną postać w programie ma instrukcja writeln ('f(',x, ') = ', 2*y - 3*(sin(y*y))). Następuje w niej obliczenie wartości funkcji dla podanego argumentu i wyświetlenie otrzymanego wyniku w odpowiednio sformatowanej formie. Efekt wykonania tej instrukcji i całego programu widać na rysunku 2.10.
Rysunek 2.10. Efekt wykonania programu cw2_2 dla argumentu x = 5 widoczny w oknie działania Turbo Pascala
Wynik (pośredni i końcowy) wyświetlany jest w notacji naukowej, gdzie liczba przedstawiana jest jako iloczyn liczby z zakresu 0. Są dwa różne pierwiastki obliczane według wzorów x0 =
x1 =
−b− Δ −b+ Δ . oraz x2 = 2a 2a
Zauważ, że w mianowniku ułamka występuje współczynnik a równania kwadratowego. Zatem założenie a ≠ 0 jest ze wszech miar słuszne, gdyż pozwala uniknąć dzielenia przez zero.
70
Algorytmy • Ćwiczenia
Mamy już wszystkie informacje potrzebne do zrealizowania algorytmu. Najpierw musi nastąpić pobranie danych i sprawdzenie wartości współczynnika a. Następnie obliczamy deltę i wykonujemy rozgałęzienie na trzy niezależne tory. W każdym torze wystąpi sekwencja obliczeń i prezentacji wyniku. Tym razem schemat nie posiada bloków z komentarzami. Mimo to wyróżnienie w jego strukturze segmentu wprowadzania danych, segmentu sterowania przepływem oraz segmentu przetwarzania danych i ich wyprowadzania (wyświetlania) nie powinno sprawić kłopotu. Fragment wprowadzania danych posiada blok warunkowy sprawdzający poprawność wczytywanego współczynnika a. W algorytmie został zastosowany trzywyjściowy blok warunkowy, w którym dokonuje się rozgałęzienie. Blok z trzema wyjściami pozwala uprościć strukturę algorytmu. Ć W I C Z E N I E
3.5
Rozwiązania równania kwadratowego. Program w Turbo Pascalu
Wykorzystując schemat blokowy z rysunku 3.9, napisz program w Turbo Pascalu obliczający, z dokładnością do czterech cyfr dziesiętnych, pierwiastki równania kwadratowego ax2 + bx + c = 0, gdzie x∈R, a∈R\{0}, b∈R, c∈R.
Rozwiązanie 1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając z paska
menu polecenia File/New. 2. W oknie edycyjnym wpisz kod z listingu 3.2 albo wczytaj
program z pliku cw3_4.pas znajdującego się w katalogu TP/Rozdz_03. Efekt tych działań przedstawia rysunek 3.10. Listing 3.2. Kod programu do rozwiązywania równania kwadratowego program cw3_4; { Program oblicza pierwiastki rownania kwadratowego { w postaci ax*x + bx + c = 0, gdzie a0
} }
{ Deklaracja zmiennych uzywanych w programie. { a, b, c - wspolczynniki rownania drugiego stopnia, { delta - wyroznik trojmianu kwadratowego,
} } }
Rozdział 3. • Algorytmy z rozgałęzieniami
71
Rysunek 3.9. Algorytm przedstawiający rozwiązanie równania kwadratowego w zbiorze liczb rzeczywistych { x0, x1, x2 - pierwiastki rownania. var a, b, c, delta, x0, x1, x2 : Real; begin writeln; writeln (' Pierwiastki rownania kwadratowego '); writeln ('-----------------------------------');
}
72
Algorytmy • Ćwiczenia
Rysunek 3.10. Kod programu cw3_4 wpisany w oknie edycyjnym Turbo Pascala repeat write (' a = '); readln (a); until a0; write (' b = '); readln (b); write (' c = '); readln (c); delta := b*b - 4*a*c; writeln (' delta = ', delta:0:4); writeln; if (delta < 0) then { ---- Brak rozwiazan w R ---- } writeln ('Brak rozwiazan w zbiorze liczb rzeczywistych.') else if (delta = 0) then { ---- Jedno rozwiazanie ---- }
Rozdział 3. • Algorytmy z rozgałęzieniami
73
begin x0 := -b/(2*a); writeln ('Rownanie posiada jedno miejsce zerowe:'); writeln (' x0 = ', x0 :0:4); end else { ---- Dwa rozne rozwiazania ---------------} begin x1 := (-b+Sqrt(delta))/(2*a); x2 := (-b-Sqrt(delta))/(2*a); writeln ('Rownanie ma dwa rozne rozwiazania:'); writeln (' x1 = ', x1 :0:4); writeln (' x2 = ', x2 :0:4); end; readln; end.
Po zadeklarowaniu zmiennych używanych w programie następuje blok instrukcji wczytujących dane wejściowe a, b oraz c. Wczytywanie współczynnika a odbywa się w pętli programowej w postaci: repeat [Blok instrukcji] until Warunek
Instrukcje w obrębie tej pętli są wykonywane, dopóki nie zostanie spełniony warunek umieszczony po słowie kluczowym until (ang. aż). W naszym programie każdorazowe wykonanie pętli powoduje wczytanie wartości współczynnika a, który następnie porównywany jest z zerem. Jeżeli a = 0, to następuje powrót do linii repeat (ang. powtórz). Program kontynuuje działanie i oczekuje na podanie prawidłowej wartości. Wyjście z pętli i przejście do kolejnych linii programu następuje, gdy a0. Po wczytaniu danych obliczana jest wartość zmiennej sterującej delta, a następnie wykonywany jest segment programu z instrukcjami warunkowymi if … then … else. Sprawdzana jest wartość zmiennej delta w porównaniu z zerem. W programie odbywa się to w dwóch kolejnych blokach porównawczych — analogicznie jak na rysunku 3.2 b. Szeregowe połączenie instrukcji komparacyjnych modeluje trzywyjściowy blok porównawczy, występujący w algorytmie na rysunku 3.9. Zależnie od wartości i znaku zmiennej delta program podejmuje decyzję o liczbie rozwiązań, oblicza je i wyświetla na ekranie za pomocą minimalnej liczby znaków z czterema miejscami dziesiętnymi.
74
Algorytmy • Ćwiczenia
Poeksperymentuj z różnymi wartościami danych wsadowych. Oceń dokładność zaokrąglania pierwiastków równania. Przykładowy obraz ekranu monitora z rezultatami otrzymanymi w programie widoczny jest na rysunku 3.11.
Rysunek 3.11. Wyniki uzyskane w programie cw3_4
Wpływ metody obliczeniowej na dokładność wyników końcowych Obliczenia wykonywane przez komputer nie zawsze dają dokładne wyniki. Zazwyczaj jest to spowodowane ograniczeniem liczby cyfr znaczących występujących w wynikach pośrednich i końcowych. Aby się o tym przekonać, dokonajmy sprawdzenia wyników obliczeń widocznych na rysunku 3.11. W tym celu wystarczy podstawić wyznaczone wartości x1 = 40 oraz x2 = 0,0012 do wyrażenia znajdującego się po lewej stronie L(x) równania kwadratowego — dla obu wartości powinniśmy otrzymać w wyniku zero. Dla danych z rysunku 3.7 równanie to ma postać: x2 – 40,00125x + 0,05 = 0, więc L(x) = x2 – 40,00125x + 0,05. Stąd L(x1) = L(40) = 0 oraz L(x2) = L(0,0012) = 0,00199994 ≠ 0.
Rozdział 3. • Algorytmy z rozgałęzieniami
75
Okazuje się, że jedynie x1 = 40 jest dokładnym rozwiązaniem. Pierwiastek x2 = 0,0012 obarczony jest błędem wynikającym z przybliżenia. Dokładna wartość pierwiastka x2 = 0,00125. Jego prawidłowe przybliżenie, do czterech miejsc dziesiętnych, to 0,0013. Komputer, wykonując obliczenia na zmiennych typu Real, pamięta jedynie 11 cyfr. Stąd zmienna obliczona w programie i zapisana w notacji naukowej x2 = 1,249999972E-03. Po zaokrągleniu x2 = 0,0012. Błąd procentowy obliczenia x2:
σ=
0,00125 − 0,0012 ⋅100% = 4% . 0,00125
Błąd jest on na tyle duży, że warto rozważyć zmianę (o ile to możliwe) algorytmicznej metody rozwiązania równania.
Wzory Viete’a Sumę i iloczyn pierwiastków równania kwadratowego można przedstawić za pomocą wzorów Viete’a:
−b a c x1 ⋅ x2 = a
x1 + x2 =
Po przekształceniu druga z tych zależności otrzymuje postać, z której można obliczyć drugi pierwiastek, o ile znany jest pierwszy: c x2 = a ⋅ x1 Wykorzystując tę zależność, zmodyfikujemy program cw3_4. Ć W I C Z E N I E
3.6
Rozwiązanie równania kwadratowego z wykorzystaniem wzoru Viete’a
Wykorzystując wzór Viete’a na iloczyn pierwiastków, zmodyfikuj program cw3_4 tak, by obliczenie drugiego pierwiastka było ściśle związane z wyznaczoną wartością pierwszego rozwiązania.
76
Algorytmy • Ćwiczenia
Rozwiązanie 1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając z paska
menu polecenia File/New, a następnie wczytaj program z pliku cw3_4.pas znajdującego się w katalogu TP/Rozdz_03. 2. Dokonaj zmian w kodzie programu zgodnie z listingiem 3.3, a następnie zapisz program pod nazwą cw3_5.pas w wybranym przez siebie katalogu. Listing 3.3. Kod programu umożliwiający rozwiązywanie równania kwadratowego z użyciem wzoru Viete’a program cw3_5; { Zmodyfikowany program obliczajacy pierwiastki { rownania kwadratowego w postaci ax*x + bx + c = 0, { gdzie a0, z wykorzystaniem wzorow Viete'a { Deklaracja zmiennych uzywanych w programie. { a, b, c - wspolczynniki rownania drugiego stopnia, { delta - wyroznik trojmianu kwadratowego, { x0, x1, x2 - pierwiastki rownania. var a, b, c, delta, x0, x1, x2 : Real;
} } } } } } }
begin writeln; writeln (' Pierwiastki rownania kwadratowego II '); writeln ('--------------------------------------'); repeat write (' a = '); readln (a); until a0; write (' b = '); readln (b); write (' c = '); readln (c); delta := b*b - 4*a*c; writeln (' delta = ', delta:0:4); if (delta < 0) then { ---- Brak rozwiazan w R ---- } writeln ('Brak rozwiazan w zbiorze liczb rzeczywistych.') else if (delta = 0) then { ---- Jedno rozwiazanie ---- } begin x0 := -b/(2*a); writeln ('Rownanie posiada jedno miejsce zerowe:'); writeln (' x0 = ', x0 :0:4); end
Rozdział 3. • Algorytmy z rozgałęzieniami
77
else { ---- Dwa rozne rozwiazania --------------- } begin x1 := (-b+Sqrt(delta))/(2*a); x2 := c/(a*x1); writeln ('Rownanie ma dwa rozne rozwiazania:'); writeln (' x1 = ', x1 :0:4); writeln (' x2 = ', x2 :0:4); end; readln; end.
Przeanalizuj zmiany w programie. Na rysunku 3.12 podane są wyniki otrzymane w programie cw3_5 dla tych samych danych wsadowych, jakich użyto w obliczeniach zilustrowanych na rysunku 3.11. Tym razem rozwiązanie x2 jest już prawidłowo zaokrąglone.
Rysunek 3.12. Wyniki w programie cw3_5 Ć W I C Z E N I E
3.7
Rozwiązania równania kwadratowego. Aplikacja w Excelu
Napisz aplikację w Excelu obliczającą miejsce zerowe funkcji kwadratowej f(x) = ax2 + bx + c. Przyjmij, że x∈R, a∈R\{0}, b∈R, c∈R. Do obliczeń wykorzystaj funkcję samodzielnie ułożoną w VBA.
78
Algorytmy • Ćwiczenia
Rozwiązanie Miejsca zerowe funkcji znajdujemy, rozwiązując równanie f(x) = 0 ⇔ ax2 + bx + c = 0. Możemy zatem postępować analogicznie, jak w algorytmie przedstawionym na rysunku 3.9. 1. Uruchom program Excel i zapisz domyślnie pojawiający się Zeszyt1 w wybranym przez siebie katalogu pod nazwą cw3_6. Można również wczytać arkusz cw3_6.xls z katalogu EX/Rozdz_03. 2. Zmień nazwę zakładki Arkusz1 na Równanie kwadratowe. 3. Usuń zakładki Arkusz 2 i Arkusz3. 4. Rozpoczynamy od utworzenia procedury wprowadzania danych potrzebnych do obliczeń. Wprowadzanie danych odbywać się będzie poprzez wywoływane okna dialogowe. T Wywołaj okno edytora VBE i wstaw moduł standardowy Module1 (Moduł1). Jeżeli zapomniałeś, jak to zrobić, zajrzyj do ćwiczenia 2.5. T W sekcji General (Ogólne) modułu Module1 (Moduł1) wpisz kod z listingu 3.4. Listing 3.4. Procedura Zmien_dane Sub Zmien_dane() 'Procedura wprowadzania danych, czyszczenia ekranu i wyświetlania ich. 'Zmiana koloru tła arkusza. Cells.Select Selection.Interior.ColorIndex = 34 'Wyczyszczenie zawartości komórek B4:H17. Range("B4:H17").Select Selection.ClearContents Selection.Font.Bold = True Range("A1").Select 'Wprowadzanie danych za pomocą okna dialogowego. Do 'Sprawdzenie, czy a0. a = InputBox("Podaj różną od zera wartość współczynnika a.", "Dane do arkusza", 1) Loop Until a 0 b = InputBox("Podaj wartość współczynnika b.", "Dane do arkusza", 5) c = InputBox("Podaj wartość współczynnika c.", "Dane do arkusza", 6)
Rozdział 3. • Algorytmy z rozgałęzieniami
79
'Wyświetlenie wartości danych w komórkach arkusza. Range("B5").Value = "Wartości współczynników:" Range("B6").Value = "a = " & a Range("B7").Value = "b = " & b Range("B8").Value = "c = " & c 'Wywołanie funkcji wykonującej obliczenia i wyświetlającej wyniki. Call Oblicz_pierwiastki(a, b, c) End Sub
Umieszczoną w module procedurę przedstawia rysunek 3.13. Kod procedury zajmuje cały ekran, gdyż zostały zamknięte okienka eksploratora projektu i własności modułu — nie zawsze są potrzebne. Okna zamyka się albo przywraca, wybierając View (Pokaż), a następnie Project Explorer (Eksplorator projektu) albo Properties Window (Okno właściwości). Rozwinięte menu View przedstawia rysunek 3.14.
Rysunek 3.13. Okno edytora VBE z wpisanym kodem procedury Zmien_dane
Poszczególne bloki procedury Zmien_dane są opatrzone komentarzami. Komentarze poprzedza znak apostrofu (').
80
Algorytmy • Ćwiczenia
Rysunek 3.14. Rozwinięte menu View edytora VBE
Procedura rozpoczyna się od zaznaczenia wszystkich komórek w arkuszu (polecenie Cells.Select) i ustawienia bladozielonego koloru tła arkusza (polecenie Selection.Interior.ColorIndex = 34). Następnie za pomocą polecenia Range("B4:H17").Select zostaje zaznaczony obszar B4:H17. Po usunięciu jego zawartości następuje zmiana właściwości czcionki na pogrubioną. Do wprowadzania danych służą trzy (po jednej dla każdej stałej: a, b, c) instrukcje InputBox, które wyświetlają okno dialogowe o stylistyce i zawartości zgodnej ze standardem systemu Windows. Zawiera ono pasek tytułu, tekst i przyciski. Skrócona składnia funkcji ma postać: InputBox(Tekst, Tytuł, Wartość)
Znaczenie argumentów: T Tekst — argument obowiązkowy, zawiera tekst zachęty, który jest wyświetlany w oknie; T Tytuł — tekst wyświetlany na (niebieskim) pasku tytułowym; gdy ten parametr zostanie pominięty, wyświetlany jest tytuł aplikacji;
Rozdział 3. • Algorytmy z rozgałęzieniami
81
— domyślna wartość wyświetlana w polu tekstowym okna; klikając OK, użytkownik zatwierdza ją.
T Wartość
Jedno z okien dialogowych — dla zmiennej a — przedstawione jest na rysunku 3.15. Pozostałe wyglądają podobnie. Rysunek 3.15. Okno dialogowe wyświetlone po wywołaniu funkcji InputBox o składni: InputBox („Podaj różną od zera wartość współczynnika a.”, „Dane do arkusza”, 1)
Dodatkowo wprowadzanie wartości zmiennej a odbywa się w pętli Do … until a0 — wykonywanej, aż a wpisywane przez użytkownika będzie różne od zera. Wartości wprowadzonych danych wyświetlane są w kolumnie B arkusza za pomocą polecenia Range. Na przykład Range("B6"). Value = "a = " & a wyświetla w komórce B6 wartość zmiennej a poprzedzoną tekstem a =. W ostatniej linii przed końcem procedury End Sub, za pomocą polecenia Call Oblicz_pierwiastki(a, b, c) następuje wywołanie funkcji Oblicz_pierwiastki i przekazanie do niej argumentów a, b, c. 5. Wprowadzenie funkcji przeliczeniowej Oblicz_pierwiastki, która wyznacza miejsca zerowe funkcji kwadratowej. T W oknie edytora VBE, w module Module1 (Moduł1), poniżej procedury Zmien_dane wpisz kod z listingu 3.5 (rysunek 3.16). Listing 3.5. Kod funkcji przeliczeniowej Oblicz_pierwiastki Function Oblicz_pierwiastki(a, b, c) 'Funkcja oblicza pierwiastki równania kwadratowego 'dla zadanych wartości współczynników równania a, b, c.
82
Algorytmy • Ćwiczenia
Rysunek 3.16. Efekt wpisania kodu funkcji przeliczającej Oblicz_pierwiastki 'Obliczenie wyróżnika trójmianu kwadratowego. Delta = b ^ 2 - 4 * a * c 'Sprawdzenie wartości zmiennej delta, 'obliczenie wartości pierwiastków i ich wyświetlenie. Select Case Delta Case Is > 0 'Funkcja ma dwa różne miejsca zerowe. x1 = (-b + Sqr(Delta)) / (2 * a) x2 = (-b - Sqr(Delta)) / (2 * a) Range("D10").Value = "Funkcja posiada dwa różne miejsca zerowe:" Range("D11").Value = "x1 = " & x1 Range("D12").Value = "x2 = " & x2 Case Is = 0 'Funkcja ma jedno miejsce zerowe. x0 = -b / (2 * a) Range("D10").Value = "Funkcja posiada jedno miejsce zerowe:" Range("D11").Value = "x0 = " & x0 Case Is < 0 'Funkcja nie posiada miejsc zerowych. Range("D10").Value = "Funkcja nie posiada miejsc zerowych." End Select End Function
Rozdział 3. • Algorytmy z rozgałęzieniami
83
Funkcja pobiera wartości współczynników a, b, c, wyznacza wartości miejsc zerowych i wyświetla je. Wyjaśnienia wymaga jedynie fragment kodu z instrukcją Select … Case, realizujący trzywyjściowy blok warunkowy z algorytmu pokazanego na rysunku 3.2. Instrukcję Select … Case można przetłumaczyć jako „wybierz … przypadek”. Składnia struktury Select Case: Select Case Wartość Case Wyrażenie1 [Instrukcje do wykonania, gdy Wyrażenie1 = Wartość] Case Wyrażenie2 [Instrukcje do wykonania, gdy Wyrażenie2 = Wartość] End Select
Bloków Case w zapisie może być dowolna liczba. Jeżeli żadne z wyrażeń nie jest równe wartości, to następuje skok poza strukturę End Select. W programie instrukcja sprawdza wartość zmiennej delta i porównuje ją z zerem, a następnie wykonuje odpowiednie obliczenia. 6. Utwórz tabelę arkusza, wykonując podane poniżej polecenia: T We wskazanych komórkach arkusza umieść nagłówki: T komórka B2 — Obliczanie miejsc zerowych funkcji kwadratowej ax2 + bx + c, T sformatuj cyfrę 2 w komórce B2 i nadaj jej styl Indeks górny; opis, jak to wykonać, znajduje się w ćwiczeniu 2.3. 7. Umieść przycisk sterujący w obrębie arkusza: T Wywołaj pasek narzędziowy Formularze i w obrębie komórek D15:F16 wstaw przycisk — jest to pierwszy z prawej formant, w najwyższym rzędzie na rysunku 2.26. Jeżeli pojawi się okno Przypisz makro, kliknij Anuluj. Wstawianie przycisku odbywa się poprzez kliknięcie jego symbolu graficznego, a następnie narysowanie go poprzez przeciągnięcie kursora myszy od lewego górnego rogu komórki D15 do prawego dolnego rogu F16. Umieszczony w arkuszu przycisk przedstawia rysunek 3.17. T Zaznacz kursorem myszy napis na przycisku, wciśnij Delete na klawiaturze, po czym napisz: Zmień dane i oblicz. Po wykonaniu tych operacji przycisk powinien wyglądać tak, jak na rysunku 3.18.
84
Algorytmy • Ćwiczenia
Rysunek 3.17. Przycisk w arkuszu narysowany po wybraniu jego ikony w pasku Formularze Rysunek 3.18. Gotowy przycisk sterujący w arkuszu Równanie kwadratowe
T
Przycisk już istnieje, ale jest „martwy”, gdyż po jego kliknięciu nie zachodzi żadna akcja. Należy zatem przyporządkować mu obsługujący go kod. Wywołaj ponownie okno edytora VBE , a następnie wprowadź takie zmiany, by otrzymać efekt widoczny na rysunku 3.19: T przełącz się do arkusza Arkusz1 (Równanie kwadratowe); w tym celu w oknie eksploratora projektu Project — VBA Project kliknij dwukrotnie pozycję listy Arkusz1 (Równanie kwadratowe), T w sekcji CommandButton1 wpisz kod z listingu 3.6, po czym zamknij okno edytora.
Rozdział 3. • Algorytmy z rozgałęzieniami
85
Rysunek 3.19. Kod obsługi przycisku wpisany w sekcji CommandButton arkusza Równanie kwadratowe Listing 3.6. Kod przyporządkowany przyciskowi w arkuszu Równanie kwadratowe Private Sub CommandButton1_Click() 'Wywołanie procedury Zmien_dane. Call Zmien_dane End Sub
Jeżeli okno eksploratora jest zamknięte, otwórz je, korzystając z menu View edytora VBE pokazanego na rysunku 3.14.
Każdorazowe kliknięcie przycisku Zmień dane i oblicz spowoduje wywołanie procedury Zmien_dane. Służy do tego komenda Call (wywołaj). Zakończyłeś tworzenie arkusza. Nie dziw się, że widać jedynie pusty arkusz z nagłówkiem i przyciskiem sterującym — taki jak na rysunku 3.20. Arkusz ożyje dopiero po kliknięciu przycisku.
86
Algorytmy • Ćwiczenia
Rysunek 3.20. Arkusz Równanie kwadratowe zaraz po zaprojektowaniu
Kliknij przycisk Zmień dane i oblicz, a następnie wprowadź dane liczbowe w pojawiających się kolejno okienkach InputBox. Po wprowadzeniu ostatniej danej, w arkuszu pojawią się wartości i wyniki, na przykład takie, jak na rysunku 3.21. Jeżeli przycisk nie reaguje na kliknięcie, sprawdź, czy wyszedłeś z Trybu projektowania poprzez wyłączenie drugiego z prawej przycisku w Pasku narzędzi Visual Basic (rysunek 2.27).
Uwagi końcowe Funkcja a procedura Im dłuższy i bardziej złożony jest program, tym trudniej przedstawić go w takiej formie, by był czytelny i zrozumiały. Częstym powodem jest to, że w opracowywanym programie występują (niezbędne) powtórzenia całych fragmentów wykonujących te same operacje przy takich
Rozdział 3. • Algorytmy z rozgałęzieniami
87
Rysunek 3.21. Efekt końcowy — arkusz Równanie kwadratowe z przykładowymi danymi i wynikami
samych lub niewiele zmienionych danych wejściowych. Aby tego uniknąć, warto stosować funkcje i procedury. Są one wydzielonymi z programu blokami strukturalnymi realizującymi wybrane fragmenty algorytmu. Stanowią część programu, dlatego nazywane są podprogramami. Ich budowa przypomina budowę innych obiektów strukturalnych, na przykład instrukcji warunkowych if. Pomiędzy znacznikami początku (nagłówek) i końca podprogramu znajduje się blok poleceń do wykonania — tzw. ciało podprogramu. Podprogramy wymagają zadeklarowania, a przed ich użyciem wywołania. Deklaracja, zwana też definicją, procedury albo funkcji odbywa się w nagłówku podprogramu. Do tego celu używane jest odpowiednie słowo kluczowe, po którym występuje nazwa. Deklaracje w Pascalu i VBA różnią się od siebie. W Turbo Pascalu tuż przed nazwą w nagłówku procedury znajduje się słowo kluczowe function, a w VBA Function. Nazwa w deklaracji procedury w Pascalu jest poprzedzona poleceniem procedure, w VBA jest
88
Algorytmy • Ćwiczenia
to polecenie Sub (skrót od subroutine, z ang. procedura, podprogram). Po nazwie znajduje się nawias z wykazem parametrów funkcji lub procedury. Zapamiętaj! Podstawowa różnica między procedurą i funkcją polega na tym, że funkcja może zwracać wartość, a procedura nie.
W dalszej części książki funkcje i procedury będą często stosowane. Musisz je więc znać i rozumieć. Ten podrozdział zawiera tylko kilka uwag ogólnych, które zostały zamieszczone po to, byś mógł się przekonać, czy problematyka podprogramów jest Ci dobrze znana. Jeżeli nie, koniecznie zajrzyj do podręcznika wybranego języka programowania, na przykład do jednej z pozycji oferowanych przez wydawnictwo Helion.
Ćwiczenia do samodzielnego wykonania Ć W I C Z E N I E
3.8 W każdym z programów występujących w rozdziale dokonaj podstawienia niedozwolonej wartości i zaobserwuj, czy pojawi się błąd. Jeżeli tak, to sprawdź w literaturze lub pliku Pomocy, co on oznacza. Ć W I C Z E N I E
3.9 Zmodyfikuj program z ćwiczenia 3.4 i zamiast instrukcji warunkowych if…then użyj instrukcji wyboru case…of.
Rozdział 3. • Algorytmy z rozgałęzieniami
89
Ć W I C Z E N I E
3.10 Dokonaj takiej zmiany w funkcji przeliczeniowej w ćwiczeniu 3.7, by drugie z obliczanych miejsc zerowych wyznaczane było za pomocą wzorów Viete’a. Wykorzystaj najpierw wzór na sumę, a potem na iloczyn. Ć W I C Z E N I E
3.11 Ułóż algorytm w postaci schematu blokowego obliczający pole albo obwód koła o danym promieniu R. Wskazówka: promień koła musi spełniać warunek R > 0. Inne wartości nie powinny być przyjmowane podczas wprowadzania danych. Ć W I C Z E N I E
3.12 Ułóż algorytm w postaci listy kroków oraz program w Turbo Pascalu wyznaczający ekstremum funkcji kwadratowej i wyświetlający wyniki wraz z odpowiednim komentarzem. Wskazówka: zastosuj wzory opisujące współrzędne wierzchołka paraboli. Ć W I C Z E N I E
3.13 Ułóż algorytm w postaci listy kroków oraz aplikację w Excelu obliczającą opór wypadkowy dwóch oporników R1 i R2 połączonych szeregowo albo równolegle. Właściwe wzory znajdziesz w podręczniku do fizyki.
90
Algorytmy • Ćwiczenia
Ć W I C Z E N I E
3.14 Ułóż w formie schematu blokowego algorytm, który oblicza należny podatek dochodowy, a następnie zakoduj go w Pascalu. Przyjmij, że system podatkowy nie uwzględnia zwolnień od podatku, kosztów i odliczeń oraz że są trzy stawki podatkowe: T 20% dla dochodów z przedziału od 0 do 20 000 zł, T 30% dla dochodów od 20 000 zł 1 gr do 50 000 zł, T 40% dla dochodów powyżej 50 000 zł. Ć W I C Z E N I E
3.15 Ułóż w formie schematu blokowego algorytm, który oblicza należny podatek dochodowy według zasad obowiązujących w Polsce, i zakoduj go za pomocą VBA. Niezbędne dane o systemie podatkowym znajdziesz na www.podatki.pl.
4 Algorytmy iteracyjne. Przetwarzanie danych w pętli programowej Algorytm iteracyjny Algorytm iteracyjny cechuje powtarzanie (iteracja) tych samych kroków w algorytmie dopóty, dopóki nie zostanie spełniony wymagany warunek. Podczas przetwarzania wykonywane są wielokrotnie te same operacje. Zmianie ulegają jedynie wartości pewnych zmiennych i wyników pośrednich. Iteracja jest typem algorytmu cyklicznego, który działa poprawnie niezależnie od liczby elementów w zbiorze. Mówimy, że program działa w pętli programowej. Elementami zbioru mogą być zarówno liczby, jak i inne elementy, które da się jednoznacznie opisać poprzez przyporządkowanie im wartości liczbowych lub łańcuchów znakowych. Na przykład zbiorem są lokale mieszkalne w bloku, którym przyporządkowano numery. Zbiorem jest grupa ludzi pracujących w tym samym zakładzie — poszczególne osoby są identyfikowane poprzez nazwisko i imię albo poprzez nadany im numer (np. PESEL). Zbiór elementów tej samej klasy może być odwzorowany poprzez zbiór liczbowy.
92
Algorytmy • Ćwiczenia
Rodzaje pętli programowych z instrukcjami warunkowymi Pętla programowa jest jedną z podstawowych struktur występujących w algorytmach. Wymusza powtarzanie tych samych operacji, aż zostanie osiągnięty oczekiwany warunek. Określenie końca przetwarzania dokonywane jest w bloku warunkowym na podstawie analizy zmiennej sterującej, modyfikowanej podczas procesu przetwarzania. Sprawdzenia warunku można dokonać przed albo po wykonaniu bloku instrukcji. Przedstawia to rysunek 4.1.
Rysunek 4.1. Struktury pętli programowych: a) komparacja odbywa się przed blokiem instrukcji wewnętrznych, b) komparacja odbywa się po bloku instrukcji wewnętrznych
Sprawdzenie warunku na początku pętli Sekwencja instrukcji umieszczonych wewnątrz pętli wykonywana jest dopóty, dopóki prawdziwy jest WARUNEK umieszczony w bloku porównującym. Jeżeli WARUNEK jest fałszywy, to następuje wyjście
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych
93
z pętli i wykonywana jest dalsza część programu. Przy tak zorganizowanej strukturze może się zdarzyć, że blok instrukcji umieszczony wewnątrz pętli nie zostanie wykonany ani razu.
Sprawdzenie warunku na zakończenie pętli Blok instrukcji umieszczonych wewnątrz pętli jest wykonywany dopóty, dopóki fałszywy jest WARUNEK umieszczony w bloku porównującym. Gdy WARUNEK zostanie spełniony (jest prawdą), wówczas następuje wyjście z pętli i wykonanie dalszej części programu. W tym typie pętli blok instrukcji umieszczonych w jej obrębie będzie wykonany przynajmniej jeden raz. Tego typu pętla wystąpiła już podczas wprowadzania danej a w ćwiczeniu 3.6.
Instrukcje iteracyjne w Turbo Pascalu i Visual Basicu W obu językach występują trzy odmiany instrukcji iteracyjnej (tabela 4.1). Dokładny opis iteracyjnych obiektów strukturalnych znajduje się w plikach pomocy Pascala i VBA. Tabela 4.1. Instrukcje iteracyjne w Pascalu i ich odpowiedniki w Visual Basicu Pascal
Visual Basic
Uwagi
for … to … do
For … to … Step … Next
Zmienna sterująca cyklu sprawdzana jest na początku pętli. Przed wejściem do pętli musi być znana liczba jej powtórzeń.
while … do …
While … Wend
Zmienna sterująca cyklu sprawdzana jest na początku pętli. Liczba powtórzeń nie musi być znana. Pętla może nie zostać wykonana ani razu.
repeat … until …
Do … Loop Until …
Zmienna sterująca cyklu sprawdzana jest na końcu pętli. Liczba powtórzeń nie musi być znana. Pętla musi być wykonana choć jeden raz.
lub for … downto … do
94
Algorytmy • Ćwiczenia
Możliwe jest również zrealizowanie iteracji poprzez instrukcje skoku bezwarunkowego. Poniższy przykład pokaże, w jaki sposób można rozwiązać ten sam problem, stosując różne kombinacje instrukcji iteracyjnych.
Rysowanie gwiazdek Ć W I C Z E N I E
4.1
Algorytm rysowania trójkąta z gwiazdek
Za pomocą opisu słownego przedstaw algorytm wykorzystujący iterację do wyświetlania na ekranie symbolu gwiazdki (*) według zasad: T w pierwszej wyświetlanej linii liczba gwiazdek jest równa liczbie naturalnej wprowadzonej przez użytkownika, T w każdej kolejnej linii wyświetlana jest jedna gwiazdka mniej, T linia z gwiazdkami zaczyna się w pierwszej od lewej kolumnie ekranu.
Rozwiązanie Dane: Liczba naturalna wprowadzona przez użytkownika. Oczekiwany wynik: Rysunek trójkąta prostokątnego utworzony z gwiazdek. Analiza problemu — algorytm opisowy: Liczbę gwiazdek w pierwszej linii określa użytkownik, wpisując ją z klawiatury w postaci liczby naturalnej. W kolejnej linii ma być wyświetlona jedna gwiazdka mniej. Na przykład dla wprowadzonej wartości 4 wynikiem powinien być obraz taki, jak na rysunku 4.2. Rysunek 4.2. Oczekiwany wynik działania algorytmu dla wprowadzonej liczby 4
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych
95
Liczba linii z gwiazdkami jest równa liczbie gwiazdek w pierwszym rzędzie. Zarazem jest to wartość początkowa podana przez użytkownika. W każdej kolejnej linii liczba gwiazdek jest równa wartości z linii poprzedniej pomniejszonej o 1. Aby otrzymać wiersz gwiazdek, należy znać ich liczbę w rzędzie. Następnie należy wyświetlić gwiazdkę i powtórzyć tę operację (iterować) tyle razy, ile gwiazdek ma się pojawić w linii. Gdy wszystkie gwiazdki w wybranym rzędzie zostaną wyświetlone, należy przejść do następnego rzędu, gdzie wyświetlona zostanie o jedna gwiazdka mniej. Z powyższych rozważań wynika, że konieczne jest liczenie zarówno wyświetlanych linii, jak i gwiazdek w pojedynczym rzędzie. Dane te należy przechowywać w dwóch zmiennych — zmiennej zawierającej informację o wyświetlanym rzędzie i zmiennej zawierającej informację o liczbie gwiazdek w linii. Należy zatem zastosować dwie pętle programowe. Pętla rysowania gwiazdek w linii musi być umieszczona wewnątrz pętli określającej kolejne linie. Zapamiętaj! Zmienna sterująca iteracją jest najczęściej nazywana licznikiem pętli. Ć W I C Z E N I E
4.2
Algorytm rysowania gwiazdek w Turbo Pascalu
Zakoduj algorytm z ćwiczenia 4.1 w języku Turbo Pascal, stosując instrukcje iteracyjne z tabeli 4.1.
Rozwiązanie Zgodnie z opisem z ćwiczenia 4.1 potrzebne są dwie struktury iteracyjne wybierane spośród trzech przedstawionych w tabeli 4.1. Stąd łatwo obliczyć, że program można zrealizować na 9 sposobów. Wybór instrukcji jest bowiem dwuelementową wariacją z powtórzeniami ze zbioru trzyelementowego. Umiejętne używanie instrukcji iteracyjnych jest bardzo ważnym zagadnieniem w algorytmice. W wielu wypadkach można je stosować zamiennie, dokonując jedynie niewielkich modyfikacji w kodzie programu. Aby to unaocznić, ćwiczenie zostanie zrealizowane na trzy sposoby:
96
Algorytmy • Ćwiczenia
T z użyciem dwóch instrukcji for — program cw4_2a, T z użyciem instrukcji while oraz for — program cw4_2b, T z użyciem struktury repeat i while — program cw4_2c.
Pozostałych 6 sposobów pozostawiam Czytelnikowi do samodzielnego rozważenia.
Program cw4_2a — zastosowanie dwóch instrukcji for 1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając z paska
menu polecenia File/New. 2. W oknie edycyjnym wpisz kod z listingu 4.1 albo wczytaj program z pliku cw4_2a.pas znajdującego się w katalogu TP/Rozdz_04. Listing 4.1. Kod programu cw4_2a program cw4_2a; { Program wykorzystuje dwie petle "for" i rysuje { gwiazdki na ekranie monitora. { W kazdej kolejnej linii jest jedna gwiazdka mniej. { Deklaracja zmiennych uzywanych w programie: { i - licznik linii do wypelnienia, { k - licznik gwiazdek w linii. var i, k : Integer; begin writeln; writeln (' Rysowanie gwiazdek - wersja I '); writeln ('-------------------------------'); writeln; write (' Liczba linii = '); readln (i); for i := i downto 1 do begin writeln; for k := 1 to i do write ('*'); end; readln; end.
} } } } } }
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych
97
Zewnętrzna pętla w programie — for i := i downto 1 do — to pętla odliczająca w dół. Jej wykonanie kończy się, gdy licznik linii i osiągnie wartość 1. Pętla wewnętrzna for k := 1 to i do odpowiada za rysowanie rzędu gwiazdek. Użyta instrukcja write ('*') powoduje dopisywanie kolejnej gwiazdki w tej samej linii na ekranie. Koniec pętli następuje, gdy licznik gwiazdek k zrówna się z aktualną wartością licznika linii i. Efekt działania programu pokazuje rysunek 4.3.
Rysunek 4.3. Rezultat wykonania programu rysującego gwiazdki
Program cw4_2b — zastosowanie instrukcji while oraz for 1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając
z paska menu polecenia File/New. 2. W oknie edycyjnym wpisz kod z listingu 4.2 albo wczytaj
program z pliku cw4_2b.pas znajdującego się w katalogu TP/Rozdz_04. Listing 4.2. Kod programu cw4_2b program cw4_2b; { Program wykorzystuje petle "while" oraz "for" { i rysuje gwiazdki na ekranie monitora. { W kazdej kolejnej linii jest jedna gwiazdka mniej. { Deklaracja zmiennych uzywanych w programie: { i - licznik linii do wypelnienia, { k - licznik gwiazdek w linii. var i, k : Integer;
} } } } } }
98
Algorytmy • Ćwiczenia begin writeln; writeln (' Rysowanie gwiazdek - wersja II '); writeln ('--------------------------------'); writeln; write (' Liczba linii = '); readln (i); while i > 0 do begin writeln; for k := 1 to i do write ('*'); i := i - 1 end; readln; end.
Po wczytaniu, ile linii należy wyświetlić (instrukcja readln (i)), rozpoczyna się zewnętrzna pętla iteracyjna. Tworzy ją struktura while i > 0 do. Pętla ta wykonywana jest dopóty, dopóki wartość licznika linii i jest naturalną liczbą dodatnią. Podczas każdego wykonania pętli następuje pomniejszenie o 1 wartości zmiennej sterującej i. Odbywa się to poprzez podstawienie i := i - 1. Pętlę wewnętrzną tworzy instrukcja for zorganizowana tak samo jak w programie cw4_2a. Efekt działania programu jest identyczny jak w ćwiczeniu poprzednim.
Program cw4_2c — zastosowanie struktury repeat i while 1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając
z paska menu polecenia File/New. 2. W oknie edycyjnym wpisz kod z listingu 4.3 albo wczytaj program z pliku cw4_2c.pas znajdującego się w katalogu TP/Rozdz_04. Listing 4.3. Kod programu cw4_2c program cw4_2c; { Program wykorzystuje petle "while" oraz "repeat" { i rysuje gwiazdki na ekranie monitora. { W kazdej kolejnej linii jest jedna gwiazdka mniej.
} } }
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych { Deklaracja zmiennych uzywanych w programie: { i - licznik linii do wypelnienia, { k - licznik gwiazdek w linii. var i, k : Integer;
99
} } }
begin writeln; writeln (' Rysowanie gwiazdek - wersja III '); writeln ('---------------------------------'); writeln; write (' Liczba linii = '); readln (i); repeat writeln; k := i; while k > 0 do begin write ('*'); k := k - 1; end; i := i - 1; until i < 1; readln; end.
Tym razem pętlę wewnętrzną tworzy instrukcja iteracyjna while k > 0 do. Przed jej rozpoczęciem licznik gwiazdek k przyjmuje wartość licznika linii i. Następnie, w pętli, k jest zmniejszany o 1 za pomocą podstawienia k := k – 1. Takie zmniejszenie wartości zmiennej nazywane jest dekrementacją. Zewnętrzną pętlę tworzy struktura repeat … until i < 1. Powtarzanie następuje tak długo, aż licznik linii i stanie się mniejszy od jedności. Obraz narysowany w programie jest ponownie taki sam, jak na rysunku 4.3. Ć W I C Z E N I E
4.3
Algorytm rysowania gwiazdek w Excelu
Zapisz algorytm rysowania gwiazdek z ćwiczenia 4.1 w formie aplikacji w Excelu. Zastosuj różne instrukcje iteracyjne podane w tabeli 4.1.
100
Algorytmy • Ćwiczenia
Rozwiązanie Dla większych walorów edukacyjnych, w rozwiązaniu zostaną użyte wszystkie trzy instrukcje iteracyjne. 1. Uruchom program Excel i zapisz domyślnie pojawiający się
Zeszyt1 w wybranym przez siebie katalogu pod nazwą cw4_3. Można również wczytać arkusz cw4_3.xls z katalogu EX/Rozdz_04. 2. Zmień nazwę zakładki Arkusz1 na Gwiazdki. 3. Usuń zakładki Arkusz 2 i Arkusz3. 4. Umieść przycisk sterujący w obrębie arkusza: T
Wywołaj pasek narzędziowy Formularze i w obrębie komórek E14:F16 wstaw przycisk — jest to pierwszy z prawej formant w najwyższym rzędzie na rysunku 2.27. Kliknij Anuluj, jeśli pojawi się okno Przypisz makro.
T
Zmień podpis przycisku na: Zmień dane i rysuj.
T
Wywołaj okno edytora VBE i przełącz się do Arkusza1 (Gwiazdki). W sekcji CommandButton1 wpisz kod z listingu 4.4. Następnie zamknij okno edytora.
Listing 4.4. Kod przyporządkowany przyciskowi w arkuszu Gwiazdki Private Sub CommandButton1_Click() 'Procedura wprowadzania danych, 'czyszczenia ekranu i wyświetlania gwiazdek. 'Wyczyszczenie arkusza i zmiana koloru tła arkusza 'oraz rozmiaru czcionki. Cells.Select Selection.ClearContents Selection.Interior.ColorIndex = 35 Selection.Font.Size = 14 Range("E15").Select Do 'Wprowadzenie liczby gwiazdek i sprawdzenie, czy a > 0. a = InputBox("Podaj liczbę gwiazdek w pierwszej linii.", "Dane do ´arkusza", 4) Loop Until a > 0 'Rysowanie gwiazdek. For i = 1 To a star = " " k = i
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych
101
While k > 0 star = star & "*" k = k - 1 Wend Range("A" & a - i + 1).Value = star Next i End Sub
Tym razem nie został użyty moduł standardowy. Cały kod programu umieszczono w sekcji CommandButton arkusza Gwiazdki (rysunek 4.4).
Rysunek 4.4. Kod programu umieszczony w procedurze obsługi przycisku arkusza Gwiazdki
Za każdym razem, gdy użytkownik kliknie przycisk Zmień dane i rysuj, nastąpi wywołanie przyporządkowanej mu procedury CommandButton1_Click(). W pętli Do … Loop Until wywoływane jest okno dialogowe pokazane na rysunku 4.5, w którym użytkownik podaje liczbę gwiazdek do wyświetlenia. Powtórzenie pętli następuje tylko wtedy, gdy wprowadzana liczba nie jest dodatnia.
102
Algorytmy • Ćwiczenia
Rysunek 4.5. Okno dialogowe arkusza Gwiazdki z ćwiczenia 4.3, dla a = 10
Program działa poprawnie tylko wtedy, gdy użytkownik poda liczbę całkowitą. W innym wypadku pojawi się błąd wykonania. Jak go wyeliminować, dowiesz się w kolejnym rozdziale.
Podobnie jak w Turbo Pascalu, do rysowania wykorzystywane są dwie pętle. Pierwsza — zewnętrzna — to pętla For … Next. Druga — wewnętrzna — to struktura While … Wend. Zmienna star użyta w programie przechowuje łańcuch znakowy (w naszym przypadku łańcuch gwiazdek) przeznaczony do wyświetlenia w kolumnie A arkusza. Komenda Range ("A" & a - i + 1).Value = star nakazuje wyświetlenie w kolejnych wierszach kolumny A wartości ukrytej w zmiennej star. Analiza całości kodu nie powinna przysporzyć kłopotu, dlatego pozostawiam ją Czytelnikowi. Efekt wykonania aplikacji dla domyślnych danych przyjętych w programie przedstawia rysunek 4.6. 5. Zakończ pracę z arkuszem i Excelem, wybierając Plik oraz Zakończ.
Co umożliwia iteracja? Struktury iteracyjne pozwalają przetwarzać informacje bardziej złożone niż w podanych powyżej przykładach wprowadzających. Pozwalają obliczać wybrane wartości funkcji lub ciągów, w skład których wchodzi dowolna liczba wyrazów. Nie ma przy tym znaczenia rodzaj operacji arytmetycznych określonych na analizowanym ciągu.
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych
103
Rysunek 4.6. Efekt wykonania programu cw4_3.xls
Dzięki iteracji można przeszukiwać dany zbiór elementów o dowolnej rozpiętości i dokonywać na nim operacji, na przykład uporządkować ciąg elementów według zadanego kryterium. Iteracja znajduje zastosowanie przy wyznaczaniu wartości funkcji, wyszukiwaniu liczb pierwszych, operacjach na ułamkach, znajdowaniu cech szczególnych liczb, obliczaniu wartości stałych matematycznych i fizycznych. Poniżej podany jest przykład zastosowania struktur iteracyjnych i pętli do obliczania wartości iloczynu kolejnych liczb naturalnych.
Funkcja silnia Funkcja silnia często występuje w literaturze opisującej algorytmy, ponieważ pozwala doskonale zapoznać Czytelnika z dwoma technikami stosowanymi w algorytmice — iteracją i rekurencją. Ta druga zostanie omówiona w kolejnym rozdziale.
104
Algorytmy • Ćwiczenia
Ć W I C Z E N I E
4.4
Algorytm iteracyjnego obliczania n!
Przedstaw w postaci schematu blokowego iteracyjny algorytm obliczania silni.
Rozwiązanie Dane: Liczba naturalna n wprowadzona przez użytkownika, równa ostatniemu wyrazowi iloczynu. Oczekiwany wynik: Wartość funkcji n!. Analiza problemu: Silnią n! liczby naturalnej n nazywamy iloczyn liczb naturalnych w postaci:
n!= 1 ⋅ 2 ⋅ 3 ⋅ ... ⋅ n . Ponadto 0! = 1 oraz 1! = 1. Podstawową własnością silni jest n! = n(n – 1)!, na przykład 5! = 5 ⋅ 4!. Opierając się na definicji, stwierdzamy, że do obliczenia wartości potrzebna jest znajomość naturalnej wartości n. Obliczenie powinno przebiegać w pętli, w której wartość aktualna argumentu (licznika pętli) jest mnożona przez wynik obliczenia silni dla argumentu poprzedniego (licznik pętli pomniejszony o jeden). Gdy licznik pętli osiągnie wprowadzoną wartość n, powinno nastąpić wyprowadzenie wyniku. Kompletny algorytm realizujący obliczanie n! metodą iteracyjną przedstawia rysunek 4.7. Ć W I C Z E N I E
4.5
Obliczanie silni. Program w Turbo Pascalu
Napisz program w Turbo Pascalu, który obliczy i wyświetli wartość funkcji silnia n! dla n ∈ N. Wykorzystaj algorytm z ćwiczenia 4.4.
Rozwiązanie 1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając z paska
menu polecenia File/New. Następnie wczytaj program z pliku cw4_5.pas, znajdującego się w katalogu TP/Rozdz_04 na serwerze ftp wydawnictwa Helion.
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych
105
Rysunek 4.7. Iteracyjny algorytm obliczający wartość silni
2. Wpisz kod z listingu 4.5, a następnie zapisz program pod nazwą
cw4_5.pas w wybranym przez siebie katalogu. Listing 4.5. Kod programu obliczającego wartość silni program cw4_5; { Program oblicza wartosc silni n!
}
{ Deklaracja zmiennych uzywanych w programie: } { n - ostatni wyraz iloczynu n! } { i - licznik petli, a zarazem kolejna liczba iloczynu} { Silnia - wynik obliczen. } var n, i : Integer; Silnia : Longint; begin writeln; writeln (' Obliczanie wartosci n! '); writeln ('------------------------'); writeln;
106
Algorytmy • Ćwiczenia write (' n = '); readln (n); i := 0; Silnia := 1; while i < n do begin i := i + 1; Silnia := i * Silnia; end; writeln (' Podaje wynik obliczen:'); writeln (' ', n, '! = ', Silnia); readln; end.
Rolę instrukcji iteracyjnej tworzącej pętlę programową odgrywa struktura while … do. Naturalnie można również zastosować inny typ instrukcji, ale nie byłoby wówczas pełnej zgodności ze schematem blokowym z rysunku 4.7. Zmienna Silnia przechowuje zarówno wyniki cząstkowe, jak i wynik końcowy, który jest wyświetlany. Wartość licznika i ulega zwiększeniu o 1 przy każdym wykonaniu pętli. Takie zwiększanie wartości zmiennej nazywane jest inkrementacją. Poeksperymentuj z różnymi, naturalnymi danymi wejściowymi. Pamiętaj, że w programie wymagane jest podanie liczby naturalnej. Przy innej wartości wejściowej wynik obliczeń jest nieprawidłowy. Ekran z przykładowym wynikiem przedstawia rysunek 4.8.
Rysunek 4.8. Efekt wykonania programu cw4_5, dla n = 8
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych
107
Ć W I C Z E N I E
4.6
Aplikacja obliczająca silnię w Excelu
Stosując instrukcję iteracyjną For … Next, zrealizuj w Excelu algorytm obliczania silni n!.
Rozwiązanie 1. Uruchom program Excel i zapisz domyślnie pojawiający się
2. 3. 4.
5.
Zeszyt1 w wybranym przez siebie katalogu pod nazwą cw4_6. Możesz również wczytać arkusz cw4_6.xls z katalogu EX/Rozdz_04. Zmień nazwę zakładki Arkusz1 na Silnia. Usuń zakładki Arkusz 2 i Arkusz3. W komórce D2 umieść tekst: Aplikacja oblicza wartość silni n!. Proponowana czcionka: Arial CE, pogrubiona, w kolorze czerwonym, rozmiar 18. W arkuszu Silnia umieść przycisk: T Wywołaj pasek narzędziowy Formularze i w obrębie komórek E6:H7 wstaw przycisk. Kliknij Anuluj, jeżeli pojawi się okno Przypisz makro. T Zmień podpis przycisku na: Kliknij i wpisz liczbę naturalną n.
Po wykonaniu poleceń z punktów 2 – 5 wygląd arkusza powinien być taki, jak na rysunku 4.9. 6. Wpisz kod obsługi przycisku CommandButton1. W tym celu wywołaj okno edytora VBE i przełącz się do arkusza Arkusz1 (Silnia). W sekcji CommandButton1 wpisz kod z listingu 4.6, a następnie zamknij okno edytora. Listing 4.6. Kod przyporządkowany przyciskowi CommandButton1 w arkuszu Silnia Private Sub CommandButton1_Click() 'Procedura wprowadzania danych i obliczania n! 'Wprowadzenie naturalnej wartości n. n = InputBox("Podaj naturalną wartość n.", "Obliczanie n!", 4) i = 0 Silnia = 1
108
Algorytmy • Ćwiczenia
Rysunek 4.9. Wygląd arkusza Silnia 'Blok obliczeń. For i = 1 To n Silnia = i * Silnia Next i 'Wyświetlenie wyniku. Komunikat = "Podaję wynik: " & n & "! = " & Silnia MsgBox Komunikat, vbOKOnly, "Wynik" End Sub
Okno edytora VBE z wpisanym kodem przedstawia rysunek 4.10. Program operuje na takiej samej liczbie zmiennych i o tym samym znaczeniu, co w ćwiczeniu 4.5. Wartość n przekazywana jest do programu poprzez omawianą już instrukcję InputBox. Pętla z użyciem instrukcji For … Next jest krótsza w zapisie, gdyż polecenie Next i automatycznie dokonuje inkrementacji zmiennej sterującej. Będzie się ona zmieniała z krokiem 1.
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych
109
Rysunek 4.10. Okno edytora VBE z wpisanym kodem obsługi przycisku CommandButton1
Dzieje się tak zawsze, gdy w strukturze iteracyjnej pomija się słowo kluczowe Step (z ang. krok) informujące o wartości przyrostu zmiennej sterującej. Blok iteracyjny w programie rozpoczyna się linią For i = 1 To n. Wartość początkowa przyporządkowana zmiennej sterującej i to liczba 1. Ponieważ nie ma polecenia Step (krok), kolejne wartości i rosną z krokiem 1, aż do osiągnięcia wartości końcowej n. Zapamiętaj! Po wykonaniu pętli zmienna sterująca przyjmie wartość końcową powiększoną o krok iteracji.
Blok instrukcji wewnątrz pętli jest powtarzany tak długo, aż zmienna sterująca i stanie się większa od wartości końcowej n. Wówczas program rozpoczyna wykonywanie instrukcji następujących po pętli.
110
Algorytmy • Ćwiczenia
Co się jednak stanie, gdy w naszym programie n przyjmie wartość zero? Ile razy zostanie wykonana pętla? Ani razu, ponieważ dla n = 0 wartość końcowa równa jest 0 i jest mniejsza od wartości początkowej równej 1. Nie jest więc spełniony warunek konieczny i 12 daje różne wyniki w programach cw4_5 i cw4_6. Kilka przykładowych wyników przedstawionych jest w tabeli 4.2. Z tabeli widać, że program w Turbo Pascalu generuje błędne wyniki. Dlaczego? Przyczyna tkwi w deklaracji typu zmiennych.
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych
111
Tabela 4.2. Ilustracja rozbieżności wyników Wartość n
Prawidłowa wartość n!
Wartość obliczona w ćwiczeniu 4.5 — Turbo Pascal
Wartość obliczona w ćwiczeniu 4.6 — Excel
12
479001600
479001600
479001600
13
6227020800
1932053504
6227020800
14
87178291200
1278945280
87178291200
W programie cw4_5.pas używane są dwa typy: Integer i Longint. W zmiennej typu Integer może być przechowywana liczba stałoprzecinkowa z zakresu –32768 … 32767, a w zmiennej typu Longint liczba stałoprzecinkowa z zakresu –2147483648 … 2147483647. Zmienna Silnia jest typu Longint. Może przechowywać prawidłowo tylko wartości z zakresu –2147483648 … 2147483647. Ponieważ dla n > 12 wyniki uzyskiwane w programie wykraczają poza kres górny podanego zbioru, generowany jest błąd. Aby się o tym przekonać, proszę zmienić typ zmiennej Silnia na Real, który przeznaczony jest do przechowywania liczb zmiennoprzecinkowych z zakresu 2.9⋅10-39 – 1.7⋅1038. Teraz wyniki są poprawne. Okazuje się, że badając własności bardzo dużych liczb, nie można używać danych typu całkowitego. Należy o tym pamiętać, ponieważ źle dobrany typ zmiennej zawsze prowadzi do błędu, który jest trudno zauważyć.
Ćwiczenia do samodzielnego wykonania Ć W I C Z E N I E
4.7 Napisz algorytm zakodowany w Turbo Pascalu lub VBA Excel, wykorzystujący dowolne instrukcje iteracyjne do wyświetlania na ekranie symbolu gwiazdki (*) według zasad:
112
Algorytmy • Ćwiczenia
T w najdłuższej linii liczba gwiazdek jest równa wartości liczby
naturalnej wprowadzonej przez użytkownika, T po narysowaniu na ekranie pojawi się geometryczny układ gwiazdek, taki jak na rysunku 4.12 a, b i c .
Rysunek 4.12. Układy gwiazdek do ćwiczenia 4.7 Ć W I C Z E N I E
4.8 Dokonaj takich zmian w schemacie blokowym z ćwiczenia 4.4, by precyzyjnie odpowiadał programowi cw4_6.xls. Ć W I C Z E N I E
4.9 Zapisz w postaci listy kroków algorytm, który oblicza sumę n pierwszych liczb naturalnych 1 + 2 + … + n, dla n ∈ N+. Rozwiąż zadanie dwoma sposobami: T za pomocą iteracji, T stosując znany z matematyki wzór na sumę tego ciągu. Ć W I C Z E N I E
4.10 Zakoduj algorytm z ćwiczenia 4.9: a)
w Turbo Pascalu,
b)
w VBA Excel.
Rozdział 4. • Algorytmy iteracyjne. Przetwarzanie danych
113
Ć W I C Z E N I E
4.11 Napisz aplikację w Excelu wykorzystującą funkcje wbudowane, która wyznaczy wartość: T iloczynu kolejnych liczb naturalnych, T sumy kolejnych liczb naturalnych. Porównaj otrzymane wyniki z rezultatami otrzymanymi w innych ćwiczeniach z tego rozdziału.
114
Algorytmy • Ćwiczenia
5 Algorytmy rekurencyjne Algorytm rekurencyjny Rekurencja, zwana również rekursją, jest techniką programowania, w której stosowany jest podprogram (funkcja lub procedura) wywołujący sam siebie albo wywołujący inną procedurę, która wywoła podprogram pierwotny. W tym drugim przypadku mówimy o rekursji podwójnej lub skrośnej. Kolejne wywołania trwają, aż do osiągnięcia warunku zakończenia rekurencji. Jest nim oczekiwany wynik albo przekroczenie rozmiaru zbioru, na którym wykonywane są obliczenia. Liczba kolejnych wywołań rekursywnych nie ma znaczenia. Często jest wręcz niemożliwa do określenia przed rozpoczęciem przetwarzania danych, nie zawsze bowiem da się określić poziom zagłębienia w wywołania. Wynik aktualnie realizowanego obliczenia rekurencyjnego zależy od poprzedzającego go powtórzenia. Każde kolejne wywołanie powoduje zmniejszenie rozmiaru badanego zbioru (np. tablicy) o 1, dzięki czemu problem zostaje rozbity na części elementarne, które operują na mniejszej liczbie danych — są zatem mniej skomplikowane. Dopiero w momencie powrotu z wywołań wyznaczane są wszystkie poprzednie wartości.
116
Algorytmy • Ćwiczenia
Rekurencja wokół nas Postępowanie o charakterze rekurencyjnym trwale związane jest z wieloma czynnościami zachodzącymi w otaczającej nas rzeczywistości, choć często nie zauważamy tego lub nie jesteśmy świadomi. Można wskazać wiele przykładów czynności, które mają cechy rekursji, a są wykonywane przez człowieka, zwierzęta albo zaprogramowane automaty. Chodzenie i bieganie, tańczenie, jedzenie, masowe toczenie na tokarce, zbieranie rozsypanych przedmiotów, mycie, zrywanie owoców z drzewa itp. Równie często opisujemy słownie procesy, stosując język typowy dla rekursji. Instruując kogoś, jak należy myć stos talerzy, mówimy: „Umyj talerz do czysta i myj dalej”. Tłumacząc, jak ułożyć na półce rozsypane na podłodze książki, powiemy: „Podnieś książkę, ustaw na półce i podobnie układaj kolejne”. Ten schemat postępowania jest przedstawiony graficznie na rysunku 5.1. W obu przykładach czynność jest powtarzana. Różne są jednak warunki zakończenia rekurencji. W pierwszym przykładzie koniec powinien nastąpić, gdy talerze są czyste, w drugim — gdy braknie książek do ustawiania.
Rysunek 5.1. Model rekurencyjnego układania książek na półce
Funkcja silnia Zgodnie z obietnicą daną w poprzednim rozdziale wracamy do funkcji silnia. Tym razem poznamy algorytm i rekurencyjne wersje programów wykonujących stosowne obliczenia. Ć W I C Z E N I E
5.1
Algorytm rekurencyjnego obliczania n!
Przedstaw w postaci schematu blokowego rekurencyjny algorytm obliczania silni n!, n∈N. Dokonaj analizy przepływu w algorytmie dla n = 3.
Rozdział 5. • Algorytmy rekurencyjne
117
Rozwiązanie Dane: Liczba naturalna n wprowadzona przez użytkownika, równa ostatniemu wyrazowi iloczynu. Oczekiwany wynik: Wartość funkcji n!. Analiza problemu: Definicja silni n! liczby naturalnej n wystąpiła w poprzednim rozdziale w ćwiczeniu 4.4. Z definicji klasycznej n! = 1 · 2 · 3 · … · n wynika własność silni n! = n(n – 1)!, która pozwala określić tę funkcję w postaci rekurencyjnej:
⎧0!= 1 ⎨ ⎩n!= n ⋅ (n − 1)! Obliczenie kolejnej wartości n! następuje poprzez pomnożenie wartości poprzedniej (n – 1)! przez następną liczbę naturalną n. Tak zdefiniowana rekurencja nazywana jest liniową. Proces obliczeniowy powinien być powtarzany, aż n osiągnie wartość zadaną przez użytkownika. Na podstawie powyższego można zapisać w innej formie rekurencyjną definicję funkcji silnia: ⎧a 0 = 1 ⎨ ⎩a n = a n−1 ⋅ n,
n∈ N
Algorytm przedstawiony na rysunku 5.2 składa się z dwóch części: algorytmu (programu) głównego i podprogramu realizującego rekurencyjne obliczanie funkcji silnia. Powyższy algorytm można próbować scalić, co pokazuje rysunek 5.3. W tej formie rekurencyjny algorytm obliczania silni występuje w literaturze najczęściej. Niestety obarczony jest poważnym błędem, jakim jest wczytywanie wartości n przy każdym kolejnym odwołaniu rekurencyjnym! Ten algorytm nie działa prawidłowo.
Analiza przepływu w rekurencyjnym algorytmie obliczania silni W algorytmie z rysunku 5.2 stosowane są dwie zmienne: n — liczba naturalna wprowadzona przez użytkownika (dana wsadowa), Silnia — wartość funkcji silnia. Zapis z użyciem nawiasu: Silnia(argument) oznacza wartość funkcji dla podanego argumentu, na przykład Silnia(2) oznacza wartość funkcji silnia dla n = 2.
118
Algorytmy • Ćwiczenia
Rysunek 5.2. Rekurencyjny algorytm obliczania silni: a) program główny, b) podprogram rekurencyjnego obliczania silni Rysunek 5.3. Błędny algorytm obliczania silni bez użycia podprogramu
Rozdział 5. • Algorytmy rekurencyjne
119
Algorytm główny z rysunku 5.2 a ma postać schematu sekwencyjnego, łatwego do analizy i zrozumienia. Rozpoczyna się od wczytania wartości n. W kolejnym bloku wywoływany jest podprogram Silnia, któremu jest przekazywana wczytana liczba naturalna. Po dokonaniu obliczeń następuje powrót z podprogramu, a wynik jest wyświetlany na ekranie. Cała złożoność obliczeniowa algorytmu przeniesiona jest do podprogramu przedstawionego na rysunku 5.2 b. Oto, jak działa algorytm z rysunku 5.2 b dla n = 3: T Wraz z wywołaniem funkcji Silnia jest do niej przekazywany
argument n = 3. Ponieważ 3 jest różne od 0, wynikiem komparacji w bloku warunkowym jest odpowiedź negatywna. Zgodnie z formułą podaną w klatce wykonawczej funkcja przyjmuje, że jej wynikiem jest 3*Silnia(2). Jednak Silnia(2) nie jest znana, więc następuje chwilowe wstrzymanie obliczania wyrażenia 3*Silnia(2) oraz uruchomienie (wywołanie) algorytmu dla n = 2. T Algorytm wywołał sam siebie z argumentem n = 2. Obliczana
jest wartość Silnia(2). Ponieważ 2 > 0, odpowiedzią w bloku warunkowym jest ponownie NIE. Podprogram uruchomi Silnia(1) i pomnoży ją przez dwa. Wartość wyniku cząstkowego Silnia(1) jest nieznana, dlatego następuje wstrzymanie obliczania wartości 2*Silnia(1) i ponowne odwołanie do tej samej procedury rekurencyjnej z argumentem n = 1. T Dla przekazanego argumentu n = 1 nadal nie jest spełniony
warunek n = 0 i odpowiedzią komparatora jest NIE. Silnia(1) odwoła się zatem do kolejnej instancji podprogramu rekurencyjnego — uruchomi Silnia(0) i pomnoży ją przez jeden. Ponieważ wartość wyrażenia Silnia(0) w tym odwołaniu nie jest znana, obliczanie 1*Silnia(0) zostaje wstrzymane, a podprogram rekurencyjny wykonuje swą kolejną bliźniaczą kopię z argumentem równym zero. T Uruchomiony po raz kolejny podprogram wykonywany jest
dla n = 0 i obliczana jest Silnia(0). Wynikiem porównania argumentu z zerem jest odpowiedź twierdząca. Wykonywany jest blok, w którym Silnia(0) przyjmuje wartość 1.
120
Algorytmy • Ćwiczenia
T Skoro znany jest wynik Silnia(0), może już nastąpić powrót
z wywołań i obliczenie rzeczywistych wartości iloczynów. Znana już wartość Silnia(0) = 1 zostaje przekazana do instancji ją wywołującej i wówczas Silnia(1) = 1 · 1 = 1, analogicznie Silnia(2) = 2 · 1 i przyjmuje wartość dwa. Cofając się ponownie, otrzymujemy Silnia(3) = 3 · 2, co daje wynik końcowy równy 6, a to właśnie 3! = 1 · 2 · 3. Zapamiętaj! Wywoływanie kolejnych, bliźniaczych egzemplarzy podprogramu trwa dopóty, dopóki dla pewnego argumentu istnieje konkretny wynik cząstkowy.
W naszym algorytmie jest to wartość argumentu n = 0.
Poziomy i zagłębianie się Każde kolejne wywołanie rekurencyjne odbywa się dla argumentu o 1 mniejszego niż w poprzednim egzemplarzu procedury rekurencyjnej. Każda wywołana instancja podprogramu rekurencyjnego nazywana jest poziomem. Kolejne poziomy identyfikowane są poprzez numer równy wartości n. Poziom 0 oznacza elementarny egzemplarz procedury rekurencyjnej, podczas wykonania której uzyskuje się jednoznaczny wynik. Dopiero w chwili powrotu z wywołań obliczane są wyniki rzeczywiste. Z poziomu 0 wynik cząstkowy przekazywany jest na kolejne wyższe poziomy: poziom 1, poziom 2 itd. Wywoływanie kolejnych rekurencyjnych egzemplarzy podprogramu nazywane jest zagłębianiem się z poziomu n na poziom n – 1. Przekazywanie informacji (danych wsadowych i wyników cząstkowych) odbywa się za pomocą pamięci komputerowej zwanej stosem. Więcej na ten temat znajduje się w uwagach końcowych do tego rozdziału. Działanie opisanego powyżej algorytmu rekurencyjnego obliczającego Silnia(3) przedstawia rysunek 5.4. Na rysunku 5.4 strzałka pionowa oznacza zagłębianie się algorytmu z poziomu wyższego na poziom niższy. Strzałka ukośna oznacza przekazanie wyniku cząstkowego z poziomu niższego na wyższy.
121
Rozdział 5. • Algorytmy rekurencyjne
Rysunek 5.4. Drzewo wywołań rekurencyjnych i przekazywania wyniku cząstkowego przy obliczaniu Silnia(3) Ć W I C Z E N I E
5.2
Algorytm rekurencyjnego obliczania n!. Program w Pascalu
Wykorzystując algorytm z ćwiczenia 5.1, napisz rekurencyjny program w Turbo Pascalu, który obliczy i wyświetli wartość funkcji n!, dla n∈N.
Rozwiązanie 1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając z paska
menu polecenia File/New. 2. W oknie edycyjnym wpisz kod z listingu 5.1 albo wczytaj program z pliku cw5_2.pas znajdującego się w katalogu TP/Rozdz_05. Rezultat powinien być identyczny jak na rysunku 5.5. Listing 5.1. Kod rekurencyjnego programu obliczającego wartość silni program cw5_2; { Program oblicza wartosc silni n!, { stosujac funkcje zdefiniowana rekurencyjnie.
} }
122
Algorytmy • Ćwiczenia
Rysunek 5.5. Okno edycyjne TP z kodem rekurencyjnego programu obliczania n! { Deklaracja zmiennej uzywanej w programie: { n - ostatni wyraz iloczynu n! var n : Integer;
} }
{ -- Deklaracja i kod funkcji rekurencyjnej Silnia -- } function Silnia (n : Integer): Longint; begin if n = 0 then Silnia := 1 else Silnia := n * Silnia (n-1); end; { ----------------- Koniec funkcji Silnia ---- }
Rozdział 5. • Algorytmy rekurencyjne
123
{ ---- Program glowny ------------------------------- } begin writeln; writeln (' Rekurencyjne obliczanie wartosci n! '); writeln ('-------------------------------------'); writeln; write (' n = '); readln (n); writeln (' Podaje wynik obliczen:'); writeln (' ', n, '! = ', Silnia(n)); readln; end.
Symbole i nazwy użyte w programie są identyczne jak w algorytmie z rysunku 5.2, dzięki czemu jego zrozumienie nie powinno sprawić kłopotu. W razie wątpliwości proszę jeszcze raz przeanalizować przykład poprzedni. Najistotniejszym fragmentem programu jest rekurencyjna funkcja użytkownika o nazwie Silnia. Blok instrukcji ją tworzących funkcję rozpoczyna się deklaracją w postaci: function Silnia (n : Integer): Longint. Argument funkcji n jest liczbą całkowitą wprowadzaną przez użytkownika, a jej wynik jest typu Longint. Funkcja wywoływana jest w głównym torze programu. Służy do tego komenda Silnia(n), umieszczona w linii organizującej sposób wyświetlenia wyniku w postaci writeln (n, ‘! = ‘, Silnia(n)). Wywołana funkcja działa zgodnie z przepływem na schemacie z rysunku 5.2 b. Obliczenia rekurencyjne zostały zrealizowane za pomocą bloku warunkowego. Jeżeli n > 0, to wykonywana jest instrukcja rekursyjna Silnia := n * Silnia (n-1). Kolejne odwołania trwają tak długo, aż argument funkcji zyska wartość równą zero. Oznacza to, że został osiągnięty poziom zerowy zagłębienia w podprogram. Uzyskany na tym poziomie wynik cząstkowy jest konkretną liczbą i może być przekazany na poziom wyższy, gdzie następują kolejne obliczenia. Na najwyższym poziomie n obliczana jest wartość stanowiąca wynik końcowy wyświetlany na ekranie (rysunek 5.6).
124
Algorytmy • Ćwiczenia
Rysunek 5.6. Efekt wykonania programu cw5_2 Ć W I C Z E N I E
5.3
Aplikacja rekurencyjnego obliczania silni w Excelu
Napisz w Excelu aplikację obliczającą rekurencyjnie silnię n!. W tym celu utwórz funkcję użytkownika działającą według algorytmu z rysunku 5.2 b.
Rozwiązanie 1. Uruchom program Excel i zapisz domyślnie pojawiający się
2. 3. 4.
5.
Zeszyt1 w wybranym przez siebie katalogu pod nazwą cw5_3. Można również wczytać arkusz cw5_3.xls z katalogu EX/Rozdz_05. Zmień nazwę zakładki Arkusz1 na Silnia. Usuń zakładki Arkusz 2 i Arkusz3. W komórce C2 umieść tekst: Aplikacja rekurencyjnego obliczania silni n!. Proponowana czcionka: Arial CE, pogrubiona, w kolorze niebieskim, rozmiar 18. Wprowadź funkcję przeliczeniową Silnia. W tym celu: T Wywołaj okno edytora VBE i wstaw moduł standardowy Module1 (Moduł1). T W sekcji General (Ogólne) modułu Module1 (Moduł1) wpisz kod z listingu 5.2. Powinieneś uzyskać efekt jak na rysunku 5.7.
Rozdział 5. • Algorytmy rekurencyjne
125
Listing 5.2. Funkcja użytkownika Silnia w ćwiczeniu cw5_3 Function Silnia(n As Integer) As Long 'Funkcja rekurencyjnego obliczania wartości n! If n = 0 Then Silnia = 1 Else Silnia = n * Silnia(n - 1) End If End Function
Rysunek 5.7. Wygląd okna edytora VBE z wpisaną funkcją Silnia
Wprowadzona funkcja jest bliźniaczo podobna do funkcji utworzonej w ćwiczeniu poprzednim. Działa również identycznie. Jedynie znaczniki początku i końca nieco się od siebie różnią. 6. Dokończ budowę tabeli arkusza, wykonując podane poniżej polecenia: T We wskazanych komórkach arkusza umieść nagłówki:
126
Algorytmy • Ćwiczenia T T T
komórka C6 — n, komórka D6 — n!, komórka C7 — wpisz liczbę 4.
Proponowana czcionka: Arial CE, normalna, rozmiar 10. Wyrównaj do prawej zawartość C6:D6 oraz podkreśl komórki stylem Krawędź dolna. T Wpisz w komórce D7 formułę wywołującą funkcję: =SILNIA(C7). Możesz również skorzystać z menu Wstaw, kliknąć polecenie Funkcja…i wybrać funkcję użytkownika o nazwie Silnia. Jako jej argument należy podać komórkę C7. T Wyłącz siatkę arkusza. Zakończyłeś tworzenie arkusza, który powinien mieć wygląd jak na rysunku 5.8.
Rysunek 5.8. Arkusz aplikacji cw5_3
Sprawdź działanie aplikacji. Poeksperymentuj, zmieniając wartości w komórce C7, a następnie zakończ pracę z arkuszem i Excelem, wybierając Plik oraz Zakończ.
Rozdział 5. • Algorytmy rekurencyjne
127
Obliczanie potęgi liczby rzeczywistej Zagadnienie obliczania potęg zostało już zasygnalizowane w ćwiczeniu 2.1 podczas omawiania algorytmów sekwencyjnych. Rozważania dotyczyły jednak tylko potęg z wykładnikiem parzystym. Obecnie zostanie przedstawiona rekurencyjna metoda obliczania wartości potęgi o dowolnym wykładniku. Przykład zobrazuje jednocześnie, jak w jednym podprogramie użyć dwóch instrukcji rekurencyjnych. Ć W I C Z E N I E
5.4
Rekurencyjne obliczanie potęgi liczby rzeczywistej
Przedstaw w postaci listy kroków rekurencyjny algorytm funkcji obliczającej potęgę an, gdzie a∈R, n∈N.
Rozwiązanie Dane: Wartość podstawy a∈R oraz potęgi n∈N. Oczekiwany wynik: Wartość podstawy (argumentu) a podniesionej do potęgi n. Analiza problemu: Potęgowanie rekurencyjne bazuje na podnoszeniu liczby do kwadratu. Dla n = 1 wynikiem obliczeń jest wartość podstawy a. Dla n > 1 pierwsze działanie zależy od tego, czy wykładnik jest parzysty, czy nie: T Jeżeli wykładnik jest liczbą naturalną parzystą, to doprowadza się go do takiej postaci, by występowało potęgowanie wewnętrzne i zewnętrzne o wykładniku 2, na przykład 34 = (32)2, 210 = (25)2. Dla dowolnej parzystej liczby n, zapis ten ma postać: n
a n = (a 2 ) 2 . T Jeżeli wykładnik jest nieparzysty większy od jedności,
to wyodrębnia się fragment z potęgą parzystą i otrzymany wynik pośredni mnoży się przez podstawę a, na przykład 39 = 38 · 3. Dla dowolnej liczby nieparzystej n, zapis ten ma postać: a n = a n−1 a .
128
Algorytmy • Ćwiczenia
Teraz wykładnik n – 1 we wzorze jest już parzysty, zatem potęgowanie można zapisać w postaci: a n = (a
n −1 2
)2 a .
Operacje redukowania należy powtarzać tak długo, aż wszystkie działania w wyrażeniu otrzymają opisaną wyżej postać. Obrazują to przykłady: 39 = 38 · 3 = (34)2 · 3 = ((32)2)2 · 3, 714 = (77)2 = (76 · 7)2 = ((73)2 · 7)2 = ((72 · 7)2 · 7)2. Skoro za każdym razem istotna jest informacja, czy podstawa jest parzysta, czy nieparzysta, to w algorytmie musi wystąpić fragment, który sprawdza parzystość wykładnika. W tym celu wystarczy podzielić liczbę będącą wykładnikiem przez 2. Jeżeli reszta z dzielenia równa jest zero, to wykładnik jest podzielny przez 2, a reszta ma wartość zero. Drugim stałym elementem w zredukowanych wyrażeniach jest podnoszenie do kwadratu. Warto tę operację zrealizować za pomocą odrębnej funkcji, do której przekazuje się odpowiedni argument. Po uwzględnieniu parzystości i dokonaniu redukcji wykładnika według reguł podanych powyżej otrzymujemy zależność klamrową w postaci: ⎧a, ⎪ n ⎪ a n = ⎨(a 2 ) 2 , ⎪ n−1 ⎪⎩(a 2 ) 2 a,
dla n = 1
(5.1)
n jest liczbą parzystą
(5.2)
n jest liczbą nieparzystą
(5.2)
Algorytm w postaci listy kroków Zakładamy, że tworzymy dwuargumentową funkcję o nazwie Potega, do której przekazywane są następujące argumenty: podstawa — dowolna liczba rzeczywista a∈R, wykładnik — liczba naturalna n∈N. Postać funkcji rekurencyjnej jest zatem dwuargumentowa: Potega(a, n). Funkcja ta wywoływana jest każdorazowo, gdy wystąpi w algorytmie. Krok 1. Sprawdź, czy n = 1. Jeżeli tak, to podstaw Potega = a, po czym przejdź do kroku 7. Jeżeli nie, to przejdź do kroku 2. Krok 2. Sprawdź, czy reszta z dzielenia wykładnika n przez 2 jest równa zero. Jeżeli tak, to przejdź do kroku 3. Jeżeli nie, to przejdź do kroku 5.
Rozdział 5. • Algorytmy rekurencyjne
129
Krok 3. {Wykładnik jest liczbą parzystą.} Przypisz n = n/2 i przejdź do kroku 4. Krok 4. {Obliczanie potęgi liczby a zgodnie ze wzorem (5.2) z zależności klamrowej podanej powyżej}. Wywołaj funkcję rekurencyjną Potega(a, n), a następnie podnieś ją do kwadratu: Potega = (Potega (a, n))2. Przejdź do kroku 7. Krok 5. {Wykładnik jest liczbą nieparzystą.} Podstaw n = (n – 1)/2 i przejdź do kroku 6. Krok 6. {Obliczanie potęgi liczby a zgodnie ze wzorem (5.3) z zależności klamrowej.} Wywołaj funkcję Potega(a, n), po czym podnieś ją do potęgi drugiej i pomnóż przez podstawę a: Potega = (Potega(a, n))2*a. Przejdź do kroku 7. Krok 7. Zakończ działanie algorytmu. Wynikiem jest bieżąca wartość Potega. Sprawdź — wykonując obliczenia na papierze — poprawność algorytmu dla wybranych wartości a oraz n. Ć W I C Z E N I E
5.5
Algorytm rekurencyjnego obliczania potęgi. Program w Turbo Pascalu
Napisz w Turbo Pascalu program rekurencyjnego obliczania potęgi naturalnej dowolnej liczby rzeczywistej. W programie wykorzystaj funkcję zbudowaną z wykorzystaniem algorytmu przedstawionego w ćwiczeniu 5.4. Podnoszenie do kwadratu wykonaj za pomocą funkcji elementarnej Sqr.
Rozwiązanie Funkcja zrealizowana według opisu podanego w algorytmie z ćwiczenia 5.4 nie zawiera bloku wprowadzania danych i wyświetlania wyniku. Odpowiednie, umożliwiające to instrukcje muszą znaleźć się w programie głównym, z którego nastąpi wywołanie funkcji potęgującej.
130
Algorytmy • Ćwiczenia
1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając
z paska menu polecenia File/New. 2. W oknie edycyjnym wpisz kod z listingu 5.3 albo wczytaj
program z pliku cw5_5.pas znajdującego się w katalogu TP/Rozdz_05. Listing 5.3. Kod rekurencyjnego programu obliczającego wartość naturalnej potęgi liczby rzeczywistej program cw5_5; { Program oblicza rekurencyjnie wartosc { liczby a podniesionej do potegi n. { Deklaracja zmiennych uzywanych w programie: { a - liczba potegowana, n - wykladnik potegi. var a: Real; n: Integer;
} } } }
{ ---- Deklaracja i kod funkcji rekurencyjnej Potega ------ } function Potega (a: Real; n : Integer): Real; begin if n = 1 then Potega := a else if (n mod 2 = 0) then begin n := n div 2; Potega := Sqr( Potega(a, n)); end else begin n := (n - 1) div 2; Potega := Sqr(Potega(a, n)) * a; end end; { ----------------- Koniec funkcji Potega ---- } { ---- Program glowny ------------------------------- } begin writeln; writeln (' Rekurencyjne obliczanie potegi podanej liczby '); writeln ('-----------------------------------------------'); writeln;
Rozdział 5. • Algorytmy rekurencyjne
131
write (' Podstawa a = '); readln (a); write (' Wykladnik n = '); readln (n); writeln; writeln (' Wynik obliczen: '); writeln (' ', a:0:2, ' do potegi ', n, ' = ', Potega(a,n):0:2); readln; end.
Funkcja rekurencyjna Potega występująca w listingu 5.3 jest dokładnym odwzorowaniem algorytmu i tak też działa. Do podnoszenia do kwadratu służy funkcja wbudowana Sqr(argument), która oblicza kwadrat podanego w nawiasie argumentu. Sprawdzenie parzystości liczby dokonywane jest w instrukcji warunkowej przy wykorzystaniu instrukcji mod o składni: n mod 2. Wynikiem tej operacji jest reszta z dzielenia liczby całkowitej n przez 2. Rezultat zero oznacza, że n jest podzielne przez 2 — jest zatem liczbą parzystą i wykonywany jest blok instrukcji po słowie kluczowym then. W przypadku n nieparzystego program wykonuje polecenia po słowie else. Iloraz w podprogramie obliczany jest za pomocą funkcji div, która realizuje dzielenie całkowite liczb całkowitych. Oznacza to, że nie występuje reszta z dzielenia, na przykład 7 div 4 = 1. Wynik dzielenia jest przypisywany argumentowi n, który jest liczbą naturalną. Główny tor programu to deklaracja zmiennych oraz wczytanie danych: podstawy a i wykładnika n. Potem wywoływana jest dwuargumentowa funkcja Potega(a, n). Wywołanie następuje bezpośrednio z linii wyprowadzającej wyniki na ekran: writeln (a:0:2, ‘ do potegi ', n, ' = ', Potega(a,n):0:2). Sposób wyświetlania danych i rezultatu obliczeń — z dwoma miejscami dziesiętnymi — można oczywiście dostosować według uznania. Efekt wykonania programu przedstawia rysunek 5.9. Ć W I C Z E N I E
5.6
Algorytm rekurencyjnego obliczania potęgi. Aplikacja w Excelu
Napisz w Excelu program rekurencyjnego obliczania potęgi naturalnej dowolnej liczby rzeczywistej. W programie wykorzystaj funkcję użytkownika zbudowaną z wykorzystaniem algorytmu przedstawionego w ćwiczeniu 5.4.
132
Algorytmy • Ćwiczenia
Rysunek 5.9. Efekt wykonania programu cw5_5
Rozwiązanie 1. Uruchom program Excel i zapisz domyślnie pojawiający się
2. 3. 4.
5.
Zeszyt1 w wybranym przez siebie katalogu pod nazwą cw5_6 albo wczytaj arkusz cw5_6.xls z katalogu EX/Rozdz_05. Zmień nazwę zakładki Arkusz1 na Potęgowanie. Usuń zakładki Arkusz 2 i Arkusz3. W komórce C2 umieść tekst — Aplikacja rekurencyjnego obliczania potęgi. Proponowana czcionka: Arial CE, pogrubiona, w kolorze fioletowym, rozmiar 18. Utwórz funkcję przeliczeniową Potega. W tym celu: T Wywołaj okno edytora VBE i wstaw moduł standardowy Module1 (Moduł1). T W sekcji General (Ogólne) modułu Module1 (Moduł1) wpisz kod z listingu 5.4, tak jak przedstawia to rysunek 5.10.
Listing 5.4. Kod funkcji Potega z ćwiczenia 5.6 Function Potega(a, n) 'Funkcja potęgowania rekurencyjnego. 'Znaczenie argumentów a - podstawa, n - wykładnik. If n = 1 Then Potega = a Else If (n Mod 2) = 0 Then
Rozdział 5. • Algorytmy rekurencyjne
133
Rysunek 5.10. Edytor VBE z kodem funkcji Potega. Po lewej widoczne jest okno eksploratora, a poniżej okno właściwości budowanego arkusza Potęgowanie 'Wykładnik n jest parzysty. n = n / 2 Potega = Potega(a, n) Potega = Potega * Potega Else 'Wykładnik n jest nieparzysty. n = (n - 1) / 2 Potega = Potega(a, n) Potega = Potega * Potega * a End If End If End Function
Działanie funkcji jest identyczne jak w ćwiczeniu poprzednim. Niewielkie różnice w kodzie polegają na innym zorganizowaniu podnoszenia do kwadratu (mnożenie przez siebie) oraz na zastosowaniu zwykłego operatora dzielenia (/). 6. Dokończ budowę arkusza, tworząc tabelę przeliczeniową: T We wskazanych komórkach arkusza umieść nagłówki:
134
Algorytmy • Ćwiczenia
komórka C4 — Podstawa a, T komórka E4 — Wykładnik n, T komórka G4 — an; sformatuj literę n jako Indeks górny, T komórka C5 — 2, T komórki E5:E14 — wprowadź kolejne liczby naturalne od 1 do 10. T Zmień szerokość kolumn C, E, G na 85 pikseli. T Podkreśl komórki arkusza C4, E4 i G4 stylem Gruba krawędź dolna,. Zmień kolor tekstu w komórkach na zielony, po czym go wyśrodkuj. 7. W komórce G5 wpisz formułę przeliczeniową — =Potega ($C$5;E5), a następnie skopiuj ją do komórek G6:G14. T
Znak ($) oznacza adresowanie bezwzględne (absolutne) — podczas kopiowania formuły adres komórki C5, do której odwołuje się formuła, nie ulegnie zmianie. W formule występuje też odwołanie względne, które we wklejanej formule jest aktualizowane i dotyczy innych komórek względem położenia formuły. W naszej funkcji są to kolejne komórki z kolumny E, poczynając od E5. 8. Tworzenie arkusza zostało zakończone. Efekt widoczny jest na rysunku 5.11. 9. Poeksperymentuj z wartościami podstawy a oraz wykładnika n, zmieniając wartości w odpowiednich komórkach, a następnie zakończ pracę z arkuszem i Excelem, wybierając Plik oraz Zakończ.
Uwagi końcowe Mocne i słabe strony rekurencji Zalety programów realizowanych rekurencyjnie: T pozwalają rozwiązywać problemy o dowolnej rozpiętości zbioru i trudne do opisu, T zwięzłość i elegancja zapisu.
Rozdział 5. • Algorytmy rekurencyjne
135
Rysunek 5.11. Arkusz Potęgowanie z aplikacji cw5_6
Niestety, są też poważne wady. Zaliczamy do nich: T powielanie tych samych obliczeń, T niejasny i trudny do analizy przebieg wywołań, T niebezpieczeństwo nieskończonej liczby odwołań, T duże zapotrzebowanie na pamięć podczas przetwarzania.
Niedogodności są spowodowane głównie tym, że po każdym odwołaniu rekurencyjnym zachodzi konieczność zapamiętania informacji potrzebnych do odtworzenia stanu procesu sprzed wywołania. Zapamiętywane informacje przechowywane są w obszarze pamięci zwanym stosem.
Stos Stos (ang. stack) to obszar wewnętrznej pamięci komputerowej przeznaczonej do czasowego przechowywania informacji związanych z wykonywanym programem. Dla rekurencji istotne jest, by stos
136
Algorytmy • Ćwiczenia
posiadał strukturę LIFO (akronim z ang. Last In First Out). W dosłownym tłumaczeniu oznacza ostatni na wejściu jest pierwszym na wyjściu. Komputer odzyskuje potrzebne do wykonania programu informacje, pobierając je z wierzchołka stosu. Żądany element lokalizowany jest dzięki rejestrowi zwanemu wskaźnikiem stosu (ang. stack pointer), który jest powiększany o 1 każdorazowo przed umieszczeniem kolejnego elementu na stosie i dekrementowany o 1 po zdjęciu elementu ze stosu. Łatwo zauważyć, że gdy wskaźnik ma wartość zero, to stos jest pusty. Stos jest obszarem pamięci o ograniczonej pojemności, dlatego łatwo może dojść do jego przepełnienia. Podczas rekursji zdarza się to nader często i wywołuje błąd, który sygnalizowany jest komunikatem stack overflow (z ang. przepełnienie stosu). Działanie stosu obrazuje rysunek 5.12.
Rysunek 5.12. Poglądowa struktura stosu obrazująca: a) dodanie informacji, b) przechowanie informacji, c) pobranie informacji ze stosu
Z rysunku widać, że stos ma strukturę studni. Dane umieszczane są zawsze na szczycie stosu i stąd też pobierane. Informacja wprowadzona jako pierwsza zostanie pobrana jako ostatnia.
Rozdział 5. • Algorytmy rekurencyjne
137
Ćwiczenia do samodzielnego wykonania Ć W I C Z E N I E
5.7 Ułóż algorytm obliczania sumy kolejnych liczb naturalnych. Ć W I C Z E N I E
5.8 Sprawdź, czy w podprogramie z listingu 5.3 można zastosować zwykły operator dzielenia (/). Czy instrukcję n := (n - 1) div 2 można zastąpić poleceniem n := n div 2? Jak to wyjaśnić? Ć W I C Z E N I E
5.9 Przedstaw algorytm z ćwiczenia 5.4 w postaci schematu blokowego. Ć W I C Z E N I E
5.10 Ułóż algorytm obliczania pierwiastka stopnia n z podanej liczby, a następnie zakoduj go w Turbo Pascalu i Visual Basicu.
138
Algorytmy • Ćwiczenia
6 Schemat Hornera. Obliczanie wartości wielomianu Schemat Hornera Schemat Hornera to zoptymalizowany algorytm obliczania wartości wielomianu dowolnego stopnia dla podanej wartości argumentu. Optymalizacja polega na zminimalizowaniu liczby działań potrzebnych do uzyskania wyniku. Chodzi głównie o zmniejszenie liczby mnożeń, które zajmują procesorowi najwięcej czasu, a za pomocą których wykonywane jest podnoszenie argumentu do potęgi. Schemat Hornera to również specjalna postać zapisu wielomianu. Jest ona podana w dalszej części rozdziału.
Wielomian stopnia n Wielomianem W(x) stopnia n zmiennej rzeczywistej x nazywamy funkcję postaci:
W ( x) = an x n + an −1 x n −1 + an − 2 x n − 2 + ... + a1 x + a0 , gdzie : an ≠ 0 . (6.1)
140
Algorytmy • Ćwiczenia
Współczynniki wielomianu ai∈R, dla i∈N, występujące przy niewiadomej x, mogą przyjmować dowolne wartości rzeczywiste. Wielomian, w którym wszystkie współczynniki oprócz a0 są równe zero, nazywamy wielomianem stopnia zerowego. Wielomian taki jest liczbą, niezależnie od wartości zmiennej x, W(x) = a0. Każdy wielomian stopnia n można przedstawić jako sumę lub różnicę co najwyżej n + 1 jednomianów w postaci aixi, gdzie i∈N, n > i. Jednomian w postaci a0 jest jednomianem stopnia zerowego. Ć W I C Z E N I E
6.1
Obliczanie wartości wielomianu i liczby wykonywanych działań
Nie dokonując przekształceń funkcji W(x) oraz korzystając z kalkulatora czterodziałaniowego (mnożenie, dzielenie, dodawanie, odejmowanie), oblicz wartość wielomianu:
W ( x) = x 4 − 3x 3 + 5 x 2 − x + 1 , dla x = –2. Podaj liczbę działań, które należy wykonać, by otrzymać wynik.
Rozwiązanie Dane: Postać wielomianu i wartość argumentu x = –2. Oczekiwany wynik: Liczba rzeczywista. Analiza problemu: Obliczanie wartości wielomianu polega na podstawieniu liczby w miejsce argumentu, a następnie wykonaniu zadanych działań. Kolejność działań musi być zgodna z porządkiem określonym w matematyce. Z tematu zadania wynika, że kalkulator nie posiada opcji potęgowania, dlatego podnoszenie do potęgi należy wykonać poprzez wielokrotne mnożenie, na przykład x3 = x · x · x. Obliczenie wartości: W (−2) = 1(−2) 4 − 3(−2) 3 + 5(−2) 2 − 1(−2) + 1 = 16 + 24 + 20 + 2 + 1 = 63.
Podczas obliczeń zostało wykonanych 10 mnożeń (6 mnożeń wynikających z potęgowania i 4 mnożenia przez współczynniki wielomianu) oraz 4 operacje dodawania lub odejmowania. Łączna liczba działań w tym wielomianie wynosi 14.
R o z d z i a ł 6 . • S c h e m a t H o r n e r a . O b l i c z a n i e w a r t oś c i w i e l o m i a n u
141
Liczba działań w klasycznie zapisanym wielomianie stopnia n Załóżmy, że wszystkie współczynniki wielomianu (6.1) są różne od zera. Zatem rozważany wielomian składa się z n + 1 jednomianów postaci aixi, gdzie i∈N+. W każdym takim jednomianie należy wykonać i mnożeń: i – 1 wynikających z potęgowania oraz jedno mnożenie przez współczynnik ai. Liczba mnożeń w następujących po sobie składnikach wielomianu stopnia n tworzy ciąg arytmetyczny o kolejnych wyrazach n, n – 1, … , 2, 1. Stosując wzór na sumę składników ciągu arytmetycznego, stwierdzamy, że liczba mnożeń M wynosi: M =
n(n + 1) . 2
Do tej liczby dochodzi n operacji dodawania albo odejmowania. Łączna, maksymalna liczba działań D w wielomianie stopnia n wynosi: D=
n(n + 1) n 2 + n 2n n 2 + 3n n(n + 3) +n= + = = . 2 2 2 2 2
Liczba działań zmniejsza się za każdym razem, gdy którykolwiek współczynnik wielomianu przyjmuje wartość zero.
Przekształcenie wielomianu do postaci Hornera Zanim dokonamy przekształcenia ogólnej postaci wielomianu W(x) określonego wzorem (6.1), rozważmy przykładowy wielomian przedstawiony w ćwiczeniu 6.1. Ć W I C Z E N I E
6.2
Przekształcanie postaci wielomianu
Dokonaj przekształcenia wielomianu z ćwiczenia 6.1, postępując według zasad: T wyłącz poza nawias argument x ze wszystkich jednomianów niezerowego stopnia, T podobnie dokonaj wyłączenia w każdym kolejno otrzymywanym nawiasie.
142
Algorytmy • Ćwiczenia
Policz, ile działań należy wykonać, by otrzymać wynik dla wybranej wartości x. Porównaj otrzymaną wartość z liczbą operacji wyznaczoną w rozwiązaniu ćwiczenia 6.1. Stosując otrzymany wzór, oblicz wartość wielomianu dla x = –2.
Rozwiązanie Wielomian: W ( x) = x 4 − 3x 3 + 5 x 2 − x + 1
można równoważnie zapisać w postaci H(x), dokonując wyłączenia argumentu poza nawias: H ( x) = ( x 3 − 3x 2 + 5 x − 1) x + 1 .
Postępując podobnie z występującym w nawiasie wielomianem stopnia trzeciego, otrzymamy:
H ( x) = (( x 2 − 3x + 5) x − 1) x + 1 . Wyłączając jeszcze dwukrotnie argument x, otrzymamy postać:
H ( x) = ((( x − 3) x + 5) x − 1) x + 1 , która nie jest jeszcze postacią końcową, ponieważ w najbardziej wewnętrznym nawiasie występuje jeszcze wielomian niezerowego stopnia 1x. Naturalnie, zgodnie z przyjętą w matematyce konwencją, współczynnika o wartości jeden najczęściej nie pisze się. Jednak on istnieje i podczas programowania wartość ta musi być również wprowadzona w formie danej wsadowej. Dokonujemy zatem kolejnego, już ostatniego wyłączenia:
H ( x) = ((((1) x − 3) x + 5) x − 1) x + 1 . Otrzymana postać nosi miano postaci Hornera. W tak zapisanym wielomianie do uzyskania wyniku potrzeba 8 działań: czterech iloczynów i czterech operacji sumy lub różnicy. W porównaniu z klasyczną metodą obliczania z ćwiczenia 6.1 łączna liczba działań zmniejszyła się o sześć — wszystkie z tych działań to operacje mnożenia. Zauważ, że liczba sum i różnic nie uległa zmianie. Zysk obliczeniowy jest olbrzymi!
R o z d z i a ł 6 . • S c h e m a t H o r n e r a . O b l i c z a n i e w a r t oś c i w i e l o m i a n u
143
Zapamiętaj! Bardzo często podczas określania liczby działań popełniany jest błąd polegający na nieuwzględnianiu mnożenia przez stałą o wartości jeden. Powodem jest to, że 1 to liczba neutralna dla mnożenia i wykonanie operacji mnożenia argumentu przez 1 nie zmienia jego wartości.
Tym razem obliczenie wartości można przeprowadzić krokowo, poczynając od najbardziej zagnieżdżonego nawiasu. Znajduje się w nim wartość 1. Przyjmijmy, że wartość ta jest wynikiem cząstkowym w, który mnożymy przez wartość argumentu i dodajemy –3 (odejmujemy 3). Otrzymaną liczbę można uznać za kolejny wynik cząstkowy i przypisać go zmiennej pomocniczej w. Ten schemat postępowania dla x = –2 można zapisać następująco: Krok 1: w 1 Krok 2: w w · (–2) + (–3) = –5 Krok 3: w w · (–2) + 5 = 15 Krok 4: w w · (–2) + (–1) = –31 Krok 5: w w · (–2) + 1 = 63 Zgodnie z oczekiwaniami uzyskany wynik jest identyczny z rezultatem z ćwiczenia 6.1. Uderza jednak powtarzalność wykonywania operacji — każdy wynik pośredni mnożony jest przez wartość argumentu, po czym dodawana jest wartość kolejnego współczynnika wielomianu. Podany schemat obliczeniowy przedstawiony jest na rysunku 6.1.
Rysunek 6.1. Graficzne zobrazowanie schematu obliczania wartości wielomianu
Strzałka ukośna symbolizuje mnożenie przez zadaną wartość argumentu (w naszym przykładzie –2) i dodanie wskazanego grotem współczynnika wielomianu. Strzałka pionowa obrazuje przypisanie wyznaczonej wartości do wyniku w.
144
Algorytmy • Ćwiczenia
Postać Hornera wielomianu stopnia n Postępując podobnie jak powyżej, dokonamy teraz przekształcenia wielomianu W(x) określonego wzorem 6.1:
W ( x) = an x n + an−1 x n−1 + an−2 x n−2 + ... + a1 x + a0 do postaci Hornera H(x) = W(x). Rozpoczynamy od wyłączenia argumentu poza nawias z pierwszych n składników (jednomianów) wielomianu: H ( x) = (a n x n −1 + a n −1 x n − 2 + a n − 2 x n −3 + ... + a1 ) x + a 0 .
((6.2)
Postępując analogicznie, dokonujemy kolejnego wyłączenia zmiennej x z wielomianu w nawiasie:
H ( x) = ((a n x n −2 + a n −1 x n −3 + a n− 2 x n −4 + ... + a 2 ) x + a1 ) x + a0 . Liczba przekształceń potrzebnych do pełnego zapisu wielomianu w postaci Hornera zależy od stopnia wielomianu. Po wielokrotnej przebudowie wielomianu otrzymujemy postać Hornera wielomianu stopnia n-tego: H ( x ) = ((...((a n x + a n −1 ) x + a n − 2 ) x + ... + a 2 ) x + a1 ) x + a0 .
((6.3)
Liczba działań w wielomianie stopnia n podanym w postaci Hornera Liczba działań, które należy wykonać w celu wyznaczenia wartości wielomianu dla zadanego argumentu x, jest sumą operacji mnożenia i dodawania. Proszę pamiętać, że liczba tych działań zależy nie tylko od stopnia wielomianu n, ale również od wartości współczynników wielomianu. Zmniejsza się zawsze, gdy współczynnik wielomianu ma wartość zero. Załóżmy, podobnie jak przy analizie postaci klasycznej, że wszystkie współczynniki wielomianu we wzorze 6.3 są różne od zera. Dzięki temu wyznaczymy maksymalną liczbę działań, jaką należy wykonać w celu obliczenia wartości wielomianu n-tego stopnia. W porównaniu z postacią klasyczną wielomianu liczba operacji sumowania nie uległa zmianie i wynosi n. Wielomian w postaci Hornera zawiera tylko n operacji mnożenia, dlatego łączna liczba działań prowadzących do wyniku równa jest 2n.
R o z d z i a ł 6 . • S c h e m a t H o r n e r a . O b l i c z a n i e w a r t oś c i w i e l o m i a n u
145
Obliczanie wartości wielomianu Ć W I C Z E N I E
6.3
Algorytm obliczania wartości wielomianu w punkcie
Wykorzystując postać Hornera wielomianu, ułóż w postaci schematu blokowego algorytm funkcji przeliczeniowej wyznaczającej wartość wielomianu w punkcie x. Przyjmij, że dane są wartości rzeczywistych współczynników wielomianu oraz jego stopień.
Rozwiązanie Dane: Liczba naturalna n określająca stopień wielomianu i rzeczywiste wartości współczynników wielomianu ai. Oczekiwany wynik: Liczba rzeczywista przypisana zmiennej pomocniczej. Analiza problemu: Obliczanie wartości wielomianu w punkcie x polega na wyznaczeniu jego wartości dla argumentu równego x. Zatem ta wartość musi być również wprowadzona przez użytkownika. Ponieważ temat zadania wymaga zaprojektowania jedynie funkcji przeliczeniowej, na schemacie nie muszą występować bloki wprowadzania danych i wyprowadzania wyniku. Te klatki należy umieścić w głównym torze algorytmu lub programu, co nie jest tematem ćwiczenia. Z postaci wielomianu określonych wzorami 6.2 i 6.3 wynika, że problem rozwiązać można zarówno metodą iteracyjną, jak i rekurencyjną. W dalszej części zostaną podane obydwa sposoby otrzymania wyniku. Rozpoczniemy od określenia funkcji, która pomoże w przeprowadzeniu obliczeń.
Definicja iteracyjna W hornerowskiej postaci wielomianu określonej wzorem 6.3 wprowadźmy jeszcze jeden nawias obejmujący współczynnik an H ( x) = ((...(((a n ) x + a n −1 ) x + a n − 2 ) x + ... + a 2 ) x + a1 ) x + a 0 .
((6.4)
146
Algorytmy • Ćwiczenia
Nawiasy występujące we wzorze narzucają jednoznacznie kolejność wykonywania działań. Najbardziej zagnieżdżoną wielkością we wzorze jest współczynnik wielomianu an i od niego należy rozpocząć definicję funkcji przeliczeniowej. Gdy n = 0, wówczas współczynnik ten jest równy a0. Jeśli przyjmiemy, że zmienna pomocnicza w przechowuje aktualny wynik, definicja iteracyjna funkcji obliczającej wartość wielomianu w punkcie x ma postać:
i=n ⎧an , w=⎨ ⎩wx + ai , i = n − 1,...,1, 0 W chwili rozpoczęcia obliczeń wartość zmiennej pomocniczej w jest równa najbardziej zagnieżdżonemu współczynnikowi an. Wartości w kolejnych, mniej zagnieżdżonych nawiasach wyliczane są w pętli poprzez zależność w = wx + ai, do której podstawiane są kolejne wartości współczynników wielomianu. Przy każdym powtórzeniu licznik pętli i jest dekrementowany o jeden. Iteracja kończy się, gdy i = 0. Iteracyjny algorytm, będący rozwiązaniem, przedstawia rysunek 6.2. Rysunek 6.2. Iteracyjny algorytm wyznaczania wartości wielomianu
R o z d z i a ł 6 . • S c h e m a t H o r n e r a . O b l i c z a n i e w a r t oś c i w i e l o m i a n u
147
Definicja rekurencyjna W celu wprowadzenia definicji rekurencyjnej prowadzącej do rozwiązania problemu wykorzystamy zależność 6.2, która jest pierwszym hornerowskim uproszczeniem klasycznego zapisu wielomianu. Wielomian opisany tym wzorem jest stopnia n, co sygnalizuje indeks w jego oznaczeniu — Hn(x). Wzór 6.2 przyjmie postać:
H n ( x) = (a n x n−1 + a n−1 x n− 2 + a n −2 x n−3 + ... + a1 ) x + a0 . Wielomian w nawiasie ma stopień o jeden niższy:
H n−1 ( x) = an x n−1 + an−1 x n− 2 + an− 2 x n −3 + ... + a1 . Operując wprowadzonymi rozróżnieniami, analizowaną postać wielomianu można przedstawić w postaci: H n ( x) = H n −1( x ) x + a 0
((6.5)
Zapis oznacza, że wartość wielomianu stopnia n można obliczyć, o ile znana jest wartość odpowiedniego wielomianu stopnia n – 1, a także wartość współczynnika a0. Gdy uwzględnimy powyższe uwagi i przyjmiemy, że tym razem użyjemy zmiennej pomocniczej h przechowującej aktualny wynik, definicja rekurencyjna funkcji obliczającej wartość wielomianu w punkcie x otrzyma postać: ⎧a n , hi ( x) = ⎨ ⎩hi −1 ( x) x + a n −i ,
i=0 0 Liczba 'Główny blok obliczeniowy Petla: 'Etykieta sygnalizująca początek pętli programowej. 'Obliczanie wagi i wartości ASCII cyfry w liczbie w = w / p a = Int(Liczba / w) 'oraz pozostałej wartości do przeliczenia. Liczba = Liczba - a * w
Rozdział 7. • Pozycyjne systemy liczbowe
205
'Sprawdzenie poprawności i ewentulane skorygowanie 'wartości cyfry w kodzie ASCII. If a > 9 Then a = a + 7 a = a + 48 'Zamiana wartości ASCII cyfry na jej alfanumeryczny odpowiednik. cyfra = Chr(a) 'Wyznaczenie, poprzez konkatenację, łańcucha tworzącego liczbę. c = c & cyfra 'Sprawdzanie warunków pętli, zakończenie działania funkcji 'albo powrót do miejsca oznaczonego etykietą Petla. If (w 1 Or Liczba > 0) Then GoTo Petla End Function
W kodzie funkcji nie ma deklaracji zmiennych. Dzięki temu jest wyraźnie mniej linijek kodu. Wprawdzie Excel pozwala na pisanie programów bez deklaracji (czego dowodem jest powyższy zapis), ale błąd w aplikacji pozbawionej deklaracji zmiennych jest niezmiernie trudny do zlokalizowania i poprawienia. T
W wybranych komórkach arkusza wpisz nagłówki, wartości i wywołanie przed chwilą utworzonej formuły. Tym razem nie zaproponuję żadnego formatowania. Proszę uczynić to samodzielnie. T
komórka C3 — Liczba decymalnie,
T
komórka D3 — Nowa podstawa,
T
komórka F3 — Wynik,
T
komórka C4 — wpisz 12,
T
komórka D4 — wpisz 2,
T
komórka F4 — wpisz wywołanie funkcji użytkownika =c(C4;D4).
To wszystko. Zauważ, że w komórce F4 pojawił się od razu wynik. Po zmianie wartości argumentów w komórkach C4 i D4 dwuargumentowa funkcja użytkownika c(argument1, argument2) natychmiast dokonuje przeliczeń. Efekt działania aplikacji ilustruje zrzut ekranowy z rysunku 7.13.
206
Algorytmy • Ćwiczenia
Rysunek 7.13. Efekt działania „oszczędnej” aplikacji cw7_11 Ć W I C Z E N I E
7.12
Algorytm przeliczania liczby poprzez wyznaczanie cyfr nowego systemu pozycyjnego w kolejności od najmniej do najbardziej znaczącej pozycji
Zmodyfikuj algorytm z ćwiczenia 7.9 tak, by wyznaczanie ciągu cyfr docelowego systemu pozycyjnego następowało w kolejności od najmniej do najbardziej znaczącej pozycji. Przyjmij dane analogiczne, jak w ćwiczeniu 7.9.
Rozwiązanie Dane: Liczba podana w systemie dziesiętnym oraz podstawa p docelowego systemu liczbowego. Oczekiwany wynik: Reprezentacja n-znakowa liczby w pozycyjnym systemie liczbowym o podstawie p.
Rozdział 7. • Pozycyjne systemy liczbowe
207
Analiza problemu: Każde wykonalne, posiadające wynik dzielenie dwóch liczb całkowitych A i B można przedstawić w postaci: A r =C+ , B B
((7.10)
gdzie: A — dzielna, B — dzielnik, C — część całkowita wyniku, r — reszta z dzielenia (0 r < B). Mnożąc obie strony wzoru 7.10 przez mianownik, otrzymujemy:
A=C⋅B+r . Na przykład:
11 3 = 2+ 4 4
((7.11) ⇒
11 = 2 ⋅ 4 + 3 .
Wzór 7.11 ma identyczną postać jak omawiany wcześniej wzór 7.5:
Ln = Ln−1 p + c0 , który po podzieleniu przez podstawę p przyjmuje postać: Ln c = Ln −1 + 0 . p p
((7.12)
Porównując wzory 7.5 i 7.11 (albo 7.10 i 7.12), widzimy, że: T odpowiednikiem podstawy p ze wzoru 7.5 jest dzielnik B
we wzorze 7.11, T cyfrze c0 w 7.5 odpowiada we wzorze 7.11 reszta z dzielenia r, T część całkowita wyniku C w 7.11 to we wzorze 7.5 liczba Ln-1,
o mniejszej liczbie cyfr (bez c0).
Zatem w metodzie wyznaczania cyfr od najmniej do najbardziej znaczącej pozycji wystarczy wykorzystać dwa operatory występujące w większości języków programowania wysokiego poziomu — również w TP i VBA — przeznaczone do działań na liczbach całkowitych. Są to: T operator dzielenia całkowitego liczb — wynikiem jest liczba
całkowita, powstała poprzez odcięcie od wyniku dzielenia jego części ułamkowej, T operator wyznaczający resztę z dzielenia całkowitego liczb,
która nie przekracza wartości dzielnika.
208
Algorytmy • Ćwiczenia
Obliczając resztę z dzielenia liczby L przez podstawę p systemu docelowego, wyznaczamy aktualnie najmłodszą pozycję w liczbie. Dzielenie całkowite przeliczanej liczby przez podstawę p wyznacza liczbę Ln-1 o zmniejszonej o jeden ilości cyfr. Po przypisaniu L = Ln-1, najmłodszą pozycją w tej liczbie jest c1 i ponownie może być obliczona jako reszta z dzielenia przez p. W ten sposób wyznacza się jedną po drugiej wszystkie kolejne cyfry przeliczanej liczby cn-1cn-2… c1 c0. Opisany schemat postępowania kończy się, gdy wynik dzielenia całkowitego jest równy zeru. Oznacza to bowiem, że nie da się już wyodrębnić niezerowych cyfr. Zauważ, że w tej metodzie nie trzeba wyznaczać wagi o największej wartości w liczbie. Znacznie to upraszcza i skraca algorytm przetwarzania. W tabeli 7.1 przedstawiono operatory ilorazu całkowitego, reszty z tego działania charakterystyczne dla języka programowania, sposób oznaczania oraz przykłady w Pascalu i VBA. Dokładny opis tych działań znajduje się w plikach pomocy każdego z tych języków, a sam algorytm wykorzystujący operatory pokazany jest na rysunku 7.14. Tabela 7.1. Operacje dzielenia całkowitego oraz wyznaczania reszty z dzielenia całkowitego w Turbo Pascalu i ich odpowiedniki w VBA Symbol działania (operator)
Zapis
Pascal
VBA
Pascal
VBA
Pascal
VBA
Dzielenie całkowite
div
\
A div B
A\B
7 div 2 =3
7\2 =3
Wyznaczanie reszty z dzielenia całkowitego
mod
Mod
A mod B
A Mod B
7 mod 2 =1
7 Mod 2 =1
Działanie
Przykład
W algorytmie na oznaczenie operatora dzielenia całkowitego i operatora reszty z tego dzielenia zostały wykorzystane symbole stosowane w Pascalu.
Rozdział 7. • Pozycyjne systemy liczbowe
Rysunek 7.14. Algorytm przeliczania liczby do nowego systemu pozycyjnego metodą wyznaczania cyfr w kolejności od najmniej do najbardziej znaczącej pozycji
209
210
Algorytmy • Ćwiczenia
Ć W I C Z E N I E
7.13
Przeliczanie liczby do nowego systemu pozycyjnego. Program w Turbo Pascalu
Napisz program w Turbo Pascalu, który metodą wyznaczania cyfr w kolejności od najmniej do najbardziej znaczącej pozycji, przeliczy liczbę podaną w systemie dziesiętnym do systemu pozycyjnego o podstawie z zakresu . W rozwiązaniu wykorzystaj schemat z rysunku 7.14.
Rozwiązanie 1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając
z paska menu polecenia File/New. 2. W oknie edycyjnym wpisz kod z listingu 7.9 albo wczytaj
program z pliku cw7_12.pas znajdującego się w katalogu TP/Rozdz_07. Listing 7.9. Kod programu cw7_12. program cw7_12; { Program przelicza liczbe z decymalnego do innego { pozycyjnego systemu liczbowego o podstawie { z uzyciem operatorow mod oraz div. { Deklaracja zmiennych stosowanych w programie: { Liczba - wprowadzana liczba, p - podstawa systemu, { cyfra - cyfra w liczbie, pom - zmienna pomocnicza, { a - wartosc ASCII wyznaczonej cyfry. var cyfra, c : String; a, Liczba, p, w : Integer; pom : Integer;
} } } } } } }
{ ---- Kod programu --------------------------------- } begin writeln; writeln (' Zapis liczby dziesietnej w dowolnym systemie liczbowym '); writeln ('--------------------------------------------------------'); writeln; { Pobranie danych i nadanie wartosci zmiennym.
}
Rozdział 7. • Pozycyjne systemy liczbowe
211
write(' Podaj liczbe w systemie dziesietnym '); readln(Liczba); write(' Podaj podstawe systemu docelowego '); readln(p); writeln; c := ''; pom := Liczba; { Blok glowny programu. while Liczba > 0 do begin a := Liczba mod p; Liczba := Liczba div p; if a>9 then a := a + 7; a := a + 48; cyfra := Chr(a); c := cyfra + c; end;
}
{ Organizacja postaci i wyswietlenie wyniku
}
writeln; writeln; writeln(' ', pom,' (w systemie 10) = ', c, ' (w systemie ', p, ')'); writeln; writeln; writeln(' - Aby zakonczyc, nacisnij klawisz ENTER - '); readln; end.
Zastosowanie operatorów mod oraz div znacznie uprościło — w porównaniu z zapisem z ćwiczenia 7.10 — kod programu przeliczającego. Przykładowe wyniki widoczne są na rysunku 7.15. Ć W I C Z E N I E
7.14
Przeliczanie liczby do nowego systemu pozycyjnego. Aplikacja w Excelu
Bazując na algorytmie z rysunku 7.14, napisz w języku VBA funkcję użytkownika, która wykorzystuje działanie dzielenia całkowitego i działanie reszty z dzielenia całkowitego w celu przeliczenia liczby dziesiętnej do systemu o podstawie z przedziału .
212
Algorytmy • Ćwiczenia
Rysunek 7.15. Ekran wynikowy programu z ćwiczenia 7.13
Rozwiązanie 1. Uruchom program Excel i zapisz domyślnie pojawiający się
Zeszyt1 w wybranym przez siebie katalogu pod nazwą cw7_13 albo wczytaj gotowy program znajdujący się w arkuszu cw7_13.xls w katalogu EX/Rozdz_07. 2. Po wywołaniu okna edytora VBE wstaw moduł standardowy
(Insert/Module) oraz wpisz w sekcji General (Ogólne) modułu Module1 (Moduł1) kod z listingu 7.10, realizujący w Visual Basicu procedurę przeliczającą. Listing 7.10. Deklaracja zmiennych i kod funkcji przeliczającej 'Wszystkie zmienne muszą być zadeklarowane. Option Explicit Dim cyfra As String Dim Liczba, p, a As Integer Function c(Liczba As Integer, p As Integer) As String 'Funkcja przeliczająca liczbę do nowego systemu pozycyjnego 'z wykorzystaniem działań "\" oraz "Mod". 'Nadanie zmiennej wartości początkowej. c = ""
Rozdział 7. • Pozycyjne systemy liczbowe
213
'Główny blok obliczeniowy. While Liczba > 0 a = Liczba Mod p Liczba = Liczba \ p 'Określenie odpowiednika "a" w kodzie ASCII. If a > 9 Then a = a + 7 a = a + 48 cyfra = Chr(a) 'Konkatenacja aktualnie wyznaczonej cyfry z ciągiem c. c = cyfra & c Wend End Function
Funkcja przeliczająca nazwana jest pojedynczą literą — c. Ma dwa argumenty całkowite, a jej postać wygląda następująco: c(Liczba As Integer, p As Integer). W kodzie wykorzystane zostały operatory dzielenia całkowitoliczbowego i wyznaczania reszty z tego dzielenia, które wystąpiły w schemacie blokowym z rysunku 7.14. Aby sprawdzić działanie programu, należy w arkuszu umieścić wywołanie funkcji c oraz podać wartości jej argumentów. Arkusz służy tylko do testowania, dlatego jego forma będzie niezwykle oszczędna — wystąpią w niej niesformatowane wpisy i wywołanie funkcji. 3. We wskazanych komórkach arkusza wpisz nagłówki, wartości i wywołanie funkcji przeliczeniowej. Ewentualne formatowanie poleca się Czytelnikowi dokonać samodzielnie. T komórka C5 — Liczba decymalnie, T komórka D5 — Nowa podstawa, T komórka F5 — Wynik, T komórka C6 — wpisz 123, T komórka D6 — wpisz 8, T komórka F6 — wpisz wywołanie funkcji użytkownika =c(C6;D6). Jeżeli wszystko wpisałeś prawidłowo, to powinieneś zaobserwować efekt analogiczny jak na ilustracji 7.16.
214
Algorytmy • Ćwiczenia
Rysunek 7.16. Wyniki w programie cw7_13
Sprawdź działanie aplikacji, dokonując zmian w komórkach C6 i D6 zawierających argumenty wsadowe dla funkcji przeliczeniowej.
Uwagi końcowe Operatory logiczne i ich praktyczna realizacja W rozdziale został zastosowany operator sumy logicznej Or (LUB). Oprócz niego w językach programowania stosuje się kilka innych operatorów. Najczęściej są to: operator iloczynu logicznego (koniunkcji) And (I), operator alternatywy wykluczającej (ALBO) Xor oraz operator negacji Not (NIE). Realizowane przez nie funkcje opisane zostały w tabeli 7.2 — argumentami są bity A oraz B, przyjmujące wartości 1 albo 0. Wykorzystując podane operatory lub ich kombinację z operatorem Not, można wykonywać złożone operacje porównywania.
215
Rozdział 7. • Pozycyjne systemy liczbowe Tabela 7.2. Funkcje realizowane przez operatory logiczne występujące w językach programowania A
B
Not A
A Or B
A And B
A Xor B
0
0
1
0
0
0
0
1
1
1
0
1
1
0
0
1
0
1
1
1
0
1
1
0
Nie tylko informatyka wykorzystuje opisane wyżej działania logiczne. Znajdują one powszechne zastosowanie w technice cyfrowej (również w sprzęcie komputerowym). Pojedyncze elementy realizujące opisane funkcje nazywane są bramkami logicznymi (funktorami), a konstruowane z nich urządzenia układami logicznymi albo cyfrowymi. Najczęściej spotykane symbole bramek przedstawia rysunek 7.17. Rysunek 7.17. Symbole bramek logicznych
Część bramek z rysunku 7.17 posiada wrysowane na wyjściu kółko. Symbol ten oznacza negację, a funktory określane są jako bramki z negacją. W opisie literowym negacja oznaczana jest literą N. Na przykład NAND oznacza bramkę AND z negacją, czyli NIE I.
216
Algorytmy • Ćwiczenia
Z bramek buduje się większe struktury zwane przerzutnikami, które z kolei są elementami składowymi wielu złożonych układów cyfrowych, na przykład portów komputera, układów pamięci, liczników i wielu innych.
Ćwiczenia do samodzielnego wykonania Ć W I C Z E N I E
7.15 W ćwiczeniu 7.4 zastosowana została instrukcja n = Len(Str(Liczba)) – 1, która oblicza liczbę znaków łańcucha z nawiasu. Wyjaśnij, dlaczego wyznaczona wartość jest pomniejszana o jeden. Ć W I C Z E N I E
7.16 Uzupełnij kod programu z ćwiczenia 7.6 o procedury obsługi błędów wynikających ze zbyt dużej podstawy i skojarzonych z nią cyfr. Ć W I C Z E N I E
7.17 Zbadaj działanie programu z ćwiczenia 7.10 po usunięciu z kodu programu funkcji Trunc(). Czy można ją zastąpić inną komendą? Ć W I C Z E N I E
7.18 Porównaj instrukcje konkatenacji występujące w algorytmach z rysunków 7.10 i 7.14. Czy ich różna postać jest przypadkowa?
8 Algorytmy sortowania danych Sortowanie zbioru danych Sortowaniem, zwanym również porządkowaniem albo (rzadko) szeregowaniem, nazywamy proces układania elementów zbioru według ściśle określonych zasad logicznych. Zbiór posortowany charakteryzuje porządek, a elementy tego zbioru są ustawione w kolejności określonej relacją porządkowania. Najczęściej stosuje się relację większości >, mniejszości < lub równości =. W oparciu o te relacje każdy zbiór elementów można posortować: T rosnąco — element poprzedni zbioru jest nie większy niż
element następny, na przykład {1, 2, 5, 5, 7}, T malejąco — element poprzedni jest nie mniejszy niż element
następny zbioru, na przykład {8, 4, 4, 3, 2}. Element zbioru poddawany sortowaniu nazywany jest obiektem. Efektem końcowym sortowania jest ciąg złożony z elementów zbioru wsadowego, zorganizowany tak, by podmiot gromadzący dane mógł je łatwo i szybko interpretować, wykorzystać lub kontrolować. Uporządkowany poprzez sortowanie ciąg zwany jest też tablicą lub listą uporządkowaną.
218
Algorytmy • Ćwiczenia Zapamiętaj! Posortowanie danych ułatwia wyszukiwanie elementów.
Klucz sortowania danych Reguła logiczna, według której przeprowadza się sortowanie nazywana jest kluczem. Jest nim zazwyczaj grupa danych tego samego typu zawarta w zbiorze wsadowym. Na przykład tabelę z rozkładem jazdy pociągów można sortować według następujących kluczy: godziny odjazdu, godziny przyjazdu, rodzaju składu (pospieszny czy osobowy), najkrótszej trasy, stacji docelowej itp. Klucz jest liczbą. Dlatego przed rozpoczęciem porządkowania elementów opisanych w innej formie (np. łańcuchów tekstowych), musi nastąpić ich zamiana na odpowiedniki liczbowe. Klucz, który jednoznacznie identyfikuje obiekt, nazywany jest kluczem podstawowym (ang. primary key) albo kluczem głównym. Jeżeli w grupie danych określona cecha (wartość) powtarza się (np. stacja docelowa), to nie może ona stanowić klucza podstawowego. Jest tak dlatego, że wybrana wartość nie jest unikatowa i niepowtarzalna w zbiorze danych. By wyeliminować niepożądane wartości, należy uściślić zapytanie, zwane kwerendą, poprzez zdefiniowanie klucza złożonego, inaczej wielopolowego, tzn. takiego, który łączy w sobie kilka kryteriów wyszukiwania (np. stacja docelowa i najkrótsza trasa). Ć W I C Z E N I E
8.1
Klucz sortowania danych osobowych
Określ klucz dla alfabetycznego, rosnącego sortowania rekordów zawierających dane osobowe pracowników firmy. Czy jest to klucz podstawowy? Przeprowadź analizę, wykorzystując przykładowe nieuporządkowane dane z tabeli 8.1.
Rozwiązanie Dane: Dane personalne pracownika firmy zapisane w tabeli 8.1. Oczekiwany wynik: Rekordy (obiekty) uporządkowane według kryterium alfabetycznego.
Rozdział 8. • Algorytmy sortowania danych
219
Tabela 8.1. Tabela z danymi osobowymi pracowników Nazwisko i imię
Data urodzenia
Miejsce urodzenia
Jonatan Adam
14.07.1981
Krawiec Sabina
Numer telefonu
Adres
PESEL
Kraków
Kraków, Jana Matejki 3
810714 02759
562-37-82
21.10.1972
Rzeszów
Kraków, Plac Wolności 4
721021 04576
335-72-02
Jonatan Edward
14.07.1981
Kraków
Kraków, Floriańska 2/1
810714 02763
671-44-65
Ćma Barnaba
1.03.1968
Wieliczka
Wieliczka, Lipowa 5/12
680301 00666
378-22-42
Analiza problemu: Rekord to ustalona, zazwyczaj pozioma struktura zawierającą korelujące z sobą dane, które jednoznacznie opisują jeden obiekt. W ćwiczeniu 8.1 obiektem jest pracownik, a rekord go opisujący to pojedynczy wiersz tabeli, w której kolumnami są grupy tworzących go danych. W tabeli 8.1 rekord tworzą dane różnych typów. Są to dane typu łańcuchowego, numerycznego (liczbowego) i typu data (dzień, miesiąc i rok). Rekord składa się z pól. Pole powstaje z przecięcia wiersza (rekordu) z kolumną tabeli (grupą danych). Każde pole przechowuje jedną daną. Zgodnie z tematem ćwiczenia porządkowanie ma się odbyć alfabetycznie. Sortowanie alfabetyczne oznacza, że rekordy powinny być uporządkowane zgodnie z kolejnością liter i cyfr alfabetu. Możliwe jest sortowanie rosnące od A do Z i od 0 do 9 albo malejące od Z do A i od 9 do 0. W temacie ćwiczenia narzucona jest rosnąca kolejność rekordów. W tabeli znajduje się sześć kolumn, z których każda zawiera dane o innej wartości. Możliwe jest więc uporządkowanie alfabetyczne w porządku rosnącym według klucza dotyczącego każdej kolumny, na przykład według nazwiska i imienia, daty urodzenia, numeru PESEL itp. W każdej kolumnie, z wyjątkiem kolumny z numerem PESEL, wartości mogą się powtórzyć. Zatem tak ustanowiony klucz nie jest podstawowy. Jedynie sortowanie według numeru PESEL jest porządkowaniem z kluczem podstawowym jednopolowym. Jednopolowy oznacza, że unikatowa (niepowtarzalna) informacja identyfikująca rekord znajduje się w pojedynczym polu.
220
Algorytmy • Ćwiczenia
Podsumowując, stwierdzamy, że kluczy jest sześć: T nazwisko i imię, T data urodzenia, T miejsce urodzenia, T adres, T PESEL, T numer telefonu. Kluczem podstawowym jest numer PESEL — tabela 8.2. Tabela 8.2. Tabela z danymi osobowymi pracowników uporządkowana według klucza „PESEL” Nazwisko i imię
Data urodzenia
Miejsce urodzenia
Ćma Barnaba
1.03.1968
Krawiec Sabina
Numer telefonu
Adres
PESEL
Wieliczka
Wieliczka, Lipowa 5/12
680301 00666
378-22-42
21.10.1972
Rzeszów
Kraków, Plac Wolności 4
721021 04576
335-72-02
Jonatan Adam
14.07.1981
Kraków
Kraków, Jana Matejki 3
810714 02759
562-37-82
Jonatan Edward
14.07.1981
Kraków
Kraków, Floriańska 2/1
810714 02763
671-44-65
Na zakończenie warto zaznaczyć, że człowiek najchętniej porządkuje dane osobowe według klucza alfabetycznego, jakim jest nazwisko i imię (tabela 8.3). Czasem jednak stosuje się rozbudowane sortowanie przy zastosowaniu jednocześnie kilku kluczy.
Metody sortowania zbioru danych Opracowano wiele algorytmów porządkujących dane, ale większość z nich jest rzadko stosowana i stanowi jedynie przedmiot rozważań teoretycznych.
221
Rozdział 8. • Algorytmy sortowania danych Tabela 8.3. Tabela z danymi osobowymi pracowników uporządkowana według klucza „Nazwisko i imię” Nazwisko i imię
Data urodzenia
Miejsce urodzenia
Ćma Barnaba
1.03.1968
Jonatan Adam
Numer telefonu
Adres
PESEL
Wieliczka
Wieliczka, Lipowa 5/12
680301 00666
378-22-42
14.07.1981
Kraków
Kraków, Jana Matejki 3
810714 02759
562-37-82
Jonatan Edward
14.07.1981
Kraków
Kraków, Floriańska 2/1
810714 02763
671-44-65
Krawiec Sabina
21.10.1972
Rzeszów
Kraków, Plac Wolności 4
721021 04576
335-72-02
Najczęściej stosowane są: T sortowanie przez wybieranie, T sortowanie przez wstawianie, T sortowanie bąbelkowe, T sortowanie szybkie. Nie ma idealnej metody sortowania danych. Wybór właściwej zależy najczęściej od potrzeb użytkownika, liczby obiektów (elementów) w zbiorze, wielkości pamięci komputera przetwarzającego dane, kryteriów oraz kosztów porównywania i przesuwania danych. Dla dalszych rozważań w rozdziale przyjmujemy, że sortowany zbiór liczy n obiektów oznaczanych jako a1, a2 itp. oraz że porządkowanie odbywa się rosnąco, w kierunku od lewej do prawej.
Sortowanie przez wybieranie Przy zastosowaniu tej metody, każdy porządkowany n-elementowy zbiór obiektów można w danej chwili przedstawić jako połączenie dwóch sekcji: uporządkowanej, znajdującej się na początku zbioru danych, i nieuporządkowanej stanowiącej pozostałą część zbioru. Fragment zbioru nazywany jest często partycją. Przedstawia to rysunek 8.1.
222
Algorytmy • Ćwiczenia
Rysunek 8.1. Zbiór obiektów podczas porządkowania
Sekcja posortowana p-elementowa
Sekcja nieposortowana q-elementowa
Cały n-elementowy zbiór danych: n = p + q
Przed rozpoczęciem porządkowania wszystkie elementy zbioru wsadowego znajdują się w sekcji nieposortowanej. W części uporządkowanej nie ma żadnego elementu, czyli zawiera ona zero elementów. Po uporządkowaniu wszystkie obiekty znajdują się w partycji posortowanej, a partycja nieposortowana jest pusta. Niemniej w każdym momencie suma obiektów obu sekcji jest równoliczna zbiorowi wsadowemu. Metoda sortowania przez wybieranie polega na wyszukaniu w nieuporządkowanej części zbioru obiektu o najmniejszej wartości (kluczu). Następnie znaleziony element jest wstawiany na początek sekcji nieposortowanej, co odbywa się poprzez wymianę wyszukanego elementu z obiektem na pierwszej pozycji. Dalszemu sortowaniu podlega nieuporządkowany zbiór złożony z n – 1 obiektów, położony na prawo od ciągu posortowanego. Ponownie wyszukiwany jest element najmniejszy i ponownie przemieszczany na początek zbioru nieposortowanego (czyli na koniec listy uporządkowanej). Proces ten powtarzany jest tak długo, aż w części nieuporządkowanej pozostanie jeden, największy obiekt, który również powiększy ciąg posortowany. W każdym kroku sortowania początkowa (posortowana) część zbioru ulega powiększeniu o jeden element, maleje natomiast liczba obiektów w części nieuporządkowanej. Doskonałą metodą zrozumienia działania algorytmów sortujących jest porządkowanie zbioru liczb na papierze, bez użycia komputera i oprogramowania. Stąd poniższe ćwiczenie. Ć W I C Z E N I E
8.2
Sortowanie przez wybieranie
Postępując zgodnie z zasadami sortowania przez wybieranie, uporządkuj bez pomocy komputera zbiór elementów {3, 2, 7, 5, 1}.
Rozdział 8. • Algorytmy sortowania danych
223
Rozwiązanie Dane: Zbiór liczb do porządkowania tworzący pięcioelementową tablicę {3, 2, 7, 5, 1}. Oczekiwany wynik: Liczby uporządkowane w kolejności od najmniejszej do największej. Analiza problemu: Porządkowanie polega na wielokrotnym przeglądaniu od lewej do prawej nieuporządkowanej części tablicy i wyszukaniu elementu najmniejszego. Następnie znaleziona najmniejsza liczba jest zamieniana z pierwszym elementem w partycji nieposortowanej. Model porządkowania przez wybieranie zilustrowany jest poniżej. W nawiasach kwadratowych zamknięta jest część uporządkowana zbioru. Krok 1. 3 2 7 5 1 — wyszukaną, najmniejszą w zbiorze liczbę 1 należy wymienić z liczbą 3. Krok 2. [1] 2 7 5 3 — bez wymiany, ponieważ 2 jest najmniejszą liczbą w sekcji porządkowanej. Krok 3. [1 2] 7 5 3 — liczbę 3 należy wymienić z liczbą 7. Krok 4. [1 2 3] 5 7 — bez wymiany. Krok 5. [1 2 3 5] 7 — bez wymiany. W każdym kolejnym kroku partycja nieposortowana tablicy skraca się o jeden element, który jest przenoszony do strefy uporządkowanej. W krokach 2 oraz 4 nie trzeba dokonywać wymiany, ponieważ pierwszy element w sekcji nieuporządkowanej jest zarazem jej elementem najmniejszym. W kroku piątym wymiana też jest zbędna, gdyż pozostał jeden obiekt, większy niż każdy element partycji uporządkowanej, a zatem stoi on na właściwej pozycji. Ć W I C Z E N I E
8.3
Algorytm sortowania przez wybieranie. Realizacja w postaci listy kroków
Ułóż w postaci listy kroków algorytm rosnącego porządkowania przez wybieranie zbioru n liczb rzeczywistych.
224
Algorytmy • Ćwiczenia
Rozwiązanie Dane: Zbiór liczb do porządkowania a1, a2, …, an tworzący tablicę a[1,…n] oraz ilość liczb n. Oczekiwany wynik: Liczby uporządkowane w kolejności od najmniejszej do największej. Analiza problemu: Sortowanie odbywa się zgodnie z opisem przedstawionym powyżej. Ponieważ zbiór zawiera n elementów, wyszukiwanie kolejnych najmniejszych elementów należy powtórzyć n – 1 razy. Zatem przetwarzanie powinno odbywać się iteracyjnie w pętli programowej. Licznikiem pętli będzie zmienna i przyjmująca wartość od 1 do n – 1. W każdym kolejnym kroku sekcja posortowana powiększa się o jeden element. W danej chwili zawiera ona i – 1 liczb. A oto i sam algorytm: Krok 1. Powtórz kolejne kroki algorytmu dla każdej wartości i∈{1, 2, …, n – 1}. Krok 2. Znajdź najmniejszy element ak w nieposortowanym zbiorze a[i,…n], gdzie k∈{i, …, n}. Krok 3. Zamień miejscami obiekty ak oraz ai. Ć W I C Z E N I E
8.4
Algorytm sortowania przez wybieranie — schemat blokowy
Podaj schemat blokowy algorytmu realizującego rosnące porządkowanie przez wybieranie zbioru n liczb rzeczywistych.
Rozwiązanie Dane: Zbiór liczb do porządkowania a1, a2, …, an tworzący tablicę a[1,…n] oraz ilość liczb n. Oczekiwany wynik: Liczby uporządkowane w kolejności od najmniejszej do największej. Analiza problemu: W algorytmie należy zastosować dwie pętle programowe. Pierwsza z nich, zewnętrzna (główna), sterowana licznikiem i∈{1, 2, …, n-1}, wyznacza wielkość porządkowanej sekcji, a zarazem
Rozdział 8. • Algorytmy sortowania danych
225
określa kolejne pozycje, na których znajdzie się element najmniejszy amin porządkowanej części zbioru. Przy pierwszym przeszukiwaniu tablicy będzie to pozycja pierwsza, przy drugim druga itd. Na początku każdego obiegu pętli zapamiętywany jest, w zmiennej amin, najmniejszy obiekt sortowanego zbioru oraz jego wskaźnik (indeks) w zmiennej imin. Przyjmujemy, że obiektem minimalnym jest element wskazany aktualną wartością licznika pętli i — inaczej mówiąc, pierwszy element aktualnie sortowanego zbioru. Na przykład w trzecim obiegu będzie to element o wskaźniku i = 3; dwa poprzedzające go obiekty są już uporządkowane i nie biorą udziału w procesie przetwarzania. Druga pętla, wewnętrzna (sortująca), sterowana jest licznikiem k∈{i, …, n} zależnym od licznika pętli zewnętrznej i. W tej pętli występuje porównywanie kolejnych elementów zbioru o indeksie k z elementem amin. Jeżeli element a[k] jest mniejszy od amin, to następuje przypisanie zmiennej amin wartości a[k] i zapamiętanie jego pozycji k w zmiennej imin. Obieg ten należy powtarzać dla kolejnych wartości licznika pętli k. Każdorazowo po zakończeniu pętli wewnętrznej znaleziony element minimalny ustawiany jest, poprzez wymianę a[imin] z a[i], na początku porządkowanego zbioru, po czym następuje inkrementacja licznika i. Ć W I C Z E N I E
8.5
Sortowanie przez wybieranie. Program w Turbo Pascalu
Napisz w Turbo Pascalu podprogram sortowania przez wybieranie, zgodny ze schematem blokowym z rysunku 8.2. Podprogram umieść w kodzie programu, którego zadaniem jest losowy wybór dwudziestu liczb z osiemdziesięciu , które następnie mają zostać uporządkowane w kolejności rosnącej.
Rozwiązanie 1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając
z paska menu polecenia File/New. 2. W oknie edycyjnym wpisz kod z listingu 8.1 albo wczytaj
program z pliku cw8_5.pas znajdującego się w katalogu TP/Rozdz_08.
226
Algorytmy • Ćwiczenia
Rysunek 8.2. Schemat blokowy algorytmu sortowania przez wybieranie
Rozdział 8. • Algorytmy sortowania danych Listing 8.1. Kod programu sortowania przez wybieranie program cw8_5; { Program do testowania procedury sortowania { przez wybieranie o nazwie Sort_Wybieranie.
} }
uses Crt; { Deklaracja stalej { n - liczba elementow w sortowanej tablicy. const n = 20;
} }
{ Deklaracja zmiennych uzywanych w programie: { i - licznik petli zewnetrznej, { k - licznik petli sortujacej, { amin - najmniejszy element w sekcji porzadkowanej, { imin - indeks elementu amin, { a - tablica z liczbami do sortowania. var i, k, amin, imin : Integer; a : array [1..n] of Integer;
} } } } } }
{ ---- Deklaracja i kod procedury Utworz_Tablice ---- } procedure Utworz_Tablice; begin Randomize; for i := 1 to n do a[i] := Random(80)+1; end; { ------- Koniec procedury Utworz_Tablice ---- } { ---- Deklaracja i kod procedury Sort_Wybieranie --- } procedure Sort_Wybieranie; begin for i := 1 to n-1 do begin amin := a[i]; imin := i; for k := i+1 to n do if a[k] < amin then begin amin := a[k]; imin := k; end; a[imin] := a[i]; a[i] := amin; end; end; { ------ Koniec procedury Sort_Wybieranie ---- }
227
228
Algorytmy • Ćwiczenia { ---- Program glowny ------------------------------- } begin ClrScr; writeln; writeln (' Sortowanie przez wybieranie '); writeln ('-----------------------------'); writeln; { Wywolanie procedury Utworz_Tablice. Utworz_Tablice;
}
writeln (' Tablica nieposortowana:'); for i := 1 to n do write (a[i]:3); writeln; { Wywolanie procedury Sort_Wybieranie. Sort_Wybieranie;
}
writeln (' Tablica posortowana:'); for i :=1 to n do write (a[i]:3); readln; end.
W programie cw8_5 zostały użyte takie same symbole, jak w algorytmie z rysunku 8.2 oraz dwa podprogramy — ten o nazwie Utworz_Tablice generuje losową tablicę, ten o nazwie Sort_Wybieranie sortuje przez wybieranie. Program rozpoczyna się od zadeklarowania modułu standardowego Crt. Deklarację musi poprzedzać komenda uses. Moduł Crt ułatwia obsługę ekranu, okien, klawiatury, kolorów i dźwięków wbudowanego głośnika. Zawiera szereg gotowych do użycia, przydatnych funkcji wbudowanych. W programie została wykorzystana tylko jedna funkcja — ClrScr, która każdorazowo po wywołaniu czyści ekran i ustawia kursor w pierwszym wierszu ekranu. Dzięki temu w oknie użytkownika wyświetlane są jedynie aktualne wyniki wykonania programu, bez śmieci pozostałych po poprzednich uruchomieniach. Po zadeklarowaniu stałej n, określającej liczebność tablicy, oraz zmiennych używanych w programie wpisywany jest kod podprogramu Utworz_Tablice. Jego zadaniem jest zapełnienie tablicy 20 losowymi
Rozdział 8. • Algorytmy sortowania danych
229
liczbami z zakresu . Podprogram rozpoczyna się poleceniem Randomize uruchamiającym generator liczb losowych. Losowanie odbywa się w pętli z wykorzystaniem funkcji Random. Oto składnia tej funkcji: Random (parametr typu Word).
Funkcja Random zwraca liczbę typu Word z przedziału prawostronnie otwartego ai a(j + 1) = a(j) j = j - 1 Wend a(j + 1) = ai Next i 'Koniec procedury. 'Zawartość posortowanej tablicy. For i = 1 To n 'Utworzenie tekstu do wyświetlenia. Tablica_po = Tablica_po & a(i) & ", " Next i Komunikat2 = "Tablica posortowana rosnąco: " & Tablica_po 'Łączenie tekstów i wyświetlenie okna komunikatu z wynikiem. Komunikat = Komunikat1 & Chr(10) & Chr(10) & Komunikat2 MsgBox Komunikat, vbOKOnly + vbMsgBoxRight, "Sortowanie tablicy" End Sub
Proszę porównać zapis procedury Sort_Wstawianie z procedurą w ćwiczeniu 8.6. Różni się użytymi zmiennymi i kodem procedury sortującej. Identyczne są fragmenty losowego zapełniania tablicy i przygotowywania tekstu do wyświetlenia. Różnica występuje w sposobie wyświetlania wyniku. Najpierw łańcuchy tekstowe zawierające elementy tablicy Tablica_przed i Tablica_po są wstawiane do zmiennych typu łańcuchowego Komunikat1 i Komunikat2. Pod koniec programu następuje ich konkatenacja za pomocą polecenia Komunikat = Komunikat1 & & Chr(10) & Komunikat2. Tekst ukryty w zmiennej Komunikat jest wyświetlany dzięki użyciu instrukcji MsgBox, która została omówiona w ćwiczeniu 4.6. Tajemnicze Chr(10)
to funkcja, która zwraca znak stowarzyszony z numerem
Rozdział 8. • Algorytmy sortowania danych
243
w nawiasie — również znaki niepisane. Na przykład Chr(65) oznacza dużą literę A. Każdorazowe użycie w programie Chr(10) zwraca znak niepisany nakazujący rozpoczęcie nowej linii. Dzięki temu łańcuchy tekstowe Komunikat1 i Komunikat2 będą zapisane w nowych liniach rozdzielonych jedną linią pustą. Argument vbMsgBoxRight oznacza, że tekst w oknie komunikatu ma zostać wyrównany do prawej. Inne argumenty funkcji MsgBox oraz ich znaczenie znajdziesz w plikach pomocy VB. 3. Uruchom program, wybierając Run/RunSub/UserForm albo wciskając klawisz F5. Wynik pojawi się w oknie wyskakującym, takim jak na rysunku 8.7. Możliwe jest również bezpośrednie uruchomienie procedury poprzez wpisanie jej nazwy w oknie Immediate (rysunek 8.8) i wciśnięcie Enter. Rysunek 8.7. Okno komunikatu z wynikami sortowania przez wstawianie
Sortowanie bąbelkowe Metoda sortowania bąbelkowego polega na cyklicznym porównywaniu dwóch sąsiadujących elementów. Jeżeli nie są one ustawione we właściwej kolejności, to następuje ich zamiana (przestawienie). W konsekwencji, przy porządkowaniu rosnącym, element o mniejszej wartości znajdzie się przed elementem większym. Wielokrotne przeglądanie tablicy w tym samym kierunku oraz powtórzenie operacji porównywania i ewentualnej zamiany spowoduje uporządkowanie całego zbioru. Dlatego sortowanie bąbelkowe zwane jest też sortowaniem przez prostą zamianę. Standardowo zbiór sortowanych elementów przedstawiany jest poziomo. Spróbujmy wyjaśnić nazwę — sortowanie bąbelkowe. Wyobraźmy sobie, że tablica elementów została ustawiona pionowo i jest wypełniona wodą, a porządkowane liczby są pęcherzykami (bąbelkami) powietrza. Liczby „lżejsze”, czyli o mniejszej wartości są szybciej wynoszone w górę, a cięższe opadają na dno. Najszybciej do powierzchni dociera bąbelek najlżejszy.
244
Algorytmy • Ćwiczenia
Rysunek 8.8. Edytor VBE — w oknie Immediate wpisana jest nazwa procedury sortującej Sort_Wstawianie Ć W I C Z E N I E
8.11
Sortowania bąbelkowe
Postępując zgodnie z zasadami sortowania bąbelkowego, uporządkuj bez pomocy komputera zbiór elementów {3, 2, 7, 5, 1}.
Rozwiązanie Dane: Zbiór liczb do porządkowania tworzący pięcioelementową tablicę {3, 2, 7, 5, 1}. Oczekiwany wynik: Liczby uporządkowane w kolejności od najmniejszej do największej. Analiza problemu: W celu posortowania tablicy przeglądamy zbiór elementów od końca tablicy do początku (od prawej do lewej). Każdorazowo porównywane są dwa sąsiadujące elementy. Element o mniejszej wartości ustawiany jest jako pierwszy w porównywanej parze,
Rozdział 8. • Algorytmy sortowania danych
245
w wyniku czego staje się drugim elementem w kolejnej parze porównywanych liczb. Kolejne porównywania przesuwają mniejsze liczby w stronę początku zbioru. Podczas pierwszego przeszukiwania tablicy najmniejsza z liczb dociera na pierwszą pozycję w nieuporządkowanej tablicy, podczas drugiego kolejna itd. Model tego porządkowania pokazany jest poniżej. W nawiasach kwadratowych zamknięta jest porównywana para liczb. Porządek przed pierwszym obiegiem {3, 2, 7, 5, 1} Obieg 1 Krok 1. 3 2 7 [5 1] — w pierwszej parze dochodzi do zamiany liczb. Krok 2. 2 3 [7 1] 5 — w drugiej parze dochodzi do zamiany liczb. Krok 3. 2 [3 1] 7 5 — w trzeciej parze dochodzi do zamiany liczb. Krok 4. [2 1] 3 7 5 — w czwartej, ostatniej parze, też dochodzi do zamiany liczb. Porządek po pierwszym obiegu {1, 2, 3, 7, 5} Obieg 2 Krok 1. 1 2 3 [7 5] — dochodzi do zamiany liczb. Krok 2. 1 2 [3 5] 7 — bez zamiany. Krok 3. 1 [2 3] 5 7 — bez zamiany. Krok 4. [1 2] 3 5 7 — bez zamiany. Porządek po drugim obiegu {1, 2, 3, 5, 7} Obieg 3 Krok 1. 1 2 3 [5 7] — bez zamiany. Krok 2. 1 2 [3 5] 7 — bez zamiany. Krok 3. 1 [2 3] 5 7 — bez zamiany. Krok 4. [1 2] 3 5 7 — bez zamiany. Porządek po trzecim obiegu {1, 2, 3, 5, 7}
246
Algorytmy • Ćwiczenia
Obieg 4 Krok 1. 1 2 3 [5 7] — bez zamiany. Krok 2. 1 2 [3 5] 7 — bez zamiany. Krok 3. 1 [2 3] 5 7 — bez zamiany. Krok 4. [1 2] 3 5 7 — bez zamiany. Porządek po uporządkowaniu {1, 2, 3, 5, 7} Dwa ostatnie przeglądania tablicy nie zmieniają kolejności ustawienia elementów, gdyż pełne posortowanie zbioru nastąpiło już w drugim obiegu. W tym przykładzie obiegi 3 oraz 4 są zbędne i jedynie znacznie wydłużają czas sortowania. Oczywiście przy innej kolejności danych wsadowych może dochodzić do zamiany podczas każdego przeglądania. Skoro jednak zdarzają się puste przejścia przez algorytm, to warto wyeliminować obiegi, w których nie dochodzi do zmiany kolejności porównywanych obiektów. Operacje przejścia przez algorytm, podczas których dochodzi do porównania, ale nie następuje zamiana elementów, nazywane są pustymi. W omówionym ćwiczeniu wykonywanych jest aż jedenaście pustych operacji i dwa puste obiegi.
Sortowanie bąbelkowe z ograniczeniem liczby pustych operacji Podczas pierwszego przejścia przez algorytm wykonywanych jest n – 1 porównań, gdzie n oznacza liczbę elementów w tablicy. Po wykonaniu pełnego obiegu element o najmniejszej wartości zajmuje prawidłową pozycję na początku porządkowanej listy, dlatego nie musi już brać udziału w kolejnych etapach porządkowania. Liczba elementów porządkowanych w drugim obiegu wynosi zatem n – 1, a wykonać trzeba n – 2 porównań. W każdym kolejnym obiegu liczba sortowanych elementów i porównań zmniejsza się o 1. Aby zrealizować ten model porządkowania, należy przy każdym kolejnym przemiataniu zbioru (od końca do początku) wykonywać o jedno porównanie mniej.
Rozdział 8. • Algorytmy sortowania danych
247
Sortowanie bąbelkowe z wcześniejszym zakończeniem Jeżeli podczas pełnego obiegu nie dochodzi do przynajmniej jednej zamiany elementów w porównywanej parze, to znaczy, że kolejność wszystkich obiektów jest prawidłowa i można wcześniej zakończyć działanie algorytmu. Można to zrealizować poprzez użycie dodatkowej zmiennej (flagi) monitorującej, czy doszło do operacji porządkującej, czyli wymiany elementów. Jeżeli tak, to w algorytmie realizowany jest kolejny obieg. W przeciwnym razie następuje zakończenie algorytmu. Ć W I C Z E N I E
8.12
Schemat blokowy algorytmu sortowania bąbelkowego
Podaj schemat blokowy algorytmu realizującego rosnące sortowanie bąbelkowe zbioru n liczb rzeczywistych. Algorytm należy wyposażyć w mechanizm: T eliminujący puste operacje porównań w sekcji uporządkowanej, T szybszego kończenia algorytmu w sytuacji, gdy podczas pełnego obiegu nie dochodzi do co najmniej jednej zamiany sortowanych liczb.
Rozwiązanie Dane: Zbiór liczb do porządkowania a1, a2, …, an tworzący tablicę a[1,…n] oraz ilość liczb n. Oczekiwany wynik: Liczby uporządkowane w kolejności od najmniejszej do największej. Analiza problemu: W celu uporządkowania zbioru n liczb należy go przeglądać n – 1 razy w pętli zewnętrznej sterowanej zmienną i∈{2, …, n}. Podczas pojedynczego obiegu trzeba wykonać szereg komparacji sąsiadujących z sobą par liczb. Eliminację operacji pustych uzyskuje się poprzez ograniczenie liczby dokonywanych porównań. Powinno ich być o jedno mniej niż liczba obiektów w aktualnie porządkowanym zbiorze. Coraz mniejsza liczba porównań spowodowana jest tym, że po każdym obiegu pętli zewnętrznej najmniejszy element w porządkowanej partycji zajmuje prawidłową pozycję w zbiorze i nie musi już brać udziału w dalszym przetwarzaniu. Przeglądanie tablicy w sortującej pętli wewnętrznej
248
Algorytmy • Ćwiczenia
należy wykonywać w kierunku od końca do początku i kończyć bez wchodzenia w sekcję uporządkowaną w poprzednich obiegach. W tym celu licznik pętli sortującej k musi przyjmować wartości malejące — k∈{n, n – 1, …, i}. Kresem dolnym licznika k jest aktualna wartość i. Dzięki temu podczas każdego kolejnego obiegu algorytmu wykonywane jest o jedno porównanie mniej — unika się pustych operacji porównania. W pętli sortującej należy porównać dwie sąsiadujące liczby. Jeżeli liczba większa a[k – 1] znajduje się przed mniejszą a[k], to powinna zostać zepchnięta w kierunku końca zbioru. Tym samym element mniejszy a[k] przesuwany jest w stronę początku — lżejszy bąbelek wynoszony jest w górę. Dokonywane jest to poprzez zamianę, za pośrednictwem pomocniczej zmiennej tmp, źle ustawionych liczb a[k] oraz a[k – 1]. Fakt przestawienia liczb należy odnotować w zmiennej monitorującej o nazwie zam. Przyjmijmy, że zamiana ma skutkować wartością zam = 1, a brak zamiany zam = 0. Ta ostatnia wartość musi być nadawana zmiennej przed wejściem w pętlę sortującą. Użycie flagi zam pozwala na wcześniejsze zakończenie algorytmu w sytuacji, gdy podczas pełnego obiegu pętli wewnętrznej nie wystąpi ani jedno przestawienie elementów w porządkowanej partycji. Odpowiada to następującemu, jednoczesnemu stanowi zmiennych k < i oraz zam = 0. Schemat blokowy będący odzwierciedleniem powyższych rozważań przedstawiony jest na rysunku 8.9. Ć W I C Z E N I E
8.13
Sortowanie bąbelkowe. Podprogram w Turbo Pascalu
Napisz w Turbo Pascalu program realizujący sortowanie bąbelkowe. Wykorzystaj w nim procedurę sortowania zgodną ze schematem przedstawionym na rysunku 8.9.
Rozwiązanie 1. Uruchom Turbo Pascala i utwórz nowy plik, wybierając
z paska menu polecenia File/New. 2. W oknie edycyjnym wpisz kod z listingu 8.5 albo wczytaj program z pliku cw8_13.pas znajdującego się w katalogu TP/Rozdz_08.
Rozdział 8. • Algorytmy sortowania danych
Rysunek 8.9. Schemat blokowy sortowania bąbelkowego
249
250
Algorytmy • Ćwiczenia
Listing 8.5. Kod programu sortowania bąbelkowego program cw8_13; { Program do testowania procedury sortowania { babelkowego o nazwie Sort_Babelkowe.
} }
uses Crt; { Deklaracja stalej { n - liczba elementow w sortowanej tablicy. const n = 8;
} }
{ Deklaracja zmiennych uzywanych w programie: { i - licznik petli zewnetrznej, { k - licznik petli sortujacej, { tmp - zmienna pomocnicza, { zam - zmienna monitorujaca przestawienie elementow, { y - wiersz, w ktorym znajduje sie kursor, { a - tablica z liczbami do sortowania. var i, k, tmp, zam : Integer; y : Byte; a : array [1..n] of Integer;
} } } } } } }
{ ---- Deklaracja i kod procedury Sort_Babelkowe ---- } procedure Sort_Babelkowe; begin for i := 2 to n do begin for k := n downto i do if a[k-1]>a[k] then begin tmp := a[k-1]; a[k-1] := a[k]; a[k] := tmp; end; end; end; { --------- Koniec procedury Sort_Babelkowe ---- } { ---- Program glowny ------------------------------- } begin { Wywolanie procedury czyszczenia ekranu. } ClrScr; { Wpisanie tekstu naglowka. GotoXY (2, 3); writeln ('Sortowanie babelkowe tablicy a'); writeln ('--------------------------------');
}
Rozdział 8. • Algorytmy sortowania danych { Wprowadzenie danych do tablicy. for i := 1 to n do begin write (' a[', i, '] = '); readln (a[i]); end;
251
}
{ Wpisanie zachety, a potem wyczyszczenie tej linii. } write (' Wcisnij SPACJE, by dokonac sortowania.'); repeat { Powtarzaj do wcisniecia klawisza. } until KeyPressed; y := WhereY; DelLine; { Wyswietlenie tablicy przed sortowaniem. GotoXY (1, y+2); write ('Tablica nieposortowana: '); for i := 1 to n do write (a[i]:4);
}
{ Wywolanie procedury Sort_Babelkowe. Sort_Babelkowe;
}
{ Wyswietlenie tablicy po sortowaniu. GotoXY (4, y+4); TextBackground (7); TextColor (0); write ('Tablica posortowana: '); for i :=1 to n do write (a[i]:4);
}
readln; end.
Przedstawiony program jest bardzo podobny do programu z ćwiczenia 8.9. Inna jest procedura sortowania — tym razem bąbelkowego — o nazwie Sort_Babelkowe oraz związane z nią zmienne. Podprogram sortujący bez odstępstw realizuje algorytm przedstawiony na rysunku 8.9, a omówiony w rozwiązaniu ćwiczenia 8.12. Program główny (w porównaniu z programem cw8_9) został wzbogacony o kolejne procedury z modułu Crt. Są to KeyPressed, TextBackground i TextColor. W dotychczasowych programach w Turbo Pascalu w celu wstrzymania wykonywania programu stosowana była bezparametrowa instrukcja readln. Zatrzymywała ona wykonanie programu, aż do wciśnięcia Enter. W tym programie w celu wstrzymania biegu programu została użyta funkcja KeyPressed. Funkcja ta bada stan bufora klawiatury i zwraca
252
Algorytmy • Ćwiczenia
wartość True po wciśnięciu dowolnego klawisza. W programie jest umieszczona w pętli programowej w postaci repeat until KeyPressed, co oznacza „powtarzaj, aż do wciśnięcia klawisza”. Po wciśnięciu klawisza program opuszcza pętlę, po czym wykonywana jest kolejna instrukcja w programie. Użyte w programie procedury TextBackground (parametr typu Byte) i TextColor (parametr typu Byte) pozwalają na zmianę, odpowiednio, koloru tła i koloru wyprowadzanego tekstu. Wartość liczbowa parametru w nawiasie określa kolor tła i znaków wpisywanego tekstu. Dla procedury TextBackground dopuszczalne są wartości parametru w zakresie , a dla TextColor . Poeksperymentuj, wstawiając inne wartości parametrów w procedurach zmieniających kolor. Działanie programu przedstawia rysunek 8.10.
Rysunek 8.10. Zrzuty ekranowe dokumentujące działanie programu cw8_13. Na pierwszym planie po wykonaniu programu, w tle — przed wciśnięciem Spacji
Rozdział 8. • Algorytmy sortowania danych
253
Ć W I C Z E N I E
8.14
Sortowanie bąbelkowe. Aplikacja w Excelu
Wykorzystując algorytm z rysunku 8.9, napisz w języku VBA procedurę, która metodą sortowania bąbelkowego uporządkuje wygenerowaną losowo tablicę liczb. Wyświetlanie wyników w oknie Immediate należy wymusić za pomocą oddzielnej procedury.
Rozwiązanie 1. Uruchom program Excel i zapisz domyślnie pojawiający się
Zeszyt1 w wybranym przez siebie katalogu pod nazwą cw8_14 albo wczytaj gotowy arkusz cw8_14.xls z katalogu EX/Rozdz_08. 2. Po wywołaniu okna edytora VBE i wstawieniu moduł standardowego (Insert/Module) wpisz w sekcji General (Ogólne) modułu Module1 (Moduł1) kod z listingu 8.6, realizujący w Visual Basicu algorytm sortowania bąbelkowego. Listing 8.6. Kod programu sortowania bąbelkowego 'Wszystkie zmienne muszą być zadeklarowane. Option Explicit 'Deklaracja zmiennych. Dim i, k, tmp, zam As Integer Dim a(1 To 20) As Integer Dim Tablica, Dopisek As String Sub Sort_Babelkowe() 'Procedura sortowania przez wybieranie 'wraz z kodem umożliwiającym jej sprawdzenie. 'Deklaracja stałej. Const n = 20 'Zapełnienie tablicy losowo wybranymi liczbami. Randomize For i = 1 To n a(i) = Int(Rnd * 20) Next i 'Wywołanie procedury wyświetlającej tablicę. Dopisek = "przed sortowaniem: " Call Wyswietl_Tablice(n)
254
Algorytmy • Ćwiczenia 'Kod procedury sortującej. For i = 2 To n For k = n To i Step -1 If a(k - 1) > a(k) Then tmp = a(k - 1) a(k - 1) = a(k) a(k) = tmp End If Next k Next i 'Koniec procedury. 'Wywołanie procedury wyświetlającej tablicę. Dopisek = "po sortowaniu: " Call Wyswietl_Tablice(n) End Sub Sub Wyswietl_Tablice(n As Integer) 'Składanie łańcucha znaków do wyświetlenia. Tablica = "Tablica " & Dopisek 'Wyświetlenie zawartości posortowanej tablicy. For i = 1 To n 'Utworzenie tekstu do wyświetlenia. Tablica = Tablica & a(i) & " " Next i 'Wyświetlenie posortowanej tablicy 'w oknie Immediate. Debug.Print Tablica End Sub
Gdy kod zostanie wpisany, Visual Basic automatycznie wyróżni w nim trzy oddzielone linią poziomą (rysunek 8.11) fragmenty: T deklarację zmiennych, T procedurę sortującą Sort_Babelkowe wraz z dodatkowym kodem umożliwiającym jej sprawdzenie, T procedurę wyświetlania wyników Wyswietl_Tablice. Inaczej niż w przypadku wcześniejszych programów w tym rozdziale, zakodowanych w VBA, zmienne zostały zadeklarowane na poziomie modułu, a nie na poziomie procedury. Dzięki temu są one dostępne dla każdej procedury w module. Poziom poznajemy wizualnie po miejscu wpisania deklaracji zmiennych. Jeżeli wpisane są na samej górze okna General, jest to poziom modułu. Jeżeli deklaracja odbywa się w obrębie procedury, jest to poziom procedury.
Rozdział 8. • Algorytmy sortowania danych
255
Rysunek 8.11. Wygląd ekranu z kodem i wynikami procedury sortującej Sort_Babelkowe
W obrębie procedury Sort_Babelkowe znajduje się kod realizujący porządkowanie bąbelkowe zgodnie ze schematem blokowym przedstawionym na rysunku 8.9, kod generujący tablicę i wywołanie procedury wyświetlania wyników Wyswietl_Tablice(n). Procedura jest jedna, ale wywoływana jest dwukrotnie. Zależnie od miejsca wywołania inną wartość ma zmienna tekstowa Dopisek — tuż po wygenerowaniu tablicy Dopisek = "przed sortowaniem: ", a po uporządkowaniu Dopisek = "po sortowaniu: ". Ponieważ zmienne zostały zadeklarowane na poziomie modułu, każda zmiana zmiennej Dopisek w procedurze Sort_Babelkowe widoczna jest w procedurze Wyswietl_Tablice(n). Dlatego po wyświetleniu wyniku pojawia się inny napis wprowadzający przed wierszem liczb tworzących tablicę w oknie Immediate — najpierw jest to „Tablica przed sortowaniem:”, potem „Tablica po sortowaniu:”. Wyswietl_Tablice(n) jest procedurą jednoparametrową
(jednoargumentową). Parametrem jest stała n, której wartość jest nadawana w Sort_Babelkowe i dopiero potem przekazywana
256
Algorytmy • Ćwiczenia
dalej. Proszę rozważyć, co należy zmienić w kodzie programu, by obie procedury mogły być bezparametrowe. 3. Uruchom kilkakrotnie program, wciskając klawisz F5. Zmieniaj wartości stałej n oraz rozmiar tablicy i obserwuj zmiany.
Sortowanie szybkie Metoda sortowania szybkiego to, jak sama nazwa wskazuje, najszybsza metoda porządkowania zbioru liczb. Zaliczana jest do grupy metod typu „dziel i rządź” (z ang. divide and conquer). Złożony problem rozbijany jest na podproblemy, po rozwiązaniu których wyniki są przekazywane do procedury wywołującej, gdzie następuje ich łączenie. Rezultat stanowi posortowany ciąg elementów. W porządkowaniu szybkim istotą przetwarzania jest wielokrotne, rekurencyjne wywołanie procedury dokonującej podziału tablicy. Tablica dzielona jest na dwie sekcje względem pewnego elementu środkowego stanowiącego jej oś (z ang. pivot). Pierwsza zawiera elementy mniejsze od elementu środkowego, druga —elementy większe od niego lub mu równe. Następnie stosuje się ponownie procedurę sortowania szybkiego dla każdej utworzonej sekcji. Ten wielokrotny podział rozdziela tablicę na coraz mniej liczne partycje, co automatycznie powoduje posortowanie. Rekursja dokonuje połączenia wyników cząstkowych w jedną całość. Element środkowy zwany jest również elementem osiowym. Najczęściej przyjmuje się, że elementem osiowym jest centralny element tablicy albo, prościej i szybciej, jej pierwszy element. Ć W I C Z E N I E
8.15
Sortowania szybkie
Postępując zgodnie z zasadami sortowania szybkiego, uporządkuj bez pomocy komputera zbiór elementów {3, 2, 7, 5, 1}.
Rozwiązanie Dane: Zbiór liczb do porządkowania tworzący pięcioelementową tablicę {3, 2, 7, 5, 1}.
Rozdział 8. • Algorytmy sortowania danych
257
Oczekiwany wynik: Liczby uporządkowane w kolejności od najmniejszej do największej. Analiza problemu: Porządkowanie należy rozpocząć od określenia elementu osiowego aktualnie sortowanej części tablicy. Przyjmijmy, że będzie nim pierwszy element porządkowanej sekcji. Następnie elementy tablicy są tak przestawiane, by po lewej stronie elementu środkowego znajdowały się elementy mniejsze, pozostałe zaś po prawej. Model tego porządkowania jest bardziej złożony niż omawiane wcześniej. W tym miejscu rozdziału nie jest możliwe wskazanie wszystkich kolejnych wywołań rekurencyjnych, ponieważ brakuje algorytmu realizującego szybkie sortowanie. W nawiasach klamrowych zamknięty jest element środkowy, a w nawiasach kwadratowych aktualnie porządkowany zbiór elementów. Krok 1. Porządkowana jest cała tablica [3 2 7 5 1]. Element osiowy to 3. Elementy mniejsze od med = 3 są przestawiane na lewo od osi, pozostałe na prawo. Efektem porządkowania jest tablica w postaci [2 1 {3} 7 5]. Krok 2. Porządkowana jest część tablicy na lewo od 3, czyli [2 1]. Teraz elementem osiowym jest 2. Liczbę mniejszą należy przestawić na lewo od 2. Po uporządkowaniu sortowana część tablicy przyjmuje postać [1 {2}]. Krok 3. Porządkowana jest część tablicy na prawo od elementu 3 z kroku pierwszego, czyli [7 5]. Elementem osiowym jest 7. Liczbę 5 należy przestawić na lewo od 7. Efektem sortowania jest partycja w postaci [5 {7}]. Krok 4. Składanie posortowanych sekcji tablicy. Otrzymujemy tablicę w postaci [1 2 3 5 7]. Ć W I C Z E N I E
8.16
Schemat blokowy sortowania szybkiego
Przedstaw w postaci schematu blokowego procedurę szybkiego sortowania zbioru n liczb rzeczywistych.
258
Algorytmy • Ćwiczenia
Rozwiązanie Dane: Zbiór liczb do porządkowania a1, a2, …, an tworzący tablicę a[1,…n], ilość liczb n, wskaźnik pierwszego elementu sortowanej tablicy o nazwie lewy oraz wskaźnik ostatniego elementu sortowanej tablicy o nazwie prawy. Oczekiwany wynik: Liczby uporządkowane w kolejności od najmniejszej do największej. Analiza problemu: Sortowanie odbywa się zgodnie z opisem przedstawionym powyżej. Procedura szybkiego sortowania ma postać Sortuj (lewy, prawy). Każdorazowo po wywołaniu funkcji Sortuj należy sprawdzić, czy w porządkowanym zbiorze znajduje się przynajmniej jeden element. Jest tak, o ile prawy > lewy. W przeciwnym razie porządkowanie nie jest potrzebne. Następnie określa się partycję tablicy, która będzie sortowana. Służą do tego zmienne pomocnicze l — dolny wskaźnik partycji oraz p — wskaźnik górny. Przyjmują one przekazywane przez procedurę zewnętrzną wartości. Na początku są to, odpowiednio, lewy oraz prawy. Po określeniu sekcji do porządkowania wyliczany jest element osiowy med. Każdorazowo tym elementem jest pierwszy element sortowanej partycji. Gdy postępujemy od lewej, w głównej części algorytmu należy wyszukać obiekt l mniejszy od med. Gdy przemieszczamy się od prawej, szukamy elementu p większego lub równego med (jeżeli badany element nie spełnia odpowiedniego warunku, to bada się kolejny, na prawo od l oraz na lewo od p). Po znalezieniu takich elementów i jednoczesnym spełnieniu warunku l p należy zamienić je miejscami. Następnie, po inkrementacji l oraz dekrementacji p, opisaną operację powtarza się. Gdy l staje się większe od p, to oznacza, że należy zmienić porządkowana partycję. Dokonuje się tego poprzez rekurencyjne wywołania kolejnych instancji procedury sortującej Sortuj(lewy, p) oraz Sortuj(l, prawy) ze zmienionymi granicami sekcji przeznaczonych do porządkowania. Proces powtarza się, aż do osiągnięcia najniższego poziomu wywołań rekurencyjnych. Następnie wyniki cząstkowe przekazywane są z poziomów niższych na wyższe. Wynikiem końcowym jest posortowana rosnąco tablica a.
Rozdział 8. • Algorytmy sortowania danych
259
Ć W I C Z E N I E
8.17
Sortowanie szybkie. Podprogram w Turbo Pascalu
Postępując zgodnie ze schematem przedstawionym na rysunku 8.12, napisz w Turbo Pascalu podprogram realizujący sortowanie szybkie. Rysunek 8.12. Schemat blokowy sortowania szybkiego
260
Algorytmy • Ćwiczenia
Rozwiązanie W listingu poniżej podany jest kod procedury sortowania szybkiego zgodnego z algorytmem z rysunku 8.12. Wszelkie informacje potrzebne do zrozumienia kodu znajdują się w rozwiązaniu do ćwiczenia 8.16. Listing 8.7. Kod podprogramu sortowania szybkiego { ---- Deklaracja i kod procedury Sort_Szybkie ------ } procedure Sort_Szybkie(lewy, prawy : Integer); begin if lewy