Algorytmy Struktury Danych i Technika Program Ow Ani A

Embed Size (px)

Citation preview

Spis treciMoje dopiski K.

Przedmowa... Rozdzia1 Zanim wystartujemy1.1. Jak to wczeniej bywao, czyli wyjtki z historii maszyn algorytmicznych 1.2. Jak to si niedawno odbyo, czyli o tym kto "wymyli" metodologi programowania 1.3. Proces koncepcji programw 1.4. Poziomy abstrakcji opisu i wybr jzyka 1.5. Poprawno algorytmw

9 1719 21 22 23 25

Rozdzia 2 Rekurencja

29

2.1. Definicja rekurencji 29 2.2. Ilustracja pojcia rekurencji 31 2.3. Jak wykonuj si programy rekurencyjne? 33 2.4. Niebezpieczestwa rekurencji 34 2.4.1. Cig Fibonacciego 35 2.4.2. Stack overflow! 36 2.5. Puapek cig dalszy 37 2.5.1. Std do wiecznoci 38 2.5.2. Definicja poprawna, ale 38 2.6. Typy programw rekurencyjnych.................................................................................... 40 rekurcncyjnych 2.7. Mylenie rekurencyjne 42 2.7.1. Spirala 42 2.7.2. Kwadraty parzyste" 44 2.8. Uwagi praktyczne na temat technik rekurencyjnych 45 2.9. Zadania 47 2.10. Rozwizania i wskazwki do zada 49

Rozdzia 3 Analiza sprawnoci algorytmw3.1. Dobre samopoczucie uytkownika programu

5354

63.2. Przykad 1: Jeszcze raz funkcja silnia 3.3. Przykad 2; Zerowanie fragmentu tablicy 3.4. Przykad 3: Wpadamy w puapk 3.5. Przykad 4: Rne typy zoonoci obliczeniowej... 3.6. Nowe zadanie: uproci obliczenia! 3.7. Analiza programw rekurencyjnych 3.7.1. Terminologia 3.7.2. Ilustracja metody na przykadzie 3.7.3. Rozkad logarytmiczny" 3.7.3 3.7.4. Zamiana dziedziny rwnania rekurencyjnego 3.7.5. Funkcja Ackermanna, czyli co dla smakoszy 3.8. Zadania 3.9. Rozwizania i wskazwki do zada Rozdzia 4 Algorytmy sortowania 2 4.1. Sortowanie przez wstawianie, algorytm klasy 0(N ) 2 4.2. Sortowanie bbelkowe, algorytm klasy O(N2) 4.3. Quicksort, algorytm klasy 0(N log^N) 2 4.4. Uwagi praktyczne...........................................57 ...........................................61 ...........................................64

.........................................65 .........................................68 .........................................68 .........................................69 .........................................71 .........................................72 .........................................72 .........................................74 .........................................75 .........................................76 .........................................78 .........................................81 .........................................82 .........................................84 .........................................87 .........................................90

Rozdzia5 Struktury danych ...........................................93 5.1. Listy jednokierunkowe ...........................................94 5.1.1. Realizacja struktur danych listy jednokierunkowej ...........................................96 ...........................................98 5.1.2. Tworzenie listy jednokierunkowej 5.1.3. Listy jednokierunkowe- teoria i rzeczywisto.... .........................................108 .........................................122 5.2. Tablicowa implementacja list .........................................122 5.2.1. Klasyczna reprezentacja tablicowa .........................................124 5.2.2. Metoda tablic rwnolegych .........................................127 5.2.3. Listy innych typw .........................................128 5.3. Stos 5.3.1. Zasada dziaania stosu .........................................128 5.4. Kolejki FIFO .........................................133 5.5. Sterty i kolejki priorytetowe .........................................136 5.6. Drzewa i ich reprezentacje .........................................143 5.6.1. Drzewa binarne i wyraenia arytmetyczne .........................................147 5.7. Uniwersalna struktura sownikowa .........................................152 5.8. Zbiory .........................................159 5.9. Zadania .........................................161 5.10. Rozwizania zada .........................................162 Rozdzia 6 Derekursywacja 6.1. Jak pracuje kompilator? 6.2. Odrobina formalizmu... nie zaszkodzi! 6.3. Kilka przykadw derekursywacji algorytmw 6.4. Derekursywacja z wykorzystaniem stosu 6.4.1. Eliminacja zmiennych lokalnych 6.5. Metoda funkcji przeciwnych 6.6. Klasyczne schematy derekursywacji

.........................................165 .........................................166 .........................................169 .........................................170 .........................................174 .........................................175 .........................................177 .........................................180

Spis treci

7

6.6.1. Schemat typu while 181 6.6.2. Schemat typu if... else........................................................................................182 if.. else 6.6.3. Schemat z podwjnym wywoaniem rekurencyjnym 185 6.7. Podsumowanie 187

Rozdzia 7 Algorytmy przeszukiwania 7.1. Przeszukiwanie liniowe7.2. Przeszukiwanie binarne 7.3. Transformacja kluczowa. 7.3.1. W poszukiwaniu funkcji H 7.3.2. Najbardziej znane funkcje H 7.3.3. Obsuga konfliktw dostpu 7.3.4. Zastosowania transformacji kluczowej 7.3.5. Podsumowanie metod transformacji kluczowej

189 189190 191 193 194 197 204 204

Rozdzia 8 Przeszukiwanie tekstw 8.1. Algorytm typu brute-force8.2. Nowe algorytmy poszukiwa 8.2.1. Algorytm K-M-P 8.2.2. Algorytm Boyera i Moore'a 8.2.3. Algorytm Rabina i Karpa

207 207210 211 216 218

Rozdzia 9 Zaawansowane techniki programowania

223

9.1. Programowanie typu"dziel-i-rzd"................................................................................ 224 d z i e l i rzd" . 9.1.1. Odszukiwanie minimum i maksimum w tablicy liczb 225 y 9.1.2. Mnoenie macierz)' o rozmiarze NxN................................................................ 229 N*N 9.1.3. Mnoenie liczb cakowitych 232 9.1.4. Inne znane algorytmy "dziel-i-rzd".................................................................. 233 dziel-i-rzd".. 9.2. Algorytmy ,"aroczne", czyli przeksi co nadszed ju czas 234 9.2.1.Problem plecakowy, czyli nieatwe nieatwe turysty-piechura.......................... 235 Problem plecakowy, czyli jest ycie jest ycie turysty-piechura 9.3. Programowanie dynamiczne 238 9.4. Uwagi bibliograficzne 243

Rozdzia 10 Elementy algorytmiki gratw

245

10.1. Definicje i pojcia podstawowe 246 10.2. Sposoby reprezentacji grafw 248 10.3. Podstawowe operacje na grafach 249 10.4. Algorytm Roy-Warshalla 251 10.5. Algorytm Floyda 254 10.6. Przeszukiwanie grafw 257 10.6.1. Strategia w gb" 257 10.6.2. Strategia wszerz" 259 10.7. Problem waciwego doboru........................................................................................ 261 doboru 10.8. Podsumowanie 266

Rozdziau

Algorytmy numeryczne

267268 269 270 272

11.1. Poszukiwanie miejsc zerowych funkcji 11.2. Iteracyjne obliczanie wartoci funkcji 11.3. Interpolacja funkcji metod Lagrange'a 11.4. Rniczkowanie funkcji

8 B

Spis treci 11.5. Cakowanie funkcji metod Simpsona 11.6. Rozwizywanie ukadw rwna liniowych metod Gaussa 11.7. Uwagi kocowe 274 276 279

Rozdzia 12 W stron sztucznej inteligencji12.1. Reprezentacja problemw 12.2. Gry dwuosobowe i drzewa gier12.3. Algorytm mini-max

281282 283286

Rozdzia13 Kodowanie i kompresja danych.....13.1. Kodowanie danych i arytmetyka duych liczb 13.2. Kompresja danych metod Huffmana

293294 302

Rozdzia 14 Zadania rne14.1. Teksty zada14.2. Rozwizania

309. 309312

Dodatek A Poznaj C++ w pi minut Literatura Spis ilustracji Spis tablic Skorowidz

317 337 339 343 345

PrzedmowaAlgorytmika stanowi ga wiedzy, ktra w cigu ostatnich kilkudziesiciu lat do starczya wielu efektywnych narzdzi wspomagajcych rozwizywanie rnorod nych problemw przy pomocy komputera. Ksika ta prezentuje w miar szeroki. ale i zarazem pogbiony wachlarz tematw z tej dziedziny. Ma ona rwnie ukaza Czytelnikowi odpowiedni perspektyw moliwych zastosowa komputerw i pozwoli mu - j e l i mona uy takiej metafory' - nie wywaa drzwi tam, gdzie kto dawno ju je otworzy.

Dla kogo jest ta ksika?Niniejszy podrcznik szczeglnie polecam osobom zainteresowanym programowaniem. a nie majcym do tego solidnych podstaw teoretycznych. Poniewa grupuje on do obszern klas zagadnie z dziedziny informatyki, bdzie rwnie uyteczny jako repetytorium dla tych, ktrzy zajmuj si programowaniem zawodowo. Jest to ksika dla osb, ktre zetkny si ju z programowaniem i rozumiej podstawowe pojcia, takie jak zmienna, program, algorytm, kompilacja... - tego typu terminy bd bowiem stanowiy podstaw jzyka uywanego w tej ksice.

Co odrnia t ksik od innych podrcznikw?Przede wszystkim - nie jest to publikacja skierowana jedynie dla informatykw. Liczba osb wykorzystujcych komputer do czego wicej ni do gier i pisania listw jest wbrew pozorom do dua. Zaliczy do tego grona mona niewtpliwie studentw kierunkw informatycznych, ale nie tylko; w programach wikszoci

10

Przedmowa

studiw technicznych znajduj si elementy informatyki, majce na celu przygoto wanie do sprawnego rozwizywania problemw przy pomocy komputera. Nie wolno pomija take stale rosncej grupy ludzi zajmujcych si programowaniem traktowanym jako hobby. Uwzgldniajc tak du rnorodno potencjalnych odbiorcw tej publikacji duy nacisk zosta pooony na prostot i klarowno wykadu oraz unikanie niedomwie - oczywicie w takim stopniu, w jakim to byo moliwe ze wzgldu na ograniczon objto i przyjty ukad ksiki.

Dlaczego C++?Niewtpliwe kilka sw wyjanienia naley powici problemowi jzyka pro gramowania, w ktrym s prezentowane algorytmy w ksice. Wybr pad na no woczesny i modny jzyk C++ ktrego precyzja zapisu i modulamo przemawiaj za uyciem go do programowania nowoczesnych aplikacji. Warto jednak przy oka zji podkreli, e sam jzyk prezentacji algorytmu nie ma istotnego znaczenia dla jego dziaania -jest to tylko narzdzie i stanowi wycznie zewntrzn powok, ktra ulega zmianom w zalenoci od aktualnie panujcych md. Poniewa C++ zdobywa sobie olbrzymia popularno, zosta wybrany dla potrzeb tej ksiki. Dla kogo, kto niedawno pozna C++, moe to by doskonaa okazj do prze studiowania potencjalnych zastosowa tego jzyka. Dla programujcych do tychczas tylko w Pascalu zosta przygotowany mini-kurs jzyka C++, ktry powinien umoliwi im byskawiczne opanowanie podstawowych rnic midzy C++ i Pascalem. Oczywicie niemoliwe jest szczegowe nauczenie tak obszernego pojciowo jzyka, jakim jest C++, dysponujc objtoci zaledwie krtkiego dodatku - bo tyle zostao przeznaczone na ten cel. Zamiarem byo jedynie przeamanie bariery skadniowej, tak aby byy zrozumiae prezentowane listingi. Czytelnik pragncy poszerzy zasady programowania w C++ moe sign na przykad po [PohI89], | WF92] lub [Wr94] gdzie zagadnienia te zostay poruszone szczegowo. Ambitnym i odwanym programistom mona poleci dokadne przestudiowanie [STR92] - dziea napisanego przez samego twrc jzyka i stanowicego osta teczn referencj na temat C++.

Jak naley czyta t ksik?Czytelnik, ktry zetkn si wczeniej z tematyk podejmowan w tej ksice, moe j czyta w do dowolnej kolejnoci.

Przedmowa

11

Pocztkujcym zalecane jest trzymanie si porzdku narzuconego przez ukad rozdziaw. Ksika zawiera szczegowy skorowidz i spis ilustracji - powinny one uatwi odszukiwanie potrzebnych informacji. Wiele rozdziaw zawiera przy kocu zestaw zada zwizanych tematycznie z aktualnie opisywanymi zagadnieniami. W duej czci zadania te s rozwizane. ewentualnie podane s szczegowe wskazwki do nich. Oprcz zada tematycznych ostatni rozdzia zawiera zestaw rnorodnych zada. ktre nie zmieciy si w toku wykadu. Przed rozwizaniem zada w nim zamiesz czonych zaleca si dokadne przestudiowanie caego materiau, ktry obejmuj po przedzajce go rozdziay.

Ostrono nie zawadzi...Niemoliwe jest zaprezentowanie wszystkiego, co najwaniejsze w dziedzinie algorytmiki, w objtoci jednej ksiki. Jest to niewykonalne z uwagi na roz pito dziedziny, z jak mamy do czynienia. Moe si wic okaza, e to, co zostao pomylane jako logicznie skonstruowana cao, jednych rozczaruje. innych za przytoczy ogromem poruszanych zagadnie. Pragnieniem autora byo stworzenie w miar reprezentacyjnego przegldu zagadnie algorytmicz nych przydatnego dla tych Czytelnikw, ktrzy programowanie maj zamiar potraktowa w sposb profesjonalny.

