数据结构(初阶)(三)----单链表
- 电脑硬件
- 2025-09-19 18:30:02

单链表 概念
概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
结点与顺序表不同的是,链表的结构类似于带车头的火车车厢,,链表的每个车厢都是独立申请下来的空间,每个车厢被叫做结点。
结点由当前结点要保存的数据和保存下一个节点的地址(指针变量)。
图中指针变量 plist保存的是第⼀个结点的地址,我们称plist此时“指向”第⼀个结点,如果我们希望plist“指向”第⼆个结点时,只需要修改plist保存的内容为0x0012FFc8
链表中每个结点都是独⽴申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针 变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点
链表的性质链表在逻辑上是连续的,在物理结构上不一定连续
结点一般是从堆上申请的
从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续
定义链表的结构实际上就是定义链表中结点结构
每个结点对应的结构体代码:
typedef int SLTDataType;//因为我们在事先并不确定数据的类型,所以需要定义一下 typedef truct SListNode { SLTDataType data;//结点要保存的数据 struct SListNode* next;//指向下一个结点的指针,定义的数据类型是结点类型 }SLTNode;//定义全局变量当我们想要保存一个数据时,实际上是向操作系统申请一块内存,这块内存不仅要保存数据,也要保存指向下一个结点的地址(当下一个结点为NULL时,地址为NULL)
当我们想要从第一个节点走向最后一个结点时,只需要在当前结点拿上下⼀个结点的地址就可以了。
链表的打印 void SLTPrint(SLTNode* phead) { SLTNode* pcur = phead;//pcur存储的是当前的结点 while (pcur)//等价于pcur != NULL { printf("%d-> ",pcur->data); pcur = pcur->next; } //走到这里,说明pcur == NULL,那么直接打印 printf("NULL\n"); } 单链表的实现创建三个文件分别是SList.h和SList.c和test.c
SList.h用来包含所需头文件,声明函数
SList.c用来定义函数,实现方法
test.c用来测试我们想要实现的功能
这里使用的是VS2022 win11环境
手动构造链表 SList.h #pragma once #include<stdio.h> #include<stdlib.h> typedef int SLTDataType;//定义数据类型 typedef struct SListNode { SLTDataType data; //定义下一个结点的地址,类型是struct SListNode struct SListNode* next; }SLTNode;//定义全局变量 //打印链表 void SListPrint(SLTNode* phead); SList.c #define _CRT_SECURE_NO_WARNINGS #include"SList.h" //打印链表 void SListPrint(SLTNode* phead) { //创建pcur存储phead的地址,直接用phead也可以 //这里是因为再次使用第一个结点时,还可以找到,不会影响后续的使用 SLTNode* pcur = phead; //判断当前结点是否为NULL,等价于pcur != NULL while (pcur) { //结点不为NULL,打印当前节点数据 printf("%d -> ",pcur->data); //将下一个结点的地址赋值给pcur pcur = pcur->next; } //走到这里说明pcur == NULL,直接打印NULL printf("NULL\n"); } test.c #define _CRT_SECURE_NO_WARNINGS #include"SList.h" //手动构造链表 void test1() { //为结点申请内存,大小为结构体大小 //node1来接收,类型为SLTNode*,其他同理 SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode)); SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode)); SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode)); SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode)); //在结点中放入要存储的数据 node1->data = 1; node2->data = 2; node3->data = 3; node4->data = 4; //在结点中放入指向下一个结点的地址,如果是尾结点,则为NULL node1->next = node2; node2->next = node3; node3->next = node4; node4->next = NULL; //打印链表看看效果 //将首结点的地址给plist,传给实现打印的函数 SLTNode* plist = node1; SListPrint(plist); } int main() { test1(); return 0; } 单链表实现 尾插 //向操作系统申请一个新结点 SLTNode* SLTBuyNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { perror("malloc"); exit(1); } //成功 newnode->data = x; newnode->next = NULL; return newnode; } //尾插 //其实在链表为NULL的情况下,不需要再改变头结点的内容,所以也可以使用SLTNode* pphead void SLTPushBack(SLTNode** pphead, SLTDataType x) { //向操作系统申请一个新结点 SLTNode* newnode = SLTBuyNode(x); //链表为空,phead直接指向newNode if (*pphead == NULL) { *pphead = newnode; } //链表不为空,找尾节点,将尾节点和新节点连接起来 else { SLTNode* ptail = *pphead; while (ptail->next != NULL) { ptail = ptail->next; } //此时有:ptail->next == NULL ptail->next = newnode; } } 尾删普遍情况是有多个结点,思路是:
1,找到尾结点
2,将尾结点的前驱结点置为空,再将尾结点释放并置空
特殊情况是,只有一个结点,思路是:
将唯一一个结点,也就是头结点释放并置空
//尾删 void SLTPopBack(SLTNode** phead) { //二级指针不为空,链表不为空 assert(phead && *phead); //在只有一个结点的情况下,prev = NULL,所以在下面对空指针的操作就是不合法的, //所以此时要分离出来,特殊处理,之后我们再测试assert断言就发挥了作用 //注意*的优先级是低于->的,所以需要加上括号 if ((*phead)->next == NULL) { free(*phead); *phead = NULL; } //此时是结点个数大于一的情况 else { SLTNode* prev = NULL; SLTNode* ptail = *phead; while (ptail->next) { prev = ptail; ptail = ptail->next; } prev->next = NULL; free(ptail); ptail = NULL; } } 头插 //头插 void SLTPushFront(SLTNode** pphead, SLTDataType x) { SLTNode* newnode = SLTBuyNode(x); newnode->next = *pphead; *pphead = newnode; } 头删头删时,只需要将头结点的next结点保存起来,然后将原来的头结点释放,最后将next结点变成新的头结点即可。
//头删 void SLTPopFront(SLTNode** phead) { assert(phead && *phead); //经过分析,在只有一个结点和多个结点的情况时,都是符合预期的,所以不必分离讨论 SLTNode* next = (*phead)->next; free(*phead); *phead = next; } 查找如果只是在链表中查找数据,那么就不会修改,只需传一级指针和要查找的数据,
而返回值则是所查找到的结点
//查找 SLTNode* SLTFind(SLTNode* phead, SLTDataType x) { //创建pcur来遍历链表 SLTNode* pcur = phead; //当结点不为空,进入循环 while (pcur) { if (pcur->data == x) { return pcur; } pcur = pcur->next; } //没有找到 return NULL; } 指定位置pos之前插入既然是插入数据,那么就要对链表进行修改,需要传址调用,要传二级指针,结点pos,和数据x
//指定位置pos之前插入 void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { assert(pphead && pos); //如果pos的位置就是头结点,那么就相当于头插 if (pos == *pphead) { //头插 SLTPushFront(pphead, x); } else { //创建新结点 SLTNode* newnode = SLTBuyNode(x); //创建prev记录pos的前驱结点 SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } //此时找到了pos newnode->next = pos; prev->next = newnode; } } 指定位置之后pos插入 //指定位置之后pos插入 void SLTInsertAfter(SLTNode* pos, SLTDataType x) { //在pos之后插入,就不需要知道头结点 SLTNode* newnode = SLTBuyNode(x); newnode->next = pos->next; pos->next = newnode; } 指定位置删除 //指定位置删除 void SLTErase(SLTNode** pphead, SLTNode* pos) { assert(pphead && pos); if (pos == *pphead) { //头删 SLTPopFront(pphead); } else { SLTNode* del = *pphead; while (del->next != pos) { del = del->next; } del->next = pos->next; free(pos); pos == NULL; } } 指定位置之后删除 //指定位置之后删除 void SLTEraseAfter(SLTNode* pos) { assert(pos); SLTNode* del = pos->next; pos->next = del->next; } 销毁链表 //销毁链表 void SListDestroy(SLTNode** pphead) { assert(pphead); SLTNode* pcur = *pphead; while (pcur) { SLTNode* next = pcur->next; free(pcur); pcur = next; } *pphead = NULL; } 链表的分类
数据结构(初阶)(三)----单链表由讯客互联电脑硬件栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“数据结构(初阶)(三)----单链表”