数据结构:双向链表(带头循环)

news2024/11/26 10:39:07

朋友们、伙计们,我们又见面了,本期来给大家解读一下数据结构方面有关双向链表的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通  

数据结构专栏:数据结构

个 人 主 页 :   stackY、

目录

前言:

1.双向链表的认识

 2.链表的实现

2.1链表的创建

2.2结点的创建

2.3初始化链表

2.4从尾部插入数据

2.5从头部插入数据

2.6打印链表

2.7从尾部删除数据

2.8从头部删除数据

2.9在链表中查找数据

2.10修改链表中的数据

2.11在pos之前插入数据

2.12删除pos位置的数据

2.13代码复用

2.14销毁单链表

3.完整代码展示


前言:

 在前面我们了解过单向链表(无头非循环)之后发现单向链表虽然是结构最简单的链表,但是写起来是比较麻烦的,在之前我们提到过链表一共有八种结构,那么都是哪八种结构呢?

链表的分类:

1.带头和不带头。

2.单向和双向。

3.循环和非循环。

这几种结构进行排列组合一共就是有八种链表结构,但是这么多的链表结构我们最常用的只有两种:

1.单向无头非循环链表:

2. 双向带头循环链表:

如果单纯的通过比较两种链表的复杂程度的话单向无头非循环链表就比双向带头循环链表简便好多,但是呢双向带头循环链表的实现代码是比单向无头非循环链表要简单许多,因为双向带头循环链表的结构设计的非常厉害,许多操作可以直接进行,不需要做一些铺垫,那么话不多说,我们直接开始实现双向带头循环链表:

1.双向链表的认识

 双向带头循环链表中有一个哨兵位的头结点,这个哨兵位的头结点是不存放任何数据的,接下来的每一结点(包含哨兵位)中都有两个指针,一个指向前面,一个指向后面,还有一个变量用来存放有效数据,在最后的一个结点的next指针会指向这个链表的头结点,而头结点的prev是指向最后的尾结点的。

当只有一个头结点的时候,这个头结点的prev和next都是指向自己的,这样来构成一个循环结构。

 2.链表的实现

实现双向带头循环链表的步骤和单向链表的步骤是一样的,它们都是具备增删查改的功能。 

在进行插入,删除的时候需不需要使用二级指针的传参?答案是不需要的,因为带头双向循环链表是有一个哨兵位的头结点,这时我们进行插入、删除时改变的是结构体的成员,使用结构体的指针就可以了。

注:小编提供的这种双向链表的写法仅代表个人意见。

2.1链表的创建

链表的实现还是和顺序表的实现一样分模块来写, 当然也是可以在一个源文件中完成的,我们这里创建两个源文件:Test.c和List.c,再创建一个头文件:List.h,这里的文件命名不做要求,表示的有意义就行。同样的我们在Test.c中实现基本逻辑,在Lits.c中实现顺序表的细节,在List.h中进行函数声明和头文件包含等,话不多说,我们直接开始:
 

头文件:List.h

#pragma once

//                     带头双向循环链表 

#include <stdio.h>

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;   //指向前一个结点
	struct ListNode* next;   //指向后一个结点
	LTDataType data;         //存放数据
}LTNode;

2.2结点的创建

链表的结点是malloc动态开辟出来的,所以我们按需申请按需释放,所以为了后面的方便,我们可以直接分装一个函数专门用来创建链表的结点,双向链表的结点的创建于单链表不同的是还需要有一个指向前一个结点的指针:

头文件:List.h

//创建结点
LTNode* BuyLTNode(LTDataType x);

源文件:List.c

//创建结点
LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));  //动态开辟结点
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;

	return newnode;
}

2.3初始化链表

在单向链表中我们讲过是不需要进行初始化的,但是在双向带头循环链表中有一个哨兵位的头结点,并且刚创建的链表只有一个哨兵位,所以为了达到循环,我们需要将它的prev和next都指向它自己:

头文件:List.h

//初始化链表
LTNode* LTInit();

源文件:Lits.c

