单链表复习 (C语言版)

目录

一.顺序表与链表的区别

二.链表概念

三.单链表

1.单链表的开始与初始化

2.单链表的打印

3.单链表的尾插

重难点:单链表实现时的指针详解

4.单链表的头插

5.单链表的尾删

6.单链表的头删

小结:

7.单链表的查找

8.在指定位置前插入数据

9.在指定位置后插入数据

10.删除pos结点

11.删除pos结点之后的结点

12.删除链表

13.可能存在的疑惑解答

 14.全部用于测试的代码

四.文章链接


 

一.顺序表与链表的区别

顺序表的问题及思考:

  1. 中间/头部插入删除,涉及到移动数据,时间复杂度为o(n)
  2. 增容需要申请新空间,拷贝数据,释放旧空间,会有损耗。(realloc)
  3. 增容一般呈2倍的增长,当前容量为100,增加到200,只存放5字节,浪费了95字节
  4. 顺序表中间头部插入效率低下,增容造成运行效率降低
  5. 链表解决了上述问题

顺序表相关文章链接:顺序表复习(C语言版)

f6f0317216824cfe8941ab0aff0724b3.jpg

 上图就形象地表示了线性表与链表的区别,链表存储位置不连续,是用指针相连;线性表(SeqList)在内存中是连续存放的

 

二.链表概念

线性表是一类相同元素的集合,例如苹果和香蕉(都是水果)

逻辑结构:一定线性

物理结构:不一定线性(因为在内存中的存放位置是不连续的)

int a =10;float f =0.1 变量a和f的物理空间不一定连续,即存放位置不一定连续

0c1f39d1dc964dc4b3c867789eca40af.jpg

链表和火车很相似

通过一个钩子连在一起,地址空间是链接在一起的,车头也是车厢,因为它也可以装人

旺季:增加车厢

淡季:减少车厢

链表是由一个一个节点组成,结点可以看成车厢

c048c7bf10a9408a860546cd6ab64c9a.jpg

 plist位置称之为头结点,头结点指向的结点叫做首元结点,最后一个指针域指向NULL的结点叫做尾元结点

结点和节点:同一个东西,随便用哪个

结点由什么组成的呢?

有两个组成部分

1.数据域  --->在该域内存储了数据

2.指针域  --->存储了指向下一个结点的指针

三.单链表

1.单链表的开始与初始化

链表就是在定义链表的结点结构

struct SListNode single list node{int data;                          //int a =10;//int* pa =&astruct SListNode* next;            //指向下一个节点的指针}SLTNode;typedef int SLTTDataType;

上述代码就是在对链表进行初始化,定义了一个结构体,并在里面定义了一个data还有一个指针next(next指向下一个结点)

void SListTest01(){//创建节点SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));node1->data = 1;SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));node2->data = 2;SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));node3->data = 3;SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));node4->data = 4;//将四个节点连接起来node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;//调用链表的打印SLTNode* plist = node1;void SLTPrint(plist); //为了让代码逻辑更加清晰}

上述代码依旧是对链表的初始化,将四个结点进行创建,然后相连接;在初始化代码写完以后,直接跟了一个链表打印函数

2.单链表的打印

920ec114d1554024aa1a8dbc538c0809.jpg

紧接上文代码,上文中 SLTNode* plist = node1就是在说明,plist是个指针,且与node1一样都是一级指针;因此它指向node1所在的这块空间,这就可以看成让node1和plist两个指针指向同一块空间,且这块空间在早些时候已经通过node1指针初始化完毕了;因此如果创建一个pcur,让他等于plist,那么pcur依旧指向node1所指向的空间,这就可以看出三个指针指向同一块空间

7fd278659e714d43aed2a9cc3fdf06e8.jpg

先是打印pcur所指向空间的data,然后让pcur里指向下一个结点的指针覆盖pcur这个结点,pcur就自然而然向后移动了

e99983d4b84f497582bb0098d17178bf.jpg

重复上述操作,先打印所指向空间的data值,然后再让其指向的下一个结点地址将现在这个结点地址覆盖

084955dd4e1d4191829f3f40f46e67b7.jpg

由上图可知,当pcur指向的空间为NULL时,打印操作完成;由上四图,不难得出下述代码:

void SLTPrint(SLTNode* phead) //传的是首元结点{SLTNode* pcur = phead;while (pcur)  //pcur != NULL{printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");}

 

3.单链表的尾插

因为在此以后我们需要多次进行单链表的增删查改操作,因此我们可以专门写一个函数,这个函数是专门用来创建新结点的,在顺序表那篇文章中已经讲解过,故在此省略

	SLTNode* SLTBuyNode(SLTDataType x){SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode*));if (newnode == NULL){perror("malloc fail!");exit(-1);}newnode->data = x;newnode->next = NULL; //此处指向空指针是为了方便后续操作}

接下来就要开始进行尾插操作

89f24ff48fd94fe1a9a10b62150ffa27.jpg

77fc5ec222b34612b9b24215ac2a42b7.jpg

