【数据结构】手把手教你单链表(c语言)(附源码)

news2024/9/24 13:15:53

🌟🌟作者主页:ephemerals__

🌟🌟所属专栏:数据结构

目录

前言

1.单链表的概念与结构

2.单链表的结构定义

3.单链表的实现

3.1 单链表的方法声明

3.2 单链表方法实现

3.2.1 打印链表

3.2.2 创建新节点

3.2.3 尾插

3.2.4 头插

3.2.5 尾删

3.2.6 头删

3.2.7 查找

3.2.8 指定位置之前插入

3.2.9 指定位置之后插入

3.2.10 删除指定位置的节点

3.2.11 销毁链表

4.程序全部代码

总结


前言

        之前我们学习了顺序表,基于顺序表的结构和实现方式,它有以下缺陷

1.指定位置、头部的插入/删除的时间复杂度是O(N),效率并不是很高。

2.在增容时,需要申请额外的空间,当连续的空间不足时,就需要重新开辟空间并且拷贝数据,消耗较大。

3.由于增容操作每次都是以2倍的形式增长,所以势必会造成一定的空间浪费。

如何解决以上问题呢?这就需要我们学习一个新的数据结构:单链表

1.单链表的概念与结构

链表的概念:链表是一种数据内存地址不连续、但是逻辑顺序连续的数据结构。它的逻辑顺序由链表中节点的指针相连接。

节点:由两部分组成:存储数据元素的部分称之为“数据域”,存放其他节点地址的部分称之为“指针域”。每一个数据元素存放于一个“节点”中。

单链表,也叫做单向链表,它的节点的指针域中存放的是下一个节点的地址。这样节点与节点之间互相连接,就像链条一样将数据串联起来。

单链表的结构如图:

可以看到,单链表就像火车一样,而每一个节点就相当于是一节车厢,它们之间用指针串联在一起。注意:单链表只能做到由前一个节点找到后一个几点,无法逆转;最后一个节点的指针域为空指针。

2.单链表的结构定义

        我们在定义单链表的结构时,定义的是它的节点的结构。代码如下:

typedef int SLTDataType;

//定义单链表的节点
typedef struct SListNode
{
	SLTDataType data;//数据域
	struct SListNode* next;//指针域
}SLTNode;

可以看到,它的指针域是指向自己本身类型的指针,这种定义方式也叫做结构体的自引用。它可以使得该节点能够存放一个相同类型节点的地址,并且进行访问操作

3.单链表的实现

3.1 单链表的方法声明

        单链表的一些常用方法的声明如下:

//打印链表
void SLTPrint(SLTNode* phead);

//创建新节点
SLTNode* SLTBuyNode(SLTDataType n);

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType n);

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType n);

//尾删
void SLTPopBack(SLTNode** pphead);

//头删
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType n);

//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType n);

//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType n);

//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos);

//销毁链表
void SLTDestroy(SLTNode** pphead);

接下来,我们尝试逐一实现以上方法。

3.2 单链表方法实现

3.2.1 打印链表

        打印链表时,我们需要定义一个指针,通过它遍历链表并访问它的数据元素:

//打印链表
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;//定义指针指向头节点
	while (cur != NULL)//最后一个节点的next为空,cur等于空则说明遍历结束
	{
		printf("%d ", cur->data);//访问数据并打印
		cur = cur->next;//对cur解引用拿到下一个节点的地址,然后赋值给cur,cur就指向了下一个节点
	}
	printf("\n");
}

这里我们需要注意理解语句“cur = cur->next”,由于next存放的是下一个节点的地址,所以将其赋值给cur,cur就指向了下一个节点,循环往复,就达到了遍历的效果。

3.2.2 创建新节点

        在我们进行元素插入操作时,往往要将数据存放在一个节点当中,然后将这个节点插入链表。所以我们将创建节点的操作封装成一个函数:

//创建新节点
SLTNode* SLTBuyNode(SLTDataType n)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//动态开辟一个节点大小的内存
	if (newnode == NULL)//内存开辟失败,则直接退出程序
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = n;//将数据赋值给节点的数据域
	newnode->next = NULL;//为了确保链表末尾为空指针,所以创建的所有节点默认next为空
	return newnode;//将节点返回
}

