Sunteți pe pagina 1din 6

Algoritmi de dispersare si rezolvarea coliziunilor prin inlantuire si adresare deschisa Dispersare prin inlantuire In inlantuire, punem toate elementele

de dispersare in aceeasi pozitie intr-o lista inlantuita, dupa cum se vede in fig. 3. Pozitia j (0 <= j <= m 1) contine un pointer in capul listei tuturor elementelor stocate care disperseaza in j; daca nu sunt asemenea elemente, pozitia j contine NIL. In general, daca exista n chei si m liste inlantuite, marimea medie a listei este n/ m; astfel dispersia descreste volumul de munca necesar pentru pentru cautare secventiala cu aproximativ m. Un set SU este reprezentat ca m liste liniare; lista i contine toate elementele xS cu h(x) = i. Operatia Access (x, S) este realizata de urmatorul program: 1) calculeaza h(x) 2) cauta elementul x in lista T[h(x)] Operatiile Insert (x, S) si Delete (x, S) sunt implementate similar. Trebuie doar sa adaugam x sau sa stergem x din lista T[h(x)]. Adesea este o idee buna sa pastram listele individuale in ordine dupa cheie, astfel incat insertiile si cautarile fara succes sa mearga mai repede. Alternativ putem utiliza conceptul de fisier autoorganizat; mai degraba decat pastram listele in ordine dupa cheie, acestea pot fi tinute in ordine in acord cu timpul celui mai recent eveniment. De dragul vitezei am putea face pe m prea mare. Dar cand m este mare, multe din liste vor fi goale si mult din spatiul alocat pentru capetele listelor m va fi pierdut. Aceasta sugereaza o alta abordare, cand inregistrarile sunt mici: putem suprapune partial stocarea inregistrarilor cu capetele listei, facand loc unui total de m inregistrari si m link-uri in loc de n inregistrari si m+n link-uri. Urmatorul algoritm este un mod convenabil de a rezolva problema. Algoritmul C : (Algoritm pentru dispersie prin inlantuire) Acest algoritm cauta intr-o tabela cu m noduri o cheie data K. Daca K nu este in tabela si tabela nu este plina, K este inserat. Nodurile tabelei sunt notate cu TABLE[i], pentru 0 <= i <= m, si exista doua tipuri distincte, goale si ocupate. Un nod ocupat contine un camp pentru cheie KEY[i], un camp de legatura LINK[i], si posibil alte campuri. Algoritmul utilizeaza o functie de dispersie h(K). Este utilizata, de asemenea, o variabila auxiliara R, pentru a ne ajuta sa gasim spatiile goale, avem R = m + 1, si pe masura ce sunt facute inserarile va fi intotdeauna adevarat ca TABLE[j] este ocupat de toti j de rang R <= j <= m. Prin conventie, TABLE[0] va fi intotdeauna goala. 1. Set i h(K) + 1 (acum 0 <= i <= m). 2. If TABLE[i] este goala, go to 6. (Altfel TABLE[i] este ocupata; vom urmari lista nodurilor ocupate care incepe aici) 3. If K = KEY[i], algoritmul se termina cu succes. 4. If LINK[i] A, set i LINK[i] si go back to 3. 5. (Cautarea a fost fara succes si vrem sa gasim o pozitie goala in tabela) Decrementam R o data sau de mai multe ori pana cand gasim o valoare pentru care TABLE[R] este goala. Daca R = 0, algorimul se termina in mod cert (nu mai raman noduri goale); altfel set LINK[i] R, i R.

