本篇主要介绍“The Singly-Linked List with a sentinel”。它是单链表诸多变种类型中的一种,参考上一篇的图d。sentinel是一个空节点,始终占据链表的首位,并一直存在链表中(即使是空链表),它本身不存储数据。sentinel节点的设计,实际上是一种编程技巧,它可以简化链表的某些操作。比如,对Extract函数的简化。此外,该链表的尾节点的next指针不再指向NULL,而是指向sentinel,它是一个循环链表。
一、实现代码
#ifndef LINKED_LIST_H
#define LINKED_LIST_H
#include <stdexcept>
namespace FoundationalDataStructure
{
// forward declaration
template <typename T>
class LinkedList;
template <typename T>
class Node
{
public:
T const & Datum() const;
Node const * Next() const;
friend LinkedList<T>;
private:
T datum;
Node * next;
Node(T const &, Node *);
};
template <typename T>
class LinkedList
{
public:
LinkedList();
~LinkedList();
LinkedList(LinkedList const &);
LinkedList & operator=(LinkedList const &);
Node<T> const * Head() const;
Node<T> const * Tail() const;
Node<T> const * Sentinel() const;
bool IsEmpty() const;
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> sentinel;
Node<T> *& head; // the reference of a pointer
Node<T> * tail;
};
//Implementation/
template <typename T>
Node<T>::Node(T const & _datum, Node * _next)
: datum(_datum)
, next(_next)
{}
template <typename T>
T const & Node<T>::Datum() const
{
return datum;
}
template <typename T>
Node<T> const * Node<T>::Next() const
{
return next;
}
template <typename T>
LinkedList<T>::LinkedList()
: sentinel({}, &sentinel)
, head(sentinel.next)
, tail(&sentinel)
{}
template <typename T>
void LinkedList<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 != &sentinel)
{
auto temp = head;
head = head->next; // move head pointer
delete temp;
}
tail = &sentinel;
}
template <typename T>
LinkedList<T>::~LinkedList()
{
Purge();
}
template <typename T>
Node<T> const * LinkedList<T>::Head() const
{
return head;
}
template <typename T>
Node<T> const * LinkedList<T>::Tail() const
{
return tail;
}
template <typename T>
Node<T> const * LinkedList<T>::Sentinel() const
{
return &sentinel;
}
template <typename T>
bool LinkedList<T>::IsEmpty() const
{
return head == &sentinel;
}
template <typename T>
T const & LinkedList<T>::First() const
{
if (head == &sentinel)
throw std::domain_error("List is empty");
return head->datum;
}
template <typename T>
T const & LinkedList<T>::Last() const
{
if (head == &sentinel)
throw std::domain_error("List is empty");
return tail->datum;
}
template <typename T>
void LinkedList<T>::Prepend(T const & item)
{
Node<T> * const temp = new Node<T>(item, head);
if (head == &sentinel)
tail = temp; // tail = &sentinel; before this operation
head = temp; // move head pointer
}
template <typename T>
void LinkedList<T>::Append(T const & item)
{
Node<T> * const temp = new Node<T>(item, &sentinel);
if (head == &sentinel)
head = temp; // the first node
else
tail->next = temp; // insert temp at the end of the list
tail = temp; // move tail to the end of the list
}
template <typename T>
void LinkedList<T>::Extract(T const & item) // pass by reference or value ?
{
if (head == &sentinel)
throw std::domain_error("list is empty");
auto ptr = head, prevPtr = &sentinel;
while (ptr != &sentinel)
{
if (ptr->datum == item)
{
if (ptr == tail)
tail = prevPtr;
auto temp = ptr;
prevPtr->next = ptr->next; // bridget over ptr
ptr = ptr->next;
delete temp;
continue;
}
ptr = ptr->next;
prevPtr = prevPtr->next;
}
}
template <typename T>
LinkedList<T>::LinkedList(LinkedList const & linkedList)
: sentinel({}, &sentinel)
, head(sentinel.next)
, tail(&sentinel)
{
for (auto ptr = linkedList.Head(); ptr != linkedList.Sentinel(); ptr = ptr->next)
Append(ptr->datum);
}
template <typename T>
LinkedList<T> & LinkedList<T>::operator=(LinkedList const & linkedList)
{
if (&linkedList != this)
{
Purge();
for (auto ptr = linkedList.Head(); ptr != linkedList.Sentinel(); ptr = ptr->next)
Append(ptr->datum);
}
return *this;
}
template <typename T>
void LinkedList<T>::InsertAfter(Node<T> const * arg, T const & item)
{
Node<T> * ptr = const_cast<Node<T>*> (arg);
if (NULL == ptr)
throw std::invalid_argument("invalid position");
Node<T> * const temp = new Node<T>(item, ptr->next);
ptr->next = temp;
if (tail == ptr)
tail = temp;
}
template <typename T>
void LinkedList<T>::InsertBefore(Node<T> const * arg, T const & item)
{
Node<T> * ptr = const_cast<Node<T>*> (arg);
if (NULL == ptr)
throw std::invalid_argument("invalid position");
Node<T> * const temp = new Node<T>(item, ptr);
if (head == ptr)
head = temp;
else
{
auto prevPtr = head;
while (prevPtr != &sentinel && prevPtr->next != ptr)
prevPtr = prevPtr->next;
if (prevPtr == &sentinel)
throw std::invalid_argument("invalid position");
prevPtr->next = temp;
}
}
} // namespace FoundationalDataStructure
#endif // LINKED_LIST_H
二、代码比较与分析
借助文本比较工具,对比上一篇(无sentinel)和本篇(有sentinel)的单链表实现代码。
1,类的声明
1)新增一个sentinel节点和一个访问接口;
2)head设计为指针的引用,同步操作;
2,构造和析构函数
1)head是sentinel节点的next指针的引用;
2)终端判断由NULL改为“&sentinel”。
3,复制和赋值函数
注意迭代结束条件
4,其他不同之处,主要是“终端判断由NULL改为“&sentinel””。
5,重点关注“Extract函数”的变化。它无需对头结点和其他节点分别处理。