数据结构学习之路--玩转队列的内核知识(附C源码)

   嗨嗨大家~我又来啦!今天为大家带来的是与队列相关的知识。我们马上进入知识的海洋~


目录

前言

一、队列 

1 队列的概念 

2 队列的实现 

2.1 队列的定义 

2.2 队列的初始化 

2.3 队列的判空 

2.4 入队 

2.5 出队 

2.6 取队头元素 

2.7 取队尾元素 

2.8 取有效元素个数(队列大小)

2.9 队列的销毁 

二、源代码 

三、栈与队列的经典题 

 1 有效的括号

2 用队列实现栈 

3 用栈实现队列 

4 设计循环队列 


前言

   说起队列,不妨来想象一下,在我们的生活中总是存在一种现象:排队。它就可以看作是队列,比如:我们在排队打饭的时候,先排队的人先打饭,打完饭后便出队了,也就是说最先排队的人最先出队,最后排队的人最后出队。有了此依据,我们来对队列作出详细讲解:

一、队列 

首先根据下图直观的了解队列的相关内容:

1 队列的概念 

   队列是一种特殊的线性表,特性是先进先出,即First In First Out(FIFO)。最先加入的元素最先取出,最后加入的元素最后取出。队列有头部和尾部,队列头部称为队头(首),队列尾部称为队尾,队列内的元素从队头到队尾的顺序符合加入队列的顺序。它只允许在一端进行插入(入队),在另一端删除的线性表(出队)。文字描述未免过于死板,为了更好的帮助大家理解,附以下图解:

左边为队头,右边为队尾。将数字 1  到 7 依次入队之后,此时队首元素是 1 ,队尾元素是 7 ,第一个出队的元素是 1 。 

2 队列的实现 

队列一般需要这样几个功能:

  • 初始化队列
  • 判断队列是否为空
  • 入队
  • 出队
  • 取队头元素
  • 取队尾元素
  • 取有效元素个数(大小)
  • 队列的销毁

2.1 队列的定义 

//定义
typedef int QDataType;
 
//链式队列结点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;
 
//链式队列
typedef struct Queue
{
	QNode* head;//队列的头
	QNode* tail;//队列的尾
}Queue;

2.2 队列的初始化 

//初始化
void QueueInit(Queue* pq)
{
	//判空
	assert(pq);
 
	//不带哨兵位(即不带头结点)
	pq->head = pq->tail = NULL;
}

这里实现的是不带头结点的链式队列,初始时 front 和 rear 都指向NULL。 

2.3 队列的判空 

//判空
bool QueueEmpty(Queue* pq)
{
	//判空
	assert(pq);
 
	//看队头元素是否为NULL
	return pq->head == NULL;
}

判断队列是否为空,仅需要看队头元素是否为NULL。 

2.4 入队 

//入队
void QueuePush(Queue* pq, QDataType x)
{
	//判空
	assert(pq);
 
	//创建新结点newnode
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
 
	//判空
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
 
	newnode->data = x;
	newnode->next = NULL;
 
	//链表为空
	if (pq->tail == NULL)
	{
		//在空队列中插入第一个元素
		//修改队头队尾指针
		pq->head = pq->tail = newnode;
	}
	else
	{
		//链表不为空
		pq->tail->next = newnode;//新结点插入到tail结点之后
		pq->tail = newnode;//修改tail指针
	}
}

在入队之前,需要调用 malloc 函数开辟一个新结点 newnode。对于不带头结点的情况,第一个元素入队时要特殊处理。由于一开始这两个指针都是指向NULL的,因此插入第一个元素时对这两个指针都要进行修改。 

2.5 出队 

