3. Bevezetés
A dolgozat címében hivatkozott versenyfeladatok az ACM szervezésében rendezett egyetemi
programozói csapatversenyek feladatai. A versenysorozatot évente rendezik és három fordulóból áll.
Az első forduló általában valamilyen helyi (egyetemi, városi) verseny. Ezeket a versenyeket a helyi
(lelkes) szervezők szervezik, általában önkéntes alapon. Szervezésen itt a verseny tényleges
lebonyolítását kell érteni és a feladatok kiválasztását. A helyi versenyekről tovább jutó csapatok
vesznek részt a második fordulóban (egyetemenként legfeljebb egy csapat), amelyet már az ACM
adott szervezete szervez. A második fordulóban (Regional Final - területi döntő) általában valamilyen
nagyobb területi egység (Pl. Kalifornia, Nyugat-Európa) egyetemeinek csapatai versenyeznek. A
területi döntőkből egy-három csapat juthat tovább a döntőbe, amely mindig valamelyik USA-beli
nagyvárosban zajlik.
A dolgozatom célja a verseny és a verseny feladatainak általános bemutatása. Egyetemi éveim alatt
összesen 7 -szer vettem részt 1. és 2. fordulós versenyeken. Az itt összegyüjtött tapasztalatokat és
szép megoldásokat, a megoldásokhoz felhasmált alapvető matematikai algoritmusokat úgy éreztem
mindenképpen szükséges rendszerezve megörökíteni, ehhez kínált kiváló fórumot a diploma
dolgozatom.
A dolgozat melléklete kb. 90 eredeti versenyfeladatot tartalmaz, amely biztosan nagyon hasznos lesz
az egyetemünkön folyó első és másod éves programozó-matematikus, matematikus, informatika és
matematika tanár szakos hallgatók különböző tantárgyainak gyakorlati oktatásához. . A feladatok
angol nyelvűek, mert a versenyek hivatalos nyelve az angol.
A dolgozatban található alapvető matematikai algoritmusok segítségével mintegy 30 (általában a
nehezebbek közé sorolt probléma) oldható meg. Az algoritmusok kisebb módosításaival további
feladatok megoldhatók. Az egyes algoritmusoknál hivatkozás található a feladatokra, amelyek
megoldásához az adott algoritmus jó hatásfokkal felhasználható. Az egyes algoritmusok újabb
problémákat vetnek fel (P-jelű problémák) ezek megoldása szintén jó gyakorlat lehet.
Ezúton szeretnék a csapatom nevében köszönetet mondani szakmai támogatóínknak és tanárainknak
akik nélkül még az így elért szerény eredményeinket sem értük volna el, név szerint: Kuki Attilának a
helyi versenyek szervezéséért és az európai döntőre való kiutazás szervezéséért, valamint hasznos
tanácsaiért, Dr Arató Mátyásnak, Dr Lajkó Károlynak, Dr. Juhász Istvánnak és Dr Végső Jánosnak
általános támogatásaikért, Herendi Tamásnak igen hasznos szakmai tanácsaiért és az IFSz Kft.
valamint az IQSoft Rt. munkatársainak szakmai és egyéb támogatásukért. Végül szeretnénk
megköszönni anyagi támogatásukat azon cégeknek és szervezeteknek akik nélkül semmiképpen sem
képviselhettük volna egyetemünket az európai döntőkön:
A Külkereskedelmi és Hitel Bank Rt.,
A Biogal Rt.,
Az IQSoft Rt.,
A Dataware Kft.,
A KL TE Diákönkormányzata.
Valamint szeretném megköszönni csapattársaimnak Fekete Zoltánnak, Jakó Jánosnak,
Molnár Tamásnak és tanáraimnak Kuki Attilának és Herendi Tamásnak a dolgozat elkészüléséhez
adott hasznos tanácsaikat és bölcs észrevételeiket.
2
4. A versenyekről
A verseny szabályai:
A versenyeken kezdetben 4, majd (1991 után) 3 fós csapatok indulhattak. A csapat tagjai között
kezdetben lehetett egy diplomás is, később diplomások részvétele nem volt megengedett. A verseny
ideje általában 5 óra. A csapatok a verseny ideje alatt egy darab IBM PC típusú számítógépet
használhatnak a szükséges fordítóprogrammal felszerelve, ez a TURBO PASCAL 5.0 -ás verziója
volt. A csapatok a verseny alatt bármilyen írott forrást használhatnak, viszont semmilyen más (pl.
mágneses) forrás használata nem megengedett csakúgy, mint a programozható zsebszámológépek
használata sem. A csapatoknak a verseny ideje alatt 5-8 problémát kell megoldaniuk. A problémák
között semmilyen sorrendiség sincs. A verseny nyelve angol, ezért a feladatok szövege, a zsűrinek
feltett kérdések, a zsűri válaszai is angol nyelvűek.
A problémák megoldása
A csapatoknak az elkészült megoldást -amely mindig egy pascal program forráskódja - az e célra
fenntartott mágneslemezen kell a zsűrihez eljuttatniuk. A zsűri a kódot lefordít ja és saját input
adataival teszteli. A programoknak 1 perc futásidő áll rendelkezésére, ez alatt kell outputot
produkálniuk. Az output alapján a zsűri a következő válaszokat adhatja a csapatoknak:
1. Syntax Error - fordítási hiba
2. Run Time Error - Futás közbeni programhiba, pl O-val való osztás
3. Time Limit Exceded - Időtúllépés
4. Wrong Answer - hibás válasz.
5. Accepted - Elfogadva
Egy probléma megoldásával többször is lehet próbálkozni, de zsűri csapatonként és problémánként
méri a verseny kezdetétől a megoldáshoz felhasznált időt. Az 1.-4. esetben (és minden további
sikertelen kísérlet után) a zsűri az adott csapatnak adott probléma megoldásához felhasznált idejét 20
perccel növeli.
A verseny ideje alatt az egyes feladatokkal kapcsolatban felmerült értelmezési stb. problémákkal
kapcsolatban a csapatok írásban kérdéseket tehetnek fel a zsűrinek, aki szintén írásban köteles
válaszolni ezekre. A zsűri a csapatokat szabályszegés esetén kizárással sújthatja.
A Kiértékelés szabályai:
A verseny végeztével a zsűri összeszámolja az egyes csapatok által megoldott problémák számát, és
összeadja a helyesen megoldott problémákhoz felhasznált időket. Így minden csapat eredménye két
mennyiségből áll:
- A megoldott problémák száma
- Az ehhez felhasznált idő
Az a csapat a verseny győztese, amely a legtöbb feladatot oldotta meg, ha ilyen több van akkor a
verseny győztese az a csapat, amely a legkevesebb időt használta fel.
Hazai Versenyek
Az egyetemi programozói versenyek története Magyarországon 1990-ben kezdődött egy a Budapesti
Műszaki Egyetemen (BME) rendezett versennyel, ahol nemcsak a BME csapatai, hanem a fóvárosi
egyetemeken kívül vidéki csapatok is indultak. Ezen a versenyen a KL TE 3 csapatot indított, melyek
a középmezőnyben végeztek. 1991 után minden évben a fóvárosban és Debrecenben is rendeztek
versenyeket. Ezeken a versenyeken az induló csapatok száma nagyjából állandó volt: Budapesten kb
25, míg Debrecenben kb 10.
3
5. A versenyek tapasztalatai
A következő néhány mondatban a versenyeken, a problémák megoldásával kapcsolatban szerzett
tapasztalatokról szeretnék írni. A verseny kezdetén érdemes minden feladatot átolvasni és értelmezni.
Az egyes csapatokon belül többféle megoldási módszer is kialakulhatott, az egyik lehetséges, hogy az
értelmezés után a csapattagok egymás között szétosztják a feladatokat és eztán egyenként, vagy
problémás feladat esetén együtt keresik a megoldást és valósítják meg a kivitelezést. A másik
módszer, hogy minden feladat elvi megoldását a csapattagok együtt keresik, csak a konkrét
megvalósítás ideje alatt dolgoznak különböző feladatokon a csapattagok. De ezektől különböző más
módszerek is kialakulhattak, valószínűleg erre nincs általános alkalmazható stratégia. A feladatok
megoldásakor szerenesés esetben, amikor egyszerre több feladat elvi megoldása is elkészült, a szűk
keresztmetszetet a rendelkezésre álló egyetlen számítógép gépideje (5 óra) jelenti. Érdemes a
legkönnyebb, legegyszerűbb probléma megoldásának megvalósításával kezdeni. A megoldásokban
nem kell szépségre és az eleganci ára törekedni, mert a zsűri ezt nem értékeli. Sokkal inkább a kód
egyszerűségére és átláthatósága kell, hogy a cél legyen. A feladatok elég nagy része többféleképpen is
megoldható. Azoknál a feladatoknál, ahol a megoldást el lehet érni a feladatbeli objektumok összes
esetének (pl. összes permutáció) vizsgálatával, ott a program rendelkezésére álló 1 perces futási időre
kell figyelnünk, azaz tisztában kell lennünk az átlagos PC-k (különösen a zsűri által használt PC) .:»
gyorsaságával. Viszont ha az összes eset vizsgálata belefér az egy perces futási időbe, nem érdemes a
szép és "eszes" megoldás megkeresésévei foglalkozni. A későbbiekben az adott helyen az egyes
algoritmusok futási idejére utalni fogunk.
A mellékletben található feladatok
A mellékletben található feladatokra a dolgozatban a feladatok azonosítójával hivatkozunk, amely
XXXXéé-n alakú, ahol XXXX a verseny helyszínének rövidítése, éé a verseny megrendezésének
évszáma évszázad nélkül, n pedig a feladat sorszáma. A mellékletben a feladatok évszám szerint
növekvő sorrendben találhatók. A formátumuk eltér az eredetitől, hogy egységesen kezelhessük őket.
A legnagyobb része a feladatoknak négy jól körbehatárolható csoportból kerül ki. Ezek a
következőek:
1. Szimulációs feladatok
Ezekben a feladatokban jól definiált objektumokkal találkozhatunk, amelyekhez szabályok tartoznak.
Az objektumok ezen szabályok szerint viselkednek. A megoldáshoz nem kell egyéb, mint a feladat
pontos megértése, az objektumok megfelelő gépi reprezentál ása és a szabályok pontos programozása.
Könnyebb szimulációs problémák: USSC85-3, KLTE91-1, RUGB91-1, RUGB91-2,
ACMF91-5, IOAG91-1, ODUN92-1, RUGB92-2, RUGB92-5, KLTE92-2, USEC92-
2, ACMF92-1, KLTE93-4
Nehezebb szimulációs problémák:KLTE91-2, RUGB91-5, RUGB91-7, ACMF91-3,
ACMF91- 4, RUGB92 -6, TUBP92-1,
A szimulációs feladatok megoldásával a dolgozatban nem foglalkozunk. Megoldásukat a Pascal
nyelvvel ismerkedőknek ajánlhatjuk. Megoldásukhoz csak alapvető matematikai ismeretekre van
szükség.
2. Kombinatorikai feladatok
lásd a dolgozat első fejezetét
3. Geometriai feladatok
lásd a dolgozat második fejezetét
4. Gráfelméleti problémák
lásd a dolgozat harmadik fejezetét.
4
6. Az egyes versenyek helyszínei és időpont ja:
Rövidítés Színt Helyszín Időpont
(forduló)
USSC85 2 ?, California , USA 1985.
KLTE91 1 KL TE, Debrecen 1991. Június
IOAG91 * Athen, 3rd International 1991. Május
Olympiad in Informatics
TUBP91 1 BME, Budapest 1991. Október
RUGB91 2 Gent, Belgium 1991. November
ACMF91 3 USA 1991.
USSC92 2 ?, USA 1992.
ODUN92 1 Norfolk, Va USA 1992. Szeptember
TUBP92 1 BME, Budapest 1992. Október
KLTE92 1 KL TE Debrecen 1992. Október
RUGB92 2 Gent, Belgium 1992. November
ACMF92 3 Indíanapolis, USA 1992.
KLTE93 1 KL TE, Debrecen 1993. Szeptember
5
7. 1. Kombinatorikai algoritmusok
1.1. Ismétlés nélküli permutációk
Vizsgáljuk először az ismétlés nélküli permutációk generálásának problémakörét. A probléma pontos
defmíciója a következő:
Adott a P={ 1,2 ..n} halmaz, előállítandó
a) az összes permutációja tetszőleges sorrendben.
b) az összes permutáció ja lexikografikus sorrendben.
c) a lexikografikus rendezés szerinti i-edik permutációja
A P összes permutációinak halmazát jelöljük P! -al : P! = {~, pz 'o 00' Pn! }
Először az egyik legegyszerubb módszer bemutatásával kezdjük. A módszert Fike publikálta 1975-
ben [Fike1975] , majd 1976-ban Rohl módosította [Roh1l976] .
Legyen S={(d2,d3, ••• ,dn) II~dk s k: k=2,3, .. n} ekkor S összesen 2*3* ..*n=n! vektort
tartalmaz. Vegyük észre, hogy S elemeit programmal könnyen lehet generálni: kis n esetén n-l darab
egymásba ágyazott ciklussal, ahol a ciklusváltozók értékei rendre az [1..2], [1..3], ... ,[1..n]
intervallumokat futják be, nagy n esetén rekurzívan. Ha egyszeruen programozható egy-egy értelmű
megfeleltetést adnánk S és P! elemei között akkor, mivel S elemeit könnyen generálhatjuk egyszeru
módszert kapnánk P! generálására. A Fike módszere a következő egy-egy értelmű megfeleltetést adja
S és P! elemei között: Legyen (d2, d3, ••• , dn) egyelem S-ből, ekkor a hozzátartozó P permutációt
úgy kapjuk, hogy kiindulva ~ = (1,2, ... , n) - ből, mint kezdeti permutációból cseréljük fel P; -ben a
k-adik elemet a dk -adikkal.
Mindezek alapján az algoritmus először az S-beli elemeket generálja, majd ebből állítja elő a fent
leírtaknak megfelelően a kapcsolódó permutációt.
Észrevehető, hogy a fenti algoritmus redundáns elemeket tartalmaz (pl ha dk = k, akkor felesleges
csere), ezen elemek kiküszöbölésére tett módosítást 1976-ban J. S. Rohl. A Rohl által módosított
algoritmus pascal programja a következő:
proeedure Fike_Rohl_perm(n:integer);
var p:array[l ..max) of integer; { n <= max}
i:integer;
proeedure permute(k:integer);
var temp, dk, dn:integer;
begin
if k=n then begin
proe (p) ;
temp: =p [nl ;
for dn:=n-l downto 1 do
begin
p[n) :=p[dn) r p l dn ) :=temp;
p.r t p j r
oc
p j dn l r=p I n l r
end;
p[n) :=temp;
end
else begin
permute (k+l) ;
temp :=p [k) ;
for dk:=k-l downto 1 do
begin
p [k) :=p [dk) ;
6
8. p[dk] :=tempi
permute(k+l)i
p [dk] :=p [k] i
e rid r
p[k] :=tempi
endi
endi
be gin {Fike_Rohl perm}
for i:=l to n do p[i] :=ii
permute(2)i
endi
Fig. 1.1.1..' Fike algoritmusa Rohl módosításaival
A versenyfeladatok megoldásánál a közölt eljárás általában jól használható, de azokban a
problémákban ahol a feladat szempontjából n állandó és n nem túl nagy (n<6), elképzelhető olyan
eljárás is amely n darab egymásba ágyazott ciklust tartalmaz az S elemeinek generálásához. Ennek
az algoritmusnak a gyorsaság mellett a kód egyszerűsége is az előnye.
A fenti módszer csupán az (a) problémára ad választ. Ha pl. valamely feladat a lexikografikus
sorrendben követeli meg tőlünk a permutációk felsorolását, akkor a módszerek egy újabb családjával
kell megismerkednünk [We1ll971] . Most egy olyan módszert mutatunk be, amely 1812-ből
származik, első említése [FiscI812] majd [ShenI962] . A módszer lényege négy lépés alkalmazása
=
egy adott P (Pl' P2'''' Pn) permutációra, amely eredményeként a lexikografikusan következö
permutációt kapjuk
A négy lépés:
(1) Legyen i a legnagyobb index, amelyre p i-l< Pi
(2) Legyen} az a legnagyobb index, amelyre Pi-I<Pj
(3) Cseréljük fel Pi-l -et p.-vel.
(4) Fordítsuk meg Pl' Pi+I,··.,Pnsorrendjét.
Fig. 1.1.2 ..'A lexikografikus felsorolás négy lépése
Pl. 1.1. Írjunk olyan pascal programot, amely az 1.1.2 ábra alapján lexikografikus sorrendben
generálja egy halmaz partícióit
Most egy másik, a permutációkat lexikografikusan felsoroló módszer mutatunk be. P! egy általános
elemének generálásakor az összes N={l,2 ..n} számnak hozzá kell rendelődnie a Pl -hez, majd az
N {Pl} -beli összes elemnek hozzá kell rendelődnie a P2-höz, és így tovább. Ezek alapján
algoritmusunk szerkezete a következő: Ahhoz, hogy a permutációkat lexikografikus sorrendben
kapjuk korlátoznunk kell az egyes Pj -k kiválasztásának sorrendjét. Ha a választható elemek közül
elsőként mindig a kisebbiket választ juk, akkor a permutációkat lexikografikusan növekvő sorrendben
kapjuk.
Kézenfekvő, hogy algoritmusunkban a választható elemeket egy listában tároljuk, a listák kezelése
(elem-törlés, -beszúrás, stb.) a pascal nyelvben a jól ismert mutatós módszerrel talán túl sok
adminisztrációs lépéssei járna, ezért kihasználva a jelen probléma specifikurnát a megfelelő listát egy
tömbbel szimuláljuk, legyen ez a:array[O ..n} o/integer. A tömb egy elemének indexe reprezentálja
i-edik listaelem által tárolt értéket, míg maga az elem a lista következő elemére mutat. Vagyis pl. az
[1,3,3,4,6,6,0] tömb az 1 ~ 3 ~ 4 ~ 6 listát reprezentálja.
7
9. Ekkor az a[O..n] inicializálását, feltöltését a következő eljárás végzi:
procedure Init;
var i: integer;
begin
for i:=O to n-l do a[i):=i+l;
a[n):=O;
end;
Tegyük fel, hogy p[1..n] egy globális tömb var p:array[1 ..n} of integer defmícióval, a permutációk
tárolásához, valamint már létezik a PrintPerm eljárás a kész permutációk megjelenítéséhez. Ekkor a
permutációkat lexikografikusan felsoroló algoritmus pascal kódja a következő:
procedure enum(i:integer);
var t:integer;
begin
t:=Oi
while a[t)<> O do
begin
p[i]:=a[t];
if i<> n
then
begin
a [t] :=a [a [t]];
enum(i+l) ;
a [t] : =p [i] ;
end
else
Printperm;
t:=a[t];
end;
Fig. J. J. 3.: Permutációk lexikografikusan
Az algoritmusunknál alkalmazott gondolatmenet, mint majd látni fogjuk ismétléses permutációkra is
általánosítható lesz.
A fenti algoritmusok teljesítménye között a versenyfeladatok megoldásának szempontjából lényeges
különbség nincs. Ezen azt kell érteni, hogy a megoldások a futásra felhasználható idő (1 perc) alatt
nagyjából n=ll-ig képesek az összes permutációt előállítani. Természetesen a fenti módszereken
kívül számos más módszer is ismeretes, melyek más-más célra használhatók a legalkalmasabban. A
különböző algoritmusok több szempontú összehasonlításával foglalkozik Roy [RoyI978] és Ives
[Ivesl976].
A probléma (c) részében megfogalmazottakra mind Fike [FikeI975], mind Wells [WeIIsi971] kínál
megoldást. A (c)-ben megfogalmazott probléma speciális esete (n=k) annak a problémának amikor
egy n elemű halmaz k-ad osztályú kombinációinak összes permutációit rendezzük lexikografikusan és
ezek között keressük a i-ediket. Ennek az általánosabb problémának a megoldása a "Kombinációk"
című fejezetben található.
A (c) problémára ezen kívül hasznos eligazítást találhatunk [BrowI970] -ban is.
Irodalomjegyzék az 1.1. fejezethez
[FikeI975]: C. T. Fike (1975). A permutation generation method. The Computer Journal, Vol.
18,p21.
[Rohll976]: 1. S. Rohl (1976). Programming improvements to Fike's algorithm for generating
permutations. The Computer Journal, Vol. 19, p 156.
8
10. [WeIll 1971]: M. B. Wells (1971). Elements ofCombinatorial Computing. Pergamon Press,
NewYork
[Fisc1912]: L. L. Fischer and K. Chr Krause (1812). Lehrbuch der Combinationslehre und der
Arithmetik. Dresden.
[Shen1962]: Shen, Mok-Kong (1962). BIT Vol. 2. p. 228.
[RoyI978]: M. K. Roy. (1978). The Computer Journal, VoI2l., p. 296.
[Ivesl976]: F. M. Ives. (1976).Permutation Enumeration: Four New Permutation Algorithms
CACM, Vol. 19., Nr. 2., p. 68.
[Brow 1970]: R. M. Brown: Decoding Combinations of the First n Integers Taken k at a Time.
CACM Vol. 3-4 p 235.
9
11. 1.2. Ismétléses permutációk
A probléma pontos definíciója a következő:
Adott 1 <= r <= n pozitív egész (n darab, r különböző elem permutációit keressük),
r
valamint azF = (ft .t; ···,fr') vektor, ahol n = LJ; és 1::;; J; (i = 1,2, ... r)
;=1
generálandó az M = {u,..,1,2,2, .. ,2, .... ,r,r, .. ,r}
~ ~ '---v---'
ft h /,
halmaz összes (ismétléses) permutációja.
Egy ilyen permutációt jelöljünk csakúgy, mint az előzőekben P = (Pl' P2"" Pn)-vel.
A P-t generáló algoritmusunk egybeesik azzal a módszerrel ahogyan "kézzel" felírnánk a fenti
permutációkat: Válasszunk M-ből egy elemet az összes lehetséges módon ez lesz Pl' minden egyes
ilyen választás után válasszunk egy elemet M{Pl}-ből P2-helyére, ..., és végül minden egyes Pn-l
kiválasztása után Pn helyére válasszunk M {Pl' P2"'" Pn-l} -ből, mint az előző fejezetben. Ha r=n
akkor az ismétlés nélküli permutációkat kapjuk. Ha az 'összes lehetséges módon' történő választást
az elemek növekvő sorrendjében végezzük el, akkor a permutációkat is lexikografikusan ebben a
sorrendben kapjuk. Rohl 1978-ban publikálta [Roh1l978] a fenti módszert némi általánosítással: Ha
az algoritmus során a kiválasztásokat nem végezzük el csak Ps -ig (S < n )-ig akkor n-elem s-ed
osztályú kombinációinak (ismétléses) permutációit (s-permutációit) kapjuk csakúgy, mint az előző
fejezetben.
Végül algoritmusunk programja a következő:
procedure genperm(m,f:vect;r,rO:integer);
const max=20;
type vect=array[l ..max] of integer;
var
p:intvect;
k:integer;
procedure choose(k:integer);
var
i:integer;
begin
for i:=l to r do
if f[i] <> O then
begin
p [k] : =m [i] ;
dec(f[i]);
if k<>rO then choose(k+l) else proc(p);
inc(f[i]);
end
end;
begin {genperm}
choose(l);
end;
Fig. 1.2.1.: Rohl algoritmusa (1978).
10
12. Ha valamely n elem ismétléses permutációi közül a lexikografikus sorrendben pontosan az i. -re van
sziikségünk akkor az ezt előállító algoritmust Wells [Welli971] munkájában találjuk. Készítsük most
el az i-edik lexikografikus ismétléses permutációt generáló algoritmus saját verzióját. Kiindulásként
alkalmazzuk Wells az "inverzfeladat"-ot megoldó algoritmusát [We1ll971] . Ez az algoritmus az
inputjaként egy permutációból előállítja az adott permutáció lexikografikus sorrendbeli sorszámát:
const max=100i
type intvect=array[O ..max] of longinti
function nalatt k (n:longintik:longint) :longinti
Var
i :integer i
result:longinti
Begin
result:=li
if k<>O then
for i:=O to (k-1) do result:=(result div (i+1) )*(n-i)i
nalatt k:=result
endi
function iperm2num(n,r:integerif,p:intvect) :longinti
(* osszesen k-1 fele objektumunk van,
osszesen n darab objektumunk van,
n=f [O]+f [1]+ ...+f [r-1]
f[j]: a j. objektumból f[j] darab van O <= j <= r-1
*)
var
H,MM,J:intvecti
q,i,jj:integeri
nn,v:longinti
begin
for i:=O to r-1 do begin h[i] :=OiMM[i] :=Oij[i] :=1 endi
for i:=O to n-1 do
begin
MM[p[i]] :=MM[p[i] ]+n_alatt k(h[p[i]],j [p[i]]) i
inc(h[p[i]]) iinc(j [p[i]]) i
for q:=O to p[i]-l do inc(h[q])i
e nd r
v:=liNN:=Oiq:=f[r-1]i
for jj:=r-2 downto O do
begin
NN:=NN+MM[jj]*Vi
q:=q+f[jj];
v:=v * n_alatt k(q,f[jj]);
end;
iperm2num:=NN
endi
Fig. 1.2.2.: Wellsféle JPERM2NUM foggvény (1971).
II
13. A mi feladatunk azonban olyan algoritmus írása, amely a sorszám alapján "legyártja" a hozzá
tartozó (ismétléses) permutációt.
Vegyük észre, hogy adott M és resetén M bármely MO részhalmazának a lexikografikusan első
(jelöljük [MO]F-al) ill. lexikografikusan utolsó permutációja (jelöljük [MO]L-el) egyszeruen
megadható az elemek sorbarendezésével.
Keressük tehát a M lexikografikusan K-adik permutációját P-t. Próbáljuk megkeresni Pl-et P első
betűjét, ekkor PI-re:
ipemünumcn -1, r', f', [{MPI }]L ) =< K,
ahol f ,[;] .= {fU] - 1, ha j = Pl ,(; .=
. . . " 1,2, ...r) (1)
f[;], egyébként
r' = {r :-1, ha f[PI] =1
r , egyébkén!
és nyilván Pl az a szimbólum amelyre iperm2num(n-l,r', f', [{MPI}]L) maximális (1)
tulajdonságú.
Pl után P2-t mint n-1 darab és r' különböző szimbólum lexikografikus sorrendben vett
K - perm2num(n -1, r', f', {M Pl}) sorszámú permutációjának első betűjeként keressük ..
és így tovább egészen Pn-ig. Legyen az ezt megvalósító pascal kód megírása ismét az olvasó feladata!
P 1.2.1. Írjunk olyan pascal függvényt, amely az előzőek alapján generálja a K. sorszámhoz tartozó
lexikografikus permutációját valamely M halmaznak !
Valamely halmaz ismétléses permutációit előállító algoritmust találunk még [Barti967] -ben és
[SagI964]-ben is.
Kapcsolódó versenyfeladatok:
Feladat Instrukció
UUSC85-2 Az összes esetek száma (kb. 9!) lehetővé teszi, hogy egyenként megvizsgáljuk őket
A megadott öt szám összes permutációit (5!) vizsgáljuk, az összes lehetséges
müveletjelezéssel (44).
TUBP91-4 Az egyenlő számjegyek elhagyása után az összes esetek száma 10!
RUGB92-1 Ismétléses permutációk lexikografikusan
RUGB92-4 Mivel a gráf csúcsainak száma nem több mint 8, ezért a csúcsok összes lehetséges
sorrendjét megvizsgálva (8!) a minimálisat bizonyosan megtaláljuk
ACMF92-2 A hálózatba kapcsolt gépek maximális száma 8, ezért az összes eset vizsgálata
KLTE93-3 lehetséges.
Irodalomjegyzék az 1.2 fejezethez
[Rohll978]: J. S. Rohl. (1978). Generating permutations by choosing. The Computer Journal,
Vol 21., p 303.
[we1ll971 ]: M. B. Wells (1971). Elements ofCombinatorial Computing. Pergamon Press, New
York
[Bratl967]: P. Bratley (1967).Permutations with repetitions. CACM Vol. 10. p. 450
[Sag1964]: T. W. Sag (1964) Permutations of set with repetitions. CACM Vol. 7. p 585.
12
14. 1.3. Kombinációk
Ebben a fejezetben a feladatunk n-elem r-edosztályú ismétlés nélküli kombinációinak generálása,
úgy, a generált kombinációk sorrendje is számít. Ahogy már az ismétlés nélküli permutációkkal
foglalkozó fejezetben utaltunk rá, ez a probléma az ismétlés nélküli permutáció generálás
általánosításának tekinthető.
Most az előző fejezetekkel ellentétben csak a legáltalánosabb eljárást mutatjuk be. Nem foglalkozunk
a "rendezettlen" kombinációk generálásával. A lexikografikus algoritmusok közül is csak azzal
foglalkozunk, amely a lexikografikusan t. kombinációt fogja közvetlenül előállítani.
Keressük praktikusan a Z; = {l, 2, .... , n} halmaz r-edosztályú kombinációinak összes permutációit,
vagyis a Pn.r = Pl>P2"'"
Pr ,ahol Pl>P2'" .,Pr E {l, 2, ... ,nl és Pi :j:; P, ha i :j:; j. vektorokat.
Ezek halmazát jelöljük P(n, r)-el. Nevezzük Pn. r = Pl> P2'"'' Pr -t a rövidség kedvéért el egy r-
permutációnak !
Legyen P; r = Pl' P2"'" Pr egy r-permutáció, ekkor Pn. r inverziója a Cn r = C1 , C2,···, Cr vektor,
ahol
Ci E {O, 1, .... ,n-i} és
r-i
Ci = Pi - i+ Lo i, j
j=l
ahol
o = {O,hap.<p. J'
i.j 1, ha P, > Pi
Egy Pn. r-hez tartozó Cn. r előállítása a fenti képIetet követve meglehetősen egyszerű, de
kihasználhatjuk a Pn.r és Cn.r kapcsolatának egy speciális tulajdonságát. Nevezetesen, hogy ha Z;
elemeit a már l.l-ben megismert listában tároljuk, akkor c1-et úgy kapjuk, hogy megszámláljuk,
hogy ebben a listában hány elem előzi meg Pl -et, majd Pl -et töröljük a listából, c2 értékének
meghatározásához az így nyert listában meg kell számlálnunk a P2 -t megelőző elemek számát, majd
P2 -t is töröljük a listából a többi Ci -t ugyanilyen módszerrel kapjuk:
proeedure eodingi
var i,t, eount:integeri
begini
Init;
for i:=l to r do
begin
eount:=O;
t:=O;
while p[i)<>a[t) do
begin
t:=a[t);
ine(eount)
end;
eli] :=eounti
a [t) :=a [a [t) )
end;
Fig. 1.3.1.: Permutációk inverziójánakgenerálása
13
15. A Cn, r ~ Pn, r átalakítást végző eljárás szintén a fenti tulajdonságot használja ki:
procedure Decoding;
var i,j,t:integer;
begin
Init;
for i:=l to r
begin
t:=O;
for j :=1 to c[i] do t:=a[t];
p [i] : =a [t] ;
a [t] :=a [a [t] ];
end
end;
Fig. 1.3.2.: A C-fP átalakítást végző eljárás.
Meg kell még jegyeznünk, hogy lexikograftkusan kisebb r-permutációhoz lexikografikusan kisebb
inverzió fog tartozni, vagyis, hogy a Cn,rBPn.r megfeleltetés, ilyen értelemben rendezés tartó.
A lexikografikusan t. r-permutációt közvetlenül előállító rPermGen nevű eljárás egyegy-egy értelmű
megfeleltetés a Pen, r) és a Z =
{l,2, ...,IP(n, r)l} halmazok között. Konstruáljuk meg először
rPermGen inverzét, vagyis azt a RankrPerm nevű eljárást amely lexikografikus sorrendben
megsorszámozza Z; r-permutációit.
RankrPerm konstruálásához felhasználjuk a fent bevezetett inverziók és P(n,r) egy-egy értelmű
=
kapcsolatát. Cn, r definíciójából látható, hogy egy cp cz, ... , c -vel kezdődő Cn. r CI' Cz, ... , Ci>"" Cr
j
inverziót pontosan [Pm-i, r-i)1 olyan inverzió előz meg, amelyeknek c ,c Z , •.• ,c előtagjára az igaz,
I l
j
hogy clj=cj j=I,2, .. ,i-Iéscl <c j ugyanis, a c +l végigfut ja a [O,l, ..,n-(i+l)] intervallum
j j
értékeit, C +Z pedig [O,1,..,n-(i+2)] intervallum on fut végig, és így tovább. Ekkor az így összeszámolt
j
esetek száma:
(n-i)*(n-(i+l))* ... *(n-(r-2))*(n-(r-l))= (n-i)! = IP(n-i,r-i)1 O
(n-r)!
Vagyis a RankrPerm eljárás a következő lesz:
Procedure RankrPerm(c:codeword, var Rank:integer);
var i:integer;
begin
Rank:=l;
for i:=l to r do Rank:=Rank+c[i]*P(n-i, r-i);
end;
Fig. 1.3.3.: A RankrPerm eljárás.
A hivatkozott P(n,k) függvény definíciója a pontosság kedvéért a következő:
nl
P(n,k) := .
(n-k)!
A RankrPerm eljárás az adott r-permutáció inverziójához rendeli a kívánt sorszámot, vagyis egy
adott r-permutáció esetén RankrPerm hívását meg kell előzze a Coding eljárás hívása.
Az rPermGen eljáráshoz a tulajdonképpen már l.2-ben is alkalmazott trial-and-error módszer
alkalmazásával jutunk. A módszert most is az inverzfüggvényre (a RankrPerm eljárás) alkalmazzuk:
14
16. procedure rPermGen(rank, k:integer; var c:codeword);
var i:integer;
be gin
for i:=n-k downto O do
if rank > i*p (n-k, r-k) {Trial}
then begin
c[k):=i;
if k <= r then RankrPerm(rank-i*P(n-k, r-k),k+l);
exit;
end
end;
Fig. 1.3.4.: Az rPermGen eljárás.
Azonos kiindulás után kissé eltérő gondolatmenetet találunk még [Knot1976]-ban a problémára. Az
eredeti problémához kapcsolódó feladatokat találunk még [Welll971] -ben.
Kapcsolódó versenyfeladatok:
Feladat Instrukció
KLTE91-3
KLTE92-1 Lexikografikus kombinációk felsorolása
Irodalomjegyzék az 1.3 fejezethez
[Knot197 6]: G. D. Knott (1976). A Numbering System for Permutations ofCombinations.
Communications of ACM, Vol 19, p 355.
[We1ll971]: M. B. Wells (1971). Elements of combinatarial programming. p 130.
15
17. 1.4. Egy halmaz összes részhalmazai, particionálása
Tegyük fel, hogy az a feladatunk, hogy egy halmaz (praktikusan az {1,2, ... ,n} halmaz) összes
részhalmazait kell generálnunk lexikografikus sorrendben.
A fenti halmaz részhalmazainak reprezentációja legyen a következő:
Minden egyes részhalmazt jelöljön egy f!. = ~ ~ ...
ar számsorozat, úgy, hogy
~ < a2 < ... < ar' ís r :s;n, ahol ~ ~ ... ar az adott részhalmaz elemi. Könnyen látható, hogy ez
a jelölés egyértelmű.
Definiáljuk most, az egy halmaz részhalmazainak halmazán értelmezett a lexikografikus rendezést:
Legyen f!.= ~ ~ ... =
ap és f l1 <; ... cq két részhalmaz,
akkor
ha létezik olyan i (l :s; i :s; q) amelyre minden 1 :s; j :s; i esetén aj = cj
és vagy ai < Ci
(1)
vagy p=i-1
akkor azt mondjuk, hogy ~ lexikografikusan megelőzi (kisebb, mint) c-t.
Mindezek alapján pl. n=4 esetén a fenti halmaz részhalmazai lexikografikusan növekvő sorrendben a
következők: 1,12,123,1234,124,13,134,14,2,23,234,24,3,34,4
A fenti halmaz részhalmazait n-jegyű bináris számokkal is reprezentálhatjuk: A '1 b2 ••• bn pontosan
akkor tartozik az A részhalmazhoz, ha bi = 1 <=> i E A (i = 1,2, .. , n)
Vegyük észre, hogya részhalmazokon értelmezett lexikografikus rendezés nem ugyanazt a sorrendet
adja, mint az őket reprezentáló bináris számokon (bit-sztringeken) értelmezett lexikografikus
rendezés.
Ha valamely feladat a részhalmazok felsorolásán kívül, nem követeli meg tőlünk a lexikografikus
rendezettséget, akkor az {1,2, ...,n} halmaz részhalmazainak felsorolása bináris
reprezentációjukban, nem más, mint az egész számok bináris alakjainak felsorolása. O-től 2n-l-ig.
Egy szám bináris alakjának keresésekor kihasználhatjuk, hogy a Pascal a Word típus esetén a
számokat éppen a nekünk megfelelő formában tárolja.
Egy kicsit bonyolultabb probléma az (l)-el defmiált rendezés szerint a részhalmazok felsorolása, ezt
valósítja meg a következő program:
Program Subset_enumeration;
Const n=13;
{ <n> elemű halmaz részhalmazait soroljuk föl lexikografikusan
A program ebben a sorrendben bármely t.-ediket képes generálni
(két részhalmaz akkor különbözik, ha van különböző elemük)
A teljesítményről: 486SX25-on n=13 esetén az összes részhalmaz
felsorolása kb 40 másodpercet vesz igenybe
var
t:longint;
i,r:integer;
b:array[l ..n] of integer; {ha az b[i]>O akkor az i-edik elem a
reszhalmazban van}
a:array[l ..n] of integer; {az elso r eleme nem mas, mint a t.
reszhalmaz }
function kettoad(x:integer) :longinti
var c:longint;i:integer;
be gin c:=l; for i:=l to x do c:=c*2; Kettoad:=c end;
16
18. procedure subset(t:integerivar r:integer)i
var
k:integerih:longinti
begin
for i:=l to n do b[i] :=Oi
r:=Oi
k:=li
repeat
h:=kettoad(n-k)i
if t<= h then begin b[k] :=li inc(r)i arr] :=k endi
t:=t-(l-b[k])*h-b[k]i
inc (k)
until ((k>n) or (t=O))
endi
begin
for t:=l to kettoad(n) do
begin
subset(t,r)i
writelniwrite(t,' :')ifor i:=l to r do write(a[i],' 'li
end
end.
Fig. 1.4.1.: Az {1,2, .... ,n} halmaz osszes részhalmazának felsorolása.
A fenti program az eredeti célkitűzést általánosítva alkalmas arra, hogy az {1,2, ... ,n} halmaz az (1)
szerinti rendezésben t-edik részhalmazát generálja. A program subset eljárása hívás után az a, b
globális változóban t-edik részhalmazt, az r-paraméterében pedig a t.-részhalmaz elemszámát adja
vissza.
Legyen Z; = {I, 2, .... , n}. Ekkor a Z; halmaz egy partíciója az azon halmazok
P = {7rl' 7r2, ••• , 7rm} halmaza, amelyre
7ri n 7rj = 0, ha i'1':. j
és 7r '1':.0, i=I,2, ... ,m
1
(2 )
m
és Unt
i=l
= Z;
feladatunk rögzített n esetén az összes fenti tulajdonságú halmazrendszer előállítása.
Számítsuk először ki, hányféle különböző particionálása létezik az {1,2, ... ,n} halmaznak !
Jelölje Bn a keresett partíciók számát (Bell-féle szám), ekkor n= 1 esetén nyilván B 1= 1
Tegyük fel, hogy Bl, B2, ••• ,Bn_l ismert. Ekkor Hn_l-hez vegyünk egy a többitől különböző elemet,
jelöljük ezt a-val. Keressük meg H; = Hn_
{a} összes partícióit !
l U
Világos, hogy azon partíció száma, amelyekben {a} szerepel B n-l' hiszen ezeket úgy nyerhetjük,
hogy Hn_l-minden partíciójához hozzávesszük a {a} halmazt. Továbbá az {a, p}-t (p EHn_l)
tartalmazó partíciók száma, egy rögzített p -esetén Bn_2, mivel p-t összesen (n~l)-féleképpen
választhat juk ki, ezért azon partíciók száma, amelyekben ex, egy kételemű részhalmazba esik
17
19. ( n~l) . Bn_l' és ugyanígy gondolkozva azoknak a partícióknak a száma, ahol a. egy i-elemű
részhalmazba esik (~~:). Bn_i
Vagyis H; = Hn- 1 u {a} összes partícióinak száma:
B n = n~.
1=1
(n-l)
1-
n (n-l) n-I
1 B n-m = 1=1 n - 1. B n-l.= k=O
L L
(n-l)
k Bk
A táblázat Bn értékeit mutatja:
12
4213597
Fig. 1.4.2.: s; értékei
A következőkben M. C. Er 1987-ben publikált [MCER1987] módszerét mutatjuk be.
Legyen a C = llC2 ••• cn kódszó a Z; egy partícióját leíró kódszava, úgy, hogy
n=4 esetén a következő táblázat mutatja a kódszavakat:
Kódszó
1111
1112
1121
1122
1123
1211
1212
1213
1221
1222
1223
1231
1232
1233
1234
Fig. 1.4.3.: Az {1,2,3,4} halmaz összes partíciói és a hozzájuk tartozó kódszavak.
A táblázatból is látható, hogy ebben a kódszó konstrukcióban 1 ~ Ci ~ i. Másszóval i E Z; nem
kerülhet 1ttbe, ha j > i. A következőkben azt vizsgáljuk, hogy a Z; halmaz összes partícióinak
kódszavai hogyan vezethetők le a Zn_l halmaz összes partícióit leíró kódszavak sorozatából
hozzáadva cn -t a már meglévő kódszavakhoz. cn értékei az 1,2 ... ,max( ll, C;... Cn-1) + 1 intervallum
értékei közül kerülnek ki.
Ezen tulajdonságok segítségével működik 4.4 algoritmusunk, amely a megfelelő kódszókat generálja.
A hivatkozott SP(m, p) eljárás definíciójában az m = max( eJ, ~ ... Cn_l) és p paraméter adja az
aktuális kódszó aktuális jegyét. Eljárásunk feltételezi a PrintPartition eljárást, amely a kódszó B
partíció konvertálást ill. az így nyert partíció megjelenítését végzi. Mindezek után eljárásunk a
következő:
18
20. type codeword=array[l ..max] of integeri
procedure SetPartitions(n:integer)i
var c:codewordi
procedure SP(m,p:integer)j
var i:integeri
begin
if p > n then PrintPartition(c)
else
begin
for i:=l to m do
begin
c[p] :=iiSP(m, p+1) i
endi
c[p] :=m+1i SP(m+1, p+1)
end
endi
begin
SP(O, 1)
endi
Fig. 1.4.4.: M C. Er rekurzív algoritmus az {l.2 ....•n} halmaz összes partíciójának generálásához.
A fenti Pascal kód Borland Pascal 7.0-ás verziójú fordítót használva napjaink átlagosnak mondható
teljesítményű PC-jén (486 SX 25) a következő futási időeredményeket adta:
n Futási idő
10 < 1 sec
11 < 4 sec
12 < 21 sec
13 < 130 sec
Fig. 1.4.5.: Az Er-féle rekurzív halmaz particionáló algoritmus futási ideje néhány n-re.
Mint a táblázatból is látható, a versenyfeladatok esetén akkor alkalmazható az algoritmus ha
feldatbeli n < 13.
Ha az eredeti problémát, úgy módosít juk, hogy csupán a lexikografikus sorrendben t. partíciót
keressük, akkor a fenti Er-féle algoritmus nem használható hatékonyan, hiszen ahhoz, hogy
megkapjuk a a t. partíciót le kell generáltatnunk a megelőző t-l darab partíciót is.
A lexikografikusan t. partíciót előállító algoritmust megkaphatjuk, ha megfeleltetést találunk a
természetes számok és a már ismertetett konstrukcióval előállított kódszavak között.
Az említett megfeleltetés bemutatásához vezessük be a következő jelöléseket:
Dn(r, d) :azon CIC2,,,,Cn kódszavak száma,
ahol CIC2,,,,Cr rögzített és
d = max(c ,cl 2, •. ,cr_l) (r=2, ... ,n+1; d=1, .. ,r-1)
Könnyű látni, hogy mivel minden kódszóra igaz, hogy CI = 1 ezért D; (2, 1) = En, továbbá az is igaz,
hogy Dn(n+l,d)=l (d=l, 2, ... n) ,valamint Dn(n,d)=d+l (d=l, ... ,n-l).
Határozzuk meg D; (n -1, h) -t!
19
21. Ekkor
Cl ,C2,··· ,cn-2 ,Cn-l ,Cn
'----v------'
max=h
(1)
vagyis cn_l helyére 1,2,,,.,h, h+ 1 kerülhet, ezek közül 1,2,,,.,h úgy, hogy maxfc, ,c2' oo,cn_l) = h
igaz marad, vagyis az ilyen kódszavak száma h* Dn(n, h),
ha cn-l = h + 1 (azaz cn_l-et is rögzítjük) , akkor az ilyen ( 1) tulajdonságú kódszavak száma
Dn (n, h + 1), vagyis összesen:
(2)
Hasonló okoskodással, kapjuk, hogy
Dn(r, d) = d * Dn(r + 1, d) + Dn(r + 1,d + 1) (r = 3,oo,n -1; d = l,oo.,r -1) (3)
Vagyis D; (r, d)értékei könnyen kiszámíthatók. Ezek alapján a sorszám ~ kódszó konverzíót végző
algoritmust könnyű elkészíteni:
procedure rank(t, n:integer; c:codeword)i
var r:integer;
begin
t:=l; d:=l;
for r:=2 to n
begin
do t:=t+([r]-1)*Dn[r+1, d];
if c[r]> d then d:=c[r]
end
end;
Az eljárás feltételezi, hogya Dn[2"n+ 1, 2"n+ 1] tömbben e D; (r, d) megfelelő értékei vannak.
Az inverz eljáráshoz, vagyis a kódszó ~ sorszám konverzióhoz, hasonlóan gondolkodva, mint az l.2
fejezetben a következő algoritmust kapjuk:
program Set_Partitionsi
const
Bell:array [0..15] of longint=
(1, 1, 2, 5, 15,
52, 203, 877, 4140, 21147,
115975, 678570, 4213597, 27644437, 190899322,
1382958545 )i
var
n:integerit:longint;r,i,j:bytei
Dn:array[1 ..18,1 ..18] of longinti
C:codewordi
procedure PreCalcD(n:integer); { A D(n) értékek kiszámítása}
var d,r:integer;
begin
Dn [2,1] :=BELL [n] ;
for d:=l to n-1 do Dn[n,d] :=d+1i
for d:=l to n do Dn[n+1,d] :=1;
for r:=n-1 downto 3 do
for d:=r-1 downto 1 do Dn[r,d]:= d*Dn[r+1,d] + Dn[r+1,d+1]i
20
22. endi
Procedure partition(tO:longint var d:byte)i
var r:integeri
t,m,k:longinti
Begin
t:=tO id: =1 i
for r:=2 to n do
begin
m r=O)
repeat inc(m) until t <= m * Dn[r+1,d])i
if d+1 < m then m:=d+1i
c [r] : =mr
t:=t-(m-1)*Dn[r+1,d]i
if d < m then d:=mi
endi
endi
begin
writelni write(' n:')i readln(n)i
PreCalcD(n)i c[l] :=li
for t:=l to BELL[n] do {a ciklus az összes partíciót generálja}
begin
partition(t,r)i {a t. partíció elő állítása}
for j:=l to r do
begin
write(j,'. reszhalmaz elemei:')i
for i:=l to n do if c[i]=j then write(i, " ')i
writeln
e nd r
end
end.
A programpartition(t, r) eljárása generálja a lexikografikusan t. kódszót. Az eljárás a kész kódszót a
már fent definiált codeword típusú C globális változóba teszi, míg a kódszóban leírt partíció
részhalmazainak számát az r-paraméterében adja vissza. A kódszó partícióvá alakítását a fóprogram
végzi. A fenti program egy adott n-hez tartozó {1,2, ...,n} halmaz összes partícióját előállítja úgy,
hogy végrehajtja a partition(t, r) eljárást a t= 1,2,3, .... ,Bn értékekre.
Az összes partíció generálását új algoritmusunk valamivellassabban végzi, viszont cserében a t.
partíciót közvetlenül, az ezt megelőző t-I partíció előállítása nélkül állítja elő.
A témakörhöz a következő feladat kapcsolódik:
KLTE92-7
A probléma neve: Szállítás
Mivel a probléma csupán az n<100 korlátozást tartalmazza, ezért az összes eset generálása és
ellenőrzése a fenti módszerek bármelyikévei is, nem valószínű, hogy megoldáshoz vezet akkor ha a
zsúri teszt inputadataiban a csomagok száma több, mint 13. Az előző korlátozás bevezetésévei 4.4
algoritmusunkkal a csomagok halmazának összes partícióinak előállításával és ellenőrzésévei
kiválaszthatjuk az optimálist.
Az eredeti feladat megoldásához más módszert kell keresnünk !
További kapcsolódó versenyfeladat: RUGB91-6
21
23. Irodalomjegyzék az 1.4 fejezethez
[MCER1987]: M.C. Er (1987) Alghorithm for generating Set Partitions. The Computer Journal
Vol. 31 No 3, pp 283
22
24. 1.5. Gray kódok
A Gray kódok olyan k-bites bitminták sorozata, amelyek olyan tulajdonságúak, hogy az egymást
követőek egymástól egyetlen bitben különböznek. Például a következő 3-bites Gray kódok
alkalmasak arra, hogy a O-tói 7-ig a számokat kódolják:
Gray kódok Sorszám Decimális
érték
000 O O
100 1 4
101 2 5
001 3 1
011 4 3
010 5 2
110 6 6
111 7 7
Fig. 1.5.1.: Gray kódok
A Gray kódok között speciális tulajdonságúak a Ciklikus Gray kódok, amelyekre a fentieken kívül az
is igaz, hogy az utolsó és az első is egyetlen bitben különbözik egymástól. A fentiek nem Ciklikus
Gray kódok. A továbbiakban csak Ciklikus Gray kódokkal foglalkozunk, ezért a rövidség miatt csak
Gray kódokként hivatkozunk rájuk.
Gray kódok Sorszám Decimális
érték
000 O O
001 1 1
011 2 3
010 3 2
110 4 6
111 5 7
101 6 5
100 7 4
Fig. 1.5.2.: Ciklikus Gray kódok
Itt megjegyezzük, hogy a Gray kódok O-val (minden bit O) kezdődnek és a következő sza.bállyal
generálhatóak: Minden egyes lépésben az adott Gray kódból egyetlen bit negálásával apjuk a
következő Gray kódot. Meg kell határoznunk ennek a bitnek a pozícióját. Erre a pozícióra pedig az
igaz, hogy pontosan fele annyiszor kel1 változtatni, mint a tőle közvetlenül jobbra ál1ót. Ez a módszer
viszonylag könnyen programozható, de megvan az a hátránya, hogyan-edik Gray kódot csak a
megelőző n-l kód legyártása után adja. Ismét két problémát fogalmazhatunk meg.:
(a) Soroljuk fel az összes k-bites Gray kódot
(b) Írjuk fel ak-bites Gray kódok közül az n. et.
Természetesen, a (b) probléma az általánosabb. De néha, ha csupán az (a) -problémát kell
megoldanunk akkor ha az (a)-t megoldó algoritmusunk egyszerűbb és gyorsabb, mint az
általánosabb (b)-t megoldó algoritmusunk, akkor érdemesebb azt használni (lásd például permutáció
generálás esetét).
Keressünk a fent már leírt módszemél egyszerűbb et az (a) problémához, valamint minél egyszerűbb
megoldást a (b)-problémához!
Jelöljük G(k) = (go. gp ...• g2k_) -val a k bites Gray kódokat.
23
25. Ekkor G-t a következő rekurzióval
defmiálhatjuk:
G(1) = (0,1)
{ G(n + 1) = (Ogo,Ogpo .. ,Og2n_l'Ig _, , ... , Ig ) (1)
2n o
Vagyis ezzel egyben módszert adtunk az (a) probléma megoldásához. Az (1) képletből könnyen
megkap hatjuk az m-edik Gray kód n. bitjét:
g(m, n) = (mmOd2"
2n- <1
)
( 2)
'
Vagyis a (b) problémát akár egy, a (2) formulát használó algoritmussal is megoldhat juk,
Legyen
D(n) = (ct.,~,... ,d2n), úgy, hogy di gi hányadik biten tér el gi-l -től.
akkor
D(1) =1
{ D(n+ 1) = (D(n),n+ (3)
1,D,ev(n)), ahol D,ev(n):= D(n) jordítottja
Ha
és
n
g; = (bn,bn_ p ••• ,'1), hogy i = 'Llj2j (4)
j=O
akkor
b. =/. xor J-
J J
l, I j = 1,2, .... ,n
Ez utóbbi formula teremt kapcsolatot egy szám bináris alakja és a hozzá tartozó Gray kód között.Az
ezen a formulán alapuló algoritmusokat megvalósító program oknak megfelelő típusválasztás esetén
(Pascal esetén a Word típus ilyen) nem kell tartalmazniuk bináris számmá alakító eljárást, hiszen azt
a számítógép változó-értékadáskor elvégzi. Sőt ha észreveszzük, hogya bj-t előállító sor egy bináris
l-bittel jobbratolást fed, akkor a programunk a szám bináris alakjából egyetlen utasítással
előállíthatja a megfelelő Gray kódot bitmintáját. Mivel a feladatok között találtam olyat, amely
megoldásához az (a) ill. (b) problémát is meg kell oldani, ezért a Gray kód generáló algoritmusunkat
ebbe ágyazva közöljük:
USEC92-1
A probléma neve Bit Twiddler:
Származása: 1992 ACM East Central Regional Programming Contest
A probléma lényege k-biten (k<=15 ) generálni a Gray kódokat az n.-től az m.-ig, n, m, k a program
inputjai. A program outputja a megfelelő Gray kódok decimális alakja:A problémát két
részproblémára lehet bontani:
(1) k-biten az n-edik Gray kód generálása
(2) valamely k-bites Gray kódból kiindulva a következő m darab k-bites Gray kód generálása
Mivel a feladat k-ra vonatkozó korlátja (k<=15) elegendően kicsi, ezért használhat juk a már fent
emIített pascalbeli Word típu st, valamint a memóriában elfér az összes l5-bites Gray-kód, amely
24
26. tartalmazza (l)-miatt az összes 14, 13, 12, ..,2, l-bites Gray kódot. Az init eljárás generálja a 15-
bites Gray kódokat, ahogy ígértük egyetlen értékadással. A program többi eljárása a
követelményeknek megfelelő outputot (az adott Gray kód decimális alakját) állítja elő.
program Twiddler;
uses ert;
type gray = array[O ..O] of word; {Csak a cím miatt}
var g: Agray;
b,j,t,i: word;
proeedure init;
{ A gray kódok előállítása}
var i:word;
begin
for i:=O to maxint do gA[i] := (i xor (i shr 1));
end;
proeedure put(tol,ig,biten:word);
var i:word;
begin
for i:=tol to ig do writeln(gA[i]);
end;
be gin
elrseri
getmem(g,65535);
init;
repeat
write('Mettől? ');readln(t);
write('Meddig? ');readln(i);
write('Hány biten? ')ireadln(b);
if b=O then halt(O);
put(t,i,b-1)
until false
end.
Fig. 1.5.3.: Twiddler.pas
25
27. 2. Geometriai algoritmusok
Bevezetés
A számítógépeket egyre többen és egyre gyakrabban használják olyan alapvetően geometriai eredetű,
nagy mennyiségű adat feldolgozásával kapcsolatos problémák megoldására, mint az alakfelismerés, a
térinformatikai, térképészeti vagy háromdimenziós szimulációs problémák. A geometriai
algoritmusok szintén nagyon fontosak az olyan komplex fizikai rendszerek tervezésében és a
elemzésében, mint például az épületek, autók, gépek és integráltáramkörök. Az ilyen
programrendszerekben a tervezők a fizikai objektumoknak megfeleitetett gépi objektumokkal
dolgoznak. Ezen gépi objektumok megfelelő szintű számítógépes kezelése, megjelenítése igazán
komoly feladat.
Az ilyen alkalmazások olyan alapvető objektumai, mint a pontok, szakaszok vagy poligonok és a
hozzájuk kapcsolódó alapvető eljárások adják az általában a nehezebbek közé sorolható geometriai
versenyfeladatok megoldásának alapjait.
A geometriai problémákat könnyen vizualizálhatók, de ez néha nem könnyíti meg a megoldás
keresését, inkább csak a probléma megértésében segítenek. Nagyon sok probléma megoldása, amely
"szabadkézzel" pillanatok alatt megoldható egy darab papír és ceruza segítségével (pl. eldönteni,
hogy egy pont egy poligon belsejében van -e vagy sem) követel egyáltalán nem triviális számítógépes
programot.
Néhány geometriai versenyfeladat megoldásához elegendő a középiskolai koordinátageometriából
tanult alapvető függvények, eljárások (pl. pontok távolsága a síkon, egyenes és pont távolsága,
háromszög területe) pontos (le)programozása. Ezek megoldása inkább a szimulációs problémák
megoldásához hasonlít.
További "geometriai" problémák megoldását tisztán vagy nagyrészt kombinatorikai algoritmusok
adják, ezért az ilyen problémák elemzése előtt érdemes a "Kombinatorikai algoritmusok" című
fejezetet (még egyszer) áttanulmányozni.
A geometriai tartalmú versenyfeladatok harmadik csoportja olyan problémákból áll, amelyek
megoldásához az első csoportbeli alapvető geometriai függvények pontos megvalósítása, valamint
kombinatorikai alapismeretek szükségesek.
A legtöbb algoritmus, amit tanulmányozni fogunk a legegyszerűbb geometriai objektumokkal fog
dolgozni a két dimenziós véges, de megfelelően kiterjedt síkon. A legalapvetőbb geometriai
objektumot a pontot egy számpárral fogjuk jellemezni (a pont 'koordinátái' a derékszögú koordináta
rendszerben). A szakasz reprezentációja egy pontpár, amelyeket az adott szakasz összeköt. A poligon
esetünkben pontok listája, és feltételezzük, hogy az egymásután következő pontok szakaszokkal
vannak összekötve, valamint, hogy az utolsó pontot az elsővel szintén egy szakasz köti össze, így
alkotva egy zárt alakzatot.
Az egységesség és az egyszerűség kedvéért rögzítsük le most, hogy hogyan fogjuk ezeket az
objektumokat a programjainkban reprezentálni.
A poligonok reprezentációja egy tömb. Használható lenne még láncolt lista is, de a listák alapvető
műveleteinek programozása a pascalban egy kicsit sok adminisztrációt követel meg a programtói,
ezért a tömbök használata a programokat egyszerűbbé teszi. Ha valamely probléma pontoknak egy
halmazát érinti, akkor a reprezentációban szintén az array[O ..max] of pont definíciót fogjuk
alkalmazni, ahol max valamilyen elegendően nagy szám.
Tehát programjaink a következő reprezentációkat fogják használni:
type pont = record
x :integeri
y: integer
endi
type szakasz = record
pl:ponti
p2:pont
26
28. endi
var poligon: array{O ..max] of ponti
Fig. 2. O.J.: Geometriai objektumok reprezentáció ja.
Furcsának tűnhet, hogy a pontok a koordinátarendszer rácspontjaira korlátozódnak. A valós
reprezentáció ugyanígy használható lenne, de az egész értékeket használata sokkal átláthatóbb és
hatékonyabb (az egész számok műveleteit a számítógép sokkal gyorsabban végzi, mint a
lebegőpontosakat) programot eredményez, nem beszélve arról, hogy a versenyfeladatok általában
megelégszenek az egész értékű geometriai programozással is.
27
29. 2.1. Szakaszok metszete
Az első probléma melyet tárgyalni fogunk az egyik legalapvetőbb probléma ezért számos más
algoritmus is hivatkozni fog rá, ezért nagyon fontos a precíz kidolgozása .. A probléma a következő:
Adott két szakasz, amelyek ponttá is fajulhatnak (kezdő és végpontjuk egybeesik) eldöntendő, hogy
metszik-e egymást vagy sem. A következő ábra néhány lehetséges előfordulást mutat:
f·
Fig. 2.1.1.: Szakaszok a síkon.
A kézenfekvő algoritmus esetünkben az, hogy számítsuk ki annak a két egyenesnek a metszéspontját,
amelyek tartalmazzák a kérdéses szakaszokat, majd vizsgáljuk meg, hogy a szakaszaink
tartalmazzák-e ezt a metszéspontot. Amennyiben a kérdéses egyenesek párhuzamosak, akkor azt kell
vizsgálnunk, hogy valamely szakasz végpontja a másik szakaszra esik-e. Az egyenesek
metszéspont jának kiszámítása visszavezethető egy kétismeretlenes egyenletrendszer megoldására,
amely útmutatást pl. [BELT1989]-ben találhatunk.
function metszet (pO,pl,p2,p3: pont) :booleanj
var
nev,a,b,c,d,x,y:reali
flagl,flag2,flag3,flag4:booleanj
begin
a:=pl.y-pO.Yi b:=pO.x-pl.x;
c:=p3.y-p2.Yi d:=p2.x-p3.Xi
nev:=a*d-b*ci {az egyenletrendszer matrixanak determinansa}
if (abs(a)+abs(b»O) and (abs(c)+abs(d»O)
{ha egyik szakasz sem pont}
then
if nev<>O {nem parhuzamosak}
then begin
{ (x,y) a tartalmazo egyenesek metszespontja }
y:=(a*(c*p2.x+d*p2.y)-c*(a*pO.x+b*pO.y))/nevi
x:=(d*(a*pO.x+b*pO.y)-b*(c*p2.x+d*p2.y))/nevi
flagl:=((pO.x<=x) and (x<=pl.x)) or
((pl.x<=x) and (x<=pO.x));
flag2:=((p2.x<=x) and (x<=p3.x)) or
((p3.x<=x) and (x<=p2.x))j
flag3:=((pO.y<=y) and (y<=pl.y)) or
((pl.y<=y) and (y<=pO.Y))j
flag4:=((p2.y<=y) and (y<=p3.y)) or
((p3.y<=y) and (y<=p2.Y))j
metszet:=flagl and flag2 and flag3 and flag4
end
else {a szakaszok parhuzamosak}
28
30. if a*p2.x+b*p2.y=a*pO.x+b*pO.y
then begin
flagl:=((( (pO.x<=p2.x) and (p2.x<=pl.x) ) or
( (pl.x<=p2.x) and (p2.x<=pO.x) ) ));
flag2:=((( (pO.x<=p3.x) and (p3.x<=pl.x) ) or
( (pl.x<=p3.x) and (p3.x<=pO.x) ) ));
flag3:=((( (p2.x<=pO.x) and (pO.x<=p3.x) ) or
( (p3.x<=pO.x) and (pO.x<=p2.x) ) ));
flag4:=((( (p2.x<=pl.x) and (pl.x<=p3.x) ) or
( (p3.x<=pl.x) and (pl.x<=p3.x) ) ));
metszet:= flagl or flag2 or flag3 or flag4;
end
else
metszet:=false
else {valamelyik szakasz pont l}
if ((abs(a)+abs(b))>O) and (abs(c)+abs(d)=O)
{a masodik szakasz pont}
then
if a*p2.x+b*p2.y=a*pO.x+b*pO.y
then
metszet:=((pO.x<=p2.x) and (p2.x<=pl.x)) or
((pl.x<=p2.x) and (p2.x<=pO.x))
else
metszet:=false
else
if (abs(a)+abs(b)=O) and (abs(c)+abs(d»O)
{az elso szakasz pont}
then
if c*pl.x+d*pl.y=c*p2.x+d*p2.y
then metszet:=((p2.x<=pl.x) and (pl.x<=p3.x)) or
((p3.x<=pl.x) and (pl.x<=p2.x))
else metszet:=false
else {mindketto pont}
metszet:=(p2.x=p3.x) and (pO.y=p2.y)
end;
Fig. 2.1.2.,' Szakaszok metszetén ek vizsgálata.
Sajnos a túl sok eset vizsgálata és kezelése eléggé bonyolult pascal kódot eredményez. A
versenyfeladatok megoldásához nem mindig szükséges a fentihez hasonló, általános algoritmus (pl.
ha a szakaszok nem fajulnak pontokká, akkor az algoritmus és vele együtt a kód is lényegesen
egyszerűbb). A fuggvény alkalmazhatóságát megnehezíti, hogy a kód nem eléggé átlátható és ezzel
együtt nehezen debug-olható,
Az algoritmust megvalósító fuggvényt könnyedén át lehet alakítani úgy, hogy amennyiben van
metszéspont annak koordinátáit adja is vissza, hiszen pl. nem elfajuló esetben a fuggvény x,y
változójában a metszéspont koordinátái előállnak.
A fentinél talán egyszerűbb és szintén általános algoritmust mutat be R. Sedgewick [SEDG1988]
művében. A közölt algoritmus nem számítja ki explicit módon a szakaszokat tartalmazó egyenesek
metszéspontját, hanem a szakaszvégpontok elhelyezkedése alapján dönti el, hogy van-e metszéspont.
Sedgewick közli az algoritmus pascal kódját is, de az sajnos nem működik. Viszont az algoritmusnak
megvan az az előnye a fentivel szemben, hogy a (helyes) pascal kódja talán kevesebb sorból állna.
29
31. Kapcsolódó problémák:
P2.2.1. Adott n szakasz, eldöntendö, hogy zárt poligont alkotnak-e.
P2.2.2. Adott n szakasz, eldöntendö, hogy páronként metszik-e egymást.
P2.2.3. Hány metszéspont ja van egy adott n csúcsú konvex poligon átlóinak.
Irodalomjegyzék a 2.1.1. fejezethez.
[SEDG 1988]: Robert Sedgewick: Algorithms. Second Edition. 1988. p. 349.
[BELTI989]: Bélteky Károly: Analitikus Geometria és Lineáris Algebra. 1989.
30
32. 2.2. Poligon és pont
A versenyeken gyakran tűnnek fel olyan feladatok, melyek arra az alapproblémára vezethetők vissza,
amelyben az algoritmusnak el kell döntenie, hogy egy adott pont egy adott (konvex vagy konkáv)
poligon belsejében van-e. A kérdést 'emberi' intelligenciával, szabadkézzel meglehetősen egyszerű
eldönteni, viszont a gépi megoldás már nem triviális.
Az egyik legegyszerűbb algoritmus a következő:
l. Ellenőrizzük, hogy a vizsgálandó pont nem esik-e a poligon valamely csúcsára vagy
oldalára. Ha igen akkor a pont az adott poligon belső pontja.
2. Keressünk egy olyan pontot a síkon, amely biztosan az adott poligonon kívül van. Mivel a
poligont véges sok pont feszíti ki, ezért valamely irányban egy elegendően távoli pont
biztosan a poligonon kívülre esik. Legyen ez a pont Q
3. A vizsgálandó pontból (P) húzzunk egy egyenest Q-ba.
4. Vizsgáljuk meg, hogy a PQ egyenesnek van-e közös pontja a poligon csúcsaival, ha igen
keressünk egy a mostani Q-tól különböző új külső pontot-o 2. pont
5. Számláljuk meg a PQ egyenes és a poligon oldalainak metszéspontjainak számát. Ha ez
páros akkor Papoligonon kívül volt, páratlan esetben P a poligon belső pontja.
A 4.pontbeli feltétel az ábrán is látható kivételek miatt kell ellenőriznünk. A programunkban egy
adott P-hez a megfelelő Q-t a következő módszerrel keressük meg:
4.1. Megkeressük azt a poligon[i] csúcsot, amely a poligon legjobboldalibb csúcsa
4.2.q.x:= poligon[i] .x+1, q.y:=poligon[i].y
4.3. Ellenőrizzük, hogy Q megfelel-e a fentieknek, ha nem q. y: =q. y+ 1, majd .....-;3.3.pont.
Mivel a poligon véges sok csúcsból áll, ezért a 4.3 ciklus véges sokszor (legfeljebb n-szer, ha n a
csúcsok száma) fog végrehajtódni, véges sok lépésben megtaláljuk a megfelelő Q pontot.
Q
Q
p
------..---,"
Fig. 2.2. J.: Pont és Poligon elhelyezkedései a síkon
A fenti algoritmus egy lehetséges megvalósítását mutatja a következő program:
31
33. program insidei
uses crti
const max=20i
type pont=record x,y:integer endi
var poligon: array[O ..max] of ponti
p,q:ponti
count,i,n:integeri
ctrl, ctr12:booleani
procedure InputPoligoni
var i:integeri
begin
clrscri
write('n :')ireadln(n)i
writeln('-------------------------------------------')i
for i:=l to n do
begin
writeln('Az " i , '-edik csucs koordinatai')i
write('x: ')ireadln(poligon[i] .X)i
write('y: ')ireadln(poligon[i] .y)i
endi
poligon[O] :=poligon[n]i
endi
procedure InputPonti
begin
writeln('A pont koordinatai')i
write('x: ')ireadln(p.x)i write('y: ')ireadln(p.y)i
endi
begin
InputPoligoni
InputPonti
ctr12:=falsei i:=Oi
repeat
ctr12:=ctr12 or metszet2(p,p,poligon[i],poligon[i+l])i
.i nc t í.j r
until (ctr12) or (i=n-l)i
if ctr12 then writeln('*8enne')
else be gin
q.x:=-maxinti
for i:=l to n do
begin
if poligon[i] .x > q.x
then
begin q.x:=poligon[i] .x+li q.y:=poligon[i].y endi
endi
repeat
q.y:=q.y+li
ctrl:=falsei
for i:=l to n do ctrl:=ctrl or
metszet(poligon[i],poligon[i],p,q)i
until not ctrli
32
34. if (p.x = q.x) and (p.y q.y) then writeln('*Kivul')
else begin
count:=O;
for i:=O to n-l do
if metszet(poligon[i], poligon[i+l], p,q) then inc(count);
if odd(count) then writeln('*Benne ') else writeln('*Kivul ');
end
end;
end.
Fig. 2.2.2.,' A fenti program eldönti, hogy egy adott pont egy adott poligon belsejében van e.
Az algoritmus egyetlen feltételezést támaszt az input adatokkal szemben, feltételezi, hogy azok a
megadott sorrendben egy zárt, önmagát nem metsző alakzat valamilyen rögzített körbejárás szerinti
csúcsai. Az alábbi poligonokat algoritmusunk inputjaként meg lehet úgy adni, hogy kielégítsék e
feltételt, ezért ezek az esetek is kezelhetők:
Sedgewick az előző problémánál hivatkozott könyvében erre a problémára is ad megoldást.
Sedgewick algoritmusa csakúgy, mint a fenti, egy félegyenest húz a vizsgálandó pontból egy fiktív, a
poligonon garantáltan kívülre eső ponton át, ám bármilyen legyen is ez (mindegy, hogy a poligon
csúcsain megy át vagy a poligon valamely oldalát tartalmazza ), ennek a félegyenesnek és a
poligonnak számolja össze a metszéspontjait, úgy, hogy közben figyeli az előforduló speciális
eseteket, de sajnos a fenti ábrán láthatókhoz hasonló esetekkel nem tud mit kezdeni.
Kapcsolódó problémák:
P2.2.1. Írjunk hatékony algoritmust, amely eldönti, hogy egy pont egy konvex poligon belsejében
van-el
P2.2.2. Írjunk olyan pascal függvényt, amely eldönti, hogy két háromszögnek van-e metszéspont ja.
Kapcsolódó versenyfeladatok
Feladat Instrukció
RUGB92-3 Itt csak azt kell vizsgálni, hogy a sík egy adott pontja egy adott háromszög
belsejébe esik-e, ezért a feladat jóval egyszerűbb módszerrel is megoldható
IOAG91-2
33
35. 2.3. Ponthalmaz konvex burka
Ebben a fejezetben egy olyan algoritmust kell készítenünk, amely meghatározza egy adott
ponthalmaz konvex burkát. A konvex burok nem más, mint egy konvex poligon, melynek csúcsai az
adott ponthalmaz pontjai közül valók és a többi pont a poligon oldalaira vagy belsejébe esik.
Fig. 2.3.1.: Ponthalmazok konvex hurka.
A pontosság kedvéért egyértelművé kell még tennünk azt az esetek, amikor a ponthalmaz 3 vagy több
pontja egy egyenesbe esik a ponthalmaz határán. Ilyenkor algoritmusunk outputját csak a poligon
csúcsaira eső pontok 'alkotják. Azaz a 2.3.1 b) ábrán látható ponthalmaz konvex burkát az
ABCDEFGH-val megjelölt pontokkal azonosítjuk.
A konvex burok egyik tulajdonsága, hogy bármely a poligonon kivüli egyenest a poligon felé
mozgatva az a poligont egy csúcsában érinti. Ezt a tulajdonságot alkalmazva a koordináta
tengelyekkel párhuzamos egyenesekre kapjuk, hogy a pontok közül azok, amelyek minimális ill.
maximális x illetve y koordinátákkal rendelkeznek biztosan a burokhoz fognak tartozni. Ezt a tényt
használjuk fel a soron következő algoritmusunk kiindulásaként.
Algoritmusunk bemenete pontok egy halmaza, amelyet egy array[J..max] alpont fog reprezentálni.
A kimenete pedig egy poligon. Minthogy a poligon típus is egy előbbihez hasonló tömb típus, ezért
az algoritmus a bemenetként kapott tömb elemeit egyszeruen átrendezi, úgy, hogy az első M
tömbelem fogja a ponthalmaz konvex burkát reprezentálni.
Látnunk kell azt, hogy azzal, hogy megtaláltuk és valamilyen módon megjelöltük a ponthalmaz
konvex burkát alkotó pontokat, még nem oldottuk meg teljesen a problémát. Az így megjelölt
pontokat ugyanis úgy kell sorrendbe rendeznünk, hogy azok valóban poligont alkossanak. Azaz a
2.3.1 b) ábrán látható ponthalmazt inputként megadva a GBCEDFAH output nem elfogadható.
Tehát a csúcspontként megjelölt pontokat, még rendeznünk kell. Ha ezen pontok (x, y) derékszögű
koordinátáit (r, d) polárkoordinátákká alakítjuk (ahol d az Origótól mért távolság, r pedig a pont
helyvektorának valamely rögzített egyenessei vett szöge), majd a pontokat a hozzájuk tartozó r érték
alapján rendezzük akkor a kívánt sorrendet kapjuk. Megfontolandó az, hogy az egész probléma nem
vezethető-e vissza a ponthalmaz pontjainak (r, d) szerinti lexikografikus rendezésére!
Algoritmusunk azonban explicit módon semmilyen rendezést nem fog tartalmazni, lesz azonban egy
kódrésze, amely a fent leírtak szerinti legkisebb r értékü pontot választja ki, anélkül, hogy
trigonometrikus fuggvényeket használna. A trigonometrikus fuggvények kiküszöbölése az
algoritmusból a megvalósított programot gyorsabbá, hatékonyabbá teszi. Az algoritmus:
1 Válasszuk ki a minimális y koordinátájú pontok közül azt amelynek a legkisebb x
koordinátája van.. A fentiek alapján ez a pont biztosan a konvex burok csúcspontja lesz.
Legyen ez a burok első pontja.
2 A burok második pontját úgy kaphat juk, hogy a burok első pontján át húzunk egy x-
tengely jel párhuzamos egyenest (az előző pont garantálja, hogy ezen egyenes alatt, már
nincsenek pontok), majd ezt az egyenest forgassuk az előző pont körül az óramutató
34
36. járásával ellentétes irányban addig, amíg valamely pontba ütközünk. Ha egyszerre több
pontba ütköztünk akkor válasszuk ki a távolabbi pontot, és ez lesz a burok következő pontja.
3. A burok következő pontját úgy kapjuk, hogy egyenesünket az utoljára burokpontként
megjelölt pont körül forgatjuk az eddigiekkel megegyező irányban addig amíg valamely
pontba nem ütközünk. Ha egyszerre több pontot is elért az egyenesünk, akkor mindig a
távolabbi pontot választ juk a burok következő pontjaként.
4. Ismételjük a 3.-beli eljárást addig míg a kiindulási pontunkat el nem értük.
Fig. 2.3.2.: Ponthalmaz konvex burkát előállító algoritmus.
Az algoritmusbeli feltételek garantálják, hogy a burokbeli pontokat a helyes sorrendben állítjuk elő.
Az algoritmust megvalósító pascal program technikai megoldása az, hogy az így megjelölt pontokat
az input tömb elejére cseréli és a már beválasztott pontokat nem vizsgálja újra. A következő ábra az
algoritmus működését szemlélteti:
1 1
4
3 3
1 1
4 4
5
5 3 3
1 1
Fig. 2.3.3.: A "csomagoló" eljárás működése.
Visszatérve a konvex burok értelmezésére, megadására, az algoritmust és így a programot is
egyszeruen át lehet alakítani, úgy, hogy az a burok csúcsoktói különböző pontjait is előállítsa
35
37. outputként. Egyszerűen akkor, amikor egyenesünk több ponttal ütközik egyszerre, akkor az érintett
pontok közül a legközelebbit kell a burok következő csúcspontjaként megjelölnünk.
Mindezek után az algoritmust megvalósító pascal program:
program becsomagoli
const
max=100i
type
pont=record
x:integeri
y:integer
erid r
var
inp,out:texti
ok,s:stringi
n,i,j,k:integeri
poligon = array[O ..max] of ponti
procedure str_to_pont(s:stringivar q:pont)i
var v,j,xl,x2:integeri
sl:stringi
begin
v:=pos(',',s)i sl:=copy(s,l,v-l)i
delete(s,l,v)i val(sl,xl,j)i
val(s,x2,j)j
q.x:=xli q.y:=x2i
e nd r
function theta(pl,p2:pont) :reali
var
dx,dy,ax,ay:integeri
t:reali
be gin
dx:=p2.x-pl.xiax:=abs(dx)i
dy:=p2.y-pl·Yiay:=abs(dY)i
if (dx=O) and (dy=O) then t:=-l else t:=dy/(ax+aY)i
if dx<O then t:=2-t else if dy<O then t:=4+t;
theta:=t*90.00
endi
function d(pl,p2:pont) :reali
var
dx,dy:reali
begin
dx:=p2.x-pl.Xi
dy:=p2.y-pl·Yi
d:=sqrt(dx*dx+dy*dy)
endi
function csomag (c:integer) :integeri
var
i,min,m:integeri
minszog,v,mintav,th:reali
t:ponti
begin
min:=li
36
38. for i:=2 to n do
if poligon[i] .y<poligon[min].y then min:=i
else
if poligon[i] .y=poligon [min] .y
then
if poligon[i] .x<poligon[min].x then min:=i;
m:=O;
poligon[n+l] :=poligon[min];
minszog:=-l;
repeat
inc (m) ;
t:=poligon[m];poligon[m] :=poligon[min];poligon[min] :=t;
min:=n+l;
v:=minszog;
minszog:=360.00imintav:=O;
for i:=m+l to n+l do
begin
th:=theta(poligon[m],poligon[i])i
if th >= v then
if th < minszog then
begin
min:=ii
minszog:=th;mintav:=d(poligon[m],poligon[min])
end
else
if th minszog
then
if c*d(poligon[m],poligon[i]) > c*mintav
then
begin
min:=i; minszog:=thi
mintav:=d(poligon[m),poligon[min))
end
end
until min=n+l;
csomag:=m
end;
begin
assign(inp, 'pontok.inp');
assign(out, 'burok.out');
reset(inp); rewrite(out);
repeat
readln(inp,s);
n:=O;
while s<>'*' do
begin
inc (n);
str_to_pont(s,poligon[n))i
readln(inp,s)
end;
for i:=l to csomag(-l) do
writeln(out,poligon[i) .x,',' ,poligon[i) .y) i
writeln (out, '*')
until eof(inp)i
close(inp);close(out);
end.
37
39. Fig. 2.3.4.: A Konvex Burok algoritmus.
A program a 'pontok imp' nevű imput állományból olvassa be a ponthalmazokat, és a 'burok.out'
nevű output állományba írja a megfelelő konvex burok csúcspontjait. Az állományok szerkezete a
következő:
Az input állomány blokkokból áll, egy blokk egy ponthalmaz pontjainak megadását tartalmazza,
úgy, hogy egy sorban pontosan egy pont kordinátái találhatók vesszővel elválasztva. A blokk végét '*'
jelzi.
Az output állomány az inputként megadott ponthalmazok konvex burkát tartalmazza . Egy sorban
pontosan egy pont koordinátái vesszővel elválasztva. A burok végét egy új sorban egy '*' jelzi.
A program csomag(c:integer) eljárása végzi a fent jellemzett eljárást. A c:integer paraméter
értékétől függ, hogy az output állományba, melyik megadás szerint kerüljenek a poligon pontjai. c >
O esetén, csak a poligon csúcspontjai, c < O esetén azon pontok is, amelyek a poligon oldalaira
esnek
A probléma általánosításának egyik lehetséges módja a több dimenziós térre történő kiterjesztése.
Könnyű belátni, hogy a fenti algoritmus több dimenziós térben is helyesen működik, ha elkészítjük a
fenti pascal program több dimenziós, hipersíkokat is kezelő változatát.
Kapcsolódó Problémák:
P2.3.l. Írjunk olyan algoritmust, amely online módon adja meg az inputként megadott ponthalmaz
konvex burkát. Az input pontokat egymás után egyesével adjuk meg az eljárásunknak és az eddig
megadott pontok konvex burkát adja meg lépésenként, módosítva azt minden újabb pont után ha
szükséges. lásd [SHAM1977]
Kapcsolódó versenyfeladatok:
Feladat Instrukció
IOAG91-2 A feladat megoldása mindkét eddig ismertetett algoritmus alkalmazását
megköveteli
ACMF92-4 A konvex burok algoritmus explicit alkalmazása.
KLTE93-2
Irodalomjegyzék a 2.3 fejezethez
[SHAM1977]: Shamos, M. 1. 1977, Computational geometry
[GRAHI972]: Graham, R. L. An efficent algorithm for determining the convex hull ofa fmite
planar set. Inform. Processing Letters 1 1972. P 132-133
[PREP 1977]: Prepatra, F. P., Hong S. J.: Convex hulls of fmite sets in two and three dimensions.
Communications of ACM, Vol 20. Num 2., p 87-93
38
40. 3. Gráfalgoritmusok
Bevezetés
A programozói versenyek "nehezebb" feladatainak legnagyobb hányadát a gráfelméleti feladatok
teszik ki. Ezek a feladatok lehetnek expliciten gráf elméleti fogalmakkal megfogalmazottak
(olyanokkal, mint feszítőfa vagy legrövidebb út), vagy valamilyen a mindennapi életből vett köntösbe
bújtatottak.
A gráf, mint matematikai objektum nem más, mint csúcsok és élek véges halmaza. A csúcsok a
legkülönfélébb más objektumok lehetnek, egyetlen kikötésünk, hogy valamilyen megkülönböztetésük
létezzen: legyen nevük vagy más azonosítójuk. A gráf egy éle összeköttetést, kapcsolatot reprezentál
a gráf pontosan két csúcsa között.
®
A fenti ábrán látható ABCDF, G, EHI csúcspontokkal jellemzett rajzok alkothatnak egy kettő vagy
három gráfot. Tekinthetjük például ABCDFG-t egy gráfnak. Ezt a gráfot definiálhat juk úgy, hogy
felsoroljuk a csúcsait: A, B, C, D, F, G majd az éleit AF, AD, BC, BD, Bf, CD.
Egy X csúcspontból pontosan akkor vezet út egy Y csúcsba egy gráfban, ha létezik a csúcsoknak egy
olyan listája, amelyben az egymás után következő csúcsok a gráfban éllel vannak összekötve. A gráf
összefüggő minden csúcsából létezik út minden más csúcsába. Pl a fenti ABCDF gráf összefüggő,
míg az ABCDFEHI gráf nem összefüggő. A gráf egy egyszerű útja azon út amelyhez tartozó
listában nincs ismétlődő csúcspont. A gráf egy kör-e azon egyszerű út, amelyben a kezdő és a
végpont megegyezik (vagyis a listában egyetlen ismétlődés van). Azt a gráfot, amelyben egyetlen kör
sincs és összefüggő fá-nak nevezzük (a nem összefüggő, körmentes gráf ot erdő-nek nevezzük). A
gráf feszítőfá-ja a gráf azon részgráfja, amely a gráf összes csúcsát tartalmazza, és pontosan annyi
élt a gráf élei közül, hogy az így nyert részgráf összefüggő legyen. Könnyen látható, hogy n csúcsú
gráf feszítőfájának pontosan n-l éle van. A feszítőfa másik ismert tulajdonsága, hogy bármely új élt
hozzáadva már kört kapunk. Azt a gráf ot, amely rendelkezik az összes lehetséges éllel teljes gráf-
nak, amelyik viszonylag kevés éllel rendelkezik ritka gráf-nak, amelyik elég sok éllel rendelkezik
sűrű gráf-nak nevezzük.
A gráf ok gépi reprezentációiról nagyon hasznos dolgokat tudhatunk meg [SEDGI988]-ban valamint
[NIE 11977] -ben.
A gráf algoritmusok bonyolultsága (itt most bonyolultságon az adott körülmények közötti legrövidebb
idejű megvalósíthatóságot kell érteni, ennek egyik leglényegesebb komponense a kód rövidsége és
egyszerűsége) nagyban függ attól, hogy a gráfok milyen reprezentációját választottuk. Általában a
legegyszerűbb kódot azon algoritmusok eredményezik, amelyekben a gráf reprezentációjaként
valamilyen mátrixos (szomszédsági vagy összefüggőségi) megoldást választottunk. Persze vannak
problémák, amelyeknél a listás reprezentáció elkerülése sokkal "költségesebb" lenne, mint annak
megvalósítása.
Irodalomjegyzék a 3. fejezethez
[SEDGI988]: Robert Sedgewick: Algorithms pp418 - 421
[NIEI1977]: J. Nievergelt, J. C. Farrar, E. M. Reingold: Matematikai Problémák Megoldásainak
Számítógépes Módszerei. 72-73 oldal
39
41. 3.1. Mélységi keresés
Az egyik legklasszikusabb gráf elméleti algoritmus a keresés. Ebben a problémában olyan kérdésekre
kell az algoritmusnak válaszolnia, mint van-e az adott gráfban kör, vagy melyek a gráf összefüggő
komponensei.
Fogalmazzuk meg először, mit is kell most keresésen értenünk! Legyen adott a gráfnak két csúcsa,
nevezetesen START és GOAL, a feladat megkeresni a STARTból a GOALba vezető utat, vagyis azt
a listát amelynek első elem START utolsó eleme pedig GOAL és az egymás után következő
listaelemeket a gráfban él köti össze.
l. Meg kell vizsgálnunk, hogya START csúcs megegyezik-e a GOAL-al, ha igen, akkor
kereső eljárásunk már véget is ért hiszen a keresett listának egyetlen elemből áll: a ST ART
csúcsból.
2. Ha nem vagyunk ilyen "szerencsések", akkor keressük meg a START csúcs összes
szomszédját, ezt a folyamatot kiterjesztésnek nevezzük. Az így nyert csúcsokat a START
csúcs "gyermekeinek", "utódjainak" vagy "rákövetkezőinek" nevezzük. Ennek a lépésnek az
eredménye egy lista.
3. Eztán a fenti két lépést alkalmazzuk az így nyert új csúcsokra, úgy, hogya 2. lépés után az
újabb listát az előző listához fűzzük.
p.z S1 csúcs kiterjesztése
Fig. 3.1.1.: Altalános kereső eljárás működése
A módszerek különbözőségének okát abban kell keresnünk, hogy a 2. lépés után kapott csúcsokat
milyen sorrendben terjesztjük ki. A két legismertebb kereső stratégia a szélességi és a mélységi
keresés. A mélységi keresés alkalmával mindig olyan "messze" haladunk a gráfban a START
csúcstól, amilyen messze csak lehet, vagyis a fenti ábrán mélységi stratégia szerint az S II csúcs
következne kiterjesztésre, szélességi stratégia szerint pedig az S2. De mindennél többet mond talán az
algoritmus formális leírása.
INPUT: A gráf két csúcsa START és GOAL
FELADAT: Meghatározni, hogy létezik-e egyszerű út START és GOAL között.
OUTPUT: Igen vagy Nem
1. Legyen NYILTAK egy lista, első eleme legyen START
Legyen ZÁRT szintén egy üres lista
2. Ha NYILTAK üres, akkor algoritmus vége és a válasz Nem
3. Vedd az első csúcsot a NYILTAK-ból legyen ez A. Szúrd A-t a ZÁRT lista elejére.
a) Ha A = GOAL akkor az algoritmus vége és a válasz Igen.
b) Hajtsd végre a kiterjesztést A-ra, ennek eredménye legyen az UTÓDOK listája.
b1. Vedd ki az UTÓDOK listájából azon csúcsokat, melyek szerepelnek a
ZÁRT listában
b2. Fűzd az UTÓDOK listáját a NYILTAK listája elé.
b3 Menj 2. -re
Fig. 3.1. 2.: A mélységi keresés algoritmusa.
40
42. A fenti algoritmus, nem használja ki az élek reflexivitását, vagyis irányított gráf okra is tökéletesen
működik. A mélységi keresési stratégiát természetesen nem csak arra használhatjuk, hogy
megkeressük két csúcs között egy gráfbeli utat. Használhatjuk pl. arra is, hogy megtudjuk, hogy egy
adott gráf tartalmaz-e kört. Ugyanis ha a 3bl-ben ténylegesen ki kell vennünk valamely csúcsot a
ZÁRT listából, akkor ez azt jelenti, hogy ehhez a csúcshoz egy újabb útvonalon eljutottunk, vagyis,
hogy a gráf kört tartalmaz. Azt is könnyen beláthatjuk, hogy a stratégia segítségével leválogathatjuk
egy gráf összefiiggő komponenseit: Meg kell jelölnünk a kiterjesztett csúcsokat és ha a NYILTAK
listája már üres és még van a gráfban meg nem jelölt csúcs, akkor ez azt jelenti, hogy ez a meg nem
jelölt csúcs a gráf eddigitől különböző komponensében van.
A témával kapcsolatban nincs már más hátra, mint a fenti ellenőrzéseket elvégző pascal program:
program deptfirsti
{
input formatum (a graf megadasa)
1. sor csucsok szama (v)
2. sor elek szama (e)
2+1. sor elso ,1: a ket csucs sorszama amelyeket osszekot
[space]-szel elvalasztva.
2+2. sor masodik,l
2+e sor e-edik ,1
const maxV=200;
type link=Anode;
node=record
v :integer;
next :link
end;
var inp,out:text;
id,k,l,v,e,last:integer;
components:integer; {ennyi osszefuggo komponens}
graph: array[l ..MaxV] of link;
order: array[l ..MaxV] of integer;
cycle:boolean {=true ha van benne kor };
procedure Str2Vertex(s:string;var k,j:integer);
var v,h:integer;
xl,x2:integer;
sl:string;
begin
v:=pos(' ',s); sl:=copy(s,l,v-l);
delete(s,l,v); val(sl,k,h); val(s,j,h)
end;
procedure ReadInput;
var
j,i,x,y:integer;
s:string; t:link;
begin
assign(inp, 'deptlst.inp'); reset(inp); readln(inp,v);
41
43. readln(inp,e); for i:=l to v do graph[i] :=NIL;
for j:=l to e do
begin
readln(inp,s);Str2Vertex(s,x,y);
new(t);tA.v:=x; tA.next:=graph[y];graph[y] :=t;
new(t);tA.v:=y; tA.next:=graph[x];graph[x] :=t;
end;
end;
Procedure visit(k:integer);
var t:link;
begin
inc(id);order[k] :=id; t:=graph[k];
while t<>NIL do
begin
if order[tA.v]=O then visit(tA.v) else cycle:=true;
t:=tA.next
end
end;
Procedure Conclusion;
begin
writeln(out, 'Osszefuggo komponensek: ',components); {/ A /}
if cycle then writeln('kor :van ')
else if components=l then writeln('fa') else writeln('erdo');
writeln(out)
end;
begin
assign(out, 'deptlst.out'); rewrite(out);
ReadInput;
id:=O; last:=O; components:=O;
for k:=l to v do order[k] :=0;
for k:=l to v do
begin last:=idi
if order[k]=O
then begin
visit(k); inc(components);
writeln(components, osszefuggo reszgraf:');
for 1:=1 to v do
if (order[l]<=id) and (order[l]>last) then write(l, I ')i
writeln;
end
end;
Conclusion;
close(out)
end.
Fig. 3.1.3.: A mélységi keresés.
A program feltételezi, hogy az input fileban megadott csúcsok és élek egyetlen gráfhoz tartoznak. A
program az előzőek szerinti stratégiát használva alkalmas:
-a gráf bejárására (minden csúcsot pontosan egyszer meglátogat)
- a gráf összefüggő komponenseinek leválogatására
42
44. - eldönti egy gráfról, hogy van-e benne kör; (nem sorolja fel a köröket)
- ezek alapján eldönti, hogy a gráf fa ill erdő-e.
A gráf reprezentációja most egy tömb. A tömb elemei listák, minden csúcshoz pontosan egy. a lista
elemei az adott csúcs szomszédjai. Vagyis a fenti gráfot programunkban a következő tömb
reprezentálja:
graph[1]= 6 -> 4
graph[2]= 3 -> 4 -> 6
graph[3]= 2 -> 4
graph[4]= 2 -> 3
graph[5]= 8 -> 9
graph[6]= 1 -> 2
graph[7]=
graph[8]= 5 -> 9
graph[9]= 5 -> 8
Könnyű látni, hogy ez a reprezentáció alkalmas irányított gráf ok gépi reprezentálására is. Irányított
gráfok esetén egy adott élt, csak egyszer kell szerepeltetni a struktúrában.
Az order[l..MaxVJ tömb tartalmazza a gráf bejárási sorrendjét, kezdetben minden eleme O. A gráf
azon összefuggő komponensének mélységi bejárását, amely a paraméterként megadott csúcsot
tartalmazza a rekurzívan működő visít eljárás végzi. A visít eljárást (a kiterjesztést) a l-es csúcsra
végrehajtva az eljárás megkeresi az l-es csúcs szomszédjai közül, azt amely még nem volt megjelölve
az order tömb segítségével. Ha talál, ilyet akkor erre végrehajtja a visít eljárást, ha nem talál ilyet
akkor ez azt jelenti, hogy, az adott csúcs minden szomszédját bejártuk, vagyis vissza kell lépnünk egy
szinttel "feljebb" (backtrack) ....
Egy gráf összefüggő komponenseinek leválogatására alkalmas Warshall algoritmusa is [NIE21977]
Kapcsolódó versenyfeladatok
Feladat Instrukció
KLTE91-5 Megoldható más, nem gráf elméleti módszerrel is. A gráf os módszer megvalósítása
hosszabb és bonyolultabb módszert követel.
IOAG91-4 A feladatban egy gráf legtöbb csúcsot tartalmazó összefuggő komponensét kell
megkeresnünk, ez a fenti program segítségével lehetséges. Mivel a feladatban a
gráf a szomszédsági mátrixával adott, ezért Warshall algoritmusa is nagyon jól
alkalmazható.
ACMF91-1 Útkeresés.
KLTE92-3 Körkeresés
KLTE92-5 Útkeresés.
USEC92-3 Útkeresés.
TUBP92-5 Összefuggő komponensek szétválasztása.
ACMF92-5 Vegyünk sorra minden élt a feladat irányított gráfjában. Majd az adott élt hagyjuk
el az eredeti gráfból . Ha ebben a gráfban létezik út az adott él végpontjai között
akkor az adott él által reprezentált függőség redundáns.
Irodalomjegyzék a 3.1 fejezethez
[NIE21977]: J. Nievergelt, J. C. Farrar, E. M. Reingold: Matematikai Problémák Megoldásainak
Számítógépes Módszerei. 77 - 78 oldal.
43