• No results found

Kezdokonyv.az.Algoritmusokrol.2006.eBook DigIT

N/A
N/A
Protected

Academic year: 2021

Share "Kezdokonyv.az.Algoritmusokrol.2006.eBook DigIT"

Copied!
631
0
0

Loading.... (view fulltext now)

Full text

(1)

Simon Harris - James Ross

Kezdőkönyv az

algoritmusokról

(2)

Simon Harris - James Ross

Kezdőkenyv az

algoritmusokról

(3)

Kezdőkönyv az algoritmusokról

Eeginning algorithms, Simon Harris-James Ross

Copyright © 2005 by Wiley Publishing, Inc., Indianapolis, Indiana All rights reserved. This translation published by license.

Trademarks: Wiley, the Wiley logo, Wrox, the Wrox logo, Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc. and/ or i ts affiliates, in the United States and other countries, and may not be used without written permission, SQL Server is a trademark of Microsoft Corporation in the United States and/ or other countries. All other trademarks are the property of their respective owners, Wiley Publishing, Inc., is not associated with any product or vendor mentioned in this book. The Wrox Brand trade dress is a trademark of Wiley Publishing, Inc. in the United States and/ or other countries. Used by permission.

Minden jog fenntartva. A fordítás a Wiley Publishing, Inc. engedélyével jelent meg. Védjegyek: Wiley, a Wiley embléma, Wrox, a Wrox embléma, a Programmer to Prog­ rammer, és a hozzá kapcsolódó arculat a John Wiley & Sons, Inc. és/vagy partnerei véd­ jegye vagy bejegyzett védjegye az Amerikai Egyesült Államokban és más országokban, és nem használható fel írásbeli engedély nélkül. SQL Server a Microsoft Corporation véd­ jegye az Egyesült Államokban és más országokban. Minden további védjegy a megfelelő védjegybirtokos tulajdona. A könyvben említett cégekkel és termékekkel sem a Wiley Publishing, Inc., sem pedig a SZAK Kiadó nem áll függőségi viszonyban. A Wrox Brand arculat a Wiley Publishing, Inc. védjegye az Amerikai Egyesült Államokban és más országokban. Felhasználva a Wiley Publishing, Inc. engedélyével.

Magyar fordítás (Hungarian translation) © SZAK Kiadó 2006.

Fordította a SZAK Kiadó fordítócsoportja: Baksáné Varga Erika, Barát Éva, Csapó Ádám, Csomay Dávid, Egenhoffer Norbert, Gyimesi Csaba, Herczeg Géza, Lucza Mónika, Tiber Melinda

Terminológiai előkészítés: Kis Balázs Lektor: dr. Csink László

ISBN 963 9131 89 X

A könyv fordítása a Kilgray Kft. MemoQ (http://www.memoqtrn.com) programjával készült, a szöveg helyességét és az elválasztásokat pedig a MorphoLogic Helyesek nevű programjával ellenőriztük

Minden jog fenntartva. Jelen könyvet, illetve annak részeit a kiadó engedélye nél­ kül tilos reprodukálni, adatrögzítő rendszerben tárolni, bármilyen formában vagy eszközzel elektronikus úton vagy más módon közölni.

SZAK Kiadó Kft. • Az 1795-ben alapított Magyar Könyvkiadók és Könyvte*sztők Egyesülésének a tagja • 2060 Bicske, Diófa u. 3. • Tel.: 36-22-350-209 • Fax: 36-22-565-311 • www.szak.hu • e-mail: info@szak.hu • Kiadóvezető: Kis Ádám, e-mail: adam.kis@szak.hu Főszerkesztő: Kis Balázs MCSE, MCT, e-mail: balazs.kis@szak.hu

(4)

Tartalomjegyzék

Köszönetnyi Ivánitás

Bevezetés

Kinek szól a könyv? Elvárt előismeretek A könyv témája A könyv használata A megközelités alapelvei

Törekedjünk az egyszerűségre!

Ne optimalizáljunk előre!

.

Felhasználói interfészek

Tesztelni, tesztelnil

Legyünk alaposak!

Mire van szükség a könyv használatához? A könyvben használt jelölések

Forráskód Hibajegyzék p2p.wrox.com

1.

Az alapok

Az algoritmusok definíciója Az algoritmusok bonyolultsága A nagy

O

jelölés

Konstans idő: 0(1)

Lineáris idő: O(N)

Kvadratikus idő: O(NZ)

Logaritnúkus idő: O (log N) és O (N log N)

Faktoriális idő: O(N!)

Egységtesztelés

Mi az egységtesztelés?

Miért fontos az egységtesztelés?

)Unit-bevezető

Tesztelésen alapuló programozás

Összefoglalás

x i

xiii

xiii xiii xiv xiv XV xvi xvi XV11 xvii xviii xix XX xxi xxi xxii

1

1

4

5

7

7

8

9

9

10

11

13

13

17

18

(5)

Tartalomjegyzék

2.

Iteráció és rekurzió

19

Számítások végrehajtása

20

Tömbök feldolgozása

22

Iterátorak használata tömbalapú problémák megoldására 23

Rekurzió

42

Rekurzív könyvtárfa-nyomtatási példa 44

A rekurzív algoritmus működése 47

Összefoglalás

48

Gyakorlatok

49

3.

Listák

51

A listákról

51

A listák tesztelése

55

Listák megvalósítása 68 A tömblista 69 Láncolt lista 77 Összefoglalás

87

Gyakorlatok

87

4.

Várakozási sorok

89

Asorok

89

Sarműveletek 90 A sorinterfész 91 AFIFO-sor 92 A FIFO-sor megvalósítása 96 Blokkolósorok

97

Példa: telefonos ügyfélszolgálat szimulátora

102

Az alkalmazás futtatása 112 Összefoglalás

114

Gyakorlatok

114

5.

Vermek

115

Vermek

115

A tesztek

118

Megvalósítás

121

Példa: az undo/redo parancs megvalósítása

124

Az undo/ red o parancs tesztelése 125

Összefoglalás

134

(6)

6.

7.

8.

Alapvető rendezés

A rendezés fontossága Rendezési alapismeretek Az összehasonlítókról Összehasonlító műveletek Az összehasonlító interfész Néhány szabványos összehasonlító A buborékrendezésről

A ListSorter interfész

Az AbstractListSorter tesztelése A kiválasztásos rendezés alkalmazása A beszúrásos rendezésről

A stabilitásról

Az alapvető rendezési algoritmusok összehasonlítása CallCountingListComparator ListSorterCallCountingTest Az algoritmus-összehasonlításról Összefoglalás Gyakorlatok

Fejlettebb rendezés

A Shell-rendezési algoritmus alapjai A gyorsrendezésről

Az összetett összehasonlítóról és a stabilitásról Az összefésüléses rendezési algoritmusról

Összefésülés

Az összefésüléses rendezési algoritmus

A fejlettebb rendezési algoritmusok összehasonlításáról Összefoglalás

Gyakorlatok

Prioritásos sorok

A prioritásos sorok áttekintése Egyszerű példa prioritásos sorra Prioritásos sorok kezelése

Rendezetlen listás prioritásos sor áttekintése Rendezetlen listás prioritásos sor megvalósítása Halmon alapuló prioritásos sorok működése Prioritásos sorok megvalósításainak összehasonlítása Összefoglalás Gyakorlatok

135

135 136 137

137

138

138

143

146

146

151 156 160 161

162

163

166

167 168

169

169 175 182 186

186

187

194 198 198

199

199

200

203

206

208

210

219 222 223

(7)

Tartalomjegyzék

9.

Bináris keresés és beszúrás

225

A

bináris keresés működése 225

A bináris keresés megközelítései 228

Listabeli kereső 228

Iteratív bináris kereső 236

A listabeli kereső teljesítményének vizsgálata 238

Bináris beszúrás működése 245

Listabeszúró 246 Teljesítmény vizsgálata 250 Összefoglalás 254

10.

Bináris keresőfák

257

A bináris keresőfákról

257 Minimum 258 Maximum 259 A következő csomópont 259 A megelőző csomópont 260 Keresés 260 Beszúrás 262 Törlés 264 Inorder bejárás 266 Preorder bejárás 267 Posztorder bejárás 267 Kiegyensúlyozás 268

A

bináris keresőfa tesztelése és megvalósítása 270

A bináris keresőfa teljesítményének megállapítása

295

Összefoglalás 299 Gyakorlatok 299

11.

Hasitás

301

A

hasítás megértése 301 Munka a hasítással 309 Lineáris vizsgálat 312 Vödrös módszer 319

A

teljesítmény megállapítása 324 Összefoglalás 331' Gyakorlatok 331 ..

12.

Halmazok

333

A halmazokról

333 Halmazmegvalósítások tesztelése 337 Listahalmaz 344 viii

(8)

Tartalomjegyzék

Hasítóhalmaz

346

Fahalmaz

350

Összefoglalás

357

Gyakorlatok

358

13. Leképezések

359

A leképezésekró1