//初始化
LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(-1);  //哨兵位在这里是不存储任何有效数据,
                                    //所以传给它什么值都可以
	//实现循环,指向自己
	phead->next = phead;       
	phead->prev = phead;

	return phead;
}

源文件:Test.c

#include "List.h"

void LTNodeTest1()
{
	LTNode* plist = LTInit();  //哨兵位的头结点
}

int main()
{

	LTNodeTest1();
	
	return 0;
}

2.4从尾部插入数据

从尾部插入数据首先得找到尾,那么这时就用到了头指针的prev,prev指向的就是尾,然后将要插入的结点链接在链表中。需要将尾结点的next指向要插入的结点,将新插入的结点的prev指向尾结点,然后让头结点的prev指向要插入的结点,再将要插入的结点的next指向头,这样子就大功告成了。

有多个结点:

 只有一个结点:

头文件:List.h

//尾插
void LTPushBack(LTNode* phead, LTDataType x); 

源文件:List.c

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);  //创建要插入的结点

	//找尾
	LTNode* tail = phead->prev;   

	//进行链接
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

2.5从头部插入数据

从头部插入数据就更加简单了,就是将一个新结点直接插入到哨兵位的后面,这里还要注意,如果你不保存哨兵位的下一个结点,插入时对对于指针指向的改变就要有顺序,先让原来的头结点后面的结点的prev指向新的结点,然后再让头结点的next指向新节点,但是如果将头结点后面的结点存起来就跟顺序没有关系,小编推荐大家使用存起来的方法,因为这样也不容易出错,当然了,还是按照个人喜好:

有多个结点:

只有一个结点:

头文件:List.h

//头插
void LTPushFront(LTNode* phead, LTDataType x);

源文件:List.c

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	//保存结点,方便使用
	LTNode* first = phead->next;  

	//创建要插入的结点
	LTNode* newhead = BuyLTNode(x);

	//链接新的结点
	phead->next = newhead;
	newhead->prev = phead;

	newhead->next = first;
	first->prev = newhead;
}

2.6打印链表

在之前的打印单向链表中我们使用链表何时为空来作为判断标志,但是在双向链表中可不敢再用这个条件作为判断依据,应该以哨兵位的头结点作为判断依据, 创建一个指向头指针后面的结点的指针用来迭代遍历链表,当这个指针不为头指针时就继续遍历,当等于头指针的时候就表示遍历一遍已经结束了,所以在这里我们需要停止:

头文件:List.h

//打印
void LTPrint(LTNode* phead);

源文件:List.c

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;  //头结点后面的结点
	printf("phead<==>");
	//遍历
	while (cur!= phead)   
	{
		printf("%d<==>", cur->data);
		//迭代
		cur = cur->next;
	}
	printf("\n");
}

当写好了打印函数我们可以将我们前面写的尾插和头插来测试一下:

源文件:Test.c

void LTNodeTest1()
{
	LTNode* plist = LTInit();  //哨兵位的头结点

	//头插
	LTPushFront(plist, 2);
	LTPushFront(plist, 1);

	//尾插
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);

	//打印
	LTPrint(plist);
}

int main()
{
	LTNodeTest1();
	return 0;
}

2.7从尾部删除数据

从尾部删除数据首先要找尾结点,再找到尾结点的前一个结点,然后将尾的前一个结点和头结点链接,再将尾结点释放,但是这里要注意如果只有一个头结点时是不能删除的,所以需要再加上对链表的头结点的判断:

 

只有头结点时就不能再删除了,因此当head的next是head时就要停止,因为后面的头删也存在这样的问题,所以我们干脆分装一个函数来实现:

源文件:List.c

//判断链表是否为空
bool LTEmpty(LTNode* phead)
{
	assert(phead);

	//如果等于phead就返回的是true(真),不是就返回false(假)
	return phead->next == phead;  
}

注:这里的判断函数不一定要使用这个,只要能判断出来是否只有一个头结点就行,按照自己喜欢的代码风格来写

头文件:List.h

//尾删
void LTPopBack(LTNode* phead);

源文件:List.c

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	//判断链表是否只有头结点
	assert(!LTEmpty(phead));

	//找尾结点和尾结点的前一个结点
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	//链接尾结点的前一个结点和头结点
	tailPrev->next = phead;
	phead->prev = tailPrev;
	//释放掉尾结点完成尾删
	free(tail);
}

2.8从头部删除数据

头删就更简单了,只需要找到头结点的下一个结点first,和头结点的下一个结点的下一个结点second,然后将头结点与second结点链接,再释放掉first,当然也是要判断链表中是否只有一个头结点:

头文件:List.h

//头删
void LTPopFront(LTNode* phead);

源文件:List.c


//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	//判断链表是否只有一个头结点
	assert(!LTEmpty(phead));

	
	LTNode* first = phead->next;
	LTNode* second = first->next;

	//链接
	phead->next = second;
	second->prev = phead;
	//删除
	free(first);
}

2.9在链表中查找数据

查找数据与单向链表的查找数据一样,只是遍历链表的条件不同,找到了就返回结点的地址,找不到就返回NULL:

头文件:List.h 

//查找
LTNode* LTFind(LTNode* phead, LTDataType x);

源文件:List.c

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* cur = phead->next;
	//遍历链表
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}
	return NULL;
}

2.10修改链表中的数据

修改链表中的数据是需要配合查找数据来使用,既然我们已经查找到了这个结点的地址,那么将它存起来然后进行修改就可以了,在这里我们来演示一下:

源文件:Test.c

void LTNodeTest1()
{
	LTNode* plist = LTInit();  //哨兵位的头结点

	//头插
	LTPushFront(plist, 2);
	LTPushFront(plist, 1);
	//尾插
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	//打印
	LTPrint(plist);

	//头删
	LTPopFront(plist);
	//尾删
	LTPopBack(plist);
	//打印
	LTPrint(plist);

	//查找
	LTNode* pos = LTFind(plist, 3);
	//修改
	if (pos != NULL)
	{
		pos->data = 2;
	}
	//打印
	LTPrint(plist);

}

int main()
{
	LTNodeTest1();
	return 0;
}

 

2.11在pos之前插入数据

在指定位置插入数据也是要配合查找函数来完成,先通过查找函数找到pos,然后找到pos的前一个结点,然后将将新节点链接就可以了,这里只需改变四个指针的位置即可:

 头文件:Lits.h

//在pos位置之前插入
void LTInsert(LTNode* pos, LTDataType x);

源文件:List.c

//在pos位置之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	//找pos的前一个结点
	LTNode* posPrev = pos->prev;
	LTNode* newnode = BuyLTNode(x);

	//链接
	posPrev->next = newnode;
	newnode->prev = posPrev;

	newnode->next = pos;
	pos->prev = newnode;
}

2.12删除pos位置的数据

删除pos位置的数据也是要通过查找函数来找到pos这个结点,然后记录pos的前一个结点和pos的后一个结点,然后将pos的前一个结点和pos的后一个结点链接,将pos结点释放掉:

 

头文件:List.h

//删除pos位置上的数据
void LTErase(LTNode* pos);

源文件:List.c

//删除pos位置上的数据
void LTErase(LTNode* pos)
{
	assert(pos);
	
	//找前一个结点
	LTNode* posPrev = pos->prev;
	//找后一个结点
	LTNode* posNext = pos->next;

	//链接
	posPrev->next = posNext;
	posNext->prev = posPrev;
	//释放
	free(pos);
}

2.13代码复用

当我们写好了在指定位置的插入和删除时,那么头插、尾插、头删、尾删都可以复用这些函数,只需要传递相应的结点地址就可以了:

尾插:

LTInsert(phead, x);

头插:

LTInsert(phead->next, x);

尾删:

LTErase(phead->prev);

头删:

LTErase(phead->next);

代码演示:

源文件:List.c

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	//代码复用
	LTInsert(phead, x);
	//LTNode* newnode = BuyLTNode(x);  //创建要插入的结点

	找尾
	//LTNode* tail = phead->prev;   

	进行链接
	//tail->next = newnode;
	//newnode->prev = tail;
	//newnode->next = phead;
	//phead->prev = newnode;
	
}

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	//判断链表是否只有头结点
	assert(!LTEmpty(phead));

	//代码复用
	LTErase(phead->prev);

	找尾结点和尾结点的前一个结点
	//LTNode* tail = phead->prev;
	//LTNode* tailPrev = tail->prev;

	链接尾结点的前一个结点和头结点
	//tailPrev->next = phead;
	//phead->prev = tailPrev;
	释放掉尾结点完成尾删
	//free(tail);
}


//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	//代码复用
	LTInsert(phead->next, x);

	保存结点,方便使用
	//LTNode* first = phead->next;  

	创建要插入的结点
	//LTNode* newhead = BuyLTNode(x);

	链接新的结点
	//phead->next = newhead;
	//newhead->prev = phead;

	//newhead->next = first;
	//first->prev = newhead;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	//判断链表是否只有一个头结点
	assert(!LTEmpty(phead));

	//代码复用
	LTErase(phead->next);

//	LTNode* first = phead->next;
//	LTNode* second = first->next;
//
//	//链接
//	phead->next = second;
//	second->prev = phead;
//	//删除
//	free(first);
}

源文件:Test.c

void LTNodeTest2()
{
	LTNode* plist = LTInit();  //哨兵位的头结点

	//头插
	LTPushFront(plist, 2);
	LTPushFront(plist, 1);
	//尾插
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	//打印
	LTPrint(plist);

	//查找
	LTNode* pos1 = LTFind(plist, 3);
	
	//在pos之前插入
	LTInsert(pos1, 3);
	//打印
	LTPrint(plist);

	//删除pos位置的值
	LTNode* pos2 = LTFind(plist, 1);
	LTErase(pos2);
	//打印
	LTPrint(plist);
}

int main()
{
	//LTNodeTest1();
	LTNodeTest2();

	return 0;
}

2.14销毁单链表

在使用完链表之后我们需要对其进行销毁,因为结点都是动态开辟的,所以需要进行手动释放,否则会造成内存泄漏:

头文件:Lits.h

//销毁链表
void LTDestroy(LTNode* phead);

源文件:List.c

//销毁链表
void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;

	//遍历依次进行释放
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

3.完整代码展示

头文件:List.h

#pragma once

//                     带头双向循环链表 

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

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;   //指向前一个结点
	struct ListNode* next;   //指向后一个结点
	LTDataType data;         //存放数据
}LTNode;


//初始化链表
LTNode* LTInit();

//创建结点
LTNode* BuyLTNode(LTDataType x);

//尾插
void LTPushBack(LTNode* phead, LTDataType x); 

//尾删
void LTPopBack(LTNode* phead);

//打印
void LTPrint(LTNode* phead);

//头插
void LTPushFront(LTNode* phead, LTDataType x);

//头删
void LTPopFront(LTNode* phead);

//查找
LTNode* LTFind(LTNode* phead, LTDataType x);

//在pos位置之前插入
void LTInsert(LTNode* pos, LTDataType x);

//删除pos位置上的数据
void LTErase(LTNode* pos);

//销毁链表
void LTDestroy(LTNode* phead);

源文件:List.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "List.h"

//创建结点
LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));  //动态开辟结点
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;

	return newnode;
}

//初始化
LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(-1);  //哨兵位在这里是不存储任何有效数据,所以传给它什么值都可以
	//实现循环,指向自己
	phead->next = phead;       
	phead->prev = phead;

	return phead;
}

