数据结构链表(C语言实现)
绪论
机遇对于有准备的头脑有特别的亲和力。本章将讲写到链表其中主要将写到单链表和带头双向循环链表的如何实现。
话不多说安全带系好,发车啦(建议电脑观看)。
附:红色,部分为重点部分;蓝颜色为需要记忆的部分(不是死记硬背哈,多敲);黑色加粗或者其余颜色为次重点;黑色为描述需要
目录
链表
1.单链表
1.1.单链表的结构
1.2放入数据到单链表中
1.3 删除单链表中的数据
1.4 摧毁单链表
1.5单链表的打印
2.带头双向循环列表
2.1带头双向链表的结构
2.2双向链表的初始化
2.3双向链表中放入数据
2.4双向链表中的删除数据
2.5双向链表的摧毁
2.6打印双向链表中的各个数据
3.如何快速的实现一个链表
链表
知识点:
链表是一个在逻辑上连续的线性结构,但在物理结构上他的地址并不是连续的,是通过指针的形式链接而成的一个非顺序的存储结构。
细节:
链表的属性:
单向/双向、循环/不循环、带头/不带头
对此就能有多种排列组合,但我们主要学的是:单链表(单向、不带头、不循环)、带头双向循环链表(因为这两个链表最具代表性且常用,当我们学会这两种链表后其他的链表基本也不在话下)
对于上面的属性其实也很好理解下面通过一张图片具体展示:
其中节点表示的就是每个数据的每个小空间(图中的矩形)
1.单链表
单链表是我们在链表中最简单的一种链表形式,因为过于简单所以其一般并不会用于存储数据,但却经常出现于习题以及笔试面试题中,并且还会作为一些数据结构的子结构(如:图的领接表、哈希桶中)。
单链表的基本框架:
1. 单链表的结构
1.一个数剧date(存储当前位置的数据)
2.一个指针next(指向下一个数据)
2. 所要实现的功能
1.将数据放入结构中
2.查找单链表中的某个的数据
3.将数据从结构中删除(删除节点,前提是要先找到该节点所在的位置)
4.将数据展现打印出来
5.摧毁单链表
1.1.单链表的结构
单链表的是由数据、指针组成:
typedef struct SListNode
{SLDateType data;//SLDateType typedef定义类型和顺序表一样都是为了更加方便去改变结构中的存的类型struct SListNode* next;//定义一个结构体类型的指针,因为到时候next指向一个结构体类型
}SListNode;//用typedef将struct SListNode改变成SListNode这样更方便于我们后面使用(可以不用加上struct,直接用代替的就好)//typedef int SLDateType; int重命名为SLDateType
1.2放入数据到单链表中
在插入数据之前需要先开辟好空间(也就是创建节点):
SListNode* BuySListNode(SLDateType x) {//接收传进来的数据SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));//用malloc开辟一个结构体大小的空间给新节点if (newnode == NULL)//熟系的判断是否申请成功{perror("malloc failed");return NULL;}newnode->data = x;//在申请的结构体空间中放入数据newnode->next = NULL;//将其下一个位置暂时指向NULLreturn newnode;//返回这个节点,返回类型是个指针类型也就是newnode的地址
}
申请好空间后就可以进行插入数据(并且链接了)
- 尾插:从尾部插入,分析尾部的特征是其下一个指向的是NULL对此就能找到最后一个数据的位置,将其指向的NULL改成新节点的位置即可。
动图展示具体步骤:void SListPushBack(SListNode** pphead, SLDateType x) {//注意此处用二级指针是因为可能要修改结构体变量(或理解成传进来的是一级指针类型为了修改他就需要用二级指针)assert(pphead);//判空,注意此处的判空是判的pphead 而不是 STL(*pphead) SListNode* newnode = BuySListNode(x);//先创建一个新节点,调用创建节点的函数SListNode* tail = *pphead;//一个指针指向链表开始(可能为NULL)if (*pphead == NULL)//此处就是一个类似初始化的步骤{*pphead = newnode;//此处修改了了结构体变量,即修改了STL的指向指向新创建的链表的开始}else {//当不是最开始时就正常的进行尾插 while (tail->next) {//找尾部的NULLtail = tail->next;//没找到就改变tail 让其变成next 继续往后找}tail->next = newnode;//找到后把最后一个位置的数据的next 变成 新节点即可} }
- 头插,比较简单直接看注释就能理解
//头插就会简单很多 void SListPushFront(SListNode** pphead, SLDateType x){//同样用到二级指针,因为需要改变链表结构体变量assert(pphead);SListNode* newnode = BuySListNode(x);//创建节点newnode->next = *pphead;//将新开辟的空间的地址指向链表原本第一个元素的 地址*pphead = newnode;//改变链表的起始地址 }
从指定位置插入数据:
单独拎出来是因为其比较特殊,它可以分为在pos位置前插入数据和在pos位置后插入数据两种,并且我们在使用前需要先找到指定的位置后才能进行数据的插入(所以还需要先写一个查找数据的函数接口)。查找单链表中的某个数据(思路类似于尾插):
SListNode* SListFind(SListNode* phead, SLDateType x) {//指针接收结构体SListNode* find = phead;while (find)//一个循环来找想要的数据{if (find->data == x)//当找到时{return find;//返回此处的地址}find = find->next;//不断往后找和找尾方法一样}printf("找不到\n");return NULL;//若找不到返回NULL }
有了之后我们就可以利用这个查找函数还进行对该数据的前/后插入数据了:
- 先向pos位置前插入(一般不考虑这种,因为比较麻烦且效率低,多在后面插入数据):
void SListInsertFront(SListNode** pphead,SListNode* pos, SLDateType x) {//当是在pos位置前面插入时就要考虑头插了所以可能会改变链表的开始就需要用到二级指针assert(pphead);assert(pos); if (pos == *pphead)//如果pos位置在第一个那就等于在第一个位置前面插入数据也就是头插{SListPushFront(pphead, x);//此处用pphead因为pphead已经是一个二级指针了}else{SListNode* tail = *pphead;while (tail->next != pos)//找到pos位置的前一个位置{tail = tail->next;}SListNode* newnode = BuySListNode(x);//申请一个节点newnode->next = tail->next;//先把newnode的指针改变,这样避免当tail改变后找不到tail->next的情况tail->next = newnode;//再将tail->next 指向改变成 newnode} }
- 先pos位置之后插入数据:
void SListInsertAfter(SListNode* pos, SLDateType x) {//此处把链表的地址传给了posassert(pos);SListNode* newnode = BuySListNode(x);//创建新节点newnode->next = pos->next;//先改变newnode的节点pos->next = newnode;//再把pos->next改成newnode }
附:当不给头怎么才能在pos位置前面的插入数据,有一种思路现在其pos位置后面插入一个数据然后再将pos位置的值和后面插入的值进行交换就相当于在pos位置前插入数据了。
1.3 删除单链表中的数据
- 尾删:找到倒数第二个数据的位置后再把最后一个位置的空间进行释放即可,然后把倒数第二个位置的next指向NULL。
void SListPopBack(SListNode** pphead) {//可能会修改外部数据所以用二级指针assert(pphead);//判空assert(*pphead);//判空,防止第一个位置都没有if ((*pphead)->next == NULL)//查看第一个数据位置是不是最后一个数据如果是的话{free(*pphead);//直接释放第一个位置的数据即可*pphead = NULL;//把没用的指针置为NULL}else{SListNode* tail = *pphead;//用tail代替*pphead,使*pphead不被改变while (tail->next->next) {//往后看两位,当为空时,就表示到了倒数第二的位置tail = tail->next;//一步一步走}free(tail->next);//找到后,tail表示的是倒数第二,而tail->next就是尾,free释放尾即可tail->next = NULL;//tail->next改变成NULL,因为此时tail变成了最后一个数据} }
头删:分析首先我们肯定是需要一个二级指针接收来改变链表的开始,其次就是要用一个临时指针指向头的位置,先把头改成下一个位置,然后把临时变量指向的原本的头的空间释放。
void SListPopFront(SListNode** pphead) {assert(pphead);//防止pphead为空assert(*pphead);//查看是否有第一个数据SListNode* frist = *pphead;//记录第一个数据的位置*pphead = frist->next;//改变链表的头,改成下一个位置(可能为空当只有一个数据的时候)free(frist);//(释放第一个数据的位置)frist = NULL;//没有的指针置为空 }
在pos位置/pos位置后删除数据
在前面已经提过在pos位置前面插入基本不会用到所以此处就不再写了
- 在pos位置处删除,需要先找到pos位置前面的数据然后后才能改变链接关系
void SListErase(SListNode** pphead,SListNode* pos) {assert(pos);//不用再对*pphead进行检查了因为pos已经间接的检查了*pphead 检查的是是否链表中是否有数据假如没有的话pos也会报错assert(pphead);//判空SListNode* tail = *pphead;if (*pphead == pos)//若是第一个元素{SListPopFront(pphead);//那就直接头删}else{while (tail->next != pos)//找到pos位置的前一个位置{tail = tail->next;}tail->next = pos->next;//将前面位置的next改变成pos位置的next,这样就把pos位置给断开了free(pos);//把pos位置处的空间给释放} }
- 把pos位置后面的数据删除,方法很简单同样就是改变链接关系即可即吧pos->next = pos->next->next(即先找到pos位置后面的数据,然后把pos的next改成后面数据的next即可思想和上面的是一样的)
void SListEraseAfter(SListNode* pos) {assert(pos);if (pos->next == NULL)//注意查看pos位置后面是否还有数据{return;//若没有则直接返回了}else {SListNode* del = pos->next;//记录pos后面的位置pos->next = pos->next->next;//改变链接关系让pos的后面位置指向pos位置后的后面位置free(del);//释放deldel = NULL;} }
若没给头怎么删除pos位置处的数据呢?可以先存一份pos位置后面的数据再将后面的数据给赋给pos位置再把pos位置后面的数据给删除即可。
1.4 摧毁单链表
从前往后的依次free(单链表不能从后往前是因为找不到是前面的数据的),并且注意还需要一个指针来在后面,才能保证释放前面数据后能找到后面的数据。
void SListDestroy(SListNode* plist) {assert(plist);SListNode* prev, * tail;//双指针prev = plist;//指向头tail = plist->next;//指向第二个数据的位置while (prev)//当prve==NULL就不用进循环了也表示释放完了{ free(prev);//释放prev出的空间prev = tail;//将prve指向tailif(tail != NULL)//判断tail是不是NULLtail = tail->next;//若是NULL就不能再往后了}plist = NULL;//将没用的指针赋成空指针 }
1.5单链表的打印
直接遍历整个数组然后找到其数据进行打印
void SListPrint(SListNode* phead) {SListNode* tail = phead;//用一个指针来指向开始while (tail != NULL)//只要tail不到NULL都要进群{printf("%d->", tail->data);//进来打印tail的datetail = tail->next;//往后走}printf("NULL\n");//打印一下最后的NULL }
附:为什么要用二级指针?
因为如果不用二级指针的话就无法改变传进来的参数(只是传调用无法改变参数),所以只有用传址调用能改变形参,而传递进来的是一个一级指针类型,所以我们就需要二级指针类型来接收他的地址(接收指针类型的指针三步法)。
2.带头双向循环列表
知识点:
本质还是一个链表所以还是一样的通过指针的方法链接,但是需要我们去改变一下结构,增加了一个prev指针指向前面的数据。
结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
细节:
带头双向循环链表的基本框架:
- 带头双向循环链表的结构
- 一个变量存数据
- 一个指针指向前面的数据
- 一个指针指向后面的数据
- 带头双向循环链表所要实现的功能
- 初始化结构
- 将数据放进结构中
- 将数据从结构中删除
- 在指定位置处插入/删除数据
2.1带头双向链表的结构
它的结构是由存数据的变量、两个分别指向前后的指针组成的
typedef int LTDataType;typedef struct ListNode
{LTDataType data;//数据struct ListNode* next;//指向后面的数据struct ListNode* prev;//指向前面的数据//struct ListNode结构体类型,这里要加上struct因为类型重命名是在后面的
}ListNode;//typedef类型重命名
2.2双向链表的初始化
在那之前还需要去写一个申请空间的接口
ListNode* BuyMemory(LTDataType x)//申请空间的函数
{ListNode* node = (ListNode*)malloc(sizeof(ListNode));//malloc申请一个大小为一个结构体(一个节点的)空间if (node== NULL)//若申请失败{perror("malloc::BuyMemory");//报错//return NULL;exit(-1);//直接退出程序}node->data = x;//将申请中的空间数据置为给定的xnode->next = NULL;//将next先置为NULLnode->prev = NULL;//将prev先置为NULLreturn node;//返回借号的空间
}
因为是带头的链表所以需要去先申请一个空间给头
ListNode* ListCreate()//给头申请空间并且返回头指针的地址
{ListNode* head = BuyMemory(-1);//申请空间并且将其数据置为-1head->next = head;//将next指向headhead->prev = head;//prev也是指向头的return head;//返回头申请好的空间
}
此处因为有了头,那就不再需要先去创建一个链表的结构(ListNode *STL = NULL )来确定链表的头了(不用传STL/&STL、而是直接传head即可找到链表并且修改链表(即使是改变链表的开始也不用再使用二级指针了))。
下面的头插、头删中的头并不是头而是表示插入/删除第一个节点
2.3双向链表中放入数据
- 头插思路:先记录通过head找到的原本的头再创建一个新节点改变链接关系即可完成
void ListPushFront(ListNode* pHead, LTDataType x) {assert(pHead);//判空ListNode* newnode = BuyMemory(x);//创建新节点ListNode* frist = pHead->next;//记录原本的链表的第一个数据//改变链接关系// phead <-> first// phead <-> newnode <-> first//先改变newnode与第一个数据间的关系,// newnode <-> firestnewnode->next = frist; frist->prev = newnode;//再改变新节点个head的链接关系// head <-> newnodenewnode->prev = pHead;pHead->next = newnode;//改完后 :phead <-> newnode <-> frist }
- 尾插方法和头插类似,先记录尾部数据,然后新建一个节点,改变head、尾部数据、新节点三者的链接关系
void ListPushBack(ListNode* pHead, LTDataType x) {assert(pHead);ListNode* newnode = BuyMemory(x);//创建新节点ListNode* tail = pHead->prev;//记录原本的尾部数据结构//原本的链接顺序:tail <-> head//改变后的链接顺序:tail <-> newnode <-> head//先改变 tail <-> newnodetail->next = newnode;newnode->prev = tail;//再把newnode和head链接起来:newnode<->headnewnode->next = pHead; pHead->prev = newnode; }
在在pos位置插入数据,因为这是一个双向且循环的链表,所以我们只需要写一个插入接口即可实现所有想要的结果(此处实现在pos位置前面插入数据,其实现方法和上面的都几乎一样)。
void ListInsert(ListNode* pos, LTDataType x) {assert(pos);//判空ListNode* prev = pos->prev;//记录前面的数据ListNode* newnode = BuyMemory(x);//创建新节点//改变链接关系// prev pos// prev newnode pos// prev newnode prev->next = newnode;newnode->prev = prev;// newnode posnewnode->next = pos;pos->prev = newnode;// prev newnode pos }
同样的为了获取pos的结构的空间地址,我们需要写一个查找的接口
ListNode* ListFind(ListNode* pHead, LTDataType x) {assert(pHead);ListNode* tail = pHead;//tail指向链表while (tail)//通过tail指针来找{if (tail->data == x)//找结构中的值是不是等于x{return tail;//找到后返回节点的地址}tail = tail->next;//往后走}printf("不存在,找不到\n"); //若到了此处就表示链表中的数据没有xreturn NULL;//此时返回NULL表示不存在该数据的节点 }
2.4双向链表中的删除数据
在我们删除数据的前提是得链表中有数据所以就需要一个判断是否有数据的接口:
bool If_DTLEmpty(ListNode* pHead)//返回bool值,即返回真或假
{if (pHead->next == pHead)//就判断phead的->是不是自己即可,因为如果不是则表示是有数据的,反之因为是循环链表没数据就会指向自己了{return true;//若指向自己则是真 空的}else{return false;//反之则非空}
}
- 头删,记录第一、二个节点,改变链接关系即可
void ListPopFront(ListNode* pHead) {assert(pHead);assert(!If_DTLEmpty(pHead));//判断是否为空ListNode* front = pHead->next;//记录第一个节点ListNode* frist = pHead->next->next;//记录第二个节点//改变链接关系// head <-> front <-> frist == head <-> firstpHead->next = frist;frist->prev = pHead;free(front);//改变链接关系后把第一个节点释放front = NULL; }
- 尾删,记录倒数第一、二个节点,改变链接关系即可
void ListPopBack(ListNode* pHead) {assert(pHead);assert(!If_DTLEmpty(pHead));//判断是否为空ListNode* new_tail = pHead->prev->prev;//记录倒数第二个节点ListNode* old_tail = pHead->prev;//记录倒数第一个节点//改变链接关系new_tail->next = pHead;pHead->prev = new_tail;free(old_tail);//释放倒数第一个节点old_tail = NULL; }
- 删除指定pos位置处的数据,记录pos位置的next和prev节点然后改变链接关系即可
void ListErase(ListNode* pos) {assert(pos);//判空ListNode* prev = pos->prev;//记录pos前面节点ListNode* tail = pos->next;//记录pos后面的节点//改变链接关系prev->next = tail;tail->prev = prev;//释放posfree(pos); }
2.5双向链表的摧毁
其摧毁的方法和单链表很像,但要注意他是一个循环的链表,区别在于多了个头所以就可以以这个头为结束,从第一个节点开始,当遇到头时就表示走完一遍链表了就跳出循环,最后再把head释放一下即可
void ListDestory(ListNode* pHead) {assert(pHead);ListNode* tail = pHead->prev;//从第一个节点开始while (tail != pHead)//当没遇到头前就不断释放再往前走{ListNode* destroy = tail;//记录要释放的节点地址tail = tail->next;//tail往后走free(destroy);//释放空间}free(pHead);//最后当把链表中的数据释放完后,再释放头 }
2.6打印双向链表中的各个数据
从第一个节点开始遍历链表,当循环回来到head时就退出
void ListPrint(ListNode* pHead) {assert(pHead);ListNode* cur = pHead->next;//从第一个节点开始printf("《=》head《=》");while (cur != pHead)//当不是pHead就进去{printf("%d《=》", cur->data);//打印cur = cur->next;//往后走}printf("\n"); }
3.如何快速的实现一个链表
其实如果要快速的实现一个链表的话,用到的就是双向循环链表,因为其是双向的这样就可以直接通过在pos位置插入/删除,这样我们就能剩下写头插、删、尾插、删的时间直接通过插入/删除接口间接实现。
ListNode* BuyMemory(LTDataType x)//申请空间的函数 {ListNode* node = (ListNode*)malloc(sizeof(ListNode));//malloc申请一个大小为一个结构体(一个节点的)空间if (node== NULL)//若申请失败{perror("malloc::BuyMemory");//报错//return NULL;exit(-1);//直接退出程序}node->data = x;//将申请中的空间数据置为给定的xnode->next = NULL;//将next先置为NULLnode->prev = NULL;//将prev先置为NULLreturn node;//返回借号的空间 }ListNode* ListCreate()//给头申请空间并且返回头指针的地址 {ListNode* head = BuyMemory(-1);//申请空间并且将其数据置为-1head->next = head;//将next指向headhead->prev = head;//prev也是指向头的return head;//返回头申请好的空间 }void ListPushBack(ListNode* pHead, LTDataType x) {assert(pHead);ListInsert(pHead,x); }void ListPrint(ListNode* pHead) {assert(pHead);ListNode* cur = pHead->next;//从第一个节点开始printf("《=》head《=》");while (cur != pHead)//当不是pHead就进去{printf("%d《=》", cur->data);//打印cur = cur->next;//往后走}printf("\n"); }bool If_DTLEmpty(ListNode* pHead)//返回bool值,即返回真或假 {if (pHead->next == pHead)//就判断phead的->是不是自己即可,因为如果不是则表示是有数据的,反之因为是循环链表没数据就会指向自己了{return true;//若指向自己则是真 空的}else{return false;//反之则非空}}void ListPopBack(ListNode* pHead) {assert(pHead);assert(!If_DTLEmpty(pHead));//判断链表是否为空ListErase(pHead->prev); }void ListPushFront(ListNode* pHead, LTDataType x) {assert(pHead);//判空ListInsert(pHead->next,x); }void ListPopFront(ListNode* pHead) {assert(pHead);assert(!If_DTLEmpty(pHead));//判断是否为空ListErase(pHead->next); }ListNode* ListFind(ListNode* pHead, LTDataType x) {assert(pHead);ListNode* tail = pHead;//tail指向链表while (tail)//通过tail指针来找{if (tail->data == x)//找结构中的值是不是等于x{return tail;//找到后返回节点的地址}tail = tail->next;//往后走}printf("不存在,找不到\n"); //若到了此处就表示链表中的数据没有xreturn NULL;//此时返回NULL表示不存在该数据的节点 }void ListInsert(ListNode* pos, LTDataType x) {assert(pos);//判空ListNode* prev = pos->prev;//记录前面的数据ListNode* newnode = BuyMemory(x);//创建新节点//改变链接关系// prev pos// prev newnode pos// prev newnode prev->next = newnode;newnode->prev = prev;// newnode posnewnode->next = pos;pos->prev = newnode;// prev newnode pos }void ListErase(ListNode* pos) {assert(pos);//判空ListNode* prev = pos->prev;//记录pos前面节点ListNode* tail = pos->next;//记录pos后面的节点//改变链接关系prev->next = tail;tail->prev = prev;//释放posfree(pos); }void ListDestory(ListNode* pHead) {assert(pHead);ListNode* tail = pHead->prev;//从第一个节点开始while (tail != pHead)//当没遇到头前就不断释放再往前走{ListNode* destroy = tail;//记录要释放的节点地址tail = tail->next;//tail往后走free(destroy);//释放空间}free(pHead);//最后当把链表中的数据释放完后,再释放头 }
如果有任何问题欢迎讨论哈!
如果觉得这篇文章对你有所帮助的话点点赞吧!
持续更新大量数据结构细致内容,早关注不迷路。
相关文章:

数据结构链表(C语言实现)
绪论 机遇对于有准备的头脑有特别的亲和力。本章将讲写到链表其中主要将写到单链表和带头双向循环链表的如何实现。 话不多说安全带系好,发车啦(建议电脑观看)。 附:红色,部分为重点部分;蓝颜色为需要记忆的…...

Springboot实现接口传输加解密
前言 先给大家看下效果,原本我们的请求是这样子的 加密后的数据传输是这样子的 加解密步骤: 1.前端请求前进行加密,然后发送到后端 2.后端收到请求后解密 3.后端返回数据前进行加密 4.前端拿到加密串后,解密数据 加解密算法&…...

TypeScript类型系统:强类型的优势和使用方式
目录 引言强类型的优势更好的代码可读性更好的代码可维护性更好的代码重构能力更好的代码可靠性更好的代码重用能力 使用方式声明变量类型函数参数和返回值类型类型别名泛型类型(了解) 总结 引言 在上一篇文章《TypeScript入门指南:从JS到TS的…...

有没有可以代替风铃系统的专业问卷工具?
风铃系统问卷是一种流行的调查和数据分析工具,已广泛应用于学术研究、市场营销和社会科学。然而,有几种替代产品提供了与风铃系统类似的特性和功能,可以被企业用来进行调查和分析数据。在这篇文章中,我们将介绍风铃系统的十大替代…...

【数字调制】数字调制技术FSK与PSK分析与研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

html实现好看的个人介绍,个人主页模板4(附源码)
文章目录 1.设计来源1.1 主界面1.2 我的文章界面1.3 我的相册界面1.4 关于我界面1.5 联系我界面 2.效果和源码2.1 动态效果2.2 源代码2.2 源代码目录 源码下载 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/131265259 …...

内存不够用,那你的内存去哪了?
一、前言 近几年开发了一些大型的应用程序,在程序性能调优或者解决一些疑难杂症问题的过程中,遇到最多的还是与内存相关的一些问题。例如glibc内存分配器ptmalloc,google的内存分配器tcmalloc都存在“内存泄漏”,即内存不归还操作…...

哈希表--day4--(leetcode202/leetcode1/leetcode454)
文章目录 leetcode202. 快乐数基本思路AC-code leetcode1. 两数之和基本思路AC-code 454.四数相加II基本思路AC-code leetcode202. 快乐数 链接 基本思路 实际上题目隐藏着一个小细节,就是告诉你会发生无限循环,那我们该如何跳出这个无限循环就是一个…...

基于Python+Django+mysql+html通讯录管理系统
基于PythonDjangomysqlhtml通讯录管理系统 一、系统介绍二、功能展示1.用户登陆2.用户注册3.密码修改4.查询5.添加6.修改7.删除 三、其它系统四、获取源码 一、系统介绍 该系统实现了 用户登陆、用户注册、密码修改、查询信息、添加信息,修改信息、删除信息 运行环…...

Rabbitmq学习
文章目录 前言RabbitMQ 1 同步调用和异步调用2 常见的MQ对比3 安装RabbitMQ4 RabbitMQ学习4.1 helloworld学习 5 Spring AMQP5.1 AMQP的入门案例(使用rabbittemplate进行消息发送和接受)5.2 RabbitMQ的workquene5.3 发布订阅模型(exchange(广播fanout 路由direct 话题topic))5.…...

初识轻量级分布式任务调度平台 xxl-job
文章目录 前言xxl-job的目录结构项目依赖 (父 pom.xml)xxl-job-admin 启动xxl-job-executor-sample (项目使用示例)xxl-job-executor-sample-frameless : 不使用框架的接入方式案例xxl-job-executor-sample-springboot : springboot接入方案案例 xxl-job执行器器启动流程分析调…...

web 语音通话 jssip
先把封装好的地址安上(非本人封装):webrtc-webphone: 基于JsSIP开发的webrtc软电话 jssip中文文档:jssip中文开发文档(完整版) - 简书 jssip使用文档:(我没有运行过,但…...

随风摇曳的她——美蕨(matlab实现)
目录 1 随风摇曳的她 2 摇曳带来的哲思 3 Matlab代码实现 1 随风摇曳的她 梦幻的场景、浪漫的气息,带上心爱的人,拥抱在这片花海之下,便有了电影男女主角的氛围感; 就算阅尽了世间风貌,也抵不上和她在一起时锦短情长&a…...

时序数据库的流计算支持
一、时序数据及其特点 时序数据(Time Series Data)是基于相对稳定频率持续产生的一系列指标监测数据,比如一年内的道琼斯指数、一天内不同时间点的测量气温等。时序数据有以下几个特点: 历史数据的不变性数据的有效性数据的时效…...

springboot启动流程 (3) 自动装配
在SpringBoot中,EnableAutoConfiguration注解用于开启自动装配功能。 本文将详细分析该注解的工作流程。 EnableAutoConfiguration注解 启用SpringBoot自动装配功能,尝试猜测和配置可能需要的组件Bean。 自动装配类通常是根据类路径和定义的Bean来应…...

ansible-roles模块
roles用于层次性,结构化地组织playbook,roles能够根据层次型结构自动装载变量文件,tasks以及handlers等。要使用只要载playbook中使用include指令引入即可。 (roles就是通过分别将变量,文件,任务ÿ…...

聊聊我做 NeRF-3D重建性能优化经历
我们新推出大淘宝技术年度特刊《长期主义,往往从一些小事开始——工程师成长总结专题》,专题收录多位工程师真诚的心路历程与经验思考,覆盖终端、服务端、数据算法、技术质量等7大技术领域,欢迎一起沟通交流。 本文为此系列第四篇…...

未磁科技全球首台64通道无液氦心磁图仪及首个培训基地落户北京安贞医院
【全球首台64通道无液氦心磁图仪在北京安贞医院举行开机仪式】 近日,在北京安贞医院举行了未磁科技全球首台64通道无液氦心磁图仪开机仪式,中国医学装备协会赵自林理事长、北京安贞医院纪智礼书记、张宏家院长、宋现涛教授,以及未磁科技蔡宾…...

SpringBoot 如何使用 ApplicationEventPublisher 发布事件
SpringBoot 如何使用 ApplicationEventPublisher 发布事件 在 SpringBoot 应用程序中,我们可以使用 ApplicationEventPublisher 接口来发布事件。事件可以是任何对象,当该对象被发布时,所有监听该事件的监听器都会收到通知。 下面是一个简单…...

【深度学习】2-3 神经网络-输出层设计
前馈神经网络(Feedforward Neural Network),之前介绍的单层感知机、多层感知机等都属于前馈神经网络,它之所以称为前馈(Feedforward),或许与其信息往前流有关:数据从输入开始,流过中间计算过程,最后达到输出…...

Python网络爬虫开发:使用PyQt5和WebKit构建可定制的爬虫
部分数据来源:ChatGPT 引言 在网络爬虫开发中,使用Web浏览器模拟用户行为是非常重要的。而在这个过程中,基于 WebKit 的框架可以提供比其他技术更紧密的浏览器集成,以及更高效、更多样化的页面交互方式。 在本文中,我们将通过一个使用基于 WebKit 的爬虫示例,并与类似…...

Laya3.0游戏框架搭建流程(随时更新)
近两年AI绘图技术有了长足发展,准备把以前玩过的游戏类型重制下,也算是圆了一个情怀梦。 鉴于unity商用水印和启动时间的原因,我决定使用Laya来开发。目前laya已经更新到了3.0以上版本,就用目前比较新的版本。 之后关于开发中遇到…...

.net 软件开发模式——三层架构
三层架构是一种常用的软件开发架构模式,它将应用程序分为三个层次:表示层、业务逻辑层和数据访问层。每一层都有明确的职责和功能,分别负责用户交互、业务处理和数据存储等任务。这种架构模式的优点包括易于维护和扩展、更好的组织结构和代码…...

SpringBoot如何优雅的实现重试功能
文章目录 使用背景spring-retry介绍快速使用加入依赖开启Retry使用参数 使用背景 在有些特定场景,如和第三方对接。 我们调用接口时需要支持重试功能,第一次调用没成功,我们需要等待x秒后再次调用。 通常会设置重试次数,避免业务…...

【CEEMDAN-VMD-GRU】完备集合经验模态分解-变分模态分解-门控循环单元预测研究(Python代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

OpenText Exceed TurboX(ETX)—— 适用于 UNIX、Linux 和 Windows 的远程桌面解决方案
由于新技术的采用,以及商业全球化和全球协作的现实,几乎所有企业(无论其规模和所处行业)的员工的工作方式、时间和地点都发生了重大变化。业务领导者正在推动其 IT 部门提出解决方案,以帮助其远程员工提高工作效率&…...

【人工智能】— 逻辑回归分类、对数几率、决策边界、似然估计、梯度下降
【人工智能】— 逻辑回归分类、对数几率、决策边界、似然估计、梯度下降 逻辑回归分类Logistic Regression ClassificationLogistic Regression: Log OddsLogistic Regression: Decision BoundaryLikelihood under the Logistic ModelTraining the Logistic ModelGradient Desc…...

k8s pod “cpu和内存“ 资源限制
转载用于收藏学习:原文 文章目录 Pod资源限制requests:limits:docker run命令和 CPU 限制相关的所有选项如下: Pod资源限制 为了保证充分利用集群资源,且确保重要容器在运行周期内能够分配到足够的资源稳定运行&#x…...

datagrip 连接 phoenix
jar替换完后尽量重启datagrip. 然后重新连接即可. 不重启貌似报错... 效果:...

黑客入侵的常法
1.无论什么站,无论什么语言,我要渗透,第一件事就是扫目录,最好一下扫出个上传点,直接上传 shell ,诸位不要笑,有时候你花很久搞一个站,最后发现有个现成的上传点,而且很容…...