//出队
void QueuePop(Queue* pq)
{
	//判空
	assert(pq);
 
	//判断队列是否为空
	assert(!QueueEmpty(pq));
 
	//只含一个结点
	if (pq->head->next == NULL)
	{
		free(pq->head);//释放最后一个结点
		pq->head = pq->tail = NULL;//将队头与队尾指针都置空
	}
	else//含多个结点
	{
		QNode* next = pq->head->next;//next为队头结点的下一个结点
		free(pq->head);//释放队头结点
		pq->head = next;//修改头指针
	}
}

在出队之前,首先需要判断队列是否为空,若队列为空,则无法进行出队操作。当只剩下最后一个元素未出队时需特殊处理。首先需要调用 free函数释放首元素,然后将队头指针与队尾指针都置为 NULL。当还剩多个元素时,首先找到队头结点的下一个结点,然后调用 free函数释放掉队头结点,最后将 队头指针head 指向 next,更新队头元素。 

2.6 取队头元素 

//取队头元素
QDataType QueueFront(Queue* pq)
{
	//判空
	assert(pq);
 
	//判断队列是否为空
	assert(!QueueEmpty(pq));
 
	//取队头元素
	return pq->head->data;
}

在取队头元素之前,首选需要调用函数 QueueEmpty(pq) 判断队列是否为空,若为空,则无法取队头元素。若队列不为空,则取队头元素。 

2.7 取队尾元素 

//取队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
 
	//判断队列是否为空
	assert(!QueueEmpty(pq));
 
	//取队尾元素
	return pq->tail->data;
}

在取队尾元素之前,首选需要调用函数 QueueEmpty(pq) 判断队列是否为空,若为空,则无法取队尾元素。若队列不为空,则取队尾元素。 

2.8 取有效元素个数(队列大小)

//取有效元素个数(队列大小)
int QueueSize(Queue* pq)
{
	//判空
	assert(pq);
 
	//设置指针变量cur,用于遍历队列
	QNode* cur = pq->head;
	int size = 0;
 
	//遍历队列
	while (cur)
	{
		++size;
		cur = cur->next;
	}
	return size;
}

在求队列的元素个数时,首先设置指针变量 cur,并使其指向队头元素,然后设置一个变量 size,用于统计元素个数。让指针变量 cur进入 while循环遍历整个队列,每遍历一个元素, size就自增1,直到 cur走到队尾,则跳出循环,并返回 size大小。 

2.9 队列的销毁 

//销毁
void QueueDestory(Queue* pq)
{
	//判空
	assert(pq);
 
	//设置指针变量cur,用于遍历队列
	QNode* cur = pq->head;
 
	//遍历队列
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
 
	//将队头指针,队尾指针均置为空
	pq->head = pq->tail = NULL;
}

首先设置指针变量 cur,并使其指向队头元素,然后让指针变量 cur进入 while循环遍历整个队列,每遍历一个元素,就将其前一个元素释放掉,直到 cur走到队尾,则跳出循环。在销毁整个链表之后要将队头指针与队尾指针均置为 NULL。 

二、源代码 

Queue.h

#pragma once
 
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
 
typedef int QDataType;
 
//链式队列结点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;
 
//链式队列
typedef struct Queue
{
	QNode* head;//队列的头
	QNode* tail;//队列的尾
}Queue;
 
 
//初始化
void QueueInit(Queue* pq);

//判空
bool QueueEmpty(Queue* pq);
 
//入队
void QueuePush(Queue* pq, QDataType x);
 
//出队
void QueuePop(Queue* pq);
 
//取队头元素
QDataType QueueFront(Queue* pq);
 
//取队尾元素
QDataType QueueBack(Queue* pq);
 
//元素个数(队列大小)
int QueueSize(Queue* pq);

//销毁
void QueueDestory(Queue* pq);
Queue.c

#include"Queue.h"

​​//初始化
void QueueInit(Queue* pq)
{
	//判空
	assert(pq);
 
	//不带哨兵位(即不带头结点)
	pq->head = pq->tail = NULL;
}

​​//判空
bool QueueEmpty(Queue* pq)
{
	//判空
	assert(pq);
 
	//看队头元素是否为NULL
	return pq->head == NULL;
}