Co zostao opisane w tej ksice?Opis poniszy jest w pewnym sensie powtrzeniem spisu treci, jednak zawiera on co, czego aden spis treci nie potrafi zaoferowa - minimalny komentarz dotyczcy zawartoci.

Rozdzia 1 Zanim wystartujemyRozbudowany wstp pozwalajcy wzi gboki oddech" przed przystpieniem do klawiatury...

Rozdzia 2 RekurencjaRozdzia ten jest powicony jednemu z najwaniejszych mechanizmw uywa nych w procesie programowania - rekurencji. Uwiadamia zarwno oczywiste zalety, j a k i nie zawsze widoczne wady tej techniki programowania.

12

Przedmowa

Rozdzia 3 Analiza sprawnoci algorytmwPrzegld najpopularniejszych i najprostszych metod sucych do obliczania spraw noci obliczeniowej algorytmw i porwnywania ich ze sob w celu wybrania najefektywniejszego".

Rozdzia 4 Algorytmy sortowaniaPrezentuje najpopularniejsze i najbardziej znane procedury sortujce.

Rozdzia 5 Struktury danychOmawia popularne struktury danych (listy, kolejki, drzewa binarne etc.) i ich implementacj programow. Szczegln uwag powicono ukazaniu moli wych zastosowa nowo poznanych struktur danych.

Rozdzia 6 Derekursywacja i optymalizacja algorytmwPrezentuje sposoby przeksztacania programw rekurencyjnych na ich wersje iteracyjne. Rozdzia ten ma charakter bardzo techniczny" i jest przeznaczony dla programistw zainteresowanych problematyk optymalizacji programw.

Rozdzia 7 Algorytmy przeszukiwania

Rozdzia ten stosuje kilka poznanych ju wczeniej metod do zagadnienia wy szukiwania elementw w sowniku, a nastpnie szczegowo omawia metod transformacji kluczowej (ang. hashing).

Rozdzia 8 Przeszukiwanie tekstwZe wzgldu na wag tematu algorytmy przeszukiwania tekstw zostay zgrupowane w osobnym rozdziale. Szczegowo omwiono metody brute-force, K-M-P, Boyera i Moore'a, Rabina i Karpa.

Rozdzia 9 Zaawansowane techniki programowaniaWieloletnie poszukiwania w dziedzinie algorytmiki zaowocoway wynalezie niem pewnej grupy metod o charakterze generalnym: programowanie dyna miczne. dziel-i-rzd, algorytmy aroczne (ang. greedy). Te meta-algorytmy rozszerzaj znacznie zakres moliwych zastosowa komputerw do rozwizy wania problemw.

Przedmowa

13

Rozdzia 10 Elementy algorytmiki grafwOpis jednej z najciekawszych struktur danych wystpujcych w informatyce. Grafy uatwiaj (a czasami po prostu umoliwiaj) rozwizanie wielu problemw, ktre traktowane przy pomocy innych struktur danych wydaj si nie do rozwizania.

Rozdzia 11 Algorytmy numeryczneKilka ciekawych problemw natury obliczeniowej, ukazujcych zastosowanie komputerw w matematyce, do wykonywania oblicze przyblionych.

Rozdzia 12 Czy komputery mog myle?Wstp do bardzo rozlegej dziedziny tzw. sztucznej inteligencji. Przykad im plementacji programowej popularnego w teorii gier algorytmu Mini-Max.

Rozdzia 13 Kodowanie i kompresja danychOmwienie popularnych metod kodowania i kompresji danych: systemu krypto graficznego z kluczem publicznym 1 metody Huffmanna Rozdzia zawiera ponadto dokadne omwienie sposobu wykonywania operacji arytmetycznych na bardzo duych liczbach cakowitych.

Rozdzia 14 Zadania rneZestaw rnorodnych zada, ktre nie zmieciy si w gwnej treci ksiki.

Wersje programw na dyskietceProgramy znajdujce si na doczonej do ksiki dyskietce s zazwyczaj peniejsze i bardziej rozbudowane. Jeli w trakcie wykadu jest prezentowana jaka funkcja bez podania explicite sposobu jej uycia, to na pewno dyskietkowa wersja za wiera reprezentacyjny przykad j e j zastosowania (przykadowa funkcja main i komplet funkcji nagwkowych). Warto zatem podczas lektury porw nywa wersje dyskietkowe z tymi, ktre zostay omwione na kartach ksiki! Pliki na dyskietce s w formacie MS-DOS. Programy zostay przetestowane zarw no systemie DOS (kompilator Borland C++), jak i w systemie UNIX U (kompilator GNIJ C++). Na dyskietce znajduj si zatem pene wersje programw, ktre z zaoenia powinny da si od razu uruchomi na dowolnym kompilatorze C++ (UNIX lub

14

Przedmowa DOS/Windows). Jedyny wyjtek stanowi programy graficzne" napisane dla popularnej serii kompilatorw firmy Borland; wszelkie inicjacje trybw graficznych itp. s tam wykonane wedug standardu tej firmy. W tekcie znajduje si jednak tabelka wyjaniajca dziaanie uytych instrukcji gra ficznych. tak wic nawet osoby, ktre nigdy nie pracoway z kompilatorami Borlanda, poradz sobie bez problemu z analiz programw przykadowych.

Konwencje typograficzne i oznaczeniaPoniej znajduje si kilka typowych oznacze i konwencji, ktre mona napotka na kartkach ksiki. W szczeglnoci regu jest, e wszystkie listingi i teksty ukazujce si na ekranie zostay odrnione od zasadniczej treci ksiki czcionk C o u r i e r :

prog.cpp Tekst programu znajduje s i na d y s k i e t c e w p l i k u prog.cpp(w tej wersji nie bardzo) K.

Inna konwencja dotyczy odnonikw bibliograficznych:

Patrz [Odn93] - odnonik do pozycji bibliograficznej [Odn93] ze spisu na kocu ksiki.