359

Leképezésmegvalósítások vizsgálata

364

Listaleképezés

373

Hasítóleképezés

377

F aleképezés

381

Összefoglalás

388

Gyakorlatok

389

14. Hármas keresőfák

391

Hármas keresőfák

391

Szó keresése

392

Szó beszúrása

396

Prefix keresés

398

Mintaillesztés

399

A hármas keresőfák gyakorlati alkalmazása

403

Keresztrejtvény megoldását segítő példa

417

Összefoglalás

422

Gyakorlat

422

15.

B-fák

423

A B-fákról

423

B-fák a gyakorlatban

429

Összefoglalás

443

Gyakorlatok

443

16. Sztri ngkeresés

445

Általános sztringkereső interfész

445

Általános tesztcsomag

447

Letámadásos algoritmus

451

A Boyer-Moore-algoritmus

454

A tesztek létrehozása

456

Az algoritmus megvalósítása

457

Sztringillesztő iterátor

461

A teljesítmény összehasonlítása

462

A teljesítmény mérése

463

Az összehasonlítás eredménye

467

Összefoglalás

468

(9)

Tartalomjegyzék

17.

Sztri ngillesztés

A Soundex algoritmus A Levenshtein-szótávolság Összefoglalás

18.

Számítógépes geometria

Rövid geometriai ismédés

Koordináták és pontok Egyenes szakaszok Háromszögek

Két egyenes szakasz metszéspontjának meghatározása Meredekség

Az y tengely metszése A metszéspont meghatározása

A legközelebbi pontpár meghatározása Összefoglalás Gyakorlatok

19.

Pragmatikus optimalizálás

Az optimalizálás szerepe A profilírozásról A FileSortingHelper példaprogram Profilirozás a hprof modullal

Profilírozás a J ava Memory Profiler programmal Az optimalizálásról

Optimalizálás a gyakorlatban Összefoglalás

"A" függelék: Ajánlott irodalom

"B" függelék: Internetes források

"C"

függelék: Bibliográfia

"D"

függelék: A gyakorlatok megoldásai

Tárgymutató

A szerzőkről

x

471

471 483 494

495

495 495 497 497 498 499 500 501 517 529 529

531

531 533 534 538 541 543 544 552

553

555

557

559

609

621

(10)

Köszönetnyi lvánitás

Simon Harris

Először is hatalmas köszönet illeti J on Eavest, aki biztosította számunkra ezt a lehe­ tőséget, és Jamest, akinek tudása és professzionalizmusa mind a mai napig lenyűgöz. A könyvet egyikük segítsége nélkül sem tudtam volna befejezni.

Köszönettel tartozom azoknak is, akik elolvasták és véleményezték a kéziratot: Andrew Harris, Andy Trigg, Peter Barry, Michael Melia és Darrell Deboer (egészen biztos, hogy valakit kihagytam). Remélem, hogy a végeredmény méltónak bizonyul az erőfeszítéseikhez.

Szeretnék köszönetet mondani testvéremnek, Timnek, aki elviselte folyamatos locsogásomat, Kerri Rusnaknak és családjának, akik elláttak teával és rágcsálnivaló­ val, és nem utolsósorban aikido tanitványaimnak, akik távollétemben is szargalma­ san edzettek.

És végül szeretnék őszinte köszönetet mondani mindazoknak a Wiley kiadóná!, akik a munka során végig segítségemre voltak, valarnint barátaimnak és családom­ nak, akik akkor is mögöttem álltak és bátoritottak, arnikor már azt hittem, hogy az egész világ összedől. Igen fontos tapasztalat volt.

James Ross

Először szeretnék köszönetet mondani Simonnak, arniért megengedte, hogy első könyvének társszerzője legyek. Remek alkalom volt arra, hogy életemben először komolyan írjak, ráadásul Simonnal dolgozni örömteli és tanulságos. Gyakran hallani olyan történeteket, amelyekben a szerzők barátságát tönkretette a közös munka, örü­ lök, hogy nekünk sikerült ezt a csapdát kikerülni.

Szeretném megköszönni a Wiley összes munkatársának, hogy ilyen megértőek voltak két újonc szerzővel, és tévedhetetlenül terelgettek bennünket a cél felé - kü­ lön köszönet Ami Sullivannek és Carol Longnak Segítségüket nagyra becsüljük.

Köszönöm a szuperguruk segítségét is a ThoughtWorksnél, akik az elmúlt né­ hány évben szakmai életemet csodálatossá varázsolták, különösen Andy Triggét, aki azóta igen nagy programozó cimborám, arnióta megírtuk az első közös egységteszt­ jeinket, és aki lankadatlan figyelemmel és gondossággal olvasta át a fejezeteimet, va­ lamint Jon Eavesét, a könyv szakmai szerkesztőjéét, aki mindig megnevettet, és új dolgokra tanít. Simon Stewart az első kéziratok véleményezésével járult hozzá a könyvhöz, és Gregor Hohpe, valamint Martin Fowler biztosította az energiát és az inspirációt a hosszú éjszakákon át húzódó, lázas gépeléshez.

(11)

Ha már a hosszú éjszakákról esett szó, oszmtén meg kell vallanom, hogy a

könyv Oegalábbis az én részem) nem készülhetett volna el az életemben fontos sze­

repet betöltő hölgyek szeretete és megértése nélkül: ők Catherine, a

mi

külön nap­

rendszerünk középpontj a, Jessica, Ruby és a kis Ella, aki hat hónapos volt, amikor a

könyv írásába belekezdtem, és a munka során minden egyes éjjel legalább

12

órát

aludt. Lehet, hogy soha nem olvasod el ezt a könyvet, kicsim, de ha én a kezembe

veszem, mindig te jutsz eszembe!

(12)

Bevezetés

A Kezdókiitryv az algoritmusokróllépésenkénti bevezetőt nyújt a szárrútástechnikai algo­ ritmusok életszerű használatának világába.

A fejleszták mindennapi munkájuk során algoritmusokkal és adatstruktúrákkal dolgoznak. Az algoritmusok alapos ismerete és annak felismerése, hogy mikor kell alkalmazni őket, nélkülözhetetlen a szoftverek készítése során, hogy azok nemcsak helyesen, hanem megfelelő teljesítménnyel is működjenek.

A könyv célja, hogy a napról napra haladó szaftverfejlesztés során leggyakrab­ ban előforduló algoritmusokat és adatstruktúrákat bemutassa, ugyanakkor maradjon gyakorlatias, pontos, lényegre törő, és igyekezzen nem eltérni az alapszintű témakö­ röktől és példáktóL

Kinek szól

a

könyv?

A könyv azoknak szál, akik alkalmazásokat fejlesztenek, vagy éppen fejlesztésbe fognak, és szeretnék megérteni az algoritmusokat és az adatstruktúrákat. A célkö­ zönség a programozók, fejlesztők, szoftvermérnök-hallgatók, információrendszer­ hallgatók és informatikushallgatók népes tábora.

A könyv szerzői feltételezik, hogy a számítógépes programozás általános ismere­ tei a birtokukban vannak, és remélik, hogy a kötet a kód kihagyásával - még ha nagy­ részt fogalmi szinten is - olvasható és követhető az első oldaltól az utolsóig. Ebből kifolyólag csoportvezetők, építészek és üzleti elemzők is haszonnal forgathatják.

Elvárt előismeretek

Mivel a példaprogramok mindegyike a Java programozási nyelv felhasználásával ké­ szült, használható Java-tudásra, valamint a szabványos Java-könyvtárak- különösen a j ava. l an g csomag - ismeretére szükség lehet. A tömb ökkel, ciklusokkal és egyéb

programozási technikákkal sem árt tisztában lenni, és természetesen a J ava-osztályok létrehozásának és fordításának mikéntje is lényeges.

Az itt említett előismereteken kívül más követelmény nem szükséges a kötet adatstruktúrákra vagy algoritmusokra vonatkozó ismeretanyagának elsajátításához.

(13)

A

könyv témája

A kötetben részletes magyarázatokat, néhány megvalósítást, a mindennapi használat­ ra vonatkozó példákat és gyakorlatokat találunk, amelyek mindegyikének célja, hogy olyan tudás birtokába jussunk, amellyel új ismereteinket az életben is kamataztami tudjuk. A könyvben található példák ritkán elméleti természetűek. Az egyes fejezetek kódjait különös gonddal válogattuk össze, és azokat a legtöbb esetben akár azonnal is használhatjuk életszerű alkalmazásokban.

Próbáltunk ragaszkodni a legáltalánosabban elfogadott szaftverfejlesztési gya­ korlatokhoz. Ezek közé tartozik a tervezési minták [GoF - Gang of Four, Design Patterns], a kódolási konvenciók, a minóség-ellenórzések és a teljesen automatizált egységtesztek használata. Remélhetőleg az algoritmusok és az algoritmusok problé­ mamegoldásban betöltött rendkívül fontos szerepének megértésén kívül megtanul­ juk, hogy a robusztus, bővíthető és természetesen működó szoftverek építése tiszte­ letet érdemlő tevékenység.

