数据结构学习之路--实现带头双向循环链表的详解(附C源码)

news2024/11/24 6:36:44

   嗨嗨大家~本期带来的内容是:带头双向循环链表的实现。在上期文章中我们提到过带头双向循环链表,那么它的实现又是怎样的呢?今天我们来一探究竟!


目录

前言 

一、认识带头双向循环链表

1 认识双向链表 

2 带头双向循环链表的定义

二、带头双向循环链表的实现 

2.1 定义

2.2 创建结点 

2.3 初始化 

方法一: 

方法二:

2.4 链表的判空

2.5 链表的尾插 

2.6 链表的头插 

方法一: 

方法二:

2.7 链表的尾删 

2.8 链表的头删 

2.9 在pos位置之前插入 

2.10  删除pos位置的结点

2.11 链表的长度

2.12 链表的打印

2.13 链表的销毁

三、总代码 


前言 

   我们在上期内容中讲过,链表结构是多样化的。但在实际中最常用的只有两种:无头单向非循环链表带头双向循环链表。前者已经在上篇博客(http://t.csdnimg.cn/s8ieT)进行了全面的讲解,现在我们来认识并实现后者。

一、认识带头双向循环链表

1 认识双向链表 

   单链表虽然能够实现从任一结点出发沿着链能找到其前驱结点,但时间耗费是O(n)。如果希望从表中快速确定某一个结点的前驱,另一个解决方法就是在单链表的每个结点里再增加一个指向其前驱的指针域prev。这样形成的链表中就有两条方向不同的链,称之为双(向)链表。

   与单链表类似,双向链表也可增加头结点使双向链表的某些运算变得方便。同时双向链表也可以有循环表,称为双向循环链表。 由于在双向链表中既有前向链又有后向链,所以寻找任一结点的直接前驱结点与直接后继结点都变得非常便捷。

2 带头双向循环链表的定义

   带头双向循环链表:结构最复杂,一般用于单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。

二、带头双向循环链表的实现 

2.1 定义

代码实现:

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

分析:这里与单链表的定义不同,带头双向循环链表要定义两个指针:前驱指针prev和后继指针next。前驱指针prev用于指向当前结点的上一个结点,后继指针next用于指向当前结点的下一个结点。 

2.2 创建结点 

代码实现:

//创建结点
LTNode* BuyListNode(LTDataType x)
{
	//动态开辟一个结点node
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
 
	//判空
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(-1);
	}
 
	//前驱与后继结点均置为空
	node->data = x;
	node->next = NULL;
	node->prev = NULL;
 
	return node;
}

分析:结点的创建主要是通过调用malloc函数来实现,初始化时要将前驱指针和后继指针都置为NULL。 

2.3 初始化 

  • 带头双向循环链表的初始化可以使用两种方法:传二级指针设置返回值

方法一: 

代码实现:

//初始化
void ListInit(LTNode** phead)
{
	//这里需要传入二级指针,即传地址,才能实现对链表的修改
 
	//判空
	assert(phead);
	
	//创建头结点
	*phead = BuyListNode(-1);
 
	//因为是带头双向链表,故将头结点的前驱指针和后继指针均指向它们自己
	(*phead)->next = *phead;
	(*phead)->prev = *phead;
}

方法二:

代码实现:

初始化
LTNode* ListInit()
{
	//创建头结点
	LTNode* phead = BuyListNode(-1);
 
	//因为是带头循环双向链表,故将头结点的前驱指针和后继指针均指向它们自己
	phead->next = phead;
	phead->prev = phead;
 
	//返回头结点
	return phead;
}

注意:

  • 若想要改变头指针,就要传二级指针;不需要改变头指针的话,便传入一级指针。
  • 在使用带头结点的单链表时:
  1. 初始化链表头指针需要传二级指针;
  2. 销毁链表需要传二级指针;
  3. 插入、删除、遍历、清空结点用一级指针即可。
  • 不带头结点的单链表,除了初始化和销毁,插入、删除和清空结点也需要二级指针。 

2.4 链表的判空

代码实现:

bool ListEmpty(LTNode* phead)
{
	//判空
	assert(phead);
 
	//如果phead->next等于phead,则链表为空,返回true
	//如果phead->next不等于phead,则链表不为空,返回false
	return phead->next == phead;
}

