探索数据结构:特殊的双向队列

这篇具有很好参考价值的文章主要介绍了探索数据结构:特殊的双向队列。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

探索数据结构:特殊的双向队列,数据结构与算法:C/C++全解析,数据结构,双向队列,队列,双向队列的应用,C语言

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 双向队列的定义

**双向队列(double‑ended queue)**是一种特殊的队列,它允许在队列的队尾与队头插入与删除元素。根据其定义,我们也可以理解为两个栈在栈底相连。

  1. 队尾入队

探索数据结构:特殊的双向队列,数据结构与算法:C/C++全解析,数据结构,双向队列,队列,双向队列的应用,C语言

  1. 队首入队

探索数据结构:特殊的双向队列,数据结构与算法:C/C++全解析,数据结构,双向队列,队列,双向队列的应用,C语言

  1. 队尾出队

探索数据结构:特殊的双向队列,数据结构与算法:C/C++全解析,数据结构,双向队列,队列,双向队列的应用,C语言

  1. 队尾出队

探索数据结构:特殊的双向队列,数据结构与算法:C/C++全解析,数据结构,双向队列,队列,双向队列的应用,C语言

2. 双向队列的分类

双向队列也是线性表的一种,所以也可以分别用链表数组实现。基于链表实现:为了方便双向队列在尾部的插入与删除操作,所以我们选用双向链表。基于数组实现:与队列实现类似,需要用循环数组(原因参考队列实现)。

探索数据结构:特殊的双向队列,数据结构与算法:C/C++全解析,数据结构,双向队列,队列,双向队列的应用,C语言

3. 双向队列的功能

  1. 队列的初始化。
  2. 判断队列是否为空。。
  3. 返回队头与队尾的元素。
  4. 返回队列的大小。
  5. 入队与出队。
  6. 打印队列的元素。
  7. 销毁队列。

4. 双向队列的声明

4.1. 链式队

双向队列与普通队列的声明区别就在于双向队列是基于双向链表的方式实现。

typedef int QDataType;
typedef struct DuListNode
{
	QDataType data;
	struct Node* prev;
	struct Node* next;
}DuListNode;

typedef struct Deque
{
	size_t size;
	DuListNode* front;
	DuListNode* rear;
}Deque;

4.2. 循环队

循环队列的实现方式与普通队列差不多。

typedef int QDataType;
#define MAXSIZE 50  //定义元素的最大个数
typedef struct {
    QDataType *data;
    int front;  //头指针
    int rear;   //尾指针
}Deque;

5. 队列的初始化

5.1. 链式队

void DequeInit(Deque* d)//初始化
{
	assert(d);
	d->front = NULL;
	d->rear = NULL;
	d->size = 0;
}

5.2. 循环队

void DequeInit(Deque* d)//初始化
{
	d->data = (QDataType*)malloc(sizeof(QDataType )* MAXSIZE);
	if (d->data == NULL)
	{
		perror("malloc fail:");
		return;
	}
	d->front = 0;
	d->rear = 0;
}

5.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:链式队空间是一个固定大小,空间复杂度为O(1)。而需要开辟整个队列的大小,空间复杂度为O(N)。

6. 判断队列是否为空

6.1. 链式队

bool DequeEmpty(Deque* d)//判断是否为空
{
	assert(d);
	return (d->front == NULL) && (d->rear == NULL);
}

6.2. 循环队

bool DequeEmpty(Deque* d)//判断是否为空
{
	assert(d);
	return d->front == d->rear;
}

6.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

7. 判断队列是否为满

7.1. 链式队

链式队并不需要判断。

7.2. 循环队

为什么要取模操作,可以参考一下上一篇普通队列的实现,同下。

bool DequeFull(Deque* d)//判断队列是否满
{
	assert(d);
	return (d->rear + 1) % MAXSIZE == d->front;
}

8. 返回队头与队尾的元素

8.1. 链式队

QDataType DequeFront(Deque* d)//获取队头元素
{
	assert(d);
	assert(!DequeEmpty(d));
	return d->front->data;
}
QDataType DequeBack(Deque* d)//获取队尾元素
{
	assert(d);
	assert(!DequeEmpty(d));
	return d->rear->data;
}

8.2. 循环队

