<数据结构与算法>队列&&OJ


目录

前言

一、队列的概念及结构

二、函数实现

1.特殊的两个结构体定义

2. QueueInit 初始化

3.QueueDestroy 释放 

4.QueuePush 入队

5.QueuePop 出队

6.QueueSize 元素个数

7.QueueEmpty 判空

8.QueueFront 取队头数据

9.QueueBack 取队尾数据

三、代码 

Queue.h

Queue.c

 三、队列OJ

1.用队列模拟栈

2.用栈实现队列 

3.设计循环队列

4. 双端队列 

总结


前言

队列的实现:队列也可以数组和链表的结构实现但使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

栈,对于操作系统来说是一块区域,每个函数在其中创建栈帧,而对于数据结构来说是一种结构,这是不同的概念,只是用了同一个名字;同样的,堆,也是如此


一、队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表队列具有先进先出FIFO(First In First Out)特性

入队列:进行插入操作的一端称为队尾

出队列:进行删除操作的一端称为队头

二、函数实现

1.特殊的两个结构体定义

  • 用单链表实现队列,所以需要定义单链表
  • 由于队列需要队尾和队头,即单链表的尾节点和头节点,所以队列单独创建一个结构体,用来存储两个指针,再加上一个成员size记录队列元素个数(传多个数据,就多创建结构体)
typedef int QDatatype;

typedef struct QueueNode	//链表结构
{
	struct QueueNode* next;
	QDatatype data;
}QNode;

typedef struct Queue		//为了方便形参书写,定义一个结构体包含头指针,尾指针
							//是队列自己的结构体
{
	QNode* head;
	QNode* tail;
	QDatatype size;
}Queue;

2. QueueInit 初始化

  • 改变什么就传它的地址
void QueueInit(Queue* pq)//改变结构体,传结构体指针
{
	assert(pq);

	pq->head = pq->tail = NULL;
	pq->size = 0;
}

3.QueueDestroy 释放 

  • 注意最后的初始化
void QueueDestroy(Queue* pq)//释放
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

4.QueuePush 入队

  • 尾插操作,判断链表是否为空
  • 最后不要忘了size++
void QueuePush(Queue* pq, QDatatype x)//入队
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	if (pq->head == NULL)//判断链表是否为空
	{
		assert(pq->tail == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;//更新尾指针
	}

	pq->size++;
}

5.QueuePop 出队

  • 特殊情况:当链表只剩一个节点时,head和tail同时指向一个节点,head释放后,tail成为野指针出现错误,所以要外加判断进行赋值
  • 最后不要忘了size--
void QueuePop(Queue* pq)//出队
{
	assert(pq);
	assert(pq->head != NULL);

	//法一:
	//QNode* next = pq->head->next;
	//free(pq->head);
	//pq->head = next;
	//if (pq->head == NULL)//这里有问题,当链表只剩一个节点时,head和tail同时指向一个节点,head释放后,tail成为野指针出现错误,所以要外加判断进行赋值
	//{
	//	pq->tail == NULL;
	//}

	//法二:提前判断是否只有一个节点
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}

	pq->size--;//最后不要忘了size--
}

6.QueueSize 元素个数

  • 这就体现了size的便利之处
int QueueSize(Queue* pq)
{

	return pq->size;
}

7.QueueEmpty 判空

  • pq->size == 0 为真返回true,否则返回false
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;//也可判断 head == NULL && tail == NULL
}

8.QueueFront 取队头数据

  • 这里要判断队内元素是否为空
QDatatype QueueFront(Queue* pq)//取队头数据
{
	assert(pq);
	assert(!QueueEmpty(pq));//为空就不能再取数据了

	return pq->head->data;
}

9.QueueBack 取队尾数据

  • 这里要判断队内元素是否为空
QDatatype QueueBack(Queue* pq)//取队尾数据
{
	assert(pq);
	assert(!QueueEmpty(pq));//为空就不能再取数据了

	return pq->tail->data;
}

