数据结构:线性表

声明:本文为学习数据结构与算法分析(第三版) Clifford A.Shaffer 著的学习笔记,代码有参考该书的示例代码。

线性表应该能够在任意位置插入和删除。读取任意位置的值,并且允许做出更改。
对于线性表的实现有两种方法,数组或者链表。

ADT

首先定义线性表的ADT

template<typename E>
class List
{
    List(const List& list) {}
    void operator= (const List& list) {}
    public:
    virtual ~List() {}
    virtual void clear() = 0;
    virtual void insert(const E& item) = 0;
    virtual void append(const E& item) = 0;
    virtual E remove() = 0;
    virtual void moveToStar() = 0;
    virtual void moveToEnd() = 0;
    virtual void prev() = 0;
    virtual void next() = 0;
    virtual int length() const = 0;
    virtual int currPos() const = 0;
    virtual void moveToPos(int) = 0;
    virtual E& getValue() const = 0;
};

对于线性表的实现有数组和链表两种。
在数组的实现方面,我将它设计为动态的数组。
当数组满时,怎么办?

数组

如果数组容量满时,容量增加一倍;
如果数组空时,容量减少一半;

链表

线性表的另一种实现方法是用链表。
链表本身就是动态的。
链表的实现方面就有一些技巧了。在线性表的实现中,保存了head指针、tail指针和curr指针(当前指针)
为了操作的便利有如下两个方法:

  • 设计一个表头结点
  • 让curr指针指向当前元素的前一个元素

设计一个表头结点,就不再需要考虑空链表的情况,节省了源代码。
让curr的指针指向当前元素的前一个元素,好处是

  • 执行插入操作便利不少
  • 和表头节点配合使用,味道更佳(其实是空链表的情况比较便利)

还可以建立一个可利用空闲链表,重载结点的new和delete操作符,进行程序的空间管理。
结点的实现代码如下:

template<typename E>
struct Link
{
    private:
    static Link<E>* freeLink;
    public:
    E item;
    Link* next;

    //-------function
    Link(Link* n = nullptr) : next(n) {}
    Link(const E& it, Link* n = nullptr): item(it), next(n) {}
    void* operator new(size_t t)
    {
        if(freeLink==nullptr) return ::new Link;
        Link<E>* temp = freeLink;
        freeLink = freeLink->next;
        return reinterpret_cast<void*>(temp);
    }
    void operator delete (void* ptr)
    {
        Link<E>* t = reinterpret_cast<Link<E>* >(ptr);
        t->next = freeLink;
        freeLink = t;
    }
    static void clear()
    {
        Link<E>* t;
        while(freeLink!=nullptr)
        {
            t = freeLink;
            freeLink = freeLink->next;
            ::delete t;
        }
    }
};

template<typename E>
Link<E>* Link<E>::freeLink = nullptr;

链表的实现过程中,为了知道什么时候回收可空闲链表,实现了一个计数类:

template<typename E>
class Counter
{
    static size_t count;
    public:
    Counter() { ++count; }
    Counter(const Counter& c) { ++count; }
    virtual ~Counter() { --count; }
    size_t getCounter() const { return count; }
};
template<typename E>
size_t Counter<E>::count = 0;

当类的对象个数只有1个时,在它的析构函数里对可利用空闲链表回收空间。

两种方法的比较

设 n 表示线性表中当前元素的数目,P 表示指针的存储单元大小(一般为4),E 表示数据元素的存储单元大小,D 表示可以在数组中存储的线性表示元素的最大数目,则顺序表(数组的实现)的空间需求为 DE。如果不考虑任何给定时刻链表中实际村粗元素的数目,则链表的空间需求为 n(P+E) 。
一般情况下,当线性表中的元素相对较少时,链表的实现比顺序表的更省空间。反之,当数组几乎被填满时,顺序表的实现空间效率更高。
为此,可以求出 n 的临界值:

n>DE/(P+E)

满足上述条件是,在任何实际情况下,顺序表的空间效率都更高。

双链表

还可以使用双链表的方式实现,即结点中除了有元素的值、下一个结点的指针外,还有前驱结点的指针。这里并没有实现

代码将会放到github上: xiaosa233

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值