Sunteți pe pagina 1din 10

Rezolvari probleme liste

Mijlocul unei liste (5p)


Cea mai simpla solutie presupune numararea elementelor din lista, si apoi
parcurgerea listei pana la index mijloc. Daca consideram elementele indexate de la 0 atunci,
conform cerintei, daca n = 2k+1 indexul elementului din mijloc este k, iar pentru n = 2k tot k.
Asadar, index mijloc = n / 2. Complexitatea solutiei este O(N), impartita astfel: O(N) pentru a
gasi numarul de elemente si O(N) pentru a gasi mijlocul. Codul este prezentat mai jos.

Exista totusi si o solutie mai scurta, ce foloseste idea de nod incet / rapid. Mai exact,
nodul incet parcurge lista element cu element, in timp ce nodul rapid o parcurge din 2 in 2.
Atunci cand nodul rapid ajunge la finalul listei, nodul incet se afla fix la mijlocul ei (dupa
definitia din cerinta). Pentru ce nodul rapid este mereu in fata celui incet, trebuie sa-l
verificam doar pe el ca nu atinge finalul. De asemenea, pentru ca sare din 2 in 2, trebuie sa
veriicam ca exista atat el, cat si elementul urmator (atlfel nu ar putea ajunge peste 2
elemente). Algoritmul are aceeasi complexitate O(N), dar se scrie mai usor. Codul este
prezentat mai jos.
Este o lista ciclica? (3p)
Solutia primitiva, ca si in cazul anterior este sa facem fix ce ne spune problema. Mai
exact, parcurgem lista in timp ce numaram pozitia actuala. Pentru fiecare nod, verificam
daca prima lui aparitie in lista coincide cu indexul. Daca nu, inseamna ca nodul s-a repetat,
deci avem ciclu. Codul este prezentat mai jos.

Complexitatea solutiei este O(N​2​), si din pacate nu intra in timp pentru testul cel
mare. Este necesar deci sa gasim o solutie mai eficienta.

Revenim la idea de nod incet / rapid. Stim deja ca daca lista nu este ciclica, nodul
rapid ajunge la final in complexitate O(N). Ce se intampla totusi daca lista are ciclica? Sa
presupunem ca lista este ciclica, si ciclul incepe pe o pozitie aleatoare. Atunci cand nodul
incet intra in ciclul, nodul rapid deja se afla in el. Fie K distanta dintre cele 2 cand nodul incet
intra in ciclu. La fiecare pas, distanta dintre cele noduri creste cu 1. Fie C lungimea ciclului.
Daca la fiecare pas distanta creste, inseamna ca va exista un moment cand distanta va fi
egala cu C. Acest lucru inseamna ca cele 2 noduri se suprapun (mai exact, nodul rapid a
facut unul sau mai multe cicluri intregi si a ajuns peste cel incet). Deci avem nevoie sa
creasca distanta de la K la C. In cel mai rau caz K = 1 si C = N, deci avem nevoie de N
operatii, de unde rezulta complexitatea solutiei ca fiind O(N) si pentru cazul in care lista este
ciclica. Codul este prezentat mai jos.
Este o lista palindrom? (2p)
Solutia primitiva este formata din mai multi pasi. Primul este sa construim o lista
noua, dar care contine valorile din lista originala in ordine inversa. Pentru a realiza acest
lucru, parcurgem lista originala, construim un nou nod ce contine aceeasi informatie ca cel
din lista originala si-l legam de nodul anterior creat. Complexitatea acestui pas este O(N).
Codul este prezentat mai jos.

Dupa ce avem atat lista originala, cat si cea inversata, trebuie sa le comparam
element cu element. Daca 2 elemente sunt diferite, lista nu e palindom. Complexitatea
pasului este O(N). Codul este prezentat mai jos.
Functia de verificare nu tine cont de faptul ca cele 2 liste au acelasi numar de
elemente, motiv pentru care verifica ca ambele sa ajunga la finalul lor, in loc sa intoarca
direct valoarea adevarat.

Nu in ultimul rand, trebuie tinut cont de faptul ca am construit o noua lista, deci
trebuie sa o distrugem. Stergem pe rand fiecare nod din lista inversata. Complexitatea
acestui pas este O(N). Complexitatea finala a algoritmului este O(N) + O(N) + O(N) = O(N).
Codul pentru stergerea listei, cat si pentru combinarea pasilor este prezentat mai jos.

Desi algoritmul are complexitate O(N) si primeste punctaj maxim, solutia nu este
eficienta deoarece construieste o noua lista pentru care dubleaza memoria listei originale.

Pentru a rezolva problema fara spatiu suplimentar, inversam a 2-a jumatate a listei.
Folosind ideile din prima problema, gasim elementul de dinainte de mijloc. Indexul lui este
(n-1)/2. Adaptam functia de inversat sa modifice lista (in loc sa construiasca una noua), si
modificam si functia care verifica daca 2 liste sunt egale astfel incat sa verifice daca valorile
din 2 serii de noduri sunt egale pana unul dintre ele ajunge la null. Codul este prezentat mai
jos. Functiile care nu sunt afisate au fost deja prezentate.
Mediana unei liste (bonus)
Solutia primita de a sorta lista si apoi a extrage elementul din mijloc are o
complexitate de O(N * logN) motiv pentru care nu trece nici un test.

