本篇主要介绍“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函数”的变化。它无需对头结点和其他节点分别处理。