在学习完循环链表之后,我们是不是觉得链表就完美了嘛?既有单链表,还有循环链表供我们使用,看起来貌似链表已经够我们所使用了,其实不然。试想下:如果我们在某个结点处需要查看它之前的结点,那么我们只能利用循环链表来遍历一次才能获取到这个结点,这种做法是很低效的。因此,我们需要另一种新的链表来供我们调用,那便是双向链表了。

        单链表天生的缺陷,那便是它的高效性是建立在从头结点开始访问链表中的数据元素的;如果需要逆向访问的话,那么它的效率极其低下。我们来看看下面的代码

双向链表(十八)_双向链表

        我们在插入的时候,其时间复杂度为 O(n),而在其获取的时候,其时间复杂度为 O(n²)。是不是觉得很奇怪?那么这时双向链表便产生了。它的设计思路是:在“单链表”的结点中增加一个指针 pre,用于指向当前结点的前驱结点。结构如下所示

双向链表(十八)_DualLinkList_02

        下来我们来看看双向链表的继承结构层次,如下

双向链表(十八)_DualLinkList_03

        它是基于 List 类的,和 LinkList 归于同一层次的。下来我们来看看它的具体实现,和 LinkList 很是相似


DualLinkList.h 源码

#ifndef DUALLINKLIST_H
#define DUALLINKLIST_H

#include "List.h"
#include "Exception.h"

namespace DTLib
{

template < typename T >
class DualLinkList : public List<T>
{
protected:
    struct Node : public Object
    {
        T value;
        Node* next;
        Node* pre;
    };

    mutable struct : public Object
    {
        char reserved[sizeof(T)];
        Node* next;
        Node* pre;
    } m_header;

    int m_length;
    int m_step;
    Node* m_current;

    Node* position(int i) const     // O(n)
    {
        Node* ret = reinterpret_cast<Node*>(&m_header);

        for(int p=0; p<i; p++)
        {
            ret = ret->next;
        }

        return ret;
    }

    virtual Node* create()
    {
        return new Node();
    }

    virtual void destroy(Node* pn)
    {
        delete pn;
    }
public:
    DualLinkList()      // O(1)
    {
        m_header.next = NULL;
        m_header.pre = NULL;
        m_length = 0;
        m_step = 1;
        m_current = NULL;
    }

    bool insert(const T& e)  // O(n)
    {
        return insert(m_length, e);
    }

    bool insert(int i, const T& e)      // O(n)
    {
        bool ret = ((0 <= i) && (i <= m_length));

        if( ret )
        {
            Node* node = create();

            if( node )
            {
                Node* current = position(i);
                Node* next = current->next;

                node->value = e;

                node->next = next;
                current->next = node;

                if( current != reinterpret_cast<Node*>(&m_header) )
                {
                    node->pre = current;
                }
                else
                {
                    node->pre = NULL;
                }

                if( next != NULL )
                {
                    next->pre = node;
                }

                m_length++;
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert new element ...");
            }
        }

        return ret;
    }

    bool remove(int i)      // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            Node* current = position(i);
            Node* toDel = current->next;
            Node* next = toDel->next;

            if( m_current == toDel )
            {
                m_current = toDel->next;
            }

            current->next = toDel->next;

            if( next != NULL )
            {
                next->pre = toDel->pre;
            }

            m_length--;

            destroy(toDel);
        }

        return ret;
    }

    bool set(int i, const T& e)     // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            position(i)->next->value = e;
        }

        return ret;
    }

    virtual T get(int i) const      // O(n)
    {
        T ret;

        if( get(i, ret) )
        {
            return ret;
        }
        else
        {
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invaild parameter i to get element ...");
        }
    }

    bool get(int i, T& e) const     // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if( ret )
        {
            e = position(i)->next->value;
        }

        return ret;
    }

    int find(const T& e) const      // O(n)
    {
        int ret = -1;
        int i = 0;
        Node* node = m_header.next;

        while( node )
        {
            if( node->value == e )
            {
                ret = i;
                break;
            }
            else
            {
                node = node->next;
                i++;
            }
        }

        return ret;
    }

    int length() const      // O(1)
    {
        return m_length;
    }

    void clear()        // O(n)
    {
        while( m_length > 0 )
        {
            remove(0);
        }
    }

    virtual bool move(int i, int step = 1)  // O(n)
    {
        bool ret = (0 <= i) && (i < m_length) && (step > 0);

        if( ret )
        {
            m_current = position(i)->next;
            m_step = step;
        }

        return ret;
    }

    virtual bool end()      // O(n)
    {
        return (m_current == NULL);
    }

    virtual T current()
    {
        if( !end() )
        {
            return m_current->value;
        }
        else
        {
            THROW_EXCEPTION(INvalidOPerationException, "No value at current position ...");
        }
    }

    virtual bool next()
    {
        int i = 0;

        while( (i < m_step) && !end() )
        {
            m_current = m_current->next;
            i++;
        }

        return (i == m_step);
    }

    virtual bool pre()
    {
        int i = 0;

        while( (i < m_step) && !end() )
        {
            m_current = m_current->pre;
            i++;
        }

        return (i == m_step);
    }

    ~DualLinkList()
    {
        clear();
    }
};

}

#endif // DUALLINKLIST_H

        我们下来做个测试,看看是否正确

#include <iostream>
#include "DualLinkList.h"

using namespace std;
using namespace DTLib;

int main()
{
    DualLinkList<int> d1;

    for(int i=0; i<5; i++)
    {
        d1.insert(0, i);
    }

    for(d1.move(0); !d1.end(); d1.next())
    {
        cout << d1.current() << endl;
    }

    cout << "begin" << endl;

    for(d1.move(d1.length()-1); !d1.end(); d1.pre())
    {
        cout << d1.current() << endl;
    }

    d1.move(d1.length()-1);

    cout << "end" << endl;

    return 0;
}

        我们先是正向打印,再是反向打印。结果应该是4-0, 0-4。我们来看看结果

双向链表(十八)_前驱后继_04

        结果正确,实现上基本没啥问题。再来测试下,测试代码如下

#include <iostream>
#include "DualLinkList.h"

using namespace std;
using namespace DTLib;

int main()
{
    DualLinkList<int> d1;

    for(int i=0; i<5; i++)
    {
        d1.insert(0, i);
        d1.insert(0, 5);
    }

    for(d1.move(0); !d1.end(); d1.next())
    {
        cout << d1.current() << endl;
    }

    cout << "begin" << endl;

    d1.move(d1.length()-1);

    while( !d1.end() )
    {
        if( d1.current() == 5 )
        {
            cout << d1.current() << endl;

            d1.remove(d1.find(d1.current()));
        }
        else
        {
            d1.pre();
        }
    }

    cout << "end" << endl;

    for(d1.move(d1.length()-1); !d1.end(); d1.pre())
    {
        cout << d1.current() << endl;
    }

    return 0;
}

        我们插入 i 的同时也插入5 ,也就是说,每插入一个 i ,便会插入一个 5。再次查找并删除 5 ,最后打印的是 0-4(因为是反向遍历打印)。看看结果

双向链表(十八)_DualLinkList_05

        我们看到结果是正确的,如我们所期望的那样。通过今天对双向链表的学习,总结如下:1、双向链表是为了弥补单链表的缺陷而设计的;2、在概念上,双向链表不是单链表,没有继承关系;3、双向链表的游标能够直接访问当前结点的前驱和后继;4、双向链表是线性表概念的最终实现(更贴近理论上的线性表)。后面我们会继续学习双向链表的子类:DualStaticLinkList 和 DualCircleList。