数据结构——实现双向链表

news2024/12/22 14:22:10

文章目录

  • :cool:前言
  • :smile:带头双向循环链表的结构体搭建和初始化的操作
  • :bear:创造一个哨兵位头结点
  • :monkey:申请一个节点
  • :dog:初始化
  • :cat:打印
  • :potato:判空
  • :tomato:销毁
  • :cow:尾插
  • :strawberry:头插
  • :banana:尾删
  • :orange:头删
  • :pear:查找
  • :watermelon:在pos位置之前插入
  • :apple:删除pos位置的节点
  • :peach:完整代码
  • :smile:写在最后

🆒前言

  • 怎么说呢?光乍一听名字好像很难的样子是吧,那如果你这样认为的话,可就要让你大跌眼镜了哦,其实双向带头循环链表从操作和理解上来说都是要易于单项不带头不循环链表(俗称单链表)的。

  • 咱们就来见识见识吧!希望真的能让你们“大跌眼镜”哈!

  • 双向带头循环链表是一种最为常见的数据结构之一,非常适合于需要常操作插入、删除等操作的情况。其基本结构和单向链表相似,不同的是除了对前驱结点的引用,还有对后继结点的引用。

  • 在循环链表中,表头和表尾是相连的,形成了一个闭环。而带头指针则是为了方便操作而加入的,同时便于对空链表的判断。由于双向循环链表可以双向循环遍历,因此它的操作非常灵活,可以很容易地修改链表,使得其更加高效、稳定。

😄带头双向循环链表的结构体搭建和初始化的操作

  • 先简单看看这个逻辑图,整体的节点之间的联系在图中就能表现的非常清楚了。
    在这里插入图片描述
  • 这一步还是一如既往的相同,所需头文件的包含和结构体的定义。
    具体代码:
//带头双向循环链表
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

//数据的类型
typedef int LTDataType;
// 结构体的定义
typedef struct ListNode
{
	// 指向下一个节点的指针
	struct ListNode* next;
	// 指向前一个节点的指针
	struct ListNode* prev;
	// 储存数据
	LTDataType data;
}LTNode;
  • 各个函数的的声明
    具体代码:
//创建返回链表的头结点
LTNode* ListCreate();

// 双向链表的节点申请
LTNode* BuyLTNode(LTDataType x);

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

// 双向链表的打印
void LTPrint(LTNode* phead);

// 双向链表的判空
bool LTEmpty(LTNode* phead);

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

// 双向链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);

// 双向链表的头插
void LTPushFront(LTNode* phead, LTDataType x);

// 双向链表的尾删
void LTPopBack(LTNode* phead);

// 双向链表的头删
void LTPopFront(LTNode* phead);

// 双向链表的查找
LTNode* LTFind(LTNode* phead, LTDataType x);

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

// 删除pos位置的节点
void LTErase(LTNode* pos);

🐻创造一个哨兵位头结点

  • 哨兵位头节点(sentinel node)是一种特殊的节点,通常用于链表等数据结构中。这个节点不代表任何真实的值,而是在表头部提供了一个便利的占位符,来简化一些操作的实现,从而减少代码的复杂性。
  • 创建哨兵位头节点:
    这个节点的值可以是任意值,但是它的 next 指针应该指向链表的头节点。这个函数会返回一个指向哨兵节点的指针,你可以像使用普通链表一样使用它。但是,注意到哨兵节点不存储任何有用的值,因此,当你插入或者删除元素时,要特别小心喔。
    具体代码:
// 创建返回链表的头结点
LTNode* ListCreate()
{
	// BuyListNode() 该函数是申请一个节点,下面会讲解
	LTNode* head = BuyLTNode(-1);
	return head;
}
  • 这里需要注意一个小细节:上述代码中头结点与哨兵位头结点是等价的。

🐒申请一个节点

  • 因为后面频繁需要用到申请一个节点,所以我们不妨就奖这个功能单独实现一个函数,在后面需要这个功能的时候直接调用就OK啦。
  • 这个功能实际上就是在内存上申请一块空间,这块空间就用作newnode这块空间。
  • 申请这个节点我们可以让每个节点首先指向空,然后再将自己给定的值存入数据中,最后直接返回这个节点的地址即可。(因为这段空间的是在堆上的,所以销毁栈帧不会影响这段空间)
    具体代码:
//申请一个节点
LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

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

🐶初始化

  • 这个操作相信大家已经在熟悉不过了,而双向链表也不过如此,就是将链表的头节点的nextprev指向自己。
  • 最后记得返回头结点。
    具体代码:
//初始化
LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

🐱打印

  • 相较于单向链表,它可以通过指向前驱节点的指针实现反向遍历,提高了遍历的效率。
  • 我们定义了一个指针变量 cur,将其初始化为链表头节点的指针 phead。然后,使用 while 循环遍历链表,同时打印节点的值。最后,在每次打印完所有节点后,我们使用 printf 函数输出一个换行符,以便美化输出结果。
    具体代码:
//打印
void LTPrint(LTNode* phead)
{
	assert(phead);

	printf("guard<==>");
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

总的来说,打印双向链表并非复杂的操作,只需遍历链表,并打印每个节点的值即可。

🥔判空

  • 这里的判空不是判断有没有数据,而是判断有没有节点,若有节点,说明不为空,就返回false;若无节点,说明为空,就返回true
  • LTEmpty函数接收一个头指针 phead 作为参数,如果 phead 指向空,那么我们认为该双向链表为空,返回头节点。否则链表不为空。
    具体代码:
//判空
bool LTEmpty(LTNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

🍅销毁

  • 在使用完双向链表之后需要将它销毁以释放占用的内存空间。
  • 由于双向链表中每个节点都有两个指针,销毁该链表时需要遍历整个链表来把每个节点都销毁。
  • 可以定义一个名为LTDestroy的函数来销毁双向链表。该函数接收一个头指针 phead 作为参数,并将每个节点都销毁,最后将头节点也销毁并将头指针置free掉。
    具体代码:
//销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}


	free(phead);
}

🐮尾插

  • 因为是尾插,就是在链表的最后一节点后面链接一个节点,我们首先要申请一个节点,然后再找到尾部的节点,在进行链接。
  • 又因为这是循环链表,所以找到尾部节点不就是找到newnodeprev吗,那就变得so easy了。
  • 过程就是可以简单理解为这样:
    在这里插入图片描述
    具体代码:
//尾插
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;
}

需要注意的是:
在执行这些指针操作时,要确保所有指针都指向正确的节点,否则可能导致程序崩溃或出现其他错误。

🍓头插

  • 首先将新节点的 prev 指针置为NULL,即表示该节点是双向链表的头节点。然后将新节点的 next 指针指向头节点的后继节点。
  • 如果头节点的后继节点不为空,则将其 prev 指针指向新节点;否则,新节点就是双向链表的尾节点。
  • 最后,将头节点的 next 指针指向新节点,完成头插操作。
  • 过程简单的可视化:
    在这里插入图片描述
    具体代码:
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	//申请一个节点
	LTNode* newnode = BuyLTNode(x);
	//存放下一个节点
	LTNode* first = phead->next;
	//将链表连接起来
	phead->next = newnode;
	newnode->prev = phead;

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

需要注意的是:
如果不存放当前头节点的下一个节点的地址,那么需要先将新的节点与头节点的下一个节点连接,然后才与头节点连接,因为先与头节点连接的话,就找不到下一个节点了,此时就会连接中断。

🍌尾删

  • 使用指针遍历整个链表,直到找到最后一个节点,然后将其删除。(删除就记得要判空)
  • 删除操作需要改变两个指针值,即前驱节点的 next 指针和待删除节点的 prev指针,同时需要使用 free 函数释放该节点占用的内存空间。
  • 至于如果是空指针咋办,交给assert就好了。其他的情况都是这套操作。
  • 静态过程就放在下面了:
    在这里插入图片描述
    具体代码:
//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	free(tail);
	tailPrev->next = phead;
	phead->prev = tailPrev;

}

🍊头删

  • 首先找到第一个实际节点,并将其存储在指针 phead 中。(记得删除就要判空)
  • 然后,将头节点的 next 指针设置为 pheadnext 指针,以绕过要删除的节点,并修改待删除节点的后继节点的 prev 指针。
  • 最后,使用 free 函数释放该节点占用的内存空间。
    在这里插入图片描述
    具体代码:
//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	// 存放当前链表头节点的下一个节点的地址
	LTNode* prev = phead->next;
	// 存放当前链表头节点的下一个节点的下一个节点的地址
	LTNode* next = prev->next;
	//链接
	phead->next = next;
	next->prev = phead;
	//释放
	free(prev);
}

🍐查找

  • 这里的查找我们输入的是数据,然后再链表中寻找data与输入的数据相等的节点,最后返回这个节点的地址。若没有找到,则返回NULL
  • 查找?不就是将整个链表遍历一遍吗?
    上代码:
//查找
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位置之前插入

  • 这里的插入是指在任意位置的插入,就是在你输入的节点之前插入数据。
  • 竟然时任意位置,自然的这里就会再多一个变量,这时候这里需要传递一个指针(pos)来指向你输入的节点的前面。
  • 再加上咱们前面学习的头插,直接复用就好了,这里就不再多啰嗦了。
  • 然后要记得存放要插入的前一个位置的节点,插入之后记得链接。
    具体代码:
// 在pos之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyLTNode(x);

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

🍎删除pos位置的节点

  • 删除任意节点的位置放在这个位置是不是显得格外渺小,与插入类似,就是需要多传递一个变量来接收任意位置节点的地址。
  • 都看到这里了,其他的操作就不必多说了。算了,还是描述一下为好。
  • 当我们想要删除节点 pos 时,首先需要修改其前驱节点的 next 指针,使其指向 pos 的后继节点;再修改其后继节点的 prev 指针,使其指向 pos 的前驱节点。最后,使用 free 函数释放待删除节点占用的内存空间。
    具体代码:
// 删除pos位置的值
void LTErase(LTNode* pos)
{
	assert(pos);

	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

🍑完整代码

List.h

//带头双向循环链表
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

// 结构体的定义
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;

//创建返回链表的头结点
LTNode* ListCreate();

// 双向链表的节点申请
LTNode* BuyLTNode(LTDataType x);

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

// 双向链表的打印
void LTPrint(LTNode* phead);

// 双向链表的判空
bool LTEmpty(LTNode* phead);

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

// 双向链表的尾插
void LTPushBack(LTNode* phead, LTDataType x);

// 双向链表的头插
void LTPushFront(LTNode* phead, LTDataType x);

// 双向链表的尾删
void LTPopBack(LTNode* phead);

// 双向链表的头删
void LTPopFront(LTNode* phead);

// 双向链表的查找
LTNode* LTFind(LTNode* phead, LTDataType x);

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

// 删除pos位置的节点
void LTErase(LTNode* pos);

List.c

#include"List.h"

// 创建返回链表的头结点
LTNode* ListCreate()
{
	// BuyListNode() 该函数是申请一个节点,下面会讲解
	LTNode* head = BuyLTNode(-1);
	return head;
}

//申请一个节点
LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

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

//初始化
LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

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

	printf("guard<==>");
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

//判空
bool LTEmpty(LTNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

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

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}


	free(phead);
}

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

	LTInsert(phead, x);

//	LTNode* tail = phead->prev;
//	LTNode* newnode = BuyLTNode(x);
//
//	tail->next = newnode;
//	newnode->prev = tail;
//	newnode->next = phead;
//	phead->prev = newnode;
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	/*assert(phead);
	LTNode* newnode = BuyLTNode(x);
	LTNode* first = phead->next;

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

	newnode->next = first;
	first->prev = newnode;*/

	LTInsert(phead->next, x);
}

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));
	LTErase(phead->prev);

	/*LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	free(tail);
	tailPrev->next = phead;
	phead->prev = tailPrev;*/

}

//头删
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);

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyLTNode(x);

	prev->next = newnode;
	newnode->prev = prev;
	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);
}

Test.c

#include"List.h"

void TestList1()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);

	LTPrint(plist);

	LTPopBack(plist);
	LTPrint(plist);

	LTPopBack(plist);
	LTPrint(plist);

	LTPopBack(plist);
	LTPrint(plist);

	LTPopBack(plist);
	LTPrint(plist);

	LTDestroy(plist);
	plist = NULL;
}