//判断链表是否为空
bool LTEmpty(LTNode* phead)
{
	assert(phead);

	//如果等于phead就返回的是true(真),不是就返回false(假)
	return phead->next == phead;  
}

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	//代码复用
	LTInsert(phead, x);
	//LTNode* newnode = BuyLTNode(x);  //创建要插入的结点

	找尾
	//LTNode* tail = phead->prev;   

	进行链接
	//tail->next = newnode;
	//newnode->prev = tail;
	//newnode->next = phead;
	//phead->prev = newnode;
	
}

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	//判断链表是否只有头结点
	assert(!LTEmpty(phead));

	//代码复用
	LTErase(phead->prev);

	找尾结点和尾结点的前一个结点
	//LTNode* tail = phead->prev;
	//LTNode* tailPrev = tail->prev;

	链接尾结点的前一个结点和头结点
	//tailPrev->next = phead;
	//phead->prev = tailPrev;
	释放掉尾结点完成尾删
	//free(tail);
}

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;  //头结点后面的结点
	printf("phead<==>");
	//遍历
	while (cur!= phead)   
	{
		printf("%d<==>", cur->data);
		//迭代
		cur = cur->next;
	}
	printf("\n");
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	//代码复用
	LTInsert(phead->next, x);

	保存结点,方便使用
	//LTNode* first = phead->next;  

	创建要插入的结点
	//LTNode* newhead = BuyLTNode(x);

	链接新的结点
	//phead->next = newhead;
	//newhead->prev = phead;

	//newhead->next = first;
	//first->prev = newhead;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	//判断链表是否只有一个头结点
	assert(!LTEmpty(phead));

	//代码复用
	LTErase(phead->next);

//	LTNode* first = phead->next;
//	LTNode* second = first->next;
//
//	//链接
//	phead->next = second;
//	second->prev = phead;
//	//删除
//	free(first);
}

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* cur = phead->next;
	//遍历链表
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}
	return NULL;
}


//在pos位置之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	//找pos的前一个结点
	LTNode* posPrev = pos->prev;
	LTNode* newnode = BuyLTNode(x);

	//链接
	posPrev->next = newnode;
	newnode->prev = posPrev;

	newnode->next = pos;
	pos->prev = newnode;
}

//删除pos位置上的数据
void LTErase(LTNode* pos)
{
	assert(pos);
	
	//找前一个结点
	LTNode* posPrev = pos->prev;
	//找后一个结点
	LTNode* posNext = pos->next;

	//链接
	posPrev->next = posNext;
	posNext->prev = posPrev;
	//释放
	free(pos);
}

//销毁链表
void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;

	//遍历依次进行释放
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

源文件:Test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "List.h"

void LTNodeTest1()
{
	LTNode* plist = LTInit();  //哨兵位的头结点

	//头插
	LTPushFront(plist, 2);
	LTPushFront(plist, 1);
	//尾插
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	//打印
	LTPrint(plist);

	//头删
	LTPopFront(plist);
	//尾删
	LTPopBack(plist);
	//打印
	LTPrint(plist);

	//查找
	LTNode* pos = LTFind(plist, 3);
	//修改
	if (pos != NULL)
	{
		pos->data = 2;
	}
	//打印
	LTPrint(plist);

}

void LTNodeTest2()
{
	LTNode* plist = LTInit();  //哨兵位的头结点

	//头插
	LTPushFront(plist, 2);
	LTPushFront(plist, 1);
	//尾插
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	//打印
	LTPrint(plist);

	//查找
	LTNode* pos1 = LTFind(plist, 3);
	
	//在pos之前插入
	LTInsert(pos1, 3);
	//打印
	LTPrint(plist);

	//删除pos位置的值
	LTNode* pos2 = LTFind(plist, 1);
	LTErase(pos2);
	//打印
	LTPrint(plist);
}

int main()
{
	//LTNodeTest1();
	LTNodeTest2();

	return 0;
}

今天的博客就分享到这里,喜欢的老铁留下你的三连,感谢感谢!我们下期再见!! 

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

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

相关文章

时至今日,Linux会开源,也是一种态度

什么是开源&#xff1f;开源通常指开发者公开系统/应用程序源代码。通过对代码进行共享和重用&#xff0c;可以快速开发出高质量、低维护成本的应用程序。这意味着你不再需要花很多时间来学习新技术或编写复杂的代码。 一、Linux永远的神 就拿linux来举例子。 Linux系统的发起…

