线性表【双向循环链表基本定义与操作】(带头结点)

news2024/9/20 5:38:51

1.双向循环链表的特征与图解

  • 让头结点的 前驱指针 指向 链表的最后一个结点
  • 让 最后一个结点 的后继指针 指向 头结点。

在这里插入图片描述
在这里插入图片描述

2.双向循环链表的重要操作

1.双向循环链表的结构定义

双向循环链表的结构与双向链表完全一致,不同之处在于它的尾结点的next指针指向头结点,而头结点的prior指针指向尾结点,从而形成一个循环。

//1.双向循环链表的结构定义(与双向链表完全一致)
typedef struct DNode
{
	ElemType data;// 数据域
	struct DNode* prior, * next; //前驱和后继指针
}DNode,*HLinkList;
//HNode * 用来定义结点指针
//HLinkList  用来定义链表指针

2.双向循环链表的初始化操作

InitLinkList(HLinkList* L)
1.双向循环链表的头结点和双向链表有所区别

双向链表初始化时,其头结点的next和prior分别指向NULL
双向循环链表初始化时,其头结点的next和prior分别指向自己,构成一个环

2.算法步骤:

(1)初始化头结点内存空间
(2)初始化头结点两个指针域指向自身

// 2. 双向循环链表的初始化操作
bool InitLinkList(HLinkList* L)
{
	// 1 为链表的头结点分配内存
	*L = (DNode*)malloc(sizeof(DNode));

	// 2 检查内存分配是否成功
	if (*L == NULL)
	{
		printf("内存分配失败!\n");
		return false;
	}

	// 3 初始化头结点的前驱和后继指针,指向自身,形成空的循环链表
	(*L)->next = *L;
	(*L)->prior = *L;

	// 4 返回初始化成功的标志
	return true;
}

3.双向循环链表的判空操作

DLinkListEmpty(HLinkList L)
1.算法步骤:

(1)检查头指针是否为NULL,如果是,则链表未初始化,可以认为是空
(2)若头结点的后继和前驱指针指向自身,则链表为空

// 3. 双向循环链表的判空操作
bool DLinkListEmpty(HLinkList L)
{
	//1 如果链表指针为空,则链表为空
	if (L == NULL)
	{
		return true;
	}

	//2 如果链表的头结点的后继和前驱指针指向自身,则链表为空
	//这里可以取其一,但是为了严谨性,取两者
	if (L->next == L&&L->prior==NULL)
	{
		return true;
	}
	else
	{
		return false;
	}
}

4.双向循环链表的按位插入操作

InsertDLinkList(HLinkList L, int i, ElemType e)
1.注意事项:
1.循环的终止条件:

由于链表是循环链表,因此终止条件不能直接使用 p != L;
应通过 p->next != L 来终止遍历
那么相应的 i 大于链表长度加1的情况的判断就有所不同

2.插入时指针的修改:
2.1正常情况下的插入(在表中 头结点 与 尾结点之间 任意位置插入)与双向链表的插入步骤完全一致,不需要考虑头尾结点的指针域的指向

假设在p结点p->next结点中插入一个新结点s
这里的p结点相当于指向待插入结点的前一个结点

  • (1)首先解决新结点的前驱的指向
  • (2)其次解决新结点的后继的指向
  • (3)然后解决p->next结点的前驱指向
  • (4)最后再解决p结点的后继指向
  <1>s->prior=p;//改变新结点前驱指向
  <2>s->next=p->next;//改变新结点后继指向
  <3>p->next->prior=s;//改变p->next结点前驱指向
  <4>p->next=s;//改变p结点后继指向

图解图下:
在表中 头结点 与 尾结点之间任意位置插入
在这里插入图片描述

2.2空表尾部插入,需要注意改变头结点的prior与next域指向,以形成完整的环
<1>s->prior = p;//等价于s->prior =L;
<2>s->next = p->next;//等价于s->next = L;
<3>L->prior = s;	
<4>L->next = s;

