在C语言中有指针,所以我们可以通过链表来灵活的操作数据,但是在一些语言如Basic、Fortran等早期的编程高级语言中,由于没有指针,那么我们所讲的线性表的链式存储就无法使用,我们应该怎样来在这些语言中实现链表呢?
我们可以使用数组来代替指针,首先我们让数组的元素都是由两个数据域组成,data
和cur
,也就是说数组的每个下标都对应一个data
和cur
。数据域data
用来存放数据元素,也就是通常我们要处理的数据,而cur
相当于单链表中的next
指针,存放该元素的后继在数组中的下标,我们把cur
叫游标
我们把用数组描述的链表叫做静态链表
我们将之前的代码全部删除,重新编写:
# define MAXSIZE 1000
# define OK 1
# define ERROR 0
# define TRUE 1
# define FALSE 0
typedef int ElemType; //ElemType就是线性表中的元素的类型,这里设置为int
typedef int Status;
typedef struct{
ElemType data;
int cur;
} Component, StaticLinkList[MAXSIZE];
我们先定义一个结构体,结构体中有两部分:数据域和游标;在定义好结构体后我们再定义一个结构体数组,我们通过上述代码实现了结构体的创建和结构体数组类型的定义
接下来我们来初始化数组
Status InitList(StaticLinkList space){
int i;
for(i = 0; i < MAXSIZE-1; i++){
space[i].cur = i+1;
}
space[MAXSIZE-1].cur = 0;
return OK;
}
静态链表的第一个元素和最后一个元素需要分别处理:
cur
存放备用链表的第一个结点,备用链表就是指未被使用的数据元素cur
存放0,在链表中存放有数据时,最后一个结点的cur
存放第一个有数值的元素的下标,相当于头结点我们在学习动态链表时有个方法叫做malloc,这个方法可以在计算机内存空间中申请一段和结构体大小一样的空间来存放数据和下个结点的信息,那么在静态链表中我们如何来实现malloc呢?
代码实现:
int Malloc_SLL(StaticLinkList space){
int i = space[0].cur;
if(space[0].cur){
space[0].cur = space[i].cur;
}
return i;
}
我在这里使用的代码是在《大话数据结构》这本书里的代码,但在阅读过程中发现一个问题,我不明白在执行space[0].cur = space[i].cur;
前为什么要执行条件语句if(space[0].cur)
,感觉这个判断多此一举,但这本书的作者肯定是不会将没有用的代码写在书本中,所以这个判断语句应该是在对整条链表有无备用链表进行判断
如果结构体数组的长度为1000,那么除了第一个和最后一个元素时不能存储数据的,其余一共有998个结点可以让我们来使用,这998个结点的下标为1~998,当我们使用了997个结点后,我们在malloc空间时,执行Malloc_SLL
方法,此时space[0].cur
=998,i
=998,经过if(space[0].cur)
判断后,space[0].cur
=999,方法执行完成后返回998,但是在我们下次继续执行Malloc_SLL
方法时,按理说数组空间已经满了,不能再返回一个结点的下标,但按照Malloc_SLL方法还是会返回999,有逻辑上的错误,所以我们需要将最后一个可用结点也就是下标为998的结点也就是倒数第二个结点的cur设置为0,这样条件语句if(space[0].cur)
的作用才能讲得通
要将倒数第二个结点的cur
设置为0,我们需要将初始化数组的代码记性相应的修改:
Status InitList(StaticLinkList space){
int i;
for(i = 0; i < MAXSIZE-2; i++){
space[i].cur = i+1;
}
space[MAXSIZE - 2].cur = 0;
space[MAXSIZE - 1].cur = 0;
return OK;
}
修改后当我们将第998个结点使用完成后,再次执行Malloc_SLL
方法时将会返回0,线性表索引都是从1开始,所以可以通过对返回值的判断来确定malloc是否成功
通过返回的结点的索引我们可以在结点中存储数据,这个结点同时又下个结点的信息,相当于我们malloc了一段空间,在这段空间的数据域和指针域中分别存入了信息
要在静态链表中进行插入操作,我们只需要执行Malloc_SLL
方法后“申请”一段空间,在该空间完成数据的存入后,修改该结点的cur
中存放的数组元素索引即可
当存放入一些数据时(“甲”“乙”“丁”“戊”“己”“庚”),静态链表为:
代码实现:
Status ListInsert(StaticLinkList L, int i, ElemType e){
int j, k, l;
k = MAXSIZE - 1;
if(i < 1 || i > ListLength(L) + 1){ //插入位置错误
return ERROR;
}
j = Malloc_SLL(L); //获得一个结点的索引
if(j){
L[j].data = e;
for(l = 1; l < i - 1; l++){
k = L[k].cur;
}
L[j].cur = L[k].cur;
L[k].cur = j;
return OK;
}
return ERROR;
}
静态链表的物理位置和逻辑位置不一样,i
是逻辑上我们要删除的结点的位置,而我们通过for
循环来找到了我们要删除的结点在数组中的实际索引k
在动态链表中,当我们删除了一个结点后,我们通过free
方法来释放这段空间,那么在静态链表中我们应该如何来实现free功能呢?
我们先讲解删除操作的代码实现:
Status ListDelete(StaticLinkList L, int i){
int j, k;
if(i < 1 || i > ListLength(L)){
return ERROR;
}
K = MAXSIZE - 1;
for(j = 1; j <= i - 1; j++){
k = L[k].cur;
}
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SSL(L, j);
return OK;
}
和动态链表的删除结点的操作相似,找到要删除的结点的前一个结点,之后通过修改结点中的cur
来将我们要删除的结点删除,之后释放空间,这里要注意静态链表的物理位置和逻辑位置不一样,i
是逻辑上我们要删除的结点的位置,而我们通过for
循环来找到了我们要删除的结点在数组中的实际索引j
Free_SSL的代码实现为:
void Free_SSL(StaticLinkList L, int k){
L[k].cur = L[0].cur;
L[0].cur = k;
}
我们将Free_SSL嵌套到 ListDelete函数中,主要是因为在 ListDelete函数中可以找到我们要删除的结点的前一个结点在数组中的实际索引,之后通过Free_SSL释放这段空间
将释放的这段空间的索引赋给L[0].cur
,就是想在malloc的时候优先处理这个空位
在前面我们还调用了ListLength函数,下面是这个函数的代码实现:
int ListLength(StaticLinkList L){
int j = 0;
int i = L[MAXSIZE - 1].cur
while(i){
i = L[i].cur;
j++;
}
return j;
}
关于静态链表的free操作和ListLength函数的实现我在这里还不是太理解,后续我会将自己的理解进行补充
线性表中除了之前讲过顺序存储、链式存储以及静态链表外,还有循环链表和双向链表,这些链表只是对单链表的改进,操作类似,这里不做过多讲解