Cap01 PDF
Cap01 PDF
Cap01 PDF
Preliminarii
1
2 Preliminarii Capitolul 1
Un algoritm este compus dintr-o multime finita de pasi, fiecare necesitand una sau
mai multe operatii. Pentru a fi implementabile pe calculator, aceste operatii
trebuie sa fie in primul rand definite, adica sa fie foarte clar ce anume trebuie
executat. In al doilea rand, operatiile trebuie sa fie efective, ceea ce inseamna ca –
in principiu, cel putin – o persoana dotata cu creion si hartie trebuie sa poata
efectua orice pas intr-un timp finit. De exemplu, aritmetica cu numere intregi este
efectiva. Aritmetica cu numere reale nu este insa efectiva, deoarece unele numere
sunt exprimabile prin secvente infinite. Vom considera ca un algoritm trebuie sa
se termine dupa un numar finit de operatii, intr-un timp rezonabil de lung.
Programul este exprimarea unui algoritm intr-un limbaj de programare. Este bine
ca inainte de a invata concepte generale, sa fi acumulat deja o anumita experienta
practica in domeniul respectiv. Presupunand ca ati scris deja programe intr-un
limbaj de nivel inalt, probabil ca ati avut uneori dificultati in a formula solutia
pentru o problema. Alteori, poate ca nu ati putut decide care dintre algoritmii care
rezolvau aceeasi problema este mai bun. Aceasta carte va va invata cum sa evitati
aceste situatii nedorite.
Studiul algoritmilor cuprinde mai multe aspecte:
i) Elaborarea algoritmilor. Actul de creare a unui algoritm este o arta care nu
va putea fi niciodata pe deplin automatizata. Este in fond vorba de
mecanismul universal al creativitatii umane, care produce noul printr-o
sinteza extrem de complexa de tipul:
tehnici de elaborare (reguli) + creativitate (intuitie) = solutie.
Un obiectiv major al acestei carti este de a prezenta diverse tehnici
fundamentale de elaborare a algoritmilor. Utilizand aceste tehnici, acumuland
si o anumita experienta, veti fi capabili sa concepeti algoritmi eficienti.
ii) Exprimarea algoritmilor. Forma pe care o ia un algoritm intr-un program
trebuie sa fie clara si concisa, ceea ce implica utilizarea unui anumit stil de
programare. Acest stil nu este in mod obligatoriu legat de un anumit limbaj de
programare, ci, mai curand, de tipul limbajului si de modul de abordare.
Astfel, incepand cu anii ‘80, standardul unanim acceptat este cel de
programare structurata. In prezent, se impune standardul programarii
orientate pe obiect.
iii) Validarea algoritmilor. Un algoritm, dupa elaborare, nu trebuie in mod
necesar sa fie programat pentru a demonstra ca functioneaza corect in orice
situatie. El poate fi scris initial intr-o forma precisa oarecare. In aceasta
forma, algoritmul va fi validat, pentru a ne asigura ca algoritmul este corect,
independent de limbajul in care va fi apoi programat.
iv) Analiza algoritmilor. Pentru a putea decide care dintre algoritmii ce rezolva
aceeasi problema este mai bun, este nevoie sa definim un criteriu de apreciere
a valorii unui algoritm. In general, acest criteriu se refera la timpul de calcul
si la memoria necesara unui algoritm. Vom analiza din acest punct de vedere
toti algoritmii prezentati.
Sectiunea 1.1 Ce este un algoritm? 3
45 19 19
22 38
11 76 76
5 152 152
2 304
1 608 608
855
de sub inmultitor. Se aplica regula, pana cand numarul de sub deinmultit este 1. In
final, adunam toate numerele din coloana inmultitorului care corespund, pe linie,
unor numere impare in coloana deinmultitului. In cazul nostru, obtinem:
19 + 76 + 152 + 608 = 855.
Cu toate ca pare ciudata, aceasta este tehnica folosita de hardware-ul multor
calculatoare. Ea prezinta avantajul ca nu este necesar sa se memoreze tabla de
inmultire. Totul se rezuma la adunari si inmultiri/impartiri cu 2 (acestea din urma
fiind rezolvate printr-o simpla decalare).
Pentru a reprezenta algoritmul, vom utiliza un limbaj simplificat, numit
pseudo-cod, care este un compromis intre precizia unui limbaj de programare si
usurinta in exprimare a unui limbaj natural. Astfel, elementele esentiale ale
algoritmului nu vor fi ascunse de detalii de programare neimportante in aceasta
faza. Daca sunteti familiarizat cu un limbaj uzual de programare, nu veti avea nici
o dificultate in a intelege notatiile folosite si in a scrie programul respectiv.
4 Preliminarii Capitolul 1
algoritm de sortare (quicksort) cu timp patratic pentru cel mai nefavorabil caz, dar
cu timpul mediu in ordinul lui n log n. (Prin log notam logaritmul intr-o baza
oarecare, lg este logaritmul in baza 2, iar ln este logaritmul natural). Deci, pentru
cazul mediu, quicksort este foarte rapid.
Analiza comportarii in medie a unui algoritm presupune cunoasterea a priori a
distributiei probabiliste a cazurilor considerate. Din aceasta cauza, analiza pentru
cazul mediu este, in general, mai greu de efecuat decat pentru cazul cel mai
nefavorabil.
Atunci cand nu vom specifica pentru ce caz analizam un algoritm, inseamna ca
eficienta algoritmului nu depinde de acest aspect (ci doar de marimea cazului).
1.6 Exemple
Poate ca va intrebati daca este intr-adevar posibil sa acceleram atat de spectaculos
un algoritm. Raspunsul este afirmativ si vom da cateva exemple.
1.6.1 Sortare
Algoritmii de sortare prin insertie si prin selectie necesita timp patratic, atat in
cazul mediu, cat si in cazul cel mai nefavorabil. Cu toate ca acesti algoritmi sunt
excelenti pentru cazuri mici, pentru cazuri mari avem algoritmi mai eficienti. In
capitolele urmatoare vom analiza si alti algoritmi de sortare: heapsort, mergesort,
quicksort. Toti acestia necesita un timp mediu in ordinul lui n log n, iar heapsort
si mergesort necesita timp in ordinul lui n log n si in cazul cel mai nefavorabil.
Pentru a ne face o idee asupra diferentei dintre un timp patratic si un timp in
ordinul lui n log n, vom mentiona ca, pe un anumit calculator, quicksort a reusit
sa sorteze in 30 de secunde 100.000 de elemente, in timp ce sortarea prin insertie
ar fi durat, pentru acelasi caz, peste noua ore. Pentru un numar mic de elemente
insa, eficienta celor doua sortari este asemanatoare.
n
det( M ) = ∑ ( −1) j +1 a1 j det( M 1 j )
j =1
Un prim algoritm pentru aflarea celui mai mare divizor comun al intregilor
pozitivi m si n, notat cu cmmdc(m, n), se bazeaza pe definitie:
function cmmdc-def (m, n)
i ← min(m, n) + 1
repeat i ← i − 1 until i divide pe m si n
return i
Timpul este in ordinul diferentei dintre min(m, n) si cmmdc(m, n).
Exista, din fericire, un algoritm mult mai eficient, care nu este altul decat celebrul
algoritm al lui Euclid.
function Euclid(m, n)
if n = 0 then return m
else return Euclid(n, m mod n)
Prin m mod n notam restul impartirii intregi a lui m la n. Algoritmul functioneaza
pentru orice intregi nenuli m si n, avand la baza cunoscuta proprietate
cmmdc(m, n) = cmmdc(n, m mod n)
Timpul este in ordinul logaritmului lui min(m, n), chiar si in cazul cel mai
nefavorabil, ceea ce reprezinta o imbunatatire substantiala fata de algoritmul
precedent. Pentru a fi exacti, trebuie sa mentionam ca algoritmul originar al lui
Euclid (descris in “Elemente”, aprox. 300 a.Ch.) opereaza prin scaderi succesive,
si nu prin impartire. Interesant este faptul ca acest algoritm se pare ca provine
dintr-un algoritm si mai vechi, datorat lui Eudoxus (aprox. 375 a.Ch.).
12 Preliminarii Capitolul 1
f 0 = 0; f 1 = 1
f n = f n −1 + f n − 2 pentru n≥2
Acest celebru sir a fost descoperit in 1202 de catre Leonardo Pisano (Leonardo
din Pisa), cunoscut sub numele de Leonardo Fibonacci. Cel de-al n-lea termen al
sirului se poate obtine direct din definitie:
function fib1(n)
if n < 2 then return n
else return fib1(n−1) + fib1(n−2)
Aceasta metoda este foarte ineficienta, deoarece recalculeaza de mai multe ori
aceleasi valori. Vom arata in Sectiunea 5.3.1 ca timpul este in ordinul lui φ , unde
n
h ← 2kh+t
k ← k +t
2
n ← n div 2
return j
Va recomandam sa comparati acesti trei algoritmi, pe calculator, pentru diferite
valori ale lui n.
Secþiunea 1.7 Exercitii 13
1.7 Exercitii
1.1 Aplicati algoritmii insert si select pentru cazurile T = [1, 2, 3, 4, 5, 6] si
U = [6, 5, 4, 3, 2, 1]. Asigurati-va ca ati inteles cum functioneaza.
1.2 Inmultirea “a la russe” este cunoscuta inca din timpul Egiptului antic,
fiind probabil un algoritm mai vechi decat cel al lui Euclid. Incercati sa intelegeti
rationamentul care sta la baza acestui algoritm de inmultire.
Indicatie: Faceti legatura cu reprezentarea binara.
1.4 Elaborati un algoritm care sa returneze cel mai mare divizor comun a trei
intregi nenuli.
Solutie:
function Euclid-trei(m, n, p)
return Euclid(m, Euclid(n, p))
1.6 Elaborati un algoritm care returneaza cel mai mare divizor comun a doi
termeni de rang oarecare din sirul lui Fibonacci.
Indicatie: Un algoritm eficient se obtine folosind urmatoarea proprietate *,
valabila pentru oricare doi termeni ai sirului lui Fibonacci:
cmmdc( f m , f n ) = f cmmdc(m, n)
0 1
1.7 Fie matricea M = . Calculati produsul vectorului ( f n−1 , f n ) cu
1 1
m
matricea M , unde f n−1 si f n sunt doi termeni consecutivi oarecare ai sirului lui
Fibonacci.
*
Aceastã surprinzãtoare proprietate a fost descoperitã în 1876 de Lucas.