3.2.3 尾插

        接下来我们学习尾插操作。既然要在链表尾部插入数据,那么就需要我们顺着链表的头节点找到尾部的节点,然后将其指针域指向我们的新节点就好。代码如下:

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType n)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(n);//创建新节点
	if (*pphead == NULL)//头指针为空说明链表为空
	{
		//链表为空,此时插入第一个元素,需要将头指针指向新节点,
		//而在函数内修改头指针就要传入头指针的地址,也就是二级指针
		*pphead = newnode;
	}
	else//链表不为空的情况
	{
		SLTNode* cur = *pphead;
		while (cur->next != NULL)//从头节点开始,循环遍历找到最后一个节点
		{
			cur = cur->next;
		}
		cur->next = newnode;//将新节点的地址赋值给最后一个节点的指针域
	}
}

这里需要注意:当链表为空时,如果我们进行循环遍历,就会发生对空指针解引用的错误,所以直接使头指针指向新节点就好。由于要在函数体内改变参数的值,并且参数是一个一级指针变量,所以要传入一级指针的地址,也就是二级指针。

3.2.4 头插

        对于头插操作,我们需要将新节点的next指向原来的第一个节点,然后将头指针指向新节点。

我们画图表示一下:

代码实现:

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType n)
{
	assert(pphead);//确保传入的不是空指针
	SLTNode* newnode = SLTBuyNode(n);//创建新节点
	newnode->next = *pphead;//使新节点的next指针指向原来的第一个节点
	*pphead = newnode;//头指针指向新节点
}

注意:最后两句代码的顺序不能颠倒,因为如果先让头指针指向新节点,原来的链表的地址就会丢失,无法访问到了。

3.2.5 尾删

        进行尾删操作时,我们也需要遍历链表,找到链表的末尾并释放内存。实际操作要做一些特殊情况和细节的处理:

//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);//确保传入的不是空指针并且链表不为空
	if ((*pphead)->next = NULL)//链表只有一个节点的情况
	{
		free((*pphead)->next);//释放该节点的空间
		*pphead == NULL;//改变了头节点的值,所以也要传二级指针
	}
	else//节点大于1的情况
	{
		SLTNode* prev = *pphead;
		while (prev->next->next != NULL)//循环遍历,使prev指向倒数第二个节点
		{
			prev = prev->next;
		}
		free(prev->next);//释放最后一个节点的空间
		prev->next = NULL;//将此时的最后一个节点的next制为空
	}
}

3.2.6 头删

        对于头删操作,我们需要记录第二个节点,然后再将第一个节点释放,最后使头指针指向记录的节点即可。当链表只有一个节点时,我们记录的就是NULL,最后将NULL赋值给头指针也合情合理。无需分类讨论。

//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;//保存第二个节点的地址/空指针(只有一个节点时)
	free(*pphead);//释放第一个节点的空间
	*pphead = next;//让头指针指向刚才保存的节点/空指针,也要传二级指针
}

3.2.7 查找

        查找操作十分简单,只需要遍历链表,如果有匹配的节点,将其地址返回即可。

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType n)
{
	SLTNode* cur = phead;
	while (cur != NULL)//遍历链表的所有节点
	{
		if (cur->data == n)//匹配成功,返回该节点的地址
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;//没有找到,返回空指针
}

3.2.8 指定位置之前插入

        进行指定位置之前插入时,要进行分类讨论:如果指定位置是头节点,则进行头插;其他情况遍历找到该节点的前驱节点prev,然后进行插入操作:

代码实现:

//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType n)
{
	assert(pphead && pos);
	SLTNode* newnode = SLTBuyNode(n);
	if (*pphead == pos)//指定位置是头节点的情况
	{
		//进行头插
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//遍历找到pos节点的前驱节点
		{
			prev = prev->next;
		}
		newnode->next = pos;//新节点的next指针指向pos
		prev->next = newnode;//前驱节点的next指针指向新节点
	}
}

3.2.9 指定位置之后插入

        对于指定位置之后插入元素,由于已经找到了前驱节点和后继节点,相比就没有那么麻烦了,只需要直接插入即可。

代码实现:

//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType n)
{
	assert(pos);//确保pos不为空指针
	SLTNode* newnode = SLTBuyNode(n);
	newnode->next = pos->next;//newnode的next指向后继节点
	pos->next = newnode;//前驱节点的next指向newnode
}

3.2.10 删除指定位置的节点

        对于指定位置的删除,我们需要分类讨论:如果此位置是头节点,就进行头删;否则就要找到其前驱节点和后继节点,然后进行删除操作。

//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && pos && *pphead);//确保传入的不是空指针并且链表不为空
	if (pos == *pphead)//指定位置是头节点情况
	{
		//头删
		*pphead = pos->next;//先使头指针指向第二个节点
		free(pos);//释放掉pos节点
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//循环遍历,找到pos的前驱节点
		{
			prev = prev->next;
		}
		prev->next = pos->next;//使前驱节点的next指针指向pos的后继节点
		free(pos);//释放掉pos节点
	}
	pos = NULL;//对野指针及时制空
}

