外强中干——双向带头循环链表

news2025/2/23 23:47:19

前言:众所周知,链表有八种结构,由单向或双向,有头或无头,循环或不循环构成。在本篇,将介绍8种链表结构中最复杂的——双向带头循环链表。听着名字或许挺唬人的,但实际上双向带头循环链表实现起来比结构最简单的单向不带头不循环链表简单的多,是个“外强中干”的链表。这里只是说它名字唬人哦,实际上它是最优的链表结构,可以达到再任意位置插入,删除数据复杂度都是O(1)。(文末附有完整代码)

首先用你喜欢的IDE创建一个头文件和两个源文件,不同的文件具有以下不同的功能:

文件作用
list.h接口函数的声明
list.c接口函数的实现——链表的主体,文章的核心内容
test.c测试链表的运行逻辑
在这里插入图片描述下文将实现的11个链表接口
在这里插入图片描述双向循环链表图

这里是目录

  • 接口
    • 0.开始前的准备
    • 1.创建返回链表的头结点
    • 2.链表销毁
    • 3.链表打印
    • 4.链表尾插
    • 5.链表判空
    • 6.链表尾删
    • 7.链表头插
    • 8.链表头删
    • 9.链表查找
    • 10.在链表pos位置前插入
    • 11.删除链表pos位置的结点
  • 完整代码

接口

0.开始前的准备

头文件的包含;结构体的创建;.c文件引用.h文件

list.h

#include<stdio.h>
#include<assert.h>  //assert断言所需头文件
#include<stdlib.h>  //malloc动态开辟内存空间所需头文件
#include<stdbool.h> //在c语言中使用bool类型所需头文件,下文实现的ListEmpty函数用到

typedef int LTDataType; //因不知链表中存储的是什么数据类型,所以用重命名,要修改数据类型时直接将int换成其它的即可
typedef struct ListNode  
{
	LTDataType data;
	struct ListNode* next;  //指向后一个结点的指针
	struct ListNode* prev;  //指向前一个结点的指针
}ListNode;      //将结构体struct ListNode 重命名为 ListNode,方便操作

list.c

#include"list.h"  //引用头文件

test.c

#include"list.h"  //引用头文件

1.创建返回链表的头结点

这里的头结点就是我们常说的哨兵位,需要注意的是,哨兵位中不存储数据(即不给data赋值)。

list.h

//1.创建返回链表的头结点.
ListNode* ListInit();  //在头文件中声明

list.c

//要创建一个链表的结点,就需要开辟一个结构体,因此在.c文件中创建一个开辟新结点的函数,
//在下文中需要创建新结点时直接引用此函数即可
ListNode* ListCreate(LTDataType x) 
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode)); //malloc一个结构体
	if (newnode == NULL) //此处if判断malloc是否成功,常规情况下都会成功,毕竟失败了咱们链表不就创建失败了吗?
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x; //给数据赋值
	newnode->next = NULL;  //next指针置空
	newnode->prev = NULL;  //prev指针置空
	return newnode;
}

//1.创建返回链表的头结点
ListNode* ListInit()
{
	ListNode* pHead = ListCreate(-1); //这里的头结点随便给一个值,开头时提过头结点不存储数据,
//这样不是前后矛盾吗?不用担心,后文中的打印函数实现时并不会打印头结点。
	pHead->next = pHead;  //因为链表只有一个头结点,所以前后指针都指向自己
	pHead->prev = pHead;
	return pHead;
}

这里是引用

test.c

int main()
{
	ListNode* plist = ListInit();  
	return 0;
}

2.链表销毁

将每个创建的结点通过free函数释放,需要注意的是,因为传参我们传的是一级指针,所以需要在使用完销毁函数后手动置空。

list.h

//2.链表销毁
void ListDestory(ListNode* pHead);

list.c

//2.链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);  //断言一下pHead是否传有效数据进来
	ListNode* cur = pHead->next; //存储头结点的后一个结点
	while (cur != pHead)  //从头结点的后一个结点遍历到头结点停止循环
	{
		ListNode* next = cur->next;  //存储后一个结点
		free(cur);  //释放当前结点
		cur = next; //将cur指向后一个结点,使循环继续
	}
	free(pHead);
	//pHead = NULL; 置空无效,因为形参的改变不影响实参,在主函数中置空。
}

test.c

