Nazwa przedmiotu: Algorytmy i złoŜoność (Laboratorium) Prowadzący zajęcia: mgr inŜ. Przemysław Pardel Laboratorium 10 LP Zakres materiału Czas realizacji Abstrakcyjne struktury danych i ich implementacje: stosy kolejki, listy, drzewa, grafy, słowniki, haszowanie, kolejki priorytetowe, drzewa poszukiwań binarnych. 7. 9 Czas realizacji zadań: 3 godziny Teoria: Lista Lista - rodzaj kontenera, dynamiczna struktura danych używana w informatyce, składająca się z podstruktur wskazujących na następniki i/lub poprzedniki. Najprościej listę moŜna określić jako skończony ciąg elementów naleŜących do pewnego zbioru. JeŜeli jest to ciąg e1,e2,…,en to listę zapisuje się w postaci: (e1,e2,…,en). wskaźnik na pierwszy element e1 … e2 en nil Elementarny składnik tej struktury składa się z dwóch pól, które moŜna traktować jako odrębne komórki pamięci. Pierwsze z pól zawiera sam pamiętany element (jego zawartość informacyjną), a drugie tzw. wskaźnik informujący o połoŜeniu następnego elementu w liście. Wyjątkiem jest ostatni składnik, którego wskaźnik odwołuje się do elementu pustego (nil). Wskaźnik na pierwszy element pamiętany jest w specjalnie do tego celu przeznaczonej zmiennej (head). Struktura taka jest przykładem tzw. listy jednokierunkowej, w której pojedynczy składnik zawiera wskaźnik tylko na element następny. Najczęściej listę przedstawia się w postaci bardziej rozbudowanej struktury określanej jako lista dwukierunkowa. wskaźnik na pierwszy element nil e1 e2 … en nil Pojedynczy składnik listy dwukierunkowej zawiera pole informacyjne i dwa pola wskaźników: lewy wskazujący na poprzedni element oraz prawy wskazujący na element następny. Istnieją dwie popularne implementacje struktury listy: tablicowa i wskaźnikowa. Implementacja tablicowa Jak wskazuje nazwa, lista zaimplementowana w ten sposób opiera się na tablicy obiektów (lub rekordów) danego typu. Dopisanie elementu do listy to wstawienie elementu do tablicy: • jeśli ma ono nastąpić na końcu listy, będzie to kolejny element w tablicy; • jeśli nowy element ma znaleźć się między innymi elementami, naleŜy przesunąć o jedno pole w prawo wszystkie elementy o indeksie wyŜszym niŜ pole, na które będzie wstawiany obiekt; następnie w powstałą lukę wpisuje się nowy element. Usunięcie elementu znajdującego się pod danym indeksem tablicy to przesunięcie o jedno pole w lewo wszystkich elementów o indeksie wyŜszym. Zalety tej implementacji: prosta nawigacja wewnątrz listy, korzystanie z gotowej struktury tablicy, szybki dostęp do elementu o konkretnym numerze, większa odporność na błędy. Wady: niska elastyczność, szczególnie dotycząca rozmiaru tablicy, złoŜoność operacji wstawiania i usuwania. Implementację tablicową stosuje się tam, gdzie elastyczność nie odgrywa istotnej roli, a wymagana jest szybka i prosta nawigacja. Implementacja wskaźnikowa W tej implementacji kaŜdy obiekt na liście musi (co nie było konieczne w wersji tablicowej) zawierać dodatkowy element: wskaźnik do innego obiektu tego typu. Wynika to z faktu, Ŝe to wskaźniki są podstawą nawigacji w tym typie listy, a dostęp do jej elementów jest moŜliwy wyłącznie przez wskaźnik. Dopisanie elementu (dla prostej listy jednostronnej): • jeśli ma ono nastąpić na końcu listy, to wskaźnik wiąŜący w obiekcie ostatnim ustawia się na nowy obiekt danego typu; • jeśli ma ono nastąpić wewnątrz listy, to najpierw tworzy się nowy obiekt danego typu i jego wskaźnik wiąŜący ustawia się na następnik elementu, za którym ma być wstawiany. Później wskaźnik poprzednika przestawia się na ten nowy obiekt. W tym przypadku bardzo waŜna jest kolejność, której zachwianie jest częstą przyczyną błędów. Np. moŜna najpierw przestawić wskaźnik poprzednika na nowy obiekt, co spowoduje bezpowrotną utratę dostępu do dalszych elementów listy, na które juŜ nie będzie pokazywał Ŝaden wskaźnik. Ustawienie wskaźnika nowego elementu na następnik nie będzie moŜliwe, bo nie będzie znany jego adres. Usunięcie elementu jest odwrotne do wstawiania: w pewnym miejscu zapisuje się wskaźnik do usuwanego elementu (aby nie "zgubić" jego adresu), następnie wskaźnik wiąŜący poprzednika przestawia się na następnik, i dopiero w tym momencie zwalnia się pamięć po obiekcie usuwanym (do tego potrzebny jest ten wskaźnik tymczasowy). Zalety i wady tej implementacji są komplementarne w stosunku do implementacji tablicowej. Podstawowe operacje na listach • Poszukiwanie elementu na liście • Wstawianie elementu do listy • Usuwanie elementu z listy MoŜna wyróŜnić następujące rodzaje list: • lista jednokierunkowa - w kaŜdym elemencie listy jest przechowywane odniesienie tylko do jednego sąsiada (następnika lub poprzednika). • lista dwukierunkowa - w kaŜdym elemencie listy jest przechowywane odniesienie zarówno do następnika jak i poprzednika elementu w liście. Taka reprezentacja umoŜliwia swobodne przemieszczanie się po liście w obie strony. • lista cykliczna - następnikiem ostatniego elementu jest pierwszy element, a poprzednikiem pierwszego ostatni. Po liście moŜna więc przemieszczać się cyklicznie. Nie ma w takiej liście charakterystycznego ogona (ani głowy), często rozpoznawanego po tym, Ŝe jego następnik jest pusty (NULL). • lista z wartownikiem - lista z wyróŜnionym elementem zwanym wartownikiem. Jest to specjalnie oznaczony element niewidoczny dla programisty wykorzystującego listę. Pusta lista zawiera wtedy tylko wartownika. Zastosowanie wartownika znacznie upraszcza implementację operacji na listach. Zadania do realizacji: 1. Zaimplementować listę jednokierunkową z wykorzystaniem: a. tablicy jednowymiarowej, b. tablicy wielowymiarowej, c. dwóch tablic. Zaimplementować do każdego z przypadków podstawowe operacje na listach. 2. Zaimplementować listę dwukierunkową z wykorzystaniem: a. tablicy jednowymiarowej, b. tablicy wielowymiarowej, c. trzech tablic. Zaimplementować do każdego z przypadków podstawowe operacje na listach. Bibliografia: 1. Świder K.: Algorytmy i struktury danych 2. Wikipedia, Wolna encyklopedia, http://www.wikipedia.pl