分析:若phead->next等于phead,则链表为空,返回true;若phead->next不等于phead,则链表不为空,返回false。 

2.5 链表的尾插 

代码实现:

//尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
	//判空
	assert(phead);
 
	//创建新结点
	LTNode* newnode = BuyListNode(x);
 
	//查找尾结点
	LTNode* tail = phead->prev;
 
	//原尾和新尾相互链接
	tail->next = newnode;
	newnode->prev = tail;
	//头结点和新尾相互链接
	newnode->next = phead;
	phead->prev = newnode;
}

分析:与单链表的尾插相比,带头双向循环链表的尾插不需要从头结点开始依次向后遍历,因为头结点的前驱结点便指向尾结点tail。在找到尾结点tail之后,便可将新结点newnode插入到尾结点tail的后面。此时newnode变为新的尾结点。 

2.6 链表的头插 

  • 带头双向循环链表的头插有两种方式实现。

方法一: 

代码实现:

//头插
void ListPushFront(LTNode* phead, LTDataType x)
{
	//判空
	assert(phead);

	//创建新结点
	LTNode* newnode = BuyListNode(x);

	//phead newnode next:三者不分先后顺序
	LTNode* next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;
}

方法二:

代码实现:

//头插
void ListPushFront(LTNode* phead, LTDataType x)
{
	//判空
	assert(phead);
	//创建新结点
	LTNode* newnode = BuyListNode(x);
	
    //phead newnode phead->next:先处理后两个,再处理前两个
	phead->next->prev = newnode;
	newnode->next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
}

分析:当进行头插时,要注意结点之间插入的先后顺序,这里主要介绍两种方式:

  • 方式一:创建一个临时变量next,然后将头结点的下一个结点保存在next当中。首先调用BuyListNode(x)创建一个新结点newnode,然后将phead,newnode和next三个结点进行链接。三个结点不分先后顺序,直接进行链接即可。该方式最为简单,也最不容易出错;
  • 方式二:不创建临时变量next。首先调用BuyListNode(x)创建一个新结点newnode,然后将phead,newnode和phead->next三个结点进行链接。链接是关键:要先将后两个结点进行链接,然后再将前两个结点进行链接。三个结点一定要注意先后顺序,不可随意链接。 

2.7 链表的尾删 

代码实现:

//尾删
void ListPopBack(LTNode* phead)
{
	//判空
	assert(phead);
 
	//判断链表是否为空
	assert(phead->next != phead);
	//assert(!ListEmpty(phead));
 
	//找尾结点
	LTNode* tail = phead->prev;
 
	//找尾结点的前一结点
	LTNode* tailPrev = tail->prev;
 
	//释放尾结点
	free(tail);
 
	tailPrev->next = phead;
	phead->prev = tailPrev;
}

分析: 在进行尾删之前,首先要判断链表是否为空,可以通过phead->next != phead进行判断,也可以调用ListEmpty(phead)函数进行判断;然后找到链表的尾结点tail,以及链表尾结点的前一个结点tailPrev;接着调用free函数释放尾结点tail,并将tailPrev作为新的尾结点;最后再将新的尾结点与头结点phead进行相连即可。

2.8 链表的头删 

代码实现:

//头删
void ListPopFront(LTNode* phead)
{
	//判空
	assert(phead);
 
	//判断链表是否为空
	assert(phead->next != phead);
	//assert(!ListEmpty(phead));
 
	//tail记录第一个结点之后的下一个结点
	LTNode* tail = phead->next->next;
	
	//释放第一个结点
	free(phead->next);
 
	//将头结点和tail相链接
	phead->next = tail;
	tail->prev = phead;
}

分析:在进行头删之前,首先要判断链表是否为空,可以通过phead->next != phead进行判断,也可以调用ListEmpty(phead)函数进行判断;然后找到链表的第二个有效结点tail;接着调用free函数释放掉第一个有效结点,并将tail作为新的第一个有效结点;最后再将新的第一个结点tail与头结点phead进行相连即可。 

2.9 在pos位置之前插入 

代码实现:

//在pos前插入结点
void ListInsert(LTNode* pos, LTDataType x)
{
	//判空
	assert(pos);
 
	//查找pos的前一个结点
	LTNode* prev = pos->prev;
 
	//创建新结点
	LTNode* newnode = BuyListNode(x);
 
	//prev newnode pos
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

分析:给定一个结点pos,如果是带头双向循环链表,那么pos之前的结点和pos之后的结点都是可知的。要在pos位置之前插入,首先要找到pos的前一结点prev,然后调用BuyListNode(x)创建一个新结点newnode,接着将prev,newnode和pos三个结点进行链接即可。此时pos位置的结点将由pos变为newnode。 

2.10  删除pos位置的结点

代码实现:

//删除pos位置的结点
void ListErase(LTNode* pos)
{
	//判空
	assert(pos);
 
	//查找pos的前一个结点
	LTNode* prev = pos->prev;
	
	//查找pos的后一个结点
	LTNode* next = pos->next;
 
	//将前一个结点pre与后一个结点next相链接
	prev->next = next;
	next->prev = prev;
 
	//释放pos结点
	free(pos);
}

分析:在删除pos位置的结点之前,首先要找到pos位置的前一个结点prev,然后找到pos位置的后一个结点next,接着将结点prev与next相链接,最后再调用free函数释放掉pos结点即可。 

2.11 链表的长度

 代码实现:

//求链表长度(结点个数)
int ListSize(LTNode* phead)
{
	//判空
	assert(phead);
 
	//cur指向当前链表的第一个结点
	LTNode* cur = phead->next;
	
	//用于记录遍历过的结点数
	int size = 0;
 
	//从第一个结点开始依次向后遍历,直到遍历到头结点
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}
	return size;
}

2.12 链表的打印

代码实现:

//打印
void ListPrint(LTNode* phead)
{
	//判空
	assert(phead);
 
	//cur指向链表的第一个结点
	LTNode* cur = phead->next;
 
	//cur依次向后遍历,直到cur重新回到头结点
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

分析:设置一个临时变量cur,指向当前链表的第一个结点(非头结点),然后依次向后遍历该链表,直到cur重新回到头结点phead的位置。 

2.13 链表的销毁

代码实现:

//销毁
void ListDestory(LTNode* phead)
{
	//判空
	assert(phead);
 
	//cur指向当前第一个结点
	LTNode* cur = phead->next;
 
	while (cur != phead)
	{
		//保存cur的下一个结点
		LTNode* next = cur->next;
 
		//删除cur
		ListErase(cur);
 
		//更新cur
		cur = next;
	}
 
	//释放头结点
	free(phead);
}

总结:可以在该链表的任意位置插入和删除(但不能删除head),也无需考虑特殊情况进行单独判断。 

三、总代码 

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* BuyListNode(LTDataType x);
 
//初始化:方法一
//void ListInit(LTNode** phead);
 
//初始化:方法二
LTNode* ListInit();
 
//判空
bool ListEmpty(LTNode* phead);
 
//尾插
//不用二级指针的原因:尾插时不会改变phead,因为它带哨兵位,尾插时不会对哨兵位进行修改
void ListPushBack(LTNode* phead, LTDataType x);
 
//头插
void ListPushFront(LTNode* phead, LTDataType x);
 
//尾删
void ListPopBack(LTNode* phead);
 
//头删
void ListPopFront(LTNode* phead);
 
//在pos位置之前插入
void ListInsert(LTNode* pos, LTDataType x);
 
//删除pos位置的结点
void ListErase(LTNode* pos);
 
//链表长度
int ListSize(LTNode* phead);

//打印
void ListPrint(LTNode* phead);
 
//销毁
void ListDestory(LTNode* phead);
List.c

#include"List.h"

​//创建结点
LTNode* BuyListNode(LTDataType x)
{
	//动态开辟一个结点node
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
 
	//判空
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(-1);
	}
 
	//前驱与后继结点均置为空
	node->data = x;
	node->next = NULL;
	node->prev = NULL;
 
	return node;
}
​​//初始化
void ListInit(LTNode** phead)
{
	//这里需要传入二级指针,即传地址,才能实现对链表的修改
 
	//判空
	assert(phead);
	
	//创建头结点
	*phead = BuyListNode(-1);
 
	//因为是带头双向链表,故将头结点的前驱指针和后继指针均指向它们自己
	(*phead)->next = *phead;
	(*phead)->prev = *phead;
}

​​/*
//初始化
LTNode* ListInit()
{
	//创建头结点
	LTNode* phead = BuyListNode(-1);
 
	//因为是带头循环双向链表,故将头结点的前驱指针和后继指针均指向它们自己
	phead->next = phead;
	phead->prev = phead;
 
	//返回头结点
	return phead;
}
*/