#include"list.h"
int main()
{
	ListNode* plist = ListInit();
	ListDestory(plist);
	plist = NULL;  //手动置空
	return 0;
}

3.链表打印

从头到尾遍历打印,注意不要打印头结点(哨兵位)

list.h

//3.链表打印
void ListPrint(ListNode* pHead);

list.c


//3.链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	//打印头结点
	printf("guard<==>"); //为了打印的美观,哨兵位这样表示
	ListNode* cur = pHead->next; //存储头结点的后一个结点
	while (cur != pHead)  //从头结点的后一个结点遍历到头结点停止循环
	{
		printf("%d<==>", cur->data);  //打印输出
		cur = cur->next;  //将cur指向后一个结点,使循环继续
	}
}

test.c

#include"list.h"
int main()
{
	ListNode* plist = ListInit();
	ListPrint(plist);
	ListDestory(plist);
	plist = NULL;
	return 0;
}

此时链表中只有头结点,显示台窗口如下:
在这里插入图片描述

4.链表尾插

链表中的重要接口之一,在单链表中,进行尾插操作的话需要从头找到尾结点,而在双向链表中,头结点的前一个结点就是尾结点(pHead->prev)。从这里就可以看出双向带头循环链表实现起来的简单之处。

list.h

//4.链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);

list.c

//4.链表尾插
void ListPushBack(ListNode* pHead, LTDataType x) //插入拥有两个参数,第二个参数为你要插入的数值
{
	assert(pHead); //断言
	ListNode* newnode = ListCreate(x); //插入需要创建新结点,这里我们开头第1部分创建的ListCreate函数就有了用武之地。
	struct ListNode* tail = pHead->prev; //存储原来的尾结点,注意这个结点的存储会让你的尾插实现写的很舒服
	newnode->next = pHead;  //实现链接
	newnode->prev = tail;   
	tail->next = newnode;   
	pHead->prev = newnode;  //注意这里的pHead->prev不要再用tail代替了,因为改变tail并不会改变pHead->prev。
}

在这里插入图片描述

以上图为例,进行尾插操作的话,需要将新结点与d3,head链接起来,同时断开head和原尾结点d3。
上面的代码使用tail结构体指针存储了pHead->prev,这样的话下面实现链接的4行代码就可以是任意顺序,若是没有tail的话,必须在结尾改变pHead->prev,因为前面几行代码需要用到pHead->prev,若是改了它,则代码不能实现。

test.c

#include"list.h"  //引用头文件
int main()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 4);  //尾插4
	ListPushBack(plist, 3);  //尾插3
	ListPushBack(plist, 2);  //尾插2
	ListPushBack(plist, 1);  //尾插1
	ListPrint(plist);
	ListDestory(plist);
	plist = NULL;
	return 0;
}

此时显示台窗口如下:
在这里插入图片描述

5.链表判空

很简单的一个接口,返回一个bool类型的值。因在链表尾删中需要用到,所以将其放在尾删前

list.h

//5.链表判空
bool ListEmpty(ListNode* pHead);

list.c

bool ListEmpty(ListNode* pHead)
{
	assert(pHead); //断言
	return pHead->next == pHead; // 若是头结点的后一个结点还是头结点,说明链表中只有头结点,
	//而头结点不存放有效数据,因此只有头结点的链表就是空链表。 
	//该语句为真,则返回true,若为假,则返回false
}

test.c

#include"list.h"  //引用头文件
int main()
{
	ListNode* plist = ListInit();
	if (ListEmpty(plist))
	{
		printf("链表为空\n");
	}
	ListDestory(plist);
	plist = NULL;
	return 0;
}

此时显示台窗口如下:
在这里插入图片描述

6.链表尾删

链表中的重要接口之一,双向链表找尾很方便,即pHead->prev。删除操作比起插入操作实现起来更简单。

list.h

//6.链表尾删
void ListPopBack(ListNode* pHead);

list.c

//6.链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);  //断言
	assert(!ListEmpty(pHead));
	ListNode* tail = pHead->prev;
	tail->prev->next = pHead;
	pHead->prev = tail->prev;
	free(tail);
	tail = NULL;
}

test.c

#include"list.h"  //引用头文件
int main()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 2);
	ListPushBack(plist, 1);
	ListPushBack(plist, 0);
	ListPrint(plist);  //第一行打印
	ListPopBack(plist);
	printf("\n");
	ListPrint(plist);  //第二行打印
	ListDestory(plist);
	plist = NULL;
	return 0;
}

