Referat Analiza Eficientei Algoritmilor
Mai jos puteti citi fragmente din
Referat Analiza Eficientei Algoritmilor si de asemenea puteti face
Download Referat Analiza eficientei algoritmilorCiteste fragmente din Referat Analiza Eficientei Algoritmilor
5. Analiza eficientei algoritmilor
Vom dezvolta in acest capitol aparatul matematic necesar pentru analiza
eficientei algoritmilor, incercand ca aceasta incursiune matematica sa
nu fie excesiv de formala. Apoi, vom arata, pe baza unor exemple, cum
poate fi analizat un algoritm. O atentie speciala o vom acorda
tehnicilor de analiza a algoritmilor recursivi.
5.1 Notatia asimptotica
In Capitolul 1 am dat un inteles intuitiv situatiei cand un algoritm
necesita un timp in ordinul unei anumite functii. Revenim acum cu o
definitie riguroasa.
5.1.1 O notatie pentru “ordinul luiâ€Â
Fie N multimea numerelor naturale (pozitive sau zero) si R multimea
numerelor reale. Notam prin N+ si R+ multimea numerelor naturale,
respectiv reale, strict pozitive, si prin R multimea numerelor reale
nenegative. Multimea {true, false} de constante booleene o notam cu B.
Fie f : N  R o functie arbitrara. Definim multimea
O( f ) = {t : N  R | (c  R+) (n0  N) (n  n0)
[t(n) ï‚£ cf (n)]}
 n0, acest lucru sa nu mai apara. De exemplu, vom vorbi despre
ordinul lui n/log n, chiar daca pentru n = 0 si n = 1 functia nu este
definita. In loc de t  O( f ), uneori este mai convenabil sa folosim
notatia t(n)  O( f (n)), subintelegand aici ca t(n) si f (n) sunt
functii.
Fie un algoritm dat si fie o functie t : N  R astfel incat o
anumita implementare a algoritmului sa necesite cel mult t(n) unitati de
timp pentru a rezolva un caz de marime n, n  N. Principiul
invariantei (mentionat in Capitolul 1) ne asigura ca orice implementare
a algoritmului necesita un timp in ordinul lui t. Mai mult, acest
algoritm necesita un timp in ordinul lui f pentru orice functie f : N
 R pentru care t  O( f ). In particular, t  O(t). Vom cauta
in general sa gasim cea mai simpla functie f, astfel incat t  O( f ).
Proprietatile de baza ale lui O( f ) sunt date ca exercitii (Exercitiile
5.1-5.7) si este recomandabil sa le studiati inainte de a trece mai
departe.
Notatia asimptotica defineste o relatie de ordine partiala intre functii
si deci, intre eficienta relativa a diferitilor algoritmi care rezolva o
anumita problema. Vom da in continuare o interpretare algebrica a
notatiei asimptotice. Pentru oricare doua functii f , g : N  R,
definim urmatoarea relatie binara: f  g daca O( f ) O(g). Relatia
“†este o relatie de ordine partiala in multimea functiilor
definite pe N si cu valori in R (Exercitiul 5.6). Definim si o
relatie de echivalenta: f  g daca O( f ) = O(g).
In multimea O( f ) putem inlocui pe f cu orice alta functie echivalenta
cu f. De exemplu, lg n  ln n log n si avem O(lg n) = O(ln n) =
O(log n). Notand cu O(1) ordinul functiilor marginite superior de o
constanta, obtinem ierarhia:
O(1)  O(log n)  O(n)  O(n log n)  O(n2)  O(n3)  O(2n)
Aceasta ierarhie corespunde unei clasificari a algoritmilor dupa un
criteriu al performantei. Pentru o problema data, dorim mereu sa obtinem
un algoritm corespunzator unui ordin cat mai “la stangaâ€Â. Astfel,
este o mare realizare daca in locul unui algoritm exponential gasim un
algoritm polinomial.
In Exercitiul 5.7 este data o metoda de simplificare a calculelor, in
care apare notatia asimptotica. De exemplu,
3n2n8  O(n3(3n2n8)) = O(max(n3, 3n2n8)) =
O(n3)
Ultima egalitate este adevarata, chiar daca max(n3, 3n2n8)  n3
pentru 0 ï‚£ n ï‚£ 3, deoarece notatia asimptotica se aplica doar pentru
n suficient de mare. De asemenea,
n3ï€Â3n2ï€Ânï€Â8  O(n3/2(n3/2ï€Â3n2ï€Ânï€Â8)) = O(max(n3/2,
n3/2ï€Â3n2ï€Ânï€Â8))
= O(n3/2) = O(n3)
chiar daca pentru 0 ï‚£ n ï‚£ 6 polinomul este negativ. Exercitiul 5.8
trateaza cazul unui polinom oarecare.
Notatia O( f ) este folosita pentru a limita superior timpul necesar
unui algoritm, masurand eficienta algoritmului respectiv. Uneori este
util sa estimam si o limita inferioara a acestui timp. In acest scop,
definim multimea
ï—( f ) = {t : N ï‚® R | (c  R+) (n0  N) (n  n0)
[t(n)  cf (n)]}
Exista o anumita dualitate intre notatiile O( f ) si ï—( f ). Si anume,
pentru doua functii oarecare f, g : N  R, avem: f  O(g), daca
si numai daca g  ï—( f ).
O situatie fericita este atunci cand timpul de executie al unui algoritm
este limitat, atat inferior cat si superior, de cate un multiplu real
pozitiv al aceleiasi functii. Introducem notatia
ï‘( f ) = O( f )  ï—( f )
numita ordinul exact al lui f. Pentru a compara ordinele a doua functii,
notatia ï‘ nu este insa mai puternica decat notatia O, in sensul ca
relatia O( f ) = O(g) este echivalenta cu ï‘( f ) = ï‘(g).
Se poate intampla ca timpul de executie al unui algoritm sa depinda
simultan de mai multi parametri. Aceasta situatie este tipica pentru
anumiti algoritmi care opereaza cu grafuri si in care timpul depinde
atat de numarul de varfuri, cat si de numarul de muchii. Notatia
asimptotica se generalizeaza in mod natural si pentru functii cu mai
multe variabile. Astfel, pentru o functie arbitrara f : N  N  R
definim
O( f ) = {t : N  N  R | (c  R+) (m0, n0  N) (m
 m0) (n  n0)
ï›t(m, n) ï‚£ cf (m, n)]}
Similar, se obtin si celelalte generalizari.
5.1.2 Notatia asimptotica conditionata
 R o functie arbitrara si fie P : N  B un predicat.
