Bevezetés az algoritmikába [PDF]

  • 0 0 0
  • Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen! Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau

Bevezetés az algoritmikába Egyetemi jegyzet

Ionescu Klára [email protected]

Kolozsvár, 2007

Bevezetés az algoritmikába

Műszaki szerkesztő: Ionescu Klára és Buzogány László Formátuma: B5 vágott Nyomtatta és kötötte: Egyetemi Könyvkiadó Kolozsvár Szaklektorok: dr. Kása Zoltán, Buzogány László és Takács Andrea

Tartalomjegyzék Bevezetés............................................................................................................9 1. A SZÁMÍTÓGÉPES FELADATMEGOLDÁS LÉPÉSEI...........................13 1.1.

A programozói tevékenység...............................................................13

1.2.

A feladatmegoldás lépései számítógépes környezetben.....................14

1.3.

Alkalmazások minőségi szempontjai.................................................15

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA....................................................17 2.1.

Algoritmusok.....................................................................................17

2.1.1. 2.1.2.

Az algoritmus fogalma....................................................................................................17 Az algoritmusok leírásánál használt elemek...................................................................19

2.2. Algoritmusok ábrázolása folyamatábrák és pszeudokód nyelvek segítségével..........................................................................................23 2.3.

A strukturált programozás alapelvei...................................................26

2.3.1. 2.3.2. 2.3.3. 2.3.4. 2.3.5.

2.4.

A feladatok számítógépes megoldásához fűződő általános kérdések. 35

2.4.1. 2.4.2. 2.4.3. 2.4.4. 2.4.5. 2.4.6.

2.5.

Algoritmusok helyessége................................................................................................36 Az algoritmus végrehajtásához szükséges idő................................................................39 Az algoritmus által feldolgozott adatok számára szükséges memória mérete...............43 Algoritmusok egyszerűsége............................................................................................43 Algoritmusok optimalitása..............................................................................................43 Algoritmusok létezése.....................................................................................................44

Megoldott feladatok...........................................................................45

2.5.1. 2.5.2. 2.5.3.

2.6.

Lineáris struktúrák...........................................................................................................27 Elágazási struktúrák........................................................................................................27 Ismétlő struktúrák............................................................................................................28 Az alapstruktúrák jelölése pszeudokódban.....................................................................32 Egyszerű alapszabályok..................................................................................................35

Felcserélés.......................................................................................................................45 Maximumérték................................................................................................................45 Elnökválasztás.................................................................................................................47

Kitűzött feladatok..............................................................................49

3. LÉPÉSEK FINOMÍTÁSA...........................................................................51 3.1.

Bevezetés...........................................................................................51

3.2.

Megoldott feladatok...........................................................................51

3.2.1. 3.2.2. 3.2.3. 3.2.4. 3.2.5. 3.2.6. 3.2.7.

Eukleidész algoritmusa...................................................................................................51 Prímszámok.....................................................................................................................53 Fibonacci-számok............................................................................................................59 Háromszög.......................................................................................................................62 Fordított szám..................................................................................................................63 Törzstényezők.................................................................................................................65 Konverzió........................................................................................................................66

6

TARTALOMJEGYZÉK 3.2.8.

3.3.

Gyors hatványozás..........................................................................................................69

Kitűzött feladatok..............................................................................69

4. PROGRAMOZÁSI TÉTELEK....................................................................73 4.1.

Bevezetés...........................................................................................73

4.2.

Egyszerű programozási tételek..........................................................73

4.2.1. 4.2.2. 4.2.3. 4.2.4. 4.2.5. 4.2.6. 4.2.7.

4.3.

Összetett programozási tételek...........................................................87

4.3.1. 4.3.2. 4.3.3. 4.3.4. 4.3.5.

4.4.

Szétválogatás...................................................................................................................87 Sorozat halmazzá alakítása.............................................................................................90 Keresztmetszet................................................................................................................92 Egyesítés..........................................................................................................................93 Összefésülés....................................................................................................................94

Megoldott feladatok...........................................................................97

4.4.1. 4.4.2. 4.4.3. 4.4.4. 4.4.5. 4.4.6. 4.4.7. 4.4.8. 4.4.9. 4.4.10.

4.5.

Összeg és szorzat.............................................................................................................73 Döntés..............................................................................................................................75 Kiválasztás.......................................................................................................................78 Szekvenciális (lineáris) keresés......................................................................................79 Megszámlálás..................................................................................................................82 Minimum- és maximumkiválasztás................................................................................83 Kiválogatás......................................................................................................................85

Összeg..............................................................................................................................97 Párok................................................................................................................................98 Arány...............................................................................................................................98 Hőmérséklet.....................................................................................................................98 Teljes négyzet..................................................................................................................99 Négyzetgyök..................................................................................................................100 Statisztika......................................................................................................................101 Osztályfőnök.................................................................................................................102 Bűvös négyzet...............................................................................................................103 Maximális összegű leghosszabb részsorozat................................................................104

Kitűzött feladatok............................................................................107

5. ALPROGRAMOK.....................................................................................111 5.1.

Bevezetés.........................................................................................111

5.2.

Algoritmusok és programok fejlesztési módozatai...........................113

5.2.1. 5.2.2. 5.2.3. 5.2.4.

5.3.

A moduláris programozás alapszabályai..........................................117

5.3.1. 5.3.2. 5.3.3. 5.3.4.

5.4.

Moduláris dekompozíció...............................................................................................117 Moduláris kompozíció..................................................................................................118 Modulok tulajdonságai..................................................................................................118 A modularitás alapelvei.................................................................................................118

Algoritmusok helyességének ellenőrzése.........................................119

5.4.1. 5.4.2.

5.5.

A top-down típusú (fentről lefele) programozás..........................................................113 A bottom-up (lentről felfele) programozás...................................................................113 Moduláris algoritmustervezés.......................................................................................114 Megoldott feladat..........................................................................................................114

A fekete doboz módszere..............................................................................................119 Az átlátszó doboz módszere..........................................................................................119

Megoldott feladatok.........................................................................120

TARTALOMJEGYZÉK 5.5.1. 5.5.2. 5.5.3. 5.5.4.

5.6.

7

Polinom értéke adott pontban.......................................................................................120 Polinomok összege........................................................................................................121 Polinomok szorzata.......................................................................................................121 Determináns..................................................................................................................122

Kitűzött feladatok............................................................................126

6. RENDEZÉSI ALGORITMUSOK.............................................................131 6.1.

Bevezetés.........................................................................................131

6.2.

Összehasonlításos rendezési módszerek...........................................131

6.2.1. 6.2.2. 6.2.3. 6.2.4. 6.2.5.

6.3.

Buborékrendezés (Bubble-sort)....................................................................................131 Egyszerű felcseréléses rendezés...................................................................................134 Válogatásos rendezés....................................................................................................135 Minimum/maximum kiválasztásra épülő rendezés......................................................136 Beszúró rendezés...........................................................................................................136

Rendezések lineáris időben..............................................................137

6.3.1. 6.3.2.

Leszámláló rendezés (ládarendezés, Binsort)...............................................................138 Számjegyes rendezés (Radix-sort)................................................................................138

7. REKURZIÓ...............................................................................................141 7.1.

Bevezetés.........................................................................................141

7.2.

Közvetlen rekurzió...........................................................................142

7.3.

Megoldott feladatok.........................................................................145

7.3.1. 7.3.2. 7.3.3. 7.3.4. 7.3.5. 7.3.6. 7.3.7. 7.3.8. 7.3.9.

7.4.

Rekurzív szerkezetű feladatok.........................................................155

7.4.1. 7.4.2. 7.4.3. 7.4.4. 7.4.5.

7.5.

Egy szó betűinek megfordítása.....................................................................................145 Szavak sorrendjének megfordítása...............................................................................147 Faktoriális......................................................................................................................147 Legnagyobb közös osztó...............................................................................................148 Számjegyösszeg............................................................................................................148 Descartes-szorzat...........................................................................................................149 k elemű részhalmazok...................................................................................................151 Konverzió......................................................................................................................153 Fibonacci-sorozat (Vigyázat: ellenpélda!)....................................................................153 A {0, 1, 2} halmaz elemeivel generált sorozatok.........................................................155 Az {1, 2, ..., n} halmaz minden részhalmaza................................................................156 Partíciók.........................................................................................................................156 Halmazpartíciók............................................................................................................157 Kamatos kamat..............................................................................................................159

Kitűzött feladatok............................................................................160

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)......161 8.1.

Bevezetés.........................................................................................161

8.2.

A visszalépéses keresés általános bemutatása..................................161

8.2.1. 8.2.2. 8.2.3. 8.2.4. 8.2.5.

Iteratív algoritmus.........................................................................................................162 Rekurzív algoritmus......................................................................................................163 8 királynő a sakktáblán..................................................................................................165 Variációk.......................................................................................................................168 Zárójelek........................................................................................................................171

8

TARTALOMJEGYZÉK 8.2.6. 8.2.7. 8.2.8.

8.3.

A visszalépéses keresés bővítése......................................................175

8.3.1. 8.3.2.

8.4.

S pénzösszeg kifizetése.................................................................................................177 Összegkifizetés, minimum számú bankjeggyel............................................................179

Visszalépéses keresés a síkban.........................................................180

8.4.1. 8.4.2. 8.4.3.

8.5.

Legrövidebb utak...........................................................................................................172 Játékok...........................................................................................................................173 Szürjektív függvények..................................................................................................174

Labirintus.......................................................................................................................180 Fénykép.........................................................................................................................183 Legnagyobb méretű tárgyak..........................................................................................184

Kitűzött feladatok............................................................................186

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)...191 9.1.

Bevezetés.........................................................................................191

9.2.

Az oszd meg és uralkodj módszer általános bemutatása..................191

9.3.

Megoldott feladatok.........................................................................192

9.3.1. 9.3.2. 9.3.3. 9.3.4. 9.3.5. 9.3.6. 9.3.7. 9.3.8.

9.4.

Szorzat...........................................................................................................................192 Minimumszámolás........................................................................................................193 Hatványozás..................................................................................................................194 Bináris keresés...............................................................................................................195 Összefésülésen alapuló rendezés (MergeSort).............................................................200 Gyorsrendezés (QuickSort)...........................................................................................201 Hanoi tornyok................................................................................................................205 Úszómedence................................................................................................................207

Kitűzött feladatok............................................................................210

10.MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)...............................213 10.1. Bevezetés.........................................................................................213 10.2. A mohó algoritmus általános bemutatása.........................................214 10.3. Megoldott feladatok.........................................................................216 10.3.1. 10.3.2. 10.3.3. 10.3.4. 10.3.5. 10.3.6. 10.3.7.

Összeg...........................................................................................................................216 Az átlagos várakozási idő minimalizálása....................................................................216 Buszmegállók................................................................................................................220 Autó bérbeadása............................................................................................................221 Hátizsák.........................................................................................................................224 Minimális feszítőfák (Kruskal és Prim)........................................................................226 Minimális hosszúságú utak (Dijkstra algoritmusa)......................................................230

10.4. Heurisztikus mohó algoritmusok.....................................................236 10.4.1. Utazóügynök.................................................................................................................236 10.4.2. Gráfszínezés..................................................................................................................239 10.4.3. Összegkifizetés legkevesebb számú bankjeggyel.........................................................240

10.5. Kitűzött feladatok............................................................................241 Algoritmusok listája........................................................................................249 Szakirodalom..................................................................................................251

Bevezetés A számítógép megjelenése, de főleg erőteljes behatolása a gazdaságba, majd­ nem minden munkahelyre és az emberek személyes életébe ma már azt jelenti, hogy a számítógéppel megoldható feladatok és alkalmazások egyre változato­ sabbakká, egyre összetettebbekké és elengedhetetlenül szükségesekké váltak. Amikor egy számítógépet használunk, nemcsak a gép „működik”, hanem azok a programok is, amelyek biztosítják, ellenőrzik és összehangolják a számítógép működését, és lehetővé teszik azoknak a programoknak a végrehajtását, ame­ lyek közvetlenül fontosak az alkalmazásaink szempontjából. Ebből következik, hogy a távoli jövőben is egészen biztosan egyre több programra lesz szükség a világban. Ez jól hangzik a kezdő programozó számára, de számolnunk kell egy sor sajátos következménnyel. Már ma sem lehet csak úgy „összedobálni” egy programot, de a jövőben még több szabály, módszer és megszorítás várható, amelyek a csoportmunkát, olvasható programok írását és a könnyű frissítéseket hivatottak biztosítani. A programokat, általában egy megrendelő számára írja a programozó cég, tehát az első legfontosabb cél a megrendelő igényeit kielégíteni. De ahhoz, hogy egy cégnek ez eredményesen sikerüljön, megfelelő tervezési és megvalósítási stratégiát kell követnie. Egy alkalmazás kivitelezéséhez nem elég a programozási környezet isme­ rete. Ennél fontosabb elsajátítani a feladatok elemzési módszereit, megtanulni kiválogatni a legelőnyösebb eszközöket (programozási nyelvet, illetve környeze­ tet, a környezet könyvtáraiban létező alprogramokat, korábban megírt program­ részleteket, saját unitokat stb.), megtalálni a leghatékonyabb algoritmusokat és eredményesen kódolni ezeket. Ennek a jegyzetnek az egyik célja felkészíteni a kezdő programozót arra, hogy a fent felsorolt követelményeknek eleget tehessen. Látni fogjuk, hogy ezek között a legigényesebb tevékenység az algoritmus megtervezése és annak belátása, hogy az valóban helyes és megfelelően hatékony. Ehhez elsősorban gondolkodnunk kell, de a gyakorlat is rendkívül fontos. A jegyzet tehát az algoritmusokkal foglakozik. A hallgató implementálhatja ezeket valamely, már eddig megismert programozási környezetben, illetve, en­ nek hiányában, ajánljuk a Pascal nyelv elsajátítását. (A jegyzet nem tartalmazza egyetlen programozási nyelv szintaxisát sem.) A kitűzött feladatok megoldása

10

BEVEZETÉS

az algoritmus megtervezését és a megfelelő program megvalósítását jelenti. Ezért a jegyzetbe programozásra vonatkozó sajátos tanácsok is bekerültek. Egy adott feladat megoldására lehetséges több, különböző programot írni: valójában k programozó k különböző megoldást fog adni, de ezeknek az alapja sok esetben ugyanaz az algoritmus. De az algoritmusok is különbözhetnek ugyanazon feladat megoldásakor. Ezek lehetnek „egyenértékűek”, vagyis azo­ nos hatékonyságúak, de lehetnek különbözők a hatékonyság szempontjából is. Ilyenkor mondjuk, hogy egyik algoritmus kisebb bonyolultságú mint a másik. A bonyolultság fogalmának tárgyalása nem célja a jegyzetnek, de a feladatok elemzése és megoldása során elkerülhetetlen a bonyolultság megállapítása, fő­ leg akkor, amikor két vagy több algoritmust össze fogunk hasonlítani. Bemuta­ tásra kerül néhány alapvető kérdés az algoritmusok létezésének, helyességének, futási idejének és optimalitásának bizonyítását illetően. Az algoritmusokat a tervezés során nem szoktuk azonnal egy adott progra­ mozási nyelvben írni, hanem valamilyen ábrázolási módszert választunk. E módszerek közül egyesek grafikai eszközöket használnak (például a folyamat­ ábrák), mások leírják vázlatosan az algoritmust (pszeudokód nyelvek). Ezen áb­ rázolásmódokban egyszerűsítéseket és a programozási nyelveknél kevésbé szi­ gorú szabályokat alkalmazunk. A jegyzetben az algoritmusokat egy egyszerű pszeudokód nyelvben írjuk le, amelynek szintaxisát a 2. fejezetben adjuk meg. A programfejlesztés egyik módja, amelyet ebben a jegyzetben alkalmazunk a strukturált programozás alapszabályaihoz igazodik. A strukturált programozás elemeit, valamint a moduláris programozás előnyeit szintén a 2. fejezetben tár­ gyaljuk. A fejezet végén megoldott feladatok egyszerűek, a kezdőknek sem okoznak gondot, a kitűzött feladatok pedig alkalmat adnak a gyakorlásra. Algoritmustervezéskor először egy vázlatot készítünk, ezt ellenőrizzük, kiegé­ szítjük, módosítjuk, többször javítgatjuk. Az algoritmust kódolás után is tovább finomítjuk. A lépésenkénti finomítás elvével a 3. fejezetben ismerkedünk meg. Ahhoz, hogy valóban hasznos tagjai lehessünk egy programozó csoportnak, nem elég, hogy értsünk az algoritmusok tervezéséhez és elemzéséhez. Olyan al­ goritmusokat kell megvalósítanunk, amelyek könnyen olvashatók és amelyek bizonyos mintákhoz (szabályokhoz) igazodnak. Ezeket a mintákat „programo­ zási tételek” elnevezés alatt a 4. fejezetben mutatjuk be. A programozási tételek egyszerű feladatok hatékony algoritmusai, amelyek részalgoritmusok formájá­ ban felhasználhatók későbbi feladataink megoldásaiban. Az 5. fejezetben tulajdonképpen már a programozási gyakorlatra készülünk. Megismerkedünk a feladatok részfeladatokra bontásának következményeként megvalósítandó olyan algoritmusokkal, amelyeket alprogramokkal implementá­

11

BEVEZETÉS

lunk és ezeknek az összefűzési módjaival (top-down és bottom-up programozási stílus). A 6. fejezet a legfontosabb rendezési algoritmusokat tárgyalja: az összeha­ sonlításos rendezési módszerek közül a buborékrendezést, az egyszerű felcseré­ léses rendezést, a válogatásos rendezést, a minimum/maximumkiválasztásra épülő rendezést és a beszúró rendezést fogjuk megismerni. A lineáris rendezé­ sek közül bemutatásra kerül a leszámláló rendezés és a számjegyes rendezés. Miután a 7. fejezetben megismerkedünk a rekurzió fogalmával, a 8. fejezet ­ ben elsajátítjuk a visszalépéses keresés (backtracking) technikáját, a 9. fejezet­ ben az oszd meg és uralkodj módszert (divide et impera) tanulmányozzuk, a 10.-ben pedig a mohó algoritmusokat (greedy). A fejezetek végén több megoldott feladatot mutatunk be, és más feladatokat pedig megoldásra ajánlunk. Az algoritmusok tanulmányozása nem ér véget ezzel a jegyzettel. A későbbi­ ekben az adatszerkezetek tanulmányozása vár ránk. Ezeket az adatszerkezeteket sajátos algoritmusokkal dolgozzuk fel. Algoritmusok tervezése és elemzése cím­ mel újabb előadássorozat keretén belül más algoritmusokkal és módszerekkel (például a dinamikus programozás módszerével, a Branch and Bound-dal, Di­ richlet skatulya-elvével, véletlenszám generátorra épülő nemdeterminisztikus al­ goritmusokkal stb.) fogunk találkozni. Ugyanott újabb elemzési módszereket és az algoritmusok helyességének bizonyításait fogjuk elsajátítani. Nem állítom, hogy minden csak úgy igaz, ahogy ebben a jegyzetben le van írva. Várom az olvasók észrevételeit, és előre köszönöm a javaslatokat. Kívánok sikeres tanulást és sok örömöt az algoritmusok szépségeinek felfe­ dezése során! *** Köszönöm Kása Zoltán és Horia Georgescu tanár urak sok éve tartó meg­ tisztelő barátságát, szakmai tanácsaikat, Buzogány Lászlónak és Kása Zoltánnak a figyelmes szaklektorálást. Köszönöm Takács Andreának a második kiadás előtt végzett pontos és igé­ nyes ellenőrző munkáját.

Kolozsvár, 2007. augusztus 28.

I. K.

12

BEVEZETÉS

1

A SZÁMÍTÓGÉPES FELADATMEGOLDÁS LÉPÉSEI

1.1. A programozói tevékenység A számítógép egy feladatot akkor képes megoldani, ha végrehajtjuk rajta azt a programot, amelyet abból a célból írtunk, hogy a feladatot megoldja. A felhasz­ náló a programon keresztül irányítja a számítógép által elvégzendő lépéseket és biztosítja a feladat automatikus módon való megoldását. Tudvalevő, hogy egy alkalmazás nem egyetlen program megírását jelenti, hanem több személy, többhetes, esetleg többéves munkáját egy teljes program­ rendszer létrehozására. Ha például egy olyan rendszert kell megvalósítani, amely egy város vízellátását hivatott ellenőrizni és irányítani, a programokba a legapróbb részletekig be kell vinni minden lehetséges részfeladat megoldását úgy, hogy az eredmény a lehető legrövidebb idő alatt megszülessen. De ezt a programrendszert a városban megjelenő új épületek számára állandóan bővíteni kell, más épületek módosulhatnak, illetve eltűnhetnek, tehát az ilyen típusú mó­ dosításokat is folyamatosan el kell végezni. A programozók ma már főleg csapatban dolgoznak, így szükségessé vált a programozói tevékenység szabályozása, egy bizonyos sajátos fegyelem megkö­ vetelése, a programozók közti kommunikáció szabványosítása, valamint a meg­ oldandó feladatok szövegeinek megfelelő specifikálása. A programozás történetében az első kísérlet, amely a programozási munka egyszerűsítését tűzte ki céljául a moduláris programozás elveként vált ismertté (1956). Ennek az elvnek az értelmében az eredetileg bonyolult feladatot rész­ feladatokra bontjuk, és az ezeket megoldó algoritmusokat alprogramokkal kó­ doljuk. A részfeladatokat megoldó alprogramokat összefűzve, megkapjuk az eredeti feladat megoldását. A következő fontos lépést a strukturált programozás megjelenése jelentette 1972-ben, amikor E. W. Dijkstra olyan algoritmusokat javasolt, amelyekből már

14

1. A SZÁMÍTÓGÉPES FELADATMEGOLDÁS LÉPÉSEI

hiányzik a goto utasítás1. Ezáltal nagymértékben megnőtt a programok olvasha­ tósága, frissíthetősége, sőt könnyebb lett azok karbantartása is. A strukturált programozás elméleti megalapozását folytatta Böhm és Jacopini, akik bebizo­ nyították, hogy bármely algoritmus megvalósítható a három alapstruktúrával, amelyek: a szekvencia, az elágazás (vagy döntés) és az előltesztelő ismeretlen lépésszámú ciklus (iteráció).

1.2. A feladatmegoldás lépései számítógépes környezetben Ha egy bizonyos feladatot számítógéppel szeretnénk megoldani, az ember által elvégzendő lépések a következő módon csoportosíthatók [7]:

a) A követelmények és a feladat megfogalmazása ■ A megrendelő leírja a ki­ vitelező számára (sokszor pontatlanul és ellentmondásosan) azt a feladatot, amelyet számítógépes programmal szeretne megoldani a jövőben. A leírás (általában) a kimenetre összpontosít.

b) A feladat specifikációja ■ Több beszélgetés szükséges ahhoz, hogy a prog­ ramozó csoport rendszertervezője pontosan megfogalmazhassa a feladatot (ismernie kell milyen körülmények között, mekkora méretű adatokkal, mi­ lyen számítógépen kerül fölhasználásra az elkészítendő program). A követ­ kező lépésben a rendszertervezők elemzik a megrendelő követelményeit és lefordítják „saját nyelvükre” a feladatot, vagyis létrehozzák azt a belső hasz­ nálatra készülő dokumentációt, amely a feladat pontos és helyes specifikáci­ óját tartalmazza. Itt már bemeneti és kimeneti adatokról, valamint megszorí­ tásokról (a bemeneti adatok és az adatszerkezetek méretéről, az elfogadható legnagyobb futási időről stb.) van szó. A specifikációt ellenőrzi a megrende­ lő, ellenőrzi a programozó csoport vezetője, de sokszor szükséges, hogy egy, a csoporttól független szakember is elmondja a véleményét.

c) Az algoritmus megtervezése (elemzés, modellek és módszerek megállapí­ tása) ■ A specifikáció létrejötte után a csoport tagjai kiválasztják a megfelelő matematikai modelleket, megtervezik az adatszerkezeteket és eldöntik, hogy milyen programozási környezetben milyen módszereket fognak alkalmazni. A módszerek és az adatszerkezetek egymást befolyásolják, ezért gyakran előfordul, hogy több megoldási lehetőség is elemzésre kerül. Az a megoldás lesz a jobb, amely a számítógép erőforrásait (memória és idő) a leghatéko­ nyabban használják. A feladatot részfeladatokra bontják és megtervezik a A goto utasítás hatására a számítógép nem a programban következő utasítást, hanem a goto-ban feltüntetett címkével azonosítottat hajtja végre. 1

1. A SZÁMÍTÓGÉPES FELADATMEGOLDÁS LÉPÉSEI

15

megoldásokat. A matematikai modellek, az adatszerkezetek és az algoritmu­ sok kiválasztása általában összefonódik. Ekkor alakulnak ki a modulok és a köztük levő kapcsolatok. A tervezés során elkészül egy dokumentáció, amely tartalmazza a megoldás vázát, a választások indoklását. Minderre a kódolás során, valamint a program karbantartásakor lesz szükség.

d) Kódolás (az algoritmus implementálása egy programozási nyelvben)



Ebben a fázisban az algoritmust átírjuk valamilyen programozási nyelvre, amelyet – a feladat jellegének megfelelően – gondosan választunk ki.

e) Tesztelés (ellenőrzés és „érvényesítés”, vagyis annak ellenőrzése, hogy a bemeneti és kimeneti adatok megfelelnek-e a specifikációknak) ■ E tevé­ kenység során a programozó különböző tesztadatok esetében tanulmányozza a program működését. Természetesen, arra számít, hogy a program a várt (helyes) adatokat adja, de ezen túlmenően megfigyeli a futási időt, a bizton­ ságos működést stb. A felfedezett hibákat kijavítja, majd újra ellenőrzi az eredményt. Sok esetben a tesztelés beindulhat sokkal hamarabb, egy-egy részfeladat megoldása után. Tulajdonképpen olyan ellenőrzésről van szó, amely nem áll egy különálló fázisból, hanem egy folyamatos tevékenység, amely a tervezés, kódolás és karbantartás során zajlik. Az ellenőrzés lénye­ ges összetevője az úgynevezett érvényesítés. Ezt előbb elvégzi a kivitelező, később a felhasználás során a megrendelő. Megjegyzendő, hogy a megrende­ lő nem végez ellenőrzéseket, hanem valós adatokkal futtatja a programot és azt várja, hogy a program a megfelelő alakban írja ki az eredményeket.

f) A tervezői és a felhasználói dokumentáció elkészítése ■ A dokumentációk a program fontos tartozékai, amelyek a programhoz kapcsolódó fontos infor­ mációkat tartalmazzák. A fejlesztői dokumentáció leírja, hogyan volt meg­ tervezve a program, a felhasználói dokumentáció elmagyarázza, hogyan kell használni a programot.

g) Felhasználás és karbantartás

A megrendelő a programot a felhasználás közben valós adatokkal futtatja. Ez természetesen hosszabb ideig zajlik, és előfordulhat, hogy a felhasználás során bizonyos programrészeket a progra­ mozónak meg kell változtatnia. ■

1.3. Alkalmazások minőségi szempontjai Helyesség ■ A program helyes, ha pontosan megoldja a feladatot, megfelel a pontos és helyes specifikációnak.

16

1. A SZÁMÍTÓGÉPES FELADATMEGOLDÁS LÉPÉSEI

Megbízhatóság ■ Egy programot megbízhatónak nevezünk, ha helyes ered­ ményt határoz meg, és a specifikációban nem leírt helyzetekben is intelligens módon viselkedik. Karbantarthatóság ■ A karbantarthatóság annak mérőszáma, hogy milyen könnyű a programterméket a specifikáció esetleges változtatásához adaptálni. Egyes felmérések szerint a szoftverköltségek 70%-át a szoftverek karbantartásá­ ra fordítják! A karbantarthatóság növelése szempontjából a két legfontosabb alapelv: a tervezési egyszerűség és a decentralizáció (minél önállóbb modulok létrehozása). Újrafelhasználhatóság ■ Az újrafelhasználhatóság a szoftvertermékek azon ké­ pessége, hogy egészben vagy részben újrafelhasználhatók új alkalmazásokban. Kompatibilitás ■ A kompatibilitás azt mutatja meg, hogy milyen könnyű a szoftvertermékeket egymás között kombinálni. Hordozhatóság ■ A program hordozhatósága arra vonatkozik, hogy menynyire könnyű a programot más géphez, konfigurációhoz, vagy operációs rendszerhez – általában más fizikai környezethez – igazítani. Hatékonyság ■ A program hatékonysága a futási idővel és a felhasznált memó­ ria méretével arányos – minél gyorsabb, illetve minél kevesebb memóriát hasz­ nál, annál hatékonyabb. Barátságosság ■ A program emberközelisége, barátságossága a felhasználó számára rendkívül fontos: ez megköveteli, hogy a bemenet logikus és egyszerű, az eredmények formája áttekinthető legyen. Tesztelhetőség fontos.



A tesztelhetőség, a program karbantartói, fejlesztői számára

2

AZ ALGORITMUSOK ÁBRÁZOLÁSA

2.1. Algoritmusok Mivel a számítástechnika egyik bűvös szava az algoritmus, lássuk hogyan is alakult ki ez a szó. D. E. Knuth így vélekedik a szó eredetéről: „Az algoritmus szó már önmagában is nagyon érdekes. Úgy tűnhet egy pillanatig, hogy a loga­ ritmus szót akarta valaki leírni, de nem sikerült neki, mert összezagyválta az el­ ső négy betűt.” A matematikatörténészek azt állítják, hogy az algoritmus szó Abu-Ja far Mohammed ibn Mura al-Kvarîzmi (780 k.–850 k.) arab matematikus nevének, al-Kvarîzmi latinos elferdítéséből származik.

2.1.1. Az algoritmus fogalma A mindennapi életben gyakran kerülünk olyan helyzetbe, amikor meglévő isme­ reteink alapján nem tudjuk azonnal megmondani, hogy elérhető-e a kitűzött cé­ lunk, és ha igen, hogyan. A „megoldás” két dolgot feltételez:  az eredmény milyenségének, azaz elvárásainknak pontos ismeretét;  annak a folyamatnak a tervét, amely az eredmény eléréséhez vezet. A cél elérése érdekében megtervezzük az elvégzendő cselekvések sorozatát, vagyis megtervezzük az algoritmust, amely a kívánt eredményhez vezet. Olyan problémamegoldó eljárást (algoritmust) kell kidolgoznunk, amely vé­ ges számú lépésben befejeződik, és a rendelkezésre álló (bemeneti) adatokból előállítja az úgynevezett eredményt (kimeneti adatokat), vagyis megoldja a fela­ datot. [18] Azt gondolhatnánk, hogy mi sem egyszerűbb, mint a feladat megoldását a számítógépre bízni. Igen ám, de a számítógép csak azt tudja elvégezni, amit a felhasználó megnevezett! Ezért, amikor a számítógépnek kell megmondanunk, hogy mit csináljon, akkor egyértelmű leírást kell adnunk úgy, hogy gépünk szá­ mára is követhető legyen az elképzelésünk.

18

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Az algoritmust úgy is definiálhatjuk, mint egy adott feladatkör megoldására kidolgozott olyan eljárást, amely utasításszerűen előre megadott lépések soro­ zatából áll. De ahhoz, hogy az algoritmus fogalmát tisztázhassuk, még több tu­ lajdonságra is oda kell figyelnünk. Mielőtt sorra vennénk az algoritmusok főbb jellemzőit, próbáljuk megadni az algoritmus pontosabb leírását. Napjainkban algoritmus alatt egy véges, bizonyos sorrendben megadott egy­ értelmű műveletsort értünk, amelyek mechanikusan elvégezhetők (anélkül, hogy az ember arra szorulna, hogy saját maga döntéseket hozzon), és amelyek a ren­ delkezésre álló adatokból új adatokat hoznak létre, vagy más formában vezetnek a kívánt eredményhez. Az algoritmusokat valamilyen feladat megoldásának céljából tervezzük, majd valamilyen programozási nyelv segítségével kódoljuk, hogy számítógépen végrehajthassuk. Mielőtt számba vennénk az algoritmusok tulajdonságait, hangsúlyozzuk, hogy a számítógépes világ ismer szekvenciális és párhuzamos algoritmusokat. Egy szekvenciális algoritmus az utasításait egymás után hajtja végre, míg egy párhuzamos algoritmus esetében több utasítás is végrehajtódik ugyanabban a pillanatban, mivel ezeket több processzor végzi párhuzamosan. Valamely szekvenciális algoritmus akkor helyes, ha eleget tesz az alábbi kö­ vetelményeknek:

a) elvégezhető ■ vagyis az ember is képes eljutni az eredményhez, papírral és ceruzával a kezében követve az algoritmusban leírt műveletek hatását;

b) meghatározott ■ azaz, bármelyik pillanatban tudjuk, hogy éppen mi fog tör­ ténni (ez csak a determinisztikus algoritmusokra érvényes);

c) általános ■ egy adott feladatkör megoldására képes; d) véges

az algoritmus véges számú lépésben vezet eredményre, bármilyen kezdeti értékekből kiindulva. ■

Ezekhez hozzátesszük, hogy egy algoritmust a helyességén kívül a követke­ ző tulajdonságok is jellemezhetik:

e) világos ■ az algoritmus leírása pontos, érthető, követhető; f) hatékony

egy bizonyos feladatot több algoritmussal is megoldhatunk, de ezek közül mindig megkeressük azt, amely a legkevésbé veszi igénybe a szá­ mítógép erőforrásait, vagyis nem terheli fölösleges műveletek elvégzésével, illetve fölösleges adatok tárolásával; azt mondjuk, hogy ezek az algoritmu­ sok „hatékonyak”, „optimálisak”, vagyis a bonyolultságuk a lehető legkisebb. ■

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

19

Az algoritmusok helyességének bizonyítását később fogjuk alaposabban ta­ nulmányozni. Egyelőre marad a lehetőség, miszerint az algoritmusokat tesztel­ jük, ellenőrizzük. Egy algoritmus kezdeti értékeket igényel, ezekből „származtatja” az ered­ ményt. A kezdeti értékeket bemeneti (bemenő) adatoknak nevezzük. Ide sorol­ hatjuk azokat a konstans értékeket is, amelyekkel inicializálunk. Az algoritmus által szolgáltatott adatokat (eredményt) kimeneti (kimenő) adatoknak nevezzük. Mindaz amit az algoritmus elvégez, a funkciójaként nevezhető meg.

2.1.2. Az algoritmusok leírásánál használt elemek A. Adatok Az adatokat típusuk szerint a következőképpen osztályozhatjuk:

a) A numerikus adatok csoportjába tartoznak az egész típusú adatok, azaz egész számok, valamint a valós típusú adatok, azaz a tizedes számok. A va­ lós típusú adatok jelölésekor tizedesvessző helyett tizedespontot használunk (például 3,47 helyett 3.47-et).

b) A logikai adatok mindössze két értéket vehetnek fel: a logikai igaz (true), valamint a logikai hamis (false) értékeket.

c) A karakterláncok egyszeres idézőjelek (aposztrófok) közé zárt karaktersort jelentenek (például 'ez egy szoveg'). B. Állandók (konstansok) Algoritmusok írásakor numerikus, logikai és karakterlánc típusú állandókat használhatunk. Ezek nem változtatják értéküket az algoritmusban. C. Változók Az algoritmusok leírásakor használt olyan elemeket, amelyeknek értéke az algo­ ritmus végrehajtása során időben módosulhat, változóknak nevezzük. Amikor az algoritmus alapján programot írunk, amelyet a továbbiakban szá­ mítógépünkön szeretnénk futtatni, akkor minden változónak helyet kell foglal­ nunk a memóriában. A lefoglalt hely mérete attól függ, hogy milyen típusú 2 adatot fogunk az illető változóban tárolni. Azok a változók, amelyek még nem kaptak értéket, inicializálatlanok (nincs kezdőértékük). Egy típus meghatározza azoknak az értékeknek a halmazát, amelyből értékeket kaphat a változó, valamint azokat a műveleteket, amelyeket ezekkel az értékekkel elvégezhetünk. 2

20

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Összefoglalva: minden változónak van neve, attribútumhalmaza (például típus), helye (címe) a memóriában, értéke. A változók típusa, a konstansokéhoz hasonlóan lehet numerikus, logikai és karakterlánc típusú. Ezeket a típusokat standard típusoknak nevezzük. Mellet­ tük még végtelen sok típust definiálhat maga a programozó. D. Kifejezések A kifejezés egy számítás jelölése: előírja valamely érték kiszámításának módját. A kifejezés műveletekből és operandusokból áll. Az operandusok lehetnek konstansok, változók vagy függvényhívások (a függvénykifejezés valamely függvény kiértékeléséről gondoskodik). A kifejezés kiértékelése a megszokott matematikai szabályoknak megfelelően, az ismert precedencia figyelembevéte­ lével történik. Azonos prioritású műveletek esetében a kifejezés kiértékelését balról jobbra haladva végezzük. Az algoritmusok leírásakor numerikus, logikai és karakterlánc típusú kifeje­ zéseket használhatunk. D1. Egész típusú kifejezések Az egész típusú kifejezések operandusai egész típusúak. Az egész típusú kifeje­ zésekben a következő műveleteket használhatjuk: Művelet jele + – * [a/b] maradék[a/b]

Jelentése összeadás kivonás szorzás egész számok osztásának hányadosa egész számok osztási maradéka

D2. Valós típusú kifejezések A valós típusú kifejezések operandusai valós típusú változók vagy állandók, de ezek közé kerülhetnek egész típusúak is. A valós típusú kifejezésekben a követ­ kező műveleteket használhatjuk: Művelet jele + – * /

Jelentése összeadás kivonás szorzás osztás

21

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Megjegyzés ■ A kifejezés típusát, a kifejezésben szereplő operandusok típusa, valamint a műveletek határozzák meg! D3. Logikai kifejezések A logikai kifejezések operandusai logikai típusúak. A logikai kifejezések leírá­ sában a következő műveleteket használhatjuk:  relációs műveletek: , , , =, ≠;  logikai műveletek: vagy, és, nem (vagyis az eredeti állítás tagadása, ellenkezője), xor (kizárólagos vagy);  a logikai műveletek eredményeit a következő táblázatok tartalmazzák: o r I H

I H I I I H

an d I H

I

H

I H H H

no t

I

H

H

I

xo r I H

I

H

H I

I H

Ha egy logikai kifejezés több részkifejezésből áll, ezeket zárójelek közé tesszük, mivel a logikai műveleteknek elsőbbségük van. A leghamarabb a nem (not) hajtódik végre, utána az és (and), végül a vagy (or). A relációs műveletek az aritmetikai kifejezések kiértékelése után kerülnek sorra. D4. Karakterlánc típusú kifejezések A karakterlánc típusú kifejezések operandusai karakterláncok. Ha a egy karak­ terlánc típusú változó és értéke például ' Ez egy ', akkor például az a + 'barack' kifejezés értéke ' Ez egy barack' lesz. A karakterláncok szerepelhetnek relációs kifejezésekben, ilyenkor a lexiko­ gráfikus sorrendnek megfelelően történik az összehasonlítás: Legyen a és b két karakterlánc típusú változó: a = 'barack', b = 'dió', ebben az esetben a < b. Az a = b relációs kifejezés értéke hamis, az a ≤ b értéke igaz. E. Műveletek Az algoritmusok leírásakor az alábbi műveleteket használhatjuk:  ki/bemeneti műveletek;  értékadás;  döntéshozatal.

22

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

E1. Ki/bemeneti műveletek A bemeneti művelet szerepe bekérni a szükséges bemeneti adatokat. Ezeket bil­ lentyűzetről vagy állományokból olvashatjuk be. A kimeneti művelet szerepe a feldolgozott adatokat, eredményeket megjele­ níteni. Ez a kiírás történhet a képernyőre, papírra vagy valamilyen állományba. E2. Értékadás Az értékadó művelet a  (vagy :=) műveleti jel bal oldalán található változóhoz hozzárendeli a jobb oldalon lévő kifejezés értékét. Példa ■ Az a  10 értékadás hatására a elveszti (esetleges) régi értékét, mivel az értékadás végrehajtása következtében az értéke 10 lesz. Az a  a + 1 értékadás hatására a új értéke a régi értékénél 1-gyel nagyobb lesz. E3. Döntéshozatal A döntéshozó művelet lényege, hogy adott feltételek teljesülésekor egy bizonyos műveletsort kell elvégeznünk, egyébként egy másik műveletsort. Ha valamelyik ág üres, akkor természetesen azon az ágon nem történik semmi. F. Adatszerkezetek3 Adatszerkezet alatt az adatok olyan csoportosulását értjük, amelyben pontos sza­ bályok szerint szervezzük a feldolgozandó adatokat. Egy adattípuson belül azo­ nos tulajdonságú értékeket foglalunk egybe, amelyekhez hozzárendeljük az el­ végezhető műveleteket. Megkülönböztetünk:  egyszerű adattípusokat (például az egész típusú számot nem tekintjük számjegyek sorozataként).  összetett adattípusokat (az értékek elemekre bonthatók, az elemek egy adott szerkezetnek megfelelően csoportosulnak). Így az adatszerkezet egy olyan adattípus, amelynek az értékei felbonthatók adatelemek értékeinek halmazára, amelyeknek típusa lehet egyszerű vagy össze­ tett. Az adatszerkezeten belül ismerjük az elemek közti kapcsolatot (szerkezetet).

Az adatszerkezeteket egy másik tantárgy keretén belül fogjuk alaposabban tanulmányozni. 3

23

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Például az Integer egy egész adattípus, amelyet a Pascal programozási nyelv ismer. Ha több egész számot szeretnénk egy adatkent kezelni, akkor egy soroza­ tot képezünk belőlük. Ekkor a sorozat egy adatszerkezet (vektor), amelynek az elemei az egész számok. Az elemek közti kapcsolat az, hogy egymás után kö­ vetkeznek. Műveletek: elemek lekérdezése a sorszámuk (indexük) segítségével, értékadás stb.

2.2. Algoritmusok ábrázolása folyamatábrák és pszeudokód nyelvek segítségével Az algoritmusokat többféleképpen lehet ábrázolni. Ismeretesek a folyamatáb­ rák, a pszeudokód nyelv, különböző típusú diagrammok, de a hétköznap hasz­ nált nyelv is előfordul. Mivel ez utóbbi nehézkes lehet, hosszú és nem eléggé szabatos, az első két ábrázolással fogunk gyakrabban találkozni. A folyamatábra az algoritmusok leírásának egyik legegyszerűbb és legszemléletesebb módja. A folyamatábra grafikus ábrázolásmód, amelynek segítségével nem csupán az egyes műveleteket, hanem ezek elvégzésének sorrendjét és összefüggését is feltüntetjük. Folyamatábrák készítésekor az egyes műveleteket blokknak neve­ zett ábrák segítségével jelöljük, amelyeknek alakja a művelet típusára, tartalma pedig a művelet leírására utal. A folyamatábra hasonlít egy gráfhoz, ahol a lerajzolt nyilak irányításának megfelelően egy kiinduló helyzetből eljutunk az algoritmus végére úgy, hogy menet közben feltüntetjük az elvégzendő műveleteket. Az alapműveleteknek különböző síkidomok felelnek meg, ezeknek a belsejébe írjuk a műveleteket.

a) indító- és záróblokk

START

b) be és kimeneti műveletek

STOP

Be v

Ki v

c) értékadás  előbb kiszámítódik a kifejezés értéke;  a kiszámított érték átadódik a változónak (be­ másolódik a változó számára lefoglalt memória­ rekeszbe); ha a változónak volt előző értéke, az

változó  kifejezés

24

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

az aktuális értékadás következtében elvész, mi­ vel felülíródik az új értékkel.

d) alprogram (algoritmus) hívása  feltételezzük, hogy létezik az ELJÁRÁS nevű algoritmus, vagy legalább a funkcióját ismerjük;  az ELJÁRÁS(L) blokk egy műveletsort jelöl, amelyet egy különálló folyamatábrával írunk le. E folyamatábra indítóblokkja a műveletsor nevét és az L paraméterlistát, míg záróblokkja az EXIT szót tartalmazza.

ELJÁRÁS(L)

e) elágazás (döntés)  feltételezzük, hogy a feltétel egy logikai kifejezés, amely vagy igaz, vagy hamis;  az algoritmus végrehajtását a feltételtől függően vagy a jobb vagy a bal ágon folytatjuk tovább

HAMIS

feltétel

IGAZ

Példa ■ Legyen egy természetes szám. Ábrázoljuk folyamatábrával azt az algo­ ritmust, amely megvizsgálja, hogy a szám palindrom szám-e vagy sem. Egy számot palindromszámnak (vagy tükörszámnak) hívunk, ha egyenlő a „fordí­ tott”-jával, vagyis azzal a számmal, amelyet a szám számjegyei fordított sor­ rendben alkotnak. Megoldás ■ A számot számjegyekre bontjuk, és a bontással párhuzamosan fel­ építjük az új számot. Az új szám generálását a Horner-séma néven ismert módszer segítségével végezzük (a ciklusban újszám  újszám  10 + számjegy). Mivel az algoritmus a számjegyeket úgy határozza meg, hogy ismételten osztja az eredeti számot 10zel, az algoritmus végén az eredeti szám értéke 0. De nekünk szükségünk van az eredeti értékre, hogy összehasonlíthassuk a kapott új számmal. Ezért a beolva­ sott számról a feldolgozás előtt készítünk egy másolatot. A feladatot megoldó algoritmus folyamatábrája ekkor a következőképpen alakul:

25

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

START Be szám másolat  szám újszám  0

szám > 0

IGAZ

számjegy  maradék[szám/10]

HAMIS

újszám  újszám*10+számjegy szám  [szám/10]

HAMIS

másolat= újszám

Ki 'Nem'

IGAZ

Ki 'Igen'

STOP

Annak ellenére, hogy a folyamatábrák segítségével az algoritmusok szemlé­ letesen és viszonylag egyszerűen írhatók le, a gyakorlatban ezek használata ne­ hézkesnek bizonyult, egyrészt, mert bonyolultabb feladatok esetében a folya ­ matábrák áttekinthetetlenekké válhatnak, másrészt pedig a grafikus ábrázolás önmagában is sok kellemetlenséget okozhat. Arról ne is beszéljünk, hogy itt megengedett a goto típusú átirányítás, ami végképp megnehezíti az algoritmus „olvasását”. A folyamatábrák helyett használhatjuk az úgynevezett pszeudokódot, amely az elemi struktúrákat írja le egyszerű utasítások formájában. A pszeudokód „nyelv” sokban hasonlít a programozási nyelvekhez, emiatt a pszeudókodban leírt algoritmus nagyon könnyen átírható bármely programozási nyelvre.

26

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Feltevődik a kérdés: akkor miért nem írjuk algoritmusainkat egyenesen vala­ melyik programozási nyelven? Elsősorban azért nem, mert a pszeudokód leírás­ mód nem annyira kötött, mint egy programozási nyelv. Másodsorban pedig azért nem, mert a pszeudokódban leírt algoritmus – éppen az imént említett ru­ galmasság miatt – bármely programozó számára érthető, általános, nem tartal­ mazza egy programozási nyelv sajátosságait, és így nemcsak az illető nyelvet ismerő programozók fogják megérteni. A pszeudokódok többfélék lehetnek. Bárki megalkothatja saját pszeudokód­ ját az elemi programozási struktúrák ismeretében.

2.3. A strukturált programozás alapelvei A programozók és a programozást oktatók nagy gondja volt a fegyelmezetlen, minden szabálytól, megkötéstől mentes programozói stílus. Nehézzé, néha lehe­ tetlenné vált a karbantartás. Ezt elhárítandó, N. Wirth kidolgozta a lépésenkénti finomítás (stepwise re­ finement) elvét. De ez nem bizonyult elegendőnek. A strukturált programozást E. W. Dijkstra és C. A. R. Hoare vezették be 1965-ben. Olyan algoritmustervezési szabályokról van szó, amelyeknek lényege abban áll, hogy olyan algoritmusokat írunk, amelyek csak az elfogadott struktú­ rákat tartalmazzák. Lényeges észrevenni, hogy kizárták a goto utasítást a fel­ használható utasítások közül, ezáltal az algoritmusok olvashatóbbakká, könnyen beláthatóakká váltak és így eredményesebb programozói teljesítményekhez ve­ zettek. A strukturált programozás alapelve szerint az algoritmusok leírására né­ hány alapstruktúrát (elemi struktúrát) használunk, melyeknek egyetlen bemene­ te és egyetlen kimenete van. A kitűzött feladatot részfeladatokra bontjuk, majd e részfeladatok megoldásával jutunk el az eredeti feladat megoldásához. Ily mó­ don bármely algoritmus alapstruktúrák lineáris szekvenciájaként tekinthető. Böhm és Jacopini tétele kimondja, hogy bármely algoritmus megvalósítható a három alapstruktúra segítségével: lineáris struktúrával, elágazással és Amíg típusú ismétlő struktúrával. Strukturált algoritmusok írásakor a következő alapstruktúrákat használhatjuk:

a) lineáris struktúra ■ valamely művelet feltétel nélküli elvégzését jelenti; b) elágazási (alternatív) struktúra

lehetővé teszi, hogy valamely műveletet csak adott feltételek teljesülésekor végezzünk el; ■

c) ismétlő struktúra (ciklus) ■ adott műveletsor véges számszor történő ismé­ telt elvégzése.

27

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Az elemi struktúrák használata megkönnyíti az algoritmusírást, ugyanakkor a már kész algoritmus érthetőbb, áttekinthetőbb lesz.

2.3.1. Lineáris struktúrák A lineáris struktúrák a következők:  indítóblokk (START);  záróblokk (STOP);  ki- és bemeneti műveletek;  értékadás;  az ELJÁRÁS blokk „hívása” (az ELJÁRÁS blokkhoz tartozó struktúrák nem feltétlenül lineárisak). Lineáris struktúrának tekintjük továbbá a fentiek bármilyen szekvenciáját is.

2.3.2. Elágazási struktúrák Az elágazás a döntéshozó művelet megfelelője, amelyben a feltétel egy logikai kifejezés, amely állhat több részkifejezésből. Ha a feltétel teljesül, akkor a q műveletsort végezzük el, különben a p-t, ahol a q és p tetszőle­ ges számú, bármilyen típusú struktú­ rákból állhat.

HAMIS

p

feltétel

IGAZ

q

Ha a p vagy a q műveletsor hiányzik, ajánlott úgy megfogalmazni a feltételt, hogy a HAMIS ág váljon üressé. A későbbiekben meg fogunk ismerkedni egy olyan elágazási struktúrával is, amely egyetlen feltétel kiértékelése következtében kettőnél több felé ágaztatja az algoritmust. Példa ■ Olvassunk be egy évszámot (1000 ≤ évszám ≤ 3000). Döntsük el, hogy az adott év szökőév-e vagy sem! Megoldás ■ Egy szökőév osztható 4-gyel és nem osztható 100-zal, vagy osztha­ tó 400-zal.

28

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA START Be évszám

HAMIS

maradék[évszám/4]=0 és IGAZ maradék[évszám/100]≠0 vagy maradék[évszám/400]=0

Ki 'Szökőév'

Ki 'Nem'

STOP

2.3.3. Ismétlő struktúrák Ha egy adott műveletsort többször is meg kell ismételnünk, ismétlő struktúrát vagy más néven ciklust használunk. A ciklusnak tartalmaznia kell egy feltételt, amely lehetővé teszi a ciklusból való kilépést. A ciklusokat három csoportba soroljuk:

a) elöltesztelő ciklus (Amíg típusú struktúra); b) hátultesztelő ciklus (Ismételd típusú struktúra); c) ismert számú ismétlés (Minden típusú struktúra). A továbbiakban e három típust egyenként ismertetjük.

a) Elöltesztelő ciklus (Amíg típusú struktúra)  Amíg a feltétel értéke igaz, ismételjük a p műveletsort.  Amint a feltétel értéke hamissá válik, kilé­ pünk a ciklusból.  A p műveletsorban kötelezően lennie kell egy olyan műveletnek, amely megváltoztatja a feltétel logikai értékét, lehetővé téve a cik­ lusból való kilépést.  Előfordulhat, hogy a feltétel értéke már a ciklusba lépés előtt hamis. Ebben az esetben a p műveletsort egyszer sem végezzük el!

feltétel HAMIS

IGAZ

p

29

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Példa ■ Olvassunk be több egész számot. A számok beolvasása befejeződik, amikor a beolvasott szám értéke 0. Számítsuk ki az összegüket! Az összeget 0 kezdőértékkel látjuk el. Mivel a számok számát nem ismer­ jük, sőt az is előfordulhat, hogy az első beolvasott szám értéke 0, ismeretlen számú, elöltesztelő ciklust alkalmazunk. START Be szám összeg  0

szám ≠ 0

IGAZ összeg  összeg + szám

HAMIS

Be szám

Ki összeg

STOP

b) Hátultesztelő ciklus (Ismételd típusú struktúra)  A p műveletsort addig ismételjük, ameddig a feltétel értéke igazzá nem válik. (Kilépünk amikor a feltétel értéke igaz.)  A p műveletsorban szükség van egy olyan műveletre, amely megváltoztatja a feltétel logikai értékét és lehetővé teszi a ciklusból való kilépést.  A feltétel értékétől függetlenül a p műveletsort leg­ alább egyszer elvégezzük!

p

feltétel

HAMIS

IGAZ

Példa ■ Adva van egy pozitív egész szám. Írjuk ki számjegyeinek számát! Megoldás ■ A szám számjegyeinek számát a 10-zel való ismételt egész osztá­ sok száma adja meg, amelyeket addig végzünk, amíg a szám 0-vá nem válik. Mivel az eredeti számról tudjuk, hogy 0-tól különbözik, de nem tudjuk, hány számjegye van, a feldolgozást hátultesztelő ismeretlen lépésszámú ciklussal vé­ gezzük. Ha az adott szám 0 is lehetett volna, az eredmény akkor is 1, de ebben az esetben egyszer sem kellett volna osztani a számot, tehát elöltesztelő ciklust alkalmaztunk volna.

30

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

START Be szám sz  0

szám  [szám/10] sz  sz + 1

szám = 0

HAMIS

IGAZ Ki sz STOP

Feladat ■ Próbáljunk folyamatábrát rajzolni egy elöltesztelő ciklussal megoldott feladat, hátultesztelő ciklussal történő megoldására.

c) Ismert számú ismétlés (Minden típusú struktúra) Az Amíg és az Ismételd struktúrák esetében nem tudhatjuk pontosan, hányszor fog ismétlődni a p műveletsor. Ha ismerjük a végrehajtások szá­ mát, az előbb bemutatott, módosított elöltesztelő ismétlő struktúrát használ­ hatjuk, amelyben egy úgynevezett ciklusszámláló segítségével ellenőrizzük a műveletsor ismétléseinek számát. Ezt az ismétlő struktúrát Minden típusú struktúrának nevezzük.  A p műveletsort addig ismételjük, ameddig az i sorszámozott típusú változó értéke kisebb vagy egyenlő mint az u utolsó megengedett érték (az i első értéke az e kezdőérték).  Az i változó a ciklus minden lépé­ sében megváltoztatja értékét. A lé­ pés lehet pozitív vagy negatív egész szám.

i  e

i ≤ u

IGAZ p

HAMIS i  i + lépés

31

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

 Különböző programozási nyelvekben a Minden típusú struktúra különböző­ képpen van megvalósítva. Példa ■ Számítsuk ki az első n (2 ≤ n ≤ 20) természetes szám összegét! Megoldás ■ A feladat megoldására Minden típusú struktúrát használunk. A cik­ lusváltozót i-vel jelöljük, és értékei a ciklus folyamán 1-től n-ig változnak, azaz n darab számot fogunk a ciklusban feldolgozni. Az n értékét a felhasználótól kérjük be. Jelen (és a legtöbb) esetben a lépésszámlálót 1-gyel növeljük. A cik­ lus által „biztosított” i ciklusváltozót így nyugodtan felhasználhatjuk az összeg kiszámítására, amelyet az összeg változóban tárolunk. A feladat megoldását a következő folyamatábra szemlélteti. Megjegyzés ■ Látható, hogy az ábra igazán szemléletes, de képzeljünk el egy ennél mondjuk 5-ször bonyolultabb algoritmust. Hogyan ábrázolnánk azt egy ilyen méretű lapon? A továbbiakban mindenkinek az algoritmusok pszeudokód­ dal történő leírását javasoljuk. START Be n összeg  0 i  1

i ≤ n

IGAZ

összeg  összeg + i HAMIS i  i + 1 Ki összeg STOP

32

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

2.3.4. Az alapstruktúrák jelölése pszeudokódban a) A pszeudokódban leírt algoritmus első utasítása: Algoritmus Neve:

b) Lineáris struktúrák ábrázolása pszeudokód nyelvben: Struktúra neve Bemeneti struktúra Kimeneti struktúra Értékadó művelet Eljáráshívás

Jelölés Be: változólista Ki: változólista változónév  kifejezés eljárásnév(paraméterlista)

c) Elágazási struktúrák ábrázolása pszeudokód nyelvben: Ha-akkor-különben típusú struktúra: Ha feltétel akkor

utasítás(ok)1 különben

utasítás(ok)2 vége(ha)

Ha az elágazási struktúrából hiányzik a különben ág, akkor Ha-akkor típusú struktúránk van: Ha feltétel akkor

utasítás(ok) vége(ha)

Példa ■ Határozzuk meg és írjuk ki adott valós szám abszolút értékét! Elemzés ■ Matematikából tudjuk, hogy valamely szám abszolút értéke:  x, ha x  0 x   x, ha x  0.

Beolvassuk az x valós számot. Ha a szám pozitív, akkor az abszolút érték maga a szám lesz, egyébként az abszolút érték maga a szám, de megváltoztatott előjellel. Algoritmus Abszolút_érték(x,mod): Ha x  0 akkor mod  x különben mod  -x vége(ha) Vége(algoritmus)

{ bemeneti adat: x, kimeneti adat: mod }

33

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Megjegyzés ■ Ha az x változó eredeti értékére a továbbiakban nincs szüksé­ günk, elegánsabb megoldást kapunk, ha nem használunk külön változót az ab­ szolút érték tárolására! Egyébként a megoldás hátránya lehet, hogy módosul az x értéke! Algoritmus Abszolút_érték_másként(x): Ha x < 0 akkor x  -x vége(ha) Vége(algoritmus)

{ bemeneti és kimeneti adat: x }

d) Ismétlő struktúrák ábrázolása pszeudokód nyelvben: Amíg típusú struktúra ábrázolása: Amíg feltétel végezd el:

utasítás(ok) vége(amíg)

Példa ■ Számítsuk ki két természetes szám egész hányadosát ismételt kivoná­ sokkal! Algoritmus Osztás(a,b,hányados): hányados  0 { bemeneti adatok: a, b, kimeneti adat: hányados } Amíg a ≥ b végezd el: hányados  hányados + 1 a  a - b vége(amíg) Vége(algoritmus)

Ismételd típusú struktúra ábrázolása: Ismételd

utasítás(ok) Ameddig feltétel

Példa ■ Számítsuk ki két természetes szám legnagyobb közös osztóját! Megoldás ■ Alkalmazzuk Eukleidész algoritmusát. Kiszámítjuk a két szám osz­ tási maradékát. Ha ez nem 0, ismételten kiszámítjuk az aktuális osztó és az ak­ tuális maradék egész osztási maradékát. Az algoritmus véget ér, amikor az aktu­ ális maradék értéke 0.4 Algoritmus Eukleidész(a,b,lnko): Ismételd { bemeneti adatok: a, b, kimeneti adat: lnko } r  maradék[a/b] { kiszámítjuk az aktuális maradékot } a  b { az osztandót felülírjuk az osztóval } b  r { az osztót felülírjuk a maradékkal } ameddig r = 0 { amikor a maradék 0, véget ér az algoritmus } lnko  a { lnko egyenlő az utolsó osztó értékével } Vége(algoritmus) 4

Eukleidész algoritmusára visszatérünk a jegyzet során többször is.

34

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Minden típusú struktúra ábrázolása: Minden i = e, u, lépés végezd el:

utasítás(ok) vége(minden)

Példa ■ Számoljuk meg n beolvasott szám közül a páros számokat! Megoldás ■ Tekintsük az alábbi algoritmust! Algoritmus Páros(n,db): db  0 { bemeneti adat: n és a számok, kimeneti adat: db, a páros számok száma } Minden i=1,n végezd el: Be: szám Ha szám páros akkor db  db + 1 vége(ha) vége(minden) Vége(algoritmus)

e) A pszeudokódban leírt algoritmus utolsó utasítása: Vége(algoritmus)

Megjegyzés ■ Megjegyezzük, hogy az algoritmus és a struktúrák zárását jelöl­ hetnénk egyszerűen a vége sorral, de ha a zárójelben feltüntetjük a struktúrát megnevező szót is (ha, amíg stb.), világosabb lesz az algoritmus leírása, hiszen így jelezzük azt is, hogy minek van vége. Mielőtt zárnánk a jegyzetben alkalmazott pszeudokód nyelvvel való ismer­ kedést, lássuk a folyamatábrák bemutatása után tárgyalt feladatot megoldó algo­ ritmus ábrázolását pszeudokóddal. (A feladat megoldásában eldöntjük egy adott számról, hogy palindromszáme vagy sem.) Algoritmus Palindrom(szám,válasz): másolat  szám { bemeneti adat: szám, kimeneti adat: válasz } újszám  0 Amíg szám > 0 végezd el: számjegy  maradék[szám/10] újszám  újszám*10 + számjegy szám  [szám/10] vége(amíg) válasz  újszám = másolat { ha újszám = másolat, akkor válasz értéke igaz } { ha újszám ≠ másolat, akkor válasz értéke hamis } Vége(algoritmus)

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

35

2.3.5. Egyszerű alapszabályok  A változók jelentését alaposan ismernünk kell!  Adjunk minden változónak beszédes azonosítót!  Ne használjunk inicializálatlan (kezdőérték nélküli) változókat!  Ne használjunk több változót a szükségesnél, mert ez növelheti az algorit­

mus alapján implementált program memóriaigényét.  Az algoritmus utasításait mindig a logikai felépítésnek megfelelően inden­

táljuk (igazítsuk) úgy, hogy abból mindig egyértelműen kitűnjön, melyik rész, melyik másik résznek az alárendeltje (melyik blokk alá tartozik).  Írjunk optimális algoritmusokat! (Egy algoritmus optimalitása nem a kód

hosszától, hanem annak hatékonyságától, azaz futási idejétől és erőforrás­ igényétől függ.)  Amennyiben lehetséges írjunk általános algoritmusokat, amelyek más prog­ ramozási környezetben is különösebb átalakítás nélkül felhasználhatók.  Az algoritmusnak minden bemenetre valamilyen kimenetet (eredményt vagy hibaüzenetet) kell szolgáltatnia.  A megoldandó feladatot teljes egészében elemezzük!  Amíg lehet, a feladatot bontsuk részfeladatokra, az algoritmust pedig rész­ algoritmusokra (alprogramokra)!  Ha a Ha struktúrában egy ág hiányzik, ez legyen a különben!  Az egymásba ágyazott Ha struktúrák következzenek a megvalósulásuk va­ lószínűsége szerinti csökkenő sorrendben!  A Minden típusú struktúrában ne módosítsuk a ciklusváltozót, és a kezdő-, illetve a végsőértéket sem!  Ha az algoritmus tervezésekor ismerjük a programozási környezetet, amelyben ezt kódolni fogjuk, kihasználhatjuk a programozási környezet és a nyelv lehetőségeit is, de ne essünk túlzásba, mivel így az algoritmu­ sunk veszít az általánosságából!

2.4. A feladatok számítógépes megoldásához fűződő általános kérdések A programozótól azt várják el, hogy megvalósítson egy algoritmust, amely meg­ old egy adott feladatot (a megoldás lehet közelítő is, ha ez fel van tüntetve). A megoldhatóság, első megközelítésre, azt jelenti, hogy létezik egy algoritmus, amelynek megfelel egy számítógépes program, amelyet, ha elegendő ideig fut­

36

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

tatunk tetszőleges bemeneti adatokra, és annyi memória igénybevételével, amennyire szükség van, bizonyos idő multán megkapjuk a helyes eredményt. [9] A feladatmegoldás során elméletileg az alábbi lépések hajtódnak végre:  annak bizonyítása, hogy létezik algoritmus, amely megoldja a feladatot;  az algoritmus megvalósítása (ebben az esetben az első lépés fölöslegessé válik);  az algoritmus elemzése. Az elemzés során a következő kritériumokat vesszük figyelembe:  helyesség;  az algoritmus végrehajtásához szükséges idő;  a bemeneti és kimeneti adatok számára szükséges memória mérete;  egyszerűség;  optimalitás.

2.4.1. Algoritmusok helyessége Mielőtt még azt tárgyalnánk, hogy hogyan vizsgáljuk egy algoritmus helyessé­ gét, lássuk, hogyan jellemezzük a helyességet: érvényes (a specifikációnak meg­ felelő) bemeneti adatokra, bizonyos idő után helyes eredményt kapunk. Ebből következik, hogy mindenek előtt szükség van a feladatnak pontos, specifikált megfogalmazására, amelyből egyértelműen derüljön ki, hogy mit értünk érvé­ nyes bemeneti adatok alatt, és mit tekinthetünk helyes eredménynek. Tételezzük fel, hogy kiválasztottunk egy megoldási módszert, és megtervez­ tük az utasítások sorozatát, amellyel megoldjuk a feladatot. A módszer kiválasz­ tásának helyességét tételekkel bizonyítjuk. Amikor áttérünk a megvalósítás módjának vizsgálatára, fontos szerepet kap a ciklusinvariánsok kimutatása. Az algoritmus bizonyos pontjaiban értelmezünk egy vagy több megmaradó tulaj­ donságot, amelynek teljesülnie kell akárhányszor az algoritmus végrehajtása érinti azt a pontot. Ezt többnyire matematikai indukcióval végezzük. A ciklusinvariáns bizo­ nyos adatok olyan tulajdonsága, amely teljesül (igaz közvetlenül a ciklus első iterációjának megkezdése előtt), megmarad (ha igaz a ciklus egy iterációjának megkezdése előtt, akkor igaz marad a következő iteráció előtt is) és befejeződik (amikor a ciklus befejeződik, az invariáns olyan hasznos tulajdonságot ír le, amely segít abban, hogy az algoritmus helyességét bebizonyítsuk). [6] Az algoritmusok helyességének bizonyításakor két fontos dologra kell fi­ gyelnünk [9]:

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

37

 A részleges helyesség. Feltételezve, hogy az algoritmus véges számú lépés­ ben véget ér, be kell bizonyítanunk, hogy a meghatározott eredmény he­ lyes.  A végesség. Be kell bizonyítanunk, hogy az algoritmus véges idő alatt be­ fejeződik. Természetesen, a fenti feltételeknek teljesülniük kell bármely megengedett bemeneti adathalmaz esetében. Példa ■ Határozzuk meg két természetes szám legnagyobb közös osztóját és legkisebb közös többszörösét. Legyen a, b  N* és a következő jelölések:  (a, b) = a és b legnagyobb közös osztója;  [a, b] = a és b legkisebb közös többszöröse. Algoritmus Lnko_és_Lkkt(a,b,lnko,lkkt): x  a { bemeneti adatok: a, b } y  b { kimeneti adatok: lnko, lkkt } u  a v  b Amíg x  y végezd el: { xv + yu = 2ab és (x, y) = (a, b) (*) } Ha x > y akkor x  x - y u  u + v különben y  y - x v  u + v vége(ha) vége(amíg) lnko  x lkkt  (u+v)/2 Vége(algoritmus)

A helyesség bizonyítását három lépésben végezzük:

a) A (*) kifejezések invariánsok: Az Amíg-ba lépés előtt a relációk igazak. Ki kell mutatnunk, hogy a ciklus minden lépésében az invariánsok megmaradnak. Legyenek az aktuális értékek x, y, u, v a ciklusba lépés előtt (amikor a relációk teljesülnek), és x', y', u', v' a ciklus következő lépése előtti értékek. Tehát: xv + yu = 2ab és (x, y) = (a, b). Feltételezzük, hogy x > y,  x' = x – y, y' = y, u' = u + v, v' = v. x'v' + y'u' = (x – y)v + y(u + v) = xv + yu = 2ab. Az x < y esetet hasonlóan tárgyaljuk.

38

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

b) Részleges helyesség: Legyen a legnagyobb közös osztó d = (a, b). A (*) relációk alapján d(u + v) = 2d2, ahol a = d és b = d.  (u + v)/2 = d = ab/d = [a, b] = lkkt.

c) Az algoritmus véges: Legyenek {xn}, {yn}, {un}, {vn} a változók egymás utáni értékeinek soroza­ tai. Ezek az értékek mind természetes számok. Észrevesszük, hogy az {xn} és az {yn} csökkenőek (egy bizonyos lépésben vagy x csökken vagy y, vagyis előfordul, hogy xn = xn–1 – yn–1, de az is lehet, hogy xn nem változik, mivel abban a lépésben yn csökken), míg az {xn – yn} sorozat szigorúan csökkenő (a különbség abszolút értéke biztos csökken minden lépésben, mivel vagy x csökken vagy y). A csökkenés 0-ig tarthat, amikor x = y. Példa: Orosz szorzás ■ Legyen a, b  N*. Számítsuk ki a és b szorzatát! Tudjuk, hogy az orosz muzsik csak a következő műveleteket tudja elvégezni:  el tudja dönteni, hogy egy szám páros vagy páratlan;  össze tud adni két számot;  össze tud hasonlítani egy számot 0-val;  felezni tud egy számot (elosztani 2-vel). Algoritmus Orosz_szorzás(a,b,p): x  a y  b p  0 Amíg x > 0 végezd el: Ha x páratlan akkor p  p + y vége(ha) x  [x/2] y  y + y vége(amíg) Vége(algoritmus)

{ bemeneti adatok: a, b } { kimeneti adat: p } { xy + p = ab (*) }

Példa ■ x = 45, y = 17: x 45 45 22 11 5 2 1 y 17 17 34 68 136 272 544 p 0 17 85 221 765 Az algoritmus helyességét három lépésben bizonyítjuk:

a) A (*) reláció invariáns: Az Amíg-ba lépés előtt a relációk igazak. Kimutatjuk, hogy a ciklus minden lésében az invariáns megmarad.

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

39

Legyenek x, y, p az aktuális értékek a ciklusba lépés előtt (amikor a relá­ ciók teljesülnek, tehát xy + p = ab), és legyenek x', y', p' a ciklus következő lépése előtti értékek. Feltételezzük, hogy x páratlan.  (x', y', p') = ((x – 1)/2, 2y, p + y).  x' y' + p' = [(x – 1)/2]  (2y) + p + y = xy + p = ab. Feltételezzük, hogy x páros.  (x', y', p') = (x/2, 2y, p).  x' y' + p' = (x/2)(2y) + p = xy + p = ab.

b) Részleges helyesség: Amikor az algoritmus véget ér, x = 0, tehát p = ab.

c) Az algoritmus véges: Legyenek {xn} és {yn} a változók egymás utáni értekeinek sorozatai. Észre­ vesszük, hogy az {xn} szigorúan csökkenő sorozat. Ebből következik, hogy véges számú lépés után x = 0.

2.4.2. Az algoritmus végrehajtásához szükséges idő A végrehajtáshoz szükséges idő az algoritmus „bonyolultságát” fejezi ki. Itt a bonyolultságnak semmi köze az algoritmus hosszához, illetve a szerkezetéhez. Amikor meg szeretnénk határozni az algoritmus végrehajtásához szükséges időt, nem mérünk pontos időt, hiszen ez függ a számítógéptől, a programozási nyelvtől stb. Szükségünk van mégis egy „mértékegységre”, ami algoritmuson­ ként változhat. Megnevezünk egy bizonyos műveletet, amit az adott algoritmus valamelyik ciklusában adott számszor végrehajt, és ennek számosságával mér­ jük az algoritmus teljesítményét. Például, ha egy sorozatban meg kell keresnünk az adott X értéket, akkor a művelet az összehasonlítás lesz. Ha össze kell szo­ roznunk két mátrixot, akkor két valós szám szorzása (vagy szorzások és össze­ adások száma) lesz a mértékegység. Ha két nagyon nagy számot kell összead­ nunk, akkor két számjegy feldolgozása lesz az elemi művelet. Egy algoritmusnak az adott feladatot nemcsak egyetlen bemeneti adathalmaz esetében kell megoldania, hanem az összes lehetséges bemenetre. Ezeknek a be­ meneteknek (általában) ismert a számosságuk (illetve a határok, amelyek között ez mozog). Az alapműveletet általában minden adatra elvégezzük, és így a be­ menet mérete megadja a végrehajtások számát is. Például, ha egy n elemű soro­ zatban meg kell keresnünk egy bizonyos tulajdonsággal rendelkező elemet, ak­ kor számíthatunk arra, hogy az algoritmus n összehasonlítást fog elvégezni, ha össze kell szoroznunk két mátrixot, akkor a műveletek számát a két mátrix mé ­ retének függvényében számoljuk. Ha két nagyon nagy számot kell összeadnunk,

40

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

akkor a két szám számjegyeinek száma lesz a méret. A gráfelméleti fela datok­ ban a csomópontok száma vagy az élek száma jöhet számításba, esetleg mind a kettő. Ugyanakkor vegyük észre, hogy a bemeneti adatok tulajdonságainak függvé­ nyében előfordulhat, hogy az algoritmus változó mennyiségű idő alatt hajtódik végre. Például a keresés leállhat az első összehasonlítás után, de ha a keresett elem nincs a sorozatban, akkor n összehasonlítást végzünk. Tehát kifejezhetnénk a végrehajtási időt a végrehajtási idők átlagával, de ez nem biztos, hogy túl hasznos lenne, mivel a gyakorlatban elfordul, hogy bizo­ nyos tulajdonságú bemeneti adatok gyakoribbak mint mások. Ha ismerjük, vagy közelítőleg fel tudjuk becsülni a különböző tulajdonságú bemenetek előfordulá­ sának valószínűségét, akkor az átlagos végrehajtási idő jobban kifejezi az algo­ ritmus teljesítményét. Példa ■ Legyen S egy n elemű, egész számokból álló sorozat. Keressük meg az X adott számmal egyenlő elem indexét a sorozatban. Ha X nincs a sorozatban, az eredmény legyen 0. Algoritmus Keres(n,S,X,i): { bemeneti adatok: n, S, X; kimeneti adat: i } i  1 Amíg (i  n) és (Si  X) végezd el: i  i + 1 vége(amíg) Ha i > n akkor i  0 vége(ha) Vége(algoritmus)

Alapművelet ■ A sorozat egy elemének összehasonlítása X-szel. Átlagos végrehajtási idő ■ A bemenetek különbözőségét X előfordulásának pozíciója fejezi ki. Ha X megtalálható a sorozatban, ez lehet az első, a második, ..., az n-edik elem. Tehát összesen n + 1 eset lehetséges. Minden i = 1, 2, ..., n-re jelöljük Bi-vel azt a bemeneti adathalmazt, amelyben X egyenlő a sorozat i-edik elemével és Bn+1-nel azt a bemenetet, amelyre X nincs a sorozatban. Legyen továbbá t(B) az összehasonlítások száma a B bemenet esetében. Ha i = 1, 2, ..., n, akkor t(Bi) = i, és ha i = n+1, akkor t(Bn+1) = n. Jelöljük q-val annak a valószínűségét, hogy X előfordul a sorozatban. Feltételezzük, hogy X bármely helyen előfordulhat ugyanazzal a valószínűséggel. Tehát, ha i = 1, 2, ..., n, akkor p(Bi) = q/n és p(Bn+1) = 1 – q. n 1

T(n) =

 i 1

n

p( Bi )t ( Bi ) =

 i 1

q q i  (1  q )n = n n

n

 i  (1  q)n = i 1

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

=

41

q n( n  1) ( n  1)  (1  q) n = q  (1  q )n. n 2 2

Ha X megtalálható a sorozatban, akkor q = 1 és T(n) = (n + 1)/2, ami azt jelenti, hogy általános esetben a sorozat felét kell megvizsgálnunk. Ha q = 1/2, vagyis 50% esélyünk van arra, hogy megtaláljuk X-et, akkor T(n) = [(n + 1)/4] + n/2  3n/4, vagyis a sorozatnak körülbelül 3/4-dét fogjuk megvizsgálni. A legrosszabb eset ■ Ha X a sorozat utolsó elemével egyenlő vagy X nincs a so­ rozatban, T(n) = max{t(Bi): i = 1, 2, …, n + 1} = n. Tehát, ha n-nel jelöljük a bemenet méretét, a végrehajtás idejét n függvénye­ ként fejezzük ki. Ideális körülmények között meghatározható egy matematikai képlet, amely kifejezi az algoritmus végrehajtásához szükséges T(n) időt, ahol n a bemeneti adatok mérete. Sajnos, általában ez nem lehetséges. Ezért az esetek túlnyomó többségében arra kell szorítkoznunk, hogy a végrehajtási idő nagyságrendjét határozzuk meg. Ilyenkor, keresünk egy f(n) függvényt, amelynek segítségével kifejezzük az idő nagyságrendjét: T(n) = O(f(n)), ha T(n) és f(n) aránya egy valós szám, ami­ kor n  . Bevezetünk egy további egyszerűsítést. Bennünket igazán a futási idő növe­ kedési sebessége vagy növekedési rendje érdekel, ezért a kiszámított képletnek csak a fő tagját vesszük figyelembe (például, ha a képlet an2 + bn + c, csak az an2 tagot tartjuk meg), mivel az alacsonyabb rendű tagok nagy n-re kevésbé lé­ nyegesek. Szintén figyelmen kívül hagyjuk a fő tag állandó együtthatóját, mivel a nagy bemenetekre jellemző számítási hatékonyság meghatározásában az állandó tényezők kevésbé fontosak, mint a növekedés sebessége. Ezt a növekedési rendet a (n) függvénnyel jelöljük. Általában akkor tartunk egy algoritmust hatékonyabbnak egy másiknál, ha legrosszabb futási idejének alacsonyabb a növekedési rendje. Az állandó ténye­ zők és alacsonyabb rendű tagok miatt ez az értékelés hibás lehet kis bemenetre. Elég nagy bemenetekre azonban például egy (n2) algoritmus gyorsabban fut a legrosszabb esetben, mint egy (n3) algoritmus. Az algoritmus futási idejének növekedési rendje, (sebessége) az algoritmus hatékonyságának egyszerű leírását adja, valamint lehetővé teszi a szóba jövő al­ goritmusok relatív teljesítményének összehasonlítását is. Bár néha pontosan meg tudjuk határozni az algoritmus futási idejét, általában nem éri meg az erő ­ feszítést ez a különleges pontosság. Elég nagy bemeneti adatokra a pontos futási idő multiplikatív állandóinak és alacsonyabb rendű tagjainak a hatása eltörpül a futási idő nagyságrendjéhez képest.

42

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Ha a bemenet mérete elég nagy, akkor az algoritmus futási idejének csak a nagyságrendje lényeges, ezért az algoritmusnak az aszimptotikus hatékonyságát vizsgáljuk. Ekkor csak azzal foglalkozunk, hogy a bemenet növekedésével mi­ ként növekszik az algoritmus futási ideje ha n  . Általában az aszimptotiku­ san hatékonyabb algoritmus lesz a legjobb választás, kivéve a kis bemenetek esetét. Egy adott g(n) függvény esetén (g(n))-nel jelöljük a függvényeknek azt a halmazát, amelyre (g(n)) = { f(n) : létezik c1, c2 és n0 pozitív állandó úgy, hogy 0  c1g(n)  f(n)  c2g(n) bármely n  n0 esetén } Más szóval az f(n) függvény hozzátartozik a (g(n)) halmazhoz, ha léteznek c1 és c2 pozitív állandók úgy, hogy f(n) – elég nagy n-re – „beszorítható” c1g(n) és c2g(n) közé. Bár (g(n)) egy halmaz, mégis úgy írjuk, hogy „f(n) = (g(n))”, ha azt akarjuk jelezni, hogy f(n) eleme (g(n))-nek, azaz „f(n)  (g(n))”. Más szóval, minden n  n0 esetén az f(n) függvény – egy állandó szorzóté­ nyezőtől eltekintve – egyenlő g(n)-nel. Ekkor azt mondjuk, hogy g(n) aszimpto­ tikusan éles korlátja f(n)-nek. A formális definíció segítségével bizonyíthatjuk, hogy 6n3  (n2). Indirekt bizonyítást használva tegyük fel, hogy létezik c2 és n0 úgy, hogy 6n3  c2n2 min­ den n  n0 esetén. Ekkor azonban n  c2/6, ami lehetetlen tetszőleges nagy n ese­ tén, hiszen c2 állandó. Mivel bármely állandó egy nulla fokú polinom, ezért bármelyik konstans függvényre fennáll a (n0) vagy (1) becslés. Az utóbbi jelölés kissé pontatlan, hiszen nem nyilvánvaló, hogy ekkor mely változó tart a végtelenhez. Gyakran fogjuk használni a (1) jelölést akár állandóra, akár egy adott változóra nézve konstans függvényre is. A -jelölés aszimptotikus alsó és felső korlátot ad a függvényre. Amikor csak az aszimptotikus felső korlát jön szóba, akkor az O-jelölést használjuk. Egy adott g(n) függvény esetén O(g(n))-nel jelöljük a függvényeknek azt a halmazát, amelyre O(g(n)) = { f(n) : létezik c és n0 pozitív állandó úgy, hogy 0  f(n)  cg(n) bármely n  n0 esetén }. Ha f(n) eleme O(g(n))-nek, akkor azt írjuk, hogy f(n) = O(g(n)). Figyeljük meg, hogy f(n) = (g(n)) maga után vonja f(n) = O(g(n)) teljesülését. Ha f(n) = O(g(n) és g(n) = O(f(n)) viszont maga után vonja, hogy f(n) = (g(n)). A hal­ mazelméletben szokásos jelöléseket használva: (g(n))  O(g(n)). Abból, hogy bármely másodfokú an2 + bn + c, a > 0 függvény benne van (n2)-ben, követ­ kezik, hogy az ilyen másodfokú függvények benne vannak O(n2)-ben. Meglepő lehet, hogy bármely elsőfokú an + b alakú függvény is benne van O(n2)-ben.

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

43

Az O-jelölés használatával gyakran úgy is meg tudjuk határozni az algorit­ mus futási idejét, hogy pusztán az algoritmus egészének a szerkezetét vizsgáljuk. Mivel az O-jelölés egy felső korlátot határoz meg, ezért amikor egy algorit­ mus legrosszabb futási idejére használjuk, akkor ezzel minden bemenet eseté­ ben érvényes felső korlátot adunk az algoritmus futási idejére.

2.4.3. Az algoritmus által feldolgozott adatok számára szükséges memória mérete Az algoritmusok által feldolgozott adatok számára lefoglalt memória mérete ugyanúgy sajátos tulajdonság, mint a végrehajtási idő. Sok esetben a feladat meg­ oldását biztosító program, a bemeneti és kimeneti adatokon kívül, munka közben ideiglenesen létrehozott adatszerkezetekkel is dolgozik. Ha egy rendező algorit­ mus a rendezés közben csak konstans méretű plusz memóriát vesz igénybe, azt mondjuk, hogy helyben rendez. Összehasonlítva ezt egy olyan algoritmussal, amely létrehoz egy másolatot a rendezendő, vagy rendezett sorozatról, termé­ szetesen az előbbit hatékonyabbnak tekintjük a tárfelhasználás szempontjából.

2.4.4. Algoritmusok egyszerűsége Algoritmusok elemzésekor az egyszerűség nem feltétlenül mérvadó tulajdonság. Ha egy algoritmust a legkézenfekvőbb ötlet alapján tervezünk meg (brute force), sokszor fölöslegesen terheljük a számítógép erőforrásait. Ugyanakkor egy egyszerű algoritmust könnyebben írunk meg, olvashatóbb, könnyebben javítjuk és könnyebben bizonyítjuk a helyességét.

2.4.5. Algoritmusok optimalitása Feltétezzük, hogy egy adott feladatnak megvalósítottuk az algoritmusát, és ki­ számítottuk a végrehajtásához szükséges T(n) időt. A következőkben azon fo­ gunk elgondolkozni, hogy vajon létezik-e jobb (kisebb végrehajtási idejű) algo­ ritmus, mint amit megterveztünk? Egy algoritmus optimalitásának bizonyítása nehéz feladat, elsősorban azért, mert számításba kell vennünk minden lehetsé­ ges algoritmust, és ki kell mutatnunk, hogy ezeknek a futási ideje nagyobb mint a tárgyalt algoritmusé. Feltételezzük, hogy egy adott feladat megoldására talált minden algoritmus legalább T(n) időt igényel. Ha egy időegység alatt az algoritmus egy műveletet végez, akkor a T(n) az elvégzendő műveletek számát jelenti. Egy algoritmus akkor optimális, az adott algoritmushalmazon belül, ha legrosszabb esetben T(n) műveletet végez.

44

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

A következőkben bebizonyítjuk egy egyszerű algoritmus optimalitását. Példa ■ Határozzuk meg egy n elemű sorozat minimumát! Algoritmus Minimum(n,a,min): min  a1 Minden i=2,n végezd el: Ha ai < min akkor min  ai vége(ha) vége(minden) Vége(algoritmus)

Ebben az algoritmusban pontosan n – 1 összehasonlítást végzünk az a = (a1, a2, ..., an) sorozat elemeivel. Állítás ■ A fenti algoritmus optimális. Bizonyítás ■ Indukciót használva kimutatjuk, hogy bármely, összehasonlítások­ ra épülő algoritmus legkevesebb n – 1 műveletet végez. [9] Ha n =1, nem végzünk egyetlen összehasonlítást sem. Feltételezzük, hogy bármely algoritmus, amely megoldja a feladatot n szám esetében, legkevesebb n – 1 összehasonlítást végez, és tárgyaljuk azt a feladatot, amely n + 1 szám minimumát kéri. Legyen az első összehasonlítás, amely (anél­ kül, hogy vesztenénk az általánosságból) összehasonlítja a1-t a2-vel, és megálla­ pítja, hogy a1 < a2. A továbbiakban meg kell oldanunk a feladatot az (a1, a3, ..., an+1) feladat ese­ tében. De ezt a feladatot (n eleme van) n – 1 összehasonlítással végeztük, tehát az n + 1 elemű sorozat minimumát összesen n összehasonlítás után kaptuk meg.

2.4.6. Algoritmusok létezése Ez a kérdés a fentieknél is kényesebb, mivel előbb matematikai rigurozítással definiálnunk kellene az algoritmus fogalmát. A következőkben nem teszünk mást mint, hogy bemutatunk néhány eredményt (bizonyítások nélkül). A részle­ tesebb tanulmányozás külön ismeretkörhöz tartozik! Azokat a feladatokat, amelyeket nem tudunk egy algoritmussal megoldani megoldhatatlanoknak (eldönthetetleneknek) nevezzük. Az algoritmus fogalmá­ nak matematikai definíciója lehetővé tette néhány ilyen feladat felderítését: [9]

a) A programok befejeződésének kérdése: tetszőleges program és tetszőleges bemeneti adatok esetében döntsük el, hogy a program befejeződik-e!

b) A programok befejeződésének kérdése (változat): adott program és tetszőle­ ges bemeneti adatok esetében döntsük el, hogy a program befejeződik-e!

45

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

c) A programok ekvivalenciájának kérdése: döntsük el bármely két program esetében, hogy ekvivalensek-e (azonos bemeneti adatok mellett azonos ki­ meneti adatokat produkálnak-e).

2.5. Megoldott feladatok 2.5.1. Felcserélés Adott két egész típusú érték. Cseréljük fel ezeket, és írjuk ki a két értéket az új sorrendben! Elemzés ■ Végezzük el a következő egyszerű kísérletet: adott két pohár, az egyikben ásványvíz, a másikban sima víz van. Fel szeretnénk cserélni a két po­ hár tartalmát. Mit tehetünk? Természetesen, felhasználunk a művelet elvégzése érdekében egy harmadik poharat. Ugyanígy, ha két változó értékét kell fölcse­ rélnünk, szükségünk lesz egy segédváltozóra, amelyben ideiglenesen megőriz­ zük annak a változónak értékét, amely az a  b utasítás következtében elvesz­ tené értékét. Algoritmus Felcserél(a,b): { bemeneti és kimeneti adatok: a, b } segéd  a { a további algoritmusokban e három utasítás jelölése: a ↔ b lesz } a  b b  segéd Vége(algoritmus)

A fenti algoritmusban egy segédváltozót (segéd) használtunk az a változó régi értékének ideiglenes tárolására, ily módon zökkenőmentesen elvégezhető a felcserélés.

2.5.2. Maximumérték Írjuk ki három, páronként különböző valós szám közül a legnagyobbat! Elemzés ■ Ahhoz, hogy megtaláljuk a három szám közül a legnagyobbat, ter­ mészetesen teljesülnie kell a feltételnek, hogy ez a szám nagyobb, mint a másik kettő. Mivel bármelyik szám lehet a legnagyobb, a három számot rendre össze­ hasonlítjuk a másik kettővel. Mivel a feladat csak a legnagyobb kiíratását kéri, íme a megoldás segédvál­ tozó használata nélkül: Algoritmus Maximum(a,b,c): Ha (a > b) és (a > c) akkor Ki: 'A legnagyobb: ', a vége(ha)

{ bemeneti adatok: a, b, c }

46

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

Ha (b > c) és (b > a) akkor Ki: 'A legnagyobb: ', b vége(ha) Ha (c > a) és (c > b) akkor Ki: 'A legnagyobb: ', c vége(ha) Vége(algoritmus)

Ez a megoldás három elágazási struktúrát használ. Több észrevételünk is van. Például, feltevődik a kérdés: mi történik, ha a = b = c? A következő változatban bevezetjük a max segédváltozót, amely minden lé­ pésben a két összehasonlított szám közül a nagyobbik értéket fogja megtartani. Így a max-ban tárolt értéket felhasználhatjuk a későbbi feldolgozások során. A max kezdőértéke az a változó értéke lesz, de ez megváltozhat a számok értékei­ nek függvényében. Így, az algoritmus végrehajtása során nem kötelező kiírnunk a legnagyobb értéket, hanem a max változóban (paraméterben5) visszatérítjük. Algoritmus Maximum_másként(a,b,c,max): max  a { bemeneti adatok: a, b, c, kimeneti adat: max } Ha max < b akkor max  b vége(ha) Ha max < c akkor max  c vége(ha) Vége(algoritmus)

Figyeljük meg, hogy mennyire leegyszerűsödött az elágazási feltétel, vala­ mint azt is, hogy ez a megoldás csupán két elágazási struktúrát használ! Mi tör­ ténik, ha a = b = c? Észrevesszük, hogy ha például a a legnagyobb, a másik két elágazást fölösle­ gesen fogjuk elvégezni. De ez igaz akkor is, ha b a legnagyobb vagy c. Érdemes keresni egy olyan megoldást, amely azonnal leállítja az algoritmust, mihelyt biztonságosan eldőlt, melyik szám a legnagyobb. Algoritmus Maximum_harmadik_változata(a,b,c,max): Ha a > b akkor { bemeneti adatok: a, b, c, kimeneti adat: max } Ha a > c akkor max  a különben max  c vége(ha) különben

A paraméter fogalmával és a paraméterátadások típusaival az 5. fejezetben ismerkedünk meg. 5

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

47

Ha b > a akkor Ha b > c akkor max  b különben max  c vége(ha) vége(ha) vége(ha) Vége(algoritmus)

Az algoritmus tömörebb lesz, ha a feltételeket megfelelően csoportosítjuk: Algoritmus Maximum_negyedik_változata: Ha (a > b) és (a > c) akkor { bemeneti adatok: a, b, c, kimeneti adat: max } max  a különben Ha (b > a) és (b > c) akkor max  b különben max  c vége(ha) vége(ha) Vége(algoritmus)

Ez a megoldás egyetlen elágazást tartalmaz, melynek különben ága viszont magában foglal egy újabb elágazást. Első látásra nem írtunk egyszerűbb algorit­ must, de ha követjük esetenként a szükséges összehasonlítások számát, meg fo­ gunk győződni, hogy ez az algoritmus átlagosan kevesebb művelet elvégzését igényli, mint az első változat: ha a a legnagyobb, az első elágazás különben ága már nem kerül végrehajtásra, tehát egyetlen összehasonlítást kell elvégezni. Ha b vagy c a legnagyobb, akkor csak az első két elágazást kell végrehajtani. Ha a fe­ ladat adatainak sajátossága lehetővé teszi, hogy a feltételek megvalósulási való­ színűségét előrelássuk, akkor az elágazásokat előnyösen lehet egymásba ágyaz­ ni, éspedig úgy, hogy a legvalószínűbb feltétel kerül a legelső elágazásba stb. Befejezésül megjegyezzük, hogy két szám maximuma meghatározható egyetlen összehasonlítás nélkül is: max  (a + b + a - b)/2

2.5.3. Elnökválasztás Egy elnökválasztás alkalmával több jelölt indul. Az országban legalább 1 millió személy fog szavazni. A szavazatokat egy állomány 6 tartalmazza. Ezek egész számok, amelyekkel a jelölteket kódolták. Tehát az állomány egy-egy jelölt kódját annyiszor tartalmazza, ahányan szavaztak az illető jelöltre. Nem ismerjük 6

Az állomány egy külső adattároló, amely azonos típusú adatokat tartalmaz.

48

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

sem a jelöltek számát (előfordulhat, hogy több mint 1 millió jelölt van), sem a szavazatok számát. Állapítsuk meg a győztest, ha van ilyen! Elemzés ■ Tehát: adva van nagyon sok nagy szám (ezeket nem lehet egy tömb­ ben tárolni). Meg kell találnunk (ha létezik!), azt a számot, amely legalább a számok fele plusz 1-szer előfordul az adott számok között. [24] Észrevesszük, hogy például, ha a bemeneti adatok között egymás után k-szor ugyanaz az x szám fordul elő és utána k darab, x-től különböző szám követke­ zik, akkor ezt a 2k számból álló halmazát a bemeneti adatoknak a továbbiakban mellőzhetjük, mivel nem befolyásolják a végeredményt (mivel x ebben az adat­ szegmensben – egymás utáni elemek halmazában – csak a számok felével egyenlő számban fordul elő). Algoritmus Szavazás: Be: szám jelölt  szám hány  1

{ az első szavazat } { feltételezzük, hogy legalább fele plusz egyszer előfordul } { most fordult elő először } { nem ismerjük a szavazatok pontos számát } Amíg nincs vége az állománynak végezd el: Be: szám { a következő szavazat } Ha szám = jelölt akkor { ha ez azonos a megjegyzett kóddal } hány  hány + 1 { növeljük előfordulásainak számát } különben { ha ez nem azonos a megjegyzett kóddal } Ha hány = 0 akkor { véget ért egy szegmens feldolgozása, } { amelyen belül, nincs kért tulajdonságú elem } jelölt  szám { új jelölt } hány  1 { most fordult elő először } különben hány  hány - 1 { csökkentjük előfordulásainak számát } vége(ha) vége(ha) vége(amíg) { jelölt tartalmazza annak a kódját, akivel kiléptünk az utolsó } { adatszegmens feldolgozásakor, ha van nyertes, az csak ez lehet } hány  0 { megszámoljuk, hogy a jelölt hány szavazatot kapott } n  0 { megszámoljuk, hogy összesen hány szavazat létezik } Amíg nincs vége az állománynak végezd el: Be: szám n  n + 1 Ha szám = jelölt akkor hány  hány + 1 vége(ha) vége(amíg) Ha hány > [n/2] akkor Ki: 'Nyertes a ', jelölt, '!'

{ ellenőrizzük a feltételt }

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

49

különben Ki: 'Sajnos nem nyert senki!' vége(ha) Vége(algoritmus)

2.6. Kitűzött feladatok7 1. Adott két egész szám, cseréljük fel őket segédváltozó használata nélkül és ír­ juk ki az új sorrendben! Útmutatás ■ Nem elég fordított sorrendben kiírni a két számot. Valóban fel kell őket cserélni, hiszen lehet, hogy a későbbiekben a felcserélt sorrendben lesznek szükségesek. Ha nem szabad segédváltozót használni, alkalmazhatunk összeadást és kivonást, vagy a xor logikai műveletet. 2. Olvassunk be a billentyűzetről három egész számot. Írjuk ki őket növekvő sorrendben! Útmutatás ■ Nem kell rendeznünk a számokat, csak kiírnunk a kért sorrend­ ben. Egymás utáni elágazások helyett, egymásba ágyazottakat alkalmazunk. 3. Számítsuk ki a következő függvény értékét egy adott x (valós szám) pontban!  2 x , ha  2  x  0  f ( x )   x  x , ha 0  x  2  10 , ha x  2  Útmutatás ■ Vigyázat, vannak értékek, amelyekre nem definiált a függvény. Ezekben az esetekben írjunk ki megfelelő üzenetet! 4. Határozzuk meg három adott valós szám minimumát és abszolút értékeinek maximumát! 5. Adott négy valós szám: a, b, c, d. Írjuk ki a négy számot az adott sorrendben majd, ha d  0, az a, c, b, d sorrendben, egyébként az a, b, d, c sorrendben! 6. Adott három szigorúan pozitív valós szám: a, b, c. Képezhetik-e ezek a szá­ mok egy háromszög oldalait? Ha igen, határozzuk meg és írjuk ki a három­ szögbe írt, illetve a háromszög köré írt kör sugarát! Ha nem, írjunk ki megfe­ lelő üzenetet. 7. Határozzuk meg egy egyenes körhenger palástfelszínét, teljes felszínét, vala­ mint térfogatát! Ismert a henger sugara és magassága. 7

Tervezzünk algoritmusokat és kódoljuk ezeket programok formájában!

50

2. AZ ALGORITMUSOK ÁBRÁZOLÁSA

8. Egy téglalap alakú kertet drótkerítéssel szeretnénk bekeríteni. Ismerjük a kert hosszát, szélességét, valamint a rendelkezésre álló drót hosszát. Számít­ suk ki mennyi drót marad a kert bekerítése után, illetve mennyi drót szüksé­ ges még a kert bekerítéséhez. 9. Számítsuk ki a következő kifejezések értékét:

3x  5 , ha x  5  f ( x)   10 , ha 5  x  10 9 x  1 , ha x  10  ahol f valós függvény.

d  3b , ha a  c  2d és b  0  E ( a, b, c, d )  d  3b , ha a  c  2d és b  0  4 , egyébként  ahol a, b, c és d valós számok. 10. Írjunk algoritmust, amely kiszámítja egy személy ideális testsúlyát! Útmutatás

, férfiak esetében S  50  0,75( M  150)  ( É  20) / 40 S  [50  0,75( M  150)  ( É  20) / 40]  0,9 , nõk esetében ahol az S az ideális testsúly (kg), M a magasság (cm) és É az életkor (évek száma). 11. Írjunk algoritmust az ax2 + bx + c valós együtthatójú másodfokú egyenlet megoldására! Tárgyaljuk mind a 8 lehetséges esetet! 12. Számítsuk ki egy háromszög területét, ha ismerjük oldalainak a hosszát! 13. Számítsuk ki hány szökőév volt/lesz két különböző évszám között! Útmutatás ■ A szökőév osztható 4-gyel és nem osztható 100-zal, vagy oszt­ ható 400-zal. Példák 1964 szökőév 1900 nem szökőév 2000 szökőév 14. Számítsuk ki, hány napot éltünk a mai nappal bezárólag!

3

LÉPÉSEK FINOMÍTÁSA

Bevezetés Bonyolultabb feladatok esetében a megfelelő algoritmus leírása nem könnyű fela­ dat. Ezért célszerű először a megoldást körvonalazni, és csak azután részletezni:  a feladat elemzése során sor kerül:  a bemeneti és kimeneti adatok megállapítására;  a megfelelő adatszerkezetek kiválasztására és megtervezésére;  a feladat követelményeinek szétválasztására;  következik a megoldási módszer megállapítása;  a megoldás lépéseinek leírása;  lépések finomítása, amíg az algoritmus hatékonysága megfelelő lesz;  ellenőrzés, programkészítés (kódolás) után: tesztelés. A lépések finomítása az algoritmus kidolgozását jelenti, amely a kezdeti váz­ lattól a végleges, kidolgozott algoritmusig vezet. Kiindulunk a feladat specifiká­ ciójából és az úgynevezett fentről lefele tartó tervezési módszert alkalmazva (lé­ pésenkénti finomítással) újabb meg újabb változatokat dolgozunk ki, amelyek eleinte még tartalmaznak bizonyos, anyanyelven leírt magyarázó sorokat. Ké­ sőbb ezeket standard utasításokra írjuk át. Így az algoritmusnak több egymás utáni változata lesz, amelyek egyre bővülnek egyik változattól a másikig. [12]

3.1. Megoldott feladatok 3.1.1. Eukleidész algoritmusa Határozzuk meg két adott természetes szám legnagyobb közös osztóját (lnko) és legkisebb közös többszörösét (lkkt) Eukleidész algoritmusával. Elemzés  a bemeneti adatok megállapítása: a és b, természetes számok;

52

3. LÉPÉSEK FINOMÍTÁSA

 kimeneti adatok: lnko és lkkt, természetes számok;  a megfelelő adatszerkezetek kiválasztása és megtervezése: nincs szükség adatszerkezetekre;  a feladat követelményeinek szétválasztása: be kell olvasnunk két természe­ tes számot, ki kell számítanunk a két természetes szám lnko-ját, valamint lkkt-ét, majd ki kell írnunk a két eredményt;  a megoldási módszer megállapítása: mivel a módszer Eukleidész óta is­ mert, megtervezzük Eukleidész algoritmusát ismételt osztásokkal. A megoldás lépéseinek leírása Algoritmus Eukleidész_1(a,b,lnko,lkkt): kiszámítjuk a és b lnko-ját { bemeneti adatok: a, b, kimeneti adatok: lnko, lkkt }

kiszámítjuk a és b lkkt-ét Vége(algoritmus)

Lépések finomítása ■ Ki kell dolgoznunk a kiszámítások módját. Előbb arra gondolunk, hogy Eukleidész algoritmusában (általában) a nagyobb számot szok­ tuk elosztani, majd arra, hogy mi is történik akkor, ha a két szám egyenlő. Te ­ hát, összehasonlítjuk a két számot. Ha a két szám egyenlő, akkor az lnko éppen az a szám lesz. Ha a kisebb mint b, elvben felcserélhetjük a két számot, hogy mindig az a-ban legyen a nagyobb érték, de ha eltekintünk a felcseréléstől, ak­ kor észrevesszük, hogy az algoritmus „huncut” módon elvégzi ezt helyettünk az első lépésében. Ezután elosztjuk a-t b-vel. Legyen az osztási maradék r. Amíg a maradék nem 0, ismételten osztjuk az osztót a maradékkal. Az utolsó osztó éppen az lnko lesz. Az lkkt-t úgy kapjuk meg, hogy a két szám szorzatát osztjuk az lnko-val. Mivel az eredeti két szám értékét az algoritmus „tönkreteszi”, szükséges elmen­ teni ezeket két segédváltozóba (x és y) ahhoz, hogy felhasználhassuk ezeket az lkkt kiszámításakor. Algoritmus Eukleidész_1(a,b,lnko,lkkt): { bemeneti adatok: a, b, kimeneti adatok: lnko, lkkt } x  a { szükségünk lesz a értékére az lkkt kiszámításakor } y  b { szükségünk lesz b értékére az lkkt kiszámításakor } r  maradék[a/b] { kiszámítjuk az első maradékot } Amíg r  0 végezd el: { amíg a maradék nem 0 } a  b { az osztandót felülírjuk az osztóval } b  r { az osztót felülírjuk a maradékkal } r  maradék[a/b] { kiszámítjuk az aktuális maradékot } vége(amíg) lnko  b { lnko egyenlő az utolsó osztó értékével } vége(ha)

53

3. LÉPÉSEK FINOMÍTÁSA

lkkt  [x*y/lnko] Vége(algoritmus)

{ felhasználjuk a és b másolatait }

Ha nem akarunk osztással dolgozni, megvalósíthatjuk az algoritmust úgy, hogy ez ismételt kivonásokkal állapítsa meg az lnko-t. Amíg a két szám külön­ bözik egymástól, a nagyobbikból kivonjuk a kisebbiket, és megőrizzük a kü­ lönbséget. Az lnko az utolsó különbség lesz. Az lkkt-t ugyanúgy számítjuk ki, mint az előző változatban. Algoritmus Eukleidész_2(a,b,lnko,lkkt): x  a { bemeneti adatok: a, b, kimeneti adatok: lnko, lkkt } y  b Amíg a  b végezd el: Ha a > b akkor a  a - b különben b  b - a vége(ha) vége(amíg) lnko  a lkkt  [x*y/lnko] Vége(algoritmus)

A két változat hatékonyságának összehasonlításához ismernünk kellene, hogy mennyi időt igényel egy osztás illetve egy kivonás végrehajtása a számító­ gépben. Az világos, hogy ha egyszerűen „műveleteket” számolnánk, akkor az első algoritmus kevesebb osztást és értékadást végez. De az osztásokat általában ismételt kivonásokkal végzi a számítógép is. Az első algoritmus ciklusmagjában három utasításunk van (egy osztás és két értékadás), míg a másodikban csak egy és ez az egy utasítás egy kivonás.

3.1.2. Prímszámok Adva van egy nullától különböző természetes szám (n). Tervezzünk algorit­ must, amely eldönti, hogy az adott szám prímszám-e vagy sem! Elemzés  a bemeneti adatok megállapítása: n természetes szám;  a kimeneti adatok megállapítása: válasz logikai érték;  nincs szükség adatszerkezetekre;  a feladat követelményeinek szétválasztása: be kell olvasnunk a természetes számot, meg kell állapítanunk, hogy prímszám-e;

54

3. LÉPÉSEK FINOMÍTÁSA

 a megoldási módszer megállapítása: az adott számot a nála kisebb számok halmazából kiválasztott számokkal osztjuk, amíg vagy találunk egy osz­ tót, vagy rájövünk, hogy a szám prím. A megoldás lépéseinek leírása Algoritmus Prím(n,válasz):

{ bemeneti adat: n, kimeneti adat: válasz }

Megállapítjuk, hogy prím-e Ha n prím akkor válasz  igaz különben válasz  hamis vége(ha) Vége(algoritmus)

Lépések finomítása ■ Ki kell dolgoznunk annak a módját, hogy megállapíthas­ suk, hogy a szám prím-e. A megoldás első változatában a prímszám definíciójá­ ból indulunk ki: egy szám akkor prím, ha pontosan két osztója van: 1 és maga a szám. Első ötletünk tehát az, hogy az algoritmus számolja meg az adott szám osztóit, elosztva ezt sorban minden számmal 1-től n-ig. A döntésnek megfelelő üzenetet az osztók száma alapján írjuk ki. Algoritmus Prím_1(n,válasz): { bemeneti adat: n, kimeneti adat: válasz } osztók_száma  0 Minden osztó=1,n végezd el: Ha maradék[n/osztó] = 0 akkor osztók_száma  osztók_száma + 1 vége(ha) vége(minden) Ha osztók_száma = 2 akkor { vagy: válasz  osztók_száma = 2} válasz  igaz különben válasz  hamis vége(ha) Vége(algoritmus)

További változatok ■ Vegyük észre, hogy az osztások száma fölöslegesen nagy. Ezt a számot csökkenteni lehet, mivel ha 2 és n/2 között nincs egyetlen osztó sem, akkor biztos, hogy nincs n/2 és n között sem, tehát eldönthető, hogy a szám prím. De, ha tovább gondolkozunk, arra is rájövünk, hogy elég a szám négyzetgyökéig keresni a lehetséges osztót, hiszen ahogy az osztó értékei nőnek a négyzetgyökig, az [n/osztó] hányados értékei csökkennek szintén a négyzet­ gyök értékéig. Ha egy, a négyzetgyöknél nagyobb osztóval elosztjuk az adott

55

3. LÉPÉSEK FINOMÍTÁSA

számot, hányadosként egy kisebb osztót kapunk, amit megtaláltunk volna előbb, ha létezett volna ilyen. Algoritmus Prím_2(n,válasz): válasz  igaz

{ bemeneti adat: n, kimeneti adat: válasz }

Minden osztó=2,[ n ] végezd el: Ha maradék[n/osztó] = 0 akkor válasz  hamis vége(ha) vége(minden) Vége(algoritmus)

Úgy gondolhatnánk, hogy ezzel vége a lehetséges javításoknak. De észre­ vesszük, hogy adhatunk több munkát a logikai változónknak, hiszen a ciklus le­ állhatna abban a pillanatban amikor találtunk egy osztót és a prím nevű logikai változó hamissá vált. Ahhoz, hogy leállhassunk amikor kiderült, hogy a szám nem prím, le kell mondanunk a Minden típusú ciklusról és egy Amíg vagy Is­ mételd típusú ciklussal kell helyettesítenünk azt. Mivel n nem változik a ciklus magjában, elkerüljük a négyzetgyök kiszámíttatását újra meg újra, a ciklus min ­ den lépésében. Algoritmus Prím_3(n,válasz): { bemeneti adat: n, kimeneti adat: válasz } prím  igaz { segédváltozó: prím, (az Amíg feltételében nem használhatjuk a válasz-t } osztó  2 négyzetgyök  [ n ] Amíg prím és (osztó  négyzetgyök) végezd el: Ha maradék[n/osztó] = 0 akkor prím  hamis különben osztó  osztó + 1 vége(ha) vége(amíg) válasz  prím Vége(algoritmus)

A továbbiakban észrevesszük, hogy a páros számok mind oszthatók 2-vel, és a 2 kivételével nem prímek. De ha „megszabadultunk” a páros számok fölösle­ ges vizsgálatától, akkor a megmaradtakat fölösleges páros számokkal osztani, hiszen páratlan számnak csak páratlan osztói vannak. Ahhoz, hogy az algoritmusunk tökéletesen működjön akkor is, ha n = 1, a következőképpen járunk el: Algoritmus Prím_4(n,válasz): Ha n = 1 akkor

{ bemeneti adat: n, kimeneti adat: válasz }

56

3. LÉPÉSEK FINOMÍTÁSA

prím  hamis különben Ha n páros akkor prím  n = 2 különben prím  igaz osztó  3 négyzetgyök  [ n ] Amíg prím és (osztó  négyzetgyök) végezd el: Ha maradék[n/osztó] = 0 akkor prím  hamis különben osztó  osztó + 2 vége(ha) vége(amíg) vége(ha) vége(ha) válasz  prím Vége(algoritmus)

Ha ebben az algoritmusban felhasználjuk a matematikából ismert tulajdonsá­ got, éspedig azt, hogy minden 5-nél nagyobb prímszám 6k  1 alakú, akkor a vizsgálandó számok száma tovább csökkenthető: ... Ha n páros akkor prím  n = 2 különben Ha n ≤ 5 akkor prím  igaz különben Ha (maradék[(n - 1)/6]  0) és (maradék[(n + 1)/6]  0) akkor prím  hamis különben ...

Mielőtt lezárnánk a prímszámokhoz kapcsolódó feladatok és algoritmusok tárgyalását, megemlítjük a Wilson tételére épülő meglepő algoritmust, amellyel egy adott (n > 1) számról megállapíthatjuk, hogy prímszám-e. Ha az (n – 1)! + 1-et n maradék nélkül osztja, akkor n prímszám. Algoritmus Prím_5(n,válasz): { bemeneti adat: n, kimeneti adat: válasz } válasz  maradék[((n - 1)! + 1)/n] = 0 Vége(algoritmus)

57

3. LÉPÉSEK FINOMÍTÁSA

Az egyetlen gond, ami ennek az algoritmusnak a kódolásakor felmerül az, hogy a faktoriális rendkívül gyorsan nő, tehát szükség lesz az aritmetikai művele­ teket a „nagyszámok” aritmetikájának megfelelő algoritmusokkal megvalósítani. A továbbiakban tételezzük fel, hogy sok szám esetében szeretnénk ellenőriz­ ni, hogy melyek prímszámok és melyek nem. Ebben az esetben, ismerve a le­ hetséges legnagyobbat, előbb érdemes minden prímszámot generálni az illető határérték négyzetgyökéig! Lássuk, hogyan generálhatjuk egy adott számnál kisebb összes prímszámot? Íme egy kevésbé hatékony algoritmus, amely generálja a vizsgálandó számokat és ezekre alkalmazza az algoritmust, amely ellenőrzi, hogy egy adott szám prím-e: Algoritmus Prímek_1: Be: határ Ki: 2 n  3 Amíg n < határ végezd el: prím  igaz osztó  3

{ határ-nál kisebb számokat fogunk vizsgálni }

négyzetgyök  [ n ] Amíg (osztó  négyzetgyök) és prím végezd el: Ha maradék[n/osztó] = 0 akkor prím  hamis különben osztó  osztó + 2 vége(ha) vége(amíg) Ha prím akkor Ki: n vége(ha) n  n + 2 vége(amíg) Vége(algoritmus)

Eratoszthenész szitája Ismeretes, hogy az ókorban Eratoszthenész talált egy rendkívül eredményes módszert a prímszámok „generálására”. Felírt minden számot 2 és 3000 között egy papiruszra, megjegyezte a 2-t, mint prímszámot, majd kihúzott minden pá­ ros számot. Most megjegyezte az első számot, amelyet még nem húzott ki (ez a 3-as) és kihúzott minden 3-mal osztható számot. Természetesen ez a műveletsor átültethető egy algoritmusba, de gondoskodnunk kell a számok előzetes tárolá­ sáról, hogy megjegyezhessük, melyek azok, amelyeket kihúztunk, illetve me­

58

3. LÉPÉSEK FINOMÍTÁSA

lyek azok, amelyeket nem. Ezt a tárolást egy logikai tömbbel valósítjuk meg, amelynek az indexei jelképezik a számokat és az értékei azt, hogy prímek-e vagy sem. Algoritmus Prímek_2: Be: határ { határ-nál kisebb számokat fogunk vizsgálni } { segédtömb: prím, ha prími értéke igaz, akkor a szám még nem volt kihúzva } Minden i=2,határ végezd el: prími  igaz { még nincs kihúzva egy szám sem } vége(minden) Minden i=2,[határ/2] végezd el: Ha prími akkor { ha még nincs kihúzva } k  2 * i { az első kihúzandó szám (többszörös) } Amíg k ≤ határ végezd el: prímk  hamis { kihúzzuk a számot } k  k + i { a következő kihúzandó szám } vége(amíg) vége(ha) vége(minden) Minden i=2,határ végezd el: Ha prími akkor Ki: i { a nem kihúzott számok prímszámok } vége(ha) vége(minden) Vége(algoritmus)

Egy másik érdekes algoritmus fölhasználja az 5-nél nagyobb prímszámok említett tulajdonságát, (6k  1 alakúak, k = 1, 2, ...) vigyázva arra, hogy a tu­ lajdonság fordítva nem igaz, vagyis nem minden 6k  1 alakú szám prímszám. Az algoritmus mindenképpen figyelemreméltó, hiszen egy rögzített határon be­ lül, az eddigi algoritmusokban vizsgált számoknál jóval kevesebbet fog feldol­ gozni. Algoritmus Prímek_3: Be: határ { a legnagyobb szám, amelyet majd megvizsgálunk: 6 * határ + 1 } { a p sorozatban generáljuk a prímszámokat } p1  2 { az algoritmus csak az 5-nél nagyobb számokat vizsgálja } p2  3 psz  2 { az eddig „generált” prímszámok száma } Minden k=1,határ végezd el: x  6 * k - 1 { x lehet, hogy prímszám } i  1 { x-et osztjuk az eddig generált prímszámokkal } Amíg (i ≤ psz) és (maradék[x/pi]  0) végezd el: i  i + 1 vége(amíg)

59

3. LÉPÉSEK FINOMÍTÁSA

Ha i > psz akkor { ha nem találtunk osztót  x prímszám } psz  psz + 1 { nő a prímszámok száma } ppsz  x { betesszük a prímszámok sorozatába } vége(ha) y  6 * k + 1 { y lehet, hogy prímszám } i  1 Amíg (i ≤ psz) és (maradék[y/pi]  0) végezd el: i  i + 1 vége(amíg) Ha i > psz akkor psz  psz + 1 ppsz  y vége(ha) vége(minden) Vége(algoritmus)

3.1.3. Fibonacci-számok Generáljuk és írjuk ki az első n Fibonacci-számot! Elemzés  a bemeneti adatok megállapítása: n természetes szám;  a kimeneti adatok megállapítása: n darab Fibonacci-szám;  nincs szükség adatszerkezetre, mivel a feladat nem kéri a Fibonacci-szá­ mok megőrzését sorozat formájában, csak a kiírásukat;  a feladat követelményeinek szétválasztása: be kell olvasnunk egy termé ­ szetes számot, ki kell számítanunk és ki kell írnunk sorban a Fibonacciszámok értékét. A Fibonacci-számokat az alábbi rekurzív összefüggéssel definiáljuk: F0 = 0, F1 = 1, Fi = Fi–1 + Fi–2, ahol i  2

(*)

Minden Fibonacci-szám tehát a megelőző kettőnek az összege (kivétel az első kettő, amelyek adottak). A Fibonacci-számok sorozata: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, … A megoldás lépéseinek leírása ■ A megoldásban a (*) összefüggést alkalmaz­ zuk, amelyből kiderül, hogy az első n Fibonacci-szám generálásához elegendő három változó: a, b és c. Algoritmus Fibonacci(n): Kezdőértékadások: a, b, c Ki: a, b, c k  3 Amíg k < n végezd el:

{ bemeneti adat: n (a kiírandó számok száma) }

Egy újabb Fibonacci-szám kiszámítása (ezt a c változóban tároljuk)

60

3. LÉPÉSEK FINOMÍTÁSA

Ki: c k  k + 1 vége(amíg) Vége(algoritmus)

Lépések finomítása ■ A számsor egy új tagját (c) úgy állíthatjuk elő, hogy a már kiszámolt elemeket egyszerűen balra „toljuk” egy pozícióval. Ily módon rendre megkapjuk a és b új értékét, majd ezek ismeretében kiszámíthatjuk c új értékét. A fenti lépéseket addig ismételjük, amíg a feladat által kért számú ele­ met elő nem állítottuk! Némi módosítás után az előbbi algoritmus átalakul mivel, ha n = 1, illetve n = 2, nem kell kiírnunk három számot, csak egyet, illetve kettőt. Algoritmus Fibonacci(n): a  0 b  1 Ha n = 1 akkor Ki: a különben Ha n = 2 akkor Ki: a, b különben c  a + b Ki: a, b, c k  3 Amíg k < n végezd el: a  b b  c c  a + b Ki: c k  k + 1 vége(amíg) vége(ha) vége(ha) Vége(algoritmus)

A fenti algoritmus generálja és kiírja egyenként a Fibonacci-sorozat első n elemét, amelyeket három változó segítségével állapít meg. Lássunk egy olyan algoritmust, amely mindössze két segédváltozót használ ahhoz, hogy kiszámítsa és kiírja a Fibonacci-sorozat n elemét. Algoritmus Fibonacci(n): a  1 b  0 Minden k=1,n végezd el: Ki: b

3. LÉPÉSEK FINOMÍTÁSA

61

b  a + b a  b - a vége(minden) Vége(algoritmus)

Érdekes feladatnak bizonyul megállapítani egy adott (n > 1) számról, hogy Fibonacci-szám-e? Ha nem, bontsuk fel az adott számot egymástól különböző Fibonacci-számok összegére. [16] Generáljuk a fenti algoritmussal a Fibonacci-számokat amíg egy olyan szá­ mot nem kapunk, amely nagyobb mint az adott szám vagy egyenlő az adott számmal. Ha a c változó utolsó értéke Fibonacci-szám, az algoritmus véget ér, különben kiírjuk azt a legnagyobb Fibonacci-számot, amely kisebb vagy egyen­ lő az adott számmal (ez a b változóban áll rendelkezésünkre), majd ennek az ér­ tékét kivonjuk az adott szám értékéből. Ha b egyenlő a szám aktuális értékével, az algoritmus véget ér, különben megismételjük az ellenőrzést. Amíg n (amely­ ből minden lépésben kivonjuk a b Fibonacci-számot, amelyet kiírunk) pozitív, meghatározunk egy-egy b Fibonacci-számot, amely kisebb vagy egyenlő az adott szám aktuális értékével. A feladat egyedi felbontást kér. Ezt biztosítja az elemek növekvő sorrendben való megkeresése. Algoritmus Fibonacci_szám_e(n): { bemeneti adat: n (az adott szám) } a  0 b  1 c  a + b Amíg c < n végezd el: a  b b  c c  a + b vége(amíg) { most b a legnagyobb Fibonacci-szám, amely < n } Ha c = n akkor Ki: 'Fibonacci-szám' különben Ki: n, '=', b n  n - b Amíg n > 0 végezd el: Amíg b > n végezd el: c  b b  a a  c - b vége(amíg) Ki: '+', b n  n - b

62

3. LÉPÉSEK FINOMÍTÁSA

vége(amíg) vége(ha) Vége(algoritmus)

Érdekes alkalmazás ■ A távoli északon az eszkimók jégsátrakban laknak. Té­ len naponta ki kell ásniuk a hóból az ösvényeket, hogy iglutól igluig haladva el­ juthassanak az ismerőseikhez. A Nagy Eszkimó egy szép nap kitalálta, hogy át kellene szervezni az ösvényeket. Szeretné tudni, hány lehetőség közül válogat­ hat, kikötve azt, hogy az ösvények nem metszhetik egymást. Az igluk egy egye ­ nes vonal mentén találhatók. Példa ■ Jelöljük n-nel az eszkimók sátrainak számát:  Ha n = 1, van egy iglu + a Nagy Eszkimó sátra. Nyilvánvaló, hogy egyet­ len ösvény lesz.  Ha n = 2 a következő három lehetőség közül lehet válogatni:

 Ha n = 3, összesen 8 lehetőség van.  Ha n = 4, vajon hány lehetőség lesz? Útmutatás ■ A Fibonacci-számok értékei (0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...) talán sugalmaznak valamit...

3.1.4. Háromszög Adott három szigorúan pozitív valós szám a, b és c. Vizsgáljuk meg, hogy az a, b és c számok képezhetik-e egy háromszög oldalainak mértékeit. Ha igen, hatá­ rozzuk meg és írjuk ki a háromszög területét, egyébként írjunk ki hibaüzenetet! Elemzés ■ Az a, b, c szigorúan pozitív számok akkor képezhetik egy háromszög oldalait, ha az egyik oldal kisebb a másik kettő összegénél, de ugyanakkor nagyobb a másik kettő különbségének abszolút értékénél (például a < (b + c) és a > |b – c|). A területszámításhoz Héron képletét használjuk. Természetesen azt is vizs­ gálnunk kell, hogy az a, b és c számok szigorúan pozitívak-e? Algoritmus Háromszög(a,b,c): Ha (a > 0) és (b > 0) és (c > 0) akkor Ha a < b + c akkor Ha b  c akkor d  c - b

3. LÉPÉSEK FINOMÍTÁSA

63

különben d  b - c vége(ha) Ha a > d akkor p  (a + b + c)/2 T 

p *(p  a)*(p  b)*(p  c) Ki: 'A terület: ', T különben Ki: 'Nem alkotnak háromszöget!' vége(ha) különben Ki: 'Nem alkotnak háromszöget!' vége(ha) különben Ki: 'Hibás adatok!' vége(ha) Vége(algoritmus)

3.1.5. Fordított szám Adott az n természetes szám, amelynek legfeljebb 9 számjegye van. Hozzuk lét­ re és írjuk ki azt a számot, amely az eredeti szám számjegyeit fordított sorrend­ ben tartalmazza. Elemzés ■ Legyen n = 321, melynek számjegyei előállításuk sorrendjében: 1, 2 és 3. Az ezekből a számjegyekből alkotott új szám 123. Tudjuk, hogy 123 = 1  102 + 2  101 + 3  100. Szükségünk lesz egy segédváltozóra (újszám), amelyben a keresett számot fogjuk generálni. Az újszám kezdeti értéke nulla, majd minden lépésben újszám új értékét a következőképpen határozzuk meg: újszám régi értékét szorozzuk 10-zel és hozzáadjuk az éppen előállított számjegyet. újszám := 0 újszám := 0  10 + 1 = 1 újszám := 1  10 + 2 = 12 újszám := 12  10 + 3 = 123 A leírt módszer Horner-séma néven ismert. (Találkozunk még vele a poli­ nom értékének kiszámításakor.) Algoritmus Fordított_szám(n,újszám): { bemeneti adat: n, kimenet: újszám } újszám  0 Amíg n  0 végezd el:

64

3. LÉPÉSEK FINOMÍTÁSA

újszám  újszám * 10 + maradék[n/10] n  [n/10] vége(amíg) Vége(algoritmus)

A fenti feladathoz hasonlít a következő: olvassunk be ismeretlen számú ka­ raktert (sorvége-jelig). Építsünk fel ezekből (ha lehetséges) egy számot és írjuk ezt ki. Elemzés ■ Összehasonlítva a fenti feladattal ezt a feladatot, észreveszünk két lé­ nyeges különbséget:  az elsőben biztosan tudtuk, hogy számjegyekkel dolgozunk, míg most ka­ raktereket olvasunk be, amelyekről nem állíthatjuk, hogy egészen bizto­ san számjegyek.  az elsőben tudtuk, hogy legfeljebb 9 számjegyből kell előállítanunk egy új számot, míg most a számjegyek száma ismeretlen. Lépések finomítása ■ Mivel nem biztos, hogy minden karakter számjegy, ezt ellenőrizni fogjuk. Ha „idegen” karaktert találunk, kilépünk a feldolgozásból, és kiírjuk a megfelelő üzenetet. A második észrevétel arra késztet, hogy mi ma­ gunk vigyázzunk arra, hogy ne próbáljunk olyan túl nagy számot generálni, amelyet az adott számítógépen és az adott programozási környezetben nem tu­ dunk ábrázolni. Más szóval vigyáznunk kell a túlcsordulásra. Minden típuson belül létezik egy legnagyobb szám, amit a számítógép még ábrázolni tud (leg­ nagyobb). Természetesen, nem kérdezhetjük meg, hogy Ha szám > legnagyobb akkor ... mivel nem létezik a legnagyobbnál nagyobb szám. Következésképpen feltesszük a kérdést egy lépéssel „hamarabb” mint, hogy az aktuális számjegyet is feldolgoznánk: Ha szám > [(legnagyobb - számjegy)/10] akkor... A számgeneráló algoritmus tehát a következőképpen alakul: Algoritmus Számgenerálás: szám  0 idegen  hamis { még nem olvastunk be idegen karaktert } túlnagy  hamis { még nem generáltunk túl nagy számot } Amíg nem sorvége következik és nem idegen és nem túlnagy végezd el: Be: kar Ha (kar < '0') vagy (kar > '9') akkor idegen  igaz { a karakter a [0,9] intervallumon kívül esik } különben szj  a kar karakter számértéke Ha szám > [(legnagyobb-szj)/10] akkor { ha beépítenénk a szj számjegyet } túlnagy  igaz { a számba, a számot már nem lehetne ábrázolni }

65

3. LÉPÉSEK FINOMÍTÁSA

különben szám  szám*10 + szj { beépítjük a szj számjegyet a számba } vége(ha) vége(ha) vége(amíg) Ha nem idegen és nem túlnagy akkor Ki: szám { sikerült egy helyes számot generálni } különben Ha idegen akkor { a beolvasott karakter nem volt számjegy } Ki: 'Idegen karakter!' különben { túl nagy számot kellett volna generálnunk } Ki: 'Túl nagy szám!' vége(ha) vége(ha) Vége(algoritmus)

3.1.6. Törzstényezők Adott egy n (n > 3) természetes szám. Bontsuk törzstényezőkre! Elemzés ■ A törzstényezőkre bontást az ismert módon végezzük: Legyen n = 28.

28 14 7 1

2 2 7

Tehát n = 22  7.

 Kezdőértéket adunk a szám első lehetséges osztójának (osztó = 2);  Egy ciklust indítunk, amelyben a következő lépéseket végezzük:  0-ra állítjuk a hatvány számlálót (a törzstényező hatványkitevőjét);  Addig, amíg az osztó osztója n-nek, növeljük a számláló értékét 1-

gyel, és osztjuk az n-et az osztó-val;  Ha a ciklus után a számláló értéke nem nulla, kiírjuk az osztó törzstényezőt és a hatvány számláló értékét, majd megkeressük a következő lehetséges osztót, azaz növeljük osztó értékét 1-gyel;  A ciklust mindaddig folytatjuk, amíg n egyenlő lesz 1-gyel! Algoritmus Törzstényezők(n): osztó  2 Ismételd hatvány  0 Amíg maradék[n/osztó] = 0 végezd el: hatvány  hatvány + 1 n  [n/osztó] vége(amíg) Ha hatvány  0 akkor

{ az első lehetséges osztó }

66

3. LÉPÉSEK FINOMÍTÁSA

Ki: osztó, '^', hatvány, '*' vége(ha) osztó  osztó + 1 ameddig n = 1 Vége(algoritmus)

{ növeljük az osztót }

3.1.7. Konverzió Adva van egy szám a p alapú számrendszerben. Alakítsuk át q alapú számrend­ szerbe (p, q  10). Elemzés  a bemeneti adatok: a p és q számrendszerek, valamint egy szám, amelyet p alapú számrendszerben írtunk fel; a szám csak „úgy néz ki”, mintha a p alapú számrendszerben lenne felírva, a számítógép a 10-es számrendszer­ ben érzékeli;  a kimeneti adatok megállapítása: a q alapú számrendszerben felírt szám (itt is érvényes a fenti észrevétel);  a megfelelő adatszerkezetek kiválasztása és megtervezése: nincs szükség adatszerkezetre;  a feladat követelményeinek szétválasztása: beolvassuk az adatokat, átala­ kítjuk a számot a 10-es számrendszerbe, majd az így kapott számot átala­ kítjuk q alapú számrendszerbe és kiírjuk;  a megoldási módszer megállapítása: sajnos nem alkalmazható a Hornerséma, mivel a számjegyeket az alap hatványa szerint növekvő sorrendben építjük a generált számba és nem csökkenőben, mint a Horner-séma ese­ tében. Ezért szükségünk lesz az aktuális alap hatványainak generálására. A megoldás lépéseinek leírása Algoritmus Konverzió(p,q,szám,újszám): { bemeneti adatok: p, q a két számrendszer alapja és szám, kimeneti adat: újszám } szám átalakítása p alapú számrendszerből a 10-esbe (az eredmény: segéd) segéd átalakítása a 10-es számrendszerből a q alapúba (az eredmény: újszám) Vége(algoritmus)

Lépések finomítása ■ Észrevesszük, hogy szükséges a p alapú számrendszer­ ben megadott szám ellenőrzése: nem tartalmazhat csak p-nél kisebb értékű számjegyeket. Így bevezetjük a p alapú számrendszerben megadott szám formai helyességének ellenőrzését az algoritmusba. ... ok  igaz sz  szám Amíg ok és (sz  0) végezd el:

3. LÉPÉSEK FINOMÍTÁSA

67

Ha maradék[sz/10]  p akkor ok  hamis vége(ha) sz  [sz/10] vége(amíg) ...

Ahhoz, hogy egy számot, amelyet a p alapú számrendszerben adtunk meg, átalakítsuk úgy, hogy a q alapú számrendszerben érvényes alakban jelenjen meg, a p alapú számrendszerben megadott számot átalakítjuk a 10-es számrend­ szerbe, majd ezt átalakítjuk a q alapú számrendszerbe. Algoritmus Konverzió(p,q,szám,újszám): { bemeneti adatok: p, q a két számrendszer alapja és szám, kimeneti adat: újszám } ok  igaz sz  szám Amíg ok és (sz  0) végezd el: Ha maradék[sz/10]  p akkor ok  hamis vége(ha) sz  [sz/10] vége(amíg) Ha nem ok akkor Ki: 'Hiba!' különben { átalakítás a p alapú számrendszerből a 10-esbe } segéd  0 hatvány  1 { a p alapú számrendszer alapjának hatványai } Amíg szám  0 végezd el: számjegy  maradék[szám/10] szám  [szám/10] segéd  segéd + számjegy * hatvány hatvány  hatvány * p vége(amíg) újszám  0 { átalakítás a 10-es számrendszerből a q alapú számrendszerbe } hatvány  1 { a 10-es számrendszer alapjának hatványai } Amíg segéd  0 végezd el: számjegy  maradék[segéd/q] segéd  [segéd/q] újszám  újszám + számjegy * hatvány hatvány  hatvány * 10 vége(amíg) Ki: újszám vége(ha)

68

3. LÉPÉSEK FINOMÍTÁSA

Vége(algoritmus)

Vajon hogyan lehetne tovább finomítani ezt az algoritmust? Észrevesszük, hogy ha p = 10, akkor teljesen fölösleges a számot átalakítani 10-esbe. Ugyan­ így, ha q = 10, fölösleges az új számot átalakítani a 10-es számrendszerből q alapúba. Így algoritmusunk a következő: Algoritmus Konverzió(p,q,szám,újszám): { bemeneti adatok: p, q a két számrendszer alapja és szám, kimeneti adat: újszám } ok  igaz sz  szám Amíg ok és (sz  0) végezd el: ... vége(amíg) Ha nem ok akkor Ki: 'Hiba!' különben Ha p = 10 akkor { nincs szükség az átalakításra a 10-esbe } segéd  szám különben segéd  0 hatvány  1 Amíg szám  0 végezd el: számjegy  maradék[szám/10] szám  [szám/10] segéd  segéd + számjegy * hatvány hatvány  hatvány * p vége(amíg) vége(ha) Ha q = 10 akkor { nincs szükség az átalakításra a 10-esbe } újszám  segéd különben újszám  0 hatvány  1 Amíg segéd  0 végezd el: számjegy  maradék[segéd/q] segéd  [segéd/q] újszám  újszám + számjegy * hatvány hatvány  hatvány * 10 vége(amíg) vége(ha) Ki: újszám vége(ha) Vége(algoritmus)

69

3. LÉPÉSEK FINOMÍTÁSA

Az algoritmus még mindig nem teljes, hiszen, ha teszteléskor megpróbáljunk például a 2-es számrendszerbe átalakítani egy tetszőleges, például négyjegyű számot, nagyon furcsa eredményt kapunk. Az olvasóra bízzuk, hogy finomítsa tovább az algoritmust úgy, hogy védekezzen a túlcsordulások ellen is. Ugyan­ akkor a p alapú számrendszerben megadott szám esetében sem elégséges, ha beolvasáskor a program megszakad, mert túl sok számjegyet gépeltünk be!

3.1.8. Gyors hatványozás Adottak az x > 0 valós és az n > 6 természetes számok. Számítsuk ki az xn szá­ mot minél kevesebb szorzással! Elemzés ■ A feladat megoldható n – 1 szorzással (Minden típusú struktúrával): ... eredmény  alap Minden i=2,n végezd el: eredmény  eredmény * alap vége(minden) ...

A feladat megoldható kevesebb szorzással is, ha a kitevőt kifejezzük a 2-es számrendszerben, és xn-t felírjuk x2k alakú számok szorzataként (k > 0). Az x2k számokat kiszámolhatjuk egymást követő négyzetre emelésekkel. Példák

  

9 = (1001)2, x 9  x 8  x   x 2 

2

2

x

 

15 = (1111)2, x 15  x 8  x 4  x 2  x   x 2 

2

2

 

  x 2 

2

 x2  x

Algoritmus Gyorshatvány(alap,kitevő,eredmény): eredmény  1 { bemeneti adatok: alap, kitevő, kimeneti adat: eredmény } Amíg kitevő > 0 végezd el: Ha kitevő páratlan akkor eredmény  eredmény * alap kitevő  kitevő - 1 vége(ha) alap  alap*alap kitevő  [kitevő/2] vége(amíg) Vége(algoritmus)

3.2. Kitűzött feladatok

70

3. LÉPÉSEK FINOMÍTÁSA

1. Írjunk algoritmust, amely megkeresi és kiírja az első n tökéletes számot! Egy szám tökéletes, ha egyenlő a nála kisebb osztóinak összegével. Példa ■ 6 = 1 + 2 + 3. 2. Számoljuk meg hány olyan szám van adott n természetes szám között, amelyeknek 13-mal való osztási maradéka 7! Írjuk ki ezeket a számokat, és számítsuk ki a többi szám szorzatát! 3. Számoljuk meg, hogy n beolvasott természetes szám közül hány osztható 3mal, hány osztható 5-tel, illetve hány osztható 15-tel! 4. Olvassunk be természetes számokat! A számok beolvasása a 0 szám bevite­ léig tart. Számoljuk meg és írjuk ki, hány olyan számot találtunk, amelyek­ nek 7-tel való osztási maradéka 5, illetve hány olyant, amelyeknek 13-mal való osztási maradéka 7! 5. Határozzuk meg az n  N* szám legnagyobb valódi osztóját! Ha a szám prím, írjunk megfelelő üzenetet! n Útmutatás ■ Ha a legkisebb osztó d, akkor a legnagyobb lesz. d 6. Írjuk ki egy adott n  N* szám minden osztóját! Útmutatás ■ Legyen n = 100. Az osztók: 1, 2, 4, 5, 10, 20, 25, 50, 100. Tehát elég, ha az osztókat az {1, 2, 3, ..., [n/2]} halmazban keressük, majd kiírjuk a 100-at. Ha az osztókat kiírjuk párosával (d és n/d), csak a szám négyzetgyökéig kell keresnünk. 7. Írjunk programot, amely megtalálja az összes k-val osztható számot, ame­ lyek két adott szám (n1 és n2) között találhatók! 8. Írjunk algoritmust, amely egy 0-nál nagyobb természetes számról eldönti, hogy prímszám-e, vagy sem! Ha a szám nem prím, írjuk ki a szám valódi osztóit, valamint az osztók számát! 9. Határozzuk meg az 1 milliónál kisebb, legnagyobb prímszámot! 10. Keressük meg adott számig a legtöbb osztójú természetes számot! 11. Írjunk algoritmust, amely egy adott, 6-nál nagyobb páros számot felír két kü­ lönböző prímszám összegeként (Goldbach-sejtés). 12. Írjunk algoritmust, amely megkeresi és kiírja az első n ikerprímet! Két prím­ szám, p és q ikerprímek, ha q – p = 2. Példa ■ Az első 5 ikerprím: (3, 5), (5, 7), (11, 13), (17, 19) és (29, 31). 13. Írjuk ki n természetes szám legnagyobb közös osztóját!

3. LÉPÉSEK FINOMÍTÁSA

71

14. Egy Internet-mániás szeretne karácsonyi üdvözletet küldeni n > 1 milliárd email címre, de kényelemből végül úgy dönt, hogy csak minden k-adik meg­ lévő címre fog üdvözletet küldeni. Nem szeretné, ha a barátja kimaradna az üdvözölt személyek közül, ezért vele kezdi a listát. Amikor a lista végére ér, kezdi elölről, mintha ez körkörös lenne. Addig küldi az üzeneteket, amíg valakinek másodszor kellene küldeni. Állapítsuk meg, hogy adott n, k és b (a barátja címének a sorszáma a lis­ tában) esetében, hányan nem kapnak üdvözletet! 15. Egy elhagyatott szigeten három tengerész és egy majom él. A tengerészek összegyűjtöttek egy halom kókuszdiót és úgy döntöttek, hogy másnap szét­ osztják a diókat testvériesen. Az éjszaka folyamán, az egyik tengerész fel­ kelt, három egyenlő részre osztotta a diókat és egy részt elrejtett. Maradt egy dió, ezt a majomnak adta. Reggelig ezt a titkos beavatkozást megismételte a másik két tengerész is. Reggel a tengerészek elosztották a megmaradt diókat három egyenlő részre, újból maradt egy dió, amit a majomnak adtak. Határozzuk meg az összes n (n  1000) számot, amely az eredetileg ösz­ szegyűlt diók száma lehetne. 16. Egy állatkertben n kalitkában élnek a papagájok. Egy játékos majom kinyi­ totta minden kalitka ajtaját, majd visszatért és az elsővel kezdődően becsukta minden másodikat. Ezután újraindult az első kalitkától, bezárta minden har­ madik kalitka ajtaját, ha ez nyitva volt, illetve kinyitotta minden harmadik kalitka ajtaját, ha ez zárva volt. Ezt így folytatta minden negyedik, ötödik stb. kalitkával, (n-szer) mindig az elsővel kezdve. Írjuk ki a végén nyitva maradt kalitkák sorszámait! 17. Írjunk algoritmust, amely megadja a Fibonacci-sorozat egy adott számnál ki­ sebb elemeinek számát! 18. Keressük meg azt a legkisebb Fibonacci-számot, amely nagyobb mint egy adott n szám! 19. A billentyűzetről beolvasunk egy legfeljebb 9 számjegyet tartalmazó egész számot és egy számjegyet. Számoljuk meg hányszor fordul elő az adott számjegy a számban! 20. Írjunk programot, amely megkeresi azokat az 1000-nél kisebb számokat, amelyek egyenlők számjegyeik köbének összegével! 21. Írjunk algoritmust, amely egy természetes számról eldönti, hogy palindrome! (Palindrom az a szám, amelynek számjegyeit balról jobbra, illetve jobbról balra olvasva ugyanazt a számot kapjuk.)

72

3. LÉPÉSEK FINOMÍTÁSA

22. Írjunk ki egy, a 60-as számrendszerben megadott számot a 10-es számrend­ szerben! A 60-as számrendszer számjegyei: 0,1, …, 9, (10), (11), …, (59). Például az 5(15)(55)0(20)4 szám lehet egy 60-as számrendszerben megadott szám. 23. Határozzuk meg azokat a természetes számokat, amelyek m és n között talál­ hatók és amelyeknek bináris alakja azonos számú 1-es számjegyet tartalmaz! 24. Alakítsunk át egy 16-os számrendszerben megadott számot a 10-es szám­ rendszerbe! Vigyázzunk a túlcsordulásra, és írjuk ki azt a legnagyobb 16-os számrendszerbeli számot, amit még át lehet alakítani! 25. Bontsunk fel egy adott n számot 2 hatványainak összegére! 26. Babszem Jankó el szeretne jutni Óriásországba egy elvarázsolt paszuly segít­ ségével. A paszuly minden percben nő, és az aktuális magasságának 1/2, 1/3, 1/4, ... részével lesz nagyobb. Tudva azt, hogy Babszem Jankó 300m magasra szeretne eljutni és, hogy a paszuly eredeti magassága 1m, számítsuk ki hány perc alatt jut el Óriásországba! [16] 27. Adva van egy római szám, írjuk ki arab számjegyekkel! 28. Adva van egy arab szám (n ≤ 5000), írjuk ki római számjegyekkel!

4

PROGRAMOZÁSI TÉTELEK

4.1. Bevezetés A megoldandó feladatok elemzése során, már az első fázisban el kell dönte­ nünk, hogy mely feladatosztályba tartozik egy-egy részfeladat. Miután ez eldőlt, megállapítjuk az alkalmazandó „programozási tételt”. Ezek a „tételek” tulajdon­ képpen programozási minták, amelyeket a szakemberek azonnal felismernek és magabiztosan alkalmaznak. A felsorolt okok miatt használatuk nemcsak ajánla­ tos, hanem szinte kötelező. Például, nem mindegy, hogy egy ismert lépésszámú ciklust Amíg-gal vagy Minden típusú struktúrával tervezünk meg, hiszen, ha egy programozó Amíg-ot lát, keresi az okát az ismeretlen lépésszámú ismétlő struktúra használatának. Nem találja, időt veszít, bosszankodik. Ugyanígy, nem tanácsos fölöslegesen terhelni a számítógépet Minden típusú szerkezetekkel, ha a ciklus leállítható egy bizonyos feltétel teljesülésekor.

4.2. Egyszerű programozási tételek 4.2.1. Összeg és szorzat Az első feladatosztály, amellyel foglalkozunk egyetlen kimenő adat kiszámítását kéri adott számú bemeneti adat feldolgozásának eredményeként. Ilyenek például azok a feladatok, amelyekben a bemeneti adatok összegét, illetve szorzatát kell kiszámítanunk. A feladat megoldása előtt szükséges tudni, hogy mely érték felel meg a bemeneti adatok halmazára és az elvégzendő műveletre nézve a semleges elemnek (ha a műveletet elvégezzük a semleges elemre és egy tetszőleges x adatra a bemeneti adatok halmazából, az eredmény ugyancsak x). [22, 23] A továbbiakban szeretnénk a részfeladatok megoldására írt algoritmusokra tenni a hangsúlyt, ezért alprogramok formájában szerkesztjük meg ezeket. A fejlécben (paraméterekként) feltüntetjük az esetleges bemenet és kimeneti ada­ tokat. A feladatok bemeneti adatai egész számok, amelyeknek a számossága is­ mert, de a ciklus típusának kicserélésével megkapjuk a megfelelő változatot arra

74

4. PROGRAMOZÁSI TÉTELEK

az esetre, amikor a bemeneti adatok száma ismeretlen (állományvége-jelig, sorvége-jelig, esetleg egy adott érték beviteléig – végjelig – olvasunk). Megjegyzés ■ Miután elsajátítjuk az alprogramokra vonatkozó ismereteket, eze­ ket az algoritmusokat alprogramokként implementáljuk, és az ezekhez tartozó ki- és bemeneti adatokat paraméterlistákba írjuk, a felhasznált programozási nyelv megfelelő szabályai szerint. Az Összegszámítás(n,x,összeg) n bemeneti adat (esetünkben egész számok, amelyeket az x tömb tárol) összegének kiszámítását oldja meg, de a feladat általánosítható úgy, hogy az összeadás helyett egy általános műveletet hajtunk végre a megfelelő semleges elem felhasználása mellett. Algoritmus Összegszámítás(n,x,összeg): összeg  0 { bemeneti adatok: n, x; kimeneti adat: összeg } Minden i=1,n végezd el: { minden adatot feldolgoznunk } összeg  összeg + xi vége(minden) Vége(algoritmus)

Feladatok elemzése és általánosítás ■ Keressük meg a következő feladatok kö­ zös vonását! F1. Adottak egy n létszámú osztály tanulóinak év végi átlagai. Számítsuk ki az osztályátlagot! F2. Adva van m karakter. Fűzzük össze ezeket egy karakterláncba! F3. k kutató feljegyezte a Duna-deltában megfigyelt madárfajokat. Határozzuk meg a Duna-deltában élő madárfajok halmazát! F4. Számítsuk ki az első n természetes szám szorzatát! Elemzés ■ A fenti feladatok mind megoldhatók Összegszámítás típusú algorit­ mussal. Az elemzés során próbáljuk meg felfedezni azt ami közös a fenti felada­ tokban. Mindegyikben több bemeneti adatunk van és mindegyik feladat eseté­ ben egyetlen eredményt kell kiszámolnunk, amelybe az adatok, a feldolgozásuk során beépülnek. A fenti feladatok esetében az eredményt egy olyan utasítássorozattal kapjuk meg, amelynek funkciója az n szám számtani középarányosának kiszámítása (F1), m karakter felfűzése egy karakterláncba (F2), k halmaz egyesítése (F3), valamint n szám szorzata (F4). A feladatok megoldása az adatszerkezetek megtervezésével folytatódik (megjegyezzük a változók jelentését is).  n: a feldolgozandó adatok száma, egész típusú szám;  x: elem, a feldolgozandó adatokat jelöli, típusa feladatonként változik;

75

4. PROGRAMOZÁSI TÉTELEK

 F0: semleges elem, típusa megegyezik az elemek típusával;  eredmény: típusa feladatonként változik. A művelet lehet összeadás, szorzás, halmazok egyesítése, keresztmetszete, logikai művelet, konkatenáció stb. Ezen műveletek mind egyszerűsíthetők úgy, hogy visszavezetjük őket bináris műveletekre. Példák ■ Legyenek a lehetséges műveletek (F): összeg szorzat  egyesítés keresztmetszet) logikai és logikai vagy, & (konkatená­ ció). Ezeknek a műveleteknek a következő semleges elemek (F0) felelnek meg: 0, 1, {}, Univerzum, igaz, hamis, '' (üres karakterlánc). A megoldást, általános esetben a matematikai indukcióra vezetjük vissza, egy bináris műveletet és a semleges elemet felhasználva.  Ha 0 elemet dolgozunk fel, az eredmény a semleges elem (F0).  Ha ismerjük az első i – 1 elem feldolgozásának eredményét (Fi–1), akkor az első i elem feldolgozásának eredményét az Fi = f(Fi–1, xi) függvénnyel számítjuk ki. A következő algoritmus az M művelettel dolgozza fel az n elemű x sorozatot: Algoritmus Feldolgoz(n,x,eredmény): eredmény  F0 Minden i=1,n végezd el: Fi  M(Fi-1,xi) vége(minden) eredmény  Fn Vége(algoritmus)

{ minden adatot feldolgoznunk }

4.2.2. Döntés Lesznek olyan részfeladataink, amelyekben meg kell állapítanunk, hogy a be­ meneti adatok rendelkeznek-e valamilyen tulajdonsággal. Előfordulhatnak olyan feladatok, amelyeknek megoldása során azt kell eldöntenünk, hogy létezik-e adott tulajdonságú elem, de lesznek olyan feladatok is, amelyekben egy adathalmaz tulajdonságát vizsgáljuk. Ezen feladatok közös tulajdonsága a vá­ laszadás milyenségében rejlik. Ez a válasz általában egy üzenet, amelyet az al­ goritmus kimenő paramétere (például egy logikai változó) értéke alapján írunk ki. Első megközelítésben tekintsük az előbbi algoritmust és alakítsuk át úgy, hogy oldja meg a feladatot. Az eredményt a talált logikai változó fogja tartalmazni a döntésnek megfelelő érték formájában. Az alábbi algoritmusban a T(xi) jelölés azt

76

4. PROGRAMOZÁSI TÉTELEK

jelenti, hogy vizsgáljuk az x sorozat i-edik elemét, hogy rendelkezik-e a T tulaj­ donsággal. Algoritmus Döntés_1(n,x,talált): talált  hamis { bemeneti adatok: n, x; kimeneti adat: talált } Minden i=1,n végezd el: talált  talált vagy T(xi) { szám T tulajdonságú? } vége(minden) Vége(algoritmus)

Vegyük észre, hogy ha egy adott pillanatban a talált változó értéke igazzá vált, többet biztos nem válhat hamissá az algoritmus végéig. Ennek az észrevé­ telnek az értelmében megváltoztatjuk az algoritmust. Elsősorban lemondunk a Minden típusú ciklusról és egy ismeretlen lépésszámú struktúrát választunk, ahhoz, hogy azonnal kiléphessünk a ciklusból amint a döntés megszületett. Ter­ mészetesen, ha nem találunk adott tulajdonságú adatot, minden elemet meg kell vizsgálnunk. A következő algoritmusban i számláló, a talált kezdőértéke pedig hamis. Amíg nem találtunk adott tulajdonságú értéket, haladunk előre, ellenkező eset­ ben, a talált változó értéke megváltozik és „sietteti” az Amíg-ból való kilépést. Algoritmus Döntés_2(n,x,talált): i  1 talált  hamis Amíg nem talált és (i  n) végezd el: Ha T(xi) akkor talált  igaz különben { amíg xi nem rendelkezik a T tulajdonsággal, haladunk előre } i  i + 1 vége(ha) vége(amíg) Vége(algoritmus)

Tekintsük a következő (tömörebb) algoritmust: Algoritmus Döntés_3(n,x,talált): i  1 { x tömb, n eleme van } Amíg (i  n) és nem T(xi) végezd el: { amíg xi nem rendelkezik a T tulajdonsággal } i  i + 1 { haladunk előre } vége(amíg) talált  i  n { kiértékelődik a relációs kifejezés, } { a kapott logikai érték átadódik a talált változónak } Vége(algoritmus)

Gondoljunk most arra az esetre, amikor a bemeneti adathalmaz minden ele­ mét megvizsgáljuk és így döntjük el, hogy az adatok, teljességükben, rendelkez­

77

4. PROGRAMOZÁSI TÉTELEK

nek-e egy adott tulajdonsággal vagy sem. Ez azt jelenti, hogy minden adatnak megvan ugyanaz a keresett tulajdonsága vagy, másképp kifejezve: nem létezik egyetlen adat sem, amelyiknek ne lenne meg a kért tulajdonsága. Ha ezt a két­ szeres tagadást alkalmazzuk a Döntés_3 (n,x,talált) algoritmusban, megkapjuk ennek a feladatosztálynak a megoldását. Mivel a döntés jelentése az összes adatra érvényes, felcseréljük a talált változót a mind változóval. Algoritmus Döntés_4(n,x,mind): i  1 Amíg (i  n) és T(xi) végezd el: i  i + 1 vége(amíg) mind  i > n Vége(algoritmus)

{ kimeneti adat: mind } { a nem T(xi) tagadása }

{ az i ≤ n részkifejezés tagadása }

Keressük meg a következő feladatok közös vonását! F5. Állapítsuk meg egy számról, hogy prím-e vagy sem! F6. Az év hónapjainak ismeretében, állapítsuk meg egy szóról, hogy egy hónap neve vagy sem! F7. Június folyamán naponta megmérték a Fekete-tenger vizének hőmérsékletét Mamaián. Állapítsuk meg, hogy a hónap folyamán naponta emelkedett-e a hőmérséklet vagy sem! F8. Ismerjük egy osztály tanulóinak nevét és év végi átlagát. Állapítsuk meg, hogy egy adott nevű tanuló az első három díjazott között van-e vagy sem! Az előbbi feladatok közös vonása abban áll, hogy mindegyikben el kell dön­ tenünk valamit, és a döntés eredményét egy válasz formájában kell megadnunk, tehát a fenti Döntés típusú algoritmusok egyikével oldjuk meg ezeket (az F5., F6. és F8. feladatokat Döntés_2(n,x,talált)-tal, az F7.-et Döntés_4(n,x,mind)-del). A döntések implementálásakor különböző gondokkal kell megbirkóznunk. Vannak programozási nyelvek, amelyek a logikai kifejezések részkifejezéseit kiértékelik teljes egészükben, mások pedig az első részkifejezés és a következő logikai operátor függvényében, az első részkifejezés kiértékelése után leállnak. Például, ha az első részkifejezés értéke igaz és a vagy művelet következik, a második részkifejezés nem kerül kiértékelésre, mivel tudott dolog, hogy a má­ sodik részkifejezés értékétől függetlenül a teljes kifejezés értéke igaz lesz (igaz vagy igaz eredménye igaz, de igaz vagy hamis értéke szintén igaz). Hasonlóan, ha az első részkifejezés értéke hamis és az és művelet következik, a második részkifejezés nem kerül kiértékelésre, mivel tudott dolog, hogy a második rész­ kifejezés értékétől függetlenül a teljes kifejezés értéke hamis lesz (hamis és igaz eredménye hamis, de hamis és hamis értéke szintén hamis).

78

4. PROGRAMOZÁSI TÉTELEK

Ha olyan programozási nyelvet használunk, amely kiértékel minden részki­ fejezést vagy, ha (az előbb leírt körülmények között) a programozó figyelmetle­ nül választja meg a részkifejezések sorrendjét, a döntés kódolása kellemetlen hi­ bákat okozhat. Legyen az (i ≤ n) és T(xi) részkifejezés a Döntés_4(n,x,mind) al­ goritmusból. Ha i > n, az (i ≤ n) részkifejezés értéke hamis, és ebben az esetben, ha kiértékelődne a T(xi), akkor egy nem létező xn+1 elemet tesztelnénk az Amígban, pedig már tudjuk, hogy i > n! A fenti csapdát elkerülhetjük többféleképpen:  létrehozhatunk egy fiktív (n + 1)-dik elemet (őrszemet);  leállhatunk az Amíg-gal egy lépéssel hamarabb (amikor i = n – 1), majd ellenőrizzük a kilépés okát és az utolsó elemet;  szétválaszthatjuk a részkifejezéseket úgy, hogy a tulajdonság ellenőrzését „leköltöztetjük” a ciklusmagba (lásd Döntés_2(n,talált)) és ahhoz, hogy a megfelelő pillanatban leállhassunk, bevezetünk egy logikai változót.

4.2.3. Kiválasztás A Döntés típusú algoritmusokkal egy adott tulajdonságú elem létezését állapít­ juk meg. Észrevesszük, hogy különösebb változtatás nélkül az algoritmus meg­ adhatná az adott tulajdonságú elem helyét is. Feltételezzük, hogy a feladat garantálja, hogy az adatok között létezik lega­ lább egy olyan elem, amely rendelkezik az adott tulajdonsággal. Ha megelég­ szünk egyetlen ilyen elemmel, akkor az eredmény egy sorszám, amely megfelel az első adott tulajdonságú elem helyének a sorozatban. Mivel a feladat szövege biztosítja, hogy létezik legalább egy ilyen elem, nem szükséges a számláló érté­ két is ellenőrizni. Algoritmus Kiválasztás(n,x,sorszám): sorszám  1 Amíg nem T(xsorszám) végezd el: sorszám  sorszám + 1 vége(amíg) Vége(algoritmus)

{ kimeneti adat: sorszám }

Keressük meg a következő feladatok közös vonását! F9. Adott egy szó, amely egy hónapnak a neve. Írjuk ki a sorszámát! F10. Határozzuk meg egy adott természetes szám legkisebb osztóját, amely na­ gyobb mint 1!

79

4. PROGRAMOZÁSI TÉTELEK

F11. Adott egy naptár, amelyben több keresztnév és az adott keresztnévhez tar­ tozó névnap található. Írjuk ki egy adott keresztnevű személy névnapját (a név biztos megtalálható a naptárban)! A feladatok egyértelműen egy adott tulajdonságú elem sorszámát (esetleg a sorszámhoz tartozó valamilyen más adatot) kérik. Az egyetlen érdekesebb fela­ dat az F10-es, ahol nincsenek bemeneti adatok, mivel ezek generálhatók. Ami a „garanciát” illeti: ha a szám nem prímszám, az algoritmus megkeresi az első va­ lódi osztót, különben az eredmény maga a szám, amely osztja önmagát. Algoritmus LegkisebbOsztó_1(szám,osztó): { bemeneti adat: szám; kimeneti adat: osztó } osztó  2 { az első „megengedett” osztó } Amíg maradék[szám/osztó]  0 végezd el: osztó  osztó + 1 { a következő „lehetséges” osztó } vége(amíg) Vége(algoritmus)

Ezt a változatot azonban tovább lehetne javítani, hiszen, ha nem találunk egyetlen osztót sem a szám négyzetgyökéig, akkor levonhatjuk a következtetést, hogy a legkisebb osztó maga a szám. Algoritmus LegkisebbOsztó_2(szám,osztó): osztó  2 { az első „megengedett” osztó } ngy  [ szám ]

{ kiszámítjuk a szám négyzetgyökét a ciklus előtt }

Amíg (maradék[szám/osztó]  0) és (osztó  ngy) végezd el: osztó  osztó + 1 { a következő „lehetséges” osztó } vége(amíg) Ha osztó > ngy akkor osztó  szám { szám prímszám } vége(ha) Vége(algoritmus)

4.2.4. Szekvenciális (lineáris) keresés A következőkben azzal a feladatosztállyal foglalkozunk, amely nem garantálja, hogy létezik adott tulajdonságú elem az adathalmazban. Így ezekben a felada­ tokban egyesülnek az előbbi két feladattípus követelményei. Ezen feladatokat olyan algoritmussal oldjuk meg, amely a döntés után (létezik?) képes megadni a keresett elem helyét is. A hely változó értéke 0 marad, ha a keresett T tulajdon­ ságú elem nem található meg a sorozatban. Algoritmus Keres_1(n,x,hely): i  1 hely  0

{ bemeneti adatok: n, x; kimeneti adat: hely }

80

4. PROGRAMOZÁSI TÉTELEK

Amíg (hely = 0) és (i  n) végezd el: Ha T(xi) akkor hely  i különben i  i + 1 vége(ha) vége(amíg) Vége(algoritmus)

Megírhatjuk az algoritmus azon változatát is, amely az elemek tulajdonságát az Amíg feltételében ellenőrzi. Úgy is mondhatnánk: amíg az aktuális elem tu­ lajdonsága nem megfelelő, haladunk a sorozatban az utolsótól az első fele. Ha az Amíg-ból való kilépéskor a számláló értéke nagyobb mint 0, ez azt jelenti, hogy találtunk adott tulajdonságú elemet, ha egyenlő 0-val, ez azt jelenti, hogy elértünk a sorozat végére és nem találtunk ilyet. A következő algoritmus i-ben tárolja az eredményt, és a sorozatot a végéről indulva járja be. Így nem a legki­ sebb indexű, adott tulajdonságú elemet találja meg, ha van ilyen, hanem a leg­ nagyobb indexűt. Algoritmus Keres_2(n,x,i): { bemeneti adatok: n, x; kimeneti adat: i } i  n Amíg (i  0) és nem T(xi) végezd el: i  i - 1 vége(amíg) { ha kiléptünk az Amíg-ból, mielőtt i 0-vá vált volna,} {  találtunk adott tulajdonságú elemet, különben i értéke 0 } Vége(algoritmus)

Tekintsük a következő feladatot! F12. Egy bizonyos cég nyilvántartásában a hónap minden napján feljegyzik a befizetett, illetve a kifizetett összegeket. Keressük meg, ha létezik, azt a napot, amely a cég számára veszteséggel zárult. (Tehát, azt a napot keres­ sük, amikor a kifizetések összege nagyobb volt, mint a bevételé.) Elemzés ■ Ha történetesen az utolsó ilyen napot kellene megkeresnünk, a soro­ zatot fordított irányban járnánk be! Ha a feladat azt kérné, hogy minden olyan elemet keressük meg, amely ren­ delkezik az adott tulajdonsággal, be kellene járnunk a teljes adathalmazt, és vagy kiírnánk azonnal a pozíciókat, ahol megfelelő elemet találtunk, vagy megőriz­ nénk ezeket egy sorozatban. Ilyenkor egy Minden típusú struktúrát használunk. Ha a keresés után a talált elemeket ki kell zárnunk a sorozatból (törölnünk kell ezeket), akkor a feladat specifikációjától függően, a következő lehetőségek közül fogunk választani:

4. PROGRAMOZÁSI TÉTELEK

81

a) Ha a törlés után nem kötelező, hogy az elemek az eredeti sorrendben marad­ janak, akkor a törlendő elemre rámásoljuk a sorozat utolsó elemét és csök­ kentjük 1-gyel a sorozat méretét: Algoritmus Keres_3(n,x): i  1 { bemeneti adatok: n, x; kimeneti adatok: n, x (megváltoznak) } Amíg i  n végezd el: { Amíg típusú ciklus, mivel változtatjuk n értékét } Ha nem T(xi) akkor xi  xn { a nem T tulajdonságú elemeket töröljük } n  n - 1 különben i  i + 1 vége(ha) vége(amíg) Vége(algoritmus)

b) Ha a törlés ideiglenes, akkor a kereséssel párhuzamosan nyilvántartjuk egy logikai tömbben a „törölt” elemeket: a törölt tömbben a törlendő elemek sor­ számával azonos sorszámú elemek értéke igaz lesz: Algoritmus Keres_4(n,x,törölt): { bemeneti adatok: n, x; kimeneti adat: törölt (n elemű tömb) } Minden i=1,n végezd el: törölti  nem T(xi) { a nem T tulajdonságúakat töröljük } vége(minden) Vége(algoritmus)

c) Ha a törlés után kötelező, hogy a kiválogatott elemek az eredeti sorrendben maradjanak, akkor a törlendő elemre rámásoljuk a sorozat következő elemét, az átmásolt elemre a következőt, és így tovább, míg elérünk a sorozat végére. Most a sorozat végén két azonos elemünk lesz, de csökkentjük 1gyel a sorozat méretét. Vegyük észre, hogy ez a megoldás időigényesebb mint az eddigiek, mivel két egymásba ágyazott ismétlő struktúrát tartalmaz: Algoritmus Keres_5(n,x): i  1 { bemeneti adatok: n, x; kimeneti adatok: n, x (változnak) } Amíg i  n végezd el: Ha nem T(xi) akkor { a nem T tulajdonságú elemeket töröljük } Minden j=i,n-1 végezd el: xj  xj+1 vége(minden) n  n – 1 { i nem nő, az „új” i-edik elemet még egyszer megvizsgáljuk } különben i  i + 1 vége(ha) vége(amíg) Vége(algoritmus)

82

4. PROGRAMOZÁSI TÉTELEK

d) Ha az eredeti sorozatot meg kell őriznünk, akkor azokból az elemekből, amelyek nem törlendők készítünk egy új sorozatot. De alkalmazható a mód­ szer akkor is, ha az eredetire nincs többet szükség: ebben az esetben az ere­ deti sorozatot a feldolgozás végén felülírjuk ezzel a másolattal, amely csak azokat az elemeket tartalmazza, amelyek megmaradtak. Algoritmus Keres_6(n,x,k,újsor): k  0 { bemeneti adatok: n, x; kimeneti adatok: k, újsor } Minden i=1,n végezd el: Ha T(xi) akkor k  k + 1 újsork  xi vége(ha) vége(minden) Vége(algoritmus)

e) Azt a megoldást, amely nem hoz létre új helyen, új sorozatot, hanem helyben végzi a kiválogatást, a megfelelő programozási tétel tanulmányozásakor is­ merjük meg (Kiválogatás).

f) Egy másik feladatban a többször előforduló elemeket keressük. Egy ilyen ke­ resés célja lehet a csak különböző elemekből álló sorozat létrehozatala (hal­ maz) vagy a legalább kétszer előforduló elemek sorozatának meghatározása.

4.2.5. Megszámlálás Legyenek a következő feladatok: F13. Ismerjük egy megyében élő családok számát, a családok jövedelmét és a megélhetéshez szükséges minimális összeget. Állapítsuk meg a minimális megélhetési szint alatt élő családok számát! F14. Adott egy programozási verseny eredménylistája, valamint a következő fordulóra való benevezéshez szükséges legkevesebb pontszám. Számítsuk ki azoknak a versenyzőknek a százalékát, akik benevezhetnek a követke­ ző fordulóra! F15. Számoljuk meg egy adott szövegben a magánhangzók számát! Ezekben a feladatokban a bemeneti adatok szintén sorozatok. Az eredmény egy szám lesz, amely adott tulajdonságú elemek számosságát fejezi ki. Észre­ vesszük, hogy a feladatok nem garantálják, hogy biztosan létezne legalább egy adott tulajdonságú elem, tehát az is lehetséges, hogy az eredmény 0 lesz. Mivel minden elemet meg kell vizsgálnunk (bármely adat rendelkezhet a kért tulajdonsággal), Minden típusú struktúrával dolgozunk. A darabszámot a db változóban tartjuk nyilván. Algoritmus Megszámlálás(n,x,db): db  0 { bemeneti adatok: n, x; kimeneti adat: db }

4. PROGRAMOZÁSI TÉTELEK

83

Minden i=1,n végezd el: Ha T(xi) akkor db  db + 1 vége(ha) vége(minden) Vége(algoritmus)

4.2.6. Minimum- és maximumkiválasztás Figyeljük meg a következő feladatok követelményeit! F16. Egy kórházban megmérik minden reggel a betegek hőmérsékletét. Hatá­ rozzuk meg a leglázasabb beteg hőmérsékletét! F17. Egy táblázatban feltüntették az elsőéves informatika szakos egyetemisták nevét a felvételi után, a vizsgaátlaguk sorrendjében. Határozzuk meg an­ nak az egyetemistának a nevét, akivel az ábécé sorrendű táblázat kezdőd­ ni fog! F18. A szociális ösztöndíj kiosztása céljából összegyűjtötték az egyetemisták családjainak jövedelmére vonatkozó adatokat. Határozzuk meg a legki­ sebb jövedelmet! Az előbbi feladatok megoldásai hasonlóak lesznek, mivel mindegyikben meg kell határoznunk az adatok között található legnagyobb, illetve legkisebb értéket. Természetesen, előfordulhat, hogy több ilyen legnagyobb, illetve legki­ sebb elem létezik. A fenti feladatok nem kérik, például hogy számoljuk meg hány leglázasabb beteg létezik, és hasonlóan, nem kell megadnunk a legláza­ sabb beteg nevét sem. Vegyük észre, hogy a Kiválasztás(n,x,sorszám) programozási tétel sajátos esetével állunk szemben, amikor a T tulajdonság azt jelenti, hogy a keresett elem az adott sorozat legkisebb/legnagyobb értékű eleme. Az algoritmus, egy Minden típusú struktúrával dolgozik, hiszen minden ada­ tot meg kell vizsgálnunk. Algoritmus Maximumkiválasztás(n,x,max): max  x1 { bemeneti adatok: n, x; kimeneti adat: max } Minden i=2,n végezd el: Ha max < xi akkor max  xi vége(ha) vége(minden) Vége(algoritmus)

84

4. PROGRAMOZÁSI TÉTELEK

A maximumot/minimumot tartalmazó segédváltozó inicializálását egy, a so­ rozatban létező elem értékével tanácsos végeznünk (általában az első elem érté­ kével), mivel így nem áll fenn a veszély, hogy a maximum/minimum egy ide­ gen végső értéket tartalmazzon. Az előbbi algoritmus a maximum értékét határozza meg. Ha nem az érték, hanem a pozíció érdekel, amelyen elsőként fedeztük fel a legnagyobb értéket, akkor ez az algoritmus a következőképpen alakul: Algoritmus Maximum_helye(n,x,hely): { bemeneti adatok: n, x; kimeneti adat: hely } hely  1 { hely az első elem pozíciója } Minden i=2,n végezd el: Ha xhely < xi akkor hely  i { a maximális elem indexe (pozíciója) } vége(ha) vége(minden) Vége(algoritmus)

Ebben az esetben a maximum/minimum értéke elérhető a sorozat megfelelő indexén keresztül. Ha nem tároltuk volna tömbben az elemeket, az érték megke­ resése megkövetelte volna az adatok másodszori beolvasását. De ha tömbbel dolgozunk, akkor meghatározható minden olyan pozíció, ahol a minimummal/ maximummal azonos értékünk van. Ha nem az első, maximumot tároló helyet kell meghatároznunk, hanem az utolsót, bejárhatjuk a tömböt fordított irányban (az előző algoritmust használ­ va), de alkalmazhatjuk a fenti algoritmust úgy is, hogy a „ jövedelmeki akkor min  jövedelmeki vége(ha) vége(minden) Minden i=1,n végezd el: Ha min = jövedelmeki akkor Ki: egyetemistáki

4. PROGRAMOZÁSI TÉTELEK

85

vége(ha) vége(minden) Vége(algoritmus)

Ha szükséges megőrizni a memóriában ezeket a neveket egy későbbi feldol­ gozás érdekében, akkor ezeket egy segédtömbben tároljuk a következőképpen: Algoritmus F18_2(n,egyetemisták,jövedelmek,segéd,j): { bemeneti adatok: n, egyetemisták, jövedelmek; kimeneti adatok: segéd,j } min  jövedelmek1 Minden i=2,n végezd el: Ha min > jövedelmeki akkor min  jövedelmeki vége(ha) vége(minden) j  0 { j: a segéd tömb elemeinek száma } Minden i=1,n végezd el: Ha min = jövedelmeki akkor j  j + 1 segédj  egyetemistáki vége(ha) vége(minden) Vége(algoritmus)

Ha ezt a megoldást nem tartjuk megfelelőnek, mivel az adott tömböt kétszer kell bejárnunk, vagy olyan feladatunk van, ahol a minimumhoz tartozó adatok egy másik (esetleg bonyolult) algoritmus végrehajtásának eredményei, írhatunk olyan algoritmust, amely csak egyszer dolgozza fel az adatokat: Algoritmus F18_3(n,egyetemisták,jövedelmek,segéd,j): { bemeneti adatok: n, egyetemisták, jövedelmek; kimeneti adatok: segéd,j } min  jövedelmek1 j  1 segéd1  egyetemisták1 Minden i=2,n végezd el: Ha min > jövedelmeki akkor { új minimum } j  1 segédj  egyetemistáki min  jövedelmeki különben Ha min = jövedelmeki akkor { a minimum újabb előfordulása } j  j + 1 segédj  egyetemistáki vége(ha) vége(ha) vége(minden) Vége(algoritmus)

86

4. PROGRAMOZÁSI TÉTELEK

Ez a megoldás egy újabb programozási tételt vetít elénk, a Kiválogatás-t.

4.2.7. Kiválogatás Láttuk, hogy a Kiválasztás egyetlen adott tulajdonságú elem helyét határozta meg. Ha szükség van arra, hogy az adott adathalmazban megkeressünk minden olyan elemet, amely rendelkezik ezzel a tulajdonsággal, akkor már kiválogatás­ ról beszélünk. Ennek a feladatnak a megoldása egyrészt hasonlít a Keresés-hez, másrészt a Megszámlálás-hoz. Ha az adott tulajdonságú elemek pozícióit egy másik részfeladat fel szeretné használni, akkor létrehozunk egy sorozatot, amely tárolja ezeket. A pozíciók sorozatának számossága legfeljebb az adott sorozaté­ val lesz megegyező, mivel előfordulhat, hogy minden eleme adott tulajdonságú. Ezt a számosságot a db változóban tartjuk nyilván, és természetesen ugyanúgy felhasználható, mint ahogy azt a Megszámlálás algoritmus végrehajtása után tennénk. Algoritmus Kiválogatás_1(n,x,db,pozíciók): db  0 { bemeneti adatok: n, x; kimeneti adatok: db, pozicíók } Minden i=1,n végezd el: Ha T(xi) akkor db  db + 1 pozíciókdb  i { pozíciókdb-ben tároljuk az xi helyét, } vége(ha) { amelynek adott T tulajdonsága van } vége(minden) Vége(algoritmus)

Ha nincs szükség a kiválogatott elemekre (illetve pozícióikra), akkor egysze­ rűen kiírjuk ezeket. Algoritmus Kiválogatás_2(n,x): Minden i=1,n végezd el: Ha T(xi) akkor Ki: xi vége(ha) vége(minden) Vége(algoritmus)

{ bemeneti adatok: n, x }

A következő változatban feltételezzük hogy a feldolgozás után nincs többé szükségünk az eredeti sorozatra, és így a kiválogatott elemekkel felülírjuk az eredeti adatokat. Így nem használunk egy újabb sorozatot számukra (illetve az eredeti sorozatban elfoglalt pozícióik számára), hanem az adott sorozat számára lefoglalt tárrészt használva helyben végezzük a kiválogatást. A db változó ebben az esetben ennek az „új” sorozatnak a számosságát tartja nyilván: Algoritmus Kiválogatás_3(n,x,db): db  0 { bemeneti adatok: n, x; kimeneti adat: db } Minden i=1,n végezd el:

87

4. PROGRAMOZÁSI TÉTELEK

Ha T(xi) akkor db  db + 1 xdb  xi vége(ha) vége(minden) Vége(algoritmus)

Abban a szélsőséges esetben, amikor az eredeti sorozat minden elemét kivá­ logatjuk, a művelet eredménye az eredeti sorozat lesz. Ha a kiválogatott elemeknek eredeti helyükön kell maradniuk a kiválogatás után is, akkor a nemkívánatosakat eltűntetjük oly módon, hogy egy sajátos ér­ tékkel felülírjuk őket. Ez az érték jelzi majd a további feldolgozások során, hogy innen „töröltünk” egy értéket. Ez az algoritmus is helyben végzi a kiválo­ gatást, de csak akkor lesz előnyös, ha a „törölt” elemek feldolgozásának elkerü­ lése egyszerűbb, mint a kért tulajdonság vizsgálata. Algoritmus Kiválogatás_4(n,x): Minden i=1,n végezd el: Ha nem T(xi) akkor xi  sajátos érték vége(ha) vége(minden) Vége(algoritmus)

{ bemeneti adatok: n, x; kimeneti adat: x }

Ha azon elemeket kívánjuk megtartani, amelyek nem rendelkeznek az adott tulajdonsággal, akkor elégséges, ha a fenti algoritmusokban tagadjuk a feltételt a Ha struktúrában. Ebbe a feladatosztályba a következő feladatok tartoznak: F19. Adott egy csoport minden tagjának neve. Készítsünk táblázatot a csoport lánytagjainak nevével! F20. Adva van egy természetes szám. Generáljuk a szám összes osztóját! F21. Abból a célból, hogy kiszámítsák az első év végén a csoport átlagát, egy kimutatásban feltüntették az első év végén szerzett vizsgajegyeket, de be­ írták az 5-ösnél kisebb jegyeket is. Mivel az átlagba nem számítanak be a nem átmenő jegyek, javítsuk ki a kimutatást úgy, hogy ezeket a jegyeket írjuk felül 0-val!

4.3. Összetett programozási tételek 4.3.1. Szétválogatás

88

4. PROGRAMOZÁSI TÉTELEK

A Kiválogatás egy sorozatot dolgoz fel, amelyből kiválogat bizonyos elemeket. Feltevődik a kérdés: mi történik azokkal az elemekkel, amelyeket nem válogat­ tunk ki? Lesznek feladatok, amelyek azt kérik, hogy két vagy több sorozatba válogassuk szét az adott sorozatot. Ezen feladatok jellemzője, hogy a bemenet egy sorozat és a kimenet több so­ rozat. Gyakorlatilag, egymás után több kiválogatást fogunk végezni, mindig a kért tulajdonság alapján. Előbb kiválogatjuk az adott sorozatból egy újba azo­ kat, amelyek az első tulajdonsággal rendelkeznek, majd a félretett adatokból, amelyek megmaradtak az első kiválogatás után, kiválogatjuk azokat, amelyek a második tulajdonsággal rendelkeznek és így tovább. Következik, hogy az algo­ ritmus többször alkalmazza azt az alprogramot, amely egy adott sorozatot két részre szétválogat. A feladatot többféleképpen oldhatjuk meg. Az első változatban az adott so­ rozatból létrehozunk két új sorozatot: az egyikbe a tulajdonság alapján tesszük az adatokat, a másodikba a megmaradtakat helyezzük. Sajnos, mind a két új so­ rozat méretét az eredetivel azonos méretűnek kell deklarálnunk, mivel nem tud­ hatjuk előre az új sorozatok valós méretét. Előfordulhat, hogy minden elem át­ vándorol valamelyik sorozatba, és a másik üres marad. Az algoritmusban ydb és zdb a szétválogatás során létrehozott y, illetve z so­ rozatba helyezett elemek számát jelöli. Algoritmus Szétválogatás_1(n,x,ydb,y,zdb,z): ydb  0 { bemeneti adatok: n, x; kimeneti adatok: ydb, y, zdb, z } zdb  0 Minden i=1,n végezd el: Ha T(xi) akkor ydb  ydb + 1 { az adott tulajdonságú elemek } yydb  xi { az y sorozatba kerülnek } különben zdb  zdb + 1 { azok, amelyek nem rendelkeznek az adott } zzdb  xi { tulajdonsággal a z sorozatba kerülnek } vége(ha) vége(minden) Vége(algoritmus)

Ha tárgazdaságos megoldást szeretnénk megvalósítani, a feladat megoldható egyetlen sorozattal is. A kiválogatott elemeket „összehordjuk” a sorozat első ré­ szébe (az elsőtől haladva a vége felé), míg a megmaradtakat az eredeti sorozat végére (az utolsótól haladva az első felé). Természetesen, nem fogunk ütközni, mivel pontosan n elemet kell n helyre „átrendezni”.

4. PROGRAMOZÁSI TÉTELEK

89

Vegyük észre, hogy a megmaradt elemek sorozata az eredeti sorozatban el­ foglalt relatív pozícióik fordított sorrendjében áll majd rendelkezésünkre. Algoritmus Szétválogatás_2(n,x,ydb,y): ydb  0 { bemeneti adatok: n, x; kimeneti adatok: ydb, y } zdb  0 Minden i=1,n végezd el: Ha T(xi) akkor ydb  ydb + 1 { az adott tulajdonságú elemek az y sorozatba } yydb  xi { kerülnek az első helytől kezdődően } különben zdb  zdb + 1 { azok, amelyek nem rendelkeznek az adott tulajdonsággal } yn-zdb+1  xi { ugyancsak az y-ba kerülnek, az utolsó helytől kezdődően } vége(ha) vége(minden) Vége(algoritmus)

Ha meg szeretnénk tekinteni a szétválogatás eredményét, akkor ezt megte ­ hetjük például a következő programrészlettel. Vegyük észre, hogy ebben az esetben nincs szükség a zdb változó értékére. ... Ki: 'A T tulajdonságú elemek sorozata:' Minden i=1,ydb végezd el: Ki: yi vége(minden) Ki: 'Azok, amelyek nem T tulajdonságúak:' Minden i=ydb+1,n végezd el: Ki: yi vége(minden) ...

Tételezzük fel, hogy egy ilyen szétválogatás után nincs már szükségünk töb­ bé az eredeti sorozatra. Ebben az esetben a szétválogatás elvégezhető helyben. Egyszerű lenne a következő algoritmus: elindulunk a tömbben elölről és há­ tulról, s keresünk olyan elemeket, amelyeket fel kell cserélni. Ha találunk, akkor cserélünk, majd folytatjuk a keresést. Mi ennél egy kicsit hatékonyabb változa­ tot fogunk vizsgálni. [23] A megoldást úgy végezzük el, hogy a tömb első elemét kivesszük a helyéről. Az utolsó elemtől visszafelé keresünk egy olyat, amely adott tulajdonságú, s ezt előre hozzuk a kivett elem helyére. Ezután a hátul felszabadult helyre elölről ke­ resünk egy nem T tulajdonságú elemet, s ha találunk azt hátratesszük. Mindezt addig végezzük amíg a tömbben két irányban haladva össze nem találkozunk. Az algoritmusban az e változó tárolja annak az aktuális elemnek az indexét, amely adott tulajdonságú, és amelyet balról jobbra haladva találtunk, az u válto­

90

4. PROGRAMOZÁSI TÉTELEK

zó pedig annak az elemnek az indexét, amely nem adott tulajdonságú, és ame­ lyet jobbról balra haladva találtunk. Algoritmus Szétválogatás_3(n,x,db): e  1 { bemeneti adatok: n, x; kimeneti adatok: db } u  n segéd  xe Amíg e < u végezd el: Amíg (e < u) és nem T(xu) végezd el: u  u - 1 vége(amíg) Ha e < u akkor xe  xu e  e + 1 Amíg (e < u) és T(xe) végezd el: e  e + 1 vége(amíg) Ha e < u akkor xu  xe u  u - 1 vége(ha) vége(ha) vége(amíg) xe  segéd Ha T(xe) akkor { az xe = segéd érték függvényében megállapítjuk a } db  e { szétválogatás eredményeként létrejött első sorozat hosszát } különben db  e - 1 vége(ha) Vége(algoritmus)

Legyenek a következő feladatok: F22. Legyen egy n elemű, természetes számokból álló sorozat. Válogassuk szét a páros számokat a páratlan számoktól! F23. Ismerjük n személy esetében, hogy az angol nyelvet vagy a németet beszé­ lik jobban. Válogassuk szét ezeket a személyeket a nyelvtudásuk alapján! F24. Adott egy cég nyilvántartásában n programozó fizetése. Segítsünk a könyvelőnek, hogy szétválogassa a programozókat a fizetésük szerint. Az első csoportba azok a személyek kerülnek, akiknek a fizetésük nagyobb mint 1000 €, a másodikba a megmaradtak közül azok, akiknek a fizetése nagyobb mint 700 €, a harmadikban maradnak a többiek. Az első két feladatot megoldhatjuk a fenti algoritmusok valamelyikével. Az F24-es feladat esetében előbb szétválogatjuk a fizetések értékei alapján a nevek

4. PROGRAMOZÁSI TÉTELEK

91

sorozatát két részsorozatba: az elsőbe az 1000 €-nál nagyobb fizetések kerülnek, a másodikba a többiek. Ezután következik a második részsorozat szétválogatása újabb két részsorozatba.

4.3.2. Sorozat halmazzá alakítása Tudjuk, hogy egy halmaz vagy üres vagy bizonyos számú elemet tartalmaz. Ha egy halmaz sorozat formájában van ábrázolva, az elemei különbözők (egy érték csak egyszer fordul elő). Lássuk előbb, hogyan állapítjuk meg, hogy egy adott sorozat ugyanakkor halmaz-e? Más szóval, hogyan döntjük el, hogy egy sorozat csak különböző ele­ meket tartalmaz-e? Algoritmus Halmaz_1(n,x,ok): i  1 { bemeneti adatok: n, x; kimeneti adatok: ok } ok  igaz Amíg ok és (i < n) végezd el: j  i + 1 Amíg (j  n) és (xi  xj) végezd el: j  j + 1 vége(amíg) ok  j > n i  i + 1 vége(amíg) Vége(algoritmus)

Ha a feladatunkban szükséges az adott sorozatot halmazzá alakítani, vagy egy nem halmazokat feldolgozó alkalmazásban ki kell zárnunk egy adott soro­ zatból a másodszor (harmadszor stb.) megjelenő értékeket, akkor az előbbi al­ goritmust úgy módosítjuk, hogy amikor egy bizonyos érték megjelenik másodszor, felülírjuk az utolsóval. Vegyük észre, hogy ez az algoritmus nagyon hasonlít a Keres_3(n,x) algoritmushoz, ahol egy bizonyos T tulajdonsággal nem rendelkező elemet írtunk felül a sorozat utolsó elemével. Az i indexet csak a különben ágon növeljük, így a sorozat végéről előrehozott értéket újból megvizsgáljuk, hiszen nem tudhatjuk, hogy esetleg nem-e egyenlő egy már létező értékkel. Minden elemkizárás után a sorozat hossza csökken 1-gyel. Algoritmus Halmaz_2(n,x): { bemeneti adatok: n, x; kimeneti adatok: n, x } i  1 Amíg i < n végezd el: j  i + 1 Amíg (j  n) és (xi  xj) végezd el: j  j + 1 vége(amíg)

92 Ha j  n akkor xj  xn n  n - 1 különben i  i + 1 vége(ha) vége(amíg) Vége(algoritmus)

4. PROGRAMOZÁSI TÉTELEK

{ találtunk egy xj-t, amely egyenlő xi-vel } { felülírjuk xj-t a sorozat utolsó elemével } { rövidítjük a sorozatot } { haladunk tovább }

4.3.3. Keresztmetszet Emlékezzünk a Szétválogatás algoritmusra! Ott a bemenet egy sorozat volt, amelyet több sorozatba válogattunk szét. Most azt vizsgáljuk, hogy miként já­ runk el, ha a bemenet áll több sorozatból és létre kell hoznunk azt a sorozatot, amely a keresztmetszetüket tartalmazza. Itt keresztmetszet alatt azt a sorozatot értjük, amely az adott sorozatok közös elemeit tartalmazza. Ez a feladat tehát egy kiválogatás annak a tulajdonságnak az alapján, hogy az illető elem minden adott adathalmazban megtalálható-e. Feltételezzük, hogy az adott sorozatok mind különböző elemeket tartalmaz­ nak és nem rendezett sorozatok. A következő algoritmussal generáljuk az n elemű x és az m elemű y sorozat keresztmetszetét a db elemű z sorozatba. Tehát z olyan elemeket tartalmaz az x sorozatból, amelyek megtalálhatók az y-ban is. Algoritmus Keresztmetszet(n,x,m,y,db,z): db  0 { bemeneti adatok: n, x, m, y; kimeneti adatok: db, z } Minden i1,n végezd el: j  1 Amíg (j  m) és (xi  yj) végezd el: j  j + 1 vége(amíg) Ha j  m akkor db  db + 1 zdb  xi vége(ha) vége(minden) Vége(algoritmus)

Oldjuk meg a következő feladatokat! F25. Határozzuk meg két szám minden közös osztóját!

4. PROGRAMOZÁSI TÉTELEK

93

F26. Emlékezzünk az F3-as feladatra! k kutató, egyenként feljegyezte a Dunadeltában megfigyelt madárfajokat nyáron is, meg télen is. Határozzuk meg a nem költöző madárfajok halmazát! F27. Ismerjük négy személy heti programját. Állapítsuk meg, mely napok esté­ in szervezhetnek kártyapartit! Ezeket a feladatokat természetesen a fenti Keresztmetszet algoritmussal old­ juk meg. De ez a minta több, másként megfogalmazott feladat esetében is alkal­ mazható. Legyen két halmaz.

a) Állapítsuk meg, hogy van-e legalább egy közös elemük! b) Határozzuk meg a közös elemet (miután tudjuk, hogy létezik, Kiválasztás-t alkalmazunk).

c) Határozzunk meg egy közös elemet, ha van ilyen! (Keresés). d) Számoljuk meg a közös elemeket (Megszámlálás)! Bemutatjuk a Keresztmetszet alkalmazását a c) kérdés megválaszolása céljá­ ból: megkeressük az x sorozatban azt az első értéket, amely létezik az y sorozat­ ban is. Szekvenciális Keresés-t alkalmazunk, amely tartalmaz egy Döntés-t is. Természetesen, miután megtaláltuk a két azonos értékű elemet, leállunk. Az érték változó a közös értéket tartalmazza, az xh a közös érték helyét az x soro­ zatban, az yh a közös érték helyét az y sorozatban. Algoritmus Közös_Elem(n,x,m,y,van,érték,xh,yh): i  1 { bemeneti adatok: n, x, m, y; kimeneti adatok: van, érték, xh, yh } van  hamis Amíg nem van és (i  n) végezd el: j  1 Amíg (j  m) és (xi  yj) végezd el: j  j+1 vége(amíg) Ha j  m akkor van  igaz érték  xi xh  i yh  j különben i  i+1 vége(ha) vége(amíg) Vége(algoritmus)

4.3.4. Egyesítés

94

4. PROGRAMOZÁSI TÉTELEK

Az egyesítés algoritmusa szintén hasonló az eddigiekhez, a különbség abban áll, hogy olyan elemeket helyezünk az eredménybe, amelyek legalább az egyik so­ rozatban megtalálhatók. Előbb a z sorozatba másoljuk az x sorozatot (ehhez olyan értékadást használhatunk, amely egy tömbnek egy másik tömb értékét ad­ ja át), majd kiválogatjuk y-ból azokat az elemeket, amelyeket nem találtunk meg x-ben. Algoritmus Egyesítés(n,x,m,y,db,z): { bemeneti adatok: n, x, m, y; kimeneti adatok: db, z } z  x { bemásoljuk a teljes x tömböt a z tömbbe } db  n Minden j=1,m végezd el: i  1 Amíg (i  n) és (xi  yj) végezd el: i  i + 1 vége(amíg) Ha i > n akkor db  db + 1 zdb  yj vége(ha) vége(minden) Vége(algoritmus)

Oldjuk meg a következő feladatokat! F28. Ismerjük két szám prímosztóit. Határozzuk meg a két szám legkisebb kö­ zös többszörösének prímosztóit! F29. Adott két tanár órarendje! Határozzuk meg azokat az órákat, amikor leg­ alább egyikük helyettesíthet egy esetleg hiányzó másik tanárt!

4.3.5. Összefésülés Az Egyesítés és a Keresztmetszet algoritmusok négyzetes bonyolultságúak, mi­ vel két-két egymásba ágyazott ciklust tartalmaznak 8. Ezekben az algoritmusok­ ban nem rendezett sorozatokkal dolgoztunk. Ez a két művelet megvalósítható li­ neáris algoritmussal, ha a sorozatok rendezettek, és a két rendezett sorozatot egy harmadik, szintén rendezett sorozatban szeretnénk egyesíteni. Legyen például a következő feladat: F30. Adott az elsőéves egyetemisták névsora csoportonként ábécésorrendben. Állítsuk elő az évfolyam névsorát ábécésorrendben!

Két egymásba ágyazott ismétlő struktúrát tartalmaznak, mindkettő maximális lépésszáma egyenlő a bemeneti adatok méretével. 8

4. PROGRAMOZÁSI TÉTELEK

95

Az elemzés során észrevesszük, hogy a bemeneti adatok több rendezett soro­ zat formájában állnak rendelkezésre, a kimeneti adat pedig egyetlen rendezett sorozat, amely tartalmaz minden bemeneti adatot egyszer. A megoldás visszave­ zethető két rendezett sorozat egyesítésére (összefésülésére), így az eredményt majd egyesítjük egy újabb sorozattal és így tovább. A következőkben általánosítjuk a fenti feladatot, vagyis eltekintünk attól az észrevételtől, miszerint az adott sorozatok csak különböző elemeket tartalmaz­ nak (a névsorok keresztmetszete üres halmaz). Az általánosításnak megfelelően vizsgáljuk az egyenlőség lehetőségét is. A megoldásban elindulunk a két sorozat elejéről és az eredménybe elhelyez­ zük az aktuális elempár azon értékét, amelyik kisebb. Abban a sorozatban, ame­ lyikből „elfogyasztattunk” egy elemet, továbblépünk. Ha a két összehasonlított érték egyenlő volt, mind a két sorozatban továbblépünk és az aktuális értéket az eredménysorozatba csak egyszer írjuk be. Addig végezzük ezeket a művelete­ ket, amíg valamelyik sorozatnak a végére érünk. Ekkor a másik sorozatban megmaradt elemeket átmásoljuk egyenként az eredménysorozatba. Mivel nem tudhatjuk előre melyik sorozat ért véget, megírjuk a megfelelő algoritmusrészle­ tet mindkét sorozat esetében. Algoritmus Összefésülés_1(n,x,m,y,db,z): db  0 { bemeneti adatok: n, x, m, y; kimeneti adatok: db, z } i  1 j  1 Amíg (i  n) és (j  m) végezd el: db  db+1 Ha xi < yj akkor zdb  xi i  i+1 különben Ha xi = yj akkor zdb  xi i  i+1 j  j+1 különben zdb  yj j  j+1 vége(ha) vége(ha) vége(amíg) Amíg i  n végezd el: db  db+1 zdb  xi i  i+1

96

4. PROGRAMOZÁSI TÉTELEK

vége(amíg) Amíg j  m végezd el: db  db+1 zdb  yj j  j+1 vége(amíg) Vége(algoritmus)

Vegyük észre, hogy, ha olyan szerencsések lettünk volna, hogy xn egyenlő lett volna ym-mel, akkor a két utolsó Amíg struktúrát nem hajtotta volna végre a program egyetlen egyszer sem. Kihasználjuk ezt az észrevételt és elhelyezünk mindkét sorozat végére egy fiktív elemet (őrszem). A két elem értéke legyen na­ gyobb mint az utolsó elem a két sorozatban, és legyenek egyenlők! A következő algoritmusban ezt az értéket végtelen-nel jelöljük. Algoritmus Összefésül_2(n,x,m,y,db,z): db  0 { bemeneti adatok: n, x, m, y; kimeneti adatok: db, z } i  1 j  1 xn+1  végtelen ym+1  végtelen Amíg (i  n) vagy (j  m) végezd el: { és helyett vagy } db  db+1 Ha xi < yj akkor zdb  xi i  i+1 különben Ha xi = yj akkor zdb  xi i  i+1 j  j+1 különben zdb  yj j  j+1 vége(ha) vége(ha) vége(amíg) Vége(algoritmus)

Az összefésült sorozat nem fogja tartalmazni a végtelen-t. Természetesen, ha szükségünk van rá, mivel az eredményt össze kell fésülnünk egy másik rende­ zett sorozattal, akkor dolgozhatunk úgy is, hogy ezt az eredménysorozat végére tesszük. Ezt úgy érjük el, hogy az algoritmus Amíg struktúrájában a feltételt (i  n + 1) és (j  m + 1)-re változtatjuk.

4. PROGRAMOZÁSI TÉTELEK

97

Lássuk, hogyan egyszerűsíthetjük a fenti algoritmust, ha tudjuk, hogy úgy mint az F30-as feladatban, a két egyesítendő halmaz diszjunkt (a sorozatok kü­ lönböző elemeket tartalmaznak) vagy a sorozatokban többször előforduló ele­ meket is szeretnénk megőrizni. Ebben az esetben tudjuk, hogy az eredményso­ rozat hossza pontosan n + m. Így az algoritmus ismétlő struktúrája nyugodtan lehet Minden típusú. Algoritmus Összefésül_3(n,x,m,y,db,z): i  1 { bemeneti adatok: n, x, m, y; kimeneti adatok: db, z } j  1 xn+1  végtelen ym+1  végtelen Minden db=1,n+m végezd el: Ha xi < yj akkor zdb  xi i  i+1 különben zdb  yj j  j + 1 vége(ha) vége(minden) Vége(algoritmus)

4.4. Megoldott feladatok 4.4.1. Összeg Adott egy p természetes szám, amely az alábbi kifejezés tagjainak számát jelöli 1 1 és adottak az ni, i = 1, 2, ..., p egész számok. Számoljuk ki az S = 1 + + n1 n2 +...+

1 np

kifejezés értékét!

Ha létezik i  {1, 2, …, p} amelyre ni = 0, akkor csak az aktuális tag nem ér­ telmezett. Ebben az esetben számoljuk meg a 0-val egyenlő ni értékeket! [8] Megoldás ■ Beolvassuk p-t és az ni értékeket. Az S összeg kezdőértéke 1 lesz, a nullák számának kezdőértéke szintén 0. Minden szám esetében megvizsgáljuk, hogy egyenlő-e 0-val. Ha igen, akkor növeljük a megfelelő számláló értékét 1gyel, és nem adjuk hozzá az összeghez. A két alkalmazott programozási tétel az Összegszámítás és a Megszámlálás. Algoritmus Összeg(p,n,s,nullák_száma):

98

4. PROGRAMOZÁSI TÉTELEK

nullák_száma  0 { bemeneti adatok: p, n; kimeneti adatok: s, nullák_száma } s  1 { kezdőérték 1, az adott kifejezésnek megfelelően } Minden i=1,p végezd el: Ha ni  0 akkor s  s + 1/ni különben nullák_száma  nullák_száma + 1 vége(ha) vége(minden) Vége(algoritmus)

4.4.2. Párok Adott n egész szám. Írjuk ki azoknak az egymás után következő számoknak a sorszámát, amelyek egyenlők! [8] Megoldás ■ A beolvasás után egy Minden típusú ciklust használva vizsgáljuk az elemeket. Mivel páronként dolgozzuk fel őket, a ciklusváltozó utolsó értéke n – 1 lesz, ha kezdetben 1-től indulunk. Ha 2-től indulunk, akkor az utolsó érték n. Az első esetben az i és i + 1 indexű elemeket hasonlítjuk össze, a második esetben pedig az i – 1 és i indexű elemeket. Ebben a feladatban egy egyszerű Kiválogatás-t végzünk, vagyis a kiválogatott elemeket kiírjuk. Algoritmus Párok(n,x): Minden i=1,n-1 végezd el: Ha xi = xi+1 akkor Ki: i, ' ', i+1 vége(ha) vége(minden) Vége(algoritmus)

{ bemeneti adatok: n, x }

{ kiírjuk a párok indexeit }

4.4.3. Arány Adott n (1  n  100) egész szám (–10000  xi  10000, i = 1, 2, ..., n). Határozzuk meg a páros és páratlan számok száma közötti arányt! Megoldás ■ A páros változóban számoljuk a páros számokat, a páratlan-ban pedig a páratlanokat. Mindkét változó kezdőértéke 0. Az algoritmus a Meg­ számlálás-ra épül, majd kiszámolja az arányt. Természetesen, a hívás helyén előbb megvizsgáljuk a páratlan számok számát, mivel, ha ez 0, nem tudunk osz­ tani és megfelelő üzenet kiírásával zárjuk a programot. Algoritmus Arány(n,a,páros,páratlan): páros  0 { bemeneti adatok: n, a; kimeneti adatok: páros, páratlan } páratlan  0 Minden i=1,n végezd el: Ha ai páratlan akkor

4. PROGRAMOZÁSI TÉTELEK

99

páratlan  páratlan + 1 különben páros  páros + 1 vége(ha) vége(minden) Vége(algoritmus)

4.4.4. Hőmérséklet Június hónap folyamán megmérték a Fekete-tenger vizének hőmérsékletét. Állapítsuk meg, hogy a tenger vize folyamatosan melegedett-e vagy sem! Megoldás ■ A Döntés_4 programozási tételt alkalmazzuk, mivel a teljes sorozat egy bizonyos tulajdonságát vizsgáljuk. Nincs szükség megőrizni a tömb elemei­ nek számát egy változóban, mivel tudjuk, hogy június 30 napos. Algoritmus Hőmérséklet(t,i): { bemeneti adat: t; kimeneti adat: i } i  1 Amíg (i < 30) és (ti  ti+1) végezd el: { amíg az i-edik nap hőmérséklete  az i+1-dikénél } i  i + 1 { haladunk tovább } vége(amíg) Vége(algoritmus)

A hívás helyén az i kimeneti paraméter függvényében kiírjuk a megfelelő üzenetet: ... Ha i = 30 akkor { ha i értéke 30, minden nap legalább olyan meleg volt } Ki: 'Folyamatosan melegedett!' { mint az előző nap } különben Ki: 'Nem melegedett folyamatosan!' vége(ha) ...

4.4.5. Teljes négyzet Döntsük el egy adott n természetes számról, hogy teljes négyzet-e! Megoldás ■ Egy természetes szám akkor teljes négyzet, ha a négyzetgyöke egész szám. Egyszerű lenne meghívni az adott programozási nyelv standard unitjában rendelkezésre álló négyzetgyökvonó alprogramot, meghatározni az eredmény egész részét, majd ezt összehasonlítani a kapott négyzetgyökkel. De nem min ­ den programozási nyelvben áll rendelkezésünkre ilyen függvény. Ezen túlmenő­ en, ezek a függvények a négyzetgyökvonást egy, viszonylag sok lépésben vég­ rehajtott számolás eredményeként adják meg (lásd a következő feladatot).

100

4. PROGRAMOZÁSI TÉTELEK

Mivel a feladat nem a szám négyzetgyökére „kíváncsi”, hanem arra, hogy a szám teljes négyzet-e, a következő algoritmus sokkal eredményesebb, mint a fent leírt lépéssorozat. Algoritmus Teljes_négyzet_e(n,válasz): id  1 { bemeneti adat: n; kimeneti adat: id } Amíg id*id < n végezd el: id  id + 1 vége(amíg) válasz id*id  n Vége(algoritmus)

4.4.6. Négyzetgyök Számítsuk ki

a értékét (a természetes szám), felhasználva a következő soro­

zatot: xn 

1 a   x n 1  , x 0  a x n 1   2

Az eredményt  = 10–6 pontossággal írjuk ki. Megoldás ■ A fenti képlet rekurzív algoritmust sugalmaz, de erre nincs szükség, még akkor sem, ha az x sorozat n-edik tagját az n – 1-edik tag segítségével szá­ moljuk ki. Ugyanakkor szükségünk van egy leállási feltételre. Ha két egymás után kiszámolt tag értéke közötti különbség kisebb vagy egyenlő mint , (0 <   10-6), akkor xn értékét a megfelelő pontosságú közelítő értékének tekintjük. Észrevesszük, hogy ilyen körülmények között x egy tömb, de nem ismerjük az elemek számát. A feladat követelményeinek szétválasztása



A fenti sorozat konvergens és

határértéke éppen

a . A sorozatból annyi tagot számítunk ki amennyi egy adott  pontosságot biztosít, azaz eljárásunk az n-edik tag kiszámítása után megáll, ha  xn – xn–1  < . A módszer megállapítása ■ Megközelítés bizonyos adott pontossággal. A fela­ dat szövegében meg van adva a használandó modell (képlet), tehát csak a soro­ zat elemeit kell kiszámítanunk, a kért pontosság eléréséig. Amikor ez megtör­ tént, kiírhatjuk a kért számot. A megoldás lépéseinek leírása

a)

Be: a, 

4. PROGRAMOZÁSI TÉTELEK

b) Kezdőértékadások:

101

x0  a; x1  (a+1)/2; i  1

c)

Amíg  xi - xi-1  >  végezd el: i  i+1 xi  1/2*(xi-1 + a/xn-1) vége(amíg)

d)

Ki: xi, i, ' közelítő értéket (tagot) számoltunk.'

Lépések finomítása ■ Ha tármegtakarítással is foglalkozunk, mivel nincs szük­ ség az egész sorozatra, a következőképpen járunk el (hasonlóan a Fibonacci-so­ rozathoz): Algoritmus Négyzetgyök(a,eps,y): x  a { bemeneti adatok: a,eps; kimeneti adat: y } y  (a + 1)/2 Amíg  x - y   eps végezd el: x  y y  1/2*(x + a/x) vége(amíg) Vége(algoritmus)

4.4.7. Statisztika Adva van n személy születési dátuma, születési helye, valamint aktuális lakcí­ me. Állapítsuk meg azon 1989 előtt született személyek számát, akik jelenleg a szülőhelyükön laknak. Írjuk ki ezek számát, valamint a születési évüket! Ha nincs ilyen személy, írjunk ki megfelelő üzenetet! (Az eredeti adatokra nincs többé szükség.) Megoldás ■ Alkalmazzuk a Kiválogatás-t. Kiválogatjuk az 1989 előtt született személyeket akiknek a születési helye megegyezik az aktuális lakóhellyel. Meg­ számoljuk őket és az eredeti tömb első felébe gyűjtjük. Így helyben végezzük a kiválogatást (ezzel elrontjuk az eredeti adatokat, de a feladat szövege ezt nem tiltja). Az eredményt a módosított tömb tartalmazza. Algoritmus Statisztika(n,év,szül_hely,lakhely,hány): { bemeneti adatok: n, év, szül_hely lakhely; kimeneti adat: hány, év } hány  0 { még nem találtunk 1989 előtt született személyt } Minden i=1,n végezd el: Ha (évi < 1989) és (szül_helyi = lakhelyi) akkor hány  hány + 1 { nő az adott tulajdonságú személyek száma } évhány  évi vége(ha) vége(minden) Vége(algoritmus)

102

4. PROGRAMOZÁSI TÉTELEK

A hívás helyén a hány kimeneti paraméter függvényében kiírjuk a megfelelő üzenetet, illetve a kiválogatott éveket: ... Ha hány = 0 akkor Ki: 'Nem létezik ilyen személy!' különben Ki: hány { az 1989 előtt született személyek száma } Minden i=1,hány végezd el: Ki: évi { az 1989 előtti születési évek } vége(minden) vége(ha) ...

4.4.8. Osztályfőnök Az év végén az osztályfőnöknek ki kell töltenie egy kérdőívet, amelyen a követ­ kező rovatok találhatók: a 9–10 közötti átlagok száma, a 8–9 közötti átlagok száma stb. Írjunk programot, amely elvégzi a feladatot és kiszámítja minden ka­ tegóriának az átlagát is! Megoldás ■ A Szétválogatás_3 algoritmust használjuk fel. Az algoritmusban az első változóval haladunk balról jobbra és megkeressük az első olyan átlagot, amely nem tartozik az aktuális kategóriába. Az utolsó változóval jobbról balra haladunk és megkeressük azt az első átlagot, amely az aktuális kategóriához tar­ tozik. A határ változóval meghatározzuk a keresett átlagkategóriákat, a db pedig az aktuális részsorozat utolsó elemének az indexe és így az aktuális részsorozat hosszát jelöli. Algoritmus Osztály(n,átlagok): { bemeneti adatok: n, átlagok } első  1 { az első index az aktuális sorozatban } utolsó  n { az utolsó index az aktuális sorozatban } Minden határ=9,5 végezd el: { lehetséges átlagok } p  első { a p segédváltozó, az aktuális sorozat első indexét tartalmazza } segéd  átlagokelső { az első átlagot félretesszük } Amíg első < utolsó végezd el: { jobbról balra haladva } { keressük az első elemet, amely rendelkezik a tulajdonsággal, } Amíg (elsőhatár) végezd el: utolsó  utolsó - 1 vége(amíg) Ha első < utolsó akkor { átlagokutolsó rendelkezik a tulajdonsággal, felülírjuk átlagokelső-re } átlagokelső  átlagokutolsó első  első + 1 { balról jobbra haladva } { keressük az első elemet, amely nem rendelkezik a tulajdonsággal }

4. PROGRAMOZÁSI TÉTELEK

103

Amíg (elsőhatár) végezd el: első  első + 1 vége(amíg) { átlagokelső nem rendelkezik a tulajdonsággal, } Ha első < n akkor { felülírjuk átlagokutolsó-ra } átlagokutolsó  átlagokelső utolsó  utolsó - 1 { hátulról haladunk előre } vége(ha) vége(ha) vége(amíg) { a segéd-be helyezett elemet berakjuk a „szabad” helyre } átlagokelső  segéd { ha a segéd-ben lévő elem rendelkezik a kért tulajdonsággal, } Ha átlagokelső > határ akkor db  első { az aktuális részsorozat utolsó elemének indexe } különben db  első - 1 { átlagokelső a továbbbontandó részsorozathoz tartozik } vége(ha) k  db - p + 1 { az aktuális részsorozat elemeinek száma } Ha k ≠ 0 akkor átl  0 Minden j=p,db végezd el: Ki: átlagokj átl  átl + átlagokj vége(minden) Ki: átl/k vége(ha) első  db + 1 { a továbbbontandó részsorozat első elemének az indexe } utolsó  n { helyreállítjuk az utolsó változót egy újabb feldolgozás végett } vége(minden) Vége(algoritmus)

4.4.9. Bűvös négyzet Vizsgáljuk meg, hogy egy adott, n  n méretű négyzetes tömb, amelynek elemei természetes számok, bűvös négyzet-e vagy sem (n ≤ 10)! Megoldás ■ Egy különböző természetes számokkal feltöltött négyzetes tömböt akkor nevezünk bűvös négyzetnek, ha a számok összege soronként és oszlopon­ ként, valamint a két átló mentén azonos. Ezt a közös összeget bűvös összegnek nevezzük és az algoritmusban BÖsszeg-gel jelöljük. A főátlón levő elemek: a1,1, a2,2, ..., an,n, tehát ai,i, i = 1, 2, ..., n.

104

4. PROGRAMOZÁSI TÉTELEK

A mellékátlón levő elemek: a1,n, a2,n–1, ..., an,1, vagyis ai,n–i+1, ahol az i = 1, 2, ..., n (a sorindex és az oszlopindex összege n + 1). Az aktuális összeget Összeg-gel jelöljük. Az algoritmust azonnal leállítjuk, ha kiderült, hogy a négyzet nem bűvös. Algoritmus Bűvös_négyzet(n,t,bűvös): Összeg  0 { bemeneti adatok: n, t; kimeneti adat: bűvös } BÖsszeg  0 Minden i=1,n végezd el: BÖsszeg  BÖsszeg + ti,i { a főátló elemeinek összege } Összeg  Összeg + ti,n-i+1 { a mellékátló elemeinek összege } vége(minden) bűvös  Összeg=BÖsszeg Ha bűvös akkor i  1 { sorok összegei } Amíg bűvös és (i  n) végezd el: Összeg  0 Minden j=1,n végezd el: Összeg  Összeg + ti,j vége(minden) bűvös  Összeg=BÖsszeg i  i + 1 vége(amíg) j  1 { oszlopok összegei } Amíg bűvös és (j  n) végezd el: Összeg  0 Minden i=1,n végezd el: Összeg  Összeg + ti,j vége(minden) bűvös  Összeg=BÖsszeg j  j + 1 vége(amíg) vége(ha) Vége(algoritmus)

Példa ■ Az alábbi példában a bűvös összeg 34.9 16 3 2 13 5 10 11 8 9 6 7 12 4 15 14 1

4.4.10.Maximális összegű leghosszabb részsorozat 9

A példában szereplő bűvös négyzet Albrecht Dürer Melancolia című képén látható.

4. PROGRAMOZÁSI TÉTELEK

105

Adott egy n egész számból álló számsorozat, amely biztosan tartalmaz legalább egy pozitív számot. Írjunk programot, amely meghatározza azt a leghosszabb részsorozatot, amelynek összege a lehető legnagyobb. Példa ■ n = 10, a sorozat: 1, 2, –6, 3, 4, 5, –2, 10, –5, –6. Megoldjuk papíron ceruzával:  Maximális összeg: 20  A részsorozat hossza: 5  A részsorozat első elemének sorszáma: 4  A részsorozat utolsó elemének sorszáma: 8  A keresett részsorozat: 3, 4, 5, –2, 10. A megoldás első változata ■ A legegyszerűbb megoldás arra az ötletre támaszko­ dik, amely szerint generálni fogunk minden bal és jobb egész számpárt, amelyek­ re: 1  bal  jobb  n, és kiszámítjuk a megfelelő tbal, tbal+1, ..., tjobb részsorozatok összegét. Közben, kiválasztjuk azt a részsorozatot, amelynek összege maximális. Algoritmus MaxÖsszegűRészsorozat_1(n,t,MaxS): { bemeneti adatok: n, t; kimeneti adat: MaxS } MaxS  t1 { a keresett maximális összeg } Minden bal=1,n végezd el: { bal: a részsorozat első elemének indexe } Minden jobb=bal,n végezd el: { jobb: a részsorozat utolsó elemének indexe } össz  0 { össz: az aktuális részsorozat összege } Minden i=bal,jobb végezd el: össz  össz + ti vége(minden) Ha MaxS < össz akkor MaxS  össz vége(ha) vége(minden) vége(minden) Vége(algoritmus)

Ennek az algoritmusnak a bonyolultsága O(n3) mivel három egymásba ágya­ zott ciklusból áll. Ha n = 5000, egy közepes teljesítményű számítógép körülbe­ lül hét percet dolgozna. Ez a bonyolultság elfogadhatatlan, ezért előbb keresünk egy négyzetes algo­ ritmust, majd egy lineárisat! A megoldás második változata ■ Észrevesszük, hogy a tbal, tbal+1, ..., tjobb részso­ rozat összegét ki lehet számítani az előző lépésben kiszámolt tbal, tbal+1, ..., tjobb–1 részsorozat összegének segítségével. Minden tbal, tbal+1, ..., tjobb-nak megfelelő öszszeget összehasonlítjuk az eddig megtalált maximális összeggel azonnal a kiszámolás után.

106

4. PROGRAMOZÁSI TÉTELEK

Az algoritmusunk így négyzetes lesz, mivel csak két egymásba ágyazott cik­ lust fog tartalmazni: Algoritmus MaxÖsszegűRészsorozat_2(n,t,MaxS): MaxS  t1 { bemeneti adatok: n, t; kimeneti adat: MaxS } Minden bal=1,n végezd el: össz  0 Minden jobb=bal,n végezd el: össz  össz + tjobb Ha MaxS < össz akkor MaxS  össz vége(ha) vége(minden) vége(minden) Vége(algoritmus)

A megoldás harmadik változata ■ Jelöljük Részösszi-vel a t1, t2, ..., ti részsorozat elemeinek összegét. Az algoritmus elején kiszámítjuk ezeket a parciális összege­ ket minden i = 0, 1, …, n esetében, és megőrizzük a Részössz0, ..., Részösszn tömbben. A tbal, tbal+1, ..., tjobb részsorozatok összegét a Részössz0, ..., Részösszn tömb segítségével számítjuk ki: Algoritmus MaxÖsszegűRészsorozat_3(n,t,MaxS,eleje,vége): { bemeneti adatok: n, t; kimeneti adatok: MaxS, eleje, vége } Részössz0  0 { szükség van a 0-dik elemre amikor i = 1 } Minden i=1,n végezd el: { kiszámítjuk a parciális összegeket } Részösszi  Részösszi-1 + ti vége(minden) MaxS  t1 { a maximális összeg kezdőértéke } Minden bal=1,n végezd el: Minden jobb=bal,n végezd el: össz  Részösszjobb - Részösszbal-1 Ha MaxS < össz akkor MaxS  össz { az aktuális maximális összeg } eleje  bal vége  jobb vége(ha) vége(minden) vége(minden) Vége(algoritmus)

Ez az algoritmus jobb mint az első, de hasonlóan a második változathoz, a bonyolultsága négyzetes. A megoldás negyedik változata ■ Ha sikerül úgy megoldani a feladatot, hogy a tömböt csak egyszer járjuk be, az algoritmus lineáris lesz. A bejárással párhuza­ mosan kiszámoljuk és megőrizzük a maximális összeget. Tételezzük fel, hogy meghatároztuk a maximális összeget és a neki megfelelő t1, t2, ..., ti–1 részsorozatot. Észrevétel ■ A sorozat első i elemének bejárása után a maximális összeg vagy egyenlő az első i – 1 elem maximális összegével, vagy nagyobb ennél. Ha na­

4. PROGRAMOZÁSI TÉTELEK

107

gyobb, akkor ez azt jelenti, hogy a maximális összeghez hozzátartozik az i-edik elem is, tehát olyan részsorozatunk van, amelynek utolsó eleme az adott sorozat i-edik eleme. (Ezt az aktuális összeget AktMax-nak hívjuk). Az AktMax összeget nem fogjuk kiszámítani mindig elölről, hanem a már tárgyalt módszer alapján: felhasználjuk az (i–1)-edik helyen végződő részsorozat maximális összegét. ... MaxS  t1 AktMax  MaxS Minden i=2,n végezd el: AktMax  max(AktMax+ti,0) MaxS  max(MaxS,AktMax) vége(minden) ...

{*}

Fontos ■ A feladat garantálja, hogy a sorozatban van legalább egy pozitív szám! Következik, hogy, ha a sorozatnak n – 1 negatív eleme és egyetlen pozitív eleme lenne, akkor a maximális összegű részsorozat ebből az egyetlen pozitív számból állna. Következik, hogy amíg egy aktuális összeg pozitív, addig végrehajthatjuk a {*} csillaggal jelzett értékadást: az AktMax-hoz hozzáadjuk a ti-t. Ha, egy adott pillanatban AktMax negatívvá válik, akkor AktMax új értéke ti lesz. Algoritmus MaxÖsszegűRészsorozat_4(n,t,eleje,vége,MaxS): { bemeneti adatok: n, t; kimeneti adatok: MaxS, eleje, vége } MaxS  t1 { a maximális összeg kezdőértéke } AktMax  MaxS { az aktuális összeg kezdőértéke } kezd  1 { az aktuális részsorozat első eleme } eleje  1 { a részsorozat első elemének sorszáma } vége  1 { a részsorozat utolsó elemének sorszáma } Minden i=2,n végezd el: Ha AktMax < 0 akkor AktMax  ti kezd  i különben AktMax  AktMax + ti vége(ha) Ha MaxS < AktMax akkor MaxS  AktMax eleje  kezd vége  i vége(ha) vége(minden) Vége(algoritmus)

108

4. PROGRAMOZÁSI TÉTELEK

4.5. Kitűzött feladatok10 1. Olvassunk be n valós számot! Határozzuk meg a beolvasott számok abszolút értékeinek számtani középarányosát! 2. Egyenként beolvasunk n valós számot. Számítsuk ki a szigorúan pozitív szá­ mok szorzatát, valamint a negatív számok számtani középarányosát! 3. Olvassunk be n természetes számot! Határozzuk meg a 7-nél kisebb számok szorzatát, valamint a 10-nél nagyobb számok összegét! 4. Számítsuk ki a következő összegeket: a) S1 = 12 + 32 + 52 +...+ (2n – 1)2; b) S2 = 1 + 1 · 2 + 1 · 2 · 3 + ... + 1 · 2 · 3 · ... · n. ha tudjuk, hogy n egy 10-nél kisebb természetes szám. Útmutatás ■ A második feladatnál észrevesszük, hogy az összeg olyan egy­ másután következő tagokból áll, amelyek csak egyetlen szorzótényezőben különböznek. Ebből következik, hogy egy-egy tag kiszámítását nem kezdjük mindig elölről, hanem az előző lépésben kiszámolt tagot „visszük tovább”. 5. Számítsuk ki az első n természetes szám összegét és szorzatát! Figyelem ■ Gondoljunk egy nem hozzáértő felhasználóra is. Előbb számít­ suk ki melyik az a legnagyobb szám, amelynek ki lehet számítani a faktoriá­ lisát, figyelembe véve annak a legnagyobb egész számnak az értékét, amit a használt programozási nyelv megfelelő típusa ábrázolni képes. A számokat a beolvasás sorrendjében dolgozzuk fel. Nincs szükség tömbre. Mindkét eset­ ben vigyázzunk, hogy ne lépjük túl a legnagyobb ábrázolható számot. 6. Tegyük fel, hogy félévi vizsgára készülünk és a laborjegyek már megvan ­ nak. Számítsuk ki az 5-ösnél nagyobb laborjegyek átlagát. Útmutatás ■ Nincs szükség tömbre, mivel a jegyeket egymás után dolgoz­ zuk fel. Meg kell számolni, hány tagja van az összegnek, egyébként nem tudjuk mennyivel kell osztani. Vigyázat: előfordulhat, hogy nincs egyetlen átmenő jegy sem! 7. Számítsuk ki a következő összeget: S = 1 + 1 / 2! + 1 / 3! + … + 1/ n! Útmutatás ■ A faktoriálist nem számoljuk ki, hanem minden lépésnél elosztjuk az előző tagot az aktuális lépésszámmal. Oldjuk meg a fejezetben található F1, ..., F30 feladatokat! Alkalmazzuk a megfelelő programozási tételt! 10

109

4. PROGRAMOZÁSI TÉTELEK

8. Számítsuk ki a következő összeget: S 1

x

2

2!



x

4

4!



x

6

6!

 ...  (1)

x

n

2n

(2n)!

Útmutatás 



x2 2! x6 6!



x2 1 2





 1   

x2 

 ;  1 2 

x4  x2 1 2  3  4  5  6



x4 4! x4





x2  x2 1 2  3  4

x2

4! 5  6





x2   x2        1 2   3  4 

  

x4  x2   ; ...  4!  5  6 

9. Írjunk programot, amely megállapítja, hogy legkevesebb hány számot kell összeadni ahhoz, hogy az 1 + 2 + 3 + ... összeg nagyobb vagy egyenlő le ­ gyen egy adott n  N* számnál. 10. Erősen összetett számnak nevezzük azt a természetes számot, amelynek több osztója van, mint bármely, nála kisebb természetes számnak. Írjunk progra­ mot, amely adott n-ig erősen összetett számokat keres! 11. Két természetes számot rokonnak nevezünk, ha van legalább két (különbö­ ző) közös számjegyük. Döntsük el adott két számról, hogy rokonok-e vagy sem! 12. Két természetes számot barátnak nevezünk, ha a 10-es számrendszerben fel­ írva van legalább egy közös számjegyük. Határozzuk meg két adott szám számjegyeit a 10-es számrendszerben, majd döntsük el, hogy a két szám barát-e. 13. Adva van egy n elemű természetes számokat tartalmazó sorozat. Keressük meg és írjuk ki azt a leghosszabb sb, sb+1, …, sj tömbszakaszt, amely csak prímszámokat tartalmaz! Útmutatás ■ Elemezzük a következő eseteket:  Ha minden szám prím?  Ha nincs egyetlen prímszám sem?  Ha az aktuális részsorozathoz hozzátartozik az adott sorozat utolsó eleme is? 14. Ismert, hogy, ha egy adott n természetes számot ismételten alávetünk a kö­ vetkező feldolgozásnak, eljutunk az 1-es számhoz: ha n páros, akkor eloszt­ juk 2-vel, ha n páratlan, megszorozzuk 3-mal és az eredményhez hozzá­ adunk 1-et. Azoknak a lépéseknek a számát, amelyek egy adott n-ből az 1hez vezetnek, karakterisztikának nevezzük.

110

4. PROGRAMOZÁSI TÉTELEK

Írjunk programot, amely meghatározza azt az n számot, amely az [a, b] intervallumhoz tartozik és amelynek a karakterisztikája a legnagyobb. 15. Adott a következő sorozat, amelynek minden elemét – az elsőt kivéve – az előző elem segítségével generáljuk: 1, 11, 21, 1211, 111221, ... A generálási szabály a következő:  megszámoljuk, balról jobbra haladva az előző érték számjegyeit;  az új értéket úgy kapjuk meg, hogy beírjuk a régi érték összes számjegyei­ nek előfordulási számát és az illető számjegyet . Határozzuk meg az n-edik (n  20) elemét a sorozatnak! Útmutatás ■ Mivel a generálási szabályt ismerjük a feladat szövegéből, lássuk a megoldáshoz vezető algoritmust. Feltételezzük, hogy a sorozat 6-dik elemét akarjuk kiírni. Generáljuk egyenként az elemeket a 6-dikig:  T1 = 1 tartalmaz egy számjegyet, egy egyest, tehát T2 = 11.  T2 tartalmaz két számjegyet, két egyest, tehát T3 = 21.  Mivel T3-ban van egy kettesünk és egy egyesünk, T4 = 1211 lesz.  T4-ben van egy egyes és egy kettes, majd még két darab egyes, tehát T5 = 111221 lesz.  T6 = 312211, mivel T5-ben volt három darab egyes, két darab kettes, és egy darab egyes.

5

ALPROGRAMOK

5.1. Bevezetés Láttuk, hogy egy viszonylag bonyolult feladat megoldása visszavezethető bizo­ nyos egyszerű feladatok megoldására. De bármely egyszerű feladat előfordulhat egy másik, bonyolultabb feladat részfeladataként. Ebben az esetben az egyszerű részfeladat megoldása megjelenik a bonyolult feladat megoldásában mint al­ program. [7] Egy algoritmus valamely része alprogramként építhető fel, ha:  Egy algoritmusban több helyen is megjelenik ugyanaz a műveletsor, ame­ lyet ugyanazokra, vagy más-más adatokra kell végrehajtanunk. Ezeket egyszer írjuk le (alprogramként) de többször meghívhatjuk és végrehajt­ hatjuk.  Az illető algoritmus egy részfeladat megoldása. Az algoritmustervezés fentről lefele való (top-down) módszerét alkalmazva (és így a strukturált programozás alapelvét tiszteletben tartva) az eredeti feladatot fokozatosan felbontjuk egyre egyszerűbb részfeladatokra. Amikor olyan részfeladat­ hoz érünk, amelynek a megoldása már eléggé egyszerű, alprogramot írunk e rész megoldására.  Olyan algoritmus, amely egyidőben több alkalmazás része is lehetne (pél­ dául egy egyenletrendszer megoldása). Az ilyen feladatok megoldására írt algoritmusokat unit-okba (modulokba, könyvtárakba) szervezzük, ahon­ nan átvehetők a felhasználók programjaiba. Egyik alapvető szabály, amelyet tiszteletben kell tartanunk arra vonatkozik, hogy amikor megtervezünk egy alprogramot, általánosságra törekszünk ahhoz, hogy a feladat megoldása minél több más feladat megoldásában felhasználható legyen részmegoldásként. Az alprogram fejlécét az algoritmus fejlécéhez hasonlóan jelöljük, de feltün­ tetjük a paraméterlistában az alprogram bemeneti és a kimeneti adatainak meg­ felelő azonosítókat is. A paraméterek azokat az adatokat képviselik, amelyekre

112

5. ALPROGRAMOK

az alprogramnak szüksége van a rajta kívül levő algoritmusokból, valamint azo­ kat, amelyeket a funkciója megvalósításának eredményeként, továbbítania kell. Így az alprogram a paraméterlistán keresztül tartja a kapcsolatot a hívó algorit ­ mussal. A hívó algoritmus változóit (ha ez az úgynevezett főprogramnak felel meg) globális változóknak nevezzük. Ezeket tulajdonképpen minden alprogram „látja”. Azokat a változókat, amelyekkel csak az alprogram dolgozik, és csak innen „láthatók”, lokálisaknak nevezzük. Az alprogramok tervezésekor óvakodjunk a beolvasásoktól és a kiírásoktól, hiszen az alprogram a feldolgozandó adatokat, valamint a kiszámolt eredménye­ ket a paramétereken keresztül kapja, illetve továbbítja. Kivételt képeznek azok az alprogramok, amelyeket éppen a beolvasás, illetve a kiírás megvalósítása cél­ jából írunk. Másik fontos szabályként említjük meg, hogy az alprogram (általában) nem dolgozik globális változókkal. Ezáltal sokkal általánosabb lesz, ugyanis egy olyan alprogram, amely bizonyos globális változókat dolgoz fel, csak abban a programban használható és futtatható, amely pontosan az illető globális változó­ kat tartalmazza. Amikor alprogramokat tervezünk, tanácsos figyelembe venni, hogy az a programozási nyelv, amelyben algoritmusainkat implementálni fogjuk, milyen típusú alprogramokat ismer. A továbbiakban megismerjük a függvény és az eljá­ rás típusú alprogramokat. A kettő közötti különbségek pontos ismerete segít el­ dönteni, hogy melyiket használjuk a különböző feladatok megoldásakor. Az alprogram definiálásakor megadjuk a fejlécében, a nevét, a paramétereit, majd leírjuk az összetett utasítását (az alprogram törzsét). A hívás különböző ­ képpen történik eljárások és függvények esetében. Mivel a függvény az azono­ sítóján keresztül egyetlen értéket térít vissza, meghívható bármely kifejezés ope­ randusaként. Az azonosítót az aktuális paraméterek listája követi. Az eljárást a nevén keresztül, külön utasításban hívjuk, úgyszintén az aktuális paraméterek feltüntetésével a név után. Megjegyezzük, hogy az eljárások és függvények ki­ számolhatnak több értéket, de hangsúlyozzuk, hogy csak a függvény térít vissza (egyetlen) értéket, mégpedig az azonosítóján keresztül. A többi adat a paramé­ terátadás típusának megfelelően „közlekedik” az alprogram és a hívó algoritmus között. A paraméterezés szabályai közül megemlítjük, hogy a paraméterek számá­ nak és típusának rendre meg kell egyezniük az eljárás/függvény definíciójában szereplő paraméterekkel. A formális paraméterek elnevezései (az alprogram fej­ lécében) függetlenek az aktuális paraméterek elnevezéseitől, amelyek a hívó utasításban szerepelnek.

5. ALPROGRAMOK

113

5.2. Algoritmusok és programok fejlesztési módozatai 5.2.1. A top-down típusú (fentről lefele) programozás A top-down típusú programozás lényege abban áll, hogy egyes alprogramokat egymásba ágyazunk. Az eredeti feladatot úgy bontjuk részfeladatokra, hogy ezek különálló egész­ ként legyenek megoldhatók. A felbontást addig folytatjuk amíg a feladatok meg­ felelően egyszerűkké váltak. Felbontáskor feltételezzük, hogy a részfeladatokat már megoldottuk és a felbontás után fejtjük ki az alprogramokat. Így tulajdon­ képpen lépésenként finomítjuk az algoritmust. Mivel a részfeladatok megoldásait egymásba ágyazzuk, egy-egy alprogram további alprogramokat tartalmazhat11.

5.2.2. A bottom-up (lentről felfele) programozás A bottom-up programozási stílusnak megfelelően az alprogramokat az úgyneve­ zett deklarációs szegmensben deklaráljuk, egyiket a másik után, egy bizonyos sorrendben, vagyis úgy, hogy bármely azonosítót a felhasználása előtt deklarál­ junk. Megtervezzük az alprogramokat, amelyek egy-egy részfeladatot oldanak meg, és miután ezek készen vannak, összefűzzük őket. Tehát az alprogram megírása után következik az őt hívó program vagy alprogram megírása, így egy sor alprogram megírása után következik a közöttük levő viszony meghatározása. Nem fogunk arról elmélkedni, hogy melyik módozat a jobb, hiszen a progra­ mozási stílust meghatározza a megoldandó feladat milyensége és a programozó szokásrendszere, de megjegyezzük, hogy a leggyakoribb a vegyes algoritmus­ tervezés. Ezen túlmenően, mindkettőnek vannak előnyei és hátrányai. Fontos, hogy a feladat sajátosságai alapján döntsük el, hogy fentről lefele, vagy lentről felfele fejlesztjük programjainkat. Amikor az alprogramok összefűzési módján gondolkodunk, tartsuk szem előtt a következőket:  Ha egy bizonyos alprogram klasszikus, gyakran előforduló feladatot old meg, és több helyről, több alprogramból is meg kell hívni (például maxi­ Kódoláskor (de már tervezéskor is) tartsuk szem előtt, hogy egy olyan alprogram, amely egy már kifejtett alprogram deklarációs részéhez tartozik, csak az őt tartalmazó alprogramból hívható meg. 11

114

5. ALPROGRAMOK

mumszámolás, rendezés, keresés stb.), akkor nem előnyös ezt elrejteni egy másik alprogramba.  Ha egy bizonyos P alprogram kizárólag csak egy másik Q alprogram ré­ szeként épül a megoldásba, akkor P-t beágyazzuk Q-ba, ezáltal jelezve a P és a Q feltétlen összetartozását.

5.2.3. Moduláris algoritmustervezés A modul egy önálló szerkezeti egység. Lehet program vagy alprogram, leggyak­ rabban több alprogram. Egy modul tartalmazhat egy másik modult, vagy része lehet egy másik modulnak. (A Pascal programozási nyelv unit-jai ugyancsak modulok.) Ha a programunk több modulból tevődik össze, moduláris algoritmusterve­ zésről beszélünk. A bevezetőben részletesen taglaltuk az ilyen szerkezetek elő­ nyeit és szabályait. A modulnak jól meghatározott funkciója van. A modulok alkalmazása leg­ természetesebben a top-down technikával fejlesztett programokra jellemző. Tehát egy modul állhat egy vagy több alprogramból, de lehet egy program is, amely nem tartalmaz alprogramot. A modul független, de hívhat (más) alprogramokat. A modulok között lehet­ séges a kommunikáció, de egy modult nem befolyásolhat egy másik modulon belüli tevékenység. Ne tévesszük össze a modult az alprogrammal! Megemlítjük még egyszer a moduláris algoritmustervezés előnyeit:  A feladatok részfeladatokra bontása a bonyolultság csökkentésének haté­ kony eszköze.  Ha a feladat (és ezáltal a program) mérete csökken, könnyebb az ellenőr­ zés és a karbantartás.  Többen dolgozhatnak ugyanazon a feladaton.  Egy modult úgy írunk meg, hogy újra fel lehessen használni.  Nő a biztonság.  Nő a termelékenység.  Egy modult (mivel független) könnyű kicserélni.

5.2.4. Megoldott feladat

5. ALPROGRAMOK

115

Adott egy legalább három számjegyű természetes szám. Írjunk algoritmust, amely megállapíthatja, hogy az eredeti szám első és utolsó számjegyének elha­ gyásával keletkező új szám teljes négyzet-e! Elemzés Be: n

Feldolgozás Ki: üzenet

Ahol Feldolgozás:

a) Új szám létrehozása a két szélső számjegy elhagyása által. b) A tulajdonság ellenőrzése (teljes négyzet-e?). Az a) megvalósítása:  A számot elosztjuk 10-zel, így levágtuk az utolsó számjegyet.  Amíg a 10 hatványaival való osztás hányadosa nagyobb mint 10, növeljük az osztót. Ezután az új számot elosztjuk az így meghatározott osztóval. Ekkor a maradék lesz a keresett szám. Az alábbiakban az algoritmus megtervezésének három, különböző stílusú változatát mutatjuk be. A ■ Az első változatban (amelyről a továbbiakban lebeszélünk mindenkit), mo­ noblokk formájában ábrázoljuk a feladat megoldását. Algoritmus Teljes_négyzet_e: { monoblokk } Be: n új_szám  [n/10] { új szám generálása } osztó  10 Amíg [új_szám/osztó] > 10 végezd el: osztó  osztó * 10 vége(amíg) új_szam  maradék[új_szám/osztó] { új szám generálásának vége } id  0 { a kért tulajdonság ellenőrzése } Amíg id * id < új_szám végezd el: id  id + 1 vége(amíg) Ha id * id = új_szám akkor Ki: új_szám, ' teljes négyzet!' különben Ki: új_szám, ' nem teljes négyzet!' vége(ha) Vége(algoritmus)

116

5. ALPROGRAMOK

B ■ A második megoldásban a bottom-up programfejlesztési stílusnak megfele­ lően, előbb leírjuk a részfeladatokat megoldó algoritmusokat, majd a főblokk algoritmusa következik, ahonnan meghívjuk az alprogramokat. Algoritmus Beolvas(n): Be: n Vége(algoritmus) Algoritmus Kiír(új_szám,négyzet): Ha négyzet akkor Ki: új_szám, ' teljes négyzet!' különben Ki: új_szám, ' nem teljes négyzet!' vége(ha) Vége(algoritmus)

{ négyzet: logikai változó }

Algoritmus Új_szám_létrehozása(n,új_szám): új_szám  [n/10] { bemeneti paraméter: n; kimeneti: új_szám } osztó  10 Amíg [új_szám/osztó] > 10 végezd el: osztó  oszto * 10 vége(amíg) új_szam  maradék[új_szám/osztó] Vége(algoritmus) Algoritmus Tulajdonság_ellenőrzése(új_szám,négyzet): id  1 { bemeneti paraméter: új_szám; kimeneti: négyzet } Amíg id * id < új_szám végezd el: id  id + 1 vége(amíg) négyzet  id * id = új_szám Vége(algoritmus) Algoritmus Teljes_negyzet_bottom_up: Beolvas(n) Új_szám_létrehozása(n,új_szám) Tulajdonság_ellenőrzése(új_szám,négyzet) Kiír(új_szám,négyzet) Vége(algoritmus)

C ■ A harmadik alak szokatlannak tűnhet, és valóban, algoritmusokat ritkán áb­ rázolunk az alábbi módon. Mégis megpróbáljuk a top-down programfejlesztési módot is bemutatni. Figyeljük meg, hogy a fejléc „elszakad” az algoritmus tu­ lajdonképpeni utasításaitól, mivel magába foglal egy másik alprogramot. Ugyanakkor azt is észrevesszük, hogy nincs szükség a bottom-up változatban feltüntetett minden paraméterre, mivel a megfelelő változók „láthatók” azokban az algoritmusokban, amelyeket egy másik magába foglal. Algoritmus Új_szám_létrehozása(n): Algoritmus Tulajdonság_ellenőrzése:

117

5. ALPROGRAMOK

Algoritmus Kiír: Ha négyzet akkor { kiírás kezdődik } Ki: új_szám, ' teljes négyzet!' különben Ki: új_szám, ' nem teljes négyzet!' vége(ha) Vége(algoritmus) { kiírás vége } id  1 { a tulajdonság ellenőrzése kezdődik } Amíg id * id < új_szám végezd el: id  id + 1 vége(amíg) négyzet  id * id = új_szám Kiír { a kiírás hívása } Vége(algoritmus) { a tulajdonság ellenőrzése véget ért } új_szám  [n/10] osztó  10 Amíg [új_szám/oszto] > 10 végezd el: osztó  osztó * 10 vége(amíg) új_szam  maradék[új_szám/osztó] Tulajdonság_ellenőrzése Vége(algoritmus)

{ új szám generálása kezdődik }

{ meghívjuk a belső eljárást } { új szám generálása véget ért }

Algoritmus Beolvas(n): Be: n Vége(algoritmus) Algoritmus Teljes_négyzet_top_down: Beolvas(n) Új_szám_létrehozása(n) Vége(algoritmus)

5.3. A moduláris programozás alapszabályai  Az eredeti feladatot részfeladatokra bontjuk.  minden rész számára megtervezzük a megoldást jelentő algoritmust.  Ezek az algoritmusok legyenek minél függetlenebbek, de álljanak jól defi­ niált kapcsolatban egymással.  A részfeladatok megoldásainak összessége tartalmazza a feladat megoldási algoritmusát.

5.3.1. Moduláris dekompozíció

118

5. ALPROGRAMOK

A moduláris dekompozíció a feladat több, egyszerűbb részfeladatra bontását je­ lenti, amely részfeladatok megoldása már egymástól függetlenül elvégezhető. A módszert általában ismételten alkalmazzuk, azaz az alrendszereket magu­ kat is felbontjuk. Ezzel lehetővé tesszük azt is, hogy a feladat megoldásán egy­ szerre több személy is dolgozzon. A módszer egy fával ábrázolható, ahol a fa csomópontjai az egyes dekompozíciós lépéseknek felelnek meg.

5.3.2. Moduláris kompozíció Olyan szoftverelemek létrehozását támogatja, amelyek szabadon kombinálhatók egymással. Algoritmusainkat már meglévő egységekből építjük fel.

5.3.3. Modulok tulajdonságai Moduláris érthetőség ■ A modulok önállóan is egy-egy értelmes egységet alkos­ sanak, megértésükhöz minél kevesebb „szomszédos” modulra legyen szükség. Moduláris folytonosság ■ A specifikáció „kis” változtatása esetén a program­ ban is csak „kis” változtatásra legyen szükség. Moduláris védelem ■ Célunk a program egészének védelme az abnormális hely­ zetek hatásaitól. Egy hiba hatása egy – esetleg néhány – modulra korlátozódjon!

5.3.4. A modularitás alapelvei A modulokat nyelvi egységek támogassák ■ A modulok illeszkedjenek a hasz­ nált programozási nyelv szintaktikai egységeihez. Kevés kapcsolat legyen ■ Minden modul minél kevesebb másik modullal kom­ munikáljon! Gyenge legyen a kapcsolat amennyi csak lehetséges!



A modulok olyan kevés információt cseréljenek,

Explicit interface használata ■ Ha két modul kommunikál egymással, akkor annak ki kell derülnie legalább az egyikük szövegéből. Információ elrejtés ■ Egy modul minden információjának rejtettnek kell lennie, kivéve, amit explicit módon nyilvánosnak deklaráltunk. Nyitott és zárt modulok ■ Egy modult zártnak nevezünk, ha más modulok szá­ mára egy jól definiált felületen keresztül elérhető, a többi modul ezt változatlan formában felhasználhatja. Egy modult nyitottnak nevezünk, ha még kiterjeszt­ hető, ha az általa nyújtott szolgáltatások bővíthetőek vagy, ha hozzávehetünk

5. ALPROGRAMOK

119

további mezőket a benne levő adatszerkezetekhez, s ennek megfelelően módo­ síthatjuk eddigi szolgáltatásait. Az újrafelhasználhatóság igényei  A típusok változatossága ■ A moduloknak többféle típusra is működniük kell, azaz a műveleteket több különböző típusra is definiálni kellene.  Adatstruktúrák és algoritmusok változatossága.  Egy típus, egy modul ■ Egy típus műveletei kerüljenek egy modulba.  Reprezentáció-függetlenség.

5.4. Algoritmusok helyességének ellenőrzése Egy programot általában helyesnek tartunk, ha véges számú lépés után, a meg­ engedett (lehetséges) bemeneti adatokból meghatározza a feladat megoldásának megfelelő kimeneti adatokat. Az így definiált helyességet nem tudjuk bebizonyítani, de matematikai esz­ közökkel be lehet bizonyítani azt, hogy a program véges számú lépés után véget ér. Ezen belül, egyszerű eszközökkel elvégezhetők azok az ellenőrzési művele­ tek, amelyek a hibakeresésre szorítkoznak. [16] Ellenőrző táblázat ■ Az ellenőrző táblázatban az algoritmusban szereplő válto­ zók értékeinek módosulásait lépésenként követjük. Minden bemeneti adat szá­ mára kiválasztunk egy-egy megfelelő értéket. Ez lehet véletlenszerűen válasz­ tott is, de szükséges bizonyos feltételeknek eleget tevő bemeneti teszteseteket is keresni. Az algoritmus lépéseit elvégezzük, a változók értékeit bevezetjük a táb­ lázatba és a végeredmény alapján eldöntjük, hogy ennek a tesztnek az esetében helyesen működik-e az algoritmus.

5.4.1. A fekete doboz módszere Az algoritmust fekete doboznak tekintjük, amelynek a tartalma nem érdekel bennünket. Csak a bemeneti adatokra figyelünk és azokra a kimeneti adatokra, amelyeket az algoritmust kódoló program futtatása eredményeként kapunk. Amikor a tesztekben a bemeneti adatokat a feladat specifikációinak megfelelően kiválasztjuk, a következőket vesszük figyelembe:

a) Jellemző bemeneti adatok (gyakori, általános); b) Sajátos bemeneti adatok (szélsőséges tesztesetek);

120

5. ALPROGRAMOK

c) Nem megengedett adatok (figyelmetlenségből, esetleg más, program eredmé­ nyeként kapott adatok). Ismeretes, hogy a programozási versenyeken a megoldásokat automata elle­ nőrző rendszerekkel értékelik, ezek pontosan a fekete doboz módszerét alkal­ mazzák.

5.4.2. Az átlátszó doboz módszere Az adatokat úgy választjuk ki, hogy az algoritmus belső szerkezetére figyelünk, és azt a célt követjük, hogy ezt minden ágán lefuttatva, ellenőrizhessük. Tehát olyan különböző teszteseteket választunk, amelyek az algoritmus minden alter­ natív végrehajtási útvonalát vizsgálják. Legyen a fejezet elején megoldott feladat, amelyet mindkét módszerrel elle­ nőrizni fogunk. Emlékezzünk a szövegére: Adott egy legkevesebb három számjegyű természetes szám. Írjunk algorit­ must, amely megállapíthatja, hogy az eredeti szám első és utolsó számjegyének elhagyásával keletkező új szám teljes négyzet-e! Fekete doboz módszere ■ Jellemző eset: n =123 vagy 1812 vagy 1234. Sajátos esetek lehetnének az egy- és a kétszámjegyű számok, de ezek nem szerepelnek a feladat megengedett esetei között. Ezen kívül sajátos esetnek tekinthetjük pél­ dául a 102-t, és minden olyan számot, amelynek első és utolsó számjegyét el­ hagyva 0-t kapunk, amely szintén teljes négyzet. Az átlátszó doboz módszere ■ Az algoritmus tartalmaz két Amíg ciklust és egy Ha utasítást. Ha olyan adatokat keresünk, amelyekre az algoritmus nem lép be legalább az egyik Amíg-ba, rájövünk, hogy ezek nem megengedett értékek. A Ha ellenőrzésére 71698 és 1234 megfelelnek (a 169 teljes négyzet, a 23 nem az). Megjegyzés ■ Ha szeretnénk, hogy az algoritmus intelligens módon reagáljon a nem megengedett értékekre is, akkor a következő módosítást kellene elvégez­ nünk: Ismételd Be: n Ha n < 100 akkor Ki: 'Nem jó érték!' vége(ha) ameddig n  100

5.5. Megoldott feladatok

5. ALPROGRAMOK

121

5.5.1. Polinom értéke adott pontban Számítsuk ki egy n-edfokú polinom értékét egy adott x pontban! Elemzés ■ Legyen a polinom: P(X) = anXn + an–1Xn–1 + an–2Xn–2 +... + a1X + a0. Tulajdonképpen nem számít, hogy milyen sorrendben olvassuk be az együtt­ hatókat. Ahhoz, hogy alkalmazhassuk a Horner-sémát, megőrizzük őket a me­ móriában úgy, hogy az együtthatók x növekvő hatványai szerint kövessék egy­ mást. Ekkor P(X) felírható a következőképpen: P(X) = (...((anX + an–1)X + an–2)X + ... + a1)X + a0. Algoritmus Polinom_értéke(n,a,x,P): P  0 { bemeneti adatok: n, a, x; kimeneti adat: P } Minden i=n,0,-1 végezd el: P  P*x + ai vége(minden) Vége(algoritmus)

5.5.2. Polinomok összege Számítsuk ki két polinom (P(X) és Q(X)) összegét. A polinomokat a fokszámuk (m és n) és az együtthatóik tömbjén keresztül adjuk meg. Elemzés ■ Mivel a két polinom fokszáma nem egyenlő, szükségünk lesz egy Segéd(n,T,r,S) alprogramra, amely összead két polinomot, (S(X)-et és T(X)-et), ahol a T(X) foka (n) kisebb, mint az S(X) foka (r). [7] Algoritmus Segéd(n,T,r,S): {rn} { összeadjuk a T(X) polinomot az S(X) polinommal, eredmény S(X) } Minden i=0,n végezd el: Si  Si + Ti vége(minden) Vége(algoritmus) Algoritmus PolÖsszeg(m,P,n,Q,r,S): { összeadjuk a P(X) polinomot a Q(X) polinommal, eredmény S(X) } Ha m < n akkor r  n S  Q Segéd(m,P,r,S) különben r  m S  P Segéd(n,Q,r,S)

122

5. ALPROGRAMOK

vége(ha) Vége(algoritmus)

5.5.3. Polinomok szorzata Számítsuk ki két polinom szorzatának együtthatóit. Elemzés ■ Legyen a két polinom: P(X) = anXn + an–1Xn–1 + ... + aiXi + ... + a0 Q(X) = bmXm + bm–1Xm–1 + ... + bjXj + ... + b0. A szorzat kiszámítása céljából az egyik polinom egyik tagját meg kell szo­ roznunk a másik polinom minden tagjával: aiXi-t megszorozzuk sorra minden bjXj-vel (j = 0, 1, ..., m, i = 0, 1, ..., n), majd az azonos hatványú tagok együtt­ hatóit összeadjuk: aiXibjXj = aibjXi+j. A szorzat: R(X) = cn+mXn+m + cn+m–1Xn+m–1 + ... + c0, ahol Xi+j együtthatója ci+j. A szorzat minden együtthatójának 0 kezdőértéket adunk és ci+j-ben összead­ juk az aibj szorzatokat (j = 0, 1, ..., m minden i = 0, 1, ..., n-re). Algoritmus Polinomok_szorzata(n,m,a,b,c): { összeszorozzuk a P(X)-et a Q(X)-szel, az eredmény R(X), amelynek foka n + m } Minden i=0,n+m végezd el: ci  0 vége(minden) Minden i=0,n végezd el: Minden j=0,m végezd el: ci+j  ci+j + ai*bj vége(minden) vége(minden) Vége(algoritmus)

5.5.4. Determináns Készítsünk algoritmust egy n-edrendű determináns kiszámítására, ahol n (0 < n  10) természetes szám. A determinánst négyzetes kétdimenziós tömb formájá­ ban adjuk meg. [12] Megoldás ■ A determinánst át kell alakítanunk úgy, hogy a főátló alatt csak nullákat tartalmazzon. Az így kapott determináns értéke a főátló elemeinek szorzata, és ez egyben az eredeti determináns értéke is. Példa

123

5. ALPROGRAMOK

2 3 1 4 6 3 2 2 2

Az első sort 2-vel szorozva, majd kivonva a második sorból, a második sor első eleme nulla lesz. Az első sort a harmadikból ki­ vonva, a harmadik sor első eleme is nulla lesz.

Tehát a determináns, az átalakítás után: 2 3 1 0 0 1 0 1 1 2 3 1 0 1 1 0 0 1

Fölcserélve a második sort a harmadikkal, a determináns elő­ jele is megváltozik, eredményül a következőt kapjuk:

= –(–2) = 2 ami tehát az eredeti determináns értéke is.

A megoldás lépéseinek leírása

a)

Be: n, aij; i=1, ..., n; j=1, ..., n

b) Kezdőérték adás: d

 1

c)

Minden i=1,n-1 végezd el: Ha aii = 0 akkor Csere(n,a,i,d) { alprogram, amely sorokat cserél } vége(ha) d  d * aii Nullázás(n,a,i) { alprogram, amely a főátló alatti elemeket nullává alakítja } vége(minden) d  d * ann

d)

Ki: d

Lépések finomítása ■ Ki fogjuk dolgozni a két alprogramot, amelyek a mód­ szer alkalmazásához szükségesek. Ezenkívül észrevesszük, hogy a Nullázás(n,a,i)ra nincs szükség csak akkor, ha az aii elem nullától különbözik. A főátló alatti elemeket úgy alakítjuk nullává, hogy osztjuk az i-edik sort a főátlón lévő aii elemmel (tehát ennek különböznie kell 0-tól). Ezután a sort megszorozzuk az aji elemmel és kivonjuk a j-edik sorból (j i+1-től n-ig változik). Következik, hogy ha aii nulla, föl kell cserélnünk egy másikkal úgy, hogy az aii-t helyettesítő elem nullától különbözzék. A feladat megoldása ebben a formában csak akkor helyes ha a Csere (n,a,i,d) alprogram tényleg felcserél két sort úgy, hogy a Nullázás(n,a,i) már nullától különböző elemet találjon az aii helyén.

a)

Be: n, aij; i=1, ..., n; j=1, ..., n

124

b) Kezdőérték adás: d

5. ALPROGRAMOK

 1

c)

Minden i=1,n-1 végezd el: Ha aii = 0 akkor Keresés_Csere(n,a,i,d) { megkeressük (ha van) a nullától különböző elemet az i-edik oszlopban } { az i-edik sor alatt, és az i-edik sort felcseréljük ezzel a sorral } vége(ha) d  d * aii Ha aii  0 akkor Nullázás(n,a,i) vége(ha) vége(minden) { a főátló alatt minden elem nulla } d  d * ann

d)

Ki: d

Tehát a Keresés_Csere(n,a,i,d) nullától különböző elemet keres a főátló alatt az i-edik oszlopban. Ha találunk ilyen elemet, felcseréljük a megfelelő két sort, ha nem találunk, változatlanul hagyjuk a determináns elemeit. A Nullázás(n,a,i)-t csak akkor hajtjuk végre, ha aii nem nulla. Ellenkező esetben, vagyis mikor a főátló alatt minden elem nulla, a determináns értéke is nulla. Ezt az eredményt meg is adja a program, hiszen adott pillanatban nullával fogja megszorozni a d-t. A további számítások fölöslegesek. Ahhoz, hogy ezt elkerüljük, átalakítjuk a ciklust úgy, hogy vagy fusson 1-től n–1-ig, vagy álljon meg mikor kiderült, hogy a determináns értéke nulla. Algoritmus Determináns_értékének_kiszámítása: Be(n,a) d  1 i  1 Amíg (i < n) és (d  0) végezd el: Ha aii = 0 akkor Keresés_Csere(n,a,i,d) vége(ha) d  d * aii Ha aii  0 akkor Nullázás(n,a,i) vége(ha) i  i + 1 vége(amíg) d  d * ann Ki: d Vége(algoritmus) Algoritmus Keresés_Csere(n,a,i,d): j  i + 1 { bemeneti adatok: n, a, i; kimeneti adatok: a, d }

5. ALPROGRAMOK

125

Amíg (j  n) és (aji = 0) végezd el: j  j + 1 vége(amíg) Ha j  n akkor Sorcsere(n,a,i,j,d) vége(ha) Vége(algoritmus)

Az Amíg utasításban a feltétel értékelése úgy történik, hogy amenynyiben az első része (j  n) hamis, a második részt (aji = 0) már nem is értékeljük, hiszen ez az eredményt nem befolyásolja. Így elkerüljük azt, hogy a második feltétel­ ben hibás indexre (j = n + 1) hivatkozzunk. Ez egy implementációfüggő észrevétel hasznosításán alapuló algoritmus­ részlet. Ha olyan programozási nyelvben implementáljuk az algoritmust, amely eltérően a Pascal nyelvtől, minden logikai kifejezést kiértékel, akkor a „ve­ szély” elkerülése érdekében (mint nem létező elemre történő hivatkozás, és ez­ által valamilyen idegen érték elbírálása) a következő változatot ajánlatosabb használni: Algoritmus Keresés_Csere(n,a,i,d): megvan  hamis { bemeneti adatok: n, a, i; kimeneti adatok: a, d } j  i + 1 Amíg nem megvan és (j  n) végezd el: Ha aji  0 akkor megvan  igaz különben j  j + 1 vége(ha) vége(amíg) Ha megvan akkor Sorcsere(n,a,i,j,d) különben d  0 vége(ha) Vége(algoritmus)

A Sorcsere(n,a,i,j,d) az i-edik és j-edik sorok felcserélését jelöli. Ne felejtsük el, hogy sorcsere esetén a determináns értéke előjelet vált. Algoritmus Sorcsere(n,a,i,j,d): Minden k=1,n végezd el: { bemeneti adatok: n, a, i, j; kimeneti adatok: a, d } t  aik aik  ajk

126

5. ALPROGRAMOK

ajk  t vége(minden) d  -d Vége(algoritmus)

Mivel a sor elején az elemek nullával egyenlők mindkét felcserélendő sor­ ban, elegendő lenne csupán az i-edik elemtől felcserélni őket (vagyis a fenti le­ írásban elég, ha k értéke i-től n-ig nő. Az i-edik sor általános eleme: aik, a j-edik soré ajk. a jk  a jk 

aik  a ji ; k  i, i  1, ..., n aii

Ha ezt a képletet így írnánk be az algoritmusunkba, meglepetésünkre az eredmény nem lenne jó. Ennek az az oka, hogy ha k egyenlő i-vel, akkor aji a képletnek megfelelően nullává válik, és a következő értékadásnál már ez az új érték fog szerepelni a régi helyett. Ezt a hibát úgy lehet áthidalni, hogy vagy megőrizzük a régi értéket (esetleg aii-vel osztott értékét), s ezt használjuk a következőkben, vagy az aji új értékét ki sem számítjuk, tudván, hogy az nulla. Ez utóbbi esetben a fenti képletben k értékét csak i + 1-től vesszük. Így az átló alatti értékeket nem számítjuk ki, de nullának tekintjük. Algoritmus Nullázás(n,a,i): Minden j=i+1,n végezd el: b  aji/aii Minden k=i,n végezd el: ajk  ajk - aik * b vége(minden) vége(minden) Vége(algoritmus)

{ bemeneti adatok: n, a; kimeneti adat: a }

5.6. Kitűzött feladatok 1. Adott egy valós számsorozat. Határozzuk meg a tömb azon elemeinek szám­ tani középarányosát, amelyek nagyobbak, mint egy adott a szám, és kiseb­ bek, mint egy adott b! Képezzünk egy új számsorozatot az eredeti sorozat többi eleméből! 2. Tekintsünk egy n egész számból álló sorozatot. Helyettesítsük a sorozat min­ den egyes elemét a többi (n – 1) elem számtani középarányosával!

127

5. ALPROGRAMOK

3. Adott n  n méretű kétdimenziós tömböt forgassuk el 90-kal jobbra! Példa 178 321 269 Megdöntve : 4 6 7 345 598 Útmutatás ■ A sorok az oszlopokkal rendre helyet cserélnek. Eredeti tömb:

4. Adjunk össze n darab négyzetes tömböt! 5. A karácsonyi vásáron m áruházban n különböző terméket árusítanak. Az egyes termékek árát egy m  n méretű tömbben tároljuk (m a sorok száma, n az osz­ lopoké). Feltéve, hogy minden terméknek ismerjük az árát, határozzuk meg:  annak az áruháznak a sorszámát, amelyben a legmagasabb az átlagár;  a legolcsóbb áru sorszámát;  azt az áruházat, ahol egy adott j sorszámú áru a legdrágábban vásárolható, és azt ahol a legolcsóbban;  egy adott i sorszámú áruház termékeinek átlagárát. 6. Módosítsuk a fenti példát: nem ismert minden termék ára, így egy-egy cellá­ ba a 0 érték került. (Ez azt jelenti, hogy az illető áruház hanyagul kezeli az árlistát.)  Határozzuk meg a leghanyagabb áruházat!  Adjuk meg egy adott j sorszámú áru átlagárát úgy, hogy csak ismert árakat vegyünk figyelembe. 7. Adott egy m  n méretű tömb. Keressük meg azokat az elemeket, amelyek­ nek indexösszege maradék nélkül osztja az elem értékét! (Tehát ha aij-t el­ osztjuk i + j-vel, a maradék 0.) 8. Írjuk ki egy adott négyzetes tömb elemeit körkörösen: a1,1 a2,1 a3,1 a4,1

a1,2 a2,2 a3,2 a4,2

a1,3 a2,3 a3,3 a4,3

a1,4 a2,4 a3,4 a4,4

9. Adott egy m  n méretű tömb. Határozzuk meg azt a tömböt, amit úgy ka­ punk, hogy a tömb minden oszlopából kivonjuk az illető oszlop legkisebb elemét! 10. Adott egy n  n méretű tömb. Határozzuk meg a mellékátló alatti elemek közül a legnagyobbat!

128

5. ALPROGRAMOK

11. Adott egy m  n méretű tömb. Keressük meg a leggyakoribb elemét! 12. Adott egy n  n méretű négyzetes tömb. Rendezzük át a tömb oszlopait úgy, hogy az oszlopelemek összege növekvő sorozatot alkosson! Példa Az eredeti tömb: 10 7 1 2 6 4 3 4 5

Az átrendezett tömb: 1 10 7 4 2 6 5 3 4

13. Helyezzük el sorról-sorra egy n  n-es tömbbe az első n2 Fibonacci-számot! (f0 = 1, f1 = 1, fn = fn–1 + fn–2) 14. Egy adott [x, y] intervallum Fibonacci-számaiból készítsük el a lehetséges leg­ nagyobb négyzetes tömböt! Az esetlegesen hiányzó elemek helyére írjunk 0-t. 15. Adott egy szöveg, amelyben számjegyek is szerepelnek. Alakítsuk át a szö­ veget úgy, hogy a számjegyek helyett annak neve szerepeljen. Példa ■ „ez 1 szam” => „ez egy szam” 16. Adott egy n elemű, egész számokból álló sorozat. Írjunk programot, amely szétrendezi a sorozatot! Szétrendezés alatt azt a műveletsort értjük, amelynek elvégzése után a legkisebb elem az első helyen, az érték szerint következő az utolsó helyen stb. helyezkedik el. A szétrendezést orgonasíp-rendezésnek is nevezzük. Példa  Legyen a sorozat: 5, 2, 6, 1, 7, 9, 0  Szétrendezés után : 0, 2, 6, 9, 7, 5, 1 17. Palindromszámnak nevezzük azt a számot, amely balról jobbra és jobbról balra olvasva ugyanazt az értéket eredményezi. Például a 101 palindrom­ szám, de a 102 nem az. Alakítsunk át egy nem palindrom számot palindrom számmá a következő módon: megfordítjuk a számjegyek sorrendjét az adott számban, és az így kapott számot hozzáadjuk az eredetihez. Ezt az eljárást addig folytatjuk, míg az összeg palindrom nem lesz. Például a 86-ból indulva: 86 + 68 = 154, 154 + 451 = 605, 605 + 506 = 1111; azaz három lépésben palindromhoz jutottunk. Erre az átalakításra a számok eltérően reagálnak. Vannak számok, amelyek egy lépésben átalakíthatók (például 43 + 34 = 77), és van olyan szám is, amely száz lépés után sem „hajlandó” átváltozni.

5. ALPROGRAMOK

129

Írjunk programot, amely megkeresi azokat az n (n ≤ 104) jegyű számokat, amelyeket n lépésben palindrommá lehet varázsolni! A számolás az első összeadással kezdődik. 18. Ha egy szám számjegyeinek összege osztható magával a számmal, akkor a számot Niven-számnak nevezzük. A 21 Niven szám, mivel 2 + 1 = 3, és a 3 osztja a 21-t. A fenti megállapítás kiterjeszthető más számrendszerekre is. Például a 2-es alapú számrendszerben a 110 Niven-szám, mivel 1 + 1 + 0 = 2 és (110)2 = 6 osztható 2-vel. Írjunk programot, amely egy adott, b (2  b  10) számrendszerben felírt számról eldönti, hogy Niven-szám-e vagy sem! A bemeneti adatokat a bil­ lentyűzetről olvassuk be, minden sorból egy-egy számpárt: a számrendszert jelölő számot és egy számot az adott számrendszerben. A sorok száma isme­ retlen. A program írjon ki egy megfelelő üzenetet (igen/nem)! Példa 2 110

igen

10 123 nem 19. Döntsük el egy adott számról, hogy völgyszám-e vagy hegyszám. Egy völgy­ szám számjegyei csökkenő sorrendben követik egymást egy bizonyos elem­ mel bezárólag, azután növekvő sorozatot alkotnak. A hegyszám számjegyei növekvő sorrendben követik egymást egy bizonyos elemmel bezárólag, azu­ tán csökkenő sorozatot alkotnak. Például 13752 hegyszám, 85369 völgy­ szám, 1234 nem völgyszám és nem hegyszám. Írjunk ki egy megfelelő üzenetet, aszerint hogy az adott szám völgyszám-e vagy hegyszám. Ha a szám nem völgyszám és nem hegyszám, vágjuk le a szám első néhány számjegyét amíg az így kapott szám völgyszámmá vagy hegyszámmá válik. Ha a válasz „nem völgyszám” és „nem hegyszám”, írjuk ki az adott szám azon részét, amely völgyszám vagy hegyszám. Nem számít a levágott számjegyek száma. 20. Olvassunk be soronként több karakterláncot. A karakterláncok kisbetűket és számjegyeket tartalmaznak. Írjunk programot, amely kiszámolja minden ka­ rakterlánc esetében a bennük esetlegesen előforduló számok összegét! Példa 12abc12 x

24 0

21. Olvassunk be több, természetes számokból álló növekvően rendezett szám­ sorozatot. A sorozatok száma ismeretlen, a számsorozatok végét egy üres sor

130

5. ALPROGRAMOK

jelzi. A sorozatokban levő számok száma ismeretlen, egy számsorozat a sor­ vége jellel ér véget. Hozzunk létre ezekből a sorozatokból egy új sorozatot, amely tartalmazni fogja az összes számot, növekvő sorrendben. Írjuk ki az így kapott sorozatot, majd hozzunk létre ebből egy újat, amely csak különböző számokból áll. Írjuk ki az így kapott sorozatot, majd minden szám esetében írjuk ki előfor­ dulásainak számát az előbb kiírt sorozatban. 22. Adott egy kétdimenziós tömb, amelynek elemei egész számok. Döntsük el, hogy létezik-e nyeregpont a mátrixban vagy sem. Nyeregpont alatt azt az aij elemet értjük, amely a legkisebb a mátrix i-edik sorában és legnagyobb a jedik oszlopban, vagy legnagyobb a mátrix i-edik sorában és legkisebb a jedik oszlopban. A program egy megfelelő üzenettel válaszoljon a kérdésre. Igenlő válasz esetén a program írja ki a nyeregpont koordinátáit, vagyis a megfelelő elem sor- és oszlopindexét. 23. Egy adott szöveg szavakat tartalmaz, ismeretlen számú szóközzel elválaszt­ va. Írjunk programot, amely törli a szöveg leghosszabb szavát! Több azonos hosszúságú szó esetén töröljük mindeniket! A kimeneti szövegben a szavak egy-egy szóközzel legyenek elválasztva! 24. Adva van több valós együtthatójú polinom. A polinomok száma ismeretlen. Írjunk programot, amellyel kiszámítjuk az összegüket és kiválasztjuk azt a polinomot, amelynek az a pontban kiszámított értéke a legnagyobb az összes megadott polinom ugyanazon a pontban kiszámított értékei között. Egy polinomot kétféleképpen adhatunk meg:  a fokszámán és az együtthatóin keresztül, vagy  a monomjain keresztül (fokszám és együttható). Az adatok beolvasása előtt a program kérdezze meg, milyen alakban lesz megadva a polinom. Írjuk ki a megadott polinomok összegét, valamint azt a polinomot, amelynek az értéke maximális az adott a pontban. 25. Egy számot szerencsésnek nevezünk, ha számjegyei két csoportba oszthatók úgy, hogy a két csoportban levő számjegyek összege azonos. Ilyen szám pél­ dául a 32843, mert 8 + 2 = 3 + 4 + 3. Írjunk programot, amely kiírja azokat a szerencsés számokat, amelyek egy adott intervallumon belül találhatók! 26. Írjunk programot, amely beolvas több, természetes számokból álló számso­ rozatot. A sorozatok száma ismeretlen, a számsorozatok végét egy üres sor jelzi. A sorozatokban levő számok száma ismeretlen, a számsorozat a sorvé­ ge jellel ér véget.

5. ALPROGRAMOK

131

Minden sorozat esetében határozzuk meg azt a leghosszabb, egymás utáni elemekből álló részsorozatot, amely csak prímszámokat tartalmaz. Írjuk ki a sorozat sorszámát, a részsorozat első elemének indexét, valamint az utolsó elem indexét. Ezután írjunk ki minden előforduló prímszámot egyszer. 27. Ali Baba rajtakapta a 40 rablót, miközben ezek a kincsei között kotorásztak. A rablók sokan voltak, ő egyedül, ezért megpróbált egyezkedni velük. Volt egy különleges láda, amelyre fel volt írva a benne levő gyémántok száma, egy (legtöbb) 40 számjegyes szám egy, a rablók által ismeretlen számrend­ szerben (b < 10). Ali Baba felajánlotta a rablók vezérének, hogy vágjon ki b számjegyet a számjegysorból, mert a megmaradó számjegyekből összeálló számnak megfelelő számú gyémántot átenged neki. A rabló előbb megállapította a legkisebb lehetséges b-t, mivel minél ke­ vesebb számjegyet akart kivágni, majd elkezdődött a „faricskálás”. Termé­ szetesen, a célja az volt, hogy a megmaradó számjegyekből összeálló szám a lehető legnagyobb legyen. Írjunk programot, amely „súg” a rablónak! Írjuk ki a számrendszer alap­ ját, a számjegyeket és helyüket a számban abban a sorrendben, ahogy a rab­ lónak azokat ki kell vágnia, majd azoknak a gyémántoknak a számát, ame­ lyeket Ali Baba át kell neki engedjen.

6

RENDEZÉSI ALGORITMUSOK

6.1. Bevezetés Legyen egy n számot tartalmazó (a1, a2, ..., an) sorozat. Rendezett sorozatnak nevezzük a bemeneti sorozat olyan (a1', a2', ..., an') permutációját, amelyben a1'  a2'  ...  an'. D. Knuth „A számítógép programozásának művészete” című könyvében, (III. kötet: Keresések és rendezések) 33 rendező algoritmust ír le.

6.2. Összehasonlításos rendezési módszerek 6.2.1. Buborékrendezés (Bubble-sort) A rendezés során ellenőrizni fogjuk az elemek sorrendjét. A sorozat akkor ren­ dezett, ha érvényes a kért tulajdonság, vagyis a1  a2  ...  an–1  an. Tehát pá­ ronként összehasonlítjuk a számokat (az elsőt a másodikkal, a másodikat a har­ madikkal és így tovább), és ha a sorrend nem megfelelő, akkor az illető két ele­ met felcseréljük. Ha volt csere, a vizsgálatot újrakezdjük. Az algoritmus akkor ér véget, amikor az elemek páronként a megfelelő sorrendben találhatók, vagyis a sorozat rendezett. Példák a) Legyen a következő sorozat: (1, 4, 7, 9, 12)    

Az ellenőrzés eredményeként kiderül, hogy a sorozat rendezett.

b) Ha az (1, 4, 7, 12, 9) sorozatot kell rendezni, látható, hogy csak az utolsó 







két szám nincs megfelelő helyen. Felcseréljük ezt a két számot: (1, 4, 7, 9, 12). 

  

Ebben az esetben, egyetlen csere után rendezett sorozathoz jutottunk.

133

6. RENDEZÉSI ALGORITMUSOK

c) Legyen a rendezendő sorozat: (7, 1, 4, 12, 9) 

 



Ha ugyanúgy járunk el mint az előbb, akkor miután felcseréljük a 7-et az 1gyel, a sorozat a következőképpen alakul: (1, 7, 4, 12, 9). Majd újabb csere után: (1, 4, 7, 12, 9), (1, 4, 7, 9, 12).  







 





 



Ezen példa alapján úgy tűnik, hogy egyszeri bejárás és a megfelelő cserék után a sorozat rendezetté vált.

d) Legyen az utolsó példánk: Sz. 1) 2) 3) 4) 5) 6) 7) 8) 9)

(a1, a2, a3, a4, a5) (10, 9, 6, 7, 8) 

Megjegyzések

  

(9, 10, 6, 7, 8) 



 

(9, 6, 10, 7, 8)  





(9, 6, 7, 10, 8)   



(9, 6, 7, 8, 10) 

  

Egyszeri bejárás után a sorozat nem rendezett.

(6, 9, 7, 8, 10) 

  

(6, 7, 9, 8, 10)  

 

(6, 7, 8, 9, 10)   



(6, 7, 8, 9, 10)   



Vége a második bejárásnak. Nem biztos, hogy a sorozat rendezett. A harmadik ellenőrzés után eldőlt, hogy a sorozat rendezett.

Algoritmus Növekvő_sorrendbe_rendezés(n,a): Ismételd { bemeneti adatok: n, a; kimeneti adat: a } rendben  igaz Minden i=1,n-1 végezd el: Ha ai > ai+1 akkor ai  ai+1 rendben  hamis vége(ha) vége(minden) ameddig rendben Vége(algoritmus)

134

6. RENDEZÉSI ALGORITMUSOK

Lépések finomítása ■ Megfigyelhető, hogy a sorozat első bejárása után leg­ alább az utolsó elem a helyére kerül, és a ciklusmag minden újabb végrehajtása után, jobbról balra haladva újabb elemek kerülnek a megfelelő helyre. Ezért, ha az első lépésnél az i ciklusváltozó (n–1)-ig nő, akkor a következő lépésekben ez a felső határ legalább 1-gyel csökkenthető. Algoritmus Javított_Buborékrendezés_1(n,a): nn  n - 1 { bemeneti adatok: n, a; kimeneti adat: a } Ismételd rendben  igaz Minden i=1,nn végezd el: Ha ai > ai+1 akkor rendben  hamis ai  ai+1 vége(ha) vége(minden) nn  nn - 1 ameddig rendben Vége(algoritmus)

Az is előfordulhat, hogy a sorozat végén levő elemek már a megfelelő sor­ rendben vannak, és így azokat már nem kell rendeznünk. Észreveszszük, hogy elegendő a sorozatot csak az utolsó csere helyéig vizsgálni. Algoritmus Javított_Buborékrendezés_2(n,a): k  n { bemeneti adatok: n, a; kimeneti adat: a } Ismételd nn  k - 1 rendben  igaz Minden i=1,nn végezd el: Ha ai > ai+1 akkor rendben  hamis ai  ai+1 k  i vége(ha) vége(minden) ameddig rendben Vége(algoritmus)

További vizsgálódás után kiderül, hogy elegendő a sorozatot csak az első és az utolsó csere helye között vizsgálni. Íme az algoritmus: Algoritmus Javított_Buborékrendezés_3(n,a): { bemeneti adatok: n, a; kimeneti adat: a } régibal  1 { a vizsgálandó sorozat bal szélének indexe } régijobb  n - 1 { a jobb szél indexe }

6. RENDEZÉSI ALGORITMUSOK

135

Ismételd rendben  igaz { feltételezzük, hogy a sorozat rendezett } Minden i=régibal,régijobb végezd el: Ha ai > ai+1 akkor { vizsgáljuk az aktuális részsorozatot } rendben  hamis ai  ai+1 jobb  i - 1 { az utolsó csere helye } vége(ha) vége(minden) Ha nem rendben akkor { volt csere } régijobb  jobb { aktualizáljuk a jobb szél értékét } rendben  igaz { vizsgáljuk az aktuális részsor elemeit jobbról balra haladva } Minden i=régijobb,régibal végezd el: Ha ai > ai+1 akkor rendben  hamis ai  ai+1 bal  i + 1 { az első csere helye } vége(ha) vége(minden) régibal  bal { aktualizáljuk a részsorozat bal szélét } vége(ha) ameddig rendben Vége(algoritmus)

Ha a sorozat viszonylag sok elemet tartalmaz, a buborékrendezés, még a fenti javítások ellenére sem tartozik a hatékony rendezési módszerek közé (leg ­ rosszabb esetben a bonyolultsága O(n2)). Azért vegyük észre, hogy ha a sorozat már eleve rendezett, akkor egyetlen bejárás után az algoritmus véget ér, de gyors akkor is, ha a sorozat csak „majdnem” rendezett.

6.2.2. Egyszerű felcseréléses rendezés Ez a rendezési módszer hasonlít a buborékrendezéshez, de kötelezően elvégez minden páronkénti összehasonlítást (Ez az algoritmus mindig O(n2) bonyolult­ ságú). Ha egy elempár sorrendje nem megfelelő, felcseréli őket. A külső ciklus következő lépését mindig az aktuális elemtől folytatja, mivel az első lépésben az első helyre a sorozat legkisebb eleme kerül, a második helyre az aktuális részsorozat legkisebb eleme és így tovább. Algoritmus FelcserélésesRendezés(n,a): Minden i=1,n-1 végezd el: { bemeneti adatok: n, a; kimeneti adat: a } Minden j=i+1,n végezd el: Ha ai > aj akkor ai  aj vége(ha) vége(minden) vége(minden) Vége(algoritmus)

136

6. RENDEZÉSI ALGORITMUSOK

6.2.3. Válogatásos rendezés A módszer alkalmazása során az eredeti sorozatból létrehozunk egy új, ren­ dezett sorozatot. Minden elemet összehasonlítunk az összes többi elemmel és megszámoljuk (k-ban) azokat az elemeket, amelyek kisebbek mint a vizsgált elem. Így megkapjuk az illető elem sorszámát a rendezett sorozatban. Ha a fela­ dat követelményei szerint a rendezett sorozat az eredeti tömbben szükséges, ak­ kor az algoritmus végén a régi tömbváltozót felülírjuk a rendezett sorozatot tá­ roló változóval. Algoritmus Válogatásos_Rendezés_1(n,a): Minden i=1,n végezd el: { bemeneti adatok: n, a; kimeneti adat: a } k  1 Minden j=1,n végezd el: Ha ai > aj akkor k  k + 1 { megszámoljuk az ai-nél kisebb elemeket } vége(ha) vége(minden) { ai-t a rendezett sorozat megfelelő helyére tesszük } rendezett_sork  ai vége(minden) a  rendezett_sor { a rendezett sorozatot rámásoljuk az eredetire } Vége(algoritmus)

Mivel a fenti algoritmus kizárólag csak különböző értékű elemek esetében működik helyesen, lássuk azt a változatot is, amelyben, ha már foglalt helyre kellene tegyük (másodszor, harmadszor stb.) ugyanazt az értéket, akkor tovább megyünk a rendezett sorozatban addig, amíg megtaláljuk az első szabad helyet. Ha a rendezést alprogrammal implementáljuk, ne felejtsük el a rendezett_sor elemeit előbb lenullázni vagy egy megfelelő „idegen” kezdőértékkel ellátni. Algoritmus Válogatásos_Rendezés_2(n,a): Minden i=1,n végezd el: { bemeneti adatok: n, a; kimeneti adat: a } rendezett_sor  0 vége(minden) Minden i=1,n végezd el: k  1 Minden j=1,n végezd el: Ha ai > aj akkor k  k + 1 { megszámoljuk az ai-nél kisebb elemeket } vége(ha) vége(minden) { ha a kiszámolt hely már foglalt, akkor megyünk tovább a sorozatban }

6. RENDEZÉSI ALGORITMUSOK

137

Amíg rendezett_sork  0 végezd el: k  k + 1 { esetleg egy „idegen” érték 0 helyett } vége(amíg) rendezett_sork  ai { ai-t a rendezett sorozat megfelelő helyére tesszük } vége(minden) a  rendezett_sor { a rendezett sorozatot rámásoljuk a-ra } Vége(algoritmus)

6.2.4. Minimum/maximum kiválasztásra épülő rendezés Az előbbi módszernek hátránya, hogy memóriapazarló. A plusz memóriaigényt kiküszöbölhetjük, ha a kiválasztott elemet az adott sorozaton belül a végleges helyére tesszük. Például, növekvő sorrendbe rendezés esetén kiválaszthatjuk a sorozat legki­ sebb elemét. Ezt az első helyre tesszük úgy, hogy felcseréljük az első helyen ta­ lálható elemmel. A következő lépésben hasonlóan járunk el, de a minimumot a második helytől kezdődően keressük. A továbbiakban ugyanezt tesszük, míg a sorozat végére nem érünk. Algoritmus Minimumkiválasztás(n,a): Minden i=1,n-1 végezd el: { bemeneti adatok: n, a; kimeneti adat: a } ind_min  i Minden j=i+1,n végezd el: Ha aind_min > aj akkor ind_min  j vége(ha) vége(minden) ai  aind_min vége(minden) Vége(algoritmus)

Az algoritmus megírható úgy is, hogy minden lépésben kiválasztjuk az aktu­ ális részsorozat legnagyobb elemét, és annak függvényében, hogy növekvő, il­ letve csökkenő sorrendű rendezést végzünk, ezt az elemet az aktuális rész­ sorozat végére, illetve elejére helyezzük.

6.2.5. Beszúró rendezés A beszúró rendezés hatékony algoritmus kis számú elem rendezésére. Úgy dol­ gozik, ahogy bridzsezés közben a kezünkben lévő lapokat rendezzük: üres bal kézzel kezdünk, a lapok fejjel lefelé az asztalon vannak. Felveszünk egy lapot az asztalról, és elhelyezzük a bal kezünkben a megfelelő helyre. Ahhoz, hogy

138

6. RENDEZÉSI ALGORITMUSOK

megtaláljuk a megfelelő helyet, a felvett lapot összehasonlítjuk a már kezünk­ ben lévő lapokkal, jobbról balra. [6] A bemeneti elemek helyben rendeződnek: a számokat az algoritmus az adott tömbön belül rakja a helyes sorrendbe, belőlük bármikor legfeljebb csak állan­ dónyi tárolódik a tömbön kívül. Amikor a rendezés befejeződik, az eredeti tömb tartalmazza a rendezett elemeket. Algoritmus Beszúró_Rendezés(n,a): Minden j=2,n végezd el: { bemeneti adatok: n, a; kimeneti adat: a } segéd  aj { beszúrjuk aj-t az a1, ..., aj–1 rendezett sorozatba } i  j - 1 Amíg (i > 0) és (ai > segéd) végezd el: ai+1  ai i  i - 1 vége(amíg) ai+1  segéd vége(minden) Vége(algoritmus)

6.3. Rendezések lineáris időben Az eddigiekben bemutattunk néhány olyan algoritmust, amelyek a legrosszabb esetben O(n2) időben rendeznek n elemet. Ez azt jelenti, hogy ezek az algorit­ musok időigényesek, hiszen ahhoz, hogy egy 1000 elemű sorozatot rendezzenek körülbelül 1 millió összehasonlítást végeznek el. Észrevesszük, hogy ezeknek az algoritmusoknak van egy érdekes, közös tulajdonságuk: a rendezéshez csak a bemeneti tömb elemein történő összehasonlításokat használják. Éppen ezért, ezeket az algoritmusokat összehasonlító rendezéseknek nevezzük. Az összes ed­ dig bemutatott algoritmus összehasonlító rendezés volt. A következőkben megismerünk két lineáris idejű rendező algoritmust: a leszámláló rendezést és a számjegyes rendezést. Ezek az algoritmusok nem az összehasonlítást használják a rendezéshez, hanem kihasználják a rendezendő sorozat bizonyos tulajdonságait. Következik, hogy ezeket a rendezéseket csak adott tulajdonságú sorozatokkal végezhetjük. Egy lineáris algoritmus az elemek számosságával arányos számú műveletet végez a bemeneti adatokon: 1000 elem rendezéséhez c  1000 műveletet hajt végre, ahol c egy pozitív konstans.

139

6. RENDEZÉSI ALGORITMUSOK

6.3.1. Leszámláló rendezés (ládarendezés, binsort) A leszámláló rendezésnél feltételezzük, hogy az n bemeneti elem mindegyike 1 és k közötti egész szám, ahol k egy egész szám. [6] Szükségünk lesz egy segédtömbre, amelynek i-edik elemében azt tartjuk nyilván, hogy hány darab i-vel egyenlő elemet találtunk az eredeti tömbben. A lineáris feldolgozás után felülírjuk az eredeti tömb elemeit a segédtömb elemei­ nek értékei alapján. Megjegyezzük, hogy k nem lehet túlságosan nagy, mivel a segédtömb mérete pontosan k lesz. A rendezendő elemek értékeinek feltétlenül olyan típusúaknak kell lenniük, amelyek használhatók indexként. Ugyanakkor elégséges, ha az értékek egy bi­ zonyos [x, y] intervallumhoz tartoznak, azzal a feltétellel, hogy a segédtömb in­ dexelése lehető legyen x-től y-ig, esetleg 1-től (y – x + 1)-ig. Algoritmus Ládarendezés(a,n): Minden i=1,k végezd el: segédi  0 vége(minden) Minden j=1,n végezd el: segédaj  segédaj + 1 vége(minden) q  0 Minden i=1,k végezd el: Minden j=1,segédi végezd el: q  q + 1 aq  i vége(minden) vége(minden) Vége(algoritmus)

{ bemeneti adatok: n, a; kimeneti adat: a }

{ a segéd tömbnek k eleme van, } { segédi elemek összege n } { tehát a feldolgozások száma n }

6.3.2. Számjegyes rendezés (radix-sort) A számjegyes rendezést a már csak múzeumokban található lyukkártyarendező berendezéseknél használták először. A kártyáknak 80 oszlopa volt és minden oszlopban a létező 12 pozíció közül egy ki volt lyukasztva. A rendező-berende­ zés mechanikusan „programozható” volt oly módon, hogy megvizsgálta a kár­ tyacsomag minden lapjának egy adott oszlopát és szétosztotta a lapokat 12 do­ bozba, aszerint, hogy melyik pozíció volt kilyukasztva. Ezután egy kezelő do­ bozról dobozra összegyűjthette a lapokat úgy, hogy az első pozícióban kilyu ­ kasztott lapok a második pozícióban kilyukasztott lapok felett legyenek, és így tovább. [6]

140

6. RENDEZÉSI ALGORITMUSOK

Decimális számjegyek esetén csak 10 pozíció szükséges minden oszlopban. Így egy d számjegyű szám esetén d oszlopra van szükség. n darab d számjegyű szám rendezéséhez rendezőalgoritmust alkalmazunk. A számjegyes rendezés először a legkevésbé fontos számjegy alapján rendez. A számokat az utolsó számjegyük alapján rendezzük oly módon, hogy, ha csak ezt a számjegyet tekintjük, növekvő sorrendet lássunk. Ezután a számokat újra rendezzük a második legkevésbé értékes számjegyük alapján. Ezt mindaddig végezzük, ameddig a számokat mind a d számjegy szerint nem rendeztük. Fi­ gyelemre méltó, hogy ezen a ponton a számok teljesen rendezettek. Így csak dszer kell megvizsgálni a sorozatot. Példa 3 4 6 8 4 7 3

2 5 5 3 3 2 5

9 7 2 0 7 2 0 3 2 9 7 3 5 5 3 2 9 3 5 5 7 4 3 6 4 3 6 4 3 6 9  4 5 7  8 3 9  4 5 7 6 6 5 7 3 5 5 6 5 7 0 3 2 9 4 5 7 7 2 0 5 8 3 9 6 5 7 8 3 9

A fenti példa a számjegyes rendezés működését mutatja be 7 darab 3 számje­ gyű számot tartalmazó sorozat esetén. A bal szélső oszlop a bemenet. A többi oszlop a sorozatot mutatja, az egyre értékesebb számjegyek alapján történt ren­ dezések után. Az árnyékolás azt a számjegypozíciót mutatja, amelyet rendezve, megkaptuk az új sorozatot az előzőből. Algoritmus Számjegyes_Rendezés(n,a,d): { bemeneti adatok: n, a, d; kimeneti adat: a } Minden i=1,d végezd el: { d számjegyet vizsgálunk } { először a legkevésbé fontos számjegy szerint rendezünk }

leszámlálással rendezzük a tömböt jobbról az i. számjegy szerint vége(minden) Vége(algoritmus)

A kérdés: hogyan építsük be a fenti vázlatba a leszámláló rendezést? Szüksé­ günk lesz a ládarendezésnek arra a változatára, amely „nem veszíti el” a kísérő adatokat, vagyis a számjegyek rendezését úgy végzi, hogy a szám többi számje­ gye nem szakad le a számról. A következő algoritmusban legtöbb k számjegyű számokat fogunk rendezni, ahol:  az szj változó értéke 0-tól k-ig az aktuális számjegypozíciót tárolja.  a hatv10 tömb értékei 10 hatványait tartalmazzák (1, 10, 100 stb.), ame­ lyeknek segítségével megállapítjuk az aktuális számjegyet.

6. RENDEZÉSI ALGORITMUSOK

141

 a hány tömb i-edik elemében egy adott számjegynek megfelelő „osz­ lop”ban i-vel egyenlő számjegyek számát tároljuk. Ennek a tömbnek lesz –1-edik eleme is, amelyre akkor van szükség, amikor kiszámítjuk a 0-dik elemet. Algoritmus Szjegy(szám,szj): { bemeneti adatok: szám, szj; kimenet (függvényazonosítóban): Szjegy } Szjegy  maradék[([szám/hatv10szj])/10] Vége(algoritmus) Algoritmus Radix(n,a,rendezettsor): { bemeneti adatok: n, a; kimeneti adat: rendezettsor } Minden szj=0,k végezd el: Minden j=-1,9 végezd el: hányj  0 vége(minden) { még nincs egy számjegy sem megszámolva } Minden i=1,n végezd el: melyik  Szjegy(ai,szj) { mely számjegy jelent meg } hánymelyik  hánymelyik + 1 vége(minden) Minden i=0,9 végezd el: hányi  hányi + hányi-1 vége(minden) { hány eleme van a számjegysorozatnak, amelyek  i } Minden i=1,n végezd el: segéd  Szjegy(ai,szj) { újból szükség van az i-edik szám i-edik számjegyére } { (jobbról balra haladva) } hova  hánysegéd-1 + 1 { hova kerül az i-edik szám } { az i-edik számjegy szerint rendezett sorozatban } rendezettsorhova  ai hánysegéd-1  hánysegéd-1 + 1 { csökken azoknak a számoknak a száma amelyekben az } { i-edik számjegy  i, mivel elhelyeztük a rendezett sorozatba } vége(minden) a  rendezettsor { felülírjuk az eredeti sorozatot a rendezettel } vége(minden) Vége(algoritmus)

7

REKURZIÓ

7.1. Bevezetés A rekurzió egy különleges programozási stílus, inkább „technika” mint mód­ szer. A rekurzív programok tömören és világosan kódolják az algoritmusokat, bonyolultságuktól függetlenül. A rekurzív programozás, mint fogalom, a mate­ matikai értelmezéshez közelálló módon került közhasználatba. Példák

a) Pistike kifogta a Szamosból az aranyhalat. Mint az ismerős mesében, az arany­ hal felajánlotta, hogy teljesíti Pistike három kívánságát, ha megkegyelmez neki, és visszadobja a folyóba. Pistike előbb 100 tábla csokit kért, második kívánságként egy számítógépet, majd elgondolkozott. Mit kérhetne még, hogy a lehető legnagyobb haszonnal érjen véget ez a kaland? Felderült az arca, mikor kitalálta és kérte az aranyhalat: „teljesítsd még három kívánságomat”.

b) A legenda szerint Hanoiban egy templom előtt volt három rúd, amelyek kö­ zül az elsőre fel volt fűzve n darab, különböző átmérőjű korong az átmérőjük csökkenő sorrendjében helyezve egymás fölé. A másik két rúd üres volt. Egy szerzetes a jóslatnak megfelelően át kellett volna költöztesse a korongokat az első rúdról a másodikra, ugyanolyan sorrendben, ahogyan eredetileg elhe­ lyezkedtek. Közben felhasználhatta volna ideiglenesen a harmadik rudat. Egyszerre csak egy korongot volt szabad mozdítani, és csak kisebb átmérőjű korongot lehetett egy nagyobb átmérőjű korong fölé tenni. A szerzetes rájött, hogy a feladat számára túl nehéz, de ha egy társa elköl­ töztetne n – 1 korongot az első rúdról a harmadikra, akkor neki csak egyet kellene áttenni az elsőről a másodikra. Utána a társa visszahordaná a harma­ dik rúdról az ott található n – 1 korongot a másodikra. Igen ám, de a társának is nehéz lett volna cipelgetni az n – 1 korongot... Ő is megkért valakit, hogy költöztessen el n – 2 korongot...12

12

Erre a feladatra visszatérünk a 9. fejezetben (Az Oszd meg és uralkodj módszer).

7. REKURZIÓ

143

c) A matematikában, egy fogalmat rekurzív módon definiálunk, ha a definíción belül felhasználjuk magát a definiálandó fogalmat. Például, a faktoriális re­ kurzív definícióját egy adott n szám esetében, a matematikus így fejezi ki: ha n  0 1, n!   * n  (n  1)!, ha n  N

d) A bináris fa Knuth által megfogalmazott definíciója már szorosan kapcsolódik az informatikához: Egy bináris fa vagy üres, vagy tartalmaz egy csomópontot, amelynek van egy bal meg egy jobb utóda, amelyek szintén bináris fák. A programozásban, a rekurzió a magas szintű programozási nyelvekkel (Algol, Pascal, C) együtt jelent meg. Ezzel együtt lehetővé vált egyszerűen kó­ dolni olyan algoritmusokat, amelyeknek szerkezete rekurzív, vagy amelyeket rekurzívan definiált adatszerkezetekre alkalmazunk. A programozásban a rekurzió alprogramok formájában jelenik meg, éspedig olyan függvényeket, illetve eljárásokat nevezünk rekurzívaknak, melyek meg­ hívják önmagukat. Ha ez a hívás az illető alprogram összetett utasításában benne foglaltatik, közvetlen (direkt) rekurzióról beszélünk. Ha egy rekurzív alprogramot egy má­ sik alprogram hív meg, amelyet ugyanakkor az illető alprogram hív (közvetve, vagy közvetlenül) akkor közvetett (indirekt) rekurzióról beszélünk. Közvetett rekurzió esetén is arról van szó, hogy egy alprogram meghívja önmagát, hiszen a rekurzív hívás aközben történik, miközben a számítógép azt az összetett utasí­ tást hajtja végre, amely az illető alprogramot alkotja. Egy alprogram aktív a hívásától kezdődően, addig amíg a végrehajtás vissza­ tér a hívás helyére. Egy alprogram aktív marad akkor is, ha végrehajtása során más alprogramokat hív meg. Tehát, a rekurzió fogalmát kifejezhetjük úgy is, hogy egy alprogram akkor rekurzív ha meghívja önmagát, akkor amikor még aktív.

7.2. Közvetlen rekurzió Mielőtt még valaki azt gondolná, hogy a rekurzió egy bonyolult programozási technika, leszögezzük, hogy egy rekurzív alprogram végrehajtása azonos módon történik, mint bármely nem rekurzív alprogramé. Egy P nem rekurzív eljárás végrehajtása, amely meghív egy P1 nem rekurzív eljárást a következő módon történik (1. ábra):  végrehajtódik a P eljárás (1) része, amely a P1 hívása előtti részhez tartozik;

144

7. REKURZIÓ

 végrehajtódik a P1 eljárás hívása, amelynek eredményeképpen a verembe másolódik annak az utasításnak a címe, amelyhez a P1 végrehajtása után vissza kell térnünk;  végrehajtódik a P1 eljárás összetett utasítása és visszatérünk a P eljárás azon utasításához, ahonnan ezt folytatnunk kell ((2), (3), (4));  végrehajtódik a P eljárás (5) része, amely a P1 eljárás hívása utáni részhez tartozik. (1)

(2)

(3)

P1

P

(5)

A P eljárás aktiválódása

(4)

A P1 eljárás aktiválódása 1. ábra

Megjegyzések  Ha a P eljárás közvetlenül rekurzív, a P1 eljárás hívása helyett a P eljárás meghívásáról van szó (2. ábra).  Amikor először hívjuk meg a P eljárást, ez először válik aktívvá, amely ál­ tal előbb végrehajtódik a P (1) része, majd amiatt, hogy P meghívja ön­ magát, a P eljárás újból aktívvá válik. Ennek végrehajtása a P eljárás kez­ deti részének végrehajtását jelenti (2), majd az újabb hívás a P eljárást új­ ból aktiválja stb. (1)

P

P

P

(2) ....

(n)

P

 Verem túlcsordulás

a P eljárás első meghívása

a P eljárás második meghívása

a P eljárás n-edik meghívása

2. ábra

A P rekurzív eljárás végrehajtása következtében a P ismételten válik aktív­ vá. Ennek az a következménye, hogy ismételten végrehajtódik a P eljárás kez­ deti része, valamint a P hívása. Minden aktiválás eredményeként a verembe ke­ rül minden, a hívás kontextusához tartozó adat: annak az utasításnak a címe, amelyhez a P aktuális végrehajtása után vissza kell térnünk, helyi (lokális) vál­ tozók címe, valamint a paraméterek értéke (gyakran csak érték szerint átadott paraméterekkel dolgozunk ahhoz, hogy ne terheljük a végrehajtási vermet (stack)). Mivel a verem mérete véges, bizonyos n aktiválás után bekövetkezhet a túl­ csordulás és a program hibaüzenettel kilép.

145

7. REKURZIÓ

Mivel ezt a hibát feltétlenül el kell kerülnünk, a P eljárást csak egy bizonyos feltétel teljesülésekor hívjuk meg újra. Legyen F az a feltétel amelynek igaz ér­ téke esetén a P eljárás meghívhatja önmagát. Az önmeghívás legegyszerűbb alakja: Ha F akkor P. Egy P rekurzív eljárás a következő módon hajtódik végre (3. ábra):  a P eljárás első aktiválása során végrehajtódik a P eljárás (1) része;  amíg az F feltétel értéke igaz a P eljárás aktiválásainak sora következik; ez azt jelenti, hogy végrehajtódik a P eljárás bevezető része, valamint az újrahívás;  ismételten, a hívások sorrendjéhez viszonyítva fordítottan, végrehajtódik a P eljárás (5) része, amely az eljárás hívása utáni részhez tartozik. Vegyük észre, hogy a legutolsó aktiválás alkalmával a feltétel hamis volt, ennek következtében nem történik újrahívás, hanem a feltétel másik ágának megfelelő utasítás (ennek hiányában, a feltétel utáni utasítás) kerül sorra. A kilépést követően ugyanígy, az F feltétel utáni utasítások végrehajtása következik addig, amíg kilép az első hívásból is és ennek megfelelően utoljára hajtja végre az (5) részt. Amikor kilép, visszatér a P eljárás első hívásának megfelelő utasítás utáni helyre. F = igaz (3) (2)

(1)

P

Ha F akkor P (11)

a P eljárás első aktíválása

(10)

Ha F akkor P (9) (8)

a P eljárás második aktíválása

F = igaz (5)

(4)

Ha F akkor P (6) (7) { itt F = hamis }

a P eljárás harmadik aktíválása

3. ábra

Megjegyzések  A magyarázat egyszerűbbé tételének érdekében, a P eljárásnak nem tün­ tettük fel a paramétereit.  A rekurzív eljárások esetében is, hasonlóan a nem rekurzívakhoz, az akti­ válás feltételezi a veremhasználatot, ahol a paramétereket, a visszatérés helyének címét, valamint a lokális változókat tárolja (minden aktuális ak­ tiválás idejére) a programozási környezet.  Új aktiválás csak az újrahívási feltétel teljesülésekor történik; az újrahívá­ sok száma meghatározza a rekurzió mélységét; az előző megjegyzést fi­ gyelembe véve, egy rekurzív megoldás csak akkor hatékony, ha ez a mélység nem túl nagy.  Ha az újrahívási feltétel egy adott pillanatban nem teljesül, az újraaktiválá­ sok sora leáll; ennek következtében az F feltétel tagadása a rekurzióból

146

7. REKURZIÓ

való kilépés feltétele; az F feltételnek a rekurzív eljárás paramétereitől kell függnie és/vagy a helyi változóktól; a kilépést a paraméterek és a lo­ kális változók módosulása (egyik hívástól a másikig) biztosítja. Ha ezeket a feltételeket nem tartjuk be, a program hibaüzenettel kilép.  Egy újrahívás (közvetlen rekurzió esetén), többször is előfordulhat egy re­ kurzív eljárásban; ebben az esetben, természetesen, különbözni fognak a visszatérési címek.  A rekurzió késlelteti az eljárás azon utasításainak végrehajtását, amelyek a rekurzív hívás utáni részhez tartoznak.  Minden eddigi állítás igaz a rekurzív függvények esetében is, csak a hívás módja más; egy rekurzív függvényt egy kifejezésből hívunk meg; egy re­ kurzív függvény összetett utasítása, hasonlóan a nem rekurzív függvé­ nyekhez, tartalmazni fog egy értékadó utasítást, amely a függvény azono­ sítójának ad értéket. Ebbe az utasításba kerül, többnyire, az újrahívás.

7.3. Megoldott feladatok 7.3.1. Egy szó betűinek megfordítása Olvassunk be egymás után több betűt a szóközkarakter megjelenéséig, majd írjuk ki ezeket a betűket fordított sorrendben! Elemzés ■ A feladat követelményének megfelelően betűk szintjén fogunk dol­ gozni. A megfordított kiírás azt jelenti, hogy miután beolvastunk egy betűt, nem írjuk ki, csak azután, hogy megfordítottuk a többi betűt. A fennmaradt rész ese­ tében ugyanígy járunk el; a módszer addig folytatódik, amíg eljutunk az utolsó betűhöz, amikor nincs mit megfordítani. Rekurzív módon ezt a következőkép­ pen lehet leírni: Algoritmus Fordít: Be: betű { nincs paraméter, mivel az alprogramban olvasunk be és írunk ki } Ha nem szóköz akkor Fordít { meghívja önmagát, hogy megfordíthassa a fennmaradt részt } különben Ki: 'Fordított szó: ' { ez egyszer hajtódik végre } vége(ha) Ki: betű Vége(algoritmus)

A rekurzió meghatározza az eljárás záró részének az aktiválások fordított sorrendjében való végrehajtását (a mi esetünkben: Ki: betű), így természetes módja a feladat megoldásának.

147

7. REKURZIÓ

Példa ■ Legyenek a betűk 'z', 'á', 'r'. Amikor az első hívás történik, a ve­ remben lefoglalódik egy megfelelő méretű hely a betű számára. Amikor beol­ vassuk az első betűt, ez az érték a verembe kerül. Mivel ez nem szóköz, újra meghívódik az algoritmus, ami azt jelenti, hogy újból szükség van a veremben egy helyre a lokális változó számára. Ide kerül a következő beolvasott érték (ez 'á'). Ez a folyamat addig tart, amíg az utoljára beolvasott karakter a szóköz. Amikor a veremben található „betű” szóköz, az algoritmus Ha utasításának különben ága hajtódik végre, vagyis kiíródik: „Fordított szó:”.

'z' Első hívás. betű = 'z'

'á' 'z' Második hívás. betű = 'á'

'r' 'á' 'z' Harmadik hívás. betű = 'r'

' ' 'r' 'á' 'z' Kiírjuk a betűt, amely ' '

'r' 'á' 'z' Kilépünk az aktuális hívásból

'r' 'á' 'z' Kiírjuk a betűt, amely 'r'

'á' 'z' Kiírjuk a betűt, amely 'á'

'z' Kilépünk az aktuális hívásból

'z' Kiírjuk a betűt, amely 'z'

' ' 'r' 'á' 'z' Negyedik hívás. betű = ' '

'á' 'z' Kilépünk az aktuális hívásból

Figyelemre méltó, hogy ez csak egyszer történik, mivel a Ha utáni utasítás következtében kiírjuk a verem „tetején” található „betűt” (ez a szóköz), majd ki­ lépünk az alprogramból. Ez azt jelenti, hogy kilépünk az aktuális hívásból. En­ nek következtében megszűnik a veremben lefoglalt tárrész (kiürül a verem, de nem teljesen: csak az aktuális híváskor lefoglalt rész), és visszatérünk ahhoz az utasításhoz, amely azután az utasítás után következik, amelyben az alprogram meghívta önmagát. Ez az utasítás a Ha volt, utána újból a betű kiírása követke­ zik (és nem a Ha különben ága!). Tehát kiírjuk a verem tetején „látható” betűt (ez 'r'). Újból kilépünk, az aktuális hívásból és visszatérünk a hívás helye utáni utasításhoz. Most a verem tetején az 'á' betű látható, tehát ezt írjuk ki. Az utolsó lépés az alprogram befejező részének utoljára történő végrehajtá­ sát jelenti, mivel kilépéskor a verem üressé válik és visszatérünk az eredeti hí ­ vás helyére, ahonnan először hívtuk meg ezt a rekurzív alprogramot.

148

7. REKURZIÓ

7.3.2. Szavak sorrendjének megfordítása Olvassunk be n szót, majd írjuk ki ezeket a beolvasás fordított sorrendjében! Ne használjunk tömböt! Megoldás Algoritmus Szavakat_fordít_1(n): Be: szó { az első hívás aktuális paramétere n = szavak száma } Ha n > 1 akkor Szavakat_fordít_1(n-1) különben Ki: 'Fordított sorrendben: ' vége(ha) Ki: szó Vége(algoritmus)

Az első hívás aktuális paramétere a beolvasott n szám. Az eredeti feladat te­ hát n szó megfordítását valósítja meg, a részfeladatok pedig egyre kevesebb szó megfordítását végzik. Ha fordítva indulunk, vagyis „megfordítjuk” egy szónak a sorrendjét, majd a többiét, akkor az algoritmus a következő: Algoritmus Szavakat_fordít_2(i): Be: szó { most az első hívás aktuális paramétere 1 } Ha i < n akkor Szavakat_fordít_2(i+1) különben Ki: 'Fordított sorrendben: ' vége(ha) Ki: szó Vége(algoritmus)

7.3.3. Faktoriális Írjuk ki az n adott szám faktoriálisát! Megoldás ■ Felhasználjuk a faktoriális matematikai definícióját, amit a Fakt(n) alprogramban implementálunk, amely függvény típusú. Az első hívás Fakt(n)nel történik. Algoritmus Fakt(n): Ha n = 0 akkor Fakt  1 különben Fakt  n * Fakt(n - 1) vége(ha) Vége(algoritmus)

Megjegyzések  A faktoriális tulajdonképpeni kiszámolása akkor történik, amikor kilépünk egy-egy hívásból. Mivel minden egyes alkalommal más-más n paramé­

149

7. REKURZIÓ

terre van szükség, fontos, hogy ezt értékként adjuk át; így kifejezéseket is írhatunk az aktuális paraméter helyére.  A faktoriálist nem előnyös rekurzívan számolni, mivel sokkal időigénye­ sebb mint az iteratív megoldás, hiszen a Fakt(n) függvény (n+1)-szer fog aktiválódni.

7.3.4. Legnagyobb közös osztó Számítsuk ki két természetes szám (n, m  N*) legnagyobb közös osztóját re­ kurzívan. Elemzés ■ Ha figyelmesen elemezzük Eukleidész algoritmusát, észrevesszük, hogy a legnagyobb közös osztó (Lnko(m,n)) egyenlő n-nel (ha n osztója m-nek) különben egyenlő Lnko(n,maradék[m/n])-val. Tehát fel lehet írni a következő rekurzív definíciót:

n,

ha maradék[ m / n]  0  Lnkon, maradék[ m / n], ha maradék[ m / n]  0

Lnko( m, n)  

Algoritmus Lnko(m,n): mar  maradék[m/n] Ha mar = 0 akkor Lnko  n különben Lnko  Lnko(n,mar) vége(ha) Vége(algoritmus)

Az első hívás történhet például egy kiíró utasításból: Ki: Lnko(m,n).

7.3.5. Számjegyösszeg Számítsuk ki egy adott természetes szám számjegyeinek összegét! Megoldás ■ A számjegyeket a 10-zel való ismételt osztással kapjuk meg. Ha egy számjegyet „levágtunk”, ugyanazt a feladatot kell megoldanunk a megma radt számra. Algoritmus Összeg(szám): Ha szám = 0 akkor Összeg  0 különben Összeg  Összeg([szám/10]) + maradék[szám/10] vége(ha) Vége(algoritmus)

150

7. REKURZIÓ

7.3.6. Descartes-szorzat Egy rajzon n virágot fogunk kiszínezni. A festékeket az 1, 2, ..., m számokkal kódoljuk. Bármely virág, bármilyen színű lehet, de szeretnénk tudni, hány féle módon lehetne ezeket különböző módon kiszínezni. Elemzés ■ Rájövünk, hogy tulajdonképpen a következő Descartes-szorzatot kell generálnunk:

Mn  M  M  ...  M   , ha M = {1, 2, ..., m}. n szer

Előbb megoldjuk a feladatot, ha m = 2, majd, ha m  2. A Descartes-szorzat egy eleme (x  Mn) egy n darabszámú vektor: x = (x1, x2, ..., xn), xi  M, ahol i = 1, 2, ..., n. 1. eset ■ Ha m = 2 és n = 3, M3 = M  M  M 1) 2) 3) 4)

x1 1 1 1 1

x2 1 1 2 2

x3 1 2 1 2

5) 6) 7) 8)

x1 2 2 2 2

x2 1 1 2 2

x3 1 2 1 2

Ha elhagyjuk az első elemet, az (x2, x3) elemek értékei az M  M Descartesszorzatot jelentik. Ebből következik, hogy az Mn Descartes-szorzat elemeinek generálása feltételezi, hogy az 1 és 2 értékeket egymás után válasszuk ki és ad­ juk át x1-nek, majd generáljuk az Mn–1 Descartes-szorzatot. Ennek elemeit meg­ őrizzük a sorozatunkban, a második elemtől kezdődően. Ezen elemek generá­ lása újból azt jelenti, hogy az 1 és 2 értékeket egymás után ki kell választanuk, és át kell adnunk x2-nek, majd generálnunk kell az Mn–2 Descartes-szorzatot. En­ nek az elemeit megőrizzük a sorozatunkban a harmadik elemtől kezdődően és így tovább, amíg kiválasztottuk az 1 és 2 értékeket az xn elem számára is: Az Mn–i+1 Descartes-szorzat elemeinek generálása és ezek megőrzése az x sorozatban az i-edik helytől kezdődően.

=

 az 1, 2 értékek egymás utáni kiválasztá­ sa xi számára, az Mn–i Descartes-szorzat generálása, és megőrzése x-ben az i+1edik helytől kezdődően, ha i 2 akkor Fib(n-1) fn  fn-1 + fn-2 különben f1  0 Ha n = 1 akkor f1  0 különben f2  1 vége(ha) vége(ha) Vége(algoritmus)

{ eljárás típusú algoritmus }

Mikor hasznos a rekurzió?  Ha a feladat eredménye rekurzív szerkezetű;  Ha a feladat szövege rekurzív szerkezetű;  Ha a megoldás legjobb módszere a visszalépéses keresés (backtracking) módszer13;  Ha a megoldás legjobb módszere az oszd meg és uralkodj (divide et im­ pera) módszer14;  Ha a feldolgozandó adatok rekurzívan definiáltak (pl. bináris fák). A rekurzív programozás legszembeötlőbb előnye az, hogy az algoritmus tö­ mör és világos, természetszerűen tükrözi a folyamatot, amit modellez. 13 14

A visszalépéses kereséssel a 8. fejezetben fogunk megismerkedni. Az oszd meg és uralkodj módszert a 9. fejezetben tárgyaljuk.

156

7. REKURZIÓ

Mikor nem szabad rekurziót alkalmazni?  Ha ugyanazt a feladatot megoldhatjuk egy egyszerűbb iteratív algoritmus­ sal, akkor tanácsosabb ez utóbbit alkalmazni, mivel így a program gyor­ sabb lesz. Egy iteratív algoritmusnak az ellenőrzése is könnyebb.  A Fibonacci-sorozat elemeinek generálására.  Kombinációk generálására és más kombinatorikai feladatok megoldására.

7.4. Rekurzív szerkezetű feladatok 7.4.1. A {0, 1, 2} halmaz elemeivel generált sorozatok Generáljunk minden lehetséges sorozatot, amelynek elemei a {0, 1, 2} halmaz­ ból vannak, és a 0 m-szer, az 1 p-szer és a 2 q-szor fordul elő (m, p, q  N*). Elemzés ■ Ha n = m + p + q, tulajdonképpen azokat az x1, x2, ..., xn, xi  {0, 1, 2}, (i = 1, 2, ..., n) sorozatokat kell generálnunk, amelyekben m darab 0, p darab 1 és q darab 2 fordul elő. Kiválasztjuk valamelyik értéket a három közül, átadjuk x1-nek és a követke­ ző lépésben a feladat ugyanaz, de most egy n – 1 elemű sorozatot kell generál­ nunk úgy, hogy annak az értéknek az előfordulási száma, amelyet kiválasztot­ tunk már, 1-gyel csökken. A kiválasztott elemet rögzítjük, és a sorozat további elemeit a második helytől kezdődően generáljuk. Egy bizonyos sorozat generá­ lása leáll, amikor xn is kapott értéket. Az alábbi algoritmust Sorozat(1,m,p,q) utasítással hívjuk meg először, miután kiszámoltuk n értékét (m + p + q). Algoritmus Sorozat(i,m,p,q): Ha i = n + 1 akkor Kiír { az n+1-edik elemet nem generáljuk, ez a kilépési feltétel } különben Ha m > 0 akkor xi  0 Sorozat(i+1,m-1,p,q) vége(ha) Ha p > 0 akkor xi  1 Sorozat(i+1,m,p-1,q) vége(ha) Ha q > 0 akkor xi  2 Sorozat(i+1,m,p,q-1) vége(ha) vége(ha) Vége(algoritmus)

157

7. REKURZIÓ

7.4.2. Az {1, 2, ..., n} halmaz minden részhalmaza Generáljuk az {1, 2, ..., n} halmaz minden részhalmazát! Elemzés ■ Egy halmazt ebben az esetben is egy x1 < x2 < ... < xi sorozattal ábrá­ zolunk, ahol i = 1, 2, ..., n. Az alábbi algoritmust i = 1-re hívjuk meg. Az x sorozatot 0 indexű elemét 0 kezdőértékkel látjuk el. Szükségünk lesz az x0 elemre is, mivel az algoritmusban a sorozat minden xi elemét, tehát x1-et is az elöző elemből számítjuk ki. A j vál­ tozóban generáljuk azokat az értékeket, amelyeket rendre felvesz az x sorozat aktuális eleme. Ezek a j értékek 1-gyel nagyobbak mint a részhalmazba utoljára betett elem értéke és legfennebb n-nel egyenlők. Így a részhalmazokat az úgy­ nevezett lexikográfikus sorrendben generáljuk. Figyelemre méltó, hogy minden új elem generálása egy új részhalmazhoz vezet. Ha n = 3, az üres halmaztól különböző részhalmazok a következők: 1) 2) 3) 4) 5) 6) 7)

x1 1 1 1 1 2 2 3

x2

x3

2 2 3

3

3

Algoritmus Részhalmaz(i): { M = (1, 2, ..., n), x globális  xi = 0, i = 0,20 } Minden j=xi-1+1,n végezd el: xi  j Kiír(i) Részhalmaz(i+1) vége(minden) Vége(algoritmus)

A kilépési feltétel (xi = n) el van rejtve a Minden típusú struktúrába: ha xi = n, a ciklusváltozó kezdőértéke xi + 1 nagyobb, mint a végső érték, így a Minden ciklusmagja nem lesz többet végrehajtva és a program kilép az aktuális hívásból. Az algoritmust Részhalmazok(1) alakban hívjuk meg.

7.4.3. Partíciók Generáljuk az nN* szám partícióit! Partíció alatt azt a felbontást értjük, amelynek során az n  N* számot pozitív számok összegeként írjuk fel: n = p1 + p2 + ... + pk, ahol pi  N*, i = 1, 2, ..., k, k = 1, ..., n. Két partíciót kétféleképpen tekinthetünk különbözőnek:

158

7. REKURZIÓ

 Két partíció különbözik egymástól, ha vagy az előforduló értékek vagy az előfordulásuk sorrendje különbözik (erre adunk megoldást).  Két partíció különbözik egymástól, ha az előforduló értékek különböznek (lásd a 8. kitűzött feladatot a fejezet végén). Példa ■ Ha n = 4: 4=1+1+1+1 4=1+1+2 4=1+2+1 4=1+3 4=2+1+1 4=2+2 4=3+1 4=4 A generálás során, rendre kiválasztunk egy lehetséges értéket a partíció első p1 eleme számára és generáljuk a fennmaradt n – p1 szám partícióit. Ez a különb­ ség az n új értéke lesz, amellyel ugyanúgy járunk el. Egy partíciót legenerál­ tunk, és kiírhatjuk ha n aktuális értéke 0. Az alábbi algoritmust a Partíció(1,n) utasítással hívjuk meg először. Algoritmus Partíció(i,n): Minden j=1,n végezd el: pi  j Ha j < n akkor Partíció(i+1,n-j) különben Kiír(i) vége(ha) vége(minden) Vége(algoritmus)

7.4.4. Halmazpartíciók Egy M = {1, 2, ..., n} halmaz partíciói alatt a halmaz diszjunkt részhalmazokra való felbontását értjük. Ezek egyesítése az M halmazhoz vezet: M = M1  M2  ...  Mk, Mi  M, i = 1, 2, ..., k, Mi  Mj = , i, j = 1, 2, ..., k, i  j, k = 1, 2,..., n. Generáljuk az {1, 2, ..., n} (nN*) halmaz partícióit! Elemzés ■ Legyen M = {1, 2, 3}. {1, 2, 3} {1, 2}  {3} {1, 3}  {2} {1}  {2, 3}

M = M1 M = M1  M2 M = M1  M2 M = M1  M2

x1 1 1 1 1

x2 1 1 2 2

x3 1 2 1 2

(1, 2, 3  M1) (1, 2  M1, 3  M2) (1, 3  M1, 2  M2) (1  M1, 2, 3  M2)

159

7. REKURZIÓ

{1}  {2}  {3}

M = M1  M2  M3

1

2

3

(1  M1, 2  M2, 3  M3)

Megjegyzések

a) Egy halmaz partíciói esetében a sorrend nem számít, csak az Mi részhalma­ zok. Ahhoz, hogy ne generáljuk ezeket többször, a hozzájuk tartozó legki­ sebb elem növekvő sorrendjében következnek egymás után.

b) A partíció definíciójából következik, hogy minden i elem, amely hozzátarto­ zik az M halmazhoz, csak egyetlen Mj részhalmazhoz tartozhat. Ebből követ­ kezik az ötlet, amelynek alapján a partíciót egy olyan x1, x2, ..., xn sorozattal kódoljuk, amelynek xi elemei azoknak az Mj részhalmazoknak a j indexei, amelyhez az i elem tartozik.

c) Az 1 mindig eleme M1-nek, a 2 eleme M1-nek vagy M2-nek és a 3 eleme M1nek vagy M2-nek vagy M3-nak. Ebből következik, hogy az i nem tartozhat csak az M1, M2, ..., illetve Mi halmazok egyikéhez. (Az 1-es megjegyzésből tudjuk, hogy az Mj részhalmazok a hozzájuk tartozó legkisebb elem növekvő sorrendjében vannak megadva, tehát az i elem csak akkor tartozik az Mi rész­ halmazhoz, ha az előző elemek mind hozzátartoznak egy-egy másik részhal­ mazhoz.) Ahhoz, hogy az x1, x2, ..., xn sorozat értékeit meghatározhassuk, megállapít­ juk azokat a részhalmazokat, amelyekhez hozzátartoznak az 1, 2, ..., n elemek. Lényegében kiválasztjuk azt a részhalmazt, amelynek eleme lesz az i, i = 1, 2, ..., n. Ha eljutottunk már az i-hez, vagyis az előző elemeket már elhelyeztük, a partícióban már megvannak az M1, M2, ..., Mk, (k < i) részhalmazok. Az i elemet vagy hozzáadjuk valamely már létező részhalmazhoz, vagy új Mk+1 részhalmazt alakítunk belőle. A továbbiakban megoldjuk ugyanazt a feladatot, amit megoldottunk i-re, most i + 1-re (ebben a pillanatban van már k vagy k + 1 részhalmaz a partícióban). Algoritmus HalmazPartíciók(i,k): Minden j=1,k végezd el: xi  j { az első választási lehetőség: i-t betesszük az Mj részhalmazba } Ha i < n akkor HalmazPartíciók(i+1,k) különben Kiír(k) vége(ha) vége(minden) xi  k+1 { második választási lehetőség: i-t betesszük az Mk+1 részhalmazba } Ha i < n akkor HalmazPartíciók(i+1,k+1) különben Kiír(k+1) vége(ha)

160

7. REKURZIÓ

Vége(algoritmus)

A fenti algoritmust a HalmazPartíciók(1,0) utasítással hívjuk meg. A partí­ ciók kiírását a következő algoritmussal valósíthatjuk meg: Algoritmus Kiír(k): { k a részhalmazok száma a partícióban } Minden i=1,k végezd el: Ki: '{' { kiírjuk az Mi részhalmazt } Minden j=1,n végezd el: Ha xj = i akkor Ki: j, ',' vége(ha) vége(minden) Ki: visszavisszük egy pozícióval a kurzort , '}U' vége(minden) Ki: visszavisszük egy pozícióval a kurzort , ' ' Vége(algoritmus)

7.4.5. Kamatos kamat Írjunk rekurzív függvényt kamatos kamat számítására adott évre! A kamatokat évenkénti bontásban is írjuk ki! Megoldás ■ A hívó programegységben beolvassuk az összeget, a kamatlábat (százalékban) valamint, hogy hány évre számoljuk a kamatos kamatot. Mivel az összeget évenként is ki kell írnunk, előbb a KamatKiír (1,év,összeg) függvényt hívjuk meg, a végeredmény kiírása céljából pedig a KamatosKamat(év,összeg) függvényt. Algoritmus KamatosKamat(év,összeg): Ha év = 0 akkor KamatosKamat  összeg különben összeg  összeg*(1+kamatláb/100) KamatosKamat  KamatosKamat(év-1,összeg) vége(ha) Vége(algoritmus) Algoritmus KamatKiír(évk,év,összeg): Ha év = 0 akkor KamatKiír  összeg különben összeg  összeg*(1+kamatláb/100) Ki: 'Az ', évk, '. év végén az összeg: ', összeg KamatKiír  KamatKiír(évk+1,év-1,összeg) vége(ha) Vége(algoritmus)

7. REKURZIÓ

161

7.5. Kitűzött feladatok 1. Írjunk rekurzív algoritmust, amely kiír egy n természetes számot törzsténye­ zőkre bontva! 2. Írjunk rekurzív algoritmust, amely ellenőrzi, hogy egy beolvasott szám töké­ letes szám-e (egyenlő-e a nála kisebb osztóinak összegével)! 3. Írjunk rekurzív algoritmust, amely kiszámítja egy n-ed fokú polinom értékét egy adott x pontban! 4. Írjunk rekurzív algoritmust, amely kiszámítja n természetes szám legna­ gyobb közös osztóját! 5. Írjunk rekurzív algoritmust, amely kiszámítja az Ackermann-függvény érté­ két adott m és n értékekre: Ack(0, n) = n + 1, nN Ack(m, 0) = Ack(m – 1, 1) mN* Ack(m, n) = Ack(m – 1, Ack(m, n – 1)) m, nN*. 6. Írjunk rekurzív algoritmust, amely kiszámítja az n-edik Fibonacci-számot. Hasonlítsuk össze a megfelelő iteratív algoritmus futási idejét ennek az algo­ ritmusnak a futási idejével! 7. Írjunk rekurzív algoritmust, amely átalakít egy, a 16-os számrendszerben megadott számot a 10-es számrendszerbe! 8. Írjunk rekurzív algoritmust, amely generálja egy adott n szám minden partí­ cióját. Azok közül a partíciók közül, amelyek csak a tagok sorrendjében kü­ lönböznek, csak egyet kell kiírnunk.

8

A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

8.1. Bevezetés Az algoritmusok behatóbb tanulmányozása meggyőzött bennünket, hogy terve­ zésükkor meg kell vizsgálnunk a végrehajtásukhoz szükséges időt. Ha ez az idő elfogadhatatlanul nagy, más megoldásokat kell keresnünk. Egy algoritmus vég­ rehajtásához szükséges idő méretének megállapításához ismernünk kellene a bonyolultságelmélet alapjait. Addig is, lássuk be, hogy egy algoritmus „elfogad­ ható”, ha végrehajtási ideje polinomiális, vagyis nk-val arányos (adott k-ra és n bemeneti adatra). [9] Legyen például egy számítógép, amely 1 millió utasítást hajt végre egy má­ sodperc alatt. A következő táblázat három különböző bonyolultságra és külön­ böző méretű bemenetre tartalmazza a végrehajtás idejét. 3

n 2n 3n

n = 20  1 mp 58 perc

n = 40  12,7 nap 3855 évszázad

n = 60 0,2 mp 366 évszázad 1013 évszázad

Az előbbi táblázat meggyőz bárkit, hogy az exponenciális bonyolultságú al­ goritmusok elfogadhatatlanok, még akkor is, ha tudjuk, hogy a mai számítógé­ pek képesek másodpercenként többször tízmilliárd műveletet végrehajtani. De mit tegyünk, ha egy feladatot csak exponenciális algoritmussal tudunk megoldani? Ha jobb megoldás nincs, alkalmazzuk a backtracking (visszalépéses keresés) módszert, amely exponenciális ugyan, de megpróbálja csökkenteni a generálandó próbálkozások számát (ez nem mindig sikerül).

8.2. A visszalépéses keresés általános bemutatása A visszalépéses keresés azon feladatok megoldásakor alkalmazható, amelyek­ nek eredményét az M1  M2  ...  Mn Descartes-szorzatnak azon elemei alkot­ ják, amelyek eleget tesznek bizonyos belső feltételeknek. Az M1  M2  ...  Mn Descartes-szorzat a megoldások tere (az eredmény egy x sorozat, amelynek xi eleme az Mi halmazból való).

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

163

Egy elképzelhető stratégia az lenne, hogy határozzuk meg az összes lehetsé­ ges megoldást (a Descartes-szorzat minden elemét) majd döntsük el, hogy ezek közül melyek azok, amelyek teljesítik a belső feltételeket. De egy ilyen algorit­ mus megengedhetetlenül nagy végrehajtási időhöz vezetne. A visszalépéses keresés nem generálja a Descartes-szorzat minden x = (x1, x2, ..., xn)  M1  M2  ...  Mn elemét, hanem csak azokat, amelyek esetében remélhető, hogy megfelelnek a belső feltételeknek. Így, megpróbálja csökken­ teni a próbálkozásokat. Az algoritmusban az x tömb elemei egymás után, egyenként kapnak értéke­ ket: xi számára csak akkor „javasolunk értéket”, ha x1, x2, ..., xi–1 már kaptak végleges értéket az aktuálisan generált eredményben. Az xi-re vonatkozó „javas­ lat”-ot akkor fogadjuk el, amikor x1, x2, ..., xi–1 értékei az xi értékével együtt megvalósítják a belső feltételeket. Ha az i-edik lépésben a belső feltételek nem teljesülnek, xi számára új értéket választunk az Mi halmazból. Ha az Mi halmaz minden elemét kipróbáltuk, visszalépünk az i–1-edik elemhez, amely számára új értéket „javasolunk” az Mi–1 halmazból. Ha az i-edik lépésben a belső feltételek teljesülnek, az algoritmus folytató­ dik. Ha szükséges folytatni, mivel a számukat ismerjük és még nem generáltuk mindegyiket, vagy valamilyen másképp kifejezett tulajdonság alapján eldöntöt­ tük, hogy még nem jutottunk eredményhez, a folytatási feltételek alapján foly­ tatjuk az algoritmust. Azokat a lehetséges eredményeket, amelyek a megoldások teréből vették ér­ tékeiket úgy, hogy teljesítik a belső feltételeket, és amelyek esetében a folytatási feltételek nem kérnek további elemeket végeredményeknek nevezzük. Az algoritmus fontos jellemzője a visszalépés. Innen ered az elnevezése is (backtracking = visszalépéses keresés, to track = követni; back = vissza). A belső feltételek és a folytatási feltételek között szoros kapcsolat áll fenn. Ezek kifejezésmódjának szerencsés megválasztása többnyire a számítások csök­ kentéséhez vezethet. A módszert leírhatjuk iteratívan illetve rekurzívan.

8.2.1. Iteratív algoritmus A belső feltételeket egy külön algoritmusban vizsgáljuk. Legyen ennek a neve Megfelel, és paramétere az aktuálisan generált elem i indexe. Ez az alprogram igaz értéket térít vissza, ha az xi elem az eddig generált x1, x2, ..., xi–1 elemekkel együtt megfelel a belső feltételeknek, és hamis értéket ellenkező esetben. [8]

164

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

{ függvény típusú algoritmus }

Algoritmus Megfelel(i): Megfelel  igaz

Ha a belső feltételek x1, x2, ..., xi esetében nem teljesülnek akkor Megfelel  hamis vége(ha) Vége(algoritmus)

Az eredményt a következő algoritmussal generáljuk: Algoritmus Iteratív_Backtracking: i  1 { az eredmény első elemének fogunk értéket keresni } x1  0 { kezdőérték az első elem számára } Amíg i > 0 végezd el: ok  hamis Amíg még vannak ki nem próbált értékek az Mi halmazban és nem ok végezd el: xi  következő érték az Mi halmazból ok  Megfelel(i) { lehet, hogy xi számára ez megfelelő } vége(amíg) Ha nem ok akkor { ha xi értéke nem megfelelő, akkor xi–1 számára új értéket keresünk } i  i - 1 különben { ha xi megfelelt } Ha i = n akkor { ha ez volt az utolsó generálandó elem } Ki: x1, x2, ..., xn különben i  i + 1 xi  0 vége(ha) vége(ha) vége(amíg) Vége(algoritmus)

{ ha még vannak generálandó elemek } { a következő elem indexe az eredmény sorozatában } { kezdőérték }

8.2.2. Rekurzív algoritmus A rekurzív algoritmust a következő módon írjuk le: Algoritmus Rekurzív_Backtracking(i): Minden mj  Mi értékre végezd el: xi  mj Ha Megfelel(i) akkor { megvalósulnak a belső feltételek x1, x2, ..., xi esetében } Ha i < n akkor Rekurzív_Backtracking(i+1)

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

165

különben Ki: x1, x2, ..., xn vége(ha) vége(ha) vége(minden) Vége(algoritmus)

Az algoritmust az i = 1 értékre hívjuk meg először. A módszer eredményessége nagy mértékben függ a folytatási feltételek sze­ rencsés kiválasztásától. Minél hamarabb állítjuk le egy eredmény generálását, annál kisebb a rekurzió mélysége, de a feltételek nem lehetnek túl bonyolultak, mivel ezeket minden lépésnél végrehajtja az algoritmus. Ha az xi-be átvihetők az mi elemek indexei az Mi-ből, a folytatási feltételek egyszerűbben fejezhetők ki. Legyen szi az Mi (i = 1, 2, ..., n) halmaz elemeinek száma. Az előbbi Rekurzív_Backtracking(i) algoritmus a következőképpen írható át: Algoritmus Backtracking(i): Minden j = 1,szi végezd el: xi  j Ha Megfelel(i) akkor { megvalósulnak a belső feltételek x1, x2, ..., xi esetében } Ha i < n akkor Backtracking(i+1) különben Ki: x1, x2, ..., xn vége(ha) vége(ha) vége(minden) Vége(algoritmus)

Ha az M1  ...  Mn Descartes-szorzat elemeinek száma exponenciális, és a belső feltételek nem csökkentik drasztikusan ezt a számot, a programok futási ideje megengedhetetlenül nagy és más módszerhez kell folyamodnunk (esetleg valószínűség számításra épülő módszerekhez). A módszer azoknak a feladatoknak a megoldásakor alkalmazható, amelyek­ ben a követelményeknek megfelelően minden eredményt meg kell állapítanunk. Ha az M1  ...  Mn Descartes-szorzat számossága nem túl nagy, valamint a fel­ tételek biztosítanak egy nem túl mély rekurziót, eredményesen alkalmazható. Összefoglalva a lényeget, a következő lépéseket kell elvégeznünk:

a) az eredmény kódolása – meg kell állapítanunk az xi elemek jelentését az illető feladat esetében, valamint meg kell határoznunk az Mi, i = 1, 2, ..., n halmazokat.

166

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

b) a belső, majd a folytatási feltételek megállapítása. c) a Rekurzív_Backtracking(i) vagy iteratív változatának átírása. Megjegyzések  Ha egyetlen eredményt kell megkeresnünk, a kiírás után leállítjuk az al­ goritmust.  Ha egy kiszámolt eredményt meg kell vizsgálnunk, (például, összehasonlí­ tanunk más eredményekkel), akkor a kiírás helyett meghívjuk a megfelelő alprogramot, amely elvégzi ezt a feldolgozást.

8.2.3. 8 királynő a sakktáblán Írjuk ki az összes lehetséges módját annak, ahogyan 8 királynő elhelyezhető egy sakktáblán úgy, hogy ne támadják egymást. Két királynő támadja egymást, ha ugyanazon a soron, oszlopon, illetve átlón helyezkedik el. Elemzés ■ Mivel az összes lehetőséget meg kell találnunk, rendszeresen kell dol­ goznunk. A királynőket rendre tesszük fel a sakktáblára: az elsőt az első sor első oszlopába, a másodikat a második sor harmadik oszlopába, (az első két oszlop­ ba nem tehetjük, mivel akkor támadja egymást az első és a második királynő). Hasonlóképp járunk el a következő három királynő esetében is és eljutunk a következő konfigurációhoz:

Most a hatodikat nincs hova tenni, bárhova tennénk, támadná az eddig feltett királynőket. Ezért visszatérünk az ötödikhez és megpróbáljuk a következő lehetséges oszlopba tenni (megtaláljuk a 8. oszlopot). Most újból nincs hova tenni a hato­ dik királynőt. Visszatérünk az ötödik­ hez, de ezt nem lehet tovább mozgatni.

oszlop királynő 1 2 3 4 5 6 7 8

1 2 3 4 5 6 7 8

oszlop királynő 1 2 3 4 5 6 7 8

1 2 3 4 5 6 7 8

167

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

Levesszük a tábláról és új helyet keresünk a negyediknek. A hetedik oszlopba tesszük, így további hármat tudunk elhelyezni, anélkül, hogy visszatérnénk:

oszlop királynő 1 2 3 4 5 6 7 8

1 2 3 4 5 6 7 8

A továbbiakban, hasonló próbálga­ tással megtaláljuk az első eredményt:

oszlop királynő 1 2 3 4 5 6 7 8

1 2 3 4 5 6 7 8

Észrevételek 

Minden királynő más sorba került.



A királynőket egymás után he­ lyeztük a sakktáblára, mindegyiket a megfelelő sorba.



Minden királynőt egymás után helyeztünk a neki megfelelő sorba az első oszloppal kezdődően, addig amíg meg nem találtuk azt az oszlopot, amely­ ben nem támad más, eddig feltett királynőt.



Ha egy királynőt nem lehetett elhelyezni, visszatértünk az előzőhöz és szá­ mára tovább kerestünk megfelelő, nagyobb sorszámú oszlopot.

Az első észrevételből következik, hogy egy eredményt egy egydimenziós tömbbel (Ki, i = 1, 2, ..., 8) kódolunk. A tömb Ki elemeinek értéke az oszlop sor­ száma, ahova az i-edik királynőt tettük (az i-edik sorban). A sakktáblának 8 oszlopa van, tehát Ki  {1, 2, …, 8}, i = 1, …, 8. Az eddigiekből következik, hogy egy eredmény az {1, 2, …, 8} 8 Descartesszorzat eleme. Tehát, ha meg akarjuk oldani a feladatot, tulajdonképpen az {1, 2, …, 8}8 Descartes-szorzat egy részhalmazát kell meghatároznunk, azzal a feltétellel, hogy a 8 királynő, amelyek a K1, K2, ..., K8 oszlopokban találhatók, ne támadja egymást. A kódolás sajátos módja biztosítja, hogy soronkénti támadási lehetőség nincs, hiszen minden királynő új sorba kerül. De például, ha az első két királynő egymást támadja (K1 = 1 és K2 = 1, vagy K1 = 1 és K2 = 2), nem generálunk fölöslegesen 86 = 262144 elemet a {1, 2, ..., 8}8 Descartes-szorzatból. A második észrevétel a feladat rekurzív megfogalmazását teszi lehetővé: el­ helyezzük az első királynőt, rendre az első sor első, második, ..., 8-dik oszlopá­ ba, majd megoldjuk a feladatot a fennmaradt 7 királynő esetében, de úgy, hogy mindig ellenőrizzük, hogy egy új királynő ne támadjon egyet sem a már elhe­ lyezettek közül.

168

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

Általánosan megfogalmazva: az i-edik királynő esetében meg kell határoz­ nunk minden helyet, ahova ezt el lehet helyezni az i-edik sorban úgy, hogy ne támadjon egyet sem azok közül, amelyek az első, második, ..., i–1-edik sorban már el vannak helyezve. Tehát elhelyezzük az i-edik királynőt, majd megoldjuk ugyanezt a feladatot az i+1-edik királynő esetében. Ha minden királynőt elhelyeztük, van egy eredmény, amit ki kell írnunk. Az elhelyezést a Királynő(i) rekurzív alprogram végzi el, a támadási lehetőséget a Nem_támad(i) logikai függvény ellenőrzi. Ahhoz, hogy két királynő ne támadja egymást, a következő relációknak kell teljesülniük: Ki  Kj, i – j  Ki – Kj, j = 1, 2, ..., i – 1. [17] Algoritmus Nem_támad(i): Jó  igaz { Jó lokális változó, K globális } j  1 Amíg (j  i-1) és Jó végezd el: Ha (Ki = Kj) vagy (i-j = Ki - Kj akkor Jó  hamis különben j  j + 1 vége(ha) vége(amíg) Nem_támad  Jó Vége(algoritmus) Algoritmus Királynő(i): Minden j=1,8 végezd el: Ki  j Ha Nem_támad(i) akkor Ha i < 8 akkor Királynő(i+1) különben Kiír vége(ha) vége(ha) vége(minden) Vége(algoritmus)

{ az i és j királynők támadják egymást }

{ az i-edik királynőt a j-edik oszlopba tesszük } { az i-edik királynő nem támadja egyiket sem }

Az első hívás alakja: Királynő(1). Az sakktáblát az eredményt tároló sorozat értékei alapján írjuk ki. Az i-edik sor '*'-okat tartalmaz, azt a j pozíciót kivéve, amelynek megfelelően Ki = j, ahova 'K' betűt írunk.

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

169

Algoritmus Kiír: Minden i=1,8 végezd el: Minden j=1,8 végezd el: Ha Ki = j akkor Ki: 'K' különben Ki: '*' vége(ha) vége(minden) vége(minden) Vége(algoritmus)

8.2.4. Variációk Az óvónéni a karácsonyi ünnepélyre készül. A díszterem színpadán n széket lehet egy sorban elhelyezni, de a csoportban m óvódás van (n < m). Írjuk ki minden lehetséges módját annak, ahogy az óvódások leülhetnek az n székre. [17] Elemzés ■ Két megoldás következik (A és B), mindkettő rekurzív, de különbö­ zik az eredmény kódolása, valamint a belső feltételek vizsgálatának módja. Az elhelyezési módok különbözők, ha különböznek a gyerekek, illetve ha különbözik a sorrend ahogy elhelyezkednek az n széken. minden megoldás tar­ talmaz n óvódást. A ■ Első megoldás Az eredmény kódolása ■ xi = annak az óvódásnak a neve, aki az i-edik székre ül (i = 1, ..., n). Ha a nevek név1, név2, ..., névm akkor: xi {névj,  j = 1, 2, ..., m} = M, (i = 1, 2, ..., n). Belső feltételek ■ Egy elhelyezési módon belül, egy gyerek csak egy széken ül­ het, tehát az n gyerek nevének különbözőnek kell lennie, vagyis xi  xj, i  j, j = 1, 2, ..., n. Folytatási feltétel ■ Az i-edik székre olyan gyerek ül, aki nem ül már valame­ lyik széken az előző i – 1 szék közül, és még nem ült le mindenki. A Variáció(i) rekurzív alprogram az első Backtracking(i) algoritmus alapján készült. Megállapítja az i-edik székre ültetett gyermek nevét, majd hívja a Variáció(i+1) alprogramot, hogy leültethesse a gyerekeket a következő i + 1, i + 2, ..., n székre. Algoritmus Még_nem_ül(i): j  1

170

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

Jó  igaz Amíg Jó és (j ≤ i - 1) végezd el: Ha xi = xj akkor { az xi nevű gyermek már ül valamelyik j (j = 1, 2, ..., i – 1) széken } Jó  hamis különben j  j + 1 vége(ha) vége(amíg) Még_nem_ül  Jó Vége(algoritmus) Algoritmus Variáció(i): Minden j=1,m végezd el: xi  névj { az i-edik székre a névj gyerek ül } Ha Még_nem_ül(i) akkor { teljesül a belső feltétel } Ha i < n akkor { folytatjuk az eredmény meghatározását } Variáció(i+1) { leültetünk gyerekeket a következő i + 1, ..., n székre } különben Kiír vége(ha) vége(ha) vége(minden) Vége(algoritmus)

A hívó programban megvizsgáljuk az n és m közötti relációt, és csak akkor végezzük el az „ültetést”, ha n < m. Az alprogramot az első székre ültetéssel kezdjük, tehát a paraméter 1. B ■ Második megoldás Az eredmény kódolása ■ Az xi az i-edik székre ülő gyerek nevének az indexe. Tehát xi {1, 2, ..., m}, ahol m a gyermekek száma, (i = 1, 2, ..., n). Belső feltételek ■ xi  xj, i  j, i, j = 1, 2, ..., n. Folytatási feltételek ■ xi  xj, i  j, j = 1, 2, ..., i – 1.

(*)

Mivel xi értéke egy index, a folytatási feltételek kifejezhetők egyszerűbben egyetlen feltétellel. Azt fogjuk kifejezni, hogy az i-edik székre csak olyan gye­ rek ülhet le, aki pillanatnyilag még áll. Fölhasználunk egy még_áll logikai töm­ böt, ahol még_állj igaz, ha a j-edik gyerek még nem ült le, és hamis ellenkező esetben. Az xi  xj, j = 1, 2, ..., i – 1 feltételek a következőképpen alakulnak át: még_állxi = igaz.

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

171

A még_áll tömb elemeinek kezdőértéke igaz, mivel még senki nem ült le; majd az ültetési folyamat során a megfelelő elemek hamis értéket kapnak. Vala­ hányszor egy ültetési rend megváltozik a j-edik gyermek feláll az i-edik székről és oda más gyermek ül le; a j-edik gyermek felállítása maga után vonja a meg­ felelő még_állj visszaállítását igaz-ra. Vegyük észre, hogy a belső feltételek ellenőrzése során legfeljebb i – 1 összehasonlítást végez az algoritmus, ha ezek nem teljesülnek, és pontosan i – 1 összehasonlítást, ha ezek teljesülnek. A még_áll tömb használata mindkét eset­ ben egyetlen összehasonlítást és két értékadást igényel, ha a belső feltételek tel­ jesülnek. Következésképpen, ez a második megoldás hatékonyabb mint az első. Algoritmus Kiír: Minden i=1,n végezd el: Ki: névxi, ' ' vége(minden) Vége(algoritmus)

{ az eredménytömb a nevek sorozatában index }

A hívásnál igaz értékkel inicializáljuk a még_áll logikai tömb elemeit. Algoritmus Variáció(i): Minden j=1,m végezd el: Ha még_állj akkor xi  j még_állj  hamis Ha i < n akkor Variáció(i+1) különben Kiír vége(ha) még_állj  igaz vége(ha) vége(minden) Vége(algoritmus)

{ a j-edik gyermek még áll } { a j-edik gyermek az i-edik széken ül }

{ a j-edik gyermek feláll }

Ennek a megoldásnak az alapján körvonalazódik a Backtracking(i) algorit­ mus harmadik változata: Algoritmus Backtracking(i): Minden j=1,szi végezd el: { ha j kiválasztható az x1, ..., xi–1 eredmény i-edik elemeként } Ha Megfelel(j) akkor xi  j

megváltozik minden adat amelyeket érint a j kiválasztása Ha i < n akkor Backtracking(i+1)

172

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

különben Ki: mx1,mx2,...,mxn vége(ha)

visszaállítunk minden értéket, amelyeket a j kiválasztásakor megváltoztattunk vége(ha) vége(minden) Vége(algoritmus)

8.2.5. Zárójelek Írjunk ki minden helyesen nyitó és csukó n zárójelet tartalmazó karakterláncot! Az eredmény kódolása ■ Ha n páros szám, az eredmények az Mn halmaz elemei, ahol M = {'(', ')'} és xi  M, i = 1, ..., n. Ha n páratlan, akkor nincs megoldás. Belső feltételek ■ Adott pillanatban ne létezzen több csukó zárójel, mint nyitó. Folytatási feltételek ■ Mivel a belső feltételek megengedik, hogy a nyitó záróje­ lek száma meghaladja a csukókét, bármikor nyithatunk új zárójelet, ha a számuk nem nagyobb mint n/2. Bármikor becsukhatunk egy zárójelet, ha a számuk ki­ sebb mint a nyitóké. Jelöljük ny-nyel és cs-vel a nyitó, illetve a csukó zárójelek számát. A folytatási feltételek különböznek az xi elemek értékének függvényében: n , ha x ' ('   ny  i  2 , ha x ' )' i  2,3,..., n i cs  ny Mivel bármely eredményben x1 = '(' és xn = ')', a hívó programegységben elvégezzük az inicializálásokat: x1  '(' és xn  ')'. Az első hívás alakja: Zárójel(2,1,0). Algoritmus Zárójel(i,ny,cs): Ha i = n akkor Ki: x különben Ha ny < [n/2] akkor xi  '(' Zárójel(i+1,ny+1,cs) vége(ha) Ha cs < ny akkor xi  ')' Zárójel(i+1,ny,cs+1) vége(ha) vége(ha) Vége(algoritmus)

{ kilépési feltétel }

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

173

8.2.6. Legrövidebb utak Adva van két természetes szám n és k (0  k  n  10). Legyen egy négyzetháló, amely meghatározza a P pontokat az xOy síkban: P(p, q), p, q  , p  {0, 1, ..., k} és q  {0, 1, ..., n – k}. Határozzuk meg azokat a legrövidebb utakat, amelyek összekötik az O(0, 0) és M(k, n – k) pontokat és a hálót alkotó négyzetek oldalai mentén haladnak.

y n–k

O

M(k, n – k)

k

x

Az utat a rajta levő pontok koordinátáin keresztül írjuk ki. Megoldás ■ Az út meghatározásakor fölhasználjuk az észrevételt, miszerint minden pontban két választási lehetőségünk van: vízszintesen haladunk, vagy függőlegesen. Amikor vízszintesen haladunk, változik az abszcissza: xi = xi–1 + 1, és az ordináta értéke megmarad: yi = yi–1, amikor függőlegesen, akkor változik az ordináta: yi = yi–1 + 1, és az abszcissza értéke megmarad: xi = xi–1. Mivel az útvonal az origóból indul, az alprogram hívása: Út(1,0,0). Algoritmus Út(i,vízszintes,függőleges): { n és k globális változók } Ha i = n + 1 akkor { megérkeztünk az M pontba } Kiír különben Ha vízszintes < k akkor { vízszintes oldalt választunk } xi  xi-1 + 1 { lépünk egyet előre a vízszintesen } yi  yi-1 { az új pont ordinátája nem változik } Út(i+1,vízszintes+1,függőleges) vége(ha) Ha függőleges < n - k akkor { függőleges oldalt választunk } xi  xi-1 { az új pont abszcisszája nem változik } yi  yi-1+1 { lépünk egyet előre a függőlegesen } Út(i+1,vízszintes,függőleges+1) vége(ha) vége(ha) Vége(algoritmus)

Az utat a koordinátákon keresztül írjuk ki, az eredmény két n + 1 elemű so­ rozat (x és y). A kiírást a következő algoritmussal végezzük:

174

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

Algoritmus Kiír: Minden i=0,n végezd el: Ki: '(', xi, ',', yi, ') ' vége(minden) Vége(algoritmus)

8.2.7. Játékok Egy gyermek el szeretne helyezni n megszámozott játékot n megszámozott do­ bozba. Írjuk ki a csomagolás minden lehetséges módját, tudva azt, hogy egy do­ bozba legfeljebb m játék fér. Csomagolás közben az is lehetséges, hogy bizo­ nyos dobozok üresen maradjanak (1  n, m  7). [2] Megoldás Az eredmény kódolása ■ Az x eredmény i-edik eleme annak a doboznak a sor­ száma, amelyikbe az i-edik játékot csomagoljuk (i = 1, 2, ..., n). Az sz sorozatban nyilvántartjuk a becsomagolt játékok számát, dobozonként. Példa ■ Legyen n = 2 és m = 3. A megoldások: x = (1, 1) (Az 1-es és 2-es játék az 1-es dobozba kerültek). x = (1, 2) (Az 1-es játék az 1-es dobozban, a 2-es a 2-es dobozban). x = (2, 1) (Az 1-es játék a 2-es dobozban, a 2-es az 1-es dobozban). x = (2, 2) (Az 1-es és 2-es játék a 2-es dobozba kerültek). A belső feltételek ellenőrzése érdekében megszámolhatnánk, hogy az aktuá­ lis lépésig hányszor érintettünk egy-egy dobozt, majd megnézhetnénk, hogy a számuk (tehát a becsomagolt tárgyak száma) nagyobb-e mint m. Könnyen belát­ ható, hogy ezzel a módszerrel sok fölösleges megoldást generálnánk ahhoz, hogy nagyrészüket a feltétel ellenőrzésének eredménye alapján eldobjuk. Le­ mondunk erről az ötletről, és minden lépésben előbb megnézzük, hogy a doboz, ahova az aktuális játékot csomagolnánk, nincs-e már tele: Algoritmus Játékok(i): { az i-edik játékot fogjuk elhelyezni } Minden j=1,n végezd el: { n dobozba helyezhetünk } Ha Megfelel(j) akkor { ha lehetséges } xi  j { az i-edik játékot a j-edik dobozba helyezzük } szj  szj + 1 { nő a játékok száma a j-edik dobozban } Ha i = n akkor { ha elhelyeztük az n-edik játékot is } Kiír { kiírunk egy eredményt } különben Játékok(i+1) { tovább dolgozunk } vége(ha) szj  szj - 1 { csökken a j-edik dobozban található játékok száma } vége(ha) vége(minden) Vége(algoritmus)

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

175

Algoritmus Megfelel(j): Megfelel  szj < m Vége(algoritmus)

Az eredményt tároló sorozat alapján a következőképpen írjuk ki a megol­ dást: a doboz sorszáma után következnek az illető dobozba csomagolt játékok sorszámai. Algoritmus Kiír: Minden i=1,n végezd el: Ki: i, '. doboz tartalma: ' volt  hamis Minden j=1,n végezd el: Ha i = xj akkor volt  igaz Ki: j, '. játék, ' vége(ha) vége(minden) Ha volt akkor Ki: visszavisszük a kurzort két pozícióval és szóközöket írunk különben Ki: 'semmi' vége(ha) vége(minden) Vége(algoritmus)

8.2.8. Szürjektív függvények Generáljuk le az összes f: A  B szürjektív függvényt, ahol (A = {1, 2, ..., n} és B = {1, 2, ..., m}! [17] Elemzés ■ Egy függvény szürjektív ha y  B  x  A: f(x) = y, vagyis f(A) = B. Szürjektív függvényeket csak akkor generálhatunk, ha n  m. Az eredmény kódolása ■ fi = az az elem B-ből, amely az A-beli i elemnek meg­ felelő érték. fi  {1, ..., m}, i = 1, 2, ..., n. A belső feltétel ■ f(A) = B vagyis {f1, f2, ..., fn} = {1, 2, ..., m}. A folytatási feltételek ■ Azt kell kifejezniük, hogy egy f1, ..., fi részeredmény esetében remélhetjük, hogy amikor i egyenlő n-nel, a végeredmény tartalmazza B minden elemét. Ez teljesül, ha azon elemek száma f-ből, amelyek még nem kaptak értéket, mindig nagyobb vagy egyenlő, mint azon elemek száma, ame ­ lyeket még f-be tehetünk B-ből (még_van); a feltételt a következőképpen írjuk le: n – i  még_van.

176

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

Mivel egy bizonyos j  B elem több fi elemnek adhatja át értékét, meg kell őket számlálnunk az sz tömbben ahhoz, hogy a még_van pontosan meghatároz­ ható legyen: szj = a j-vel egyenlő elemek száma az f1, f2, ..., fi értékek között. Kezdetben szj = 0, j = 1, 2, ..., m, és még_van = m. Utóbb ez változik miköz­ ben fi értéket kap B-ből. Az algoritmust Szürjektív(1) alakban hívjuk meg. Algoritmus Szürjektív(i): Minden j=1,m végezd el: fi  j szj  szj + 1 Ha szj = 1 akkor még_van  még_van - 1 vége(ha) Ha n - i  még_van akkor Ha i < n akkor Szürjektív(i+1) különben Kiír vége(ha) vége(ha) szj  szj - 1 Ha szj = 0 akkor még_van  még_van + 1 vége(ha) vége(minden) Vége(algoritmus)

{ f még lehet szürjektív }

Ha a belső feltételeket csak akkor ellenőriztük volna, amikor i = n, a progra­ munk egyszerűbb, de kevésbé hatékony lett volna, mivel az Mn Descartes-szor­ zat minden elemét generáltuk volna.

8.3. A visszalépéses keresés bővítése A visszalépéses keresést ki lehet bővíteni olyan feladatok megoldására, ame­ lyeknek az x = (x1, x2, ..., xi) eredménye eleme az M1  M2  ...  Mi (1  i  n) halmaznak, és amelyek kielégítik a belső feltételeket. A változás abban áll, hogy az eredmény elemeinek száma változik (nem n, hanem i); ez minden esetben a belső feltételektől függ. Tehát az algoritmus általános alakja a következő: Algoritmus Backtracking(i): Minden mj  Mi végezd el: xi  mj { ha megvalósulnak a belső feltételek x1, x2, ..., xi esetében } Ha Megfelel(i) akkor Ha x1, x2, ..., xi eredménye a feladatnak akkor

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

177

Ki: x1, x2, ..., xi különben Backtracking(i+1) vége(ha) vége(ha) vége(minden) Vége(algoritmus)

Vegyük észre, hogy a Partíciók feladat megoldása tulajdonképpen a vissza­ lépéses keresés alkalmazásával történt. Elemezzük a megoldást a visszalépéses keresés ismeretében. Az eredmény kódolása ■ Minden partíciót egy tömbben tároltunk, ahol p1, ..., pi, 1  i  n, pi  {1, 2, ..., n}. A belső feltétel ■ p1 + p2 + ... + pi = n. A folytatási feltételek ■ Ezek a feltételek biztosítják majd annak a lehetőségét, hogy egy p1, ..., pi részeredmény átalakulhasson végeredménynyé: pi  n – (p1 + p2 + ... + pi–1) = ami fennmaradt az n-ből. A fennmaradt részt újból n-nel jelöljük. Ezáltal a folytatási feltételeket egy­ szerűbben így írhatjuk fel: pi  n. Mikor pi = j = n, a fennmaradt érték 0, tehát a részeredmény tulajdonképpen végeredmény. Az x1, x2, ..., xi, 1  i  n eredményt ábrázolhatjuk egy fával, amelynek a gyö­ kere általában nincs megcímkézve, a következő szinteken levő csomópontok mellé pedig az xi (i  1) értékeket rendeljük. Minden eredmény a fa egy ágának felel meg, amely összeköt egy, az első szinten levő csomópontot egy levéllel. A következő ábra kiemeli a visszalépéses keresés lényegét: az eredményt az összes lehetséges eredményt tartalmazó M1  M2  ...  Mn halmaz mélységi be­ járása során építjük fel. Az xi csak akkor kap új értéket, ha már kipróbáltunk minden lehetséges értéket az x1, x2, ..., xi részeredmény esetében. [17]

178

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

szintek 0

n

4

1

=

1

2

3

4

p1

+ 2 3

1 1

2 2

3

1

1

2

1

1

p2

+ p3

+ 4

1

p4

Ez a látásmód a visszalépéses keresés újabb bővítését sugalmazza azon fela­ datok esetében, amelyeknek modellje egy gráf és ezt kell bejárni mélységében. Algoritmus Mélységi_bejárás(i): Minden i-vel illeszkedő k csomópontra végezd el: Ha a k csomópont még nem volt bejárva akkor Ki: k { feldolgozzuk (jelen esetben kiírjuk) a k csomópontot } Mélységi_bejárás(k) vége(ha) vége(minden) Vége(algoritmus)

A rekurzióból akkor fogunk kilépni amikor nincs több bejáratlan csomó­ pont, amely érintkezne valamely már bejárt csomóponttal. Előfordulhat, hogy az algoritmus egy hívása után, egy bizonyos i csomóponttal kezdődően a gráf­ ban maradjanak bejáratlan csomópontok. Ebben az esetben megtörténik az újra­ hívás egy másik csomópontra, amely még nem volt bejárva addig, amíg a gráf minden csomópontját be nem jártuk. A belső feltételektől függően előfordulhat, hogy minden csomópontot be kell járnunk, vagy csak egy részüket. A Backtracking(i) alprogram abban az esetben, ha minden csomópontot be kell járnunk: Algoritmus Backtracking(i): Minden i-vel illeszkedő k csomópontra végezd el: Ha a k csomópont még nem volt bejárva és teljesülnek a belső feltételek akkor feldolgozzuk a k csomópontot Ha végeredményhez jutottunk akkor Ki: eredmény

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

179

vége(ha) Backtracking(k) vége(ha) vége(minden) Vége(algoritmus)

8.3.1. S pénzösszeg kifizetése Írjuk ki az S összeg minden lehetséges kifizetési módját a b1, b2, ..., bn címletű bankjegyek segítségével. Ezekből a bankjegyekből elegendő menynyiség áll a rendelkezésünkre. Az eredmény kódolása ■ Az eredményt a darab nevű tömbbel kódoljuk, amely­ nek elemei a darabi, a bi címletű bankjegyek darabszámai, amelyekkel kifi­ zethető az S összeg (1  i  n). darabi  {0, ..., [S/min(b1, …, bn)]}, i = 1, ..., n. A belső feltétel ■ darab1  b1 + darab2  b2 + ... + darabi  bi = S. A folytatási feltétel ■ A darab1, darab2, ..., darabi részeredmény esetében: darabi  bi  S – (darab1  b1 + ... + darabi-1  bi-1) = Smaradék A feladat hasonlít a partíciókhoz. Ha az i-edik lépésben a még kifizetetlen összeget szintén S-sel jelöljük, a folytatási feltétel a következőképpen fejezhető ki: darabi  bi  S  darabi  [S/bi]  darabi  {0, 1, ..., [S/bi]} Ha a (bi) tömböt csökkenő sorrendbe rendezzük, a bn bankjegy a legkisebb és nagy valószínűséggel a fennmaradt összeget ki lehet fizetni ennek a bankjegy­ nek a felhasználásával. Ezt az esetet (i = n) külön tárgyaljuk. Mivel a bankjegyek értéke bármilyen, előfordulhat, hogy a feladatnak nincs megoldása. Algoritmus Fizet(i,S): Ha i = n akkor Ha maradék[S/bn] = 0 akkor darabn  [S/bn] Kiír(n) vége(ha) különben Minden j=0,[S/bi] végezd el: darabi  j maradt  S - j*bi Ha maradt = 0 akkor Kiír(i) különben

{ j darab a bi bankjegyből }

180

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

Fizet(i+1,maradt) vége(ha) vége(minden) vége(ha) Vége(algoritmus)

{ j  [S/bi]  maradt  0 }

Algoritmus Kiír(i): eredmény  igaz { megjegyezzük, hogy van legalább egy eredmény } Minden j=1,i végezd el: Ha darabj > 0 akkor { a 0 darabszámú címleteket nem írjuk ki } Ki: darabj, ' darab a ', bj, ' címletűből' vége(ha) vége(minden) Vége(algoritmus)

A Fizet(1,S) hívása után megnézzük az eredmény globális logikai változó értékét. Ennek hamis értéke azt jelenti, hogy a Kiír(i) algoritmust egyszer sem hívtuk meg, vagyis a feladatnak az adott bemenetre nincs megoldása, és ezt je­ lezzük egy megfelelő üzenettel.

8.3.2. Összegkifizetés, minimum számú bankjeggyel Írjuk ki az S összeg kifizetési módját a lehető legkevesebb darabszámú b1, b2, ..., bn címletű bankjegyek segítségével. Ezekből a bankjegyekből elegendő mennyiség áll a rendelkezésünkre. Elemzés ■ Ha nagy címletű bankjegyekkel fizetünk előbb, a fennmaradt összeg nagymértékben csökkenni fog, és a szükséges darabszám is kisebb mint egyéb ­ ként. Léteznek olyan címletek, amelyeket, ha a lehetséges legnagyobb számban használunk, a legnagyobb címlettől kezdődően, akkor az első megtalált megol­ dás, egyben a legjobb is. De ez nem történik így minden esetben. A megoldást a min_darab tömbbe tesszük. Ahhoz, hogy a program futási idejét csökkentsük azok a j értékek, amelyekkel a darabi eredménynek adunk értékeket az [S/bi]-től csökkennek a 0-ig. Lemondunk az eredmény generálásá­ ról, ha új_hánydarab (a megfelelő bankjegyből igényelt darabszám összeadva az eddig igényelt darabszámmal) nagyobb vagy egyenlő mint az aktuális mi ni­ mum. Ha így járunk el, a generált eredmények száma sokkal kisebb lesz mint az előző feladatban. Algoritmus Fizet_Min(i,S,hánydarab): vége  hamis j  [s/bi] Amíg nem vége és (j  0) végezd el: { legtöbb nagy címletű bankjeggyel próbálkozunk }

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

181

darabi  j { j darab a bi címletű bankjegyből } új_hánydarab  hánydarab + j Ha (új_hánydarab  min) és (i = n) akkor vége  igaz { ezt a megoldást leállítjuk } vége(ha) Ha nem vége és (új_hánydarab < min) akkor maradt  s - j*bi Ha maradt = 0 akkor eredmény  igaz { van egy megoldás (vagy az első, vagy jobb mint az eddigi legjobb) } min  új_hánydarab { aktualizáljuk a minimumot } hányféle  i { a legutoljára felhasznált címlet } min_darab  darab { a legkisebb összdarabszámú tömb } különben Ha i < n akkor { ha nem ez volt az utolsó bankjegy } Fizet(i+1,maradt,új_hánydarab) különben vége  igaz { ezt a megoldást leállítjuk } vége(ha) vége(ha) vége(ha) j  j - 1 vége(amíg) Vége(algoritmus)

A hányféle változó tartalmazza az eredményben felhasznált legkisebb címle­ tű bankjegynek a sorszámát. Így csökkentjük a Kiír algoritmusban a lekérdezen­ dő darabszámok számát, hiszen lehet, hogy kifizettük a teljes összeget például az 1 sorszámú címlettel (emlékezzünk arra, hogy a bankjegyeket címletük sze­ rint csökkenő sorrendbe rendeztük). A Kiír megegyezik az előbbi algoritmusban alkalmazott algoritmussal, de ott az i paraméter játszotta az épp érvényes hány­ féle szerepét, hiszen minden megoldást ki kellett írnunk, és megoldásonként változott az eredménytömb hossza. A Fizet_Min(i,S,hánydarab) első meghívása az 1, S, 0 paraméterekkel történik.

8.4. Visszalépéses keresés a síkban Kétféle feladattípus esetén használjuk, ha minden megoldást meg kell adnunk: a) kétdimenziós tömbben tárolt, síkbeli utak leírásánál; b) kétdimenziós tömbben tárolt adatok feldolgozásánál.

8.4.1. Labirintus

182

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

Egy labirintust egy Ln×m kétdimenziós tömbben tárolunk, amelyben a folyosónak megfelelő elemek értéke 1; ezek az értékek egymás után következnek a la bi­ rintusnak megfelelő tömbben egy bizonyos sorban, vagy oszlopban. Egy sze­ mélyt ejtőernyővel leengednek a labirintusba az (i, j) helyre. Írjuk ki minden, a labirintusból kivezető utat! Egy út nem érintheti kétszer ugyanazt a helyet. A labirintusból a tömb szélén léphetünk ki. A ■ Első megoldás Az eredmény kódolása ■ A feladat minden kivezető utat kéri. Egy utat az x1, x2, ..., xk és y1, y2, ..., yk sorozatokkal kódolunk, amelyek azokat a sorokat és oszlopokat tartalmazzák, amelyeknek érintésével kifele haladunk a labirintus­ ból. xi  {1, 2, ..., n}, yi  {1, 2, ..., m}, i = 1, 2, ..., k. Belső feltételek ■ Az útvonalra a következő belső feltételek érvényesek:

a) A folyosón kell haladjon: L(xi, yi) =1, i = 1, 2, ..., k. b) Nem léphet kétszer egy helyre: (xi, yi)  (xj, yj), i, j = 1, 2, ..., k, i  j. c) Biztosítania kell a labirintusból való kijutást: xk  {1, n} vagy yk  {1, m} Folytatási feltételek ■ Tartalmazzák az a) és b) ellenőrzését minden lépésnél. A b) feltétel az i-edik lépésben: (xi, yi)  (xj, yj), j = 1,..., i – 1. Az eredményt az eredmij (i = 1, 2, ..., n, j = 1, 2, ..., m) tömb segítségével tároljuk, amelyben

az a lépésszám mellyel az (i, j ) helyre léptünk eredmij   0, ha az útvonal nem halad át az (i, j ) helyen Egy bizonyos helyről négy irányba léphetünk. j. oszlop

1

i. sor

4

2 3

Az utakat az Út(i,j,lépés) rekurzív algoritmus generálja. Algoritmus Út(i,j,lépés): Ha (Lij = 1) és (eredmij = 0) akkor { próbálunk az (i, j) helyre lépni; ha (i, j) folyosó és még nem jártunk itt }

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

183

eredmij  lépés { az (i, j) helyre lépünk } Ha (i  {1,n}) vagy (j  {1,m}) akkor Kiír { kijárathoz értünk, kiírjuk az eredménytömböt } vége(ha) Út(i-1,j,lépés+1) { próbálunk más utat is: felfele lépünk } Út(i,j+1,lépés+1) { jobbra lépünk } Út(i+1,j,lépés+1) { lefele lépünk } Út(i,j-1,lépés+1) { balra lépünk } eredmij  0 { töröljük az utolsó lépést, hogy új utat választhassunk } vége(ha) Vége(algoritmus)

Ez a kód tartalmaz egy figyelemreméltó egyszerűsítést ami a folytatási felté­ teleket illeti. Nem ellenőriztük azt, hogy kiléptünk-e a labirintusból, mivel a hí­ vás előtt (a labirintus beolvasása után) az L tömböt körülvettük egy 0-ból álló kerettel. Így az algoritmus gyorsabbá válik. Az algoritmust a kiindulási hely ko­ ordinátáira (i, j) és 0 lépésszámra hívjuk meg. B ■ Második megoldás A következő változatban az Út alprogramban a lépés pillanatban megpróbálunk az (i, j) helyről az (úji, újj) helyre lépni. Ezeket két konstans tömb (x, y) segítsé­ gével állapítjuk meg úgy, hogy ezek a négy szomszédos hely koordinátáit adják meg. Ezeknek a tömböknek az értékei: x = (–1, 0, 1, 0), y = (0, 1, 0, –1). [17] Algoritmus Út(i,j,lépés): Minden irány=1,4 végezd el: úji  i + xirány újj  j + yirány

{ kiválasztunk egy irányt } { (úji, újj) az új koordináták }

Ha (úji  {1, 2, ..., n}) és (újj  {1, 2, ..., m}) akkor Ha (Lúji,újj]=1) és (eredmúji,újj=0) akkor eredmúji,újj  lépés { az (úji, újj) helyre lépünk } Ha (úji  {1,n}) vagy (újj  {1,m}) akkor Kiír { kiléptünk a labirintus szélén } vége(ha) Út(úji,újj,lépés+1) eredmúji,újj  0 { lemondunk az utolsó lépésről } vége(ha) vége(ha) vége(minden) Vége(algoritmus)

Az algoritmusban nem vettük körül a labirintust az előbbi változat megoldá­ sában említett kerettel. Ennek következtében szükség volt ellenőrizni, hogy az új hely, ahova lépni akarunk a labirintuson belül van-e.

184

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

Az előbbi algoritmust az Út(i,j,2) alakban hívjuk meg, de a hívás előtt eredmij  1, ahol (i, j) a kiindulási hely. Megjegyzés ■ Lehet, hogy ez a második változat tömörebb, de az első gyorsabb. Figyeljük meg a következő programrészletet: ... Ha (úji  {1,n}) vagy (újj  {1,m}) akkor Kiír { kiléptünk a labirintus szélén } vége(ha) Út(úji,újj,lépés+1) ...

Azt várnánk, hogy az algoritmus a Ha utasítás különben ágán hívja meg ön­ magát. Ha így járnánk el, elvesztenénk azokat az eredményeket, amelyeknek esetében a labirintus szélén tovább lehet menni, és a kilépés egy másik pontban is lehetséges. Általánosítva az előbbi feladatban használt rekurzív algoritmust, amely a visszalépéses keresés módosított változata, észrevesszük, hogy mivel az előreha­ ladás egy kétdimenziós tömbben történik, az alprogramnak mindig két paramé­ tere lesz (i, j), amelyek annak a helynek a koordinátái, ahova lépni szándék­ szunk. Mivel általában az utat is meghatározzuk, szükséges a lépés paraméter is. Algoritmus Backtracking(i,j,lépés): Ha lehetséges az i,j helyre lépni akkor eredmij  lépés Ha a részeredmény végeredmény akkor Ki: eredm különben hívjuk Backtracking-et és lépünk minden lehetséges irányba vége(ha) eredmij  0 vége(ha) Vége(algoritmus)

8.4.2. Fénykép Egy n × m méretű kétdimenziós tömbben egy egyszerű fényképet ábrázolunk. Ahol a fényképen valamilyen tárgy egy része látható, a megfelelő elem értéke 1, különben 0. Ugyanahhoz a tárgyhoz tartozó megfelelő elemek szomszédosak soronként, oszloponként, vagy átlónként. Írjuk ki, minden tárgynak megfelelő­ en, a kezdőkoordinátáját (ott ahol felfedezzük) és a méretét (az őt alkotó 1-esek számát). [17]

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

185

Elemzés ■ Ahhoz, hogy megállapítsuk a tárgy méretét (az 1-esek számát), be kell járnunk a fénykép minden elemét, egyszer. Mivel nem fontos megjegyezni, mikor jártunk be egy bizonyos elemet, nincs szükség a lépés paraméterre. Ugyanakkor, azokat az elemeket, amelyeket bejártunk, megjegyezzük, hogy ne lépjünk többet oda. Ha nem lett volna szabad „tönkretenni” a fényképet, a bejá­ rás után vissza kellett volna állítanunk a tömböt az eredeti alakjára, vagy egy se­ gédtömböt kellett volna használnunk. Az algoritmus egyszerűsítése érdekében, a hívás előtt a tárgyat kódoló töm­ böt (tg) körülvesszük egy 0-kból álló kerettel. Mivel a tárgyat alkotó 1-esek soronként, oszloponként és átlónként is szom­ szédosak, összesen 8 szomszédos helyre léphetünk. Algoritmus Tárgy(i,j): Ha tgij = 1 akkor { ha az (i, j) helyen levő pont a tárgyhoz tartozik } { megváltoztatjuk az értéket, hogy még egyszer ne léphessünk oda } tgij  2 méret  méret + 1 { nő a tárgy mérete } Tárgy(i-1,j-1) { átlósan balra felfele lépünk } Tárgy(i-1,j) { felfele lépünk } Tárgy(i-1,j+1) { átlósan jobbra felfele lépünk } Tárgy(i+1,j-1) { átlósan balra lefele lépünk } Tárgy(i+1,j) { lefele lépünk } Tárgy(i+1,j+1) { átlósan jobbra lefele lépünk } Tárgy(i,j-1) { balra lépünk } Tárgy(i,j+1) { jobbra lépünk } vége(ha) Vége(algoritmus)

A hívó programegységben a fényképet kódoló tömb beolvasása után elindu­ lunk, hogy megkeressük valamelyik 1-est tartalmazó elemét, majd erre a kezdő­ pozícióra meghívjuk az alprogramot. Mivel ezeket a megoldásokat grafikusan is lehet ábrázolni, fill típusú algorit­ musoknak is nevezzük. Ezek gyorsabbak, mint az útkereső algoritmusok, mivel egy helyet csak egyszer járnak be. Általános alakja, ha a Backtracking(i) algoritmust átírjuk: Algoritmus Backtracking(i,j): Ha az (i,j) elem bejárható akkor

bejárjuk az (i,j) elemet { kiírjuk, feldolgozzuk stb. } meghívjuk a Backtracking(i,j)-t minden szomszédos elemre vége(ha) Vége(algoritmus)

186

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

8.4.3. Legnagyobb méretű tárgyak Legyen az előbbi feladat, de a fényképen különböző tárgyak, különböző (termé­ szetes) számokkal vannak kódolva. Írjuk ki a legnagyobb méretű tárgyakat, va­ lamint a tárgyak kódját! Algoritmus Tárgy(i,j): Minden k=1,8 végezd el: új_i  i + xk új_j  j + yk Ha (uj_i  {1,...,n}) és (uj_j  {1,...,m}) és tgúj_i,új_j = kód akkor tgij  -kód { megváltoztatjuk az értéket, hogy ne lépjünk még egyszer oda } méret  méret + 1 Tárgy(új_i,új_j) vége(ha) vége(minden) Vége(algoritmus)

A hívó algoritmus ebben az esetben egy kissé bonyolultabb: Algoritmus Legnagyobb_tárgyak_a_fényképen: Be: n, m, tg { tg n soros, m oszlopos tömb } max_méret  0 { biztos találunk nagyobb méretet } Minden i=1,n végezd el: Minden j=1,m végezd el: Ha tgij > 0 akkor { kezdődik egy új tárgy } méret  0 kód  tgij { bejárjuk a tárgyat } Tárgy(i,j) Ha méret > max_méret akkor { találtunk nagyobb méretet } k  1 { ez az első max_méret-ű tárgy } max_méret  méret { aktualizáljuk a legnagyobb méretet } iik  i { megjegyezzük a tárgy egy pontjának helyét } jjk  j kód_tk  kód { megőrizzük a tárgy kódját } különben Ha méret = max_méret akkor { ha van még egy max-méret-ű tárgy } k  k + 1 { megőrizzük ennek a tárgynak is az „adatait” } iik  i jjk  j kód_tk  kód vége(ha) vége(ha) vége(ha) vége(minden)

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

187

vége(minden) Ki: 'Legnagyobb tárgyak mérete=', max_méret, ':' Minden i=1,k végezd el: Ki: 'i=', iii, ' j=', jji, ' kód=', kód_ti vége(minden) Vége(algoritmus)

Megjegyzés ■ A már bejárt elemeket (–kod)-dal jelöltük, mivel így visszaállít­ ható az eredeti tömb.

8.5. Kitűzött feladatok 1. Írjunk programot, mely kiírja az {1, 2, …, n} halmaz minden permutációját! 2. Egy díszvacsorán n személy vesz részt. Írjuk ki a személyek ülésrendjének minden lehetséges módját tudva, hogy egy asztalnál legfeljebb m személy ül­ het (m < n) és a helyiségben n asztal található. 3. Oldjuk meg a királynős feladatot három logikai tömb segítségével. Ezen tömbök értékei azt fejezik ki, hogy létezik-e királynő egy bizonyos oszlop­ ban, vagy egy olyan átlón, amely párhuzamos a főátlóval, illetve egy olya­ non, amely párhuzamos a mellékátlóval. 4. Legyen M egy természetes számokból álló halmaz és S egy természetes szám. Írjuk ki az M halmaz minden részhalmazát, amelyeknek az a tulajdon­ ságuk, hogy elemeik összege S! 5. Adva van egy n egész számot tartalmazó sorozat. Generáljunk minden olyan részsorozatot, (felhasználva az adott számok közül néhányat, esetleg min ­ dent), amelyekben minden pozitív számot egy negatív követ, és fordítva). A generált részsorozatokban az elemek relatív helye egyezzen meg az eredeti sorozatban elfoglalt hellyel. Amennyiben lehetetlen megoldást találni, írjunk ki megfelelő üzenetet! 6. Adva van egy n egész számot tartalmazó sorozat (2  n < 10). Helyezzünk az adott számok közé n – 1 aritmetikai műveleti jelt (+, –, *, /), úgy, hogy a ki­ fejezés értéke legyen egyenlő egy adott K számmal! Amennyiben lehetetlen megoldást találni, írjunk ki megfelelő üzenetet! A kifejezés értéke minden lépésben legyen egész szám! A műveleteket nem a matematikából ismert operátorprecedencia szerint végezzük, hanem megjelenésük sorrendjében. Példa ■ n = 4, K = 24, a számok: 12, 12, 1, 1 Eredmény 24 = 12 + 12 + 1 – 1

188

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

24 = 12 + 12 – 1 + 1 24 = 12 + 12 * 1 * 1 24 = 12 + 12 * 1 / 1 24 = 12 + 12 / 1 * 1 24 = 12 + 12 / 1 / 1 7. Legyen az Ann tömb, amelynek elemei természetes számok. Írjuk ki azt a legkisebb, n tagot tartalmazó összeget, amelyet különböző sorokból és kü­ lönböző oszlopokból szedhetünk össze. 8. Legyen n építőkocka, amelyek 1-től n-ig vannak címkézve. Beolvassuk az oldalaik hosszát és a színüket. Írjunk ki minden k kockából álló tornyot, amelyeket úgy építhetünk fel, hogy nem teszünk egymásra azonos színű koc­ kát és oldalaik mérete (lentről felfele) csökkenő sorozatot alkotnak. 9. Legyen n építőkocka, amelyek 1-től n-ig vannak címkézve. Ismerjük az ol­ dalak hosszát és a kockák színét. Írjuk ki azt a legmagasabb tornyot, amelyet az adott kockákból úgy építhetünk fel, hogy nem teszünk egymásra azonos színű kockát és oldalaik mérete (lentről felfele) csökkenő sorozatot alkotnak. A torony magasságát az oldalak hosszának összege adja. 10. Adva van egy léc, amelynek ismerjük a hosszát és n darab pozitív egész szám, amelyek a lécből levágható lécdarabok lehetséges hosszúságainak fe­ lelnek meg. Írjuk ki minden lehetséges módját a léc feldarabolásának, tudva azt, hogy a darabolás után nem szabad eldobni semmit és egy hosszúság csak egyszer fordulhat elő! Ha nincs egyetlen megoldás sem, írjunk ki megfelelő üzenetet! Példa ■ Léc hossza =10, darab hosszúságok: 1, 2, 5, 8, 9 Eredmények 10 = 1 + 9 10 = 2 + 8 10 = 8 + 2 10 = 9 + 1 11. Adva van egy n személyből álló csoport. Minden személynek van legalább n/2 barátja a csoporton belül. Lacinak van egy könyve, amelyet a csoport minden tagja el szeretne olvasni. Írjátok ki, hogyan vándorol a könyv egyik személytől a másikig úgy, hogy mindenkihez csak egyszer kerül, és egy sze­ mély csak egy barátjának kölcsönzi a könyvet, amíg az visszatér Lacihoz. Bemeneti adatként annyi barát-párt olvasunk be, amennyi biztosan megfelel a feltételnek. Példa ■ n = 3

Eredmény

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

Laci Bandi Laci Kati Bandi Kati

189

Laci Bandi Kati Laci Laci Kati Bandi Laci

12. Egy fehér-fekete fényképet az Ann tömbbel kódoltunk; elemei a {0, 1} hal­ mazhoz tartoznak. Az 1-es azt jelképezi, hogy a megfelelő pont a fényképen levő tárgy képéhez tartozik. Két elem: Aij és Akl szomszédosak vízszintesen, függőlegesen, vagy átlósan (összesen 8 lehetséges irányban). Állapítsuk meg, hogy egy, illetve több darabból áll-e a fényképen levő tárgy, és írjunk ki megfelelő üzenetet! 13. Egy síző felfedezett a sípálya közelében egy téglalap alakú hepehupás terüle­ tet, ahol egyedül szeretne sízni. Ismerve a hely topográfiáját (egy négyzethá­ lós térképnek megfelelően) és a síző helyét, állapítsunk meg minden leeresz­ kedési lehetőséget a terület széléig. Egy adott pillanatban a síző csak olyan szomszédos négyzeten haladhat át, amelynek szintmagassága szigorúan ki­ sebb, mint az amelyen aktuálisan található. 14. Egy részlegesen befagyott tó felszínét egy kétdimenziós tömbbel ábrázoljuk. A jeges résznek megfelelő elemek értéke 1, a többi 0. A tó bal felső sarkából elindul egy béka a jobb alsó sarokban található sáska felé. Amikor elért hozzá (és megette), visszatér az őrhelyére. A béka csak jégre ugorhat és ahányszor jégre ugrik, a jég beszakad alatta. Írjuk ki a legrövidebb utat, ame­ lyet a békának meg kell tennie, ha tudjuk, hogy pontosan úgy ugrál mint a ló a sakktáblán. 15. Legyen egy n × n méretű sakktábla, amelynek bal-felső sarkába egy lovat helyeztünk. Írjuk ki minden lehetséges módját annak, ahogy ez a sakkfigura bejárhatja a teljes sakktáblát úgy, hogy minden négyzetre lépjen, és minde­ gyikre csak egyszer. Egy bizonyos helyről 8 lehetőség van továbblépni. j-edik oszlop 8

1

7

2

i-edik sor  

6

3 5

4

16. Egy n  n méretű sakktáblán az (x, y) helyen található egy futár és az (x0, y0) helyen egy ló. A lónak el kell jutnia erről a helyről az (x1, y1) helyre, anélkül,

190

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

hogy egy bizonyos négyzetre többször lépne illetve, hogy olyan helyen állna meg, ahonnan a futár kiütheti. Írjuk ki a ló útvonalát! 17. Egy n  m méretű sakktábla bal felső sarkába elhelyeztünk egy dobókockát, úgy mint ahogy az ábrán látható. A táblán, különböző helyeken akadályok vannak elhelyezve. Írjuk ki minden lehetséges módját annak, ahogyan a koc­ kát a sakktábla jobb alsó sarkába guríthatjuk. Gurítás közben mindig másmás oldalai látszanak. 6 4

2

Útmutatás ■ A kockát a kiinduló helyzetben (1, 1) három paraméterrel jelle­ mezzük: teteje, eleje, oldala, tehát Gurít(1,1,6,4,2)-re hívjuk meg. Tudjuk még, hogy a dobókockának átellenes oldallapjain (7 – teteje), (7 – eleje), illetve (7 – oldala) szám található. A gurításnak megfelelően változnak a lát­ ható oldallapok. A kocka útvonalát a sakktáblán bejárt útvonalán keresztül ír­ juk ki, ahol az érintett helyeken a kocka tetején látható értéket jelenítjük meg. Példa Ha a sakktáblát és az akadályokat a következő tömb kódolja: 0011000 0011110 0000010 0111000

Egy lehetséges eredmény: 6000000 3000000 1562100 0000453

18. Egy utazóügynök házról házra jár az áruival. A cégnek n féle áruja van; min­ den áru súlyát és árát ismerjük. Tudva azt, hogy az ügynök hátizsákjába leg­ több S súlyú árut lehet becsomagolni, írjuk ki azokat az árukat, amelyeket az ügynök kiválaszt az n áru közül tudva, hogy minden árutípusból csak egyet fog becsomagolni, és az áruk értékének legkevesebb Összeg-gel kell egyen­ lőnek lennie. 19. Adva van n dominó (egy dominón két „számjegy” van feltüntetve a {0, 1, ..., 6} halmazból úgy, hogy a nullától különböző számjegyeknek megfelelően pontok vannak a dominóra festve). Írjunk ki valamenynyi, n dominóból álló láncot, amelyben bármely, két egymás után következő dominó a szomszédos felén ugyanazt a számjegyet tartalmazza. A dominók bármelyik félfelükkel kapcsolódhatnak a láncba. Példa ■ n = 5 Eredmények 12 1226611443

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

34 14 16 62 Példa ■ n = 2 00 12

191

3441166221 3441122661 1662211443 Eredmény nincs megoldás

20. Egy n  m méretű kétdimenziós tömb több tárgy bináris kódját tartalmazza. A tárgynak megfelelően a kód 1, a határvonalának megfelelően 0. Állapítsuk meg és írjuk ki minden tárgy esetében egy tetszőleges 1 értékű elemének ko­ ordinátáit, valamint a kerületét (azon 0 értékű elemek számát, amelyek a tárgy határvonalán vannak, vagyis vízszintesen vagy függőlegesen, a legkö­ zelebb a tárgyhoz). Példa ■ n = 4, m = 8 00000000 01110110 00100000 00000000

Eredmény 1-es tárgy: Koordináta: (2, 2), Kerület = 8 2-es tárgy: Koordináta: (2, 6), Kerület = 6

21. Adva van egy kétdimenziós tömb, amelynek elemei különböző természetes számok. Találjuk meg azt az utat, amely érinti a tömbnek megfelelő négyzet­ háló minden négyzetét egyszer és amelyet ahhoz kell bejárni, hogy a követ­ kező összeg maximális legyen: nm

k  a k 1

ij

Az összegbe kerülő aij elem a k-adik a tömb bejárása során. 22. Egy programozói versenyen több első, második és harmadik díjat osztanak ki. Ezeknek a száma x, y, z. A szponzoroktól kapott értéktárgyak mind kü­ lönböző értékűek. A bizottság az x + y + z nyertesnek m darab értéki (i = 1, 2, ..., m) értékű tárgyat fog kiosztani, úgy hogy a díjak értéke csökkenő soroza­ tot alkot. Egy versenyző több tárgyat is kaphat. Az első díjak értéke legyen azonos, ugyanúgy a második díjak értéke, valamint a harmadik díjaké. Írjuk ki minden lehetséges módját annak, ahogy az m tárgy szétosztható a fenti kö­ vetelményeknek megfelelően. Minden tárgyat át kell adni. Ha nem lehet tisz­ teletben tartani a díjkiosztás szabályait, írjunk megfelelő üzenetet. Példa ■ m = 7, érték = (5, 8, 3, 7, 12, 1, 9), Lesz két első díj, két második díj és egy harmadik díj. Eredmény

192

8. A VISSZALÉPÉSES KERESÉS MÓDSZERE (BACKTRACKING)

1 díjak: 12, 9+3 2 díjak: 8, 7+1 3 díj: 5

9

AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

9.1. Bevezetés Az oszd meg és uralkodj (divide et impera) módszer alkalmazása akkor ajánlott, amikor a feladatot fel lehet bontani egymástól független részfeladatokra, ame­ lyeket az eredeti feladathoz hasonlóan oldunk meg, de kisebb méretű adathal­ maz esetében.  Az eredeti feladatot felbontjuk egymástól független részfeladatokra, ame­ lyek az eredetihez hasonlóak, de kisebb adathalmazra definiáltak. A rész­ feladatokkal hasonlóan járunk el és a felbontást akkor állítjuk le, amikor a feladat megoldása a lehető legjobban leegyszerűsödött.  Megoldjuk a maximálisan leegyszerűsített feladatot.  A részfeladatok eredményeiből fokozatosan felépítjük a következő méretű feladat eredményeit ezek összerakása által. Az utolsó összerakás az erede­ ti feladat végeredményét adja meg. Mivel a részfeladatok csak méreteikben különböznek az eredeti feladattól, a Divide et Impera módszert a legkézenfekvőbben rekurzívan írjuk le. A felbontás megtörténik a rekurzióba való belépéskor, a részeredmények összerakása pedig a kilépéskor.

9.2. Az oszd meg és uralkodj módszer általános bemutatása A DivImp(bal,jobb,eredm) algoritmus az a1, a2, ..., an sorozatot dolgozza fel, tehát DivImp(1,n,eredm) alakban hívjuk meg először. Formális paraméterei bal, jobb (az aktuális részsorozat bal és jobb indexe), valamint eredm, amelyben a végeredményt továbbítjuk.

194

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

Algoritmus DivImp(bal,jobb,eredm): Ha jobb - bal < ε akkor { ha a feladat maximálisan egyszerű } { kiszámítjuk az egyszerű feladat eredm eredményét } Megold(bal,jobb,eredm) különben { kiszámítjuk a közép indexet, ahol felosztjuk a sorozatot } Feloszt(bal,jobb,közép) { megoldjuk a feladatot a bal részsorozat esetében } DivImp(bal,közép,eredm1) { megoldjuk a feladatot a jobb részsorozat esetében } DivImp(közép+1,jobb,eredm2) Összerak(eredm1,eredm2,eredm) { összerakjuk a részeredményeket } vége(ha) Vége(algoritmus)

Az oszd meg és uralkodj stratégiát néha lehet iteratívan is implementálni (például, bináris keresés). Megjegyezzük, hogy ezek az iteratív algoritmusok mindig gyorsabbak lesznek. A rekurzív változat előnye viszont az átláthatóságában és az egyszerűségében rejlik.

9.3. Megoldott feladatok 9.3.1. Szorzat Számítsuk ki n valós szám szorzatát oszd meg és uralkodj módszerrel! Egy adott pillanatban csak egy szorzást végezzünk! Megoldás ■ Mivel egy adott pillanatban, egy adott művelettel, csak két szám szorzatát tudjuk kiszámítani, a szorzatot részszorzatokra bontjuk. Ezt úgy való­ sítjuk meg, hogy a szorzótényezőket két csoportra osztjuk, kiszámítjuk egy-egy csoport szorzatát, majd a két csoport kiszámított szorzatát összeszorozzuk. Ezt a felbontást addig lehet újra, meg újra elvégezni, amíg egy csoport legtöbb két szorzótényezőből nem áll. [17] Példa ■ n = 5 Szorzat(x1,...,x5)

Szorzat(x1,x2,x3) Szorzat(x1,x2)

Szorzat(x3)

Szorzat(x4,x5)

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

195

p5 = p3  p4

p3 = p 1  p2 p 1 = x1  x2

p 4 = x4  x5

p 2 = x3

A Szorzat(x1, ..., xn) részfeladat általános alakja: Szorzat(xbal, ..., xjobb). Minden részfeladat más-más szorzatot számol ki, tehát a feladatok függetlenek egymás­ tól. Mivel előnyösebb, ha egy szorzatot egy függvénnyel számolunk ki és nem egy eljárással, a DivImp(bal,jobb,eredm) algoritmust a következőképp írjuk át: Algoritmus Szorzat(bal,jobb): { függvény típusú algoritmus } Ha jobb = bal akkor { bemeneti adatok: bal, jobb; kimeneti adat: Szorzat } Szorzat  xbal { a részsorozat egy elemből áll } különben Ha jobb - bal = 1 akkor Szorzat  xbal * xjobb { a részsorozat két elemű } különben { felbontjuk a Szorzat(bal, ..., jobb) feladatot } közepe  [(bal+jobb)/2] p1  Szorzat(bal,közepe) p2  Szorzat(közepe+1,jobb) Szorzat  p1 * p2 { összerakjuk a részeredményeket } vége(ha) vége(ha) Vége(algoritmus)

9.3.2. Minimumszámolás Állapítsuk meg n egész szám közül a legkisebbet! Algoritmus Minimum(bal,jobb): { függvény típusú algoritmus } { bemeneti adatok: bal, jobb; kimeneti adat: Minimum } Ha jobb = bal akkor { a részsorozat egy elemből áll } Minimum  xbal különben Ha jobb - bal = 1 akkor { a részsorozat két elemből áll } Ha xbal < xjobb akkor Minimum  xbal különben Minimum  xjobb vége(ha) különben { felbontjuk a Minimum(bal, jobb) feladatot részfeladatokra: } közepe  [(bal+jobb)/2]

196

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

min1  Minimum(bal,közepe) min2  Minimum(közepe+1,jobb) Ha min1 < min2 akkor Minimum  min1 különben Minimum  min2 vége(ha) vége(ha) vége(ha) Vége(algoritmus)

{ összerakjuk a részeredményeket }

9.3.3. Hatványozás Határozzuk meg egy a (a  0) valós szám k-adik hatványát (k természetes szám)! [25] Megoldás ■ Ha a legkézenfekvőbb módon, ciklussal oldjuk meg a feladatot, ak­ kor k – 1 szorzásra lenne szükségünk. De a „Gyors hatványra emelés” feladatot (3. fejezet) már az oszd meg és uralkodj módszer iteratív változatával oldottuk meg a következő összefüggés alapján. ha k  0 1



a k  a k / 2  * a k / 2 

a k / 2  * a k / 2  * a 

ha k páros ha k páratlan

Példák

a) az a15-t hat szorzással megkaphatjuk, ha a következő módon járunk el: a3 = a · a · a a7 = a3 · a3 · a a15 = a7 · a7 · a

b) az a24-t öt szorzással megkaphatjuk: a3 = a · a · a a6 = a3 · a3 a12 = a6 · a6 a24 = a12 · a12 Algoritmus Gyors_hatvány(a,k): Ha k = 1 akkor { bemeneti adatok: a, k; kimeneti adat: Gyors_hatvány } Gyors_hatvány  a { a1 = a } különben x  Gyors_hatvány(a,[k/2]) Ha k páratlan akkor Gyors_hatvány  x * x * a

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

197

különben Gyors_hatvány  x * x vége(ha) vége(ha) Vége(algoritmus)

Megjegyzések  Ha a hatványkitevő nulla, akkor előre tudjuk az eredményt, és nem hívjuk meg az algoritmust. Így kiküszöbölünk egy olyan esetet, amelyre az algo­ ritmus nem működne (a program végtelenül hívná önmagát míg hibaüze­ nettel megszakadna).  Negatív hatványkitevő esetén visszavezethetjük a megoldást a pozitív eset­ re a Gyors_hatvány(1/a,–k) meghívással.

9.3.4. Bináris keresés Adva van egy n egész számból álló, növekvően rendezett sorozat. Állapítsuk meg egy adott szám helyét a sorozatban! Ha az illető szám nem található meg, a sorszámnak megfelelő paraméter értéke 0 lesz. Megoldás ■ Legyen a rendezett sorozat x1 < x2 < ... < xn. Mivel egy bizonyos elemet keresünk, amelynek a helye ismeretlen, a sorozat közepén fogjuk először keresni. A következő esetek fordulhatnak elő:

a) keresett = xközép  keresett a sorban a közép helyen található; b) keresett < xközép  mivel a sorozat rendezett, a keresett számot a sorozat első (x1, ..., xközép–1) felében keressük tovább;

c) keresett > xközép  a keresett számot a sorozat második (xközép+1, ..., xn) felében keressük tovább. Következésképpen, ahelyett, hogy a keresett elem megkeresése két részfela­ datra bomlana, átalakul egyetlen feladattá: keressük az elemet vagy az xbal, ..., xközép–1 sorozatban, vagy az xközép+1, ..., xjobb sorozatban. Itt nincs szükség a divide et impera harmadik lépésére (a részeredmények összerakására). Algoritmus Bin_keres(x,bal,jobb,keresett,közép): { bemeneti adatok: x, bal, jobb, keresett; kimeneti adat: közép } Ha bal > jobb akkor közép  0 { keresett nincs a sorozatban } különben közép  [(bal+jobb)/2] Ha keresett < xközép akkor Bin_keres(x,bal,közép-1,keresett,közép)

198

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

különben Ha keresett > xközép akkor Bin_keres(x,közép+1,jobb,keresett,közép) vége(ha) vége(ha) vége(ha) { ha keresett = xközép megvan a pozíció } Vége(algoritmus)

E feladat esetében is létezik egy iteratív megoldás, amely a végrehajtás idejét tekintve hatékonyabb. Algoritmus Bin_Keres_Iteratív(n,x,keresett,közép): bal  1 jobb  n megvan  hamis Amíg nem megvan és (bal ≤ jobb) végezd el: közép  [(bal+jobb)/2] Ha xközép = keresett akkor megvan  igaz { közép tartalmazza a keresett helyét } különben Ha xközép > keresett akkor jobb  közép - 1 különben bal  közép + 1 vége(ha) vége(ha) vége(amíg) Ha nem megvan akkor közép  0 { ha közép értéke 0  keresett nem található } vége(ha) Vége(algoritmus)

A bináris keresésnek sok érdekes alkalmazása van. Például, eredményesen alkalmazható algoritmusok optimalizálása érdekében. Bizonyos feladattípusok esetében, a megoldásként javasolt lineáris algoritmus egy logaritmikussal he­ lyettesíthető, ha felhasználjuk a bináris keresés elvét. 1. alkalmazás ■ Adott összegű elemek kiválasztása Legyen egy n elemű (3  n  100 000) különböző természetes számokat tartal­ mazó sorozat és az S természetes szám. Válasszunk ki az adott sorozatból há­ rom elemet, amelyeknek az összege S. [24] Elemzés ■ Részletösszegeket kell számítanunk, de pontosan három elem össze­ gét hasonlítjuk S-sel. A feladat megoldható három egymásba ágyazott Minden ciklussal. Sajnos, az n értéke miatt, ez a megoldás nem biztos, hogy elfogadható időn belül eredményt adna.

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

199

Ha a megoldásba beépítjük a bináris keresést, a bonyolultság (O(n3)-nál ki­ sebb) O(n2 · log n) lesz:  Rendezzük az adott sorozatot.  Két Amíg ciklussal kiválasztunk a sorozatból két elemet (legyen ezeknek az indexe n1 és n2).  Megkeressük az S – an1 – an2 értéket a bináris keresést alkalmazva.  A megoldást tovább javítjuk (például, ha an1 értéke meghaladja S-t, kilé­ pünk az első Amíg-ból stb.). Algoritmus Generál(a,n,S): i  1 Amíg (i < n) és (ai < S) végezd el: j  i + 1 Amíg (j  n) és (ai + aj < S) végezd el: BinKeres(a,j+1,n,s-ai-aj,k) Ha k  0 akkor Ki: ai, aj, ak vége(ha) j  j + 1 vége(amíg) i  i + 1 vége(amíg) Vége(algoritmus)

2. alkalmazás ■ Teljes négyzetek száma Számoljuk meg egy n (1  n  1 000 000) elemű sorozat négyzetszámait! A számok nem nagyobbak 1 000 000-nál. Megoldás ■ Részfeladat: ellenőriznünk kell, hogy egy szám teljes négyzet-e? Tudjuk már, hogy a következő algoritmus nem lesz kielégítő hatékonyságú. Algoritmus Számol_1(n,a,p): p  0 Minden i=1,n végezd el: Ha

ai = [ ai ] akkor

p  p + 1 vége(ha) vége(minden) Vége(algoritmus)

Keresünk egy hatékonyabb módot annak ellenőrzésére, hogy egy szám teljes négyzet-e! De még tehetünk valamit. Mivel ai-t megkeresni a memóriában (az i index, a tömb első elemének címe és az a tömb típusának megfelelő elemhossz alapján) időigényesebb, mint egy egyszerű változóban tárolt értéket, az Amíg

200

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

feltételében nem ai-t használjuk, hanem kiemeljük az Amíg-ba lépés előtt a szám változóba: Algoritmus Számol_2(n,a,p): p  0 Minden i=1,n végezd el: k  0 szám  ai Amíg k * k < szám végezd el: k  k + 1 vége(amíg) Ha k * k = szám akkor p  p + 1 vége(ha) vége(minden) Vége(algoritmus)

{ p a négyzetszámokat számlálja } { a k négyzetét hasonlítjuk ai-vel } { az ai-t „megkeresni” nehezebb }

Ha implementáljuk az első, valamint a második algoritmust, és megmérjük a végrehajtási időt, látni fogjuk, hogy az utóbbi 16-szor kevesebb időt igényel, mint az első! De még nem alkalmaztuk a bináris keresés elvét! Vegyük észre, hogy nem érdemes minden k (k · k < szám) értékre elvégezni a vizsgálatot, hi­ szen tulajdonképpen egy rendezett halmazban keresünk! Algoritmus Számol_3(n,a,p): p  0 Minden i=1,n végezd el: eleje  0 vége  1000 szám  ai Amíg eleje < vége végezd el: k  [(eleje+vége)/2] Ha k * k  szám akkor vége  k különben eleje  k + 1 vége(ha) vége(amíg) Ha eleje * eleje = szám akkor p  p + 1 vége(ha) vége(minden) Vége(algoritmus)

{ a legnagyobb érték 1 000 000 }

Ez az utolsó változat, amelyben alkalmaztuk a bináris keresés elvét, fele annyi időt igényel, mint az előző!

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

201

3. alkalmazás ■ Síkmértan Két függőleges fal egymástól t távolságra található. Egy h1 hosszúságú deszkát az egyik fal alapjától a másik falnak támasztunk. Egy h2 hosszúságú deszkát a másik fal alapjától az első falnak támasztunk. A két deszka m magasságban érinti egymást egy pontban, amely valahol a két fal között található. Számítsuk ki t-t h1, h2 és m ismeretében (megengedett hibalehetőség 10–5).

h2

m

h1

t Megoldás ■ Átfogalmazzuk a követelményt: keressük meg azt a legnagyobb t ér­ téket, amelyre a magasság, ahol a két deszka találkozik ne legyen kisebb mint m. Felhasználjuk a bináris keresést a t értékének a „kitalálására”. A kiindulási érték: min(h1, h2)/2. Ha ismerjük a t, h1 és h2 értékeket, az érintkezési pont magasságát kiszámít­ juk síkmértan ismeretekkel. A Számol(h1,h2,t,sz) kiszámítja sz-ben a magaság értékét t aktuális közelítő értékére. Algoritmus Számol(h1,h2,t,sz): x 

h1 * h1 - t * t

y 

h2 * h2 - t * t

sz  (x*y)/(x+y) Vége(algoritmus)

{ bemeneti adatok: h1, h2, t; kimeneti adat: sz } { sz-ben őrizzük a t közelítő értékének } { megfelelő magasságot }

Ha a kiszámított magasság nagyobb mint az adott m, akkor növeljük t-t, kü­ lönben csökkentjük. A hívó algoritmusban kiszámítjuk a t távolság lehetséges legnagyobb érté­ két, amely egyben kezdőérték is (a rövidebb deszka hossza) és a két segédválto­ zó (min és max) kezdőértékét, amelyek: min = 0 és max = t. Algoritmus Deszkák(m,h1,h2,t): megvan  hamis { bemeneti adatok: m, h1, h2; kimeneti adat: t } Amíg nem megvan végezd el: t  (min+max)/2 Számol(h1,h2,t,sz)

202

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

Ha | sz - m | ≤ 0.0001 akkor megvan  igaz különben Ha sz > m akkor min  t különben max  t vége(ha) vége(ha) vége(amíg) Vége(algoritmus)

9.3.5. Összefésülésen alapuló rendezés (MergeSort) Ha két növekvően rendezett sorozatból úgy állítunk elő egy harmadikat, hogy ez utóbbi úgyszintén növekvő sorrendben rendezett, összefésülésről beszélünk. Rendezzünk növekvő sorrendbe egy egész számokból álló sorozatot összefésü­ léssel! (Vigyázat! Itt nem két rendezett sorozatból kell egy harmadik ugyancsak rendezettet előállítanunk, hanem egyetlen sorozatot kell rendeznünk.) Megoldás ■ Az adott sorozatot két részre osztjuk, abból a célból, hogy rendez­ hessük. De ezeket újból felosztjuk addig amíg a kapott tömb, amelyet rendezni kell, csak egy elemből áll; azok a tömbök, amelyek egy elemből állnak, termé­ szetesen rendezettek és megkezdődhet a tulajdonképpeni összefésülés. [6] Példa ■ Legyen n = 5, és a sorozat (7, 9, 1, 5, 3): 1, 3, 5, 7, 9 1, 7, 9 1

7, 9 7

3, 5 5

3

9

Algoritmus Összefésül(bal,közép,jobb): Minden i=bal,közép végezd el: ai  xi vége(minden) Minden i=közép+1,jobb végezd el: bi  xi vége(hminden) aközép+1  végtelen bjobb+1  végtelen

{ strázsák }

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

203

i  bal j  közép + 1 Minden k=bal,jobb végezd el: Ha ai < bj akkor xk  ai i  i + 1 különben xk  bj j  j + 1 vége(ha) vége(minden) Vége(algoritmus) Algoritmus Rendez(bal,jobb): Ha bal < jobb akkor közép  [(bal+jobb)/2] Rendez(bal,közép) Rendez(közép+1,jobb) Összefésül(bal,közép,jobb) vége(ha) Vége(algoritmus)

Az Összefésül(bal,közép,jobb) algoritmus eredménye az xbal, …, xjobb ren­ dezett sorozat, amelybe tulajdonképpen ugyanazon sorozat két részsorozatát, az xbal, …, xközép és az xközép+1, …, xjobb részsorozatokat fésültük össze. Ezzel magya­ rázható annak a szükségessége, hogy az összefésülendő sorozatokat átmásoltuk az a illetve a b sorozatokba. A hívó programegységben a Rendez(1,n) algorit­ must hívjuk.

9.3.6. Gyorsrendezés (QuickSort) A gyorsrendezés az oszd meg és uralkodj módszeren alapszik, mivel az eredeti sorozatot úgy rendezi, hogy két rendezendő részsorozatra bontja. Fölhasználva a quiksort algoritmust, rendezzünk növekvő sorrendbe n egész számot. Megoldás ■ A részsorozatok rendezése egymástól függetlenül történik. Meglát­ juk, hogy a részeredmények összerakása ebből az algoritmusból is hiányzik (mint a bináris keresésből). [6] Amikor az x1, , xn sorozatot készülünk rendezni, előbb előkészítünk két részsorozatot (x1, , xm–1 és xm+1, , xn) úgy, hogy az x1, , xm–1 részsorozat elemei kisebbek legyenek, mint az xm+1, , xn részsorozat elemei. Közöttük ta­ lálható az xm, amely nagyobb mint az x1, , xm–1 részsorozat bármely eleme, és kisebb mint az xm+1, , xn részsorozat összes eleme.

204

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

Azt az elemet, amely meghatározza a helyet, ahol az adott tömb két részre oszlik, strázsának (őrszem) nevezzük. Ennek a helynek a meghatározása kulcs­ kérdés az algoritmus végrehajtása során. A strázsa m helyét úgy határozzuk meg, hogy az x1, , xm tömbben legyenek azok az elemek, amelyek kisebbek mint a strázsa és az xm+1, , xn tömbben azok, amelyek nagyobbak annál. Gyakran választjuk strázsának az x1-et. Elindulunk a tömb két szélső elemé­ től és felcseréljük egymás közt azokat az elemeket, amelyek nagyobbak mint a strázsa (és a tömb első részében találhatók) azokkal, amelyek kisebbek mint a strázsa (és a tömb második részében találhatók). Ahol ez a bejárás véget ér, ott fogjuk két részre osztani a tömböt. Egy ilyen feldolgozás során egy elem a vég­ leges helyére kerül. A részsorozatok rendezése érdekében ezeket hasonló módon bontjuk fel. A felbontás addig folytatódik, amíg a rendezendő részsorozat hossza 1 lesz. Példa ■ Legyen a növekvően rendezendő sorozat: 8, 7, 6, 10, 4, 11, 2, 5, 9. Az első elemet összehasonlítjuk rendre az összes többi elemmel, jobbról bal­ ra haladva. 8, 7, 6, 10, 4, 11, 2, 5, 9   Mivel ezek megfelelő sorrendben vannak (8 kisebb mint 9), haladunk jobb­ ról balra. 8, 7, 6, 10, 4, 11, 2, 5, 9   Mivel most a sorrend nem megfelelő, ezt a két elemet felcseréljük és a kö­ vetkező konfigurációhoz jutunk: 5, 7, 6, 10, 4, 11, 2, 8, 9 Megtörtént az első felcserélés. 8 most az utolsó előtti helyen található, és mi­ vel „megfordul” az összehasonlítások iránya, most a 8-at rendre összehasonlít­ juk a sorozat első felében található elemekkel, balról jobbra haladva. 5, 7, 6, 10, 4, 11, 2, 8, 9   A 7-tel kezdjük ezt a folyamatot, mivel az előtte levő elemről tudjuk, hogy kisebb mint 8, hiszen az előző lépésben az 5-öt épp azért cseréltük fel a 8-cal, hogy megfelelő helyre kerüljön. Mivel a 7 a 8 előtt található a sorozatban, és 7 < 8 a két szám marad a helyén. Most a 6-ot hasonlítjuk a 8-cal, de az előbbi lépéshez hasonlóan, a két szám marad a helyén.

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

205

5, 7, 6, 10, 4, 11, 2, 8, 9   Következik a 10 összehasonlítása 8-cal. Ezeket felcseréljük, mivel 10 na­ gyobb mint 8, és a sorozatban 10 a 8 előtt található. 5, 7, 6, 10, 4, 11, 2, 8, 9 Felcserélés után: 5, 7, 6, 8, 4, 11, 2, 10, 9   Volt egy felcserélés. Újból a 8-at hasonlítjuk az utolsó csere helyétől balra eső elemmel. Ez a 2. Mivel 8 > 2, újabb felcserélés következik. 5, 7, 6, 8, 4, 11, 2, 10, 9 Felcserélés után: 5, 7, 6, 2, 4, 11, 8, 10, 9   A következő lépésben a 8-at a 4-gyel hasonlítjuk össze. A sorrend megfelelő, maradnak a helyükön. 5, 7, 6, 2, 4, 11, 8, 10, 9   Az utolsó összehasonlítást a 8 és 11 között végezzük. Mivel fel kell őket cse­ rélnünk, a sorozat konfigurációja a következő lépésben: 5, 7, 6, 2, 4, 8, 11, 10, 9 Vegyük észre, hogy a fent leírt műveletek eredményeképpen a 8, amely az eredeti sorozatban az első helyet foglalta el, végleges helyére került, ugyanakkor a 8-tól balra eső részsorozatban csak 8-nál kisebb számok találhatók, és a tőle jobbra eső részsorozatban pedig csak 8-nál nagyobb számaink vannak. (5, 7, 6, 2, 4), 8, (11, 10, 9) Ezt a két részsorozatot feldolgozzuk az előbbi módon. A bal részsorozat a következő átalakulásokon megy át: (5, 7, 6, 2, 4)  (4, 7, 6, 2, 5)  (4, 5, 6, 2, 7)  (4, 2, 6, 5, 7)  (4, 2), 5, (6, 7) Most a (4, 2) részsorozat átalakul (2, 4)-gyé, a (6, 7) marad változatlanul. Az eredeti sorozat jobb részsorozata a következő lépések során rendeződik: (11, 10, 9)  (9, 10, 11)  (9, 10, 11)  (9, 10), 11 Így az eredeti sorozat tartalma most: 2, 4, 5, 6, 7, 8, 9, 10, 11. [2] Algoritmus QuickSort(bal,jobb): Ha bal < jobb akkor { meghatározzuk azt az m helyet, ahol a sorozatot } { két részre bontjuk, miközben egy elem (xm) a végleges helyére kerül } m  Strázsa_helye(bal,jobb) QuickSort(bal,m) { hasonlóan járunk el az (xbal, ..., xm) részsorozattal } QuickSort(m+1,jobb) { valamint az (xm+1, ..., xjobb) }

206

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

vége(ha) Vége(algoritmus)

Látható, hogy a rekurzív hívásoknak megfelelően, az algoritmus meghívja önmagát egy bal meg egy jobb részsorozat rendezése érdekében. De hol a ren ­ dezés, hiszen ez az algoritmus nem tartalmaz összehasonlításokat és felcserélé­ seket? Ezeket aközben végezzük, miközben keressük a strázsa m helyét: Algoritmus Strázsa_helye(bal,jobb): strázsa  xbal { bemeneti adatok: bal, jobb; kimeneti adat: Strázsa_helye } i  bal-1 j  jobb+1 { megkeressük azt a j-t, amelyre bal  j < jobb } Ismételd Ismételd { megkeressük azt a j-t (jobbról balra), amelyre xj < strázsa } j  j - 1 ameddig xj  strázsa Ismételd { megkeressük azt az i-t (balról jobbra), amelyre xi > strázsa } i  i + 1 ameddig xi  strázsa Ha i < j akkor xi  xj { felcseréljük ezt a két nem megfelelő tulajdonságú elemet } vége(ha) { addig folytatjuk a keresést és felcserélést, amíg i kisebb mint j } ameddig i  j Strázsa_helye  j { megtaláltuk az új strázsa helyét } Vége(algoritmus)

Megjegyzés ■ Ez az algoritmus főleg abban az esetben gyors, amikor a tömb elemei nem rendezettek! A Feloszt(bal,jobb,m) algoritmust megtervezhetjük másképp is. Arra készü­ lünk, hogy minden lépésben tartsuk nyilván annak a két elemnek az indexét, amelyeket össze kell hasonlítanunk. Minden lépésben megváltozik a két index közül valamelyik, aszerint, hogy jobbról haladunk balra, vagy balról jobbra. Bevezetünk két segédváltozót, ii-t és jj-t, amelyeknek segítségével minden lépésben nyilvántartjuk, hogy melyik index fog nőni és melyik fog csökkenni. Kezdetben az első elemet hasonlítjuk azokkal, amelyek a sorozat végén találha­ tók jobbról balra haladva. Tehát a bal index (i) nem változik, és a jobb index (j) csökken. Ennek megfelelően ii  0, és jj  –1. Az első felcserélés után az első index (i) nő és a második (j) rögzített, tehát ii  1 és jj  0. Az összehasonlítá­ sokat addig folytatjuk míg i egyenlővé nem válik j-vel. Amikor i = j, az eredeti sorozat első eleme az i-edik helyre került, amely egyben a végleges helye. [9] Algoritmus Feloszt_2(bal,jobb,i): i  bal { bemeneti adatok: bal, jobb; kimeneti adat: i }

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

207

j  jobb ii  0 jj  -1 Amíg i < j végezd el: Ha xi > xj akkor { ha a sorrend nem megfelelő } xi  xj { felcseréljük őket } ii  -jj { megváltozik az összehasonlítások folytatásának iránya } vége(ha) i  i + ii j  j + jj vége(amíg) Vége(algoritmus)

Ha ezt az algoritmust megpróbáljuk egyszerűsíteni, eljutunk a következő változathoz: Algoritmus Feloszt_2(bal,jobb,i): i  bal j  jobb v  0 Amíg i < j végezd el: Ha xi > xj akkor xi  xj v  1 - v vége(ha) i  i + v j  j - 1 + v vége(amíg) Vége(algoritmus)

9.3.7. Hanoi tornyok Adva van három rúd A, B, C; az elsőre fel van fűzve n darab, különböző átmé­ rőjű korong úgy, hogy a korongok az átmérőjük csökkenő sorrendjében helyez­ kednek el egymás fölött. A másik két rúd üres. Írjuk ki minden lehetséges mód­ ját annak, ahogyan a korongokat átköltöztethetjük az A rúdról a B-re, ugyan­ olyan sorrendben, ahogyan az A-n helyezkedtek el. Közben fel lehet használni, ideiglenesen a C rudat. Egy mozgatás csak egy korongot érinthet, és csak kisebb átmérőjű korongot helyezhetünk egy nagyobb átmérőjű korong fölé. Megoldás ■ A módszer újból a divide et impera. Az n korong átköltöztetése az A rúdról a B-re felbontható három, ehhez hasonló feladatra:  n – 1 korong átköltöztetése az A rúdról a C-re,

208

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

 a megmaradt korong áthelyezése B-re,  n – 1 korong áthelyezése C-ről B-re. 1) n–1 korong A

B

C

2) n–1 korong A

B

C

3)

n–1 korong A

B

C

A

B

C

A három részfeladat méretét a költöztetendő korongok száma határozza meg: n – 1, 1 és n – 1. A részfeladatok függetlenek mivel az eredeti rudak konfigurá­ ciói, valamint az időközben váltakozva ideiglenesnek használt rudaké különbö­ zők. A feladat felbontása ugyanígy folytatódik, míg olyan részfeladathoz nem é­ rünk, amelynek mérete 1. Ennek megoldása egyetlen korong költöztetését jelenti. Ha megfigyeljük a rajzot, észrevesszük, hogy egy költöztetés mindig egy legfelül található korong mozgatását jelenti egy másik rúd tetejére. A mozgatást egyértelműen meghatározza a rúd „neve”. A részeredmények összerakása ebben az esetben is hiányzik. Algoritmus Hanoi_1(n,A,B,C): Ha n  1 akkor Hanoi_1(n-1,A,C,B) Tedd a korongot A-ról B-re Hanoi_1(n-1,C,B,A) vége(ha) Vége(algoritmus)

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

209

Ennek az algoritmusnak a hívása Hanoi_1(n,A,B,C) alakú, ahol A, B, C a három rudat jelképezi, és ha a hívó programegységben az aktuális paraméterek értékei 'A', 'B', 'C', akkor a Tedd a korongot A-ról B-re egy egyszerű kiírás: Ki: A, '-', B. Mivel a paraméterek értékei változnak, n = 3-ra a következőket kapjuk: A-B A-C B-C A-B C-A C-B A-B Egy másik megoldási javaslat ■ Jelöljük a rudakat 1, 2, 3-mal, és a költözteté­ sek sorozatát, amellyel n korongot átteszünk az i rúdról a j rúdra, H(n; i, j)-vel, ahol i, j  {1, 2, 3}, i  j A költöztetések közben felhasználjuk a harmadik rudat is, amelynek a sorszáma 6 – i – j. A feladat tehát H(n; 1, 2) meghatározása. Észrevesszük, hogy H(n; i, j) a következő tevékenységeket jelenti: H(n; i, j) = H(n – 1; i, 6–i–j), H(1; i, j), H(n – 1; 6–i–j, j). [9] A Tedd_a_korongot(n,i,j) algoritmus kiírja az elvégzendő költöztetéseket: Algoritmus Tedd_a_korongot(n,i,j): Ki: n, ' költözik ', i, '-ről ', j, '-re' Vége(algoritmus) Algoritmus Hanoi_2(n,i,j): Ha n  1 akkor k  6-i-j Hanoi(n-1,i,k) Tedd_a_korongot (n,i,j) Hanoi(n-1,k,j) vége(ha) Vége(algoritmus)

{ az n-edik az i-edik rúdról a j-edikre }

Megjegyzések  A fent leírt algoritmust Hanoi_2(n,1,2) alakban hívjuk.  Bebizonyítható (indukcióval), hogy a költöztetések száma: 2 n – 1.

9.3.8. Úszómedence Egy tulajdonos szeretne egy úszómedencét építeni a kertjében. A kert téglalap alakú, és bizonyos pontjain dísznövények találhatók. A tulajdonos szeretné tud­

210

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

ni, hogy mekkora lehetne az a legnagyobb területű úszómedence, amelyet úgy építhetne, hogy egy növényt sem kell elköltöztetni az eredeti helyéről. A tulaj­ donos olyan téglalap alakú medencét szeretne, amelynek oldalai párhuzamosak a kertet körülvevő kerítéssel. Adottak a kert bal alsó sarkának, valamint jobb felső sarkának koordinátái, és a dísznövényeknek megfelelő pontok koordinátái. Egy dísznövény maradhat a medence szélén is. Példa ■ Legyen a következő kert, amelynek bal alsó sarka a (0, 0) koordinátájú pontban van, jobb felső sarka pedig a (10, 10) koordinátájú pontban. A dísznö­ vények koordinátái: (3, 8), (4, 4), (7, 7). (10, 10) (3, 8) (7, 7)

(10, 7)

(4, 4)

(0,0)

(4, 0)

A legnagyobb medencének a területe, amelyet ebben a kertben meg lehet va­ lósítani 42, bal alsó sarka a (4, 0) koordinátájú pontban, a jobb felső sarka pedig a (10, 7) pontban lesz. Megoldás ■ Ha a kertben nem lenne egy dísznövény sem, a medence lefedhetné a teljes kertet. Ha egy dísznövényt kellene elkerülni, tételezzük fel, hogy ez az (x, y) pontban található. A feladat megoldását a kert kisebb téglalapokra osztása jelenti, a növényen áthaladó vonalak által, ahogy a következő ábra mutatja. Ez a felosztás négy lehetőséget kínál (Z1, Z2, Z3, Z4), amelyek közül kiválasztjuk a legnagyobb területűt. (c, d) (a, b) (x, d)

Z1 (a, b)

(c, d)

Z2 (x, b)

(x, y) (c, d) (a, y)

Z3

(c, y)

Z4 (a, b)

Legyenek a kert sarkainak koordinátái (a, b) és (c, d) és a növény koordiná­ tái x, y. Az új téglalapok sarkainak koordinátái:  Z1: bal alsó sarka (a, b), jobb felső sarka (x, d)  Z2: bal alsó sarka (x, b), jobb felső sarka (c, d)

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

211

 Z3: bal alsó sarka (a, y), jobb felső sarka (c, d)  Z4: bal alsó sarka (a, b), jobb felső sarka (c, y) Az (a, b), (c, d) koordinátákkal rendelkező téglalap területe: T = (c – a)  (d – b). Minden meghatározott téglalap esetében el kell döntenünk, hogy található-e dísznövény az illető felületen. Az algoritmus, amely a téglalapon belül található pontokat (növényeket) keresi 0-t térít vissza, ha ilyen pont nincs vagy, ha létezik ilyen pont, akkor annak az indexét téríti vissza a pontokat tároló sorozatból. Ha egy (x, y) koordinátájú pont az (a, b), illetve (c, d) koordinátájú téglalapon belül található, akkor a < x, x < c és b < y, y < d. Algoritmus Keres(a,b,c,d,i): { bemeneti adatok: a, b, c, d; kimeneti adat: i } { az (a, b) és (c, d) koordinátájú téglalapon belül keres } { belső pontot (a széleken nem) }

i  1 van  hamis Amíg (i  n) és nem van végezd el: Ha (a < xi) és (xi < c) és (b > yi) és (yi > d) akkor van  igaz { visszatérítjük az i belső pont indexét } különben i  i + 1 vége(ha) vége(amíg) Ha nem van akkor { ha nincs belső pont } i  0 vége(ha) Vége(algoritmus)

Az oszd meg és uralkodj módszer stratégiája szerint az eredeti feladat meg­ oldását visszavezetjük ugyanazon feladat megoldására négy kisebb téglalap ese­ tében. A téglalapokat addig vágjuk kisebb téglalapokra, amíg nem találunk egy olyan téglalapot, amelynek belsejében nincs belső pont. Minden ilyen téglalap területét kiszámítjuk és aktualizáljuk a legnagyobb méretű téglalapot. A maxi­ mális területű téglalapra vonatkozó adatokat globális változókban tároljuk. Algoritmus Feloszt(a,b,c,d): Keres(a,b,c,d,i) Ha i  0 akkor Feloszt(a,b,xi,d) Feloszt(xi,b,c,d) Feloszt(a,yi,c,d) Feloszt(a,b,c,yi) különben T  (c-a) * (d-b) Ha T > max akkor

{ keresünk egy belső pontot } { ha találtunk, felosztjuk a téglalapot: } { négy új téglalapot vizsgálunk }

{ ha a téglalap belseje szabad } { kiszámítjuk a téglalap területét }

212

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

max  T am  a bm  b cm  c dm  d vége(ha) vége(ha) Vége(algoritmus)

{ aktualizáljuk a legnagyobb téglalap adatait }

9.4. Kitűzött feladatok 1. Írjunk programot a következő feladatok oszd meg és uralkodj módszerrel va­ ló megoldására! Legyen n természetes szám, állapítsuk meg:  a számok legnagyobb közös osztóját;  a számok összegét;  a számok legkisebb és legnagyobb elemét egyidőben.  tudva, hogy a sorozat nem rendezett, állapítsunk meg minden olyan helyet, ahol egy adott érték áll. 2. Legyen egy n elemű sorozat, amely egész számokat tartalmaz. A sorozatot kettéhajtjuk úgy, hogy az egyik felét, az adományozót, ráhelyezzük a másik felére, a fogadóra. Ha a tömb elemeinek száma páratlan, a középső elemet elhagyjuk. A fent említett módon egy olyan résztömböt kapunk, amelynek elemei a „fogadó” elemei lesznek. Példa ■ Az (1, 2, 3, 4, 5, 6, 7) sorozatot kettőbe hajtjuk. (1, 2, 3)-ra ráhajtjuk az (5, 6, 7) részsorozatot, a 4-et a definíció értelmében elhagyjuk. A hajtoga­ tást addig folytatjuk, míg az eredmény egy 1 elemű sorozat lesz, amelyet végső elemnek nevezünk.  Legyen i{1, 2, ..., n} egy index a sorozatban. Állapítsuk meg, hogy ismé­ telt hajtogatások eredményeként válhat-e végső elemmé vagy sem az iedik elem!  Határozzuk meg az összes lehetséges végső elemet!  Adott i végső elem esetében határozzuk meg a hajtogatások sorozatát!  Feltételezzük, hogy a hajtogatás során a fogadó elem kétszereséből levon­ juk a neki megfelelő adományozó elem kétszeresét. Határozzuk meg, hogy létezik-e olyan végső elem, amelynek értéke nulla lesz a fenti mó­ don leírt „hajtogatás” eredményeként! 3. Legyen a következő játék: az egyik játékos gondol egy 0 és 1000 közötti természetes számra. A másik játékosnak ki kell találnia ezt a számot minél

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

213

kevesebb próbálgatással. A titkos szám „birtokosa” egy-egy találgatásra csak annyit válaszol, hogy a titkos szám kisebb vagy nagyobb mint a másik játé­ kos által feltételezett szám. 4. Adott egy n egész számot tartalmazó sorozat. Határozzuk meg azt a legna­ gyobb összeget, amelyet a tömb egymás utáni elemeinek összeadásával kap­ hatunk. Útmutatás ■ Szétbontjuk a tömböt résztömbökre. Ha a résztömb már csak egy elemet tartalmaz, akkor a maximális összeg ennek az elemnek az értéke. A maximális összegű részsorozat helyzete háromféle lehet:  az első résztömbben van;  a második résztömbben van;  a sorozat elemeinek egy része az első résztömbben van, a többi a má so­ dikban. Mivel csak a harmadik eset okoz gondot, lássuk, hogyan járunk el ebben az esetben: a középső elemtől jobbra és balra haladva keressük a legnagyobb ké­ pezhető összeget. A középső pozíciótól indulva kiszámítjuk az elemek össze­ gét és minden egyes érték hozzáadásakor ellenőrizzük, hogy a kapott összeg meghaladja-e az eddigi maximális összeget, ha igen akkor azt módosítjuk. Majd a két kiszámított összeget összeadva megkapjuk a maximális összeget. Tehát a programnak „gondolnia kell” arra, hogy a maximális összegű részsorozat bármelyik helyzetben előfordulhat (az első résztömbben, a máso­ dikban vagy a két résztömbben felosztva), ezért mindhárom eset által szol­ gáltatott maximális összeget kiszámolja és a legnagyobbat választja. [25] 5. Egy kiállítási csarnokot le fognak fedni előre gyártott műanyaglapokkal. Előbb felosztották a csarnok alapját négyzet alakú felületekre úgy, hogy a csarnok belsejében található oszlop pontosan egy ilyen négyzetnyi helyet foglaljon el. A műanyaglapok alakja olyan, hogy három ilyen négyzetet fed­ nek majd le. [25]

A tervező rájött, hogy nem fogja tudni ezekkel lefedni a teljes felületet, ezért kiválasztott egy 2n  2n méretű részt a csarnokból, amelyen ott van az oszlop is (a példában n = 3):

214

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

Tervezzük meg a lefedést felhasználva az alakzatot úgy, hogy ne marad­ jon fedetlen felület a kiválasztott részben. Ezen alakzat elforgatható három irányba, 90o, 180o vagy 270o-kal. Írjuk ki az eredményt a következőképpen: minden sorba írjunk 6 számot, amely egy alakzat három négyzetének sor- és oszlopindexét jelenti. Útmutatás ■ A felületet felosztjuk négy egyenlő részre. Tudjuk, hogy az egyik részben van az oszlop, de mivel arra törekszünk, hogy alkalmazhassuk az oszd meg és uralkodj módszert, ki kellene találjunk nem létező „oszlopo­ kat” a másik három részben is. Ezt a következőképpen valósítjuk meg: a fe­ lület középpontjában levő négy kis négyzet közül lefedjük azt a hármat, amelyek nincsenek azon a részfelületen, amelyben az oszlop van (az alábbi példa esetén a (4, 4), (5, 4), (5, 5) koordinátájú négyzeteket). Mivel lefedtük őket, a továbbiakban ezt a három kis négyzetet oszlopokként kezeljük. Meg­ hívjuk tehát az eljárást a négy, felosztás során létrejött, részfelületre úgy, hogy egyikben ott van az eredeti oszlop, a másik háromban pedig az oszlop helyett egy-egy lefedett kis négyzet. Példa ■ n = 3, az oszlop koordinátái (3, 6). A fent leírt módon elhelyezzük az első alakzatot úgy, hogy a hiányzó négyzetnek megfelelő részben legyen az oszlop:

Felosztás után a négy felület a következőképpen fog kinézni:

Ezzel a négy felülettel hasonlóképpen járunk el, addig amíg a kapott felület mérete 2  2 lesz, ahova elhelyezünk egy alakzatot a lefedetlen négyzetekre.

9. AZ OSZD MEG ÉS URALKODJ MÓDSZER (DIVIDE ET IMPERA)

215

6. Adott egy ponthalmaz, amely 2n (n > 1) pontot tartalmaz. Ismerve a pontok koordinátáit, határozzuk meg a legközelebb eső két pont közötti távolságot!

10

MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

10.1. Bevezetés A greedy módszert (mohó algoritmusokat) optimumkiszámításokra használjuk. E feladatok eredményei részhalmazai vagy elemei annak a Descartes-szorzat­ nak, amelyre a célfüggvény eléri minimumát vagy maximumát. A mohó algorit­ mus mindig egyetlen eredményt határoz meg. Ezt az eredményt fokozatosan építjük fel: a feladatokban általában adott egy L halmaz, amelynek meg kell ha­ tároznunk egy M részhalmazát, amely megfelel bizonyos követelményeknek (T tulajdonságnak), és amely általában a végeredmény. Az M halmaz eredetileg az üres halmaz. Ehhez, egymás után hozzáadunk L-beli elemeket, amelyeket úgy választunk ki, hogy lokális optimumot biztosítanak. Ezek az elemek azok, ame ­ lyek a legtöbbet ígérők az aktuális lépésben, és amelyek megfelelnek a feladat­ nak az adott pillanatban. A stratégia mohó jellegének következtében kapta ez az algoritmus a greedy (mohó) elnevezést. Mivel a stratégia egy helyi optimum kiválasztására épül, nem biztosítja a megoldás globális optimalitását, tehát nem mindig határozza meg a legjobb megoldást. Nem lehetünk biztosak a megoldásban, de ha sikerül bebizonyítani, hogy az adott feladat esetében a mohó algoritmus optimumot ha­ tároz meg, akkor biztonságosan alkalmazható. Ha viszont olyan feladatunk van, amelynek pontos megoldását csak exponenciális algoritmussal tudjuk megadni, sok esetben akkor is alkalmazható, de természetesen számításba vesszük, hogy az eredmény közelítő. Ilyenkor heurisztikus mohó algoritmusról beszélünk. Legyen az L halmaz az {a1, a2, ..., an} sorozat és T egy tulajdonság, amelyet az L részhalmazaira definiáltunk: T: T(L)  {0, 1}, ahol T() = 1 (igaz, vagyis teljesül T), ha T(X), akkor  T(Y), bármely Y  X részhalmaz esetében. Egy S  L részhalmazt eredménynek nevezünk, ha T(S) = 1. minden lehetséges ered­ ményből azt szeretnénk kiválasztani, amely optimalizálja a T: T(L)  R adott függvényt. A mohó algoritmus nem generál minden lehetséges részhalmazt (ami exponenciális végrehajtási időhöz vezetne), hanem megpróbál közvetlenül az optimális megoldás felé haladni. Amint előbb is említettük, előbb be kell bizonyítanunk, hogy ez a stratégia valóban biztosítja az optimális eredmény ki­ számítását. [9]

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

217

A módszer sokkal egyszerűbb mint a dinamikus programozás módszere15, a programok gyorsak, még nagyméretű adatszerkezetek esetében is. Az egyszerű­ ség abban áll, hogy minden pillanatban, csak az adott kontextusnak megfelelő részfeladatot tekintjük. A módszer különbözik a backtracking (visszalépéses ke­ resés16) módszertől mivel, ha egy elemről kiderül, hogy hiába volt sokat ígérő, akkor nem kerül be a megoldásba és soha nem térünk vissza ehhez az elemhez. Fordítva, ha egy elem bekerült egy adott pillanatban egy megoldásba, nem fog­ juk kivenni onnan. Példa ■ Adott egy ország n labdarugója, akik közül ki kell válogatnunk k spor­ tolót az országos válogatott csapatba. Természetesen, előbb készítünk egy nyil­ vántartást, amelyben bizonyos elképzeléseknek megfelelően pontozzuk a spor­ tolókat. Elsőnek a legtöbbet ígérő labdarugót választjuk, aki a pontozás szerint a legeredményesebb. Ezután vesszük a következő legtöbbet ígérőt és így tovább. A válogatást addig folytatjuk, amíg kialakul az országos csapat. Minél megfele­ lőbb a pontozási rendszerünk, annál biztosabb, hogy a csapat eredményes lesz a jövőben.

10.2. A mohó algoritmus általános bemutatása A módszer általános alakját két változatban ismertetjük. (A feladat megoldását az M halmaz tartalmazza, a megoldásokat az L – lehetséges megoldások hal­ mazából – válogatjuk): Algoritmus Greedy_1(L,M): M   Amíg M nem megoldás és L   végezd el: Választ(L,x) { kiválasztjuk a legtöbbet ígérő elemet L-ből } L  L\{x} { töröljük a legtöbbet ígérő elemet L-ből } Ha T(M  {x}) = 1 akkor M  M  {x} vége(ha) vége(amíg) Vége(algoritmus)

{ ha lehetséges } { ezt hozzáadjuk M-hez }

Ezt a módszert később fogjuk megismerni, az Algoritmusok tervezése és elemzése előadássorozat keretén belül. 16 Lásd a 8. fejezetben. 15

218

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Megjegyzések  Ha a kiválasztott elemet töröljük L-ből, akkor biztosítottuk az algoritmus számára, hogy L minden elemét csak egyszer dolgozzuk fel (töröljük, füg­ getlenül attól, hogy betesszük az eredménybe vagy sem).  Mivel a bemeneti adatoktól függően nem mindig találunk eredményt, a hívó programegységben meg kell vizsgálnunk, hogy az M halmaz valóban eredmény-e: ... Ha M megoldás akkor Ki: M különben Ki: 'Nem talált megoldást.' vége(ha) ...

 Az ilyen típusú feladatok megoldása során gyakran bizonyul előnyösnek, ha a tulajdonképpeni feldolgozás előtt előbb rendezzük a feldolgozandó adatokat (az L halmazt). A rendezett sorozat elemeit ({a1, a2, ..., an}) egy­ más után vizsgáljuk és a követelményektől függően betesszük az ered­ ménybe vagy sem (nincs szükség ezek törlésére L-ből, mivel egy meg­ vizsgált elemhez nem térünk vissza). Az algoritmus ebben a változatban a következő lesz: Algoritmus Greedy_2(n,a,M): Feldolgoz(n,a) M  

{ ez a feldolgozás gyakran rendezés }

i  1 Amíg M nem megoldás és (i  n) végezd el: Ha T(M  {ai}) akkor M  M  {ai} vége(ha) i  i + 1 vége(amíg) Vége(algoritmus)

{ ha lehetséges } { ai-t hozzáadjuk M-hez }

 A fenti algoritmusok lineárisak (eltekintve a Választ(L,x) és a Feldolgoz(n,a) algoritmusok bonyolultságától)!  A tulajdonképpeni nehézséget a Választ(L,x), valamint a Feldolgoz (n,a) je­ lenti, mivel ezekbe „rejtjük” el a célfüggvényt.

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

219

10.3. Megoldott feladatok 10.3.1.Összeg Adott egy n elemű, valós számokból álló sorozat. Határozzuk meg az adott so­ rozat azon részsorozatát, amelynek összege a lehető legnagyobb. Megoldás ■ Alkalmazzuk a Greedy_1(L,n) algoritmust, ahol a Választ(L,x) al­ programnak megfelelően az adott sorozatból kiválasztjuk a szigorúan pozitív elemeket. Ezúttal könnyű belátni, hogy az algoritmus garantáltan maximális összegű részsorozatot határoz meg, hiszen, ha az összeghez hozzáadnánk egy negatív értéket, akkor az kisebbé válna. Ha egy 0 értékű elemet adunk az ösz­ szeghez, az nem változik. Ebből az észrevételből következik, hogy, ha a sorozat tartalmaz 0 értékeket is, akkor több megoldás is létezik. Algoritmus Összeg(n,a,k,Pozitívak): k  0 { bemeneti adatok: n, a; kimeneti adatok: k, Pozitívak } Minden i=1,n végezd el: Ha ai > 0 akkor k  k + 1 Pozitívakk  ai vége(ha) vége(minden) Vége(algoritmus)

10.3.2.Az átlagos várakozási idő minimalizálása Egy ügyvédi irodába egyszerre érkezik n személy, akiknek az intéznivalóit az ügyvéd ismeri, és így azt is tudja, hogy egy-egy személlyel hány percet fog el­ tölteni. Állapítsuk meg azt a sorrendet, amelyben fogadnia kellene a személye­ ket ahhoz, hogy az átlagos várakozási idő minimális legyen. Megoldás ■ Az átlagos várakozási idő az n személy várakozási idejének szám­ tani középarányosa, tehát az átlagos várakozási idő csökkentése a várakozási idők összegének csökkentését jelenti. Példa ■ Legyen n = 3 és a tárgyalási idők: t1 = 60, t2 = 10 és t3 = 30. Akit első­ nek fogad az ügyvéd, az a személy nem kell várakozzon, a második személy annyit várakozik, amennyi ideig tárgyal az első, a harmadik pedig addig várako­ zik ameddig befejeződik a két előtte levővel való beszélgetés. Ha a fogadás sor­ rendje változik, a várakozási idők összege is változik.

220

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Sorrend 1 2 3 1 3 2 2 3 1 2 1 3

A várakozási idők összege vi = 0 + 60 + (60 + 10) = 130 vi = 0 + 60 + (60 + 30) = 150 vi = 0 + 10 + (10 + 30) = 50 vi = 0 + 10 + (10 + 60) = 80

Összehasonlítva a fentieket, kiderül, hogy a minimális várakozási időössze­ get a személyekkel való tárgyalási idők növekvő sorrendben való rendezése eredményezi. Dacára annak, hogy ez természetesnek tűnik, be kell bizonyítanunk, hogy a mohó algoritmus jó megoldási módszer. Tulajdonság ■ Ha tk1  tk2  ...  tkn, akkor a fogadást a k1, k2, ..., kn sorrendben végezve, a várakozási idők összege minimális lesz. (Természetesen {k1, k2, ..., kn } = {1, 2, ..., n}). Bizonyítás ■ A bizonyítást a lehetetlenre való visszavezetés módszerével vé­ gezzük. Feltételezzük, hogy a k1, k2, ..., kn sorrend nem biztosítja a minimális összes várakozási időt. (*) Ebből következik, hogy létezik egy másik sorrend, amely különbözik a k1, k2, ..., kn sorrendtől, és amely egy kisebb összvárakozási időt biztosít. A legjobb esetben az új sorrend a k1, k2, ..., kn sorrendtől csak az i és a j (i < j) helyeken különbözik. Ha j < n, akkor a k1, ..., ki, ..., kj, ..., kn sorrendnek megfelelően a várakozási idők összege: vi(k1, ..., ki, ..., kj, ..., kn) = = (n – 1)tk1 + (n – 2)tk2 +...+ (n – i)tki +...+ (n – j)tkj +...+ tkn–1. Ugyanakkor az előbbi (*) feltételezés szerint a k1, ..., kj, ..., ki, ..., kn sorrend­ nek megfelelően a várakozási idők összege: vi(k1, ..., kj, ..., ki, ..., kn) = = (n – 1)tk1 + (n – 2)tk2 +...+ (n – i)tkj +... + (n – j)tki +...+ tkn–1 < < (n – 1)tk1 + (n – 2)tk2 +...+ (n – i)tki +... + (n – j)tkj +... + tkn–1 = = vi(k1, ..., ki, ..., kj, ..., kn). Ha a fenti relációs művelet bal és jobb oldalán leredukáljuk az azonos tagokat: (n – i)tkj + (n – j)tki < (n – i)tki + (n – j)tkj  (n – i – n + j)tkj < (n – i – n + j)tki  (j – i)tkj < (j – i)tki Ezt a relációt eloszthatjuk (j – i)-vel, és mivel ez egy pozitív érték (abból a feltételezésből indultunk ki, hogy i < j), a reláció megmarad. Osztás után kap­ juk, hogy tkj < tki, ami ellentmond a feltételezésnek, amely szerint, ha i < j, akkor

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

221

tki < tkj. Ezzel ellentmondásba kerültünk az eredeti feltételezéssel, és így bebizo­ nyítottuk az állítást. Ha j = n, az állítást ugyanígy bizonyjuk. Magától értetődik, hogy ha létezik több egyenlő fogadási idő, nem számít milyen sorrendben kerülnek sorra az egyenlő tárgyalási időt igénylő személyek. (A feladatnak több megoldása lesz.) [17] A mohó algoritmus alkalmazása optimális eredményt biztosít. Ahhoz, hogy minimalizáljuk az átlagos várakozási időt, minimalizálnunk kell a várakozási idők összegét. Egy személy addig várakozik, amíg az összes előtte fogadott személlyel tárgyal az ügyvéd. Ha csak két személy érkezett volna az irodába, akkor az lenne előnyösebb (az átlagos várakozási idő szempontjából), ha előbb a kevesebb időt igénylő személlyel tárgyalna az ügyvéd. Az eredmény tehát a személyek sorszámainak egy olyan permutációja, amelynek megfelelően az ügyvéd minden lépésben a legkevesebb időt igénylő személyt fogadja: M = (k1, k2, ..., kn)  {(x1, x2, ..., xn) | xi  {1, 2, ..., n}, xi  xj  i, j = 1, 2, ..., n, i  j}. Az L eredetileg az {1, 2, ..., n} halmaz. A legtöbbet ígérő x elem az L-ből annak a személynek a sorszáma, akinek a fogadási ideje minimális azok között akik még az L-hez tartoznak. Ezt hozzáadjuk az M-hez és kizárjuk az L-ből. Az x kizárását az L-ből úgy valósítjuk meg, hogy 0 értéket másolunk rá. Minden lépésnél csak 0-tól különböző értéket választunk az L-ből. Algoritmus Legkisebb(n,t,L,min_ind): { bemeneti adatok: n, t, L; kimeneti adat: min_ind } i  1 { keressük a legkisebb indexű még nem kizárt elemet } Amíg nem Li végezd el: i  i + 1 vége(amíg) min_ind  i { ez lesz a legtöbbet ígérő elemnek az ideiglenes kezdőértéke } min  tmin_ind Minden i=i+1,n végezd el: Ha Li akkor { keressük a nem kizárt elemek között a legkisebb értékűt } j  i Ha tj < min akkor min_ind  j min  tmin_ind vége(ha) vége(ha) vége(minden) Vége(algoritmus) Algoritmus Sorrend(n,t,M,átlag): { bemeneti adatok: n, t, M; kimeneti adatok: átlag, M } vi_min  0 { a minimális összvárakozási idő }

222

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

vi  0 { egy személy várakozási ideje } Minden i=1,n végezd el: Li  igaz { még minden elem lehetséges} vége(minden) Minden i=1,n-1 végezd el: Legkisebb(n,t,L,min_ind) { min_ind a legtöbbet ígérő elem indexe } Lmin_ind  hamis { kizárjuk L-ből } Mi  min_ind { betesszük M-be } vi  vi + tmin_ind { kiszámítjuk egy személy várakozási idejét } { ezt hozzáadjuk a minimális összvárakozási időhöz } vi_min  vi_min + vi vége(minden) i  1 { megkeressük az egyetlen még nem kizárt elemet } Amíg nem Li végezd el: i  i + 1 vége(amíg) Mn  i { ezt betesszük M-be } átlag  vi_min / n Vége(algoritmus)

A következő implementáció előbb inicializálja az M halmazt az 1, 2, ..., n értékekkel, és növekvő sorrendbe rendezi az időket, megfelelően módosítva az M halmaz elemeit. A rendezés után: M = k1, k2, ..., kn és t1  t2  ...  tn. A kiírást az M halmazban található indexpermutáció alapján végezzük. Algoritmus Sorrend(n,t,M,átlag): { bemeneti adatok: n, t, M; kimeneti adatok: átlag, M } Minden i=1,n végezd el: Mi  i vége(minden) { növekvően rendezzük a t sorozatot és módosítjuk az M-et is } Növekvő_sorrendbe_rendezés(n,M,t) vi_min  0 vi  0 Minden i=1,n-1 végezd el: vi  vi + ti { a t már növekvően rendezett } vi_min  vi_min + vi vége(minden) átlag  vi_min / n Vége(algoritmus)

10.3.3.Buszmegállók

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

223

Egy közszállítási vállalat olyan gyorsjáratot szeretne indítani, amely csak a vá­ ros főutcáján közlekedne, és a már létező n megálló közül használna néhányat. Ezeket a megállókat úgy kell kiválasztanunk, hogy két megálló között a távol­ ság legkevesebb x méter legyen (gyorsjáratról van szó), és a megállók száma le­ gyen a lehető legnagyobb (minél több utas használhassa). Adott a főutcán már meglevő egymás után található megállók közti távolságok sorozata. [2] Példa ■ Legyen n = 10 és x = 60m. A főutcán már meglevő egymás után talál­ ható megállók közti távolságok sorozata: (100, 50, 25, 25, 50, 10, 10, 80, 20). Ezekből a távolságokból ki lehet számítani a megállók koordinátáit: (0, 100, 150, 175, 200, 250, 260, 270, 350, 370). A gyorsjáratnak öt megállója lesz (ezeknek a sorszámai: 1, 2, 4, 6, 9). Megoldás ■ Az L halmazt a létező megállók sorszámai alkotják: L = {1, 2, …, n}. Ismerjük az n megálló közötti n – 1 távolságot: a1, a2, …, an–1. Meg kell határoznunk azt a maximális elemszámú M  L részhalmazt (M = {i1, i2, …, ik}), amelyben a sorszámok növekvő sorrendben követik egymást (a főutcán található megállóknak egymás utáni sorszámaik vannak), és amelynek megfelelően bármely két kiválasztott megálló között a távolság legkevesebb x méter (aij+1 – aij  x, j = 1, 2, …, k – 1). Algoritmus Megállók(n,a,M): i  1 { bemeneti adatok: n, a; kimeneti adat: M } M1  1 { az eredménybe betett utolsó megállótól mért távolság } táv_az_utolsótól  0 Minden j=2,n végezd el: Ha aj-1 + táv_az_utolsótól  x akkor i  i + 1 Mi  j táv_az_utolsótól  0 különben táv_az_utolsótól  táv_az_utolsótól + aj-1 vége(ha) vége(minden) Vége(algoritmus)

Látható, hogy az első megállót betettük a megoldásba, majd megkerestük azt a megállót, amelyik megfelelő távol található az elsőtől. Ha találtunk ilyent, be­ tettük a megoldásba. Ezt addig folytattuk, amíg bejártuk az összes, már létező megállót. Bizonyítás ■ Előbb bebizonyítjuk a következő állítást: Mindig létezik optimális megoldás, amelyhez hozzátartozik az 1-es megálló.

224

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Jelöljük táv(i, j)-vel az i-edik és a j-edik megálló közötti távolságot. Legyen M  L egy optimális megoldás, ahol M = {j1, j2,…, jk} nem tartalmazza az 1-es megállót. Ekkor táv(j1, j2)  x. De, mivel a főutcán az 1-es megálló a j1-edik előtt található: táv(1, j2) = táv(1, j1) + táv(j1, j2). Innen következik, hogy táv(1, j2)  x, vagyis az M' = {1, j2,…, jk} halmaz szintén megfelel a követelményeknek, és ugyanannyi eleme van mint M-nek. Így M' optimális megoldás. Bebizonyítottuk, hogy létezik olyan optimális megoldás, amely tartalmazza az 1-es megállót. A következő lépésben csak olyan megállókat választhatunk a megoldásba, amelyek az 1-es megállótól legkevesebb x távolságra találhatók. Ha M egy optimális megoldás az L-ből, kimutatjuk, hogy az M'' = M – {1} megoldás, amit a fenti algoritmussal állapítunk meg, optimális az L'-ből, ahol L' = L – {j | táv(1, j) < x}. Feltételezzük, hogy M'' nem optimális, vagyis létezik M''' úgy, hogy |M'''| > |M''|. De ekkor az {1}  M''' halmaz számossága |{1}  M'''| > |M|, ami ellentmond annak a feltételezésnek, hogy M optimális megoldás. Következik, hogy az M'', amit a fenti algoritmussal határoztunk meg, maximális számú elemet tartalmaz. Tehát M = {1}  M'' maximális számú elemet tartal­ maz, és így optimális megoldása a feladatnak.

10.3.4.Autó bérbeadása Egy szállítási vállalat autókat kölcsönöz. Egy bizonyos jármű iránt igen nagy az érdeklődés, ezért az igényeket egy évre előre jegyzik. Az igényt két számmal je­ löljük, amelyek az év azon napjainak sorszámait jelölik, amellyel kezdődően, illetve végződően igénylik az illető autót. Állapítsuk meg a bérbeadást úgy, hogy a lehető legtöbb személyt szolgáljuk ki. Adott a személyek száma n, (n  100) és az igényelt intervallumok (ai, bi, i = 1, 2, ..., n, ai < bi ≤ 365). Írjuk ki azt a számot, amely a lehetséges legnagyobb az igénylő személyek számából és a bérbeadási időintervallumokat. Példa ■ Legyen n = 10, az igényelt intervallumok pedig: a

1 12

5 12 13 40 30 22

80

19

22 20 10 29 25 66 35 33 100

65

i

b i

Az igényléseket növekvő sorrendbe rendezzük az időintervallum második eleme szerint: a

5 12

1 13 12 22 30 40 19

80

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

225

i

b

10 20 22 25 29 33 35 66 65

100

i

A következő bérbeadási lehetőségeinken kívül, természetesen van még más lehetőség is: Bérbeadás 1 2 3 4 5 személy [5, 10] [12, 20] [22, 33] [40, 66] 4 személy [1, 22] [30, 35] [40, 66] [80, 100] 5 személy [5, 10] [13, 25] [30, 35] [40, 66]

5 [80, 100] [80, 100]

A legtöbb kiszolgálható személy 5 és így az optimális megoldások az [5, 10] intervallummal kezdődnek, amely a legkisebb bi-vel végződik. Ha kiválasztjuk az [5, 10] igényt, maximális számú nap marad fenn a további igények számára. Ha általánosítjuk az ötletet, a példák közül az első megoldáshoz jutunk. De a módszer helyességét és alkalmazhatóságát be kell bizonyítanunk. Tulajdonság ■ Ha az igényelt időintervallumok a bi érték szerinti növekvő sor­ rendben követik egymást vagyis b1  b2 ... bn, akkor a legkisebb bi szerinti ki­ választás maximális számú igény kielégítését teszi lehetővé. Bizonyítás ■ Feltételezzük, hogy a greedy stratégia alkalmazása m igény kielé­ gítését teszi lehetővé és, hogy az 1-es számú igényt kiválasztottuk az eredmény­ be (lásd a Buszmegállók feladatot). i1 =1, i2, ..., im (1  m  n): bi2 = min{bkak > b1, k = 2, 3, ..., n} bi3 = min{bkak > b i2, k = i2 + 1, ..., n} ... bim = min{bkak > b im-1, k = im–1 + 1, ..., n} ahol, 1 = i1 < i2 0) végezd el: Ha súlyi  Hely akkor xi  1 Hely  Hely - súlyi különben xi  Hely / súlyi Hely  0 Minden j=i+1,n végezd el: xj  0 vége(minden) vége(ha) i  i + 1 vége(amíg) Vége(algoritmus)

Az algoritmus végrehajtásának eredménye az x sorozat: x = (1, ..., 1, xj, 0, ..., 0) ahol xj  [0, 1). Ennek alapján kiírhatjuk a becsomagolt áruk sorszámait (vigyáz­ zunk, hogy az eredeti sorszámokat írjuk ki) és a hátizsák tartalmának értékét. [9] Be kell bizonyítanunk, hogy az algoritmus optimális eredményt határoz meg. Legyen y az optimális eredmény: y = (1, ..., 1, yk, 0, ..., 0), ahol S = súly1  y1 + súly2  y2 + ... + súlyn  yn és érték1  y1 + érték2  y2 +...+ értékn  yn maximális. Ha y  x, (x = (1, ..., 1, xj, 0, ..., 0)) legyen k az első pozíció ahol yk  xk. Megjegyzés  Ebben az esetben k  j, mivel, ha k > j meghaladjuk az S súlyt.  Feltételezzük, hogy yk < xk  ha k < j, ez igaz mivel xk = 1;  ha k = j, ez igaz, mivel ha yk > xk meghaladjuk az S súlyt.

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

229

Legyen egy y' = (y1, ..., yk-1, xk, yk+1, …, yn) eredmény, ahol  < 1 (az első k – 1 komponens azonos x-ben és y'-ben). súlykxk + (súlyk+1yk+1 + ... + súlynyn) = súlykyk + súlyk+1yk+1 + ... + súlynyn. Következik: súlyk(xk – yk) = (súlyk+1yk+1 + ... + súlynyn) – súlyk+1yk+1 – ... – súlynyn. súlyk(xk – yk) = (1 – )(súlyk+1yk+1 + … + súlynyn)

(**)

Összehasonlítjuk y' és y teljesítményeit: f(y') – f(y) = értékkxk + értékk+1yk+1 + ... + értéknyn – (értékkyk + értékk+1yk+1 + ... + értéknyn) = értékk(xk – yk) + ( – 1)(értékk+1yk+1 + ... + értéknyn) A fenti relációból kiemeljük értékk/súlyk-t: = értékk/súlyk[súlyk(xk – yk) + ( – 1)(súlyk/értékkértékk+1yk+1 + ... + súlyk/értékkértéknyn)] Tudjuk, hogy  – 1 > 0 és súlyk/értékk  súlys/értéks, s > k. Akkor f(y') – f(y) > értékk/súlyk[súlyk(xk – yk) + ( – 1)(súlyk+1yk+1 + ... + súlynyn)] = 0. Tehát f(y') > f(y), ami ellentmondás az eredeti feltételezéssel. Megjegyzés ■ A „diszkrét hátizsák feladat” abban különbözik a „folytonos”-tól, hogy csak egész tárgyakat csomagolhatunk. Ebben az esetben a mohó algorit­ mus nem vezet optimális eredményhez. Legyen S = 5, n = 3 és súly = (4, 3, 2), érték = (6, 4, 2.5). A greedy módszer­ rel becsomagolnánk az első tárgyat (több nem fér a hátizsákba). Az érték 6. De, ha az utolsó két tárgyat csomagoljuk (ezek beférnek a hátizsákba), a nyereség 6.5 lenne.

10.3.6.Minimális feszítőfák (Kruskal és Prim) Legyen egy G = (X, U) nem irányított összefüggő gráf, amelynek n csomópont­ ja és m éle van. Minden élhez hozzá van rendelve egy szigorúan pozitív szám, a „költség”. Határozzuk meg a gráfnak egy minimális feszítőfáját. A fa egy összefüggő gráf, amelyben nincsenek körök. A G = (X, U) gráf részgráfja az a G' = (X, U') gráf, amelyben U'  U. A G = (X, U) gráf feszítőfája egy olyan A = (X, V) részgráf, amely fa. Egy A = (X, V) fa összköltsége egyenlő a fa éleihez rendelt költségek összegével.

230

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Legyen a következő összefüggő gráf, n = 5 csomóponttal és m = 7 éllel:

2

6

2

4

1 3

5 5 4

7 3

1

G legegyszerűbb feszítőfája a G0 = (X, ) nem összefüggő gráf, amely csak az eredeti gráf csomópontjait tartalmazza. Innen indulunk a minimális feszítőfa megállapításának útján. Mivel minimális összköltséget kell megállapítanunk, az éleket a költségeik szerint növekvő sorrendbe rendezzük. A kiválasztásnál csak arra kell vigyáznunk, hogy ezek az élek fát alkossanak, vagyis ne hozzanak létre kört a részgráfban. xi yi költség

3 1 1 2 4 2 3 4 2 3 4 5 5 5 m=7 1 2 3 4 5 6 7

i

Az üres fában lesz n = 5 összefüggő komponens (ezek egy-egy csomópont­ ból állnak). Kiválasztjuk a (3, 4) élet, aminek következtében a 3-as és 4-es cso­ mópont ugyanabba az egyetlen összefüggő komponensbe kerül. Az (1, 2) él ugyancsak összeköt két különböző összefüggő komponenshez tartozó két cso­ mópontot, és így ezek újabb egyetlen összefüggő komponensbe kerülnek. Mielőtt kiválasztanánk az (1, 3) élet, észrevesszük, hogy az 1-es csomópont és a 3-as két különböző komponenshez tartoznak; a kiválasztás után az 1-es és 2-s illetve a 3-as és a 4-es egyetlen összefüggő komponensbe kerülnek. A kö­ vetkező él a (2, 4). Ha ezt kiválasztanánk, megjelenne egy kör (mivel az él cso­ mópontjai ugyanahhoz az összefüggő komponenshez tartoznak, a (2, 4) él létre­ hozna egy kört). Ezért ezt az élet nem tesszük be a fába, hanem továbblépünk, és végül a (4, 5) élet is beillesztve a fába megkapjuk a minimális feszítőfát. A greedy stratégia ebben az esetben:

a) Eredetileg a feszítőfa üres; b) Minden lépésben kiválasztjuk az élek közül azt a legkisebb költségűt, amely nem volt még feldolgozva és amellyel a gráfban nem jelenik meg kör (az él végpontjai különböző komponensekhez tartoznak);

c) n – 1 él kiválasztása után leállunk, illetve ha nem lehet kiválasztani n – 1 élet, levonjuk a következtetést, hogy a gráf nem volt összefüggő.

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

231

Ahhoz, hogy alkalmazhassuk a greedy stratégiát, meg kell találnunk a mód­ ját annak, hogy hogyan fejezzük ki azt, hogy egy csomópont hozzátartozik-e, vagy sem egy bizonyos összefüggő komponenshez. Az üres gráfban minden csomópont különálló összefüggő komponenst alkot, így megszámozhatjuk őket 1-től n-ig. Mikor kiválasztjuk a (3, 4) élet, a 4-es csomópont ugyanabba a komponensbe kerül mint a 3-as, tehát azonosan lesznek címkézve. A címkék változásait az L tömbben követjük. Lépés Részgráf Kiválasztott él aleasă 1 2 3 4 5 1 2l

1

3

1

4

1

2 2

2

3

2

3

2

3

2

3

1 1 1

4

5

(3,4) _

4

5

4

5

(1,2) _ (1,3) _

3 5

1

2 3

1

4

5

5

(4,5) _

A 4-es lépésben kiválasztjuk az (1, 3) élet. Ennek következtében a 3-as és 4es csomópontok az 1-est és 2-t tartalmazó összefüggő komponensbe kerülnek. Minden csomópontnak, amelynek a címkéje 3 volt meg kell változtatni a címké­ jét 1-re. Mivel az első csomópont, amely megváltoztatja a címkéjét a 3-as, en ­ nek eredeti címkéjét meg kell őriznünk egy változóban ahhoz, hogy biztosíthas­ suk a 4-es címkéjének megváltoztatását is. Megjegyzés ■ Ahhoz, hogy a gráf minimális feszítőfáját meghatározhassuk, a gráfnak eredetileg összefüggőnek kell lennie. Algoritmus Minimális_feszítőfa(n,m,x,y,költség): { bemeneti adatok: n, m, x, y, költség } Rendezés(m,x,y,költség) Minden i=1,n végezd el: Li  i vége(minden) i  0 összköltség  0 Minden j=1,m végezd el: Ha Lxj ≠ Lyj akkor { a j-edik él végpontjai különböző összefüggő komponensekben vannak }

232

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

i  i + 1 kivi  j { kiválasztjuk kiv-be a j-edik élt } összköltség  összköltség + költségj Ha i = n-1 akkor Kiír(n,kiv,x,y)

Kilépünk az algoritmusból vége(ha) id  Lyj { egyesítjük az xj és yj csomópontok összefüggő komponenseit } Minden k=1,n végezd el: Ha Lk = id akkor Lk  Lxj vége(ha) vége(minden) vége(ha) vége(minden) Ki: 'Az eredeti gráf nem összefüggő!' Vége(algoritmus) Algoritmus Kiír(n,kiv,x,y): Ki: 'A feszítőfa összköltsége: ', összköltség Ki: 'A minimális feszítőfa élei: ' Minden i=1,n-1 végezd el: Ki: xkivi, '-', ykivi, ' ' vége(minden) Vége(algoritmus)

Egy gráf minimális feszítőfáját meghatározhatjuk egy másik mohó algorit­ mussal is, amelyet Prim algoritmusa néven ismerünk. Ebben az algoritmusban minden pillanatban szintén a legtöbbet ígérő elemet választjuk ki: egy adott csomópontból kivezető legrövidebb élet. Kiválasztjuk az 1-es csomópontot (megtehetjük, hiszen a fában minden cso­ mópont szerepelni fog). Az 1-esből induló legrövidebb élen megyünk tovább, és minden lépés után javítunk a legrövidebb utak listáján, és megjegyezzük az él kiinduló csomópontját. Az élek költségeit az illeszkedési mátrixban (adjacen­ cia-mátrix) tároljuk. Ha két csomópont között nincs él, a megfelelő érték a vég­ telen. Algoritmus Prim(n,am,kiv1,kiv2,összköltség): összköltség  0 { bemeneti adatok: n, am; kimeneti adatok: kiv1, kiv2, összköltség } Minden i=1,n végezd el: kivi  hamis vége(minden) kiv1  igaz

{ még nincs kiválasztva egy csomópont sem } { kiválasztjuk az elsőt }

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

233

kiv11  1 { a kiválasztott él első csomópontja 1 } Minden i=1,n végezd el: di  am1,i { am = a gráf adjacencia mátrixa, di = 1 és i közötti távolság } ei  1 { minden útnak kezdőpontja az 1-es csomópont } vége(minden) Minden i=1,n-1 végezd el: { n – 1 élet kell kiválasztanunk } min  végtelen { min az aktuálisan (ki nem választott) legkisebb költség } Minden j=2,n végezd el: Ha nem kivj és (dj < min) akkor { ha j nincs kiválasztva és a csomóponttól a többihez vezető } min  dj { út hossza rövidebb mint az aktuális min } m  j { a vizsgált utak összköltsége között a legkisebb } vége(ha) vége(minden) Ha min = végtelen akkor Ki: 'A gráf nem összefüggő!'

Kilépünk az algoritmusból vége(ha) kivm  igaz { kiválasztjuk az m csomópontot } kiv1i  em { a megfelelő él } kiv2i  m összköltség  összköltség + min { az összköltségbe bekerül az [em, m] él költsége } Minden j=2,n végezd el: Ha nem kivj és (dj > amm,j) akkor { ha találunk m és valamelyik csomópont között } dj  amm,j { rövidebb távolságot mint a régi, kicseréljük } ej  m { és megjegyezzük az él kiindulópontját } vége(ha) vége(minden) vége(minden) { itt ér véget a Minden i } Vége(algoritmus)

10.3.7.Minimális hosszúságú utak (Dijkstra algoritmusa) Legyen a G = (X, U) nem irányított gráf, amelynek n csomópontja és m éle van. Legyen az i0  X csomópont. Minden élhez egy költség van rendelve: k: U  N*. Határozzuk meg a legrövidebb utak hosszát, amelyek az i0 csomópontból in­ dulnak a gráf összes többi csomópontja felé és írjunk ki egy ilyen utat. (Az út hossza egyenlő az éleihez rendelt költségek összegével.) Dijkstra algoritmusa mohó algoritmus.

234

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Legyen Lx = (i0, i1, ..., ik = x) egy legrövidebb út az i0 és az x csomópont kö­ zött. Az Lij = (i0, i1, ..., ij) utak ugyancsak legrövidebbek az i0 és ij (j < k) között. (Ha nem lenne így, létezne egy másik L'[i0, ij] rövidebb út mint Lij; de akkor az L'  (ij, ..., ik) rövidebb lenne mint Lx; következésképpen az Lx nem lenne legrö­ videbb.) Természetesen előbb meghatározzuk az i0 és i1 közti legrövidebb utat, majd azokat a legrövidebb utakat, amelyek az i2, ..., ik csomópontokhoz vezetnek. Ezeket úgy kapjuk meg, hogy meghosszabbítjuk a legrövidebb utat egy minimá­ lis hosszúságú éllel. Egy legrövidebb Ly út, amely az y csomóponthoz vezet (y különbözik az i1, i2, ..., ik csomópontoktól) teljesen különbözhet az Lx-től, vagy ellenkezőleg, kiszámítható az Lx-ből. Dijkstra algoritmusában az M eredmény azon csomópontok halmaza, ame­ lyeket egy legrövidebb út köt össze az i0 csúccsal. Az M halmazt fokozatosan építjük fel, kiindulva az i0 csomópontból. A halmazhoz minden lépésnél hozzá­ adjuk azt a csomópontot, amely növekvő sorrendben következik az őt és az i0-t összekötő legrövidebb utak hossza szerint. A következőkben H-val a még feldolgozatlan csomópontok halmazát jelöl­ jük. [17] Legyen a következő gráf és i0 = 1. 5

2 10

2 4

1

5 30

7

3

7 15

9

4

5

2 6

1. lépés ■ M = {1}, H = {2, 3, ..., 7}. A legrövidebb úthoz egyetlen él fog tartozni, és ez lesz a legrövidebb út azok közül, amelyek egyetlen élből állnak és az 1-es csomópontból indulnak: L2 = (1, 2), L3 = (1, 3) és L4 = (1, 4). A di értékek jelölik a utak hosszát. min(d2, d3, d4) = min(10, 4, 9) = 4 = d3 A 3-as csomópont található a legközelebb az 1-eshez, így bekerül az M-be. L3 = (1, 3) a legrövidebb út 1-től 3-ig.

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

2

10

d 2=10

4

1 d 1=0

d 3=4

3 4

9

235

d 4=9

2. lépés ■ M = {1, 3}, H = {2, 4, ..., 7}. A következő legrövidebb utat azok között fogjuk keresni, amelyek egy élből állnak és az 1-es csomópontból indulnak vagy amelyek az L3 meghosszabbítá­ sai. Meghosszabbítva az L3 utat a (3, 2) éllel, az L2 = (1, 3, 2) utat kapjuk, amelynek hossza 4 + 2 = 6 kisebb mint d2 = 10. Következik, hogy 1 és 2 között a távolság d2 = 6. Ha meghosszabbítjuk az L3-at a (3, 5) éllel, illetve a (3, 6)-tal, az L5 = (1, 3, 5), illetve L6 = (1, 3, 6) utakat kapjuk, amelyeknek hossza d5 = 34 és d6 = 19. min(d2, d4, d5, d6) = min(6, 9, 34, 19) = 6 = d2 Mivel a 2-es csomópont a legközelebbi az 1-eshez, hozzáadjuk M-hez. L2 = (1, 3, 2) a legrövidebb út az 1-es és a 2-es csomópont között. 2 10

2 4

1 9

5

d 2=6

d 5=34

30 3 d 3=4 15

4 d =9 4

6 d 6=19

3. lépés ■ M = {1, 3, 2}, H = {4, 5, ..., 7}. Meghosszabbítjuk az L2-t a (2, 5) éllel; L5 = (1, 3, 2, 5) hossza 6 + 5 = 11 < d5 = 34. Tehát módosul d5 és 11 lesz. A következő legrövidebb utat L4, L5 és L6 között keressük. min(d4, d5, d6) = min(9, 11, 19) = 9 = d4 A 4-es csomópontot tesszük be M-be, így az L4 = (1, 4) út lesz a legrövidebb 1től 4-ig.

236

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

5 d 2=6

2

5 d 5=11 30

4

1 d 1 =0

d 3=4

3

15

9

4 d =9 4

6 d 6=19

4. lépés ■ M = {1, 3, 2, 4}, H = {5, 6, 7}. Ha a legrövidebb L4 utat meghosszabbítjuk a (4, 6) éllel, az L6 = (1, 4, 6) utat kapjuk, amelynek hossza 9 + 5 = 14 < d6 = 19. Megváltozik d6 és 14 lesz. A kö­ vetkező legrövidebb utat L5 és L6 között keressük. min(d5, d6) = min(11, 14) = 11 = d5 Az 5-ös csomópont bekerül az M-be, a legrövidebb út az 1 és 5 között L5 = (1, 3, 2, 5). 5

d 2=6 2

5

d 5=11

2 4

1 d 1=0

9

3 d 3=4 15 4

5 d 4 =9

6 d 6=14

5. lépés ■ M = {1, 3, 2, 4, 5}, H = {6, 7}. Meghosszabbítjuk az L5 utat az (5, 7) éllel, így az L7 = (1, 3, 2, 5, 7) út hossza d7 = 11 + 7 = 18. Tovább az L6 és L7 között válogatunk. min(d6, d7) = min(14, 18) = d6 A 6-os csomópont bekerül az M-be, így 1 és 6 között a legrövidebb út L6 = (1, 4, 6). d 2=6 2

5

2 4

1 d 1 =0

9

3 d 3=4 4

5 d 4=9

6. lépés ■ M = {1, 3, 2, 4, 5, 6}, H = {7}.

5

d 5=11 7 7 d7 =18

6 d 6=14

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

237

Meghosszabbítjuk az L6 utat a (6, 7) éllel, így az L7 = (1, 4, 6, 7) út hossza 14 + 2 = 16 < d7 = 18. Változik d7 és értéke 14 lesz. Csak az L7-et választhatjuk. min(d7) = d7 Betesszük a 7-est az M-be, a minimális út 1-től 7-ig L7 = (1, 4, 6, 7). 5

d 2=6 2

5

d 5=11 7 7

2 4

1 d 1=0

3

d 3=4

d 7=16 2

9 4

5 d 4 =9

6 d 6=14

Ekkor az 1-es csúcsból induló legrövidebb utak a többi csúcsponthoz az alábbiak lesznek: d 1=0 1

4 9

3

2

d 3=4=d1+4 4

5

d 4=9=d1+9

2

5

5

d 5=11=d 2+5

7

d 7=16=d 6+2

d 2=6=d 3+2 6

2

d 6=14=d 4+5

Ha a gráf összefüggő, minden i  i0 csomóponthoz létezik út. Ha nem, csak az i0-t tartalmazó összefüggő komponenshez tartozó csomópontokhoz lesznek utak. Az algoritmusban di az Li utak hosszát jelöli i0-tól i-ig. Ezek a utak (i rögzí­ tett) az algoritmus során változnak (egyre rövidebbek lesznek) és az algoritmus végén Li a legrövidebb út lesz i0 és i között. A greedy stratégia, Dijkstra algoritmusában azon alapszik hogy minden lé­ pésnél azt az i csomópontot választjuk ki, amelynek az a tulajdonsága, hogy az Li, amely összeköti i0-t i-vel, a legrövidebb minden Lj legrövidebb út között, amelyet addig a lépésig megállapítottunk. Az i csomópont kiválasztása után ezt betesszük az M halmazba és hozzákez­ dünk új utak megkereséséhez. Mindig a legrövidebbet „tartjuk meg”. Az alábbi algoritmusban az élek költségeinek beolvasása előtt az L tömb ér­ tékeinek  kezdőértéket adunk, ezért a di értéke csak akkor különbözik -től, ha létezik él i és i0 között. Algoritmus Kezdőértékek(n,m,i0,L,d,előző,H):

238

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Be: n, m, i0 Minden i=1,n végezd el: Minden j=1,n végezd el: Lij  végtelen vége(minden) vége(minden) Ki: 'Élek hossza: ' Minden k=1,m végezd el: Be: i, j, h Lij  h Lji  h vége(minden) Minden i=1,n végezd el: di  Li0,i

{ i és i0 között van él }

Hi  igaz előzői  i0 vége(minden) di0  0 Hi0  hamis Vége(algoritmus) Algoritmus Minimum(n,d,H,ind,min): min  végtelen Minden j=1,n végezd el: Ha Hj akkor Ha dj < min akkor min  dj ind  j vége(ha) vége(ha) vége(minden) Vége(algoritmus)

{ i0 bekerül M-be, a többi marad H-ban }

{ j még a H-ban van }

Algoritmus Dijkstra: Kezdőértékek(n,m,i0,L,d,előző,H) Minimum(n,d,L,ind,min) Amíg min < végtelen végezd el: Hind  hamis { ind-et kivesszük H-ból és betesszük M-be } Minden j=1,n végezd el: Ha Hj és (Lind,j < végtelen) akkor dd  dind + Lind,j { j H-ban van és illeszkedik ind-del } Ha dd < dj akkor dj  dd előzőj  ind

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

239

vége(ha) vége(ha) vége(minden) Minimum(n,d,L,ind,min) vége(amíg) Ki(n,i0,L,d) Vége(algoritmus) Algoritmus Legrövidebb(i): Ha i ≠ i0 akkor Legrövidebb(előzői) vége(ha) Ki: i Vége(algoritmus) Algoritmus Ki(n,i0,H,d): Minden i=1,n végezd el: Ha i ≠ i0 akkor Ha Hi akkor Ki: 'Nincs út ', i0, ' és ', i, ' között.' különben { i az M-ben van } Ki: 'Legrövidebb út hossza ', i0, ' és ', i, ' között: ' Ki: di Ki: 'Az út: ' Legrövidebb(i) vége(ha) vége(ha) vége(minden) Vége(algoritmus)

10.4. Heurisztikus mohó algoritmusok Azt a greedy módszert, amely nem mindig határozza meg egy feladat optimális eredményét, heurisztikus greedy módszernek nevezzük (a görög heuriskein = felfedezni). Ahhoz, hogy bebizonyítsuk, hogy a mohó algoritmus nem állapítja meg mindig az optimális eredményt elég, ha találunk egy olyan esetet, amire a módszer nem határozza meg az optimumot. Ezeket még közelítő algoritmusok néven is ismerjük. Ezeknek a heurisztikus mohó algoritmusoknak azért van mégis fontosságuk, mert lehetővé teszik néhány sajátos eset kizárása után a feladat gyors meg oldá­ sát olyankor, amikor a pontos megoldás exponenciális időt igényelne. Más eset­ ben, megelégszünk egy „majdnem” optimummal, vagy egy közelítő eredménnyel, mivel a pontos eredményt meghatározó algoritmus költsége túlságosan nagy.

240

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

10.4.1.Utazóügynök Egy utazóügynök az áruját olyan körúton szeretné eladni, amely n városon ha­ lad át úgy, hogy a körút végén visszatér a városba ahol lakik. Adottak páronként a városok közötti közvetlen távolságok. Határozzuk meg az utazóügynök útvonalát úgy, hogy az a lehető legrövidebb legyen, és a kiindulópontot kivéve, minden városon csak egyszer haladjon át. A városokat 1-től n-ig számozzuk, a kiindulópont az 1-es város. Megoldás ■ A feladathoz egy nem irányított gráfot rendelünk: G = (X, U), ahol X a városok halmaza és U a várospárok halmaza, amelyek között közvetlen ösz­ szeköttetés van. A feladat szerint bármely két város között van közvetlen út, tehát a gráf teljes. Következik, hogy a G teljes gráfban egy minimális hosszúsá­ gú Hamilton-kört kell meghatároznunk (egy Hamilton-kör, olyan kör a gráfban, amely a gráf minden csomópontján pontosan egyszer halad át, és visszatér a ki­ indulópontba). Egy n csomóponttal rendelkező teljes gráfnak n!/2n = (n – 1)!/2 különböző Hamilton-köre van (az {1, 2, …, n} halmaz minden permutációja egy-egy kör­ nek felel meg, de mindenik felírható 2n különböző módon aszerint, hogy hon­ nan indulunk, és milyen irányban járjuk be a kört). A halmaz, ahonnan a legrö­ videbb Hamilton-kört ki kell választanunk, még n kicsi értékeire is nagy; ugyanakkor egyetlen Hamilton-kör meghatározása is csak költséges algoritmusokkal lehetséges. Minden pontos algoritmus, amely a feladatot megoldja exponenciális bonyolultságú, tehát hasznosnak bizonyul egy mohó algoritmus. A mohó stratégia szerint sorba vesszük a városokat, és minden lépésnél a legtöbbet „ígérőt” választjuk ki. Mivel a kiindulópont az 1-es város, ez lesz az első, amit kiválasztunk. A továbbiakban, ha a v1, v2,…, vi–1 városokat már kivá­ lasztottuk, a legtöbbet ígérő következő város az lesz, amely a legkisebb távol­ ságra van a vi–1-től és még nem volt kiválasztva. Ez a válogatás nem mindig vezet optimális eredményhez, mivel miközben haladunk az útvonal meghatározásával, a halmaz számossága, ahonnan válogat­ juk a városokat, egyre csökken és így előfordulhat, hogy az egy adott pillanat­ ban még kiválasztható városok mind nagy távolságra esnek az utoljára kiválasz­ tott várostól. A lokális minimum kiválasztása (az előző városhoz legközelebb fekvő kiválasztása) nem garantálja a globális minimumot (a legrövidebb útvo­ nala). Példa ■ A legrövidebb Hamilton-kör:

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

241

Cmin = {1, 4, 2, 3, 5, 1}, ennek hossza: 6 + 5 + 2 + 4 + 30 = 47. A mohó algoritmussal generált kör: C1 = {1, 2, 3, 4, 5, 1}, ennek hossza: 1 + 2 + 3 + 100 + 30 = 136.

2 1

1 40 20 6

30

2 5

3

4 3

5

100

4

Ha megcseréljük a kiindulópontot lehet, hogy találunk jobb megoldást. Pél­ dául a C2 = {2, 1, 4, 3, 5, 2} körnek, amelyet felírhatunk {1, 4, 3, 5, 2, 1} alak­ ban, a hossza 1 + 6 + 3 + 4 + 40 = 54, ami már jobban közelít 47-hez, mint 136. Algoritmus Kör_hossza(n,t,k,táv,kör): { meghatározzuk a legrövidebb kört, amely k-ból indul } Minden i=1,n végezd el: még_nincsi  igaz { még egy csomópont sincs a körön } vége(minden) kör1  k { a kör a k csomópontból indul } még_nincsk  hamis { k rajta van a körön } táv  0 { az aktuális kör hossza } Minden i=2,n végezd el: { vizsgáljuk a többi csomópontot } előző  köri-1 { megjegyezzük a körön található előző csomópontot és } { megkeressük azt a csomópontot, amely ehhez a legközelebb van } város  Legközelebb(n,t,még_nincs,előző) köri  város { a legközelebbi pont bekerül a körbe } még_nincsváros  hamis { város rajta van a körön } { az előző és a város közti távolságot hozzáadjuk a kör hosszához } táv  táv + telőző,város vége(minden) táv  táv + tváros,k { zárul a kör k-ban } Vége(algoritmus) Algoritmus Legközelebb(n,t,még_nincs,i): { egyszerű minimumkiválasztás, a függvény az i-hez } min  végtelen { legközelebb található csomópont indexét téríti vissza } Minden j=1,n végezd el: Ha még_nincsj és (tij < min) akkor min  tij jmin  j vége(ha) vége(minden) Legközelebb  jmin Vége(algoritmus)

242

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Algoritmus Utazóügynök(n,t,mintáv,minkör): { egyszerű minimumkiválasztás, a legrövidebb kört határozzuk meg } mintáv  végtelen Minden k=1,n végezd el: Kör_hossza(n,t,k,táv,kör) Ha táv < mintáv akkor minkör  kör mintáv  táv { a legkisebb hosszúságú kör hossza } vége(ha) vége(minden) Vége(algoritmus)

Megjegyzés ■ Ha a városok közötti távolságok nem annyira különbözőek, mint a példában, a megoldás is sokkal közelebb lesz az optimális megoldáshoz.

10.4.2.Gráfszínezés Legyen egy n csomópontú nem irányított gráf, amelynek adottak az élei. Hatá­ rozzuk meg a csomópontok legkevesebb színnel való befestését úgy, hogy bár­ mely két szomszédos csomópont különböző színű legyen. Megjegyzés ■ A térképszínezés egy sajátos esete a gráfszínező feladatnak, amelyben az éleket úgy adjuk meg, hogy ne metsszék egymást. Ezt a feladatot általában a backtracking módszerrel oldjuk meg. Ha n értéke kicsi, a backtrack­ ing jól használható, de nagy értékekre elfogadhatatlan. Ha mohó algoritmussal oldjuk meg a feladatot, a gráf csomópontjait egymás után úgy próbáljuk kiszínezni, hogy a szükséges színek száma minimális le­ gyen. A színeket 1-től n-ig számozzuk. Befestjük az 1-es csomópontot az 1-es színnel, majd a többi i (i = 2, ..., n) csomópont számára kiválasztjuk a legkisebb sorszámú színt, amely különbözik az illető csomóponttal illeszkedő csomópon­ tok színétől. De: nem bízhatunk abban, hogy a greedy stratégia optimális meg­ oldást talál. [17] Példa a) 3 szín

4

2. szín 3. szín

1. szín

1

3. szín

b) 4 szín (mohó algoritmussal talált megoldás) 4 2. szín

5

2

3 2. szín

1. szín

4. szín

1

2. szín 2

5

3

3. szín

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

243

Algoritmus Min_szín(szomszéd,szín,i): Minden j=1,i végezd el: vanj  hamis vége(minden) Minden j=1,i-1 végezd el: Ha szomszédij akkor vanszinj  igaz vége(ha) vége(minden) min  1 Amíg vanmin végezd el: min  min + 1 vége(amíg) Min_szín  min Vége(algoritmus) Algoritmus Színezés(n,szomszéd,színek_száma,szín): színek_száma  1 szín1  1 Minden i=2,n végezd el: színi  Min_szín(szomszéd,szín,i) { a legkisebb szín, amely különbözik a szomszédok színétől } Ha színi > színek_száma akkor színek_száma  színi vége(ha) vége(minden) Vége(algoritmus)

10.4.3.Összegkifizetés legkevesebb számú bankjeggyel Határozzuk meg egy módját az S pénzösszeg minimumszámú bankjegygyel va­ ló kifizetésének, tudva, hogy a b1, b2, …, bn címletű bankjegyek végtelen szám­ ban állnak a rendelkezésünkre. Megoldás ■ A pontos megoldást (ismerjük) a backtracking módszerrel adhatjuk meg. Most megpróbáljuk megoldani a feladatot egy mohó algoritmussal. Ha mini­ mum számú bankjegyet szeretnénk felhasználni, előbb ki kellene fizessük azt az összeget amit még ki lehet fizetni a legnagyobb címletű bankjeggyel. Követ ke­ zésképpen, a bankjegyeket értékük szerint csökkenő sorrendbe rendezzük: b1  b2  …  bn. Egymás után megállapítjuk azt a legnagyobb indexű bi bankjegyet, amivel ki lehet fizetni az S-ből még kifizetetlen összeget: szi = [S/bi] (S az összeg, amit a bi, …, bn bankjegyekkel fogunk kifizetni). [17]

244

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Ha így járunk el előfordulhat, hogy a fennmaradt összeget nem lehet kifizet­ ni a még kiválasztatlan bankjegyekkel, és így a mohó algoritmus nem vezet megoldáshoz (ez persze nem áll fenn, ha van 1 értékű bankjegyünk is). Példa ■ Ha S = 96 és a bankjegyek címletei b1 = 13, b2 = 12 és b3 = 3, az opti­ mális megoldás 8 darab 12 értékű bankjeggyel fizeti ki az összeget. A mohó algoritmus előbb kiválaszt sz1 = 7 bankjegyet a 13 értékűből, marad S = 5 amit nem lehet kifizetni a megmaradt bankjegyekkel. Ha a bankjegyek értéke b1 = 13, b2 = 12 és b3 = 1, az S = 96 összeg 12 bank­ jeggyel fizethető ki a greedy stratégiát használva: 7 darab 13 értékű és 5 darab 1 értékű. Észrevesszük, hogy ha túl sok nagy értékű bankjegyet használunk fel, előfordulhat, hogy a fennmaradt összeget túl sok kis értékű bankjeggyel kell ki­ fizetnünk. Algoritmus Összegkifizetés(n,b,S): Minden i=1,n végezd el: szi  [S/bi] { kifizetünk [S/bi] darab bi címletű bankjegyet } nrb  nrb + szi S  S - szi*bi Ha S = 0 akkor Kiír(i,sz,b,nrb,S)

Leállítjuk az algoritmust vége(ha) vége(minden) Ki: 'A mohó algoritmus nem talált megoldást.' Vége(algoritmus) Algoritmus Kiír(i,sz,b,nrb,S): Ki: S, ' kifizethető ', nrb, ' bankjeggyel: ' Minden j=1,i végezd el: Ha nrj > 0 akkor Ki: szj, ' darab a ', bj, ' bankjegyből.' vége(ha) vége(minden) Vége(algoritmus)

Az előző feladatoktól eltérően, e feladatban előfordulhat, hogy a greedy nem talál egyáltalán megoldást, pedig létezik megoldás. Egyébként a feladat back­ trackinges megoldása használta a greedy stratégia ötletét, így viszonylag hamar megtaláltuk az optimális megoldást, és leállíthattuk a rekurzív hívásokat, ami­ kor valamely részeredmény túllépte az eddig megtalált minimumot. A greedy és a visszalépéses keresés kombinálása hozzásegített a pontos megoldás gyors megtalálásához.

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

245

10.5. Kitűzött feladatok 1. Legyen két, egész számokat tartalmazó halmaz: A = {a1, a2, ..., am} és B = {b1, b2, …, bn} (1  m  n  100). Határozzuk meg a B halmaz azon X = {x1, x2, …, xm} részhalmazát, amelynek megfelelően az E = a1x1 + a2x2 + … + amxm kifejezés értéke a lehető legnagyobb. Példa ■ Ha m =3, n = 5, A = (2, 4, 3) és B = (5, –3, 8, –1, 2)  E = 51. Útmutatás ■ Növekvő sorrendbe rendezzük mindkét sorozatot. Az A halmaz negatív elemei mellé a B halmaz legkisebb elemeit, a pozitív elemek mellé a legnagyobbakat választjuk. 2. A kolozsvári színház műsorában n előadás szerepel. Mindenik esetében is­ mert a kezdés időpontja, valamint az előadás végének pontos ideje. Egy kü­ lönleges vendég kedvéért a színház be szeretné mutatni egyetlen nap folya­ mán minden előadását. Mivel csak egy színhely áll rendelkezésre, állapítsuk meg azt a maximális számú színdarabot, amelyeket a színház bemutathat anélkül, hogy az eredeti kezdési és zárási időpontokat változtatnák. 3. Adott n zeneszámot tartalmazó állomány hossza. Ezeket az állományokat ugyanazon a mágnes szalagon szeretnénk tárolni. Állapítsuk meg az állomá­ nyok optimális elrendezését a szalagon ahhoz, hogy az átlagos elérési idő a lehető legkisebb legyen. Megjegyzés ■ Egy állomány elérése érdekében minden előtte tárolt állo­ mányt el kell olvasnunk. 4. Egy vízműnek n fogyasztóhoz kell eljuttatnia a vizet. Ismert bármely két fo­ gyasztó közötti távolság (a csővezeték költsége) és a vízmű és minden fo­ gyasztó közötti távolság. Tervezzük meg azt a csőhálózatot, amelynek legki­ sebb az összköltsége. 5. Állapítsuk meg Kruskal algoritmusának egy változatával, hogy egy adott, nem irányított gráf összefüggő-e vagy sem. [17] 6. Egy országban van k olajfinomító és n benzinkút, ahova a benzint el kell jut­ tatni. A csővezetékeket úgy kell megtervezni, hogy minimális hosszúságú csövet kelljen lefektetni. A csővezeték csak benzinkútnál vagy finomítónál ágazhat el. Határozzuk meg, hogy mely csomópontok (olajfinomítók és/vagy benzinkútak) között kell kiépíteni a csővezetéket úgy, hogy a csővezeték hossza minimális legyen! Beolvassuk az olajfinomítók és a benzinkutak számát, valamint az olajfinomítók és a benzinkutak koordinátáit.

246

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Írjuk ki azon helyek indexeit, amelyek között csövet kell lefektetni a minimális hosszúságú csőhálózat kiépítéséhez. Az olajfinomítók indexét O, a benzinkutakét pedig B betű előzze meg! 7. Állapítsuk meg egy adott nem irányított gráf összefüggő komponenseit Kruskal algoritmusának egy változatával! [17] 8. Egy jótékonysági intézmény orvosi rendelőt működtet. Sajnos csak egyetlen rendelő áll rendelkezésre, ahol egy adott pillanatban egyetlen orvos rendel­ het. Az intézmény felkért n különböző szakorvost, hogy közölje a nap mely óráiban tudná fogadni a betegeket a rendelőben. Az n orvos megadta az [ei, vi] időintervallumokat. Határozzuk meg azt az órarendet, amelynek megfelelően a legtöbb orvos állhat majd a betegek rendelkezésére. [2] 9. Több sportoló azonos erőgépeken, de különböző ti ideig fog edzeni. Egy sportoló folytonosan edz és nem cseréli az edzéshez használt erőgépet. Ők csak akkor mehetnek pihenni, miután minden sportoló elvégezte az edzést. Osszuk be az m sportolót n erőgéphez úgy, hogy a lehető leghamarabb me­ hessenek pihenni. Beolvassuk az n és m számokat, valamint az idők ti (i = 1, 2, ..., m) soro­ zatát. Kiírjuk a közös edzés időtartamát, valamint minden erőgépre az edzé­ sek idejét, ameddig az illető gépre beosztott sportoló edzése tart. Példa ■ Legyen n =3 és m = 6. A 6 sportoló edzési ideje: (6, 5, 1, 3, 4, 7). A közös edzés 9 órát tart, ha mohó algoritmust (ebben az esetben greedy heu­ risztikát) alkalmazunk. A 3 erőgépen az edzések idői: (1, 7), (3, 6), (4, 5). A megoldásban csoportosítjuk az m sportoló edzési idejét úgy, hogy a legna­ gyobb összegű csoport összege legyen minimális. [2] 10. Adva van n + 1 cipősdoboz és n pár cipő, amelyek meg vannak számozva 1től n-ig. Az n pár cipő n dobozban található, a dobozok közül egy üres. Mi­ vel a cipők nincsenek a megfelelő dobozban, el kell rendezni ezeket úgy, hogy minden cipő a saját dobozába kerüljön. Munka közben csak egy pár ci­ pőt szabad kivenni a dobozból, amelyben található, és azonnal be kell tenni az üres dobozba. Állapítsuk meg a költöztetések sorozatát minimális számú művelettel. Beolvassuk a cipők számát. Feltételezzük, hogy az i-edik cipő eredetileg az i-edik dobozban van (i = 1, 2, ..., n) és az n + 1-edik doboz üres. Továbbá beolvasunk még n + 1 számot, amelyek közül az i-edik (i = 1, 2, ..., n) azt fejezi ki, hogy az i-edik dobozban milyen sorszámú cipőnek kell lennie. Az üres doboznak megfelelő szám 0. Példa Cipők száma = 4.

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

247

Kívánt elrendezés: (2, 3, 1, 0) Minimális költöztetések száma: 4 Magyarázat  1. lépés: a 3-as cipőt kivesszük a 3-as dobozból és betesszük a 4-esbe  2. lépés: az 1-es cipőt kivesszük az 1-es dobozból és betesszük a 3-asba  3. lépés: a 2-es cipőt kivesszük az 2-es dobozból és betesszük az 1-esbe  4. lépés: a 3-as cipőt kivesszük a 4-es dobozból és betesszük a 2-esbe Útmutatás ■ Ahhoz, hogy rendet teremtsünk a dobozokban, minden p cipőt a kívánt dobozba kell tennünk, természetesen, ha már nincs ott. Két esetet különböztetünk meg:  A doboz ahova a p cipőt kell tennünk, üres;  A doboz ahova a p cipőt kell tennünk foglalt (a q cipő által). Az első esetben elhelyezzük a p cipőt az üres dobozba. A második eset­ ben előbb ki kell üresítenünk a q cipő által elfoglalt dobozt, majd az így fel­ szabadított dobozba betesszük a p cipőt. 11. Költözik a múzeum. A tárgyakat kocka alakú, különböző méretű ládákba csomagolták. Kicsomagoláskor több személy dolgozik egyidőben. A rendet­ lenség elkerülése végett, azokba a helyiségekbe, ahol a kicsomagolás folyik felszereltek egy futószalagot, amelyre az üres ládákat helyezik, a nyitott fe ­ lükkel felfele. A futószalag végéhez egy gyereket állítottak, akinek az a fela­ data, hogy összeszedje a ládákat és úgy helyezze egyiket a másikba, (ha le­ hetséges), hogy végül a ládacsomagok száma a lehető legkisebb legyen. Az igazgató úgy látja, hogy a gyerek tanácstalan, ezért még hozzáteszi:  A ládákat az érkezésük sorrendjében kell a futószalagról levenni.  Az aktuális láda csak egy nála nagyobb méretű ládába helyezhető.  Ha nincs olyan megkezdett csomag, amelybe elhelyezhető az aktuális láda, akkor ez a láda egy új csomag első ládája lesz.  Egy megkezdett csomagba csak egyetlen láda helyezhető, vagyis nem le­ het két ládát egymás mellé helyezni még akkor sem, ha ez egyébként le­ hetséges volna.  Egy elhelyezett ládát többé nem szabad mozgatni.  Egy megkezdett csomag nem helyezhető egy másik csomagba még akkor sem, ha ez egyébként lehetséges volna.  Egyetlen ládát sem lehet figyelmen kívül hagyni.

248

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Állapítsuk meg, hogy minimálisan hány ládába lehet a ládasorozatot ösz­ szepakolni, továbbá, hogy mely ládák lesznek egybepakolva. Példa Ládák száma = 10 Ládák méretei: 4, 1, 5, 10, 7, 9, 2, 8, 3, 2. Ládacsomagok száma: 4 1. csomag: 4, 1 2. csomag: 5, 2 3. csomag: 10, 7, 3, 2 4. csomag: 9,8 Útmutatás ■ A feladatban adott sorozat minimális számú csökkenő részsoro­ zatra bontását kell elvégeznünk. Megoldható egy mohó algoritmussal, amely mindig az első olyan ládába csomagol, amelybe lehetséges. Észrevesszük, hogy így a ládacsomagok legfelső ládáinak mérete növekvő sorozatot alkot, tehát a megfelelő csomag megkeresése lehetséges bináris kereséssel. Fakultatív ■ Határozzuk meg azt a legnagyobb ládaszámot amit a megírt programmal még fel tudunk dolgozni! 12. Az állami kincstárba n (n  2000) zsákban hozzák a pénzérméket. A raktár főnöke ismeri minden zsák tartalmát (az érmék számát) és át szeretné ren ­ dezni minden zsák tartalmát az érmék költöztetésével úgy, hogy végül min­ den zsákban azonos számú érme legyen. Segítsünk a főnöknek, hogy, amennyiben ez lehetséges a legkevesebb költöztetéssel érje el a célját. Ha nincs megoldás, írjunk ki egy megfelelő üzenetet! A megoldásban a költöz­ tetést a következő alakban adjuk meg: honnan (zsák sorszáma), hova (zsák sorszáma), hány Az összes érme száma  1 000 000 000. Példa Zsákok száma: 10 Az érmék száma zsákonként: (13, 14, 1, 3, 5, 15, 7, 9, 11, 12) Útmutatás ■ Ahhoz, hogy létezzen eredmény, szükséges, hogy n osztója legyen a számok összegének. Az egyszerű greedy stratégia szerint csökkenő sorrendbe rendezzük a számokat: (15, 14, 13, 12, 11, 9, 7, 5, 3, 1). A továbbiakban kiszámítjuk, hogy hány érmét kellene tartalmazzon min­ den zsák, és a legtöbb érmét tartalmazó zsákból átteszünk a legkevesebb ér­

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

249

mét tartalmazó zsákba annyit, amennyi szükséges az utóbbiba, vagy annyit, amennyi felesleges az előbbiben. Ezt addig folytatjuk, amíg minden zsákban kiegyenlítődik az érmék száma. A példa esetében a következő költöztetéseket végezzük: 1. A 10-es zsákból az 1-es zsákba átteszünk 6 érmét. 2. A 9-es zsákból az 1-es zsákba átteszünk 2 érmét. 3. A 9-es zsákból a 2-es zsákba átteszünk 3 érmét. 4. A 8-as zsákból a 2-es zsákba átteszünk 3 érmét. 5. A 8-as zsákból a 3-es zsákba átteszünk 1 érmét. 6. A 7-es zsákból a 3-es zsákba átteszünk 3 érmét. 7. A 6-os zsákból a 4-es zsákba átteszünk 2 érmét. Tehát az eredmény 7 lépés. Megpróbálhatjuk gyümölcsöztetni a következő jobb ötletet: kiszámítjuk minden elem esetében a „pluszt”. Lehet, hogy lesznek olyanok, amelyeket „párba” lehet állítani (a példában *-gal jelöltük ezeket): Zsák 1 2 3 4 5 6 7 8 sorszáma * ** *** *** ** érmék száma 1 3 5 7 9 11 12 13 plusz/minusz –8 –6 –4 –2 0 2 3 4 Így az eredmény 5 lépés: A 10-es zsákból a 2-es zsákba átteszünk 6 érmét. A 8-as zsákból a 3-as zsákba átteszünk 4 érmét. A 6-os zsákból a 4-es zsákba átteszünk 2 érmét. A 9-es zsákból az 1-es zsákba átteszünk 5 érmét. A 7-es zsákból az 1-es zsákba átteszünk 3 érmét.

9 14 5

10 * 15 6

13. Egy tolvaj betört egy hentesüzletbe, ahol n áru közül válogat. Minden árunak ismeri a súlyát és az értékét. Mivel a hátizsákjába legtöbb S súly fér, szeretne úgy válogatni, hogy a nyeresége maximális legyen. (Ha egy áru nem fér be egészében a hátizsákba, a tolvaj nem vághat le belőle.) 14. Egy kiállítási csarnokban a pavilonokat egy m  n méretű négyzetháló men­ tén jelölik ki a résztvevő cégek számára. Egy őrző cég szerződést kötött a cé­ gek egy részével, amelynek értelmében felügyelet alatt tartják az illető pavi­ lonokat. A szervezők lehetővé teszik az őrző cégnek, hogy megfigyelőhelye­ ket szereljenek a pavilonok fölé. Egy ilyen helyről egy pavilon csak akkor látható, ha ez egyvonalban található a megfigyelőhellyel sorosan vagy oszlo­ posan és, ha a megfigyelőhely és a pavilon között nem található más pavilon. Ugyanakkor, nem látható az a pavilon, amely fölött található a megfigyelő­ hely.

250

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER)

Határozzuk meg annak a legkevesebb megfigyelőhelynek a számát, ame­ lyekkel a szerződés szerint biztosítani lehet a pavilonok őrzését. Beolvasunk egy n soros és m oszlopos tömböt, ahol az 1 érték egy olyan pavilont jelent, amelyet őrizni fognak és a 0 olyan pavilont, amelyet nem kell őrizni. Írjuk ki a legkevesebb megfigyelőhelynek a számát és a megfigyelőhe­ lyek sor és oszlopindexeit is. [2] Példa ■ Legyen a csarnoknak megfelelő kétdimenziós tömb: 11100 01000 00100 01001 01001 A példa esetében, ha (közelítő) mohó algoritmussal oldjuk meg a felada­ tot, 4 megfigyelőhelyre lesz szükség a következő helyeken: (1, 2), (1, 1), (4, 3), (5, 3).  Meghatározzuk az első olyan (i, j) helyet, ahonnan maximális számú pavi­ lont lehet megfigyelni.  (i, j)-t betesszük az eredménybe.  Megjelöljük azokat a pozíciókat, amelyeket az (i, j) helyről meg lehet fi­ gyelni ahhoz, hogy ne vizsgáljuk ezeket még egyszer.  Aktualizáljuk a felvigyázott pozíciók számát. 15. Az egyetem kapott n számítógépet, amelyeket különböző épületekbe és ter­ mekbe fognak elhelyezni úgy, hogy a számítógépek egy lineáris hálózatot al­ kossanak: minden számítógép két másikkal lesz összekötve, kivéve az elsőt és az utolsót, amelyek csak egy-egy számítógéppel lesznek összekötve. Be­ olvassuk az n számítógép x és y koordinátáit. Írjuk ki a hálózatot úgy, hogy minimális hosszúságú kábelre legyen szükség. Példa ■ Legyen n = 4, és a számítógépek koordinátái: (1, 1), (2, 1), (1, 2), (3, 1). Egy greedy heurisztika a kábel minimális hosszának 3.00-at ad meg, ha a számítógépeket a következő sorrendben kötjük össze: (3, 1, 2, 4). Kiválasztjuk az első számítógépet, majd megkeressük a hozzá legköze­ lebb találhatót stb. Ezt az algoritmust megismételjük úgy, hogy más-más ki­ indulópontot választunk. Megtartjuk a legrövidebb kábelt igénylő megoldást. [2]

Algoritmusok listája Abszolút_érték 34 Abszolút_érték_másként 35 Arány 113 Autó_kölcsönzés(n,a,b,max,M) 261 Backtracking(i) 191, 198, 204, 206 Backtracking(i,j) 214 Backtracking(i,j,lépés) 213 Beszúró_Rendezés(n,a) 158 Bin_keres(x,bal,jobb,keresett,közép) Bin_Keres_Iteratív(n,x,keresett,közép) Bűvös_négyzet(n,t,bűvös) 119 Descartes_szorzat_1(i) 173 Descartes_szorzat_2(i) 174 Descartes_szorzat_3(i) 175 Deszkák(m,h1,h2,t) 233 Determináns_értékének_kiszámítása Dijkstra 279 DivImp(bal,jobb,eredm) 224 Döntés_1(n,x,talált) 86 Döntés_2(n,x,talált) 86 Döntés_3(n,x,talált) 87 Döntés_4(n,x,mind) 87 Egyesítés(n,x,m,y,db,z) 107 Eukleidész 36 Eukleidész_1 58 Eukleidész_2 59 F18_1(n,egyetemisták,jövedelmek) F18_2(n,egyetemisták,jövedelmek,segéd) F18_3(n,egyetemisták,jövedelmek,segéd) Fakt(n) 171 Felcserél 50 FelcserélésesRendezés(n,a): 155 Feldolgoz(n,x,eredmény) 85 Feloszt(a,b,c,d) 244 Feloszt_2(bal,jobb,i) 239 Fib(n) 178 Fibo(n) 178 Fibonacci 67, 68 Fibonacci_szám_e 69 Fizet(i,S) 207 Fizet_Min(i,S,hánydarab) 208 Főnök 117 Fordít 168 Fordított_szám 72 Generál(a,n,S) 230 Greedy_1(L,M) 251 Greedy_2(n,a,M) 251 Gyors_hatvány(a,k) 227

228 229

142

96 97 97

Gyorshatvány 78 Halmaz_1(n,x,ok) 104 Halmaz_2(n,x) 105 HalmazPartíciók(i,k) 184 Hanoi_1(n,A,B,C) 241 Háromszög 70 Hátizsák(n,S,súly,érték,sorszám,x) Hőmérséklet 114 Iteratív_Backtracking 189 Játékok(i) 202 Javított_Buborékrendezés_1(n,a) 153 Javított_Buborékrendezés_2(n,a) 153 Javított_Buborékrendezés_3(n,a) 154 KamatosKamat(év,összeg) 185 Keres(a,b,c,d,i) 244 Keres(n,S,X,i) 44 Keres_1(n,x,hely) 91 Keres_2(n,x,i) 91 Keres_3(n,x) 92 Keres_4(n,x,törölt) 92 Keres_5(n,x) 93 Keres_6(n,x,k,újsor) 93 Keresés_Csere(n,a,i,d) 142, 143 Keresztmetszet(n,x,m,y,db,z) 106 Királynő(i) 195 Kiválasztás(n,x,sorszám) 89 Kiválogatás_1(n,x,db,pozíciók) 98 Kiválogatás_2(n,x) 99 Kiválogatás_3(n,x,db)99 Kiválogatás_4(n,x) 100 Költöztet(n,i,j) 242 Konverzió 75, 76, 77 Konverzió(szám,újszám,hatvány)177 Kör_hossza(n,t,k,táv,kör) 282 Közös_Elem(n,x,m,y,van,érték,xh,yh) Ládarendezés(a,n) 159 Legkisebb(n,t,L,min_ind) 255 LegkisebbOsztó_1(szám,osztó) 90 LegkisebbOsztó_2(szám,osztó) 90 Legközelebb(n,t,még_nincs,i) 282 Legnagyobb_tárgyak_a_fényképen Lnko(m,n) 171 Lnko_és_Lkkt(a,b,lnko,lkkt) 40 Maximum 50 Maximum_harmadik_változata 51, 52 Maximum_helye(n,x,hely) 96 Maximum_másként 51 Maximumkiválasztás(n,x,max) 95

263

107

215

10. MOHÓ ALGORITMUSOK (GREEDY MÓDSZER) MaxÖsszegűRészsorozat_1(n,t,MaxS) 121 MaxÖsszegűRészsorozat_2(n,t,MaxS) 122 MaxÖsszegűRészsorozat_3(n,t,MaxS,eleje,vége) 122 MaxÖsszegűRészsorozat_4(n,t,eleje,vége,MaxS) 123 Megállók(a,n,M) 257 Megfelel(i) 189 Megszámlálás(n,x,db) 94 Mélységi_bejárás(i) 206 Min_szín(szomszéd,szín,i) 284 Minimális_feszítőfa(n,m,x,y,költség) 267 Minimum(bal,jobb) 226 Minimum(n,a) 48 Minimumkiválasztás(n,a) 157 Négyzetgyök 116 Nem_támad(i) 194 Növekvő_sorrendbe_rendezés(n,a) 153 Nullázás(n,a,i) 144 Orosz_szorzás(a,b,p) 41 Összefésül(bal,közép,jobb) 234 Összefésül_2(n,x,m,y,db,z) 110 Összefésül_3(n,x,m,y,db,z) 111 Összefésülés_1(n,x,m,y,db,z) 109 Összeg 112 Összeg(n,a,k,Pozitívak) 252 Összeg(szám) 172 Összegkifizetés(n,b,S) 285 Összegszámítás(n,x,összeg) 84 Osztály 118 Osztás 35 Palindrom 37 Párok 113 Páros 36 Partíció(i,n) 182 Polinom_értéke(n,a,x,P) 138 Polinomok_szorzata(n,m,a,b,c) 139 PolÖsszeg(m,P,n,Q,r,S) 139 Prím 60 Prim(n,am,kiv1,kiv2,összköltség) 268 Prím_1 61 Prím_2 61 Prím_3 62 Prím_5 63

Prímek_1 64 Prímek_2 65 Prímek_3 66 QuickSort(bal,jobb) 237 Rekurzív_Backtracking(i) 190 Rendez(bal,jobb) 234 Részhalmaz(i) 181 Részhalmazok(i) 176 Sorcsere(n,a,i,j,d) 143 Sorozat(i,m,p,q) 180 Sorrend(n,t,M,átlag) 255, 256 Strázsa_helye(bal,jobb) 238 Számgenerálás 73 Számjegyes_Rendezés(n,a,d) 160 Számol_1(n,a,p) 230 Számol_2(n,a,p) 231 Számol_3(n,a,p) 231 Szavakat_fordít_1(n) 170 Szavakat_fordít_2(i) 170 Szavazás 53 Szétválogatás_1(n,x,ydb,y,zdb,z) 101 Szétválogatás_2(n,x,ydb,y) 102 Szétválogatás_3(n,x,db) 103 Színezés(n,szomszéd,színek_száma,szín) Szjegy(szám,szj) 161 Szorzat(bal,jobb) 225 Szürjektív(i) 203 Tárgy(i,j) 214, 215 Teljes_negyzet_bottom_up 133 Teljes_négyzet_e 115, 132 Teljes_négyzet_top_down 134 Törzstényezők 74 Út(i,j,lépés) 211, 212 Út(i,vízszintes,függőleges) 200 Utazóügynök(n,t,mintáv,minkör) 282 Válogatásos_Rendezés_1(n,a) 156 Válogatásos_Rendezés_2(n,a) 156 Variáció(i) 196, 198 Zárójel(i,ny,cs) 199

253

284

Szakirodalom 1. Baase S. – Computer Algorithms, Addison-Wesley, 1983. 2. Bălan, G., Giurgea, M., Ionescu C. – Informatică pentru grupele de excelenţă, clasa a X-a, Editura Dacia, Cluj, 2003. 3. Bălănescu T., Gavrilă S., Georgescu H., Gheorghe M., Sofonea L., Văduva I. – Pascal şi Turbo Pascal, Editura Tehnică, Bucureşti, 1992. 4. Brassard G., Bratley P. – Algorithmics – Theory and Practice, PrenticeHall, Inc, 1988. 5. Cormen T., Leiserson C., Rivest R. – Algoritmusok, Műszaki Könyvkiadó, Budapest, 1997. 6. Cormen T., Leiserson C., Rivest R., Stein, C. – Új algoritmusok, Scolar, Budapest, 2003. 7. Frenţiu M., Lazăr Ioan – Bazele programării, Proiectarea algoritmilor, Editura Universităţii Petru Maior, Tg. Mureş, 2000. 8. Ionescu E., Haidu, S., Vaida D. – Informatică pentru grupele de excelenţă, clasa a IX-a, Editura Dacia, Cluj, 2003. 9. Georgescu H. – Tehnici de programare, kiadatlan kurzus. 10. Georgescu H., Livovschi L. – Analiza şi sinteza algoritmilor, Editura Ştiinţifică şi Enciclopedică, Bucureşti, 1986. 11. Horowitz E. – Fundamentals of Data Structures in C++, Computer Science Press, 1995. 12. Kása Z. – Algoritmusok tervezése, Stúdium Könyvkiadó, Kolozsvár, 1994. 13. Knuth D. E. – A számítógép-programozás művészete, I, II, III kötet, Editura Tehnică, Bucureşti, 1973, v. magyarul 1992. 14. Oltean M. – Culegere de probleme cu rezolvări în Pascal, Editura Libris, Cluj, 1997. 15. Oltean M. – Proiectarea şi implementarea algoritmilor, Editura Computer Libris Agora, Cluj, 1997. 16. Rancea D. – Limbajul Pascal, Editura Computer Libris Agora, Cluj, 1998.

256

SZAKIRODALOM

17. Rancea D. – Analiza şi proiectarea algoritmilor, Editura Computer Libris Agora, Cluj, 1999. 18. Rónyai, L., Ivanyos, G., Szabó, R. – Algoritmusok, Typotex, Budapest, 1999. 19. Wirth N. – Algorithms + Data Structures = Programs, Prentice Hall Inc., 1976. 20. Sedgewick R. – Algorithms in C++, Addisson-Wesley, 1992. 21. Storer, J.A. – An Introduction to Data Structures and Algorithms, Birkhauser Springer 2002. 22. Szlávi P., Zsakó L. – Elemi programozási tételek, Neumann János Számítógép-tudományi Társaság, Budapest, 2001. 23. Szlávi P., Zsakó L. – Összetett programozási tételek, Neumann János Számítógép-tudományi Társaság, Budapest, 2001. 24. Gazeta de Informatică, Computer Press Agora, Tg. Mureş, 1993-2004. 25. Középiskolai tankönyvek – Ábel Könyvkiadó, 2002-2005.  Balázs K., Kovács B., IX. osztály  Kovács B., IX, X. és XI. osztály  Incze K., Jakab I.T., Ignát J.A., IX. és X. osztály  Avornicului M., Buzogány L., Lukács S., IX. osztály