Uwagi na marginesieKsika ta powstaa w trakcie mojego kilkuletniego pobytu we Francji, gdzie miaem niepowtarzaln okazj korzystania z interesujcych zasobw bibliogra ficznych kilku bibliotek technicznych. Wikszo tytuw, ktrych lektura zain spirowaa mnie do napisania tej ksiki, jest cigle w Polsce do trudno (jeli w ogle) dostpna i bdzie dla mnie du radoci, jeli znajd si osoby, ktrym niniejszy podrcznik oszczdzi w jaki sposb czasu i pienidzy. Wstpny wydruk (jeszcze w pieluchach") ksiki zosta przejrzany i opatrzony wieloma cennymi uwagami przez Zbyszka Chamskiego. Ostateczna wersja ksiki zostaa poprawiona pod wzgldem poprawnoci jzykowej przez moj siostr, Ilon. Chciabym gorco podzikowa im obojgu za wykonan prac, liczc jednoczenie, e efekt kocowy ich zbytnio nie zawid... P.W. Lannion Wrzesie 1995

Przedmowa

15

Uwagi do wydania 2W biecej edycji ksiki, wraz z caym tekstem zostay gruntownie przejrzane i poprawione programy przykadowe, jak rwnie, rysunki znajdujce sic w tekcie. ktre w pierwszym wydaniu zawieray kilka niekonsekwencji. Zostaa zwikszona czytelno listingw (wyrnienie sw kluczowych), oraz dooono trzy nowe rozdziay (77 - 13) Uzupenieniu ulegy ponadto rozdziay: 10 (gdzie omwione zostao dodatkowo m.in. przeszukiwanie grafw) i 5 (omwiono implementacj zbiorw). Liczc, e wniesione poprawki odbij si pozytywnie na jakoci publikacji, ycz przyjemnej i poytecznej lektury. P.W. Czerwiec 1997

Rozdzia 1

Zanim wystartujemyZanim na dobre rozpoczniemy operowanie takimi pojciami jak wspomniany we wstpie algorytm", warto przedyskutowa dokadnie, co przez nie rozumiemy. ALGORYTM: skoczony cig/sekwencja regu, ktre aplikuje si na skoczonej liczbie danych, pozwalajcy rozwizywa zblione do siebie klasy problemw, zesp regu charakterystycznych dla pewnych oblicze lub czynno ci informatycznych C. definicje powysze wydaj si klarowne i jasne, jednak obejmuj na tyle rozlege obszary dziaalnoci ludzkiej, e daleko im do precyzji. Pomijajc chwilowo znaczenie, samo pochodzenie terminu algorytm nie zawsze byo do koca jasne. Dopiero specjalici zajmujcy si histori matematyki odnaleli najbardziej prawdopodobne rdo sw: termin ten pochodzi od nazwiska per skiego pisarza-matematyka Abu Ja'far Mohammed ibn Musa al-Khowarizmi (IX wieku n.e.). Jego zasug jest dostarczenie klarownych regu wyjaniaj cych krok po kroku zasady operacji arytmetycznych wykonywanych na licz bach dziesitnych. Sowo algorytm czsto jest czone z imieniem greckiego matematyka Euklidesa (365-300 p.n.e.) i jego synnym przepisem na obliczanie najwikszego wspl nego dzielnika dwch liczb a i b (NWD): dane wejciowe: a i b;11

Definicja pochodzi ze sownika Le Nouveau Petit Robert (Dictionnaircs le Robert - Paris 1994) - (tumaczenie wasne). Jego nazwisko pisane byo po acinie jako Algorismus.

18

Rozdzia. Zanim wystartujemy dopki a>0 wykonuj; podstaw za c reszto z dzielenia a przez b; podstaw za b liczb a; podstaw za a liczb c; podstaw za res liczb b; rezultat: res. Oczywicie Euklides nie proponowa swojego algorytmu dokadnie w ten sposb (w miejsce funkcji i reszty z dzielenia stosowane byy sukcesywne odejmowania), ale jego pomys mona zapisa w powyszy sposb bez szkody dla wyniku, ktry w kadym przypadku bdzie taki sam. Nie jest to oczywicie jedyny algo rytm, z ktrym mielimy w swoim yciu do czynienia. Kady z nas z pewnoci umie zaparzy kaw:

wczy gaz; zagotowa niezbdn ilo wody; wsypa zmielon kaw do szklanki; zala kaw wrzc wod; osodzi do smaku: poczeka, a odpowiednio nacignie...

Powyszy przepis dziaa, ale zawiera kilka sabych punktw: co to znaczy odpo wiednia ilo wody"? Co dokadnie oznacza stwierdzenie osodzi do smaku"? Przepis przygotowania kawy ma cechy algorytmu (rozumianego w sensie zacyto wanych wyej definicji sownikowych), ale brak mu precyzji niezbdnej do wpi sania go do jakiej maszyny, tak aby w kadej sytuacji umiaa ona sobie poradzi z poleceniem przygotuj mi ma kaw". (Np. jak w praktyce okreli warunek, e kawa "odpowiednio nacigna"?). Jakie w zwizku z tym cechy powinny by przypisane algorytmowi rozumianemu w kontekcie informatycznym? Dyskusj na ten temat mona by prowadzi do dugo, ale przyjmujc pewne uproszczenia mona zadowoli si nastpu jcymi wymogami: Kady algorytm: posiada dane wejciowe (w iloci wikszej lub rwnej zero) pochodz ce z dobrze zdefiniowanego zbioru (np. algorytm Euklidesa operuje na dwch liczbach cakowitych); produkuje pewien wynik (niekoniecznie numeryczny); jest precyzyjnie zdefiniowany (kady krok algorytmu musi by jedno znacznie okrelony);

1.1. Jak to wczeniej bywao, czyli...

19

jest skoczony (wynik algorytmu musi zosta kiedy" dostarczony majc algorytm A i dane wejciowe D powinno by moliwe precyzyj ne okrelenie czasu wykonania 1(A)). Ponadto niecierpliwo kae nam szuka algorytmw efektywnych, t/n. wyko nujcych swoje zadanie w jak najkrtszym czasie i wykorzystujcych jak naj mniejsz ilo pamici (do tej tematyki powrcimy jeszcze w rozdziale 3). Zanim jednak popieszymy do klawiatury, aby wpisywa do pamici komputera programy speniajce powysze zaoenia, popatrzmy na algorytmik z per spektywy historycznej.

1.1. Jak to wczeniej bywao, czyli wyjtki z historii maszyn algorytmicznychCytowane na samym pocztku tego rozdziau imiona matematykw kojarzonych z algorytmik rozdzielone s ponad tysicem lat i mog atwo zasugerowa, e ta ga wiedzy przeywaa w cigu wiekw istnienia ludzkoci burzliwy i bysko tliwy rozwj. Oczywicie nijak si to ma do rzeczywistego postpu tej dziedziny. ktry by i cigle jest cile zwizany z rewolucj techniczn dokonujc si na przestrzeni zaledwie ostatnich dwustu lat. Popatrzmy zreszt na kilka charakte rystycznych dat z tego okresu:

-1801Francuz Joseph Marie Jacquard wynajduje krosno tkackie, w ktrym wzorzec tkaniny by "programowany" na swego rodzaju kartach perforowanych. Proces tkania by kontrolowany przez algorytm zakodowany w postaci sekwencji otworw wybitych w karcie.

-1833Anglik Charles Babbage czciowo buduje maszyn do wyliczania niektrych formu matematycznych. Autor koncepcji tzw. maszyny analitycznej, zblionej do swego poprzedniego dziea, ale wyposaonej w moliwo przeprogramo wywania, jak w przypadku maszyny Jacquarda.

- 1890Pierwsze w zasadzie publiczne i na du skal uycie maszyny bazujcej na kartach perforowanych. Chodzi o maszyn do opracowywania danych statystycznych. dzieo Amerykanina Hermana Holleritha uyte przy dokonywaniu spisu ludnoci.

20

Rozdzia 1. Zanim wystartujemy (Na marginesie warto doda, e przedsibiorstwo H o I e r i t h a przeksztacio si w I911 roku w International business Machines Corp., bardziej znane jako IBM).

- lata 30-te Rozwj bada nad teori algorytmw (plejada znanych matematykw: Turing, Godel, Markow.

- lata 40-te Budowa pierwszych komputerw oglnego przeznaczenia (gwnie dla potrzeb obliczeniowych wynikych w tym "wojennym" okresie: badania nad amaniem" kodw, pocztek kariery" bomby atomowej). Pierwszym urzdzeniem, ktre mona okreli jako komputer" by, automatyczny kalkulator M A R K 1 skonstruowany w 1944 roku (jeszcze na przekanikach. czyli jako urzdzenie elektromechaniczne). Jego twrc by Amerykanin Howard Aiken z uniwersytetu Harvard. Aiken bazowa na idei Babbage'a, ktra musiaa czeka 100 lat na swoj praktyczn realizacj! W dwa lata pniej powstaje pierwszy elektroniczny" komputer ENIAC (Jego wynalazcy: J. P. Eckert i J. W. Mauchly z uniwersytetu Pensylwania). Powszechnie jednak za naprawd" pierwszy komputer w penym tego sowa znaczeniu uwaa si EDVAC zbudowany na uniwersytecie w Princeton. Jego wyjtkowo polegaa na umieszczeniu programu wykonywanego przez kom puter cakowicie w pamici komputera. Autorem tej przeomowej idei by ma tematyk Johannes von Neumann (Amerykanin wgierskiego pochodzenia).

- okres powojenny Prace nad komputerami prowadzone s w wielu krajach rwnolegle. W gr zaczyna wchodzi wejcie na obiecujcy nowo powstay rynek komputerw (koczy si bowiem era budowania unikalnych uniwersyteckich" prototypw). Na rynku pojawiaj si kalkulatory I B M 604 i B U L L Gamma3?, a nastpnie due kompu tery naukowe np. UNIVAC 1 i IBM 650. Zaczynajcej si zarysowywa domina cji niektrych producentw usiuj przeciwdziaa badania prowadzone w wielu krajach (mniej lub bardziej systematycznie i z rnorakim poparciem politykw) ale... to j u jest lemat na osobn ksik!

- TERAZ Burzliwy rozwj elektroniki powoduje masow, do dzi trwajc komputeryzacj wszelkich dziedzin ycia. Komputery staj si czym powszechnym i niezbdnym, wykonujc tak rnorodne zadania, jak tylko kae im to wyobrania ludzka.

1.2. Jak to si niedawno odbyo, czyli...

21

1.2. Jak to si niedawno odbyo, czyli o tym kto wymyli" metodologi programowaniaZamieszczony w poprzednim paragrafie kalendarz" zosta doprowadzony do momentu, w ktrym programici zaczli mie do dyspozycji komputery z praw dziwego zdarzenia. Olbrzymi nacisk, j a k i by kadziony na rozwj sprztu. w istocie doprowadzi do znakomitych rezultatw - efekt jest widoczny dzisiaj w kadym praktycznie biurze i w coraz wikszej iloci domw prywatnych. W latach 60-tych zaczto konstruowa pierwsze naprawd due systemy infor matyczne - w sensie iloci kodu, gwnie asemblerowego, wyprodukowanego na poczet danej aplikacji. Poniewa jednak programowanie byo cigle traktowane jako dziaalno polegajca gwnie na intuicji i wyczuciu, zdarzay si cakiem powane wpadki w konstrukcji oprogramowania: albo byy twoi/one szybko systemy o maej wiarygodnoci albo te nakad pienidzy woonych w rozwj produktu znacznie przewysza szacowane wydatki i stawia pod znakiem zapytania sens podjtego przedsiwzicia. Brak byo zarwno metod, jak i narzdzi umo liwiajcych sprawdzanie poprawnoci programowania, powszechn metod programowania byo testowanie programu a do momentu jego cakowitego odpluskwienia" 1 . Zwrmy jeszcze uwag, e oba wspomniane czynniki: wiary godno systemw i poziom nakadw s niezmiernie wane w praktyce; infor matyczny system bankowy musi albo dziaa stuprocentowo dobrze, albo nie powinien by w ogle oddany do uytku! Z drugiej strony poziom nakadw przeznaczonych na rozwj oprogramowania nic powinien odbi si niekorzystnie na kondycji finansowej przedsibiorstwa. W pewnym momencie sytuacja staa si tak krytyczna, e zaczto nawet mwi o kryzysie w rozwoju oprogramowania! W roku 1968 zostaa nawet zwoana kon ferencja NATO (Garmisch, Niemcy) powicona na przedyskutowanie zaistniaej sytuacji. W rok pniej zostaa utworzona w ramach IFIP (InternationaI Federation for Information Processing) specjalna grupa robocza pracujca nad tzw. meto dologi programowania. Z historycznego punktu widzenia dyskusja na temat udowadniania poprawnoci algorytmw zacza si jednak od artykuu Johna McCarthy-ego "A basis for a mathematical theory of computation" gdzie pado zdanie: w miejsce sprawdzania programw komputerowych metod prb i bdw a do momentu ich cakowitego odpluskwienia, powinnimy udowadnia, e posiadaj one podane wasnoci". Nazwiska ludzi, ktrzy zajmowali si teoretycznymi pracami na metodologii

argonowe okrelenie procesu usuwania bdw z programu.

22

Rozdzia 1. Zanim wystartujemy programowania nic zniky bynajmniej z horyzontu; Dijkstra, Hoare, Floyd. Wirth... (Bd oni jeszcze nie raz cytowani w lej ksice!). Krtka prezentacja, ktrej dokonalimy w poprzednich dwch paragrafach. ukazuje do zaskakujc modo algorytmiki jako dziedziny wiedzy. Warto rwnie zauway, e nie jest to nauka, ktra powstaa samorodnie. O ile obec nie warto ja odrnia jako odrbn ga wiedzy, to nie sposb nie doceni wielowiekowej pracy matematykw, ktrzy dostarczyli algorytm ice zarwno narzdzi opisu zagadnie, jak i wielu uytecznych teoretycznych rezultatw. (Powysza uwaga tyczy si rwnie wielu innych dziedzin wiedzy). Teraz, gdy ju zdefiniowalimy sobie gwnego bohatera tej ksiki (bohatera zbiorowego: chodzi bowiem o algorytmy!), przejrzymy kilka sposobw uywanych do jego opisu.

1.3. Proces koncepcji programwW paragrafie poprzednim wyszczeglnilimy kilka cech charakterystycznych, ktre powinien posiada algorytm rozumiany jako pojcie informatyczne. Szcze glny nacisk pooony zosta na precyzj zapisu. Wymg ten jest wynikiem ogra nicze narzuconych przez wspczenie istniejce komputery i kompilatory- nie s one bowiem w stanie rozumie polece nieprecyzyjnie sformuowanych, zbu dowanych niezgodnie z wbudowanymi" w nie wymogami syntaktycznymi. Rysunek 1 - 1 obrazuje w sposb uproszczony etapy procesu programowania komputerw. Olbrzymia arwka symboli/uje etap. ktry jest od czasu do czasu pomijany przez programistw (dodajmy, e typowo z opakanymi skutkami...) REFLEKSJ.

Rys. 1 - /. Etapy konstrukcji programu.

Nastpnie jest tworzony tzw. tekst rdowy nowego programu, majcy posta pliku tekstowego, wprowadzanego do komputera przy pomocy zwykego edytora teksto wego. Wikszo istniejcych obecnie kompilatorw posiada taki edytor ju wbudowany, wic uytkownik w praktyce nie opuszcza tzw. rodowiska zintegro wanego. grupujcego programy niezbdne w procesie programowania. Ponadto niektre rodowiska zintegrowane zawieraj zaawansowane edytor)' graficzne umoliwiajce przygotowanie zewntrznego interfejsu uytkownika praktycznie bez

1.3. Proces koncepcji programw

23

pisania jednej linii kodu. Pomijajc ju jednak tego typu szczegy, generalnie efektem pracy programisty jest plik lub zespl plikw opisujcych w formie symbolicznej sposb zachowania si programu wynikowego. Opis ten jest kodowany w tzw. jzyku programowania, ktry stanowi na og podzbir jzyka1. Kompilator dokonuje mniej lub bardziej zaawansowanej analizy poprawnoci i, jeli wszystko jest w porzdku, produkuje tzw. kod wykonywalny, zapisany w postaci zrozumiaej przez komputer. Plik zawierajcy kod wykonywalny moe by nastpnie wykonywany pod kontrol systemu operacyjnym komputera (ktry notabene take jest zbiorem programw). Gdzie w tym procesie umiejscowione jest to, co stanowi tematyk ksiki, ktr trzymasz. Czytelniku, w rku? Ot z caego skomplikowanego procesu tworzenia oprogramowania zajmiemy si tym, co do tej pory nic jest (jeszcze?) zauto matyzowane: koncepcj algorytmw, ich jakoci i technikami programowania aktualnie uywanymi w informatyce. Bdziemy anonsowa pewne problemy dajce si rozwizywa przy pomocy komputera, a nastpnie omwimy sobie, jak to zadanie wykona w sposb efektywny. Tworzenie zewntrznej otoczki programw, czyli tzw. interfejsu uytkownika jest w chwili obecnej procesem praktycznie do koca zauto matyzowanym, co wyklucza konieczno poruszania tego tematu w ksice.

1.4. Poziomy abstrakcji opisu i wybr jzykaJednym z delikatniejszych problemw zwizanych z opisem algorytmw jest spo sb ich prezentacji zewntrznej". Mona w tym celu przyj dwie skrajne pozycje: zbliy si do maszyny (jzyk asemblera: nieczytelny dla nieprzygoto wanego odbiorcy); zbliy si do czowieka (opis sowny: maksymalny poziom abstrakcji zakadajcy poziom inteligencji odbiorcy niemoliwy aktualnie do wbu dowania" w maszyn").

Wybr jzyka asemblera do prezentacji algorytmw wymagaby w zasadzie zwizania si z okrelonym typem maszyny, co zlikwidowaoby jakkolwiek oglno rozwaa i uczynioby opis trudnym do analizy. Z drugiej za strony opis sowny wprowadza ryzyko niejednoznacznoci, ktra moe by kosztowna: program, po przetumaczeniu go na posta zrozumia przez komputer, moe nie zadziaa!1 2

W praktyce jest to jzyk angielski. Niemowl radzi sobie bez problemu z problemami, nad ktrymi biedz si specjalici od tzw. sztucznej inteligencji usiujcy je rozwizywa przy pomocy komputerw! (Chodzi o efektywno uczenia si, rozpoznawanie form etc).

24

Rozdzia 1. Zanim wystartujemy A b y zaradzi zaanonsowanym wyej p r o b l e m o m , przyjo si z w y c z a j o w o prezentowanie algorytmw w dwojaki sposb: przy pomocy istniejcego jzyka programowania; uywajc pseudojzyka programowania (mieszanki jzyka naturalnego i form skadniowych pochodzcych z kilku reprezentatywnych jzykw programowania).

W niniejszym podrczniku mona napotka obie te formy i wybr ktrej z nich zostanie podyktowany kontekstem omawianych zagadnie. Przykadowo, jeli dany algorytm jest moliwy do czytelnej prezentacji przy pomocy jzyka progra mowania, w y b r bdzie oczywisty! Od czasu do czasu jednak napotkamy na sytuacje, w ktrych prezentacja kodu w penej postaci, gotowej do wprowadzenia do komputera, byaby zbdna (np. zbliony materia by j u przedstawiony wczeniej) lub nieczytelna (liczba linii kodu przekracza objto jednej strony). W kadym jednak przypadku ewentualne przejcie z jednej f o r m y w drug nie powinno stanowi dla Czytelnika wikszego problemu. Ju we wstpie zostao zdradzone, i jzykiem prezentacji programw bdzie C++. Pora zatem dokadniej wyjani powody, ktre obstaway za tym wyborem. C + + jest jzykiem programowania okrelanym jako strukturalny, co z zaoenia uatwia pisanie w nim w sposb czytelny i z r o z u m i a y . Z w i z e k tego j z y k a z klasycznym C u m o l i w i a oczywicie tworzenie absolutnie nieczytelnych listingw, bdziemy tego jednak starannie unika. W istocie, czstokro bd omijane pewne moliwe mechanizmy optymalizacyjne, aby nie zatraci prostoty zapisu. Najwaniejszym jednak powodem uycia C + + jest fakt, i uatwia on programowanie na wielu poziomach abstrakcji. Istnienie klas i wszelkie obiektowe cechy lego jzyka powoduj, i bardzo atwe jest ukrywanie szczegw imple mentacyjnych, rozszerzanie j u zdefiniowanych moduw (bez ich kosztownego przepisywania"), a s to waciwoci, ktrymi nie mona pogardzi. By moe cenne bdzie podkrelenie u s u g o w e j " r o l i , jak w procesie progra mowania peni jzyk do tego celu wybrany. Wiele osb pasjonuje si wykazy waniem wyszoci jednego jzyka nad drugim, co jest sporem tak samo j a o w y m , jak wykazywanie wyszoci wit Wielkiej Nocy nad witami Boego Naro dzenia" (cho zapewne mniej miesznym...). Jzyk programowania jest w ko cu t y l k o narzdziem, ulegajcym zreszt znacznej (r)e wol uc ji na przestrzeni ostatnich lat Pracujc nad pewnymi partiami tej ksiki musiaem zwalcza od czasu do czasu siln pokus prezentowania niektrych a l g o r y t m w w takich jzykach j a k LISP czy PROLOG. Uprocioby to znacznie wszelkie rozwaania o listach i rekurencji - niestety ograniczyoby rwnie potencjalny krg odbiorcw ksiki do ludzi profesjonalnie zwizanych wycznie z informatyk.

1.4. Poziomy abstrakcji opisu i wybr jzyka

_____25

Zdajc sobie spraw, e C++ moe by pewnej grupie Czytelnikw nieznany, zosta w dodatku A przygotowany mini kurs tego jzyka. Polega on na rwnolegej prezentacji struktur skadniowych w C++ i Pascalu, tak aby poprzez porwnywanie fragmentw kodu nauczy si czytania listingw prezentowanych w tej ksice. Kilkustronicowy dodatek nie zastpi oczywicie podrcznika powiconego tylko i wycznie C++, umoliwi jednak lektur ksiki osobom pragncym z niej skorzysta bez koniecznoci poznawania nowego jzyka.

1.5. Poprawno algorytmwWpisanie programu do komputera, skompilowanie go i uruchomienie jeszcze nie gwarantuj, e kiedy nie nastpi jego zaamanie" (cokolwiek by to miao znaczy w praktyce). O ile jednak w przypadku niewinnych" domowych aplikacji nie ma to specjalnego znaczenia (w tym sensie, e tylko my ucierpimy.,.), to w momencie zamierzonej komercjalizacji programu sprawa znacznie si komplikuje. W gr zaczyna wchodzi nie tylko kompromitacja programisty, ale i jego odpowiedzialno za ewentualne szkody poniesione przez uytkownikw programw. Od bdw w swoich produktach nie ustrzegaj si nawet wielkie koncerny pro gramistyczne - w miesic po kampanii reklamowej produktu A' pojawiaj si po cichu darmowe" (dla legalnych uytkownikw) uaktualnione wersje, ktre nie maj wczeniej niezauwaonych bdw... Mamy tu do czynienia z popiechem majcym na celu wyprzedzenie konkurencji, co usprawiedliwia wypuszczanie przez dyrekcje firm niedopracowanych produktw - ze szkod dla uytkownikw. ktrzy nie maj adnych moliwoci obrony przed tego typu praktykami. 7 drugiej jednak strony uniknicie bdw w programach wcale nie jest problemem banalnym i stanowi temat powanych bada naukowych ! Zajmijmy si jednak czym bliszym rzeczywistoci typowego programisty: pisze on program i chce uzyska odpowied na pytanie: Czy bdzie on dziaa po prawnie w kadej sytuacji, dla kadej moliwej konfiguracji danych wejcio wych?". Odpowied jest tym trudniejsza, im bardziej skomplikowane s pro cedury, ktre zamierzamy bada. Nawet w przypadku pozornie krtkich w za pisie programw ilo sytuacji, ktre mog zaistnie w praktyce wyklucza rczne przetestowanie programu. Pozostaje wic stosowanie dowodw natury matema tycznej, zazwyczaj do skomplikowanych... Jedn z moliwych cieek, ktrymi mona doj do stwierdzenia formalnej poprawnoci algorytmu, jest stosowanie

Formalne badanie poprawnoci systemw algorytmicznych jest moliwe przy uyciu specjalnych jzykw stworzonych do tego celu.

26_

Rozdzia 1. Zanim wystartujemy

1,5

metody niezmiennikw (zwanej niekiedy metod Floyda). Majc dany algorytm, moemy atwo wyrni w nim pewne kluczowe punkty, w ktrych dziej si in teresujce dla danego algorytmu rzeczy. Ich znalezienie nie jest zazwyczaj trudne: wane s momenty inicjalizacji zmiennych, ktrymi bdzie operowa procedura, testy zakoczenia algorytmu, ptla gwna"... W kadym z tych punktw moli we jest okrelenie pewnych zawsze prawdziwych warunkw - tzw. niezmien nikw. Mona sobie zatem wyobrazi, e dowd formalnej poprawnoci algoryt mu moe by uproszczony do stwierdzenia zachowania prawdziwoci niezmien nikw dla dowolnych danych wejciowych. Dwa typowe sposoby stosowane w praktyce to: sprawdzanie stanu punktw kontrolnych przy pomocy debuggera (odczytujemy wartoci pewnych w a n y c h " zmiennych i sprawdzamy, czy zachowuj si p o p r a w n i e " dla pewnych reprezentacyjnych" da nych wejciowych"). formalne udowodnienie (np. przez indukcj matematyczn) zachowania niezmiennikw dla dowolnych danych wejciowych.

Zasadnicza wad powyszych zabiegw jest to, e s one nuce i potrafi atwo zabi ca przyjemno zwizan z efektywnym rozwizywaniem problemw przy pomocy komputera. Tym niemniej Czytelnik powinien by wiadom istnienia rwnie i tej strony programowania. Jedn z prostszych (i bardzo kompletnych) ksiek, ktr mona poleci Czytelnikowi zainteresowanemu formaln teori programowania, metodami generowania algorytmw i sprawdzania ich wasno ci, jest [Gri84] - entuzjastyczny wstp do niej napisa sam Dijkstra , co jest chyba najlepsz rekomendacj dla tego typu pracy. Inny tytu o podobnym charakterze, [Kal90], mona poleci mionikom formalnych dowodw i mylenia matematycznego. M e t o d y matematycznego dowodzenia poprawnoci a l g o r y t m w s prezentowane w tych ksikach w pewnym sensie niejawnie; zasadniczym celem jest dostarczenie narzdzi, ktre u m o l i w i quasi-automatyczne generowanie algorytmw. Kady program wyprodukowany" przy pomocy tych metod jest automatycznie poprawny - pod warunkiem, e nie zosta p o drodze" popeniony jaki bd. W y generowanie" algorytmu jest moliwa dopiero po j e g o poprawnym zapisaniu wg schematu;

Stwierdzenia: wane zmienne", poprawne" zachowanie programu, reprezenta tywne" dane wejciowe etc. nale do gatunku bardzo nieprecyzyjnych i s cile zwizane z konkretnym programem, ktrego analiz si zajmujemy.3

Jeli ju jestemy przy nim, to warto poleci przynajmniej pobien lektur [DF89], kt ra stanowi do dobry wstp do metodologii programowania.

1.5. Poprawno algorytmw {warunki wstpne 4 } poszukiwany-program {warunki kocowe}

27

Moliwe jest przy pewnej dozie dowiadczenia wyprodukowanie cigu instruk cji, ktre powoduj przejcie z warunkw wstpnych" do warunkw koco wych" - wwczas formalny dowd poprawnoci algorytmu jest zbdny. Mona te podej do problemu z innej strony; majc dany zespl warunkw wstp nych i pewien program: czy jego wykonanie zapewnia ustawienie" poda nych warunkw kocowych? Czytelnik moe nieco si obruszy na oglnikowo powyszego wywodu, ale jest ona wymuszona przez rozmiar" lematu, ktry wymaga w zasadzie osobnej ksiki! Pozostaje zatem tylko ponowi zaproszenie do lektury niektrych zacy towanych wyej pozycji bibliograficznych - niestety w momencie pisania tej ksiki niedostpnych w polskich wersjach jzykowych.

4

Wartoci zmiennych, pewne warunki logiczne je wice etc.

Rozdzia 2

RekurencjaTematem niniejszego rozdziau jest jeden z najwaniejszych mechanizmw uywanych w informatyce - rekurencja. zwana rwnie rekursj1. Mimo i uycie rekurencji nie jest obowizkowe", jej zalety s oczywiste dla kadego, kto cho raz sprbowa tego stylu programowania. Wbrew pozorom nie jest to wcale mechanizm prosty i wiele jego aspektw wymaga dogbnej analizy. Niniejszy rozdzia ma kluczowe znaczenie dla pozostaej czci ksiki - o ile j e j lektura moe by do swobodna i nieograniczona naturaln kolejnoci rozdziaw, o tyle bez dobrego zrozumienia samej istoty rekurencji nie bdzie moliwe swobodne czytanie" wielu zaprezentowanych dalej algorytmw i metod programowania.

2.1. Definicja rekurencjiPojcie rekurencji poznamy na przykadzie. Wyobramy sobie mae dziecko w wieku lat - przykadowo - piciu. Dostaje ono od rodzicw zadanie zebrania do pudeka wszystkich drewnianych klockw, ktre nierozmylnie" zostay rozsypane na pododze. Klocki s bardzo prymitywne, s to zwyczajne drewniane szecianiki, ktre doskonale nadaj si do budowania nieskomplikowanych budowli. Polecenie jest bardzo proste: Zbierz to wszystko razem i poukadaj tak jak byo w pudeku". Problem wyraony w ten sposb jest dla dziecka1

Subtelna rnica midzy tymi pojciami w zasadzie ju si zatracia w literaturze, dlatego te nie bdziemy si niepotrzebnie rozdrabnia w szczegy terminologiczne. Programy zapisane w formie rekurencyjnej mog by przeksztacone - z mniejszym lub wikszym wysikiem - na posta klasyczn, zwan dalej iteracyjn (patrz rozdzia 6).

30

Rozdzia 2. Rekurencja potwornie skomplikowany: klockw jest caa masa i niespecjalnie wiadomo jak si do tego caociowo zabra. Mimo ograniczonych umiejtnoci na pewno niw przerasta go nastpujca czynno: wzi jeden klocek z podogi i woy do pudeka. Mae dziecko zamiast przejmowa si zoonoci problemu, ktrej by moe sobie nawet nie uwiadamia, bierze si do pracy i rodzice z przyjem noci obserwuj jak strefa porzdku na pododze powiksza si z minuty na minut. Zastanwmy si chwilk nad metod przyjta przez dziecko: ono wic, e pro-. blem postawiony przez rodzicw to wcale nie jest zebra wszystkie klocki"! (bo to de Facto jest niewykonalne za jednym zamachem), ale: wzi jeden klocek przeoy go do pudelka, a nastpnie zebra do pudelka pozostae". W jaki sposb mona zrealizowa to drugie? Proste, zupenie tak jak poprzednio: bierzemy jeden klocek..." itd. - postpujc tak do momentu wyczerpania si klockw. Spjrzmy na rysunek 2 - 1 , ktry przedstawia w sposb symboliczny tok rozumowania przyjty przy rozwizywaniu problemu sprztania rozsypanych klockw".

Rys. 2 - I.Sprztanie rencja kloc-

kw", czyli rekuw praktyce.

Jest mao prawdopodobne, aby dziecko uwiadamiao sobie, e postpuje w sposb rekurencyjny, cho tak jest w istocie! Jeli uwaniej przyjrzymy si opisanemu powyej problemowi, to zauwaymy, e jego rozwizanie charakteryzuje si nastpujcymi cechami, typowymi dla algorytmw rekurencyjnych: zakoczenie algorytmu jest jasno okrelone ( w momencie gdy na pododze nie bdzie wicej klockw, moesz uzna, e zadanie zostao wykonane"). duy" problem zosta rozoony na problem elementarny (ktry umiemy rozwiza) i na problem o mniejszym stopniu skomplikowania ni ten. z ktrym mielimy do czynienia na pocztku.

Zauwamy, e w sposb do miay uyte zostao okrelenie algorytm". Czy jest sens mwi o opisanym powyej problemie w kategorii algorytmu? Czy w ogle moemy przypisywa picioletniemu dziecku wiedz, z ktrej ono nic zdaje sobie sprawy? Przykad, na podstawie ktrego zostao wyjanione pojcie algorytmu rekurencyjnego, jest niewtpliwie kontrowersyjny. Prawdopodobnie dowolny specjalista

2.2. Ilustracja pojcia rekurencji

31

od psychologii zachowa dziecka chwyciby si za gow z rozpaczy czytajc powyszy wywd... Dlaczego jednak zdecydowaem si na uycie takiego wanie a nie innego - moe bardziej informatycznego - przykadu? Ot zasadniczym celem bya ch udowodnienia, i mylenie w sposb rekurencyjny jest jak naj bardziej zgodne z natur czowieka i dua klasa problemw rozwizywanych przez umys ludzki jest traktowana podwiadomie w sposb rekurencyjny. Pjdmy dalej za tym miaym stwierdzeniem; jeli tylko zdecydujemy si na intuicyjne podejcie do algorytmw rekurencyjnych, to nie bd one stanowiy dla nas tajemnic, cho by moe na pocztku nie w peni uwiadomimy sobie mechanizmy w nich wykorzystywane. Powysze wyjanienie pojcia rekurencji powinno by znacznie czytelniejsze ni typowe podejcie zatrzymujce si na niewiele mwicym stwierdzeniu, e program rekurencyjny jest to program, ktry wywouje sam siebie"...

2.2. Ilustracja pojcia rekurencjiProgram, ktrego analiz bdziemy si zajmowali w tym podrozdziale, jest bardzo zbliony do problemu klockw, z ktrym spotkalimy si przed chwil. Schemat rekurencyjny zastosowany w nim jest identyczny, jedynie za gadnienie jest nieco blisze rzeczywistoci informatycznej. Mamy do rozwizania nastpujcy problem: dysponujemy tablic n liczb cakowitych -1]; tab[n-I]; lab[n]=tab[0], tab[1]...

t czy w tablicy rab wystpuje liczba x (podana jako parametr)?

Jak postpioby dziecko z przykadu, ktry posuy nam za definicj pojcia rekurencji, zakadajc oczywicie, e dysponuje ju ono pewn elementarn wiedz informatyczn? Jest wysoce prawdopodobne, e rozumowaoby ono w sposb nastpujcy: Wzi pierwszy niezbadany element tablicy n-elementowej; jeli aktualnie analizowany element tablicy jest rwny A, to: wypisz Sukces " i zakocz; w przeciwnym wypadku Zbadaj pozosta cz tablicy n-1-elementowej.

32

Rozdzia 2. Rekurencja Wyej podalimy warunki pozytywnego zakoczenie programu. W przypadku, gdy przebadalimy ca tablic i element x nie zosta znaleziony, naley oczywicie zakoczy program w jaki umwiony s p o s b - np. komunikatem o niepo wodzeniu. Prosz spojrze na przykadow realizacj, jedn z kilku moliwych; const n=10; int tab[n] = { 1 , 2 , 3 , 2 , - 7 , 4 4 , 5 , l , 0 , - 3 } ; void szukaj( i n t t a b [ n ] , i n t l e f t , i n t r i g h t , i n t x ) //Left, r i g h t = lewa i prawa g r a n i c a obszaru poszukiwa // tab = tablica // x = warto do odnalezienia { if (left>right) cout 100)

2 Ctrl-ALT-Del w systemie DOS, instrukcja kill w systemie Unix...1

W szczegy wnika nie bdziemy, gdy tematyka ta nie ma dla nas wikszego zna czenia w tym miejscu.

2.4. Niebezpieczestwa rekurencjireturn else return MacCarthy(MacCarthy(x+ 1 1 ) ) ; (x-10);

37

Ju na pierwszy nawet rzut oka wida, e funkcja jest jaka dziwna". Kto potrafi powiedzie w przyblieniu, jak si przedstawia jej ilo wywoa w zalenoci od parametru x podanego w wywoaniu? Chyba niewielu byoby w stanie od razu po wiedzie, e zaleno ta ma posta przedstawion na wykresie z rysunku 2-4... Nie byo to wcale takie oczywiste, prawda?

wicz. 2-1Prosz dokadnie zbada funkcj MacCarthy'ego w wikszym przedziale liczbowym, ni ten na rysunku. Jakich niebezpieczestw mona si doszuka? Rys. 2 - 4. Ilo wywoa funkcji Mac Carthy 'ego w zalenoci od parametru wywoania,

200

150

100

50

0

20

40

60

80

100

2.5. Puapek cig dalszyJakby nie do byo negatywnych stron programw rekurencyjnych, naley jeszcze dorzuci te, ktre nie Wynikaj z samej natury rekurencji, lecz raczej z bdw programisty. By moe warto w tym miejscu podkreli, i omawianie ciemnych stron" rekurencji nie ma na celu zniechcenia Czytelnika do jej sto sowania! Chodzi raczej o wskazanie typowych puapek i sposobw ich omija nia - a te ostatecznie istniej zawsze (pod warunkiem, e wiemy CO omija). Zapraszam zatem do lektury nastpnych paragrafw...

38

Rozdzia 2. Rekurencja

2.5.1.Std do wiecznociW wielu funkcjach rekurencyjnych, pozornie dobrze skonstruowanych, moe z atwoci ukry si bd polegajcy na sprowokowaniu nieskoczonej iloci wywoa rekurencyjnych. Taki wanie zwodniczy przykad jest przedstawiony poniej: std.cppint StadDoWiecznosci(int n){

if (n == 1) return 1; else if ((n % 2) == 0) // czy n jest parzyste? return StadDoWiecznosci(n-2)*n;

else

return StadDoWiecznosci(n-1)*n; }

Gdzie jest umiejscowiony problem? Patrzc na ten program trudno dopatrzy si szczeglnych niebezpieczestw. W istocie, definicja rekurencyjna wydaje si poprawna: mamy przypadek elementarny koczcy acuch wywoa, problem o rozmiarze n jest upraszczany do problemu o rozmiarze n-1 lub n-2. Puapka tkwi wanie w tej naiwnej wierze, e proces upraszczania doprowadzi do przypadku elementarnego (czyli do n=l)! Po dokadniejszej analizie mona wszake zauway, e dla n>2 wszystkie wywoania rekurencyjne kocz si parzyst wartoci n. Implikuje to, i w kocu dojdziemy do przypadku n=2, ktry zostanie zredukowany do n=0. ktry zostanie zredukowany do w=-2, ktry... Mona tak kontynuowa w nieskoczono, nigdzie po drodze" nie ma adnego przypadku elementarnego! Wniosek nasuwa si sam: naley zwraca baczn uwag na to, czy dla wartoci parametrw wejciowych nalecych do dziedziny wartoci, ktre mog by uyte, rekurencja si kiedy koczy.

2.5.2.Definicja poprawna, ale...Rozpatrywany poprzednio przykad suy do zilustrowania problemw zwizanych ze zbienoci procesu rekurencyjnego. Wydaje si, e dysponujc poprawn de liniej rekurencyjna, dostarczon przez matematyka, moemy ju by spokojni o to, e analogiczny program rekurencyjny take bdzie poprawny (tzn. nie zaptli si, bdzie dostarcza oczekiwane wyniki etc.). Niestety jest to wiara do naiwna i niczym nie uzasadniona. Matematyk bowiem jest w stanie zrobi wszystko zwizane ze swoj" dziedzin: okreli dziedziny wartoci funkcji, udowodni, e ona si zakoczy, wreszcie poda zoono obliczeniow -jednej jednak rzeczy

2.5. Puapek cig dalszy

39

nie bdzie mg sprawdzi: jak rzeczywisty kompilator wykona t funkcj! Mimo, e wikszo kompilatorw dziaa podobnie, to zdarzaj si pomidzy nimi drobne rnice, ktre powoduj, e identyczne programy bd dawa rne wyniki. Nasz kolejny przykad bdzie dotyczy wanie takiego przypadku. Prosz spojrze na nastpujc funkcj: i n t N(int n , i n t p) { if (n==0) r e t u r n 1; else r e t u r n N(n-1,N(n-p,p)) }} Mona przeprowadzi dowd matematyczny 1, e powysza definicja jest poprawna w tym sensie, i dla dowolnych wartoci n>0 i p>0 jej wynik jest okrelony i wynosi l. Dowd ten opiera si na zaoeniu, e warto argumentu wywoania funkcji jest obliczana tylko wtedy, gdy jest naprawd niezbdna (co wydaje si do logiczne). Jak si to za ma do typowego kompilatora C++? Ot regu w jego przypadku jest to, i wszystkie parametry funkcji rckurencyjnej s ewaluowane jako pierwsze, a nastpnie dokonywane jest wywoanie samej funkcji. (Taki sposb pracy jest zwany wywoaniem przez warto. Problem moe zaistnie wwczas, gdy w wywoaniu funkcji sprbujemy umieci j sam; zobaczmy j a k to si odbdzie w przypadku naszej funkcji, np. dla N(1,0) (patrz rysunek 2-5).Rys. 2-5. Nieskoczony cig wywoa rekurencyjnych.

Patrz [Kro89].

40

Rozdzia

2.

Rekurencja

Zaptlenie jest spowodowane prb obliczenia parametru p, tymczasem to drugie wywoanie jest w ogle niepotrzebne do zakoczenia funkcji! Istnieje w niej bowiem warunek obejmujcy przypadek elementarny: jeli n=0, to zwr 1. Niestety, kompilator o tym nie wie i usiuje obliczy ten drugi parametr, powo dujc zapetlenie programu... Przykad omwiony w niniejszym paragrafie naley traktowa jako swoista ciekawostk, niemniej warto go zapamita ze wzgldw czysto edukacyjnych.

2.6.Typy programw rekurencyjnychNa podstawie lektury poprzednich paragrafw Czytelnik mgby wycign kilka oglnych wnioskw na temat programw uywajcych technik rekurencyjnych: typowo zachanne w dysponowaniu pamici komputera, niekiedy zawieszaj" system operacyjny... Na szczcie jest to bdne wraenie! Programy rekurencyjne maj jedn olbrzymi zalet: s atwe do zrozumienia i zazwyczaj zajmuj mao miejsca jeli rozpatrujemy liczb linii kodu uytego na ich realizacj. Z tym ostatnim jest cile zwizana atwo odnajdywania ewentualnych bdw. Wrmy jednak do tematu. Zauwaylimy wsplnie, e program rekurencyjny moe by pamiciochonny i wy konywa si do wolno. Pytanie brzmi: czy istniej jakie techniki programowania pozwalajce usun (lub co najmniej zredukowa) powysze wady z programu rekurencyjnego? Odpowied jest na szczcie pozytywna! Ot pewna klasa problemw natury "rekurencyjnej" da si zrealizowa na dwa sposoby, dajce dokadnie taki sam efekt kocowy, ale rnice si nieco realizacj praktyczn. Podzielmy metody rekurencyjne, tytuem uproszczenia, na dwa podstawowe typy: rekurencja naturalna"; rekurencja z parametrem dodatkowym"1. Typ pierwszy mielimy okazj zobaczy podczas analizy dotychczasowych przykadw, teraz zapoznamy si z drugim. Rozwamy raz jeszcze przykad funkcji obliczajcej silni. Do tej pory znalimy j w postaci: rekS.cppunsigned long int silnia1 (unsigned long int x)

{1

Pozostaniemy na moment przy tej nieprecyzyjnej nazwie; ten typ rekurencji powrci nam jeszcze w rozdziale 6 - w innym jednake kontekcie.

2.6. Typy programw rekurencyjnych

41

if else }

(x==0) return 1; return x * s i l n i a 1(x-1); ; lfx-l)

Nie jest to bynajmniej jedyna moliwa realizacja funkcji obliczajcej silni. Spjrzmy dla przykadu na nastpujc wersj:unsigned long int silnia2(unsigned long int x, unsigned long int tmp=l)

if (x==0) return tmp; else return silnia2(x-l,x*tmp);}

{

W pierwszym momencie dziaanie tej funkcji nie jest by moe oczywiste, ale wystarczy wzi kartk i owek, aby przekona si na kilku przykadach, e wykonuje ona swoje zadanie. Osobom nie znajcym dobrze C++ naley si niewtpliwie wyjanienie konstrukcji funkcji silnia!. Ot dowolna funkcja w C++ moe posiada parametry domylne. Dziki temu funkcja o nagwku:FunDom(int a,int k=l)

moe by wywoana na dwa sposoby: okrelajc warto drugiego parametru, np FumDom(12,5): w tym przypadku k przyjmuje warto 5; nie okrelajc wartoci drugiego parametru, np. FunDom(12)\ k przyj muje wtedy warto domyln rwn tej podanej w nagwku, czyli 1. Ta uyteczna cecha jzyka C++ wykorzystana zostaa w drugiej wersji funkcji do obliczania silni. Jednak jakie istotne wzgldy przemawiaj za uywaniem tej osobliwej z pozoru metody programowania? Argumentem nie jest tu wzrost czytelnoci programu, bowiem ju na pierwszy rzut oka silnia2 jest o wiele bardziej zagmatwana ni silnia I ! Istotna zaleta rekurencji z parametrem dodatkowym" jest ukryta w sposobie wykonywania programu. Wyobramy sobie, e program rekurencyjny bez parametru dodatkowego" wywoa sam siebie 10-krotnie, aby obliczy dany wynik. Oznacza to, e wynik czstkowy z dziesitego, najgbszego poziomu rekurencji bdzie musia by przekazany przez kolejne dziesi poziomw do gry, do swojego pierwszego egzemplarza. Jednoczenie z kadym zamroonym" poziomem, ktry czeka na nadejcie wyniku czstkowego, wie si pewna ilo pamici, ktra suy do odtworzenia

42

Rozdzia 2. Rekurencja 2_ m.in. wartoci zmiennych tego poziomu (tzw. kontekst). Co wicej, odtwarzanie kontekstu ju samo w sobie zajmuje cenny czas procesora, ktry mgby by wykorzystany np. na inne obliczenia... Czytelnik domyla si ju zapewne, e program rekurencyjny z parametrem dodat kowym" robi to wszystko nieco wydajniej. Poniewa parametr dodatkowy suy do przekazywania elementw wyniku kocowego, dysponujc nim nie ma po trzeby przekazywania wyniku oblicze do gry, pitro po pitrze". Po prostu w momencie, w ktrym program stwierdzi, e obliczenia zostay zakoczone, procedura wywoujca zostanie o tym poinformowana wprost z ostatniego ak tywnego poziomu rekurencji. Co za tym wszystkim idzie, nie ma absolutnie ad nej potrzeby zachowywania kontekstu poszczeglnych poziomw porednich, liczy si tylko ostatni aktywny poziom, ktry dostarczy wynik i basta!

2.7. Mylenie rekurencyjnePomimo oczywistych przykadw na to, e rekurencja jest dla czowieka czym jak najbardziej naturalnym, niektrzy maj pewne trudnoci z uywaniem jej podczas programowania. Nieumiejtno wyczucia" istoty tej techniki progra mowania moe wynika z braku dobrych i pogldowych przykadw na jej wykor/Ysianie. Idc za tym stwierdzeniem, postanowiem wybra kilka prostych programw rekurencyjnych. generujcych znane motywy graficzne - ich dobre zrozumienie bdzie wystarczajcym testem na oszacowanie swoich zdolnoci mylenia rekurencyjnego (ale nawet wwczas wykonanie zada zamieszczo nych pod koniec rozdziau bdzie jak najbardziej wskazane...).

2.7.1.SpiralaZastanwmy si. jak mona narysowa rekurencyjnie jednym pocigniciem" kreski rysunek 2 - 6 . Parametrami programu s: odstp pomidzy liniami rwnolegymi: alpha: dugo boku rysowanego w pierwszej kolejnoci: Ig. Algorytm iteracyjny byby rwnie nieskomplikowany (zwyk ptla), ale za my, e zapomnimy chwilowo o jego istnieniu i wykonamy to samo rekuren cyjnie. Istota rekurencji polega gwnie na znalezieniu waciwej dekompozycji problemu. Tutaj jest ona przedstawiona na rysunku i w zwizku z tym ewentu alne przetumaczenie jej na program w C++ powinno by znacznie uatwione.

2.7. Mylenie rekurencyjne

43

Rekurencyjno naszego zadania jest oczywista, bowiem program wyniko wy zajmuje si powtarzaniem gwnie tych samych czynnoci (rysuje linie poziome i pionowe, jednake o rnej dugoci). Naszym zadaniem bdzie odszukanie schematu rekurencyjnego i warunkw zakoczenia procesu wywoa rekurencyjnych.Rys. 2 - 6.Spirala narysowa na rekurencyjnie.

alpha

Jak rozwiza to zadanie? Wpierw przyblimy si nieco do rzeczywistoci ekranowej" i wybierzmy jako punkt startowy pewn par (x,y). Idea rozwizania polega na narysowaniu 4 odcinkw zewntrznych" spirali i dotarciu do punktu (x',y'). W tym nowym punkcie startowym moemy ju wywoa rekurencyjnie procedur rysowania, obarczon oczywicie pewnymi warunkami gwarantujcymi jej poprawne zakoczenie. Elementarny przypadek rozwizania prezentuje rysunek 2 - 7 .Rys. 2 - 7. Spirala narysowa na rekurencyjnie szkic rozwizania.

Jedna z kilku moliwych wersji programu, ktry realizuje to, co zostao wyej opisane, jest przedstawiona poniej. W celu uatwienia lektury programu zamieszczone zostay rwnie objanienia instrukcji graficznych. spirala.cpp const double alpha=10; void spirala(double Ig,double x,double y)

441 if

Rozdzia2. Rekurencja

{ lineto(x+lg,y); lineto(x+lg,y-lg); lineto(x+alpha,y + I g ) ;

(lg>0)

lineio(x+alpha,y+alpha); spirala(lg-2*alpha,x+alpha,y+alpha);}

void main// tu zainicjuj tryb graficzny moveto(90,50); spirala{getmaxx()/2,getx(),gety()); g e t c h ( ) ; // poczekaj na n a c i n i c i e klawisza / / T u zamknij t r y b g r a f i c z n y } Tabela 2 - L Objanienia instrukcji graficznych.

FUNKCJAlineto (x,y) moveto(x,y) gctmaxx()

ZASTOSOWANIEkreli odcinek prostej od pozycji biecej do punktu (x, y) przesuwa kursor graficzny do punktu (x, y) zwraca maksymaln wsprzdn poziom (zaley od rozdzielczoci trybu graficznego) zwraca maksymaln wsprzdn, pionow (j. w.) zwraca aktualn wsprzdn poziom zwraca aktualn wsprzdn pionow

Uwaga! Dziaa tylko na starych kompilatorach Borlanda tj. TurboC i Turbo C++ Jeli koniecznie chcecie tak pisa -->Google (WinBGI) ( "odkurzona" biblioteka graficzna BGI, skadnia taka sama) K.

getmaxy()getx() gety()

2.7.2.Kwadraty parzyste"Zadanie jest podobne do poprzedniego: jak jednym pocigniciem kreski naryso wa figur przedstawion na rysunku 2 - 8 ?

Rys. 2-8. Kwadraty "parzyste" (n=2)

2.7, Mylenie rekurencyjne

45

Przypadkiem elementarnym bdzie tutaj narysowanie jednej pary kwadratw (wewntrzny obrcony w stosunku do zewntrznego). To zadanie jest nawet prostsze ni poprzednie, sztuka polega jedynie na wyborze waciwego miejsca wywoania rekurencyjnego: kwadraty.cppv o i d kwadraty(t i n t n,double lg, double x, double y) { // n = parzysta i l o kwadratw // x,y = punkt s t a r t o w y i f (n>0)

I

{

lineto{x+lg,y) ; lineto(x-t-lg,y+lg); lincto(x,y+lg); lineto(x,y+lg/2) ; lineto(x+lg/2, y+lg) ; lineto ( x + l g f y + I g / 2 ) ; l i n e t o ( x + ]g/2,y) ; lineto(x+lg/4,y+lg/4) ; kwadraty(n-l,lg/2,x+lg/4,y-lg/4); lineto(x,y+lg/2) ; lineto(x,y); )}

})void main{) // inicjuj tryb graficzny moveto(90,50) ; kwadraty (5, gtmaxx() /2, getx(), g e t y ( )); ; getch(); // zamknij t r y b g r a f i c z n y