此时显示台窗口如下:尾删了一个0
在这里插入图片描述

7.链表头插

list.h

//7.链表头插
void ListPushFront(ListNode* pHead, LTDataType x);

list.c

//头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead); //断言
	ListNode* newnode = ListCreate(x);  //创建新结点
	ListNode* head = pHead->next;   //存放头结点的后一个结点
	newnode->next = head;
	head->prev = newnode;
	pHead->next = newnode;
	newnode->prev = pHead;
}

这里是引用

test.c

#include"list.h"  //引用头文件
int main()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 2);
	ListPushBack(plist, 1);
	ListPushBack(plist, 0);
	ListPushFront(plist, 3);
	ListPrint(plist);  //第二行打印
	ListDestory(plist);
	plist = NULL;
	return 0;
}

此时显示台窗口如下:头插一个3
在这里插入图片描述

8.链表头删

跟头删一样,删除操作都要进行判空,若是空链表,则不能再进行删除操作。

list.h


list.c

//链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));  
	ListNode* head = pHead->next;
	head->next->prev = pHead;
	pHead->next = head->next;
	free(head);
	head = NULL;
}

test.c

#include"list.h"  //引用头文件
int main()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 2);
	ListPushBack(plist, 1);
	ListPushBack(plist, 0);
	ListPrint(plist);  //第一行打印
	printf("\n");
	ListPopFront(plist); //头删
	ListPrint(plist);  //第二行打印
	ListDestory(plist);
	plist = NULL;
	return 0;
}

此时显示台窗口如下:头删一个2
在这里插入图片描述

9.链表查找

查找链表中是否存在指定的数,若存在,则返回这个结点,若不存在,则返回NULL。

list.h

//9.链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);

list.c

//9.链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead); //断言
	ListNode* cur = pHead->next;  //存储头结点的后一个结点
	while (cur != pHead)
	{
		if (cur->data == x)  //如果是,则返回该结点
			return cur;
		cur = cur->next;   
	}
	return NULL;  //如果没有,则返回NULL
}

test.c

#include"list.h"  //引用头文件
int main()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 2);
	ListPushBack(plist, 1);
	ListPushBack(plist, 0);
	ListNode* pplist = ListFind(plist,2);
	if (pplist)
	{
		printf("链表中包含2");
	}
	ListDestory(plist);
	plist = NULL;
	return 0;
}

此时显示台窗口如下:查找链表中有没有2

在这里插入图片描述

10.在链表pos位置前插入

list.h

//10.在链表pos位置前插入
void ListInsert(ListNode* pos, LTDataType x);

list.c

//10.在链表pos位置前插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos); //断言
	ListNode* newnode = ListCreate(x); //创建新结点
	newnode->next = pos;  
	newnode->prev = pos->prev;
	pos->prev->next = newnode;
	pos->prev = newnode;
}

test.c

#include"list.h"  //引用头文件
int main()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 2);
	ListPushBack(plist, 1);
	ListPushBack(plist, 0);
	ListNode* pos = ListFind(plist,2);
	if (pos)
	{
		ListInsert(pos, 3);
	}
	ListPrint(plist);
	ListDestory(plist);
	plist = NULL;
	return 0;
}

此时显示台窗口如下:在2的前面插入3
在这里插入图片描述

11.删除链表pos位置的结点

list.h

//11.删除链表pos位置的结点
void ListErase(ListNode* pos);

list.c

//11.删除链表pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos); //断言
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

在这里插入图片描述

test.c

#include"list.h"  //引用头文件
int main()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 2);
	ListPushBack(plist, 1);
	ListPushBack(plist, 0);
	ListPrint(plist);  //第一行打印
	printf("\n");
	ListNode* pos = ListFind(plist,2);
	if (pos)
	{
		ListErase(pos);  //删除2
	}
	ListPrint(plist);  //第二行打印
	ListDestory(plist);
	plist = NULL;
	return 0;
}

此时显示台窗口如下:查找链表中是否有2,若有,则删除2(仅删除一个)
在这里插入图片描述

至此,双向带头循环链表的11个接口就写完了。怎么样,是不是感觉实现起来非常简单,说它"外强中干"丝毫为过吧。

完整代码