3.2.11 销毁链表

        当我们使用完链表之后,应当及时释放掉链表的所有节点内存,这个过程称之为销毁链表。代码如下:

//销毁链表
void SLTDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;//从头节点开始遍历
	while (cur != NULL)
	{
		SLTNode* next = cur->next;//先记录下一个节点
		free(cur);//释放当前节点
		cur = next;//释放后,cur指向记录的节点
	}
	*pphead = NULL;//将头指针制空
}

4.程序全部代码

        程序全部代码如下:

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

typedef int SLTDataType;

//定义单链表的节点
typedef struct SListNode
{
	SLTDataType data;//数据域
	struct SListNode* next;//指针域
}SLTNode;

//打印链表
void SLTPrint(SLTNode* phead);

//创建新节点
SLTNode* SLTBuyNode(SLTDataType n);

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType n);

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType n);

//尾删
void SLTPopBack(SLTNode** pphead);

//头删
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType n);

//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType n);

//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType n);

//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos);

//销毁链表
void SLTDestroy(SLTNode** pphead);

//打印链表
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;//定义指针指向头节点
	while (cur != NULL)//最后一个节点的next为空,cur等于空则说明遍历结束
	{
		printf("%d ", cur->data);//访问数据并打印
		cur = cur->next;//对cur解引用拿到下一个节点的地址,然后赋值给cur,cur就指向了下一个节点
	}
	printf("\n");
}

//创建新节点
SLTNode* SLTBuyNode(SLTDataType n)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//动态开辟一个节点大小的内存
	if (newnode == NULL)//内存开辟失败,则直接退出程序
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = n;//将数据赋值给节点的数据域
	newnode->next = NULL;//为了确保链表末尾为空指针,所以创建的所有节点默认next为空
	return newnode;//将节点返回
}

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType n)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(n);//创建新节点
	if (*pphead == NULL)//头指针为空说明链表为空
	{
		//链表为空,此时插入第一个元素,需要将头指针指向新节点,
		//而在函数内修改头指针就要传入头指针的地址,也就是二级指针
		*pphead = newnode;
	}
	else//链表不为空的情况
	{
		SLTNode* cur = *pphead;
		while (cur->next != NULL)//从头节点开始,循环遍历找到最后一个节点
		{
			cur = cur->next;
		}
		cur->next = newnode;//将新节点的地址赋值给最后一个节点的指针域
	}
}

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType n)
{
	assert(pphead);//确保传入的不是空指针
	SLTNode* newnode = SLTBuyNode(n);//创建新节点
	newnode->next = *pphead;//使新节点的next指针指向原来的第一个节点
	*pphead = newnode;//头指针指向新节点
}

//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);//确保传入的不是空指针并且链表不为空
	if ((*pphead)->next = NULL)//链表只有一个节点的情况
	{
		free((*pphead)->next);//释放该节点的空间
		*pphead == NULL;//改变了头节点的值,所以也要传二级指针
	}
	else//节点大于1的情况
	{
		SLTNode* prev = *pphead;
		while (prev->next->next != NULL)//循环遍历,使prev指向倒数第二个节点
		{
			prev = prev->next;
		}
		free(prev->next);//释放最后一个节点的空间
		prev->next = NULL;//将此时的最后一个节点的next制为空
	}
}

//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;//保存第二个节点的地址/空指针(只有一个节点时)
	free(*pphead);//释放第一个节点的空间
	*pphead = next;//让头指针指向刚才保存的节点/空指针,也要传二级指针
}

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType n)
{
	SLTNode* cur = phead;
	while (cur != NULL)//遍历链表的所有节点
	{
		if (cur->data == n)//匹配成功,返回该节点的地址
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;//没有找到,返回空指针
}

//指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType n)
{
	assert(pphead && pos);
	SLTNode* newnode = SLTBuyNode(n);
	if (*pphead == pos)//指定位置是头节点的情况
	{
		//进行头插
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//遍历找到pos节点的前驱节点
		{
			prev = prev->next;
		}
		newnode->next = pos;//新节点的next指针指向pos
		prev->next = newnode;//前驱节点的next指针指向新节点
	}
}

//指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType n)
{
	assert(pos);//确保pos不为空指针
	SLTNode* newnode = SLTBuyNode(n);
	newnode->next = pos->next;//newnode的next指向后继节点
	pos->next = newnode;//前驱节点的next指向newnode
}

//删除指定位置节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && pos && *pphead);//确保传入的不是空指针并且链表不为空
	if (pos == *pphead)//指定位置是头节点情况
	{
		//头删
		*pphead = pos->next;//先使头指针指向第二个节点
		free(pos);//释放掉pos节点
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//循环遍历,找到pos的前驱节点
		{
			prev = prev->next;
		}
		prev->next = pos->next;//使前驱节点的next指针指向pos的后继节点
		free(pos);//释放掉pos节点
	}
	pos = NULL;//对野指针及时制空
}