{

2.8. Uwagi praktyczne na temat technik rekurencyjnychSzczegowy wgld w techniki rekurencyjne uwiadomi nam, e nios one ze sob zarwno plusy, jak i minusy. Zasadnicz zalet jest czytelno i naturalno zapisu algorytmw w formie rekursywnej - szczeglnie gdy zarwno problem, jak i struktury danych z nim zwizane s wyraone w postaci rekurencyjnej. Procedury rekurencyjne s zazwyczaj klarowne i krtkie, dziki czemu do atwo jest wykry w nich ewentualne bdy. Du wad wielu algorytmw

46

__

Rozdzia 2 , Rekurencja

rekurencyjnych jest pamicioerno: wielokrotne wywoania rekurencyjne mog atwo zablokowa ca dostpn pami! Problemem jest tu jednak nie Takt zajtoci pamici, ale typowa niemono atwego jej oszacowania przez konkretny algorytm rekurencyjny. Mona do tego wykorzysta metody suce do analizy efektywnoci algorytmw (patrz rozdzia 3), jednake jest to do nuce obliczeniowo, a czasami nawet po prostu niemoliwe. W podrozdziale Typy programw rekurencyjnych poznalimy metod na ominicie kopotw z pamici poprzez stosowanie rekurencji z parametrem dodatkowym". Nie wszystkie jednak problemy dadz si rozwiza w ten sposb, ponadto programy uywajce tej metody trac odrobin na czytelnoci. No c, nic ma ry bez kolcw... Kiedy nie naley uywa rekurencji? Ostateczna decyzja naley zawsze do pro gramisty. tym niemniej istniej sytuacje, gdy w dylemat jest do atwy do rozstrzygnicia. Nie powinnimy uywa rozwiza rekurencyjnych, gdy: w miejsce algorytmu rekurencyjnego mona poda czytelny i/lub szybki program iteracyjny; algorytm rekurencyjny jest niestabilny (np. dla pewnych wartoci parametrw wejciowych moe si zaptli lub dawa dziwne" wyniki).