三、代码 

Queue.h

#pragma once

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

typedef int QDatatype;

typedef struct QueueNode	//链表
{
	struct QueueNode* next;
	QDatatype data;
}QNode;

typedef struct Queue		//为了方便形参书写,定义一个结构体包含头指针,尾指针
							//似乎是队列自己的结构体
{
	QNode* head;
	QNode* tail;
	QDatatype size;
}Queue;

void QueueInit(Queue* pq);//改变结构体改变结构体改变结构体改变结构体,传结构体指针
void QueueDestroy(Queue* pq);//释放
void QueuePush(Queue* pq,QDatatype x);//入队
void QueuePop(Queue* pq);//出队
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);

QDatatype QueueFront(Queue* pq);//取队头数据
QDatatype QueueBack(Queue* pq);//取队尾数据

Queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"

void QueueInit(Queue* pq)//改变结构体,传结构体指针
{
	assert(pq);

	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueueDestroy(Queue* pq)//释放
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq, QDatatype x)//入队
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	if (pq->head == NULL)//判断链表是否为空
	{
		assert(pq->tail == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;//更新尾指针
	}

	pq->size++;
}
void QueuePop(Queue* pq)//出队
{
	assert(pq);
	assert(pq->head != NULL);

	//法一:
	//QNode* next = pq->head->next;
	//free(pq->head);
	//pq->head = next;
	//if (pq->head == NULL)//这里有问题,当链表只剩一个节点时,head和tail同时指向一个节点,head释放后,tail成为野指针出现错误,所以要外加判断进行赋值
	//{
	//	pq->tail == NULL;
	//}

	//法二:提前判断是否只有一个节点
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}

	pq->size--;//最后不要忘了size--
}
int QueueSize(Queue* pq)
{
	/*QNode* cur = pq->head;
	QDatatype count = 0;
	while (cur)
	{
		count++;
	}*/

	return pq->size;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;//也可判断 head == NULL && tail == NULL
}

QDatatype QueueFront(Queue* pq)//取队头数据
{
	assert(pq);
	assert(!QueueEmpty(pq));//为空就不能再取数据了

	return pq->head->data;
}
QDatatype QueueBack(Queue* pq)//取队尾数据
{
	assert(pq);
	assert(!QueueEmpty(pq));//为空就不能再取数据了

	return pq->tail->data;
}

 三、队列OJ

1.用队列模拟栈

 思路:

  1. 将非空队列前size-1个数据倒到另一个空队列,出队
  2. 再将前size-1个数据倒到另一个空队列,出队,来回转移,循环操作
typedef int QDatatype;

typedef struct QueueNode	//链表
{
	struct QueueNode* next;
	QDatatype data;
}QNode;

typedef struct Queue		//为了方便形参书写,定义一个结构体包含头指针,尾指针
							//似乎是队列自己的结构体
{
	QNode* head;
	QNode* tail;
	QDatatype size;
}Queue;

void QueueInit(Queue* pq)//改变结构体,传结构体指针
{
	assert(pq);

	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueueDestroy(Queue* pq)//释放
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq, QDatatype x)//入队
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	if (pq->head == NULL)//判断链表是否为空
	{
		assert(pq->tail == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;//更新尾指针
	}

	pq->size++;
}
void QueuePop(Queue* pq)//出队
{
	assert(pq);
	assert(pq->head != NULL);

	//法一:
	//QNode* next = pq->head->next;
	//free(pq->head);
	//pq->head = next;
	//if (pq->head == NULL)//这里有问题,当链表只剩一个节点时,head和tail同时指向一个节点,head释放后,tail成为野指针出现错误,所以要外加判断进行赋值
	//{
	//	pq->tail == NULL;
	//}

	//法二:提前判断是否只有一个节点
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}

	pq->size--;//最后不要忘了size--
}
int QueueSize(Queue* pq)
{
	/*QNode* cur = pq->head;
	QDatatype count = 0;
	while (cur)
	{
		count++;
	}*/

	return pq->size;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;//也可判断 head == NULL && tail == NULL
}