//销毁链表
void SLTDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;//从头节点开始遍历
	while (cur != NULL)
	{
		SLTNode* next = cur->next;//先记录下一个节点
		free(cur);//释放当前节点
		cur = next;//释放后,cur指向记录的节点
	}
	*pphead = NULL;//将头指针制空
}

总结

        相比于顺序表,单链表采用了不同的物理结构,这使得头插、头删等操作的效率高于顺序表,并且插入一个数据就会创建一个节点,避免了空间的浪费

        学习单链表是数据结构中相当重要的一个环节,学会了单链表,才会更容易地理解其他数据结构的底层逻辑。我们在学习数据结构时,要注意勤画图,勤调试,才能让我们的编程能力更上一层楼。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

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

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

相关文章

一文详解 JuiceFS 读性能:预读、预取、缓存、FUSE 和对象存储

在高性能计算场景中&#xff0c;往往采用全闪存架构和内核态并行文件系统&#xff0c;以满足性能要求。随着数据规模的增加和分布式系统集群规模的增加&#xff0c;全闪存的高成本和内核客户端的运维复杂性成为主要挑战。 JuiceFS&#xff0c;是一款全用户态的云原生分布式文件…

[240726] Mistral AI 发布新一代旗舰模型 | Node.js 合并 TypeScript 文件执行提案

目录 Mistral AI 发布新一代旗舰模型&#xff1a;Mistral Large 2Node.js 合并 TypeScript 文件执行提案&#xff1a;--experimental-strip-types Mistral AI 发布新一代旗舰模型&#xff1a;Mistral Large 2 Mistral AI 宣布推出新一代旗舰模型 Mistral Large 2&#xff0c;该…

ubuntu20.04 安装vscode 并且配置环境

目录 1.下载 2.安装 3.装插件&#xff0c;配置环境 1.下载 打开浏览器&#xff0c;进入vscode主页 Visual Studio Code - Code Editing. Redefined 2.安装 双击安装包 安装 安装完成后&#xff0c;我们打开vscode 3.装插件&#xff0c;配置环境 打开后我们 ctrlj 打开终…

操作系统的底层管理

一、冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本和我们不常见的计算机&#xff0c;如服务器大多都遵循冯诺依曼体系结构。 根据冯诺依曼结构 &#xff0c;我们可以了解到计算机是由一个个硬件组成&#xff0c;共分为三部分&#xff1a;输入、内存/CPU和输出。 如果…

【odoo17 | Owl】前端js钩子调用列表选择视图

概要 在我们选择多对一或者多对多字段的时候&#xff0c;经常看到可以弹出列表弹窗让人一目了然的效果&#xff0c;效果如下&#xff1a; 那么&#xff0c;这种效果是odoo本身封装好的组件&#xff0c;我们在平时的前端界面开发的时候&#xff0c;既不是后端视图的情况下&#…

title可修改点击展示input

<spanv-if"isEdit"click.stop"headerClick(activityForm.activityName)"><span>課程名稱{{ activityForm.activityName}}</span><el-buttonlinktype"primary"style"color: #1d2129"click"editCourseName&qu…

STM32存储左右互搏 QSPI总线读写64 Mbit容量SRAM VTI7064

STM32存储左右互搏 QSPI总线读写64 Mbit容量SRAM VTI7064 QSPI&#xff08;Quad-SPI&#xff09;设备有两种常见操作模式&#xff0c;一种QSPI设备上电后直接进入QSPI模式&#xff0c;操作时命令&#xff0c;地址和数据都是多线传输。另一种QSPI设备上电后进入常规SPI操作模式…

谷粒商城实战笔记-踩坑-跨域问题

一&#xff0c;When allowCredentials is true, allowedOrigins cannot contain the special value “*” since that cannot be set on the “Access-Control-Allow-Origin” response header. To allow credentials to a set of origins, list them explicitly or consider u…

shell-awk文本处理工具

1、awk概述 AWK 是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具。 它是专门为文本处理设计的编程语言&#xff0c;也是行处理软件&#xff0c;通常用于扫描、过滤、统计汇总工作 数据可以来自标准输入也可以是管道或文件 在 linux 上常用的是 gawk,awk …

PyTorch+AlexNet代码实训