Ostatni uwag podaj ju raczej, by dopeni formalnoci. Ot w literaturze mona czasem napotka rozwaania na temat niekorzystnych cech tzw. nkurencji skronej: podprogram A wywouje podprogram B, ktry wywouje z kolei podprogram A. Nie podaem celowo przykadu takiego dziwolga", gdy nadmiar zych przykadw moe by szkodliwy. Praktyczny wniosek, ktry moemy wysnu analizujc "osobliwe" programy rekurencyjne. pene niepraw dopodobnych konstrukcji, jest jeden: U N I K A J M Y ICH, jeli tylko nie jestemy cakowicie pewni poprawnoci programu, a intuicja nam podpowiada, e w danej procedurze jest co nieobliczalnego. Korzystajc z katalogw algorytmw, formalizujc programowanie etc. mona bardzo atwo zapomnie, e wiele piknych i eleganckich metod powstao samo z siebie - jako przebysk geniuszu, intuicji, sztuki... A moe i my mogli bymy dooy nasze co nieco" do tej kolekcji? Proponuj oceni wasne siy poprzez rozwizywanie zada, ktre odpowiedz w sposb najbardziej obiektyw ny, czy rozumiemy rekurencj jako metod programowania.

2.9. Zadania

47

2.9.ZadaniaWybr reprezentatywnego dla rekurencji zestawu zada wcale nie by atwy dla autora tej ksiki - dziedzina ta jest bardzo rozlega i w zasadzie wszystko w niej jest w jaki sposb interesujce... Ostatecznie, co zwykem podkrela, zadecydoway wzgldy praktyczne i prostota.

Zad. 2-1Zamy, e chcemy odwrci w sposb rekurencyjny tablic liczb cakowi tych. Prosz zaproponowa algorytm z uyciem rekurencji naturalnej", ktry wykona to zadanie.

Zad. 2-2Powrmy do problemu poszukiwania pewnej zadanej liczby x w tablicy, tym razem jednak posortowanej od wartoci minimalnych do maksymalnych. Metoda poszukiwania, bardzo znana i efektywna, (tzw. przeszukiwanie binarne) polega na nastpujcej obserwacji: podzielmy tablic o rozmiarze n na poow: t[0], t[l]... t[n/2-l], t[n/2], t[n/2+1]... t[n-l]] jeli x=t[n/2J,to element x zosta znaleziony1;

jeli A 1 jest rwny, zgodnie z formu rekurencyjn, T(n)-tc+ T(n-1). Niestety, tego typu zapis jest nam do niczego nieprzydatny - trudno np. powie dzie od razu, ile czasu zajmie obliczenie silnia(100)... Wida ju, e do proble mu naley podej nieco inaczej. Zastanwmy si. jak z tego ukadu wyliczy T(n), tak aby otrzyma jak funkcj nierekurencyjn pokazujc, jak czas wy-

58

Rozdzia 3. Analiza sprawnoci algorytmwkonania programu zaley od danej wejciowej n? W tym celu sprbujmy rozpisa rwnania:

T(n) = tc + T(n-1), T(n-1) = tc + T(n-2), T(n-2) = tc + T(n-3), : : T(1) = tc + T(0), T(0) = tc.

Jeli dodamy je teraz stronami, to powinnimy otrzyma:

T(n) + T(n-1)+...+T(0) = (n+1)tc + T(n-1)+...+t(0).co powinno da, po zredukowaniu skadnikw identycznych po obu stronach rwnoci, nastpujc zaleno:

T(n) = (n+1)tcJest to funkcja, ktra w satysfakcjonujcej, nieskomplikowanej formie poka zuje, w jaki sposb rozmiar danej wejciowej wpywa na ilo instrukcji porw na wykonanych przez program - czyli dc facto na czas wykonania algorytmu. Znajc bowiem parametr tc i warto n moemy powiedzie dokadnie w cigu ilu sekund (minut, godzin, lat...) wykona si algorytm na okrelonym komputerze. Tego typu rezultat dokadnych oblicze zwyko si nazywa zoonoci praktyczn algorytmu. Funkcja ta jest zazwyczaj oznaczana tak jak wyej, przez T. W praktyce rzadko interesuje nas a tak dokadny wynik. Niewiele bowiem si zmieni, jeli zamiast T(n) = (n+1)tc.otrzymamy T(n)=(n+3)tc ! t L)o D czego zmierzam? Ot w dalszych rozwaaniach bdziemy gwnie szuka od powiedzi na pytanie: Jaki typ funkcji matematycznej, wystpujcej w zalenoci okrelajcej zoo no praktyczn programu, odgrywa w niej najwaniejsz rol, wpywajc najsilniej na czas wykonywania programu?

3.2. Przykad 1: Jeszcze raz funkcja silnia...

59

T poszukiwan funkcj bdziemy zwa zoonoci teoretyczn1 i z ni najczciej mona si spotka przy opisach katalogowych" okrelonych algorytmw. Funkcja ta jest najczciej oznaczana przez O. Zastanwmy si, w jaki sposb moemy j otrzyma. Istniej dwa klasyczne podejcia, prowadzce z reguy do tego samego rezultatu: albo bdziemy opiera si na pewnych twierdzeniach matematycznych i je apli kowa w okrelonych sytuacjach, albo te dojdziemy do prawidowego wyniku metod intuicyjn. Wydaje mi si, e to drugie podejcie jest zarwno szybkie, jak i znacznie przy stpniejsze, dlatego skoncentrujemy si najpierw na nim. Popatrzmy w tym celu na tablic 3 - 2 zawierajc kilka przykadw wyuskiwania" zoonoci teoretycznej z rwna okrelajcych zoono praktyczn. Wyniki zawarte w tej tabelce moemy wyjani w nastpujcy sposb: w rw naniu pierwszym pozwolimy sobie pomin sta l i wynik nie ulegnie zna czcej zmianie. W rwnaniu drugim o wiele waniejsza jest funkcja kwadratowa ni liniowa zaleno od w; podobnie jest w rwnaniu trzecim, w ktrym dominuje n funkcja 2".Tabela 3 - 2. Zoono teoretyczna algorytmw - przykady,

T(n)3n+l n2-n+l 2n+n2+4

O0(n)

0(n2) 0(2 n )

