数据结构--静态单链表

来源:互联网 发布:淘宝卖家论坛首页 编辑:程序博客网 时间:2024/06/14 21:03

学习了顺序表和单链表为什么还要学习静态单链表?顺序表和单链表的优势劣势不都分析了吗(见上一篇文章)?我想,存在即合理,那么我们就先分析分析为什么还会有这个需求。

问一个问题,如果需要频繁且长时间的增删数据元素应该选择哪种线性表?首先看到大量增删肯定会想到单链表,因为它不需要移动结点,效率会高不少,但是单链表真的是理想的选择吗?如果需要的数据最大个数是固定的呢?这就是需要实现静态单链表的原因之一。

当我们使用单链表长时间并且频繁的增删数据元素会发生什么?可能的结果就是产生大量的堆空间内存碎片,因为结点空间都是单个申请和释放的,长时间下来必定会导致堆空间逐渐变得碎片化,最终导致系统运行缓慢甚至崩溃。

静态单链表就是一种新型线性表,在原先的单链表的内部增加一片预留的空间,所以的Node对象都在这片空间中动态创建和动态销毁。这片连续的内存可以来自于堆、栈、全局区。当需要增加结点时就在这片空间中找到一个空闲的空间然后作为结点空间,当不需要时直接标记这片空间为可用即可。

那么静态单链表既有顺序表的固定长度特性又有单链表动态增删的特性了。

那么我们该如何实现呢?由于和单链表不同的地方仅在于结点空间申请和释放的方式不同而已,所以我们只需实现create和destroy函数的重写即可。所以它需要继承自LinkList类。

那么具体实现思路呢?

首先通过类模板实现StaticLinkList类。

在类中定义固定大小的空间。

重写create和destroy函数,改变内存的分配和归还方式。

在Node类中重载operator new,用于在指定内存上创建对象。

下面看StaticLinkList类的声明:

    template <typename T, int N>    class StaticLinkList : public LinkList<T>    {    protected:        typedef typename LinkList<T>::Node Node;//要想使用LinkList的Node需要指明作用域,但是这又导致编译器不知道它代表类型还是静态成员变量,所以需要加上typename关键字来标识T是泛指类型        struct SNode : public Node        {            void* operator new (unsigned int size, void* loc)            {                (void)size;                return loc;            }        };        unsigned char m_space[sizeof(SNode) * N];        int m_used[N];        Node* create();        void destroy(SNode* pn);    public:        StaticLinkList();        int capacity();    };
首先使用typedef将LinkList类里的Node使用起来,为什么需要这样的写法呢,在LinkList类中的Node牵扯到了泛指类型,所以 不能直接在子类中使用父类的成员变量,所以需要LinkList<T> :: Node,但是这样正确了吗?不是的,因为此时编译器在编译时不知道这种写法到底是一种类型还是静态变量,所以就需要typename来提示编译器这是一种类型了,为了简洁,所以用了typedef来重命名。

然后定义一个类SNode,继承自Node,然后在类里实现new的重载,即返回需要创建结点的返回地址。为什么定义SNode?因为Node里含有泛指类型T,当T为类类型时必然就会牵涉构造函数的调用。当我们在创建结点时分配好可用的空间后,我们分配好内存后在哪里调用构造函数呢?所以我们就需要重载new了。即返回需要调用构造函数的地址。

第三步是设置一片内存空间用来给定静态单链表的固定空间。

第四步设置一个数组来标识空间是否可以。

然后就是实现create和destroy的重写了。

create的实现:

Node* create()        {            SNode* ret = NULL;            for(int i = 0; i < N; i++ )            {                if(!m_used[i])                {                    ret = reinterpret_cast<SNode*>(m_space) + i;//分配空间,强转后进行指针操作                    ret = new(ret)SNode();//重载new,类类型在ret处调用构造函数创建对象,在ret处申请SNode大的空间。                    m_used[i] = 1;//标记空间已用                    break;//退出                }            }            return ret;        }
原理是先遍历寻找可用空间,再进行分配空间,找到了就标记一下退出遍历。


destroy的实现:

        void destroy(Node* pn)        {            SNode* space = reinterpret_cast<SNode*>(m_space);            SNode* psn = dynamic_cast<SNode*>(pn);//父类指针转换成子类指针            for(int i = 0; i < N; i++)            {                if( psn == (space + i) )                {                    m_used[i] = 0;//标记可用                    psn->~SNode();//调用析构函数归还空间,Node类里的T为类类型时就可以调用析构函数                }            }        }

它的主要作用就是归还空间。

StaticLinkList构造函数实现:

        StaticLinkList()        {            for(int i = 0; i < N; i++)            {                m_used[i] = 0;            }        }
将所有内存单元标记成可用。


capacity的实现:

        int capacity()        {            return N;        }

最后来一个获取最大容量的实现。


小结:

顺序表和单链表结合衍生出静态单链表。

静态单链表是LinkList的子类。

静态单链表在预留的空间中创建和删除结点对象。

静态单链表适合于频繁增删数据元素且最大元素个数固定的场合。



相关信息请访问单链表的实现博文。



原创粉丝点击