【数据结构】C语言实现链表的相关操作

链表

概念与讨论

以链式结构存储的线性表称之为线性链表,线性链表中逻辑上相邻的数据元素的存储空间可以是不连续的,为表示逻辑上的顺序关系,对线性链表中的每个数据元素除存储本身的信息之外,还需存储其后继的地址(即用指针表示逻辑关系)。线性链表中的每个元素(由数据域和指针域构成)称为结点(node)。
在这里插入图片描述

首元结点:链表中存储第一个数据的结点

头结点:在首元结点前附设的一个结点(不存储数据,指针指向首元结点)

头指针:指向链表中第一个结点的指针(有头结点就指向头结点,无头结点则指向首元结点)
在这里插入图片描述

链表可以带头结点,也可以不带头结点,推荐使用带头结点的链表

如何表示空表?

不带头结点时,头指针为空表示空表

带头结点时,头结点的指针域为空表示空表

为什么在链表设置头结点?

  1. 便于处理首元结点,首元结点的地址保存在头结点的指针域中,所以对链表上首元结点的操作和之后的其他结点一样,无需特殊处理
  2. 便于统一处理空表和非空表,无论链表是否为空,头指针都是指向头结点的非空指针。

头结点的数据域内装的是什么?

头结点的数据域可以为空,也可以存放线性表长度等附加消息,但此结点不能计入链表长度值

链表的特点

  1. 结点在内存中的位置是任意的,即逻辑上相邻的数据元素在物理地址上不一定相邻
  2. 访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间是不等的

链表类型

根据指针域的不同,链表的表示形式也不同,最普通的链表是单链表,其指针域为一个指向后继结点的指针

除此以外还有循环链表和双链表,后文中会有介绍,以下内容都是关于单链表,如无特殊说明,后文中的链表指的就是单链表

表示

带头结点的单链表

单链表是由表头(这里的表头指的是头结点)唯一确定的,因此单链表可以用头指针的名字来命名,若头指针的名字是 L,则把该单链表称为表 L

typedef int ElemType;
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode, * LinkList;

LNode:结构体,表示单链表的结点

data:存储的数据

next:结点指针,指向下一个结点

LinkList:头指针,用来表示单链表

初始化

生成一个结点为头结点,头指针指向头结点,此时单链表为空表,将头结点的指针域置空

返回NULL表示初始化失败,非NULL则表示初始化成功

LinkList init(void)
{
LinkList L = (LNode*)malloc(sizeof(LNode));
if (L == NULL)
{
return NULL;
}
L->next = NULL;
L->data = 0; //头结点的数据域有没有初始化都可以
return L;
}

主函数main中的操作

LinkList L = NULL;
L = init();

在这里插入图片描述

插入

在结点 n1 之后插入一个值为 x 的新结点 n2
在这里插入图片描述

先让结点 n2 的指针域指向结点 n1 的后继结点

再让结点 n1 的指针域指向结点 n2

参数:单链表 L,结点 n1,新结点 n2 的值 x

void insert(LinkList L, LNode* n1, ElemType x)
{
LNode* n2 = (LNode*)malloc(sizeof(LNode));
if (n2 == NULL)
{
return;
}
n2->data = x;
n2->next = n1->next;
n1->next = n2;
}

删除

在链表中删除结点 n
在这里插入图片描述

先找到要删除的结点 n 的前驱结点 p

再让结点 p 的指针域指向结点 n 的后继结点

释放结点 n 的内存空间

参数:链表 L,要删除的结点 n

void del(LinkList L, LNode* n)
{
LNode* p = L;
while (p->next != n)
{
p = p->next;
}
p->next = p->next->next;
free(n);
}

访问

访问链表中索引为 index 的结点(index 大于等于1,首元结点的 index 值为1)

参数:链表 L,索引 index

LNode* access(LinkList L, int index)
{
LNode* p = L;
int i = 0;
for (i = 0; i < index; i++)
{
p = p->next;
if (p == NULL)
{
return NULL;
}
}
return p;
}

判断 p == NULL 可有可不有,有的话,作用在于防止访问到链表之后的空间

查找

查找链表中第一个值为 x 的结点,返回该结点在链表中的索引值(索引从首元结点开始,值大于等于1)

参数:链表 L,数据 x

返回值:返回值为索引值,若返回0,表示找不到

int find(LinkList L, ElemType x)
{
LNode* p = L;
int i = 1;
p = p->next;
while (p != NULL)
{
if (p->data == x)
{
return i;
}
p = p->next;
i++;
}
return 0;
}

是否为空

判断链表是否为空

参数:链表 L

返回值:返回1表示链表为空,返回0表示链表不为空

int isEmpty(LinkList L)
{
LNode* p = L;
if (p->next == NULL)
{
return 1;
}
return 0;
}

求表长

求链表表长(从首元结点开始计算结点个数)

参数:链表 L

返回值:返回值为链表长度

int get_length(LinkList L)
{
LNode* p = L;
int i = 0;
p = p->next;
while (p != NULL)
{
p = p->next;
i++;
}
return i;
}

清空

清空单链表,只保留头结点和头指针

参数:链表 L

void clear(LinkList L)
{
LNode* p = L;
LNode* pFree = L;
p = p->next;
pFree = p;
while (p != NULL)
{
p = p->next;
free(pFree);
pFree = p;
}
L->next = NULL;
}

注意最后要将头结点的指针域指向 NULL

销毁

销毁单链表,所有结点,包括头结点全部销毁,头指针置 NULL

参数:链表 L

返回值:返回 NULL,在主函数中将头指针置 NULL

LinkList destroy(LinkList L)
{
LNode* p = L;
LNode* pFree = L;
while (p != NULL)
{
p = p->next;
free(pFree);
pFree = p;
}
return NULL;
}

打印

查看链表中各元素的数据

void print_LinkList(LinkList L)
{
LNode* p = L->next;
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}

建立链表

头插法

创建新结点,新结点的指针域指向链表的首元结点,头结点的指针域指向新结点,新结点成为新的首元结点
在这里插入图片描述

void creat_head(LinkList L)
{
ElemType x = 0;
ElemType flag = -999;
printf("input x(ElemType:int)(flag == -999):");
scanf("%d", &x);
while (x != flag)
{
LNode* p = (LNode*)malloc(sizeof(LNode));
if (p == NULL)
{
return;
}
p->data = x;
p->next = L->next;
L->next = p;
printf("input x(ElemType:int)(flag == -999):");
scanf("%d", &x);
}
}

尾插法

创建新结点,新结点的指针域指向NULL,首元结点的指针域指向新结点

需要设立一个尾指针,来指向链表中的最后一个结点
在这里插入图片描述

void creat_tail(LinkList L)
{
LNode* r = L; //尾指针,指向链表中的最后一个结点
ElemType x = 0;
ElemType flag = -999;
printf("input x(ElemType:int)(flag == -999):");
scanf("%d", &x);
while (x != flag)
{
LNode* p = (LNode*)malloc(sizeof(LNode));
if (p == NULL)
{
return;
}
p->data = x;
p->next = NULL;
r->next = p;
r = p;
printf("input x(ElemType:int)(flag == -999):");
scanf("%d", &x);
}
}

链表类型

单向链表:即单链表,指针域指向下一结点

循环链表:表中最后一个结点的指针域指向头结点(如果不设头结点,则指向首元结点),循环链表通常不使用头指针,而是用一个尾指针来表示循环链表,尾指针指向循环链表中最后一个结点

双向链表:在单链表的每个结点里再增加一个指向其直接前驱的指针域 prior,这样链表中就有了两个不同方向的链

posted @   长白秋沙  阅读(121)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示