声明:本文为学习数据结构与算法分析(第三版) 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