6. (Inseram o cheie noua) Marcam TABLE[i] ca un nod ocupat, cu KEY[i] K si LINK[i] . Acest algoritm permite fuzionarea mai multor liste, deci inregistrarile nu mai trebuie mutate dupa ce au fost inserate in tabela. De exemplu, vezi fig. 4, daca m = 7 si cheile 5, 19, 42, 75, 23, 18, 50 au fost inserate in aceasta ordine, pentru h(K) = K (mod 7). Deci 18 apare in aceeasi lista cu 5, 19 si 75, dar avem h(5) = h(19) = h(75) = 5, si h(18) = 4. Vom face unele consideratii de complexitate pentru cazul in care oricare doua liste nu fuzioneaza. Timpul complex al dispersarii prin inlantuire este usor de determinat: timpul care evalueaza functia de dispersie h plus timpul de cautare in lista T[h(x)]. Presupunem pentru aceasta sectiune ca h poate fi evaluat in timp constant si, prin urmare, definim costul operatiei referitoare la cheia x ca O(1 + h(x, S)) unde S este un set de elemente memorate si h(x, S) = yS h(x, y), si h(x, y) = 1, daca x y si h(x) = h(y) 0, in celelalte cazuri Cel mai defavorabil caz de complexitate al dispersiei este usor de determinat. Cel mai defavorabil caz ia nastere cand functia de dispersie h restrictioneaza setul S sa fie constant, astfel incat h(x) = i0, pentru toti xS. Atunci dispersia deterioreaza cautarea printr-o lista liniara; fiecare trei operatii costa O(S) unitati de timp. Dispersare prin adesare deschisa Alta metoda de a rezolva problema coliziunilor este sa terminam complect cu inlantuirea, si doar sa urmarim diferitele intrari din tabela una cate una pana gasim sau cheia K, sau o pozitie goala. Ideea este sa formulam o regula prin care fiecare cheie K determina un sir de verificare, si anume un sir al pozitiilor tabelei care trebuie sa fie inspectate oricand K este inserat sau urmarit. Daca intalnim o pozitie goala cat timp cautam dupa K, utilizand sirul de verificare determinat de K, putem concluziona ca K nu este in tabela, din moment ce acelasi sir de verificare va lua nastere de fiecare data cand K este procesat. Aceasta clasa generala de metode a fost numita adresare deschisa. Astfel, in adresarea directa, toate elementele sunt memorate in chiar tabela de dispersie; tabela de dispersie se poate umple, astfel incat nici o alta insertie sa nu poata fi facuta. Factorul de incarcare nu poate depasi valoarea 1. Avantajul adresarii deschise este ca evita cu desavarsire pointer-ii. In locul urmaririi pointer-ilor, vom evalua sirul de pozitii care urmeaza a fi examinat. Memoria disponibila eliberata de lipsa memorarii pointer-ilor asigura tabela de dispersie cu un numar mai mare de pozitii pentru acelasi volum de memorie, potential producand mai putine coliziuni si o restabilire mai rapida. Cea mai simpla schema de adresare deschisa, cunoscuta ca verificare liniara, utilizeaza sirul de proba ciclic h(K), h(K) 1,..., 0, m 1, m 2,..., h(K) + 1 (formula 1) ca in urmatorul algoritm. Algoritmul L (Cautarea si insertia intr-o tabela de imprastiere cu adresare deschisa) Acest algoritm cauta intr-o tabela de m noduri o cheie data K. Daca K nu este in tabela si tabela nu este plina, K este inserat. Nodurile tabelei sunt date de TABLE[i], pentru 0 <= i

<= m 1, si exista doua tipuri distincte, liber si ocupat. Un nod ocupat contine o cheie numita KEY[i], si posibil alte campuri. O variabila auxiliara n este utilizata pentru a tine cont de cate noduri sunt ocupate; aceasta variabila este considerata ca facand parte din tabela, si este incrementata cu 1 ori de cate ori o cheie noua este inserata. Acest algoritm utilizeaza o functie de dispersie h(K) si sirul de verificare liniara (formula 1) tabela de adresare. 1. Set i h(K). (Acum 0 <= i <= m 1). 2. Daca TABLE[i] este goala, go to 4. Altfel daca KEY[i] = K, algoritmul se termina cu succes. 3. Set i i 1; daca acum i < 0, set i i + m. Go back to step 2. 4. (Cautarea a fost fara succes) Daca n = m 1, algoritmul se termina in mod cert. (Acest algoritm considera tabela ca este plina cand n = m 1, nu cand n = m). Altfel set n n + 1, marcam TABLE[i] ocupat, si set KEY[i] K. De exemplu, vezi fig. 1, unde m = 7 si cheile 5, 19, 42, 75, 23, 18 au fost inserate prin algoritmul L in aceasta ordine cu functia de dispersie h(K) = K (mod 7). Acum n = m 1 = 6 si tabela este plina; nici o alta insertie nu se mai poate efectua. Experienta cu verificarea liniara ne spune ca algoritmul merge bine pana cand tabela incepe sa se umple; dar in cele din urma procesul incetineste, cu cautari lungi indecise aparand din ce in ce mai frecvent. Ulterior performanta verificarii liniare descreste rapid cand n se apropie de m din moment ce liste separate sunt combinate in liste lungi unde cautarea este inceata. De fapt, cand n = m 1, exista doar un loc vacant in tabela, deci numarul mediu de verificari intr-o cautare fara succes este (m +1)/ 2. Un mod de a asigura protectia impotriva problemelor consecutive ale codului de dispersie este sa folosim urmatoarea idee: mai degraba decat sa fie fixat intr-o ordine (formula 1) implicand pozitii consecutive ale tabelei, sirul pozitiilor verificate depinde de cheia care a fost inserata. Pentru a determina ce pozitii sa verificam, extindem functia de dispersie sa includa numarul de varificare (incepand cu 0) ca o a doua intrare. Astfel, functia de dispersie devine h: U {0, 1,..., m 1} {0, 1,..., m 1}. In adresare directa, avem nevoie ca pentru fiecare cheie K sirul de verificare < h(K, 0), h(K, 1),..., h(K, m 1) > sa fie o permutare a lui < 0, 1,..., m 1 >, astfel incat fiecare pozitie din tabela de dispersie sa fie considerata eventual ca un pozitie pentru o cheie noua, pe masura ce tabela se umple. Urmarind aceasta strategie de insertie si cautare, algoritmul L devine: Algoritmul L1 (Cautarea si insertia intr-o tabela de imprastiere cu adresare deschisa utilizand o functie de dispersie cu doua variabile) 1. Set i 0. 2. Set j h(K, i). 3. Daca TABLE[j] este goala, go to 5. Altfel, daca KEY[j] = K, algoritmul se termina cu succes. 4. Daca i = m 1, algoritmul se termina in mod cert. Altfel, set i i + 1 si go back to step 2. 5. Marcam TABLE[j] ocupata, si set KEY[j] K. (Cheia K a fost inserata in tabela).

