Upload
others
View
3
Download
0
Embed Size (px)
Citation preview
SŁOWNIKISłownik składa się z elementów, które są różne między sobą.
Podstawowe operacje:Member(x,A) - Zwraca wartość true, gdy obiekt należy do słownika i false w przeciwnym wypadku.
Insert(x,A) – Czyni x elementem A. Jeśli x już jest w A, to Insert nic nie zmienia.
Delete(x,A) – Usuwa x z A.
MAKENULL(x,A) – Czyni A słownikiem pustym.
1
Zad. Jak można zaimplementować słowniki?
2
Zad. Jak można zaimplementować słowniki?• Jako listy posortowane • Jako listy nieposortowane• Jako wektory bitowe
(przy założeniu, że elementy są z przedziału (1,..,n) lub ze zbioru dla którego można ustalić odpowiedniość z tym przedziałem
3
HASZOWANIE• Ważną implementacją słowników jest haszowanie.
• Haszowanie daje średnio stały czas dostępu do elementu słownika
• W najgorszym przypadku daje czas proporcjonalny do rozmiaru zbioru
4
• T ablica mieszająca (tablica haszująca) to struktura danych służąca do przechowywania informacji, w taki sposób, aby możliwy był do nich szybki dostęp.
• Odwołania do przechowywanych obiektów dokonywane są na podstawie klucza, który jest identyfikatorem danego obiektu.
• Kluczem może być na przykład ciąg znaków zawierający nazwisko pracownika, a wyszukiwaną informacją jego adres domowy.
• Położenie informacji w tablicy określa się obliczając wartość funkcji haszującej (funkcji mieszającej) dla danego klucza
5
Wyróżniamy dwa rodzaje haszowania:
o haszowanie otwarte (ang. open lub external)
o haszowanie zamknięte (ang. Closed lub internal)
6
HASZOWANIE OTWARTE• Haszowanie otwarte pozwala na to, aby pamiętany zbiór
miał potencjalnie nieograniczone rozmiary.
• Zasadniczą ideą haszowania jest to, że zbiór uniwersalny (który może być nieskończony) zostaje podzielony na skończoną liczbę klas.
• Jeżeli chcemy mieć B klas ponumerowanych od 0 do B-1, to używamy funkcji haszującej h, która każdemu obiektowi (kluczowi) x przyporządkuje h(x), będące liczbą z przedziału od 0 do B-1.
• Wartość h(x) jest numerem klasy, do której x należy.
7
• W tablicy klas znajdują się nagłówki list elementów należących do tych klas.
8
• Chcemy, aby klasy były w przybliżeniu równych rozmiarów.
• Nie zawsze da się tak dobrać h, aby elementy były równo rozdzielone na klasy.
• Jeżeli w zbiorze uniwersalnym jest N elementów, to długość listy wynosi średnio N/B
• Jeśli określimy N i przyjmiemy B zbliżonej wielkości to każda klasa będzie miała w przybliżeniu 1-2 elementy- mały czas dostępu do elementów
9
const B=65;typedef char * elementtype; struct celltype{elementtype element;celltype * next;};typedef celltype * position;
10
class dictionary{protected : position D[B];public: dictionary(); ~dictionary(); void Makenul(); bool Member(elementtype x); void Insert(elementtype x); void Delete(elementtype x); int H(elementtype x);};
11
12
dictionary::dictionary(){for (int i=0;i<B;i++)D[i]=NULL;}
13
bool dictionary::Member(elementtype x){
position current;current=D[H(x)];while (current!=NULL){
if (current->element==x) return true;else current=current->next;
}return false;
}
14
void dictionary::Insert(elementtype x){
int bucket;position oldheader;if (!Member(x)){
bucket=H(x);oldheader=D[bucket];D[bucket]=new celltype;D[bucket]->element=x;D[bucket]->next=oldheader;
}}
15
void dictionary::Delete(elementtype x){
position p,current;int bucket;bucket=H(x);if (D[bucket]!=NULL){ if (D[bucket]->element==x)//x jest w pierwszej komórce
{p=D[bucket];D[bucket]=D[bucket]->next;delete p;
}
16
else //jeśli x występuje w zbiorze, to nie w pierwszej komórce
{current=D[bucket];while (current->next!=NULL)
if ( (current->next->element)==x ){
p=current->next;current->next=current->next->next;delete p;break;//natychmiastowe wyjście z pętli while
}else current=current->next;
17
} } }dictionary::~dictionary(){position p;for (int i=0;i<B;i++){ while (D[i]!=NULL)
{ p=D[i];
D[i]=D[i]->next; delete p;
}
18
}}void dictionary::Makenul(){position p;for (int i=0;i<B;i++){ while (D[i]!=NULL)
{ p=D[i];
D[i]=D[i]->next; delete p;
}
19
}}int dictionary::H(elementtype x){return (int(x[0]))%B;}
20
HASZOWANIE ZAMKNIĘTE• Zamknięta tablica haszująca nie zawiera nagłówków list
klas elementów, lecz same elementy.• W konsekwencji każda klasa zawiera tylko jeden
element.
21
• Gdy chcemy umieścić element x w tablicy, a h(x) wskazuje klasę zajętą (mamy kolizję) to stosujemy rehaszowanie, czyli poszukiwanie alternatywnej lokalizacji h1(x), h2(x),… aż do znalezienia miejsca lub stwierdzenia, że tablica jest już pełna i element nie może być już umieszczony.
22
Przykład. Przykładowa kolizja:
h(a)=3h(b)=0h(c)=4h(d)=3 //czyli chcemy wstawić “d”, a komórka tablicy o numerze h(d) jest już zajęta przez “a”
• Najprostsza strategia rehaszowania (tzw. haszowanie liniowe):
hi(x)= (h(x)+i) mod B
23
• W takiej sytuacji, jeśli np. chcemy wstawić a, gdy trzecia komórka jest zajęta, możemy szukać kolejnych lokalizacji w komórkach 4,5,6,7,0,1,2.
• Początkowo zakładamy, że tablica jest pusta. • Każdy element w tablicy powinien zawierać wartość
wyróżnioną w typie elementów słownika oznaczającą pusty element (np.”-„ dla typu znakowego).
• Przy wstawianiu do pustej tablicy kolejno elementów a, b, c mamy:
24
h(a)=3h(b)=0h(c)=4
h(d)=3
25
Przy próbie wstawienia „d” natrafimy na klasę h(d)=3 – zajęte. Stosujemy rehaszowanie.h1(d)= h(d)+1=3+1=4 – też zajęte
h2(d)= h(d)+2=3+2=5 – wolne.Do komórki 5 wstawiamy d
26
const b=65;
char * EMPTY="!!!!!!!!!"; char * DELETED="**********";
typedef char * elementtype; class c_dictionary{ elementtype D[b];
public:
c_dictionary();
~c_dictionary();
void Makenul();
bool Member(elementtype x);
void Insert(elementtype x);
void Delete(elementtype x);
int H(elementtype x);
int Locate(elementtype x);
int Locate1(elementtype x);};
27
28
c_dictionary::c_dictionary()
{
for (int i=0;i<b;i++)
D[i]=EMPTY;
}
Uwaga. Operacja Makenull wstawia do wszystkich komórek wartość empty.
void c_dictionary::Makenul()
{
for (int i=0;i<b;i++)
D[i]=EMPTY;
}
29
Uwaga. Operacja pomocnicza Locate(x:elementtype; A:dictionary) przegląda słownik począwszy od komórki h(x) do momentu aż natrafi na x, lub na klasę pustą lub obejdzie całą tablicę dookoła stwierdziwszy, że x nie ma w słowniku.Locate zwraca numer klasy, na której się zatrzymała z któregokolwiek z wyżej wymienionych powodów.
int c_dictionary::Locate(elementtype x)
{
int i, initial; //initial pamięta H(x), i- zlicza klasy, które zostały przejrzane do tej pory
//w poszukiwaniu x
initial=H(x);
i=0;
while ( (i<b) && (D[(initial+i)%b]!=x) && (D[(initial+i)%b]!=EMPTY)) i++;
return ((initial+i)%b);
30
}
Uwaga. Potrzebna będzie też operacja Locate1. Analogiczna do Locate, lecz dodatkowo zatrzymuje się po napotkaniu deleted.
int c_dictionary::Locate1(elementtype x)
{
int i, initial; //initial pamięta H(x), i- zlicza klasy, które zostały przejrzane do tej pory w poszukiwaniu x
initial=H(x);
i=0;
while ( (i<b) && (D[(initial+i)%b]!=x) && (D[(initial+i)%b]!=DELETED)&& (D[(initial+i)%b]!=EMPTY) ) i++;
return ((initial+i)%b);
}
31
Uwaga. Operacja Member polega na sprawdzaniu lokalizacji h(x), h1(x), … aż do odnalezienia elementu lub natrafienia na element pusty
bool c_dictionary::Member(elementtype x)
{
if (D[Locate(x)]==x) return true;
else return false;
}
32
Uwaga. Operacja Insert. Wstawia x do komórki o numerze Locate1(x,A) o ile Locate1 zwróciło wartość empty lub deleted.
void c_dictionary::Insert(elementtype x)
{
int bucket;
if (D[Locate(x)]!=x)
{
bucket=Locate1(x);
if ((D[bucket]==EMPTY)||(D[bucket]==DELETED)) D[bucket]=x;
}
}
33
Uwaga. Operacja Delete. Jeżeli x znajduje się w słowniku, to do komórki, w której się x znajduje wstawiana jest wartość deleted.
void c_dictionary::Delete(elementtype x)
{
int bucket;
bucket=Locate(x);
if (D[bucket]==x) D[bucket]=DELETED;
}
34
int c_dictionary::H(elementtype x)
{
return (int(x[0]))%b;
}
35