list.h

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

//1.创建返回链表的头结点.
ListNode* ListInit();
//2.链表销毁
void ListDestory(ListNode* pHead);
//3.链表打印
void ListPrint(ListNode* pHead);
//4.链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
//5.链表判空
bool ListEmpty(ListNode* pHead);
//6.链表尾删
void ListPopBack(ListNode* pHead);
//7.链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
//8.链表头删
void ListPopFront(ListNode* pHead);
//9.链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
//10.链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
//11.链表删除pos位置的结点
void ListErase(ListNode* pos);

list.c 核心代码——接口的实现

#include"list.h"  //引用头文件

//要创建一个链表的结点,就需要开辟一个结构体,因此在.c文件中创建一个开辟新结点的函数,
//在下文中需要创建新结点时直接引用此函数即可
ListNode* ListCreate(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode)); //malloc一个结构体
	if (newnode == NULL) //此处if判断malloc是否成功,常规情况下都会成功,毕竟失败了咱们链表不就创建失败了吗?
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x; //给数据赋值
	newnode->next = NULL;  //next指针置空
	newnode->prev = NULL;  //prev指针置空
	return newnode;
}

//1.创建返回链表的头结点
ListNode* ListInit()
{
	ListNode* pHead = ListCreate(-1); //这里的头结点随便给一个值,开头时提过头结点不存储数据,
	//这样不是前后矛盾吗?不用担心,后文中的打印函数实现时并不会打印头结点。
	pHead->next = pHead;  //因为链表只有一个头结点,所以前后指针都指向自己
	pHead->prev = pHead;
	return pHead;
}

//2.链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next; //存储头结点的后一个结点
	while (cur != pHead)  //从头结点的后一个结点遍历到头结点停止循环
	{
		ListNode* next = cur->next;  //存储后一个结点
		free(cur);  //释放当前结点
		cur = next; //将cur指向后一个结点
	}
	free(pHead);
	//pHead = NULL; 置空无效,因为形参的改变不影响实参,在主函数中置空。
}

//3.链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	printf("guard<==>"); //为了打印的美观,哨兵位这样表示
	ListNode* cur = pHead->next; //存储头结点的后一个结点
	while (cur != pHead)  //从头结点的后一个结点遍历到头结点停止循环
	{
		printf("%d<==>", cur->data);  //打印输出
		cur = cur->next;  //将cur指向后一个结点,使循环继续
	}
}

//4.链表尾插
void ListPushBack(ListNode* pHead, LTDataType x) //插入拥有两个参数,第二个参数为你要插入的数值
{
	assert(pHead); //断言
	ListNode* newnode = ListCreate(x); //插入需要创建新结点,这里我们开头第1部分创建的ListCreate函数就有了用武之地。
	struct ListNode* tail = pHead->prev;  //存储原来的尾结点
	newnode->next = pHead;  //实现链接
	tail->next = newnode;
	pHead->prev = newnode;  //注意这里的pHead->prev不要再用tail代替了,因为改变tail并不会改变pHead->prev。
	newnode->prev = tail;
	
}

//5.链表判空
bool ListEmpty(ListNode* pHead)
{
	assert(pHead);
	return pHead->next == pHead; //该语句为真,则返回true,若为假,则返回false
}

//6.链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);  //断言
	assert(!ListEmpty(pHead));
	ListNode* tail = pHead->prev;
	tail->prev->next = pHead;
	pHead->prev = tail->prev;
	free(tail);
	tail = NULL;
}

//7.链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead); //断言
	ListNode* newnode = ListCreate(x);  //创建新结点
	ListNode* head = pHead->next;   //存放头结点的后一个结点
	newnode->next = head;
	head->prev = newnode;
	pHead->next = newnode;
	newnode->prev = pHead;
}

//8.链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListNode* head = pHead->next;
	head->next->prev = pHead;
	pHead->next = head->next;
	free(head);
	head = NULL;
}

//9.链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead); //断言
	ListNode* cur = pHead->next;  //存储头结点的后一个结点
	while (cur != pHead)
	{
		if (cur->data == x)  //如果是,则返回该结点
			return cur;
		cur = cur->next;
	}
	return NULL;  //如果没有,则返回NULL
}

//10.在链表pos位置前插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos); //断言
	ListNode* newnode = ListCreate(x); //创建新结点
	newnode->next = pos;
	newnode->prev = pos->prev;
	pos->prev->next = newnode;
	pos->prev = newnode;
}

