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

        本篇主要介绍一种重要的链表——“双向链表”(Doubly Linked Lists)。

        双向链表,顾名思义,它既可以从head开始正向遍历链表,也可以从tail开始反向遍历链表。双向链表的节点包含两个指针,一个指向下一个节点(successor,同单链表),另一个指向前一个节点(predecessor),因此,也可以将它看作是两个迭代方向不同的叠在一起的单链表。如下图a。

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

        以上是几种双向链表的结构图,包括空链表的状态。

        其中,图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(); }”的声明和定义格式,以及类模版前缀的添加方式。


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