//判空
​​bool ListEmpty(LTNode* phead)
{
	assert(phead);
 
	//如果phead->next等于phead,则链表为空,返回true
	//如果phead->next不等于phead,则链表不为空,返回false
	return phead->next == phead;
}
//尾插
​​void ListPushBack(LTNode* phead, LTDataType x)
{
	//判空
	assert(phead);
 
	//创建新结点
	LTNode* newnode = BuyListNode(x);
 
	//查找尾结点
	LTNode* tail = phead->prev;
 
	//原尾和新尾相互链接
	tail->next = newnode;
	newnode->prev = tail;
	//头结点和新尾相互链接
	newnode->next = phead;
	phead->prev = newnode;
}
​//头插
void ListPushFront(LTNode* phead, LTDataType x)
{
	//判空
	assert(phead);

	//创建新结点
	LTNode* newnode = BuyListNode(x);

	//phead newnode next:三者不分先后顺序
	LTNode* next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;
}
​​//头插
void ListPushFront(LTNode* phead, LTDataType x)
{
	//判空
	assert(phead);
	//创建新结点
	LTNode* newnode = BuyListNode(x);
	
    //phead newnode phead->next:先处理后两个,再处理前两个
	phead->next->prev = newnode;
	newnode->next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
}
​​//尾删
void ListPopBack(LTNode* phead)
{
	//判空
	assert(phead);
 
	//判断链表是否为空
	assert(phead->next != phead);
	//assert(!ListEmpty(phead));
 
	//找尾结点
	LTNode* tail = phead->prev;
 
	//找尾结点的前一结点
	LTNode* tailPrev = tail->prev;
 
	//释放尾结点
	free(tail);
 
	tailPrev->next = phead;
	phead->prev = tailPrev;
}
​​//头删
void ListPopFront(LTNode* phead)
{
	//判空
	assert(phead);
 
	//判断链表是否为空
	assert(phead->next != phead);
	//assert(!ListEmpty(phead));
 
	//tail记录第一个结点之后的下一个结点
	LTNode* tail = phead->next->next;
	
	//释放第一个结点
	free(phead->next);
 
	//将头结点和tail相链接
	phead->next = tail;
	tail->prev = phead;
}
​​//在pos前插入
void ListInsert(LTNode* pos, LTDataType x)
{
	//判空
	assert(pos);
 
	//查找pos的前一个结点
	LTNode* prev = pos->prev;
 
	//创建新结点
	LTNode* newnode = BuyListNode(x);
 
	//prev newnode pos
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
​​//删除pos位置的结点
void ListErase(LTNode* pos)
{
	//判空
	assert(pos);
 
	//查找pos的前一个结点
	LTNode* prev = pos->prev;
	
	//查找pos的后一个结点
	LTNode* next = pos->next;
 
	//将前一个结点pre与后一个结点next相链接
	prev->next = next;
	next->prev = prev;
 
	//释放pos结点
	free(pos);
}
​​//求链表长度(结点个数)
int ListSize(LTNode* phead)
{
	//判空
	assert(phead);
 
	//cur指向当前链表的第一个结点
	LTNode* cur = phead->next;
	
	//用于记录遍历过的结点数
	int size = 0;
 
	//从第一个结点开始依次向后遍历,直到遍历到头结点
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}
	return size;
}
​​
//销毁
void ListDestory(LTNode* phead)
{
	//判空
	assert(phead);
 
	//cur指向当前第一个结点
	LTNode* cur = phead->next;
 
	while (cur != phead)
	{
		//保存cur的下一个结点
		LTNode* next = cur->next;
 
		//删除cur
		ListErase(cur);
 
		//更新cur
		cur = next;
	}
 
	//释放头结点
	free(phead);
}

​
test.c
 
#include"List.h"
 
void Test()
{
	LTNode* plist = NULL;
 
	//初始化
	plist = ListInit();
 
	//头插
	ListPushFront(plist, 1);
	ListPushFront(plist, 2);
	ListPushFront(plist, 3);
	ListPushFront(plist, 4);
	ListPushFront(plist, 5);
	ListPrint(plist);
 
	ListDestory(plist);
	ListPrint(plist);
}
 