//11.删除链表pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos); //断言
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

test.c 该文件随大家喜欢测试

#include"list.h"  //引用头文件
int main()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 2);
	ListPushBack(plist, 1);
	ListPushBack(plist, 0);
	ListPrint(plist);  //第一行打印
	printf("\n");
	ListNode* pos = ListFind(plist,2);
	if (pos)
	{
		ListErase(pos);  //删除2
	}
	ListPrint(plist);  //第二行打印
	ListDestory(plist);
	plist = NULL;
	return 0;
}

文末BB:对哪里有问题的朋友,可以在评论区留言,若哪里写的有问题,也欢迎朋友们在评论区指出,博主看到后会第一时间确定修改。最后,制作不易,如果对朋友们有帮助的话,希望能给点点赞和关注.
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

九盾安防智能叉车管理系统告诉你叉车电池安全使用的十要点

叉车电池是叉车的动力源&#xff0c;对于保证叉车安全运行具有非常重要的作用。但是&#xff0c;叉车电池在使用过程中也会存在一些安全问题&#xff0c;如果使用不当可能会引起严重后果。下文就九盾安防智能叉车管理系统介绍叉车电池安全使用的十要点。 一、保证通风良好。在使…

课程《JavaWeb基础框架程序设计》考试题下篇——数据库与表单操作用题(人事管理平台的添加员工档案信息的操作题)

文章目录 &#x1f4cb;前言&#x1f3af;第三题&#xff08;40分&#xff09;&#x1f3af;报错以及解决方法&#x1f4dd;最后 &#x1f4cb;前言 这篇文章是大学课程《JavaWeb基础框架程序设计》考试题目的内容&#xff0c;包括了原题和答案。题目只包括了三道编程题&#…

2直接连接的网络与VLAN划分-2.3【实验】【计算机网络】

2直接连接的网络与VLAN划分-2.2-2.3【实验】【计算机网络】 前言推荐2直接连接的网络与VLAN划分2.1共享式以太网和交换式以太网2.2交换机MAC地址表建立与帧转发2.3 STP工作过程实验目的实验内容及实验环境实验原理物理环路引发的问题1:广播风暴物理环路引发的问题2:MAC地址表翻…

ChatGPT之公文写作

公务文章主要适用于政府部门、机关、事业单位以及其他公共组织的文件、公告、通知等文稿。 根据《党政机关公文处理工作条例》&#xff0c;公文种类主要有15种。按照行文流向&#xff0c;可以分为上行文、平行文、下行文。 1、上行文&#xff1a;请示、报告、意见。 2、平行…

Qt 文件IO

目录 1. QFileDialog 文件选择对话框 示例代码 dialog.h dialog.cpp dialog.ui 运行效果&#xff1a; 2. QFileInfo 文件信息类 dialog.cpp 3. QFile 文件读写类 UI与耗时操作 QThread 线程类 1 复现阻塞 dialog.h dialog.cpp 2.新建并启动子线程 mythread.h mythread.cpp dial…

Py之tf2onnx:tf2onnx库的简介、安装、使用方法之详细攻略

Py之tf2onnx&#xff1a;tf2onnx库的简介、安装、使用方法之详细攻略 目录 tf2onnx库的简介 tf2onnx库的安装 tf2onnx库的使用方法 1、基础用法 tf2onnx库的简介 tf2onnx是一个将TensorFlow&#xff08;tf-1.x或tf-2.x&#xff09;、keras、tensorflow.js和tflite模型转换…

【python安装】linux环境安装python

linux环境安装python 小白都能看懂的python安装前置步骤下载python安装python 小白都能看懂的python安装 前置步骤 使用 python -V 或者 python -version 查看是否安装python如果Linux有python3需要更新指定版本的话&#xff0c;先把自带的删除&#xff0c;输入 rpm -qa|grep…

JavaScript通过js的方式来计算平行四边形的面积的代码

以下为通过js的方式来计算平行四边形的程序代码和运行截图 目录 前言 一、通过js的方式来计算平行四边形&#xff08;html部分&#xff09; 1.1 运行流程及思想 1.2 代码段 二、通过js的方式来计算平行四边形&#xff08;js部分&#xff09; 2.1 运行流程及思想 2.2 代码…