	void SLTPushBack(SLTNode** pphead, SLTDataType x){SLTNode* newnode = SLTBuyNode(x);//空链表的情况if (*pphead == NULL){*pphead = newnode; //newnode的data有了,next指向空指针,将pphead地址覆盖,指向新的newnode空间,完成尾插}//非空链表的情况else{//找尾SLTNode* ptail = *pphead; //不想让*pphead变动位置(变动了就需要使用二级指针了),因此又定义了一个变量ptail指针while (ptail->next){ptail = ptail->next;}//退出循环就说明已经找到尾了,此时ptail指向空指针ptail->next = newnode; //让ptail指向新结点}
}

上述代码指针部分较难理解,因此笔者将在下文进行详细解释

重难点:单链表实现时的指针详解

上述代码中,为什么使用二级指针?
在函数中,想要通过形参改变实参的值(即对其进行某些操作,且这些操作是针对该参数而言的,而不是该参数针对别的变量而言的),就必须在传参时传地址。

即使实际参数是一个指针,我们也需要通过指针的地址来改变该指针本身。(如下图所示)

所以,我们在函数传参时,传的是指针的地址,因此需要函数用二级指针来接收。

但请注意,只有二级指针类型的形式参数,在进行了一次解引用操作状态下的改变,才需要用到二级指针类型的形式参数。

此处我们可以类比一级指针和整型变量。要通过形式参数改变实际参数,那么需要形式参数是一个指向需改变变量的指针,然后才可以通过形式参数改变该变量;而想要改变一级指针变量,那么就需要通过二级变量来改变。(前者是结点里的指针域、数据域的内容的改变,后者是结点本身的改变)

所以,如果需要改变结点,就用二级指针;如果只需要改变某个结点指针域、数据域内容,那就传一级指针,此时就没有必要传二级指针了。

上述代码中,为什么有些地方需要解引用,而有些地方不需要呢?

解引用(即*操作符)可以看作是把指针降级(文章链接部分有指针其他内容复习),把二级指针变成一级指针,把一级指针变成指针指向的内容;所以上述代码中,部分地方使用了一次解引用操作,即从指向结点的指针地址变为了指向结点的指针(也可以看成是结点本身)。

为什么单链表的打印不需要二级指针,而单链表的尾插就需要呢?

因为单链表的打印并没有改变链表的首元结点本身,只是完成了打印操作,所以不需要通过形式参数来改变实际参数;而尾插操作需要改变链表的首元结点本身(从NULL变成了newnode),因此需要通过形参来改变实参。

SLTNode* 和 * 的区别是?

SLTNode* 是在告诉编译器,这个变量是个指针,指向了SLTNode这个类型的数据,而并不是在对变量进行解引用操作;*是解引用操作符,在上文已经讲解完毕;而在函数当中,对二级指针进行解引用,其本质上还是个二级指针,因为形式参数依然还是二级指针。

045a54bf69824b03a12d409368acadfe.jpg