int main()
{
	Test();
 
	return 0;
}

   大家学到这里,链表的知识分享已经接近尾声,综合本期文章以及前两篇博客来看,其实不难发现,对于任何一个数据结构,基本操作大致上都能归纳为创建销毁,增删改查。其中改建立在查的基础上。


   那么本期的内容就告一段落,有关于链表的总结也已经结束,此时的你是否对链表有了更深层次的了解和掌握呢?如果大家觉得这篇文章对你们有所帮助,记得给博主留下三连支持哈~你们的支持是我创作的最大动力!博主也会继续竭尽所能地为大家带来更加优质的内容,当然啦,或许我存在许多不足之处,欢迎各位佬们的指点!请相信相信的力量,一切也终有回甘!诸君加油~我们下期再会啦。

 

 

 

 

 

 

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

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

相关文章

这 6 个探索性数据分析(EDA)工具,太实用了!

当进行数据分析时&#xff0c;探索性数据分析(EDA)是一个至关重要的阶段&#xff0c;它能帮助我们从数据中发现模式、趋势和异常现象。而选择合适的EDA工具又能够极大地提高工作效率和分析深度。 在本文中&#xff0c;我将介绍6个极其实用的探索性数据分析(EDA)工具&#xff0…

UTONMOS元宇宙游戏特点

在元宇宙的世界里&#xff0c;游戏不再只是一种娱乐方式&#xff0c;而是一种全新的生活体验。UTONMOS元宇宙游戏带你穿越虚拟与现实的边界&#xff0c;开启一段前所未有的冒险之旅。 在这个充满无限可能的UTONMOS元宇宙游戏中&#xff0c;你将成为自己游戏世界的主角。可以自…

Java(多线程)

一、基本概念 进程&#xff1a;一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元&#xff0c;在传统的操作系统中&#xff0c;进程既是基本的分配单元&#xff0c;也是基本的执行单元。线程&#xff1a;操作系统中能够进行运算的最…

蓝桥杯 2019 省A 糖果 动态规划/二进制

#include <bits/stdc.h> // 包含标准库中的所有头文件 using namespace std;int main() {int n,m,k; // 定义变量n&#xff08;糖果包数&#xff09;、m&#xff08;口味数&#xff09;、k&#xff08;每包糖果的个数&#xff09;cin>>n>>m>>k; // 输入…

院子里种点什么树风水好呢?

植物本身是一个丰富的生活领域&#xff0c;有着强烈的视觉暗示。其实&#xff0c;在家中养植物&#xff0c;是有许多好处的&#xff0c;它不仅能够装点庭院的环境让家更美丽&#xff0c;还能调节室内的空气质量&#xff0c;对家人的运势也有着非常大的帮助。 不过&#xff0c;并…

Android 四大组件启动

service: startService启动过程分析 - Gityuan博客 | 袁辉辉的技术博客 在整个startService过程&#xff0c;从进程角度看服务启动过程 Process A进程&#xff1a;是指调用startService命令所在的进程&#xff0c;也就是启动服务的发起端进程&#xff0c;比如点击桌面App图标…

Java 中文官方教程 2022 版(四十九)

原文&#xff1a;docs.oracle.com/javase/tutorial/reallybigindex.html JAXB 示例 原文&#xff1a;docs.oracle.com/javase/tutorial/jaxb/intro/examples.html 以下部分描述如何使用包含在 JAXB RI 捆绑包中的示例应用程序。JAXB RI 捆绑包可从jaxb.java.net获取。下载并安装…

华为云配置安全组策略开放端口

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C &#x1f525;座右铭&#xff1a;“不要等到什么都没有了&#xff0c;才下…

文件上传【2】--靶场通关

1.前端禁用js绕过 上传文件&#xff0c;进行抓包&#xff0c;没有抓到&#xff0c;说明这里的验证是前端js验证跳出的弹窗 禁用js后&#xff0c;php文件上传成功。 2.文件上传.htaccess 上传png木马后连接不上 代码中存在.htaccess&#xff0c;判断此时应该就是需要用到.htac…

单细胞RNA测序(scRNA-seq)cellranger count的细胞定量和aggr整合

单细胞RNA测序(scRNA-seq)基础知识可查看以下文章: 单细胞RNA测序(scRNA-seq)工作流程入门 单细胞RNA测序(scRNA-seq)细胞分离与扩增 单细胞RNA测序(scRNA-seq)SRA数据下载及fastq-dumq数据拆分 单细胞RNA测序(scRNA-seq)Cellranger流程入门和数据质控 细胞定量…