A Java-nyelvben járatos olvasók felfedezhetnek némi átfedést a könyvben is­ mertetett osztályok és a j ava. uti l csomag osztályai között. A könyv nem foglalko­ zik a Java-könyvtárakban található specifikus megvalósításokkal. Ehelyett inkább bepillantást enged abba, miért tartották fontosnak a Java-nyelv tervezői bizonyos al­ goritmusok és adatstruktúrák megvalósításainak beépítését csakúgy, mint azok mű­ ködését és használatát is.

A kötet nem a számítógépes programozás alapjait tanítja meg, sem általában, sem a Java-programozás tekintetében. Nem ismerteti a szabványos Java-könyvtárak használatának szabályait sem: nem ez célja. Noha a példaprogramok használják a j ava. l an g osztályait és néhány esetben a j ava. i o csomagokat, az összes többi J ava­ csomag túlmutat a könyv témáján. Ehelyett az összes szükséges osztályt kézzel épít­ jük meg, ezáltal tapasztalha�uk az algoritmusok felfedezésének örömét.

Noha az egységtesztelés minden fejezetben kiemeit figyelmet kap, a kötet nem egységtesztdési kézikönyv vagy útmutató. Inkább az egységtesztek kódolásának be­ mutatásával próbálja meg elsajátíttatni az alapszintű egységtesztelés alapismereteit.

A

könyv használata

A könyvet az elejétól a végéig érdemes elolvasni. Rendezési, keresési és egyéb meg­ határozott algoritmusok segítségével a kötet az algoritmusok, adatstruktúrák és telje­ sítménykarakterisztikák alapjain vezeti végig az olvasót. A könyv négy fő részból áll.

(14)

• Az első öt fejezet az algoritmusok alapjait, például az iterációt, a rekurziót ismerteti, mielőtt bevezetné az olvasót az alapvető adatstruktúrák, a listák, a vermek és a sorok világába.

• A 6-10. fejezet különböző rendezési algoritmusokkal foglalkozik, valamint olyan nélkülözhetetlen témákkal, mint a kulcsok és a sorrend kérdése. • A 7-15. fejezet a tárolás és keresés hatékony módszereivel foglalkozik hasí­

tótáblák, fák, halmazok és leképezések segítségéveL

• A 16-19. fejezet speciális és bonyolultabb témaköröket érint, emellett részle­ tezi az általános teljesítménybeli buktatókat és az optimalizálási módszereket. Minden fejezetben újabb, az előző fejezetek témaköreire épülő fogalmakkal találko­ zunk, amelyek megalapozzák a következő fejezetek ismeretanyagát. Tehát a könyvet bármelyik fejezetnél felüthetjük, és néhány fejezet átlapozásával megfelelő képet kaphatunk a témáról. Mindenesetre tanácsos minden fejezetben elvégezni a példabeli megvalósításokat, példaprogramokat és gyakorlatokat, hogy a tárgyalt fogalmak és elvek teljesen letisztulhassanak. A könyv végén lévő függelékekben megtaláljuk a to­ vábbi ajánlott olvasmányok listáját, a felhasznált weboldalak listáját és a bibliográfiát.

A megközelítés alapelvei

A kód megértésének többnyire az a legnehezebb része, hogy átlássuk a döntéshoza­ tali folyamatot befolyásoló, gyakran íratlan feltételezéseket és elveket. Ezért tartjuk fontosnak, hogy részleteiben megvilágítsuk a könyvben alkalmazott megközelítést. Betekintést engedünk a logikai alapokba, amelyeket alapvető fejlesztési gyakorlatnak tekintettünk a könyv megírása során. A könyv elolvasása után remélhetőleg az olva­ só is méltányolja majd, hogy miért hiszünk a következő elvekben.

• Az egyszerűség jobb kódot eredményez. • Ne optimalizáljunk idejekorán!

• Az interfészek hozzájárulnak a tervezés rugalmasságához.

• A kódot automatikus egység- és funkcionális tesztelésnek kell alávetni.

(15)

Bevezetés

Törekedjünk az egyszerűségre!

Milyen gyakran halljuk ezt a megjegyzést:

"Ó,

ez túl bonyolult! Úgysem értené meg." Vagy: "A kódunk túl nehezen tesztelhető." A szoftvermérnökség lényege a bonyolultság kezelése.

Ha sikerült a célnak megfelelő rendszert építenünk, de a rendszer ismertetése vagy tesztelése túl bonyolult, akkor a rendszer csak véletlenül működik. Gondolha�uk azt, hogy a megoldást szándékosan valósítottuk meg az adott módon, de a tény, hogy a rendszer működése inkább a valószínűségtől és nem a tiszta determinizmustól függ.

Ha túl összetettnek tűnik, bontsuk le a feladatot kisebb, könnyebben kezelhető részekre. Kezdjük a kisebb problémák megoldásával. Majd a közös kód, a közös megoldások alapján kezdjük el átszervezni és absztrahálni a problémákat. liy módon a nagy rendszerek kisebb feladatok összetett elrendezésévé alakulnak.

Az

"EHMM

-egyszeruen, hogy rnindenki megértse" jelszóhoz ragaszkodva a

könyv összes példája a lehető legegyszerubb. Mivel a könyv célja, hogy gyakorlati se­ gédletet biztosítson az algoritmusokhoz, a példaprogramokat az életszerű alkalmazá­ sokhoz a lehető legközelebb igazítottuk Bizonyos esetekben azonban a metódusokat kissé hosszabbra kellett hagynunk, rnint szerettük volna, de végül is oktató célzattal készült könyvről van szó, és nem a lehető legtömörebb kód megírásáról.

Ne optimalizáljunk előre!

Csábító lehet rögtön a kezdetektől fogva a kód gyorsaságára törekedni. Az optimali­ zálás és a teljesítmény érdekessége, hogy a szűk keresztmetszetek sohasem ott van­ nak, ahol várnánk, és nem is olyan természetűek, rnint amilyeneket várnánk. Az ilyen kényes pontok előzetes találgatása költséges gyakorlat. Sokkal jobban járunk, ha jól megtervezzük a kódot, és külön kezeljük a teljesítményjavítás feladatát, amihez a 19. fejezetben ismertetett speciális ismeretekre lesz szükség.

Ha a könyvben komprornisszumot kellett kötni a teljesítmény és az érthetőség között, igyekeztünk az érhetáségre törekedni. Sokkal fontosabb, hogy megértsük a kód elvét és célját, rninthogy milliszekundumokat lefaragjunk a futásidőbőL

A jó tervet sokkal könnyebb profilirozni és optimalizálni, rnint az "okos" kódo­ lással előállitott spagettikódot, és tapasztalataink szerint az egyszerű terv eredménye­ ként készített kód kis optimalizálás mellett is remekül teljesít.

(16)

Felhasználói interfészek

A:z adatstruktúrák és algoritmusok nagy része ugyanazt a külsó működést muta�a, még akkor is, ha a mögöttes megvalósítás eléggé eltérő. A:z életszerű alkalmazásokban a kü­ lönbözó megvalósítások között gyakran feldolgozási vagy memóriamegszorítások mi­ att kell választanunk. A:z esetek zömében ezek a megszorítások előre nem ismertek.

Az interfészek lehetóvé teszik, hogy mögöttes megvalásításra való tekintet nél­ kül meghatározzuk a megállapodást. Ebből kifolyólag a tervezést a megvalósítás be­ köthetóségének támogatásával teszik rugalmassá. Ezért van szükség arra, hogy minél inkább az interfészeknek megfelelóen kódoljunk, és így lehetóvé tegyük a különbözó megvalósítások helyettesítését.

A könyv minden példabeli megvalósítása a meghatározott· működés interfész­ műveletekre való fordításával kezdődik. A legtöbb esetben ezek a műveletek a kö­ vetkező két csoport valamelyikébe sorolhatóak: alapszintű vagy elhagyható.

Az alapszintű műveletek biztosítják az adott interfészhez szükséges alapműkö­ dést. A megvalósítások általában az első elvekból származnak, és ezért szarosan ösz­ szefüggnek egymással.

Az elhagyható műveleteket ezzel szemben az alapszintű műveletekre alapozva valósíthatjuk meg, és rendszerint a fejlesztő kényeimét szalgálják Szükség szerint magunk is könnyűszerrel megvalósítha�uk őket saját alkalmazáskódunkban. Mivel a gyakorlatban sokan használják őket, ezeket a műveleteket az alapszintű API részé­ nek tekinthetjük, és egy adott témakör tárgyalását addig nem fejezzük be, amíg mindegyiket részleteiben meg nem valósítottuk

Tesztelni, tesztelnil

A korszeru fejlesztési gyakorlat megköveteli, hogy a szaftver szigorúan egyesített és funkcionálisan tesztelt legyen a kód integritásának biztosítása érdekében. A megkö­ zelítést követve az interfész definiálása után, de még bármilyen konkrét megvalósítás definiálása előtt funkcionális követelményeinket tesztesetre fordítjuk annak ellenőr­ zésére, hogy minden feltétellel foglalkoztunk, és megerősítettünk őket.

