最近在复习大学学习的静态链表数据结构,从代码分析结构,卡在了备用链表那里,通过画图分析才掌握了它的结构。
静态链表结构
首先我们先来看看静态链表的结构定义
typedef struct{
ElemType data;
int cur;
}Component,StaticLinkList[MAXSIZE];
如上面代码所示,静态链表需要预先分配容量为MAXSIZE的存储空间(接下来以MAXSIZE值为5进行演示),data定义了数据元素,cur定义了数据元素的游标(可以理解为指向下一节点的指针),用来指示下一数据元素的位置。这种数据结构,虽不能满足动态申请空间,但在线性表的插入和删除元素时,不需要移动元素,只需修改游标就能实现,故仍具有链式存储的主要优点。
静态链表初始化过程如下:
Status InitList(StaticLinkList L)
{
int i;
for(i = 0;i < MAXSIZE-2; i++)
{
L[i].cur = i+1; //除备用链表最后元素,所有结点游标赋值为下一结点的下标
}
L[MAXSIZE-2].cur = 0; //备用链表的表尾
L[MAXSIZE-1].cur = 0; //数据链表表头
return OK;
}
初始化为空的静态链表如下图一所示
图一 空的静态链表
在备用链表中插入数据
接下来才是重点!!!实际上静态链表分为两个链表:备用链表和数据链表(两表的表头位置如上图一所示)。备用链表将未使用空间串联起来,数据链表才是真正存放数据的地方,那么具体是怎样存储数据的呢?接下来我们在数据链表中插入数字1,看以下向数据链表中插入数据的代码:
Status InsertList(StaticLinkList L,int i,ElemType e)
{
if(i<1||i>ListLength(L)+1)
return ERROR;
int j = Malloc_L(L); //备用链表返回空闲数据元素下标以供插入元素
int k = MAXSIZE-1; //数据链表表头的数据元素下标
if(j) //判断备用链表非空
{
L[j].data = e;
for(int s=1;s<=i-1;s++)
k = L[k].cur; //从数据链表头结点遍历到第i-1个元素
L[j].cur = L[k].cur; //将数据插入数据链表的第i-1个元素后
L[k].cur = j; //将第i-1个元素的游标指向刚插入元素的位置
return OK;
}
printf("备用链表为空!\n");
return ERROR; //备用链表为空
}
Malloc_L函数用来返回备用链表表头所指向的第一个空闲空间(此空间将会转换成数据链表空间用来插入数据),此外Malloc_L函数还会修改备用链表表头的游标指向下一空闲空间。函数如下:
int Malloc_L(StaticLinkList L)
{
int i = L[0].cur;
if(L[0].cur) //备用链表非空
{
L[0].cur = L[i].cur; //更新备用链表首结点游标,让其指向下一空白节点
}
return i;
}
调用如下插入函数,其效果如下图二。
InsertList(L,1,1);
图二 向备用链表添加一个元素
图二中用红色字体标注的显然是数据链表了,剩下的黑体字标注的是备用链表,每当要插入一个数据时,备用链表中便有一块空间转化成数据链表的空间,其转换细节分为以下四个步骤:
- 记录备用链表头L[0]游标,更改L[0]游标为2,使其指向L[2],此时备用链表操作完成
- 给L[1]的数据元素赋值,L[1]将加入数据链表中
- 遍历数据链表L[4]到要插入位置(第i位置)的前一个数据元素(第i-1位置),将位于i-1位置的元素游标赋值给位于i位置的元素游标
- 将插入元素的位置i赋值给位于i-1位置的元素游标,数据链表的插入操作完成
通过以上步骤的分析相信大家都已经熟悉了静态链表的插入操作,那么接下来再执行以下函数,你们能画出静态链表的数据结构图吗?
InsertList(L,1,2);
参考图如下,是不是画对了呢?
图三 向备用链表添加一个元素
关于静态链表的基本操作完整代码如下,欢迎大家一起交流学习
#include <stdio.h>
#define MAXSIZE 5
#define ERROR 0
#define OK 1
typedef bool Status;
typedef int ElemType;
typedef struct{
ElemType data;
int cur;
}Component,StaticLinkList[MAXSIZE];
Status visit(ElemType c); //刷出函数
Status InitList(StaticLinkList L); //初始化静态链表
int Malloc_L(StaticLinkList L); //返回备用链表表头所指向的第一个空闲空间供插入元素,更新表头游标指向下一空闲空间
Status InsertList(StaticLinkList L,int i,ElemType e);
Status DeleteList(StaticLinkList L,int i);
void Free_L(StaticLinkList L,int j);
int ListLength(StaticLinkList L);
void OutList(StaticLinkList L);
int main()
{
StaticLinkList L;
InitList(L);
printf("初始化后长度为:%d\n",ListLength(L));
for(int i=1;i<=2;i++)
{
InsertList(L,1,i);
}
OutList(L);
printf("插入两个元素后链表的长度为:%d\n",ListLength(L));
DeleteList(L,2);
OutList(L);
printf("删除一个元素后链表的长度为:%d\n",ListLength(L));
return 0;
}
Status InitList(StaticLinkList L)
{
int i;
for(i = 0;i < MAXSIZE-2; i++)
{
L[i].cur = i+1; //除最后元素,所有结点游标赋值为下一结点的下标
}
L[MAXSIZE-2].cur = 0; //用链表的表尾
L[MAXSIZE-1].cur = 0; //数据链表表头
return OK;
}
//返回备用链表表头所指向的第一个空闲空间供插入元素,更新表头游标指向下一空闲空间
int Malloc_L(StaticLinkList L)
{
int i = L[0].cur;
if(L[0].cur) //备用链表非空
{
L[0].cur = L[i].cur; //更新备用链表首结点游标
}
return i;
}
Status InsertList(StaticLinkList L,int i,ElemType e)
{
if(i<1||i>ListLength(L)+1)
return ERROR;
int j = Malloc_L(L); //备用链表返回空闲数据元素下标以供插入元素
int k = MAXSIZE-1; //数据链表表头的数据元素下标
if(j) //备用链表非空
{
L[j].data = e;
for(int s=1;s<=i-1;s++)
k = L[k].cur; //从数据链表头结点遍历到第i-1个元素
L[j].cur = L[k].cur; //将数据插入数据链表的第i-1个元素后
L[k].cur = j;
return OK;
}
printf("备用链表为空!\n");
return ERROR; //备用链表为空
}
Status DeleteList(StaticLinkList L,int i)
{
if(i<1||i>ListLength(L)+1)
return ERROR;
int k = MAXSIZE-1;
int j;
for(int s=1;s<=i-1;s++)
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_L(L,j);
return OK;
}
void Free_L(StaticLinkList L,int j)
{
L[j].cur = L[0].cur;
L[0].cur = j;
}
int ListLength(StaticLinkList L)
{
int j=0;
int i = L[MAXSIZE-1].cur;
while(i)
{
i = L[i].cur;
j++;
}
return j;
}
void OutList(StaticLinkList L)
{
int j = 0;
int i = L[MAXSIZE-1].cur; //从数据链表开始遍历
while(i)
{
visit(L[i].data);
i=L[i].cur;
j++;
}
printf("\n");
}
Status visit(ElemType c)
{
printf("%d ",c);
return OK;
}