Prima observatie importanta este faptul ca gasirea medianei reprezinta un caz


particular al unei probleme mai generale: gasirea celui mai mic K element intr-o colectie
nesortata. Mediana (atunci cand colectia are un numar impar de elemente, lucru garantat de
cerinta) are indexul N / 2.

Problema generala, gasirea celui mai mic K element intr-o colectie nesortata poarta
numele de problema de selectie. Mai multe informatii despre ea puteti gasi la
https://en.wikipedia.org/wiki/Selection_algorithm

Orice algoritm ce rezolva problema de selectie in complexitatea de timp O(N) putea fi


folosit pentru a rezolva problema. In continuare se va descrie idea algoritmului quickSelect si
cum va fi prezentata implementarea lui peste liste.

Idea principala a algoritmul quickSelect este acea de a imparti colectie in 3 colectii


disjuncte in functie de un pivot. Impartirea se face astfel: o colectie contine doar elemente
mai mici decat pivotul, o colectie contine elementele egale cu pivotul si ultima colectie
contine elementele mai mari decat pivotul.

Pentru a evita crearea de noduri suplimentare, si implicit de memorie extra,


majoritatea solutiilor construiesc “in-place” cele 3 colectii. Acelasi lucru se va realiza si aici.
Mai exact, fie o lista definita prin nodul de inceput si nodul de final, cu garantia ca urmatorul
element de dupa nodul de final este pointer null. Semnatura functiei este prezentata mai jos:

Alegem ca pivot primul element din lista. Deoarece se garanteaza faptul ca lista va fi
generata aleator, ne putem astepta ca numarul de elemente mai mici decat pivotul sa fie
egal cu numarul de elemente mai mari decat pivotul. De asemenea, declaram si cele 3
colectii mentionate anterior. Adaugam in plus si numarul de elemente pentru fiecare colectie
in parte.

Aici variabilele simple (l, g, e) reprezinta capetele listelor, variabilele terminate in c


reprezinta capatul lor, iar variabilele ce incep cu n numarul lor de elemente.
Parcurgem apoi lista, si pentru fiecare element, in functie de cum se rapoarteaza la
pivot, il adaugam in colectia aferenta. Adaugarea reprezinta defapt modificarea legaturilor
pentru a nu ocupa memorie suplimentara. Mai jos este prezentat cazul pentru elemente mai
mici decat pivotul, in mod similar se realizeaza si pentru celelalte elemente.

Dupa ce am parcurs toata lista trebuie sa ne asiguram ca cele 3 noi liste sunt valid
conform definitiei (dupa ultimul element urmeaza pointerul null). In mod sigur exista
elemente egale cu pivotul, pentru celelalte 2 liste trebuie verificat daca a existat un astfel de
element.

In continuare trebuie sa decidem daca cautam continuarea, si daca da, in ce lista.


Astfel, stim ca ne intereseaza elementul k. Daca k < nl, atunci elementul care ne intereseaza
se afla in lista de mai mici si este tot cel mai mic k. Altfel, daca k < nl + ne atunci elementul
se afla in lista de egale. Din moment ce toate elementele din lista de egale sunt egale cu
pivotul, rezulta ca elementul cautat este pivotul. Altfel, el se afla in lista de mai mari, dar
pozitia lui este (k - nl - ne).
Nu in ultimul rand, trebuie refacuta lista originala in functie de cele 3 colectii disjucte.
Daca exista elemente mai mici, atunci primul element al listei originale va fi primul element
dintre cele mici, iar dupa cele mici va urma liste de elemente egale. Daca nu exista elemente
mai mici, primul element va fi primul element al listei celor egale. Din nou, este garantat ca
cel putin un element va fi egal cu pivotul. Intotdeauna, dupa egale va fi lista de elemente mai
mari (chiar si cand nu exista, rezulta ca dupa egale urmeaza null ceea ce e corect). In
schimb, daca exista elemente mai mari, finalul listei originale va fi finalul listei de elemente
mai mari, altfel finalul listei de elemente egale.

La sfarsit, lista va fi partial sortata. Daca nu ne dorim sa modificam lista, atunci


trebuie sa construim liste noi, ceea ce ne costa memorie suplimentara.

La fiecare pas, datorita generarii aleatoare a listei, ne asteptam ca lista pe care o


avem de verificat sa contina jumatate din elementele anterioare. Daca prima lista are N
elemente, a 2-a va avea N/2, etc.

Asadar, numarul de operatii al algoritmului este N + N / 2 + N / 4 + … = N (1 + ½ + ¼


+ ..) = N * 2, deci complexitatea lui va fi O(N).

S-ar putea să vă placă și