空表尾部插入
在这里插入图片描述

2.算法步骤:

(0) 错误处理:头结点不存在 或 i小于1
(1)初始化临时指针p指向头结点
(2)初始化计数器为0
(3)循环查找第i-1个结点的位置(注意终止条件:p->next != L)
(4)异常判断(i大于表长加1的情况(该插入位置错误))
(5)建立并初始化插入结点
(6)空表尾部插入操作(注意改变头结点的prior与next域)
(7)非空表的尾部插入或一般插入操作(与双向链表的正常插入过程一致)

// 4. 双向循环链表的按位插入操作
// 由于链表是循环链表,因此终止条件不能直接使用 p != L;
// 应通过 p->next != L 来终止遍历
bool InsertDLinkList(HLinkList L, int i, ElemType e)
{
	//[0]错误处理:头结点不存在 或 i小于1
	if (L == NULL || i < 1)
	{
		return false;
	}

	//[1]初始化临时指针p指向头结点
	DNode* p = L;

	//[2]初始化计数器为0
	int j = 0;

	//[3]循环查找第i-1个结点的位置
	//p->next!=L:
		//1.保证首元结点不存在时,不进入循环
		//2.保证i大于表长时, p指向尾结点,终止循环(此时如果待插入位置为尾结点的下一个结点,这是有效的,所以i大于表长加1的情况需要特判)
	//j<i-1:
		//正常情况下,找到第i-1个结点
	while (p->next != L && j < i - 1)
	{
		p = p->next;
		j++;
	}

	//[4]异常判断
	//p->next == L && j != i - 1:
		//i大于表长加1的情况(该插入位置错误)
	if (p->next == L && j != i - 1)
	{
		return false;
	}

	//[5]建立并初始化插入结点
	DNode* s = (DNode*)malloc(sizeof(DNode));
	if (s == NULL)
	{
		printf("内存分配失败!\n");
		return false;
	}

	s->data = e;

	//[6]空表尾部插入操作(注意改变头结点的prior与next域)
	// 此时 p 指向头结点,且链表为空(头结点的 next 和 prior 都指向头结点自身)
	if (p->next == L && p->prior == L)
	{
		s->prior = p;//等价于s->prior =L;
		s->next = p->next;//等价于s->next = L;
		//当前p指向空表的头结点,并不存在p->next 结点,所以直接更改头结点的prior,next域即可(为了维护循环结构的完整性)
		L->prior = s;
		L->next = s;
	}
	//[7]非空表的尾部插入或一般插入操作(与双向链表的正常插入过程一致)
	else
	{
		s->prior = p;
		s->next = p->next;//先解决待插入结点的前驱后继指向
		

		p->next->prior = s;//然后解决 待插入结点 后继结点 的前驱指向
		p->next = s;//最后解决 待插入结点 前驱结点的 后继指向      
	}
	return true;
}

5. 双向循环链表的按位删除操作(与双向链表的删除过程完全一致)

DeleteDLinkList(HLinkList L, int i, ElemType *e)
1.思路回顾

假设删除p结点,即待删除结点

  • (1)首先解决 待删除结点 前驱结点的 后继指向
  • (2)其次解决 待删除结点 后继结点的 前驱指向
  • 注意:两者的顺序可以更换
2.几种删除情况的分析:

只有一个元素时删除首元结点
在这里插入图片描述

正常情况下删除尾结点
在这里插入图片描述

正常情况下删除中间结点
在这里插入图片描述

3.算法步骤:

(0)错误处理:头结点不存在 或 i小于1
(1)初始化临时指针p指向首元结点
(2)初始化计数器为1
(3)循环查找第i个结点的位置
(4)异常判断(主要是i大于表长的情况)
(5)核心:删除结点并释放空间