​​//入队
void QueuePush(Queue* pq, QDataType x)
{
	//判空
	assert(pq);
 
	//创建新结点newnode
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
 
	//判空
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
 
	newnode->data = x;
	newnode->next = NULL;
 
	//链表为空
	if (pq->tail == NULL)
	{
		//在空队列中插入第一个元素
		//修改队头队尾指针
		pq->head = pq->tail = newnode;
	}
	else
	{
		//链表不为空
		pq->tail->next = newnode;//新结点插入到tail结点之后
		pq->tail = newnode;//修改tail指针
	}
}

​​//出队
void QueuePop(Queue* pq)
{
	//判空
	assert(pq);
 
	//判断队列是否为空
	assert(!QueueEmpty(pq));
 
	//只含一个结点
	if (pq->head->next == NULL)
	{
		free(pq->head);//释放最后一个结点
		pq->head = pq->tail = NULL;//将队头与队尾指针都置空
	}
	else//含多个结点
	{
		QNode* next = pq->head->next;//next为队头结点的下一个结点
		free(pq->head);//释放队头结点
		pq->head = next;//修改头指针
	}
}

​//取队头元素
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;
}

​​//取有效元素个数(队列大小)
int QueueSize(Queue* pq)
{
	//判空
	assert(pq);
 
	//设置指针变量cur,用于遍历队列
	QNode* cur = pq->head;
	int size = 0;
 
	//遍历队列
	while (cur)
	{
		++size;
		cur = cur->next;
	}
	return size;
}

​​//销毁
void QueueDestory(Queue* pq)
{
	//判空
	assert(pq);
 
	//设置指针变量cur,用于遍历队列
	QNode* cur = pq->head;
 
	//遍历队列
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
 
	//将队头指针,队尾指针均置为空
	pq->head = pq->tail = NULL;
}
​
test.c
 
#include"Queue.h"
 
void TestQueue()
{
	Queue q;
 
	//初始化
	QueueInit(&q);
 
	//入队
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);
    QueuePush(&q, 6);
    QueuePush(&q, 7);
 
	//出队
	while (!QueueEmpty(&q))
	{
		//取对头元素
		printf("%d ",QueueFront(&q));
		//出队
		QueuePop(&q);
	}
	printf("\n");
 
	//销毁
	QueueDestory(&q);
}
 
int main()
{
	test();
 
	return 0;
}

三、栈与队列的经典题 

 1 有效的括号

题目描述:

给定一个只包括'(',')','{','}','[',']'的字符串s,判断字符串是否有效

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合
  2. 左括号必须以正确的顺序闭合

分析:

   我们可以把左括号依次压入栈中,越往后被压入的左括号越先被弹出栈进行匹配。每出现一个右括号,就”消耗”一个左括号进行匹配检查,这个过程对应出栈操作。扫描一连串括号的过程中若发现下列情况都说明括号序列不合法,终止操作。

注意:

  • 弹出栈的左括号与刚刚遇到要检查的右括号不匹配;
  • 扫描到右括号时发现栈空了(右括号单身);
  • 处理完所有括号后,栈非空(右括号单身)。

代码实现: 

bool isValid(char * s)
{
    Stack st;
    StackInit(&st);
    while(*s)
    {
        //左括号入栈,右括号找最近的左括号匹配
        if(*s == '[' || *s == '(' || *s == '{')
        {
            StackPush(&st, *s);
            s++;
        }
        else
        {
            if(StackEmpty(&st))//只有右括号的情况
            {
                StackDestroy(&st);//销毁
                return false;
            }
            char top = StackTop(&st);
            //不匹配的情况
            if ( (top == '[' && *s != ']') 
            || (top == '(' && *s != ')')
            || (top == '{' && *s != '}') ) 
            {
                StackDestroy(&st);
                return false;
            }
            else //匹配的情况
            {
                StackPop(&st);
                s++;
            }
        }
    }
 
    //如果最后栈内为空才说明是匹配的(防止最后栈内还剩下左括号的情况)
    bool ret = StackEmpty(&st);
    StackDestroy(&st);
 
    return ret;
 
    //特别注意:在return之前需要先把栈销毁掉
}

