数据结构修炼——栈和队列是什么?如何实现?从入门到实战

news2024/10/11 18:43:31

目录

  • 一、栈
    • 1 栈的概念及结构
    • 2 栈的实现
  • 二、队列
    • 1 队列的概念及结构
    • 2 队列的实现
  • 三、栈和队列OJ题
    • 1 有效的括号
    • 2 用队列实现栈
    • 3 用栈实现队列
    • 4 循环队列
  • 四、概念选择题

一、栈

1 栈的概念及结构

:一种特殊的线性表。栈只允许在固定端进行插入和删除操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFOLast In First Out)的原则。
在这里插入图片描述

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。

2 栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言顺序结构实现更优一些。因为在尾上插入数据的代价比较小。下面将使用动态数组来实现栈。
需要实现的操作有:栈的创建与销毁压栈出栈获取栈顶元素获取栈中元素个数栈的判空等。
在这里插入图片描述
代码示例:

头文件

#include<stdio.h>
#include<assert.h>// 提供assert函数
#include<stdlib.h>// 提供malloc,realloc等函数
#include<stdbool.h>// 提供bool类型

typedef int STDataType;// 定义栈数据类型
typedef struct Stack
{
	STDataType* arr;// 动态数组
	int top;// 栈顶的下标
	int capacity;// 栈/数组容量
}ST;

//栈的初始化、销毁
void STInit(ST* pst);
void STDestroy(ST* pst);

//入栈、出栈
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);

//获取栈顶数据
STDataType STTop(ST* pst);

//获取数据个数
int STSize(ST* pst);

//判空
bool STEmpty(ST* pst);

源文件

//栈的初始化、销毁
void STInit(ST* pst)
{
	assert(pst);
	pst->arr = NULL;
	pst->top = 0;// 栈顶设置为末尾元素的后一个位置,若直接设为末尾元素则为-1
	pst->capacity = 0;
}
void STDestroy(ST* pst)
{
	assert(pst);
	free(pst->arr);
	pst->arr = NULL;
	pst->top = pst->capacity = 0;
}

//入栈、出栈
void STPush(ST* pst, STDataType x)
{
	assert(pst);
	//判断是否满栈决定是否扩容
	if (pst->top == pst->capacity)
	{
		int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;
		STDataType * tmp = (STDataType*)realloc(pst->arr, newcapacity * sizeof(STDataType));
		if (!tmp)
		{
			perror("STPush::realloc fail!");
			return;
		}
		pst->arr = tmp;
		pst->capacity = newcapacity;
	}
	pst->arr[pst->top++] = x;
}
void STPop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	pst->top--;
}

//获取栈顶数据
STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	return pst->arr[pst->top - 1];
}

//获取数据个数
int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}

//判空
bool STEmpty(ST* pst)
{
	assert(pst);
	return pst->top == 0;
}

代码分析:
用动态数组实现栈,代码在栈的结构体定义中设计了3个变量,

  1. arr是栈数据类型的指针变量,用于记录动态数组;
  2. top是整型变量,用于记录栈顶位置;
  3. capacity也是整型变量,用于记录动态数组的最大容量。

其中top变量我们需要特别关注一个小问题,即它记录的是最后一个有效元素的下标还是最后一个有效元素后一位置的下标。这虽然是个小问题,但关系到栈的初始化与销毁,以及一系列接口的实现细节。
在这里插入图片描述

例如:如果记录的是最后一个有效元素的下标,那么在栈的初始化与销毁时,top的初始值就是-1;如果记录的是最后一个有效元素后一位置的下标,那么top的初始值就是0

接着是动态数组的扩容问题,代码示例选择在数据入栈时判断是否需要扩容与进行扩容操作,我们当然可以有另外的选择,例如将容量检查操作与扩容操作单独封装为一个模块。

二、队列

1 队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。队列中的数据元素遵守先进先出FIFO(First In First Out) 的原则。

