之前介绍的各种链表都是使用指针类型实现的,链表中结点空间的分配和回收(即释放)均是由编译系统提供的标准函数malloc和free动态实现的,故称之为动态链表。
BASIC、FORTRAN 等高级语言并没有提出“指针”这种数据类型,在这种语句设施下,若仍想采用链表作存储结构,采用顺序存储结构数组模拟实现链表,在数组的每个表目中设置“游标(Cursor)”来模拟实现指针,由程序员自己编写从数组中“分配结点”和“回收结点”的过程。这种方式被称为静态单链表(Static Linked List)。
用游标模拟实现链表的方法是:定义一个较大的结构体数组作为备用结点空间(即存储池)。每个结点应包含两个域:data域和next域。data域用来存放结点的数据信息,注意:此时的next域存放的不再是指针而是游标,游标存放的是其后继结点在结构体数组中的相对位置(即数组下标值)。数组的第0个分量可以设计成表的头结点,头结点的next域指示了表中第一个结点的位置。表尾结点的next域为-1,表示静态单链表的结束。
静态单链表定义如下
typedef char DataType;//这样写方便下面的举例
#define Maxsize (100)
typedef struct StaticList
{
DataType data;
int cursor;
} StaticList[Maxsize];
通过变量定义语句StaticList S; 定义的静态单链表S中存储着线性表{a,b,c,d,f,g,h,i}。
例1、要在第四个元素后插入e,方法是:先申请一个空闲空间并置入元素e,即:S[9].data = e,然后修改第4个元素的游标域,将e插入到链表中,即:S[9].cursor = S[4].cursor,S[4].cursor = 9。
例2、若要删除第8个元素h,则先顺着游标链通过记数找到第7个元素存储位置6,删除的具体做法是令S[6].cursor = S[7].cursor。
在上述例子中未考虑对已释放空间的回收,这样在经过多次插入和删除后,会造成静态单链表的“假满”。即表中有很多的空闲空间,但却无法再插入元素。造成这种现象的原因是未对已删除元素所占的空间进行回收。
解决这个问题的方法是将所有未被分配的结点空间以及因删除操作而回收的结点空间用游标链成一个备用静态单链表。当进行插入操作时,先从备用静态单链表上取一个分量来存放待插入的元素,然后将其插入到已用静态单链表的相应位置。当进行删除操作时,则将被删除的结点空间链接到备用链表上以备后用。这种方法是指在已申请的大的存储空间中有一个已用静态单链表,还有一个备用静态单链表。已用静态单链表的头指针为1,备用静态单链表的头指针需另设一个变量av来表示。
//初始化
//所谓初始化操作,是指将这个静态单链表初始化为一个备用静态单链表。
//设space为静态单链表的名字,av为备用静态单链表的头指针。
void initial(StaticList space, int *av)
{
int k;
space[0].cursor = -1;//设置已用静态单链表的头指针指向位置0
for(k=1; k<Maxsize-1; k++)
{
space[k].cursor = k+1;//连链
}
space[Maxsize-1].cursor = 0;//标记链尾
*av = 1;//设置备用静态单链表头指针初值
}
//分配结点
//从备用静态单链表摘下一个结点空间,分配给待插入静态单链表中的元素。
int getnode(Static space, int *av)
{
int i;
i = *av;
*av = space[*av].cursor;
return i;
}
//结点回收
//将下标为k的空闲结点插入到备用静态单链表
void freenode(Static space, int *av, int k)
{
space[k].cursor = *av;
*av = k;
}