您的位置:首页 > 其它

(学习笔记 5)静态链表

2017-10-21 22:54 465 查看
静态链表(static linked list):用数组来描述的链表,用数组元素的下标来模拟单链表的指针。这种描述方法叫做游标实现法。

游标52345671
数据ABDE
下标012345620
线性表的静态链表存储结构如下:

#define MAXSIZE 20
#define ElemType int
typedef struct{
ElemType data; //数据
int cur; //游标(cursor)
} StaticLinkList[MAXSIZE];


静态链表初始化:

对静态链表的初始化相当于初始化数组。

#include <stdio.h>

#define OK 1
#define ERROR 0
#define MAXSIZE 20
#define ElemType int

typedef int status;
typedef struct{
ElemType data; //数据
int cur; //游标cursor
} StaticLinkList[MAXSIZE];

/* 初始化静态链表 */
status initList(StaticLinkList space){
int i;
for(i = 0; i < MAXSIZE; i++){
space[i].data = 0; //初始化数据为0
space[i].cur = i + 1;
}

//初始化时将最后一个游标指向0,实际有数据时指向第一个有数值的元素的下标
space[MAXSIZE - 1].cur = 0;
return OK;
}

/* 输出静态链表 */
status printList(StaticLinkList space){
int i;
for(i = 0; i < MAXSIZE; i++){
printf("space[%d].data = %c; space[%d].cur = %d\n",i,space[i].data,i,space[i].cur);
}
return OK;
}

int main(){
//初始化静态链表
StaticLinkList space;
initList(space);
return 0;
}


初始化后的静态链表,如图所示:



静态链表说明:

① 静态链表的第一个和最后一个元素做特殊处理,他们的data不存放数据;

② 静态链表中,通常把未使用的数组元素称为备用链表;

③ 数组的第一个元素,即下标为0的那个元素的cur就存放备用链表的第一个结点的下标;

④ 数组的最后一个元素,即下标为MAXSIZE-1的cur,则存放第一个有数值的元素的下标,相当于单链表头结点的作用;

⑤ 在有值的结点中,最后一个结点的cur保存0,即指向数组的第一个元素

静态链表赋值:

比如,创建一个data分别为A、B、D、E的静态链表。最终需要实现如下效果:

游标52345671
数据ABDE
下标012345620
代码实现如下:

/**
* 创建一个有值静态链表
*/
status createList(StaticLinkList space,char data[4]){
int i,len;
len = strlen(data);

//第一个结点(i = 0)不存放数据,所以循环从i = 1开始
for(i = 1; i <= len; i++){
if(i == 1){
//最后一个结点的cur存放第一个有值的结点的下标
space[MAXSIZE - 1].cur = i;
}
space[i].data = data[i-1]; //给i对应的结点赋值
if(i == len){
//第一个结点(i = 0)的cur存放备用链表的第一个结点的下标,即i的cur
space[0].cur = space[i].cur;
//i为最后一个有值的节点,其cur中保存第一个结点的下标,即0
space[i].cur = 0;
}
}
return OK;
}

/* 输出静态链表 */
status printList(StaticLinkList space){
int i;
for(i = 0; i < MAXSIZE; i++){
printf(" space[%d].data = %c; space[%d].cur = %d\n",i,space[i].data,i,space[i].cur);
}
return OK;
}

/* 只输出赋值部分的静态链表(不再包含未用到的备用链表)*/
status printList2(StaticLinkList space){
int i,k;
//静态链表的最后一个结点的cur中,保存的是第一个有值的节点的下标
k = space[MAXSIZE-1].cur;
for(i = 1; i < MAXSIZE; i++){
//只输出有值的静态链表,备用链表不再输出
//因为静态链表中最后一个赋值的结点的cur保存的是0,所以k == 0 可以作为结束标识
if(k == 0) break;
printf(" space[%d].data = %c; space[%d].cur = %d\n",k,space[k].data,i,space[k].cur);
k = space[k].cur; //移动到下一个结点
}
return OK;
}

int main(){
//定义字符串数组
char data[] = "ABDE";

//初始化静态链表
StaticLinkList space;
initList(space);
//printList(space);

//为静态链表赋值,生成具有现实意义的静态链表
createList(space,data);
printList(space); //输出整个静态链表
printf("==================\n");
printList2(space); //只输出赋值部分的静态链表
return 0;
}


输出截图如下:



静态链表的插入:

思考:如何用静态链表墨迹动态链表结构的存储空间分配呢?也就是需要的时候申请,不需要的时候能立即释放呢?

