OBOWIĄZKOWE DO PRZESŁANIA: jedno zadanie.
Listy powiązane podwójnie mają elementy powiązane dwoma łączami: do elementu następnego (next) i do elementu poprzedniego (prev). W łatwy sposób możemy poruszać się po liście do przodu i wstecz. Ponadto przy usuwaniu węzła musimy znać tylko łącze do niego samego, podczas gdy przy listach połączonych pojedynczo potrzebne było też łącze do węzła poprzedzającego.
Lista powiązana podwójnie może być wykorzystana do implementacji std::list z biblioteki STL.
| head tail | | | | | | o o | | +-+--+-+ +-+--+-+ puste | | |--+ |11| +--o+-+--+-+o--+ |22| +--| łącze | | +-+--+-+o--+ |33| +--o+-+--+-+ | | +-+--+-+ |
// Szablon dla pojedynczego węzła listy. template <typename T> struct DoubleNode { T value; DoubleNode *next, *prev; // konstruktor domyślny DoubleNode() : value(T()), next(nullptr), prev(nullptr) {} DoubleNode(const T& item, DoubleNode *nptr=nullptr, DoubleNode *pptr=nullptr) : value(item), next(nptr), prev(pptr) {} // konstruktor ~DoubleNode() {} // destruktor };
// Szablon dla listy powiązanej podwójnie. template <typename T> class DoubleList { DoubleNode<T> *head, *tail; public: DoubleList() : head(nullptr), tail(nullptr) {} ~DoubleList(); // tu trzeba wyczyścić węzły DoubleList(const DoubleList& other); // copy constructor DoubleList(DoubleList&& other); // move constructor NIEOBOWIAZKOWE DoubleList& operator=(const DoubleList& other); // copy assignment operator, return *this DoubleList& operator=(DoubleList&& other); // move assignment operator, return *this NIEOBOWIAZKOWE bool empty() const { return head == nullptr; } std::size_t size() const; // O(n) bo trzeba policzyć void push_front(const T& item); // O(1) void push_front(T&& item); // O(1) NIEOBOWIAZKOWE void push_back(const T& item); // O(1) void push_back(T&& item); // O(1) NIEOBOWIAZKOWE T& front() const { return head->value; } // zwraca początek, nie usuwa T& back() const { return tail->value; } // zwraca koniec, nie usuwa void pop_front(); // usuwa początek O(1) void pop_back(); // usuwa koniec O(1) void clear(); // czyszczenie listy z elementów O(n) void display(); // O(n) void display_reversed(); // O(n) };
Przygotować implementację realizującą ADT LIST na bazie listy powiązanej podwójnie. Będzie to klasa DoubleList umieszczona w pliku doublelist.h. Uzupełnić plik doublelist.h, stworzyć funkcję main() i testy sprawdzające działanie interfejsu list.
Zmienić klasę DoubleList przez wprowadzenie wartownika, czyli sztucznego węzła łączącego głowę i ogon listy powiązanej podwójnie. Wartownik nie przechowuje żadnych istotnych danych w polu value. W ten sposób powstaje lista cykliczna, co upraszcza wstawianie i usuwanie węzłów. W klasie DoubleList wystarczy przechowywać tylko łącze do wartownika.
| nil | nil +----------------------------+ | | | | | | | | | o | o o | | | +-+---+-+ | +-+---+-+ +-+---+-+ | | | +--+ | ? | +--+ | +--+ | ? | +--o+-+---+-+o--+ | B | +--+ | | | +-+---+-+ | | | +-+---+-+o--+ | A | +--o+-+---+-+ | | | o o | | | +-+---+-+ o | | | | | | | | | | | +-----+ +-----+ | +------------------------------+ |
// Szablon dla listy powiązanej podwójnie z wartownikiem. template <typename T> class DoubleList { DoubleNode<T> *nil; public: DoubleList(); // trzeba alokować wartownika ~DoubleList(); // tu trzeba wyczyścić węzły i wartownika bool empty() const { return nil->next == nil; } T& front() const { return nil->next->value; } // zwraca początek, nie usuwa T& back() const { return nil->prev->value; } // zwraca koniec, nie usuwa };
template <typename T> DoubleList<T>::DoubleList() { nil = new DoubleNode<T>(); // wartownik nil->next = nil; nil->prev = nil; }
template <typename T> void DoubleList<T>::push_front(const T& item) { DoubleNode<T> *node = new DoubleNode<T>(item, nil->next, nil); nil->next->prev = node; nil->next = node; }
template <typename T> void DoubleList<T>::pop_front() { assert(!empty()); DoubleNode<T> *node = nil->next; // zapamiętujemy node->next->prev = node->prev; // ogólny schemat usuwania node->prev->next = node->next; // Przywracanie usuniętego węzła [Knuth]: // node->next->prev = node; // node->prev->next = node; delete node; }
Zmienić klasę DoubleList przez wprowadzenie atrybutu length typu std::size_t, który przechowuje aktualną długość listy powiązanej. Dzięki temu metoda size() może działać w czasie O(1), a nie O(n).