void TestList2()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);

	LTDestroy(plist);
	plist = NULL;
}

void TestList3()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPrint(plist);

	LTNode* pos = LTFind(plist, 3);
	if (pos)
	{
		LTInsert(pos, 30);
	}
	LTPrint(plist);

	LTDestroy(plist);
	plist = NULL;
}

int main()
{
	//TestList1();
	//TestList2();
	TestList3();

	return 0;
}

有了任意位置的插入和删除,我们可以在其他位置插入和删除操作上调用

😄写在最后

❤希望本篇博客能让大家更好地理解双向链表的基本原理及实现方法,帮助大家在实际开发中更好地使用该数据结构。

💗也特别感谢大家持续的关注和支持,我会继续努力更新更好的博客。

最后感谢各位阅读本小白的博客,希望能帮助到大家!也请大家严厉指出并纠正我在文章中的错误。😄

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

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

相关文章

electron-vue 运行报错 Object.fromEntries is not a function

文章目录 1. 背景2. 解决方案2.1 第一步&#xff1a;安装依赖2.2 第二步&#xff1a;项目中引入 3. 组件详解 1. 背景 最近研究一款桌面端应用的开发框架electron-vue&#xff0c;在按照 electron-vue官方文档 操作之后操作如下&#xff0c;Object.fromEntries is not a funct…

抖音seo源码搭建,抖音矩阵系统源码分发,抖音矩阵同步分发

前言&#xff1a;抖音seo源码&#xff0c;抖音矩阵系统源码搭建&#xff0c;抖音矩阵同步分发。抖音seo源码部署是需要对接到这些正规接口再来做开发的&#xff0c;目前账号矩阵程序开发的功能&#xff0c;围绕一键管理多个账号&#xff0c;做到定时投放&#xff0c;关键词自动…

腾讯云服务器端口怎么全开?教程来了

腾讯云服务器端口怎么全开&#xff1f;云服务器CVM在安全组中设置开通&#xff0c;轻量应用服务器在防火墙中设置&#xff0c;腾讯云百科来详细说下腾讯云服务器端口全开放教程&#xff1a; 目录 腾讯云服务器端口全部开通教程 云服务器CVM端口全开放教程 轻量应用服务器开…

一文学会TypeScript

TypeScript笔记 文章目录 TypeScript笔记[toc]第一章 TypeScript简介1.1、TypeScript简介1.2、TypeScript安装1.3、TypeScript项目初始化1.4、Hello TypeScript 第二章 TypeScript数据类型2.1、TypeScript的类型2.2、字面量类型2.3、联合类型2.4、any 与 unknown2.5、类型断言2…

5.1 因特网概述

5.1 因特网概述 我们知道因特网是一个很大的互联网&#xff0c;它由大量的通过路由器互联起来的物理网络构成&#xff0c;我们下思考几个问题 为什么因特网要考虑包容多种物理网络技术呢&#xff1f; 因为价格低廉的局域网只能够提供短距离的高速通信&#xff0c;而能够跨越长…

渲大师云主机按量付费功能上线!

云主机可以提供强大的计算和存储能力&#xff0c;通过使用云主机&#xff0c;政企办公、视觉设计、影视制作和深度学习领域的专业人士可以获得更大的灵活性、可扩展性和计算能力&#xff0c;提高工作效率和效果。 然而&#xff0c;当我们在选择和使用云主机时&#xff0c;需要…

如何优雅的在SpringBoot中编写选择分支,而不是大量if else?

一、需求背景二、创建项目三、基础工作四、定义 Handler 类五、实现员工接口六、功能测试6.1 开发控制器6.2 功能测试 七、总结 一、需求背景 部门通常指的是在一个组织或企业中组成的若干人员&#xff0c;他们共同从事某一特定工作&#xff0c;完成共同的任务和目标。在组织或…

Logisim 头歌 偶校验解码电路设计 图解及代码(计算机组成原理)

努力是为了不平庸~ 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。 急的同学请直接点击目录跳到下方解答处&#xff01;&#xff01; 目录 图解&#xff1a;​编辑 代码题解&#xff08;免费&#xff09;&#x…