通过前面所学我们知道,在动态链表中,结点的申请和释放分别借用c语言的malloc()和free()两个函数来实现的。在静态链表中,操作的是数组,不存在像动态链表的节点申请和释放的问题,为了明确数组中哪些分量未被使用,解决的办法是将所有未被使用过的及被删除的用游标链成一个备用链表。

每当进行插入操作时,便可以从备用链表中取得第一个结点作为待插入的新结点。假设要在B后面插入C,图例如下:



算法实现:

首先需要获得空闲分量的下标,即备用链表的第一个结点的下标;

修改第一个结点和赋值的最后一个结点的cur保存的备用链表的下标,修改为备用链表第二个节点的下标,即备用链表的第一个结点的cur保存的值;

将待插入数据赋值给备用链表的第一个结点的下标,将其cur指向待插入位置的下标;

修改待插入位置的前一个元素的cur为待插入元素的下标。

代码实现如下:

/**
* 向静态链表中下标i位置插入元素e
* @param StaticLinkList space 静态链表
* @param int i 待插入位置的下标,静态链表的下标从0开始
* @param ElemType e 待插入的值
*/
status insertList(StaticLinkList space,int i, ElemType e){
int l,j,precur,k;
if(i < 1 || i >= space[0].cur){
return ERROR;
}

//首先需要获得空闲分量的下标,即备用链表的第一个结点的下标
l = space[0].cur;

//给备用链表中的第一个结点的data赋值
space[l].data = e;

//因为备用链表的第一个结点被使用了,所以需要修改space[0].cur保存的值
//将第一个节点cur(space[0].cur)保存为备用链表的第二个节点的下标,即备用链表第一个结点的cur中保存的值
space[0].cur = space[l].cur;

//k为临时存放当前节点下标的参数,初始值保存的是第一个有值的节点下标
k = space[MAXSIZE - 1].cur;
for(j = 1; j < MAXSIZE; j++){

//保存待插入结点的前一个结点下标
//因为其指向的下一个结点改变了,此处先记录下来
if(space[k].cur == i){
precur = k;
break;
}

//移动当前节点
k = space[k].cur;
}

//待插入结点的前一个结点的cur应该指向待插入的结点下标(i为插入结点的位置)
space[precur].cur = l;
//将待插入结点的cur(space[k].cur)指向i位置的节点下标(相当于i位置的结点要后移一位)
space[l].cur = i;

return OK;
}


输出截图如下:



静态链表的删除:

(1)删除链表中第i个数据元素

例如,删除第3个元素:



代码如下:

/**
* 删除链表中第i个数据元素
*/
status deleteList(StaticLinkList space, int i){
int j,k;

if(i < 1 || i >= space[0].cur){
return ERROR;
}

//k为临时存放当前节点下标的参数,初始值保存的是第一个有值的节点下标
k = MAXSIZE - 1;
for(j = 1; j <= i - 1; j++){
k = space[k].cur; //移动当前节点
if(j == i-1){
space[space[k].cur].data = 0;
}
}

//先保存第i个元素结点的下标
j = space[k].cur;
//将当前结点的前一个结点的游标指向当前节点的下一个结点(当前节点即为将要删除的节点)
space[k].cur = space[j].cur;
//当前节点的游标保存原备用链表的第一个下标,即将原备用链表的第一个结点变为备用第二结点
space[j].cur = space[0].cur;
//将当前节点置为备用链表的第一个结点
space[0].cur = j;
return OK;
}


输出结果截图如下:



(2)删除结点值等于data的结点

例如,删除结点值等于B的结点,代码如下:

/**
* 删除链表中结点data等于e的元素
*/
status deleteListByData(StaticLinkList space, ElemType e){
int j,i,k,precur;

//k为临时存放当前节点下标的参数,初始值保存的是第一个有值的节点下标
k = MAXSIZE - 1;
for(j = 1; j < MAXSIZE - 1; j++){
precur = k; //记录上一个节点位置
k = space[k].cur; //移动当前节点

if(space[k].data == e){ //找到了要删除的结点
space[k].data = 0;
i = k;
//将当前结点的前一个结点的游标指向当前节点的下一个结点(当前节点即为将要删除的节点)
space[precur].cur = space[space[precur].cur].cur;
break;
}
}

//当前节点的游标保存原备用链表的第一个下标,即将原备用链表的第一个结点变为备用第二结点
space[i].cur = space[0].cur;
//将当前节点置为备用链表的第一个结点
space[0].cur = i;
return OK;
}


输出截图如下:



静态链表优缺点总结:

优点:

在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入删除操作需要移动大量元素的缺点。

缺点:

没有解决连续存储分配(数组)带来的表长难以确定的问题。

失去了顺序存储结构随机存取的特性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  链表 数据 存储 结构