认识静态链表
静态链表的本质就是结构体数组。结构体中一般有两个结构成员,一个用于存放数据,一个用于存放下一个结点的数组下标(作用类似指针,有人称之为游标(cursor))。
静态数组的具体实现有多种方式,这里我作两个思路分析。
方法一
0号结点作为头结点(head),头结点不存放数据,他的游标存储着第一个存储元素的结点。这种方法插入时需要遍历链表直到第一个未使用的结点。搜索空结点的时间复杂度较大。
方法二
使用两个不存放数据的结点。一个结点作为头结点(head),游标存储着第一个存储数据的结点的下标。另一个结点备用池(pool),游标存储着第一个空闲空间的下标(该节点一般放在1号结点或最后一个结点)。通过这两个结点,把静态链表变成了一个共享链表。一个存储数据的数据表,以head为头结点;一个存储空闲空间的链表,以pool为头结点。
方法二的数据存储空间会比第一种方法少一个,但是搜索空闲结点的时间复杂度大大降低。
当数据量较小时可用方法一,数据量大时一定要用方法二。下面代码实现中,我使用了方法二。
静态链表一般用于不支持指针的语言。或元素数量固定不变的场景。
插入
后插创建
// 尾插法一次插入多元素
void CreateList(SLinkList L, int len){
int cur = 0; // 游标,用于记录next
ElemType e;
printf("Please enter %d elements one by one in order!\n", len);
while(L[cur].next != -1){
// 遍历至表尾
cur = L[cur].next;
}
while(len--){
scanf("%d", &e);
if(L[0].next == -1){
// 如果还没有结点被使用,令头结点指向第一个结点
L[0].next = 1;
}
if(L[pool].next != pool){
// 申请空间
L[cur].next = L[pool].next; // 结点指向新结点
cur = L[pool].next;
}
L[pool].next++; // 返回L[pool].next并将
L[cur].data = e; // 数据存入
L[cur].next = -1; // 声明表尾
}
}
按位插入
// 按位插入
bool ListInsert(SLinkList L, int pos, ElemType e){
if(L == NULL || pos > MaxSize-2 || pos < 0) return false; // 数据合法性判断
int cur = 0; // 游标,用于记录结点下标
while(--pos){
// 定位到插入位置的前继
cur = L[cur].next;
}
int p = L[cur].next; // p为插入位置的下一结点游标
if(L[pool].next == pool) return false; // 空间不足
L[cur].next = L[pool].next; // 指向第一个备用空间
int q = L[cur].next; // q为插入位置的游标
L[q].data = e; // 存入数据
if(L[cur].next == -1){
// 如果插入位置在表尾
L[q].next = -1;
}else{
L[q].next = p; // 指向原后继
}
return true;
}
删除
按位删除
// 按位删除
bool ListDelete(SLinkList L, int pos){
if(L == NULL || pos > MaxSize-2 || pos < 0) return false; // 数据合法性判断
int cur = 0; // 游标,用于记录结点下标
while(--pos){
// 定位到删除位置的前继
cur = L[cur].next;
}
int p = L[cur].next; // p为删除位置
if(L[p]