Pojcie funkcji O jest jednak kluczowe, zatem dla ciekawskich warto przytoczy formaln definicj matematyczn. W tym celu przypomnijmy nastpujce oznaczenia znane z podrcznikw analizy matematycznej: N, R i s zbiorami liczb odpowiednio naturalnych i rzeczywistych (wraz z zerem); Plus (+) przy nazwie zbioru oznacza wykluczenie z niego zera (np. N + jest zbiorem liczb naturalnych dodatnich); R+ bdziemy oznacza zbir liczb rzeczywistych dodatnich lub zero; 1

Znak graficzny Znak graficzny

oznacza przyporzdkowanie; naley czyta jako: dla kadego;

Lub klas algorytmu - okrelenie zreszt znacznie czciej uywane.

Znak graficzny

naley czyta jako: istnieje;

Mae litery pisane kursyw na og oznaczaj nazwy funkcji (np. g); Dwukropek zapisany po pewnym symbolu .S naley odczytywa: S,taki, e... .

Bazujc na powyszych oznaczeniach, klas O dowolnej funkcji moemy zdefiniowa jako:

Jak wynika z powyszej definicji, klasa O (wedle definicji jest to zbir funkcji) ma charakter wielkoci asymptotycznej, pozwalajcej wyrazi w postaci aryt metycznej wielkoci z gry nie znane w postaci analitycznej. Samo istnienie tej notacji pozwala na znaczne uproszczenie wielu docieka matematycznych, w ktrych dokadna znajomo rozwaanych wielkoci nie jest konieczna. Dysponujc tak formaln definicj mona atwo udowodni pewne oczywiste"

wyniki, np.: T(n) = 5n3+3n2+2wwczas

O(n3)

dowody wielu podobnych zada.

(dobieramy dowiadczalnie c=1 i n0=0 W sposb zbliony mona przeprowadzi

Funkcja O jest wielkoci, ktrej mona uywa w rwnaniach matematycznych. Oto kilka wasnoci, ktre mog posuy do znacznego uproszczenia wyrae je zawierajcych:

c *O(f(n)) = 0(f(n)) + 0(f(n)) = 0(0(f(n))) = 0(f(n))0(g(n)) 0(f(n)g(n)) = =

0(f(n)) 0(f(n)) 0(f(n)) 0(f(n)g(n)) f(n)0((n))

Do ciekawszych naley pierwsza z powyszych wasnoci, ktra niweluje" wpyw wszelkich wspczynnikw o wartociach staych. Przypomnijmy elementarny wzr podajcy zaleno pomidzy logarytmami o rnych podstawach:logb h log

x-

ln

x

ln b

3.2. Przykad 1: Jeszcze raz funkcja silnia...

61

W obliczeniach wykonywanych przez programistw zdecydowanie krluje podstawa 2, bowiem jest wygodnie zakada, e np. rozmiar tablicy jest wielo krotnoci liczby 2 etc. Nastpnie na podstawie takich zaoe czstokro wyliczana jest zoono praktyczna i z niej dedukowana jego klasa, czyli funkcja O. Kto o bardzo rady kalnym podejciu do wszelkich sztucznych" zaoe, majcych uatwi wyliczenie pewnych normalnie skomplikowanych zagadnie, mgby zakwestionowa przyjmowanie podstawy 2 za punkt odniesienia, zapytujc si przykadowo "a dlaczego nie 2.5 lub 3"? Pozornie takie postawienie sprawy wydaje si suszne, ale na szczcie tylko pozornie! Na podstawie bowiem zacytowanego wyej wzoru moemy z atwoci zauway, e logarytmy o odmiennych podstawach rni si pomidzy sob tylko pewnym wspczynnikiem staym, ktry zostanie pochonity" przez O na podstawie wasnoci

cO(f(n)) = 0(f(n))Z tego wanie wzgldu w literaturze mwi si, e algorytm

"A

O(log2N)".

Popatrzmy jeszcze na inny aspekt stosowania O-notacji. Zamy, e pewien algorytm A zosta wykonany w dwch wersjach W1 i W2. charakteryzujcych si zoonoci praktyczn odpowiednio 100 log2N i 10N. Na podstawie uprzednio poznanych wasnoci moemy szybko okreli, e W1 O(logN), W2

czyli Wl jest lepszy od W2. Niestety, kto szczeglnie zoliwy mgbysi uprze, e jednak algorytm W2 jest lepszy, bowiem dla np. N=2 mamy 100log22>10*2... Wobec takiego stwierdzenia nie naley wpada w panik, tylko wzi do rki odpowiednio due N, dla ktrego algorytm W1 okae si jednak lepszy od W2\ Nie naley bowiem zapomina, e O-notacja ma charakter ! asymptotyczny i jest prawdziwa dla ,.odpowiednio duych wartoci N".

3.3. Przykad 2: Zerowanie fragmentu tablicyRozwiemy teraz nastpujcy problem: jak wyzerowa fragment tablicy (tzn. macierzy) poniej przektnej (wraz z ni)? Ide przedstawia rysunek 3 - 1 .

62 Rys. 3 - 1. Zerowanie tablicy.

Rozdzia 3. Analiza sprawnoci algorytmw

11 1 1 1 1

11 1 1 1 1

11 1 1 1 1

11 1 1 1 1

11 1 1 1 1

11 1 1 1 1

0 0 0 0

1

1 1 0 0 0 0

1 1 1 0 0 0

1 1 1 1 0 0

1 1 1 1 10

0 0

0 0 0

00

Funkcja wykonujca to zadanie jest bardzo prosta:int tab[n] [r]; void zerowanie() { int i, j; i=O; while (iglowa==NUT,T.)

return(res); // lista pusta! else { LPTR *przed,*pos; przed=NULL; pos=int->glowa; enum (SZUKAJ,ZAKOCZ) stan=SZUKAJ; while ((stan==SZUKAJ) && (pos!=NULL)) if (decyzja(pos->adres,q)) stan=ZAKONCZ; // znalelimy miejsce w Ktrym element else // istnieje (albo ma by wstawiony) // przemieszczamy sie w poszukiwaniach przed=pos; pos=pos->nastepny;

5.1. Listy jednokierunkowe

115

res->glowa=przed; res->oqon=pos;return (res);} }

wskanik inf do struktury informacyjnej listy wskanikw; adres pocztku znajduje si w polu gowa, a adres koca w polu ogon', wskanik do pewnego fizycznie istniejcego rekordu danych. Jest to albo nowy rekord, ktry chcemy doczy do listy, albo po prostu pewien szablon poszukiwa; wskanik decyzja do funkcji porwnawczej, ktra zostanie woona do instrukcji if w ptli while.

Przykadowo, jeli chcemy odszuka i usun pierwszy rekord, ktry w polu nazwi sko zawiera Kowalski", to naley stworzy tymczasowy rekord, ktry bdzie mia odpowiednie pole wypenione tym nazwiskiem (pozostae nie bd miay wpywu na poszukiwanie):ELEMENT *f=new ELEMENT;

s t r c p y ( f - > nazwisko,"Kowalski"); Podobna uwaga naley si pozostaym kryteriom poszukiwa - wg zarobkw, imie nia, etc. Jeli poszukiwanie zakoczy si sukcesem, to w polu ogon zostanie zwr cony adres fizycznie istniejcego rekordu, ktry odpowiada wzorcowi naszych po szukiwa. W przypadku gdyby element taki nie istnia, powinny zosta zwrco ne wartoci N U L L . Znajomo wskanikw przed i po umoliwi nam zwolnie nie komrek pamici zajmowanych dotychczas przez rekord danych, jak rw nie odpowiednie zmodyfikowanie caej listy, tak aby wszystko byo na swoim miejscu. Innym przykadem zastosowa funkcji niech bdzie doczanie nowego elementu do listy. Trzeba wwczas stworzy nowy rekord, prawidowo wypeni jego pola i doczy na koniec listy danych. Nastpnie naley adres tego elementu wstawi do list wskanikw posortowanych wg zarobkw, nazwisk, czy te do wolnych innych kryteriw. W kadej z tych list miejsce wstawienia bdzie inne, czyli za kadym razem rne mog by wartoci wskanikw przed i po, ktre zwrci funkcja odszukaj__wsk. Zastosowanie funkcji odszukaj_wsk jest, jak wida, bardzo wszechstronne. Ta ka elastyczno moliwa bya do osignicia tylko i wycznie poprzez uycie wskanikw do funkcji - we waciwym miejscu i o waciwej porze... Oto gar" funkcji decyzyjnych, ktre mog zosta uyte jako parametr:

116

Rozdzia 5. Struktury danych