// 5. 双向循环链表的按位删除操作(与双向链表的删除过程完全一致,但是注意终止条件)
bool DeleteDLinkList(HLinkList L, int i, ElemType *e)
{
	//[0]错误处理:头结点不存在 或 i小于1
	if (L == NULL || i < 1)
	{
		return false;
	}

	//[1]初始化临时指针p指向首元结点
	DNode* p = L->next;
	
	//[2]初始化计数器为1
	int j = 1;


	//[3]循环查找第i个结点的位置
	//p!=L:
		//1.保证首元结点不存在时,不进入循环
		//2.保证i大于表长时,p指向尾结点next域(L),终止循环(比如:i=表长加1:此时待删除位置是尾结点 的后面 该位置是头结点的位置)
	//j<i-1:
		//正常情况下,找到第i个结点
	while (p != L && j < i)
	{
		p = p->next;
		j++;
	}

	//[4]异常判断
	//p==L:
		//1.首元结点不存在的情况(空表)
		//2.i大于表长的情况(该删除位置为头结点)
	if (p == L)
	{
		return false;
	}

	//[5]核心:删除结点:更新前驱和后继结点的指针(只要不删除头结点就是对的)
	*e = p->data;
	p->prior->next = p->next;
	p->next->prior = p->prior;

	free(p);//释放待删除结点空间

	return true;
}

6. 双向循环链表的头插法建立

bool CreateDLinkList_H(HLinkList* L, int n)
1.插入分析:

插入到空表表头(实际上与按位插入操作中插入到空表表头的操作完全一致
在这里插入图片描述

插入到非空表表头(同样与按位插入操作中插入到正常位置的操作思路一致
在这里插入图片描述

2.算法步骤:

(1)建立链表头结点并初始化next与prior域 指向自身
(2)循环插入新结点到表头

<1>初始化新结点
<2>改变指针指向(与按位插入的操作大同小异)
<2.1>s插入到空表表头
<2.2>s插入到非空表表头

// 6. 双向循环链表的头插法建立
bool CreateDLinkList_H(HLinkList* L, int n)
{
	//[1]建立链表头结点并初始化next与prior域:注意指向自身
	*L = (DNode*)malloc(sizeof(DNode));

	if ((*L) == NULL)
	{
		printf("内存分配失败!\n");
		return false;
	}

	(*L)->next = (*L);
	(*L)->prior = (*L);

	DNode* s;

	//[2]循环插入新结点到表头
	for (int i = 0; i < n; i++)
	{
		//<1>初始化新结点
		s = (DNode*)malloc(sizeof(DNode));
		if (s == NULL)
		{
			printf("内存分配失败!\n");
			return false;
		}

		scanf_s("%d", &s->data);

		//<2>改变指针指向(与按位插入的操作大同小异)
		//<2.1>s插入到空表表头
		if ((*L)->next == (*L) && (*L)->prior == (*L))
		{
			//如果链表为空,将新结点插入到头结点之后
			s->prior = *L;
			s->next = *L;
			(*L)->next = s;
			(*L)->prior = s;
		}
		//<2.2>s插入到非空表表头
		else
		{
			//如果链表非空,将新结点插入到头结点之后
			s->prior = (*L);
			s->next = (*L)->next;
			(*L)->next->prior = s;
			(*L)->next = s;
		}
	}

	return true;
}

7.双向循环链表的尾插建立:

1.插入分析:
  • 与双向链表相似,由于尾指针永远更新指向最新的尾结点,故后续的插入操作与第一次插入操作完全一致
  • 但是在改变指针指向的时候要注意改变头结点的prior域指向最新的尾结点,这是双向循环链表的特殊结构

插入到表尾
在这里插入图片描述

2.算法步骤:

(1)建立链表头结点并初始化next与prior域(注意指向自身)
(2)初始化尾指针,指向链表的头结点
(3)循环插入新结点到表尾

<1> 初始化新结点
<2> 改变指针指向 (插入到尾部 且 不需要特判)
<2.1>先解决新结点的next与prior域
<2.2> 再解决当前尾结点的next域 并 更新尾指针指向
这里着重注意更新头结点的prior域,以保证循环结构的完整性

// 7.双向循环链表的尾插建立
bool CreateDLinkList_T(HLinkList* L, int n)
{
	// [1] 建立链表头结点并初始化next与prior域(注意指向自身)
	*L = (DNode*)malloc(sizeof(DNode));
	if (*L == NULL)
	{
		printf("内存分配失败!\n");
		return false;
	}

	(*L)->next = (*L);  // 头结点的next指针指向自身,形成循环
	(*L)->prior = (*L); // 头结点的prior指针指向自身,形成循环

	// [2] 初始化尾指针,指向链表的头结点
	DNode* t = (*L);
	
	DNode* s; 

	// [3] 循环插入新结点到表尾
	for (int i = 0; i < n; i++)
	{
		// <1> 初始化新结点
		s = (DNode*)malloc(sizeof(DNode));  
		
		if (s == NULL)
		{
			printf("内存分配失败!\n");
			return false;
		}

		scanf_s("%d", &s->data);  


		// <2> 改变指针指向 (插入到尾部 且 不需要特判)
		//<2.1>先解决新结点的next与prior域
		s->prior = t;
		s->next = *L;//注意循环结构的完整性  
		
		//<2.2> 再解决当前尾结点的next域 并 更新尾指针指向
		//这里着重注意更新头结点的prior域,以保证循环结构的完整性
		t->next = s;   // 更新当前尾结点的next域
		(*L)->prior = s; // 更新头结点的prior域指向新结点(保证循环结构的完整性)

		t = s;  // 更新尾指针t,指向新的尾结点
	}

	return true;
}

8.双向循环链表的整表输出(与单链表完全一致)

printDLinkList(HLinkList L)
1.算法步骤:

(0)错误处理: 头结点不存在或首元结点不存在
(1)初始化临时指针p指向首元结点
(2)错误处理: 首元结点不存在 (空表)
(3)循环遍历链表直到尾结点

// 8.双向循环链表的整表输出
bool printDLinkList(HLinkList L)
{
	// [0] 错误处理: 头结点不存在或首元结点不存在
	if (L == NULL || L->next == L)
	{
		return false;
	}

	// [1] 初始化临时指针p指向首元结点
	DNode* p = L->next;  

	// [3] 循环遍历链表直到尾结点
	while (p != L)
	{
		printf("%d-->", p->data);
		p = p->next;
	}

	printf("end\n");
	return true;
}

9.基本操作如下:

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

typedef int ElemType;
#define bool int
#define true 1
#define false 0

//1.双向循环链表的结构定义(与双向链表完全一致)
typedef struct DNode
{
	ElemType data;// 数据域
	struct DNode* prior, * next; //前驱和后继指针
}DNode, * HLinkList;
//DNode * 用来定义结点指针
//HLinkList  用来定义链表指针


// 2. 双向循环链表的初始化操作
bool InitLinkList(HLinkList* L)
{
	// 1 为链表的头结点分配内存
	*L = (DNode*)malloc(sizeof(DNode));

	// 2 检查内存分配是否成功
	if (*L == NULL)
	{
		printf("内存分配失败!\n");
		return false;
	}

	// 3 初始化头结点的前驱和后继指针,指向自身,形成空的循环链表
	(*L)->next = *L;
	(*L)->prior = *L;

	// 4 返回初始化成功的标志
	return true;
}

// 3. 双向循环链表的判空操作
bool DLinkListEmpty(HLinkList L)
{
	//1 如果链表指针为空,则链表为空
	if (L == NULL)
	{
		return true;
	}

	//2 如果链表的头结点的后继和前驱指针指向自身,则链表为空
	//这里可以取其一,但是为了严谨性,取两者
	if (L->next == L&&L->prior==NULL)
	{
		return true;
	}
	else
	{
		return false;
	}
}


// 4. 双向循环链表的按位插入操作
// 由于链表是循环链表,因此终止条件不能直接使用 p != L;
// 应通过 p->next != L 来终止遍历
bool InsertDLinkList(HLinkList L, int i, ElemType e)
{
	//[0]错误处理:头结点不存在 或 i小于1
	if (L == NULL || i < 1)
	{
		return false;
	}

	//[1]初始化临时指针p指向头结点
	DNode* p = L;

	//[2]初始化计数器为0
	int j = 0;

	//[3]循环查找第i-1个结点的位置
	//p->next!=L:
		//1.保证首元结点不存在时,不进入循环
		//2.保证i大于表长时, p指向尾结点,终止循环(此时如果待插入位置为尾结点的下一个结点,这是有效的,所以i大于表长加1的情况需要特判)
	//j<i-1:
		//正常情况下,找到第i-1个结点
	while (p->next != L && j < i - 1)
	{
		p = p->next;
		j++;
	}

	//[4]异常判断
	//p->next == L && j != i - 1:
		//i大于表长加1的情况(该插入位置错误)
	if (p->next == L && j != i - 1)
	{
		return false;
	}

	//[5]建立并初始化插入结点
	DNode* s = (DNode*)malloc(sizeof(DNode));
	if (s == NULL)
	{
		printf("内存分配失败!\n");
		return false;
	}

	s->data = e;

	//[6]空表尾部插入操作(注意改变头结点的prior与next域)
	// 此时 p 指向头结点,且链表为空(头结点的 next 和 prior 都指向头结点自身)
	if (p->next == L && p->prior == L)
	{
		s->prior = p;//等价于s->prior =L;
		s->next = p->next;//等价于s->next = L;
		//当前p指向空表的头结点,并不存在p->next 结点,所以直接更改头结点的prior,next域即可(为了维护循环结构的完整性)
		L->prior = s;
		L->next = s;
	}
	//[7]非空表的尾部插入或一般插入操作(与双向链表的正常插入过程一致)
	else
	{
		s->prior = p;
		s->next = p->next;//先解决待插入结点的前驱后继指向
		

		p->next->prior = s;//然后解决 待插入结点 后继结点 的前驱指向
		p->next = s;//最后解决 待插入结点 前驱结点的 后继指向      
	}
	return true;
}



// 5. 双向循环链表的按位删除操作(与双向链表的删除过程完全一致,但是注意终止条件)
bool DeleteDLinkList(HLinkList L, int i, ElemType *e)
{
	//[0]错误处理:头结点不存在 或 i小于1
	if (L == NULL || i < 1)
	{
		return false;
	}

	//[1]初始化临时指针p指向首元结点
	DNode* p = L->next;
	
	//[2]初始化计数器为1
	int j = 1;


	//[3]循环查找第i个结点的位置
	//p!=L:
		//1.保证首元结点不存在时,不进入循环
		//2.保证i大于表长时,p指向尾结点next域(L),终止循环(比如:i=表长加1:此时待删除位置是尾结点 的后面 该位置是头结点的位置)
	//j<i-1:
		//正常情况下,找到第i个结点
	while (p != L && j < i)
	{
		p = p->next;
		j++;
	}

	//[4]异常判断
	//p==L:
		//1.首元结点不存在的情况(空表)
		//2.i大于表长的情况(该删除位置为头结点)
	if (p == L)
	{
		return false;
	}

	//[5]核心:删除结点:更新前驱和后继结点的指针(只要不删除头结点就是对的)
	*e = p->data;
	p->prior->next = p->next;
	p->next->prior = p->prior;

	free(p);//释放待删除结点空间

	return true;
}

// 6. 双向循环链表的头插法建立
bool CreateDLinkList_H(HLinkList* L, int n)
{
	//[1]建立链表头结点并初始化next与prior域:注意指向自身
	*L = (DNode*)malloc(sizeof(DNode));

	if ((*L) == NULL)
	{
		printf("内存分配失败!\n");
		return false;
	}

	(*L)->next = (*L);
	(*L)->prior = (*L);

	DNode* s;

	//[2]循环插入新结点到表头
	for (int i = 0; i < n; i++)
	{
		//<1>初始化新结点
		s = (DNode*)malloc(sizeof(DNode));
		if (s == NULL)
		{
			printf("内存分配失败!\n");
			return false;
		}

		scanf_s("%d", &s->data);

		//<2>改变指针指向(与按位插入的操作大同小异)
		//<2.1>s插入到空表表头
		if ((*L)->next == (*L) && (*L)->prior == (*L))
		{
			//如果链表为空,将新结点插入到头结点之后
			s->prior = *L;
			s->next = *L;
			(*L)->next = s;
			(*L)->prior = s;
		}
		//<2.2>s插入到非空表表头
		else
		{
			//如果链表非空,将新结点插入到头结点之后
			s->prior = (*L);
			s->next = (*L)->next;
			(*L)->next->prior = s;
			(*L)->next = s;
		}
	}

	return true;
}


// 7.双向循环链表的尾插建立
bool CreateDLinkList_T(HLinkList* L, int n)
{
	// [1] 建立链表头结点并初始化next与prior域(注意指向自身)
	*L = (DNode*)malloc(sizeof(DNode));
	if (*L == NULL)
	{
		printf("内存分配失败!\n");
		return false;
	}

	(*L)->next = (*L);  // 头结点的next指针指向自身,形成循环
	(*L)->prior = (*L); // 头结点的prior指针指向自身,形成循环

	// [2] 初始化尾指针,指向链表的头结点
	DNode* t = (*L);
	
	DNode* s; 

	// [3] 循环插入新结点到表尾
	for (int i = 0; i < n; i++)
	{
		// <1> 初始化新结点
		s = (DNode*)malloc(sizeof(DNode));  
		
		if (s == NULL)
		{
			printf("内存分配失败!\n");
			return false;
		}

		scanf_s("%d", &s->data);  


		// <2> 改变指针指向 (插入到尾部 且 不需要特判)
		//<2.1>先解决新结点的next与prior域
		s->prior = t;
		s->next = *L;//注意循环结构的完整性  
		
		//<2.2> 再解决当前尾结点的next域 并 更新尾指针指向
		//这里着重注意更新头结点的prior域,以保证循环结构的完整性
		t->next = s;   // 更新当前尾结点的next域
		(*L)->prior = s; // 更新头结点的prior域指向新结点(保证循环结构的完整性)

		t = s;  // 更新尾指针t,指向新的尾结点
	}

	return true;
}


// 8.双向循环链表的整表输出
bool printDLinkList(HLinkList L)
{
	// [0] 错误处理: 头结点不存在或首元结点不存在
	if (L == NULL || L->next == L)
	{
		return false;
	}

	// [1] 初始化临时指针p指向首元结点
	DNode* p = L->next;  

	// [3] 循环遍历链表直到尾结点
	while (p != L)
	{
		printf("%d-->", p->data);
		p = p->next;
	}

	printf("end\n");
	return true;
}


int main()
{
	HLinkList L1,L2;
	printf("头插法建立双向循环链表L1:\n");
	CreateDLinkList_H(&L1, 6);
	printf("尾插法建立双向循环链表L2:\n");
	CreateDLinkList_T(&L2, 6);
	printf("从头打印双向循环链表L1:\n");
	printDLinkList(L1);
	printf("\n");

	printf("从头打印双向循环链表L2:\n");
	printDLinkList(L2);

	printf("向L2中插入部分元素!\n");
	printf("表尾插入:\n");
	InsertDLinkList(L2, 7, 3);
	printDLinkList(L2);

	printf("表头插入:\n");
	InsertDLinkList(L2, 1, 33);
	printDLinkList(L2);

	printf("删除L2中部分元素:\n");
	ElemType e1;
	printf("删除表头元素!\n");
	DeleteDLinkList(L2, 1, &e1);
	printDLinkList(L2);


	ElemType e2;
	printf("删除表尾元素!\n");
	DeleteDLinkList(L2, 7, &e2);
	printDLinkList(L2);



	if (DLinkListEmpty(L2))
	{
		printf("L2为空表!\n");
	}
	else
	{
		printf("L2不为空表!\n");
	}
	return 0;
}

在这里插入图片描述

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

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

相关文章

【Docker深入浅出】【四】单体应用容器化与Dockerfile怎么写

文章目录 一. 应用的容器化——简介二. 单体应用容器化1. 获取代码与分析Dockfile2. 容器化当前应用&#xff08;构建具体的镜像&#xff09;3&#xff0e;推送镜像到仓库4. 运行应用程序5. 小结 三. 生产环境中的多阶段构建四. 应用容器化命令 本文介绍了如何容器化&#xff0…

springboot的学习(三):开发相关

简介 一些开发测试时用到的技术。 springboot 热部署 修改了代码&#xff0c;服务器不需要重启可以直接看到新的修改的效果。仅仅加载当前开发者自定义开发的资源&#xff0c;不加载jar资源。 在pom.xml配置文件中添加&#xff1a; <dependency><groupId>org.s…

AI可以写毕业论文吗?6款亲测好用人工智能写论文网站

AI写作工具在学术界的应用已经逐渐成为一种趋势&#xff0c;特别是在毕业论文的撰写过程中。这些工具不仅能够提高写作效率&#xff0c;还能帮助学生更好地组织和规划他们的研究内容。以下是六款经过亲测且好用的人工智能写论文网站推荐&#xff1a; 一、千笔-AIPassPaper 千笔…

【自动驾驶】控制算法(三)轮胎侧偏与车辆动力学模型

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

苍鹰来啦!快来看呀!NGO-BiTCN-BiGRU-Attention北方苍鹰算法优化多重双向深度学习回归预测

苍鹰来啦!快来看呀&#xff01;NGO-BiTCN-BiGRU-Attention北方苍鹰算法优化多重双向深度学习回归预测 目录 苍鹰来啦!快来看呀&#xff01;NGO-BiTCN-BiGRU-Attention北方苍鹰算法优化多重双向深度学习回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实…

Java实现MQTT通信(发布订阅消息)

文章目录 前言一、相关pom依赖二、相关代码1.MQTT工具类2.MQTT回调函数3.订阅消息4.发布消息 三、安装mosquitto1.mosquitto简介2.下载 四、安装MQTT.fx1.MQTT.fx简介2.下载3.使用 五、java订阅消息六、java发布消息 前言 MQTT是一种轻量级的物联网通信协议&#xff0c;基于客…

[Meachines] [Easy] Blue MS17-010永恒之蓝

信息收集 IP AddressOpening Ports10.10.10.40TCP:135/tcp msrpc, 139/tcp netbios-ssn, 445/tcp microsoft-ds, 49152/tcp msrpc, 49153/tcp msrpc, 49154/tcp msrpc, 49155/tcp msrpc, 49156/tcp msrpc, 49157/tcp msrpc $ nmap -p- 10.10.10.40 --min-rate 1000 -sC -sV …

YOLOV8 POSE姿态检测对图片绘制矩形和和关节点序号

代码如下 import cv2 import torchfrom ultralytics import YOLO# Load a model # model YOLO("yolov8n-pose.yaml") # build a new model from YAML model YOLO("yolov8n-pose.pt") # load a pretrained model (recommended for training) # model …

SQL— DQL语句学习【后端 11】

DQL语句 引言 DQL&#xff08;Data Query Language&#xff0c;即数据查询语言&#xff09;是SQL&#xff08;Structured Query Language&#xff09;中用于从数据库中检索数据的重要部分。在数据库管理中&#xff0c;DQL语句是日常工作中最常用的工具之一。通过DQL&#xff0…

leetcode-538. 把二叉搜索树转换为累加树

题目描述 给出二叉 搜索 树的根节点&#xff0c;该树的节点值各不相同&#xff0c;请你将其转换为累加树&#xff08;Greater Sum Tree&#xff09;&#xff0c;使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 提醒一下&#xff0c;二叉搜索树满足下列约束…

C++11:右值引用、移动语义和完美转发

目录 前言 1. 左值引用和右值引用 2. 引用范围 3. 左值引用的缺陷 4. 右值引用的作用 5. 右值引用的深入场景 6. 完美转发 总结 前言 C11作为一次重大的更新&#xff0c;引入了许多革命性的特性&#xff0c;其中之一便是右值引用和移动语义。本文将深入探讨其中引入的…

Unity抖音直播玩法开发流程

前言 近两年直播玩法逐渐新兴起来了&#xff0c;也出现不少质量还不错的作品&#xff0c;比如下列《红蓝对决》《三国全战》等。近期我们也做了一款直播玩法&#xff0c;就此记录下开发流程。 1&#xff0c;申请应用 进入抖音开发者平台&#xff0c;在首页入驻平台。 如果是…

Unity的粒子系统

目录 基础参数与模块 创建与编辑 功能与应用 实例与教程 结论 Unity粒子系统的最新功能和更新有哪些&#xff1f; 如何在Unity中使用Visual Effect Graph创建复杂粒子效果&#xff1f; Unity粒子系统的高级应用技巧有哪些&#xff1f; 在Unity中实现粒子系统时的性能优…

回溯算法(基于Python)

递归 递归(recursion)是一种算法策略&#xff0c;通过函数调用自身来解决问题。"递"指程序不断深入地调用自身&#xff0c;通常传入更小或更简化的参数&#xff0c;直到达到“终止条件”。"归"指触发终止条件后&#xff0c;程序从最深层的递归函数开始逐层…

代码块分类

局部代码块 public class Test {public static void main(String[] args) {{int a 10;}// 执行到此处时候,变量a已经从内存中消失了。 // System.out.println(a);} } 构造代码块 public class Test {private String name;private int age;{// 构造代码块System.out.…

【STM32 Blue Pill编程】-定时器与中断

定时器与中断 文章目录 定时器与中断1、硬件准备及接线2、GPIO配置3、代码实现STM32F103C8 配有四个定时器,分别为 TIM1、TIM2、TIM3 和 TIM4。 它们充当时钟并用于跟踪基于时间的事件。 我们将展示如何使用 HAL 库在 STM32Cube IDE 中对这些定时器进行编程。 本文将涉及如下内…

【网络】抓包工具的使用

抓包工具 文章目录 1.tcpdump抓包1.1安装 tcpdump1.2常见使用 2.wireshark抓包 1.tcpdump抓包 TCPDump 是一款强大的网络分析工具&#xff0c; 主要用于捕获和分析网络上传输的数据包。 1.1安装 tcpdump tcpdump 通常已经预装在大多数 Linux 发行版中。 如果没有安装&#…

常见java OOM异常分析排查思路分析

Java 虚拟机&#xff08;JVM&#xff09;发生 OutOfMemoryError&#xff08;OOM&#xff09;异常时&#xff0c;表示 JVM 在尝试分配内存时无法找到足够的内存资源。以下是几种常见的导致 OOM 异常的情况&#xff1a; 1. Java 堆空间不足 (Java Heap Space) 这种情况发生在 J…

【小球下落反弹】小球自由落下,每次落地后反跳回原高度的一半

一小球从100米高度自由落下&#xff0c;每次落地后反跳回原高度的一半&#xff1b;再落下&#xff0c;求它在第10次落地时&#xff0c;共经过多少米&#xff1f;第10次反弹多高&#xff1f; 使用C语言实现&#xff0c;具体代码&#xff1a; #include<stdio.h>int main(…

wo是如何克服编程学习中的挫折感的?

你是如何克服编程学习中的挫折感的&#xff1f; 编程学习之路上&#xff0c;挫折感就像一道道难以逾越的高墙&#xff0c;让许多人望而却步。然而&#xff0c;真正的编程高手都曾在这条路上跌倒过、迷茫过&#xff0c;却最终找到了突破的方法。你是如何在Bug的迷宫中找到出口的…