🔥个人主页:@草莓熊Lotso
🎬作者简介:C++研发方向学习者
📖个人专栏: 《C语言》 《数据结构与算法》《C语言刷题集》《Leetcode刷题指南》
⭐️人生格言:生活是默默的坚持,毅力是永久的享受。
前言:前面我们完成了顺序表的学习以及代码实现,大家一定要自己去实现一遍试试看并且画图理解。那么这篇博客我将会继续为大家分享单链表的定义以及其中打印,尾插,头擦,尾删,头删等接口的实现。还是一样,先分部分实现,最后展式总的代码。
目录
一.单链表的概念
--在之前我们学习了逻辑结构和物理结构都是线性的顺序表,但是我们会发现顺序表有以下3个比较明显的缺陷
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间,有不小的消耗
- 增容一般呈两倍的增长,会有一定的空间浪费
那么我们的链表刚好可以使头部插入删除的时间复杂度为O(1),不需要增容也不存在空间的浪费。但是这里需要声明一下,每个数据结构都是有它的优势和缺陷的。
链表:链表是一种是一种物理存储结构上非连续,非顺序的存储结构,但它的逻辑结构还是线性的。我们可以把链表想象成火车的一节节车厢链接在一起,具体形式如同所示这里不过多的去讲解了,这里的图是逻辑结构,正常来说链表不一定是这样连续的,而是在堆区中随意存放的,通过存储的地址找到下一个结点。
我们今天一起来实现一个单链表,链表是由一个一个节点组成的,它的节点由两个组成部分
- 保存的数据
- 指针,存放的是下一个结点的地址
我们先在.h文件中定义一个单链表,分3个文件以及重命名的好处啥的在顺序表中都讲过了,这里就不说了。
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
二.单链表的打印
--为了方便后续的观察,我们先来实现一个单链表的打印
SList.c:
//打印
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;//移动pcur,保证头指针phead位置不变
while (pcur!= NULL)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;//pcur往前走
}
printf("NULL\n");
}
重点提示:
打印的实现还是很简单的,这里先用一个pcur指针存下头指针,移动它去打印,打印完一个数据后就利用pcur->next往前走,直到pcur==NULL。
--我们在test.c文件中创建几个节点,并且把它们打印出来看看吧,一定注意看注释
test.c:
#include"SList.h"
int test1()
{
//创建节点
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;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//打印
SLTNode* plist = node1;//头结点
SLTPrint(plist);
}
int main()
{
test1();
return 0;
}
--测试完成没问题,能正确打印出想要的链表 ,退出码为0。
三.单链表的尾插
--在实现单链表的尾插之前,我们先封装一个申请新节点的函数,后续头插等接口的实现也会用上
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newcode = (SLTNode*)malloc(sizeof(SLTNode));
newcode->data = x;
newcode->next = NULL;
return newcode;
}
SList.c:
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
//申请新节点
SLTNode* newcode =SLTBuyNode(x);
//如果头节点为空
if (*pphead == NULL)
{
*pphead = newcode;
}
else {
SLTNode* ptail = *pphead;
//找到尾节点
while (ptail->next != NULL)
{
ptail = ptail->next;
}
//找到了之后链接新节点
ptail->next = newcode;
}
}
重点提示:
- 我们先判断当头结点为空时,是无法直接尾插的,我们直接令新节点为头结点就行了
- 再就是我们需要找到尾结点,利用ptail从头开始往后找,如图所示,直到ptail->next==NULL时结束
- 找到尾结点后,将新节点链接上去就行了
- 还有就是传参数的时候,一定要传的是plist的地址,用二级指针接收。这样形参的修改才能影响实参的修改
test.c:
#include"SList.h"
int test1()
{
//创建节点
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;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//打印
SLTNode* plist = node1;//头结点
SLTPrint(plist);
}
void test2()
{
//头结点
SLTNode* plist =NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
}
int main()
{
//test1();
test2();
return 0;
}
--测试完成,打印没有问题,退出码为0。
四.单链表的头插
SList.c:
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//申请新节点
SLTNode* newcode = SLTBuyNode(x);
newcode->next = *pphead;
*pphead = newcode;
}
重点提示:
先断言一下pphead不能为空,再就是先用申请的新节点链接上原来的头指针,再令新节点成为头指针就可以了
test.c:
#include"SList.h"
int test1()
{
//创建节点
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;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//打印
SLTNode* plist = node1;//头结点
SLTPrint(plist);
}
void test2()
{
//头结点
SLTNode* plist =NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//头插
SLTPushFront(&plist, 5);
SLTPrint(plist);
}
int main()
{
//test1();
test2();
return 0;
}
--测试完成,打印没有问题,退出码为0
五.单链表的尾删
SList.c:
//尾删
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 != NULL)
{
prev = ptail;
ptail = ptail->next;
}
//prev ptail
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
重点提示:
- 首先是断言,pphead不能为空,链表也不可以为空
- 特别判断只有一个节点的情况,这题我们还需要找到尾结点的前一个结点,如果只有一个节点的话直接释放掉就好了
- 找尾节点的同时找到尾节点的前一个节点,每次尾节点向前走之前,先让prev指向其原来的位置
- 最后直接让prev->next=NULL,释放掉ptail就好了
test.c:
#include"SList.h"
int test1()
{
//创建节点
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;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//打印
SLTNode* plist = node1;//头结点
SLTPrint(plist);
}
void test2()
{
//头结点
SLTNode* plist =NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//头插
SLTPushFront(&plist, 5);
SLTPrint(plist);
//尾删
SLTPopBack(&plist);
SLTPrint(plist);
}
int main()
{
//test1();
test2();
return 0;
}
--测试完成,删掉了4,打印没有问题,退出码为0
六.单链表的头删
SList.c:
//头删
void SLTPopFront(SLTNode** pphead)
{
//链表不能为空
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
重点提示:
断言和尾删一样,这里主要就是先定义一个next记录phead的下一个节点,再直接free掉*pphead。最后让next成为新的头节点就可以了
test.c:
#include"SList.h"
int test1()
{
//创建节点
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;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//打印
SLTNode* plist = node1;//头结点
SLTPrint(plist);
}
void test2()
{
//头结点
SLTNode* plist =NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//头插
SLTPushFront(&plist, 5);
SLTPrint(plist);
//尾删
SLTPopBack(&plist);
SLTPrint(plist);
//头删
SLTPopFront(&plist);
SLTPrint(plist);
}
int main()
{
//test1();
test2();
return 0;
}
--测试完成,删掉了头的5打印没问题,退出码为0
七.代码展现
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;
//打印
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode * *pphead);
//头删
void SLTPopFront(SLTNode * *pphead);
SList.c:
#include"SList.h"
//打印
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;//移动pcur,保证头指针phead位置不变
while (pcur!= NULL)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newcode = (SLTNode*)malloc(sizeof(SLTNode));
newcode->data = x;
newcode->next = NULL;
return newcode;
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
//申请新节点
SLTNode* newcode =SLTBuyNode(x);
//如果头节点为空
if (*pphead == NULL)
{
*pphead = newcode;
}
else {
SLTNode* ptail = *pphead;
//找到尾节点
while (ptail->next != NULL)
{
ptail = ptail->next;
}
//找到了之后链接新节点
ptail->next = newcode;
}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//申请新节点
SLTNode* newcode = SLTBuyNode(x);
newcode->next = *pphead;
*pphead = newcode;
}
//尾删
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 != NULL)
{
prev = ptail;
ptail = ptail->next;
}
//prev ptail
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
//链表不能为空
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
test.c:
#include"SList.h"
int test1()
{
//创建节点
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;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//打印
SLTNode* plist = node1;//头结点
SLTPrint(plist);
}
void test2()
{
//头结点
SLTNode* plist =NULL;
//尾插
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
//头插
SLTPushFront(&plist, 5);
SLTPrint(plist);
//尾删
SLTPopBack(&plist);
SLTPrint(plist);
//头删
SLTPopFront(&plist);
SLTPrint(plist);
}
int main()
{
//test1();
test2();
return 0;
}
往期回顾:
结语:在这篇博客中,我们完成了单链表的打印,尾/头插,尾/头删这些接口的实现,但是我们单链表还有部分接口没有实现,由于篇幅原因,博主将在下一篇中继续带大家实现单链表指定位置插入,删除,查找,修改等接口。如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。