O( f | P) = {t : N  R  (c  R+) (n0  N) (n 
n0)
[P(n)  t(n) ï‚£ cf (n)ïÂÂ}
Notatia O( f ) este echivalenta cu O( f | P), unde P este predicatul a
carui valoare este mereu true. Similar, se obtin notatiile ï—( f | P)
si ï‘( f | P).
O functie f : N  R este eventual nedescrescatoare, daca exista un
n0, astfel incat pentru orice n  n0 avem f (n)  f (n1), ceea ce
implica prin inductie ca, pentru orice n  n0 si orice m  n, avem f
(n)  f (m). Fie b  2 un intreg oarecare. O functie eventual
nedescrescatoare este b-neteda daca f (bn)  O( f (n)). Orice functie
care este b-neteda pentru un anumit b  2 este, de asemenea, b-neteda
pentru orice b  2 (demonstrati acest lucru!); din aceasta cauza, vom
spune pur si simplu ca aceste functii sunt netede. Urmatoarea
proprietate asambleaza aceste definitii, demonstrarea ei fiind lasata ca
exercitiu.
Proprietatea 5.1 Fie b  2 un intreg oarecare, f : N  R o
functie neteda si t : N  R o functie eventual nedescrescatoare,
astfel incat
t(n)  X( f (n) | n este o putere a lui b)
unde X poate fi O, ï—, sau ï‘. Atunci, t  X( f ). Mai mult, daca t
 ï‘( f ), atunci si functia t este neteda.
Pentru a intelege utilitatea notatiei asimptotice conditionate, sa
presupunem ca timpul de executie al unui algoritm este dat de ecuatia
unde a, b  R+ sunt constante arbitrare. Este dificil sa analizam
direct aceasta ecuatie. Daca consideram doar cazurile cand n este o
putere a lui 2, ecuatia devine
Prin tehnicile pe care le vom invata la sfarsitul acestui capitol,
ajungem la relatia
t(n)  ï‘(n log n | n este o putere a lui 2)
Pentru a arata acum ca t  ï‘(n log n), mai trebuie doar sa verificam
daca t este eventual nedescrescatoare si daca n log n este neteda.
Prin inductie, vom demonstra ca (n  1) [t(n)  t(n1)]. Pentru
inceput, sa notam ca
t(1) = a  2(ab) = t(2)
Fie n > 1. Presupunem ca pentru orice m < n avem t(m)  t(m1). In
particular,
t(n/2)  t((n1)/2)
t(n/2)  t((n1)/2)
Atunci,
t(n) = t(n/2)t(n/2)bn 
t((n1)/2)t((n1)/2)b(n1) = t(n1)
In fine, mai ramane sa aratam ca n log n este neteda. Functia n log n
este eventual nedescrescatoare si
2n log(2n) = 2n(log 2  log n) = (2 log 2)n  2n log n
 O(n  n log n) = O(max(n, n log n)) = O(n log n)
De multe ori, timpul de executie al unui algoritm se exprima sub forma
unor inegalitati de forma
si, simultan
pentru anumite constante c, d  R+, n0  N si pentru doua functii
t1, t2 : N ï‚® R+. Notatia asimptotica ne permite sa scriem cele doua
inegalitati astfel:
t(n)  t(n/2)  t(n/2)  O(n)
respectiv
t(n)  t(n/2)  t(n/2)  ï—(n)
Aceste doua expresii pot fi scrise si concentrat:
t(n)  t(n/2)  t(n/2)  ï‘(n)
Definim functia
Am vazut ca f  ï‘(n log n). Ne intoarcem acum la functia t care
satisface inegalitatile precedente. Prin inductie, se demonstreaza ca
exista constantele v  d, u  c, astfel incat
v ï‚£ t(n)/f (n) ï‚£ u
pentru orice n  N+. Deducem atunci
t  ï‘( f ) = ï‘(n log n)
Aceasta tehnica de rezolvare a inegalitatilor initiale are doua
avantaje. In primul rand, nu trebuie sa demonstram independent ca t 
O(n log n) si t  ï—(n log n). Apoi, mai important, ne permite sa
restrangem analiza la situatia cand n este o putere a lui 2, aplicand
apoi Proprietatea 5.1. Deoarece nu stim daca t este eventual
nedescrescatoare, nu putem aplica Proprietatea 5.1 direct asupra
inegalitatilor initiale.
5.2 Tehnici de analiza a algoritmilor
Nu exista o formula generala pentru analiza eficientei unui algoritm.
Este mai curand o chestiune de rationament, intuitie si experienta. Vom
arata, pe baza exemplelor, cum se poate efectua o astfel de analiza.
5.2.1 Sortarea prin selectie
Consideram algoritmul select din Sectiunea 1.3. Timpul pentru o singura
executie a buclei interioare poate fi marginit superior de o constanta
a. In total, pentru un i dat, bucla interioara necesita un timp de cel
mult ba(nï€Âi) unitati, unde b este o constanta reprezentand timpul
necesar pentru initializarea buclei. O singura executie a buclei
exterioare are loc in cel mult cba(nï€Âi) unitati de timp, unde c
este o alta constanta. Algoritmul dureaza in total cel mult
unitati de timp, d fiind din nou o constanta. Simplificam aceasta
expresie si obtinem
(a/2)n2  (bcï€Âa/2)n  (dï€Âcï€Âb)
de unde deducem ca algoritmul necesita un timp in O(n2). O analiza
similara asupra limitei inferioare arata ca timpul este de fapt in
ï‘(n2). Nu este necesar sa consideram cazul cel mai nefavorabil sau
cazul mediu, deoarece timpul de executie este independent de ordonarea
prealabila a elementelor de sortat.
ï€Â1)/2 ori. Exercitiul 5.23 ne sugereaza ca astfel de simplificari
trebuie facute cu discernamant.
5.2.2 Sortarea prin insertie
Timpul pentru algoritmul insert (Sectiunea1.3) este dependent de
ordonarea prealabila a elementelor de sortat. Vom folosi comparatia “x
< T[ j]†ca barometru.
Sa presupunem ca i este fixat si fie x = T[i], ca in algoritm. Cel mai
nefavorabil caz apare atunci cand x < T[ j] pentru fiecare j intre 1 si
iï€Â1, algoritmul facand in aceasta situatie iï€Â1 comparatii. Acest
lucru se intampla pentru fiecare valoare a lui i de la 2 la n, atunci
cand tabloul T este initial ordonat descrescator. Numarul total de
comparatii pentru cazul cel mai nefavorabil este
 ï‘(n2)