A tesztek a J Unit segítségével készültek, amely a J ava tényleges szabványos tesz­ tdési keretrendszere, és a tesztek a megvalósítás minden funkcionális szempontját ellenőrzik.

A tesztek a meghatározott interfészek alapján, nem pedig bármilyen konkrét meg­ valósítás alapján készültek. Ez lehetóvé teszi, hogy az összes megvalósítás esetén ugyanazokat a teszteket alkalmazzuk, és így biztosítsuk az egységes minóséget. Ezen­ kívül a különbözó teljesítménykarakterisztikákat is bemuta�ák, ami akkor fontos, ha az alkalmazásban a használni kívánt különbözó megvalósítások között válogatunk.

(17)

A tesztelés puristái kifogásolják, hogy a tesztek az ó ízlésük szerint túl hosszúak,

és egy metódusban

túl

sok dolgot tesztelnek Hajlamosak lennénk egyetérteni velük,

de a megértés támogatása érdekében leegyszerűsí�ük a dolgokat, és alkalmanként

úgy gondoltuk, hogy vehe�ük magunknak a bátorságot, és néhány helyzetet össze­

vonhatunk egyetlen tesztmetódusban.

A

lényeg, hogy mielótt bármilyen megvalósírási kódot elkészítenénk, először írjuk

meg a teszteket. Ez a megközelítés, a

tejifelésen alapuló programozás (test-driven development,

IDD) az osztályok megállapodására, azaz a közzétett viselkedésre összpontosít, nem a

megvalósításra. Lehetóvé teszi, hogy a teszteseteket majdhogynem a kód követelmé­

nyeiként vagy használatának eseteiként kezeljük; a tapasztalatok szerint ez is egyszerű­

síti az osztályok terveit. Mint a példákban látni fogjuk, azáltal, hogy az interfészekhez

kódoljuk tesz� einket, gyerekjáték lesz a tesztelésen alapuló programozás.

Legyünk alaposak!

A tesztelés szigorúsága miatt önelégültté válhatunk, és azt hihe�ük, hogy a kódunkat tel­

jes alapossággal teszteltük, ezért az hibamentes.

A

baj csak az, hogy a tesztek nem feltét­

lenül bizonyí�ák, hogy a szoftver azt a feladatot haj�a végre, amit kell. Ehelyett csupán

azt igazolják, hogy a szoftver az adott helyzetekben és feltételekkel működik, de ezek

nem mindig fedik le a valóságot. Lehet, hogy a világ legnagyszerűbb, legátfogóbb teszt­

csomagjával rendelkezünk, de ha rossz dolgokat tesztelünk, semmit sem ér az egész.

A

gyors hibázás elve alapján ajánlott a defenzív programozás: ellenőrizzük a null­

mutatókat, győződjünk meg róla, hogy az objektumok a metódus elején megfelelő ál­

lapotban vannak, és így tovább.

A

gyakorlat bebizonyította, hogy ezzel a programo­

zási móddal hamarabb megtalálha�uk az összes különös programhibát, és nem kell a

Nu ll Po i n terException

kivételre várnunk.

Mielőtt bármilyen objektum állapotáról vagy paraméter típusáról bármit is felté­

teleznénk, a kód vizsgálatával ellenőrizzük a feltevést. Ha bármikor azt gondoljuk,

hogy valami sohasem fordulhat elő, ezért nem is kell aggódnunk miatta, végezzünk

kódszintű érvényességvizsgálatot!

Képzeljük el például, hogy az adatbázisban van egy pénzügyi mezó, amelyról

"tudjuk", hogy "soha" nem tartalmaz majd negatív értéket. Ha a vizsgálatokat ki­

kapcsoljuk, valamikor, valahogyan egy negatív érték egészen biztosan

megjelenik

a

mezőben. Lehet, hogy napok, hónapok vagy évek telnek el, mire észrevesszük ennek

következményeit. Előfordulhat, hogy a rendszer más részeiben is befolyásolja más

számítások működését. Ha az összeg

-0,01

cent volt, a különbség alig észrevehető.

Mire felfedezzük a problémát, már nem tudjuk az összes káros mellékhatást megha­

tározni, nem is beszélve arról, hogy ki is kellene javítani őket.

(18)

Ha engedélyeztük volna a kódszintű érvényességvizsgálatot, a szoftver teljesen megjósolható módon hibát jelzett volna abban a pillanatban, hogy a baj előállt, és valószínűleg a probléma diagnosztizálásához szükséges összes információ is a ren­ delkezésünkre állt volna. Ehelyett jóvátehetetlenül megsérültek a rendszer adatai.

Az éles kód vizsgálata lehetővé teszi, hogy a kód hibái megjósolható módon áll­ janak elő, ami lehetővé teszi a probléma okának és természetének könnyű és gyors azonosítását, és elhanyagolható segédszámítási költségekkel jár. Egyetlen pillanatig se gondoljuk, hogy a vizsgálatok hátrányosan befolyásolják a rendszer teljesítményét. Jó esélyünk van arra, hogy a kód összes vizsgálatához szükséges idő nem összemér­ hető egy távoli eljáráshívásban vagy adatbázis-lekérdezésben töltött idővel. Ajánlatos az éles kódban bekapcsolt állapotban hagyni a vizsgálatokat.

Mire van szükség a könyv

használatához 7

A felépítés és futtatás nem is lehetne könnyebb. Ha kezdeti előnnyel szetetnénk in­ dulni, a teljesen működőképes projektet forráskóddal, tesztekkel együtt, valamint az automatizált parancssori verziót letölthetjük a Wrox webhelyéről Oásd a "Forrás­ kód" cím ű részt).

Ha a "csináld magad" megközelítés hívei vagyunk, szerencsénk van, mert így mi­ nimalizálha�uk a függőségek számát. Kiindulásként a következőkre van szükségünk:

• Java Development kit QDK) 1.4 vagy újabb verziója, amely tartalmazza a kód fordításához és futtatásához szükséges összes komponenst;

• ]Unit-könyvtár, amely egyetlen jar fájlból áll, és ha egységteszteket szetet­ nénk fordítani és futtatni, a classpath környezeti változónak tartalmaznia kell a fájlt;

• szövegszerkesztő vagy integrált fejlesztői környezet (Integrated Development

Environment- IDE) a kódoláshoz.

Az első két tétel (a JDK és a ]Unit) ingyenesen letölthető az internetről Oásd a B függeléket). Az utolsó követelmény tekintetében nem szetetnénk vitát kirobbantani, ezért a választást az olvasóra bízzuk. Egészen biztosan van kedvencünk, ragaszkod­ junk hozzá! Ha nincsen olyan program, amellyel kódolhatnánk, kérdezzük meg bará­ tainkat, hallgatótársainkat, előadóinkat vagy kollégáinkat. Egészen biztosan szívesen megosztják velünk véleményüket.

(19)

Bevezetés

Mivel a Javáról van szó, a példaprogramokat bármely operációs rendszeren le� fordíthatjuk és futtathatjuk. A könyvet Apple Macintosh és Windows alapú számító� gépeken írtuk és fejlesztettük Egyetlen kód sem különösebben processzorintenzív, tehát a szaftverfejlesztéshez használt hardverünk biztosan megfelel majd.

A

könyvben használt jelölések

Annak érdekében, hogy a legtöbb új ismeret birtokába juthassunk, és nyomon tud� juk követni, mi történik, a könyvben az alábbi jelöléseket alkalmaztuk:

Gyakorlófeladat

A Gyakorlófeladat elnevezésű részben érdemes végigcsinálni a feladatot a könyv utasí� tásait követve.

1. A Gyakorlófeladat rendszerint több kódolt lépést tartalmaz.

2. A lépések nem mindig számozottak, néhányuk nagyon rövid, míg mások a nagyobb, végső célhoz vezető, kis lépések sorozatából állnak.

A megvalósitás müködése

Minden Gyakorlófeladat után A megvalósítás működése című részben találjuk a kódblokkok működésének részletes magyarázatát. A könyv témája, az algoritmusok kérdése nem igazán felel meg számozott feladatoksarok elvégzésének, sokkal inkább a gyakorlati példáknak, tehát észre fogjuk venni, hogy a Gyakorlófeladat és A megvalósítás műkodése megfelelően módosult. Az alapelv az, hogy alkalmazzuk a megszerzett tudást.

Az ilyen dobozokban a közvetlenül a

dobozt körülvev6 szövegre

vonatkozó

fontos információkat találunk, ameJ:yela6J.

nem 82:abad elfeledkeznünk. Az aktuális témára vonatkozó tippek, ö"tletek, trükko·k dőlt betűve� kissé be!Jebb húz­ va szerepelnek.

A szövegben megjelenő betűtípusokkal kapcsolatban:

XX

