文章目录
[1. 有效的括号(括号匹配问题)](#1. 有效的括号(括号匹配问题))
[2. 用队列实现栈](#2. 用队列实现栈)
[3. 用栈实现队列](#3. 用栈实现队列)
[4. 设计循环队列](#4. 设计循环队列)
前言
栈(Stack)和队列(Queue)是两种非常重要的线性数据结构,在实际开发中有着广泛的应用。栈遵循后进先出(LIFO)的原则,而队列遵循先进先出(FIFO)的原则。在技术面试和算法竞赛中,栈与队列相关的题目出现频率极高,熟练掌握它们的常见操作和经典问题的解法,是每个程序员必备的技能。
本文精选了4道经典的栈与队列OJ题目,从思路分析到C语言代码实现,逐步详解,每道题目都附带了对应的在线练习链接,方便读者动手实践。希望能帮助读者深入理解栈与队列,轻松应对各类相关题目。
1. 有效的括号(括号匹配问题)
题目描述
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。🔗 力扣 20. 有效的括号
有效字符串需满足:
-
左括号必须用相同类型的右括号闭合。
-
左括号必须以正确的顺序闭合。
-
每个右括号都有一个对应的相同类型的左括号。
思路分析
这道题是栈的经典应用。我们可以利用栈"后进先出"的特性来检验括号的匹配。具体做法:
-
遍历字符串中的每个字符
-
如果遇到左括号(
'('、'{'、'['),就将其压入栈中,因为左括号可以暂时不与右括号匹配 -
如果遇到右括号(
')'、'}'、']'),则检查栈顶元素是否与之匹配:-
如果栈为空,说明右括号多余,返回
false -
如果栈顶元素与当前右括号不匹配,返回
false -
如果匹配,则将栈顶元素弹出
-
-
遍历完整个字符串后,检查栈是否为空:如果栈为空,说明所有括号都匹配成功;否则说明有左括号未被匹配,返回
false
代码实现(C语言)
cpp
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef char Stackdatatype;
typedef struct stack
{
Stackdatatype* arr;
int top;
int capacity;
}stack;
//栈的初始化以及销毁
void StackInit(stack* pst);
void StackDestroy(stack* pst);
//入栈出栈
void StackPush(stack* pst,Stackdatatype data);
void Stackpop(stack* pst);
Stackdatatype StackTop(stack* pst);//取栈顶元素
//判空
bool Stackempty(stack* pst);
//获取个数
int Stacksize(stack* pst);
void StackInit(stack* pst)
{
assert(pst!= NULL);
pst->arr = NULL;
//这里的top指向的是栈顶元素的下一个位置
pst->top = pst->capacity = 0;
}
void StackDestroy(stack* pst)
{
assert(pst!= NULL);
if (pst->arr)
free(pst->arr);
pst->arr = NULL;
pst->top = pst->capacity = 0;
}
void StackPush(stack* pst, Stackdatatype data)
{
assert(pst!= NULL);
//首先判断栈空间是否已经满了
if (pst->top == pst->capacity)
{
int newcapacity = (pst->capacity == 0)? 4 : 2 * pst->capacity;
//这里在原来的基础上进行扩容
Stackdatatype* newarr = (Stackdatatype*)realloc
(pst->arr,newcapacity * sizeof(Stackdatatype));
if (newarr == NULL)
{
perror("malloc failed");
exit(1);
}
//对相关数据进行更新
pst->arr = newarr;
pst->capacity = newcapacity;
}
//栈空间未满,直接存入数据
pst->arr[pst->top++] = data;
}
void Stackpop(stack* pst)
{
assert(pst!= NULL);
assert(pst->top != 0);
pst->top--;
}
Stackdatatype StackTop(stack* pst)
{
assert(pst!= NULL&&pst->top!= 0);
return pst->arr[pst->top - 1];
}
bool Stackempty(stack* pst)
{
assert(pst!= NULL);
return pst->top == 0;
}
int Stacksize(stack* pst)
{
assert(pst!= NULL);
return pst->top;
}
bool isValid(char* s)
{
stack pst;
StackInit(&pst);
while(*s)
{
if(*s=='('||*s=='{'||*s=='[')
{
StackPush(&pst,*s);
}
else
{
if(Stackempty(&pst))
return false;
if((*s==')'&&StackTop(&pst)!='(')
||(*s=='}'&&StackTop(&pst)!='{')
||(*s==']'&&StackTop(&pst)!='['))
{
return false;
}
Stackpop(&pst);
}
s++;
}
bool ret=Stackempty(&pst);
return ret;
}
2. 用队列实现栈
题目描述
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、pop、top、empty)。🔗 力扣 225. 用队列实现栈
实现 MyStack 类:
-
void push(int x)将元素 x 压入栈顶 -
int pop()移除并返回栈顶元素 -
int top()返回栈顶元素 -
boolean empty()如果栈是空的,返回true;否则,返回false
思路分析
用队列实现栈的关键在于,队列是先进先出,而栈是后进先出,我们需要在入栈或出栈时调整元素的顺序。常见做法:
-
使用两个队列,一个主队列
q1用于存储元素,一个辅助队列q2用于临时存储 -
入栈(push) :将新元素入队到
q1即可 -
出栈(pop) :将
q1中的前size-1个元素移到q2中,然后q1中剩下的最后一个元素就是要出栈的元素,将其出队并返回;最后交换q1和q2的角色 -
获取栈顶(top) :类似
pop,但不删除元素,返回q1的队尾元素
代码实现(C语言)
cpp
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QListdataType;
typedef struct QListNode //这里是队列的节点结构
{
struct QListNode* next;
QListdataType data;
}Qnode;
//这里是队列的结构,这里是为了方便后面的操作,所以使用了结构体
//包含了队列的头指针,尾指针,队列的大小
//否则我们需要多次遍历操作才能实现队列
typedef struct Queue
{
Qnode* phead;
Qnode* ptail;
int size;
}Que;
//队列的初始化以及销毁函数
void QueInit(Que* pq);
void QueDestroy(Que* pq);
//队列的入队操作
void Quepush(Que* pq, QListdataType data);
//队列的出队操作
void Quepop(Que* pq);
//队列的判空操作
bool QueEmpty(Que* pq);
//队列的长度
int QueSize(Que* pq);
QListdataType S(Que* pq);//队头
QListdataType Queback(Que* pq);//队尾
void QueInit(Que* pq)
{
assert(pq != NULL);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
bool QueEmpty(Que* pq)
{
return pq->size == 0;
}
void QueDestroy(Que* pq)
{
assert(pq != NULL);
Qnode* pcur =pq->phead;
while (pcur)
{
Qnode* next = pcur->next;
free(pcur);
pcur = next;
}
pq->size = 0;
pq->phead = pq->ptail = NULL;
}
void Quepush(Que* pq, QListdataType data)
{
assert(pq!=NULL);
Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));
if (newnode == NULL)
{
perror("malloc failed");
return;
}
newnode->data = data;
newnode->next = NULL;
if (pq->size==0)//如果一个节点都没有,就需要对头节点也进行更新
pq->phead = pq->ptail = newnode;
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
void Quepop(Que* pq)
{
//这里要保证队列中至少要有一个节点
assert(pq != NULL&&pq->phead!=NULL);
if (pq->size == 1)//如果只有一个节点
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
pq->size = 0;
return;
}
Qnode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
pq->size--;
}
int QueSize(Que* pq)
{
return pq->size;
}
QListdataType Quefront(Que* pq)
{
assert(pq != NULL && pq->size != 0);
return pq->phead->data;
}
QListdataType Queback(Que* pq)
{
assert(pq != NULL && pq->size != 0);
return pq->ptail->data;
}
typedef struct
{
Que* que1;
Que* que2;
} MyStack;
MyStack* myStackCreate()
{
MyStack* obj=(MyStack*)malloc(sizeof(MyStack));
obj->que1 = malloc(sizeof(Que));
obj->que2 = malloc(sizeof(Que));
QueInit(obj->que1);
QueInit(obj->que2);
return obj;
}
void myStackPush(MyStack* obj, int x)
{
if(!QueEmpty(obj->que1))//将数据插入不为空的队列
{
Quepush(obj->que1,x);
}
else
{
Quepush(obj->que2,x);
}
}
int myStackPop(MyStack* obj)
{
Que* emptyque=obj->que1;
Que* unemptyque=obj->que2;
if(QueEmpty(obj->que2))
{
emptyque=obj->que2;
unemptyque=obj->que1;
}
while(unemptyque->size>1)
{
Quepush(emptyque,unemptyque->phead->data);
Quepop(unemptyque);
}
int ret=unemptyque->phead->data;
Quepop(unemptyque);
return ret;
}
int myStackTop(MyStack* obj)
{
return QueEmpty(obj->que1)==true?Queback(obj->que2):Queback(obj->que1);
}
bool myStackEmpty(MyStack* obj)
{
return QueEmpty(obj->que1)&&QueEmpty(obj->que2);
}
void myStackFree(MyStack* obj)
{
QueDestroy(obj->que1);
QueDestroy(obj->que2);
free(obj);
}
3. 用栈实现队列
题目描述
请你仅使用两个栈实现一个先入先出(FIFO)的队列,并支持队列的全部操作(push、pop、peek、empty)。🔗 力扣 232. 用栈实现队列
实现 MyQueue 类:
-
void push(int x)将元素 x 推到队列的末尾 -
int pop()从队列的开头移除并返回元素 -
int peek()返回队列开头的元素 -
boolean empty()如果队列为空,返回true;否则,返回false
思路分析
用栈实现队列的思路与上一题类似,但方向相反。我们可以使用两个栈:
-
入栈(pushStack):专门用于入队操作
-
出栈(popStack):专门用于出队操作
具体规则:
-
入队(push) :直接将元素压入
pushStack -
出队(pop) :如果
popStack不为空,则直接从popStack弹出栈顶元素;如果popStack为空,则将pushStack中的所有元素依次弹出并压入popStack,这样popStack中元素的顺序就变成了队列的顺序,然后再从popStack弹出栈顶元素 -
获取队首(peek) :与
pop类似,但不删除元素
代码实现(C语言)
cpp
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int Stackdatatype;
typedef struct stack
{
Stackdatatype* arr;
int top;
int capacity;
}stack;
//栈的初始化以及销毁
void StackInit(stack* pst);
void StackDestroy(stack* pst);
//入栈出栈
void StackPush(stack* pst,Stackdatatype data);
void Stackpop(stack* pst);
Stackdatatype StackTop(stack* pst);//取栈顶元素
//判空
bool Stackempty(stack* pst);
//获取个数
int Stacksize(stack* pst);
void StackInit(stack* pst)
{
assert(pst!= NULL);
pst->arr = NULL;
//这里的top指向的是栈顶元素的下一个位置
pst->top = pst->capacity = 0;
}
void StackDestroy(stack* pst)
{
assert(pst!= NULL);
if (pst->arr)
free(pst->arr);
pst->arr = NULL;
pst->top = pst->capacity = 0;
}
void StackPush(stack* pst, Stackdatatype data)
{
assert(pst!= NULL);
//首先判断栈空间是否已经满了
if (pst->top == pst->capacity)
{
int newcapacity = (pst->capacity == 0)? 4 : 2 * pst->capacity;
//这里在原来的基础上进行扩容
Stackdatatype* newarr = (Stackdatatype*)realloc
(pst->arr,newcapacity * sizeof(Stackdatatype));
if (newarr == NULL)
{
perror("malloc failed");
exit(1);
}
//对相关数据进行更新
pst->arr = newarr;
pst->capacity = newcapacity;
}
//栈空间未满,直接存入数据
pst->arr[pst->top++] = data;
}
void Stackpop(stack* pst)
{
assert(pst!= NULL);
assert(pst->top != 0);
pst->top--;
}
Stackdatatype StackTop(stack* pst)
{
assert(pst!= NULL&&pst->top!= 0);
return pst->arr[pst->top - 1];
}
bool Stackempty(stack* pst)
{
assert(pst!= NULL);
return pst->top == 0;
}
int Stacksize(stack* pst)
{
assert(pst!= NULL);
return pst->top;
}
typedef struct
{
stack* pushstack;//专门用来放入队元素的栈
stack* popstack;//专门用来放出队元素的栈
} MyQueue;
MyQueue* myQueueCreate()
{
MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));
obj->pushstack=(stack*)malloc(sizeof(stack));
obj->popstack=(stack*)malloc(sizeof(stack));
StackInit(obj->pushstack);
StackInit(obj->popstack);
return obj;
}
void myQueuePush(MyQueue* obj, int x)
{
StackPush(obj->pushstack,x);
}
int myQueuePop(MyQueue* obj)
{
if (Stackempty(obj->popstack))
{
while(!Stackempty(obj->pushstack))
{
StackPush(obj->popstack,StackTop(obj->pushstack));
Stackpop(obj->pushstack);
}
}
int ret=StackTop(obj->popstack);
Stackpop(obj->popstack);
return ret;
}
int myQueuePeek(MyQueue* obj)
{
if (Stackempty(obj->popstack))
{
while(!Stackempty(obj->pushstack))
{
StackPush(obj->popstack,StackTop(obj->pushstack));
Stackpop(obj->pushstack);
}
}
int ret=StackTop(obj->popstack);
return ret;
}
bool myQueueEmpty(MyQueue* obj)
{
if(Stackempty(obj->popstack)&&Stackempty(obj->pushstack))
return true;
else
return false;
}
void myQueueFree(MyQueue* obj)
{
StackDestroy(obj->popstack);
StackDestroy(obj->pushstack);
free(obj);
}
4. 设计循环队列
题目描述
设计你的循环队列实现。循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为"环形缓冲器"。🔗 力扣 622. 设计循环队列
实现 MyCircularQueue 类:
-
MyCircularQueue(k):构造器,设置队列长度为 k -
enQueue(value):向循环队列插入一个元素。如果成功插入则返回真 -
deQueue():从循环队列中删除一个元素。如果成功删除则返回真 -
Front():从队首获取元素。如果队列为空,返回 -1 -
Rear():获取队尾元素。如果队列为空,返回 -1 -
isEmpty():检查循环队列是否为空 -
isFull():检查循环队列是否已满
思路分析
循环队列通常使用数组实现,通过取模运算让数组在逻辑上形成一个环。实现时需要区分队空和队满状态。常见的做法是留一个空位 ,即当 (rear + 1) % capacity == front 时认为队满,这样 front == rear 时就表示队空。
文档中给出了循环队列的示意图,展示了空的循环队列和队满时的状态(rear+1=front)[citation:课件Page6]。
代码实现(C语言)
cpp
typedef struct
{
int* arr;
int head;
int tail;
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj=( MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->arr=(int*)malloc(sizeof(int)*(k+1));
obj->head=obj->tail=0;
obj->k=k;
return obj;
}
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) //插入数据
{
if(myCircularQueueIsFull(obj))//如果满了
return false;
else
{
obj->arr[obj->tail]=value;
obj->tail++;
obj->tail%=(obj->k+1);
return true;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) //删除数据
{
if(obj->head==obj->tail)//如果循环链表为空
return false;
obj->head=(obj->head+1)%(obj->k+1);//这里对head也需要取模
return true;
}
int myCircularQueueFront(MyCircularQueue* obj)
{
if(obj->head==obj->tail)//如果循环链表为空
return -1;
else
return obj->arr[obj->head];
}
int myCircularQueueRear(MyCircularQueue* obj)
{
if(obj->head==obj->tail)//如果循环链表为空
return -1;
else
return obj->arr[(obj->tail-1+obj->k+1)%(obj->k+1)];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->head==obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return (obj->tail+1)%(obj->k+1)==obj->head;
}
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->arr);
free(obj);
}
结语
栈和队列作为基础数据结构,其核心在于理解它们的特性 和灵活运用这两种结构解决问题。通过以上4道经典题目的练习,相信大家已经掌握了栈和队列操作的基本技巧:
-
栈的"后进先出"特性在括号匹配、表达式求值等问题中大显身手
-
队列的"先进先出"特性则适用于排队场景
-
用栈实现队列 和用队列实现栈这两道题,考察的是对两种数据结构特性的深入理解
-
循环队列则是在有限空间内高效利用资源的经典设计
下一篇文章我们将开始二叉树的学习。