本篇主要介绍一种重要的链表——“双向链表”(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(); }”的声明和定义格式,以及类模版前缀的添加方式。