单链表详细解析|画图理解

前言:

        在前面我们学习了顺序表,相当于数据结构的凉菜,今天我们正式开始数据结构的硬菜了,那就是链表,链表有多种结构,但我们实际中最常用的还是无头单向非循环链表和带头双向循环链表,我们今天先学习无头单向循环链表。


1、链表介绍

       1.1链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

结点:因为链表的结点在逻辑上是连续,物理上不一定连续,所以每一个结点的类型有两部分组成:数据域(存储链表的数据)和指针域(存储后继结点的地址)。

结构:

①逻辑图:为了方便理解,想象出来的,用形象方式表示(如箭头)

②物理图:内存中真实存储,实实在在数据在内存中的变化

如上两幅图我们可知:①链式结构在逻辑上是连续的,但是在物理上不一定连续;②现实中结点一般都是malloc从堆上申请出来的;③从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。

        1.2顺序表和链表的优缺点

(1)顺序表的缺点:

        ①空间不够了,需要扩容,扩容是要付出代价的。

        ②避免频繁扩容,增容一般都是按倍数去扩(一般2倍适中),可能存在一定空间浪费(如:当前容量为100,满了以后增容到200,我们再继续插入5个数据,后面没有数据插入了,那么就浪费了95个数据空间。)

        ③头部或者中间位置的插入删除,需要挪动,挪到数据也是有消耗的。

(2)顺序表的优点:

        ①支持随机,有些算法需要结构支持随机访问,比如:二分查找,优化的快排等等。

(3)链表的优点:

        ①按需申请空间,不用了就释放空间(更合理的使用了空间)

        ②头部或者中间插入数据,不需要挪动数据

        ③不存在浪费空间

(4)链表的缺点:

        ①每一个结点,都要存一个后继结点的地址去链接后面的数据结点。

        ②不支持随机访问(就是不能使用下标直接访问第i个数据),必须得走0(N)

tip:单链表的缺陷还是有很多的,单纯单链表增删查改的意义不大但是①很多OJ题考察的都是单链表;②单链表更多用于更复杂数据结构的子结构、哈希桶、邻接表等。链表存储数据还要看双向链表,这个后面在学。

根据顺序表和链表的优缺点我们可以看出,链表与顺序表是互补的,相辅相成!

2、单链表(无头单向非循环链表)的实现

        2.1定义单链表结点

        代码演示:

//重定义链表结点中数据域的类型(优点:①见名知意;②一改全改)
typedef int SLTDataType;//定义链表结点
typedef struct SListNode
{SLTDataType data;//用来存放结点的数据struct SListNode* next;//用来存放后继结点的地址
}SLTNode;//重命名为SLTNode

        解读:

        ①typedef:类型重命名——作用:见名知意;一改全改;

        ②链表逻辑上是连续,物理上不一定连续,所以它是复杂类型——有两个变量组成,data变量存放结点的数据;next指针变量存放后继结点的地址(这也叫做结构自引用:①注意结构的自引用不能是结构体本身,因为C是自上向下编译的,所以当引用结构本身是结构不完整,报错。②正确的结构自引用是定义成结构体的指针,结构的指针不受结构的内容影响,它只是一个指针,指向你定义的一个结构,至于这个结构完不完整是什么,它都不需要知道。因此编译器能令其通过。)

        2.2单链表的打印模块

因为链表不支持随机访问,所以必须从第一个结点开始依次向后访问。(①我们只需知道头指针即可,所以参数只有一个;②又因为只是打印不用改变链表,所以只需值传递即可。)

        代码演示:

//单链表打印
void SLTPrint(SLTNode* phead)
{//assert(phead);//?——错,不用断言,链表为空时也能打印//定义一个临时指针变量指向链表的第一个结点SLTNode* cur = phead;//当链表结点到尾时打印结束:即cur为NULLwhile (cur){//打印链表结点的数据printf("%d->", cur->data);//得到后继结点的地址cur = cur->next;}printf("NULL\n");
}

       调试代码演示:

void SLTNodeText01()
{SLTNode* plist = NULL;SLTPrint(plist);
}int main()
{SLTNodeText01();return 0;
}

         解读:

        ①值传递:形参只是实参的一份临时拷贝,形参的改变不影响实参。

        ②注意:我们不要形成固定思维,看到参数是指针就断言,要根据实际情况判断。图示:

        ③cur = cur->next,为什么就能得到后继结点的地址——结构体指针访问结构体成员可以通过->操作符访问;结点成员next中存储的是后继结点的地址。图示:

        ④我们能不能通过cur++找到下一个结点呢——答案是:不能,因为链表在物理上是不一定连续的。

        2.3单链表的尾插模块

要尾插一个新节点:①我们先创建好一个新节点;②给新节点初始化;③与原链表链接——先找尾,找到后将新节点的地址拷贝给原来的尾结点的next即可。(注意特殊情况:链表为空时,是将新节点的地址拷贝给头指针)

        代码演示:

//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{//创建新节点SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//判断是否开辟成功if (NULL == newnode){perror("SLTPushBack::malloc");//打印错误信息exit(-1);}//给新节点初始化newnode->data = x;newnode->next = NULL;//判断是否为空链表if (NULL == *pphead){//为空链表,直接将新节点的地址拷贝给头指针即可*pphead = newnode;}else{//不为空链表,找到原尾结点,将新尾节点的地址拷贝给原尾结点的next/** 错误代码演示:SLTNode* tail = *pphead;//定义一个局部变量指针while (tail){tail = tail->next;}//将局部变量指针赋值为空指针tail = newnode;//再将局部变量指针指向新节点*/SLTNode* tail = *pphead;//定义一个局部变量指针while (tail->next != NULL){tail = tail->next;}//找到尾结点tail->next = newnode;//将新尾结点的地址拷贝给原尾结点的next}
}

        调试代码演示:

void SLTNodeText02()
{SLTNode* plist = NULL;//头指针指向链表的第一个结点//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//打印SLTPrint(plist);
}int main()
{SLTNodeText02();return 0;
}

        运行结果:

        解读:

        ①链表是按需申请空间的,所以malloc在堆区申请空间——使用malloc申请空间需注意:malloc申请的空间没有初始化,使用前要初始化;malloc创建失败会返回NULL,使用前要判断是否开辟成功(一般都能开辟成功);malloc申请空间传的大小单位是字节;图示:

        ②给新节点初始化——结构体指针访问成员使用操作符->;因为是尾插入的新节点,所以next为空。

        ③与原尾结点链接——非空链表:链接就是将原尾结点的next指向新节点;空链表:链接只需将头指针指向新节点即可(形参要影响实参,需要传实参的地址);

        ④因为形参的改变要影响实参,所以是传址调用——传实参的地址,在函数中可以通过解引用去改变实参,要改变什么类型的值,就传什么类型的指针(如实参是int,就传int*的指针)。

        2.4单链表的头插模块&创建新节点模块

新节点模块:因为我们每一次插入数据时,都要创建新节点,操作重复,所以我们可以将其封装为一个单独的模块。

        创建新节点代码演示:

//创建新节点
SLTNode* BuySLTNode(SLTDataType x)
{//创建新节点SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//判断是否开辟成功if (NULL == newnode){perror("SLTPushBack::malloc");//打印错误信息exit(-1);}//给新节点初始化newnode->data = x;newnode->next = NULL;//返回新节点的地址return newnode;
}

        有了该模块,以后我们需要创建新节点,直接调用即可。

头插模块:头插我们创建好新节点后,我们只需将其链接起来即可——新节点先指向原来的第一个结点,再头指针指向新节点。

        头插的代码演示:

//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{//创建新节点SLTNode* newnode = BuySLTNode(x);//创建链接//①新节点指向原第一个结点newnode->next = *pphead;//②头指针指向新节点*pphead = newnode;
}

        测试代码:

void SLTNodeText03()
{SLTNode* plist = NULL;//头指针指向链表的第一个结点//头插SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);//打印SLTPrint(plist);
}int main()
{SLTNodeText03();return 0;
}

        运行结果:

        解读:

        ①只要是要求形参的改变要影响实参,就需要址传递——传实参的地址,在函数中可以通过解引用去改变实参,要改变什么类型的值,就传什么类型的指针;

        ②链接(空链表和非空链表的方式一样),图示:

        2.5单链表的尾删

单链表的尾删有三种情况:

        情况一:空链表——处理方法①温柔的方式:使用if语句判断,如果为空则直接退出;②严格的方式:断言(为空则直接报错)。

        情况二:只有一个结点——①free释放结点(注:free释放完之后的指向动态内存开辟的空间的指针变量不会改变,仍能找到那块空间,有危险(可能非法访问),所以使用完free之后一定记得将其赋值为NULL。);②将头指针置为NULL。图示:

        情况三:多个结点——尾删掉最后一个,并且要将原来的倒数第二个节点next置为NULL。图示:

        常见性错误:只是释放了尾结点

如图:我们可知问题——可以找到尾结点,但是尾结点的前一位找不到,无法置为空。

        解决方案1:双指针——tail指针找原尾结点,prev指针存储原尾结点的前一个结点地址。

        解决方案2:找倒数第二个结点——当tail->next->next为NULL时即找到。

        尾删代码演示:

//单链表尾删
void SLTPopBack(SLTNode** pphead)
{/** 错误代码SLTNode* tail = *pphead;//找尾结点while (tail->next){tail = tail->next;}//释放尾结点free(tail);tail = NULL;*///二级指针不可能为空assert(pphead);//1、链表为空assert(*pphead);//2、只有一个结点if ((*pphead)->next == NULL){free(*pphead);//free只是将该申请的空间还给了操作系统,并没有改变其值,所以将其置空*pphead = NULL;}//3、多个结点else{双指针法://SLTNode* prev = NULL;//SLTNode* tail = *pphead;//while (tail->next)//{//	prev = tail;//	tail = tail->next;//}//free(tail);//tail = NULL;//prev->next = NULL;//找倒数第二个结点SLTNode* tail = *pphead;while (tail->next->next){tail = tail->next;}free(tail->next);tail->next = NULL;}
}

        测试代码:

void SLTNodeText03()
{SLTNode* plist = NULL;//头指针指向链表的第一个结点//头插SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);}int main()
{SLTNodeText03();return 0;
}

        运行结果:

        2.6单链表的头删

头删就比较简单了,只分为两种情况:

        情况1:空链表——直接断言即可(删除就像消费,所以和没钱就不能买都一样)

        情况2:非空链表——注意要先将头指针指向第二个结点的地址,再将释放头结点。

        头删代码演示:

//单链表头删
void SLTPopFront(SLTNode** pphead)
{//二级指针一定不为空assert(pphead);//断言是否为空链表assert(*pphead);//1、修改头指针:使之指向第二个结点SLTNode* first = *pphead;//保存头结点的地址*pphead = first->next;//2、释放头结点free(first);
}

        测试代码:

void SLTNodeText04()
{SLTNode* plist = NULL;//头指针指向链表的第一个结点//头插SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);/*SLTPopFront(&plist);SLTPrint(plist);*/}int main()
{SLTNodeText04();return 0;
}

        运行结果:

        2.7单链表的查找(修改)

查找:我们只需从头开始遍历链表即可,找到即返回结点的地址,找不到返回空。(查找同时也代表修改功能:我们找到了结点,就会返回结点的地址,通过结点的地址,我们就可以修改了)

        查找(修改)代码演示:

//单链表的查找(修改)
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;//遍历链表while (cur){//找到返回该节点地址,找不到向后遍历if (cur->data == x){return cur;}else{cur = cur->next;}}//找不到返回空return NULL;
}

        测试代码:

void SLTNodeText05()
{SLTNode* plist = NULL;//头指针指向链表的第一个结点//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 2);//打印SLTPrint(plist);//找到值2的结点将其修改为9SLTNode* ret = SLTFind(plist, 2);while (ret){ret->data = 9;ret = SLTFind(ret, 2);}SLTPrint(plist);
}int main()
{SLTNodeText05();return 0;
}

        运行结果:

        2.8单链表指定在pos位置之前插入VS在pos位置之后插入

pos位置之前插入:分两种情况:

        情况一:pos指向头结点——即头插

        情况二:pos不指向头结点——在pos前插入数据,就要找到pos的前一个结点位置(有效率损失)。

        pos之前插入代码演示:

//单链表指定pos位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pos);//pos不能为空(要我们在pos位置插入,那pos位置就得存在)assert(pphead);if (*pphead == pos){SLTPushFront(pphead, x);}//pos指向头结点,在前插入相当于头插else{SLTNode* prev = *pphead;//保存pos前一个结点的地址while (prev->next != pos){prev = prev->next;}//找到pos前一个结点SLTNode* newnode = BuySLTNode(x);//新节点与原链表链接prev->next = newnode;newnode->next = pos;}//pos不指向头结点
}

        测试代码:

void SLTNodeText06()
{SLTNode* plist = NULL;//头指针指向链表的第一个结点//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//打印SLTPrint(plist);//找到值2的结点在其前面插入6SLTNode* ret = SLTFind(plist, 2);SLTInsert(&plist, ret, 6);SLTPrint(plist);
}int main()
{SLTNodeText06();return 0;
}

        运行结果:

pos位置之后插入:pos指向头结点和不指向头结点操作都是一样的——创建好新节点之后,只是需要注意新节点先与pos的后继结点链接,再与pos位置结点链接。

        pos位置之后插入代码演示:

//单链表指定pos位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);//创建新节点SLTNode* newnode = BuySLTNode(x);//链接//1、新节点先与pos的后继结点链接newnode->next = pos->next;//2、新节点再与pos链接pos->next = newnode;
}

        测试代码:

void SLTNodeText07()
{SLTNode* plist = NULL;//头指针指向链表的第一个结点//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//打印SLTPrint(plist);//找到值2的结点在其之后插入6SLTNode* ret = SLTFind(plist, 2);SLTInsertAfter(ret, 6);SLTPrint(plist);
}int main()
{SLTNodeText07();return 0;
}

        运行结果:

        两种插入方式,那种更好呢?

        ①pos位置之前插入需要分两种情况;pos位置之后插入不用分情况,都一样。

        ②pos位置之前插入时间复杂度为O(N)——因为之前插入需要找pos的前趋结点位置才能将新节点与链表链接起来;pos位置之后插入时间复杂度为0(1)。

        终上,我们链表选择在pos位置之后插入更好。

        2.9单链表的pos位置删除VSpos位置之后删除

pos位置删除:分两种情况:

        情况一:pos指向链表的第一个结点——即头删

        情况二:pos不指向链表的头结点——①找到pos的前驱结点;②pos的前驱结点先与pos的后继结点链接好了,再释放pos位置结点(因为先释放pos结点就找不到pos的后继结点了)。

注意:该函数只是将pos位置释放了,并没有将pos指向改变,记得在主调函数置为空,防止非法访问。

        pos位置删除代码演示:

//单链表pos位置删除(注意该函数只将pos位置空间释放,并没有将其置为空)
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);//pos位置不能为空(pos位置不为空,链表也不为空)if (pos == *pphead){SLTPopFront(pphead);}//pos指向链表头结点else{SLTNode* prev = *pphead;//prev储存pos前一个结点位置while (prev->next != pos){prev = prev->next;}//找到pos的前一个结点//1、先将pos的前驱结点和后继结点链接prev->next = pos->next;//2、再将pos位置结点释放free(pos);}
}

        测试代码:

void SLTNodeText08()
{SLTNode* plist = NULL;//头指针指向链表的第一个结点//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//打印SLTPrint(plist);//找到值2的结点再将其删除SLTNode* ret = SLTFind(plist, 2);SLTErase(&plist, ret);ret = NULL;SLTPrint(plist);
}int main()
{SLTNodeText08();return 0;
}

        运行结果:

pos位置之后删除:pos指向头结点和不指向头结点操作都一样,图示:

        pos之后删除代码演示:

//单链表pos位置之后删除
void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* del = pos->next;//保存pos后继结点的位置pos->next = del->next;//链接free(del);//释放del = NULL;//free不会改变del的内容
}

        测试代码:

void SLTNodeText09()
{SLTNode* plist = NULL;//头指针指向链表的第一个结点//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//打印SLTPrint(plist);//找到值2的结点再将其后继结点删除SLTNode* ret = SLTFind(plist, 2);SLTEraseAfter(ret);SLTPrint(plist);
}int main()
{SLTNodeText09();return 0;
}

        运行结果:

        pos位置删除与pos位置之后删除,谁更好呢?

        答案是:pos位置之后删除更好,因为①pos位置之后不用分情况更简单;②pos位置之后删除不用找pos的前驱结点,效率更高。pos位置删除时间复杂度为O(N),之后删除时间复杂度为O(1).

        2.10单链表的销毁

注意单链表的销毁不是将头指针free就销毁了,是从头结点开始逐个销毁。

注意:

        ①free释放动态开辟的内存;

        ②free释放只是是将指针指向的那块动态内存还给操作系统,但是指针仍指向那块空间(为野指针),在使用指针就会造成非法访问,所以free之后记得一般将其置为空。

        销毁代码演示:

//单链表的销毁
void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;//从头结点逐个将其释放while (cur){//保存cur下一个结点的地址,因为free掉cur后内存还给操作系统了SLTNode* next = cur->next;free(cur);cur = next;}//销毁完之后将头结点置为空*pphead = NULL;
}

        测试代码:

void SLTNodeText10()
{SLTNode* plist = NULL;//头指针指向链表的第一个结点//尾插SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//打印SLTPrint(plist);//销毁链表SLTDestroy(&plist);SLTPrint(plist);
}int main()
{SLTNodeText10();return 0;
}

        运行结果:

3、总代码

        3.1单链表的声明模块:SList.h

//预处理:包含,后续常用的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//重定义链表结点中数据域的类型(优点:①见名知意;②一改全改)
typedef int SLTDataType;//定义链表结点
typedef struct SListNode
{SLTDataType data;//用来存放结点的数据struct SListNode* next;//用来存放后继结点的地址
}SLTNode;//重命名为SLTNode//单链表打印
void SLTPrint(SLTNode* phead);//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);//创建新节点
SLTNode* BuySLTNode(SLTDataType x);//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);//单链表尾删
void SLTPopBack(SLTNode** pphead);//单链表头删
void SLTPopFront(SLTNode** pphead);//单链表的查找(修改)
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//单链表指定pos位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//单链表指定pos位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//单链表pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos);//单链表pos位置之后删除
void SLTEraseAfter(SLTNode* pos);//单链表的销毁
void SLTDestroy(SLTNode** pphead);

        3.2单链表的实现模块:SList.c

