🌏个人博客主页:心.c
前言: 最近练习算法回去学了链表,收获挺大的,大概内容整理了一下,语言是用c写的,所以在这里分享给大家,希望大家可以有所收获
🔥🔥🔥文章专题:链表
😽感谢大家的点赞👍收藏⭐️评论✍您的一键三连是我更新的动力 💓
链表的概念:
链表是一种线性数据结构,它不像数组那样在内存中连续存储数据,而是通过节点(每个节点包含数据和指向下一个节点的指针)链接起来形成一个序列。每个元素不仅包含数据,还包含对下一个元素的引用,这种设计允许链表在内存中以灵活和动态的方式分布。
- 节点: 链表的基本组成单位。每个节点包含数据和一个指向下一个节点的指针。
- 头结点: 列表中的第一个节点。
- 尾节点: 列表中的最后一个节点,其指针通常为
NULL
。 - 指针: 每个节点都有一个指针,用于存储下一个节点的地址。在单向链表中,节点仅指向其后继;在双向链表中,节点还包含一个指向前驱的指针。
链表的定义:
链表是一种线性数据结构,链表的元素(通常称为节点)可以在内存的任意位置分散存储。链表中的每个节点包含两部分:一部分是用于存储数据的字段,另一部分是一个指针
//定义节点内容的数据类型
typedef int SLTDateType;
typedef struct SListNode {
SLTDateType data;
struct SLTNode* next;
}SLTNode;
下面这个是我对链表的理解与感悟,如果写的有问题希望大佬们可以指出
SLTNode *phead = NULL;
在这个例子中,phead是一个指向SListNode结构体的指针,它是链表的起点。我们通过这个指针来访问和操作链表中的节点。
链表本身是由一系列节点组成的,而我们用来管理和操作链表的主要工具是一个指向链表头结点的指针。这个指针是链表的入口点,通过它可以访问链表中的所有节点。所以,当我们说“链表本身是一个指针”,我们实际上是在指这个头结点指针。但要理解,这个指针指向的是一个结构体(链表的头结点),而链表的全部内容是由多个这样的结构体节点通过指针链接起来的。
链表节点的创建:
因为在进行链表的添加的时候,我们要不停向添加的项创建节点,所以在这里我就写一个创建节点的方法,下面在添加节点的时候就不需要在进行一系列的判断和创建动态内存和添加了
因为malloc创建内存可能会失败,所以我们要加判断语句 if (newNode)来进行判断,如果添加节点newNode成功,我们就将节点newNode所指向的地方赋值,并且返回所创建的节点
//创建链表
SLTNode* SLTBuyNode(SLTDateType x) {
SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
//如果创建链表失败
if (newNode) {
perror("malloc fail");
exit(1);
}
//如果创建成功
newNode->data = x;
newNode->next = NULL;
return newNode;
}
链表的插入:
讲节点的插入和删除讲一下二级指针的概念和在下文中的作用
!!!传一级指针,我们需要传入一级指针的地址,并把二级指针当作形参来进行传递一级指针的地址,传指针必须传地址!!!
下文中关于进行节点的删除和添加方法的参数几乎都是二级指针传参,因为在这里我们的返回值是void,我们的节点是一个结构体指针,所以我们要传二级指针来来表示一级指针的地址来传递我们节点本身的地址,也就是我们结构体指针的地址,如果我们传的是一级指针,那么我们传的就不是地址而是值,如果传值返回值为void那么我们的值就不会发生变化,方法中所进行的改变也只是在指针的副本中,并不会对节点产生本质改变
int main() {
SLTNode* plist = NULL;
//插入节点
SLTPushBack(&plist, 1);
//打印节点
SLTPrint(plist);
}
第一个节点的内容: *plist
指向第一个节点的指针(就是我们的结构体指针): plist
指向第一个节点的指针的地址:&plist
尾部插入节点:
*pphead 是我们的结构体指针 pphead是指向我们结构体指针的指针(也就是二级指针)
插入时先判断参数是否为NULL,如果为空,就停止程序,如果第一个结构体指针的内容为NULL(是否存放地址),就直接将创建新节点的内容赋值到第一个结构体指针中,如果第一个结构体指针内容不为空,那么就通过while判断找到最后一个结构体指针,然后将最后一个结构体指针内容中的next指向我们新创建的节点的内容(因为如果第一个结构体指针内容为NULL的话就不能对ptail->next = newNode;进行判断了,否则会报错,所以我们要分开写)
//尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x) {
//判断是否存在链表(传入是否为NULL)
assert(pphead);
//创建链表
SLTNode* newNode = SLTBuyNode(x);
//如果指向第一个节点的指针为空
if (*pphead==NULL) {
*pphead = newNode;
}
else {
SLTNode* ptail = *pphead;
while (ptail->next) {
ptail = ptail->next;
}
ptail->next = newNode;
}
}
头部插入节点:
在头部插入节点比较简单,也是先判断传参是否为NULL,如果不为NULL,那么将第一个节点所指向的内容赋值给我们的新节点,然后将我们创建的节点内容指向我们的第一个节点,并且将第一个节点改为我们创建的新节点
//头插
void SLTPushFront(SLTNode** pphead, SLTDateType x) {
assert(pphead);
SLTNode* newNode = SLTBuyNode(x);
newNode->next = *pphead;
//将头结点赋给新创建的节点
*pphead = newNode;
}
在指定位置之前插入节点:
在插入节点之前先保证我们的链表和节点不为空,还要保证我们的节点pos不为空,处理完这些我们就要进行分析了,如果我们想实现在pos之前插入数据,我们要得到pos之前的节点,然后进行插入,但是当我们的pos是我们的第一个节点,那么我们就不需要找到之前的节点然后进行插入了,可以直接执行我们头插的方法了
//在指定位置之前插入节点
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x) {
//判断链表是否为空
assert(pphead&&*pphead);
assert(pos);
//创建新节点
SLTNode* newNode = SLTBuyNode(x);
//如果为头插
if (*pphead == pos) {
SLTPushFront(pphead, x);
}
else {
//找到pos之前的节点
SLTNode* prev = *pphead;
while (prev != pos) {
prev = prev->next;
}
newNode->next = pos;
prev->next = newNode;
}
}
在指定位置之后插入节点:
当在指定位置插入节点时,我们就不需要头结点pphead了,我们只需要找到我们的pos节点,然后直接进行插入就可以了
//在指定位置之后插入节点
void SLTInsertAfter(SLTNode* pos, SLTDateType x) {
assert(pos);
SLTNode* newNode = SLTBuyNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
链表的删除:
尾部删除节点:
删除节点比较简单,分两种情况,第一个就是多个链表,我们在这里只需要找到最后两个节点,然后将最后最后一个节点释放,然后赋值为NULL,然后将倒数第二个节点的内容next指向的地址也赋值为NULL,防止野指针的出现,但是还要一个情况,当我们的链表只有一个节点时,我们根本不需要上面那个方法了,只需要将第一个节点也就是我们的最后一个节点删除掉,然后将结构体指针赋值为NULL就可以了
//尾删
void SLTPopBack(SLTNode** pphead) {
//链表不为空也不指向空
assert(pphead && *pphead);
if ((*pphead)->next == NULL) {
free(*pphead);
*pphead = NULL;
}
else {
//定义倒数第二个节点
SLTNode* prev = *pphead;
//定义最后一个节点
SLTNode* ptail = *pphead;
while (ptail->next) {
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
头部删除节点:
头删链表比尾删链表简单的多,这里我们不需要判断那么多情况,在这里我们只需要获得第二个节点,然后将第一个节点释放掉,赋值为NULL,然后将我们的第一个结构体指针内容换成我们的第二个节点,第二个节点是否为NULL无所谓 ,不会影响后续添加
//头删
void SLTPopFront(SLTNode** pphead) {
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
删除pos节点:
当我们删除pos节点我们需要分两种情况,第一种就是多个节点,我们正常获取pos之前的节点,然后删除一个节点,将前一个节点连接我们的后一个节点,还要一种就是当我们只有一个节点时,将直接使用头删方法
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos) {
assert(pphead && *pphead);
assert(pos);
//如果节点是第一个
if (pos == *pphead) {
SLTPopFront(pphead);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos) {
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
删除pos之后的节点:
删除pos之后的节点也很简单,这里我们就需要获得pos节点,然后在获得pos之后要删除的节点,然后将我们要删的节点的下一个节点赋值给我们pos节点的next,然后进行删除赋值
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos) {
assert(pos&&pos->next);
//获取pos后面的一个节点
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
给大家补充一个方法就是打印链表的方法
//打印链表
void SLTPrint(SLTNode* phead) {
SLTNode* pcur = phead;
while (pcur) {
printf("%d", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
讲到这里就结束了,谢谢大家的观看