入队列:向队列中插入数据元素的操作叫作入队列,插入数据的一端称为队尾
出队列:在队列中删除数据元素的操作叫作出队列,删除数据的一端称为队头

我们只需要记住队尾入,队头出,先进就先出即可。
在这里插入图片描述

2 队列的实现

队列也可以使用数组或链表实现,但链表结构实现更优一些,因为使用数组结构,出队列在数组头上出数据需要不断地将后面数据覆盖前一位置,效率会比较低。
需要实现的操作有:队列的创建与销毁入队出队获取队头元素获取队尾元素获取队列中元素个数队列的判空等。
在这里插入图片描述

头文件

#include<stdio.h>
#include<assert.h>// 提供assert函数
#include<stdlib.h>// 提供malloc,realloc等函数
#include<stdbool.h>// 提供bool类型

typedef int QDataType;// 定义队列数据类型
// 链式结构:表示队列 
typedef struct QListNode// 队列每个数据所在结点
{
	struct QListNode* next;
	QDataType data;
}QNode;
// 队列的结构 
typedef struct Queue
{
	QNode* front;// 队头
	QNode* rear;// 队尾
	int size;// 队长
}Queue;

// 初始化队列 
void QueueInit(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);
// 队尾入队列 
void QueuePush(Queue* pq, QDataType data);
// 队头出队列 
void QueuePop(Queue* pq);
// 获取队列头部元素 
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素 
QDataType QueueBack(Queue* pq);
// 获取队列中有效元素个数 
int QueueSize(Queue* pq);
// 检测队列是否为空,如果为空返回true,如果非空返回false 
bool QueueEmpty(Queue* pq);

源文件

// 初始化队列 
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->front = pq->rear = NULL;
	pq->size = 0;
}
// 销毁队列 
void QueueDestroy(Queue* pq)
{
	assert(pq);
	while (pq->front)// 销毁释放各个队列结点
	{
		QNode* pop = pq->front;
		pq->front = pq->front->next;
		pq->size--;
		free(pop);
	}
	pq->front = pq->rear = NULL;
}
// 队尾入队列 
void QueuePush(Queue* pq, QDataType data)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (!newnode)
	{
		perror("QueuePush::malloc fail!");
		return;
	}
	newnode->next = NULL;
	newnode->data = data;
	if (!pq->front)// 队列为空
	{
		pq->front = pq->rear = newnode;
	}
	else// 队列不为空
	{
		pq->rear->next = newnode;
		pq->rear = newnode;
	}
	pq->size++;
}
// 队头出队列 
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->size > 0);
	if (!pq->front->next)
	{
		//一个结点
		free(pq->front);
		pq->front = pq->rear = NULL;
	}
	else
	{
		//多个结点
		QNode* next = pq->front->next;
		free(pq->front);
		pq->front = next;
	}
	pq->size--;
}
// 获取队列头部元素 
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->front);
	return pq->front->data;
}
// 获取队列队尾元素 
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->rear);
	return pq->rear->data;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

代码分析:
用链表实现队列,需要先定义结点的结构体,包含数据域data指针域next。还需要定义队列的结构体,其中有3个变量,front代表队头指针,即链表的头结点;rear代表队尾指针,即链表的尾结点;size代表队长,即链表的有效结点个数。

队尾入数据时需要注意有队列为空与队列不为空两种情况:
如果队列为空,那么frontrear都为空,那么新结点入队列时直接将frontrear指向新结点,且队长加1即可;如果队列不为空,那么需要将rearnext指向新结点并将新结点置为新的尾结点,并队长加1。

队头出数据时需要注意有一个结点和多个结点两种情况:
如果队列有多个结点,需要将头结点front销毁,设置新的头节点为原头结点下一结点,且队长减1;如果队列只有一个结点,将头结点front销毁后,就无法设置新的头结点了,因为只有一个结点,下一结点为空,所以需要将front置为空,同时队列只有一个结点时frontrear都指向同一个结点,所以结点被销毁时还需要将rear也置为空。