#include"SList.h"//单链表打印
void SLTPrint(SLTNode* phead)
{//assert(phead);//?——错,不用断言,链表为空时也能打印//定义一个临时指针变量指向链表的第一个结点SLTNode* cur = phead;//当链表结点到尾时打印结束:即cur为NULLwhile (cur){//打印链表结点的数据printf("%d->", cur->data);//得到后继结点的地址cur = cur->next;}printf("NULL\n");
}//创建新节点
SLTNode* BuySLTNode(SLTDataType x)
{//创建新节点SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//判断是否开辟成功if (NULL == newnode){perror("SLTPushBack::malloc");//打印错误信息exit(-1);}//给新节点初始化newnode->data = x;newnode->next = NULL;//返回新节点的地址return newnode;
}//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{创建新节点//SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));判断是否开辟成功//if (NULL == newnode)//{//	perror("SLTPushBack::malloc");//打印错误信息//	exit(-1);//}给新节点初始化//newnode->data = x;//newnode->next = NULL;// //二级指针一定不为空assert(pphead);//创建新节点SLTNode* newnode = BuySLTNode(x);//判断是否为空链表if (NULL == *pphead){//为空链表,直接将新节点的地址拷贝给头指针即可*pphead = newnode;}else{//不为空链表,找到原尾结点,将新尾节点的地址拷贝给原尾结点的next/** 错误代码演示:SLTNode* tail = *pphead;//定义一个局部变量指针while (tail){tail = tail->next;}//将局部变量指针赋值为空指针tail = newnode;//再将局部变量指针指向新节点*/SLTNode* tail = *pphead;//定义一个局部变量指针while (tail->next != NULL){tail = tail->next;}//找到尾结点tail->next = newnode;//将新尾结点的地址拷贝给原尾结点的next}
}//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{//二级指针一定不为空assert(pphead);//创建新节点SLTNode* newnode = BuySLTNode(x);//创建链接//①新节点指向原第一个结点newnode->next = *pphead;//②头指针指向新节点*pphead = newnode;
}//单链表尾删
void SLTPopBack(SLTNode** pphead)
{/** 错误代码SLTNode* tail = *pphead;//找尾结点while (tail->next){tail = tail->next;}//释放尾结点free(tail);tail = NULL;*///二级指针不可能为空assert(pphead);//1、链表为空assert(*pphead);//2、只有一个结点if ((*pphead)->next == NULL){free(*pphead);//free只是将该申请的空间还给了操作系统,并没有改变其值,所以将其置空*pphead = NULL;}//3、多个结点else{双指针法://SLTNode* prev = NULL;//SLTNode* tail = *pphead;//while (tail->next)//{//	prev = tail;//	tail = tail->next;//}//free(tail);//tail = NULL;//prev->next = NULL;//找倒数第二个结点SLTNode* tail = *pphead;while (tail->next->next){tail = tail->next;}free(tail->next);tail->next = NULL;}
}//单链表头删
void SLTPopFront(SLTNode** pphead)
{//二级指针一定不为空assert(pphead);//断言是否为空链表assert(*pphead);//1、修改头指针:使之指向第二个结点SLTNode* first = *pphead;//保存头结点的地址*pphead = first->next;//2、释放头结点free(first);
}//单链表的查找(修改)
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;//遍历链表while (cur){//找到返回该节点地址,找不到向后遍历if (cur->data == x){return cur;}else{cur = cur->next;}}//找不到返回空return NULL;
}//单链表指定pos位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pos);//pos不能为空(要我们在pos位置插入,那pos位置就得存在)assert(pphead);if (*pphead == pos){SLTPushFront(pphead, x);}//pos指向头结点,在前插入相当于头插else{SLTNode* prev = *pphead;//保存pos前一个结点的地址while (prev->next != pos){prev = prev->next;}//找到pos前一个结点SLTNode* newnode = BuySLTNode(x);//新节点与原链表链接prev->next = newnode;newnode->next = pos;}//pos不指向头结点
}//单链表指定pos位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);//创建新节点SLTNode* newnode = BuySLTNode(x);//链接//1、新节点先与pos的后继结点链接newnode->next = pos->next;//2、新节点再与pos链接pos->next = newnode;
}//单链表pos位置删除(注意该函数只将pos位置空间释放,并没有将其置为空)
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);//pos位置不能为空(pos位置不为空,链表也不为空)if (pos == *pphead){SLTPopFront(pphead);}//pos指向链表头结点else{SLTNode* prev = *pphead;//prev储存pos前一个结点位置while (prev->next != pos){prev = prev->next;}//找到pos的前一个结点//1、先将pos的前驱结点和后继结点链接prev->next = pos->next;//2、再将pos位置结点释放free(pos);}
}//单链表pos位置之后删除
void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode* del = pos->next;//保存pos后继结点的位置pos->next = del->next;//链接free(del);//释放del = NULL;//free不会改变del的内容
}//单链表的销毁
void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;//从头结点逐个将其释放while (cur){//保存cur下一个结点的地址,因为free掉cur后内存还给操作系统了SLTNode* next = cur->next;free(cur);cur = next;}//销毁完之后将头结点置为空*pphead = NULL;
}

        今天我们就学完了无头单向非循环链表,下期更新带头双向循环链表,作者水平有限,希望大家多多支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/142068.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