分享Python采集190个jQuery代码,总有一款适合您

分享Python采集190个jQuery代码&#xff0c;总有一款适合您 Python采集的190个jQuery代码下载链接&#xff1a;https://pan.baidu.com/s/1KxEOw7IfgZJq7yhYBM1nwg?pwdz3r1 提取码&#xff1a;z3r1 可拖拽的谷歌样式纯javascript模态窗口插件 简单实用的轻量级jQuery评分插…

ubuntu系统配置大恒相机驱动并读取ros话题

文章目录 0. 说明1. 安装大恒相机sdk1.1 下载1.2 安装sdk(用于配置ip和调试相机参数)(1) 电脑网卡配置(网卡固定ip)(2)查看相机图像以及配置相机参数 2. 安装ros驱动包(注&#xff1a;大恒相机官方没ros驱动)2.0 正确流程2.1 错误示范2.1 报错1--缺包2.2 报错2--包编译顺序问题…

CnOpenData缺陷产品召回数据

一、数据简介 缺陷产品召回&#xff0c;是指缺陷产品的生产商、销售商、进口商在得知其生产、销售或进口的产品存在可能引发消费者健康、安全问题的缺陷时&#xff0c;依法向职能部门报告&#xff0c;及时通知消费者&#xff0c;设法从市场上、消费者手中收回缺陷产品&#xff…

Python神经网络学习(六)--机器学习--强化学习

前言&#xff1a; 属实是失踪人口回归了。继续神经网络系列。 强化学习&#xff1a; 强化学习也是一个很重要的方向了&#xff0c;很多人用强化学习玩游戏&#xff0c;可能有人觉得强化学习很难&#xff08;包括我&#xff09;&#xff0c;但是我今天用网上流传很广的、很经…

error: static assertion failed: std::atomic requires a trivially copy type

1. 报错信息 编译期错误&#xff0c;gcc version 7.5.0 错误示例代码&#xff1a; #include <atomic> #include <iostream> #include <vector>int main() {std::atomic<std::vector<int>> a; }2. 问题分析 报错信息里明确说了&#xff0c;ato…

商品说明书的翻译,中译英如何翻译效果好?

众所周知&#xff0c;国内产品进入国际市场&#xff0c;商品说明书的翻译是必不可少的&#xff0c;译文必须以准确的语言表达出原文的信息。那么&#xff0c;针对商品说明书翻译&#xff0c;中译英如何翻译效果好&#xff1f; 业内人士指出&#xff0c;很多商品说明书包含有关产…

学习PCL库:PCL库中的geometry模块介绍

公众号致力于点云处理&#xff0c;SLAM&#xff0c;三维视觉&#xff0c;高精地图等领域相关内容的干货分享&#xff0c;欢迎各位加入&#xff0c;有兴趣的可联系dianyunpcl163.com。未经作者允许请勿转载&#xff0c;欢迎各位同学积极分享和交流。 geometry模块介绍 PCL库中的…

【万字更新】Python基础教程:第六章_数据容器

原创&#xff1a;公众号 数据说话 【万字更新】Python基础教程&#xff1a;第六章_数据容器 为什么学习数据容器 思考一个问题&#xff1a;如果我想要在程序中&#xff0c;记录5名学生的信息&#xff0c;如姓名。 最好的方法是不是简单的定义5个字符串变量就可以了 name1&…

基于AT89C51单片机的计算器设计

点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/87772564 源码获取 主要内容: 本设计是基于51系列的单片机进行的设计,可以完成计算器的键盘输入,进行加、减、3位无符号数字的简单运算,并在LED上相应的显示结果。设计过…

TOB企业如何借助生态力,实现可持续增长

近年来&#xff0c;随着经济社会的高速发展&#xff0c;数字化转型已成为企业高质量发展“必答题”。企业开始通过购买产品、解决方案或者自研的方式来进行本企业的数字化建设。但是由于内部部门墙或者是系统之间的隔阂&#xff0c;难以做到以整个公司为视角的全面数字化建设&a…

数据库专题:数据库初学者的数据一致性

在这篇文章中&#xff0c;我将分享我在数据库学习课程中学到的知识&#xff0c;了解到目前为止让我着迷的数据库主题。 ​1&#xff1a;序言 在 2022 年底&#xff0c;当主题是数据库时&#xff0c;我决定把事情弄清楚&#xff0c;因为这总是一种痛苦&#xff0c;作为后端开发…

虚拟汽车加油问题——算法设计与分析(C实现)

目录 一、问题描述 二、问题剖析 三、代码实现 四、结果验证 一、问题描述 问题描述&#xff1a;一辆虚拟汽车加满油后可行驶n km。旅途中有若干加油站。设计一个有效算法&#xff0c;指出应该在那些加油站停靠加油&#xff0c;使沿途加油次数最少。并证明算法的能产生一个…

安卓Webview网页秒开策略探索

1 人赞同了该文章 痛点是什么&#xff1f; 网页加载缓慢&#xff0c;白屏&#xff0c;使用卡顿。 为何有这种问题&#xff1f; 1.调用loadUrl()方法的时候&#xff0c;才会开始网页加载流程 2.js臃肿问题 3.加载图片太多 4.webview本身问题 webiew是怎么加载网页的呢&…

品优购项目学习记录--01公共模块制作

文章目录 一、品优购项目规划1.1 开发工具以及技术栈1.1.1 开发工具1.1.2 技术栈 1.2 品优购项目搭建工作1.2.1 相关文件夹以及文件创建1.2.2 模块化开发1.2.3 网站favicon图标1.2.4 网站TDK三大标签SEO优化 二、品优购首页制作2.1 常用模块类名命名2.2 快捷导航shortcut制作2.…

传统的二次开发有哪些痛点问题?低代码平台帮你解决

一、什么是二次开发呢&#xff1f; 简单的来讲&#xff0c;二次开发就是在原有得软件中进行功能等方面得修改或者扩展&#xff0c;但是不改变原有系统的内核。 二、传统的二次开发有哪些痛点问题&#xff1f; 很多企业在业务发展的过程中会产生各种各样不同得需求&#xff0…

融云亮相「中国信息技术应用创新大会」,入选数字化转型优秀方案集

4 月 27 日&#xff0c;以“全栈创新 从可用到好用”为主题的“2023 第六届中国信息技术应用创新大会”在京顺利召开。移步【融云全球互联网通信云】回复“地图”限量免费领《社交泛娱乐出海作战地图》 大会以“论坛展示展览”的方式&#xff0c;全面、深入地反映信创产业的最新…

基于Android studio的机票管理app设计与开发案例

一 功能介绍 1. 用户模式功能&#xff1a; 用户注册登录功能&#xff08;账号、密码&#xff09;&#xff1b;航班信息&#xff08;航班号、起飞时间、登机时间、起点、终点、登机口&#xff09;&#xff1b;购买机票机票信息&#xff08;航班号、审核状态、乘客姓名、联系方…

Windows平台Qt超详细安装——5.9.6版本以及5.9都差不多,(仔细看,一定学会,学不会怪我)

目录 一、Qt 开发环境&#xff08;Windows&#xff09; 二、设置 QtCreator 编译路径 三、设置 Qt 源码路径 四、QtCreator 介绍 一、Qt 开发环境&#xff08;Windows&#xff09; ①官网下载地址&#xff1a;&#xff08;对应版本&#xff0c;可以在这个网址下面自己找&a…

字节给的比我想的还多?网友看完:打死也要去

曾经的互联网是PC的时代&#xff0c;随着智能手机的普及&#xff0c;移动互联网开始飞速崛起。而字节跳动抓住了这波机遇&#xff0c;2015年&#xff0c;字节跳动全面加码短视频&#xff0c;从那以后&#xff0c;抖音成为了字节跳动用户、收入和估值的最大增长引擎。 自从字节…