三、栈和队列OJ题

1 有效的括号

力扣面试题链接
在这里插入图片描述
题目分析:
题目大意为给出一段字符串,字符串只有[] {} ()三种括号类型,总共6种字符,我们需要判断这一系列括号是否一一对应可以闭合上,且是按正确顺序闭合上的。

例如{ ( [ } { } ] )。前三个字符都为左括号,暂时没问题。第四个为右括号},那么我们需要往前找最近的左括号,看看是否能匹配上,最近的左括号是[,括号类型不匹配,因此结果为false。

如果改成{ ( [ { } ] )呢?前四个字符都为左括号,第五个字符为右括号},与最近的左括号{匹配,将匹配的括号去掉。第六个字符是右括号],与最近的左括号[匹配,将匹配的括号去掉。第七个字符是右括号),与最近的左括号(匹配,将匹配的括号去掉。匹配完后,还剩下第一个左括号{没有右括号匹配,还剩下左/右括号没有对应括号匹配,因此结果为false。

示例代码:

bool isValid(char* s) {
    ST st;// 创建栈
    STInit(&st);// 初始化栈
    while (*s)// 遍历字符串进行匹配
    {
        if (*s == '(' || *s == '{' || *s == '[')// 左括号入栈
        {
            STPush(&st, *s);
        }
        else// 右括号与栈顶元素进行匹配
        {
            if (STEmpty(&st))
            {
                STDestroy(&st);
                return false;
            }
            char ch = STTop(&st);
            STPop(&st);
            if ((*s == ')' && ch != '(')
             || (*s == '}' && ch != '{')
             || (*s == ']' && ch != '['))
            {
                STDestroy(&st);
                return false;
            }
        }
        s++;
    }
    if (!STEmpty(&st)) return false;// 字符串遍历完后可能栈中还剩余左括号未匹配
    return true;
}

代码采取的思路与上述分析类似。由于栈后进先出的特点,我们可以将左括号都入栈,往后遍历遇到右括号时,栈顶元素就是最近的左括号,对两者进行匹配判断即可,如果匹配则继续遍历,不匹配则返回false。

2 用队列实现栈

力扣面试题链接
在这里插入图片描述
题目分析:
题目规定我们只能使用入队,出队等操作来实现栈的压栈,入栈等。
使用队列实现栈的关键是如何将先进先出的逻辑转换为后进先出。我们向队列中依次存储了一系列数据1 2 3 4,其中1是队头,4是队尾,入队从4后入,出队从1出;但如果是栈的话,那么1是栈底,4是栈顶,入栈从4后入,出栈同样从4出。

压栈操作时,我们只能使用入队操作,但直接将5入队并不影响数据序列,因为从队尾入和压栈的栈顶入方向一致,所以同样为1 2 3 4 5

出栈操作时,我们需要从栈顶出数据,但栈顶是队尾,出队操作只能从队头出数据,所以我们需要先获取队尾数据。由于题目允许我们使用两个队列,因此我们可以将当前队列的数据导入另一个闲置队列,当导到最后一个数据即队尾数据时,我们直接删除队尾数据即可完成出栈操作。

示例代码:

typedef struct {// 定义两个队列帮助我们完成栈操作
    Queue q1;
    Queue q2;
} MyStack;
MyStack* myStackCreate() {// 栈的创建,即创建栈空间并初始化两个队列
    MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
    QueueInit(&obj->q1);
    QueueInit(&obj->q2);
    return obj;
}
void myStackPush(MyStack* obj, int x) {// 压栈操作,向非闲置队列中入数据即可
    if (!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1, x);
    }
    else
    {
        QueuePush(&obj->q2, x);
    }
}
int myStackPop(MyStack* obj) {// 出栈操作
    Queue* nonEmpty = &obj->q2, *empty = &obj->q1;// 假设法,先假设某队列闲置,再进行判断
    if (!QueueEmpty(&obj->q1))
    {// 假设的闲置队列不为空,则交换空队列与非空队列
        empty = &obj->q2;
        nonEmpty = &obj->q1;
    }
    while (QueueSize(nonEmpty) > 1)// 将数据导入闲置队列
    {
        QueuePush(empty, QueueFront(nonEmpty));
        QueuePop(nonEmpty);
    }
    QDataType ret = QueueFront(nonEmpty);// 找到了队尾数据
    QueuePop(nonEmpty);// 删除队尾数据完成出栈操作
    return ret;
}
int myStackTop(MyStack* obj) {// 找栈顶数据操作,即找队尾数据
    if (!QueueEmpty(&obj->q1))// 队列1不为空就返回队列1的队尾,否则返回队列2的队尾,题目队返回值是否有效无要求,即使是空栈即两个队列都为空
    {
        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->q2);
    free(obj);
}

3 用栈实现队列

力扣面试题链接
在这里插入图片描述
题目分析:
和上一题类似,这一题需要我们使用栈来模拟实现队列的基本操作,同样的这题可以使用两个栈来实现栈后进先出到先进先出的逻辑转换。

同样一组数据1 2 3 4,1为队头/栈底,4为队尾/栈顶。

入队操作时,我们需要从队尾入,队尾即栈顶,逻辑方向对应,我们直接压栈即可,同样可以得到1 2 3 4 5

出队操作时,我们需要从队头出,队头即栈底,逻辑方向不相对应,我们需要先取得栈底元素。同样先将数据导入闲置栈中,但由于栈后进先出的特点,数据的顺序导入后就相反了,变成4 3 2 1,这时原本的栈底元素4变成了栈顶元素,所以我们直接使用出栈操作即可。

由于这种相反的变化,相较于队列我们可以做一点细节上的变化。将一个栈专门设计为入栈操作进入的栈,另一个栈设计为出栈操作的栈。入栈操作直接压入专门栈的栈顶,出栈时直接从专门栈的栈顶出即可。因为出栈的专门栈的栈顶元素一定为队头,而入栈的专门栈的栈顶一定为队尾。

示例代码:

typedef struct {// 定义两个栈来帮助我们完成队列操作
    ST pushst;// 压栈专门栈
    ST popst;// 出栈专门栈
} MyQueue;
MyQueue* myQueueCreate() {// 队列的创建,即申请队列空间并初始化两个栈
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
    STInit(&obj->pushst);
    STInit(&obj->popst);
    return obj;
}
void myQueuePush(MyQueue* obj, int x) {//入队操作,直接入专门栈栈顶即可
    STPush(&obj->pushst, x);
}
int myQueuePop(MyQueue* obj) {// 出队操作,直接出专门栈栈顶即可,题目对空栈情况无特别要求,返回值可不做是否无效的判断
    STDataType top = myQueuePeek(obj);
    STPop(&obj->popst);
    return top;
}
int myQueuePeek(MyQueue* obj) {// 获取队头元素
    if (STEmpty(&obj->popst))// 出栈专门栈为空,则倒出入栈专门栈的数据
    {
        while (!STEmpty(&obj->pushst))
        {
            STDataType tmp = STTop(&obj->pushst);
            STPop(&obj->pushst);
            STPush(&obj->popst, tmp);
        }
    }
    return STTop(&obj->popst);// 返回出栈专门栈的栈顶元素,即队头元素
}
bool myQueueEmpty(MyQueue* obj) {// 队列的判空,即对两个栈是否都为空判断
    return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}
void myQueueFree(MyQueue* obj) {// 队列的销毁,即两个栈的销毁以及队列空间的释放
    STDestroy(&obj->pushst);
    STDestroy(&obj->popst);
    free(obj);
}

4 循环队列

力扣面试题链接
在这里插入图片描述
这里扩展一个概念就是循环队列,和循环链表类似,循环队列的循环主要体现在逻辑上的循环,当队尾的下一个数据元素重新回到队头。循环队列可以是一个连续存储的数组,也可以是互相链接但空间可能不连续的一系列结点组成的链表。但循环队列设计的主要目的是为了解决使用数组,即顺序结构实现队列时的假溢出问题(空间浪费问题),而循环队列的应用场景通常是有限空间循环使用时,例如餐厅排队等桌位或者图书馆排队等座位,我们需要排号获取有限的位子。
在这里插入图片描述
题目分析:
该题是前面一般队列实现的进一步延申。要求我们实现循环队列与其的各种基本操作。需要注意循环队列的实现一般都只需要开辟固定的空间即可,即循环队列的队长一般固定,本题也只需要开辟固定空间的队列即可。

我们可以采取的实现方式有很多,链表或者数组,这里采取数组来实现,有兴趣的可以自行尝试用链表进行实现。

示例代码:

typedef int QDataType;// 定义循环队列数据类型
typedef struct {
    QDataType* arr;// 动态数组
    int front;// 队头
    int rear;// 队尾
    int capacity;// 数组容量
}MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {// 循环队列的判空
    return obj->front == obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {// 循环队列的判满
    return (obj->rear + 1) % obj->capacity == obj->front;
}
MyCircularQueue* myCircularQueueCreate(int k) {// 循环队列的创建
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));// 申请队列空间
    obj->arr = (QDataType*)malloc(sizeof(QDataType) * (k + 1));// 开辟固定空间
    obj->front = obj->rear = 0;
    obj->capacity = k + 1;
    return obj;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {// 循环队列入队
    if (myCircularQueueIsFull(obj)) return false;// 判满,若队满则入队失败返回false
    obj->arr[obj->rear] = value;// 队列不满则数据直接入队尾
    obj->rear = (obj->rear + 1) % obj->capacity;
    return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {// 循环队列出队
    if (myCircularQueueIsEmpty(obj)) return false;// 判空,若队空则出队失败返回false
    obj->front = (obj->front + 1) % obj->capacity;// 队列不空则直接删除队头数据
    return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {// 获取队头元素
    if (myCircularQueueIsEmpty(obj)) return -1;// 判空,队列为空则获取失败
    return obj->arr[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {// 获取队尾元素
    if (myCircularQueueIsEmpty(obj)) return -1;// 判空,队列为空则获取失败
    return obj->arr[(obj->rear - 1 + obj->capacity) % obj->capacity];
}
void myCircularQueueFree(MyCircularQueue* obj) {// 销毁队列
    free(obj->arr);// 先释放数组空间
    free(obj);// 再释放队列空间
}

在循环队列的数组实现方式中,关键是我们如何考虑循环队列的判空与判满。
在这里插入图片描述
我们会发现无论队列为空还是队列为满,frontrear都会指向同一个位置(当然这也是因为我们选择将最后一个有效元素后一位置的下标设为队尾导致,不同的实现方法会造成一系列截然不同的影响,但并无优劣高下之分)。

对于上述情况我们同样有多种选择,无论是设计一个变量记录队长还是考虑改变队列的实现方式,将最后一个有效元素的下标设为队尾。

示例代码采取的是另一种可行方式,即额外开辟1个空间。
在这里插入图片描述
对于这n+1个空间循环队列始终只使用其中n个空间,这样判空与判满就好判断了。
front == rear时队列为空,当队尾的下一个为队头时为满。
需要注意rear加1不一定等于front,但由于循环的特性,所以对于一个大小为n+1的数组空间,下标是从0到n,而n的下一个是0,可以得出等式rear = (rear + 1) % (n + 1),也即当front == (rear + 1) % (n + 1)时队列为满。

在循环队列的入队操作时,我们需要先对队列判满,如果队列不满,我们就可以直接进行入队操作。入队后我们需要获取下一个下标,即新的队尾下标。相同的,我们只能使用等式rear = (rear + 1) % (n + 1)得出新下标,因为reaar加1不一定有效。

在循环队列的出队操作时,我们需要先对队列判空,如果队列不为空,我们就可以删除队头元素。出队操作后我们同样需要获取下一个下标,即新的队头下标,同样使用上面等式获取front = (front + 1) % (n + 1)

在获取队头元素操作时我们可以直接返回front下标位置的元素,但获取队尾元素操作时由于rear的下标并不是最后一个有效元素,所以rear需要“减1”,为了保证rear的有效,我们同样需要使用上面等式来获取下标。可以使用等式rear = (rear - 1 + n + 1) % (n + 1),由于直接rear - 1可能取到负数,所以我们加一个循环数(下标加n+1即循环到同一个下标),这样就一定能得到正数,此时再取余不影响结果。

四、概念选择题

问题:

  1. 一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。
    A 12345ABCDE
    B EDCBA54321
    C ABCDE12345
    D 54321EDCBA

  2. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是( )。
    A 1,4,3,2
    B 2,3,4,1
    C 3,1,4,2
    D 3,4,2,1

  3. 循环队列的存储空间为 Q(1:100) ,初始状态为 front=rear=100 。经过一系列正常的入队与退队操作后, front=rear=99 ,则循环队列中的元素个数为( )。
    A 1
    B 2
    C 99
    D 0或者100

  4. 以下( )不是队列的基本运算?
    A 从队尾插入一个新元素
    B 从队列中删除第i个元素
    C 判断一个队列是否为空
    D 读取队头元素的值

  5. 现有一循环队列,其队头指针为front,队尾指针为rear;循环队列长度为N。其队内有效长度为?(假设队头不存放数据)( )。
    A (rear - front + N) % N + 1
    B (rear - front + N) % N
    C ear - front) % (N + 1)
    D (rear - front + N) % (N - 1)

答案:
1.B。依次入栈后,栈内数据为12345ABCDE,再出栈从E开始出栈,出栈顺序为EDCBA54321

2.C。进栈顺序为1,2,3,4,但出栈顺序不确定。
对于A.1432,栈内数据变化可能是1->2 3 4,1一进栈就出栈,接着2,3,4依次入栈,接着依次出栈,A符合题意。
对于B.2341,栈内数据变化可能是1 2->1 3->1 4,1,2依次进栈2就出栈,然后3进栈再出栈,最后4入栈,1,4依次出栈,B符合题意。
对于D.3421,栈内数据变化可能是1 2 3->1 2 4,1,2,3依次进栈3就出栈,然后4进栈再出栈,剩下1,2依次出栈,D符合题意。
对于C.3 1 4 2,栈内数据变化先是1 2 3,3出栈代表1,2都入栈了,但接着1是无法出栈的,因为此时栈顶元素是2,只有2出了1才能出,C不符合题意。

3.D。队头下标等于队尾下标代表队列为空。

4.B。队尾入,队头出,队列无法删除任意位置的元素。

5.B。根据下标的循环变化0 1 2 3 4...n-2 n-1 n 0 1 2 3 4...n-2 n-1 n 0...分析计算即可。


本篇文章介绍了栈与队列的概念与结构,并给出了实现栈与队列的代码示例,在代码中特别标注了许多细节,如:栈顶处的下标,栈的扩容问题与入队、出队操作的各种情况等。在初步了解栈与队列后,文章还分享了一些OJ题帮助读者进一步掌握栈与队列的各种操作与特点,并拓展了循环队列的概念与实现。最后提供了几道选择题练习与巩固所有知识。

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

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

相关文章

专业的客服话术快捷回复软件

在当今快节奏的工作环境中&#xff0c;客服行业面临着前所未有的挑战。尤其是对于刚入行的新手小白来说&#xff0c;如何快速提升响应速度、保证回复质量&#xff0c;成为了他们亟待解决的问题。而今天&#xff0c;我要向大家推荐的这款“客服宝”快捷回复软件&#xff0c;就非…

advanced skeleton绑定模型无法返回修改按钮

有时候出现问题 adv插件没有Toggle Fit返回修改部分的按钮&#xff0c;这个通常是命名造成的 解决方式&#xff0c;把骨骼模型最上层的组名重改为Group&#xff0c;然后重开插件就行了 参考https://www.reddit.com/r/Maya/comments/xcgvcq/does_anyone_know_why_advanced_ske…

美畅物联丨破解养老难题:视频汇聚平台助力银发经济蓬勃发展

​一、引言 今天是重阳佳节&#xff0c;我们就来聊一聊视频汇聚平台在智慧养老中的应用与前景。 近年来&#xff0c;中国老龄化态势愈发严峻&#xff0c;已成为社会各界高度关注的重大课题。随着时间的推移&#xff0c;老年人口数量呈现出大规模增长的趋势&#xff0c;且独居老…

HE染色:揭示细胞细节,助力病理诊断|文献速递·24-10-11

好文分享 这篇文章是一篇关于苏木精-伊红&#xff08;H&E&#xff09;染色在诊断外科病理学中重要性的综述。 角色姓名单位第一作者John K. C. Chan香港特别行政区中国女王伊丽莎白医院 文章首先强调了尽管分子医学取得了显著进展&#xff0c;但显微镜仍然是外科病理学家日…

初阶数据结构(2):空间复杂度和复杂度算法题

Hello~,欢迎大家来到我的博客进行学习&#xff01; 目录 1.空间复杂度2. 常见复杂度对比3. 复杂度算法题3.1 旋转数组解法一解法二解法三 1.空间复杂度 根据摩尔定律&#xff0c;每隔一段时间晶体管的数量会增加一倍&#xff0c;即内存会增加&#xff0c;价格会降低。内存就不…

Windows CSC服务权限提升漏洞(CVE-2024-26229)

一、漏洞描述 csc.sys驱动程序中带有METHOD_NEITHER I/O控制代码的IOCTL地址验证不正确&#xff0c;导致任意地址写零漏洞。攻击者在Windows上获得较低权限的任意代码执行后&#xff0c;可以利用该漏洞将低权限提升至system权限。 二、漏洞详情 该漏洞源于 csc.sys 驱动程序…

Zilliz获Forrester报告全球第一;OB支持向量能力;Azure发布DiskANN;阿里云PG发布内置分析引擎

重要更新 1. Azure发布PostgreSQL向量索引扩展DiskANN&#xff0c;声称在构建HNSW/IVFFlat索引上&#xff0c;速度、精准度都超越pg_vector&#xff0c;并解决了pg_vector长期存在的偶发性返回错误结果的问题( [1] )。 2. 阿里云RDS PostgreSQL 发布AP加速引擎&#xff08;rds…

【python实操】python小程序之继承

引言 python小程序之继承 文章目录 引言一、继承1.1 概念1.1.1 基本语法1.1.2 关键点解释1.1.3 示例1.1.4 总结 1.2 题目1.3 代码1.4 代码解释1.4.1 类定义1.4.2 对象创建与方法调用1.4.3 总结 二、思考 一、继承 1.1 概念 python 中的继承是一种强大的机制&#xff0c;它允许…

如何防止webpack打包被逆向?

webpack打包后的js代码&#xff0c;看起来很混乱&#xff0c;似乎源码得到了保护&#xff1f; 不然&#xff0c;因为webpack只是将多个文件合并到了一起&#xff0c;并没有多少保护代码的功能。 比如下面这个例子&#xff0c;该网站的js文件是经webpack打包编译后生成的&…

TextView把其它控件挤出屏幕的处理办法

1.如果TextView后面的控件是紧挨着TextView的&#xff0c;可以给TextView添加maxWidth限制其最大长度 上有问题的布局代码 <?xml version"1.0" encoding"utf-8"?> <layout xmlns:android"http://schemas.android.com/apk/res/android&qu…

动态爬虫管理平台构建与实现(论文+源码)_kaic

摘 要 随着互联网的迅速发展&#xff0c;Web的信息量越来越大。人们往往通过搜索引擎去从互联网上搜索想要的信息&#xff0c;比如:百度&#xff0c;谷歌&#xff0c;搜狗等。这类搜索引擎称之为通用搜索引擎&#xff0c;其为所有的用户所需的内容&#xff0c;但目前互联网上的…

【网络原理】TCP协议提高效率的秘密-滑动窗口机制

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;计算机网络那些事 如果我们严格依照“确认应答”机制&#xff0c;针对每一个发送的数据段&#xff0c;都需要一个ACK确认应答&#xff0c;当收到ACK应答报文后&#xff0c;才继续发下一个报文。这样…

2025届计算机保研经验贴(末九→浙江大学软件学院)

燕园再美美不过宁波港&#xff0c;没到过浙软的人不会明了 软微已死&#xff0c;浙软当立&#xff01; 文章目录 一、个人情况二、保研历程1、去年今日2、前期准备3、夏令营天大智算软件所西交软本校浙江大学软件学院 4、预推免 三、后记链式反应9.28下午冥场面9.29博弈 浙软当…

ClickHouse 24.9 版本发布说明

本文字数&#xff1a;7295&#xff1b;估计阅读时间&#xff1a;19 分钟 作者&#xff1a;ClickHouse Team 本文在公众号【ClickHouseInc】首发 又到新版本发布的时间了&#xff01; 发布概要 本次ClickHouse 24.9 版本包含了23个新功能&#x1f381;、14项性能优化&#x1f6f…

[已解决] HttpMessageNotReadableException: JSON parse error: Unexpected character

[已解决] HttpMessageNotReadableException: JSON parse error: Unexpected character 文章目录 写在前面问题描述报错原因分析&#xff1a; 解决思路解决办法1. 检查并修复客户端的 JSON 数据格式2. 确认请求头的 Content-Type 设置正确3. 捕获并处理 HttpMessageNotReadableE…

三层b+树估算存储多少行数据

文章目录 B树结构图示估算方法(这里要以聚簇索引来看) B树结构图示 估算方法(这里要以聚簇索引来看) 非叶子节点数* 每个叶子结点记录总数 假设mysql 数据页&#xff0c;16kb&#xff0c;刚好对应B树的一个节点 每个叶子结点记录数&#xff0c; 叶子结点存储的是对应的原始数据…

项目常用版本控制管理工具

不仅仅是代码管理工具 gitHubgitcodeSVN gitHub https://github.com/ github gitcode https://gitcode.com/ gitcode SVN 图片: 带尺寸的图片: 居中的图片: 居中并且带尺寸的图片:

git--git reset

HEAD 单独一个HEAD eg:git diff HEAD 表示当前结点。 HEAD~ HEAD~只处理当前分支。 注意&#xff1a;master分支的上一个结点是tmp分支的所在的结点fc11b74, 79f109e才是master的第二个父节点。 HEAD~ 当前结点的父节点。 HEAD~1 当前结点的父节点。 HEAD~n 当前结点索…

Python 工具库每日推荐 【easyocr】

文章目录 引言Python OCR 工具库的重要性今日推荐:EasyOCR 工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例案例:多语言名片信息提取案例分析高级特性自定义模型训练处理倾斜文本扩展阅读与资源优缺点分析优点:缺点:总结【 已更新完 TypeScript…

Qt实现侧边栏功能

本文介绍Qt实现侧边栏功能。 采用Qt进行界面应用程序开发时&#xff0c;经常遇到侧边栏功能实现&#xff0c;采用侧边栏可以将一些暂时不用到的功能隐藏&#xff0c;使用的时候点击一下相应的按钮即可弹出&#xff08;动画方式&#xff09;功能菜单。减少主界面控件数量&#…