Szelekciós vezérlés
Szelekciós vezérlések¶
Amikor véges sok rögzített művelet közül véges sok feltétel alapján választjuk ki, hogy melyik művelet kerüljön végrehajtásra, szelekciós vezérlésről beszélünk.
Attól függően, hogy a kiválasztás milyen módszerrel történik meg, különböző altípusai vannak ennek a vezérlésnek. Ezek az
- egyszerű szelekciós vezérlés,
- többszörös szelekciós vezérlés,
- esetkiválasztásos szelekció,
- illetve a föntiek kiegészítve egyébként ágakkal.
Egyszerű szelekciós vezérlés¶
Egyszerű szelekció esetén egyetlen feltétel és egyetlen művelet van (amely művelet persze lehet összetett).
Legyen F egy logikai kifejezés, A pedig tetszőleges művelet. Az F feltételből és az A műveletből képzett egyszerű szelekciós vezérlés a következő vezérlési előírást jelenti:
- Értékeljük ki az F feltételt és folytassuk a 2.) lépéssel.
- Ha F értéke igaz, akkor hajtsuk végre az A műveletet és fejezzük be az összetett művelet végrehajtását.
- Egyébként, vagyis ha F értéke hamis, akkor fejezzük be az összetett művelet végrehajtását.
A vezérlés bővíthető úgy, hogy a 3. pontban üres művelet helyett egy B műveletet hajtunk végre. Ekkor az alábbiak szerint módosíthatjuk a vezérlés megadását.
Legyen F egy logikai kifejezés, A és B pedig tetszőleges műveletek. Az F feltételből és az A és B műveletekből képzett egyszerű szelekciós vezérlés a következő vezérlési előírást jelenti:
- Értékeljük ki az F feltételt és folytassuk a 2.) lépéssel.
- Ha F értéke igaz, akkor hajtsuk végre az A műveletet és fejezzük be az összetett művelet végrehajtását.
- Egyébként, vagyis ha F értéke hamis, akkor hajtsuk végre a B műveletet és fejezzük be az összetett művelet végrehajtását.
A folyamatábrákon jól látszik, hogy az F feltételtől függ, hogy merre halad a vezérlés a feltételtől függően:
A szerkezeti ábrán most is azt látjuk, hogy milyen részproblémák megoldása lesz a szelekció megoldása. A szerkezeti ábrákon a szelekciót egy téglalappal jelöljük, ami vízszintesen két részre van osztva. Ha tudjuk, a felső részbe kerül a feltétel, vagy az az elem, ami a feltételt meghatározza, a téglalaphoz csatolt műveletek pedig azok a műveletek lesznek, amelyek a feltétel(ek) bizonyos teljesülése mellett hajtódnak végre. Egyszerű szelekció esetében maga a feltétel egyértelműen beírható a téglalap felső részébe, az alsó résznél pedig jelölni tudjuk, hogy a feltétel igaz, vagy hamis értéke mellett mely alművelet végrehajtása jelenti a művelet befejezését.
Az egyszerű szelekciót C nyelven az if
utasítással valósítjuk meg.
Ennek szintaxisa:
A szintaxis ábrán is látszik, hogy maga az else
(egyébként) ág opcionális nyelvi szinten is, ez elhagyható.
A szintaxisnak megfelelően az egyszerű szelekció C megvalósítása:
1 2 3 |
|
Ha van egyébként ág is, akkor pedig:
1 2 3 4 5 |
|
Többszörös szelekciós vezérlés¶
Előfordulhat, hogy egyetlen feltétel nem elegendő ahhoz, hogy eldöntsük, mit is kellene csinálni, merre kellene haladni a vezérlésnek. Ilyenkor, ha több feltételünk van több művelettel, akkor többszörös szelekcióról beszélünk.
Legyenek Fi logikai kifejezések, Ai pedig tetszőleges műveletek 1 <= i <= n-re (azaz mind feltételből, mint A műveletből van n darab). Az Fi feltételekből és Ai műveletekből képzett többszörös szelekciós vezérlés a következő vezérlési előírást jelenti:
- Az Fi feltételek sorban történő kiértékelésével adjunk választ a következő kérdésre: van-e olyan i (1 <= i <= n), amelyre teljesül, hogy az Fi feltétel igaz és az összes Fj (1 <= j < i) feltétel hamis? (Azaz keressük az első Fi feltételt, ami igaz.)
- Ha van ilyen i, akkor hajtsuk végre az Ai műveletet és fejezzük be az összetett művelet végrehajtását.
- Egyébként, vagyis ha minden Fi feltétel hamis, akkor fejezzük be az összetett művelet végrehajtását.
Természetesen most is ki lehet egy egyébként ággal egészíteni a vezérlést, azaz definiálhatjuk azt a feladatot, amit akkor kell végrehajtani, amikor egyik feltétel sem teljesül. Ekkor az alábbiak szerint módosítsuk a többszörös szelekció formális leírását!
Legyenek Fi logikai kifejezések, Ai és B pedig tetszőleges műveletek 1 <= i <= n-re (azaz mind feltételből, mint A műveletből van n darab). Az Fi feltételekből, Ai és B műveletekből képzett többszörös szelekciós vezérlés a következő vezérlési előírást jelenti:
- Az Fi feltételek sorban történő kiértékelésével adjunk választ a következő kérdésre: Van-e olyan i (1 <= i <= n), amelyre teljesül, hogy az Fi feltétel igaz és az összes Fj (1 <= j < i) feltétel hamis? (Azaz keressük az első Fi feltételt, ami igaz.)
- Ha van ilyen i, akkor hajtsuk végre az Ai műveletet és fejezzük be az összetett művelet végrehajtását.
- Egyébként, vagyis ha minden Fi feltétel hamis, akkor hajtsuk végre B-t és fejezzük be az összetett művelet végrehajtását.
A többszörös szelekció folyamatábráin is látható, hogy egész addig, amíg nem találunk igaz feltételt, folytatódnak a feltétel kiértékelések. Ha találunk igaz feltételt, akkor az ahhoz tartozó utasítás végrehajtásával fejeződik be az összetett utasítás végrehajtása, ha egyik feltétel sem igaz, illetve ha van egyébként ág, akkor annak a végrehajtásával.
A többszörös szelekció szerkezeti ábrája a következőképpen alakul:
Ahogy azt már az egyszerű szelekciónál is láttuk, a szelekciót egy vízszintesen felezett téglalappal jelöljük.
Mivel a dobozkába nem férne el az összes feltétel, és nem is lenne utána egyértelmű, hogy melyik feltételhez melyik utasítás tartozik, így a dobozba csak egy ?
kerül.
A doboz alatt sorakoznak fel az egyes feltételek, és az azokhoz tartozó műveletek.
A többszörös szelekcióra tulajdonképpen tekinthetünk úgy is, mint egyszerű szelekciókra egymás után, ahol az egyébként ágakhoz tartozik mindig egy újabb egyszerű szelekció:
A többszörös szelekció megvalósítására nincs külön C konstrukció, így az egymásba ágyazott egyszerű szelekciókkal tudjuk megvalósítani:
1 2 3 4 5 6 7 8 9 10 |
|
Példa: háromszögek osztályozása¶
A szelekciók bemutatására tekintsük a következő példát!
-
Problémafelvetés:
- Egy lakatosműhely egyik részlegében lemezekből vágnak ki különféle sokszögeket, köztük háromszögeket is. Többféle speciális gépük is van, amelyek bizonyos háromszögeket gyorsabban tudnak vágni, mint az "általános" vágógép. A gyártás optimalizálására a vezetőség szeretné, ha mindenféle háromszöget a neki leginkább megfelelő géppel vágnának ki. Van egy szabályos háromszögeket gyorsan vágó gép, egy-egy egyenlő szárú háromszögeket illetve derékszögű háromszögeket kicsit lassabban vágó gép, és egy általános de lassú vágógép. A háromszögeket a három oldaluk hosszával adják meg, és elképzelhetőek hibás adatok is.
- A vezetőség szeretne egy programot, amely segít meghatározni a megfelelő (a feladathoz leggyorsabb) vágógépet azzal a kikötéssel, hogy az egyenlő szárú derékszögű háromszögek esetén a kettő megfelelő közül bármelyik gépet használhatják. Kellene tehát egy olyan program, ami megmondja, hogy milyen háromszöget határoz meg három valós szám, mint a háromszög három oldalhosszúsága?
-
Specifikáció:
- A bemenő adat három valós szám, jelölje ezeket
a
,b
ésc
. - A kimenő adat a három bemenő érték mint oldalhossz alapján a következő szövegek valamelyike:
- "Nem háromszög"
- "Szabályos háromszög"
- "Egyenlő szárú háromszög"
- "Egyenlő szárú derékszögű háromszög"
- "Derékszögű háromszög"
- "Egyéb háromszög"
- A bemenő adat három valós szám, jelölje ezeket
-
Algoritmustervezés:
- Az osztályozás az alábbi feltételek alapján végezhető el:
- Valamelyik oldal nem pozitív, azaz
a<=0
vagyb<=0
vagyc<=0
. - Nem alkotnak háromszöget, azaz ha az
a
oldal a leghosszabb, akkora>=b+c
. - Mindhárom oldal egyforma hosszúságú, azaz
a==b
,b==c
(ebből tranzitivitás miatt már következik, hogya==c
). - Van legalább két egyforma oldal, azaz
a==b
vagyb==c
vagya==c
. - A háromszög derékszögű, azaz ha
a
a leghosszabb oldal, akkora*a = b*b+c*c
- Valamelyik oldal nem pozitív, azaz
- Látható, hogy az egyszerűbb feltételek érdekében az osztályozás előtt érdemes legnagyobb értéket valamelyik, mondjuk az
a
változóba átmozgatni. - Az osztályozás eredményét közvetlenül kiírjuk.
- Az osztályozás az alábbi feltételek alapján végezhető el:
-
Algoritmustervezés szerkezeti ábrával:
Az alapfeladat 3 nagyon egyszerű részfeladat egymás után (szekvenciálisan) végrehajtható feladatából áll. Ezekből a bonyolultabb feladatokat kell részletesebben kifejteni. Az adatok átrendezésének célja, hogy a legnagyobb érték az
a
változóban legyen. Ehhez egyszerű szelekciót kell alkalmazni, ahol leellenőrizzük, hogya
milyen relációban áll a másik két változóval:Maga az osztályozás egy többszörös szelekció, ahol most a feltételeket egyszerű rövidítésekkel írjuk le:
-
Megvalósítás:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
Mivel a háromszögek oldalhosszúsága nemcsak egész érték lehet, így a változók deklarálásakor legyenek a változók valós típusúak. A valós változók deklarációját láthatjuk a 9-10. sorokban.
Amikor ezeket a változókat szeretnénk inicializálni a scanf
utasítással, akkor a formátumsztringben a %lf
-et használjuk.
Valós számok esetében szó volt arról, hogy vigyázni kell az egyenlő, nem egyenlő operátorokkal. A számábrázolás pontatlansága miatt ezek nem feltétlen működnek jól, érdemes az összehasonlítást úgy megvalósítani, hogyha a két valós különbsége nem halad meg egy küszöbszámot, akkor azok egyenlőek, egyébként különbözőek.
Módosítsuk a programot ennek megfelelően!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
A #define EQUALS(X,Y) ( ( ((X) > (Y)) ? ((X) - (Y)) : ((Y) - (X)) ) <= 1e-10 )
makró segít abban, hogy két érték különbségéről eldöntsük, hogy azok különbsége kisebb-e, mint egy nagyon pici konstans.
Amikor ezt a makrót használjuk, akkor a használat helyén levő kifejezések behelyettesítődnek X
és Y
helyére a makró behelyettesítés során.
Hogy a makrók pontosan hogyan is működnek, illetve az ebben a makróban használt kifejezés micsoda, arról a későbbiekben lesz szó.
Az if utasítások ismételt alkalmazása¶
Talán az eddigi példákból már feltűnt, hogy amikor egy kódot írunk, akkor bizonyos dolgok "beljebb" kezdődnek. A kód tabulálása azt a célt szolgálja, hogy a kód áttekinthetőbb legyen, látszódjék, hogy egy utasítás, vagy utasítás blokk mely másik utasítás része.
Láttuk, hogy a függvények utasításai blokkba rendeződnek, a blokkon belüli utasításokat pedig beljebb tabulálva írjuk. Ugyanígy teszünk egy szelekciós utasításunk ágaival is. Az abban levő utasításokat blokkosítjuk, a blokkon belüli utasításokat pedig beljebb tabuláljuk. Amennyiben egy összetett utasítás részutasítása egyetlen utasításból áll, akkor a blokk persze elmaradhat, de fontos, hogy a tabulálás megmaradjon. Egyes esetekben viszont jobb, ha a blokkokat sem hagyjuk el a biztonság kedvéért.
Vegyük a következő esetet, amikor két egyszerű szelekció van egymásba ágyazva:
Írjuk le ezt C kóddal:
1 2 3 4 5 6 |
|
Mivel a belső if
egyetlen utasítás, így akár a zárójelezés elmaradhat.
De vajon egyértelmű ebben, hogy az else
melyik if-hez tartozik?
Mi van, ha elcsúszik a tabulálás?
1 2 3 4 5 |
|
Mivel a C nyelv nem érzékeny az úgynevezett whitespace, azaz nem látható karakterekre (szóköz, tabulátor, sortörés), az else
ebben az esetben is a második if
-hez tartozik, azaz a két kód ekvivalens.
(Csak megjegyezzük: vannak olyan programozási nyelvek, ahol ez nemigaz, vagyis a kód tabulálása, formája az értelmezését is befolyásolja.)
Ha azt szeretnénk, hogy az else
az első if
-hez tartozzon, akkor azt zárójelezéssel jelezni kell:
1 2 3 4 5 6 |
|
Esetkiválasztásos szelekciós vezérlés¶
Ha a többszörös szelekció valamennyi feltételét átírhatjuk úgy, hogy azok elemek valamilyen halmazokba való tartozását vizsgálja, akkor esetkiválasztásos szelekcióról beszélünk.
Legyen K egy adott típusú kifejezés, és legyenek Hi-k olyan halmazok, melynek elemeinek típusa megegyezik K típusával. Legyenek továbbá Ai tetszőleges műveletek, ahol 1<=i<=n teljesül. A K szelektor kifejezésből, Hi kiválasztó halmazokból és Ai műveletekből képzett esetkiválasztásos szelekciós vezérlés a következő vezérlési előírást jelenti:
- Értékeljük ki a K kifejezést és folytassuk a 2.) lépéssel.
- Adjunk választ a következő kérdésre: Van-e olyan i (1<=i<=n), amelyre teljesül, hogy a K ∈ Hi, és K ∉ Hj, ahol (1<=j<i)?
- Ha van ilyen i, akkor hajtsuk végre az Ai műveletet és fejezzük be az összetett művelet végrehajtását.
- Egyébként, vagyis ha K nem eleme egyetlen Hi halmaznak sem, akkor fejezzük be az összetett művelet végrehajtását.
Itt is természetesen lehet egyébként águnk, amit akkor hajtunk végre, amikor egyik feltétel sem teljesül. Ekkor a 4. lépést egészítsük ki egy nemüres B művelet végrehajtásával.
Legyen K egy adott típusú kifejezés, és legyenek Hi-k olyan halmazok, melynek elemeinek típusa megegyezik K típusával. Legyenek továbbá Ai és B tetszőleges műveletek, ahol 1<=i<=n teljesül. A K szelektor kifejezésből, Hi kiválasztó halmazokból és Ai és B műveletekből képzett esetkiválasztásos szelekciós vezérlés a következő vezérlési előírást jelenti:
- Értékeljük ki a K kifejezést és folytassuk a 2.) lépéssel.
- Adjunk választ a következő kérdésre: Van-e olyan i (1<=i<=n), amelyre teljesül, hogy a K ∈ Hi, és K ∉ Hj, ahol (1<=j<i)?
- Ha van ilyen i, akkor hajtsuk végre az Ai műveletet és fejezzük be az összetett művelet végrehajtását.
- Egyébként, vagyis ha K nem eleme egyetlen Hi halmaznak sem, akkor hajtsuk végre B-t és fejezzük be az összetett művelet végrehajtását.
Az esetkiválasztásos szelekció folyamatábrái nagyon hasonlítanak a többszörös szelekció folyamatábráihoz, a különbség csupán a feltételek megadásában van, hiszen itt fontos, hogy egy adott halmazba való tartozást vizsgálunk mindig:
Amennyiben a szelektor kifejezés, amelynek értékét egy v változóban elmentük benne van valamelyik Hi halmazban, úgy az adott esethez tartozó Ai műveletet hajtjuk végre, egyébként pedig ellenőrizzük a következő halmazba való tartozást.
A szerkezeti ábráknál szintén egy osztott téglalappal jelezzük a szelekciót, ahol a téglalap felső felében megadjuk a szelekciót meghatározó kifejezést, az egyes halmazokat pedig az átláthatóság érdekében a téglalap alatt adjuk meg, a hozzájuk tartozó műveletekkel együtt.
A C nyelvben az esetkiválasztásos szelekciót a switch
utasítással valósítjuk meg.
Kis pontatlansággal ennek a szintaktikája a következő:
A pontatlanság abból adódik, hogy ezen ábra alapján akár több default
címkéje is lehetne az utasításnak, valamint nem derül ki ebből az ábrából, hogy egy adott konstans érték csak egy case
címke után szerepelhet, illetve az is fontos, hogy a switch
szelektor kifejezés valamilyen felsorolási típusú legyen (azaz nem lehet például valós típus).
A switch
utasítás szelektorától csak az függ, hogy mely ponton kezdjük el végrehajtani a switch
magját.
Ha a végrehajtás elkezdődött, akkor onnantól kezdve az első break
vagy return
utasításig megy a switch
utasításainak a végrehajtása.
Ha ilyen nincs, akkor akár az utolsó utasításig.
Ebben a fázisban már nincs annak jelentősége, hogy van-e további case
vagy default
címke.
Vagyis ahhoz, hogy a switch
utasítás az esetkiválasztásos szelekció megvalósítása legyen, szükséges, hogy az esetkiválasztásos szelekció egy ághoz tartozó utasítások után egy break
segítségével megszakítsuk a switch
további utasításainak (és így magának a switch
utasításnak) a végrehajtását.
A break
helyett speciális esetekben a return
utasítás is megfelelő lehet.
A switch
utasítást tehát például a következő implementációval tudja az esetkiválasztásos szelekciót megvalósítani:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
A kiválasztó halmazok megadása az esetkiválasztásos szelekció kritikus pontja. Algoritmusok tervezése során bármilyen effektív halmazmegadást használhatunk, azonban a tényleges megvalósításkor csak a választott programozási nyelv eszközeit alkalmazhatjuk.
A Hi halmazok elemszáma tetszőleges lehet, a case
-ek után viszont csak egy-egy érték állhat.
Viszont kihasználhatjuk a switch
működési elvét, miszerint a címkék csak a belépési pontot határozzák meg.
Ha Hi = {xi,1, xi,2, . . . , x i,ni }, akkor az adott ág lekódolható a következő kódrészlettel:
1 2 3 4 5 6 |
|
Példa: dátum helyességének eldöntése¶
Az esetkiválasztásos szelekció használatára példa a következő program.
-
Problémafelvetés: A munkaügyi hatóság ellenőrzéseket tart építkezéseken, ahol a munkanaplókat is ellenőrzik. Mivel tapasztalat, hogy az ellenőrzés hírére pánikszerűen kitöltött naplókban sok rossz dátum is szerepel, szükségük lenne egy olyan programra, amely mintegy "előszűri" az adatokat, és a nyilvánvalóan hibás dátumokat jelzi.
-
Specifikáció:
- Az input egy
honap
nap
egész számpár által megadott dátum. - Az output "A dátum helyes" vagy "A dátum nem helyes" szövegek valamelyike attól függően, hogy a
honap
nap
dátum hónap napként helyes-e vagy sem.- Szökőévekkel nem foglalkozunk, a február 28 napos.
- Ha
honap
értéke 2, anap
értékének 1 és 28 közé kell esnie, különben a dátum nem helyes. - Ha
honap
értéke 4, 6, 9 vagy 11, anap
értékének 1 és 30 közé kell esnie, különben a dátum nem helyes. - Ha
honap
értéke 1, 3, 5, 7, 8, 10 vagy 12, anap
értékének 1 és 31 közé kell esnie, különben a dátum nem helyes. - Ha
honap
értéke más, a dátum nem helyes.
- Az input egy
Az algoritmustervezés során használtunk egy jo
nevű változót, amely logikai értéket tárol.
A következő megvalósításban ezt a változót int
típusúként deklaráljuk.
Ugyanakkor persze érdemes megjegyezni, hogy a C nyelvnek csak a \(C^{99}\) szabvány óta része a logikai (_Bool) típus (melynek értékkészlete a {0, 1} halmaz), illetve támogatja a true
, false
literálok alkalmazását.
Amíg ezek nem voltak a nyelvben, addig is lehetett logikai értékekkel dolgozni.
A műveletek eredményeként keletkező logikai hamis értéket a 0 egész érték reprezentálja, és a 0 egész érték logikai értékként értelmezve hamisat jelent.
A műveletek eredményeként keletkező logikai igaz értéket az 1 egész érték reprezentálja, de bármely 0-tól különböző egész érték logikai értékként értelmezve igazat jelent.
A logikai és egész értékek C-ben teljesen konvertibilisek (a fenti konverziók szerint), így logikai értékeket egész típusú változókban is tudunk tárolni (sőt, a \(C^{99}\) szabvány előtt abban kellett).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
|
Feltételes kifejezés¶
Az előző példában a kiíratás kissé összetettre sikeredett, gyakorlatilag 3 printf
függvény hívásával oldottuk meg egyetlen mondat kiírását:
1 2 3 4 5 |
|
Ez lerövidíthető az úgynevezett feltételes kifejezéssel így:
1 2 3 |
|
vagy még rövidebben így:
1 |
|
A feltételes operátor a C nyelv egyetlen három operandusú művelete, amely már a nyelv kezdetei óta a nyelv része. Három kifejezésből áll:
1 |
|
A teljes kifejezés kiértékelése során először kiértékelésre kerül a kifejezes_1
kifejezés.
Amennyiben ez igaz (azaz az érték nem 0), akkor a teljes kifejezés értéke a kifejezes_2
lesz, egyébként ha hamis (azaz 0), akkor a kifejezés értéke a kifejezes_3
lesz.