 实参形参(前面的*是指函数中使用的解引用操作符个数)
第一个结点的内容*plist(即xxx)

ptail -> xxx

(即一般情况下的**pphead,此处只是因为是结构体指针,所以需要用结构体指针的解引用方式)

指向第一个结点的指针plist*pphead
指向第一个结点的指针的地址&plistpphead
	SLTNode* plist = NULL; //代码1SLTNode* plist1 = node1; //代码2SLTPushBack(&plist1, 1); //代码3SLTPrint(plist1); //代码4plist1->next = NULL; //代码5

代码1:结点为空

代码2:一个有效结点

代码3:传输有效结点的地址

代码4:传输有效结点

代码5:对有效结点解引用,结构体里套了一个指向结构体类型(即为其本身)的指针变量,让该变量指向空,这一操作即是让该结点的下一个结点为空

4.单链表的头插

3abd1fb12b9347c194e420fdf3028572.jpg 4f25ddd568754c5a8f5324e4e3ae0bf0.jpg

	void SLTPushFront(SLTNode** pphead, SLTDataType x){assert(pphead);SLTNode* newnode = SLTBuyNode(x); newnode->next = *pphead; //newnode指向首元结点*pphead = newnode; //将头指针指向新创建的结点}

 

5.单链表的尾删

88de588083eb4ca795eb5a760e86e716.jpg

	void SLTPopBack(SLTNode** pphead) {assert(pphead && *pphead) //指向链表结点的指针不能为空,链表也不能为空SLTNode* prev = *pphead; //为防止野指针,要让尾元结点的next指针指向空,因此需要再创建一个指针,来完成这一操作SLTNode* ptail = *pphead;while (ptail->next){prev = ptail; //prev需要指向尾元结点的前一个结点,因此需要跟着ptail变动ptail = ptail->next; //ptail指向尾元结点}free(ptail); //释放ptail所指向的空间,就能达到尾删的目的ptail = NULL; //动态内存开辟内容,下文有链接prev->next = NULL; //要让prev里的next指针从指向野指针到指向空指针}

 

4b7639d7a4f14bb59bc72213fb92393f.jpg

当只有一个结点的时候,循环直接跳过,ptail和prev指向同一个结点,在将ptail空间释放并变成空指针以后,又一次对prev(即ptail的同一结点)进行了解引用操作,这样代码会报错(对空指针解引用)

因此当链表只有一个节点时,代码如下所示:

	//链表只有一个结点if ((*pphead)->next = NULL) //加括号是因为 -> 优先级高于 *{free(*pphead);*pphead = NULL;}

尾删的全部代码:

	void SLTPopBack(SLTNode** pphead) {assert(pphead && *pphead) //链表不能为空,指向链表结点的指针也不能为空//链表只有一个结点if ((*pphead)->next = NULL) //加括号是因为 -> 优先级高于 *{free(*pphead);*pphead = NULL;}else{SLTNode* prev = *pphead; //为防止野指针,要让尾元结点的next指针指向空,因此需要再创建一个指针,来完成这一操作SLTNode* ptail = *pphead;while (ptail->next){prev = ptail; //prev需要指向尾元结点的前一个结点,因此需要跟着ptail变动ptail = ptail->next; //ptail指向尾元结点}free(ptail); //释放ptail所指向的空间,就能达到尾删的目的ptail = NULL; //动态内存开辟内容,下文有链接prev->next = NULL; //要让prev里的next指针从指向野指针到指向空指针}}

 

6.单链表的头删

dde786103c6c45bcad2d18e0b3569991.jpg cc8648526b6647a383ed9598cf4afc93.jpg

先要保存好头删前的链表第二个节点,头删以后把*pphead指针指向新的节点

void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}
//只有一个结点的情况上述代码也能够解决

小结:

上述的插入、删除代码中,因为四种代码所以必须使用二级指针:
1.free(*pphead);

2.*pphead = NULL;

3.*pphead = newnode;

4.*pphead = next;

即对一级指针类型的参数本身进行了某些操作

7.单链表的查找

在使用查找函数以后,要分为找到了和没找到两种情况,那么我们可以通过如下代码来区分(此处的3是指数据域为3的结点):

SLTNode* find = SLTFind(plist, 3);
if (find == NULL)
{printf("没有找到");
}
else
{printf("找到了");
}

上述代码中,plist即为首元结点的指针

不需要传输首元结点指针的地址,这是因为并不需要通过查找函数来改变链表的首元结点。 

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead; //不想形式参数是个二级指针,因此可以在函数中定义一个指针变量while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}

 

8.在指定位置前插入数据

该函数必须要有三个参数,分别是链表的首元结点(SLTNode** pphead)、在链表的哪个结点前插入(SLTNode* pos)、所需要插入的数据(x)。如下所示:

void SLTInsert(SLTNode** pphead,SLTNode* pos,SLTDataType x)

一个参数使用二级指针,一个参数使用一级指针的原因:

前一个二级指针类型是链表的首元结点地址,是需要通过形式参数来改变首元结点的;下一个一级指针类型是链表指定位置的结点,可以直接通过解引用,对结点的指针域、数据域进行操作。

6fac2faf81f9429c8c9982e47ca39e66.jpg 579a96948e964c49be9432242decbd8f.jpg

 

如果要在第3个结点前插入数据,就需要先创建一个结点,然后把这个结点和第3个结点相连接,然后断开第2个结点和第3个结点的连接,让第2个结点和第3个结点相连。而第2个结点就需要通过遍历获得,循环语句的退出条件是:prev->next != pos (prev为函数中定义的指针变量,遍历以前,prev == *pphead)

aa755b4a649c4be7ad1be126a1474017.jpg

如果是在链表的首元结点之前插入数据,那么prev的next就不可能会等于pos,因此我们在代码实现里要考虑到一般情况和在首元结点之前插入数据两张情况。并且在首元结点前插入数据可以通过头插的函数来实现(这种情况也是函数需要使用二级指针的原因,因为函数需要传一个二级指针)。

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(*pphead && pphead); //链表不能为空,因为为空了,就无法确定“某一位置”了assert(pos);SLTNode* newnode = SLTBuyNode(x);if (pos == *pphead) //在首元结点之前插入{SLTPushFront(pphead, x);}else //一般情况{//找到pos结点的前一个结点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next; //没有找到就找下一个}newnode->next = pos; //完成上文的操作prev->next = newnode;}
}//pos指针即为上文查找部分出现的find

 

9.在指定位置后插入数据

在指定位置后插入数据,不需要再创建一个指针,直接通过"指定位置"结点的next以及该结点next的next来插入即可

91182df3ec8647e4b229c4a88a65f201.jpg

第一种:先红色,再绿色

第二种:先绿色,再红色

以上两种方式是否相同?

//第一种方式的代码
newnode->next = pos->next;
pos->next = newnode;

//第二种方式的代码
pos->next = newnode;
newnode->next = pos->next;

9d37c24988ea4d60811acab0693d4e2d.jpg

第一种方式可以完成我们需要的操作,即先让新节点指向链表的指定节点的下一个结点(next),然后再让指定节点指向新结点

第二种方式让指定节点指向新节点,此时指定结点的下一个结点即为新节点,因此让新节点指向指定的下一个结点时,即为新节点本身,无法完成需要的操作(如上图所示)

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//pos指针即为上文查找部分出现的find

 

10.删除pos结点

删除pos结点需要我们将pos结点的前一个结点和pos的后一个结点相接,因此还需要有个prev指针去保存pos结点的前一个结点,如下图所示 

首元结点、尾元结点的删除请读者自行判断

 ea52e351378b48f48101055f5421b69a.jpg

void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);//pos是首元结点if (pos == *pphead){//头删SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;}
}//pos指针即为上文查找部分出现的find
//在使用完find指针以后,需要在函数外进行free、置空操作

 

