栈和队列的习题
有效的括号
思路
左括号入栈, 右括号出栈
遍历一遍字符串, 遇到左括号字符串中的元素入栈, 遇到右括号让刚入栈的元素出栈(就是取栈顶元素), 二者进行匹配, 一旦匹配失败直接返回false, 否则继续遍历匹配。
要注意两种情况:
- 只有右括号, 在左括号入栈时并未入栈时, 直接返回false。
- 只有左括号,遍历时只进行了左括号的入栈, 在最后返回时检查一下栈是否为空并且保存在一个bool类型的值中, 直接返回这个值中。
参考代码
bool isValid(char * s){
ST 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);
StackPop(&st);
//不匹配
if((*s==')' && top != '(')
|| (*s=='}' && top != '{')
|| (*s==']' && top != '['))
{
StackDestroy(&st);
return false;
}
else
{
++s;
}
}
}
bool ret=StackEmpty(&st); //解决只有左括号的问题
StackDestroy(&st);
return ret;
}
用队列实现栈
思路
保持一个队列存数据, 一个队列为空, 出数据时二者倒一下
定义两个队列, 动态开辟一块空间并且初始化两个队列用这两个队列模拟栈的各个操作
入栈: 判断出二者哪个是非空队列, 向非空队列中插入元素
出栈:
先判断找出空队列
将非空队列前n-1个元素倒入到空队列中(通过循环遍历对非空队列取栈顶元素到空队列, 一共取n-1次), 同时删除非空队列前n-1个数据
记录下非空队列的最后一个数据(最后一个数据就是队头元素或队尾元素), 删掉它并且返回记录的值
取栈顶元素: 栈顶元素就是队尾元素, 返回非空队列中队尾指针所指向的元素
判空: 直接判断两个队列是否为空
释放: 先销毁两个队列最后释放动态开辟的obj(如果先释放obj后续无法找到定义的两个队列, 无法销毁)
释放的时候注意:
注意点
C语言中队列的各种操作必须动手自己写, 写好后再模拟栈的各种接口实现
下面的参考代码并不全, 只模拟了栈的各种操作, 需要添加上队列的各种操作, 见链接: 链接
参考代码
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() { //mystack需要动态开辟 ----> 创建+初始化
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*emptyQ=&obj->q1;
Queue*nonemptyQ=&obj->q2;
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) { //栈顶元素就是队尾元素
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->q2);
free(obj);
}
用栈实现队列
思路
定义两个栈, 一个模拟入队pushst, 一个模拟出队popst
定义两个栈, 在pushst中进行入队操作, popst中进行出队操作, 动态开辟一块空间并且初始化两个栈用这两个栈模拟队列的各个操作
入队: 直接向pushst中插入数据
出队: 直接调用取队头元素的函数, 记录下来值并返回, 删除掉popst的数据(就是删掉栈顶元素就是队头元素)
取队头元素:
- 判断popst是否为空
- popst不为空, 返回栈顶元素
- popst为空, 将pushst的数据倒入到popst中(循环遍历取pushst栈顶数据)并删除pushst中的数据, 最后返回popst栈顶元素
判空: 直接判断两个栈是否为空
释放: 先销毁两个栈最后释放动态开辟的obj(如果先释放obj后续无法找到定义的两个栈, 无法销毁)
注意点
C语言中栈的各种操作必须动手自己写, 写好后再模拟队列的各种接口实现
下面的参考代码并不全, 只模拟了队列的各种操作, 需要添加上栈的各种操作, 见链接: 链接
参考代码
typedef struct {
ST pushst;
ST popst;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue*obj=(MyQueue*)malloc(sizeof(MyQueue));
//初始化
StackInit(&obj->pushst);
StackInit(&obj->popst);
return obj;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->pushst, x);
}
int myQueuePop(MyQueue* obj) {
int peek=myQueuePeek(obj);
StackPop(&obj->popst);
return peek;
}
int myQueuePeek(MyQueue* obj) {
//倒数据
if(StackEmpty(&obj->popst))
{
while(!StackEmpty(&obj->pushst))
{
StackPush(&obj->popst, StackTop(&obj->pushst));
StackPop(&obj->pushst);
}
}
return StackTop(&obj->popst);
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->pushst) && StackEmpty(&obj->popst);
}
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->pushst);
StackDestroy(&obj->popst);
free(obj);
}
设计循环队列
扩展
这是一道有关环形队列的题, 关于环形队列扩展如下:
实际中我们有时还会使用一种队列叫循环队列。如操作系统中的生产者消费者模型时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。
思路
1. 前提
那么这里如何实现环形队列的定义呢?
考虑到两个问题: 1. 如何实现循环 2. 环形队列的结构使用链表还是数组实现
问题1: 两种实现方式: 1. 增加一个size用来计数控制实现循环 2. 增加一个空余结点(不用存储数据的结点)
问题2: 链表看似实现容易实则在取队尾元素时需要得到rear的前一个结点(需要定义rear的前一个指针prev)所以采用数组实现
本题的解法是: 数组+增加一个空余结点
2. 版本1
定义: 定义一个指针, 队列头指针, 队列尾指针, 变量k(队列的大小)全部放在一个结构体的环形队列中
创建环形队列: 动态开辟一个环形队列, 环形队列中动态开辟一个数组(注意多开辟一个空间解决循环和判满问题), 队列的头指针和尾指针都初始化为0, 变量k还是定义为k(队列满时能存储数据的个数是k, 实际空间大小是k+1)
判空: front = rear时队列为空
判满: 有坑, 真正满: (rear+1)% (k+1) = front一般情况: rear + 1 = front时环形队列就满了
特殊情况: rear + 1 != front
这时目的是让 rear+1 的数留在0~k之间, 所以对rear+1取模,即 (rear+1)% (k+1) = front 时,队列就满了插入元素:
先检查队列是否已满, 队列已满不能插入数据
队列未满时, 向队列中的数组中插入数据, 同时队列尾指针要向后移
当队列尾指针移到k+1下标位置越界时, 队列尾指针改成0
删除元素:
先检查队列是否为空, 队列为空不能删除数据
队列不为空时, 直接队列头指针++向后移
当队列头指针移到k+1下标位置越界时, 队列头指针改成0
取队头元素: 队列为空返回-1, 否则返回队列头指针所指向的元素
取队尾元素:
- 队列为空返回-1
- 队列不为空时, 当队列尾指针指向0下标时返回k下标的元素; 否则返回队列尾指针前一个位置的元素
释放: 先释放开辟的数组再释放开辟的队列
3. 版本2
队列的定义,判空, 判满, 取队列头元素, 释放都与版本1相同
插入元素:
先检查队列是否已满, 队列已满不能插入数据
队列未满时, 向队列中的数组中插入数据, 同时队列尾指针要向后移, 且保证尾指针位置在0~k之间
rear %= k+1
删除元素:
1. 先检查队列是否为空, 队列为空不能删除数据
- 队列不为空时, 直接队列头指针++向后移, 且保证头指针位置在0~k之间 front %= k+1
取队尾元素:
- 队列为空返回-1
- 队列不为空时, 返回rear-1+k+1同时保证范围在0~k之间 [(rear-1)+(k-1)]%(k+1), 即**rear+k % k+1**
参考代码
版本1
typedef struct {
int*a;
int front; //队列头指针
int rear; //队列尾指针
int k; //队列的大小
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//多开一个解决判满的问题
obj->a=(int*)malloc(sizeof(int)*(k+1));
obj->front=obj->rear=0;
//满的时候能存储数据的个数是k, 实际空间的大小还是k+1;
obj->k=k;
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
assert(obj);
return obj->front == obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
assert(obj);
return ((obj->rear+1)%(obj->k+1))== obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
assert(obj);
if(myCircularQueueIsFull(obj))
return false;
obj->a[obj->rear++]=value;
if(obj->rear==obj->k+1)
obj->rear=0;
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
return false;
obj->front++;
if(obj->front == obj->k+1)
obj->front=0;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
return -1;
else
return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
return -1;
int rear=obj->rear==0?obj->k:obj->rear-1;
return obj->a[rear];
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
版本2
typedef struct {
int*a;
int front; //队列头指针
int rear; //队列尾指针
int k; //队列的大小
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//多开一个解决判满的问题
obj->a=(int*)malloc(sizeof(int)*(k+1));
obj->front=obj->rear=0;
//满的时候能存储数据的个数是k, 实际空间的大小还是k+1;
obj->k=k;
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
assert(obj);
return obj->front == obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
assert(obj);
return ((obj->rear+1)%(obj->k+1))== obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
assert(obj);
if(myCircularQueueIsFull(obj))
return false;
obj->a[obj->rear++]=value;
obj->rear %= (obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
return false;
obj->front++;
obj->front %= (obj->k+1);
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
return -1;
else
return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
assert(obj);
if(myCircularQueueIsEmpty(obj))
return -1;
else
return obj->a[((obj->rear-1)+(obj->k+1)) % (obj->k+1)];
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}