2 用队列实现栈 

题目描述:

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push,top,pop和empty)。

实现MyStack类:

  1. void push(int x):将元素x压入栈顶
  2. void pop():移除并返回栈顶元素
  3. int top():返回栈顶元素
  4. boolean empty():如果栈是空的,返回true;否则,返回false

分析:

   队列 queue1保存原始输入数据,队列 queue2作为临时队列缓存数据。当进行 stack_pop操作时,先将 queue1里除最后一个元素外全部出队,并将出队的数据保存在临时队列queue2里,然后保存 queue1的最后一个元素,最后再将 queue2里的全部元素出队,且出队的元素重新放进 queue1里,返回保存的 queue1最后的元素。

代码实现: 

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
 
typedef int QDataType;
 
//链式队列结点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;
 
//链式队列
typedef struct Queue
{
	QNode* head;//队列的头
	QNode* tail;//队列的尾
}Queue;
 
 
//初始化
void QueueInit(Queue* pq);
 
//销毁
void QueueDestory(Queue* pq);
 
//入队
void QueuePush(Queue* pq, QDataType x);
 
//出队
void QueuePop(Queue* pq);
 
//取队头元素
QDataType QueueFront(Queue* pq);
 
//取队尾元素
QDataType QueueBack(Queue* pq);
 
//判空
bool QueueEmpty(Queue* pq);
 
//元素个数
int QueueSize(Queue* pq);
 
 
/**********用队列实现栈**********/
 
 
//构造一个包含两个队列的栈
typedef struct
{
	Queue q1;
	Queue q2;
}MyStack;
 
//初始化栈
MyStack* myStackCreate()
{
	//调用malloc为栈开辟内存空间
	MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
 
	//初始化两个队列
	QueueInit(&obj->q1);
	QueueInit(&obj->q2);
 
	return obj;
}
 
//入栈
void myStackPush(MyStack* obj, int x)
{
	//判空
	assert(obj);
 
	//往不为空的队列插入元素,若两个队列均为空,插入其中一个即可
	if (!QueueEmpty(&obj->q1))
	{
		QueuePush(&obj->q1,x);
    }
    else
    {
		QueuePush(&obj->q2,x);
    }

//出栈
int myStackPop(MyStack* obj)
{
    //判空
    assert(obj);

    //假设q1队列为空,q2队列不为空
    Queue* emptyQ = &obj->q1;
    Queue* nonEmptyQ = &obj->q2;

    //若q1队列不为空,则将q2队列设为空,q1队列设为非空
    if (!QueueEmpty(&obj->q1))
    {
        emptyQ = &obj->q2;
        nonEmptyQ = &obj->q1;
    }

    //把非空队列的数据导入到空队列,也就是将非空队列的前n-1个数据导入至另一个空队列
    while (QueueSize(nonEmptyQ) > 1)
    {
        QueuePush(emptyQ,QueueFront(nonEmptyQ));//取非空队列对头的数据插入到空队列中去
        QueuePop(nonEmptyQ);//出队
    }

    int top = QueueFront(nonEmptyQ);//取非空队列的队头元素
    QueuePop(nonEmptyQ);//出队

    return top;
}

//取栈顶元素
int myStackTop(MyStack* obj)
{

    //判空
    assert(obj);

    //若q1队列不为空,则取q1队尾元素
    if (!QueueEmpty(&obj->q1))
    {
         return QueueBack(&obj->q1);
    }
    else
    {
         //若q2队列不为空,则取q2队尾元素
         return QueueBack(&obj->q2);
    }
}

//判断栈是否为空
bool myStackEmpty(MyStack* obj)
{

    //判空
    assert(obj);

    //只有当两个队列均为空时,才表示栈为空
    return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

//销毁栈
void myStackFree(MyStack* obj)
{

    //判空
    assert(obj);

    //销毁队列q1和q2
    QueueDestory(&obj->q1);
    QueueDestory(&obj->q2);

    //释放栈
    free(obj);
}
    
    
   

3 用栈实现队列 

题目描述:

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push,pop,peek,empty)。

实现MyQueue类:

  1. void push(int x):将元素x推到队列的末尾
  2. int pop():从队列的开头移除并返回元素
  3. int peek():返回队列开头的元素
  4. boolean empty():如果队列为空,返回true;否则,返回false

分析:

   用两个栈实现一个队列,设置其中一个栈 pushst专门入数据,另一个栈 popst专门出数据。若要入队,则进栈 pushst;若要出队,首先看 popst栈是否为空,如果为空,就先把栈 pushst的数据转移过来,然后出队,如果不为空,则直接出栈 popst的数据。

代码实现: 

typedef struct 
{
    Stack pushST;
    Stack popST;
} MyQueue;
 
MyQueue* myQueueCreate() 
{
    MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));
    StackInit(&q->pushST);
    StackInit(&q->popST);
 
    return q;
}
 