A fontos szavakat bevezetésük során kiemeljük.

(20)

• A fájlnevek, az URL-ek és a kódok a következőképpen szerepelnek a szö­ vegben: persistence.properties.

• A kódokat kétféle változatban láthatjuk:

A példaprogramokban azrú"f-ésfo-ntos kódot szürkeháttérrel , emeljük ki.

A szürke háttér nem jelenik meg az aktuális témában kevésbé fontos vagy már korábban bemutatott kód mögött.

Forráskód

A könyv példáinak elvégzésekor mi magunk is begépelhetjük kézzel a kódot, vagy használha�uk a könyvhöz. tartozó forráskódfájlokat is. A könyvben használt példák forráskódja letölthető a h t tp: l lwww. w r ox. com címrőL Ha már ezen a címen járunk, keressük meg a könyvet (a Search doboz vagy az egyik címlista segítségéve!), majd kattintsunk a könyvet részletező oldal Download Code hivatkozására, és töltsük le a könyv összes forráskódját!

Mivel tó'bb, hasonló dmű könyv található az oldalon, keressünk az ISBN-szám segítsé­ géve�· az eredeti kó'f!Yv ISBN száma: 0-7645-9674-8 (a 200 7 januá1jában bevezetésre

kerülő 4), 13jegyű ISBN-számozás szerint ez a szám 978-0-7645-9674-2 lesi). A kód letöltése után tomöntőeszközünk segítségével csomagoljuk ki a kódot. A másik

lehetőség, ha a Wrox-kód letöltési oldalára, a h t tp: l lwww. w ro x. comldynami clbooksl download. aspx címre lépünk, és megkeressük a könyv és más Wrox könyvek kód jait .

.

Hibajegyzék

Mindent elkövettünk annak érdekében, hogy a könyv szövege és a kódok ne tartal­ mazzanak hibákat. De senki sem tökéletes, és hibák előfordulhatnak. A könyvben talált hibákkal �apcsolatos visszajelzésekért hálásak vagyunk. Hibajegyzékek bekül­ désével egy másik olvasó számára megtakaríthatunk többórányi bosszankodást, ugyanakkor segíthetünk, hogy a könyv még jobb információkat biztosítson.

A könyv hibajegyzékoldalát a h t tp: l lwww. w ro x. com címen találj uk, ha a Search doboz vagy az egyik címlista segítségével megkeressük a könyvet. A könyvet részle­ tező oldalon kattintsunk a Book Errata hivatkozásral Az oldalon megtaláljuk a

(21)

könyvvel kapcsolatban már bejelentett hibákat, amelyeket a Wrox szerkesztői küld­ tek el. A teljes könyvlista, amely az egyes könyvek hibajegyzékeit tartalmazza, a www . w ro x. com/mi sc-pages/bookl i st. shtml címen található.

Ha nem találjuk "saját'' hibánkat a hibajegyzékoldalon, a www. w r ox. com/ contact/ techsupport. shtml oldalon töltsük ki az űrlapot, és küldjük el a felfedezett hiba leírá­ sát. A Wrox szerkesztői ellenőrzik az információkat, és ha szükséges, a könyv hibajegy­ zékében üzenet jelenik meg a hibáról, a könyv következő kiadásaiban pedig kijavi�uk.

p2p.wrox.com

A szerzőkkel és az olvasókkal a p 2 p. wrox. com címen a P2P vitafórumokhoz csatlakozva beszélgethetünk A fórum olyan webes rendszer, amelyben a Wrox-könyvekre és azok­ kal kapcsólatos technológiákra vonatkozó üzeneteket küldhetünk; és a többi olvasóval, illetve a technológia felhasználójával folytathatunk beszélgetéseket. A fórumok előfize­ tési funkciót biztosítanak, amelynek segítségével a számunkra érdekes témakörökhöz va­ ló új hozzászólás érkezésekor e-mailben értesítést kapunk. A Wrox szerzői, szerkesztői, számítástechnikai szakértői és olvasói küldenek üzeneteket ezekbe a fórumokba.

A http: l l p 2 p. w r ox. com címen több fórumot is találunk, amelyek nemcsak a könyv olvasását, de saját alkalmazásaink fejlesztését is segítik. Ha szetetnénk csatla­ kozni a fórumokhoz, kövessük az alábbi lépéseket:

1. Lépjünk a p2p. wrox. com címre, és kattintsunk a Register hivatkozásral 2. Olvassuk el a felhasználás feltételeit, majd kattintsunk az Agree gombral 3. Töltsük ki a csatlakozáshoz szükséges és az egyéb információkat, amelyeket

szetetnénk megadni, majd kattiusunk a Submit gombral

4. Ezután e-mailben kapunk értesítést arról, hogyan tudjuk ellenőrizni a fió­ kunkat, és befejezni a csatlakozási folyamatot.

_/

A P2P-hez való csatlakozás nélkül is olvashatjuk a fórum üzeneteit, de ha saját üze­ netet szetetnénk küldeni, akkor csatlakoznunk kell.

Ha csatlakoztunk, új üzeneteket küldhetünk, illetve válaszolhatunk más felhasz­ nálók üzeneteire. Az üzeneteket bármikor elolvasha�uk az interneten. Ha adott fó­ rum új üzeneteit szetetnénk e-maiben megkapni, a fórumok listájában kattintsunk a fórum neve mellett a Subscribe to this Forum ikonra.

A Wrox P2P használatáról további információkat a P2P gyakran ismétlődő kér­ dések listáiban találunk, ahol a fórumszerver működésére, a P2P-re és a Wrox köny­ vekre vonatkozó kérdéseinkre is választ kaphatunk. A gyakran ismétlődő kérdéseket a GYIK-hivatkozásta kattintva bármely P2P oldalon elolvashatjuk.

(22)

ELSŐ FEJEZET

Az

alapok

Az algoritmusok világába induló utazás előkészületekkel és háttér-információkkal kezdődik. Tudnunk kell néhány dolgot, mielőtt a könyvben található algoritmusok­ kal és adatstruktúrákkal megismerkedhetnénk. Bizonyára ég bennünk a vágy, hogy mielőbb belemerüljünk, de ennek a fejezetnek az elolvasása a könyv többi részét te­ szi érthetőbb� mert olyan fontos alapfogalmakat tartalmaz, amelyek elengedhetetle­ nek a kódok és az algoritmusok elemzésének megértéséhez.

A fejezetben a következő témaköröket tárgyaljuk • mi az algoritmus,

• az algoritmusok szerepe a szaftverekben és mindennapi életünkben, • mit jelent az algoritmus bonyolultsága,

• az algoritmusbonyolultság széles osztályai, amelyek segítségével gyorsan meg­ különböztethe�ük ugyanannak a problémának a különböző megoldásait, • a "nagy O" j�lölés,

• mi az egységtesztelés, és miért fontos,

• hogyan kell a ]Unit segítségével egységteszteket írni.

Az

algoritmusok definiciója

Az talán már tudjuk, hogy az algoritmusok a számitástechnika fontos részét képezik, de egészen pontosan mik is azok? Mire használhatók? Kell egyáltalán törődnünk velük?

Az algoritmusok valójában nem korlátozódnak a számitástechnika világára; mindennapi életünkben is alkalmazunk algoritmusokat. Egyszerű meghatározással az

algoritmus

valarnilyen feladat végrehajtásához szükséges, jól meghatározott lépések sora. Ha tortát sütünk, és a recept utasításait köve�ük, tulajdonképpen egy algorit­ must használunk.

Az algoritmusok segítségével egy rendszert lehetőség szerint közbenső, átmeneti állapotok sorozatán keresztül adott állapotból egy másikba vihetünk. Az életből vett másik példa az egyszerű egész szorzás művelete. Noha általános iskolában mindany­ nyian bemagoltuk a szorzótáblákat, a szorzási folyamatot összeadások sorozatának is

(23)

tekinthe�ük. Például az 5 X 2 kifejezés a 2 + 2 + 2 + 2 + 2 (illetve az 5 +

5)

kifeje­ zés gyorsírásos változata. Vagyis bármely két egész szám, például A és B esetén el­ mondhatjuk, hogy az A X B annyit jelent, hogy B-t A-szor önmagához adjuk. Ezt az alábbi lépések soraként fejezhetjük ki:

1. Inicializáljunk egy harmadik egész számot, C-t O-ra.

2. Ha A nulla, akkor készen vagyunk, és az eredményt C tartalmazza. Ha nem ez a helyzet, haladjunk tovább a 3. lépéshez.

3. Adjuk B értékét C-hez.

4. Csökkentsük A értékét. 5. Lépjünk a 2. lépéshez.

Vegyük észre, hogy a torta receptjével ellentétben az összeadással szorzó algoritmus az 5. lépésben visszakanyarodik a 2. lépéshez. A legtöbb algoritmusban felfedezhe­ tünk valarnilyen ciklikusságot, amelynek a segítségével számításokat vagy egyéb mű­ veleteket ismétlünk Az iteráció! és a rekuqjót- a ciklusok két fő típusát - a követke­ ző fejezet részletesen ismerteti.

