1.什么是链表?
链表是一种数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表中的节点可以在内存中不连续地存储,通过指针来连接彼此。链表可以是单向链表,双向链表或循环链表。链表的优点是可以动态地分配内存空间,插入和删除操作效率高,但查找操作效率较低。
2.链表具有以下特点:
- 链表是一种动态数据结构,可以动态地分配内存空间,不需要预先指定大小。
- 链表中的节点可以在内存中不连续地存储,通过指针来连接彼此。
- 链表支持高效的插入和删除操作,时间复杂度为O(1),只需调整指针即可完成操作。
- 链表的查找操作效率较低,时间复杂度为O(n),需要从头节点开始遍历直到找到目标节点。
- 链表可以是单向链表、双向链表或循环链表,根据指针的指向关系不同而有所区别。
- 链表不需要连续的内存空间,可以更灵活地处理数据结构的变化
3.链表的样貌
链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。车厢是独立存在的,且每节车厢都有车门。想象⼀下这样的场景,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带⼀把钥匙的情况下如何从车头⾛到车尾? 最简单的做法:每节车厢里都放⼀把下⼀节车厢的钥匙。
每一节车厢长与以下类似
4.与顺序表的差别
与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为“结点/节点” 节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。图中指针变量plist保存的是第⼀个节点的地址,我们称plist此时“指向”第⼀个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012FFA0。
为什么还需要指针变量来保存下⼀个节点的位置?链表中每个节点都是独立申请的(即需要插入数据时才去申请⼀块节点的空间),我们需要通过指针变量来保存下⼀个节点位置才能从当前节点找到下⼀个节点。
5.怎样实现链表的一些功能
1.链表的创建
#include<iostream>
using namespace std;
typedef int SLTDataType;//便于适用于其他的类型如char float 等
typedef struct SListNode
{
SLTDataType data; //节点数据
struct SListNode* next; //指针保存下⼀个节点的地址
}SLTNode;
SLTNode* a = (SLTNode*)malloc(sizeof(SLTNode));//为链表扩展一个空间,相当于火车的一节
2.链表的初始化
//链表的初始化
void SLInit(SLTNode**pphead)
{
if (pphead) //判断链表头是否为NULL,如果为NULL,则不可以解引用
{
(*pphead)->data = 0;
(*pphead)->next = NULL;
}
}
3.链表的打印
//链表的打印
void SLTPrint(SLTNode* phead)
{
while (phead)
{
printf("%d ",phead->data);
phead = phead->next; //不停地往后走,到每一节车厢,直到最后。
}
printf("\n"); //如果多次打印,会是结果更加的美观
}
4.链表其中一节的销毁
//一节车厢的销毁
void SListDesTroyone(SLTNode** pphead)
{
assert(pphead && *pphead); //判断是否传入空指针
if (*pphead)
{
free(*pphead);
*pphead = NULL;
}
else
{
exit(1);
}
}
5.销毁整个链表
//销毁整个链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead&&*pphead);
while (*pphead)
{
SLTNode* cur = (*pphead)->next;
free(*pphead);
*pphead = NULL;
*pphead = cur;
}
*pphead = NULL; //不要忘了将最后的pphead置为空
}
6.链表空间的额外创建
//链表空间的额外创建
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL) //判断空间是否开辟成功
{
perror("newnode fail");
exit(1);
}
else
{
newnode->data = x;
newnode->next = NULL;
}
return newnode;
}
7.尾节点的插入
//在末尾插入
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode*newnode= SLTBuyNode(x); //代码的创建
if (*pphead == NULL) //当没有头结点时,你新创建的便是尾插的结果
{
*pphead = newnode;
}
else
{
SLTNode* cur = *pphead;
while (cur->next) //先通过循环找到尾节点
{
cur = cur->next;
}
cur->next = newnode; //插入尾节点
}
}
8.在头结点之后插入一个节点
//在头结点的后插入一个节点
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL) //判断是否为空,是空就让新的当头结点
{
*pphead = newnode;
}
else
{
newnode->next = (*pphead)->next; //插入节点时,比较简单的方法就是先让新的节点的next
//指向头结点的下一个节点的位置,再让头节点的next指
//向新的节点,当然,你也可以再创建新的节点。
(*pphead)->next = newnode;
}
}
9.尾删
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead); // 确定有节点可以让我们删除
SLTNode* cur = *pphead;
SLTNode* zuihou = *pphead;
while (cur->next) //找到尾节点和尾节点的前一个节点
{
zuihou = cur;
cur = cur->next;
}
if(zuihou)
zuihou->next = NULL; //将删除后的尾节点的next置为空,要不然会使他成为野指针
SListDesTroyone(&cur); //删除我们的尾节点
}
10.删除头结点的下一个
//删除头结点的下一个
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead&&((*pphead)->next));//确保至少两个节点
SLTNode* newcode = (*pphead)->next->next;
SLTNode* shanchu = (*pphead)->next;
(*pphead)->next = newcode;
SListDesTroyone(&shanchu);
}
11.查找
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead); //确保不为空
SLTNode* cur = phead;
while ((cur->data)!=x) //寻找我们要的值
{
cur = cur->next;
}
printf("找到了");
return cur;
}
以上便是链接的一些基本的代码,希望对你有所帮助