【数据结构】C语言实现链表的相关操作
链表
概念与讨论
以链式结构存储的线性表称之为线性链表,线性链表中逻辑上相邻的数据元素的存储空间可以是不连续的,为表示逻辑上的顺序关系,对线性链表中的每个数据元素除存储本身的信息之外,还需存储其后继的地址(即用指针表示逻辑关系)。线性链表中的每个元素(由数据域和指针域构成)称为结点(node)。
首元结点:链表中存储第一个数据的结点
头结点:在首元结点前附设的一个结点(不存储数据,指针指向首元结点)
头指针:指向链表中第一个结点的指针(有头结点就指向头结点,无头结点则指向首元结点)
链表可以带头结点,也可以不带头结点,推荐使用带头结点的链表
如何表示空表?
不带头结点时,头指针为空表示空表
带头结点时,头结点的指针域为空表示空表
为什么在链表设置头结点?
- 便于处理首元结点,首元结点的地址保存在头结点的指针域中,所以对链表上首元结点的操作和之后的其他结点一样,无需特殊处理
- 便于统一处理空表和非空表,无论链表是否为空,头指针都是指向头结点的非空指针。
头结点的数据域内装的是什么?
头结点的数据域可以为空,也可以存放线性表长度等附加消息,但此结点不能计入链表长度值
链表的特点
- 结点在内存中的位置是任意的,即逻辑上相邻的数据元素在物理地址上不一定相邻
- 访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间是不等的
链表类型
根据指针域的不同,链表的表示形式也不同,最普通的链表是单链表,其指针域为一个指向后继结点的指针
除此以外还有循环链表和双链表,后文中会有介绍,以下内容都是关于单链表,如无特殊说明,后文中的链表指的就是单链表
表示
带头结点的单链表
单链表是由表头(这里的表头指的是头结点)唯一确定的,因此单链表可以用头指针的名字来命名,若头指针的名字是 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,这样链表中就有了两个不同方向的链
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具