数据结构-双链表(图解)

news2024/11/17 21:57:12

目录

双链表(Double-Linked List)的概念与基本特性

一、双链表的基本组成

二、双链表的主要特性

三、双链表的操作 

代码展示

malloc开辟函数

解析

初始化

解析

头插

解析

尾插

解析

头删

解析

尾删

解析

pos之后插入

解析

pos删除

解析

打印

解析

全部代码展示

main.c

text.c

text.h


双链表(Double-Linked List)的概念与基本特性

双链表是一种常用且重要的线性数据结构,它在计算机科学和软件工程中扮演着不可或缺的角色。相较于单链表,双链表的每个节点包含两个指针,分别指向其前驱节点(previous node)和后继节点(next node),这一特性使得双链表在数据操作上具有更高的灵活性。

一、双链表的基本组成

双链表中的每一个元素称为节点(Node),每个节点通常包含三个部分:

  1. 数据域(Data Field):用于存储实际数据,可以是任何类型的数据。
  2. 前驱指针(Previous Pointer/Backward Pointer):指向当前节点的前一个节点。
  3. 后继指针(Next Pointer/Forward Pointer):指向当前节点的下一个节点。

二、双链表的主要特性

  1. 双向遍历:由于每个节点都有前后两个指针,因此可以在列表中双向遍历,无需像单链表那样只能从头节点开始向前遍历。
  2. 插入与删除的便捷性:在双链表中插入或删除一个节点时,只需改变相应节点的前后节点的指针指向即可,操作相对简单高效。

三、双链表的操作 

常见的双链表操作包括创建、插入(包括头部插入、尾部插入和指定位置插入)、删除(包括头部删除、尾部删除和指定节点删除)、查找以及遍历等。

代码展示