Intrucat algoritmul de cautare pentru cheia K verifica acelasi sir de pozitii examinate atunci cand a fost inserata cheia K, cautarea se poate termina (fara succes) cand gaseste o pozitie libera, din moment ce K fusese inserat acum si nu mai tarziu in sirul sau de verificare. (Mentionam ca acet argument presupune ca cheile nu sunt sterse din tabela de dispersie) Stergerea dintr-o tabela de dispersie cu adresare deschisa este dificila. Cand stergem o cheie din pozitia i, nu putem marca pur si simplu aceasta pozitie ca libera. Facand asa putem ajunge in imposibilitatea recuperarii vreunei chei K in timpul carei insertii am verificat pozitia i si am gasit-o ocupata. Din acest motiv inlantuirea este mai des aleasa ca o solutie tehnica pentru coliziuni cand trebuie sterse cheile. Trei tehnici sunt utilizate in mod uzual pentru a calcula sirul de verificare necesar pentru adresarea deschisa: verificarea liniara, verificarea patratica si dispersia dubla. Toate aceste tehnici garanteaza ca < h(K, 0), h(K, 1),..., h(K, m 1) > este o permutare a lui < 0, 1,..., m 1 > pentru oricare cheie K. ............................................................. a) Verificarea liniara Dandu-se o functie ordinara de dispersie h`: U {0, 1,..., m 1}, metoda verificarii liniare h(K, i) = h`(K) + i(mod m), pentru i = 0, 1,..., m 1. Mentionam ca formula 1 corespunde cu h(K, i) = h`(K) i(mod m). Dandu-se cheia K, prima pozitie verifica T[h`(K)]. Verificam apoi pozitia T[h`(K) + 1], si asa mai departe pana la pozitia T[m 1]. Apoi ajungem la pozitiile T[0], T[1],..., pana cand, in final, verificam pozitia T[h`(K) 1]. Verificarea liniara este usor de implementat, dar sufera de o problema cunoscuta ca clustering primar. Indepartarea de sloturile ocupate construite creste media timpului de cautare. b) Verificarea patratica utilizeaza o functie de dispersie de forma h(K, i) = h`(K) + c1i + c2i2(mod m), unde h` este o functie de dispersie auxiliara, c1, c2 0 sunt constante auxiliare, si i = 0, 1,..., m 1. Pozitia initiala verifica T[h`(K)]; pozitiile ulterioare verificate sunt compensate prin sume care depind de patratul numarului verificarii i. Aceasta metoda lucreaza mai bine decat decat verificarea liniara, dar pentru a utiliza la maximum tabela de dispersie, valorile c1, c2 si m sunt restrictionate. De asemenea, daca doua chei au aceeasi pozitie initiala de verificare, atunci sirurile lor de proba sunt aceleasi, intrucat h(K1, 0) = h(K2, 0) implica h(K1, i) = h(K2, i) pentru orice i. Aceasta duce la o forma mai usoara de clustering numita clustering secundar. La fel ca in verificarea liniara, verificarea initiala determina tot sirul, deci sunt utilizate numai m siruri de verificari distincte. Un mod de a selecta parametrii c1si c2 este sa impunem ca c1i + c2i2 = 1 + 2 +...+ i, astfel incat c1i + c2i2 = i(i + 1)/ 2, care implica c1 = c2 = 1/2. Lema 1 Daca m = 2s pentru s intreg, s >= 1 si h(K, i) = h`(K) + i/ 2 + i2/ 2(mod 2s), unde h`(K) este o functie de dispersie auxiliara, atunci pentru oricare cheie K, <h(K, 0), h(K, 1),..., h(K, m 1)> este o permutare a <0, 1,..., m 1>. Demonstratie : Vom arata ca pentru fiecare i = 0, 1,..., m 1 toate valorile h`(K) + i/ 2 + i2/ 2(mod 2s) sunt diferite doua cate doua, care va demonstra afirmatia. Presupunem,

dimpotriva, ca exista doi indici i, j {0, 1,..., m 1}, i > j, astfel incat h`(K) + i/ 2 + i2/ 2 h`(K) + j/ 2 + j2/ 2(mod 2s). Aceasta impica faptul ca exista un intreg p > 0 pentru care (i2 j2 + i j)/ 2 = p2s, sau (i j)(i + j + 1) = p2s+1. (formula 2) In aceasta egalitate i j si i + j au aceeasi paritate, intrucat diferenta lor este 2j, indiferent de numar. Prin urmare cea mai mare putere a lui 2 in primul divizor al lui (i j)(i + j + 1) este mai mic sau egal cu primul divizor al numerelor din setul {1,..., i + j + 1}. Dar i + j + 1 <= m 1 + m 2 + 1 = 2m 2 = 2s+1 2. Rezulta ca aceasta cea mai mare putere a lui 2 este 2s, care contrazice formula 2. Proprietatea este demonstrata. c) Dispersia dubla este una dintre cele mai bune metode disponibile pentru adresarea deschisa deoarece permutarile produse au multe din caracteristicile permutarilor aleatoare. Dispersia dubla utilizeaza o functie de dispersie de forma: h(K, i) = h1(K) + ih2(K)(mod m), unde h1 si h2 functii auxiliare de dispersie si h2(K) {0, 1,..., m 1} pentru oricare K. Pozitia verificata initial este T[h1(K)]; pozitiile verificarii succesive sunt compensate fata de pozitiile precedente cu suma h2(K)(mod m). Astfel, spre deosebire de cazurile verificarilor liniare si patratice, sirul de verificare de aici depinde in doua moduri de cheia K, intrucat pozitia verificarii initiale, compensatia, sau amandoua, pot varia. In fig. 2 se da un exemplu de insertie prin dispersie dubla. Fie m = 7 (un numar prim), h1(x) = x(mod 7) si h2(x) = 1 + (x(mod 4)). Daca inseram 5, 19, 42, 75, 23, 18, 50 in aceasta ordine, obtinem H(5, i) = 5 + 2i(mod 7), i = 0 merge H(19, i) = 5 + 4i(mod 7), i = 1 merge H(42, i) = 3i(mod 7), i = 0 merge H(75, i) = 5 + 4i(mod 7), i = 2 merge H(23, i) = 2 + 4i(mod 7), i = 2 merge H(18, i) = 4 + 3i(mod 7), i = 0 merge H(50, i) = 1 + 3i(mod 7), i = 0 merge Valoarea h2(K) trebuie sa fie prima in raport cu dimensiunea tabelei de dispersie m pentru intreaga tabela de dispersie in care cautam. Altfel, daca m si h2(K) au cel mai mare divizor comun d > 1 pentru oricare cheie K, atunci o cautare pentru cheia K va examina doar (1/ d) din tabela de dispersie. Lema 2 Daca h(K, i) = h1(K) + ih2(K)(mod m) unde h2(K) {0, 1,..., m 1} pentru oricare cheie K si cel mai mare divizor comun (h2(K), m) = 1, pentru oricare cheie K, <h(K, 0), h(K, 1),..., h(K, m 1)> este o permutare a <0, 1,..., m 1>. Demonstratie : Ca si in Lema 1 vom arata ca pentru fiecare i = 0, 1,..., m 1, toate valorile h1(K) + ih2(K)(mod m) sunt diferite doua cate doua. Daca exista doi indici i, j {0, 1,..., m 1}, i > j, astfel incat h1(K) + ih2(K) h1(K) + jh2(K)(mod m), atunci exista un p intreg, p > 0, astfel incat (i j)h2(K) = pm (formula 3) Dar formula 3 nu poate fi valabila decat daca 1 <= i j <= m 1 si (h2(K), m) = 1. Un mod convenabil de a asigura aceste conditii pentru h2 este sa facem pe m o putere a lui 2 si sa proiectam h2 astfel incat sa dea totdeauna un numar impar. Alta modalitate este ca m sa fie prim si sa proiectam h2 astfel incat sa returneze intotdeauna o valoare intreaga pozitiva, mai putin m. De exemplu, putem alege m prim si

h1(K) = K(mod m) h2(K) = 1 + (K(mod m`)), unde m` este ales sa fie putin mai mic decat m (sa spunem, m 1 si m 2). Rezumand cele de mai sus, dispersia cu adresare deschisa nu necesita spatiu in plus. Oricum, performanta acesteia scade cand factorul de incarcare este aproape 1 si nu poate fi sters.

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