ALGORITMUSOK ÉS PROBLÉMAOSZTÁLYOK (1. előadás) Programozási feladatok megoldásának lépései 1, a feladatok meghatározása -egyértelmű, rövid, tömör, pontos 2, a feladat algoritmusának elkészítése jól definiált (automatizálható, programozható) számítási eljárás egy meghatározott számítási feladat megoldására, amely bementként bizonyos értékeket kap és kimenetként bizonyos értékeket állít elő. Egy algoritmust helyesnek mondunk, ha minden bemenetre megáll és helyes eredményt ad. A helyes algoritmus megoldja az adott számítási feladatot. Általános feladatok fő részei: - a bemenő adatok bevitele - azokon a szükséges műveletek elvégzése - az eredmények megjelenítése, rögzítése, kiíratása Érdekes az algoritmusok hatékonysága két szempontból is: - felhasznált időben (ezt használjuk rendszerint az algoritmus bonyolultsága mértékeként) - felhasznált memóriában Ugyanannak a feladatnak a megoldására fejlesztett algoritmusok nagy mértékben eltérő hatékonyságot mutathatnak. 3, kódolás - a kész algoritmus valamilyen konkrét programnyelvre való lefordítása Program: a gép számára érthető formájú meghatározott sorrendű, véges, gépi utasítássorozat. 4, tesztelés 5, dokumentálás: felhasználói dokumentáció és fejlesztői dokumentáció Algoritmusleíró eszközök Céljuk: a megoldás menetének géptől független, szemléletes, a logikai gondolatmenetet, a szerkezeti egységeket világosan tükröző leírása. Legismertebb algoritmusleíró eszközök: a, folyamatábra b, mondatszerű leírások 1
Folyamatábrák A folyamatábra a feladat megoldási lépéseinek sorrendjét - művelettípusonként különböző geometriai alakzatok felhasználásával - szemléltető ábra. A felhasznált szimbólumok és jelentésük a következő: START STOP - ún. határszimbólumok, az algoritmus elejét és végét jelzik - beolvasó és kiíró művelet - értékadás, művelet végrehajtás, aritmetikai kifejezés kiértékelése n i - elágazás. A feladat végrehajtása, egy a rombuszba írt feltétel alapján, egyik vagy másik irányba folytatódik. A rombusznak két kimenete van, amelynek (i, n) jelzéssel vannak ellátva. Az i kimenetet kell folytatni, ha a feltétel teljesül (igaz), különben (hamis esetén) az n kimenetet kell követni. - gyakran találkozhatunk a számlálós ciklus jelölésére a következő szimbólummal is 2
A folyamatábrán az algoritmus haladási irányát nyilakkal jelöljük. Abban az esetben, ha a nyilakat elhagyjuk a folyamatábrát fentről lefelé, illetve balról jobbra kell követni. A folyamatábrák hátránya, hogy elemei nem felelnek meg az algoritmuskészítés során felhasználható, a strukturált programozásra jellemző struktúráknak (szekvencia, elágazás, ciklus). Használatukkal igen könnyen előállíthatók olyan bonyolult szerkezetek is, melyek megértése azután nagyon nagy problémát jelent. Nézzük egy nagyon egyszerű feladat algoritmusát folyamatábrán szemléltetve: FELADAT: Kérjünk be a billentyűzetről N (>0, egész) számot és számoljuk ki az összegüket! 3
ALGORITMUSOK ÉS PROBLÉMAOSZTÁLYOK (2. előadás) Mondatszerű leírások A mondatszerű leírások szemléletesebb módja az, amikor az algoritmus leírására mondatok helyett csak ún. mondatszerű szerkezeteket használjuk. Mondatszerű szerkezetek egymásutánjával írjuk le a feladat megoldását. Ennél a leírási módnál az algoritmus szerkezeti egységeit bekezdéses struktúra segítségével szemléltetjük. Az egyértelműség kedvéért meg kell határozni, milyen műveleteket (utasításokat) tartalmaz az így készített algoritmus leíró nyelv és utasítás fajtánként, hogyan definiáljuk ezeket a mondatszerű szerkezeteket! Beolvasó és kiíró utasítás Formája : Be: <változók felsorolása> Ki: <kifejezések felsorolása> // az adatokkal szemben támasztott // követelmények // a kiírás formájára vonatkozó // követelmények Értékadó utasítás Formája: <változó> := <kifejezés> // Az utasítás hatására a baloldalon álló változó felveszi a jobboldalon álló kifejezés értékét. Elágazások ( feltételes utasítások) Formája : A, Ha (<feltétel>) akkor // Ha az adott feltétel teljesül akkor az <utasítás(ok)> // utasítás(ok) kerül(nek) végrehajtásra és a // feladat megoldása a után folytatódik. // Ha a feltétel nem teljesül, akkor a feladat a Ha // vége után folytatódik. B, Ha (<feltétel>) akkor // Ha az adott feltétel teljesül akkor az <utasítás(ok)1> // utasítás(ok)1 kerül(nek) végrehajtásra. Ha a különben // feltétel nem teljesül, akkor az utasítás(ok)2 <utasítás(ok)2> // kerül(nek) végrehajtásra. Mindkét esetben a // feladat megoldása a után folytatódik. C, Elágazás // Sorban vizsgálja a feltételeket. Amikor <feltétel1> esetén <utasítás(ok)1> // megtalálja az első teljesülő feltételt, <feltétel2> esetén <utasítás(ok)1> // a hozzátartozó utasítás(ok) kerül(nek) // végrehajtásra.ha nem talált egyetlen egy <feltételn> esetén <utasítás(ok)n> // feltételt sem, amely teljesül, akkor az egyéb esetben <utasítás(ok)n+1> // egyéb esetben - hez tartozó utasítás(ok) Elágazás vége // kerül(nek) végrehajtásra. Minden // esetben a feladat megoldása az // Elágazás vége után folytatódik. 4
Ciklusszervező utasítások Formája: A, Ciklus <ciklus-változó> := <kezdőérték> -től <végérték> -ig <lépésköz> -esével <ciklusmag utasításai> // Végrehajtja a ciklusmag utasításait a ciklus-változó kezdőértékével. Ezután növeli a ciklus- // változó kezdőértékét a lépésközzel, és az új értékkel hajtja végre a ciklusmag utasításait. Ez // a folyamat addig ismétlődik, amíg a ciklus-változó kisebb vagy egyenlő, mint a végérték. // Ha a ciklus-változó értéke nagyobb, mint a végérték, akkor a feladat megoldása a Ciklus // vége után folytatódik. Ha a kezdőérték nagyobb, mint a végérték, akkor a ciklusmag // utasításai egyszer sem kerülnek végrehajtásra. A lépésköz, 1 esetén, elhagyható. Szokásos // ezt a szerkezetet számlálós ciklusnak nevezni. B, Ciklus amíg <feltétel> <ciklusmag utasításai> // A ciklusmag utasításai addig hajtódnak végre amíg a feltétel igaz, s ha a feltétel hamis // akkor a program végrehajtása a után folytatódik. Szokásos elnevezés: // elöl tesztelős ciklus. C, Ciklus <ciklusmag utasításai> amíg <feltétel> // A ciklusmag utasításai addig hajtódnak végre amíg a feltétel igaz, s ha a feltétel hamis // akkor a program végrehajtása a után folytatódik. A ciklusmag utasításai // egyszer mindenképpen végre hajtódnak. Elnevezés: hátul tesztelős ciklus Egyéb jelölések: // kiegészítések, megjegyzések (már használtuk!) : ha egy soron belül több utasítást használunk <tomb-név>[ ] - tömbök jelölése ha teljes algoritmust írunk: <algoritmus utasításai> ha eljárást( függvényt írunk): <eljárásnév>(<paraméterek>) <eljárás utasításai> <eljárásnév> vége Eljárás esetén a paraméterek az eljárás bemeneteinek a felsorolása. Az eljárás kimeneteit a Return <kifejezések felsorolása> 5
segítségével adjuk meg, amely a visszatérési értékeket adja vissza és az eljárás végrehajtását fejezi be. A megadott szabály kivétele a tömbök. Ha egy tömb szerepel paraméterként, akkor bemenő és kimenő paraméter is, azaz, ha az eljárásban módosítjuk a tömb paramétert, akkor a módosítások érvényesülnek az eljárás végrehajtása után az eljárást hívó algoritmusban is. Nézzük a szokásos feladat ( N>0 billentyűzetről olvasott egész szám összegének a kiszámítása) mondatszerű leírásának algoritmusát: Bemenet: N (az összeadandó számok száma) Kimenet: Szum (a képzett összeg) Be: N // N>0 egész Szum:=0 Ciklus i:=1-től N -ig Be: A Szum:= Szum+A Ki: Szum 6
ALGORITMUSOK ÉS PROBLÉMAOSZTÁLYOK (3. előadás) Egyszerű algoritmusok Aki már több programot készített különböző témákban, bizonyára tapasztalta, hogy az egyes problémák, feladatok, részfeladatok tipizálhatóak, megoldásukhoz gyakorlatilag ugyanaz az algoritmus szükséges legfeljebb a körítés azaz a feladat szövege, a változók, konstansok mások és mások. Ebben a részben most néhány egyszerű, ilyen típus algoritmus következik. Összegzés Adva van egy N elemű számsorozat, és ki kell számolni az elemeinek az összegét! A sorozat elemeit ennél a tételnél és a következőkben is az N elemű A[1..N] vektorban tároljuk. Bemenet: A[1..N] Kimenet: Szum Szum:=0 Ciklus i := 1-től N-ig Szum:=Szum+A[i] Példa az összegzés alkalmazására: A hét minden egyes napján megmértük egyszer a hőmérsékletet s az értékeket a HOM[7] vektorban tároljuk. Számítsuk ki a heti átlaghőmérsékletet! Atl:=0 Ciklus i := 1-től 7-ig Atl:=Atl+Hom[i] Atl:=Atl/7 Eldöntés Adva van egy N elemű sorozat, és egy a sorozat tagjain értelmezett T tulajdonság. Olyan algoritmust kell írni, amely eldönti: van-e a sorozat tagjai közt legalább egy T tulajdonságú. Bemenet: A[1..N], T tulajdonság Kimenet: Eredmény ( VAN T tulajdonságú vagy NINCS T tulajdonságú ) i:=1 Ciklus amíg i<=n és A[i] nem T tulajdonságú 7
i:=i+1 Ha( i<=n) akkor Eredmény := VAN T tulajdonságú különben Eredmény := NINCS T tulajdonságú Példa az eldöntésre: Egy N=20 fős osztály tanulóinak magasságát a Mag[20] elemű vektorban tároljuk. A feladat annak eldöntése, hogy van-e 180 cm-nél magasabb tanuló. i:=1 Ciklus amíg i<=20 és Mag[i]< =180 i:=i+1 Ha (i<=20) akkor Eredmény := VAN 180-nál magasabb különben Eredmény := NINCS 180-nál magasabb Kiválasztás Adva van egy N elemű sorozat, és egy a sorozat tagjain értelmezett T tulajdonság. Tudjuk, hogy a sorozat tagjai között van T tulajdonságú. Olyan algoritmust kell írni, amely megadja a sorozat első T tulajdonságú tagjának a sorszámát. Bemenet: A[1..N], T tulajdonság Kimenet: Sorszam i:=1 Ciklus amíg A[i] nem T tulajdonságú i:=i+1 Sorszam:=i Példa kiválasztásra : Tudjuk, hogy az N=20 tagú osztály tagja Tóth Mihály. Hányadik a rendezett névsorban? Algoritmus 8
i:=1 Ciklus amíg NEV[i] <> 'Tóth Mihály' i:=i+1 Sorszám:=i Lineáris keresés Adva van egy N elemű sorozat, és egy a sorozat tagjain értelmezett T tulajdonság. Olyan algoritmust kell írni amely eldönti, hogy van- e T tulajdonságú elem a sorozatban és ha van hányadik az első T tulajdonságú elem.( előbbi két algoritmus összegzése) Bemenet: A[1..N], T tulajdonság Kimenet: Index (a sorozat első T tulajdonságú elemének sorszáma, ha nincs, akkor 0) i:=1 Ciklus amíg i<=n és A[i] nem T tulajdonságú i:=i+1 Ha (i<=n) akkor Index:=i különben Index:=0 Megszámlálás Adva van egy N elemű sorozat, és egy a sorozat tagjain értelmezett T tulajdonság. Olyan algoritmust kell írni amely megszámlálja a T tulajdonságú elemeket. Bemenet: A[1..N], T tulajdonság Kimenet: DB DB:=0 Ciklus i:=1-től N-ig Ha (A[i] T tulajdonságú) akkor DB:=DB+1 Példa megszámlálásra: Adott egy N=20 fős osztály matematika eredménye a JEGY[20] vektorban tárolva. Számoljuk össze a bukottakat! 9
DB:=0 Ciklus i:=1-től 20-ig Ha (JEGY[i] =1 ) akkor DB:=DB+1 Kiválogatás Adva van egy N elemű sorozat, és egy a sorozat tagjain értelmezett T tulajdonság. Olyan algoritmust kell írni, amely a T tulajdonságú elemek sorszámát egy külön B[] vektorba gyűjti. Bemenet: A[1..N], T tulajdonság Kimenet: B[1..M], ahol M az A sorozat T tulajdonságú elemeinek a száma M:=0 Ciklus i:= 1-től N-ig Ha (A[i] T tulajdonságú) akkor M:=M+1 : B[M]:=i Példa kiválogatásra: Két hétig naponta megmértük a hőmérsékletet. Válogassuk ki a fagypont alatti napok sorszámát. j:=0 Ciklus i:=1- től 14-ig Ha (Hom[i] < 0) akkor j:=j+1 : Fagy[j]:=i Maximum kiválasztása Adva van egy N elemű sorozat. Meg kell keresni a sorozat legnagyobb elemének a sorszámát. Bemenet: A[1..N], T tulajdonság Kimenet: Maxindex INDEX:=1 Ciklus i:=2-től N ig Ha ( A[INDEX] < A[i] ) akkor INDEX:=i 10
Maxindex:= INDEX Minimum kiválasztásnál csak a feltételes utasítás feltétele fordul meg. Abban az esetben, ha a kérdést úgy tesszük fel, hogy menyi a legnagyobb érték, akkor egy másik megoldást is kaphatunk : Bemenet: A[1..N], T tulajdonság Kimenet: Maxertek Ertek:= A[1] Ciklus i:=2-től N-ig Ha ( Ertek < A[i]) akkor Ertek:= A[i] Maxertek:=Ertek Megtehetjük azt is, hogy a két eljárást egybeépítjük. Példa maximum kiválasztásra: Készítsünk olyan eljárást, amely N ember magasságának ismeretében megadja a legmagasabb személy magasságát, valamint megjelöl egy ilyen embert! Legmagasabb(N, A[]) Index:= 1 : Ertek:=A[1] Ciklus i:= 2-től N-ig Ha (Ertek < A[i]) akkor Ertek:=A[i] : Index:=i Maxindex:=Index Maxertek:= Ertek Return Maxindex, Maxertek Legmagasabb vége 11
ALGORITMUSOK ÉS PROBLÉMAOSZTÁLYOK (4. előadás) Rendezés Rendezés közvetlen kiválasztással A rendezendő számok legyenek az A tömb elemei. Az első menetben kiválasztjuk a tömb legkisebb elemét úgy, hogy az A[1]-et összehasonlítjuk az A[2].A[N] mindegyikével és akárhányszor A[1]-nél kisebbet találunk megcseréljük a két elemet, így az első kör után biztosan A[1] lesz a vektor legkisebb eleme. Ezt az eljárást A[2] A[N-1]-gyel folytatjuk. Az A vektor N-1 lépésben rendezett lesz. Bemenet: A[1..N] Kimenet: A[1..N] rendezett Ciklus i:=1-től N-1-ig Ciklus j:= i+1-től N-ig Ha (A[j] < A[i ]) akkor A:= A[j] A[j]:=A[i] A[i]:=A A módszer működését véletlenszerűen választott 16 szám esetén a következő táblázat mutatja: Eredeti 64 56 8 42 5 12 16 40 3 31 47 22 24 33 7 46 1.kör u. 3 64 56 42 8 12 16 40 5 31 47 22 24 33 7 46 2.kör u. 3 5 64 56 42 12 16 40 8 31 47 22 24 33 7 46 3. kör u. 3 5 7 64 56 42 16 40 12 31 47 22 24 33 8 46. 15.kör u. 3 5 7 8 12 16 22 24 31 33 40 42 46 47 56 64 Az eljárásban megfigyelhető, hogy minden menetben igen sok felesleges csere történik, mely a felesleges cserék kiküszöbölésével javítható. Rendezés minimum kiválasztással A felesleges cserék érdekében bevezetünk két segéd változót. Az 'Ertek' változóban tároljuk a az eddig megtalált legkisebb elemet, az 'Index'-ben pedig a sorozatbeli helyét. A menet végére az 'Ertek'- ben lesz a legkisebb elem, az 'Index'-ben pedig a helye. Csak a menet utolsó lépésében van szükség cserére, amikor is az értékben lévő legkisebb elemet a helyére tesszük. Bemenet: A[1..N] Kimenet: A[1..N] rendezett 12
Ciklus i:=1-től N-1-ig Index:=i : Ertek:= A[i] Ciklus j:= i+1-től N-ig Ha (Ertek > A[j]) akkor Ertek:= A[j] : Index:= j A[Index] := A[i] A[i]:= Ertek A módszer működését véletlenszerűen választott 16 szám esetén a következő táblázat mutatja: Eredeti 64 56 8 42 5 12 16 40 3 31 47 22 24 33 7 46 1.kör u. 3 56 8 42 5 12 16 40 64 31 47 22 24 33 7 46 2.kör u. 3 5 8 42 56 12 16 40 64 31 47 22 24 33 7 46 3. kör u. 3 5 7 42 56 12 16 40 64 31 47 22 24 33 8 46. 15.kör u. 3 5 7 8 12 16 22 24 31 33 40 42 46 47 56 64 Buborékos rendezés A rendezés alapgondolata a szomszédos elemek cseréje. Az első körben a rendezendő tömb végéről indulva az elemeket összehasonlítjuk az előttük levővel s amennyiben rossz sorrendben vannak felcseréljük őket. Az első kör végére a legkisebb elem biztosan a helyére kerül. Minden további körben újra a tömb végéről indulunk,de egyre rövidülnek a körök mert a tömb eleje fokozatosan rendezetté válik. Bemenet: A[1..N] Kimenet: A[1..N] rendezett Ciklus i:= 2-től N-ig Ciklus j:= N-től i- ig -1-esével Ha ( A[j-1] > A[j]) akkor A:= A[j-1] A[j-1]:= A[j] A[j]:= A A módszer működését véletlenszerűen választott 16 szám esetén a következő táblázat mutatja: Eredeti 64 56 8 42 5 12 16 40 3 31 47 22 24 33 7 46 1.kör u. 3 64 56 8 42 5 12 16 40 7 31 47 22 24 33 46 2.kör u. 3 5 64 56 8 42 7 12 16 40 22 31 47 24 33 46 13
.... 15.kör u. 3 5 7 8 12 16 22 24 31 33 40 42 46 47 56 64 Egyszerű beillesztéses rendezés (beszúrásos rendezés) Úgy végezzük a rendezést, mintha kártyáznánk és kezünkbe egyesével vennénk fel az asztalról a kiosztott lapokat. Az éppen felvett lapnak megkeressük a kezünkben lévő, már rendezett sorozatban a helyét úgy, hogy a nála nagyobbakat egy hellyel elcsúsztatjuk, végül a felvett lapot a neki megfelelő helyre illesztjük. Bemenet: A[1..N] Kimenet: A[1..N] rendezett Ciklus j:= 2-től N-ig i:= j-1 : A:=A[j] Ciklus amíg i >0 és A<A[i] A[i+1] :=A[i] : i:= i-1 A[i+1]:= A A módszer működését véletlenszerűen választott 16 szám esetén a következő táblázat mutatja: Eredeti 64 56 8 42 5 12 16 40 3 31 47 22 24 33 7 46 1.kör u. 56 64 8 42 5 12 16 40 3 31 47 22 24 33 7 46 2.kör u. 8 56 64 42 5 12 16 40 3 31 47 22 24 33 7 46 3. kör u. 8 42 56 64 5 12 16 40 3 31 47 22 24 33 7 46. 15,kör u. 3 5 7 8 12 16 22 24 31 33 40 42 46 47 56 64 A most bemutatott rendezés hátránya, hogy elég sok mozgatásra van benne szükség. 14
ALGORITMUSOK ÉS PROBLÉMAOSZTÁLYOK (5. előadás) Shell-módszer beszúrással Rendezés (folytatás) A módszer nem foglalkozik egyszerre minden rendezendő elemmel, csak az egymástól adott távolságra lévőkkel. Minden menet elején meghatározunk egy lépésközt (D), amely azt jelenti, hogy az adott menetben a tömb egymástól (D) távolságra lévő elemeit rendezzük. Adott meneten belül a rendezés több módszer szerint történhet, most beszúrással. Az induló lépésköz meghatározása úgy történik, hogy a rendezéshez szükséges menetek száma kb. log 2 N legyen. A további lépésközöket felezéssel kapjuk. Megjegyzés: A továbbiakban a log kifejezés alatt mindig 2-es alapú logaritmust értjük, azaz, számunkra, log N = log 2 N, ha külön nem említünk mást. A módszer működését véletlenszerűen választott 16 szám esetén a következő táblázat mutatja: Eredeti 64 56 8 42 5 12 16 40 3 31 47 22 24 33 7 46 D=15 u. 46 56 8 42 5 12 16 40 3 31 47 22 24 33 7 64 D=7 u. 7 3 8 42 5 12 16 40 56 31 47 22 24 33 46 64 D=3 u. 7 3 8 16 5 12 24 33 22 31 40 46 42 47 56 64 D=1 u. 3 5 7 8 12 16 22 24 31 33 40 42 46 47 56 64 Bemenet: A[1..N] Kimenet: A[1..N] rendezett D:=2 [log(n)] 1 Ciklus i:=1 Ciklus amíg i<=d és i+d<=n Ciklus j:=i+d-től N-ig D-esével A:=A[j] : B:=j-D Ciklus amíg B>0 és A<A[B] A[B+D]:=A[B] : B:=B-D A[B+D]:=A i:=i+1 D:=[D/2] amíg D>0 15
Keresés (folytatás) Logaritmikus keresés Adva van egy N elemű RENDEZETT sorozat és egy keresett elem X. Olyan algoritmust kell írni amely eldönti, hogy van-e X elem a sorozatban és ha van hányadik. Szokás az eljárást intervallum felezéses módszernek is nevezni. Bemenet: A[1..N], X elem Kimenet: Sorszam (az X elem sorszáma az A sorozatban, ill. 0, ha X nincs A-ban) p:=1 : q:=n Ciklus k:= [(p+q)/2] Ha (A[k] < X) akkor p:=k+1 Ha (A[k] > X) akkor q :=k-1 amíg p<=q és A[k] <>X Ha (p <= q) akkor Sorszam:=k különben Sorszam:=0 A logaritmikus elnevezés onnan származik, hogy evvel a módszerrel az elem megkereséséhez szükséges lépések száma max. log N (log 2 N). 16
ALGORITMUSOK ÉS PROBLÉMAOSZTÁLYOK (6. előadás) Rekurzió Rendezés (folytatás) Tervezési módszerek: - Rekurzió: A problémát megoldó algoritmus eljárásaiban alkalmazzuk a leírt eljárásokat, azaz a leírt eljárások közvetlenül vagy közvetve hívják (alkalmazzák) saját magukat. - Oszd meg és uralkodj: A probléma hasonló problémákra való bontása, a részproblémák megoldása, majd a kapott részmegoldásokból a probléma megoldásának az összeállítása. Összefésülő rendezés: Az összefésülő rendezés alkalmazza a fent említett két tervezési módszert. Egy tömb rendezéséhez felbontja azt két résztömbre (első és második fele), majd rendezi a két felet és a kapott részmegoldásokból összeállítja összefésüléssel az eredeti tömb rendezését ( Oszd meg és uralkodj alkalmazása). A felek rendezését az összefésülő rendezés alkalmazásával oldja meg ( rekurzió alkalmazása). Bemenet: A[p..r] Kimenet: A[p..r] rendezettek Összefésülő-rendezés(A, p, r): Ha (p < r ) akkor q := [(p + r) / 2] Összefésülő-rendezés vége Összefésülő-rendezés(A, p, q) Összefésülő-rendezés(A, q+1, r) Összefésül(A, p, q, r) Az alkalmazott Összefésülés eljárás leírása: Bemenet: A[p..q] és A[q+1..r] rendezettek Kimenet: A[p..r] rendezett //Az A bemenő, p-től r-ig rendezendő tömb és // kimenő, p pozíciótól r pozícióig rendezett tömb // Első fele rendezése // Második fele rendezése // A rendezett felek összefésülése Összefésülés(A, p, q, r): // Az eljárás hívó algoritmus // visszakapja az összefésült A tömböt n1 := q p + 1 // Az első fél (p-től q-ig) elemeinek száma n2 := r q // A második fél (q+1-től r-ig) elemeinek száma Ciklus i := 1-től n1-ig // A B tömb feltöltése az első fél elemeivel B[i] := A[p + i 1] Ciklus j := 1-től n2-ig // A J tömb feltöltése a második fél elemeivel J[j] := A[q + j] 17
B[n1 + 1] := : J[n2 + 1] := // A két fél végére egy minden lehetséges // elemnél nagyobb elemet teszünk, amely // már nem kerül a rendezett tömbre i := 1 : j := 1 // Mindkét félnél az első elemtől indulunk a visszahelyezésnél Ciklus k := p-től r-ig // Egyenként visszatesszük az elemeket A-ba Ha ( B[i] <= J[j] ) akkor // A soron következő kisebb elem A[k] := B[i] : i := i + 1 // visszahelyezése. A megfelelő különben // pozíció növelése abban a A[k] := J[i] : j := j + 1 // tömbben, ahonnan az elemet Összefésül vége Példa az Összefésül eljárás működésére: Bemenet: A: 2 4 5 1 3 6, p = 1, q = 3, r = 6 Összefésül: n1 = 3 n2 = 3 Kezdőértékek beállítása: B: 2 4 5 J: 1 3 6 A: 2 4 5 1 3 6 i = 1, j = 1, k = 1 Első lépés: B: 2 4 5 J: 1 3 6 B[1] = 2 > J[1] = 1 A: 1 4 5 1 3 6 i = 1, j = 2, k = 2 Második lépés: B: 2 4 5 J: 1 3 6 B[1] = 2 < J[2] = 3 A: 1 2 5 1 3 6 i = 2, j = 2, k = 3 Harmadik lépés: //visszahelyeztünk 18
B: 2 4 5 J: 1 3 6 B[2] = 4 > J[2] = 3 A: 1 2 3 1 3 6 i = 2, j = 3, k = 4 Negyedik lépés: B: 2 4 5 J: 1 3 6 B[2] = 4 < J[3] = 6 A: 1 2 3 4 3 6 i = 3, j = 3, k = 5 Ötödik lépés: B: 2 4 5 J: 1 3 6 B[3] = 5 < J[3] = 6 A: 1 2 3 4 5 6 i = 4, j = 3, k = 6 Hatodik lépés: B: 2 4 5 J: 1 3 6 B[4] = < J[3] = 6 A: 1 2 3 4 5 6 i = 4, j = 4, k = 7 Vége Példa az Összefésülő-rendezés eljárás működésére: Bemenet: A: 5 2 4 6 1 3, p = 1, r = 6 Összefésülő-rendezés: q = 3 Összefésülő-rendezés(A[5 2 4 6 1 3], 1, 3) Végrehajtás után: A: 2 4 5 6 1 3 Összefésülő rendezés(a[2 4 5 6 1 3], 4, 6) Végrehajtás után: A: 2 4 5 1 3 6 Összefésül(A[2 4 5 1 3 6], 1, 3, 6) // Előző példán láttuk! Végrehajtás után: A: 1 2 3 4 5 6 Vége 19
Gyors rendezés: A gyors rendezés egy tömb rendezéséhez először egy választott elemet a helyére teszi úgy, hogy a tömbben minden a választott elemet megelőző elem nála kisebb vagy egyenlő, minden a választott elemet követő elem nála nagyobb vagy egyenlő. Ezután az így keletkezett két résztömböt, a választott elemet megelőző elemek ill. követő elemek tömbjeit kell rendezi, és megvan az eredeti tömb rendezése. Az eredeti tömb felbontásával az oszd meg és uralkodj tervezési módszert alkalmazzuk, azzal pedig, hogy a résztömböket gyors rendezéssel rendezzük, a rekurzió tervezési módszert is alkalmazzuk. Bemenet: A[p..r] Kimenet: A[p..r] rendezett Gyors-rendezés(A, p, r): //Az A bemenő, p-től r-ig rendezendő tömb és // kimenő, p pozíciótól r pozícióig rendezett tömb Ha (p < r ) akkor q := Feloszt(A, p, r) // q a választott, helyre került elem helye Gyors-rendezés(A, p, q-1) // A választott elemnél kisebb vagy // egyenlő elemek rendezése Gyors-rendezés(A, q+1, r) // A választott elemnél nagyobb vagy // egyenlő elemek rendezése Gyors-rendezés vége Az alkalmazott Feloszt eljárás leírása (a választott elem a bemenő tömb utolsó eleme) : Bemenet: A[p..r] Kimenet: q, A[p..q-1], A[q+1..r] // q a választott (utolsó) elem végleges helye // A[p..q-1] a választott elemnél kisebb elemek // A[q+1..r] a választott elemnél nagyobb elemek Feloszt(A, p, r): // Az A bemenő, p és r pozíciók közötti felosztandó i := p -1 // i az utolsó megtalált A[r]-nél kisebb elem pozíciója Ciklus j := p-től r-1-ig Ha ( A[j] < A[r] ) akkor // Ha megtalálunk egy A[r]-nél kisebbet, i := i + 1 // akkor helycserével áthelyezzük a tömb S := A[i] : A[i] := A[j] : A[j] := S // elejére, a már megtalált // A[r]-nél kisebbek mögé S := A[i+1] : A[i+1] := A[r] : A[r] := S // A választott (utolsó) elem // áthelyezése a helyére (a nála // kisebbek mögé) Return i+1 // visszaadja a választott elem végleges pozícióját Feloszt vége 20
ALGORITMUSOK ÉS PROBLÉMAOSZTÁLYOK (7. előadás) Algoritmusok hatékonysága Az algoritmusok hatékonyságát főleg két szempont alapján szokták vizsgálni: - felhasznált idő - felhasznált memória A legfontosabb szempont az időigény és ezt használjuk mi rendszerint. Egy algoritmus időigényének a megadásához szükség van a bemenet mértékének a meghatározásához. Ez az általunk eddig látott algoritmusok esetén a tömb elemeinek a száma. Így, ha az A tömb elemeinek a száma n, akkor az időigénye n függvényeként adható meg. Az algoritmusok időhatékonyságának az összehasonlításához szükségünk van a függvények összehasonlítására. Függvények összehasonlítása: 1) f(n) = Θ(g(n)) : g(n) aszimptotikusan éles korlátja f(n)-nek, ha létezik c 1 > 0, c 2 > 0, n 0 > 0, hogy 0 c 1 g(n) f(n) c 2 g(n) minden n n 0 -ra 2) f(n) = O(g(n)) : g(n) aszimptotikusan éles felső korlátja f(n)-nek, ha létezik c 2 > 0, n 0 > 0, hogy 0 f(n) c 2 g(n) minden n n 0 -ra 3) f(n) = Ω(g(n)) : g(n) aszimptotikusan éles alsó korlátja f(n)-nek, ha létezik c 1 > 0, n 0 > 0, hogy 0 c 1 g(n) f(n) minden n n 0 -ra Tétel: f(n) = Θ(g(n)) akkor és csak akkor, ha f(n) = O(g(n)) és f(n) = Ω(g(n)) 4) f(n) = o(g(n)) : g(n) aszimptotikusan nem éles felső korlátja f(n)-nek, f(n) ha lim = 0 ( f(n) 0, g(n) 0 ) n g(n) 5) f(n) = ω(g(n)) : g(n) aszimptotikusan nem éles alsó korlátja f(n)-nek, f(n) ha lim = ( f(n) 0, g(n) 0 ) n g(n) Párhuzam a függvények és a valós számok összehasonlítása kzött: f(n) = O(g(n)) f(n) = Ω(g(n)) f(n) = Θ(g(n)) f(n) = o(g(n)) f(n) = ω(g(n)) a b a b a = b a < b a > b 21
Néhány korábban látott algoritmus időigénye: Összegzés: Eldöntés: Általános: Θ(n) Általános: O(n) Kiválasztás: Általános: O(n) Lineáris keresés (első megkeresése): Általános: O(n) Megszámlálás: Általános: Θ(n) Kiválogatás (minden előfordulás megkeresése): Általános: Θ(n) Szélsőértékek: Általános: Θ(n) Logaritmikus keresés: Általános: O(log n) Egyszerű beillesztéses rendezés (beszúrásos rendezés): - legjobb eset: Θ(n) - legrosszabb eset: Θ(n 2 ) Általános: O(n 2 ) Összefésülő rendezés: Általános: Θ(n log n) Gyors rendezés: - legjobb eset: Θ(n log n) - legrosszabb eset: Θ(n 2 ) - az átlagos eset közelít a legjobbhoz Általános: O(n 2 ) A következő példán keresztül szeretnénk rámutatni az időigény vizsgalátának a fontosságára: A számítógép sebessége: 10 9 művelet/sec B számítógép sebessége: 10 7 művelet/sec Beszúró rendezés: c 1 n 2 (2 n 2 ) Összefésülő rendezés: c 2 n log n (50 n log n) Rendezendő tömb mérete: 10 6 elem A tömb rendezésének időigénye: 2.(10 6 ) 2 művelet 50. 10 6. log 10 6 művelet = 2000 sec 100 sec 10 9 művelet/sec 10 7 művelet/sec Az A számítógép100-szor gyorsabb, mint a B, mégis B 20-szor gyorsabban végez, mint A. Nézzük meg most példaként az Összefésülő rendezés időigényének a kiszámítását: 22
Tegyük fel, hogy a rendezendő A tömb mérete n = 2 k és a rendezéséhez szükséges idő T(n). Akkor az A tömb rendezéséhez szükséges idő: Akkor: T(n/2) az első fele rendezéséhez összefésüléssel T(n/2) a második fele rendezéséhez összefésüléssel c n a rendezett felek összefésüléséhez T(n) = 2 T(n/2) + c n = 2 (2 T(n/4) + c n/2) + c n = 4 T(n/4) + 2 c n = 4 (2 T(n/8) + c n/4) + 2 c n = 8 T(n/8) + 3 c n = 2 K T(1) + k c n = n + (log n) c n = n c log n + n = n (c log n + 1) T(n) = Θ(n log n) 23