idea没有maven工具栏解决方法

背景&#xff1a;接手的一些旧项目&#xff0c;有pom文件&#xff0c;但是用idea打开的时候&#xff0c;没有认为是maven文件&#xff0c;所以没有maven工具栏&#xff0c;不能进行重新加载pom文件中的依赖。 解决方法&#xff1a;选中pom.xml文件&#xff0c;右键 选择添加为…

Denoising diffusion implicit models 阅读笔记

Denoising diffusion probabilistic models (DDPMs)从马尔科夫链中采样生成样本&#xff0c;需要迭代多次&#xff0c;速度较慢。Denoising diffusion implicit models (DDIMs)的提出是为了加速采样过程&#xff0c;减少迭代的次数&#xff0c;并且要求DDIM可以复用DDPM训练的网…

计算机网络补充

未分类文档 CDMA是码分多路复用技术 和CMSA不是一个东西 UPD是只确保发送 但是接收端收到之后(使用检验和校验 除了检验的部分相加 对比检验和是否相等。如果不相同就丢弃。 复用和分用是发生在上层和下层的问题。通过比如时分多路复用 频分多路复用等。TCP IP 应用层的IO多路…

绿色计算产业发展白皮书:2022年OceanBase助力蚂蚁集团减排4392tCO2e

9 月 15 日&#xff0c;绿色计算产业联盟在 2023 世界计算大会期间重磅发布了《绿色计算产业发展白皮书&#xff08;2023 版&#xff09;》。蚂蚁集团作为指导单位之一&#xff0c;联合参与了该白皮书的撰写。 白皮书中指出&#xff0c;落实“双碳”战略&#xff0c;绿色计算已…

基于微信小程序的场地预约系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

亮相“外滩金融峰会” 百望云实力入选“融城杯金融科技创新十佳案例”

近日&#xff0c;第五届“外滩金融峰会”在上海召开&#xff0c;百望云受邀出席峰会&#xff0c;与全球财经政要、机构高管与学界领袖齐聚外滩&#xff0c;分享真知灼见&#xff0c;以对话推动共识。 本届峰会由中国金融四十人论坛&#xff08;CF40&#xff09;与中国国际经济交…

electron之快速上手

前一篇文章已经介绍了如何创建一个electron项目&#xff0c;没有看过的小伙伴可以去实操一下。 接下来给大家介绍一下electron项目的架构是什么样的。 electron之快速上手 electron项目一般有两个进程&#xff1a;主进程和渲染进程。 主进程&#xff1a;整个项目的唯一入口&…

HashMap 源码解读(JDK1.8)

一、HashMap说明 基于哈希表的 Map 接口实现。此实现提供所有可选的map操作&#xff0c;并允许空值和空键。&#xff08;HashMap 类大致等同于 Hashtable&#xff0c;只是它不支持同步并且允许空值。&#xff09;此类不保证插入键值的顺序&#xff1b;特别是&#xff0c;它不保…

Mendix中的依赖管理:npm和Maven的应用

序言 在传统java开发项目中&#xff0c;我们可以利用maven来管理jar包依赖&#xff0c;但在mendix项目开发Custom Java Action时&#xff0c;由于目录结构有一些差异&#xff0c;我们需要自行配置。同样的&#xff0c;在mendix项目开发Custom JavaScript Action时&#xff0c;…

48v转24v 3A 48v转12v 48v转5v电源芯片AH7691X

AH7691X是一款高-效-率、高-压降压型DC-DC转换器&#xff0c;采用固定110KHz的开关频率&#xff0c;具备3A的输出电流能力&#xff0c;低纹波&#xff0c;并且具备***软启动功能、过压保护功能和温度保护。该器件还集成了峰值限流功能&#xff0c;简化了电路设计。 AH7691X内部…

MFC 绘图

效果图&#xff1a;三张bmp图 字 竖线 组成 在OnPaint()函数中 CPaintDC dc(this);CRect rect;GetClientRect(&rect); //获取客户区矩形CDC dcBmp; //定义并创建一个内存设备环境dcBmp.CreateCompatibleDC(&dc); //创建兼容性DCCBitmap …

【kafka实战】01 3分钟在Linux上安装kafka

本节采用docker安装Kafka。采用的是bitnami的镜像。Bitnami是一个提供各种流行应用的Docker镜像和软件包的公司。采用docker的方式3分钟就可以把我们想安装的程序运行起来&#xff0c;不得不说真的很方便啊&#xff0c;好了&#xff0c;开搞。使用前提&#xff1a;Linux虚拟机&…

AKKA 互相调用

SpringBoot 集成 AKKA 可以参考此文&#xff1a;SpringBoot 集成 AKKA 场景1&#xff1a;bossActor 收到信息&#xff0c;然后发给 worker1Actor 和 worker2Actor controller 入口&#xff0c;初次调用 ActorRef.noSender() Tag(name "test") RestController Req…

【MATLAB第77期】基于MATLAB代理模型算法的降维/特征排序/数据处理回归/分类问题MATLAB代码实现【更新中】

【MATLAB第77期】基于MATLAB代理模型算法的降维/特征排序/数据处理回归/分类问题MATLAB代码实现 本文介绍基于libsvm代理模型算法的特征排序方法合集&#xff0c;包括&#xff1a; 1.基于每个特征预测精度进行排序&#xff08;libsvm代理模型&#xff09; 2.基于相关系数corr的…

前端技术社区总目录

前端技术社区欢迎您的订阅。订阅后&#xff0c;您将可以查看以下所有博客内容。 注&#xff1a;专栏内容主要面向新手 注&#xff1a;每个示例都有相对应的完整代码 注&#xff1a;该专栏博客内容将会逐步迁移至https://blog.csdn.net/m0_60387551/article/details/128017725 …

用于自然语言处理的 Python:理解文本数据

一、说明 Python是一种功能强大的编程语言&#xff0c;在自然语言处理&#xff08;NLP&#xff09;领域获得了极大的普及。凭借其丰富的库集&#xff0c;Python 为处理和分析文本数据提供了一个全面的生态系统。在本文中&#xff0c;我们将介绍 Python for NLP 的一些基础知识&…

Sound/播放提示音, Haptics/触觉反馈, LocalNotification/本地通知 的使用

1. Sound 播放提示音 1.1 音频文件: tada.mp3&#xff0c; badum.mp3 1.2 文件位置截图: 1.3 实现 import AVKit/// 音频管理器 class SoundManager{// 单例对象 Singletonstatic let instance SoundManager()// 音频播放var player: AVAudioPlayer?enum SoundOption: Stri…

【C++】stack queue

stack & queue 一、容器适配器二、deque&#xff08;了解&#xff09;三、stack1. stack 的介绍2. 模拟实现 stack 四、queue1. queue 的使用2. 模拟实现 queue3. priority_queue&#xff08;1&#xff09;priority_queue 的介绍&#xff08;2&#xff09;priority_queue 的…

从零开始—【Mac系统】MacOS配置Java环境变量

系统环境说明 Apple M1 macOS Ventura 版本13.5.2 1.下载JDK安装包 Oracle官网下载地址 JDK下载【注&#xff1a;推荐下载JDK8 Oracle官网JDK8下载】 关于JDK、JRE、JVM的关系说明 JDK(Java Development Kit&#xff0c;Java开发工具包) &#xff0c;是整个JAVA的核心&#…

系统学习Mysql

1.select语句 关键字执行顺序&#xff1a; 1.from 2.where 3.group by 4.select 5.having 6.order by 7.limit SQL 语句执行顺序如下&#xff1a; FROM: 指定要查询的表或子查询&#xff0c;可以包含 JOIN、WHERE 子句过滤等。 WHERE: 对 FROM 子句指定的表或子查询进行限制和…