基础数据结构之数组与链表(四)

        本篇主要介绍“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张图片

1)新增一个sentinel节点和一个访问接口;

2)head设计为指针的引用,同步操作;


2,构造和析构函数

基础数据结构之数组与链表(四)_第2张图片

1)head是sentinel节点的next指针的引用;

2)终端判断由NULL改为“&sentinel”。


3,复制和赋值函数

基础数据结构之数组与链表(四)_第3张图片

注意迭代结束条件


4,其他不同之处,主要是“终端判断由NULL改为“&sentinel””。


5,重点关注“Extract函数”的变化。它无需对头结点和其他节点分别处理。

你可能感兴趣的:(基础数据结构之数组与链表(四))