参考文章&#xff1a;https://blog.csdn.net/red_stone1/article/details/122974771 数据集&#xff1a; 打标签&#xff1a; import os# os.path.join: 每个参数都是一个路径段&#xff0c;将它们连接起来形成有效的路径名。 train_txt_path os.path.join("data"…

Bazaar v1.4.3 任意文件读取漏洞复现(CVE-2024-40348)

0x01 产品简介 Bazarr是Sonarr和Radarr的配套应用程序&#xff0c;可根据您的要求管理和下载字幕。 0x02 漏洞概述 Bazarr存在任意文件读取漏洞&#xff0c;该漏洞是由于Bazaar v1.4.3的组件/api/swaggerui/static中存在一个问题&#xff0c;允许未经身份验证的攻击者可利用…

硅纪元AI应用推荐 | 豆包整容成了浏览器,让你的电脑秒变AI PC

“硅纪元AI应用推荐”栏目&#xff0c;为您精选最新、最实用的人工智能应用&#xff0c;无论您是AI发烧友还是新手&#xff0c;都能在这里找到提升生活和工作的利器。与我们一起探索AI的无限可能&#xff0c;开启智慧新时代&#xff01; 亲爱的技术宅们、办公高手们&#xff0c…

Tomcat项目本地部署

今天来分享一下如何于本机上在不适用idea等辅助工具的前提下&#xff0c;部署多个tomcat的web项目 我这里以我最近写的SSM项目哈米音乐为例&#xff0c;简单介绍一下项目的大致组成&#xff1a; 首先&#xff0c;项目分为4个模块&#xff0c;如下图所示&#xff1a; 其中&…

SQL 语句中的字符串有单引号导致报错的解决

1.问题 SQL 语句执行对象中&#xff0c;本内容的字符串内含有单引号导致查询或插入数据库报错&#xff0c; 例如 str 关键字 AND 附近有语法错误 2.解决 字符串中的 ’ → 替换 ”&#xff0c;则查询语句成功&#xff0c;故程式中要备注替换 单引号。

无法解析插件 org.apache.maven.plugins:maven-war-plugin:3.2.3(已解决)

文章目录 1、问题出现的背景2、解决方法 1、问题出现的背景 最开始我想把springboot项目转为javaweb项目&#xff0c;然后我点击下面这个插件 就转为javaweb项目了&#xff0c;但是我后悔了&#xff0c;想要还原成springboot项目&#xff0c;点开项目结构关于web的都移除了&am…

运放-增益带宽积-datasheet参数

在运放开环增益频率曲线中&#xff0c;在一定频率范围内&#xff0c;运放的开环增益与对应的频率乘积为常数&#xff1a;增益带宽积&#xff08;Gain Bandwidth Product&#xff0c; GBP 或者 GBW&#xff09;&#xff0c;即开环增益*频率增益带宽积。 这里有一个误区&#xf…

CompletableFuture异步线程不执行,卡死问题

1、生产上突然发现大量业务数据没执行&#xff0c;通过日志分析有段代码没执行。 2、分析原因可能是异步线程没执行导致&#xff0c;直接上代码场景 3、异步线程调用远程外部接口 超时或多次异常&#xff0c;导致服务无法再开启异步线程&#xff0c;同时代码中其他用到异步线程…

人人可学的AI与高科技普及视频课,零基础,通俗易懂,深入浅出

课程内容&#xff1a; 1 第0课:开课词&#xff0c;欢迎词 ev.mp4 2 第1课:我们为什么要学习Al ev.mp4 3 第2课:AI算法模型的基本概念MOVev,mp4 4 第3课:什么是生成性Al ev,mp4 5 第4课:人工智能的三驾马车 ev.mp4 6 加餐附加课1-谷歌双子座Gemini ev,mp4 7 第5课:关于Al…

为什么idea建议使用“+”拼接字符串

今天在敲代码的时候&#xff0c;无意间看到这样一个提示&#xff1a; 英文不太好&#xff0c;先问问ChatGPT&#xff0c;这个啥意思&#xff1f; IDEA 提示你&#xff0c;可以将代码中的 StringBuilder 替换为简单的字符串连接方式。 提示信息中说明了使用 StringBuilder 进行…

分布式相关理论详解

目录 1.绪论 2.什么是分布式系统&#xff0c;和集群的区别 3.CAP理论 3.1 什么是CAP理论 3.2 一致性 3.2.1 计算机的一致性说明 1.事务中的一致性 2.并发场景下的一致性 3.分布式场景下的一致性 3.2.2 一致性分类 3.2.3 强一致性 1.线性一致性 a) 定义 a) Raft算法…