malloc开辟函数
//内存开辟
listcode* inaugurate(LTDataType x) {
	listcode* list = (listcode*)malloc(sizeof(listcode));
	if (list == NULL)
	{
		perror("malloc");
		exit(1);
	}
	//初始值
	list->val = x;
	list->next = list->prev = NULL;

	return list;
}
解析
listcode* inaugurate(LTDataType x)`
 函数定义:`inaugurate`是一个函数,接收一个参数`x`,类型为`LTDataType`。这个函数返回一个指向`listcode`结构体类型的指针。

`listcode* list = (listcode*)malloc(sizeof(listcode));`
- 动态内存分配:使用`malloc`函数动态地在堆内存中分配一块大小等于`listcode`结构体所占用空间的连续区域,并将其地址赋值给`listcode`类型的指针变量`list`。如果内存分配失败,`malloc`将返回NULL。

if (list == NULL)
{
    perror("malloc");
    exit(1);
}
  • 错误处理:检查list是否为空(即内存分配是否成功)。若list为NULL,则说明内存分配失败,调用perror输出错误信息("malloc"),然后调用exit(1)终止程序执行。
list->val = x;
  • 初始化节点数据:将传入的参数x赋值给新创建节点的val字段,这里假设vallistcode结构体中用于存储数据的成员变量。
list->next = list->prev = NULL;
  • 初始化节点指针:将新创建节点的nextprev指针都初始化为NULL,表示这是链表中的一个独立节点,目前既没有前驱也没有后继节点。

最后,函数返回初始化后的节点指针list,这样就可以进一步将此节点添加到现有的双链表中或者其他相关操作。

phead->next = phead;
phead->prev = phead;

初始化
//初始化
listcode* initialize() {
	listcode* phead = inaugurate(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
解析
`listcode* initialize()`
- 函数定义:`initialize`函数无参数,其功能是初始化一个循环双链表,并返回链表的头节点。


listcode* phead = inaugurate(0);
  • 创建头节点:调用上面解释的inaugurate函数初始化并创建一个新的listcode节点,并将一个默认值0赋给节点的val字段。并将新创建的节点指针赋值给phead
phead->next = phead;
phead->prev = phead;
  • 构造循环结构:将头节点的next指针和prev指针都指向自己(即phead),从而形成一个循环结构。在这种情况下,尽管链表中只有一个节点,但它既是头节点也是尾节点,并形成了一个自引用的循环,这样在后续对链表进行操作时(例如插入或删除节点)可以简化边界条件的处理。

最后,函数返回初始化完成的头节点指针phead,标志着一个空的循环双链表已经创建完成。当向这个链表中添加新的节点时,新节点可以被插入到phead节点的前面或后面,同时维护循环双链表的特性。


头插
//头插
void List_Header(listcode* phead, LTDataType x) {
	assert(phead);
	//申请一个节点
	listcode* newnode = inaugurate(x);

	//先讲新节点的链接(prev  next)
	newnode->next = phead->next;
	newnode->prev = phead;
	//再讲其他与newnode链接
	phead->next->prev = newnode;
	phead->next = newnode;
}
解析

List_Header函数用于在已知的循环双链表phead的头部插入一个新节点,新节点的数据值为x。具体步骤如下:

  1. 首先,通过调用inaugurate(x)函数创建一个新节点,并将其数据域设置为输入的值x,得到新节点的指针newnode

  2. 接下来,设置新节点的指针。由于要将新节点插入到头节点之前成为新的头节点,因此将新节点newnodenext指针指向原头节点phead的下一个节点(即原本的头节点之后的第一个节点),将newnodeprev指针指向原头节点phead

  3. 然后更新原链表节点的指针以适应新节点的插入。首先,原头节点的下一个节点(即phead->next)的prev指针应改为指向新节点newnode,这样就完成了新节点与原链表中第二个节点之间的连接。

  4. 最后,修改原头节点pheadnext指针,使其指向新节点newnode,这样新节点就成为了整个循环双链表的新头节点。


尾插
//尾插
void List_Tail(listcode* phead, LTDataType x) {
	assert(phead);
	//申请节点
	listcode* newnode = inaugurate(x);
	//先讲新节点的链接(prev  next)
	newnode->prev = phead->prev;
	newnode->next = phead;
	//改变最后一个节点
	phead->prev->next = newnode;
	//改变头节点
	phead->prev = newnode;
}
解析

List_Tail函数用于在已知的循环双链表phead的尾部插入一个新节点,新节点的数据值为x。以下是详细的文字解析:

  1. 首先,通过调用inaugurate(x)函数创建一个新节点,并将其数据域设置为输入的值x,得到新节点的指针newnode

  2. 设置新节点的指针以插入到尾部。由于这是一个循环链表,尾节点的next指向头节点,所以将新节点newnodeprev指针指向原头节点phead的前一个节点(即原本的尾节点),将newnodenext指针指向原头节点phead

  3. 更新原链表尾节点的指针,使其next指针指向新节点newnode,这样新节点就被正确地链接到链表的末尾。

  4. 最后,调整原头节点pheadprev指针,使其指向新节点newnode,确保链表的循环特性仍然保持,即尾节点的next始终指向头节点。


头删
void List_Header_del(listcode* phead) {
	assert(phead && phead->next != phead);//phead->next != null代表我们的链表就只有哨兵

	//存储一下我们的第一个有效位
	listcode* tmp = phead->next;
	//改变第二个有效位(prev)
	tmp->next->prev = phead;
	//改变哨兵(next)
	phead->next = tmp->next;
	free(tmp);
	tmp = NULL;
}
解析

List_Header_del函数用于删除循环双链表phead头部的有效节点(非哨兵节点)。以下是详细的文字解析:

  1. 首先,通过assert语句确认phead不为空并且phead->next不等于phead。这实际上是在验证链表中至少存在一个有效节点,因为phead->next等于phead仅在链表为空或者只包含一个哨兵节点时成立。

  2. 定义一个临时指针tmp,用来保存待删除的头节点(第一个有效节点)的地址,即tmp = phead->next

  3. 更新待删除节点后面的节点(即第二个有效节点),使其prev指针指向原头节点phead,这样维持了链表的连贯性。

  4. 修改原头节点pheadnext指针,让它指向待删除节点(原头节点后的一个节点),这样新的头节点就变成了原来头节点的下一个节点。

  5. 使用free(tmp)释放掉不再需要的原头节点的内存空间,防止内存泄漏。

  6. 将临时指针tmp置为NULL,虽然在这个函数内部不是必须的,但在某些编程习惯中,为了清晰地表示tmp不再指向有效的内存区域,也会进行这样的操作。


尾删
//尾删
void List_Tail_del(listcode* phead) {
	assert(phead && phead->next != phead);

	listcode* tmp = phead->prev;
	
	tmp->prev->next = phead;
	phead->prev = tmp->prev;

	free(tmp);
	tmp = NULL;
}
解析

List_Tail_del函数用于删除循环双链表phead尾部的有效节点(非哨兵节点)。以下是详细的文字解析:

  1. 首先,通过assert语句确认phead不为空并且phead->next不等于phead,确保链表中至少有一个有效节点存在。

  2. 定义一个临时指针tmp,指向当前尾节点,即tmp = phead->prev

  3. 更新尾节点前一个节点(即倒数第二个节点),使其next指针指向原头节点phead,这样切断了原尾节点与链表的连接,并使新的尾节点成为原尾节点的前一个节点。

  4. 调整原头节点pheadprev指针,使其指向原尾节点的前一个节点(现在的新尾节点),保持链表的循环特性。

  5. 使用free(tmp)释放原尾节点的内存空间,避免内存泄漏。

  6. 将临时指针tmp置为NULL,同样是为了在某些编程风格中清晰地表示tmp不再指向有效的内存区域。


pos之后插入
//pos之后插入
void Pos_tail_del(listcode* pos, LTDataType x) {
	assert(pos);
	listcode* newnode = inaugurate(x);

	//改变newnode指向
	newnode->next = pos->next;
	newnode->prev = pos;
	//改变pos指向
	pos->next->prev = newnode;//pos的下一个节点的prev
	pos->next = newnode;

}
解析

Pos_tail_del函数名称似乎有误,应该是Pos_insert,表示在某个特定位置pos之后插入新节点。以下是详细的文字解析:

  1. 首先,通过assert语句确认pos不为空,确保我们有一个合法的位置来插入新节点。

  2. 调用inaugurate(x)函数创建一个新节点,并将其数据域设置为输入的值x,得到新节点的指针newnode

  3. 设置新节点的指针以便插入到pos节点之后。将新节点newnodenext指针指向pos节点的下一个节点,将newnodeprev指针指向pos节点。

  4. 更新原链表中pos节点的下一个节点,将其prev指针指向新节点newnode,建立新节点与pos节点后继节点间的连接。

  5. 最后,修改pos节点的next指针,使其指向新节点newnode,完成新节点在链表中的插入操作。


pos删除
//pos删除
void Pos_del(listcode* pos) {
	assert(pos);

	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}
解析

Pos_del函数用于删除循环双链表中指定位置的节点pos。以下是详细的文字解析:

  1. 首先,通过assert语句确认pos不为空,确保我们试图删除的是一个存在的节点。

  2. 更新pos节点的后继节点,使其prev指针指向pos节点的前驱节点,这样就断开了pos节点与其后继节点之间的连接。

  3. 同样地,更新pos节点的前驱节点,使其next指针指向pos节点的后继节点,这样就完成了pos节点与其前驱节点之间的连接调整。

  4. 使用free(pos)释放pos指向的节点内存,防止内存泄漏。

  5. pos指针设为NULL,虽然在这段代码中并非必要操作,但在某些编程实践中,这样做有助于明确标识pos不再指向有效的内存地址,以免后续误用。


打印

//打印
void List_print(listcode* phead) {
	assert(phead);
	listcode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->val);
		pcur = pcur->next;
	}
	printf("\n");
}
解析

List_print函数用于打印循环双链表中所有节点的数据值,以下是详细的文字解析:

  1. 首先,通过assert语句确认phead不为空,确保链表存在。

  2. 初始化一个指向当前节点的指针pcur,将其设置为头节点的下一个节点(因为头节点可能是哨兵节点,我们需要从第一个有效数据节点开始打印)。

  3. 使用while循环遍历链表,直到再次回到头节点为止。在循环体内,每次迭代都会打印当前节点pcur的数据值,这里通过printf("%d->", pcur->val);实现。

  4. 在每次循环迭代结束后,将pcur指针移动到下一个节点,即pcur = pcur->next;

  5. pcur再次指向头节点时,循环结束,此时所有的节点数据值已经按照顺序打印完毕。

  6. 循环结束后,打印换行符\n,使得输出结果更易于阅读。


全部代码展示

main.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"

int main() {
	listcode* p = initialize();//初始化了一个哨兵位
	//listcode* p = NULL;//初始化了一个哨兵位
	//initialize(&p);
	/*List_Header(p,1);
	List_print(p);
	List_Header(p,2);
	List_print(p);
	List_Header(p, 3);
	List_print(p);*/
	///*List_Header(p, 4);
	//List_print(p);
	//List_Tail(p, 10);
	//List_print(p);*/
	//List_Header_del(p);
	//List_print(p);
	//List_Header_del(p);
	//List_print(p);

	/*List_Tail(p, 1); 
	List_Tail(p, 2); */
	//List_Tail(p, 3);
	//List_print(p);
	List_Tail(p, 2);
	List_Tail(p, 2);
	List_Tail(p, 2);
	List_print(p);
	/*List_Tail_del(p);
	List_print(p);*/
	listcode* pos = find(p, 2);
	Pos_tail_del(pos, 10);
	List_print(p);
	Pos_del(pos);
	pos = NULL;
		List_print(p);
}
text.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"

//内存开辟
listcode* inaugurate(LTDataType x) {
	listcode* list = (listcode*)malloc(sizeof(listcode));
	if (list == NULL)
	{
		perror("malloc");
		exit(1);
	}
	//初始值
	list->val = x;
	list->next = list->prev = NULL;

	return list;
}



//初始化
listcode* initialize() {
	listcode* phead = inaugurate(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

//void initialize(listcode** phead) {
//
//	*phead = inaugurate(0);
//}

//头插
void List_Header(listcode* phead, LTDataType x) {
	assert(phead);
	//申请一个节点
	listcode* newnode = inaugurate(x);

	//先讲新节点的链接(prev  next)
	newnode->next = phead->next;
	newnode->prev = phead;
	//再讲其他与newnode链接
	phead->next->prev = newnode;
	phead->next = newnode;
}

//尾插
void List_Tail(listcode* phead, LTDataType x) {
	assert(phead);
	//申请节点
	listcode* newnode = inaugurate(x);
	//先讲新节点的链接(prev  next)
	newnode->prev = phead->prev;
	newnode->next = phead;
	//改变最后一个节点
	phead->prev->next = newnode;
	//改变头节点
	phead->prev = newnode;
}

//头删
void List_Header_del(listcode* phead) {
	assert(phead && phead->next != phead);//phead->next != null代表我们的链表就只有哨兵

	//存储一下我们的第一个有效位
	listcode* tmp = phead->next;
	//改变第二个有效位(prev)
	tmp->next->prev = phead;
	//改变哨兵(next)
	phead->next = tmp->next;
	free(tmp);
	tmp = NULL;
}

//尾删
void List_Tail_del(listcode* phead) {
	assert(phead && phead->next != phead);

	listcode* tmp = phead->prev;
	
	tmp->prev->next = phead;
	phead->prev = tmp->prev;

	free(tmp);
	tmp = NULL;
}

//查找函数
listcode* find(listcode* phead, LTDataType x) {

	listcode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->val == x) {//直接遍历比较
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//pos之后插入
void Pos_tail_del(listcode* pos, LTDataType x) {
	assert(pos);
	listcode* newnode = inaugurate(x);

	//改变newnode指向
	newnode->next = pos->next;
	newnode->prev = pos;
	//改变pos指向
	pos->next->prev = newnode;//pos的下一个节点的prev
	pos->next = newnode;

}
//pos删除
void Pos_del(listcode* pos) {
	assert(pos);

	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

//打印
void List_print(listcode* phead) {
	assert(phead);
	listcode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->val);
		pcur = pcur->next;
	}
	printf("\n");
}



void List_Destroyed(listcode* phead) {
	assert(phead);
	listcode* pcur = phead->next;
	
	while (pcur != phead) {
		listcode* tmp = pcur->next;
		free(pcur);
		pcur = tmp;
	}
	free(phead);
	phead = NULL;
}
text.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//#include<vld.h>

typedef int LTDataType;
//创建链表结构
typedef struct liatcode
{
	LTDataType val;//内容
	struct liatcode* next;//指向下一个元素
	struct liatcode* prev;//指向上一个元素
}listcode;



  
//初始化
listcode* initialize();
//头插
void List_Header(listcode* phead, LTDataType x);
//尾插
void List_Tail(listcode* phead, LTDataType x);
//头删
void List_Header_del(listcode* phead);
//尾删
void List_Tail_del(listcode* phead);
//pos之后插入
void Pos_tail_del(listcode* pos, LTDataType x);
//pos删除
void Pos_del(listcode* pos);
//打印
void List_print(listcode* phead);

//销毁
void List_Destroyed(listcode* phead);

listcode* find(listcode* phead, LTDataType x);

总结来说,双链表作为一种灵活、高效的线性数据结构,通过引入前驱和后继指针,极大地提升了数据操作的便利性和效率,是程序设计中不可或缺的一部分。在编写代码实现时,理解和掌握双链表的工作原理及其相关操作至关重要。

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

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

相关文章

抖音小店无货源怎么做?教你新店快速起店新攻略,你还在等什么?

大家好&#xff0c;我是电商花花。 从我关注抖音小店这个风口到现在已经有很长时间了&#xff0c;从了解到实操到复盘&#xff0c;陆陆续续将多家单店店铺月营业额达到数百万。 目前团队运营着80多家店铺&#xff0c;人均管理3-5个店铺&#xff0c;大部分店铺月营业额都在5W-…

【C++】飞机大战项目记录

源代码与图片参考自《你好编程》的飞机大战项目&#xff0c;这里不进行展示。 本项目是仅供学习使用的项目 飞机大战项目记录 飞机大战设计报告1 项目框架分析1.1 敌机设计&#xff1a;1.2 玩家飞机控制&#xff1a;1.3 子弹发射&#xff1a;1.4 游戏界面与互动&#xff1a;1.5…

文本美学:text-image打造视觉吸引力

当我最近浏览 GitHub 时&#xff0c;偶然发现了一个项目&#xff0c;它能够将文字、图片和视频转化为文本&#xff0c;我觉得非常有趣。于是我就花了一些时间了解了一下&#xff0c;发现它的使用也非常简单方便。今天我打算和家人们分享这个发现。 项目介绍 话不多说&#xf…

OpenHarmony 上传和下载(API 10)教程~

介绍 本示例使用ohos.request接口创建上传和下载任务&#xff0c;实现上传、下载功能&#xff0c;hfs作为服务器&#xff0c;实现了文件的上传和下载和任务的查询功能。 效果预览 使用说明 1.本示例功能需要先配置服务器环境后使用&#xff0c;具体配置见上传下载服务配置。…

百度AI大会发布的APP Builder和Agent Builder有什么区别

百度在AI大会发布了三款AI工具&#xff0c;包括智能体开发工具AgentBuilder、AI原生应用开发工具AppBuilder、各种尺寸的模型定制工具ModelBuilder 有很多人就问&#xff0c;APP Builder和Agent Builder有什么不一样&#xff0c;怎么那么多builder? 你们就这么理解&#xff…

大语言模型隐私防泄漏:差分隐私、参数高效化

大语言模型隐私防泄漏&#xff1a;差分隐私、参数高效化 写在最前面题目6&#xff1a;大语言模型隐私防泄漏Differentially Private Fine-tuning of Language Models其他初步和之前的基线微调模型1微调模型2通过低秩自适应进行微调&#xff08; 实例化元框架1&#xff09; 在隐…

MySQL面试题 3

问题1&#xff1a;char、varchar的区别是什么&#xff1f; varchar是变长而char的长度是固定的。如果你的内容是固定大小的&#xff0c;你会得到更好的性能。 问题2: TRUNCATE和DELETE的区别是什么&#xff1f; DELETE命令从一个表中删除某一行&#xff0c;或多行&#xff0…

机器学习系统的设计

1.混淆矩阵 混淆矩阵作用就是看一看在测试集样本集中&#xff1a; 真实值是 正例 的样本中&#xff0c;被分类为 正例 的样本数量有多少&#xff0c;这部分样本叫做真正例&#xff08;TP&#xff0c;True Positive&#xff09;&#xff0c;预测为真&#xff0c;实际为真真实值…

Java反射(reflection)java很多框架的底层都需要用到反射,至于API使用的话,还算简单,主要是类加载过程和反射机制的一个底层机制要了解一下

十六、反射&#xff08;reflection&#xff09; 反射可以通过外部文件配置&#xff0c;在不修改源码的情况下来控制程序&#xff0c;符合设计模式中的OCP原则&#xff08;开闭原则&#xff1a;不修改源码&#xff0c;扩容功能&#xff09;。 1、反射机制 &#xff08;1&…

SpringCloud系列(7)--Eureka服务端的安装与配置

前言&#xff1a;上一章节我们介绍了Eureka的基础&#xff0c;本章节则介绍Eureka服务端的安装与配置 Eureka架构原理图 1、创建Eureka Server端服务注册中心模块 (1)在父工程下新建模块 (2)选择模块的项目类型为Maven并选择模块要使用的JDK版本 (3)填写子模块的名称&#xf…

【C++】:函数重载,引用,内联函数,auto关键字,基于范围的for循环,nullptr关键字

目录 一&#xff0c;函数重载1.1 函数重载的定义1.1.1.形参的类型不同1.1.2参数的个数不同1.1.3.参数的顺序不同1.1.4.有一个是缺省参数构成重载。但是调用时存在歧义1.1.5.返回值不同&#xff0c;不构成重载。因为返回值可接收&#xff0c;可不接受&#xff0c;调用函数产生歧…

如何设计单元测试用例?

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 最近一些大公司在进行去测试化的操作&#xff0c;这一切的根源大概可以从几年前微软一刀切砍掉所…

【深度学习实战(10)】图像推理之预处理

一、预处理流程 在把一张图像送入模型进行推理时&#xff0c;需要先进行预处理&#xff0c;预处理流程包括&#xff1a; &#xff08;1&#xff09;读取图像 &#xff08;2&#xff09;尺寸调整&#xff0c;letter_box&#xff08;不失真&#xff09; &#xff08;3&#xff0…

MATLAB绘制圆锥曲线:抛物线,双曲线,椭圆

MATLAB绘制圆锥曲线:抛物线,双曲线,椭圆 clc;close all;clear all;warning off;%清除变量x linspace(-10, 10, 1000); % 创建一个x值的向量&#xff0c;范围从-10到10&#xff0c;共1000个点 y x.^2; % 计算每个x值对应的y值% 使用plot函数绘制图形 figure; % 创建一个新的图…

排序 “壹” 之插入排序

目录 ​编辑 一、排序的概念 1、排序&#xff1a; 2、稳定性&#xff1a; 3、内部排序&#xff1a; 4、外部排序&#xff1a; 二、排序的运用 三、插入排序算法实现 3.1 基本思想 3.2 直接插入排序 3.2.1 排序过程&#xff1a; 3.2.2 代码示例&#xff1a; 3.2.3…

使用Spring进行文件的上传和下载

概览 使用Spring进行文件的上传和下载Spring上传文件接口设计dubbo接口设计上传文件流的RPC的接口设计 Spring文件下载接口设计dubbo接口设计下载文件流的RPC的接口设计 spring上传文件大小控制 使用Spring进行文件的上传和下载 本文主要介绍在Spring框架下面调用微服务的dubb…

YOLOv9改进策略 | 添加注意力篇 | 利用ILSVRC冠军得主SENetV1改善网络模型特征提取能力

一、本文介绍 本文给大家带来的改进机制是SENet&#xff08;Squeeze-and-Excitation Networks&#xff09;其是一种通过调整卷积网络中的通道关系来提升性能的网络结构。SENet并不是一个独立的网络模型&#xff0c;而是一个可以和现有的任何一个模型相结合的模块(可以看作是一…

项目实践:贪吃蛇

引言 贪吃蛇作为一项经典的游戏&#xff0c;想必大家应该玩过。贪吃蛇所涉及的知识也不是很难&#xff0c;涉及到一些C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等。这里我会介绍贪吃蛇的一些思路。以及源代码也会给大家放到文章末尾。 我们最终的…

【Ne4j图数据库入门笔记1】图形数据建模初识

1.1 图形建模指南 图形数据建模是用户将任意域描述为节点的连接图以及与属性和标签关系的过程。Neo4j 图数据模型旨在以 Cypher 查询的形式回答问题&#xff0c;并通过组织图数据库的数据结构来解决业务和技术问题。 1.1.1 图形数据模型介绍 图形数据模型通常被称为对白板友…

【Gradle如何安装配置及使用的教程】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…