QDatatype QueueFront(Queue* pq)//取队头数据
{
	assert(pq);
	assert(!QueueEmpty(pq));//为空就不能再取数据了

	return pq->head->data;
}
QDatatype QueueBack(Queue* pq)//取队尾数据
{
	assert(pq);
	assert(!QueueEmpty(pq));//为空就不能再取数据了

	return pq->tail->data;
}

//本题
typedef struct 
{
    Queue q1;
    Queue q2;
} MyStack;

//应该是主函数内调用它
MyStack* myStackCreate() 
{
    //malloc一个结构体代表栈,malloc申请的空间在堆内,函数栈帧收回后该空间不受影响
    MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
    if(pst == NULL)
    {
        perror("malloc fail");
        return NULL;
    }
    
    //对这个栈-结构体里的队列初始化
    QueueInit(&pst->q1);//初始化,要改变结构体,传结构体指针,即地址
    QueueInit(&pst->q2);

    return pst;
}

//push保持只向非空队列进行push
void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
}

//Pop保持将n-1个数据放到空队列中
int myStackPop(MyStack* obj) {
    //这里因为如果要分类要写两次,所以用当时判断相交链表那里小技巧
    Queue* emptyQ = &obj->q1;//先认定它为空队列
    Queue* nonemptyQ = &obj->q2;
    if(!QueueEmpty(&obj->q1))//如果非空,两指针的值交换
    {
        emptyQ = &obj->q2;
        nonemptyQ = &obj->q1;
    }
    //倒数据,循环
    while(QueueSize(nonemptyQ) > 1)
    //这里因为本身就是结构体指针所以不需要再传地址,之所以传地址,是因为我们要改变结构体内的内容
    {
        QueuePush(emptyQ,QueueFront(nonemptyQ));//向空队列入队数据
        QueuePop(nonemptyQ);//拿出队头数据后出队
    }

    int top = QueueFront(nonemptyQ);//出栈并要求返回栈顶元素,即最初的非空队列的队尾元素,这里因为数据倒完了,所以成为队头数据了
    QueuePop(nonemptyQ);//实现出栈功能
    return top;
}

//看栈顶元素,看非空队列队尾数据即可
int myStackTop(MyStack* obj) {
    //Queue* emptyQ = &obj->q1;//先认定它为空队列
    // Queue* nonemptyQ = &obj->q2;
    //if(!QueueEmpty(&obj->q1))//如果非空,两指针的值交换
    //{
    //    emptyQ = &obj->q2;
    //    nonemptyQ = &obj->q1;
    //}

    //return QueueBack(&obj->q2);

    if(!QueueEmpty(&obj->q1))
    {
        return QueueBack(&obj->q1);
    }
    else
    {
        return QueueBack(&obj->q2);
    }
}

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->q1);//释放两个队列
    QueueDestroy(&obj->q1);
    free(obj);
}

三种结构之间关系: 

2.用栈实现队列 

思路:定义两个栈,一个只用来push,一个只用来pop 

 

结构体之间的关系 :

 

typedef int STDataType;

typedef struct Stack
{
	int* a;
	int top;
	int capacity;
}ST;

//初始化
void STInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	ps->capacity = 4;
	ps->top = 0;//top是栈顶元素的下一个位置
	//ps->top = -1; //top是栈顶元素
}

//释放
void STDestroy(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}


void STPush(ST* ps, STDataType x)
{
	assert(ps);

	if (ps->top == ps->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		ps->a = tmp;
		ps->capacity *= 2;
	}
	ps->a[ps->top++] = x;//在top位置把数据放进去
}

void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));//检查是否为空,为空那么top就不能再减了

	ps->top--;
}

int  STSize(ST* ps)
{
	assert(ps);

	return ps->top;//top是栈顶元素的下一个位置,正好对应栈内元素个数
}

int STEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;//top == 0为真就表示空
}

STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));//元素为空就不能访问

	return ps->a[ps->top - 1];//返回栈顶元素
}

typedef struct
{
	ST pushst;
	ST popst;
} MyQueue;


MyQueue* myQueueCreate() {
	MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
	if (obj == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	STInit(&obj->pushst);
	STInit(&obj->popst);
	return obj;
}

void myQueuePush(MyQueue* obj, int x) {
	STPush(&obj->pushst, x);
}

int myQueuePop(MyQueue* obj) {
	if (STEmpty(&obj->popst))//判断popst的栈内元素是否为空
	{
		while (!STEmpty(&obj->pushst))//将pushst栈内元素全压栈到popst
		{
			STPush(&obj->popst, STTop(&obj->pushst));
			STPop(&obj->pushst);
		}
	}

	int front = STTop(&obj->popst);
	STPop(&obj->popst);
	return front;
}

int myQueuePeek(MyQueue* obj) {
	//与myQueuePop一致,只是不需要pop栈顶元素
	if (STEmpty(&obj->popst))
	{
		while (!STEmpty(&obj->pushst))
		{
			STPush(&obj->popst, STTop(&obj->pushst));
			STPop(&obj->pushst);
		}
	}

	int front = STTop(&obj->popst);
	return front;
}

bool myQueueEmpty(MyQueue* obj) {
	return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}

void myQueueFree(MyQueue* obj) {
	STDestroy(&obj->pushst);
	STDestroy(&obj->popst);
	free(obj);
}

 3.设计循环队列

 

思路一:用单链表实现, 但初始化时有一些问题

  • 如果rear指向队尾元素,那么遍历链表时,while( rear != front ),这样链表为空情况和链表为满的情况相同

  • 解决方案1:我们可以外加一个空节点,当rear->next == front 时结束循环 
  • 解决方案2:在结构体内加一个size用于存储队内元素个数,判断size是否等于k,就可以把两种情况分开

但是,以方案一的思路下去,我们不好找队尾元素,每次都要遍历,我们采用另一种方法,数组模拟实现队列

思路:

  • 开k+1的数组空间,front和rear初始化为0,为了避免队列空和满的条件不好区分
  • push时将数据放至下标为rear的空间,rear++
  • pop时将front++,当下标到尾时将下标%(k+1)
  • 队列为空——front == rear
  • 队列为满—— (rear + 1) % (k + 1) == front

typedef struct {

    int* a;
    int k;
    int front;
    int rear;
} MyCircularQueue;

//判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->rear;
}

//判满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->rear+1) % (obj->k+1) == obj->front;//看下表是否相等,这时要考虑下标循环
}

//初始化
MyCircularQueue* myCircularQueueCreate(int k) {
    //初始化要搞好
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->front = obj->rear = 0;
    obj->a = (int*)malloc(sizeof(int)*(k+1));
    obj->k = k;

    return obj;
}
//入队
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }
    obj->a[obj->rear++] = value;
    obj->rear %= (obj->k+1);//下标循环
    return true;
}

//出队
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    obj->front++;
    obj->front %= (obj->k+1);
    return true;
}

//队头元素
int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    else
        return obj->a[obj->front];
}

//队尾元素
int myCircularQueueRear(MyCircularQueue* obj) {
    //return obj->a[obj->rear-1];//这样写是错误的,当rear == 0时,rear-1 = -1,数组越界
    //法一:
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    else
    {
        return obj->a[(obj->rear - 1 + obj->k + 1) % (obj->k + 1)];
    }
    //法二:
    //int x = obj->rear == 0? obj->k : obj->rear-1;
    //return obj->a[x];
}

//释放
void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}

4. 双端队列 

用数组模拟实现双端循环队列,即可以在两端入队与出队

问题:

        如果 front,rear 最初下标相同都为0,左入队后front--,右入队后rear++,发现在右入队时覆盖了左入队的值,那么这就表明左右指针必须错开赋值

        因此,有如下两种解决方法:

1. 改进入队方法:左边依旧先写值,后移动,右边则先移动,后写值

        但是,这样怎么看怎么别扭,左边和右边都执行了入队,但左指针位置无元素,右指针位置却有,而且左右入队操作不同,不太符合通用原则

#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;

typedef int ElemType;
typedef enum { push, pop, inject, eject, End } Operation;

typedef struct QNode
{
    ElemType *Data;      /* 存储元素的数组   */
    int Front, Rear;   /* 队列的头、尾指针 */
    int MaxSize;            /* 队列最大容量     */
}Deque;

Operation GetOp()
{
    char op[7];
    cin>>op;
    if (strcmp(op,"Push")==0) return push;
    if (strcmp(op,"Pop")==0) return pop;
    if (strcmp(op,"Inject")==0) return inject;
    if (strcmp(op,"Eject")==0) return eject;
    if (strcmp(op,"End")==0) return End;
}

void PrintDeque( Deque D )
{
    if(D.Front==D.Rear) cout<<"Deque is Empty!\n";
    else{
        cout<<"Inside Deque:";
        while(D.Front!=D.Rear)
        {
            cout<<" "<<D.Data[D.Front];
            D.Front = (D.Front+1)%D.MaxSize;
        }
    }
}

bool CreateDeque(Deque &D, int MaxSize )
{
    /* 注意:为区分空队列和满队列,需要多开辟一个空间 */
    MaxSize++;
    D.Data = new ElemType[MaxSize];
    D.Front = D.Rear = 0;
    D.MaxSize = MaxSize;
    return true;
}


bool Push(Deque &D, ElemType e)
{
    if ((D.Rear + 1) % D.MaxSize == D.Front)
    {
        return false; // 队列已满
    }
    D.Front = (D.Front - 1 + D.MaxSize) % D.MaxSize;
    D.Data[D.Front] = e;
    return true;
}

bool Pop(Deque &D, ElemType &e)
{
    if (D.Front == D.Rear)
    {
        return false; // 队列为空
    }
    e = D.Data[D.Front];
    D.Front = (D.Front + 1) % D.MaxSize;
    return true;
}

bool Inject(Deque &D, ElemType e)
{
    if ((D.Rear + 1) % D.MaxSize == D.Front)
    {
        return false; // 队列已满
    }
    D.Data[D.Rear] = e;
    D.Rear = (D.Rear + 1) % D.MaxSize;
    return true;
}

bool Eject(Deque &D, ElemType &e)
{
    if (D.Front == D.Rear)
    {
        return false; // 队列为空
    }
    D.Rear = (D.Rear - 1 + D.MaxSize) % D.MaxSize;
    e = D.Data[D.Rear];
    return true;
}

int main()
{
    ElemType X;
    Deque D;
    int N, done = 0;
    cin>>N;
    CreateDeque(D,N);
    while (!done)
    {
        switch(GetOp())
        {
        case push:
            cin>>X;
            if (!Push(D,X)) cout<<"Deque is Full!\n";
                        break;
        case pop:
            if ( !Pop(D,X) ) cout<<"Deque is Empty!\n";
            else cout<<X<<" is out\n";
            break;
        case inject:
            cin>>X;
            if (!Inject(D,X)) cout<<"Deque is Full!\n" ;
            break;
        case eject:
            if ( !Eject(D,X) ) cout<<"Deque is Empty!\n" ;
            else cout<<X<<" is out\n" ;
            break;
        case End:
            PrintDeque(D);
            done = 1;
            break;
        }
    }
    return 0;
}

2. 改进指针位置:既然上述问题是由于左右指针指向同一个位置造成的,那么就可以让指针错位,初始时右指针在左指针右边一格

  • 队空:(front + 1) % (k + 1) == rear
  • 队满:front == rear

总结

        熟练掌握栈与队列,能够相互模拟实现,注意循环队列的各种细节

下一篇,我们将学习二叉树的概念与实现,希望小伙伴们在下一篇前回顾之前内容。最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值