[大模型]Qwen1.5-7B-Chat FastApi 部署调用

Qwen1.5-7B-Chat FastApi 部署调用 环境准备 在 Autodl 平台中租赁一个 3090 等 24G 显存的显卡机器&#xff0c;如下图所示镜像选择 PyTorch–>2.0.0–>3.8(ubuntu20.04)–>11.8&#xff08;11.3 版本以上的都可以&#xff09;。 接下来打开刚刚租用服务器的 Jupyt…

yolov9训练自己的数据—vehicle 4类

yolov9训练自己的数据 1 conda环境安装指定版本torch 2 预训练模型测试3 训练自己的数据集3.1 制作数据3.2 创建模型配置文件3.3 创建数据加载配置文件3.4 使用ClearML跟踪训练日志3.5 训练3.6 模型测试3.7 转换成TensorRT模型 4 参考文档 1 conda环境 下载yolov9代码&#xf…

Traefik和HAProxy全方位对比

在面对各种现代应用部署需求时&#xff0c;选择合适的反向代理和负载均衡器至关重要。Traefik&#x1f6a6;和HAProxy&#x1f6e1;️都是领先的解决方案&#xff0c;但它们各有特点&#xff0c;适用于不同的场景。本文将从多个维度全面对比Traefik&#x1f6a6;和HAProxy&…

MySQL基础知识——MySQL日志

一条查询语句的执行过程一般是经过连接器、 分析器、 优化器、 执行器等功能模块&#xff0c; 最后到达存储引擎。 那么&#xff0c; 一条更新语句的执行流程又是怎样的呢&#xff1f; 下面我们从一个表的一条更新语句进行具体介绍&#xff1a; 假设这个表有一个主键ID和一个…

vueRouter动态路由(实现菜单权限控制)

一、权限控制管理&#xff1a; 对于企业级的项目, 我们可能需要对项目做权限控制管理, 实现不同角色的用户登录项目根据所拥有的权限访问不同的页面内容&#xff0c;此时就需要使用到动态路由来对权限页面做限制。 【使用vue-router实现动态路由&#xff0c;达到实现菜单权限…

玩爆私域,和爱豆P图,每天几分钟 轻松日入300+【揭秘】

这个项目的亮点在于能够将你的照片与你喜欢的明星合成一张合影。这种合照在社交媒体上获得了相当高的点赞量。接着&#xff0c;我们可以通过引流和评论区互动&#xff0c;将感兴趣的粉丝转化为我们的微信好友&#xff0c;进而实现交易。你们可以查看我们的收益情况&#xff0c;…

matlab 安装 mingw64(6.3.0),OPENEXR

matlab安装openexr 1. matlab版本与对应的mingw版本选择2. mingw&#xff08;6.3.0&#xff09;下载地址&#xff1a;3. matlab2020a配置mingw&#xff08;6.3.0&#xff09;流程“4. matlab 安装openexr方法一&#xff1a;更新matlab版本方法二&#xff1a;其他博文方法方法三…

每日两题 / 3. 无重复字符的最长子串 84. 柱状图中最大的矩形(LeetCode热题100)

3. 无重复字符的最长子串 - 力扣&#xff08;LeetCode&#xff09; 双指针&#xff0c;l和r从字符串最左边开始&#xff0c;保存l和r之间的所有字符 移动r&#xff0c;若新加入的字符和已有字符重复&#xff0c;则不断移动l&#xff0c;直到l和r之间不出现重复字符 注意&#…

C语言【整数与浮点数的存储区别】

例题引入 #include <stdio.h> int main() {int n 9;float* pFloat (float*)&n;printf("n的值为&#xff1a;%d\n",n);printf("*pFloat的值为&#xff1a;%f\n",*pFloat);*pFloat 9.0;printf("num的值为&#xff1a;%d\n",n);print…

MySQL知识整理

MySQL知识整理 基础第一讲&#xff1a;基础架构&#xff1a;一条SQL查询语句是如何执行的&#xff1f;架构尽量减少长连接的原因和方案为什么尽量不要依赖查询缓存 索引第四讲&#xff1a;深入浅出索引&#xff08;上&#xff09;第五讲&#xff1a;深入浅出索引&#xff08;下…