线性表之链表-万字超全总结-目录速查
<一>单链表
1.定义结点以及增加一个新的结点
-
使用struct定义
struct LNode { int data; //数据域 struct LNode *next; //指针域 }; //开辟一个结点的空间 struct LNode *p = (struct LNode*)malloc(sizeof(struct LNode));
-
用typedef关键字简化代码–使用别名代替struct LNode,语法:
typedef <数据类型> <别名>
typedef struct LNode{ int data; //数据域 struct LNode *next; //指针域 }LNode,*LinkList; //可以理解为给struct LNode取别名,此后LNode即struct LNode,LinkList即struct LNode* //开辟一个结点的空间 LNode *p = (LNode*)malloc(sizeof(LNode)); //要表示一个单链表,只需声明一个头指针,指向单链表的第一个结点 LinkList L; //等价于 LNode *L; 虽然效果上一样,但为了良好的可读性,前者强调这是一个单链表,后者强调这是一个普通的结点
2.初始化
初始化一个不带结点的单链表 (即将表头置为NULL,表示还没有任何结点,这是一个空表,初始化目的是为了防止有脏数据)
//为了便于此后判空的操作,设计返回类型为bool
bool InitList(LinkList &L) {
L = NULL;
return true;
}
初始化一个带头节点的单链表(头节点本身不存放数据,数据存在头节点之后的结点里)
bool InitList(LinkList &L) {
L = (LNode *)malloc(sizeof(LNode)); //分配一个头节点
if (L == NULL) //内存不足,分配失败
return false;
L->next = NULL; //头节点之后无结点,初始化为空
return true;
}
带头节点初始化一个单链表对之后的操作更方便,因此推荐。(比如按次序的插入操作,如果插在第一个位置,那么带头节点的(不含数据)便可看作是第0个结点,操作则和其它位置插入相同;如果带不带头结点,插入在表首时,表头被更新,还需要重置表头)
3.判空
不带头节点的单链表-判空
bool Empty(LinkList L) {
return L == NULL;
}
带头节点的单链表-判空
bool Empty(LinkList L) {
return L->next == NULL;
}
4.插入
按位序在链表中插入
按位序插入(带头节点)
bool ListInsert(LinkList &L, int i, int e) { //在第i个节点处插入数据e
if (i < 1)
return false;
LNode *p;
int j = 0; //表示当前p指向第几个结点,头节点看作第0个结点,头节点不存放数据
p = L;
while (p != NULL && j < i - 1) { //循环找到第i-1个结点位置[i]
p = p->next;
j++;
}
if (p == NULL) //i值不合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
按位序插入(不带头节点)(在带头结点的插入操作基础上,对插在第一个位置进行特殊处理)
//不带头结点的插入操作,对插在表首进行特殊处理
/*
if (i == 1) {
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s; //表头重置
return true;
}
*/
//合并即可
bool ListInsert(LinkList &L, int i, int e) { //在第i个节点处插入数据e
if (i < 1)
return false;
if (i == 1) {
LNode *s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s;
return true;
}
LNode *p;
int j = 0; //表示当前p指向第几个结点,头节点看作第0个结点,头节点不存放数据
p = L;
while (p != NULL && j < i - 1) { //循环找到第i-1个结点位置[i]
p = p->next;
j++;
}
if (p == NULL) //i值不合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
指定结点的后插操作
bool InsertNextNode(LNode *p, int e) {
if (p == NULL) //不能插在空的后面,即结点与结点之间不允许有空
return false;
LNode *s = (LNode*)malloc(sizeof(LNode));
if (s == NULL) //内存分配失败
return false;
s->data = e;
s->next = p->next;
p->next = s;
return true; //插入成功
}
指定结点的前插操作
因为节点只能往后链接到其它节点,因此,对于前插操作,考虑应该引入头指针,才能找到该节点前面的一个节点,需要循环查找到该结点的前驱,那么时间复杂度为O(n)。
从另一个角度考虑,可以前插操作可以:先直接后插,然后交换数据,这样达到了和前插一样的效果,同时时间复杂度为O(1)。
bool InsertPriorNode(LNode *p, int e) {
if (p == NULL) //p指向的是表中的一个结点(不考虑表头、表尾的下一个NULL,当它为NULL,即不合法)
return false;
LNode *s = (LNode*)malloc(sizeof(LNode));
if (s == NULL) //内存分配失败
return false;
//先后插
s->next = p->next;
p->next = s;
//然后交换数据
s->data = p->data;
p->data = e;
return true;
}
5.删除
按位序删除
-
带头结点(找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点)
bool ListDelete(LinkList &L, int i, int &e) { //删除第i个元素,并将被删除的值返回给e if (i < 1) return false; LNode *p; int j = 0; //当前p指向第几个结点 p = L; //指向不存数据的第0个结点 while (p != NULL && j < i - 1) { //找到第i-1个结点[i] p = p->next; j++; } if (p == NULL) //i值不合法,即第i-1个结点不存在 return false; if (p->next == NULL) //第i-1个结点后面已经没有结点,即第i个结点不存在 return false; LNode *q = p->next; //q指向被删除的结点 e = q->data; //用e返回被删除结点的值 p->next = q->next; free(q); return true; }
-
不带头结点(即在带头结点的基础上,对删除第一个结点特殊处理,因为要重置表头)
//不带头结点,对删除第一个结点特殊处理 /* if (i == 1) { LNode *s = L; //s指向头结点 L = s->next; //表头指向第2个结点,即重置表头 free(s); //释放原来第一个结点 } */ //合并 bool ListDelete(LinkList &L, int i, int &e) { //删除第i个元素,并将被删除的值返回给e if (i < 1) return false; if (i == 1) { LNode *s = L; //s指向头结点 L = s->next; //表头指向第2个结点,即重置表头 free(s); //释放原来第一个结点 } LNode *p; int j = 0; //当前p指向第几个结点 p = L; //指向不存数据的第0个结点 while (p != NULL && j < i - 1) { //找到第i-1个结点[i] p = p->next; j++; } if (p == NULL) //i值不合法,即第i-1个结点不存在 return false; if (p->next == NULL) //第i-1个结点后面已经没有结点,即第i个结点不存在 return false; LNode *q = p->next; //q指向被删除的结点 e = q->data; //用e返回被删除结点的值 p->next = q->next; free(q); return true; }
指定结点删除
和指定结点前插类似,第一种思路,传入头指针,循环找到其前驱,修改其前驱的next指针,循环查找过程,时间复杂度为O(n);
第二种思路,偷天换日,将需要删除的结点的后继的数据赋值给它,然后修改它的next指针,删除它的后继即可
//带头结点
bool DeleteNode(LNode *p) {
if (p == NULL)
return false;
//需要删除的p结点是最后一个结点
if (p->next == NULL)//此时只能用方法1循环找到它的前驱,因为此时用方法2在数据域赋值时会报错,NULL无数据域
{
LNode *q = L;
while (q != NULL && q->next!=p) {//循环退出时q->next==p,即q为p的前驱
q = q->next;
}
q->next = p->next; //修改需要删除的结点的前驱的next指针
free(p);
}
LNode *q = p->next; //q指向需要删除的结点的后继
p->data = q->data; //将它的后继的数据赋值给它
p->next = q->next; //修改它的next指针,将后继从链表中排除
free(q); //释放后继结点
}
6.查找
按位查找
//带头结点的按位查找,返回第i个结点,头结点不存数据,视为第0个结点
LNode* GetElem(LinkList L, int i) {
if (i == 0)
return L; //返回头结点
if (i < 0)
return false;
LNode *p = L->next; //p指向第1个结点
int j = 1; //j记录当前p指向第几个结点
while (p != NULL && j < i) { //循环找到第i个结点
p = p->next;
j++;
}
return p;
}
//不带头结点的按位查找,返回第i个结点
LNode* GetElem(LinkList L, int i) {
if (i <= 0)
return false;
LNode *p = L; //p指向第1个结点
int j = 1; //j记录当前p指向第几个结点
while (p != NULL && j < i) { //循环找到第i个结点
p = p->next;
j++;
}
return p;
}
按值查找
//按值查找(带头结点)
LNode* LocateElem(LinkList L, int e) {
LNode *p = L->next; //p指向第一个结点
while (p != NULL && p->data != e)
p = p->next;
return p; //找到后返回该指针,若不存在会返回NULL
}
7.求表长
//求单链表长度(带头结点)
int Length(LinkList L) {
LNode *p = L;
int len = 0;
while (p->next != NULL) {
p = p->next;
len++;
}
return len;
}
8.打印
//打印单链表(带头结点)
void Show(LinkList L) {
LNode *s = L;
printf("此单链表为:");
while (s->next) {
s = s->next;
printf("%d ", s->data);
}
}
9.单链表的建立
以带头结点的单链表为例
尾插法
每次插入都记录表尾,以避免每次需要循环找到表尾。
//尾插法建立单链表(带头结点)
LinkList TailBuild(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode)); //建立头结点
printf("请输入单链表数据:");
int x; //设置x临时存储用户输入的数据
LNode *s, *r = L; //s为临时结点,r记录当前表尾
while (~scanf("%d", &x)) { //只要用户输入的不是ctrl+z,即不是EOF,那么进入循环,继续获取用户输入
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
r->next = s; //连接
r = s; //重置表尾
}
r->next = NULL;
return L;
}
测试
int main() {
LinkList test;
test = TailBuild(test);
Show(test);
return 0;
}
头插法
即逆向建立单链表
和链表插入思想一致
//头插法建立单链表(带头结点)
LinkList HeadBuild(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode)); //建立头结点
L->next = NULL;
printf("请输入单链表数据:");
int x; //设置x临时存储用户输入的数据
LNode *s;
while (~scanf("%d", &x)) { //只要用户输入的不是ctrl+z,即不是EOF,那么进入循环,继续获取用户输入
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
s->next = L->next; //s为第一个结点
L->next = s; //更新头结点的指向
}
return L;
}
测试
int main() {
LinkList test;
test = HeadBuild(test);
Show(test);
return 0;
}
<二>双链表
DNode,即double node
单链表是单向的,一个结点含数据域和一个指针域next,一个结点只能通过其next指向它的后继,无法直接获取它的前驱。而双链表则是双向的,一个结点含两个指针域,一个指针域prior指向它的前驱,next指向它的后继。
以下以带头结点的双链表示例
1.结点定义
#include<stdio.h>
#include<stdlib.h>
typedef struct DNode {
int data;
struct DNode *prior, *next;
}DNode,*DLinkList;
2.初始化
//初始化双链表(带头结点)
bool InitDLinkList(DLinkList &L) {
L = (DLinkList)malloc(sizeof(DNode)); //分配一个头结点
if (L == NULL)
return false; //内存不足,分配失败
L->prior = NULL; //头结点的prior永远为NULL
L->next = NULL;
return true;
}
3.判空
bool Empty(DLinkList L) {
return (L->next == NULL);
}
4.插入
-
指定结点的后插操作
//在p结点之后插入s结点 bool InsertNextDNode(DNode *p, DNode *s) { if (p == NULL || s == NULL) return false; //参数非法 s->next = p->next; if (p->next != NULL) //如果p结点不是最后一个结点 p->next->prior = s; s->prior = p; p->next = s; return true; }
-
按位序插入
//按位序插入s,找到插入位置的前驱结点p,然后问题和在p结点之后插入s等同 bool InsertDNode(DLinkList &L, int i, int e) { //在第i个结点处插入值为e的结点 if (i < 1) return false; //i值不合理 DNode *p = L; //用p找到插入位置的前驱结点 int j = 0;//记录当前p指向第几个结点 while (p->next != NULL && j < i-1) { //找到第i个结点的前驱结点,即第i-1个结点 p = p->next; j++; } DNode *s = (DNode*)malloc(sizeof(DNode)); s->data = e; InsertNextDNode(p, s); return true; }
-
指定结点的前插操作
//在p结点之前插入s结点,先直接后插,然后交换数据域即可 bool InsertPriorDNode(DNode *p, DNode *s) { InsertNextDNode(p, s); int temp; //交换数据 temp = p->data; p->data = s->data; s->data = temp; }
5.删除
//删除p的后继结点
bool DeleteNextDNode(DNode *p) {
if (p == NULL)
return false;
DNode *q = p->next; //q为待删除的结点,即p的后继结点
if (q == NULL) //即p没有后继
return false;
p->next = q->next;
if (q->next != NULL) //即q不是最后一个结点
q->next->prior = p;
free(q);
return true;
}
6.销毁
//销毁双链表(循环释放所有结点)
void DestoryList(DLinkList &L) {
while (L->next != NULL)
DeleteNextDNode(L);
free(L);
L = NULL;
}
7.遍历
-
后向遍历(顺序遍历)
//双链表的后向遍历 void Show(DLinkList L) { DNode *p = L; printf("顺序遍历双链表:"); while (p->next != NULL) { p = p->next; printf("%d ", p->data); } }
-
前向遍历(逆序遍历)
//双链表的前向遍历 //先找到表尾 void ReverseShow(DLinkList L) { DNode *p = L; //找到最后一个结点 while (p->next != NULL) p = p->next; //前向遍历 printf("逆序遍历双链表:"); while (p->prior != NULL) { printf("%d ", p->data); p = p->prior; } }
8.完整代码与测试
#include<stdio.h>
#include<stdlib.h>
typedef struct DNode {
int data;
struct DNode *prior, *next;
}DNode,*DLinkList;
//初始化双链表(带头结点)
bool InitDLinkList(DLinkList &L) {
L = (DLinkList)malloc(sizeof(DNode)); //分配一个头结点
if (L == NULL)
return false; //内存不足,分配失败
L->prior = NULL; //头结点的prior永远为NULL
L->next = NULL;
return true;
}
//判空(带头结点)
bool Empty(DLinkList L) {
return (L->next == NULL);
}
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s) {
if (p == NULL || s == NULL)
return false; //参数非法
s->next = p->next;
if (p->next != NULL) //如果p结点不是最后一个结点
p->next->prior = s;
s->prior = p;
p->next = s;
return true;
}
//按位序插入s,找到插入位置的前驱结点p,然后问题和在p结点之后插入s等同
bool InsertDNode(DLinkList &L, int i, int e) { //在第i个结点处插入值为e的结点
if (i < 1)
return false; //i值不合理
DNode *p = L; //用p找到插入位置的前驱结点
int j = 0;//记录当前p指向第几个结点
while (p->next != NULL && j < i-1) { //找到第i个结点的前驱结点,即第i-1个结点
p = p->next;
j++;
}
DNode *s = (DNode*)malloc(sizeof(DNode));
s->data = e;
InsertNextDNode(p, s);
return true;
}
//在p结点之前插入s结点,先直接后插,然后交换数据域即可
bool InsertPriorDNode(DNode *p, DNode *s) {
InsertNextDNode(p, s);
int temp;
//交换数据
temp = p->data;
p->data = s->data;
s->data = temp;
}
//删除p的后继结点
bool DeleteNextDNode(DNode *p) {
if (p == NULL)
return false;
DNode *q = p->next; //q为待删除的结点,即p的后继结点
if (q == NULL) //即p没有后继
return false;
p->next = q->next;
if (q->next != NULL) //即q不是最后一个结点
q->next->prior = p;
free(q);
return true;
}
//销毁双链表(循环释放所有结点)
void DestoryList(DLinkList &L) {
while (L->next != NULL)
DeleteNextDNode(L);
free(L);
L = NULL;
}
//双链表的后向遍历
void Show(DLinkList L) {
DNode *p = L;
printf("顺序遍历双链表:");
while (p->next != NULL) {
p = p->next;
printf("%d ", p->data);
}
}
//双链表的前向遍历
//先找到表尾
void ReverseShow(DLinkList L) {
DNode *p = L;
//找到最后一个结点
while (p->next != NULL)
p = p->next;
//前向遍历
printf("逆序遍历双链表:");
while (p->prior != NULL) {
printf("%d ", p->data);
p = p->prior;
}
}
int main() {
DLinkList test;
InitDLinkList(test);
if (Empty(test))
printf("此时双链表为空链表\n");
int e;
printf("请输入4个整数:");
for (int i = 1; i < 5; i++) {
scanf("%d", &e);
InsertDNode(test, i, e);
}
Show(test);
ReverseShow(test);
}
<三>循环链表
循环链表的好处:
- 对于单链表,查找某个结点的前驱,可以直接从当前结点一直向后遍历,不用从头结点开始找(绕一圈,找到它的前驱);
- 对于双链表,插入或删除等操作,不需要对尾结点特殊考虑,尾结点和内部结点操作是相同的
1.循环单链表
#include<stdio.h>
#include<stdlib.h>
//循环单链表
//定义单链表结点类型
typedef struct LNode {
int data;
struct LNode *next;
}LNode,*LinkList;
//初始化一个带头结点的循环单链表,头结点的next指向自己
bool InitList(LinkList &L) {
L = (LinkList)malloc(sizeof(LNode)); //分配一个头结点
if (L == NULL)
return false; //空间不足,分配失败
L->next = L;
return true;
}
//判空,头结点的next指向自己,即为空
bool Empty(LinkList L) {
return (L->next == L);
}
//判断结点p是否是表尾结点
bool isTail(LinkList L, LNode *p) {
return (p->next == L);
}
//查找指定结点的前驱结点
LNode* FindPriorNode(LNode *p) { //查找p的前驱结点
LNode *q = p;
while (q->next != p)
q = q->next;
return q;
}
2.循环双链表
#include<stdio.h>
#include<stdlib.h>
//循环双链表
//定义双链表的结点
typedef struct DNode {
int data;
struct DNode *prior, *next;
}DNode,*DLinkList;
//初始化(带头结点)(next,prior都指向自己)
bool InitDLinkList(DLinkList &L) {
L = (DLinkList)malloc(sizeof(DNode));
if (L == NULL)
return false; //内存不足,空间分配失败
L->prior = L;
L->next = L;
return true;
}
//判空,头结点的next指向自己,即为空
bool Empty(DLinkList L) {
return (L->next == L);
}
//判断结点p是否是表尾结点
bool isTail(DLinkList L, DNode *p) {
return (p->next == L);
}
//在p结点之后插入s结点(无需考虑p是否为尾结点)
bool InsertNextDNode(DNode *p, DNode *s) {
if (s == NULL || p == NULL)
return false;
s->next = p->next;
p->next->prior = s;
s->prior = p;
p->next = s;
}
<四>静态链表
1.静态链表的特点
静态链表又称为链表的游标(cursor)实现。有些语言,如BASIC,不支持指针,但又需要链表,而静态链表中的游标就是对链表指针的模拟。
链表指针的特点:
- 链表数据存储在一组结构体中,每一个结构体包含数据以及指向下一个结构体的指针
- 可以通过malloc开辟一个新的结构体,并能通过free将其释放
游标对上面两个特点的模拟:
-
用数组下标模拟指向下一个结构体的指针
#define MAXSIZE 100 typedef struct { int data; int cur; //游标(cursor) }Component, StaticLinkList[MAXSIZE];
-
模拟malloc和free
对数组StaticLinkList[MAXSIZE]的第一个元素和最后一个元素特殊处理。
- 备用链表:未被使用的数组元素称为备用链表。数组的第一个元素的cur,即下标为0的元素的cur存放备用链表的第一个结点的下标。
- 数组的最后一个元素的cur存放第一个存放了数据的元素的下标,相当于单链表中头结点的作用。最后一个存放数据的元素的cur为0,相当于单链表最后一个结点的指针指向NULL(用数值0模拟空指针NULL。)
2.初始化
bool InitList(StaticLinkList space) {
for (int i = 0; i < MAXSIZE - 1; i++) {
space[i].cur = i + 1;
}
space[MAXSIZE - 1].cur = 0;
return true;
}
3.静态链表的插入操作:
在动态链表里,结点的申请和释放分别借用malloc和free两个函数,而在静态链表里,操作的是数组,不能使用这两个函数,但可以通过设计函数来达到与其相似的效果
为了辨别哪些分量未被使用,将所有未被使用过的以及已被删除的分量链成一个备用链表,每次进行插入时,获取备用链表的第一个结点作为待插入的新结点,该过程模拟了malloc
int ListLength(StaticLinkList L) {
int i = L[MAXSIZE - 1].cur; //i即第一个存有数据的元素的下标
int len = 0;
while (i) {
i = L[i].cur; //遍历存有数据元素的下标
len++;
}
return len;
}
int Malloc(StaticLinkList space) {
int i = space[0].cur; //返回i,即取得第一个备用空闲的下标
if (i) //如果i=0,则存储空间已满,没有空闲分量
space[0].cur = space[i].cur; //i已经拿去使用了,原来的第一个备用空闲的下标需要重置为新的空闲分量
return i;
}
//在第i个元素之前插入数据元素e,即让数据元素e成为链表中第i个数据元素(则要找到第i-1个元素)
bool ListInsert(StaticLinkList L, int i, int e) {
if (i<1 || i>ListLength(L) + 1)
return false;
int j = Malloc(L);//获得空闲分量的下标
int k = MAXSIZE - 1; //k为最后一个元素的下标,L[k].cur即第一个存有数据的元素的下标
if (j) { //j!=0时满足条件,即j分配到了空闲分量的下标
L[j].data = e;
for (int m = 1; m < i - 1; m++)
k = L[k].cur; //循环退出后,k值为第i-1个数据元素的下标
//以下两行代码类似与插入操作里的修改指针指向
L[j].cur = L[k].cur;
L[k].cur = j;
return true;
}
return false; //即j==0,此时链表已满,无法再插入新元素
}
4.静态链表的删除操作
模拟free函数:
//释放下标为k的结点,即将该结点视为空闲结点回收到备用链表(如上图,比如释放下标为1的元素甲)
void Free(StaticLinkList space, int k) {
//让回收的这个位置(即被删除的位置)成为第一个优先空位
space[k].cur = space[0].cur; //与之前的优先空位交换,把之前的优先空位“降级”
space[0].cur = k; //重置优先空位为被释放的结点k
}
删除操作:
//删除第i个数据元素(如上图,比如删除下标为1的甲)
bool ListDelete(StaticLinkList L, int i) {
if (i<1 || i>ListLength(L))
return false; //参数i值不合理
int k = MAXSIZE - 1, j;
for (j = 1; j < i; j++)
k = L[k].cur;
j = L[k].cur; //j即第i个数据元素的下标,下面这行代码L[j].cur即第i+1个数据的下标
L[k].cur = L[j].cur; //类似单链表删除结点的“覆盖”,即将第一个数据元素的下标变成第二个,第一个则被覆盖
Free(L,j); //释放第i个数据元素
return true;
}
5.优缺点分析
优点:插入和删除元素时,吸收了链表的特点,只需要修改游标,改进了顺序存储结构中插入和删除需要移动大量元素的缺点;
缺点:没有解决固定表长长度分配不灵活的问题;失去了顺序存储结构中根据下标快速访问的特性。
6.补充-给定一组数据建立静态链表
建立静态链表图解
void BuildSList(StaticLinkList L)
{
InitList(L);
printf("请输入你想添加的数据成员的数量:");
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
printf("请输入第%d个数据成员:", i);
scanf("%d", &L[i].data);
}
L[0].cur = n + 1;
L[n].cur = 0;
L[MAXSIZE - 1].cur = 1;
}
7.完整代码以及运行测试
#include<stdio.h>
#define MAXSIZE 100
typedef struct {
int data;
int cur; //游标(cursor)
}Component, StaticLinkList[MAXSIZE];
bool InitList(StaticLinkList space) {
for (int i = 0; i < MAXSIZE - 1; i++) {
space[i].cur = i + 1;
}
space[MAXSIZE - 1].cur = 0;
return true;
}
int ListLength(StaticLinkList L) {
int i = L[MAXSIZE - 1].cur; //i即第一个存有数据的元素的下标
int len = 0;
while (i) {
i = L[i].cur; //遍历存有数据元素的下标
len++;
}
return len;
}
int Malloc(StaticLinkList space) {
int i = space[0].cur; //返回i,即取得第一个备用空闲的下标
if (i) //如果i=0,则存储空间已满,没有空闲分量
space[0].cur = space[i].cur; //i已经拿去使用了,原来的第一个备用空闲的下标需要重置为新的空闲分量
return i;
}
//在第i个元素之前插入数据元素e,即让数据元素e成为链表中第i个数据元素(则要找到第i-1个元素)
bool ListInsert(StaticLinkList L, int i, int e) {
if (i<1 || i>ListLength(L) + 1)
return false;
int j = Malloc(L);//获得空闲分量的下标
int k = MAXSIZE - 1; //k为最后一个元素的下标,L[k].cur即第一个存有数据的元素的下标
if (j) { //j!=0时满足条件,即j分配到了空闲分量的下标
L[j].data = e;
for (int m = 1; m < i - 1; m++)
k = L[k].cur; //循环退出后,k值为第i-1个数据元素的下标
//以下两行代码类似与插入操作里的修改指针指向
L[j].cur = L[k].cur;
L[k].cur = j;
return true;
}
return false; //即j==0,此时链表已满,无法再插入新元素
}
//释放下标为k的结点,即将该结点视为空闲结点回收到备用链表
void Free(StaticLinkList space, int k) {
//让回收的这个位置(即被删除的位置)成为第一个优先空位
space[k].cur = space[0].cur; //与之前的优先空位交换,把之前的优先空位“降级”
space[0].cur = k; //重置优先空位为被释放的结点k
}
//删除第i个数据元素(如上图,比如删除下标为1的甲)
bool ListDelete(StaticLinkList L, int i) {
if (i<1 || i>ListLength(L))
return false; //参数i值不合理
int k = MAXSIZE - 1, j;
for (j = 1; j < i; j++)
k = L[k].cur;
j = L[k].cur; //j即第i个数据元素的下标,下面这行代码L[j].cur即第i+1个数据的下标
L[k].cur = L[j].cur; //类似单链表删除结点的“覆盖”,即将第一个数据元素的下标变成第二个,第一个则被覆盖
Free(L, j); //释放第i个数据元素
return true;
}
void BuildSList(StaticLinkList L)
{
InitList(L);
printf("请输入你想添加的数据成员的数量:");
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
printf("请输入第%d个数据成员:", i);
scanf("%d", &L[i].data);
}
L[0].cur = n + 1;
L[n].cur = 0;
L[MAXSIZE - 1].cur = 1;
}
int main() {
StaticLinkList test;
BuildSList(test);
printf("此时静态链表的长度为:%d\n", ListLength(test));
printf("此时删除了第二个数据元素\n");
ListDelete(test, 2);
printf("此时静态链表的长度为:%d\n", ListLength(test));
}