Az algoritmusokat gyakran pszeudokódként emlegetjük, amely még nem prog­ ramozó személyek számára is könnyen érthető, kitalált programozási nyelv. Az aláb­ bi kód a Mul ti pl y függvényt mutatja be, amely két egész szám- A és B- szorzatát, A X B-t adja vissza, és csak összeadást használ. A pszeudokód a két egész szám ösz­ szeadással való szorzásának műveletét muta�a be:

Function Multiply(Integer A, Integer B) Integer c = O

While A is greater than O C = C + B

A = A - l End

Return c End

A szorzás az algoritmusok nagyon egyszerű példája. A legtöbb alkalmazásban ennél jóval bonyolultabb algoritmusokkal találkozhatunk. A bonyolult algoritmusok megér­ tése nehezebb, és ezért azok nagyobb valószínűséggel tartalmaznak hibákat. (A számí­ tógép-tudomány nagy része tulajdonképpen azt próbálja igazolni, hogy bizonyos algo­ ritmusok helyesen működnek.)

Nem minden helyzetben alkalmazhatunk algoritmusokat. Előfordulhat, hogy adott problémát több algoritmussal is megoldhatunk Néhány megoldás egyszerű, mások bonyolultabbak, és egyik megoldás hatékonyabb lehet a többinéL Nem min-2

(24)

dig a legegyszerűbb megoldás a legnyilvánvalóbb. Bár a szigorú, tudományos elem­ zés mindig jó kiindulópont, gyakran találjuk magunkat az

elemzési paralí'{js

helyzeté­ ben. Néha a jól bevált régimódi kreativitásra van szükség. Próbáljunk ki több meg­ közelítést, és járjunk utána megérzéseinknek Vizsgáljuk meg, miért működnek bi­ zonyos esetekben az aktuális megoldási kísérletek, más esetekben pedig nem. Nem véletlen, hogy a szárrútógép-tudomány és a szoftvermérnökség egyik alapművének címe A

számítógép-programozás múvészete

(írta Donald E. Knuth). A könyvben ismerte­ tett algoritmusok nagy része

determinis'{!ikus-

azaz az algoritmus eredménye a beme­ netek alapján pontosan meghatározható. Előfordul azonban, hogy a probléma olyan bonyolult, hogy az idő és az erőforrások tekintetében a pontos megoldás megkere­ sése túlságosan nagy ráforditást igényel. Ilyen esetekben a

heurisztikus

megközelítés lehet a hasznosabb. A tökéletes megoldás keresése helyett a heurisztikus megközelí­ tés a probléma jól ismert tulajdonságai alapján állit elő egy közelítő megoldást. A he­ urisztikák segítségével kiválogatha�uk az adatokat, eltávolíthatunk vagy figyelmen kívül hagyhatunk lényegtelen értékeket, hogy az algoritmus számítási szempontból költségesebb részeinek kisebb adathalmazon kelljen múködniük.

A heurisztika egyik hétköznapi példája az utca egyik oldaláról a másikra való át­ kelés a világ különböző országaiban. Észak-Amerikában és Európa nagy részén a járművek az út jobboldalán haladnak. Ha életünk nagy részét eddig Észak-Ameriká­ ban töltöttük, akkor az úttesten való átkelés előtt kétségtelenül előbb balra, majd jobbra nézünk. Ha Ausztráliában balra néznénk, azt látnánk, hogy szabad az út, majd lelépnénk a járdáró� és nagy meglepetés érhetne bennünket, mert Ausztráliá­ ban az Egyesült Királysághoz, Japánhoz, illetve több más országhoz hasonlóan az út bal oldalán haladnak a járművek.

A járművek menetirányát országtól függetlenül igen könnyen megállapíthatjuk, ha egy pillantást vetünk a parkoló járművekre, és megfigyeljük, merre néznek. Ha az autók balról jobbra sorakoznak egymás után, akkor nagy valószínűséggel az úton va­ ló átkelés előtt előbb balra, majd jobbra kell figyelnünk Ha ellenben a parkoló autók jobbról balra sorakoznak, akkor először jobbra, aztán balra kell néznünk az átkelés előtt. Ez az egyszerű heurisztika az

esetek túltryomó részében

beválik. Sajnálatos módon azonban vannak helyzetek, amikor a heurisztika kudarcot vall: nem látunk parkoló autót, az autók összevissza parkolnak (ez Londonban elég gyakran megesik), vagy az autók az út bármelyik oldalán haladhatnak, mint Bangalore-ban.

Tehát a heurisztika használatának nagy hátulütője, hogy nem tudjuk minden helyzetben meghatározni, hogyan viselkedik - mint azt az előző példában láttuk. Ez az algoritmus bizonytalansági szin�éhez vezet, amely az alkalmazástól függően vagy elviselhető, vagy nem.

Végeredményben bármilyen problémát próbálunk megoldani, valamilyen algo­ ritmusra kétségtelenül szükségünk lesz; minél egyszerűbb, pontosabb és érthetőbb az algoritmus, annál könnyebben meghatározhatjuk, hogy megfelelően múködik-e, és a teljesítménye is elfogadható-e.

(25)

Az algoritmusok bonyolultsága

Miután megalkottuk, hogyan tudjuk meghatározni egy új, korszakalkotó algoritmus

hatékonyságát? Nyilvánvaló elvárás, hogy szetetnénk kódunkat a lehető leghatéko­

nyabbnak tudni, tehát be kell bizonyítanunk, hogy a hozzá fűzött reményeknek meg­

felelőerr működik. De pontosan mit értünk hatékonyság alatt? Processzoridőt, me­

móriafelhasználást, lemez bemenet-kimenetet? És hogyan mérhetjük az algoritmu­

sok hatékonyságát?

Az

algoritmusok hatékonyságának vizsgálata során a leggyakrabban elkövetett