int alfabetycznie( ELEMENT* q1, ELEMENT* q2 ) { //Czy rekordy q1 i q2 s uporzdkowane alfabetycznie return (strcmp(q1->nazwisko, q2->nazwisko) >= 0); } int wg_zarobkow( ELEMENT* q1, ELEMENT* q2 ) { //Czy rekordy q1 i q2 s uporzdkowane wg zarobkw? return (q1->zarobek >= q2->zarobek); } int equal( ELEMENT* q1, ELEMENT* q2 ) { //Czy rekordy q1 i q2 maj identyczne nazwiska? return (strcmp( q1->nazwisko, q2->nazwisko ) == 0); } int equal2( ELEMENT* q1, ELEMENT* q2 ) { //Czy rekordy q1 i q2 maj identyczne zarobki? return ( q1->zarobek == q2->zarobek ); } Dwie pierwsze funkcje z powyszej listy su do porzdkowania listy, pozo stae uatwiaj proces wyszukiwania elementw. Oczywicie, w rzeczywistej aplikacji bazy danych o pracownikach analogiczne funkcje byyby nieco bardziej skomplikowane - wszystko zaley od tego, jakie kryteria poszukiwa nia/porzdkowania zamierzamy zaprogramowa oraz jak skomplikowane struktury danych wchodz w gr.

Po tak rozbudowanych wyjanieniach dziaanie funkcji odszukaj_wsk nie po winno stanowi ju dla nikogo tajemnicy. Na stronie 97 mielimy okazj zapozna si z funkcf pusta informujc, czy li sta danych co zawiera. Nic nie stoi na przeszkodzie, aby do kompletu dooy jej kolejn wersj, badajc w analogiczny sposb list wskanikw: i n l i n e i n t pusta (LPTR_INFO *inf) { r e t u r n (inf->glowa==NULL) ;}

Poniewa uylimy dwukrotnie tej samej nazwy funkcji, nastpio w tym mo mencie jej przecienie: podczas wykonywania programu waciwa jej wersja zostanie wybrana w zalenoci od typu parametru, z ktrym zostanie wywoana (wskanik do struktury INFO lub wskanik do struktury LPTR_INFO).

5.1. Listy jednokierunkowe

117

Majc ju komplet funkcji pusta, zestaw funkcji decyzyjnych i uniwersaln funkcj odszukaj_wsk, moemy pokusi si o napisanie brakujcej procedury do rzuci, ktra bdzie suya do doczania nowego rekordu do listy danych z jednoczesnym sortowaniem list wskanikw. Zamy, e bd tylko dwa kry teria sortowania danych, co implikuje, i tablica zawierajca wskaniki do list wskanikw*" bdzie miaa tylko dwie pozycje (patrz rysunek 5 - 9). Adres tej tablicy, jak rwnie wskaniki do listy danych i do nowo utworzonego elementu zostan obowizkowo przekazane jako parametry: void LISTA::dorzu(ELEMENT *q) // rekord doczamy bez sortowania if {info_dane.glowa==NULL) // lista pusta info_danc.glowa-info_dane.ogon=q; else // co jest w licie {{ (info_dane.ogon) ->nastepny=q; info_dane.ogon=q; }} // doczamy wskanik do rekordu do listy // posortowanej alfabetycznie: dorzuc2(0,q,alfabetycznie); // doczamy wskanik do rekordu do listy// p o s o r t o w a n e j wg z a r o b k w : dorzuc2(1,,wg zarobkw);}{

Funkcja jest bardzo prosta, gwnie z uwagi na tajemnicz procedur o nazwie dorzuci. Oczywicie nie jest to jej poprzedniczka ze strony 101. cho rni si od tamtej doprawdy niewiele:void LISTA::dorzuc2( i n t nr, ELEMENT *q, int(*decyzja(ELEMENT ELEMENT *q2)}{

*ql,

LPTR *wsk=new LPTR; wsk->adres=q; // wpisujemy adres rekordu q //Poszukiwanie waciwej pozycji na wstawienie elementu; if (inf_ptr [nr] .glcwa==NULL) // lista pusta { inf_ptr[nr],glowa=inf_ptr[nr].ogon=wsk; Wsk >nastepny=NULL; }else //szukamy miejsca na wstawienie

{

LPTR_INFO

LPTR *przed,*po;

*gdzie;

gdzie=odszukaj_wsk(&inf_ptr[nr],q,decyzja); prrzed=gdzie->glowa; po-qdzie->ogon;

118

Rozdzia 5. Struktury danych

( if{przed==NUlL> // wstawiamy na pocztek l i s t y ) { inf_ptr[nr].glowa=wsk; wsk->nastepny=po;

if

(po (po=NULL) == NULL)

// wstawiamy na koniec Listy j

}

}}

{{ int_ptr[nr].oqon->nastepny=wsk; wsk->nastepny-NULL; i n f _ p t r [ n r ] .ogon=wsk; } else // wstawiamy g d z i e "w rodku" { przed->nastepny-wsk; wsk->nastepny=po; }

W celu zrozumienia dokonanych modyfikacji waciwe byoby porwnanie obu wersji funkcji dorzuc2, aby wykry rnice, ktre midzy nimi istniej, Filozoficznie" nie ma ich wiele - w miejsce sortowania danych sortujemy po prostu wskaniki do nich. Funkcja zajmujca si usuwaniem rekordw wymaga przesania m.in. fizycz nego adresu elementu do usunicia. Majc t informacj naley wyczyci" zarwno list danych, jak i listy wskanikw: int LISTA::usun(ELEMENT *q, int(*decyzja){ELEMENT *ql, ELEMENT *q2) ) {{ // usuwa cakowicie informacje z obu l i s t : //wskanikw i danych ELEMENT *ptr_dane; ptr_dane=usun_wsk (&inf_ptr [ i ] , q, decyzja) ; if (ptr_dane==NULL) return(0); else r e t u r n usun_dane(ptr_dane); } Funkcja usun_wsk zajmuje si usuwaniem wskanikw danego elementu z list wskanikw - jakakolwiek byaby ich liczba. Czytelnik moe zauway z a twoci, e raz jeszcze mamy tu do czynienia z bardzo podobnym do poprzed nich schematem algorytmu. Mona nawet odway si na stwierdzenie, e listing jest zamieszczany wy cznie gwoli formalnoci! Elementarna kontrola bdw jest zapewniana przezfor (int i=0; iglowa==NULL} // l i s t a p u s t a , c z y l i n i e ma co usuwa! r e t u r n NULL; else //szukamy e l e m e n t u d o u s u n i c i a LPTR

if

{

LPTR_INFO *gdzie=odszukaj_wsk{inf,q,decyzja); przed=gdzie->glowa; pos=gdzie >ogon; if (pos=NULL) return NULL; // element nie odnaleziony if(pos==inf->glowa) // usuwamy z pocztku listy inf->glowa=pos->nastepny; else if (pos->nastepny==NULL) //usuwamy z koca listy { inf->ogon=przed;p r z e d - > n a s t e p n y =NULL; }else / / usuwamy g d z i e " z e r o d k a " przed->nastepny=pos->nastepny; ELEMENT * r e t = p o s - > a d r e s ; d e l e t e pos; return ret;

*przed,*pos;

}}

Funkcja usu dane jest zbudowana wg podobnego schematu co funkcja usun_wsk. Poniewa przyjmowane jest zaoenie, e element, ktry chcemy usun, istnieje, programista musi zapewni dokadn kontrol poprawnoci wykonywanych operacji. Tak si dzieje w naszym przypadku - ewentualna nieprawidowo zostanie wykryta ju podczas prby usunicia wskanika i wwczas usuniecie rekordu po prostu nie nastpi. int{ {

LISTA::usun_dane(ELEMENT *q)

// zaoenie: q istnieje! ELEMENT *przed,*pos; przed=NULL;pos=info

while((pos!=q)&&(pos!=NULL))//szukany { przed-poa; pos=pos->nastepny;

dane.glowa;

elementu "przed"

Rozdzia 5. Struktury danych

if (pos!=q) return(O); // element nie odnaleziony?! if (pos==info dane.gowa) // usuwamy z pocztku listy { {{ info_dane.glowa~pos->nastepny; delete pos; else if(pos->nastepny=*NULL) // usuwany z koca listy { info_dane.ogon=przed; przed->nastepny=NULL; delete pos; }else // usuwamy gdzie "ze rodka" } przed->nastepny=pos->nastepny; delete pos; return(1);

}

|

}

Pomimo wszelkich prb uczynienia powyszych funkcji bezpiecznymi, kontrola w nich zastosowana jest cigle bardzo uproszczona. Czytelnik, ktry bdzie zaj mowa si implementacj duego programu w C++, powinien bardzo dokadnie kontrolowa poprawno operacji na wskanikach. Programy staj si wwczas co prawda mniej czytelne, ale jest to cena za may, lecz jake istotny szczeg: ich poprawne dziaanie,.. Poniej znajduje si rozbudowany przykad uycia nowej wersji listy jednokie runkowej. Jest to do spory fragment kodu. ale zdecydowaem si na jego zamieszczenie (biorc pod uwag wzgldne skomplikowanie omwionego materiau ko nieprzyzwyczajony do sprawnego operowania wskanikami mia prawo si nieco zgubi; szczegowy przykad zastosowania moe mie zatem due znaczenie dla oglnego zrozumienia caoci). Dwie proste funkcje wypisz I i wypisz zajmuj si eleganckim wypisaniem na ekranie zawartoci bazy danych w kolejnoci narzuconej przez odpowiedni lisi wskanikw: void LISTA::wypiszl(LPTR_INFO *inf) // wypisujemy zawarto posortowanej l i s t y // wskanikw ( o c z y w i c i e n i c i n t e r e s u j e nas // wypisanie wskanikw (s to a d r e s y ) , l e c z // i n f o r m a c j i na k t r e one wskazuj LPTR *q=inf->glowa; while (q ! = NULL) { cout poprzedni->nastepny=p->nastepny; if(p->nastepny!=NULL) // nic jest to element ostatni p->nastepny->poprzedni=p->pcprzedni; }{

128

Rozdzia 5. Struktury danych W zalenoci od konkretnych potrzeb mona element p fizycznie usun z pamici przez instrukcj delete lub te go w niej pozostawi do ewentualnych innych celw. Rysunek 5 - 14 jest odbiciem procedury usim2kier (potrzebne modyfikacji wskanikw s zaznaczone lini pogrubion):

Rys. 5-14. Usuwanie danych z listy dwukie runkowej

lista cykliczna - patrz rysunek 5 - 1 5 - j e s t zamknita w piercie:. wskani\ ostatniego elementu wskazuje pierwszy" element. Pewien element okrelany jest jako "pierwszy'" raczej umownie i suy wycznie do wejcia w magiczny krg" wskanikw listy cyklicznej...

Rys, 5-/5. Lista cykliczna.

Kada z przedstawionych powyej list ma swoje wady i zalety. Celem tej prezentacji byo ukazanie istniejcych rozwiza, zadaniem za Czytelnika bdzie wybranie jednego z nich podczas realizacji swojego programu.

5.3. StosStos jest kluczow struktur danych w informatyce. To zdanie brzmi bardzo gronie, lecz chciabym zapewni, e nie kryje si za nim nic strasznego. Krtko mwic jest to struktura danych, ktra uatwia rozwizanie wielu problemw natury algorytmicznej i w t wanie stron wsplnie bdziemy zda. Zanim doj dziemy do zastosowa stosu, sprbujmy go jednak zaimplementowa w C++!

5.3.1.Zasada dziaania stosuStos jest struktur danych, do ktrej dostp jest moliwy tylko od strony tzw. wierz choka, czyli pierwszego wolnego miejsca znajdujcego si na nim. Z tego te wzgldu jego zasada dziaania jest bardzo czsto okrelana przy pomocy

5.3. Stos

129 angielskiego skrtu LIFO: Last-In-First-Out. co w wolnym tumaczeniu oznacza ostatni bd pierwszymi". Do odkadania danych na wierzchoek stosu suy zwy czajowo funkcja o nazwie push(X), gdzie X jest dan pewnego typu. Moe to by dowolna zmienna prosta lub zoona: liczba, znak. rekord... Podobnie, aby pobra element ze stosu, uywa si funkcji o nazwie pop(X), ktra zaadowuje zmienn X dan zdjt z wierzchoka stosu. Obie te podstawowe funkcje oprcz swojego gwnego zadania, ktre zostao wzmiankowane wyej, zwracaj jeszcze kod bdu . Jest to staa typu cakowitego, ktra informuje pro gramist, czy czasem nie nastpia sytuacja anormalna, np. prba zdjcia czego ze stosu w momencie, gdy by on ju pusty, lub te prba odoenia na nim kolejnej danej, w sytuacji gdy brakowao w nim miejsca (brak pamici). Programowe reali zacje stosu rni si midzy sob drobnymi szczegami (ostateczne sowo w ko cu ma programista!), ale oglna koncepcja jest zbliona do opisanej wyej. Zasada dziaania stosu moe zosta zatem podsumowana dwiema reguami: po wykonaniu operacji push(X) element X sam staje si nowym wierzchokiem stosu przykrywajc" poprzedni wierzchoek (jeli oczywicie co na stosie ju byo); jedynym bezporednio dostpnym elementem stosu jest jego wierzchoek. Dla dokadniejszego zobrazowania zasady dziaania stosu prosz przeledzi kilka operacji dokonanych na nim i efekt ich dziaania patrz rysunek 5 - 16.W tej wersji niekoniecznie K.

Rys. 5 - 16.Stos i podstawowe operacje na nim.

Courier - Kod programu pochylona kursywa - komentarz

s=pop(c); s = push('A'); s = push('L'); s = pop(c); s=push('B'); s=push('C');

2 1

Rysunek przedstawia stos sucy do zapamitywania znakw. Stae symbo liczne StosPusty, OK i StosPelny s zdefiniowane przez programist w module zawierajcym deklaracj stosu. Wyraaj si one w wartociach typu int (co akurat nic ma specjalnego znaczenia dla samego stosu...). Nasz stos ma pojemno dwch elementw, co jest oczywicie absurdalne, ale zostao przyjte na uytek naszego przykadu, aby zilustrowa efekt przepenienia.

1 Nie jest to bynajmniej obowizkowe!

_130

Rozdzia 5. Struktury danych

Symboliczny stos znajdujcy si pod kad z szeciu grup instrukcji ukazuje zawsze stan po wykonaniu swojej" grupy instrukcji. Jak mona atwo zauway, operacje na stosie przebiegay pomylnie do momentu osignicia jego cakowitej pojemnoci; wwczas stos zasygnalizowa sytuacj bdn. Jakie s typowe realizacje stosu? Najpopularniejszym sposobem jest uycie tablicy i zarezerwowanie jednej zmiennej w celu zapamitania liczby danych aktualnie znajdujcych si na stosie. Jest to dokadnie taki sam pomys, jak ten zaprezentowany na rysunku 5 - 10, z jednym zastrzeeniem: mimo i wiemy, jak stos jest zbudowany od rodka", nie zezwalamy nikomu na bezporedni dostp do niego. Wszelkie operacje odkadania i zdejmowania danych ze stosu musz si odbywa za porednictwem metod push i pop. Jeli zdecydujemy si na zamknicie danych i funkcji sucych do ich obsugi w postaci klasy", to wwczas automatycznie uzyskamy bezpieczestwo" uytkowania - zapewni je sama koncepcja programowania zorientowanego obiektowo. Taki wanie spo sb postpowania obierzemy. Moliwych sposobw realizacji stosu jest mnstwo; wynika to z faktu, i ta struktura danych nadaje si doskonale do ilustracji wielu zagadnie algoryt micznych. Dla naszych potrzeb ograniczymy si do bardzo prostej realizacji tablicowej, ktra powinna by uwaana raczej za punkt wyjcia ni za gotow implementacj. W zwizku z zaoonym powyej celowym uproszczeniem, definicja klasy STOS jest bardzo krtka: stos.hconst i n t DLUGOSC_MAX=300; c o n s t i n t ST0S_PELNY=3; c o n s t i n t STOS_PUSTY=2; c o n s t i n t OK=1; template class STOS

{I

TypPodst t [ DLUGOSC__MAX+l ] ; stos=t[0]...t[DLUGOSC_MAX]

public: STOS () { szczyt=0}; // k o n s t r u k t o r void c l e a r () { szczyt=0;} // zerowanie s t o s u i n t push(TypPodst x); i n t pop (TypPodst &w); i n t StanStosu{); }; // k o n i e c d e f i n i c j i k l a s y STOS

int szczyt;

// szczyt = pierwsza WOLNA komrka

J

Nasz stos bdzie mg potencjalnie suy do przechowywania danych wszela kiego rodzaju, z tego te powodu celowe wydao si zadeklarowanie go w postaci tzw. klasy szablonowej, co zostao zaznaczone przez sowo kluczowe template." Czyli dokonamy tzw. hermetyzacji. 2

5.3. Stos

131

Idea klasy szablonowej polega na stworzeniu wzorcowego kodu, w ktrym typ pewnych danych (zmiennych, wartoci zwracanych przez funkcje...) nie zostaje precyzyjnie okrelony, ale jest zastpiony pewn sta symboliczn. W naszym przypadku jest to staa TypPodst. Zalet tego typu postpowania jest do dua uniwersalno tworzonej klasy. gdy dopiero w funkcji main okrelamy, e np. TypPodst powinien zosta za mieniony na np. float, char* lub jaki zoony typ strukturalny. Wad klasy szablonowej jest jednak do dziwna skadnia, ktrej musimy si trzyma chcc zdefiniowa jej metody. O ile jeszcze definicje znajduj si w ciele klasy (tzn. pomidzy jej nawiasami klamrowymi), to skadnia przypomina normalny kod C++. W momencie jednak gdy chcemy definicje metody umieci poza klas, to otrzymujemy tego rodzaju dziwolgi3:template int STOS: : push(TypPodst x) // element x zostanie pooony na stos if ( szczyt0) { w=t [--szczyt ]; r e t u r n (OK); }else r e t u r n (STOS_PUSTY); }

Od czasu do czasu moe zaj potrzeba zbadania stanu stosu bez wykonywania na nim adnych operacji. Uyteczna moe by wwczas nastpujca funkcja:t e m p l a t e < c l a s s TypPodst> i n t STOS:: StanStosu() { // zwraca informacje o s t a n i e s t o s u switch(szczyt) { case 0 : r e t u r n (;STOS__PUSTY) ; +1 c a s e DLUGOSC_MAXl : r e t u r n (STOS_PELNY) ; default : r e t u r n (OK) }

{{

}

}

}

Jakie s inne moliwe sposoby zdefini