QDataType DequeFront(Deque* d)//获取队头元素
{
	assert(d);
	assert(!DequeEmpty(d));
	return d->data[d->front];
}
QDataType DequeBack(Deque* d)//获取队尾元素
{
	assert(d);
	assert(!DequeEmpty(d));
	return d->data[(d->rear-1+MAXSIZE)%MAXSIZE];
}

8.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)

9. 返回队列的大小

9.1. 链式队

size_t DequeSize(Deque* d)//队列长度
{
	return d->size;
}

9.2. 循环队

size_t DequeSize(Deque* d)//获取队列长度
{
	assert(d);
	return (d->rear - d->front + MAXSIZE) % MAXSIZE;
}

9.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)

10. 入队

10.1. 链式队

10.1.1. 队头入队
void DequeFrontPush(Deque* d, QDataType x)//队首入队
{
	assert(d);
	DuListNode* newnode = (DuListNode*)malloc(sizeof(DuListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	if (d->front == NULL)
	{
		d->front = d->rear = newnode;
	}
	else
	{
		d->front->prev = newnode;
		newnode->next = d->front;
		d->front = newnode;
	}
    d->size++;
}
10.1.2. 队尾入队
void DequeRearPush(Deque* d, QDataType x)//队尾入队
{
	assert(d);
	DuListNode* newnode = (DuListNode*)malloc(sizeof(DuListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	if (d->front == NULL)
	{
		d->front = d->rear = newnode;
	}
	else
	{
		d->rear->next = newnode;
		newnode->prev = d->rear;
		d->rear = newnode;
	}
	d->size++;
}

10.2. 循环队

入队需要提前判断队列是否为满。

10.2.1. 队头入队
void DequeFrontPush(Deque* d, QDataType x)//队首入队
{
	assert(d);
	if (DequeFull(d))
	{
		printf("队列已满\n");
		return;
	}
	d->data[(d->front - 1 + MAXSIZE) % MAXSIZE]=x;
	d->front = (d->front - 1 + MAXSIZE) % MAXSIZE;
}
10.2.2. 队尾入队
void DequeRearPush(Deque* d, QDataType x)//队尾入队
{
	assert(d);
	if (DequeFull(d))
	{
		printf("队列已满\n");
		return;
	}
	d->data[d->rear] = x;
	d->rear = (d->rear + 1) % MAXSIZE;
}

10.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)

11. 出队

11.1. 链式队

出队需要提前判断队列是否为空。

11.1.1. 队头出队
void DequeFrontPop(Deque* d)//队首出队
{
	assert(d);
	assert(!DequeEmpty(d));
	//1.只有一个结点
	if (d->front == d->rear)
	{
		free(d->front);
		d->front = d->rear = NULL;
	}
	//2.有多个结点
	else
	{
		DuListNode* next = d->front->next;
		next->prev = NULL;
		d->front->next = NULL;
		free(d->front);
		d->front = next;
	}
	d->size--;
}
11.1.2. 队尾出队
void DequeRearPop(Deque* d)//队尾出队
{
	assert(d);
	assert(!DequeEmpty(d));
	//1.只有一个结点
	if (d->front == d->rear)
	{
		free(d->front);
		d->front = d->rear = NULL;
	}
	else
	{
		DuListNode* prev = d->rear->prev;
		prev->next = NULL;
		d->rear->prev = NULL;
		free(d->rear);
		d->rear = prev;
	}
    d->size--;
}

11.2. 循环队

11.2.1. 队头出队
void DequeFrontPop(Deque* d)//队首出队
{
	assert(d);
	assert(!DequeEmpty(d));
	d->front = (d->front + 1) % MAXSIZE;
}
11.2.2. 队尾出队
void DequeRearPop(Deque* d)//队尾出队
{
	assert(d);
	assert(!DequeEmpty(d));
	d->rear = (d->rear - 1+MAXSIZE) % MAXSIZE;
}

11.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)

12. 打印队列元素

12.1. 链式队

void DequePrint(Deque* d)//打印队列元素
{
	assert(d);
	DuListNode* cur = d->front;
	DuListNode* tail = d->rear;
	printf("队头:");
	while (cur != tail->next)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("队尾\n");
}

12.2. 循环队

void DequePrint(Deque* d)//打印队列元素
{
	assert(d);
	int cur = d->front;
	printf("队头->");
	while (cur != d->rear)
	{
		printf("%d->", d->data[cur]);
		cur = (cur + 1) % MAXSIZE;
	}
	printf("队尾\n");

}

12.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列都需要遍历这个队列,所以时间复杂度为O(N)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)

13. 销毁队列

13.1. 链式队

void DequeDestroy(Deque* d)//销毁队列
{
	assert(d);
	DuListNode* cur = d->front;
	while (cur)
	{
		DuListNode* del = cur;
		cur = cur->next;
		free(del);
		del = NULL;
	}
	d->front = d->rear = NULL;
}

13.2. 循环队

void DequeDestroy(Deque* d)//销毁队列
{
	assert(d);
	free(d->data);
	d->data = NULL;
	d->front = d->rear = 0;
}

13.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)

14. 对比与应用

14.1. 对比

双向队列的两种实现方式的效果与普通队列实现差不多,这里就不在一一赘述。

14.2. 应用

双向队列兼备队列与栈的性质,所以可以应用于这两种数据结构的所有应用场景。

此外它应用于撤销的一种情景:通常情况下,撤销是以栈的方式实现,当我们每次更改时就入栈,撤销就出栈。但是我们知道系统给与栈的空间是有限的,我们不可能一直入栈。当入栈超过一个限度时,我们就用过删除栈底的数据,这时栈这个数据结构就无法满足需求。所以这时我们可以使用双向队列来实现。文章来源地址https://www.toymoban.com/news/detail-845950.html

15. 完整代码

15.1. 链式队

15.1.1. Deque.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct DuListNode
{
	QDataType data;
	struct Node* prev;
	struct Node* next;
}DuListNode;

typedef struct Deque
{
	size_t size;
	DuListNode* front;
	DuListNode* rear;
}Deque;
void DequeInit(Deque* d);//初始化
bool DequeEmpty(Deque* d);//判断是否为空
QDataType DequeFront(Deque* d);//获取队头元素
QDataType DequeBack(Deque* d);//获取队尾元素
size_t DequeSize(Deque* d);//获取队列长度
void DequeFrontPush(Deque* d, QDataType x);//队首入队
void DequeRearPush(Deque* d, QDataType x);//队尾入队
void DequeFrontPop(Deque* d);//队首出队
void DequeRearPop(Deque* d);//队尾出队
void DequePrint(Deque* d);//打印队列元素
void DequeDestroy(Deque* d);//销毁队列
15.1.2. Deque.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Deque.h"
void DequeInit(Deque* d)//初始化
{
	assert(d);
	d->front = NULL;
	d->rear = NULL;
	d->size = 0;
}
bool DequeEmpty(Deque* d)//判断是否为空
{
	assert(d);
	return (d->front == NULL) && (d->rear == NULL);
}
QDataType DequeFront(Deque* d)//获取队头元素
{
	assert(d);
	assert(!DequeEmpty(d));
	return d->front->data;
}
QDataType DequeBack(Deque* d)//获取队尾元素
{
	assert(d);
	assert(!DequeEmpty(d));
	return d->rear->data;
}
size_t DequeSize(Deque* d)//队列长度
{
	return d->size;
}
void DequeFrontPush(Deque* d, QDataType x)//队首入队
{
	assert(d);
	DuListNode* newnode = (DuListNode*)malloc(sizeof(DuListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	if (d->front == NULL)
	{
		d->front = d->rear = newnode;
	}
	else
	{
		d->front->prev = newnode;
		newnode->next = d->front;
		d->front = newnode;
	}
    d->size++;
}
void DequeRearPush(Deque* d, QDataType x)//队尾入队
{
	assert(d);
	DuListNode* newnode = (DuListNode*)malloc(sizeof(DuListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	if (d->front == NULL)
	{
		d->front = d->rear = newnode;
	}
	else
	{
		d->rear->next = newnode;
		newnode->prev = d->rear;
		d->rear = newnode;
	}
	d->size++;
}
void DequeFrontPop(Deque* d)//队首出队
{
	assert(d);
	assert(!DequeEmpty(d));
	//1.只有一个结点
	if (d->front == d->rear)
	{
		free(d->front);
		d->front = d->rear = NULL;
	}
	//2.有多个结点
	else
	{
		DuListNode* next = d->front->next;
		next->prev = NULL;
		d->front->next = NULL;
		free(d->front);
		d->front = next;
	}
	d->size--;
}
void DequeRearPop(Deque* d)//队尾出队
{
	assert(d);
	assert(!DequeEmpty(d));
	//1.只有一个结点
	if (d->front == d->rear)
	{
		free(d->front);
		d->front = d->rear = NULL;
	}
	else
	{
		DuListNode* prev = d->rear->prev;
		prev->next = NULL;
		d->rear->prev = NULL;
		free(d->rear);
		d->rear = prev;
	}
    d->size--;
}
void DequePrint(Deque* d)//打印队列元素
{
	assert(d);
	DuListNode* cur = d->front;
	DuListNode* tail = d->rear;
	printf("队头:");
	while (cur != tail->next)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("队尾\n");
}
void DequeDestroy(Deque* d)//销毁队列
{
	assert(d);
	DuListNode* cur = d->front;
	while (cur)
	{
		DuListNode* del = cur;
		cur = cur->next;
		free(del);
		del = NULL;
	}
	d->front = d->rear = NULL;
}

15.2. 循环队

15.2.1. Deque.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
#define MAXSIZE 50  //定义元素的最大个数
typedef struct {
    QDataType *data;
    int front;  //头指针
    int rear;   //尾指针
}Deque;

void DequeInit(Deque* d);//初始化
bool DequeEmpty(Deque* d);//判断是否为空
bool DequeFull(Deque* d);//判断队列是否满
QDataType DequeFront(Deque* d);//获取队头元素
QDataType DequeBack(Deque* d);//获取队尾元素
size_t DequeSize(Deque* d);//获取队列长度
void DequeFrontPush(Deque* d, QDataType x);//队首入队
void DequeRearPush(Deque* d, QDataType x);//队尾入队
void DequeFrontPop(Deque* d);//队首出队
void DequeRearPop(Deque* d);//队尾出队
void DequePrint(Deque* d);//打印队列元素
void DequeDestroy(Deque* d);//销毁队列
15.2.2. Deque.c
void DequeInit(Deque* d)//初始化
{
	d->data = (QDataType*)malloc(sizeof(QDataType )* MAXSIZE);
	if (d->data == NULL)
	{
		perror("malloc fail:");
		return;
	}
	d->front = 0;
	d->rear = 0;
}
bool DequeEmpty(Deque* d)//判断是否为空
{
	assert(d);
	return d->front == d->rear;
}
bool DequeFull(Deque* d)//判断队列是否满
{
	assert(d);
	return (d->rear + 1) % MAXSIZE == d->front;
}
QDataType DequeFront(Deque* d)//获取队头元素
{
	assert(d);
	assert(!DequeEmpty(d));
	return d->data[d->front];
}
QDataType DequeBack(Deque* d)//获取队尾元素
{
	assert(d);
	assert(!DequeEmpty(d));
	return d->data[(d->rear-1+MAXSIZE)%MAXSIZE];
}
size_t DequeSize(Deque* d)//获取队列长度
{
	assert(d);
	return (d->rear - d->front + MAXSIZE) % MAXSIZE;
}
void DequeFrontPush(Deque* d, QDataType x)//队首入队
{
	assert(d);
	if (DequeFull(d))
	{
		printf("队列已满\n");
		return;
	}
	d->data[(d->front - 1 + MAXSIZE) % MAXSIZE]=x;
	d->front = (d->front - 1 + MAXSIZE) % MAXSIZE;
}
void DequeRearPush(Deque* d, QDataType x)//队尾入队
{
	assert(d);
	if (DequeFull(d))
	{
		printf("队列已满\n");
		return;
	}
	d->data[d->rear] = x;
	d->rear = (d->rear + 1) % MAXSIZE;
}
void DequeFrontPop(Deque* d)//队首出队
{
	assert(d);
	assert(!DequeEmpty(d));
	d->front = (d->front + 1) % MAXSIZE;
}
void DequeRearPop(Deque* d)//队尾出队
{
	assert(d);
	assert(!DequeEmpty(d));
	d->rear = (d->rear - 1+MAXSIZE) % MAXSIZE;
}
void DequePrint(Deque* d)//打印队列元素
{
	assert(d);
	int cur = d->front;
	printf("队头->");
	while (cur != d->rear)
	{
		printf("%d->", d->data[cur]);
		cur = (cur + 1) % MAXSIZE;
	}
	printf("队尾\n");

}
void DequeDestroy(Deque* d)//销毁队列
{
	assert(d);
	free(d->data);
	d->data = NULL;
	d->front = d->rear = 0;
}

到了这里,关于探索数据结构:特殊的双向队列的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入了解队列:探索FIFO数据结构及队列

    之前介绍了栈:探索栈数据结构:深入了解其实用与实现(c语言实现栈) 那就快马加鞭来进行队列内容的梳理。队列和栈有着截然不同的工作方式,队列遵循先进先出(FIFO)的原则,在许多场景下都表现出强大的效率和实用性 源码可以来我的github进行查找:Nerosts/just-a-tr

    2024年02月03日
    浏览(42)
  • 【数据结构】--- 探索栈和队列的奥秘

    关注小庄 顿顿解馋૮(˶ᵔ ᵕ ᵔ˶)ა 💡个人主页:9ilk 💡专栏:数据结构之旅 上回我们学习了顺序表和链表,今天博主来讲解两个新的数据结构 — 栈和队列 , 请放心食用 对于这么坨书,我们要拿到最下面的书是不是要最后才能拿到;而对于最上面的书它是最晚放上去的

    2024年04月13日
    浏览(47)
  • 数据结构与算法:双向链表

    朋友们大家好啊,在上节完成单链表的讲解后,我们本篇文章来对 带头循环双向链表进行讲解 单链表中,一个节点存储数据和指向下一个节点的指针,而双向链表除了上述两个内容,还包括了 指向上一个节点的指针 带头的双向链表,是指在双向链表的最前端添加了一个 额

    2024年02月20日
    浏览(51)
  • 【数据结构与算法】双向链表

    作者:旧梦拾遗186 专栏:数据结构成长日记   带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。 现在我们来通

    2024年02月11日
    浏览(57)
  • 数据结构与算法(四):双向链表

    双向链表概念和单向链表是一致的,区别在于双向链表在单向链表的基础上,指针区域多了一个指向上一个节点的指针。单向链表内容可以参考我的上一篇文章:http://t.csdn.cn/Iu56H。 基本的数据结构如图所示: 双向链表结构包含了节点的数据内容和两个指针:指向前一个节点

    2024年02月14日
    浏览(52)
  • 探索数据结构:链式队与循环队列的模拟、实现与应用

    队列(queue)是一种只允许在一端进行插入操作,而在另一端进行删除操作的线性表。其严格遵循 先进先出(First In First Out) 的规则,简称 FIFO 。 队头(Front) :允许删除的一端,又称队首。 队尾(Rear) :允许插入的一端。 队列与栈类似,实现方式有两种。一种是以 数组

    2024年04月08日
    浏览(83)
  • 【数据结构和算法】使用数组的结构实现链表(单向或双向)

    上文我们通过结构体的结构实现了队列 、以及循环队列的实现,我们或许在其他老师的教学中,只学到了用结构体的形式来实现链表、队列、栈等数据结构,本文我想告诉你的是,我们 可以使用数组的结构实现链表、单调栈、单调队列 目录 前言 一、用数组结构的好处 1.数

    2024年01月20日
    浏览(74)
  • 【数据结构与算法】之双向链表及其实现!

    ​                                                                                 个人主页:秋风起,再归来~                                                                                             数据结构与

    2024年04月23日
    浏览(42)
  • 【数据结构与算法】 - 双向链表 - 详细实现思路及代码

    前几篇文章介绍了怎样去实现单链表、单循环链表, 这篇文章主要介绍 双向链表 以及实现双向链表的步骤,最后提供我自己根据理解实现双向链表的C语言代码 。跟着后面实现思路看下去,应该可以看懂代码,看懂代码后,就对双向链表有了比较抽象的理解了,最后自己再

    2024年02月01日
    浏览(39)
  • 【数据结构和算法】--队列

    队列是只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有 先进先出 FIFO(First In First Out) 的原则。 入队列 :进行 插入操作的一端称为队尾 。 出队列 :进行 删除操作的一端称为队头 。 队列结构联想起来也非常简单,如其名,队列就相当于

    2024年02月05日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包