文章目录
- 5.静态链表
- 5.1定义
- 5.2初始化
- 5.3插入
- 5.4查找
- 5.5删除
- 5.6遍历
- 5.7长度
- 5.8特点
5.静态链表
静态链表是使用数组来模拟链表结构的一种数据结构,用数组的方式实现的链表。
它与传统链表的区别在于,静态链表使用数组保存节点,每个节点包括数据域和游标(指向下一个节点的位置)。
单链表与静态链表的区别:
单链表: 各个结点在内存中星罗棋布、散落天涯。
静态链表:分配一整片连续的内存空间, 各个结点集中安置。
静态链表的优点
- 不需要像动态链表那样频繁地进行内存分配和释放,可以节省内存管理的开销。
- 可以提前分配一定大小的静态存储空间,避免了动态分配内存的不确定性和运行时开销。
- 实现简单,不需要使用指针,减少了指针操作的复杂性和内存占用。
静态链表的不足
- 大小固定不可变,不支持动态扩展和缩小。
- 需要提前分配一定大小的存储空间,可能造成空间的浪费或不足。
- 插入和删除操作需要重建链表的链接关系,有一定的时间开销。
- 不能随机存取,只能从头结点依次向后遍历查找。
适用场景
- 不支持指针的低级语言
- 数据元素数量固定不变的场景(如操作系统的文件分配表FAT)
(FAT王道OS视频p51)
在静态链表中如果要表示,这个结点是最后一个结点,游标的值可以设为 -1, 表示之后已经没有其他结点了。
5.1定义
#define MaxSize 10 //静态链表的最大长度
//静态链表结构类型的定义,并且使用typedef进行重命名为SLinkList[MaxSize]
typedef struct Node{
ElemType data; //存储数据元素
int next; //下一个元素的数组下标
}SLinkList[MaxSize];
struct Node a[MaxSizze];//数组a作为静态链表
//等同于
SLinkList a;
链表定义形式,这样可以使用StackLink
来创建静态链表的节点,使用PtrStackLink
来创建指向静态链表节点的指针。
typedef struct Stack{
Elemtype data; //数据域
int cur; //游标
}StackLink,*PtrStackLink;
//例如,可以使用以下方式创建一个静态链表节点:
StackLink node;
node.data = 10;
node.cur = 1;
PtrStackLink p;
p = &node;
这段代码创建了一个指向静态链表节点的指针 p,然后将其指向 node。
可以通过 p 来访问和操作这个静态链表节点的成员变量。
例如,可以使用以下方式访问静态链表节点的 data 成员变量:
p->data = 10;
p->cur = 1;
这样就可以通过指针 p 操作静态链表节点的数据和指针。
需要注意的是,如果对指针 p 进行重新赋值,则指向的节点也会相应改变。
5.2初始化
const int MAX_SIZE = 100; //静态链表的最大容量
// 初始化静态链表
void initList(struct Node space){
for(int i=0; i<MAX_SIZE-1; i++) {
space[i].next = i+1; //每个节点的 next 指向下一个节点
}
space[MAX_SIZE-1].next = 0; //最后一个节点的 next 设置为0表示链表结束
}
5.3插入
静态链表的节点插入的步骤:
- 查找插入位置:
遍历静态链表,找到要插入位置的前一个节点。可以使用一个游标来遍历链表,初始时指向链表的头节点。
- 分配新节点:
在静态链表的空闲位置上分配一个新节点,为新节点赋值。
- 插入节点:
将新节点的下一个节点指向前一个节点的下一个节点,然后将前一个节点的下一个节点指向新节点的位置。
// 在静态链表的头部插入节点
bool insertNode(int data) {
int newNodeIndex = space[0].next; //获取可用节点的索引(即可用数组空间的下标)
if (newNodeIndex == 0) { //判断备用链表的第一个结点下标是否为0
return false; //下标为0,链表已满,插入失败
}
// 若备用链表第一个结点空闲,则征用该结点,同时更新备用链表的头结点指向,将此被征用的空闲结点的下一空闲结点填补上来。
space[0].next = space[newNodeIndex].next;
space[newNodeIndex].data = data; //设置插入节点的数据
space[newNodeIndex].next = space[1].next; //插入结点的 next 指向原先的头结点
space[1].next = newNodeIndex; //头节点指向插入节点
return true;
}
5.4查找
在静态链表中查找数据可以通过遍历链表的方式来完成。由于静态链表没有指针来直接跳转到下一个节点,所以需要使用游标来遍历链表。
以下是一种在静态链表中查找数据的示例算法:
- 遍历链表,从链表头部开始,通过头节点的索引获取第一个节点的索引。
- 遍历链表中的每个节点,判断节点的数据是否与目标数据相等。
- 如果相等,找到了目标数据,返回节点的索引。
- 如果不相等,获取当前节点的下一个节点,更新当前节点的索引为下一个节点的索引。
- 若遍历完整个链表(即当前节点的索引为-1),仍未找到目标数据,则返回-1表示未找到。
- 按值查找结点:
// 按值查找节点
int findNodeByValue(int value) {
int curr = space[1].next; //从头节点开始遍历
while (curr != 0) {
if (space[curr].data == value) {
return curr; //找到节点,返回索引
}
curr = space[curr].next;
}
return 0; //没找到节点,返回 0
}
这是一个简单的线性查找算法,时间复杂度为O(n),其中n是链表中节点的数量。
- 按索引查找结点:
// 按索引查找节点的值
int getNodeValueByIndex(int index) {
if (index < 0 || index >= MAX_SIZE) {
return -1; //索引非法,返回 -1
}
return space[index].data;
}
5.5删除
静态链表的节点删除步骤:
- 找到要删除结点的前一个结点的位置。
- 将被删除结点的下一个结点的下标保存下来。
- 修改被删除结点的前一个结点的指针域,使其指向被删除结点的下一个结点。
- 将被删除结点的下标加入备用链表。
删除指定数据的节点:
// 删除指定数据的节点
bool deleteNode(int data) {
int prev = 1; //记录当前节点的前一个节点
int curr = space[1].next; //从头节点开始查找
while (curr != 0) {
if (space[curr].data == data) {
break; //找到要删除的节点,跳出循环
}
prev = curr; //未找到,则继续向下遍历
curr = space[curr].next;
}
if (curr == 0) {
return false; //没有找到要删除的节点,删除失败
}
space[prev].next = space[curr].next;//前一个节点的 next 指向要删除节点的 next
space[curr].next = space[0].next; //被删除的节点的 next 指向当前可用节点
space[0].next = curr; //更新可用节点的索引
return true;
}
5.6遍历
// 打印静态链表
void printList() {
int curr = space[1].next; // 从头节点开始遍历
while (curr != 0) {
std::cout << space[curr].data << " ";
curr = space[curr].next;
}
std::cout << std::endl;
}
5.7长度
// 获取静态链表长度
int getListLength() {
int count = 0;
int curr = space[1].next; // 从头节点开始遍历
while (curr != 0) {
count++;
curr = space[curr].next;
}
return count;
}
5.8特点
静态链表的优点:
-
相比于动态链表,静态链表的存储空间是预先分配好的,不需要频繁地进行内存申请和释放,因此在一些内存有限或者对内存分配效率有要求的场景下,静态链表可能更为适用。
-
静态链表在存储空间上是连续的,可以提高数据访问的效率,尤其是在对元素进行遍历、查找和索引访问等操作时,相对于动态链表具有一定的性能优势。
静态链表的缺点:
- 静态链表的长度是固定的,无法随意扩展或缩小,一旦达到最大长度,就无法再插入新节点。
- 静态链表在插入和删除节点时,需要进行额外的操作来维护节点间的连接关系,可能会增加一定的编程复杂性。
- 静态链表的每个节点需预先分配固定大小的存储空间,可能会造成空间的浪费,特别是在某些节点存储的数据量较小的情况下。