Vom estima acum timpul mediu necesar pentru un caz oarecare. Presupunem
ca elementele tabloului T sunt distincte si ca orice permutare a lor are
aceeasi probabilitate de aparitie. Atunci, daca 1 ï‚£ k ï‚£ i,
probabilitatea ca T[i] sa fie cel de-al k-lea cel mai mare element
dintre elementele T[1], T[2], …, T[i] este 1/i. Pentru un i fixat,
conditia T[i] < T[iï€Â1] este falsa cu probabilitatea 1/i, deci
probabilitatea ca sa se execute comparatia “x < T[ j]â€Â, o singura
data inainte de iesirea din bucla while, este 1/i. Comparatia “x < T[
j]†se executa de exact doua ori tot cu probabilitatea 1/i etc.
Probabilitatea ca sa se execute comparatia de exact iï€Â1 ori este 2/i,
deoarece aceasta se intampla atat cand x < T[1], cat si cand T[1] ï‚£ x
< T[2]. Pentru un i fixat, numarul mediu de comparatii este
ci = 1×1/i  2×1/i  (iï€Â2)1/i  (iï€Â1)2/i =
(i1)/2 1/i
comparatii, ceea ce este egal cu
(n23n)/4 Hn  ï‘(n2)
 ï‘(log n) am notat al n-lea element al seriei armonice (Exercitiul
5.17).
Se observa ca algoritmul insert efectueaza pentru cazul mediu de doua
ori mai putine comparatii decat pentru cazul cel mai nefavorabil.
Totusi, in ambele situatii, numarul comparatiilor este in ï‘(n2).
Algoritmul necesita un timp in ï—(n2), atat pentru cazul mediu, cat si
pentru cel mai nefavorabil. Cu toate acestea, pentru cazul cel mai
favorabil, cand initial tabloul este ordonat crescator, timpul este in
O(n). De fapt, in acest caz, timpul este si in ï—(n), deci este in
ï‘(n).
5.2.3 Heapsort
Vom analiza, pentru inceput, algoritmul make-heap din Sectiunea3.4.
Definim ca barometru instructiunile din bucla repeat a algoritmului
sift-down. Fie m numarul maxim de repetari al acestei bucle, cauzat de
apelul lui sift-down(T, i), unde i este fixat. Notam cu jt valoarea lui
j dupa ce se executa atribuirea “j  k†la a t-a repetare a
buclei. Evident, j1 = i. Daca 1 < t ï‚£ m, la sfarsitul celei de-a
(tï€Â1)-a repetari a buclei, avem j  k si k  2j. In general, jt
 2jt-1 pentru 1 < t  m. Atunci,
n  jm  2jm-1  4jm-2  …  2m-1i
Rezulta 2m-1  n/i, iar de aici obtinem relatia m  1  lg(n/i).
Numarul total de executari ale buclei repeat la formarea unui heap este
marginit superior de
, unde a = n/2 ()
Pentru a simplifica aceasta expresie, sa observam ca pentru orice k 
0
, unde b = 2k si c = 2k+1ï€Â1
Descompunem expresia () in sectiuni corespunzatoare puterilor lui 2
si notam d = lg(n/2) :
Demonstratia ultimei inegalitati rezulta din Exercitiul 5.26. Dar d =
lg(n/2) implica d1 ï‚£ lg n si dï€Â1  lg(n/8). Deci,
Din () deducem ca n/23n repetari ale buclei repeat sunt
suficiente pentru a construi un heap, deci make-heap necesita un timp t
 O(n). Pe de alta parte, deoarece orice algoritm pentru formarea unui
heap trebuie sa utilizeze fiecare element din tablou cel putin o data, t
 ï—(n). Deci, t  ï‘(n). Puteti compara acest timp cu timpul
necesar algoritmului slow-make-heap (Exercitiul 5.28).
Pentru cel mai nefavorabil caz, sift-down(T[1 .. iï€Â1], 1) necesita un
timp in O(log n) (Exercitiul 5.27). Tinand cont si de faptul ca
algoritmul make-heap este liniar, rezulta ca timpul pentru algoritmul
heapsort pentru cazul cel mai nefavorabil este in O(n log n). Mai mult,
timpul de executie pentru heapsort este de fapt in ï‘(n log n), atat
pentru cazul cel mai nefavorabil, cat si pentru cazul mediu.
ï—(n log n) (Exercitiul 5.30). Pentru cel mai nefavorabil caz,
algoritmul heapsort este deci optim (in limitele unei constante
multiplicative). Acelasi lucru se intampla si cu mergesort.
5.2.4 Turnurile din Hanoi
 i  3, 1  j  3, i  j, n  1), transferam cele mai mici
nï€Â1 discuri de pe tija i pe tija 6ï€Âiï€Âj, apoi transferam discul n
de pe tija i pe tija j, iar apoi retransferam cele nï€Â1 discuri de pe
tija 6ï€Âiï€Âj pe tija j. Cu alte cuvinte, reducem problema mutarii a n
discuri la problema mutarii a nï€Â1 discuri. Urmatoarea procedura
descrie acest algoritm recursiv.
procedure Hanoi(n, i, j)
{muta cele mai mici n discuri de pe tija i pe tija j}
if n > 0 then Hanoi(nï€Â1, i, 6ï€Âiï€Âj)
write i ““ j
Hanoi(nï€Â1, 6ï€Âiï€Âj, j)
Pentru rezolvarea problemei initiale, facem apelul Hanoi(64, 1, 2).
Consideram instructiunea write ca barometru. Timpul necesar algoritmului
este exprimat prin urmatoarea recurenta:
ï€Â1. Rezulta t  ï‘(2n).
Acest algoritm este optim, in sensul ca este imposibil sa mutam n
discuri de pe o tija pe alta cu mai putin de 2nï€Â1 operatii.
Implementarea in oricare limbaj de programare care admite exprimarea
recursiva se poate face aproape in mod direct.
5.3 Analiza algoritmilor recursivi
Am vazut in exemplul precedent cat de puternica si, in acelasi timp, cat
de eleganta este recursivitatea in elaborarea unui algoritm. Nu vom face
o introducere in recursivitate si nici o prezentare a metodelor de
eliminare a ei. Cel mai important castig al exprimarii recursive este
faptul ca ea este naturala si compacta, fara sa ascunda esenta
algoritmului prin detaliile de implementare. Pe de alta parte, apelurile
recursive trebuie folosite cu discernamant, deoarece solicita si ele
resursele calculatorului (timp si memorie). Analiza unui algoritm
recursiv implica rezolvarea unui sistem de recurente. Vom vedea in
continuare cum pot fi rezolvate astfel de recurente. Incepem cu tehnica
cea mai banala.
5.3.1 Metoda iteratiei
Cu putina experienta si intuitie, putem rezolva de multe ori astfel de
recurente prin metoda iteratiei: se executa primii pasi, se intuieste
forma generala, iar apoi se demonstreaza prin inductie matematica ca
forma este corecta. Sa consideram de exemplu recurenta problemei
turnurilor din Hanoi. Pentru un anumit n > 1 obtinem succesiv
Rezulta t(n) = 2nï€Â1. Prin inductie matematica se demonstreaza acum cu
usurinta ca aceasta forma generala este corecta.
5.3.2 Inductia constructiva
Inductia matematica este folosita de obicei ca tehnica de demonstrare a
unei asertiuni deja enuntate. Vom vedea in aceasta sectiune ca inductia
matematica poate fi utilizata cu succes si in descoperirea enuntului
asertiunii. Aplicand aceasta tehnica, putem simultan sa demonstram o
asertiune doar partial specificata si sa descoperim specificatiile care
lipsesc si datorita carora asertiunea este corecta. Vom vedea ca aceasta
tehnica a inductiei constructive este utila pentru rezolvarea anumitor
recurente care apar in contextul analizei algoritmilor. Incepem cu un
exemplu.
Fie functia f : N ï‚® N, definita prin recurenta
Sa presupunem pentru moment ca nu stim ca f (n) = n(n1)/2 si sa
cautam o astfel de formula. Avem
si deci, f (n)  O(n2). Aceasta ne sugereaza sa formulam ipoteza
inductiei specificate partial IISP(n) conform careia f este de forma f
(n) = an2bnc. Aceasta ipoteza este partiala, in sensul ca a, b si
c nu sunt inca cunoscute. Tehnica inductiei constructive consta in a
demonstra prin inductie matematica aceasta ipoteza incompleta si a
determina in acelasi timp valorile constantelor necunoscute a, b si c.
Presupunem ca IISP(nï€Â1) este adevarata pentru un anumit n  1.
Atunci,
f (n) = a(nï€Â1)2b(nï€Â1)cn = an2(1bï€Â2a)n(aï€Âbc)
Daca dorim sa aratam ca IISP(n) este adevarata, trebuie sa aratam ca f
(n) = an2bnc. Prin identificarea coeficientilor puterilor lui n,
obtinem ecuatiile 1bï€Â2a = b si aï€Âbc = c, cu solutia a = b =
1/2, c putand fi oarecare. Avem acum o ipoteza mai completa, pe care o
numim tot IISP(n): f (n) = n2/2n/2c. Am aratat ca, daca
IISP(nï€Â1) este adevarata pentru un anumit n  1, atunci este
adevarata si IISP(n). Ramane sa aratam ca este adevarata si IISP(0).
Trebuie sa aratam ca f (0) = a0b0c = c. Stim ca f (0) = 0,
deci IISP(0) este adevarata pentru c = 0. In concluzie, am demonstrat ca
f (n) = n2/2n/2 pentru orice n.
5.3.3 Recurente liniare omogene
Exista, din fericire, si tehnici care pot fi folosite aproape automat
pentru a rezolva anumite clase de recurente. Vom incepe prin a considera
ecuatii recurente liniare omogene, adica de forma
a0tn  a1tn-1  ¼  aktn-k = 0 ()
unde ti sunt valorile pe care le cautam, iar coeficientii ai sunt
constante.
Conform intuitiei, vom cauta solutii de forma
tn = xn
unde x este o constanta (deocamdata necunoscuta). Incercam aceasta
solutie in () si obtinem
a0xn  a1xn-1  ...  akxn-k = 0
Solutiile acestei ecuatii sunt fie solutia triviala x = 0, care nu ne
intereseaza, fie solutiile ecuatiei
a0xk  a1xk-1  ...  ak = 0
care este ecuatia caracteristica a recurentei ().
Presupunand deocamdata ca cele k radacini r1, r2, ..., rk ale acestei
ecuatii caracteristice sunt distincte, orice combinatie liniara
este o solutie a recurentei (), unde constantele c1, c2, ..., ck sunt
determinate de conditiile initiale. Este remarcabil ca () are numai
solutii de aceasta forma.
Sa exemplificam prin recurenta care defineste sirul lui Fibonacci (din
Sectiunea 1.6.4):
tn = tn-1  tn-2 n  2
iar t0 = 0, t1 = 1. Putem sa rescriem aceasta recurenta sub forma
tn ï€Âtn-1 ï€Âtn-2 = 0
care are ecuatia caracteristica
x2 ï€Âx ï€Â1 = 0
)/2. Solutia generala are forma
Impunand conditiile initiale, obtinem
c1  c2 = 0 n = 0
D
F
H
P
R
T
ä
æ
è
-
(
,
0
2
4
6
8
:
<
>
D
F
J
L
N
P
R
X
Z
`
b
d
f
l
n
r
t
v
x
z
€
‚
„
â€Â
Ã…Â
Å’
Ž
â€Â
–
˜
ÂÂ
ÂÂ
Â
Ä
Ãâ€
Ì
㩶ý
?c2 = 1 n = 1
de unde determinam
)/2, r2 = ï€Âï¦-1 si obtinem
(ï¦nï€Â(ï€Âï¦)-n)
care este cunoscuta relatie a lui de Moivre, descoperita la inceputul
secolului XVI. Nu prezinta nici o dificultate sa aratam acum ca timpul
pentru algoritmul fib1 (din Sectiunea 1.6.4) este in ï‘(ï¦n).
Ce facem insa atunci cand radacinile ecuatiei caracteristice nu sunt
distincte? Se poate arata ca, daca r este o radacina de multiplicitate m
a ecuatiei caracteristice, atunci tn = rn, tn = nrn, tn = n2rn, ..., tn
= nm-1rn sunt solutii pentru (). Solutia generala pentru o astfel de
recurenta este atunci o combinatie liniara a acestor termeni si a
termenilor proveniti de la celelalte radacini ale ecuatiei
caracteristice. Din nou, sunt de determinat exact k constante din
conditiile initiale.
Vom da din nou un exemplu. Fie recurenta
tn = 5tn-1 ï€Â8tn-2  4tn-3 n  3
iar t0 = 0, t1 = 1, t2 = 2. Ecuatia caracteristica are radacinile 1 (de
multiplicitate 1) si 2 (de multiplicitate 2). Solutia generala este:
tn = c11n  c22n  c3n2n
Din conditiile initiale, obtinem c1 = ï€Â2, c2 = 2, c3 = ï€Â1/2.
5.3.4 Recurente liniare neomogene
Consideram acum recurente de urmatoarea forma mai generala
a0tn  a1tn-1  ...  aktn-k = bnp(n) ()
unde b este o constanta, iar p(n) este un polinom in n de grad d. Ideea
generala este ca, prin manipulari convenabile, sa reducem un astfel de
caz la o forma omogena.
De exemplu, o astfel de recurenta poate fi:
tn 2tn-1 = 3n
In acest caz, b = 3 si p(n) = 1, un polinom de grad 0. O simpla
manipulare ne permite sa reducem acest exemplu la forma (). Inmultim
recurenta cu 3, obtinand
3tn ï€Â6tn-1 = 3n+1
Inlocuind pe n cu n1 in recurenta initiala, avem
tn+1 ï€Â2tn = 3n+1
In fine, scadem aceste doua ecuatii
tn+1 ï€Â5tn  6tn-1 = 0
Am obtinut o recurenta omogena pe care o putem rezolva ca in sectiunea
precedenta. Ecuatia caracteristica este:
x2 ï€Â5x  6 = 0
adica (xï€Â2)(xï€Â3) = 0.
Intuitiv, observam ca factorul (xï€Â2) corespunde partii stangi a
recurentei initiale, in timp ce factorul (xï€Â3) a aparut ca rezultat al
manipularilor efectuate, pentru a scapa de parte dreapta.
Generalizand acest procedeu, se poate arata ca, pentru a rezolva
(), este suficient sa luam urmatoarea ecuatie caracteristica:
(a0xk  a1xk-1  ¼  ak)(xï€Âb)d+1 = 0
Odata ce s-a obtinut aceasta ecuatie, se procedeaza ca in cazul omogen.
Vom rezolva acum recurenta corespunzatoare problemei turnurilor din
Hanoi:
tn = 2tn-1  1 n  1
iar t0 = 0. Rescriem recurenta astfel
tn 2tn-1 = 1
care este de forma () cu b = 1 si p(n) = 1, un polinom de grad 0.
Ecuatia caracteristica este atunci (xï€Â2)(xï€Â1) = 0, cu solutiile 1 si
2. Solutia generala a recurentei este:
tn = c11n  c22n
Avem nevoie de doua conditii initiale. Stim ca t0 = 0; pentru a gasi cea
de-a doua conditie calculam
t1 = 2t0 1
Din conditiile initiale, obtinem
tn = 2n ï€Â1
Daca ne intereseaza doar ordinul lui tn, nu este necesar sa calculam
efectiv constantele in solutia generala. Daca stim ca tn = c11n
c22n, rezulta tn  O(2n). Din faptul ca numarul de mutari a unor
discuri nu poate fi negativ sau constant, deoarece avem in mod evident
tn  n, deducem ca c2 > 0. Avem atunci tn  ï—(2n) si deci, tn 
ï‘(2n). Putem obtine chiar ceva mai mult. Substituind solutia generala
inapoi in recurenta initiala, gasim
1 = tn 2tn-1 = c1  c22nï€Â2(c1  c22n-1) = ï€Âc1
Indiferent de conditia initiala, c1 este deci ï€Â1.
5.3.5 Schimbarea variabilei
Uneori, printr-o schimbare de variabila, putem rezolva recurente mult
mai complicate. In exemplele care urmeaza, vom nota cu T(n) termenul
general al recurentei si cu tk termenul noii recurente obtinute printr-o
schimbare de variabila. Presupunem pentru inceput ca n este o putere a
lui 2.
Un prim exemplu este recurenta
T(n) = 4T(n/2)  n n > 1
in care inlocuim pe n cu 2k, notam tk = T(2k) = T(n) si obtinem
tk = 4tk-1  2k
Ecuatia caracteristica a acestei recurente liniare este
(xï€Â4)(xï€Â2) = 0
si deci, tk = c14k  c22k. Inlocuim la loc pe k cu lg n
T(n) = c1n2  c2n
Rezulta
T(n)  O(n2 | n este o putere a lui 2)
Un al doilea exemplu il reprezinta ecuatia
T(n) = 4T(n/2)  n2 n > 1
Procedand la fel, ajungem la recurenta
tk = 4tk-1  4k
cu ecuatia caracteristica
(xï€Â4)2 = 0
si solutia generala tk = c142  c2k42. Atunci,
T(n) = c1n2  c2n2lg n
si obtinem
T(n)  O(n2log n | n este o putere a lui 2)
In fine, sa consideram si exemplul
T(n) = 3T(n/2)  cn n > 1
c fiind o constanta. Obtinem succesiv
T(2k) = 3T(2k-1)  c2k
tk = 3tk-1  c2k
cu ecuatia caracteristica
(xï€Â3)(xï€Â2) = 0
tk = c13k  c22k
T(n) = c13lg n  c2n
si, deoarece
alg b = blg a
obtinem
T(n) = c1nlg 3  c2n
deci,
T(n)  O(nlg 3 | n este o putere a lui 2)
In toate aceste exemple am folosit notatia asimptotica conditionata.
Pentru a arata ca rezultatele obtinute sunt adevarate pentru orice n,
este suficient sa adaugam conditia ca T(n) sa fie eventual
nedescrescatoare. Aceasta, datorita Proprietatii 5.1 si a faptului ca
functiile n2, n log n si nlg 3 sunt netede.
Putem enunta acum o proprietate care este utila ca reteta pentru analiza
algoritmilor cu recursivitati de forma celor din exemplele precedente.
Proprietatea, a carei demonstrare o lasam ca exercitiu, ne va fi foarte
utila la analiza algoritmilor divide et impera din Capitolul 7.
Proprietatea 5.2 Fie T : N ï‚® R+ o functie eventual nedescrescatoare
T(n) = aT(n/b)  cnk n > n0
unde: n0  1, b  2 si k  0 sunt intregi; a si c sunt numere
reale pozitive; n/n0 este o putere a lui b. Atunci avem
5.4 Exercitii
5.1 Care din urmatoarele afirmatii sunt adevarate?
i) n2  O(n3)
ii) n3  O(n2)
iii) 2n+1  O(2n)
iv) (n1)!  O(n!)
v) pentru orice functie f : N  R, f  O(n)  [ f 2  O(n2)]
vi) pentru orice functie f : N  R, f  O(n)  [2 f  O(2n)]
5.2 Presupunand ca f este strict pozitiva pe N, demonstrati ca definitia
lui O( f ) este echivalenta cu urmatoarea definitie:
O( f ) = {t : N  R | (c  R+) (n  N) [t(n)  cf
(n)ïÂÂ}
5.3 Demonstrati ca relatia “ O†este tranzitiva: daca f  O(g)
si g  O(h), atunci f  O(h). Deduceti de aici ca daca g  O(h),
atunci O(g) O(h).
5.4 Pentru oricare doua functii f, g : N  R, demonstrati ca:
i) O( f ) = O(g)  f  O(g) si g  O( f )
ii) O( f )  O(g)  f  O(g) si g O( f )
5.5 Gasiti doua functii f, g : N  R, astfel incat f O(g) si g
O( f ).
Indicatie: f (n) = n, g(n) = n1+sin n
5.6 Pentru oricare doua functii f, g : N  R definim urmatoarea
relatie binara: f  g daca O( f ) O(g). Demonstrati ca relatia
“†este o relatie de ordine partiala in multimea functiilor
definite pe N si cu valori in R.
Indicatie: Trebuie aratat ca relatia este partiala, reflexiva,
tranzitiva si antisimetrica. Tineti cont de Exercitiul 5.5.
5.7 Pentru oricare doua functii f, g : N  R demonstrati ca
O( f  g) = O(max( f, g))
unde suma si maximul se iau punctual.
5.8 Fie f (n) = amnm…a1n  a0 un polinom de grad m, cu am > 0.
Aratati ca f  O(nm).
5.9 O(n2) = O(n3(n2ï€Ân3)) = O(max(n3, n2ï€Ân3)) = O(n3)
Unde este eroarea?
5.10 Gasiti eroarea in urmatorul lant de relatii:
= 12…n  O(12…n) = O(max(1, 2, …, n)) = O(n)
5.11 Fie f , g : N ï‚® R+. Demonstrati ca:
= 0  O( f )  O(g)
Observatie: Implicatiile inverse nu sunt in general adevarate, deoarece
se poate intampla ca limitele sa nu existe.
5.12 Folosind regula lui l’Hôspital si Exercitiile 5.4, 5.11, aratati
ca
O(log n)
.
5.13 Pentru oricare f, g : N  R, demonstrati ca:
f  O(g)  g  ï—( f )
5.14 Aratati ca f  ï‘(g) daca si numai daca
(c, d  R+) (n0  N) (n  n0) [cg(n)  f (n)  dg(n)]
5.15 Demonstrati ca urmatoarele propozitii sunt echivalente, pentru
oricare doua functii f, g : N  R.
i) O( f ) = O(g)
ii) ï‘( f ) = ï‘(g)
iii) f  ï‘(g)
5.16 Continuand Exercitiul 5.11, aratati ca pentru oricare doua functii
f, g : N ï‚® R+ avem:
 R+  f  ï‘(g)
= 0  f  O(g) dar f ï‘(g)
=   f  ï—(g) dar f ï‘(g)
5.17 Demonstrati urmatoarele afirmatii:
i) logan  ï‘(logbn) pentru oricare a, b > 1
 ï‘(log n)
iv) log n!  ï‘(n log n)
Indicatie: La punctul iii) se tine cont de relatia:
= ln n  ï§  1/2n 1/12n2  ...
unde ï§ = 0,5772... este constanta lui Euler.
La punctul iv), din n! < nn, rezulta log n!  O(n log n). Sa aratam
acum, ca log n!  ï—(n log n). Pentru 0 ï‚£ i ï‚£ nï€Â1 este
adevarata relatia
(nï€Âi)(i1)  n
Deoarece
(n)2 = (n×1) ((nï€Â)2) ((nï€Â2)3)...(2(nï€Â1))
(1n)  nn
rezulta 2 log n!  n log n si deci log n!  ï—(n log n).
Punctul iv) se poate demonstra si altfel, considerand aproximarea lui
Stirling:
unde e = 1,71828... .
5.18 Aratati ca timpul de executie al unui algoritm este in ï‘(g), g :
N  R, daca si numai daca: timpul este in O(g) pentru cazul cel mai
nefavorabil si in ï—(g) pentru cazul cel mai favorabil.
5.19 Pentru oricare doua functii f, g : N  R demonstrati ca
ï‘( f ) ï‘(g) = ï‘( f  g) = ï‘(max( f, g)) = max(ï‘( f ),
ï‘(g))
unde suma si maximul se iau punctual.
5.20 Demonstrati Proprietatea 5.1. Aratati pe baza unor contraexemple ca
cele doua conditii “t(n) este eventual nedescrescatoare†si “f
(bn)  O( f (n))†sunt necesare.
5.21 Analizati eficienta urmatorilor patru algoritmi:
for i  1 to n do for i  1 to n do
for j 1 to 5 do for j  1 to i1 do
{operatie elementara} {operatie elementara}
for i  1 to n do for i  1 to n do
for j  1 to 6 do for j  1 to i do
for k 1 to n do for k  1 to n do
{operatie elementara} {operatie elementara}
5.22 Construiti un algoritm cu timpul in ï‘(n log n).
5.23 Fie urmatorul algoritm
k  0
for i  1 to n do
for j  1 to T[i] do
k  kT[ j]
unde T este un tablou de n intregi nenegativi. In ce ordin este timpul
de executie al algoritmului?
. Totusi, algoritmul necesita timp in ordinul lui ï—(n), deoarece
fiecare element al lui T este considerat cel putin o data. Nu am tinut
cont de urmatoarea regula simpla: putem neglija timpul necesar
initializarii si controlului unei bucle, dar cu conditia sa includem
“ceva†de fiecare data cand se executa bucla.
unitati de timp, unde d este o alta constanta. Simplificand, obtinem
(cb)nasd. Timpul t(n, s) depinde deci de doi parametri
independenti n si s. Avem: t  ï‘(ns) sau, tinand cont de
Exercitiul 5.19, t  ï‘(max(n, s)).
5.24 Pentru un tablou T[1 .. n], fie urmatorul algoritm de sortare:
for i  n downto 1 do
for j  2 to i do
if T[ jï€Â1] > T[ j] then interschimba T[ jï€Â1] si T[ j]
Aceasta tehnica de sortare se numeste metoda bulelor (bubble sort).
i) Analizati eficienta algoritmului, luand ca barometru testul din bucla
interioara.
ii) Modificati algoritmul, astfel incat, daca pentru un anumit i nu are
loc nici o interschimbare, atunci algoritmul se opreste. Analizati
eficienta noului algoritm.
5.25 Fie urmatorul algoritm
for i  0 to n do
j  i
while j  0 do j  j div 2
Gasiti ordinul exact al timpului de executie.
5.26 Demonstrati ca pentru oricare intregi pozitivi n si d
Solutie:
Mai ramane sa aratati ca
5.27 Analizati algoritmii percolate si sift-down pentru cel mai
nefavorabil caz, presupunand ca opereaza asupra unui heap cu n elemente.
Indicatie: In cazul cel mai nefavorabil, algoritmii percolate si
sift-down necesita un timp in ordinul exact al inaltimii arborelui
complet care reprezinta heap-ul, adica in ï‘(lg n) = ï‘(log n).
5.28 Analizati algoritmul slow-make-heap pentru cel mai nefavorabil caz.
Solutie: Pentru slow-make-heap, cazul cel mai nefavorabil este atunci
cand, initial, T este ordonat crescator. La pasul i, se apeleaza
percolate(T[1 .. i], i), care efectueaza lg i comparatii intre
elemente ale lui T. Numarul total de comparatii este atunci
C(n) ï‚£ (nï€Â1) lg n  O(n log n)
Pe de alta parte, avem
(lg i 1) = lg n! (nï€Â1)
In Exercitiul 5.17 am aratat ca lg n!  ï—(n log n). Rezulta C(n) 
ï—(n log n) si timpul este deci in ï‘(n log n).
5.29 Aratati ca, pentru cel mai nefavorabil caz, timpul de executie al
algoritmului heapsort este si in ï—(n log n), deci in ï‘(n log n).
5.30 Demonstrati ca, pentru cel mai nefavorabil caz, orice algoritm de
sortare prin comparatie necesita un timp in ï—(n log n). In particular,
obtinem astfel, pe alta cale, rezultatul din Exercitiul 5.29.
Solutie: Orice sortare prin comparatie poate fi interpretata ca o
parcurgere a unui arbore binar de decizie, prin care se stabileste
ordinea relativa a elementelor de sortat. Intr-un arbore binar de
decizie, fiecare varf neterminal semnifica o comparatie intre doua
elemente ale tabloului T si fiecare varf terminal reprezinta o permutare
a elementelor lui T. Executarea unui algoritm de sortare corespunde
parcurgerii unui drum de la radacina arborelui de decizie catre un varf
terminal. La fiecare varf neterminal se efectueaza o comparatie intre
doua elemente T[i] si T[ j]: daca T[i] ï‚£ T[ j] se continua cu
comparatiile din subarborele stang, iar in caz contrar cu cele din
subarborele drept. Cand se ajunge la un varf terminal, inseamna ca
algoritmul de sortare a reusit sa stabileasca ordinea elementelor din T.
Fiecare din cele n! permutari a celor n elemente trebuie sa apara ca
varf terminal in arborele de decizie. Vom lua ca barometru comparatia
intre doua elemente ale tabloului T. Inaltimea h a arborelui de decizie
corespunde numarului de comparatii pentru cel mai nefavorabil caz.
Deoarece cautam limita inferioara a timpului, ne intereseaza doar
algoritmii cei mai performanti de sortare, deci putem presupune ca
numarul de varfuri este minim, adica n!. Avem: n! ï‚£ 2h (demonstrati
acest lucru!), adica h  lg n!. Considerand si relatia log n! 
ï—(n log n) (vezi Exercitiul 5.17), rezulta ca timpul de executie
pentru orice algoritm de sortare prin comparatie este, in cazul cel mai
nefavorabil, in ï—(n log n).
5.31 Analizati algoritmul heapsort pentru cel mai favorabil caz. Care
este cel mai favorabil caz?
5.32 Analizati algoritmii fib2 si fib3 din Sectiunea 1.6.4.
Solutie:
i) Se deduce imediat ca timpul pentru fib2 este in ï‘(n).
ii) Pentru a analiza algoritmul fib3, luam ca barometru instructiunile
din bucla while. Fie nt valoarea lui n la sfarsitul executarii celei
de-a t-a bucle. In particular, n1 = n/2. Daca 2  t  m,
atunci
nt = nt-1/2  nt-1/2
Deci,
nt  nt-1/2  …  n/2t
Fie m = 1  lg n. Deducem:
nm ï‚£ n/2m < 1
Dar, nm  N, si deci, nm = 0, care este conditia de iesire din bucla.
Cu alte cuvinte, bucla este executata de cel mult m ori, timpul lui fib3
fiind in O(log n). Aratati ca timpul este de fapt in ï‘(log n).
La analiza acestor doi algoritmi, am presupus implicit ca operatiile
efectuate sunt independente de marimea operanzilor. Astfel, timpul
necesar adunarii a doua numere este independent de marimea numerelor si
este marginit superior de o constanta. Daca nu mai consideram aceasta
ipoteza, atunci analiza se complica.
5.33 Rezolvati recurenta tn 3tn-1 4tn-2 = 0, unde n  2, iar
t0 = 0, t1 = 1.
5.34 Care este ordinul timpului de executie pentru un algoritm recursiv
cu recurenta tn = 2tn-1  n.
Indicatie: Se ajunge la ecuatia caracteristica (xï€Â2)(xï€Â1)2 = 0, iar
solutia generala este tn = c12n c21n c3n1n. Rezulta t 
O(2n).
Substituind solutia generala inapoi in recurenta, obtinem ca, indiferent
de conditia initiala, c2 = ï€Â2 si c3 = ï€Â1. Atunci, toate solutiile
interesante ale recurentei trebuie sa aiba c1 > 0 si ele sunt toate in
ï—(2n), deci in ï‘(2n).
5.35 Scrieti o varianta recursiva a algoritmului de sortare prin
insertie si determinati ordinul timpului de executie pentru cel mai
nefavorabil caz.
Indicatie: Pentru a sorta T[1 .. n], sortam recursiv T[1 .. nï€Â1] si
inseram T[n] in tabloul sortat T[1 .. nï€Â1].
5.36 Determinati prin schimbare de variabila ordinul timpului de
executie pentru un algoritm cu recurenta T(n) = 2T(n/2)  n lg n, unde
n > 1 este o putere a lui 2.
Indicatie: T(n)  O(n log2n | n este o putere a lui 2)
5.37 Demonstrati Proprietatea 5.2, folosind tehnica schimbarii de
variabila.
ì¥Â@