11.删除pos结点之后的结点

删除pos->next,让pos结点和pos->next->next相连即能完成操作,如下图所示: 

ad6dcc92da534b8699185df2fbbbfc13.jpg

void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next; //保存pos的下一个结点pos->next = del->next;  //让pos的下一个结点(即del)和pos的下一个结点(即del)的下一个节点相链接free(del);del = NULL;
}

 

12.删除链表

d1a63226464048a1821ef0bdcdb021d5.jpg

 ed7295ad506d4d47a02a840a64af2344.jpg

传入首元结点,然后保存好首元结点的下一个结点(next指针),然后free掉pcur以后,让next指针和pcur指针都往后走一个结点,循环退出条件为pcur为空指针。(如上图所示)

void SListDesTroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next; free(pcur);pcur = next;}*pphead = NULL; //已经释放完所有的内容了,将pphead置为空
}

 

13.可能存在的疑惑解答

**pphead作为形参出现那么多次,那么他到底对应哪个结点呢?

他既可以对应空指针(链表完全没有结点,对应了插入等的情况),同时也可以对应首元结点(头删和尾删等的情况)

为什么在部分函数里有时候free不需要二级指针?

例如free(ptail),在尾删板块出现。这是因为ptail是在函数里创建、使用的,无需通过函数来改变任何一个实参(它也没有对应的实参、因为它并不是一个形式参数)。在出了函数以后,ptail指针就没有作用了,如果在函数内没有进行free、置空操作,就会变成一个野指针。 