void myQueuePush(MyQueue* obj, int x)
{
    //不管栈内有没有数据,只要是入队操作就向Push栈入数据即可
    StackPush(&obj->pushST, x);
}
 
//获取队头数据
int myQueuePeek(MyQueue* obj) 
{
    //如果pop栈为空,先把push栈数据导入pop栈
    if(StackEmpty(&obj->popST))
    {
        while(!StackEmpty(&obj->pushST))
        {
            StackPush(&obj->popST, StackTop(&obj->pushST));
            StackPop(&obj->pushST);
        }
    }
    
    return StackTop(&obj->popST);
}
 
//出队
int myQueuePop(MyQueue* obj) 
{
 
    //如果pop栈为空,先把push栈数据导入pop栈
    /*if(StackEmpty(&obj->popST))
    {
        while(!StackEmpty(&obj->pushST))
        {
            StackPush(&obj->popST, StackTop(&obj->pushST));
            StackPop(&obj->pushST);
        }
    }
    */
 
    //复用
    int top = myQueuePeek(obj);//易错点:不能写&obj->popST,因为该传入队列的指针
    StackPop(&obj->popST);
    return top;
}

bool myQueueEmpty(MyQueue* obj) 
{
    //push栈和pop栈同时为空,队列才为空
    return StackEmpty(&obj->pushST) && StackEmpty(&obj->popST);
}

void myQueueFree(MyQueue* obj)
{
    StackDestroy(&obj->pushST);
    StackDestroy(&obj->popST);
    free(obj);
}

4 设计循环队列 

 题目描述:

设计你的循环队列实现。循环队列是一种线性数据结构,其操作表现基于FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为"环形缓冲器"。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作

  1. MyCircularQueue(k):构造器,设置队列长度为k
  2. Front:从队首获取元素。如果队列为空,返回-1
  3. Rear:获取队尾元素。如果队列为空,返回-1
  4. enQueue(value):向循环队列插入一个元素,如果成功插入则返回真
  5. deQueue():从循环队列中删除一个元素。如果成功删除则返回真
  6. isEmpty():检查循环队列是否为空
  7. isFull():检查循环队列是否已满

分析:

  • 采用数组或者链表都可以,但是数组缓存利用率更高,所以这里主要采用数组的方式。
  • 用模运算将存储空间在逻辑上变成“环状”。当发现rear指针要指向MaxSize时,不应该让它指向MaxSize而是应该让它指向数组下标为0的位置。
  • 队列判空:Q.rear==Q.front;队列判满:队尾指针的下一个位置是对头,即(Q.rear+1)%MaxSize==Q.front