hibák egyike, hogy a te!Jesítmétryt (a processzoridő/memória/lemezterület-foglalás

mennyiségét) összekeverik a bof!Jolu/tsággal (az algoritmus mérhetőségéveD. A tény,

hogy az algoritmus 30

milliszekundum alatt

1 OOO rekordot dolgoz fel, nem az algo­

ritmus hatékonyságának fokmérője. Noha igaz, hogy végeredményben az erőforrás­

fogyasztás is fontos, az olyan tényezőket, mint a processzoridő a kódon kivül a mö­

göttes hardver - amelyen a kód fut - hatékonysága és teljesítménye, valamint a gépi

kód generálásához használt fordító is erősen befolyásolja. Sokkal fontosabb annak

megállapítása, hogyan viselkedik az adott algoritmus a probléma méretének növeke­

déséveL Ha például a feldolgozni kivánt rekordok száma megkétszereződik, annak

milyen hatása van a feldolgozási időre? Eredeti példánkhoz visszatérve, ha egy algo­

ritmus

1000

rekordot

30

milliszekundum alatt dolgoz fel, rníg egy másik algoritmus

40

milliszekundum alatt, akkor az első algoritmust tekinthe�ük "jobbnak". Ha azon­

ban az első algoritmus 300

milliszekundum alatt 10 OOO

rekordot (tízszer annyit) dol­

goz fel, de a második algoritmus

80

milliszekundum alatt ugyanennyit, akkor választá­

sunkat felül kell vizsgálni.

Általánosságban elmondha�uk, hogy a bonyolultság az adott funkció végrehaj­

tásához szükséges meghatározott erőforrás-roennyiség fokmérője. Lehetséges - és

gyakran hasznos - a bonyolultságot lemez bemenet-kimenet, memóriafelhasználás

tekintetében mérni, de a könyvben a bonyolultság processzoridőre gyakorolt hatását

vizsgáljuk. A bonyolultság fogalmát tovább finomí�uk az adott funkció végrehajtá­

sához szükséges számítások vagy műveletek számának mértékére.

Érdekes módon a múveletek pontos számát rendszerint nem szükséges mér­

nünk. Sokkal fontosabb az, hogyan változik a végrehajtott múveletek száma a prob­

léma méretével. Mint az előző példában: ha a probléma mérete egy nagyságrenddel

nő, ez hogyan befolyásolja az egyszerű funkció végrehajtásához szükséges múveletek

számát? Ugyanannyi múveletre lesz szükség? Vagy kétszer annyira? A szám a prob­

léma méretével lineárisan nő? Vagy exponenciálisan? Ezt kell az algoritmusbonyo­

lultság alatt értenünk. Az algoritmus bonyolultságának mérésével a teljesítményét

próbáljuk megjósolni: a bonyolultság kihat a teljesítményre, de ez fordítva nem igaz.

(26)

A könyvben az algoritmusok és adatstruktúrák bemutatása során a bonyolultsá­ gukat is elemezni fogjuk. Az elemzések megértéséhez nem lesz szükség matematikai doktorátusra. Az egyszerű elméleti bonyolultságelemzést minden esetben könnyen követhető empirikus eredmények követik tesztesetek formájában, amelyeket rni ma­ gunk is kipróbálhatunk, és kísérletezhetünk a bemenet módosításával, hogy kitapasz­ talhassuk a szóban forgó algoritmus hatékonyságát. A legtöbb esetben az ádagos bonyolultság adott - a kód elvárt tipikus esetbeli futási sebessége. Számos esetben a legrosszabb esetbeli és a legjobb esethez tartozó idő is adott. Az, hogy a legjobb, a legrosszabb és az ádagos esetek közül melyik a meghatározó, részben az algoritmus­ tól függ, de a legtöbbször attól az adattípustól, amelyet az algoritmus használ.. Min­ denesetre fontos megjegyeznünk, hogy a bonyolultság nem az elvárt teljesítmény pontos mértékét biztosítja, hanem az elérhető teljesítményt szorítja bizonyos hatá­ rok vagy korlátok közé.

A nagy O

jelölés

Mint már korábban említettük, a műveletek pontos száma valójában nem fontos. Az algoritmus bonyolultságát a funkció végrehajtásához szükséges műveletek számának

nagJságrencfjével

definiálhatjuk a nagy o jelöléssei

- order of

(nagyságrend) - innen a nagy O. Az O mögötti kifejezés a probléma méretét jelölő N-hez képest a relatív nö­ vekedést jelenti. Az alábbi lista néhány gyakran alkalmazott nagyságrendet mutat be, a későbbiekben mindegyikre részletesen visszatérünk.

• 0(1): az "ordó l" konstans futási idejű függvényt jelent

• O(N): az "ordó N" lineáris futási idejű függvényt jelent

• O(N2): az "ordó N négyzet" kvadratikus futási idejű függvényt jelent

o(log N): az "ordó logaritmus N" logaritmikus futási idejű függvényt jelent • O(N log N): az "ordó N logaritmus N" a probléma méretével és a logarit­

mikus idővel arányos futási idejű függvényt jelent

• O(N! ): az "ordó N faktoriális" faktoriilis futási idejű függvényt jelent

Természetesen a fenti lista elemein kivül is van még néhány hasznos bonyolultság, de ezek elegendőek lesznek a könyvben bemutatott algoritmusok bonyolultságának leírására.

(27)

Az 1.1. ábrán látjuk, hogy a különböző bonyolultság-nagyságrendek hogyan vi­ szonyulnak egymáshoz. A vízszintes tengely a probléma méretét jelenti - például a keresési algoritmussal feldolgozandó rekordok számát. A függőleges . tengely az egyes osztályok algoritmusainak számitásigényét jelenti. Az ábra nem jelzi a futásidőt vagy a szükséges processzorciklusokat; pusztán annyit mutat, hogy a számitógépes erőforrásigény a megoldani kívánt probléma méretével együtt nő.

1. 1. ábra. A bof!Yolultság küliinbiizó nagyságremijeinek ilsszehasonlítás a

Az előző listában talán feltűnt, hogy a nagyságrendek egyike sem tartalmaz kons­ tanst. Azaz, ha az algoritmus várt futásidejű teljesítménye az N, 2xN, 3xN vagy akár lOOxN értékekkel arányban áll, a bonyolultság minden esetben o (N). Első pillantásra kicsit furcsának tűnhet- természetes, hogy a 2xN jobb, mint a lOOxN - , de mint már korábban említettük, nem az a célunk, hogy megállapítsuk a műveletek pontos szá­ mát, hanem hogy a különböző algoritmusok relatív hatékonyságát összehasonlítsuk Más szóval az O(N) idő alatt befejezett algoritmus túlszárnyal egy másik, O(N2) ideig futó algoritmust. Továbbá, ha N nagy értékeivel akad dolgunk, a kanstansok nem sokat változtatnak a helyzeten: a teljes méret arányát tekintve az 1 OOO OOO OOO és a 20 OOO OOO OOO közötti különbség majdnem elhanyagolható, még akkor is, ha az egyik a másiknak a hússzorosa.

Természetesen szeretnénk összehasonlítani a különböző algoritmusok tényleges teljesítményét, különösen akkor, ha az egyik 20 perc alatt befejeződik, mig a másik csak 3 óra alatt, és mindkét algoritmus nagyságrendje O(N). Azt kell megjegyezünk, hogy sokkal könnyebb megfelezill egy O(N) bonyolultságú algoritmus idejét, mint módosítani egy olyan algoritmust, amely az O(N) nagyságrendhez képest O(N2) nagy­ ságrenddel bír.

(28)

Konstans idő:

O( 1)

Megbocsátható az a feltételezés, hogy az

0(1)

bonyolultság azt jelenti, hogy az algo­

ritmus egyeden művelet segítségével végrehajtja a funkciót. Noha ez valóban lehet­

séges, az

0(1)

tulajdonképpen valójában annyit jelent, hogy az algoritmus konstans

ideig fut; vagyis a teljesítményt nem befolyásolja a probléma mérete. Valószínűleg

nem tévedünk, ha úgy véljük, ez

túl

szép ahhoz, hogy igaz legyen.

Az egyszerű funkciók futása garantáltan

0(1)

ideig tart. A konstans időbeli telje­

sítmény legegyszerűbb példája a számítógép operatív memóriáját címezi, és kiterjesz­

tésként tömbbeli keresést hajt végre. A tömb egy elemének keresése a mérettől füg­

gedenill általában ugyanannyi ideig tart.

Bonyolultabb problémák esetén azonban nagyon nehéz konstans ideig futó al­

goritmust találni: a "Listák" című fejezet

(3.)

és a "Hasítás" című fejezet

(11.)

beve­

zeti az

0(1)

időbeli bonyolultsággal rendelkező adatstruktúrákat és algoritmusokat.

A konstans időbeli bonyolultsággal kapcsolatban még azt kell megjegyeznünk, hogy

ez még mindig nem garantálja az algoritmus gyorsaságát, csak azt, hogy a végrehajtásá­

hoz szükséges idő mindig ugyanannyi lesz: az algoritmus, amely egy hónapig fut, még

mindig 0(1)

algoritmus, még akkor is, ha ez a futásidő teljességgel elfogadhatatlan.

Lineáris idő:

O(N)

Az algoritmus akkor fut

O(N)

nagyságrenddel, ha a funkció végrehajtásához szüksé­

ges műveletek száma egyenesen arányos a feldolgozni kívánt elemek számával. Az

1.1.

ábrára pillantva látha�uk, hogy az

O(N)

vonala felfelé folytatódik, a meredeksége

változadan.

ilyen algoritmus például az áruházi pénztárnál való várakozás. A vásárlókat átlago­

san ugyanannyi idő alatt lehet kiszolgálni: ha egy vásárló kosarát két perc alatt fel lehet

dolgozni, körülbelül

2x10 = 20

perc kell tíz vásárló kiszolgálásához, és

2x40 = 80

perc

40

vásárlóhoz. A lényeg, hogy nem fontos, hány vásárló

áll

a sorban, az egyes vá­

sárlók kiszolgálásához szükséges idő nagyjából ugyanannyi marad. Elmondha�uk,

hogy a kiszolgálás ideje egyenesen arányos a vásárlók számával, tehát az idő

O(N).

Érdekes módon, ha bármikor megkétszerezzük vagy akár megháromszorozzuk

a műveletben a regiszterek számát, a feldolgozási idő továbbra is

O(N)

marad. Ne fe­

ledjük, hogy a nagy

O

jelölés mindig minden konstans t figyelmen kívül hagy.

Az O(N)

futási idejű algoritmusok rendszerint elfogadhatóak Legalább olyan ha­

tékonynak tekinthetők, mint az

0(1)

futásidejű algoritmusok, de ahogy már említet­

tük, igen nehéz konstans idejű algoritmust találni. Ha sikerül lineáris idővel futó al­

goritmust találnunk, mint azt a "Sztringkeresés" című fejezetben

(16.)

látni fogjuk,

kis elemzéssel- és zseniális ötletekkel- még hatékonyabbá tehetjük.

(29)

Kvadratikus idő:

O(N2)

Képzeljünk el egy csoportot, amelynek tagjai most találkoznak egymással először, és az illemszabályoknak megfelelően mindannyian kézfogással üdvözlik a csoport ösz­ szes többi tagját. Ha a csoportban hatan vannak, akkor ez az

1.2.

ábrán látható mó­ don összesen 5+4+3+2+1 = 15 kézfogást jelent.

1.2. ábra. A

csoport minden tagja iidvo'ifi a csoport osszes to'bbi tagját

Mi történne, ha a csoport hét főből állna? Az üdvözlés összesen 6+5+4+3+2+1 = 21 kézfogásba kerülne. És ha nyolcból? Ez 7+6+ ... +2+1 = 28 kézfogást jelentene. És, ha kilencen lennének a csoportban? Már nagyjából láthatjuk a lényeget: Ahányszor a csoport mérete egy fővel növekszik, egy további embernek kell kezet ráznia az ösz­ szes többivel.

Az N méretű csoportban a kézfogások száma (NLN) /2 lesz. Mivel a nagy O minden konstanst figyelmen kívül hagy - ebben az esetben a 2-t -, a kézfogások száma NLN. Mint azt az

1.1.

táblázatban látjuk, ahogy N egyre nő, N kivonása NLből a végeredményre egyre elhanyagolhatóbb hatással van, tehát bátran elhagyhatjuk a ki­ vonást, ezáltal az algoritmus bonyolultsága O(N2) lesz.

Különbség

1

1

o

100,00%

10

100

90

10,00%

100

10

OOO

9 900

1,00%

(30)

Í

N N 2 N2-N

Kutönbség

1 OOO 1 OOO OOO 999 OOO 0,10%

10 OOO 100 OOO OOO 99 990 OOO 0,01%

1.1. táblázat. Az N kivonása N-ból az N növekedése me/lett

A kvadratikus idővel futó algoritmusok a programozók legvadabb rémálmaiban je­ lennek meg; bármely O(N2) bonyolultságú algoritmus kizárólag a legjelentéktelenebb problémák megoldására alkalmas. A keresést tárgyaló 6. és 7. fejezetben további ér­ dekes példákat találunk.

Logaritmikus idő: O(log N) és O(N log N)

Az 1.1. ábrán látható, hogy az O(l og N) jobb, mint az O(N), de nem olyan jó, mint az 0(1).

A logaritmikus algoritmus futásideje a probléma méretének - rendszerint 2-es alapú - logaritmusával együtt növekszik. Ez annyit jelent, hogy ha a beviteli adat­ halmaz mérete milliós szorzóval növekszik, a futásidő csak log (1000000) = 20 szorzóval növekszik. Az egész számok 2-es alapú logaritmusát egyszerűen kiszárnit­ hatjuk, ha megkeressük, hogy a szám tárolásához hány bináris számjegy szükséges. Például, a 300 2-es ala pú logari tm usa 9, mivel a decimális 300 megj elemtéséhez 9 bi­ náris számjegy szükséges (a bináris ábrázolás 100101100).

A logaritmikus futásidők megvalósításához az algoritmusnak a beviteli adathalmaz · nagy részét rendszerint figyelmen kívül kell hagynia. Ennek eredményeként a legtöbb algoritmus - amely így viselkedik - valamilyen keresést foglal magában. A "Bináris ke­ resés és beszúrás" cím ű fejezet (9.), és a "Bináris keresőfák" című fejezet (10.) o(l og N) algoritmusokat mutat be.

Ha ismét megtekin�ük az 1.1. ábrát, látha�uk, hogy az O(N log N) jobb, mint az O(N2), de nem olyan jó, mint az O(N). A 6. és a 7. fejezetben O(N log N) algorit­ musokkal találkozhatunk.

Faktoriális idő: O(N!)

Lehet, hogy nem gondolnánk, de néhány algoritmus még az O(N2)-nél is rosszabbul tel­ jesít-az 1.1. ábrán hasonlítsuk össze az O(N2) és O(N!) bonyolultságokat. (Valójában

(31)

Elég ritkán találkozhatunk ilyen funkciókkal, különösen, ha olyan példákat kere­ sünk, amelyek nem a kódolásról szólnak. Ha tehát elfelejtettük volna, hogy mi a fak­ toriilis - vagy még nem is találkoztunk vele soha -, íme egy gyors ismédés:

A faktoriilis az egész szám és az azt megelőző természetes számok szorzata. Például a 6! ("6 faktoriális'') = 6x5x4x3x2xl = 720 és a 10! = 10x9x8x7x6x5x 4x3x2xl= 3628800.

Az 1.2. táblázat az N2 és az N ! összehasonlítását tartalmazza 1 és 1 O közötti egész számokra. N N z Nl 1 1 1 2 4 2 3 9 6 4 16 24 5 25 120 6 36 720 7 49 5 040 8 64 40 320 9 81 362 880 10 100 3 628 800

1.2. táblázat. Az N2 és az N! összehasonlítása kis egész számok esetén

Mint lá�uk, ha az N értéke N=2 vagy annál kisebb, a faktoriális bonyolultság jobb, mint a kvadratikus, de ezen a ponton a faktoriilis bonyolultság nekilendül, és katasztrófával fenyeget. Következésképpen az O(N2) bonyolultsághoz képest még inkább remény­ kednünk kell abban, hogy algoritmusunk bonyolultsága ne O(N!) legyen.

Egységtesztelés

Mielőtt folytatnánk utazásunkat az algoritmusok világában, kicsit elkalandozva beszél­ nünk kell egy szívünknek igen kedves témáról: az egységtesztelésrőL A2 elmúlt évek­ ben az egységtesztelés nagyon népszerűvé vált azoknak a fejlesztáknek a körében, akiknek fontos az általuk készített rendszerek minősége. Közülük sokan kellemedenill érzik magukat, ha a szoftver készítése közben nem építenek egy automatikus teszteket

(32)

magában foglaló csomagot is, amely bizonyítja, hogy az elkészült szoftver

az

elvárá­

soknak megfelelően műköclik.

Mi

is ezt a hozzáállást képviseljük Ezért minden ismer­

tetett algoritmus esetén bemuta�uk, hogyan működik az adott algoritmus, és egység­

tesztek révén azt is, hogy mit csinál. Mindenkinek ajánljuk, hogy fejlesztési munkája

során váljon ez a szokásává, mivel nagyban segíti a túlórázás elkerülését.

A következő néhány részben gyors áttekintést kapunk az egységtesztelésrő� és bete­

kintést nyerünk a ]Unit-keretrendszerbe Java-program egységteszteléséhez. A könyvben

a ]Unit-rendszert alkalmazzuk, tehát érdemes megismerkednünk ezzel a programm�

hogy könnyebben megértsük a könyv példáit. Ha már kemény, teszteléssei fertőzött fej­

lesztőnek érezzük magunkat, a könyv e részét nyugodtan átlapozha�uk. Örüljünk neki!

Mi az egységtesztelés?

Az egységteszt olyan program, amely egy másikat tesztel. Java-környezetben ez egy

Java-osztályt jelent, amelynek a célja a többi osztály tesztelése. Nagyjából ennyi.

Mint az életben a legtöbb dolo

g

, ez is könnyen megtanulható, de sokat kell gyako­

rolni. Az egységtesztelés művészet és egyben tudomány is; rengeteg irodalmat talá­

lunk a tesztelésről, tehát itt nem merülünk el a tesztelés részleteiben. Az A függelék

több könyvet is ajánl a témával kapcsolatban.

Az egységteszt alapvető működése az alábbiakat foglalja magában.

1.

A teszt támogatásához szükséges objektumok, mint a példaadatok előkészí­

tése. Ezek az úgynevezett

tartozékok.

2. Az

objektumok használatával futtassuk a tesztet, és győződjünk meg róla,

hogy valóban az történt, amire számítottunk. Ezek a folyamatok a

vizsgálatok. 3.

Végül szabaduljunk meg minden felesleges dologtól. Ez a

lebontás

folyamata.

A könyvben az egységtesztek elnevezésénél minden esetben úgy járunk el, hogy az

osztálynévvel létrehozott tesztosztály nevéhez hozzáfűzzük a

Teszt

szót. Ha például

a

Mütyür

névre hallgató osztályt készülünk tesztelni, az osztály egységtesz*ként lét­

rehozott osztály neve

MütyürTeszt

lesz. Erre rengeteg példát találunk majd a

könyvben. A forrásfájlok elrendezése is egységes szabály szerint történik.

Az

egység­

teszteket a fő forrásfájlok csomagstruktúrájával megegyező párhuzamos forrásfán

helyezzük el. Ha például a Mütyür a

com·. wrox. algorithms

osztályon belül létezik, a

forrásfájlok elrendezése az

1.3.

ábrán látható elrendezéshez lesz hasonlatos.

References

Related documents

This thesis work has fulfilled the three objectives by using SF physical entrapment to evaluate the influence of sonication and macromer incorporation on SF physical gelation;

“Beyond ‘Fraternal Rivalry’ : Social Cleavages and Party Preferences in Ukraine,” Paper Presented on the Panel “Political Development in Eastern Europe” at the 79 th

In this review, the research carried out using various ion-exchange resin-like adsorbents including modified clays, lignocellulosic biomasses, chitosan and its derivatives, microbial

Where supply-side reforms might help is in making some types of flow less unstable: for example, the Buiter/Sibert proposal for a Universal Debt Rollover Option (which needs

While in Table 3 we present a pooled specification, to increase the chances for the added variables to exert a significant impact, in unreported regressions we repeat the

indicative parameter of stress by physical restraint per- formed at the time of the measurements, however, cor- relating the moments of highest plasma cortisol values obtained with

Hannah Arendt. “Peace or Armistice in the Near East” in The Jewish Writings.. then, that both alternatives represent an escape from reality, and a retreat from the fight