单链表的开始和初始化必须要像1那样写吗?
并不是,其实完全可以通过尾插、头插等等方式进行链表的初始化的。

所有函数都必须要传二级指针吗?

有些一定需要传,例如*pphead=newnode(在头插板块),因为这条代码如果不写在函数里,就完成不了头插操作;在某些情况下不一定需要传,例如free(*pphead),放到函数实现外的部分,可以通过free(plist)完成操作,不写在函数里也不会影响函数的功能实现。

如果打印函数里使用形参进行打印,不创建变量了,是否可以?
其实是可以的,因为形参是一级指针,不会影响实参。

在函数中,定义一个变量pcur = *pphead了以后,为什么影响了pcur就能影响整个链表了?

在函数当中,实际上pcur能直接看成pphead解引用了一次,即可以把所有的pcur看成是(*pphead),这就像是定义宏一样;这就像是传入了一个一级指针  int *a,然后在函数了假设变量int b = *a ,然后通过b的改变来改变传入进来的*a一样。而我们会在函数里使用到pcur = *pphead有两个原因,其一就是为了代码的可读性,其二就是为了让pphead指针一直指向首元结点。

 14.全部用于测试的代码

请注意,以下代码有些是不兼容的,就比如plist指针一会是NULL,一会是首元结点;同时有些功能,例如单链表的查找,不能链表是空的,需要有前提条件。因此请读者对函数进行一一尝试,需尝试的功能直接取消注释即可。