代码实现: 

//循环队列的定义
typedef struct
{
	int* a;//动态开辟数组
	int k;//当前有效元素个数
	int head;//队头
	int tail;//队尾
}MyCircularQueue;
 
//循环队列的初始化
MyCircularQueue* myCircularQueueCreate(int k)
{
	//为队列开辟一块动态内存空间
	MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
 
	//为数组开辟一个包含(k+1)个元素的内存空间
	obj->a = (int*)malloc(sizeof(int) * (k + 1));
 
	//队头,队尾起始都指向数组下标为0的位置
	obj->head = obj->tail = 0;
 
	//当前有效元素个数设置为k个
	obj->k = k;
 
	return obj;
}
 
//判断队列是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
	//判空
	assert(obj);
 
	return obj->head == obj->tail;
}
 
//判断队列是否已满
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
	//判空
	assert(obj);
 
	int next = obj->tail + 1;
 
	//当tail指向数组最后一个下标的下一个位置时,则将tail指向数组下标为0的位置
	if (next == obj->k + 1)
	{
		next = 0;
	}
 
	//队尾指针的下一个位置是对头,则表示队列已满
	return next == obj->head;
}
 
//入队
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
	//判空
	assert(obj);
 
	//检查队列是否已满
	if (myCircularQueueIsFull(obj))
	{
		return false;
	}
 
	//未满则将value插入队尾
	obj->a[obj->tail] = value;
    obj->tail++;//队尾指针后移

    //当tail指向数组最后一个下标的下一个位置时,则将tail指向数组下标为0的位置
    if (obj->tail == obj->k + 1)
    {
        obj->tail = 0;
    }
    //obj->tail%=(k+1);
 
    return true;
}

//出队
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
    
    //判空
    assert(obj);

    //检查队列是否为空
    if (MyCircularQueueIsEmpty(obj))
    {
        return false;
    }

    //若不空,则队头指针后移
    ++obj->head;
  
    //当head指向数组最后一个下标的下一个位置时,则将head指向数组下标为0的位置
    if (obj->head == obj->k + 1)
    {
        obj->head = 0;
    }
    return true;
}

//取队头元素
int myCircularQueueFront(MyCircularQueue* obj)
{

     //判空
     assert(obj);

     //检查队列是否为空
     if (myCircularQueueIsEmpty(obj))
     {
         return -1;
     }

     //若不为空,则取队头元素
     return obj->a[obj->head]};
    
//取队尾元素
int myCircularQueueRear(MyCircularQueue* obj)
{
     //判空
     assert(obj);

     //检查队列是否为空
     if (myCircularQueueIsEmpty(obj))
     {
          return -1;
     }

     //因为tail指向队尾元素的下一个位置,所以要取tail前一位置的下标
     int pre = obj->tail - 1;

     //若tail在数组起始位置,则前一位置的下标为数组的末尾位置
     if (obj->tail == 0)
     {
          pre = obj->k;
     }
     //int pre = obj->tail - 1 + obj->k + 1;
     //pre %= (obj->k+1);

     //取队尾元素
     return obj->a[pre];
}

//销毁
void myCircularQueueFree(MyCircularQueue* obj)
{
     //判空
     assert(obj);

     //释放动态开辟的数组
     free(obj->a);

     //释放队列
     free(obj);
}

    本期的分享已经接近尾声,内容有些多,大家耐心些哈~重点还是要关注栈和队列的四道经典例题,它们能让我们更好的掌握核心知识。或许你在看的时候感觉有些难度,不要烦躁,更不要焦虑,没有谁能一蹴而就。你需要做的便是不断地重复、重复!!最能帮助你的那双手,就长在你的胳膊上。好啦,如果这篇文章对你们有帮助,记得留下三连加支持哈~你们的支持是我创作的最大动力!诸君加油,不负自己。那我们下期再会啦!

  • 32
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值