Lista, Stos, Kolejka, Tablica Asocjacyjna
Listy Lista zbiór elementów tego samego typu może dynamicznie zmieniać rozmiar, pozwala na dostęp do poszczególnych elementów Typowo dwie implementacje: tablicowa, wskaźnikowa
Na liście pamiętane są elementy E Każdy element to para lub trójek: Lista wskaźnikowa E<wartość, wskaźnik> -> lista jednokierunkowa E<wskaźnik, wartość, wskaźnik> -> lista dwukierunkowa Obiekt Lista pamięta jedynie wskaźnik do pierwszego i ostatniego elementu Lista<pierwszy,ostatni> Każdy element posiada zapamiętaną wartość oraz wskaźnik do następnego elementu (lub do następnego i poprzedniego lista dwukierunkowa) * Obrazek pochodzi z: https://pl.wikibooks.org/wiki/struktury_danych/listy
Lista wskaźnikowa c.d. Dodanie wartości -> 1. stworzenie nowego elementu wsk=e<w,e> i zapamiętanie wskaźnika do tego elementu (wsk) 2. Zapisanie w E<w=wartości,e> wartości przeznaczonej do zapamiętania 3. Odczytanie z listy ostatniego elementu E last <w,e> = Lista<ostatni> 4. Aktualizacja w E last <w,e> adresu do następnego elementu: E last <w,e = wsk>, 5. Aktualizacja w liście wskażnika do ostatniego elementu Odczyt 1. Z Lista<pierwszy,ostatni> odczytujemy pierwszy element E<w,e> 2. i x powtarzamy procedurę z E<w,e> przejdź do e 3. Z bieżącego E<w,e> wróć w
Lista wskaźnikowa cd. Usunięcie i tej wartości -> 1. Z Lista<pierwszy,ostatni> odczytujemy pierwszy element E<w,e> 2. i-1 x powtarzamy procedurę z E<w,e> przejdź do e 3. Zapamiętaj E przed <w przed,e przed >, przejdź do e ( e zawiera element do usunięcia) 4. Przejdź do e i zapamiętaj E po <w po,e po > 5. Do e przed zapisz E po 1. Szukanie 1. Z Lista<pierwszy,ostatni> odczytujemy pierwszy element E<w,e> 2. i x powtarzamy procedurę - z E<w,e> odczytaj w i e - sprawdź czy w to szukana wartość, jak nie to przejdź do e - jeśli tak to zwróć true lub indeks elementu
Lista wskaźnikowa cd. Wstawienie wartości w miejsce i -> 1. Stwórz nowy E new <w = wartość,e> i zapamiętaj w nim wartość, 2. Z Lista<pierwszy,ostatni> odczytujemy pierwszy element E<w,e> 3. i-1 x powtarzamy procedurę z E<w,e> przejdź do e 3. Zapamiętaj bieżący E przed <w przed,e przed >=E<w,e> 4. Do E new <w,e=e przed > 5. Do E przed <w,e=e new >
Lista tablicowa najczęściej stosowane rozwiązanie!! np. Java ArrayList<>, c# List<> Dane przechowywane są w tablicy, a struktura pamięta pierwszy wolny element tablicy Obiekt lista zawiera: Licznik zlicza elementy dodane do tablicy oraz wskazuje na pierwszy wolny element Tablica przechowuje elementy z listy
Lista tablicowa Operacje: Dodanie wartości:, 1. Jeśli licznik wskazuje na element poza zakresem tablicy zwiększ tablicę, przepisz wartości ze starej do nowej 2. skocz do pierwszej wolnej komórki wskazywanej przez licznik i zapisz tam wartość Odczyt wartości i 1. Zwróć z tablicy element i Usuwanie wartości i : 1. skocz do usuwanej wartości usuń, 2. przepisz wartości z komórek i+1 do i dla każdego elementu w tablicy większego od i 3. Wyzeruj ostatni element listy (uwaga na wyciek pamięci)
Lista tablicowa Wstawianie wartości w miejsce i 1. Sprawdź rozmiar tablicy jeśli trzeba zwiększ jej rozmiar (jak przy dodawaniu) 2. Przepisz wartości z komórki j do j+1, rozpoczynając od komórki i a kończąc na liczniku 3. Do komórki i zapisz wartość Wyszukiwanie wartości 1. Od indeksu początkowego do licznik 2. Sprawdź czy szukana wartość znajduje się na liście 3. Jeśli tak to zwróć ture lub nr indeksu
Zadania i pytania Pytanie: Jeśli mamy listę wskaźnikową jednokierunkową i przechowujemy na niej N elementów 64 bitowych oraz (np. typu double) a program wykorzystuje 64 bitową przestrzeń adresową to ile pamięci zajmie całość struktury? Pytanie: Jeśli mamy listę tablicową i przechowujemy na niej N elementów 64 bitowych (np. typu double) a program wykorzystuje 64 bitową przestrzeń adresową to ile pamięci zajmie całość struktury? Zadanie: Oszacuj złożoności obliczeniowe poszczególnych funkcji dla jednej i drugiej listy Pytanie: Jak można zwiększyć szybkość np. odczytu, wstawiania w liście wskaźnikowej
Listy i iteratory Iterator funkcja umożliwiająca przejście do kolejnego (i/lub) poprzedniego elementu kolekcji Typowe metody: interface Iterator (Java) interface IEnumerator (C#) hasnext() (Java) MoveNext() (C#) sprawdza czy dostępny jest kolejny element next() (Java) current (C#) zwraca kolejną wartość elementu Reset() (C#) - resetuje iterator i umieszcza na początku listy Remove() (Java) Pozwala usunąć bieżący element Iteratory wykorzystywane są pętlach typu foreach Iteratory: Dana kolekcja musi implementować interfejs Iterable (Java) -> metoda: Iterator iterator() IEnumerable (C#) -> metoda: IEnumerator GetEnumerator()
Źródło obrazka: https://pl.wikibooks.org/wiki/struktury_danych/stosy Stos Stos wiele elementów tego samego typu, zmienia rozmiar, dostęp do ostatnio dodanego elementu. Inaczej to kolejka typu last in first out (LIFO) Dodawanie wartości: każda dodawana wartość umieszczana jest na początku Usuwanie wartości: każda usuwana/pobierana wartość pochodzi z początku kolejki Którą strukturę wykorzystać do implementacji stosu?
Źródło obrazka: https://pl.wikibooks.org/wiki/struktury_danych/kolejki Kolejka Kolejka - wiele elementów tego samego typu, zmienia rozmiar, dostęp do pierwszego elementu Inaczej to kolejka typu first in first out (FIFO) Pierwszy wchodzi -> pierwszy wychodzi Dodawanie wartości: każda dodawana wartość umieszczana jest na końcu struktury Usuwanie wartości: każda usuwana/pobierana wartość pochodzi z początku kolejki Którą strukturę wykorzystać do implementacji stosu?
Tablice asocjacyjne (tablice z haszowaniem) Tablica asocjacyjna tablica zawierająca asocjację typu <klucz-> wartość> zapewniająca bardzo szybki dostęp do elementów. Głównie chodzi o minimalizację czasu dostępu: wstawienia elementu, -> wstaw(klucz, Wartosc) usunięcia elementu -> usun(klucz) znalezienia elementu -> znajdz(klucz) Inna nazwa to Słownik Przykład: książka telefoniczna klucz=nazwisko, wartość=nr_telefonu
Tablice asocjacyjne Adresowanie bezpośrednie jeśli kluczem jest zbiór liczb naturalnych dodatnich, lub klucz można łatwo zamienić na liczbę (np. litera alfabetu: A->0, B->1,, Z->24) Rozwiązanie: Tablica, a klucz jest bezpośrednim adresem komórki w tablicy Żródło obrazka: https://pl.wikibooks.org/wiki/struktury_danych/tablice_z_haszowaniem
Tablice asocjacyjne Adresowanie bezpośrednie Wady: - Gdy duża przestrzeń zbioru - > duże zużycie pamięci -> każdy element potrzebuje 1 komórkę w tablicy - Gdy mała liczba przechowywanych elementów -> tablica zawiera dużo pustych i niewykorzystanych pól-> nieefektywne wykorzystanie pamięci Żródło obrazka: https://pl.wikibooks.org/wiki/struktury_danych/tablice_z_haszowaniem
Tablice asocjacyjne tablice z haszowaniem Funkcja haszująca (mieszająca, skrótu) -> funkcja przyporządkowująca dowolnie dużej liczbie nową quasi-losową wartość posiadającą stały rozmiar (funkcja nieodwracalna) Problem: - Dysponujemy zbiorem m liczb e należących do uniwersum U U =e - Dysponujemy skończoną tablicą T o n-elementach i m >> n - Funkcja haszująca: f( e)->{1..n} Dokonuje konwersji liczby e na jedną z wartości 1 do n Uwaga ponieważ m>>n więc mogą wystąpić kolizje Kolizje występuje gdy dwie różne wartości ze zbioru mają identyczną wartość funkcji skrótu UWAGA kolejność dodawania do listy jest różna gdy iterujemy po jej elementach Żródło obrazka: https://pl.wikipedia.org/wiki/tablica_mieszaj%c4%85ca
Tablice asocjacyjne tablice z haszowaniem Rozwiązanie problemu kolizji: Metoda łańcuchowa - Utworzenie listy w każdej komórce tablicy A - Lista zawiera wszystkie elementy o identycznej wartości funkcji skrótu Implementacja: N rozmiar tablicy tablica tablica o N elementach, gdzie każdy element jest typu Lista<Element> służy do przechowywania elementów Element<Klucz,Wartość> - Obiekt składający się z pary Klucz, Wartość
Tablice asocjacyjne tablice z haszowaniem Implementacja: wstaw(klucz, wartość) 1. Oblicz wartość funkcji skrótu dla klucza id = f(klucz) 2. Idź do tablica[id] i pobierz listę 3. Stwórz E = Element<Klucz,Wartość> 4. Jeżeli lista==null 1. Stwórz listę 2. Dodaj listę do tablica[id] 3. Dodaj E do listy 4. Return 5. Iteruj po elementach listy 1. Jeżeli Klucz i = klucz 1. Usuń element Element i 2. Dodaj E do listy 3. Return 6. Dodaj E do Listy
Implementacja: wartość = odczytaj(klucz) Tablice asocjacyjne tablice z haszowaniem 1. Oblicz wartość funkcji skrótu id = f(klucz) 2. Idź do tablica[id] i odczytaj listę 3. Jeżeli lista == null: 1. Zwróć wyjątek 4. Iteruj po elementach listy 1. Jeżeli Klucz i = klucz 1. Return Wartość i 5. Zwróć wyjątek
Tablice asocjacyjne tablice z haszowaniem Implementacja: wartość = usuń(klucz) 1. Oblicz wartość funkcji skrótu id = f(klucz) 2. Idź do tablica[id] i odczytaj listę 3. Jeżeli lista == null: 1. Zwróć wyjątek 4. Iteruj po elementach listy 1. Jeżeli Klucz i = klucz 1. Usuń Element i z listy 5. Zwróć wyjątek
Funkcja skrótu Najprostsza implementacja funkcji skrótu: f(e) = {1..n} f(e) = (e mod n)+1 wówczas dostajemy liczbę z zakresu 1 do n (lub bez +1 wówczas mamy hash w zakresie {0..n-1}) UWAGA dobre jeśli n bliskie liczby pierwszej Lub: f(e) = (e mod p) mod n gdzie p > n i p jest liczbą pierwszą. Dobra funkcja haszująca zależy od rozkładu danych!!!
Equals i HashCode Problem: Od czego zależy jakość działania tablicy asocjacyjnej?
Equals i HashCode Problem: Od czego zależy jakość działania tablicy asocjacyjnej? Każdy obiekt w Javie i C# ma funkcje Equals i HashCode / GetHashCode Jeśli wiesz że obiekt może być przechowywany w słowniku lub porównywany MUSISZ!!!!! nadpisać metody EQUALS an d HASHCODE
Equals i HashCode Equals -> zwraca true tylko wtedy gdy dwa obiekty są sobie równe a.equals(b) == b.equals(a) a.equals(b) && b.equals(c) == true jeżeli a.equals(c) a.equals(null) == false Equals: - Domyślnie porównuje wskaźniki do obiektów (Java i C#) lub dla typów prymitywnych porównanie bitowe
Equals i HashCode Przykład: class A { int i; String s; } public override bool Equals(Object obj) { if (obj == null) return false; if (GetType()!= obj.gettype()) return false; A a = (A)obj; bool output; output = i == a.i; output = output && s.equals(a.s); return output; }
Equals i HashCode HashCode -> dla dowolnego obiektu zwraca liczbę typu int, uwaga liczba dla dwóch takich samych obiektów powinna być taka sama. Dla różnych obiektów powinna być różna -> ale mogą wystąpić kolizje. UWAGA: Wartości HashCode nie wolno używać do porównywania dwóch obiektów czy są równe, gdyż ze względu na kolizje może dojść do sytuacji gdzie dwa różne obiekty mają identyczny HashCode
Equals i HashCode Przykład: class A { int i; String s; public override int GetHashCode(){ Liczby pierwsze int hash = 17; hash = hash *19 + i; hash = hash *23 + s.gethashcode() return hash; } }