一、什么是链表?
链表与顺序表不同的是,链表有节点,且链表在空间中的从顺序不是连续的,链表通过地址对其进行访问。因此,链表的每一个节点地址都是随机的,每一个节点的地址即存此结点的这个地址,也存下一个结点的地址。
二、单链表的增删查改的一系列操作
(1)、链表的初始化
由于是单链表,这里我们采用动态内存开辟的方法来对其初始化,单链表在最开始是没有节点的,链表为空,其次链表的添加是需要创建新结点的。因此小陈使用malloc开辟一块新结点。
SListNode* BuySListNode(SLTDatetype x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)
{
perror("malloc");
exit(-1);
}
else
{
newnode->data = x;
newnode->next = NULL;
}
return newnode;
}
这时候有人发问了,吼?你这每一个结点指向的空间,那空间里有啥?我们要存放数据嘛,当然就是需要有一个存放数据的空间了,其次还有存放当前节点和下一个结点的空间。链表的开辟不止需要用来存放数据的,还存放有地址,方便查找下一个结点的存储。小陈也给大家看看我链表结构体的创建吧。
typedef int SLTDatetype;
typedef struct SListNode
{
SLTDatetype data;
struct SListNode* next;
}SListNode;
小陈这里使用的typedef方便我们在后期改变链表存储的数据类型。
(2)链表的打印
链表的打印与顺序表的打印不同,链表的空间不连续,如果像顺序表那样打印,指定是得不到我们想要的结果的。因此链表的打印我们还是通过地址访问的方法去打印。当链表的最后一个结点存储的内内容为NULL时,说明链表的打印完成。我们用指针plist去指向链表的头结。
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur != NULL)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
(3)链表的尾插
重点来了,大家肯定很疑惑,这链表尾插咋实现呢?当然还是需要我们重新创建一个新结点,让原来的尾结点指向新结点,让新结点指向NULL。
上图就是我们尾插的思路。 让pphead找到我们的尾结点,再改变尾结点。代码思路如下:
void SListPushBack(SListNode** pplist, SLTDatetype x)
{
SListNode* newnode = BuySListNode(x);
SListNode* tail = *pplist;
if (*pplist == NULL)
{
*pplist = newnode;
newnode->next = NULL;
}
else
{
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
newnode->next = NULL;
}
}
(4)单链表的尾删
单链表的尾删也和顺序表的尾删一样嘛?当然是不一样的了,顺序表的内存可是连续的,我们只需要把内存减减一下就得到我们预期结果了。前面铁铁们我也说了,链表内存不连续,这就意味着我们不能通过顺序表的尾删的操作方法来实现对链表的尾删。 我们用尾结点的前一个结点去指向NULL,再free掉我们尾结点。
代码如下:
void SListPopBack(SListNode** pplist)
{
//一个节点的情况
if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
SListNode* tail = *pplist;
SListNode* prv = tail;
while (tail->next != NULL)
{
prv = tail;
tail = tail->next;
}
prv->next = NULL;
free(tail);
tail = NULL;
}
}
当然了,我们同时也需要考虑链表为空的情况,如果链表为空 ,还继续操作尾删就会导致我们的hhead为一个野指针。
(5)链表的头删与头插
链表的头删:已知每个链表的空间不仅存放的有数据也有结点的地址,因此在头删时,我们需要保证需要删除节点的所保存的下一个结点的地址我们已获取才能把结点安心的进行释放。头删当然我们也需要考虑链表为空的情况。
void SListPopFront(SListNode** pplist)
{
assert(*pplist);
//一个节点
/*if ((*pplist) == NULL)
{
exit(-1);
}
else
{
SListNode* head = (*pplist)->next ;
free(*pplist);
*pplist = head;
}*/
SListNode* head = *pplist;
*pplist = (*pplist)->next;
free(head);
}
链表的头插:
void SListPushFront(SListNode** pplist, SLTDatetype x)
{
SListNode* newnode = BuySListNode(x);
newnode->next = *pplist;
*pplist = newnode;
}
(6)删除pos位置
在链表中删除pos位置的值,我们需要找到pos位置的地址
上图就是我们对pos位置的删除。当然了,pos位置的删除要求pos不为空且链表不为空,当pos位置是头结点时,那我们可以用我们之前写的头删的函数进行调用
void SLTErase(SListNode** pphead, SListNode** pos)
{
assert(*pphead);
assert(*pos);
if ((*pphead) == (*pos))
{
SListNode* head = *pphead;
*pphead = (*pphead)->next;
free(head);
}
else
{
while ((*pphead)->next !=(*pos))
{
*pphead = (*pphead)->next;
}
(*pphead)->next = (*pos)->next;
free((*pos));
*pos= NULL;
}
}
这里有个小细节,也是小陈经常犯下的
因此,给Erase函数传参时我们需要提前用Find函数查找到pos位置的地址,再把pos位置的地址传给Erase函数,在对其进行删除。这一点在函数传参对其进行删除或者查改时要额外注意,如果需要修改链表的数据,则需要我们查找到该数据的地址,再对他进行修改。
(7)销毁链表
这里我们对链表依次销毁,在每次销毁结点时,我们需要先保存这个结点所存储的下一个结点的地址,再释放该结点。
以上就是链表销毁的思路。
void SLTDestroy(SListNode** pphead)
{
//断言链表不为空
assert(*pphead);
SListNode* cur = *pphead;
SListNode* next = cur->next;
while (cur)
{
free(cur);
cur = next;
}
*pphead = NULL;
}
以上就是单链表的增删查改,希望对各位铁铁有帮助,欢迎评论区留言