对于线性链表可用一维数组来进行描述,这种描述方法便于在没有指针类型的高级程序设计语言中使用链表结构,即使用数组描述的链表称为静态链表。由于全局数组是存储在静态区也叫做静态链表。
C语言具有指针能力使其非常容易地操作内存中的地址和数据,对于面向对象的语言虽然不使用指针,但因为启用了对象引用机制,从某种角度也间接实现了指针的某些作用。但对于早期的编程语言,由于没有指针,链表结构等也就无法实现了,怎么办呢?于是,就有了使用数组来代替指针来描述单链表。使用数组描述的链表叫做静态链表(static list),又称为游标实现法。
动态链表
静态链表和动态链表是线性表链式存储的两种不同的表示方式。
- 在动态链表中,节点的申请和释放分别使用内存申请函数,如C语言的malloc()和free()两个函数实现。所以链表长度没有限制。由于是动态申请内存,所以每个节点的物理地址不连续,要通过指针来顺序访问。
- 在静态链表中,操作的是数组,不存在像动态链表的节点申请和释放问题,所以需要自己实现这两个函数,才可以做插入和删除操作。静态链表使用类似于数组方法实现,是顺序的存储结构,在物理上是连续的,且需要预先分配地址空间大小,所以静态链表的初始长度一般是固定的,在做插入和删除操作时不需要移动元素,仅需要修改指针。
静态链表原理
一个数组的逻辑分为两部分:空闲链表部分和非空闲链表部分,它们通过指针来连接,空闲链表由空闲头节点连接起来,非空闲链表由非空闲头节点连接起来。空闲链表本质上是作为需要管理的内存,当插入节点时,向管理的内存部分申请空间。当删除节点时,向管理的内存部分释放内存。
- 下标为0的节点是空闲链表的头节点
- 下标为1的节点是非空闲链表的头节点
首先,让数组的元素由两个数据域组成,data和cursor。即数组的每个下标都对应一个data和一个cursor。数据域data用来存放数据元素,游标cursor相当于单链表中的next指针,存放元素后继在数组中的下标。
/*线性表的静态链表存储结构*/
#define MAXSIZE 1000
typedef struct
{
ElemType data;//数据域
int cursor;//游标,为0表示无指向。
} Component, StaticLinkList[MAXSIZE];
StaticLinkList space;
结构体数组中,space[0]作为备用链表的头指针,space[1]作为链表的头指针。
初始化数组状态
- 对数组第一个和最后一个元素作为特殊元素处理,不存数据。
- 把未被使用的数组元素称为备用链表
- 数组第一个元素即下标为0的元素的cursor就存放备用链表的第一个节点的下标
- 数组最后一个元素的cursor则存放第一个有数值的元素的下标,相当于单链表中的头节点作用。
- 当整个链表为空时则为O^2
/**
* 将一维数组space中各分量链成一备用链表
* space[0].cursor 为头指针,0表示空指针。
*/
Status InitList(StaticLinkList space)
{
int i;
for(i=0; i<MAXSIZE; i++){
space[i].cursor = i+1;
}
//静态链表为空,最后一个元素的游标为0
space[MAXSIZE-1].cursor = 0;
return OK;
}
假设已经将数据存入静态链表,比如分别存放甲乙丙丁午己庚等数据。
静态链表的插入操作
静态链表如何模拟单链表进行插入和删除操作呢?静态链表要解决的是:如何用静态链表模拟动态链表结构的存储空间分配,也就是需要的时候申请,不需要的时候释放。
为了辨明数组中那些分量未被使用,解决的办法是将所有未被使用过的及已被删除的分量用游标链成一个备用的链表,每当进行插入时,便可以从备用链表上取得第一个节点作为待插入的新节点。
获得空闲分量的下标
/**
* 辨明数组中那些分量未被使用,解决的办法是将所有未被使用多的及已被删除的分量用游标链成一个备用的链表。
* 每当进行插入时,便可以从备用链表上取得第一个节点作为待插入的新节点。
*/
int MallocSLL(StaticLinkList space)
{
int i = space[0].cursor;//当前数组第一个元素的游标,即第一个备用空闲的下标。
//若备用空间链表非空则返回分配的节点的下标否则返回0
if(space[0].cursor){
//将下一个分量用来做备用
space[0].cursor = space[i].cursor;
}
return i;
}
在静态链表某个元素之前插入新元素
Status InsertSLL(StaticLinkList L, int i, ElemType e)
{
int j,k,l;
//边界检查
if(i<1 || i>ListLength(L)+1){
return ERROR;
}
//获得空闲分量的下标
int j = MallocSSL(L);
if(j){
//将数据赋值给此分量的数据域
L[j].data = e;
//找到第i个元素之前的位置
k = MAXSIZE - 1;//最后一个元素
for(l=1; l<=i-1; l++){
k = L[k].cursor;
}
//将第i个元素之前的游标值赋值给新元素的游标
L[j].cursor = L[k].cursor;
//将新元素的下标赋值给地i个元素之前的元素的游标
L[k].cursor = j;
return OK;
}
}