int main()
{//SListTest01(); //对于开始和初始化//SLTNode* plist = NULL; //从没有链表开始对于尾插//SLTPushBack(&plist, 1);//SLTPrintf(plist); //此处plist又变成了首元结点,在使用完该操作以后,plist由于//SLTPushBack(&plist, 2);//SLTPrintf(plist);//SLTPushBack(&plist, 3);//SLTPrintf(plist);//SLTPushBack(&plist, 4);//SLTPrintf(plist);对于头插(尾插、头插写一块了)//SLTPushFront(&plist, 6);//SLTPrintf(plist);//SLTPushFront(&plist, 2);//SLTPrintf(plist);//SLTPushFront(&plist, 3);//SLTPrintf(plist);//SLTPushFront(&plist, 4);//SLTPrintf(plist);尾删、头删(先决条件:不为空)//SLTPopBack(&plist);//SLTPrint(plist);//SLTFrontBack(&plist);//SLTPrint(plist);查找(先决条件:不为空)//SLTNode* find = SLTFind(plist, 3);//if (find == NULL)//{//	printf("没有找到");//}//else//{//	printf("找到了");//}指定位置的插入删除(先决条件:不为空+有具体指定位置)//SLTNode* find = SLTFind(plist, 3); //find即为具体指定位置//SLTInsert(&plist, find, 10);//SLTPrint(plist);//SLTInsertAfter(find, 10);//SLTPrint(plist);//SLTErase(&plist, find);//SLTPrintf(plist);//SLTEraseAfter(find);//SLTPrintf(plist);//销毁//SListDesTroy(&plist);//SLTPrintf(plist);
}
//以上代码是在vscode2022中进行的

 

四.文章链接

指针讲解:指针复习

动态内存开辟讲解:动态内存开辟复习

 

 

 

 

 

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

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

相关文章

王学岗鸿蒙开发(北向)——————(四、五、六)ArkUi声明式组件

