本篇主要介绍一种重要的链表——“双向链表”(Doubly Linked Lists)。
双向链表,顾名思义,它既可以从head开始正向遍历链表,也可以从tail开始反向遍历链表。双向链表的节点包含两个指针,一个指向下一个节点(successor,同单链表),另一个指向前一个节点(predecessor),因此,也可以将它看作是两个迭代方向不同的叠在一起的单链表。如下图a。
以上是几种双向链表的结构图,包括空链表的状态。
其中,图a是最简单的一种双向链表,它包含两个指针,head指向第一个元素,tail指向最后一个元素,完全可以看作是两个走向不同的单链表的叠加。
图b引入了sentinel节点(不是指针)。它是一种编程技巧,前面在介绍单链表的时候已经展示了sentinel节点的作用,无需特殊处理head。
图c是一个可双向循环的循环链表。
图d是带sentinel节点的循环链表。
下面选取图a的结构实现一个双向链表。
#ifndef DOUBLY_LINKED_LIST_H #define DOUBLY_LINKED_LIST_H #include <stdexcept> namespace FoundationalDataStructure { // forward declaration template <typename T> class DoublyLinkedList; template <typename T> class Node { public: T const & Datum() const; Node const * Prev() const; Node const * Next() const; friend DoublyLinkedList<T>; private: T datum; Node * prev; Node * next; Node(T const &, Node *, Node *); }; template <typename T> class DoublyLinkedList { public: DoublyLinkedList(); ~DoublyLinkedList(); DoublyLinkedList(DoublyLinkedList const &); DoublyLinkedList & operator=(DoublyLinkedList const &); Node<T> const * Head() const; Node<T> const * Tail() const; bool IsEmpty() const; operator bool() const; // operator bool() const { return !IsEmpty(); } T const & First() const; T const & Last() const; void Prepend(T const &); void Append(T const &); void Extract(T const &); void Purge(); void InsertAfter(Node<T> const *, T const &); void InsertBefore(Node<T> const *, T const &); private: Node<T> * head; Node<T> * tail; }; //////////////////////////////////////////////////Implementation///////////////////////////////////////////////////////////////////// template <typename T> Node<T>::Node(T const & _datum, Node * _prev, Node * _next) : datum(_datum) , prev(_prev) , next(_next) {} template <typename T> T const & Node<T>::Datum() const { return datum; } template <typename T> Node<T> const * Node<T>::Prev() const { return prev; } template <typename T> Node<T> const * Node<T>::Next() const { return next; } template <typename T> DoublyLinkedList<T>::DoublyLinkedList() : head(NULL) , tail(NULL) {} template <typename T> void DoublyLinkedList<T>::Purge() { // The main loop of the Purge function simply traverses all the elements of linked list, deleting each of them one-by-one. while (head) { Node<T> * temp(head); head = head->next; // move head pointer delete temp; temp = NULL; } tail = NULL; } template <typename T> DoublyLinkedList<T>::~DoublyLinkedList() { Purge(); } template <typename T> Node<T> const * DoublyLinkedList<T>::Head() const { return head; } template <typename T> Node<T> const * DoublyLinkedList<T>::Tail() const { return tail; } template <typename T> bool DoublyLinkedList<T>::IsEmpty() const { return (!head || !tail); } template <typename T> DoublyLinkedList<T>::operator bool() const { return !IsEmpty(); } template <typename T> T const & DoublyLinkedList<T>::First() const { if (!head) throw std::domain_error("List is empty"); return head->datum; } template <typename T> T const & DoublyLinkedList<T>::Last() const { if (!tail) throw std::domain_error("List is empty"); return tail->datum; } template <typename T> void DoublyLinkedList<T>::Prepend(T const & item) { Node<T> * const temp = new Node<T>(item, NULL, head); if (!head) tail = temp; // list is empty else head->prev = temp; head = temp; // move head to the front of the new list } template <typename T> void DoublyLinkedList<T>::Append(T const & item) { Node<T> * const temp = new Node<T>(item, tail, NULL); if (!tail) head = temp; // list is empty else tail->next = temp; // insert temp at the end of the list tail = temp; // move tail to the end of the new list } template <typename T> void DoublyLinkedList<T>::Extract(T const & item) // pass by reference or value ? { if (!head) throw std::domain_error("list is empty"); auto ptr = head; while (ptr != NULL) { if (ptr->datum == item) { auto temp = ptr; // backup if (ptr == head) { if (ptr == tail) tail = NULL; else ptr->next->prev = NULL; ptr = ptr->next; head = ptr; } else { if (ptr == tail) tail = ptr->prev; else ptr->next->prev = ptr->prev; // bridget over ptr ptr->prev->next = ptr->next; // bridget over ptr ptr = ptr->next; // move ptr } delete temp; temp = NULL; continue; // break; for the first one } ptr = ptr->next; } } template <typename T> DoublyLinkedList<T>::DoublyLinkedList(DoublyLinkedList const & linkedList) : head(NULL) , tail(NULL) { for (auto ptr = linkedList.Head(); ptr != NULL; ptr = ptr->next) Append(ptr->datum); } template <typename T> DoublyLinkedList<T> & DoublyLinkedList<T>::operator=(DoublyLinkedList const & linkedList) { if (&linkedList != this) { Purge(); for (auto ptr = linkedList.Head(); ptr != NULL; ptr = ptr->next) Append(ptr->datum); } return *this; } template <typename T> void DoublyLinkedList<T>::InsertAfter(Node<T> const * arg, T const & item) { Node<T> * ptr = const_cast<Node<T>*> (arg); if (!ptr) throw std::invalid_argument("invalid position"); Node<T> * const temp = new Node<T>(item, ptr, ptr->next); if (ptr->next) ptr->next->prev = temp; ptr->next = temp; if (tail == ptr) tail = temp; } template <typename T> void DoublyLinkedList<T>::InsertBefore(Node<T> const * arg, T const & item) { Node<T> * ptr = const_cast<Node<T>*> (arg); if (!ptr) throw std::invalid_argument("invalid position"); Node<T> * const temp = new Node<T>(item, ptr->prev, ptr); if (ptr->prev) ptr->prev->next = temp; ptr->prev = temp; if (head == ptr) head = temp; } } // namespace FoundationalDataStructure #endif // DOUBLY_LINKED_LIST_H
注:
1,本文的节点与链表采用的是友元类的实现方式,也可以使用嵌套类的实现方式。
2,函数“operator bool() const; // operator bool() const { return !IsEmpty(); }”的声明和定义格式,以及类模版前缀的添加方式。