泰酷辣!基于全志R818的开源超迷你安卓手持终端CyberPad,芒果派惊喜之作

​继推出大小仅与普通SD卡不相上下爱的超迷你模组MCore-H616核心板之后&#xff0c;鸽了近半年时间的芒果派&#xff0c;又带来了一款惊喜之作——MCore-R818核心板。 该款MCore的设计也是基于R818的特性&#xff0c;做出了一些小小的改变。 芯片本体封装设计较小&#xff0c;…

【力扣周赛】第347场周赛

【力扣周赛】第347场周赛 6457. 移除字符串中的尾随零题目描述解题思路 2711. 对角线上不同值的数量差题目描述解题思路 6455. 使所有字符相等的最小成本题目描述解题思路 6456. 矩阵中严格递增的单元格数题目描述解题思路 6457. 移除字符串中的尾随零 题目描述 描述&#xf…

如何让Task在非线程池线程中执行?

Task承载的操作需要被调度才能被执行&#xff0c;由于.NET默认采用基于线程池的调度器&#xff0c;所以Task默认在线程池线程中执行。但是有的操作并不适合使用线程池&#xff0c;比如我们在一个ASP.NET Core应用中承载了一些需要长时间执行的后台操作&#xff0c;由于线程池被…

Linux:shell脚本的介绍,创建与执行

linux的shell脚本就是windows的bat脚本&#xff0c;也就是通常所说的批处理。更简洁地说&#xff0c;就是很多命令的结合体&#xff0c;就像编程一样。 windows脚本的扩展名是.bat&#xff0c;而linux脚本的扩展名则是.sh centos在编写shell脚本的文件最上边&#xff0c;需要加…

如何使用Sentinel的Slot插槽实现限流熔断,看完这篇文章会有新的收获

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章将详细介绍如何使用Sentinel的Slot插槽实现限流熔断&#xff0c;后续文章将详细介绍Sentinel的其他知识。 如果文章有什么需要改进的地方还请大佬不吝赐教&#x…

对于2023年参加国家计算机软考系统分析师的感想

文章目录 前言系分简介系分知识点今年的题型综合知识(上午选择题)案例分析&#xff08;下午简答分析题&#xff09;论文&#xff08;下午小作文&#xff09; 写在最后 前言 23年3月27日参加了国家计算机软考系统分析师&#xff0c;考完后很多的题库网站就有小道估分了。当然&a…

一些零零碎碎的记录

Questions1. 用户访问多网址服务器同一个IP是怎么回事 Q:用户访问服务器的同一个IP不同网址&#xff0c;服务器是如何区分的A: 在 HTTP 协议中&#xff0c;客户端通过发送请求报文来向服务器请求资源。每个 HTTP 请求都包含一个 HTTP 头部&#xff0c;其中包括了一些关键信息&…

力扣sql中等篇练习(三十)

力扣sql中等篇练习(三十) 1 即时食物配送||| 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # Write your MySQL query statement below SELECT order_date,ROUND(100*count(IF(order_datecustomer_pref_delivery_date,customer_id,null))/count(*)…

studio one6免费版下载及配置要求 附精调效果包

提到编曲软件&#xff0c;就不得不说这款水果编曲软件。它对新手和老手都比较友好&#xff0c;是一款较为经典的编曲软件。 这款软件提供了强大而全面的音符、音效编辑器&#xff0c;可以在其中插入各种乐器声音&#xff0c;如果内置乐器无法满足编曲需求&#xff0c;还可以外…

ABAQUS计算随机振动设置及输出

ABAQUS计算随机振动设置及输出 1.分析步设置 随机振动主要包括两个分析步&#xff1a;频率和随机振动 1.1 频率设置 频率这里需要注意的是最高频率最好是扫频范围的2-2.5倍 比如随机频率区间是[0-2000hz],最高频率应该大于4000Hz&#xff0c;才能保证精度 1.2 随机响应设…

数据结构【栈】有哪些应用场景?

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

如何在前端应用中合并多个 Excel 工作簿

本文由葡萄城技术团队于博客园原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 | 问题背景 ​ SpreadJS是纯前端的电子表格控件&#xff0c;可以轻松加载 Excel 工作簿中的数据…