文章目录
一、单链表的概念及结构
1.概念
链表是一种物理结构上非线性的,但链表在逻辑结构上是线性的,而数据元素的逻辑顺序是通过链表中的指针连接次序实现的。也就是说,链表中的每一个数据元素都是独立存储的,当需要存储数据元素时就申请一块空间用来存储当前数据,每一个数据元素又通过指针串联在一起,形成逻辑上的线性。
2.结构
链表是由一个个结点组成的数据结构,每一个结点都包含了两块区域**(数据域,指针域)**,当前结点的指针域存储的是下一个结点的地址,即指向下一个结点,而最后一个结点的指针域则是指向NULL。一个一个的结点连接起来就组成了链表。
每一个结点都是通过malloc申请的,两次申请得到的空间可能是连续的,也可能不是连续的。之所以结点之间可以用箭头连接,并不是表示物理结构上连续,而是因为当前结点的指针域存储的是下一个结点的地址,即可以通过当前结点的指针域找到下一个结点。
二、顺序表和单链表的区别
1.结构
顺序表:逻辑结构线性,物理结构线性,通过数组遍历的方式对数据进行操作。
链表:逻辑结构线性,物理结构非线性,通过一个个关系紧密的指针域对数据进行操作。
2.优缺点
①顺序表:[1]优点:可以通过下标直接访问所需要的数据;[2]缺点:容易发生频繁扩容,导致内存的浪费
②链表:[1]优点:可以按照实际需求创建结点,更充分地使用内存空间,且链表头部的插入、删除时间复杂度为O(1),而顺序表为O(N);[2]缺点:链表尾部的插入、删除时间复杂度为O(N),而顺序表为O(1);
因此当头部的插入、删除较多时用链表较好;当尾部的插入较多时用顺序表较好。
三、单链表的实现
头指针:plist存放头结点的地址,plist解引用后就是头结点的值。pphead储存plist的地址,pphead解引用后是plist的值
断言:①空链表可以打印,不用断言
②空链表可以尾插、头插,即plist可以为空,因此*pphead不用断言,而pphead是&plist,必定非空,需要断言
③空链表不能够尾删、头删,即plist不能为空,*pphead需要断言,pphead也需要断言
总之,pphead就是&plist,必定非空,需要断言,*pphead就情况而论
1.代码分区
创建3个文件:①头文件SList.h ②源文件(实现文件)SList.c ③源文件(测试文件)test.c
其中头文件SList.h进行单链表的定义和函数的声明;实现文件SList.c进行函数的实现;测试文件test.c进行测试。
2.1结点的定义
//定义链表的结构--结点的结构
typedef int SLTDataType;
typedef struct SListNode {
SLTDataType data; //储存的数据
struct SListNode* next;//下一个节点的地址
}SLTNode;
2.2结点的创建
SLTNode* SLTbuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTDataType)); //申请一个结点空间
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
3.链表的打印
链表和顺序表有所不同,顺序表传过来的指针肯定不会为空,而链表传过来的指针可能为空,不过空链表可以打印,因此不用断言
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead; //phead存储的是头结点的地址,因此我们不能改变phead
while (pcur)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
4.尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode=SLTbuyNode(x);
if (*pphead == NULL) //链表为空
{
*pphead = newnode;
}
else //链表非空
{
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
==为什么形参要传二级指针?==①链表为空的情况下,尾插相当于是:将新结点直接当作头结点,即要改变plist中存储的地址,那么应该使出传址调用,因此实参应传入&plist,所以形参应该是二级指针去接收。②每一个结点里都有一级指针next,想要改变next,就必须使用二级指针。(插入,删除,销毁都会涉及next的改变,因此都传二级指针SLTNode** pphead)。
5.头插
只需要 让新结点与原来的头结点产生链接,然后更新plist就好了。
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTbuyNode(x); //创建新结点
newnode->next = *pphead;
*pphead = newnode;
}
6.尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next == NULL) //只有一个结点的情况
{
free(*pphead); //直接释放
*pphead = NULL;
}
else //有多个结点的情况
{
SLTNode* prev = NULL;
SLTNode* ptail = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next; //prev永远在ptail前的那一个位置
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
7.头删
void SLTPopFront(SLTNode** pphead)
{
assert(*pphead);
SLTNode* next = (*pphead)->next; //不能直接free,否则就无法通过*pphead找到第二个结点,即无法更新plist
free(*pphead);
*pphead = next;
}
8.查找
采用while循环遍历,函数返回的是一个结构体指针
SLTNode* SLTFind(SLTNode* phead, SLTDataType* x)
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
9.在pos之前插入结点
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && pos);
if (pos == *pphead) //pos指向第一个位置,相当于头插
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLTbuyNode(x);
//找pos的前一个结点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos; //让新结点与前后结点形成链接
}
}
10.在pos之后插入结点
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTbuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
11.删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && pos);
if (pos == *pphead) //pos指向第一个位置,相当于头删
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos) //找到pos的前一个结点
{
prev = prev->next;
}
prev->next = pos->next; //让pos的前一个结点与pos的后一个结点链接
free(pos);
pos = NULL;
}
}
12.删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);//诺pos->next==NULL,那后续free就会报错
SLTNode* del = pos->next;
pos->next = del->next; //语句3
free(del);
del = NULL;
}
临时指针del用来记录原先的pos-next,方便执行完语句3后找到位置进行free。如果不使用del,在执行完语句3后,pos->next将发生改变,想删掉的结点就不容易找到位置去删除了。
13.销毁链表
void SListDestroy(SLTNode** pphead)
{
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
链表在物理结构上是不连续存储的,销毁链表必须要一个结点一个结点去销毁,最后把phead设置为NULL。
四、总代码
①SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
//定义链表的结构--结点的结构
typedef struct SListNode {
SLTDataType data;
struct SListNode* next;
}SLTNode;
//结点的创建
SLTNode* SLTbuyNode(SLTDataType x);
//链表的打印
void SLTPrint(SLTNode* phead);
//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//链表的尾删
void SLTPopBack(SLTNode** pphead);
//链表的头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType* x);
//在pos之前插入结点
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在pos之后插入结点
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);
②SList.c
#include "SList.h"
//结点的创建
SLTNode* SLTbuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTDataType));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//链表的打印
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode=SLTbuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
//链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTbuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//链表的尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next == NULL) //只有一个结点
{
free(*pphead);
*pphead = NULL;
}
else //有多个结点
{
SLTNode* prev = NULL;
SLTNode* ptail = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next; //prev永远在ptail前的那一个位置
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
//链表的头删
void SLTPopFront(SLTNode** pphead)
{
assert(*pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType* x)
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在pos之前插入结点
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && pos);
if (pos == *pphead) //pos指向第一个位置,相当于头插
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLTbuyNode(x);
//找pos的前一个结点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
//在pos之后插入结点
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTbuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && pos);
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);//诺pos->next==NULL,那后续free就会报错
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
//销毁链表
void SListDestroy(SLTNode** pphead)
{
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
③test.c
#include "SList.h"
void test()
{
SLTNode* plist = NULL; //创建空链表
SLTPushBack(&plist, 1); //尾插,变成1234
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTPopBack(&plist); //尾删,变成123
SLTPrint(plist);
SLTPushFront(&plist,5); //头插,变成5123
SLTPrint(plist);
SLTPopFront(&plist); //头删,变成123
SLTPrint(plist);
SLTNode* Find = SLTFind(plist, 2); //查找2
if (Find)
printf("找到了\n");
else
printf("没找到\n");
SLTInsert(&plist, Find, 6); //在pos前插入结点,变成1623
SLTPrint(plist);
SLTInsertAfter(&plist, Find, 7); //在pos前插入结点,变成16273
SLTPrint(plist);
SLTEraseAfter(Find); //删除pos后的结点,变成1623
SLTPrint(plist);
SLTErase(&plist,Find); //删除pos结点,变成163
SLTPrint(plist);
SListDestroy(&plist); //销毁链表
}
int main()
{
test();
return 0;
}