produce la ieire valoarea 2+2=4. asociativitate: pentru simplificare vom presupune c numerele sunt memorate cu o singur cifr semnificativ i c la nmulire se face trunchiere. Atunci rezultatul nmulirilor (0.50.7)0.9 este 0.30.9=0.2, pe cnd rezultatul nmulirilor 0.5(0.70.9) este 0.50.6=0.3.
2) Nu intereseaz n general demonstrarea teoretic a existenei algoritmilor, ci accentul este pus pe elaborarea algoritmilor. Vom pune n vedere acest aspect prezentnd o elegant demonstraie a urmtoarei propoziii: Propoziie. Exist ,R\Q cu Q. Pentru demonstraie s considerm numrul real x=aa, unde a= 2 . Dac xQ, propoziia este demonstrat. Dac xQ, atunci xaQ i din nou propoziia este demonstrat.
Corectitudinea algoritmilor
n demonstrarea corectitudinii algoritmilor, exist dou aspecte importante: Corectitudinea parial: presupunnd c algoritmul se termin (ntr-un numr finit de pai), trebuie demonstrat c rezultatul este corect; Terminarea programului: trebuie demonstrat c algoritmul se ncheie n timp finit. Evident, condiiile de mai sus trebuie ndeplinite pentru orice set de date de intrare admis. Modul tipic de lucru const n introducerea n anumite locuri din program a unor invariani, adic relaii ce trebuie ndeplinite la orice trecere a programului prin acel loc.
Exemplul 1. Determinarea concomitent a cmmdc i cmmmc a dou numere naturale. Fie a,bN*. Se caut: (a,b)=cel mai mare divizor comun al lui a i b; [a,b]=cel mai mic multiplu comun al lui a i b. Algoritmul este urmtorul:
x a; y b; u a; v b; while xy { xv+yu = 2ab; (x,y)=(a,b) } if x>y then x x-y; u u+v else y y-x; v u+v write(x,(u+v)/2)
(*)
Demonstrarea corectitudinii se face n trei pai: 1) (*) este invariant: La prima intrare n ciclul while, condiia este evident ndeplinit. Mai trebuie demonstrat c dac relaiile (*) sunt ndeplinite i ciclul se reia, ele vor fi ndeplinite i dup reluare. Fie (x,y,u,v) valorile curente la o intrare n ciclu, iar (x',y',u',v') valorile curente la urmtoarea intrare n ciclul while. Deci: xv+yu=2ab i (x,y)=(a,b). Presupunem c x>y. Atunci x'=x-y, y'=y, u'=u+v, v'=v. x'v'+y'u'=(x-y)v+y(u+v)=xv+yu=2ab. Cazul x>y se studiaz similar. 2) Corectitudine parial: Este binecunoscut c n x=y se obine d=(a,b). Conform relaiilor (*), avem d(u+v)=2d2, unde a=d i b=d. Atunci (u+v)/2=d=ab/d=[a,b]. 3) Terminarea programului: Fie {xn}, {yn}, {un}, {vn} irul de valori succesive ale variabilelor. Toate aceste valori sunt numere naturale pozitive. Se observ c irurile {xn} i {yn} sunt descresctoare, iar irul {xn+yn} este strict descresctor. Aceasta ne asigur c dup un numr finit de pai vom obine x=y.
Exemplul 2. Metoda de nmulire a ranului rus. Fie a,bN. Se cere s se calculeze produsul ab. ranul rus tie doar: s verifice dac un numr este par sau impar; s adune dou numere; s afle ctul mpririi unui numr la 2. Cu aceste cunotine, ranul rus procedeaz astfel:
(*)
Ca i la exemplul precedent, demonstrarea corectitudinii se face n trei pai: 1) (*) este invariant: La prima intrare n ciclul while, relaia este evident ndeplinit. Mai trebuie demonstrat c dac relaia (*) este ndeplinit i ciclul se reia, ea va fi ndeplinit i la reluare. Fie (x,y,p) valorile curente la o intrare n ciclu, iar (x',y',p') valorile curente la urmtoarea intrare n ciclul while. Deci: xy+p=ab. Presupunem c x este impar. Atunci (x',y',p')=((x-1)/2,2y,p+y). Rezult x'y'+p'=(x-1)/2+p+y=xy+p=ab. Presupunem c x este par. Atunci (x',y',p')=(x/2,2y,p). Rezult x'y'+p'=xy+p=ab. 2) Corectitudine parial: Dac programul se termin, atunci x=0, deci p=ab. 3) Terminarea programului: Fie {xn}, {yn} irul de valori succesive ale variabilelor corespunztoare. Se observ c irul {xn} este strict descresctor. Aceata ne asigur c dup un numr finit de pai vom obine x=0.
Optimalitatea algoritmilor
S presupunem c pentru o anumit problem am elaborat un algoritm i am putut calcula i timpul su de executare T(n). Este natural s ne ntrebm dac algoritmul nostru este "cel mai bun" sau exist un alt algoritm cu timp de executare mai mic. Problema demonstrrii optimalitii unui algoritm este foarte dificil, n mod deosebit datorit faptului c trebuie s considerm toi algoritmii posibili i s artm c ei au un timp de executare superior. Ne mrginim la a enuna dou probleme i a demonstra optimalitatea algoritmilor propui, pentru a pune n eviden dificultile care apar. Exemplul 1. Se cere s determinm m=min(a1,a2,...,an). Algoritmul binecunoscut este urmtorul:
m a1 for i=2,n if ai<m then m ai care necesit n-1 comparri ntre elementele vectorului a=(a1,a2,...,an).
Propoziia 1. Algoritmul de mai sus este optimal. Trebuie demonstrat c orice algoritm bazat pe comparri necesit cel puin n1 comparri. Demonstrarea optimalitii acestui algoritm se face uor prin inducie. Pentru n=1 este evident c nu trebuie efectuat nici o comparare. Presupunem c orice algoritm care rezolv problema pentru n numere efectueaz cel puin n-1 comparri s considerm un algoritm oarecare care determin cel mai mic dintre n+1 numere. Considerm prima comparare efectuat de acest algoritm; fr reducerea generalitii, putem presupune c s-au comparat a1 cu a2 i c a1<a2. Atunci m=min(a1,a3,...,an=1). Dar pentru determinarea acestui minim sunt necesare cel puin n-1 comparri, deci numrul total de comparri efectuat de algoritmul considerat este cel puin egal cu n. Exemplul 2. Se cere determinarea minimului i maximului elementelor unui vector. Mai precis, se cere determinarea cantitilor m=min(a1,a2,...,an) i M=min(a1,a2,...,an). Determinarea succesiv a valorilor m i M necesit timpul T(n)=2(n-1). O soluie mai bun const n a considera cte dou elemente ale vectorului, a determina pe cel mai mic i pe cel mai mare dintre ele, iar apoi n a compara pe cel mai mic cu minimul curent i pe cel mai mare cu maximul curent:
if n impar then m a1; M a1; k 1 else if a1<a2 then m a1; M a2 else m a2; M a1; k 2 { k = numrul de elemente analizate } while kn-2 if ak+1<ak+2 then if if else if if k k+2
m M m M
S calculm numrul de comparri efectuate: pentru n=2k, n faza de iniializare se face o comparare, iar n continuare se fac 3(k-1) comparri; obinem T(n)=1+3(k-1)=3k-3=3n/2-2=3n/2-2. pentru n=2k+1, la iniializare nu se face nici o comparare, iar n continuare se fac 3k comparri; obinem T(n)=(3n-3)/2=(3n+1)/2-2=3n/2-2. n concluzie, timpul de calcul este T(n)=3n/2-2.
Propoziia 2. Algoritmul de mai sus este optimal. Considerm urmtoarele mulimi i cardinalul lor: - A= mulimea elementelor care nu au participat nc la comparri; a=|A|; - B= mulimea elementelor care au participat la comparri i au fost totdeauna mai mari dect elementele cu care au fost comparate; b=|B|; - C= mulimea elementelor care au participat la comparri i au fost totdeauna mai mici dect elementele cu care au fost comparate; c=|C|; - D= mulimea elementelor care au participat la comparri i au fost cel puin o dat mai mari i cel puin o dat mai mici dect elementele cu care au fost comparate; d=|D|; Numim configuraie un quadruplu (a,b,c,d). Problema const n determinarea numrului de comparri necesare pentru a trece de la quadruplul (n,0,0,0) la quadruplul (0,1,1,n-2). Considerm un algoritm arbitrar care rezolv problem i artm c el efectueaz cel puin 3n/2-2 comparri. S analizm trecerea de la o configuraie oarecare (a,b,c,d) la urmtoarea. Este evident c nu are sens s efectum comparri n care intervine vreun element din D. Apar urmtoarele situaii posibile: 1) Compar dou elemente din A: se va trece n configuraia (a-2,b+1,c+1,d) . 2) Compar dou elemente din B: se va trece n configuraia (a,b-1,c+1,d+1) . 3) Compar dou elemente din C: se va trece n configuraia (a-2,b,c-1,d+1) .
4) Se compar un element din A cu unul din B. Sunt posibile dou situaii: - elementul din A este mai mic: se trece n configuraia (a-1,b,c+1,d); - elementul din A este mai mare: se trece n configuraia (a-1,b,c,d+1). Cazul cel mai defavorabil este primul, deoarece implic o deplasare "mai lent" spre dreapta a componentelor quadruplului. De aceea vom lua n considerare acest caz. 5) Se compar un element din A cu unul din C. Sunt posibile dou situaii: - elementul din A este mai mic: se trece n configuraia (a-1,b,c,d+1); - elementul din A este mai mare: se trece n configuraia (a-1,b+1,c,d). Cazul cel mai defavorabil este al doilea, deoarece implic o deplasare "mai lent" spre dreapta a componentelor quadruplului. De aceea vom lua n considerare acest caz. 6) Se compar un element din B cu unul din C. Sunt posibile dou situaii: - elementul din B este mai mic: se trece n configuraia (a,b-1,c-1,d+2); - elementul din B este mai mare: se rmne n configuraia (a,b,c,d). Cazul cel mai defavorabil este al doilea, deoarece implic o deplasare "mai lent" spre dreapta a componentelor quadruplului. De aceea vom lua n considerare acest caz. Observaie. Cazurile cele mai favorabile sunt cele n care d crete, deci ies din calcul elemente candidate la a fi cel mai mic i cel mai mare. Odat stabilit trecerea de la o configuraie la urmtoarea, ne punem problema cum putem trece mai rapid de la configuraia iniial la cea final. Analizm cazul n care n=2k (cazul n care n este impar este propus ca exerciiu). Paii sunt urmtorii: - plecm de la (n,0,0,0)=(2k,0,0,0); - prin k comparri ntre perechi de elemente din A ajungem la (0,k,k,0); - prin k-1 comparri ntre perechi de elemente din B ajungem la (0,1,k,k-1); - prin k-1 comparri ntre perechi de elemente din C ajungem la (0,1,1,n-2). n total au fost necesare k+(k-1)+(k-1)=3k-2=3n/2-2 comparri.
Existena algoritmilor
Acest aspect este i mai delicat dect precedentele, pentru c necesit o definiie matematic riguroas a noiunii de algoritm. Nu vom face dect s prezentm (fr vreo demonstraie) cteva definiii i rezultate. Un studiu mai amnunit necesit un curs aparte! Noiunea de algoritm nu poate fi definit dect pe baza unui limbaj sau a unei maini matematice abstracte. Numim problem nedecidabil o problem pentru care nu poate fi elaborat un algoritm. Definirea matematic a noiunii de algoritm a permis detectarea de probleme nedecidabile. Cteva dintre ele sunt urmtoarele: 1) Problema opririi programelor: pentru orice program i orice valori de intrare s se decid dac programul se termin. 2) Problema opririi programelor (variant): pentru un program dat s se decid dac el se termin pentru orice valori de intrare. 3) Problema echivalenei programelor: s se decid pentru orice dou programe dac sunt echivalente (produc aceeai ieire pentru aceleai date de intrare).
Noiunea de clas
class C { int x; boolean y; C() { x=2; } C(int a, boolean b) { x=a; y=b; } int met() { if (y) return x; else return x+1; } void met(int x) { if (this.x==x) y=true; } } // cmpuri // constructor // constructor // metoda
// metoda
Apare prima caracteristic a OOP: ncapsularea. Declararea i crearea unui obiect de tipul C
C Ob; Ob = new C();
sau
C Ob = new C(1,true);
Invocarea metodelor:
int i = Ob.met(); Ob.met(); Ob.met(7);
Observaie. La invocarea metodelor se folosete apelul prin valoare. Observaie. Dac metoda este declarat cu modificatorul static, ea poate fi invocat i prin numele clasei. Astfel, dac metoda met cu signatura vid era declarat prin:
static int met() { . . . }
atunci ea putea fi invocat i prin: C.met(); Observaie. Dac un cmp w este declarat cu modificatorul static, el este comun tuturor obiectelor de tipul C, deci este memorat o singur dat. n plus el poate fi referit i prin C.w .
Invocrile se fac de exemplu prin: IO.writeln(i + ""); Un prim program: Fie fiierul Unu.java:
class Unu { public static void main(String[] sir) { for (int i=0; i<sir.length; i++) IO.writeln(sir[i]); } }
Compilarea:
javac Unu.java
Executarea:
java unu doi trei
produce la ieire:
unu doi trei
10
Liste
class elem { char c; elem leg; static elem p,u; elem() { } elem(char ch) { c = ch; } elem adaug(char ch) { elem x = new elem(ch); leg = x; return x; } void creare() { char ch = IO.readch(); p = new elem(ch); u = p; ch = IO.readch(); while (ch!='$') { u = u.adaug(ch); ch = IO.readch(); } } String direct(elem x) { if (x==null) return ""; else return x.c + direct(x.leg); } String invers(elem x) { if (x==null) return ""; else return invers(x.leg)+x.c; } } class Lista { public static void main(String[] s) { elem Ob = new elem(); Ob.creare(); IO.writeln( Ob.direct(elem.p) ); IO.writeln( Ob.invers(Ob.p) ); } }
unde operatorul != are sensul "diferit de". S presupunem c la intrare apare: abc$c1... La ieire vom obine:
abc cba
11
Arbori
Numim arbore un graf neorientat conex i fr cicluri. Aceasta nu este singurul mod n care putem defini arborii. Cteva definiii echivalente apar n urmtoarea teorem, expus fr demonstraie. 1) 2) 3) 4) 5) 6) Teorem. Fie G un graf cu n1 vrfuri. Urmtoarele afirmaii sunt echivalente: G este un arbore; G are n-1 muchii i nu conine cicluri; G are n-1 muchii i este conex; oricare dou vrfuri din G sunt unite printr-un unic drum; G nu conine cicluri i adugarea unei noi muchii produce un unic ciclu elementar; G este conex, dar devine neconex prin tergerea oricrei muchii.
n foarte multe probleme referitoare la arbori este pus n eviden un vrf al su, numit rdcin. Alegerea unui vrf drept rdcin are dou consecine: Arborele poate fi aezat pe niveluri astfel: rdcina este aezat pe nivelul 0; pe fiecare nivel i sunt plasate vrfurile pentru care lungimea drumurilor care le leag de rdcin este i; - se traseaz muchiile arborelui. Aceast aezare pe niveluri face mai intuitiv noiunea de arbore, cu precizarea c n informatic "arborii cresc n jos". Exemplul 3. Considerm urmtorul arbore i modul n care el este aezat pe niveluri prin alegerea vrfului 5 drept rdcin. 5 0 1 2 4 6 1 3 6 4 8 7 2 9 3 2 9 5 7 10 3 10 1 8 Arborele poate fi considerat un graf orientat, stabilind pe fiecare muchie sensul de la nivelul superior ctre nivelul inferior.
Reprezentarea pe niveluri a arborilor face ca noiunile de fii (descendeni) ai unui vrf, precum i de tat al unui vrf s aib semnificaii evidente. Un vrf fr descendeni se numete frunz.
12
Arbori binari
Un arbore binar este un arbore n care orice vrf are cel mult doi descendeni, cu precizarea c se face distincie ntre descendentul stng i cel drept. Din acest definiie rezult c un arbore binar nu este propriu-zis un caz particular de arbore. Primele probleme care se pun pentru arborii binari (ca i pentru arborii oarecare i pentru grafuri, aa cum vom vedea mai trziu) sunt: - modul de reprezentare; - parcurgerea lor. Forma standard de reprezentare a unui arbore binar const n: - a preciza rdcina rad a arborelui; - a preciza pentru fiecare vrf i tripletul st(i), dr(i) i info(i), unde acestea sunt respectiv descendentul stng, descendentul drept i informaia ataat vrfului. Trebuie stabilit o convenie pentru lipsa unuia sau a ambilor descendeni, ca de exemplu specificarea lor prin simbolul . Exemplul 4. Considerm de exemplu urmtorul arbore binar:
1
2 3 5
Presupunnd c informaia ataat fiecrui vrf este chiar numrul su de ordine, avem: - rad = 1; - st = (2,3,4,,6,,,,); - dr = (8,5,,,7,,,9,); - info= (1,2,3,4,5,6,7,8,9). Dintre diferitele alte reprezentri posibile, mai menionm doar pe cea care se reduce la vectorul su tata i la vectorul info. Pentru exemplul de mai sus: tata=(,1,2,3,2,5,5,1,8). Problema parcurgerii unui arbore binar const n identificarea unei modaliti prin care, plecnd din rdcin i mergnd pe muchii, s ajungem n toate vrfurile; n plus, atingerea fiecrui vrf este pus n eviden o singur dat: spunem c vizitm vrful respectiv. Aciunea ntreprins la vizitarea unui vrf depinde de problema concret i poate fi de exemplu tiprirea informaiei ataate vrfului.
13
Distingem trei modaliti standard de parcurgere a unui arbore binar: Parcurgerea n preordine Se parcurg recursiv n ordine: rdcina, subarborele stng, subarborele drept. Concret, se execut apelul preord(rad) pentru procedura:
procedure preord(x) if x= then else vizit(x); preord(st(x)); preord(dr(x)) end
Ilustrm acest mod de parcurgere pentru exemplul de mai sus, figurnd ngroat rdcinile subarborilor ce trebuie dezvoltai: 1 1, 2, 8 1, 2, 3, 5, 8, 9 1, 2, 3, 4, 5, 6, 7, 8, 9 Parcurgerea n inordine Se parcurg recursiv n ordine: subarborele stng, rdcina, subarborele drept. Ilustrm acest mod de parcurgere pentru Exemplul 4: 1 2, 1, 8 3, 2, 5, 1, 8, 9 4, 3, 2, 6, 5, 7, 1, 8, 9 Concret, se execut apelul inord(rad) pentru procedura:
procedure inord(x) if x= then else inord(st(x)); vizit(x); inord(dr(x)) end
Parcurgerea n postordine Se parcurg recursiv n ordine; subarborele stng, subarborele drept, rdcina. Ilustrm parcurgerea n postordine pentru Exemplul 4: 1 2, 8, 1 3, 5, 2, 9, 8, 1 4, 3, 6, 7, 5, 2, 9, 8, 1 Concret, se execut apelul postord(rad) pentru procedura:
procedure postord(x) if x= then else postord(st(x)); postord(dr(x)); vizit(x) end
14
Sortarea cu ansamble
Fie a=(a1,,an) vectorul care trebuie sortat (ordonat cresctor). Metoda sortrii de ansamble va folosi o reprezentare implicit a unui vector ca arbore binar. Acest arbore este construit succesiv astfel: - rdcina este 1; - pentru orice vrf descendenii si stng i drept sunt 2i i 2i+1 (cu condiia ca fiecare dintre aceste valori s nu depeasc pe n). Rezult c tatl oricrui vrf i este tata(i)=i/2. Evident, fiecrui vrf i i vom ataa eticheta ai. Pentru 2k-1n<2k arborele va avea k niveluri, dintre care numai ultimul poate fi incomplet (pe fiecare nivel i<k-1 se afl exact 2i vrfuri).
0 1
k-2 k-1
Vectorul a se numete ansamblu dac pentru orice i avem aia2i i aia2i+1 (dac fiii exist). S presupunem c subarborii de rdcini 2i i 2i+1 sunt ansamble. Ne propunem s transformm arborele de rdcin i ntr-un ansamblu. Ideea este de a retrograda valoarea ai pn ajunge ntr-un vrf ai crui descendeni au valorile mai mici dect ai. Acest lucru este realizat de procedura combin.
i procedure combin(i,n) i 2i; b ai while jn if j<n & aj<aj+1 then j j+1 if b>aj then aj/2 b; exit else aj/2 aj; j 2j aj/2 b end
2i
2i+1
ans
ans
Timpul de executare pentru procedura combin este O(k)=O(log n). Sortarea vectorului a se va face prin apelul succesiv al procedurilor creare i sortare prezentate mai jos. Procedura creare transform vectorul ntr-un ansamblu; n particular n a1 se obine cel mai mare element al vectorului.
15
Procedura sortare lucreaz astfel: pune pe a1 pe poziia n i reface ansamblul format din primele n-1 elemente; pune pe a1 pe poziia n-1 i reface ansamblul format din primele n-2 elemente; etc.
procedure sortare for i=n,2,-1 a1ai; combin(1,i-1) end
.
Timpul total de lucru este de ordinul O(n log n). Aa cum am menionat chiar de la nceput, structura de arbore este implicit i este menit doar s clarifice modul de lucru al algoritmului: calculele se refer doar la componentele vectorului.
3
12
11
10
16
Arbori de sortare
Un arbore de sortare este un arbore binar n care pentru orice vrf informaia ataat vrfului este mai mare dect informaiile vrfurilor din subarborele stng i mai mic dect informaiile vrfurilor din subarborele drept. 11 5 7 17 20
15 18 Observaie. Parcurgerea n inordine a unui arbore de cutare produce informaiile ataate vrfurilor n ordine cresctoare. Fie a=(a1,...an) un vector ce trebuie ordonat cresctor. Conform observaiei de mai sus, este suficient s crem un arbore de sortare n care informaiile vrfului s fie tocmai elementele vectorului. Pentru aceasta este suficient s precizm modul n care prin adugarea unei noi valori, s obinem tot un arbore de sortare. Pentru exemplul considerat: - adugarea valorii 6 trebuie s conduc la crearea unui nou vrf, cu informaia 6 i care este descendent stng al vrfului cu informaia 7; - adugarea valorii 16 trebuie s conduc la crearea unui nou vrf, cu informaia 16 i care este descendent drept al vrfului cu informaia 15. Presupunem c un vrf din arborele de sortare este o nregistrare sau obiect de tipul varf, ce conine cmpurile: - informaia info ataat vrfului; - descendentul stng st i descendentul drept dr (lipsa acestora este marcat, ca de obicei, prin ). Crearea unui nou vrf se face prin funcia varf_nou care ntoarce un nou vrf:
function varf_nou(info)
creez un nou obiect/ o nou nregistrare x n care informaia este info, iar descendentul stng i cel drept sunt ;
return x end
Inserarea unei noi valori val (n arborele de rdcin rad) se face prin apelul adaug(rad,val), unde funcia adaug ntoarce o nregistrare i are forma: function adaug(x,val) { se insereaz val n subarborele de rdcin x} if x= then return varf_nou(val) else if val<info(x) then st(x) adaug(st(x),val) else dr(x) adaug(dr(x),val) end
17
Programul principal ntreprinde urmtoarele aciuni: citete valorile ce trebuie ordonate i le insereaz n arbore; parcurge n inordine arborele de sortare; vizitarea unui vrf const n tiprirea informaiei ataate. Prezentm programul n Java:
class elem { int c; elem st,dr; elem() { } elem(int ch) { c=ch; st=null; dr=null; } elem adaug(elem x, int ch) { if (x==null) x=new elem(ch); else if (ch<x.c) x.st=adaug(x.st,ch); else x.dr=adaug(x.dr,ch); return x; } String parcurg(elem x) { if (x==null) return(""); else return( parcurg(x.st) + x.c + " " + parcurg(x.dr)); } } class Arbsort { public static void main(String arg[]) { elem rad=null; double val; elem Ob = new elem(); val = IO.read(); while ( ! Double.isNaN(val) ) { rad = Ob.adaug(rad,(int) val); val = IO.read(); } IO.writeln(Ob.parcurg(rad)); } }
Arbori oarecare
Primele probleme care se pun sunt aceleai ca pentru arborii binari: modalitile de reprezentare i de parcurgere. Exemplul 5. Considerm urmtorul arbore: 1 2 5 6 3 7 8 4 9 0 1 2 3
10 11
12
13
18
Se consider c arborele este aezat pe niveluri i c pentru fiecare vrf exist o ordine ntre descendenii si. Modul standard de reprezentare al unui arbore oarecare const n a memora rdcina, iar pentru fiecare vrf i informaiile: - info(i) = informaia ataat vrfului; - fiu(i) = primul vrf dintre descendenii lui i; - frate(i) = acel descendent al tatlui lui i, care urmez imediat dup i. Ca i pentru arborii binari, lipsa unei legturi ctre un vrf este indicat prin . Pentru arborele din Exemplul 5:
fiu =(2,5,7,8,10,11,,,,,,8,); frate =(,3,4,,6,,,9,,,12,13,).
O alt modalitate de reprezentare const n a memora pentru fiecare vrf tatl su. Aceast modalitate este incomod pentru parcurgerea arborilor, dar se dovedete util n alte situaii, care vor fi prezentate n continuare. n unele cazuri este util s memorm pentru fiecare vrf att fiul i fratele su, ct i tatl su. Parcurgerea n preordine Se parcurg recursiv n ordine rdcina i apoi subarborii care au drept rdcin descendenii si. Pentru Exemplul 5: 1 1,2,3,4 1,2,5,6,3,7,4,8,9 1,2,5,10,6,11,12,13,3,7,4,8,9. Concret, executm apelul Apreord(rad) pentru procedura: procedure Apreord(x) if x= then else vizit(x); Apreord(fiu(x)); Apreord(frate(x)) end Ca aplicaie, s presupunem c informaiile ataate vrfurilor sunt funcii de diferite ariti (aritatea unei funcii este numrul variabilelor de care depinde; o funcie de aritate 0 este o constant). Pentru Exemplul 5, vectorul de aritate este: aritate=(3,2,1,2,1,3,0,0,0,0,0,0,0). Rezultatul 1 2 5 10 6 11 12 13 3 7 4 8 9 al parcurgerii n preordine este o form fr paranteze (dar la fel de consistent) a scrierii expresiei funcionale:
1(2(5(10),6(11,12,13)),3(7),4(8,9))
19
Parcurgerea n postordine Se parcurg recursiv n ordine subarborii rdcinii i apoi rdcina. Pentru Exemplul 5: 1 2,3,4,1 5,6,2,7,3,8,9,4,1 10,5,11,12,13,6,2,7,3,8,9,4,1. Concret, executm apelul Apostord(rad) pentru procedura:
procedure Apostord(x) if x= then else yfiu(x); while y<> Apostord(y); yfrate(x) vizit(x) end
Pentru aplicaia anterioar, dorim s calculm valoarea expresiei funcionale, cunoscnd valorile frunzelor. Evident, trebuie s parcurgem arborele n postordine.
class varf { int v; varf fiu,frate; static varf rad; varf() { } varf(int val) { v = val; } void creare() { IO.write("rad : "); int i = (int) IO.read(); rad = new varf(i); creare(rad); } void creare(varf x) { // ataseaza fiu si frate lui x int i; double d; IO.write("fiul lui " + x.v + " : "); d = IO.read(); if( !Double.isNaN(d) ) { i = (int) d; x.fiu = new varf(i); creare(x.fiu); } IO.write("fratele lui " + x.v + " : "); d = IO.read(); if( !Double.isNaN(d) ) { i = (int) d; x.frate = new varf(i); creare(x.frate); } } String pre(varf x) { if (x==null) return ""; else return x.v + " " + pre(x.fiu) + pre(x.frate); } void post(varf x) { varf y = x.fiu; while (y != null) { post(y); y = y.frate; } IO.write(x.v + " "); } }
20
class Arbori { public static void main(String[] s) { varf Ob = new varf(); Ob.creare(); IO.writeln( "Preordine:\t" + Ob.pre(varf.rad) ); IO.writeln("Postordine:\t"); Ob.post(varf.rad); } }
Parcurgerea pe niveluri Se parcurg vrfurile n ordinea distanei lor fa de rdcin, innd cont de ordinea n care apar descendenii fiecrui vrf. Pentru Exemplul 5: 1,2,3,4,5,6,7,8,9,10,11,12,13. Pentru implementare vom folosi o coad C, n care iniial apare numai rdcina. Atta timp ct coada este nevid, vom extrage primul element, l vom vizita i vom introduce n coad descendenii si:
C ; C rad while C x C; vizit(x); y fiu(x); while y y C; y frate(x) end
Parcurgerea pe niveluri este n general util atunci cnd se caut vrful care este cel mai apropiat de rdcin i care are o anumit proprietate/informaie. Pentru a include n programul anterior i parcurgerea pe niveluri, putem proceda astfel: - n metoda main adugm instruciunea:
IO.writeln("\rNiveluri : " +"\t" + Ob.niveluri() ); - n clasa varf adugm clasa intern elem i metoda niveluri: class elem { varf inf; elem leg; elem (varf v) { inf = v; } } String niveluri() { elem p,u,x; varf y; String s=""; p = new elem(null); u = new elem(root); p.leg = u; while ( p != u ) { x = p.leg; s += x.inf.v + " "; p.leg = x.leg; if(p.leg == null) u = p; y = x.inf.fiu; while ( y != null) { x = new elem(y); u.leg = x; u = x; y = y.frate; } } return s; } }
21
Grafuri
Parcurgerea DF a grafurilor neorientate
Fie G=(V,M) un graf neorientat. Ca de obicei, notm n=|V| i m=|M|. Parcurgere n adncime a grafurilor (DF = Depth First) generalizeaz parcurgerea n preordine a arborilor oarecare. Eventuala existen a ciclurilor conduce la necesitatea de a marca vrfurile vizitate. Ideea de baz a algoritmului este urmtoarea: se pleac dinr-un vrf i0 oarecare, apelnd procedura DF pentru acel vrf. Orice apel de tipul DF(i) prevede urmtoarele operaii: - marcarea vrfului i ca fiind vizitat; - pentru toate vrfurile j din lista Li a vecinilor lui i se execut apelul DF(j) dac i numai dac vrful j nu a fost vizitat. Simpla marcare a unui vrf ca fiind sau nu vizitat poate fi nlocuit cu atribuirea unui numr de ordine fiecrui vrf; mai precis, n nrdf(i), iniial egal cu 0, va fi memorat al ctelea este vizitat vrful i; nrdf(i) poart numele de numrul (de ordine) DF al lui i. Este evident c plecnd din vrful i0 se pot vizita numai vrfurile din componenta conex a lui i0. De aceea, n cazul n care graful nu este conex, dup parcurgerea componentei conexe a lui i0 vom repeta apelul DF pentru unul dintre eventualele vrfuri nc neatinse.
procedure DF(i) ndf ndf+1; nrdf(i) ndf; vizit(i); for toi j Li if nrdf(j)=0 then DF(j)
for i=1,n nrdf(i) 0 for i=1,n if nrdf(i)=0 then DF(i) scrie nrdf;
Observaie. Dac dorim doar s determinm dac un graf este conex, vom nlocui al doilea ciclu for din programul principal prin: DF(1);
if ndf=n then write(CONEX) else write(NECONEX);
Observaie. Parcurgerea DF mparte muchiile grafului n: muchii de avansare: sunt acele muchii (i,j) pentru care n cadrul apelului DF(i) are loc apelul DF(j). Aceste muchii formeaz un graf parial care este o pdure: fiecare vrf este vizitat exact o dat, deci nu exist un ciclu format din muchii de avansare. muchii de ntoarcere: sunt acele muchii ale grafului care nu sunt muchii de avansare.
22
Determinarea mulimilor A i I a muchiilor de avansare i ntoarcere, precum i memorarea arborilor pariali din pdurea DF se poate face astfel: - n programul principal se fac iniializrile:
A ; I; for i=1,n tata(i) 0; - instruciunea if din procedura DF devine: if nrdf(j)=0 then A A {(i,j)}; tata(j) i; DF(j); else if tata(j) i then I I {(i,j)};
pdurea DF este format din urmtorii arbori pariali corespunztori componentelor conexe:
1 6 4 2 5 3 8 7 9
Timpul cerut de algoritmul de mai sus este O(max{n,m})=O(n+m), adic liniar, deoarece: - pentru fiecare vrf i, apelul DF(i) are loc exact o dat; - executarea unui apel DF(i) necesit un timp proporional cu grad(i)=|Li|; n consecin timpul total va fi proporional cu m=|M|. Propoziie. Dac (i,j) este muchie de ntoarcere, atunci i este descendent al lui j. Muchia (i,j) este detectat ca fiind muchie de ntoarcere n cadrul executrii apelului DF(i), deci nrdf(j)<nrdf(i). Deoarece exist muchie ntre vrfurile i i j, rezult c n timpul executrii lui DF(j) va fi vizitat i vrful i, deci i este descendent al lui j.
23
Propoziia de mai sus spune c muchiile de ntoarcere leag totdeauna dou vrfuri situate pe aceeai ramur a pdurii pariale DF. n particular, nu exist muchii de traversare (care s lege doi descendeni ai aceluiai vrf dintr-un arbore DF). Observaie. Un graf este ciclic (conine cel puin un ciclu) dac i numai dac n timpul parcurgerii sale n adncime este detectat o muchie de ntoarcere. Aplicaie. S se determine dac un graf este ciclic i n caz afirmativ s se identifice un ciclu. Vom memora pdurea format din muchiile de avansare cu ajutorul vectorului tata i n momentul n care este detectat o muchie de ntoarcere (i,j) vom lista drumul de la i la j format din muchii de avansare i apoi muchia (j,i). Procedura DF va fi modificat astfel:
procedure DF(i) ndf ndf+1; nrdf(i) ndf; vizit(i); for toi j Li if nrdf(j)=0 then tata(j) i; DF(j) else if tata(j)i then k i; while k j write(k,tata(k)); k tata(k); write(j,i); stop end.
Observaii: dac notm prin nrdesc(i) numrul descendenilor lui i n subarborele de rdcin i, aceast valoare poate fi calculat plasnd dup ciclul for din procedura DF instruciunea nrdesc(i)ndf-nrdf(i)+1; un vrf j este descendent al vrfului i n subarborele DF de rdcin i nrdf(i)nrdf(j)<nrdf(i)+nrdesc(i).
Se consider n persoane. Fiecare dintre ele emite o brf care trebuie cunoscut de toate celelalte persoane. Prin mesaj nelegem o pereche de numere (i,j) cu i,j{1,...,n} i cu semnificaia c persoana i transmite persoanei j brfa sa, dar i toate brfele care i-au parvenit pn la momentul acestui mesaj. Se cere una dintre cele mai scurte succesiuni de mesaje prin care toate persoanele afl toate brfele. Cu enunul de mai sus, o soluie este imediat i const n succesiunea de mesaje:
(1,2),(2,3),...,(n-1,n),(n,n-1),(n-1,n-2),...,(2,1). Sunt transmit deci n-2 mesaje. Dup cum vom vedea mai jos, acesta este numrul
minim de mesaje prin care toate persoanele afl toate brfele. Problema se complic dac exist persoane care nu comunic ntre ele (sunt certate) i deci nu-i vor putea transmite una alteia mesaje.
24
Aceast situaie poate fi modelat printr-un graf n care vrfurile corespund persoanelor, iar muchiile leag persoane care nu sunt certate ntre ele. Vom folosi matricea de adiacen a de ordin n n care aij este 0 dac persoanele i i j sunt certate ntre ele (nu exist muchie ntre i i j) i 1 n caz contrar. Primul pas va consta n detectarea unui arbore parial; pentru aceasta vom folosi parcurgerea DF. Fiecrei persoane i i vom ataa variabila boolean vi, care este true dac i numai dac vrful corespunztor a fost atins n timpul parcurgerii; iniial toate aceste valori sunt false. Vom pune aij=2 pentru toate muchiile de naintare.
vi false, i=1,...n nr 0; DF(1) if nr<n then write('Problema nu are soluie (graf neconex)') else rezolv problema pe arborele parial construit
S observm c n acest mod am redus problema de la un graf la un arbore! Descriem n continuare modul n care rezolvm problema pe acest arbore parial, bineneles n ipoteza c problema are soluie (graful este conex). Printr-o parcurgere n postordine, n care vizitarea unui vrf const n transmiterea de mesaje de la fiii si la el, rdcina (presupus a fi persoana 1) va ajunge s cunoasc toate brfele. Aceasta se realizeaz prin apelul postord(1), unde procedura postord are forma:
procedure postord(i) for j=1,n if aij=2 then postord(j); write(j,i) end
n continuare, printr-o parcurgere n preordine a arborelui DF, mesajele vor circula de la rdcin ctre frunze. Vizitarea unui vrf const n transmiterea de mesaje fiilor si. Pentru aceasta executm apelul preord(1), unde procedura preord are forma:
procedure preord(i) for j=1,n if aij=2 then write(i,j); preord(j); end
Observm c att la parcurgerea n postordine, ct i la cea n preordine au fost listate n-1 perechi (mesaje), deoarece un arbore cu n vrfuri are n-1 muchii. Rezult c soluia de
25
mai sus const ntr-o succesiune de 2n-2 mesaje. Mai rmne de demonstrat c acesta este numrul minim posibil de mesaje care rezolv problema. Propoziie. Orice soluie pentru problema brfei conine cel puin 2n-2 mesaje. S considerm o soluie oarecare pentru problema brfei. Punem n eviden primul mesaj prin care o persoan a ajuns s cunoasc toate brfele; fie k aceast persoan. Deoarece celelate persoane trebuie s le fi emis, nseamn c pn acum au fost transmise cel puin n-1 mesaje. Dar k este prima persoan care a aflat toate brfele, deci celelalte trebuie s mai afle cel puin o brf. Rezult c n continuare trebuie s apar nc cel puin n-1 mesaje. n concluzie, soluia considerat este format din cel puin 2n-2 mesaje.
Observaii: 1) Operaia este comutativ i asociativ; 2) Dac M1 i M2 reprezint cicluri, atunci M1 M2 este tot un ciclu sau o reuniune disjunct (n privina muchiilor) de cicluri:
Teorem. Pentru un graf i un arbore parial A al su date, ciclurile fundamentale formeaz o baz, adic sunt ndeplinite condiiile: 1) orice ciclu se poate exprima ca sum circular de cicluri fundamentale; 2) nici un ciclu fundamental nu poate fi exprimat ca sum circular de cicluri fundamentale.
26
Arborele parial A:
ciclu n G, ale crui muchii sunt partiionate n C={e1,,ek}{ek+1,,ej} unde e1,...,ek sunt corzi, iar ek+1,...,ej sunt muchii din A. Fie C(e1),...,C(ek) ciclurile fundamentale din care fac parte e1,,ek. Fie C=C(e1)... C(ek). Vom demonstra c C=C (sunt formate din aceleai muchii). Presupunem c CC. Atunci CC. S observm c att C ct i C conin corzile e1,,ek. Conform unei observaii de mai sus, C i apoi CC sunt cicluri sau reuniuni disjuncte de cicluri. Cum att C ct i C conin corzile e1,...,ek i n rest muchii din A, rezult c CC conine numai muchii din A, deci nu poate conine un circuit. Contradicie. Fie un ciclu fundamental care conine coarda e. Fiind singurul ciclu fundamental ce conine e, el nu se va putea scrie ca sum circular de alte cicluri fundamentale. Consecin. Baza format din circuitele fundamentale are ordinul mn+1. Observaie. O muchie din C(e1)...C(ek) este tears apare de numr par de ori. Determinarea mulimii ciclurilor fundamentale Printr-o parcurgere a arborelui A, putem stabili pentru el legtura tata. Atunci pentru orice coard (i,j) procedm dup cum urmeaz: 1) Determinm vectorii:
u = (u1=i, u2=tata(u1), ... , unu=tata(unu-1)=rad) v = (v1=j, v2=tata(v1), ... , vnv=tata(vnv-1)=rad). 2) Parcurgem simultan vectorii u i v de la stnga la dreapta i determinm cel mai mic indice k cu uk=vk.
Demonstraie. Considerm un
27
obinem pdurea:
1 7 8 9
10
11
i vectorul nrdf = (1,2,3,4,5,6,7,8,9,10,11). Parcurgerea DF mparte arcele (i,j) n 3 categorii: 1) arce de avansare, caracterizate prin nrdf(i)<nrdf(j): 1.1) arce componente ale pdurii DF; 1.2) arce ce leag un vrf de un descendent al su care nu este fiu al su; 2) arce de ntoarcere, ce leag un vrf de un predecesor al su n pdurea DF; evident nrdf(i)>nrdf(j); 3) arce de traversare: leag dou vrfuri care nu sunt unul descendentul celuilalt.
28
Pentru exemplul considerat avem: 1.2) : (1,6) 2) : (3,1), (6,4), (11,9) 3) : (7,2), (8,2), (8,7), (9,1), (11,2), (11,8) Propoziie. Pentru orice arc de traversare (i,j) avem nrdf(i)>nrdf(j).
S presupunem prin absurd c nrdf(i)<nrdf(j). Atunci n momentul n care s-a ajuns prima dat la i, vrful j nu a fost nc atins. Din modul n care lucreaz algoritmul, (i,j) va fi arc de avansare:
i i
sau
j j
Observaie. Spre deosebire de arcele de traversare, arcele de ntoarcere determin un circuit elementar (prin adugarea unui astfel de arc la pdurea DF ia natere un circuit elementar). Putem stabili dac un arc (i,j) cu nrdf(i)>nrdf(j) este de ntoarcere sau de traversare astfel:
ki; while k0 & kj ktata(k) if k=0 then write(traversare) else write(ntoarcere)
29
9 C (i0} while C i C; viziteaz i; vizitat(i)true for toi j vecini ai lui i if not vizitat(j) then j C
Un vector (obiect de tipul Vector) are lungime variabil i conine obiecte oarecare. Vector() : construiete vectorul vid (de lungime 0); void addElement(Object) : adaug obiect la sfritul vectorului; boolean remove Element(Object) : elimin, cu deplasare la stnga, prima apariie; boolean isEmpty() : verific dac vectorul este vid; Object firstElement() : ntoarce primil element al vectorului. Clasa nfurtoare Integer:
Integer(int): constructor ce construiete un obiect ce conine ntregul respectiv; int intValue(): ntoarce ntregul asociat obiectului.
============
class BF { public static void main(String[] s) { nivel Ob = new nivel(); Ob.parc(); } } import java.util.*; class nivel { int[][] mat; int[] temp; int n; nivel() { int[] temp; int ntemp; double d; IO.write("n= "); n = (int) IO.read(); mat = new int[n][]; temp = new int[n]; for (int i=0; i<n; i++) { IO.write("Vecinii lui "+i+" : "); ntemp = 0; d = IO.read(); while( ! Double.isNaN(d) ) { temp[ntemp++]= (int) d; d = IO.read(); } mat[i] = new int[ntemp]; for (int j=0; j<ntemp; j++) mat[i][j] = temp[j]; } IO.writeln("**************"); }
30
10 void parc() { boolean[] v = new boolean[n]; Integer Int; int i,j,k; Vector coada = new Vector(); coada.addElement(new Integer(0)); v[0] = true; while ( ! coada.isEmpty() ) { Int = (Integer) coada.firstElement(); coada.removeElementAt(0); i = Int.intValue(); IO.write(i+"\t"); for (k=0; k<mat[i].length; k++) { j = mat[i][k]; if( !v[j] ) { coada.addElement(new Integer(j)); v[j]=true; } } } } }
La fel ca pentru parcurgerea DF, algoritmul poate fi completat pentru parcurgerea ntregului graf, pentru determinarea unei pduri n care fiecare arbore este un arbore parial al unei componente conexe etc.
31
Metoda Greedy
Metoda Greedy (greedy=lacom) este aplicabil problemelor de optim. Considerm mulimea finit A={a1,...,an} i o proprietate p definit pe mulimea submulimilor lui A:
p()= 1 p:P(A){0,1} cu p(X) p(Y), Y X O submulime SA se numete soluie dac p(S)=1. Dintre soluii aleg una care optimizeaz o funcie p:P(A)R dat.
Metoda urmrete evitarea parcurgerii tuturor submulimilor (ceea ce ar necesita un timp de calcul exponenial), mergndu-se "direct" spre soluia optim. Nu este ns garantat obinerea unei soluii optime; de aceea aplicarea metodei Greedy trebuie nsoit neaprat de o demonstraie. Distingem dou variante generale de aplicare a metodei Greedy:
S for i=1,n xalege(A); AA\{x} if p(S{x})=1 then SS{x} prel(A) S for i=1,n if p(S{ai}) then SS{ai}
Observaii: n algoritm nu apare funcia f !! timpul de calcul este liniar (exceptnd prelucrrile efectuate de procedurile prel i alege); dificultatea const n a concepe procedurile alege, respectiv prel, n care este "ascuns" funcia f.
Exemplul 1. Se consider mulimea de valori reale A={a1,...,an}. Se caut submulimea a crei sum a elementelor este maxim. Vom parcurge mulimea i vom selecta numai elementele pozitive.
k0 for i=1,n if ai>0 then kk+1; skai write(s)
Exemplul 2. Caut cel mai scurt ir cresctor cu elemente din mulimea {a1,...,an}. ncepem prin a ordona cresctor elementele mulimii (corespunztor procedurii prel). Apoi:
k1; s1a1; lung1; baza1 for i=2,n if ai>sk then kk+1; sk ai else if k>lung then lungk; bazai-k k1; s1ai
32
(Contra)exemplul 3. Fie mulimea A={a1,...,an} cu elemente pozitive. Caut submulimea de sum maxim, dar cel mult egal cu M dat. Dac procedez ca n exemplul 1, pentru A=(6,3,4,2) i M=7 obin {6}. Dar soluia optim este {3,4} cu suma egal cu 7. Continum cu prezentarea unor exemple clasice.
Conform strategiei Greedy, ncepem prin a ordona cresctor vectorul L. Rezult c n continuare L(i)<L(j), i<j. Demonstrm c n acest mod am obinut modalitatea optim, adic permutarea identic minimizeaz funcia de cost T. Fie pSn optim, adic p minimizeaz funcia T. Dac p este diferit de permutarea identic i<j cu L(pi)>L(pj):
p=( pi pj )
33
Dac suma greutilor obiectelor este mai mic dect G, atunci ncarc toate obiectele: x=(1,...,1). De aceea presupunem n continuare c g1+...+gn>G. Conform strategiei Greedy, ordonez obiectele descresctor dup ctigul la unitatea de greutate, deci lucrm n situaia:
c1 c2 c ... n g1 g 2 gn
(*)
Algoritmul const n ncrcarea n aceast ordine a obiectelor, atta timp ct nu se depete greutatea G (ultimul obiect pote fi eventual ncrcat parial):
G1 G { G1 reprezint greutatea disponibil } for i=1,n if giG1 then xi1; G1G1-gi else xiG1/gi; for j=i+1,n xj 0 stop write(x)
Am obinut deci x=(1,...,1,xj,0,...,0) cu xj[0,1). Artm c soluia astfel obinut este optim.
n giy i = G Fie y soluia optim: y=(...,yk,...) cu i=1 n c y maxim i=1 i i Dac yx, fie k prima poziie pe care ykxk.
Observaii: kj: pentru k>j se depete G. yk<xk: pentru k<j: evident, deoarece xk=1; pentru k=j: dac yk>xk se depete G.
34
Considerm soluia: y=(y1,,yk-1,xk,yk+1,, yn) cu <1 (primele k1 componente coincid cu cele din x). Pstrez greutatea total G, deci: gkxk+(gk+1yk+1+...+gnyn)=gkyk+gk+1yk+1+...+gnyn. Rezult:
gk(xk-yk)=(1-)(gk+1yk+1++gnyn) (**) Compar performana lui y' cu cea a lui y: f(y)-f(y) = ckxk +ck+1yk+1 +...+ cnyn - (ckyk+ck+1yk+1 +...+cnyn) = = ck(xk-yk) + (-1)(ck+1yk+1+...+cnyn) = = ck/gk[gk(xk-yk)+(-1)(gk/ckck+1yk+1+...+gk/ckcnyn)] Dar -1>0 i gk/ck gs/cs, s>k. Atunci f(y)-f(y)>ck/gk [gk(xk-yk)+(-1)(gk+1yk+1+...+gnyn)]=0, deci f(y')>f(y). Contradicie.
Problema discret a rucsacului difer de cea continu prin faptul c fiecare obiect poate fi ncrcat numai n ntregime n rucsac. S observm c aplicarea metodei Greedy eueaz n acest caz. ntr-adevr, aplicarea ei pentru: G=5, n=3 i g=(4,3,2), c=(6,4,2.5) are ca rezultat ncrcarea primul obiect; ctigul obinut este 6. Dar ncrcarea ultimelor dou obiecte conduce la ctigul superior 6.5.
35
Conform algoritmului lui Kruskal, vor fi alese n ordine muchiile: (1,2), (1,4), (4,5), (3,4) cu costul total egal cu 10. Muchia (1,5) nu a fost aleas deoarece formeaz cu precedentele un ciclu. Dificultatea principal const n verificarea faptului c o muchie formeaz sau nu cu precedentele un ciclu. Plecnd de la observaia c orice soluie parial este o pdure, vom asocia fiecrui vrf i un reprezentant ri care identific componenta conex (arborele) din care face parte vrful n soluia parial. Atunci: - o muchie (i,j) va forma un ciclu cu precedentele ri=rj; - la alegerea (adugarea) unei muchii (i,j) vom pune rkrj pentru orice vrf k cu rk=ri (unim doi arbori, deci vrfurile noului arbore trebuie s aib acelai reprezentant). n algoritmul care urmeaz metoda descris, l este numrul liniei curente din matricea mat, nm este numrul de muchii alese, iar cost este costul muchiilor alese
ri i, i=1,n l 1; nm 0; cost 0 while lm & nm<n-1 i1 mat(l,1); i2 mat(l,2) r1 ri1; r2 ri2 if r1r2 then nm nm+1; cost cost+mat(l,3); write(i1,i2) for k=1,n if rk=r2 then rk r1 l l+1 if nm<n-1 then write('Graf neconex') else write('Graf conex. Costul=,cost)
Demonstrm n continuare corectitudinea algoritmului lui Kruskal. Fie G=(V,M) un graf conex. PM se numete mulime promitoare de muchii dac nu conine cicluri i poate fi extins la un arbore parial P de cost minim.
36
Propoziia 1. Fie P promitoare i YV astfel nct nici un vrf din Y nu este o extremitate a unei muchii din P. Fie m una dintre muchiile de cost minim cu cel puin o extremitate n Y. Atunci P{m} este promitoare. Fie P arborele parial de cost minim la care poate fi extins P.
P Y
Dac mP , O.K. Dac mP, atunci P{m} are un ciclu. n el exist, n afar de m, o alt muchie m' adiacent lui Y. Fie P'=P {m}\{m}. Observm c P{m}P' pentru c PP. P' este de cost minim deoarece costul lui m este mai mic sau egal cu costul lui m'. Rezult c mulimea de muchii P{m} este promitoare. Propoziia 2. Arborele furnizat de metoda Kruskal este arbore parial de cost minim. Artm prin inducie c dup k=0,1,...,n-1 pai, cele k muchii alese formeaz o mulime promitoare. Pentru k=0 rezultatul este evident. Fie P mulimea promitoare a muchiilor selectate la primii k pai. Fie Y=V\{x | x extremitate a unei muchii din P} = mulimea punctelor izolate. Conform Propoziiei 1, mulimea P{muchia selectat la pasul k+1} este promitoare. n final, o mulime promitoare cu n-1 muchii este chiar un arbore parial de cost minim. Observaie. Tot o ilustrare a metodei Greedy pentru problema enunat o constituie algoritmul lui Prim, care const n urmtoarele: - se ncepe prin selectarea unui vrf; - la fiecare pas aleg o muchie (i,j) de lungime minim cu i selectat, dar j neselectat. De aceast dat, la fiecare pas se obine un arbore. Propunem ca exerciiu demonstrarea faptului c dup n-1 pai se obine un arbore parial de cost minim.
37
Vizibilitate i acces
Pachetele, clasele, metodele, cmpurile i variabilele sunt identificate prin numele lor. Problema care se pune este de a stabili din ce loc putem face referire la aceste nume (cu semnificaia dorit), adic de a preciza domeniul lor de vizibilitate. Variabile locale i etichete
Prin bloc nelegem o secven (eventual vid) de instruciuni, cuprins ntre acolade. Un bloc este o instruciune i de aceea poate fi folosit n orice loc unde poate s apar o instruciune. De exemplu orice metod i orice constructor sunt formate dintr-un antet urmat de un bloc. n schimb ceea ce urmeaz dup modificatorii i numele unei clase nu constituie un bloc, dei apar acoladele deschis i nchis: coninutul unei clase nu este o succesiune de instruciuni. Orice bloc poate conine la rndul su un alt bloc, putnd astfel lua natere o structur imbricat de blocuri. Prin variabil local nelegem o variabil declarat n interiorul unui bloc, inclusiv ntr-o instruciune for. Domeniul de vizibilitate al unei variabile locale este constituit din partea din blocul n care a fost declarat, ce urmeaz declarrii. Prin urmare o variabil local nu poate fi referit nainte de a fi declarat. Variabilele locale exist numai pe perioada n care blocul n care sunt declarate este n curs de executare. Nu putem folosi facilitatea de imbricare a blocurilor pentru a redeclara o variabil local. O variabil local nu poate fi redefinit nici n blocul n care a fost declarat, nici ntr-un bloc inclus n acesta. Regulile care guverneaz domeniul de vizibilitate al etichetelor sunt aceleai ca pentru variabilele locale. Parametrii metodelor i constructorilor
Numele parametrilor dintr-o metod sau dintr-un constructor sunt vizibile doar n interiorul acestora, adic n blocul care urmeaz antetului metodei sau constructorului. Este ns posibil ca ntr-un bloc dintr-o metod sau dintr-un constructor s redeclarm numele unui parametru; n acest caz, n partea din bloc ce urmeaz redeclarrii, numele respectiv este considerat ca avnd semnificaia dat de redeclarare. Cu alte cuvinte, domeniul de vizibilitate al unui parametru este constituit din corpul metodei sau constructorului n care apare, mai puin blocurile (evident disjuncte) n care este redeclarat. Vizibilitatea n interiorul unei clase
tim c o clas este constituit din cmpuri, constructori (folosii la crearea de obiecte) i metode (folosite la invocarea lor). Din orice constructor i din orice metod putem s ne referim la orice cmp sau metod a clasei. De asemenea dintr-un constructor putem apela alt constructor prin this urmat, ntre paranteze, de o list de argumente ce respect signatura noului constructor.
38
Ordinea n care apar cmpurile nestatice i metodele n cadrul clasei nu este relevant (de exemplu nu conteaz dac un cmp nestatic este declarat la nceputul sau la sfritul clasei). Totui, dac numele unui cmp este redeclarat ntr-o metod (este folosit ca nume al unui parametru sau declarat ntr-un bloc), atunci declararea cea mai interioar este cea care primeaz cnd se face o referire la acel nume (identificator). Putem spune c un parametru poate ascunde un cmp, iar o variabil local poate ascunde un parametru i/sau un cmp. Dup cum vom vedea n continuare, numele unei metode nu poate fi ascuns. Modificatorul final
Modificatorul final poate fi ataat att variabilelor (locale sau cmpuri), ct i metodelor. n cazul variabilelor locale, variabilei i se atribuie valoare la declarare sau nainte de a fi folosit; variabila nu poate primi de dou ori valoare. Menionm c final este singurul modificator ce poate fi ataat unei variabile locale. Unui cmp final trebuie s i se atribuie valoare fie prin iniializare, fie prin constructori. Menionm c n general modificatorul final este folosit mpreun cu static, pentru a trata un cmp ca o "constant cu nume". Efectul modificatorului final asupra metodelor i claselor va fi prezentat atunci cnd vom vorbi despre extinderea claselor. Modificatorul static
Modificatorul static poate fi folosit la declararea cmpurilor i metodelor unei clase. O variabil local nu poate fi declarat cu static . Fie c un cmp declarat cu static n clasa Clasa. Aceast declarare are dou consecine: - cmpul c este comun tuturor obiectelor de tipul Clasa; - referirea la cmp din exteriorul clasei se face fie conform regulii generale (prin crearea i utilizarea unei instane a clasei Clasa), fie direct, prin precalificarea cmpului cu numele clasei (Clasa.c). Fie acum met o metod declarat cu modificatorul static n clasa Clasa; ea se numete metod static sau metod de clas. Invocarea ei se poate face fie conform regulii generale (prin crearea i utilizarea unei instane a clasei Clasa), fie direct, prin precalificarea metodei cu numele clasei: Clasa.met(...) ; De aceea ntr-o metod static nu poate fi folosit referina this. O metod static poate accesa numai cmpurile i metodele statice ale clasei din care face parte sau a altei clase. Metodele statice (de clas) sunt folosite de obicei pentru a efectua prelucrri asupra cmpurilor statice ale clasei. Putem "ocoli" ns aceast regul de exemplu astfel: - la invocarea metodei i transmitem ca parametru o referin la un obiect; - n metod construim explicit un obiect de tipul clasei la ale crei metode i cmpuri dorim s ne referim.
39
Modificatorii de acces
Toate cmpurile i metodele unei clase sunt accesibile (pot fi referite) din interiorul clasei. 3 Rolul modificatorilor public, protected, private i cel implicit (numit uneori package) referitor la accesul din exteriorul unei clase la membrii si este sintetizat n urmtorul tabel: Membrii clasei cu acest modificator sunt accesibili ... public de oriunde clasa este accesibil protected din cod din acelai pachet implicit (package din cod din acelai pachet ) private numai din interiorul clasei Reamintim c modificatorii de mai sus joac un rol nu numai n privina accesului, dar i pentru facilitatea de extindere a claselor, ceea ce va face diferena ntre modificatorul protected i cel implicit. Determinarea semnificaiei unui nume Modificator
Pentru un identificator folosit ca nume de variabil (local sau cmp), metod sau clas, semnificaia sa se obine la prima cutare ncununat de succes din urmtorul ir de cutri succesive: 1) variabilele locale declarate ntr-un bloc sau un ciclu for; 2) numai dac suntem n interiorul unei metode sau a unui constructor: parametrii metodei sau constructorului; 3) membrii clasei, inclusiv cei rezultai prin extinderea claselor (vezi capitolul urmtor) 4) tipuri importate explicit; 5) tipuri declarate n acelai pachet; 6) tipuri importate implicit; 7) pachete disponibile pe sistemul gazd. Mai mult, se face distincie clar ntre tipurile de identificatori: nume de variabile (locale), de pachete, de clase, de metode i de cmpuri; iat de ce numele unei metode nestatice nu poate fi ascuns. Aceasta permite s folosim un acelai identificator pentru a numi una sau mai multe astfel de entiti.
40
Metoda Backtracking
Aa cum s-a subliniat n capitolele anterioare, complexitatea n timp a algoritmilor joac un rol esenial. n primul rnd un algoritm este considerat "acceptabil" numai dac timpul su de executare este polinomial, adic de ordinul O(nk) pentru un anumit k; n reprezint numrul datelor de intrare. Pentru a ne convinge de acet lucru, vom considera un calculator capabil s efectueze un milion de operaii pe secund.
n=20 n 2n 3n
3
n=40
n=60
1 sec 58 min
Chiar dac n prezent calculatoarele performante sunt capabile de a efectua zeci de miliarde de operaii pe secund, tabelul de mai sus arat c algoritmii exponeniali nu sunt acceptabili.
Descrierea metodei
Fie X=X1 ... Xn. Caut xX cu (x), unde :X {0,1} este o proprietate definit pe X. Din cele de mai sus rezult c generarea tuturor elementelor produsului cartezian X nu este acceptabil. Metoda backtracking ncearc, dar nu reuete totdeauna, micorarea timpului de calcul. X este numit spaiul soluiilor posibile, iar sintetizeaz condiiile interne. Vectorul x este construit progresiv, ncepnd cu prima component. Se avanseaz cu o valoare xk dac este satisfcut condiia de continuare k(x1,...,xk). Condiiile de continuare rezult de obicei din ; ele sunt strict necesare, ideal fiind s fie i suficiente. Distingem urmtoarele cazuri posibile la alegerea lui xk: 1) Atribuie i avanseaz: mai sunt valori neconsumate din Xk i valoarea xk aleas satisface k se mrete k. 2) ncercare euat: mai sunt valori neconsumate din Xk i valoarea xk aleas dintre acestea nu satisface k se va relua, ncercndu-se alegerea unei noi valori pentru xk. 3) "Revenire": nu mai exist valori neconsumate din Xk (Xk epuizat) ntreaga Xk devine disponibil i kk-1. 4) "Revenire dup determinarea unei soluii": este reinut soluia. Reinerea unei soluii const n apelarea unei proceduri retsol care prelucreaz soluia (o tiprete, o compar cu alte soluii etc.) i fie oprete procesul (dac se dorete o singur soluie), fie prevede kk-1 (dac dorim s determinm toate soluiile.
41
Notm prin CkXk mulimea valorilor consumate din Xk. Algoritmul este urmtorul:
Ci, i; k1; while k>0 if k=n+1 then retsol(x); kk-1; { revenire dup obinerea unei soluii } else if CkXk then alege vXk\Ck; CkCk{v}; if k(x1,,xk-1,v) then xkv; kk+1; { atribuie i avanseaz } else { ncercare euat } else Ck; kk-1; { revenire }
42
Exemple
n exemplele care urmeaz, k va fi notat n continuare prin cont(k). Se aplic algoritmul de mai sus pentru diferite forme ale funciei de continuare. 1) Colorarea hrilor. Se consider o hart. Se cere colorarea ei folosind cel mult n culori, astfel nct oricare dou ri vecine (cu frontier comun de lungime strict pozitiv) s fie colorate diferit. Fie xk culoarea curent cu care este colorat ara k.
function cont(k: integer): boolean; b true; i 1; while b and (i<k) if vecin(i,k) & xi=xk then b false else i i+1 cont b end;
2) Problema celor n dame Se consider un caroiaj nn. Prin analogie cu o tabl de ah (n=8), se dorete plasarea a n dame pe ptrelele caroiajului, astfel nct s nu existe dou dame una n btaia celeilalte (adic s nu existe dou dame pe aceeai linie, coloan sau diagonal). Evident, pe fiecare linie vom plasa exact o dam. Fie xk coloana pe care este plasat dama de pe linia k. Damele de pe liniile i i k sunt: - pe aceeai coloan: dac xi=xk ; - pe aceeai diagonal: dac |xi-xk|=k-i.
function cont(k:integer): boolean; b true; i 1; while b and i<k if |xi-xk|=k-i or xi=xk then b false else i i+1; cont b end;
43
3) Problema ciclului hamiltonian Se consider un graf neorientat. Un ciclu hamiltonian este un ciclu care trece exact o dat prin fiecare vrf al grafului. Pentru orice ciclu hamiltonian putem presupune c el pleac din vrful 1. Vom nota prin xi al i-lea vrf din ciclu. x=(x1,...,xn) soluie dac: x1=1 & {x2,...,xn}={2,...,n} & xn,x1 vecine. Vom considera c graful este dat prin matricea sa de adiacen.
function cont(k:integer): boolean; if a(xk-1,xk)=0 then cont false else i 1; b true; while b & (i<k) if xk=xi then b false else i i+1; if k=n then b b a(xn,x1)=1; cont b end;
44
Rolul condiiilor de continuare este ilustrat n figura ce urmeaz. Dac pentru xk este aleas o valoare ce nu satisface condiiile de continuare, atunci la parcurgerea n adncime este evitat parcurgerea unui ntreg subarbore.
x1
x2
xk
45
Variante
Variantele cele mai uzuale ntlnite n aplicarea metodei backtracking sunt urmtoarele: - soluia poate avea un numr variabil de componente i/sau - dintre ele alegem una care optimizeaz o funcie dat. Exemplu. a=(a1,,an)Zn. Caut un subir cresctor de lungime maxim. Deci caut 1x1<...<xkn cu
k maxim ax1 < ax2 <...< axk
Pentru n=8 i a=(1,4,2,3,7,5,8,6) va rezulta k=5. Aici, soluia posibil: care nu poate fi continuat; exemplu: (4,7,8). Notm prin xf i kf soluia optim i lungimea sa. Completez cu - i + : a0 -; nn+1; an +; Funcia cont are urmtoarea form: function cont(k) cont axk-1<ak end; Procedura retsol are forma: procedure retsol(k) if k>kf then xfx; kfk; end; Algoritmul backtracking se modific astfel:
k1; x00; x10; kf0; while k>0 if xk<n then xkxk+1; if cont(k) then if xk=n { an=+ } then retsol(k); kk-1 else kk+1; xkxk-1 else else kk-1;
46
Varianta recursiv
O descriem pentru nceput pentru X1=...=Xn={1,...,s}. Apelul iniial este: back(1).
procedure back(k) if k=n+1 then retsol else for i=1,s xki; if cont(k) then back(k+1); revenire din recursivitate else end.
Exemplu. Dorim s producem toate irurile de n paranteze ce se nchid corect. Exist soluii n par. Fie dif=nr( - nr). Condiiile sunt : dif0 pentru k<n; dif=0 pentru k=n. Pornirea algoritmului backtracking se face prin:
a1( ; dif1; back(2);
Observaie. n exemplul tratat backtracking-ul este optimal! : se avanseaz dac i numai dac exist anse de obinere a unei soluii. Cu alte cuvinte, condiiile de continuare nu sunt numai necesare, dar i suficiente.
47
Bordez matricea cu 2 pentru a nu studia separat ieirea din matrice. Pentru refacerea drumurilor, pentru fiecare poziie atins memorm minte legtura la precedenta. Dac poziia e liber i pot continua, pun aij=-1 (a fost atins), continum i apoi repunem aij0 (ntoarcere din recursivitate). Programul n Java are urmtoarea form:
class elem { int i,j; elem prec; static int m,n,i0,j0,ndepl; static int[][] mat; static int[][] depl = { {1,0,-1,0}, {0,-1,0,1} }; static { ndepl = depl[0].length; } elem() { int i,j; IO.write("m,n = "); m = (int) IO.read(); n = (int) IO.read(); // de fapt m+2,n+2 mat = new int[m][n]; for(i=1; i<m-1; i++) for(j=1; j<n-1; j++) mat[i][j] = (int) IO.read(); for (i=0; i<n; i++) {mat[0][i] = 2; mat[m-1][i] = 2; } for (j=0; j<m; j++) {mat[j][0] = 2; mat[j][n-1] = 2; } IO.write("i0,j0 = "); i0 = (int) IO.read(); j0 = (int) IO.read(); } elem(int ii, int jj, elem x) { i = ii; j = jj; prec = x; } String print(elem x) { if (x == null) return "(" + i + "," + j + ")"; else return x.print(x.prec)+" "+"("+i+","+j+")"; }
48
void p() { elem x; int ii,jj; for (int k=0; k<ndepl; k++) { ii = i+depl[0][k]; jj = j+depl[1][k]; if (mat[ii][jj] == 1); else if (mat[ii][jj] == 2) IO.writeln(print(prec)); else if (mat[ii][jj] == 0) { mat[i][j] = -1; x = new elem(ii,jj,this); x.p(); mat[i][j] = 0; } } } } class DrumPlan { public static void main(String[] args) { new elem(); elem start = new elem(elem.i0,elem.j0,null); start.p(); } }
Contraexemplu. n plan, se cere s se determine toate punctele n care se poate ajunge din (i0,j0), precum i numrul minim de deplasri pn la ele.
0 0 0 0 4 1 3 2
(i0,j0)
49
EXTINDEREA CLASELOR
Cum se definesc clasele extinse
Limbajul Java pune la dispoziie i o alt facilitate important legat de OOP : posibilitatea de extindere a claselor. Pe scurt, deci incomplet, aceasta const n: - o clas poate fi extins, adugndu-se noi cmpuri i noi metode, care permit considerarea unor atribute suplimentare (dac ne gndim la cmpuri) i unor operaii noi (asupra cmpurilor "iniiale", dar i asupra cmpurilor nou adugate); - unele metode ale clasei pe care o extindem pot fi redefinite, iar anumite cmpuri ale acestei clase pot fi "ascunse"; - un obiect avnd ca tip clasa extins poate fi folosit oriunde este ateptat un obiect al clasei care a fost extins. La prima vedere, rezolvarea problemelor de mai sus se poate face simplu: modificm clasa, introducnd sau/i modificnd cmpurile i metodele clasei. n practica programrii, aceast soluie este de neconceput. Pe de o parte se pot introduce erori, iar pe de alt parte utilizatorii clasei vechi nu vor mai putea folosi clasa aa cum o fceau nainte i cum vor n continuare s o fac; clasa (firma care a elaborat-o) i va pierde astfel vechii clieni. S reinem deci ca o regul general de programare faptul c nu trebuie modificat o clas testat i deja folosit de muli utilizatori; cu alte cuvinte, nu trebuie modificat "contractul" ce a dus la scrierea clasei. De aceea vechea clas trebuie s rmn nemodificat, iar actualizarea ei trebuie fcut prin mecanismul de extindere a claselor, prezentat n continuare. Exemplu. S presupunem c dorim s urmrim micarea unui punct n plan. Vom ncepe prin a considera clasa:
class Punct { int x,y; Punct urm; Punct(int x, int y) { this.x=x; this.y=y; } void Origine() { x=0; y=0; } Punct Miscare(int x, int y) { Punct p = new Punct(x,y); urm=p; return p; } } Clasa Punct conine: - cmpurile x, y, urm ; - constructorul Punct cu signatura (int, int); - metoda Origine cu signatura () i metoda Miscare cu signatura (int,int).
50
Clasa Pixel conine: - cmpurile x,y,urm (motenite de la clasa Punct) i culoare (care a fost adugat prin extindere); - constructorul Pixel cu signatura (int,int,String); - metoda Miscare cu signatura (int,int), motenit de la clasa Punct, precum i metoda Origine cu signatura (), care redefinete metoda Origine a clasei Punct. Este adoptat urmtoarea terminologie: clasa Punct este superclas a lui Pixel, iar Pixel este subclas (clas extins) a lui Punct. n Java, clasele formeaz o structur de arbore n care rdcina este clasa Object, a crei definiie apare n pachetul java.lang. Orice clas extinde direct (implicit sau explicit) sau indirect clasa Object. tim c n informatic arborii "cresc n jos", deci rdcina (clasa Object) se afl pe cel mai de sus nivel. Termenii de superclas i subclas se refer exclusiv la relaia tat fiu, conform relaiei de extindere, n arborele de clase. Este incorect de a interpreta aceti termeni n sensul de incluziune! n exemplul de mai sus, apelurile super.Origine() i super(x,y) se refer respectiv la metoda Origine i la constructorul din superclasa Punct a lui Pixel. Vom reveni ns cu toate detaliile legate de super. Fie Sub o clas ce extinde clasa Super. Sunt importante urmtoarele precizri: - obiectele de tip Sub pot fi folosite oriunde este ateptat un obiect de tipul Super. De exemplu dac un parametru al unei metode este de tipul Super, putem invoca acea metod cu un argument de tipul Sub; - spunem c Sub extinde comportarea lui Super; - o clas poate fi subclas a unei singure clase (motenire simpl) deoarece, reamintim, clasele formeaz o structur de arbore. Drept consecin o clas nu poate extinde dou clase, deci nu exist motenire multipl ca n alte limbaje (exist ns mecanisme pentru a simula aceast facilitate i anume interfeele); - o metod din superclas poate fi rescris n subclas. Spunem c metoda este redefinit (dac nu este static) sau ascuns (dac este static). Evident, ne referim aici la o rescriere a metodei folosind aceeai signatur, pentru c dac scriem n subclas o metod cu o signatur diferit de signaturile metodelor cu acelai nume din superclas, atunci va fi vorba de o metod nou. La rescriere nu putem modifica tipul valorii ntoarse de metod. Accesul la metoda cu aceeai signatur din superclas se face, dup cum vom vedea, prin precalificare cu super; - un cmp redeclarat n Sub ascunde cmpul cu acelai nume din Super; - cmpurile neascunse i metodele nerescrise sunt automat motenite de subclas (n exemplul de mai sus este vorba de cmpurile x i y i de metoda Miscare); - apelurile super din constructorii subclasei trebuie s apar ca prim aciune.
51
- pentru tipuri numerice - pentru tipul char - pentru tipul boolean - pentru referine (la obiecte, inclusiv tablouri i String). n continuare este invocat constructorul corespunztor (determinat de signatur, n acelai mod ca pentru metode); aciunea sa cuprinde trei faze: 1) Invocarea unui constructor al superclasei i anume: - explicit, cu super(...) ; - implicit: este vorba de constructorul fr argumente. 2) Executarea, n ordinea n care apar, a iniializrilor directe ale cmpurilor; 3) Executarea corpului constructorului.
0 \u0000 false null
Observaii: - un constructor poate invoca un alt constructor i anume prin this(...); aceast invocare poate avea loc numai ca prim aciune a constructorului (deci nu pot fi invocai doi constructori). n schimb acest mecanism nu poate fi folosit pentru a invoca, din corpul unei metode, un constructor; - un constructor poate invoca o metod, caz n care se consider implementarea metodei corespunztoare tipului real al obiectului (vezi subcapitolul urmtor). Exemplu. S considerm clasele:
class a { int va=1, v; a() { v=va; } } class b extends a { int vb=2; b() { v=va+vb; } }
La crearea unui obiect de tipul b, prin new b(), au loc n ordine urmtoarele aciuni: - cmpurile v, va, vb primesc valoarea implicit 0; - este invocat constructorul b; - este invocat constructorul a (al superclasei); - are loc iniializarea direct a lui va, care primete valoarea 1; - este executat corpul constructorului a i ca urmare v primete valoarea 1; - are loc iniializarea direct a lui vb, care primete valoarea 2; - este executat corpul constructorului b i ca urmare v primete valoarea 3. Dac o clas este declarat cu modificatorul public, constructorul implicit are automat acest modificator. Constructorii unei clase nu sunt considerai membri ai clasei. De aceea ei nu pot fi motenii sau redeclarai. Constructorilor li se pot ataa modificatori de acces la fel ca metodelor (vezi subcapitolele urmtoare). Dar, spre deosebire de metode, un constructor nu poate fi declarat cu ali modificatori dect cei de acces. ntr-un constructor poate fi folosit instruciunea return, dar nensoit de o expresie.
52
Dac un constructor se invoc (direct sau indirect) pe el nsui, la compilare este semnalat o eroare. ntr-o invocare explicit a unui constructor, argumentele nu pot fi instanieri de cmpuri declarate n aceeai clas sau ntr-o superclas; de asemenea nu este permis utilizarea lui this i super n expresii. Exemplificm n continuare acest aspect pentru cmpuri. Exemplu. S considerm urmtoarea clas:
class C { int x; int y; C() { this(x); } C(int x) { y = x; } }
n clasa C, constructorul fr parametri invoc pe cel cu un parametru. Va fi semnalat o eroare la compilare. n schimb clasa va fi corect dac de exemplu cmpul x ar fi fost declarat cu modificatorul static.
53
Tipul real al unui obiect coincide cu tipul su declarat (este cazul obiectului b) sau este o subclas a tipului declarat (vezi obiectul a). Fie camp un cmp al clasei A, ce este redeclarat (ascuns) n subclasa B. Dac obiectul a face referire la cmpul camp, atunci este vorba de cmpul declarat n clasa A, adic este folosit tipul declarat al obiectului. Fie met o metod a clasei A, care este rescris n subclasa B. La invocarea metodei met de ctre obiectul a, este folosit fie implementarea corespunztoare metodei ascunse (dac este static), fie cea corespunztoare metodei redefinite (dac este nestatic). Cu alte cuvinte, pentru metode statice este folosit tipul declarat (la fel ca pentru cmpuri), iar pentru metode nestatice este folosit tipul real al obiectului. n ambele cazuri, se pleac de la tipul indicat mai sus i se merge n sus spre rdcin (clasa Object) n arborele de clase pn cnd se ajunge la primul tip n care apare metoda respectiv (evident cu signatura corespunztoare); inexistena unui astfel de tip este semnalat chiar n faza de compilare. Exemplu. Urmtorul program:
class Super { static void met1() { IO.writeln("static_Super"); } void met2() { IO.writeln("Super"); } } class Sub extends Super { static void met1() { IO.writeln("static_Sub"); } void met2() { IO.writeln("Sub"); } } class Test1 { public static void main(String[] s) { Super Ob = new Sub(); Ob.met1(); Ob.met2(); } }
produce la ieire:
static_Super Sub
54
adic rezultatele ateptate, innd cont c: - pentru obiectul b: tipul declarat coincide cu cel real i este B; - pentru obiectul a: tipul declarat este A, iar tipul real este B. S considerm o clas C i dou subclase X i Y ale sale, precum i urmtoarea secven de instruciuni:
C . . . . Ob; . . Ob = new X(...); . . Ob = new C(...); . . Ob = new Y(...); . . n care Ob are mai nti tipul real X, apoi tipul real C, apoi tipul real Y. Spunem c Ob
este o variabil polimorfic, deoarece are pe rnd forma (comportamentul) a mai multor clase.
55
56
Exemplu. Fie A o clas n care este definit o metod met, iar B o subclas a sa n care metoda met este redefinit. Secvena de instruciuni:
double d = IO.read(); A Ob; if (d>0) Ob = new A(...); else Ob = new B(...); Ob.met(...);
arat c doar la momentul invocrii metodei devine clar care dintre cele dou metode met va fi invocat.
Despre modificatori
Relum discuia despre modificatori, pentru a include i aspectele legate de extinderea claselor. Lista complet a modificatorilor folosii n Java este urmtoarea: - modificatorii de acces (public, protected, private i cel implicit); - abstract, static, final, synchronized, native, transient, volatile. Ei pot fi folosii astfel: - pentru clase: public,cel implicit, abstract, final; - pentru interfee: modificatorii de acces; - pentru constructori: modificatorii de acces; - pentru cmpuri: modificatorii de acces, final, static, transient, volatile; - pentru metode: modificatorii de acces, final, static, abstract, synchronized, native. Modificatorul final poate fi asociat (n afara variabilelor locale i cmpurilor) i metodelor i claselor. El trebuie neles n sensul de "variant final": - o metod cu acest modificator nu poate fi rescris ntr-o subclas; - o clas cu acest modificator nu poate fi extins. Metodele declarate cu private sau/i static sunt echivalente cu final, din punctul de vedere al redefinirii: nu pot fi redefinite. Dac o metod este final, "ne putem baza" pe implementarea ei (bineneles dac nu invoc metode nefinale). n unele situaii, este bine s marcm toate metodele cu final, dar nu i clasa.
57
Vizibilitate i acces
Pachetele, clasele, metodele, cmpurile i variabilele sunt identificate prin numele lor. Problema care se pune este de a stabili din ce loc putem face referire la aceste nume (cu semnificaia dorit), adic de a preciza domeniul lor de vizibilitate. Variabile locale i etichete
Prin bloc nelegem o secven (eventual vid) de instruciuni, cuprins ntre acolade. Un bloc este o instruciune i de aceea poate fi folosit n orice loc unde poate s apar o instruciune. De exemplu orice metod i orice constructor sunt formate dintr-un antet urmat de un bloc. n schimb ceea ce urmeaz dup modificatorii i numele unei clase nu constituie un bloc, dei apar acoladele deschis i nchis: coninutul unei clase nu este o succesiune de instruciuni. Orice bloc poate conine la rndul su un alt bloc, putnd astfel lua natere o structur imbricat de blocuri. Prin variabil local nelegem o variabil declarat n interiorul unui bloc, inclusiv ntr-o instruciune for. Domeniul de vizibilitate al unei variabile locale este constituit din partea din blocul n care a fost declarat, ce urmeaz declarrii. Prin urmare o variabil local nu poate fi referit nainte de a fi declarat. Variabilele locale exist numai pe perioada n care blocul n care sunt declarate este n curs de executare. Nu putem folosi facilitatea de imbricare a blocurilor pentru a redeclara o variabil local. O variabil local nu poate fi redefinit nici n blocul n care a fost declarat, nici ntr-un bloc inclus n acesta. Regulile care guverneaz domeniul de vizibilitate al etichetelor sunt aceleai ca pentru variabilele locale. Parametrii metodelor i constructorilor
Numele parametrilor dintr-o metod sau dintr-un constructor sunt vizibile doar n interiorul acestora, adic n blocul care urmeaz antetului metodei sau constructorului. Este ns posibil ca ntr-un bloc dintr-o metod sau dintr-un constructor s redeclarm numele unui parametru; n acest caz, n partea din bloc ce urmeaz redeclarrii, numele respectiv este considerat ca avnd semnificaia dat de redeclarare. Cu alte cuvinte, domeniul de vizibilitate al unui parametru este constituit din corpul metodei sau constructorului n care apare, mai puin blocurile (evident disjuncte) n care este redeclarat. Vizibilitatea n interiorul unei clase
tim c o clas este constituit din cmpuri, constructori (folosii la crearea de obiecte) i metode (folosite la invocarea lor). Din orice constructor i din orice metod putem s ne referim la orice cmp sau metod a clasei. De asemenea dintr-un constructor putem apela alt constructor prin this urmat, ntre paranteze, de o list de argumente ce respect signatura noului constructor.
58
Ordinea n care apar cmpurile nestatice i metodele n cadrul clasei nu este relevant (de exemplu nu conteaz dac un cmp nestatic este declarat la nceputul sau la sfritul clasei). Totui, dac numele unui cmp este redeclarat ntr-o metod (este folosit ca nume al unui parametru sau declarat ntr-un bloc), atunci declararea cea mai interioar este cea care primeaz cnd se face o referire la acel nume (identificator). Putem spune c un parametru poate ascunde un cmp, iar o variabil local poate ascunde un parametru i/sau un cmp. Dup cum vom vedea n continuare, numele unei metode nu poate fi ascuns. Modificatorul final
Modificatorul final poate fi ataat att variabilelor (locale sau cmpuri), ct i metodelor. n cazul variabilelor locale, variabilei i se atribuie valoare la declarare sau nainte de a fi folosit; variabila nu poate primi de dou ori valoare. Menionm c final este singurul modificator ce poate fi ataat unei variabile locale. Unui cmp final trebuie s i se atribuie valoare fie prin iniializare, fie prin constructori. Menionm c n general modificatorul final este folosit mpreun cu static, pentru a trata un cmp ca o "constant cu nume". Efectul modificatorului final asupra metodelor i claselor va fi prezentat atunci cnd vom vorbi despre extinderea claselor. Modificatorul static
Modificatorul static poate fi folosit la declararea cmpurilor i metodelor unei clase. O variabil local nu poate fi declarat cu static . Fie c un cmp declarat cu static n clasa Clasa. Aceast declarare are dou consecine: - cmpul c este comun tuturor obiectelor de tipul Clasa; - referirea la cmp din exteriorul clasei se face fie conform regulii generale (prin crearea i utilizarea unei instane a clasei Clasa), fie direct, prin precalificarea cmpului cu numele clasei (Clasa.c). Fie acum met o metod declarat cu modificatorul static n clasa Clasa; ea se numete metod static sau metod de clas. Invocarea ei se poate face fie conform regulii generale (prin crearea i utilizarea unei instane a clasei Clasa), fie direct, prin precalificarea metodei cu numele clasei: Clasa.met(...) ; De aceea ntr-o metod static nu poate fi folosit referina this. O metod static poate accesa numai cmpurile i metodele statice ale clasei din care face parte sau a altei clase. Metodele statice (de clas) sunt folosite de obicei pentru a efectua prelucrri asupra cmpurilor statice ale clasei. Putem "ocoli" ns aceast regul de exemplu astfel: - la invocarea metodei i transmitem ca parametru o referin la un obiect; - n metod construim explicit un obiect de tipul clasei la ale crei metode i cmpuri dorim s ne referim.
59
Modificatorii de acces
Toate cmpurile i metodele unei clase sunt accesibile (pot fi referite) din interiorul clasei. Rolul modificatorilor public, protected, private i cel implicit (numit uneori package) referitor la accesul din exteriorul unei clase la membrii si este sintetizat n urmtorul tabel: Membrii clasei cu acest modificator sunt accesibili ... public de oriunde clasa este accesibil protected din cod din acelai pachet implicit (package din cod din acelai pachet ) private numai din interiorul clasei Reamintim c modificatorii de mai sus joac un rol nu numai n privina accesului, dar i pentru facilitatea de extindere a claselor, ceea ce va face diferena ntre modificatorul protected i cel implicit. Determinarea semnificaiei unui nume Modificator
Pentru un identificator folosit ca nume de variabil (local sau cmp), metod sau clas, semnificaia sa se obine la prima cutare ncununat de succes din urmtorul ir de cutri succesive: 1) variabilele locale declarate ntr-un bloc sau un ciclu for; 2) numai dac suntem n interiorul unei metode sau a unui constructor: parametrii metodei sau constructorului; 3) membrii clasei, inclusiv cei rezultai prin extinderea claselor (vezi capitolul urmtor) 4) tipuri importate explicit; 5) tipuri declarate n acelai pachet; 6) tipuri importate implicit; 7) pachete disponibile pe sistemul gazd. Mai mult, se face distincie clar ntre tipurile de identificatori: nume de variabile (locale), de pachete, de clase, de metode i de cmpuri; iat de ce numele unei metode nestatice nu poate fi ascuns. Aceasta permite s folosim un acelai identificator pentru a numi una sau mai multe astfel de entiti. Exemplul 3. Urmtoarea clas este corect din punct de vedere sintactic, compilatorul nesemnalnd vreo eroare:
class obsesie { obsesie obsesie; obsesie() { obsesie = new obsesie(); } obsesie obsesie (obsesie obsesie) { obsesie: while (obsesie != null) if ( obsesie.obsesie(obsesie)==obsesie ) break obsesie; return obsesie; } }
60
Nu recomandm ns s abuzm n acest mod de distincia menionat. Clasa de mai sus poate fi scris sub urmtoarea form mai clar i "neobsesiv":
class obsesie { obsesie C; obsesie() { C = new obsesie(); } obsesie met (obsesie param) { et: while (param != null) { if ( param.met(param)==param ) break et; } return param; } }
61
62
Considerm pe rnd numerele (n ordine cresctoare). Fiecare numr va fi plasat pe linia i cu i minim i cu proprietatea c numrul curent se divide cu un numr aflat pe o linie anterioar. Pentru exemplificare, s considerm c m=5, n=4, iar numerele sunt: 3,5,9,12,14,15,33,x,... Dup plasarea primelor 8 numere, suntem n situaia: 3 9 24 5 12 14 15 33
Dac de exemplu x=72, atunci el ar trebui plasat pe a patra linie (inexistent) i am obine n=4 numere care se divid unul pe altul (de exemplu 3, 12, 24, 72). Dac de exemplu x=35, atunci el ar trebui plasat pe a doua linie i am obine m=5 numere care nu se divid ntre ele: 9, 12, 15, 33, 35. Este important de notat c pe fiecare linie numerele plasate apar n ordine cresctoare. Principiul lui Dirichlet ne asigur c cel mai trziu dup plasarea ultimului numr vom iei din "cutie": aici caroiajul (n-1)(m-1). O variant a principiului lui Dirichlet este urmtoarea: Fie k1,...kn , K=(k1+...+kn)/n i x un numr oarecare. Atunci: dac x<K i cu x<ki dac x>K i cu x>ki
Exemplul 5. Dac vrfurile unui decagon sunt etichetate distinct cu numerele 0,1,...,9 ntro ordine oarecare, atunci exist 3 vrfuri consecutive pentru care suma etichetelor este mai mare dect 13. Fie xi{0,1,...,9} eticheta vrfului i, pentru i=1,...,10. Considerm numerele: k1=x1+x2+x3 k2=x2+x3+x4 . . . . .
k9=x9+x10+x1 k10=x10+x1+x2
Atunci K=(k1+...+kn)/n = 3(0+1+...+9)/10 = 13,5. Conform principiului lui Dirichlet, va exista i cu ki>13,5>13. Exemplul 6. Se consider m calculatoare i n imprimante (m>n). Fie =numrul minim de legturi calculatorimprimant ce trebuie stabilite, astfel nct dac orice n calculatoare doresc s scrie simultan, acest lucru s fie este posibil. Se cere s se arate c n(m-n+1). Fie ki numrul de calculatoare legate la imprimanta i. Numrul de legturi este deci =(k1+...+kn).
63
Dac <n(m-n+1), atunci (k1+...+kn)/n<m-n+1. Conform variantei principiului lui Dirichlet, exist o imprimant i legat la cel mult m-n calculatoare, deci care nu este legat la cel puin n calculatoare; dac acestea vor s scrie simultan, nu vor reui. Contradicie. Exerciiu. S se descrie o modalitate prin care problema poate fi rezolvat cu exact n(mn+1) legturi.
64
Schema general
Descriem schema general pentru cazul n care aplicm metoda pentru o prelucrare oarecare asupra elementelor unui vector. Funcia DivImp, care ntoarce rezultatul prelucrrii asupra unei subsecvene ap,...au, va fi apelat prin DivImp(1,n).
function DivImp(p,u) if up< then r Prel(p,u) else m Interm(p,u); r1 DivImp(p,m); r2 DivImp(m+1,u); r Combin(r1,r2) return r end; p u
unde: - funcia Interm ntoarce un indice n intervalul p..u (de obicei m=(p+u)/2 ). - funcia Prel este capabil s ntoarc rezultatul subsecvenei p..u, dac aceasta este suficient de mic; - funcia Combin ntoarce rezultatul asamblrii rezultatelor pariale r1 i r2. Exemple: Calculul maximului elementelor unui vector; Parcurgerile n preordine, inordine i postordine ale unui arbore binar; Sortarea folosind arbori de sortare.
65
Cutarea binar
Se consider vectorul a=(a1, ...,an) ordonat cresctor i o valoare x. Se cere s se determine dac x apare printre componentele vectorului. Problema enunat constituie un exemplu pentru cazul n care problema se reduce la o singur subproblem. innd cont de faptul c a este ordonat cresctor, vom compara pe x cu elementul din "mijlocul" vectorului. Dac avem egalitate, algoritmul se ncheie; n caz contrar vom cuta fie n "jumtatea" din stnga, fie n cea din dreapta. Vom aduga a 0 =-, an+1=+ . Cutm perechea (b,i) dat de: (true,i) dac ai=x; (false,i) dac ai-1<x<ai. Deoarece problema se reduce la o singur subproblem, nu mai este necesar s folosim recursivitatea. Algoritmul este urmtorul:
procedure CautBin p u; u n while pu i (p+u)/2 case ai>x : u i-1 ai=x : write(true,i); stop ai<x : p i+1 write(false,p) end
Algoritmul necesit o mic analiz, legat de corectitudinea sa parial. Mai precis, ne ntrebm: cnd se ajunge la p>u? pentru cel puin 3 elemente : nu se poate ajunge la p>u; pentru 2 elemente, adic pentru u=p+1: se alege i=p. Dac x<ai, atunci up-1. Se observ c se iese din ciclul while i ai-1<x<ai=ap; pentru un element, adic p=u: se alege i=p=u. Dac x<ai atunci up-1, iar dac x>ai atunci pu+1; n ambele cazuri se prsete ciclul while i tiprete un rezultat corect.
66
O mutare este notat prin (i,j) i semnific deplasarea discului din vrful tijei i deasupra discurilor aflate pe tija j. Se presupune c mutarea este corect (vezi condiia de mai sus). Fie H(m;i,j) irul de mutri prin care cele m discuri din vrful tijei i sunt mutate peste cele de pe tija j, folosind i a treia tij, al crei numr este evident 6-i-j. Problema const n a determina H(n;1,2). Se observ c este satisfcut relaia: H(m;i,j)= H(m-1;i,j) (i,j) H(m-1;i,j) (*) respectndu-se condiia din enun. Deci problema pentru m discuri a fost redus la dou probleme pentru m-1 discuri, al cror rezultat este asamblat conform (*). Corespunztor, vom executa apelul Hanoi(n,1,2), unde procedura Hanoi are forma:
procedure Hanoi(n,i,j) if n=1 then write(i,j) else k6-i-j; Hanoi(n-1,i,k); Hanoi(1,i,j); Hanoi(n-1,k,j) end
67
if k1>m { au fost epuizate elementelei primei subsecvene } then for i=k2,u bk3ai; k3k3+1 else { au fost epuizate elementelei primei subsecvene } for i=k1,m bk3ai; k3k3+1 for i=p,u aibi
end
Timpul de calcul este de ordinul O(u-p), adic liniar n lungimea secvenei analizate. Programul principal urmeaz ntocmai strategia Divide et Impera, deci se face apelul SortInter(1,n), unde procedura recursiv SortInter are forma:
procedure SortInter(p,u) if p=u then else m(p+u)/2; SortInter(p,m); SortInter(m+1,u); Inter(p,m,u) end
Calculm n continuare timpul de executare T(n), unde T(n) se poate scrie: t0 (constant), pen tru n=1; 2T(n/2)+an, pentru n>1, unde a este o constant: problema de dimensiune n s-a descompus n dou subprobleme de dimensiune n/2, iar combinarea rezultatelor s-a fcut n timp liniar (prin interclasare). Presupunem c n=2k. Atunci:
T(n) = T(2k) =2 T(2k-1) + a 2k = =2[2T(2k-2) + a 2k-1] + a 2k = 22T(2k-2) + 2 a 2k = =22[T(2k-3) + a 2k-2] + 2 a 2k = 2 3 T(2k-3) + 3 a 2k = . . . = 2iT(2k-i) + i a 2k = . . . =2kT(0) + k a 2k = nt0 + a n log 2 n.
Rezult c T(n)=0(n.log n). Se observ c s-a obinut acelai timp ca i pentru sortarea cu ansamble. Meniune. Se poate demonstra c acest timp este optim.
68
Metoda Quicksort
Prezentm nc o metod de sortare a unui vector a=(a1,...,an). Va fi aplicat tot metoda Divide et Impera. i de aceast dat fiecare problem va fi descompus n dou subprobleme mai mici de aceeai natur, dar nu va mai fi necesar combinarea (asamblarea) rezultatelor rezolvrii subproblemelor. Fie (ap,...,au) secvena curent care trebuie sortat. Vom poziiona pe ap n secvena (ap,...,au), adic printr-o permutare a elementelor secvenei: x=ap va trece pe o poziie k; toate elementele aflate la stnga poziiei k vor fi mai mici dect x; toate elementele aflate la dreapta poziiei k vor fi mai mari dect x. n acest mod ap va aprea pe poziia sa final, rmnnd apoi s ordonm cresctor elementele aflate la stnga sa, precum i pe cele aflate la dreapta sa. Fie poz funcia cu parametrii p,u care ntoarce indicele k pe care va fi poziionat ap n cadrul secvenei (ap,...,au). Atunci sortarea se realizeaz prin apelul QuickSort(1,n), unde procedura QuickSort are forma:
procedure QuickSort(p,u) if p=u then else k poz(p,u); QuickSort(p,k-1); QuickSort(k+1,n) end
69
Se obine astfel (1,3,2,5,6,8,9,7), n care la stnga lui 6 apar valori mai mici, iar la dreapta lui 6 apar valori mai mari, deci l-am poziionat pe 6 pe poziia 8, valoare ntoars de funcia poz. Observaie. Cazul cel mai defavorabil pentru metoda Quicksort este cel n care vectorul este deja ordonat cresctor: se compar a1 cu a2,...,an rezultnd c el se afl pe poziia final, apoi se compar a2 cu a3,...,an rezultnd c el se afl pe poziia final etc. Trecem la calculul timpul mediu de executare al algoritmului Quicksort. Vom numra cte comparri se efectueaz (componentele vectorului nu sunt neaprat numere, ci elemente dintr-o mulime ordonat oarecare). Timpul mediu este dat de formulele:
1 n T(n)= n 1 + [T(k 1)+ T(n k)] n k =1 T(1)= T (0)=0
deoarece: n cazul cel mai defavorabil a1 se compar cu celelalte n-1 elemente; a1 poate fi poziionat pe oricare dintre poziiile k=1,2,...,n; considerm aceste cazuri echiprobabile; T(k-1) este timpul (numrul de comparri) necesar ordonrii elementelor aflate la stnga poziiei k, iar T(n-k) este timpul necesar ordonrii elementelor aflate la dreapta poziiei k.
nT(n) = n(n-1)+2[T(0) +T(1)+.+T(n-1)] (n-1)T(n-1) = (n-1)(n-2)+2[T(0)+.+T(n-2)]
T(n) T(n 1) 2(n 1) = + n +1 n n(n + 1) T(n) T(n 1) 2 1 = + 2 n +1 T(n) n +1 n T(n 1) T(n 2) 2 1 = + 2 n T(n 1) n n 1 ........................... T(2) T(1) 2 1 = + 2 3 2 3 2
f(x)=ln x
2 3
n+1
(am folosit o inegalitate bazat pe sumele Rieman pentru funcia f(x)=ln x). Deci T(n)=0(n.log n). ncheiem cu meniunea c metoda Divide et Impera are o larg aplicativitate n calculul paralel.
71
1. O problem general
Fie A i B dou mulimi oarecare. Fiecrui element xA urmeaz s i se asocieze o valoare v(x)B. Iniial v este cunoscut doar pe XA, X. Fiecrui xA\X i asociem: AxA : mulimea elementelor din A de a cror valoare depinde v(x); fx funcie care specific dependena de mai sus. Dac Ax={a1,...,ak}, : atunci v(x)=fx(v(a1),...,v(ak)). Se mai d zA. Se cere s se calculeze, dac este posibil, valoarea v(z). Exemplu.
A={1,2,...,12}; X={1,2,6,7,8,9}; A3={1,2}; A4={1,2,3}; A5={1,4}; A11={8,9}; A12={10,11}. A10={7,8}; Elementele din X au asociat valoarea 1. Fiecare funcie fx calculeaz v(x) ca fiind suma valorilor elementelor din Ax. z=5. Este evident c vom obine v=(1,1,2,4,5,1,1,1,1,1,2,2,4). O ordine posibil de a considera elementele lui A\X astfel nct s putem calcula valoarea asociat lor este: 3,10,11,12,4,2.
Lucrurile devin mai clare dac reprezentm problema pe un graf de dependene. Vrfurile corespund elementelor din A, iar descendenii unui vrf x sunt vrfurile din Ax. Vrfurile din X apar ngroate. 5 12 4 3 2 7 8 9 10 6 10 11
72
Problema enunat nu are totdeauna soluie, aa cum se vede pe graful de dependene de mai jos, n care exist un circuit. z=3. 3 1 2 4
Observaii: - A poate fi chiar infinit; - B este de obicei N, Z, R, {0,1} sau un produs cartezian; - fx poate fi un minim, un maxim, o sum etc. Pentru orice xA, spunem c x este accesibil dac, plecnd de la X, poate fi calculat valoarea v(x). Evident, problema are soluie dac i numai dac z este accesibil. Pentru orice xA, notm prin Ox mulimea vrfurilor observabile din x, adic mulimea vrfurilor y pentru care exist un drum de la y la x. Problema enunat are soluie dac i numai dac: 1) Oz nu are circuite; 2) vrfurile din Oz n care nu sosesc arce fac parte din X. Prezentm n continuare mai multe metode de rezolvare a problemei enunate.
Propoziie. Dac Xk+1=Xk, atunci Xk+i=Xk ,iN. Facem demonstraia prin inducie dup i. Pentru i=1 rezultatul este evident. Presupunem Xk+i=Xk i demonstrm c Xk+i+1=Xk : Xk+i+1 = cf. definiiei irului de mulimi
= = = = Xk+i {x A AxXk+i} = cf. ipotezei de inducie Xk {x A AxXk} = cf. definiiei irului de mulimi Xk+1 = cf. ipotezei de inducie Xk.
73
Consecine. ne oprim cu construcia irului cresctor de mulimi la primul k cu Xk=Xk+1 (A este finit!); - dac aplicm cele de mai sus pentru problema general enunat, aceasta are soluie dac i numai dac zXk.
-
Prezentm n continuare algoritmul corespunztor acestei metode, adaptat la problema general. Vom lucra cu o partiie A=UV, unde U este mulimea curent de vrfuri a cror valoare asociat este cunoscut.
U X; V A\X repeat W V for toi xV if AxU then U U {x}; V V\{x} calculeaz v(x) conform funciei fx if x=z then write v(x); stop until V=W { nu s-a avansat! } write(z, 'nu este accesibil)
Metoda descris are dou deficiene majore: - la fiecare reluare se parcurg toate elementele lui V; - nu este precizat o ordine de considerare a elementelor. Aceste deficiene fac ca aceast metod s nu fie performant. Metoda irului cresctor de mulimi este larg folosit n teoria limbajelor formale, unde de cele mai multe ori ne intereseaz existena unui algoritm i nu performanele sale. Sortarea topologic
Fie A={1,..,n} o mulime finit. Pe A este dat o relaie tranzitiv, notat prin "<". Relaia este dat prin mulimea perechilor (i,j) cu i<j. Se cere s se listeze elementele 1,..,n ale mulimii ntr-o ordine ce satisface cerina: dac i<j, atunci i apare la ieire naintea lui j. Problema enunat apare de exemplu la nscrierea unor termeni ntr-un dicionar astfel nct explicaiile pentru orice termen s conin numai termeni ce apar anterior. Este evident c problema se transpune imediat la grafurile de dependen: se cere o parcurgere a vrfurilor grafului astfel nct dac exist un arc de la i la j, atunci i trebuie vizitat naintea lui j.
74
Observaii: - problema are soluie dac i numai dac graful este aciclic; - dac exist soluie, ea nu este neaprat unic. n esen, algoritmul care urmeaz repet urmtorii pai: - determin i care nu are predecesori; - l scrie; - elimin perechile pentru care sursa este i. Fie M mulimea curent de vrfuri. Iniial M=A. Fie C coada elementelor din M care nu au predecesori. Pentru fiecare iA, considerm: Si = lista succesorilor lui i; nrpredi = numrul predecesorilor lui i din mulimea M curent. Etapa de iniializare const n urmtoarele:
Si , nrpredi0, i C ; nr 0 { nr este numrul elementelor produse la ieire } for k=1,m { m este numrul perechilor din relaia "<" } read(i,j) Si j; nrpredj nrpredj+1 for i=1,n if nrpredi=0 then i C
S observm c timpul cerut de etapa de iniializare este de ordinul O(m+n). Algoritmul propriu-zis, adaptat la problema general, este urmtorul:
while C i C; write(i); calculeaz v(x) conform funciei fx if i=z then write v(i); stop for toi jSi nrpredj nrpredj-1 if nrpredj=0 then j C if nr<n then write('Nu)
Fiecare executare a lui while necesit un timp proporional cu Si. Dar |S1|+...+|Sn|=m, ceea ce face ca timpul de executare s fie de ordinul O(m). innd cont i de etapa de iniializare, rezult c timpul total este de ordinul O(m+n), deci liniar. Totui, sortarea topologic aplicat problemei generale prezint un dezavantaj: sunt calculate i valori ale unor vrfuri "neinteresante", adic neobservabile din z.
75
Apare un avantaj: sunt parcurse doar vrfurile din Oz. Dezavantajele sunt ns decisive pentru renunarea la aceast ncercare: - algoritmul nu se termin pentru grafuri ciclice; - valoarea unui vrf poate fi calculat de mai multe ori, ca de exemplu pentru situaia:
Soluie final
-
Etapele sunt urmtoarele: Identificm Gz = subgraful asociat lui Oz ; Aplicm sortarea topologic. Fie Gz=(Xz,Mz). Iniial Xz=, Mz=. Pentru a obine graful Gz executm apelul DF(z), unde procedura DF este:
procedure DF(x) x Xz for toi yAx if yXz then (y,x) Mz; DF(y) end;
Timpul este liniar. Observaie. Ar fi totui mai bine dac : - am cunoate de la inceput Gz; - forma grafului ar permite o parcurgere mai simpl.
76
1 2 Un PD-arbore nu este neaprat un arbore, dar: - poate fi pus pe niveluri: fiecare vrf x va fi pus pe nivelul egal cu lungimea celui mai lung drum de la x la z; - poate fi parcurs (cu mici modificri) n postordine; Prin parcurgerea n postordine, vrfurile apar sortate topologic. Algoritmul de parcurgere n postordine folosete un vector parcurs pentru a ine evidena vrfurilor vizitate. Este iniializat vectorul parcurs i se ncepe parcurgerea prin apelul postord(z):
for toate vrfurile x parcurs(x) xX postord(z)
Timpul de executare a algoritmului este evident liniar. Metoda programrii dinamice se aplic problemelor care urmresc calcularea unei valori i const n urmtoarele: 1) Se asociaz problemei un graf de dependene; 2) n graf este pus n eviden un PD-arbore; problema se reduce la determinarea valorii asociate lui z (rdcina arborelui); 3) Se parcurge n postordine PD-arborele.
77
Mai pe scurt, putem spune: Metoda programrii dinamice const n identificarea unui PD-arbore i parcurgerea sa n postordine. n multe probleme este util s cutam n PD-arbore regulariti care s evite memorarea valorilor tuturor vrfurilor. Vom ncepe cu cteva exemple foarte simple, dar care pun n eviden anumite caracteristici ale metodei programrii dinamice. Exemplul 1. irul lui Fibonacci tim c acest ir este definit astfel:
F0=0; F1=1; Fn = Fn-1 + Fn-2 , n2 Dorim s calculm Fn pentru un n oarecare.
Aici A={0,...,n}, X={0,1}, B=N, iar Ak={k-1,k-2}, k2 v(k)=Fk ; fk(a,b)=a+b, k2 Un prim graf de dependene este urmtorul: 0 1
n-2
n-1
n=z
78
Exemplul 2. Calculul sumei a1+ ...+an Este evident c trebuie calculate anumite sume pariale. O prim posibilitate este s considerm un graf n care fiecare vrf s fie o submulime {i1,...,ik} a lui {1,2,...,n}, cu valoarea asociat ai1+...+aik. Aceast abordare este nerealizabil: numrul de vrfuri ar fi exponenial. O a doua posibilitate este ca vrfurile corespund mulimilor {i,i+1,...,j} cu ij i cu valoarea ataat si,j=ai+...+aj. Vom nota un astfel de vrf prin (i:j). Dorim s calculm valoarea vrfului z=(1:n). Putem considera mai muli PD-arbori: Arborele liniar constituit din vrfurile cu i=1. Obinem relaiile de recuren: s1,1=a1; s1,j=s1,j-1+aj , ji care corespund asociativitii la stnga: (...((a1+a2)+a3)+...). Arborele liniar constituit din vrfurile cu j=n. Acest arbore corespunde asociativitii la dreapta a sumei. Arborele binar strict n care fiecare vrf (afar de frunze) are descendenii (i:k) i (k+1:j) cu k=(i+j)/2. Prezentm acest arbore pentru n=7: (1:7)
(1:4)
(5:7)
(1:2)
(3:4)
(5:6)
(1:1)
(2:2)
(3:3)
(4:4)
(5:5)
(6:6)
(7:7)
79
Algoritmul de mai sus nu este att de stupid i inutil pe ct apare la prima vedere pentru o problem att de simpl. Calculele pentru fiecare reluare a ciclului while interior sunt executate asupra unor seturi de date disjuncte. De aceea, n ipoteza c pe calculatorul nostru dispunem de mai multe procesoare, calculele pe fiecare nivel al arborelui (mergnd de jos n sus) pot fi executate n paralel. Drept urmare, timpul de calcul va fi de ordinul O(log n), deci sensibil mai bun dect cel secvenial, al crui ordin este O(n). Exemplul 3. Deteminarea subirului cresctor de lungime maxim. Se consider vectorul a=(a1,...,an). Se cer lungimea celui mai lung ir cresctor, precum i toate subirurile cresctoare de lungime maxim. Introducem notaiile: nr = lungimea maxim cutat; lung(i)= lungimea maxim a subirului cresctor ce ncepe cu ai. A={1,2,...,n}; X={n}; Ai={i+1,...,n}, i<n;
Evident, suntem n prezena unui PD-arbore de rdcin 1.
fi=lung(i)
Determinarea tuturor subirurilor cresctoare de lungime maxim se face printrun backtracking recursiv optimal. Subirurile se obin n vectorul s, iar ind reprezint ultima poziie completat din s.
for i=1,n if lung(i)=nr then ind 1; s(1)ai; scrie(i)
procedure scrie(i) if ind=nr then write(s) else for j=i+1,n if ai<aj & lung(i)=1+lung(j) then ind++; s(ind)a(j); scrie(j); indind-1 end;
Observaie. Dac dorim s obinem un singur subir cresctor de lungime nr, este suficient s eliminm instruciunea indind-1. Exemplul 4. nmulirea optim a unui ir de matrici. Avem de calculat produsul de matrici A1A2...An, unde dimensiunile matricilor sunt respectiv (d1,d2), (d2,d3), ... ,(dn,dn+1). tiind c nmulirea matricilor este asociativ, se pune problema ordinii n care trebuie nmulite matricile astfel nct numrul de nmuliri elementare s fie minim. Presupunem c nmulirea a dou matrici se face n modul uzual, adic produsul matricilor A(m,n) i B(n,p) necesit mnp nmuliri elementare.
Pentru a pune n eviden importana ordinii de nmulire, s considerm produsul de matrici A1A2A3A4 unde A1(100,1), A2(1,100), A3(100,1), A4(1,100). Pentru ordinea de nmulire (A1A2)(A3A4) sunt necesare 1.020.000 de nmuliri elementare. n schimb, pentru ordinea de nmulire (A1(A2A3))A4 sunt
necesare doar 10.200 de nmuliri elementare. Fie cost(i,j) numrul minim de nmuliri elementare pentru calculul produsului Ai...Aj. Punnd n eviden ultima nmulire de matrici, obinem relaiile:
cost(i,i) = 0 cost(i,j) = min {cost(i,k)+cost(k+1,j)+didk+1dj+1 | ik<j}. Valoarea cerut este cost(1,n).
Vrfurile grafului de dependen sunt perechile (i,j) cu ik. cost(i,j) depinde de vrfurile din stnga i de cele de deasupra. Se observ uor c suntem n prezena unui PD-arbore.
j 1 n j
(i,i)
(i,j) (j,j)
81
Forma particular a PD-arborelui nu face necesar aplicarea algoritmului general de parcurgere n postordine: este suficient s parcurgem coloanele 2,...,n, iar pe fiecare coloan s mergem de la diagonal pn la (i,j). for j=2,n
for i=j-1,1,-1 cost(i,j) calculat ca mai sus; fie k valoarea pentru care se realizeaz minimul cost(j,i)k write cost(1,n)
(se observ c am folosit partea inferior triunghiular a matricii pentru a memora indicii pentru care se realizeaz minimul). Dac dorim numai costul final, nu este nevoie s memorm ntreaga matrice, ci este suficient s folosim un vector. Dac dorim s producem i o ordine de nmulire optim, avem nevoie de ntreaga matrice. Vom apela sol(1,n), unde procedura sol are forma:
procedure sol(p,u) if p=u then write(p) else kcost(u,p) write('('); sol(p,k); write(','); sol(k+1,u); write(')') end;
Pentru evaluarea timpului de lucru, vom calcula numrul de comparri efectuate. Aceste este:
j=2i=1 (j1)(j2) = O(n3) (j i + 1)= j(j 1) 2 j=2 n j1 n
Exemplul 5. Descompunerea unui dreptunghi n ptrate Se consider un dreptunghi cu laturile de m, respectiv n uniti (m<n). Asupra sa se pot face tieturi complete pe orizontal sau vertical. Se cere numrul minim de ptrate n care poate fi descompus dreptunghiul. Fie aij = numrul minim de ptrate n care poate fi descompus un dreptunghi de laturi i i j. Evident aij=aji. Rezultatul cutat este amn. Vrfurile grafului de dependene sunt (i,j), iar valorile asociate sunt aij .
k k i i-k j j-k
82
Pentru calculul lui aij avem de ales ntre a face: - o tietur pe vertical; costurile sunt: aik+ai,j-k, k j/2 ; - o tietur pe orizontal; costurile sunt: ak,j+ai-k,j, k i/2 . Rezult c valoarea aij a unui vrf (i,j) depinde de valorile vrfurilor din stnga sa i de cele aflate deasupra sa. Se observ c graful de dependene este un PDarbore.
j
(i,j)
Forma particular a PD-arborelui permite o parcurgere mai uoar dect aplicarea algoritmului general de postordine. De exemplu putem cobor pe linii, iar pe fiecare linie mergem de la stnga la dreapta. Dup iniializrile date de primele 3 dependene de mai sus, efectum calculele:
for i=2,m for j=i+1,n
83
Drumuri n grafuri
Fie G=(V,M) graf orientat cu n=|V|, m=|M|. Fie A matricea sa de adiacen. Considerm irul de matrici:
A 1 = A k A = A k-1A, k 2
a crui semnificaie este urmtoarea: Propoziia 1. Ak-1(i,j) = numrul drumurilor de lungime k de la i la j. - pentru k=1: evident. k-1 k : A k(i,j)= A k1(i,s) A(s,j), unde s este penultimul vrf din
s=1 n
drumul de la i la j. n continuare dorim s determinm numai existena drumurilor de lungime k. Considerm irul de matrici:
A(1) = A (k) (k-1) A = A A, k 2
a crui semnificaie este urmtoarea (elementele matricilor sunt 0 sau 1): Propoziia 2. Ak-1(i,j)=1 exist drum de lungime k de la i la j. Demonstraia se face prin inducie ca mai sus. Definim matricea drumurilor D prin: D(i,j)=1 drum de la i la j. D=A(1)...A(n-1), deoarece dac exist un drum de la i la j, exist i un drum de lungime cel mult egal cu n-1 de la i la j. Construciile matricilor de mai sus necesit un timp de ordinul O(n4). Vom cuta s obinem un timp de executare mai bun, inclusiv pentru cazul n care lungimea arcelor este oarecare (n cele de mai sus s-a presupus implicit c arcele au lungimea egal cu 1).
84
n continuare, fiecare arc <i,j> va avea o etichet et(<i,j>) strict pozitiv, ce reprezint lungimea arcului. et(< i,j >) dac < i,j > M Consider P(i,j)= 0 dac i = j + altfel i irul de matrici:
P0 = P Pk(i,j)= min{Pk1(i,j),Pk1(i,k)+ Pk1(k,j)}, k 1
Propoziia 3. Pn este matricea celor mai scurte drumuri. Vom demonstra prin inducie dup k urmtoarea afirmaie: Pk(i,j) = lungimea celui mai scurt drum de la i la j n care numerele de ordine ale nodurilor intermediare sunt cel mult egale cu k. - pentru k=0: evident. - k-1 k : Considerm un drum de lungime minim de la i la j. Dac drumul nu trece prin k, Pk(i,j)=Pk-1(i,j). Dac drumul trece prin k, el va trece o singur dat prin k (are lungime minim) i n drumurile de lungime minim de la i la k i de la k la j vrful k nu apare ca vrf intermediar, deci Pk(i,j)=Pk-1(i,k)+Pk-1(k,j). i k j Observaii: 1) s-a folosit metoda programrii dinamice; 2) Pk(i,i)=0; 3) Pk(i,k)=Pk-1(i,k) i Pk(k,j)=Pk-1(k,j), deci la trecerea de la Pk-1 la Pk linia k i coloana k rmn neschimbate. Rezult c putem folosi o singur matrice. Algoritmul se simplific astfel:
for k=1,n for i=i,n for j=1,n P(i,j) min {P(i,j),P(i,k)+P(k,j)} Timpul de executare este evident de ordinul O(n3).
85
Dac dorim s determinm doar existena drumurilor i nu lungimea lor minim, vom proceda similar. Considerm irul de matrici:
A 0 = 0 A k(i,j)= A k1(i,j)[A k1(i,k) A k1(k,j)] , k 0
Propoziia 4. An este matricea drumurilor. Demonstrm prin inducie dup k urmtoarea afirmaie: Ak(i,j)=1 drum de la i la j cu numerele de ordine ale vrfurilor intemediare egale cu cel mult k. - pentru k=0: evident; - k-1 k: Dac A(i,j)=1, atunci fie Ak-1(i,j)=1, fie Ak-1(i,k)=Ak-1(k,j)=1; n ambele situaii va exista, conform ipotezei de inducie, un drum de la i la j cu numerele de ordine ale vrfurilor intemediare egale cu cel mult k. Dac exist un drum de la i la j cu numerele de ordine ale vrfurilor intemediare egale cu cel mult k, prin eliminarea ciclurilor drumul va trece cel mult o dat prin k. Este suficient n continuare s considerm cazul n care drumul trece prin vrful k i cazul n care drumul nu trece prin k. Sunt valabile aceleai observaii ca la Propoziia 3, iar algoritmul are o form similar:
DA for k=1,n for i=1,n for j=1,n D(i,j) D(i,j) D(i,k)D(k,j) Timpul de executare este evident de ordinul O(n3).
86
n continuare ne vor interesa numai drumurile ce pleac dintr-un vrf x0 fixat. Este de ateptat ca timpul de executare s scad. Mai precis, cutm d(x) = lungimea drumului minim de la x0 la x, pentru orice vrf x. n plus, dorim s determinm i cte un astfel de drum. Prezentm n continuare algoritmul lui Dijkstra pentru problema enunat. Pentru simplificare, presupunem c orice vrf este accesibil din x0. Pentru regsirea drumurilor vom folosi vectorul tata. Perechiile (d(x),tata(x)) sunt iniializate astfel: - (0,0) pentru x=x0; - (et(<x0,x>),x0) dac <x0,x>M; altfel. - + Fie T = mulimea vrfurilor x pentru care d(x) are valoarea final.
T {x0} while TV Fie xV\T cu d(x) minim T T{x} for toi xT if d(x)>d(x)+et(<x,x>) then d(x) d(x)+et(<x,x>); tata(x)x
Pentru a demonstra corectitudinea algoritmului, vom arta prin inducie dup |T| c: 1) xT : d(x) = lungimea celui mai scurt drum de la x0 la x; 2) xT : d(x) = lungimea celui mai scurt drum de la x0 la x, ce trece numai prin vrfuri din T.
T
x
Pentru |T|=1, concluzia este evident. x0 |T| |T|+1: Fie x' vrful nou adugat. x' Demonstrm cele dou afirmaii de mai sus: 1) d(x) este cel final: Presupunem prin absurd c exist un drum de la x0 la x de lungime mai mic dect d(x). Acest drum trebuie s treac printr-un vrf yT.
x0 yT x
Evident d(y)<d(x). Contradicie, pentru c a fost ales x cu d(x) minim. 2) Conform actualizrilor (*) efectuate de algoritm. 1) 2) Observaii. Timpul de executare este de ordinul O(n2). Pe baza vectorului tata, putem regsi pentru orice xV drumul minim ce l leag de x0 n timp liniar. Regsirea tuturor acestor drumuri necesit un timp de ordinul O(n2), deci complexitatea n timp a algoritmului nu crete. Dac dorim numai drumul minim de la x0 la un vrf x1 dat, ne oprim cnd x=x1; aceasta nu implic ns o reducere a ordinul de mrime al timpului de calcul. Algoritmul de mai sus poate fi ncadrat la metoda Greedy, dar i la metoda irului cresctor de mulimi.
3) 4)
87
unde locul liber mai poate fi considerat drept coninnd plcua imaginar cu eticheta 16. Prezentm nti o condiie de existen a unei succesiuni de mutri prin care se poate trece de la configuraia iniial n cea final. Cele 16 locauri sunt considerate ca fiind ordonate de la stnga la dreapta i de jos n sus. Pentru plcua etichetat cu i definim valoarea n(i) ca fiind numrul locaurilor care urmeaz celei pe care se afl plcua i care conin o plcu a crei etichet este mai mic dect i. De exemplu pentru configuraia iniial de mai sus avem: n(8)=1; n(4)=0; n(16)=10; n(15)=1 etc. Fie l i c linia i coloana pe care apare locul liber. Fie x{0,1} definit astfel: x=0 dac i numai dac l+c este par. Se poate demonstra urmtorul rezultat: Propoziie. Fiind dat o configuraie iniial, putem trece din ea la configuraia final de mai sus n(1)+n(2)+...+n(16)+x este par. n continuare vom presupune c putem trece de la configuraia iniial la cea final. Cum locul liber poate fi mutat spre N, S, E, V (fr a iei din cadru), rezult c fiecare configuraie (stare) are cel mult 4 descendeni. Se observ c arborele astfel construit este infinit. Strile finale sunt stri rezultat i corespund configuraiei finale.
88
Exemplul 2. Circuitul hamiltonian de cost minim. Se consider un graf orientat cu arcele etichetate cu costuri pozitive. Inexistena unui arc ntre dou vrfuri este identificat prin "prezena" sa cu costul +. Presupunem c graful este dat prin matricea C a costurilor sale. Se cere s se determine, dac exist, un circuit hamiltonian de cost minim. S considerm de exemplu graful dat de matricea de costuri: 3 7 2 5 1 9 C = 4 8 3 6 2 6 Arborele spaiului de stri, n care vrfurile corespund vrfurilor din graf, iar muchiile reprezint arce din graf, este urmtorul:
1
(1,2)
2
(1,3)
3
(1,4)
4
(2,3)
5
(2,4)
6
(3,2)
7
(3,4)
8
(4,2)
9
(4,3)
10
(3,4)
11
(4,5)
12
(2,4)
13
(4,2)
14
(2,3)
15
(3,2)
16
subnelegndu-se c se pleac din vrful 1 i c din frunze se revine la acest vrf. Revenim la descrierea metodei Branch and Bound. tim c i metoda backtracking este aplicabil problemelor reprezentabile pe arbori. Exist ns multe deosebiri, dintre care menionm urmtoarele: - ordinea de parcurgere a arborelui; - modul n care sunt eliminai subarborii care nu pot conduce la o soluie; - faptul c arborele poate fi infinit (prin natura sa sau prin faptul c mai multe vrfuri pot corespunde la o aceeai stare). n general arborele de stri este construit dinamic. Este folosit o list L de vrfuri active, adic de stri care sunt susceptibile de a fi dezvoltate pentru a ajunge la soluie (soluii). Iniial, lista L conine rdcina arborelui, care este vrful curent. La fiecare pas, din L alegem un vrf (care nu este neaprat un fiu al vrfului curent!), care devine noul vrf curent.
89
Cnd un vrf activ devine vrf curent, sunt generai toi fiii si, care devin vrfuri active (sunt inclui n L). Apoi din nou este selectat un vrf curent. Legat de modul prin care alegem un vrf activ drept vrf curent, deci implicit legat de modul de parcurgere a arborelui, facem urmtoarele remarci: parcurgerea DF nu este adecvat, deoarece pe de o parte arborele poate fi infinit, iar pe de alt parte soluia cutat poate fi de exemplu un fiu al rdcinii diferit de primul fiu i parcurgerea n adncime ar fi ineficient: se parcurg inutil stri, n loc de a avansa direct spre soluie; parcurgerea pe lime conduce totdeauna la soluie (dac aceasta exist), dar poate fi ineficient dac vrfurile au muli fii. Metoda Branch and Bound ncearc un "compromis" ntre cele dou parcurgeri menionate mai sus, atand vrfurilor active cte un cost pozitiv, ce intenioneaz s fie o msur a gradului de "apropiere" a vrfului de o soluie. Alegerea acestui cost este decisiv pentru a obine un timp de executare ct mai bun i depinde att de problem, dar mai ales de abilitatea programatorului. Observaie. Totdeauna costul unui vrf va fi mai mic dect cel al descendenilor (fiilor). De fiecare dat drept vrf curent este ales cel de cost minim (cel considerat ca fiind cel mai "aproape" de soluie). De aceea L va fi n general un min-ansamblu: costul fiecrui vrf este mai mic dect costul descendenilor. Din analiza teoretic a problemei deducem o valoare lim care este o aproximaie prin adaos a minimului cutat: atunci cnd costul unui vrf depete lim, atunci vrful curent este ignorat: nu este luat n considerare i deci este eliminat ntregul subarbore pentru care este rdcin. Dac nu cunoatem o astfel de valoare lim, o iniializm cu +. Se poate defini o funcie de cost ideal, pentru care c(x) este dat de: nivelul pe care se afl vrful x dac x este vrf rezultat; + dac x este vrf final, diferit de vrf rezultat; min {c(y) | y fiu al lui x } dac x nu este vrf final. Aceast funcie este ideal din dou puncte de vedere: nu poate fi calculat dac arborele este infinit; n plus, chiar dac arborele este finit, el trebuie parcurs n ntregime, ceea ce este exact ce dorim s evitm; dac totui am cunoate aceast funcie, soluia poate fi determinat imediat: plecm din rdcin i coborm mereu spre un vrf cu acelai cost, pn ajungem n vrful rezultat.
Neputnd lucra cu funcia ideal de mai sus, vom alege o aproximaie a lui c, care trebuie s satisfac condiiile: 1) n continuare, dac y este fiu al lui x avem (x)<(y); 2) (x) s poat fi calculat doar pe baza informailor din drumul de la rdcin la x ; 3) este indicat ca c pentru a ne asigura c dac (x)>lim, atunci i c(x)>lim, deci x nu va mai fi dezvoltat. O prim modalitate de a asigura compromisul ntre parcurgerile n adncime i pe lime este de a alege funcia astfel nct, pentru o valoare natural k, s fie ndeplinit condiia: pentru orice vrf x situat pe un nivel nx i orice vrf situat pe un nivel nynx+k, s avem (x)> (y), indiferent dac y este sau nu descendent al lui x.
90
Condiia de mai sus spune c niciodat nu poate deveni activ un vrf aflat pe un nivel nynx+k dac n L apare un vrf situat pe nivelul nx, adic nu putem merge "prea mult" n adncime. Dac aceast condiie este ndeplinit, este valabil urmtoarea propoziie: Propoziie. Dac exist soluie, ea va fi atins ntr-un timp finit, chiar dac arborele este infinit. Putem aplica cele de mai sus pentru jocul Perspico, alegnd: (x) = suma dintre lungimea drumului de la rdcin la x i numrul de plcue care nu sunt la locul lor (aici k=15).
(*)
Observaie. La (*) am inut cont de faptul c dac j este descendent al lui i, atunci
(i)<(j).
Vom aplica algoritmul de mai sus pentru problema circuitului hamiltonian de cost minim, pe exemplul considerat mai sus. Pentru orice vrf x din arborele de stri, valoarea c(x) dat de funcia de cost ideal este: lungimea circuitului corespunztor lui x dac x este frunz min {c(y) | y fiu al lui x } altfel. Fiecrui vrf x i vom ataa o matrice de costuri Mx (numai dac nu este frunz) i valoarea (x). Observaie. Dac micorm toate elementele unei linii sau coloane cu , orice circuit hamiltonian va avea costul micorat cu , deoarece n orice circuit hamiltonian din orice vrf pleac exact un arc i n orice vrf sosete exact un arc. Conform acestei observaii, vom lucra cu matrici de costuri reduse (n care pe orice linie sau coloan apare cel puin un zero, exceptnd cazul cnd linia sau coloana conine numai ).
91
Pentru rdcina rad=1 plecm de la matricea de costuri C. Matricea ataat va fi matricea redus obinut din C, iar (1) = cantitatea cu care s-a redus C. n general, pentru un vrf y oarecare al crui tat este x i muchia (x,y) este etichetat cu (i,j): dac y este vrf terminal, (x) va fi chiar c(y), adic costul real al circuitului; n caz contrar, plecnd de la Mx i (x) procedm astfel: - elementele liniei i devin , deoarece mergem sigur ctre vrful j din graf; - elementele coloanei j devin , deoarece am ajuns sigur n vrful j din graf; - Mx(j,1) , pentru a nu reveni prematur n rdcina 1; - reducem noua matrice Mx i obinem My; fie r cantitatea cu care s-a redus Mx. Vom lua (y) (x)+r+Mx(i,j). Concret, pentru exemplul dat, calculele se desfoar astfel: Pentru rdcin: - reduc liniile n ordine cu 2, 1, 3, 2; - reduc prima coloan cu 1; - n acest mod obin (1)=9, iar matricea M1 este:
1 5 0 3 0 8 M1 = 0 5 0 3 0 4
Acum min9; L={1}. Este extras vrful 1 i sunt considerai fiii si. Pentru vrful 2: - plec de la M1 i pun pe linia 1 i coloana 2; - reduc linia 3 cu 3; - n acest mod obin (2)=3+9+1=13, iar matricea M2 este: Pentru vrful 3: - plec de la M1 i pun pe linia 1 i coloana 3; - reduc linia 2 cu 3; - n acest mod obin (3)=3+9+5=17, iar matricea M3 este: Pentru vrful 4: - plec de la M1 i pun pe linia 1 i coloana 4; - nu este necesar vreo reducere; - n acest mod obin (4)=0+9+0=9, iar matricea M4 este
M2 = 0 0
0 8 0 1
0 5 M3 = 5 0 3 0 3 0 M4 = 0 5 0 4
Acum L={2,3,4} cu (2)=13, (3)=17, (4)=9. Devine activ vrful 4. Pentru vrful 9: - plec de la M4 i pun pe linia 4 i coloana 2; - nu este necesar vreo reducere; - n acest mod obin (9)=0+9+0=9, iar matricea M9 este:
M9 = 0
92
Pentru vrful 10: - plec de la M4 i pun pe linia 4 i coloana 3; - reduc linia 2 cu 3, iar linia 3 cu 5; 0 - n acest mod obin (10)=8+9+4=21, iar matricea M10 este: M10 =
0
Acum L={2,3,9,10} cu (2)=13, (3)=17, (9)=9, (10)=21. Devine activ vrful 9. Singurul su descendent este 15, care este frunz. (15)=c(15)=9 (costul real al circuitului). Sunt eliminate din L vrfurile cu costurile mai mari dect 9, deci L devine vid. min rmne egal cu 9, va fi produs la ieire circuitul cutat (1,4,2,3,1) i algoritmul se oprete.
93
Algoritmi probabiliti
Este vorba de algoritmi n care este posibil continuarea calculelor prin alegerea (aleatoare) a uneia dintre mai multe variante posibile. Pentru aceasta va fi folosit o funcie random, prezent n orice limbaj de progamare. Drept urmare, pentru aceleai date de intrare, la executri diferite vom obine rezultate diferite. Vom ncepe prin prezentarea ctorva probleme specifice, iar apoi vom prezenta o clasificare a algoritmilor probabiliti. Exemplul 1. Acul lui Buffon. Se consider o mulime de linii paralele astfel nct oricare dou linii vecine sunt la distan de o unitate. Un ac (segment) de lungime o jumtate de unitate este aruncat aleator i se numr de cte ori a intersectat vreo linie. Se poate demonstra c probabilitatea ca acul s intersecteze o linie este 1/. Practic, dup un numr "suficient de mare" de ncercri, raportul ntre: - numrul total de ncercri i - numrul cazurilor n care acul a intersectat vreo linie va fi "suficient de aproape" de . Exemplul 2. Fie G un graf neorientat. Vom nelege prin mutare eliminarea unui vrf mpreun cu toi vecinii si. Se cere numrul minim de mutri prin care se pot elimina toate vrfurile. Aceast problem a fost propus la o olimpiad colar. Specificul acestor olimpiade const n faptul c programul este rulat pe 10 exemple i se contabilizeaz numrul de exemple pentru care rezultatul furnizat este corect. Pentru a "aduna" puncte, nu este necesar s ne gndim la o soluie efectiv, ci este suficient s aplicm urmtorul algoritm probabilist: - se alege aleator un vrf i se elimin mpreun cu vecinii si; - se repet pasul anterior pe noul graf pn cnd sunt eliminate toate vrfurile; - se memoreaz numrul de mutri efectuat. Se reia algoritmul (fr a depi timpul maxim fixat pentru un test) i se pstreaz cel mai mic numr de mutri. Dincolo de cadrul n care a fost propus problema, strategia de mai sus este aplicabil atunci cnd se cere doar o aproximaie suficient de bun a rezultatului dorit i/sau trebuie s ne ncadrm ntr-un timp dat. Exemplul 3. Se dau n texte (n foarte mare) cu urmtoarele proprieti: - exist un unic text t0 care apare de cel puin 10% ori; - celelalte texte sunt distincte. Se cere determinarea textului t0. Problema a fost propus la un concurs studenesc A.C.M. Un algoritmul probabilistic eficient este urmtorul:
94
repeat
Probabilitatea ca alegerea unui indice s conduc la textul t0 este 1/10, deci probabilitatea ca s obinem o pereche de indici (i,j) cu ti=tj=t0 este 1/100. Cu alte cuvinte, teoretic sunt suficiente 100 de ncercri, independent de valoarea lui n. Exemplul 4. Problema celor n dame. Am prezentat o rezolvare a acestei probleme folosind metoda backtracking. Implementarea i executarea algoritmului corespunztor arat c se ajunge la o soluie n timp "rezonabil" doar pentru valori mici ale lui n (n20). Un algoritm probabilist pentru aceast problem, care furnizeaz rapid o soluie chiar pentru valori ale lui n mai mari dect 100 este urmtorul: - plasm o dam pe prima linie; - presupunnd c am plasat neantagonist cte o dam pe liniile 1,...,k-1, facem un inventar al poziiilor posibile pentru dama de pe linia k i alegem aleator una dintre ele. Exista dou posibiliti: 1) Am reuit s plasm o dam pe linia n am determinat o soluie, deci o listm i oprim programul; 2) Am ajuns la o linie k i nu exist poziii posibile. Atunci relum ntreg algoritmul (deci nu ne ntoarcem la linia precedent ca la backtracking). Pentru implementarea algoritmului, trebuie inut o eviden a coloanelor i diagonalelor ocupate (pe care nu putem plasa o dam). Pentru aceasta vom folosi vectorii booleeni NV_SE[-n+1..n-1] , NE_SV[2..2n] i C[1..n] ale cror valori ne spun dac o diagonal NV-SE, o diagonal NE-SV sau o coloan sunt libere (nu exist plasat o dam pe diagonala sau coloana respectiv). 0 1 -1 n-1 2 3 n+1 n+2
NV_SE
NE_SV
-n+1
2n
Observaie. Dac sunt pe linia k, pot plasa o dam pe coloana i dac: (k,i) : Ci liber & NV_SEi-k liber & NE_SVi+k liber. n algoritmul care urmeaz, soluia este obinut n vectorul x=(x1,...xn).
95
repeat repeat
inventar al poziiilor i{1,...,n} cu (k,i) pun aceste poziii n primele na componente ale unui vector a if na>0 then aleg aleator i{1,...,na}; i ai
xk i ; NV_SEi-k false; NE_SVi+k false Ci false; k k+1 until na=0 k=n+1 until k=n+1 write(x)
Observaie. Este indicat s ne mulumim s plasm damele pe primele aprox. 90% din linii, iar pentru restul liniilor s folosim backtracking.
96
Algoritmi genetici
Algoritmii genetici constituie o generalizare a algoritmilor probabiliti. Problema general: Fie f:D R. Se caut max{ f(x) | xD }. Presupunem c orice xD poate fi codificat ca: x=(x1,...,xr) cu xi{0,1}, i=1,...,r. x se numete cromozom. Nu vom lucra cu un singur cromozom, ci cu o populaie de n cromozomi, care se transform prin trecerea de la o generaie la alta. Observaie. O populaie este un multiset (un element poate s apar de mai multe ori). Schimbarea de generaie se face prin selecia unei subpopulaii i modificarea acesteia prin operaiile de ncruciare (crossover) i mutaie, ce vor fi descrise mai jos. Algoritmul se ncheie dac s-a efectuat un numr dat de schimbri de configuraii sau dac s-a gsit o aproximare suficient de bun a maximului. Considerm n continuare prima variant. Notaii: P - populaia curent; P={p1,...,pn}, deci n=|P|; r - lungimea cromozomilor; valmax - valoarea maxim curent ; xmax - punctul pentru care este atins valmax; nmax - numrul maxim admis de schimbri de configuraii; pc - probabilitatea de crossover; pm - probabilitatea de mutaie. Se mai folosete o funcie J:X R pentru evaluarea performanelor cromozomilor din P. O modalitate simpl de crossover este: cromozom 1 cromozom 2 adic din doi prini se obin 2 descendeni. O modalitate simpl de mutaie este alegerea aleatoare a unei poziii din cromozom i modificarea ei: 01.
97
Observaii: de obicei pc este de ordinul 10-1 (de exemplu 0.3), iar pm de ordinul 10-3 (de exemplu 0.007). Experienele arat c alegerea lui pc i pm nu are foarte mare importan. Alegerea lor se face i n funcie de lungimea r a cromozomilor; de obicei cel mai performant individ este reinut i pentru viitoarea configuraie; n funcia de evaluare (dar nu numai) pot fi folosite distane ca de exemplu: cea euclidian; Hamming - numrul de poziii pe care cromozomii difer; Levenstein - numrul minim de tergeri + adugri + modificri pentru a trece de la un cromozom la cellalt.
98
Aplicaii
1) Ghicirea unei submulimi (interactiv) O submulime a lui {1,2...,r} poate fi reprezentat ca un vector x{0,1}r cu semnificaia c i face parte din submulime dac i numai dac xi=1. Programul a fost rulat pentru n=r=25; pc=0.3; pm=0.2. J(p) = numrul de poziii pe care p coincide cu p0 cutat. Variante folosite pentru crossover: - clasic; - selectm doi indici p1<p2 i interschimbm poriunile corespunztoare din cromozomi. Ne oprim dac J(p)n-2. 2) Ghicirea unei permutri (interactiv) Programul a fost rulat pentru n=10, r=10, pc=0.3. Funcia J i variantele de crossover sunt aceleai ca n exemplul precedent. Mutaie: determinm aleator j,k{1,...,n} i interschimbm p[j] cu p[k]. 3) Maximul unei funcii. Dorim s determinm max{f(x) | x [0,1]}. Fiecare cromozom se scrie ca x=0,c1...cr cu ci{0,1}, i=1,...r. Programul a fost rulat pentru n=30, r=20, pc=0.25, pm=0.1. Variante folosite pentru crossover: - clasic; - modificm ultimele k poziii aleator; - selectm doi indici p1<p2 i interschimbm poriunile corespunztoare din cromozomi.
99
La selecia subpopulaiei putem folosi o selecie Monte Carlo. n T = J(pi) i=1 n q i = pi ,i=1,...,n q i = 1 T i=1 Pot folosi o rulet: q1 q2
100
Algoritmi nedeterminiti
Este vorba de algoritmi secveniali care, spre deosebire de cei obinuii, admit i urmtoarele instruciuni suplimentare: 1) success i failure, care arat c algoritmul se termin cu succes, respectiv cu eec. Ele nlocuiesc instruciunea stop. Forma lor arat c vom studia doar probleme de decizie, pentru care rezultatul poate fi doar afirmativ sau negativ (dup cum se va vedea, foarte multe probleme pot fi aduse la aceasta form). 2) choice(A), unde A este o mulime finit; este o funcie care ntoarce ca rezultat un element oarecare al lui A. Se consider c cele 3 instruciuni nou introduse necesit un timp constant. Maina abstract care execut un astfel de algoritm, cnd ntlnete o instructiune choice lucreaz astfel: - dac exist o valoare din A care conduce la o instruciune success, va fi aleas o altfel de valoare; - n caz contrar, va fi aleas o valoare oarecare. Cu alte cuvinte, un algoritm nedeterminist se termin cu eec dac nu exist o modalitate de a efectua alegeri care s conduc la o instruciune success. Funcionarea mainii abstracte se aseamn calculului paralel. De cte ori este ntlnit o instruciune choice, intr n aciune attea procesoare cte elemente are mulimea A. Algoritmul se termin cu succes dac unul dintre procesoarele active ajunge la o instruciune success. Timpul de executare va fi, n cazul unei terminri cu succes, timpul necesar ajungerii la respectiva instruciune success . Mai precizm c ne intereseaz doar terminrile cu succes. Exemplul 1. Se cere s se verifice dac un numr x apare sau nu n mulimea {a1,...,an}. tim c timpul de executare pentru algoritmul determinist pentru aceast problem este de ordinul O(n), adic liniar. Putem scrie urmtorul algoritm nedeterminist:
i choice({1,2,...,n}) if ai=x then write i; success else failure
101
Exemplul 2. Se cere s se ordoneze cresctor vectorul a=(a1,...,an). tim c cel mai bun algoritm determinist pentru aceast problem necesit un timp de ordinul O(n.log n). Ideea algoritmului nedeterminist este urmtoarea: copiem elementele vectorului a ntr-un vector auxiliar b ntr-o ordine oarecare, dup care verificm dac elementele lui b apar n ordine cresctoare.
for i=1,n bi
{ iniializarea lui b }
for i=1,n j choice({1,2,...,n}) if bj= then bj ai { fiecare ai trece pe o poziie nou din b } else failure for i=1,n-1 if bi>bi+1 then failure write(b); success
Timpul de executare al acestui algoritm este O(n), deci liniar. Se observ c: am obinut un timp de executare mai bun; problema sortrii a fost tratat ca o problem de decizie.
Exemplul 3. Problema validrii. Fie F(x1,...,xn) o expresie boolean n forma normal conjunctiv (FNC): F=C1...Ck , unde C1,...,Ck sunt disjuncii de variabile de forma xj sau xj' (xj' este negaia lui xj). Se cere s se determine dac exist a1,...,an{0,1} cu F(a1,...,an)=1. Problema va fi referit n continuare sub numele VALID. O instan a problemei este: F(x1,x2,x3)=(x1x2x3)(x1'x2'x3'). Putem scrie urmtorul algoritm nedeterminist simplu care rezolv problema:
for i=1,n xi choice({0,1}) if F(x1,...,xn)=1 then success else failure
Timpul este proporional cu lungimea formulei, deci liniar. Observaie. Nu se cunoate un algoritm determinist polinomial pentru problema validrii ! tim c doar algoritmii polinomiali sunt eficieni, deci scopul este de a elabora algoritmi polinomiali. Este evident c nu exist algoritmi polinomiali pentru orice problem, de exemplu cei pentru care numrul datelor de ieire nu este polinomial: determinarea tuturor submulimilor unei mulimi finite, generarea permutrilor etc. De aceea cutm s delimitm clasa problemelor pentru care ncercm s elaborm algoritmi polinomiali.
102
Introducem clasele de probleme (de decizie) urmtoare: P - clasa problemelor pentru care exist algoritmi determiniti polinomiali; NP - clasa problemelor pentru care exist algoritmi nedeterminiti polinomiali. Vom studia problema existenei algoritmilor (determiniti) polinomiali doar pentru problemele din NP. Evident PNP. Este ns incluziunea strict sau P=NP ? Precizm de la nceput c aceast problem este deschis! n 1976, Samuel Cook a obinut un rezultat care a simplificat problema i a prut promitor n rezolvarea ei: Teorem. P=NP VALIDP Teorema spune c dac reuim s gsim un algoritm determinist polinomial pentru VALID, atunci va exista un algoritm determinist polinomial pentru orice problem din NP. ntruct nu s-a reuit s se obin un algoritm polinomial pentru VALID, s-a ncercat s se rezolve probleme "echivalente" cu VALID. Mai precis s-a definit clasa NPC .
NPC NP P
Clasa de probleme NPC este definit astfel: 1) VALID NPC 2) Pentru o problem P, P NPC dac: 2.1) Se cunoate pentru P un algoritm nedeterminist polinomial; 2.2) VALID se poate reduce la P n timp determinist polinomial. Problemele din NPC se numesc NPcomplete. Observaie. Este suficient s artm pentru o singura problema NPcomplet c admite un algoritm polinomial, pentru a obine egalitatea P=NP. Lista problemelor NPcomplete a depit 1000, dar pentru nici una dintre ele nu s-a reuit s se obin un algoritm determinist polinomial. Drept urmare, aa cum am menionat anterior, problema egalitii P=NP rmne o problema deschis. Prezentm n continuare una dintre multele probleme NPcomplete.
103
Problema clicii maximale. Fie G=(X,M) un graf neorientat. YX se numete clic dac pentru i,jY avem (i,j)M. Se caut ordinul unei clici maximale. Problema de decizie corespunztoare este urmtoarea: Pentru un k dat, exist n G o clica de ordin k ? Pentru aceast problem vom scrie procedura clica(G,k), care furnizeaz rspunsul n timp nedeterminist polinomial. Presupunnd cunoscut acest lucru, algoritmul pentru problema clicii maximale va fi:
for k=n,1,-1 clica(G,k)
Artm n continuare c VALID se reduce la CLICA n timp determinist polinomial. Fie F(x1,...,xn) o expresie boolean n forma normal conjunctiv (FNC): F=C1...Ck , unde C1,...,Ck sunt disjuncii de variabile de forma xj sau xj', numii literali. Atam lui F graful G=(X,M) astfel: X = {(,i) | literal din Ci} M = {[(,i),(,j)] | '}, adic i pot fi satisfcui concomitent. De exemplu, pentru F(x1,x2,x3)=(x1x2x3)(x1'x2'x3'), graful este:
(x1,1) (x2,1) (x3,1) (x1',2) (x2',2) (x3',2)
104
Mai trebuie demonstrat c: n G exist o clic de ordin k F este validabil. ncepem cu demonstrarea necesitii. Fie S = {(i,i) | i=1,...k} o clic de ordin k. Fie S1 = {i | i=1,...,k}. Conform construciei lui M, nu este posibil ca i,i' s apar simultan n S1. Alegem fiecare ai astfel: 1 dac xiS1, adic i=xi 0 dac xi'S1 arbitrar n caz contrar. Pentru aceasta alegere, F(a1,...,an)=1, fiecare conjuncie avnd valoarea 1. Pentru exemplul considerat: S={(x1,1),(x3',2)} ; S1={x1,x3'} ; a1=1 ; a2 arbitrar ; a3=0. Continum cu demonstrarea suficienei. Conform ipotezei, exist a1,...,an cu Ci(a1,...,an)=1, i=1,...,k. Pentru fiecare i=1,...,k, exist un literal i din Ci care are valoarea 1. Fie S = {(i,i) | i=1,...,k}. S este o clic. ntr-adevr, pentru (i,i),(j,j)S diferite rezult ij i ij deoarece pentru a=(a1,...,an) avem i=j=1. ncheiem prin a prezenta enunul altor probleme NP-complete. 1) Fie G=(X,M) un graf. YX se numete k-acoperire dac |Y|=k i pentru orice (i,j)M avem iY sau iY. Exist o k-acoperire? 2) Problema ciclului hamiltonian. 3) Problema colorrii hrilor. 4) Fie A o mulime i fie s:AZ+. Caut BA cu s(A\B)=s(B).
105
10
10.1. Algoritmi probabiliti
n multe probleme, n timpul rezolvrii, putem ajunge la un moment dat n situaia de a avea de ales ntre mai multe variante de continuare. Am vzut c n aceast situaie putem analiza pe rnd variantele, putem ncerca s determinm varianta optim i s o urmm etc. Algoritmii probabiliti adopt o alt abordare: se alege aleator una dintre variante. Vom vedea c n unele situaii aceast abordare poate conduce la determinarea mai rapid a unei soluii (exacte sau aproximative). Pentru alegerea aleatoare se presupune c avem la dispoziie o funcie random, care ntoarce o valoare aleatoare dintr-un interval [a,b) de numere reale sau dintr-o secven a..b de numere ntregi consecutive. Este evident c la executri diferite ale unui algoritm probabilist, rezultatele sunt n general diferite. Exist trei categorii mari de algoritmi probabiliti, pe care le prezentm n continuare. Algoritmi numerici
Algoritmii de acest tip sunt caracterizai prin urmtoarele: - urmresc determinarea aproximativ a unei valori; - cu ct timpul alocat executrii algoritmului este mai mare, precizia rezultatului se mbuntete. Exemplul 1. Acul lui Buffon. Se consider o mulime de linii paralele astfel nct oricare dou linii vecine sunt la distan de o unitate. Un ac (segment) de lungime o jumtate de unitate este aruncat aleator i se numr de cte ori a intersectat vreo linie. Se poate demonstra c probabilitatea ca acul s intersecteze o linie este 1/. Practic, dup un numr "suficient de mare" de ncercri, raportul ntre: - numrul total de ncercri i - numrul cazurilor n care acul a intersectat vreo linie va fi "suficient de aproape" de .
106
94
Exemplul 2. Se arunc repetat cu o sgeat ntr-un panou ptrat. Se presupune c sgeata nimerete totdeauna panoul. Atunci raportul dintre: - numrul cazurilor n care sgeata nimerete n cercul nscris n ptrat i - numrul total de ncercri tinde la /4, numr egal cu raportul dintre aria cercului nscris n ptrat i aria ptratului. Exemplul 3. Dat fiind o funcie f:[a,b][c,d], se cere determinarea aproximativ a valorii I= f x)dx . (
a b
Algoritmii de acest tip urmresc determinarea unei soluii exacte i sunt caracterizai prin urmtoarele: - furnizeaz totdeauna un rezultat, care ns nu este neaprat corect; probabilitatea ca rezultatul s fie corect crete pe msur ce timpul disponibil crete. Exemplul 4. Se consider vectorul x=(x1,...,xn) cu elemente distincte. Se cere determinarea unui element al vectorului care s fie mai mare sau egal cu media aritmetic a celor n numere. Problema are sens dac valoarea lui n este foarte mare, iar timpul avut la dispoziie este mic (n caz contrar alegem, n timp liniar, cel mai mare element al vectorului, care satisface evident condiia dat). Un algoritm probabilist, de tipul Monte Carlo, este urmtorul: alegem aleator un element al vectorului i repetm aceast operaie fr a depi timpul disponibil, pstrnd ntr-o variabil v cel mai mare dintre elementele alese. Rezultatul ntors va fi v. S presupunem c n timpul disponibil am analizat k elemente ale vectorului; v este cel mai mare dintre ele. Valoarea v nu ndeplinete condiia din enun numai n cazul cnd toate cele k elemente alese sunt mai mici dect media
107
95
aritmetic a elementelor lui x. Cum probabilitatea ca un element s fie mai mic dect media aritmetic este 1/2, probabilitatea ca toate elementele (deci i v) s fie mai mici ca media aritmetic este
1 2k
ntoars de algoritm s fie corect este 1 probabilitate este mai mare dect 0,999999.
Exemplul 5. Fie G un graf neorientat. Vom nelege prin mutare eliminarea unui vrf mpreun cu toi vecinii si. Se cere numrul minim de mutri prin care se pot elimina toate vrfurile. Aceast problem a fost propus la o olimpiad colar pe timpul cnd programul era rulat pe 10 exemple i se contabiliza numrul de exemple pentru care rezultatul furnizat este corect. Pentru a "aduna" puncte, nu este necesar s ne gndim la o soluie efectiv, ci este suficient s aplicm urmtorul algoritm probabilist: - se alege aleator un vrf i se elimin mpreun cu vecinii si; - se repet pasul anterior pe noul graf pn cnd sunt eliminate toate vrfurile; - se memoreaz numrul de mutri efectuat. Se reia algoritmul (fr a depi timpul maxim fixat pentru un test) i se pstreaz cel mai mic numr de mutri. Algoritmi Las Vegas
Algoritmii de acest tip urmresc, ca i algoritmii Monte Carlo, determinarea unei soluii exacte i sunt caracterizai prin urmtoarele: - nu furnizeaz totdeauna un rezultat, dar dac furnizeaz un rezultat atunci acesta este corect; probabilitatea ca rezultatul s fie corect crete pe msur ce timpul disponibil crete. Exemplul 5. Se dau n texte (n foarte mare) cu urmtoarele proprieti: - exist un unic text t0 care apare de cel puin 10% ori; - celelalte texte sunt distincte. Se cere determinarea textului t0. Problema a fost propus la un concurs studenesc A.C.M. Un algoritm probabilist eficient este urmtorul:
108
96
repeat i random(1..n); j random(1..n); if ij & ti=tj then write ti; stop until false
Probabilitatea ca alegerea unui indice s conduc la textul t0 este 1/10, deci probabilitatea ca s obinem o pereche de indici (i,j) cu ti=tj=t0 este 1/100. Cu alte cuvinte, teoretic sunt suficiente 100 de ncercri, independent de valoarea lui n. Pe de alt parte este posibil ca algoritmul s nu produc vreun rezultat ntr-un interval limitat de timp. Exemplul 6. Problema celor n dame. Am prezentat o rezolvare a acestei probleme folosind metoda backtracking. Implementarea i executarea algoritmului corespunztor arat c se ajunge la o soluie n timp "rezonabil" doar pentru valori mici ale lui n (n20). Un algoritm probabilist pentru aceast problem, care furnizeaz rapid o soluie chiar pentru valori ale lui n mai mari dect 100 este urmtorul: - plasm o dam pe prima linie; - presupunnd c am plasat neantagonist cte o dam pe liniile 1,...,k-1, facem un inventar al poziiilor posibile pentru dama de pe linia k i alegem aleator una dintre ele. Exista dou posibiliti: 1) Am reuit s plasm o dam pe linia n. Atunci am determinat o soluie, deci o listm i oprim programul; 2) Am ajuns la o linie k i nu exist poziii posibile. Atunci relum ntreg algoritmul (deci nu ne ntoarcem la linia precedent ca la backtracking). Pentru implementarea algoritmului, trebuie inut o eviden a coloanelor i diagonalelor ocupate (pe care nu putem plasa o dam). Pentru aceasta vom folosi vectorii booleeni NV_SE[-n+1..n-1] , NE_SV[2..2n] i C[1..n] ale cror valori ne spun dac o diagonal NV-SE, o diagonal NE-SV sau o coloan sunt libere (nu exist plasat o dam pe diagonala sau coloana respectiv).
109
97
0 1 -1
n-1
2 3
n+1 n+2
NV_SE
NE_SV
-n+1
2n
Observaie. Dac suntem pe linia k, putem plasa o dam pe coloana i dac este ndeplinit condiia: (k,i) : Ci liber & NV_SEi-k liber & NE_SVi+k liber. n algoritmul care urmeaz, soluia este obinut n vectorul
x=(x1,...xn). repeat repeat
facem inventarul poziiilor i{1,...,n} cu (k,i) plasm aceste poziii n primele na componente ale unui vector a
if na>0 then aleg aleator i{1,...,na}; i ai xk i ; NV_SEi-k false; NE_SVi+k false Ci false; k k+1 until na=0 k=n+1 until k=n+1 write(x)
110
98
Problema general: Fie f:D R. Se caut max{ f(x) | xD }. Presupunem c mulimea D poate fi pus n coresponden biunivoc cu o mulime C {0,1}r, adic orice element al lui D poate fi codificat ca: x=(x1,...,xr) cu xi{0,1}, i=1,...,r. Vectorul x se numete cromozom. Nu vom lucra cu un singur cromozom, ci cu o populaie de n cromozomi (indivizi), care se transform prin trecerea de la o generaie la alta. Observm deci c spre deosebire de algoritmii iterativi uzuali de optimizare, n care la fiecare etap se trece de la un element din C la urmtorul, n algoritmii genetici la fiecare etap se trece de la o submulime a lui C la o alt submulime a lui C. O alt trstur a algoritmilor genetici este c la trecerea de la o populaie la urmtoarea, cromozomii se combin ntre ei. Observaie. O populaie este un multiset (un element poate s apar de mai multe ori). Schimbarea de generaie se face prin selecia din populaia curent a unei subpopulaii i modificarea acesteia prin operaiile de ncruciare (crossover) i mutaie, ce vor fi descrise mai jos. Toate populaiile succesive au acelai numr de indivizi. Algoritmul se ncheie dac s-a efectuat un numr dat de schimbri de configuraii sau dac dup un numr de schimbri de generaie maximul curent rmne neschimbat. Considerm n continuare prima variant. Notaii: P - populaia curent; P={p1,...,pn} valmax - valoarea maxim curent ; xmax - punctul pentru care este atins valmax; nmax - numrul maxim admis de schimbri de configuraii; pc - probabilitatea de ncruciare; pm - probabilitatea de mutaie; r - lungimea cromozomilor; n=|P|. Se mai folosete o funcie J:C R pentru evaluarea performanelor cromozomilor din P. n general J este corespondenta funciei f:D R. Algoritmul general este urmtorul:
P aleator; valmax - for t=1,nmax se calculeaz valorile J(p), pP i se actualizeaz eventual valorile xmax i valmax
111
99
Scopul principal al celor trei etape este de a ne apropia ct mai mult de maxim, dar i de a acoperi prin cutri ntregul domeniu de definiie, pentru a obine maximul general i nu unul local. Etapa de selecie urmrete pstrarea (chiar multipl) a celor mai performani indivizi (cromozomi) ai populaiei curente, dar incluznd i factorul aleator. Pentru selecie se folosete de obicei algoritmul Monte Carlo descris n continuare.
J xi) ( probabilitatea de S i =1 selecie a cromozomului pi. Deci s1+...+sn=1. Mai considerm valoarea s0=0. De n ori procedm astfel: - generm un numr aleator x n intervalul [0,1); - este selectat acel cromozom pi pentru care s0+...+si-1x<s0+...+si. Cei n cromozomi selectai vor constitui noua populaie dup etapa de selecie. Se
observ c un cromozom poate fi selectat de mai multe ori i de aceea o populaie este un multiset. Algoritmul de mai sus mai este numit i algoritmul ruletei, deoarece lucrurile se petrec exact ca la o rulet mprit n sectoare corespunztoare cromozomilor, de mrimi proporionale cu valorile s1,...,sn ; la fiecare rotire (aleatoare) a ruletei se ajunge n dreptul unui cromozom. Etapa de ncruciare (crossover ) const n selectarea unei subpopulaii a populaiei curente i recombinarea a cte doi indivizi din subpopulaie. Este folosit un parametru pc[0,1) numit probabilitatea de ncruciare. Prin recombinarea (ncruciarea) a doi cromozomi se obin doi descendeni ce au caracteristici ale ambilor prini. O modalitate simpl de ncruciare a doi cromozomi este cea cu un punct de tietur, n care din "prinii" : x=(x1,...,xk,xk+1,...,xr) i y=(y1,...,yk,yk+1,...,yr) se obin "descendenii": x'=(y1,...,yk,xk+1,...,xr) i y'=(x1,...,xk,yk+1,...,yr) unde k este ales aleator din secvena 1..r-1. n etapa de ncruciare extragem mai nti o subpopulaie Q a lui P, apoi ncrucim cte doi indivizi din Q, dup care adugm noul Q lui P:
Q for i=1,n x random([0,1)) if x<pc then P P \ {pi}; Q Q {pi}
112
100
alegem aleator perechi de elemente din Q i le ncrucim (dac Q nu este par, un element rmne neschimbat)
P P Q
Exist o mare varietate de modaliti de ncruciare a doi indivizi. Ne mrginim la cele ce folosesc puncte de tietur. Considerm cromozomii:
iar dac vom folosi trei puncte de tietur vom obine descendenii:
cu precizarea c punctele de tietur sunt alese aleator. Etapa de mutaie const n selectarea unei subpopulaii a populaiei curente i aplicarea unor mici perturbri cromozomilor din aceast subpopulaie. Este folosit un parametru pm[0,1) numit probabilitatea de mutaie. O modalitate simpl de mutaie a unui cromozom const n alegerea aleatoare a unei poziii din cromozom i modificarea ei: 01. n etapa de mutaie extragem mai nti o subpopulaie Q a lui P, apoi aplicm mutaia fiecrui cromozom din Q, dup care adugm noul Q lui P:
Q for i=1,n x random([0,1)) if k<pc then P P \ {pi}; Q Q {pi} pentru fiecare qQ aplicm operatorul de mutaie P P Q
Observaii: - de obicei pc este de ordinul 10-1 (de exemplu 0.3), iar pm de ordinul 10-3 (de exemplu 0.007). Experienele arat c alegerea lui pc i pm nu are foarte mare importan. Alegerea lor se face i n funcie de lungimea r a cromozomilor;
113
101
- de regul, n etapa de selecie, cel mai performant individ este reinut i pentru viitoarea configuraie; - n funcia de evaluare (dar nu numai) pot fi folosite distane ca de exemplu: cea euclidian; Hamming - numrul de poziii pe care cromozomii difer; Levenstein - numrul minim de tergeri + adugri + modificri pentru a trece de la un cromozom la cellalt. Aplicaii
1) Ghicirea interactiv a unei submulimi p0 a lui {1,2...,r}. Interactivitatea const n faptul c pentru fiecare submulime p "calculatorul" ntoarce numrul de poziii pe care p i p0 coincid. O submulime a lui {1,2...,r} poate fi reprezentat ca un vector x{0,1}r cu semnificaia c i face parte din submulime dac i numai dac xi=1. Programul a fost rulat pentru n=r=25, pc=0.3, pn=0.2. J(p) = numrul de poziii pe care p coincide cu p0 cutat. Variante folosite pentru ncruciare: - cea clasic; - selectm doi indici p1<p2 i interschimbm poriunile corespunztoare din cromozomi. Ne oprim dac J(p)n-2. Determinarea soluiei exacte poate fi realizat apoi ncercnd variantele posibile. 1) Ghicirea unei permutri (interactiv) Programul a fost rulat pentru n=10, r=10, pc=0.3. Funcia J i variantele de crossover sunt aceleai ca n exemplul precedent. Mutaia aplicat unui cromozom p const n a determina aleator indicii j,k{1,...,n} i a interschimba p[j] cu p[k]. 2) Maximul unei funcii. Dorim s determinm valoarea x0 care maximizeaz funcia f:[a,b]R. Pentru x0 sunt cerute k cifre zecimale. Fie r cel mai mic numr natural cu (b-a)10k2r. Atunci vom lucra cu cromozomi c=(c1,c2,...,cr) de lungime r, unde fiecare ci{0,1}. Valoarea x[a,b] corespunztoare lui c se calculeaz astfel: - fie x' numrul zecimal egal cu (crcr-1...c2c1)2;
114
102
x=a+
b a
2r 1
x'.
Programul a fost rulat pentru n=30, r=20, pc=0.25, pm=0.1. Variante folosite pentru crossover: - cea clasic; - modificm ultimele k poziii aleator; - selectm doi indici p1<p2 i interschimbm poriunile corespunztoare secvenei de indici p1..p2.
115
103
2) n caz contrar, este clar c s1,...,sn{1,2,...,n-1}. Conform principiului lui Dirichlet, vor exista indicii k<l cu sk=sl. Atunci o soluie este (i,j)=(k+1,l). Exemplul 3. Pentru un numr natural n dat, se caut un multiplu N al su n a crei scriere obinuit (n baza 10) apar numai cifrele 0 i 1. Problema este asemntoare celei precedente. Pentru k=1,...,n considerm numrul sk a crui scriere n baza 10 este format din k de 1, adic s1=1, s2=11 etc.; fie sk clasa de echivalen modulo n corespunztoare. La fel ca mai sus, deosebim dou situaii: 1) dac exist k cu sk=0, atunci o soluie este chiar sk; 2) n caz contrar, este clar c s1,...,sn{1,2,...,n-1}. Conform principiului lui Dirichlet, vor exista indicii k<l cu sk=sl. Atunci o soluie este sl-sk, adic numrul n a crei scriere apar l-k de 1 urmai de k de 0. Exemplul 4. Teorema lui Erds. Se dau (m-1)(n-1)+1 numere naturale oarecare. S se arate c printre ele exist m care se divid unul pe altul, sau exist n care nu se divid ntre ele. Vom aplica tot principiul lui Dirichlet, dar n plan. Se ordoneaz mai nti cresctor numerele date. Considerm un caroiaj cu n-1 linii i m-1 coloane. Vom presupune c exist i linia imaginar cu numrul de ordine 0, pe care este plasat numrul 1. Considerm pe rnd numerele (n ordine cresctoare). Fiecare numr va fi plasat pe linia i cu i minim i cu proprietatea c numrul curent se divide cu un numr aflat pe linia anterioar. Pentru exemplificare, s considerm c m=5, n=4, iar numerele sunt: 3,5,9,12,14,15,33,x,... Dup plasarea primelor 8 numere, suntem n situaia: 3 9 24 5 12 14 15 33
Dac de exemplu x=72, atunci el ar trebui plasat pe a patra linie (inexistent) i am obine n=4 numere care se divid unul pe altul (de exemplu 3, 12, 24, 72).
116
104
Dac de exemplu x=35, atunci el ar trebui plasat pe a doua linie i am obine m=5 numere care nu se divid ntre ele: 9, 12, 15, 33, 35. Este important de notat c pe fiecare linie numerele plasate apar n ordine cresctoare. Principiul lui Dirichlet ne asigur c cel mai trziu dup plasarea ultimului numr vom iei din "cutie": aici caroiajul (n-1)(m-1). O variant a principiului lui Dirichlet este urmtoarea: Fie k1,...kn , K=(k1+...+kn)/n i x un numr oarecare. Atunci: dac x<K i cu x<ki dac x>K i cu x>ki Exemplul 5. Dac vrfurile unui decagon sunt etichetate distinct cu numerele 0,1,...,9 ntr-o ordine oarecare, atunci exist trei vrfuri consecutive pentru care suma etichetelor este mai mare dect 13. Fie xi{0,1,...,9} eticheta vrfului i, pentru i=1,...,10. Considerm numerele:
k1=x1+x2+x3 k2=x2+x3+x4
. . . . .
k9=x9+x10+x1 k10=x10+x1+x2
Atunci K=(k1+...+kn)/n = 3(0+1+...+9)/10 = 13,5. Conform principiului lui Dirichlet, va exista i cu ki>13,5>13. Exemplul 6. Se consider m calculatoare i n imprimante (m>n). Fie =numrul minim de legturi calculatorimprimant ce trebuie stabilite, astfel nct dac orice n calculatoare doresc s scrie simultan, acest lucru s fie este posibil. Se cere s se arate c n(m-n+1). Fie ki numrul de calculatoare legate la imprimanta i. Numrul de legturi este deci =k1+...+kn. Dac <n(m-n+1), atunci (k1+...+kn)/n<m-n+1. Conform variantei principiului lui Dirichlet, exist o imprimant i legat la cel mult m-n calculatoare, deci care nu este legat la cel puin n calculatoare; dac acestea vor s scrie simultan, nu vor reui. Contradicie. Exerciiu. S se descrie o modalitate prin care problema poate fi rezolvat cu exact n(m-n+1) legturi.
117
Primul pas n rezolvarea problemelor enunate const n construcia arborelui binar ataat expresiei. Exemplu. Expresiei aritmetice ((a-b)*(a+c))/(d-(e+f)) i corespunde arborele:
1 2 3
10
11
12
13
unde etichetele vrfurilor sunt et=(/,*,-,-,+,d,+,a,b,a,c,e,f). Am vzut c acest arbore binar poate fi construit pe baza algoritmului de analiz sintactic. Vom prezenta ns i un program Java care construiete direct acest arbore; la intrare poate s apar orice expresie aritmetic (cu condiia s fie corect).
118
Prezentm programul Java pentru construirea arborelui ataat expresiei i evaluarea expresiei:
class Expresie { public static void main(String[] s) { varf Ob = new varf(); varf.rad = Ob.expresie(); IO.writeln("Val. expresiei: " + Ob.valoare(varf.rad)); } } class varf { static varf rad; static char last,ch; static double[] val; char et; varf st,dr; varf() { IO.write("Ultima litera: "); last = IO.readch(); val = new double[last-'a'+1]; for (char c='a'; c<=last; c++) { IO.write(c + "= "); val[c-'a'] = IO.read(); } ch = IO.readch(); }
119
varf(char e) {et =e; } varf factor() { varf x; if ((ch>='a') && (ch<=last)) { x=new varf(ch); ch = IO.readch(); } else { ch = IO.readch(); x = expresie(); ch = IO.readch(); } return x; } varf termen() { varf x,y; x = factor(); while ( (ch=='*') || (ch=='/') ) { y = new varf(ch); y.st = x; ch = IO.readch(); y.dr = factor(); x=y; } return x; } varf expresie() { varf x,y; x = termen(); while ( (ch=='+') || (ch=='-') ) { y = new varf(ch); y.st = x; ch = IO.readch(); y.dr = termen(); x=y; } return x; } double valoare(varf x) { if (x.st == null) return val[x.et - 'a']; else if (x.et == '+') return valoare(x.st) else if (x.et == '-') return valoare(x.st) else if (x.et == '*') return valoare(x.st) else if (x.et == '/') return valoare(x.st) else return 9999; } }
+ * /
120
Observm c toate instruciunile au dou cmpuri. Se consider c toate aceste instruciuni necesit acelai timp de executare. Nu vom lua n considerare nici una dintre proprietile algebrice ale operaiilor (comutativitate, asociativitate, distributivitate), nici posibilitatea reducerilor unor termeni i nici eventuala apariie a unor subexpresii care se repet. Dorim s construi un un cod (o succesiune de instruciuni de tipurile de mai sus), a crui executare s aduc n registrul r valoarea expresiei aritmetice. Dorim de asemenea ca acest cod s fie optim, adic s fie format dintr-un numr minim de instruciuni. Observaie. n orice cod neredundant: 1) numrul de instruciuni de tipul 3) este egal cu numrul vrfurilor interne, deci cu numrul operatorilor ce apar n expresie; prin urmare acest numr este fix; 2) orice instruciune LOAD, afar de prima, este precedat imediat de o instruciune STORE. Codul ncepe cu o instruciune LOAD. Cu alte cuvinte, numrul instruciunilor LOAD este mai mare cu o unitate dect cel al instruciunilor STORE: #(LOAD)=#(STORE)+1. De aceea un cod optim este un cod cu un numr minim de instruciuni STORE . Este evident c pentru construirea codului, arborele trebuie parcurs n postordine. Atam fiecrui vrf x codul Cx n care n prima instruciune lipsete cmpul
LOAD.
Vrfurilor terminale (frunzelor) x le atam codul: _ et(x). Pentru vrfurile neterminale x vom folosi codurile ataate subarborelui stng st i subarborelui drept dr. Deosebim urmtoarele cazuri: 1) dac descendentul drept al lui x este o frunz, codul Cx va fi:
Cst et(x) Cdr
2) dac descendentul drept al lui x nu este o frunz, se observ c este mai convenabil (din punctul de vedere al numrului de instruciuni) s ncepem cu evaluarea subarborelui drept:
Cdr STORE T LOAD Cst et(x) T unde T este o variabil suplimentar.
121
Variabilele suplimentare vor fi notate cu Tk, unde k este iniial 0 i crete cu o unitate la fiecare construcie a unui nou cod pentru un vrf al crui descendent drept nu este o frunz. Pentru exemplul considerat mai sus obinem succesiv:
C8: C9: C4: C10: C11: C5: C2: + + STORE LOAD * a b a b a c a c a c T1 a b T1 d e C1: C13: C7: C3: + + STORE LOAD + STORE LOAD STORE LOAD STORE LOAD * / f e f e f T2 d T2 e f T2 d T2 T3 a T1 a b T1 T3
C6: C12:
n programul Java prezentat mai sus efectum urmtorele modificri: n metoda principal adugm instruciunea:
IO.writeln("Codul atasat:\n" + "LOAD\t" + Ob.cod(varf.rad)); - n clasa varf adaugm cmpul static k pentru evidena variabilelor suplimentare: static int k; precum i metoda cod care produce codul ataat expresiei, conform parcurgerii n
postordine:
String cod(varf x) { int k1; if (x.dr==null) return x.et + ""; if (x.dr.dr==null) return cod(x.st) +"\n" + x.et + "\t" +cod(x.dr); k1=k; k++; return cod(x.dr) + "\nSTORE\tT" + k+ "\nLOAD\t" + cod(x.st) + "\n" + x.et + "\tT" + k;
122
Mai rmne de demonstrat optimalitatea codului obinut n modul descris mai sus. Propoziie. n orice cod ataat unui arbore binar, #(STORE) este cel puin egal cu numrul vrfurilor al cror descendent drept nu este frunz. Vom face demonstraia prin inducie dup numrul n al vrfurilor din arbore. Pentru n=1, afirmaia este evident adevrat. Presupunem afirmaia din enunul propoziiei adevrat pentru orice arbore cu cel mult n vrfuri i considern un arbore cu n+1 vrfuri. Dac descendentul drept al rdcinii este o frunz, atunci numrul vrfurilor al cror descendent drept nu este frunz coincide cu numrul vrfurilor din subarborele stng al cror descendent drept nu este frunz; cum acest subarbore are cel mult n vrfuri, putem aplica ipoteza de inducie. Dac descendentul drept al rdcinii nu este o frunz, atunci fie: n= numrul vrfurilor din arbore al cror descendent drept nu este frunz; s= numrul de instruciuni STORE din codul ataat arborelui; n1= numrul vrfurilor din subarborele stng al cror descendent drept nu este frunz; s1= numrul de instruciuni STORE din codul ataat subarborelui stng; n2= numrul vrfurilor din subarborele drept al cror descendent drept nu este frunz; s2= numrul de instruciuni STORE din codul ataat subarborelui drept. Conform ipotezei de inducie avem: s1n1 i s2n2. Evident, n=n1+n2+1. Pe de alt parte n codul ataat arborelui mai trebuie introdus cel puin o instruciune STORE, deci ss1+s2+1. Rezult sn1+n2+1=n. Optimalitatea algoritmului prezentat rezult acum din faptul c el introduce o nou instruciune STORE numai pentru vrfurile al cror descendent drept nu este o frunz.
123
12
NP-COMPLETITUDINE
tim c doar algoritmii polinomiali sunt eficieni, deci urmrim totdeauna s elaborm astfel de algoritmi. Este evident ns c nu exist algoritmi polinomiali pentru orice problem, ca de exemplu n cazul n care numrul datelor de ieire nu este polinomial: determinarea tuturor submulimilor unei mulimi finite, generarea permutrilor etc. De aceea cutm s delimitm clasa problemelor pentru care ncercm s elaborm algoritmi polinomiali. n acest scop introducem noiunea de algoritm nedeterminist. Algoritmii nedeterminiti sunt algoritmi secveniali care, spre deosebire de cei obinuii, admit i urmtoarele instruciuni suplimentare: 1) success i failure, care arat c algoritmul se termin cu succes, respectiv cu eec. Ele nlocuiesc instruciunea stop. Forma lor arat c vom studia doar probleme de decizie, pentru care rezultatul poate fi doar afirmativ sau negativ (dup cum vom vedea, foarte multe probleme pot fi aduse la aceasta form). 2) choice(A), unde A este o mulime finit; este o funcie care ntoarce ca rezultat un element oarecare al lui A. Se consider c cele trei instruciuni nou introduse necesit un timp constant. Maina abstract care execut un astfel de algoritm, cnd ntlnete o instruciune choice lucreaz astfel: - dac exist o valoare din A care conduce la o instruciune success, va fi aleas o altfel de valoare; - n caz contrar, va fi aleas o valoare oarecare. Cu alte cuvinte, un algoritm nedeterminist se termin cu eec dac nu exist o modalitate de a efectua alegeri care s conduc la o instruciune success. Funcionarea mainii abstracte se aseamn calculului paralel. De cte ori este ntlnit o instruciune choice, intr n aciune attea procesoare cte
124
114
12. NP-COMPLETITUDINE
elemente are mulimea A. Algoritmul se termin cu succes dac unul dintre procesoarele active ajunge la o instruciune success. Timpul de executare va fi, n cazul unei terminri cu succes, timpul necesar ajungerii la respectiva instruciune success. Mai precizm c ne intereseaz doar terminrile cu succes. Exemplul 1. Se cere s se verifice dac un numr x apare sau nu n mulimea {a1,...,an}. tim c timpul de executare pentru algoritmul determinist pentru aceast problem este de ordinul O(n), adic liniar. Putem scrie urmtorul algoritm nedeterminist:
i choice({1,2,...,n}) if ai=x then write i; success else failure
care rezolv problema n timp constant. Exemplul 2. Se cere s se ordoneze cresctor vectorul a=(a1,...,an). tim c cel mai bun algoritm determinist pentru aceast problem necesit un timp de ordinul O(n.log n). Ideea algoritmului nedeterminist este urmtoarea: copiem elementele vectorului a ntr-un vector auxiliar b ntr-o ordine oarecare, dup care verificm dac elementele lui b apar n ordine cresctoare.
for i=1,n bi
{ iniializarea lui b }
for i=1,n j choice({1,2,...,n}) if bj= then bj ai { fiecare ai trece pe o poziie nou din b } else failure for i=1,n-1 if bi>bi+1 then failure write(b); success
Timpul de executare al acestui algoritm este O(n), deci liniar. Se observ c: - am obinut un timp de executare mai bun; - problema sortrii a fost tratat ca o problem de decizie.
125
12. NP-COMPLETITUDINE
115
Exemplul 3. Problema validrii. Fie F(x1,...,xn) o expresie boolean n forma normal conjunctiv (FNC): F=C1...Ck , unde C1,...,Ck sunt disjuncii de variabile de forma xj sau xj ( xj este negaia lui xj). Se cere s se determine dac exist
a1,...,an{0,1} cu F(a1,...,an)=1.
Problema va fi referit n continuare sub numele VALID. Un exemplu de instan a problemei este: F(x1,x2,x3)=(x1x2x3)( x1 x 2 x 3 ). Un algoritm nedeterminist simplu care rezolv problema este urmtorul:
for i=1,n xi choice({0,1}) if F(x1,...,xn)=1 then success else failure
Timpul este proporional cu lungimea formulei, deci liniar. Observaie. Nu se cunoate un algoritm determinist polinomial pentru problema validrii ! Introducem clasele de probleme (de decizie) urmtoare: P - clasa problemelor pentru care exist algoritmi determiniti polinomiali; NP - clasa problemelor pentru care exist algoritmi nedeterminiti polinomiali. Vom studia problema existenei algoritmilor (determiniti) polinomiali doar pentru problemele din NP. Evident PNP. Este ns incluziunea strict sau P=NP ? Precizm de la nceput c aceast problem este deschis! n 1976, Samuel Cook a obinut un rezultat care a simplificat problema i care a prut promitor n rezolvarea ei: Teorem. P=NP VALIDP. Teorema spune c dac reuim s gsim un algoritm determinist polinomial pentru VALID, atunci va exista un algoritm determinist polinomial pentru orice problem din NP.
126
116
12. NP-COMPLETITUDINE
ntruct nu s-a reuit s se obin un algoritm polinomial pentru VALID, sa ncercat s se rezolve probleme "echivalente" cu VALID. Mai precis s-a definit clasa NPC . Clasa de probleme NPC este definit astfel: 1) VALID NPC 2) Pentru o problem P, P NPC dac: 2.1) se cunoate pentru P un algoritm nedeterminist polinomial; 2.2) VALID se poate reduce la P n timp determinist polinomial. Problemele din NPC se numesc NPcomplete. NPC NP P Observaie. Este suficient s artm pentru o singur problem NP complet c admite un algoritm polinomial, pentru a obine egalitatea P=NP. Lista problemelor NPcomplete a depit 1000, dar pentru nici una dintre ele nu s-a reuit s se obin un algoritm determinist polinomial. Drept urmare, aa cum am menionat anterior, problema egalitii P=NP rmne o problema deschis. Prezentm n continuare una dintre multele probleme NPcomplete. Problema clicii maximale. Fie G=(X,M) un graf neorientat. YX se numete clic dac pentru i,jY avem (i,j)M. Se caut ordinul unei clici maximale. Problema de decizie corespunztoare este urmtoarea: Pentru un k dat, exist n G o clic de ordin k ? Pentru aceast problem vom scrie procedura clica(G,k), care furnizeaz rspunsul n timp nedeterminist polinomial. Presupunnd cunoscut acest lucru, algoritmul pentru problema clicii maximale va fi:
for k=n,1,-1 clica(G,k)
127
12. NP-COMPLETITUDINE
117
procedure clica(G,k) for i=1,n ales(i) 0 {elementul i nu a fost ales} { pentru fiecare i alegem un element j{1,...,n} pe care l punem n bi } for i=1,k j choice({1,...,n}) if ales(j)=1 then failure else ales(j) 1; bi j { s-au ales k vrfuri distincte b1,...,bk } for i=1,k for j=1,k if ij & (bi,bj)M then failure write(k); success end;
Timpul este evident polinomial. Prin urmare CLICA(k) NP, deci i CLICA NP. Artm n continuare c VALID se reduce la CLICA n timp determinist polinomial. Fie F(x1,...,xn) o expresie boolean n forma normal conjunctiv (FNC): F=C1...Ck , unde C1,...,Ck sunt disjuncii de variabile de forma xj sau xj , numii literali. Atam lui F graful G=(X,M) astfel: X = {(,i) | literal din Ci} M = {[(,i),(,j)] | }, adic i pot fi satisfcui concomitent. De exemplu, pentru F(x1,x2,x3) = (x1x2x3) ( x1 x 2 x 3 ), graful este:
(x1,1) (x2,1) (x3,1) ( x1 ,2) ( x2 ,2) ( x3 ,2)
Construcia grafului necesit un timp determinist polinomial. Mai trebuie demonstrat c: n G exist o clic de ordin k F este validabil.
128
118
12. NP-COMPLETITUDINE
ncepem cu demonstrarea necesitii. Fie S = {(i,i) | i=1,...,k} o clic de ordin k. Fie S1 = {i | i=1,...,k}. Conform construciei lui M, nu este posibil ca i i i s apar simultan n
S1.
Alegem fiecare ai astfel: 1 dac xiS1, adic i=xi 0 dac xi S1 arbitrar n caz contrar. Pentru aceast alegere, F(a1,...,an)=1, fiecare conjuncie avnd valoarea 1. Pentru exemplul considerat: S={(x1,1),( x 3 ,2)} ; S1={x1, x 3 } ; a1=1 ; a2 arbitrar ; a3=0. Continum cu demonstrarea suficienei. Conform ipotezei, exist a1,...,an cu Ci(a1,...,an)=1, i=1,...,k. Pentru fiecare i=1,...,k, exist un literal i din Ci care are valoarea 1. Fie S = {(i,i) | i=1,...,k}. S este o clic. ntr-adevr, pentru (i,i),(j,j)S diferite rezult ij i i j , deoarece pentru x=(a1,...,an) avem i=j=1. ncheiem prin a prezenta enunul altor probleme NP-complete. 1) Fie G=(X,M) un graf. YX se numete k-acoperire dac |Y|=k i dac pentru orice (i,j)M avem iY sau iY. Exist o k-acoperire? 2) Problema ciclului hamiltonian. 3) Problema colorrii hrilor. 4) Fie A o mulime i fie s:AZ+. Cutm BA cu proprietatea c suma elementelor lui B s fie egal cu suma elementelor lui A\B.
129
Analiza sintactic
Toate gramaticile cu care vom lucra n acest sunt independente de context; de aceea nu vom mai specifica explicit acest lucru. Fie G=(VN,VT,S,P) o gramatic (independent de context). Analiza sintactic const n urmtoarele: * - se d w VT ; - se cere s se determine dac wL(G) i, n caz afirmativ, s se determine produciile care trebuie aplicate pentru a obine o derivare stng a lui w din S. Observaie. Dac produciile au numere de ordine, irul numerelor de ordine ale produciilor ce trebuie aplicate este numit sintaxa stng a lui w. Ea este important, deoarece pe baza ei se poate construi uor arborele de derivare asociat lui w. Vom folosi urmtoarele convenii de notaii:
a,b,c,... VT A,B,C,... VN * x,y,z,u,v,w,... VT ,,,... (VN VT)*
i se va specifica explicit cnd ele sunt nclcate. n continuare considerm c n G nu apar neterminale "nefolositoare", adic: din orice neterminal deriv un cuvnt format numai din terminale (neterminalul este observabil); - pentru orice neterminal exist o derivare din S n care apare acel neterminal (neterminalul este accesibil). tim c pentru orice gramatic independent de context exist una echivalent de acelai tip, n care nu apar neterminale nefolositoare, deci presupunerea fcut nu este restrictiv. De asemenea, toate derivrile care vor aprea n continuare sunt derivri stngi (tim c existena unei derivri este echivalent cu existena unei derivri stngi). n sfrit, nchiderea tranzitiv i reflexiv a relaiei de derivare va fi notat tot prin , pentru a nu ncrca scrierea. Definim dou funcii cu care vom lucra n continuare. : (VNVT)* P(VT{}) (prin P am notat mulimea prilor), dat de: () = {aVT|ax} {|dac }. Funcia mai poart numele FIRST. * Caz particular: pentru VT , ()={a}, unde a este prima liter din (dac ) sau este (dac =).
* Definiia funciei se poate extinde la mulimi de cuvinte din (VN VT) . Pentru * L (VN VT) definim: (L)={()|L}.
130
( x) = ( y )
adic, dat fiind un cuvnt wA i un terminal a, exist cel mult o producie (cu A n membrul stng) care se poate aplica astfel nct n final s se obin un cuvnt format din terminale n care imediat dup w s apar a. Teorem. G este de tip LL(1) A , A P distincte, avem ( ( A)) ( ( A)) = . Fie
S wA w wx S wA w wy
( x) = ( y ) Cum , conform ipotezei, avem: ( ( A)) ( ( A)) = . Dar ( x ) ( ) ( ( A)) ( y ) ( ) ( ( A)) ( x ) = ( y ) , deci ( ( A)) ( ( A)) . Contradicie.
Fie A , A P distincte. Presupunem c a VT {}, a ( ( A)) ( ( A)) . Dac a VT avem patru situaii: 1)
a ( ) ( ) deci ax1 , ax 2
S wA w wax 2 y 2
= .
Contradicie.
131
2) adic
a ( A) ( )
; ax , deci ax
w, cu S wA i ay
S wA w w way
w waxay
= . Contradicie.
( ay ) = ( axay ) = {a}
3) 4)
a ( ) ( A)
a ( A), ,
Deci w, cu S wA cu ax
S wA w w wax
w w wax
= . Contradicie.
( ax ) = ( ax ) = {a}
Dac a =
= .
Contradicie.
=
Corolar. G este LL(1) { A 1 ,..., A n } P , ( i ) ( j ) =
132
Traductorul sintactic
Este asemntor unui automat push-down. Are ns n plus o band de ieire i funcioneaz pe baza unui tabel de analiz sintactic conform unui algoritm sintactic.
Band de intrare
Memorie push-down
$ Dac G este o gramatic LL(1), atunci: pe banda de intrare apar cuvinte w VT* ; pe banda de ieire apar iruri formate din numerele de ordine ale produciilor din gramatic; n memoria pushdown apar cuvinte de forma $ cu (V N VT ) * . Prin configuraie nelegem un triplet (x, , ) unde: x VT* este cuvntul care a mai rmas de citit de pe banda de intrare; este cuvntul din memoria p.d.; este irul aflat pe banda de ieire.
Configuraia iniial este (w, S$, ) unde w este cuvntul care trebuie analizat sintactic. Trecerea de la o configuraie la alta este dat de algoritmul sintactic pe baza tabelului de analiz sintactic.
133
Tabelul
de
analiz
sintactic
este
funcie
definit
pe
Pentru gramaticile de tipul LL(1) vom defini acest tabel conform regulilor: 1) Fie A i P . Definim M ( A, a ) = ( , i ) pentru a ( ) \ {} . Dac n plus ( ) , definim M ( A, b) = ( , i ), b ( A) . 2) M ( a, a ) = SALT 3) M ($, ) = DA 4) n rest, M ( , ) =NU. Observaie: Fie A i P . Atunci M ( A, a ) = ( , i ) a ( ( A)) Aceast observaie arat, pe baza corolarului precedent, c funcia M este bine definit. Exemplu: S aAS 1) S b 2) Aa 3) A bSA 4) M S A a b $ a aAS,1 a,3 SALT b b,2 bSA,4 SALT DA
Algoritmul sintactic
Pentru gramaticile LL(1) definim urmtorul algoritm sintactic: 1) ( x, X , ) | ( x, , i ) dac M ( X , ( x )) = ( , i ) , adic ( x ) ( ( X )) 2) (ax, a , ) | ( x, , ) , ceea ce corespunde situaiei M ( a, a ) = SALT 3) Algoritmul se termin dac: 3.1) se ajunge la ( ,$, ) ; se aplic deci M ($, ) = DA 3.2) se ajunge la ( x, X , ) cu M ( X , ( x )) = NU. Exemplu. Pentru gramatica de mai sus: ( abbab , S $, ) | ( abbab , aAS $,1) | (bbab , AS $,1) | (bbab,bSAS$,14) | (bab,SAS$,14) | (bab,bAS$,142) | (ab,AS$,142) | (ab,aS$,1423) | (b,S$,1423) | (b,b$,14232) | (,$,14232) deci w=abbabL(G) i sintaxa sa stng este =14232.
134
Fie acum un tabel de analiz sintactic M oarecare i fie A un algoritm sintactic construit pe baza acestui tabel. Atunci pentru w VT definim ,dac ( w, S $, ) | ( ,$, ) A(w) = nedefinit , n caz contrar Definiie. Algoritmul i tabelul sunt corecte dac: * 1) w L (G ) ( w, S $, ) | ( ,$, ) 2) Dac da, S w . Definiie. Algoritmul A este corect dac : 1) L(G ) = {w VT | A( w) definit} 2) Dac A(w) = , atunci este sintaxa stng a lui w (adic S w ).
Definiie. Tabelul M este corect dac algoritmul construit pe baza lui este corect. Remarcm c n cazul unui tabel i a unui algoritm corecte, algoritmul se oprete ntr-una dintre situaiile: 1) w L(G), adic w este corect sintactic; n acelai timp este furnizat sintaxa lui; 2) w L(G), adic w nu este corect sintactic; n aceast situaie va trebui produs un mesaj de eroare. Teorem. Algoritmul sintactic i tabelul de analiz sintactic definite mai sus pentru gramaticile LL(1) sunt corecte. Demonstraia cuprinde doi pai. * Artm mai nti c din (xy, S$, ) | (y, $, ) rezult S x :
Demonstraia se face prin inducie dup numrul n de schimbri de configuraie. Pentru n = 1 (xy, S$, ) | (y, $, ) implic x = , = i, M(S, (y)) = (, i) unde
S i P . Rezult S x . n n+1 * (xy, S$, ) | (ay, $, ) | (y, $, ), unde a VT {}. Implic x = xa. n
* Conform ipotezei de inducie, din (x(ay), S$, ) | (ay, $, ) rezult S x' . n Dac a = , atunci = A, = , =i, A i P, M ( A, ( y )) = ( , i ) . Rezult S x' = xA x = x . Dac a , atunci = a, = i S x' = x' a = x .
'
i
'
135
Mai artm c:
* S x ( xy, S $, ) | (y, $, ) y cu (y) (), unde este fie , fie ncepe cu un neterminal. n = 1 : S i x . Fie y cu (y) (). i * 1) x : (xy, S$, )| (xy, x$, ) pentru c (xy) (x(S)) | (y, $, ) 2) x = : (y, S$, )| (y, $, i) pentru c (y) ( ) ((S)). n n+1
'
A i x 2 2
x1 x1 x
A x2
1 2 1
Din 2 nceput al lui rezult c 2 = sau 2 ncepe cu un neterminal. * Fie y cu (y) (). Trebuie ( xy , S $, ) | (y, $, ). * Cf. ipotezei de inducie, pentru z cu (z) (A1), avem : (x, z, S$, )| (z, A1$, ). Lum z = x2y. Atunci (x2y) (A1) deoarece (x2y) (x2) (A1) pt. c A1 x221 = x2. * Deci (x1x2y, S$, ) | (x2y, A1$, ). =x ? * Mai trebuie (x2y, A1$, ) | (x2y, x221$, i) | (y, $, ). ? dac (x2y) (x22(A)): 1) dac x2 , evident 2) dac x2 = trebuie (y) (2(A)). Dar (y) () = (21) = (2(1))
(2(A)) pentru c S x1 A 1 .
nlocuind acum y = = n paii notai cu , sse obine rezultatul din enunul teoremei.
136
Aplicaie
Considerm urmtoarea gramatic ce genereaz expresiile aritmetice corecte n care apare variabila a, operaiile binare + i *, precum i cuprinderea ntre paranteze.
S S T T F F
Este evident c aceast gramatic nu este de tipul LL(1). Ea poate fi adus ns la o gramatic de tipul LL(1), folosind faptul c ansamblul de producii:
A A A
)
,3
+ +TE,2
,3
*FU,5
,6
137
Rezult c w este corect ( adic wL(G) ) i sintaxa sa stng este =147148586363, pe baza cruia putem construi arborele de derivare. Observaie. De fapt am redus problema la determinarea funciilor i , sarcin ce nu este deloc facil!
138
1. Interclasare optim de iruri ordonate Pentru n iruri ordonate cresctor se dau lungimile acestora. Se dorete s se obin un singur ir ordonat care conine toate elementele irurilor iniiale, interclasnd iruri dou cte dou. S se determine ordinea n care trebuie efectuate aceste interclasri astfel nct numrul de deplasri s fie minim (pentru interclasarea a dou iruri de lungimi m i respectiv n sunt necesare m+n deplasri). S se afieze i numrul total de deplasri efectuate. Observatie: Se cere doar ordinea interclasarilor, nu si interclasarea propriu-zisa. Indicaie: Strategia Greedy pentru rezolvarea acestei probleme este urmtoarea: la fiecare pas se combin cele mai scurte dou iruri disponibile la momentul respectiv.
procedure INTERCLASARE_OPTIMA for i :=1 to n do adauga( (Li, i), A) for i:= n+1 to 2n-1 (Lj, j) := extrage_minim(A) (Lk, k) := extrage_minim(A) adauga((Lj + Lk, i), A) writeln 'interclasare S,j, ' de lungime ', Lj, ' cu S',k, ' de lungime ', Lk
Pentru a reine perechile (lungime, numr ir) se va folosi un min-ansamblu n raport cu lungimea irurilor (aia2i i aia2i+1 ,dac fiii exist). Elementul minim va fi a[1]. La adugarea unui element nou nu se va recrea ntreg ansamblu. Se poate folosi o metod nou (fa de cele fcute la sortarea cu ansamble) de a gsi locul n ansamblu pentru noul element introdus, interschimbnd elementul cu printele dac printele este mai mare (deci nu este verificat condiia de min-ansamblu) i urcnd astfel n arbore pn este gsit locul elementului nou introdus. O alt modalitate de a evita crearea ansamblului din nou dup fiecare interclasare este s nu extragem cel de al doilea minim din ansamblu, ci sa l nlocuim cu noul ir obinut, apelnd apoi procedura de refacere a ansamblului din rdcina, innd cont c subarborii drept i stng verific ambii condiia de ansamblu ( combin(1,n) ). Procedura devine
procedure INTERCLASARE_OPTIMA A := creare_ansamblu((Li, i), i = 1,..,n ) for i:= n+1 to 2n-1 (Lj, j) := extrage_minim(A) (Lk, k) := minim(A) inlocuieste_minim(A, (Lj + Lk, i)) combin(A,1,n) writeln 'interclasare S,j, ' de lungime ', Lj, ' cu S',k, ' de lungime ', Lk
2. Algoritmul lui Prim de determinare a unui arbore parial de cost minim. 3. Numere frumoase
139
Numerele frumoase sunt numerele care au ca factori primi doar pe 2, 3 i 5. Dat un numr natural n (1 n 1500) afiai primele n numere frumoase (n ordine). Indicaie: Se reine la fiecare moment cel mai mic multiplu de 2 nc neadugat i indicele din tablou care conine elementul din care s-a obinut prin nmulire cu 2. La fel pentru 3 i 5. SAU 3. Se dau n i k naturale, k n. S se construiasc o matrice nn care ndeplinete simultan condiiile: - conine toate numerele de la 1 la n2 o singur dat - pe fiecare linie numerele sunt aezate n ordine cresctoare, de la stnga la dreapta - suma elementelor de pe coloana k s fie minim.
140
Clasa String Cteva metode ale clasei String care pot fi utile n problemele date sunt
public int length()
Returneaz caracterul de pe poziia index din ir. Poziiile n ir sunt numerotate de la 0 la lungimea irului minus 1. Dac index nu se ncadreaz ntre aceste limite, metoda arunc excepie (IndexOutOfBoundsException)
public String substring(int beginIndex, int endIndex)
Returneaz un nou ir care reprezint subirul care ncepe pe poziia beginIndex a irului curent i se termin pe poziia endIndex - 1.
public String substring(int beginIndex)
Returneaz un nou ir care reprezint subirul care conine caracterele de la poziia beginIndex a irului curent pn la sfrit (se termin pe poziia length()-1)
public boolean equals(Object anObject)
Compar irul curent cu un obiect. Rezultatul returnat este true dac argumentul este un obiect nenul de tip String i reprezint aceeai secven de caractere ca i irul curent, i false n caz contrar .
public int indexOf(int ch)
Returneaz poziia primei apariii a caracterului ch n ir i -1 n cazul n care caracterul ch nu se gsete n ir Programare Dinamic 1. Se d un ir de cuvinte. S se determine cel mai lung subir al irului dat cu proprietatea c oricare dou cuvinte consecutive din acest subir verific relaia: ultimele dou litere din primul cuvnt coincid cu primele dou litere din urmtorul cuvnt. (Subirul unui ir este format din elemente ale irului, nu neaprat consecutive, dar care respect ordinea din irul iniial. Un subir al irului a1, a2, , an este de forma ai1, ai2, , aik, cu 1i1<i2<<ikn) De exemplu, pentru irul seara, carte, teorema, temperatura, rar, mare, arbore cel mai lung subir care verific cerinele este carte, temperatura, rar, arbore
141
2. Se consider un ir de n piese de domino. O pies de domino are form dreptunghiular i are nscris pe ea dou numere, cuprinse ntre 1 i 6. O pies poate fi ntoars (rotit cu 1800), inversndu-se astfel ordinea celor dou numere nscrise pe ea.
Conform regulilor la domino, un lan este un subir al irului de piese iniial constituit din piese care respect urmtoarea condiie: pentru oricare dou piese consecutive din lan, al doilea numr nscris pe prima din cele dou piese coincide cu primul numr nscris pe cea de a doua pies. S se determine cel mai lung lan care se poate obine cu piesele din irul dat . Exemplu: pentru n = 8 i irul de piese (1,5), (1,3), (2,6), (2,5), (4,3), (6,4), (2,4), (1,6) lungimea celui mai lung lan este 5, un astfel de lan fiind format cu piesele de pe poziiile 1(inversat), 2, 5(inversat), 6(inversat), 8(inversat) i anume (5,1), (1,3), (3,4), (4,6), (6,1) 3. Se dau cuvintele dintr-un dicionar. S se descompun un cuvnt dat ntr-un numr minim de cuvinte existente n dicionar. Exemplu: pentru dicionarul cu cuvintele a, apa, apar, ne, par, i, ine descompunerea minim pentru cuvntul aparine este format din cuvintele apar, ine; descompunerea minim pentru cuvntul apar este format doar din cuvntul apar, deoarece acesta este n dicionar. 4. Se consider alfabetul A format din literele mici ncepnd cu a i terminnd cu litera mic last citit de la intrare. Pe acest alfabet este dat o lege de compoziie printr-o matrice t (nu se presupune c legea este asociativ sau comutativ). Fiind dat un cuvnt x de lungime n peste alfabetul A i o liter dest din A, s se determine dac exist o parantezare a literelor cuvntului astfel nct rezultatul efecturii calculelor s fie dest
Divide et Impera 1. Problema tieturilor: Se d o plac dreptunghiular cu lungimea L i nlimea h n care exist n guri punctiforme, poziiile gurilor fiind date prin coordonatele carteziene ale acestora (x, y). S se determine placa de arie maxim care se poate decupa din placa iniial i care nu conine n interior nici o gaur. Sunt permise numai tieturi verticale i orizontale complete (de la o latur la latura opus)
142
Sistemul de axe se poate considera cu originea n colul din stnga-jos al plcii. Un dreptunghi va fi identificat prin coordonatele colului din stnga-jos (x1,y1) i coordonatele colului din dreapta-sus (x2,y2) . 2. Problema plierii: Se consider un vector de lungime n. Definim plierea vectorului prin suprapunerea unei jumti numit donatoare peste cealalt numit receptoare, cu precizarea c dac numrul de elemente este impar, numrul din mijloc este eliminat. n acest mod se ajunge la un subir ale crui elemente au numerotarea corespunztoare jumtii receptoare. Plierea se poate aplica n mod repetat pn se ajunge la un subir de lungime 1, numit element final. a) S se precizeze toate elementele finale b) Dat un element i, s se scrie o succesiune de plieri prin care se ajunge la el dac i este element final; dac i nu este final se va afia un mesaj corespunztor. De exemplu, vectorul (1, 2, 3, 4, 5) se poate plia n dou moduri (1, 2) sau (4, 5), elementul 3 fiind eliminat. 3. Se dau n cri de matematic (M), romn (R), fizic (F) i informatic (I) i trei rafturi corespunztoare tipurilor crilor: RR raftul crilor de romn RMI raftul crilor de matematic sau de informatic RF raftul crilor de fizic Pentru fiecare carte se citesc numele crii i tipul ei. Crile sunt aezate n teanc pe un raft A n ordinea n care sunt citite. Avnd la dispoziie dou rafturi B i X, s se transfere crile de pe raftul A pe rafturile RR, RMI i RF dup urmtoarele reguli: - pe rafturile A i B se pot pune sau lua oricte cri - pe raftul X se poate pune sau lua o singur carte - pe RR, RMI i RF se pot pune cri, nu i lua - la orice mutare avem acces la o singur carte cea din vrful teancului - tipul crii se poate citi numai n B i numai cnd este o singur carte pe acest raft - n final crile pe RR, RMI i RF trebuie s fie n aceeai ordine n care erau pe A. a) S se afieze mutrile pentru transferul crilor fr s se fac deosebire ntre crile de matematic i informatic. Se vor afia numrul de cri mutate i rafturile ntre care se mut, iar cnd se face un transfer al unei cri din B pe raftul corespunztor tipului se va afia titlul crii care se repartizeaz i tipul ei (vezi exemplul) b) Rezolvai problema astfel nct n final crile de matematic s apar n raftul RMI naintea celor de informatic (respectnd ordinea n care erau n A). Exemplu: Pentru crile de intrare (date cu nume i tip) Mate1 de tip M Fizica1 de tip F Rom1 de tip R Info1 de tip I
143
Info2 de tip I Mate2 de tip M Rom2 de tip R Fizica2 de tip F O posibil soluie la punctul a) ar fi (o carte s-a afiat sub forma nume tip) initial A=[Mate1 - M, Fizica1 - F, Rom1 - R, Info1 - I, Info2 - I, Mate2 - M, Rom2 - R, Fizica2 - F] Sir mutari 7 carti A->B, 1 carti A->X, 7 carti B->A , Repartizez Mate1 M, 6 carti A->B, 1 carti A->X, 6 carti B->A, Repartizez Fizica1 - F 5 carti A->B, 1 carti A->X, 5 carti B->A, Repartizez Rom1 R, 4 carti A->B, 1 carti A->X, 4 carti B->A, Repartizez Info1 I, 3 carti A->B, 1 carti A->X, 3 carti B->A, Repartizez Info2 - I 2 carti A->B, 1 carti A->X, 2 carti B->A, Repartizez Mate2 - M, 1 carti A->B , 1 carti A->X, 1 carti B->A, Repartizez Rom2 - R, 1 carti A->B, Repartizez Fizica2 - F, 1 carti X->B, 1 carti X->B, 1 carti X->B, 1 carti X->B, 1 carti X->B, 1 carti X->B, 1 carti X->B,
Rafturi carti dupa repartizare RF: [Fizica1 - F, Fizica2 - F] RMI: [Mate1 - M, Info1 - I, Info2 - I, Mate2 - I] RR: [Rom1 - R, Rom2 - R]
Observaii pentru implementare n Java Rafturile se pot memora ca stive, deoarece putem pune sau lua cri de la un singur cap al raftului (mutm mereu cartea din capul teancului). n Java exist clasa Stack (din pachetul java.util) pentru lucru cu stive. Pentru a introduce un element n stiv exist metoda push, pentru a elimina vrful stivei metoda pop (care returneaz elementul eliminat)
144
Pn la varianta 5.0 ntr-o stiv (obiect de tip Stack) se puteau introduce obiecte de orice tip. La extragerea unui element din stiv programatorul trebuia s tie ce tip de elemente a introdus n stiv i s fac o conversie explicit la acel tip. Chiar dac elementele introduse erau de acelai tip, tot era nevoie de conversie explicit, elementul extras dintr-o stiv fiind de tip Object. Spre exemplu, puteam introduce n stiv alternativ numere ntregi i iruri de caractere
Stack stiva=new Stack(); for (int i=0;i<5;i++){ stiva.push(new Integer(i)); stiva.push("valoarea "+i); } String sirTop=(String)stiva.pop(); System.out.println(sirTop); Integer x=(Integer)stiva.pop(); System.out.println(x);
Acest cod este corect i n varianta 5.0, doar la compilare se va semnala ca observaie faptul c programul folosete operaii nesigure. ncepnd cu aceast variant s-a introdus posibilitatea de a defini de la declararea stivei tipul elementelor care se vor introduce n stiv. Pe parcursul programului n stiv se vor putea introduce doar elemente de acest tip, iar la extragere nu mai este nevoie de conversie explicit, cunoscndu-se tipul elementului extras. Spre exemplu, s considerm clasa Carte care poate fi folosit pentru problema rafturilor
import java.util.Stack; class Carte{ String nume; int tip;//0-F,1-M,2-I, 3-R Carte(){ } Carte(String s, int t){ nume=s; tip=(byte)t; } public String toString(){ switch(tip){ case 0: return nume+" case 1: return nume+" case 2: return nume+" case 3: return nume+" } return ""; } }
145
class Rafturi{ public static void main(String arg[]){ Stack<Carte> raft=new Stack<Carte>(); raft.push(new raft.push(new raft.push(new raft.push(new Carte("romana",3)); Carte("fizica",0)); Carte("matematica",1)); Carte("informatica",2));
Carte c=raft.pop(); //nu mai este necesara conversie System.out.print("Ultima carte introdusa: "); System.out.println(c); System.out.print("Cartile ramase pe raft "); System.out.println(raft); } }
i afieaz rezultatul ntors de aceasta. Orice clas extinde clasa Object, care are metoda toString(). Suprascriind n clasa Carte aceast metod putem afia un obiect de tip Carte sub forma dorit. (Dac nu suprascriem aceast metod, ea va returna numele clasei i adresa de memorie corespunztoare obiectului) . Mai mult, metoda toString() a clasei Stack va apela metoda toString a fiecrui element al stivei (Nu mai este astfel nevoie s facem o metod de afiare a stivei, care s parcurg fiecare element al stivei i s-l afieze). Programul va afia Ultima carte introdusa: informatica - I Cartile ramase pe raft [romana - R, fizica - F, matematica - M]
Se vor prezenta 5 probleme din care obligatoriu 2 probleme de programare dinamic i 2 de divide et impera.
146