普通组件 1,注意,如上图,build只能有一个根节点 2,Entry表示程序的入口 Component表示自定义的组件 Preview表示可以预览 3,图片存放的地方 4, Image组件最好只给宽度,给了高度又给宽度容易失真。 build() {Row() {/…

24考研408大变化,25考研高分上岸规划+应对策略

巧了,我有现成的经验: 数学和专业课的成绩都不高不低,刚好够用,其实408想上岸,不仅仅要学好408,还要学好考研数学,这是我的肺腑之言,我复试的时候,我知道的那些没有进复试…

【SQLAlChemy】常见的数据类型有哪些,Column可选的参数有哪些呢?

常见数据类型与Column参数 常见类型 Integer:整数类型,对应数据库的 int 类型。Float:浮点数类型,对应数据库的 float 类型。它占用 32 位空间。Double:双精度浮点数类型,对应数据库的 double 类型&#…

鸿蒙开发接口安全:【@system.cipher (加密算法)】

加密算法 说明: 本模块首批接口从API version 3开始支持。后续版本的新增接口,采用上角标单独标记接口的起始版本。 导入模块 import cipher from system.ciphercipher.rsa rsa(Object): void RSA 算法加解密。 系统能力: SystemCapabil…

ChatTTS 文字生成语言本地模型部署

ChatTTS部署 官方信息 [ChatTTS首页](https://chattts.com/)搭建步骤 1、下载源码 git clone https://github.com/2noise/ChatTTS.git 2、按照环境 pip install torch ChatTTS pip install -r requirements.txt 3、下载模型 git clone https://www.modelscope.cn/pzc163/ch…

深入解读Prometheus Adapter:云原生监控的核心组件

一、引言 Prometheus Adapter的背景与重要性 在现代的云原生架构中,微服务和容器化技术得到了广泛的应用。这些技术带来了系统灵活性和扩展性的提升,但同时也增加了系统监控和管理的复杂度。Prometheus作为一款开源的监控系统,因其强大的指标…

vuInhub靶场实战系列--prime:2

免责声明 本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关。 目录 免责声明前言一、环境配置1.1 靶场信息1.2 靶场配置 二、信息收集2.1 主机发现2.1.1 netdiscover2.1.2 nmap主机扫描2.1.3 arp-scan主机扫描 2.2 端口扫描…

【面试干货】MySQL 三种锁的级别(表级锁、行级锁和页面锁)

【面试干货】MySQL 三种锁的级别(表级锁、行级锁和页面锁) 1、表级锁2、行级锁3、页面锁4、总结 💖The Begin💖点点关注,收藏不迷路💖 在 MySQL 数据库中,锁是控制并发访问的重要机制&#xff0…

队列和栈的实现

本节讲解的队列与栈,如果你对之前的线性和链式结构顺利掌握了,那么下边的队列和栈就小菜一碟了。因为我们会用前两节讲到的东西来实现队列和栈。 之所以放到一起讲是因为这两个东西很类似,队列是先进先出结构(FIFO, first in first out)&…

独立游戏之路 -- 获取OAID提升广告收益

Unity 之 获取手机:OAID、IMEI、ClientId、GUID 前言一、Oaid 介绍1.1 Oaid 说明1.2 移动安全联盟(MSA) 二、站在巨人的肩膀上2.1 本文实现参考2.2 本文实现效果2.3 本文相关插件 三、Unity 中获取Oaid3.1 查看实现源码3.2 工程配置3.3 代码实现3.4 场景搭建 四、总…

Linux卸载残留MySQL【带图文命令巨详细】

Linux卸载残留MySQL 1、检查残留mysql2、检查并删除残留mysql依赖3、检查是否自带mariadb库 1、检查残留mysql 如果残留mysql组件,使用命令 rpm -e --nodeps 残留组件名 按顺序进行移除操作 #检查系统是否残留过mysql rpm -qa | grep mysql2、检查并删除残留mysql…

香橙派 Orange AIpro 测评记录视频硬件解码

香橙派 Orange AIpro 测评记录视频硬件解码 香橙派官网:http://www.orangepi.cn/ 收到了一块Orange Pi AIpro开发板,记录一下我的测评~测评简介如下:1.连接网络2.安装流媒体进行硬件解码测试3.安装IO测试 简介 Orange Pi AI Pro 是香橙派联合…

Web安全:Web体系架构存在的安全问题和解决方案

「作者简介」:2022年北京冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础对安全知识体系进行总结与归纳,著作适用于快速入门的 《网络安全自学教程》,内容涵盖系统安全、信息收集等12个知识域的一百多个知识点,持续更新。 这一章节我们需要了解w…

Git【版本控制和Git的安装介绍】

01 【版本控制和Git的安装介绍】 工程设计领域中,使用“版本控制”管理工程蓝图的设计过程。在 IT 开发中也可以使用版本控制思想管理代码的版本迭代。 1.目的 协同修改:支持在服务器对同一个文件多人协同地修改; 数据备份:同时…

力扣hot100:138. 随机链表的复制(技巧,数据结构)

LeetCode:138. 随机链表的复制 这是一个经典的数据结构题,当做数据结构来学习。 1、哈希映射 需要注意的是,指针也能够当做unordered_map的键值,指针实际上是一个地址值,在unordered_map中,使用指针的实…

M1Pro 使用跳板机

Mac (M1 Pro) 通过Iterm2 使用跳板机 1、由于堡垒机(跳板机)不能支持mac系统终端工具,只支持xshell等win生态。所以我们需要先安装iterm2 装iterms教程 这里头对rz、sz的配置不详细。我们可以这样配置: where iterm2-send-zmod…

Stable diffusion采样器详解

在我们使用SD web UI的过程中,有很多采样器可以选择,那么什么是采样器?它们是如何工作的?它们之间有什么区别?你应该使用哪一个?这篇文章将会给你想要的答案。 什么是采样? Stable Diffusion模…

Spring Boot通过自定义注解和Redis+Lua脚本实现接口限流

😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…

社区物资交易互助平台的设计

管理员账户功能包括:系统首页,个人中心,管理员管理,基础数据管理,论坛管理,公告信息管理 前台账户功能包括:系统首页,个人中心,论坛,求助留言板,公…

Go微服务: 分布式之通过本地消息实现最终一致性和最大努力通知方案

通过本地消息实现最终一致性 1 )概述 我们的业务场景是可以允许我们一段时间有不一致的消息的状态的,并没有说必须特别高的这个消息的一致性比如说在TCC这个架构中,如果采用了消息的最终一致性,整体架构设计要轻松好多即便我们库…