1. 单链表
3.1 概念与结构
概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
淡季时⻋次的⻋厢会相应减少,旺季时⻋次的⻋厢会额外增加⼏节。只需要将⽕⻋⾥的某节⻋厢去掉/ 加上,不会影响其他⻋厢,每节⻋厢都是独⽴存在的。
在链表⾥,每节“⻋厢”是什么样的呢?
3.1.1 结点
与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/结点”结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。图中指针变量 plist保存的是第⼀个结点的地址,我们称plist此时“指向”第⼀个结点,如果我们希望plist“指向”第⼆个结点时,只需要修改plist保存的内容为0x0012FFA0。链表中每个结点都是独⽴申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。
3.1.2 链表的性质
1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续
2、结点⼀般是从堆上申请的
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续
结合前⾯学到的结构体知识,我们可以给出每个结点对应的结构体代码:
假设当前保存的结点为整型:
struct SListNode
{
int data; //结点数据
struct SListNode* next; //指针变量⽤保存下⼀个结点的地址
};
当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数
据,也需要保存下⼀个结点的地址(当下⼀个结点为空时保存的地址为空)。
当我们想要从第⼀个结点⾛到最后⼀个结点时,只需要在当前结点拿上下⼀个结点的地址就可以了。
3.1.3 链表的打印
给定的链表结构中,如何实现结点从头到尾的打印?
3.2 实现单链表
首先创建三个文件(一个头文件 一个对应的源文件 一个测试文件)
首先在头文件中创建单链表结构 并引入相对于的头文件
首先要申请节点 因为单链表的地址不一定是连续的 所以需要一个一个的申请空间
尾插方法
尾插思路:
先申请新节点,判断链表是否为空,如果为空,尾插进来的就是头节点。
如果链表不为空,则先遍历链表到最后一个节点,使最后一个节点的next指向尾插进来的节点。尾插的节点next指向空。
代码实现:
//尾插
void SLTPushBack(SLTNode** s, LTDataType x)
{
assert(s);
SLTNode* newnode = SLInit(x);
if (*s == NULL)
{
*s = newnode;
}
else
{
SLTNode* phead = *s;
while (phead->next)
{
phead = phead->next;
}
phead->next = newnode;
}
}
头插
头插思路:
直接头插即可。
代码实现:
//头插
void SLTPushFront(SLTNode** s, LTDataType x)
{
assert(s);
//申请新节点
SLTNode* newnode = SLInit(x);
//SLTNode* pphead = *s;
newnode->next = *s;
*s = newnode;
}
查找节点
//查找
SLTNode* SLTFind(SLTNode* phead, LTDataType x)
{
SLTNode* pos = phead;
while (pos)
{
if (pos->data == x)
{
return pos;
}
pos = pos->next;
}
return NULL;
}
在指定位置之前插⼊数据
//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, LTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLInit(x);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
删除指定结点
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
assert(pphead);
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
在指定位置之后插⼊数据
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, LTDataType x)
{
assert(pos);
SLTNode* newnode = SLInit(x);
newnode->next = pos->next;
pos->next = newnode;
}
删除pos之后的结点
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
销毁链表
//销毁链表
void SListDestroy(SLTNode** pphead)
{
SLTNode* pos = *pphead;
while (pos)
{
pos = pos->next;
free(*pphead);
*pphead = pos;
}
}
申请节点
//申请节点
SLTNode* SLInit(LTDataType x)
{
SLTNode* tmp = (SLTNode*)malloc(sizeof(SLTNode));
if (tmp == NULL)
{
perror("malloc");
exit(1);
}
tmp->data = x;
tmp->next = NULL;
return tmp;
}
打印节点
//打印节点
void SLTPrint(SLTNode* s)
{
SLTNode* phead = s;
while (phead)
{
printf("%d -> ", phead->data);
phead = phead->next;
}
printf("null");
}
尾删
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next==NULL)
{
*pphead = NULL;
}
else
{
SLTNode* prev = NULL;
SLTNode* pos = *pphead;
while (pos->next)
{
prev = pos;
pos = pos->next;
}
free(pos);
prev->next = NULL;
pos = NULL;
}
}
头删
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pos = (*pphead)->next;
free(*pphead);
*pphead = pos;
}
完 如有问题欢迎留言哦!