前言
上一节讲了单链表的操作,可以看出单链表解决了顺序存储结构的不足,但是单链表也有其相对劣势:单链表的实现严重依赖指针!数据元素中必须包含一个额外的指针域!没有指针的语言无法实现!
为此,我们就引入了静态链表,静态链表可以说是在顺序表的基础上进行了改进。
1.静态链表的定义
在顺序表的基础上,做了如下改进:
1.顺序表数组中的元素由两个数据域组成:data和next;
2.data域用于存储数据;
3.next域用于存储下一个元素在数组中的下标。
其示意图如下:
总结出来,就是静态链表是在顺序表的基础上利用数组实现的单链表!
2.静态链表的常用操作
在对静态链表进行操作之前,我们要对数据进行封装,对结点和静态链表的结构体进行定义。
typedef void StaticList;
typedef void StaticListNode;
typedef struct _tag_StaticListNode //结点结构体定义
{
unsigned long long int data; //数据元素,在64位机中,该变量以为8字节
int next; //保存链表里的元素在数组里的下标
} TStaticListNode;
typedef struct _tag_StaticList //静态链表结构体定义
{
int capacity; //静态链表的容量(最多能容纳多少个元素)
TStaticListNode header; //链表头结点的定义
TStaticListNode node[]; //存放链表的数组,使用柔性数组来实现
} TStaticList;
2.1创建
在对静态链表进行插入和删除操作时,要对链表的空闲位置查找,为此,我们需要在创建静态链表时,把链表中的空闲位置给标定出来,我们就使用了一个宏定义。(这里的空闲位置我是这样理解的,相当于暂时将数据存储在这里,在插入和删除时我们就可以对next域进行像单链表那样的操作。若有不对,欢迎各位大佬指正!)
#define AVAILABLE -1 //-1表示该位置现在空闲可以使用,用来标识数组的空闲位置
StaticList* StaticList_Create(int capacity) //O(n)
{
TStaticList* ret = NULL;
int i = 0;
if (capacity > 0) //对静态链表的容量进行合法性判断
{
//动态地申请内存空间,由于数组下标为0的元素被当做表头,所以在申请时加1
ret = (TStaticList*)malloc(sizeof(TStaticList) + sizeof(TStaticListNode) * (capacity + 1));
}
if (ret != NULL)
{
ret -> capacity = capacity; //初始化链表的容量
ret -> header.data = 0; //初始化表链表的长度
ret -> header.next = 0; //初始化表头结点的指向下标
for (i = 0; i <= capacity; i++)
{
ret -> node[i].next = AVAILABLE; //表明该位置可以使用
}
}
return ret;
}
2.2销毁
直接free掉进行。
void StaticList_Destroy(StaticList* list) //O(1)
{
free(list);
}
2.3清空
与顺序表,单链表的清空操作不同,静态链表的清空还需要标明空闲位置。
void StaticList_Clear(StaticList* list) //O(n)
{
TStaticList* sList = (TStaticList*)list;
int i = 0;
if (sList != NULL) //对链表进行合法性判断
{
sList -> header.data = 0;
sList -> header.next = 0;
for (i = 0; i <= sList -> capacity; i++)
{
sList -> node[i].next = AVAILABLE; //表明该位置可以使用
}
}
}
2.4获取长度
同顺序表、单链表的获取长度的操作一样。
int StaticList_Length(StaticList* list) //O(1)
{
TStaticList* sList = (TStaticList*)list;
int ret = -1;
if (sList != NULL) //对链表进行合法性判断
{
ret = sList -> header.data;
}
return ret;
}
2.5获取容量
int StaticList_Capacity(StaticList* list) //O(1)
{
TStaticList* sList = (TStaticList*)list;
int ret = -1;
if (sList != NULL) //对链表进行合法性判断
{
ret = sList -> capacity;
}
return ret;
}
2.6插入元素
从思路上来说,对插入元素操作的理解与单链表中的插入元素操作大同小异,其示意图为:
操作步骤如下:
1.判断线性表是否合法;
2.判断插入位置是否合法;
3.在数组中查找空闲位置index;
4.由表头开始通过next域移动pos次后,当前元素的next域为要插入的位置;
5.将新元素插入;
6.线性表长度加1。
int StaticList_Insert(StaticList* list, StaticListNode* node, int pos) //O(n)
{
TStaticList* sList = (TStaticList*)list;
int ret = (sList != NULL);
int current = 0;
int index = 0;
int i = 0;
ret = ret && (sList -> header.data + 1 <= sList -> capacity);
ret = ret && (pos >= 0) && (node != NULL);
if (ret)
{
for (i = 1; i < sList -> capacity; i++) //在数组中查找空闲位置index
{
if (sList -> node[i].next == AVAILABLE)
{
index = i;
break;
}
}
sList -> node[index].data = (unsigned long long int)node;
sList -> node[0] = sList -> header;
for (i = 0; (i < pos) && (sList -> node[current].next != 0); i++) //由表头开始通过next域移动pos次
{
current = sList -> node[current].next;
}
sList -> node[index].next = sList -> node[current].next; //当前元素的next域为要插入的位置
sList -> node[current].next = index;
sList -> node[0].data++; //线性表长度加1
sList -> header = sList -> node[0];
}
return ret;
}
2.7获取元素
获取静态链表中的第pos个元素的操作步骤为:
(1)判断线性表是否合法;
(2)判断位置是否合法;
(3)由表头开始通过next域移动pos次后,当前元素的next域即要获取元素在数组中的下标。
StaticListNode* StaticList_Get(StaticList* list, int pos) //O(n)
{
TStaticList* sList = (TStaticList*)list;
StaticListNode* ret = NULL;
int i = 0;
int current = 0;
int object = 0;
if ((sList != NULL) && (pos >= 0) && (pos < sList -> header.data)) //对链表进行合法性判断
{
sList -> node[0] = sList -> header; //令数组里的第一个元素为表头结点
for (i = 0; i < pos; i++)
{
current = sList -> node[current].next; //由表头开始通过next域移动pos次后
}
object = sList -> node[current].next; //当前元素的next域即要获取元素在数组中的下标
ret = (StaticListNode*)(sList -> node[object].data);
}
return ret;
}
2.8删除元素
静态链表的删除元素操作与单链表中的删除元素操作思路是一样的,其示意图为:
删除静态链表中第pos个元素的操作步骤如下:
(1)判断线性表是否合法;
(2)判断插入位置是否合法;
(3)获取第pos个元素;
(4)将第pos个元素从链表中删除;
(5)线性表长度减1。
StaticListNode* StaticList_Delete(StaticList* list, int pos) //O(n)
{
TStaticList* sList = (TStaticList*)list;
StaticListNode* ret = NULL;
int i = 0;
int current = 0;
int object = 0;
if (sList != NULL && (pos >= 0) && (pos < sList -> header.data)) //对链表进行合法性判断
{
sList -> node[0] = sList -> header; //令数组里的第一个元素为表头结点
for (i = 0; i < pos; i++)
{
current = sList -> node[current].next; //由表头开始通过next域移动pos次后
}
object = sList -> node[current].next; //当前元素的next域即要获取元素在数组中的下标
sList -> node[current].next = sList -> node[object].next;
sList -> node[0].data--; //线性表长度减1
sList -> header = sList -> node[0];
sList -> node[object].next = AVAILABLE; //表明该位置可以使用
ret = (StaticListNode*)(sList -> node[object].data);
}
return ret;
}
3.测试
测试静态链表的方法和测试单链表的方法一样,在这里,我使用的是头插法来对其进行测试。
#include <stdio.h>
#include <stdlib.h>
#include "StaticList.h"
int main(int argc, char *argv[])
{
StaticList* list = StaticList_Create(10);
int index = 0;
int i = 0;
int j = 1;
int k = 2;
int x = 3;
int y = 4;
int z = 5;
StaticList_Insert(list, &i, 0);
StaticList_Insert(list, &j, 0);
StaticList_Insert(list, &k, 0);
for (index = 0; index < StaticList_Length(list); index++)
{
int *p = (int*)StaticList_Get(list, index);
printf("%d\n", *p);
}
printf("\n");
while(StaticList_Length(list) > 0)
{
int *p = (int*)StaticList_Delete(list, 0);
printf("%d\n", *p);
}
printf("\n");
StaticList_Insert(list, &x, 0);
StaticList_Insert(list, &y, 0);
StaticList_Insert(list, &z, 0);
printf("Capacity: %d, Length: %d\n", StaticList_Capacity(list), StaticList_Length(list));
printf("\n");
for (index = 0; index < StaticList_Length(list); index++)
{
int *p = (int*)StaticList_Get(list, index);
printf("%d\n", *p);
}
StaticList_Destroy(list);
return 0;
}
我们创建了一个容量为10的静态链表,对其进行了插入和删除元素的操作,得到的结果为: