单链表的概念和结构
概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
链表的结构跟⽕⻋⻋厢相似,火车在淡季时⻋次的⻋厢会相应减少,旺季时⻋次的⻋厢会额外增加⼏节。只需要将⽕⻋⾥的某节⻋厢去掉/加上,不会影响其他⻋厢,每节⻋厢都是独⽴存在的。所以链表也是可动态开辟空间的一种结构。
火车的车厢是独立存在的,且每节车厢都有车门,假设每节车厢的车门都被锁上了,并且需要不同的钥匙开锁,而每次只能携带一把钥匙,我们该如何从车头走到车尾呢?很简单,我们在每一节车厢都放一把下一节车厢的钥匙。
在C语言中,钥匙就是指针,而链表可以用指针表示为:
与顺序表不同,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/节点”。节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。 图中指针变量 plist保存的是第⼀个节点的地址,我们称plist此时“指向”第⼀个节点,如果我们希 望plist“指向”第⼆个节点时,只需要修改plist保存的内容为0x0012FFA0。
链表中每个节点都是独⽴申请的(即需要插⼊数据时才去申请⼀块节点的空间),我们需要通过指针变量来保存下⼀个节点位置才能从当前节点找到下⼀个节点,因此我们需要指针变量来保存下⼀个节点的位置。
结合前⾯学到的结构体知识,我们可以给出每个节点对应的结构体代码:
//假设当前保存的节点为整型:
struct SListNode
{
int data; //节点数据
struct SListNode* next; //指针变量⽤保存下⼀个节点的地址
};
当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存下⼀个节点的地址(当下⼀个节点为空时保存的地址为空)。
基础理论准备完毕,我们来看增删查改以及各种函数的实现:
//定义类型名
typedef int slnDataType;
//创建结构体
typedef struct SListNode
{
slnDataType elem;
struct SListNode* next;
}SLN;
//单链表申请新节点函数
SLN* SLNapply(slnDataType num)
{
SLN* newnode = (SLN*)malloc(sizeof(SLN));
if (newnode == NULL)
{
perror("malloc Fail!");
return 1;
}
newnode->elem = num;
newnode->next = NULL;
return newnode;
}
//单链表打印
void SLNprint(SLN* phead)
{
assert(phead);
//创建临时变量,代替头节点遍历链表
SLN* pcur = phead;
while (pcur)//pcur != NULL
{
printf("%d->", pcur->elem);
pcur = pcur->next;
}
printf("NULL\n");
}
//单链表头插函数
void slnHeadInsert(SLN** pphead,slnDataType num)
{
//pphead不能为空指针是因为空指针不能解引用,影响后续操作
//*pphead不能为空是因为链表不能为空链表
assert(pphead && *pphead);
//创建新节点
SLN* newnode = SLNapply(num);
newnode->next = *pphead;
*pphead = newnode;
}
//单链表尾插函数
void slnTailInsert(SLN** pphead, slnDataType num)
{
//pphead不能为空指针是因为空指针不能解引用,影响后续操作
assert(pphead);
//开辟新节点
SLN* newnode = SLNapply(num);
//空链表与非空链表两种情况
if (*pphead == NULL)//空链表情况
{
*pphead = newnode;
newnode->next = NULL;
}
else//非空链表
{
//创建临时变量
SLN* ptail = *pphead;
//遍历链表
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
newnode->next = NULL;
}
}
//单链表头删函数
void slnHeadDelete(SLN** pphead)
{
//不为空指针/空链表
assert(pphead && *pphead);
SLN* pcur = (*pphead)->next;
free(*pphead);
*pphead = pcur;
}
//单链表尾删函数
void slnTailDelete(SLN** pphead)
{
//不为空指针/空链表
assert(pphead && *pphead);
//创建临时变量
SLN* ptail = *pphead;
SLN* prev = *pphead;
//遍历链表
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
//单链表查找函数
SLN* slnFind(SLN** pphead, slnDataType pos)
{
//不为空指针/空链表
assert(pphead );
//创建临时变量
SLN* pcur = *pphead;
while (pcur)
{
if (pcur->elem == pos)//找到了
{
return pcur;
}
pcur = pcur->next;
}
return NULL;//没找到
}
//指定位置之前插入函数
void slnPosInsertFront(SLN** pphead, SLN* pos,slnDataType num)
{
assert(pphead && *pphead);
assert(pos);
//申请新节点
SLN* newnode = SLNapply(num);
//pos指向头节点时
if (*pphead == pos)
{
slnHeadInsert(pphead, num);
}
else
{
//pos指向其他位置时
SLN* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
//指定位置之后插入函数
void slnPosInsertBack(SLN* pos, slnDataType num)//不需要头节点
{
assert(pos);
//申请新节点
SLN* newnode = SLNapply(num);
//pos -> newnode -> pos->next
newnode->next = pos->next;
pos->next = newnode;
}
//单链表删除指定位置的节点函数
void slnPosDelete(SLN** pphead, SLN* pos)
{
assert(pphead && *pphead);
assert(pos);
//创建临时变量
SLN* pcur = *pphead;
//遍历链表寻找pos
while (pcur->next != pos)
{
pcur = pcur->next;
}
//先存地址后释放
pcur->next = pos->next;
free(pos);
pos = NULL;
}
//删除pos之后的节点函数
void slnDeleteAfter(SLN* pos)
{
assert(pos && pos->next);//pos和pos的下一个节点都不能为空指针
//先存再释放
SLN* pdel = pos->next;
pos->next = pos->next->next;
free(pdel);
pdel = NULL;
}
//单链表销毁函数
void slnDestroy(SLN** pphead)
{
assert(pphead && *pphead);
SLN* pcur = *pphead;
while (pcur)
{
SLN* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
各位在了解之后要尝试自己去模仿代码,以便融汇贯通。