算法修炼之练气篇——练气六层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

算法修炼之练气篇——练气十七层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

SpringBoot+Token+Redis+Lua+自动续签极简分布式锁Token登录方案

前言 用SpringBoot做一个项目&#xff0c;都要写登录注册之类的方案 使用Cookie或Session的话&#xff0c;它是有状态的&#xff0c;不符合现代的技术 使用Security或者Shiro框架实现起来比较复杂&#xff0c;一般项目无需用那么复杂 使用JWT它虽然是无状态的&#xff0c;也可…

微信开发者之AppID和AppSecret举例子

-- 请不要为爱你的人流泪&#xff0c;因为爱你的人不会让你流泪&#xff01; 序 今天对这2东西做个相对全面的介绍&#xff0c;不一定深入&#xff0c;但是对实际开发还是很有必要的 我们在微信开发中总是会绕不开2个单词&#xff1a; AppID&#xff1a;开发者ID,有些叫AppK…

蓝桥杯题单day2【题目】

动态规划 调手表https://www.lanqiao.cn/problems/230/learning/?page1&first_category_id1&sortstudents_count&second_category_id3&tags%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92,%E5%9B%BD%E8%B5%9B 最优包含https://www.lanqiao.cn/problems/239/learning…

114.【Vue-细刷-05】

Vue-04 (二十八)、Vuex1.Vuex的简介(1).vuex是什么(2).什么时候使用Vuex(3).Vuex工作原理图 2.求和案列_纯Vue版本3.求和案列_Vuex(1).初始化状态(2).操作状态 4.求和案列_Vuex的getters(1).Vuex的getters类似于 Vue的computed 5.求和案列_Vuex中的mapSteat和mapGetters(1).靠自…

PY32F072 系列单片机,LQFP64, LQFP48, QFN32, LQFP32多种封装

PY32F072 系列微控制器采用高性能的 32 位 ARM Cortex-M0内核&#xff0c;宽电压工作范围的 MCU。嵌入高达 128Kbytes flash 和 16Kbytes SRAM 存储器&#xff0c;最高工作频率 72MHz。包含多种不同封装类型多款产品&#xff0c;LQFP64, LQFP48, QFN32, LQFP32。 PY32F072芯片…

C# 实现 Websocket通讯聊天 (管用、超好使,点个赞)

1、背景 WebSocket出现之前&#xff0c;Web端为了实现即时通讯&#xff0c;所用的技术都是Ajax轮询(polling)。轮询是在特定的的时间间隔&#xff08;如每1秒&#xff09;&#xff0c;由浏览器对服务器发出HTTP request&#xff0c;然后由服务器返回最新的数据给客服端的浏览器…

基于RV1126平台分类模型全流程部署(附工程)

基于RV1126平台分类模型全流程部署 环境安装模型训练ONNX模型转换RKNN模型转换可执行文件上板推理 环境安装 首先要在虚拟机上安装瑞芯微Rv1126的SDK&#xff0c;重要的是要具有rknn_toolchain 一般在以下路径&#xff1a; sdk/external/rknn-toolkit 按照doc里面的步骤安装即…

多模态:MiniGPT-4

多模态&#xff1a;MiniGPT-4 IntroductionMethodlimitation参考 Introduction GPT-4具有很好的多模态能力&#xff0c;但是不开源。大模型最近发展的也十分迅速&#xff0c;大模型的涌现能力可以很好的迁移到各类任务&#xff0c;于是作者猜想这种能力可不可以应用到多模态模…

物联网常见协议之 Amqp 协议及使用场景解析

引言 本文围绕 AMQP 协议&#xff0c;为大家详细解析 AMQP 协议、核心技术亮点、多协议之间的对比以及使用实践&#xff0c;并介绍华为云 IoT 通过 Amqp 协议如何为开发者和企业提供了更加灵活和高效的通信方式&#xff0c;使得物联网应用得以在各个领域得到更广泛的推广和应用…

项目文档(request页面代码逻辑)

项目文档 1. 封装请求基地址 2. 添加请求拦截器并设置请求头 作用 在向服务器发送请求时,很多请求都需要在请求头中携带token&#xff0c;每一次去请求都写请求头很麻烦&#xff0c;所以我们写一个请求拦截器&#xff0c;统一拦截并添加一个请求头 代码部分 // 请求拦截器 req…