【栈和队列】常见OJ题,队列实现栈,栈实现队列,循环队列,括号匹配

本文详细介绍了如何使用两个队列来模拟栈的特性,包括入栈、出栈、返回栈顶元素和判断栈是否为空的操作。同时,也阐述了如何用两个栈实现队列的功能,以及设计循环队列的方法,包括插入、删除、检查队列状态等操作。此外,文章还涉及括号匹配问题的栈解决策略。
摘要由CSDN通过智能技术生成

用队列实现栈,点我开始做题

注意,文章使用的队列,及提到的队列函数,需要自己实现,详情看之前的文章队列的实现,
点击我开始做题
点我开始学习队列

在这里插入图片描述

这就是考察了我们队栈和队列的认识

  • 队列:先进先出
  • 栈:后进先出

思路讲解

我们用2个队列怎么实现出栈?

在这里插入图片描述

  • 假设我们队列里,存放的1 2 3 4
  • 按照队列的性质,先出的数据是1
  • 目标是模拟栈的性质,那我们就要先让4出

为了实现这个,我们可以利用另一个队列来回倒数据,要先出4,可以先将1 2 3放到另一个队列中

在这里插入图片描述

这里就实现了栈的后进先出的性质

我们用2个队列怎么实现入栈?

在这里插入图片描述

  • 还是这张图,我们已经将4出栈了
  • 如果要入栈,数据肯定要放入有数据的队列中
  • 如果2队列都没有数据,就随便放一个队列中

定义结构体

在这里插入图片描述

题目这样给的,意思是让你将2个队列放到MyStack结构体中,方便定义

typedef struct {
    Queue q1;
    Queue q2;
} MyStack;

值得注意的是,这个是匿名结构体的定义方法

初始化

 [

  • 题目给的模板的意思是,创造一个MyStack变量,这样你就拥有2个队列了
  • 也就是我们的的初始化结构体

在这里插入图片描述

  • 我们能不能这样创建初始化?
  • 不能,我们定义的obj是在函数里的,出了作用域就销毁了,但是我们仍然返回obj的地址,这不是野指针吗?
  • 为了解决这个,我们可以malloc指针来接收,malloc出来的空间是在堆上了出作用域不会销毁
MyStack* myStackCreate() {
    MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
    if (!obj)
    {
        return NULL;
    }

    queueinit(&obj->q1);
    queueinit(&obj->q2);

    return obj;
}
  • 注意,我们创建obj指针的时候,也就是创建了2个Queue结构体,其结构体中有首尾指针,我们也要给这2指针初始化,不然就是野指针
  • 我们传的实参是要取地址的,因为你创建MyStack的时候,是定义了变量,不是指针

在这里插入图片描述

注意,如果为了在传参的时候不取地址,我们创建MyStack的时候定义指针行不行?

在这里插入图片描述

在这里插入图片描述

可以是可以,但这样要考虑的问题太多了

  • 在结构体里定义Queue指针,那q1和q2就是没有初始化的野指针
  • 将q1和q2传递给QueueInit函数初始化,不就是对野指针解引用访问吗
  • 也就是说,我们只有结构体指针,没有结构体
  • 为了解决这个问题,我们必须在初始化前创建结构体出来

在这里插入图片描述> 这样就要free3次,malloc几次释放几次

在这里插入图片描述

这就是2种方法的区别,显然第一种好太多

入栈

入栈的逻辑很简单,哪个不为空,往哪个入栈,都为空指定一个队列进行入栈

void myStackPush(MyStack* obj, int x) {
    if (!queueempty(&obj->q1))
    {
        queuepush(&obj->q1, x);
    }
    else
    {
        queuepush(&obj->q2, x);
    }
}

出栈并返回栈顶元素

题目要求是出栈的同时返回数据

代码逻辑:

  • 出不为空的队列中的数据,放到另一个队列中,直到剩一个

    我们不需要关心是哪个队列不为空,可以用假设法来定义变量即可

    在这里插入图片描述

  • 这一个就是我们要出栈的数据

    出到还剩一个,这个就是要返回并删除的数据

    在这里插入图片描述

int myStackPop(MyStack* obj) {
    Queue* noempty = &obj->q1;
    Queue* empty = &obj->q2;
    if (queueempty(&obj->q1))
    {
        noempty = &obj->q2;
        empty = &obj->q1;
    }
    while (queuesize(noempty) > 1)
    {
        queuepush(empty, queuefront(noempty));
        queuepop(noempty);
    }
    int top = queuefront(noempty);
    queuepop(noempty);
    return top;
}

返回栈顶元素

我们想一想,队列的尾是不是就是栈顶元素,这个时候我们的back函数是不是有用了

int myStackTop(MyStack* obj) {
    Queue* noempty = &obj->q1;
    Queue* empty = &obj->q2;
    if (queueempty(&obj->q1))
    {
        noempty = &obj->q2;
        empty = &obj->q1;
    }
    return queueback(noempty);
}

判空

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

free函数

void myStackFree(MyStack* obj) {
    queuedestroy(&obj->q1);
    queuedestroy(&obj->q2);

    free(obj);
}
  • 我们只释放obj行不行?
  • 不行,如果只释放obj,相对于释放了q1和q2,那q1和q2里面的首尾指针怎么办?

在这里插入图片描述

用栈实现队列,

点我开始做题

在这里插入图片描述

这就是考察了我们队栈和队列的认识

  • 队列:先进先出
  • 栈:后进先出

思路讲解

我们用2个栈怎么实现出队列?

在这里插入图片描述

  • 假设我们栈a里面,存放数据1 2 3 4(1 2 3 4顺序进栈),怎么实现队列一样的先进先出?
  • 可以将数据全部倒到栈b里面

在这里插入图片描述

  • 在栈b中依次出顺序即可

  • 这样我们就做到了在栈b出数据,是先进先出了的

  • 值得注意的是,我们再将栈b的数据倒到栈a里面顺序会变的,不是跟以前队列模拟栈一样了

我们用2个栈怎么实现入队列?

在这里插入图片描述

  • 还是这个图为例,这个时候假设我们要再入数据 5 6,怎么入?
  • 肯定不能入栈b里面去,这样出队列的顺序就改变了
  • 只能将数据入到栈a里面
  • 来回几次就会发现,出数据和入数据是在2个不同的栈里实现的
  • 我们不妨定义专门的出栈,专门的入栈
  • 当然,我们还可以将数据再倒回栈a里面,再倒到栈b里面,这样不就很麻烦了吗

在这里插入图片描述

要出队列就专门将数据倒到popst里面,入数据就放入pushst里面

定义结构体,初始化

这里和我们的用2队列实现栈的代码一样,并且注意点都一致,这里不再叙述,详情可以看之前的文章

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


MyQueue* myQueueCreate() {
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
    if (!obj)
    {
        return NULL;
    }

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

入队列

入队列的逻辑非常简单,我们只需要往pushst里面无脑入即可

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

peek函数,返回队列头元素

在这里插入图片描述

题目提供了这2个函数

  • pop函数是删除队列头元素,并且返回改元素
  • peek函数是单纯返回队列头元素

为了,代码的复用性,我们是不是可以先定义peek函数,到时候直接在pop函数复用即可

代码逻辑也很简单,当popst为空的时候,就在pushst里面倒数据,不为空的时候就直接返回队列头元素

  • 注意,在往popst里面倒数据的时候,要在pushst里面删除数据
  • 对于pushst来说就是出数据然后删除
int myQueuePeek(MyQueue* obj) {
    if (stempty(&obj->popst))
    {
        while (!stempty(&obj->pushst))
        {
            stpush(&obj->popst, sttop(&obj->pushst));
            stpop(&obj->pushst);
        }
    }

    return sttop(&obj->popst);
}

出队列

int myQueuePop(MyQueue* obj) {
    int top = myQueuePeek(obj);
    stpop(&obj->popst);
    return top;
}

直接复用我们的peek函数

  • 当popst为null的时候,不需要我们手动倒数据,在peek函数里面就完成了
  • 当popst不为null的时候,可以直接拿到队列元素,只需要删除这个元素即可

判空函数

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

free函数

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

设计循环队列,点我开始做题

在这里插入图片描述

思路讲解

我们先不能确定设计循环队列用数组还是用链表实现,可以先画图来理解

在这里插入图片描述

  • 我们的rear指针是指向元素还是指向元素的下一个位置?
  • 指向元素的下一个位置,插入后,使rear++
  • 那什么情况下是代表队列满了?什么情况下代表队列是空的?

在这里插入图片描述

  • 当最后一个元素位置被插入后,rear和front指向同样的位置
  • 倘若我们用rear == front来表示,是不能确定队列是满的还是空的

解决方案:

  • 空置一个位置,不存放数据,判满条件就是,rear下一个位置等于front。判空就是rear=front
  • 添加计数器size,判满就是size=目标元素个数,判空就是size=0
  • 我们一般默认使用空置一个的方法

实现方案

用数组还是用链表?

  • 当用链表实现的时候

在这里插入图片描述

上图是连续5次的插入,并且rear->next = front代表队列满了

下图为删除数据,当我们要删除2次数据的时候,只需要将front往前移动2个

在这里插入图片描述

如果要再添加数据,覆盖掉前面的数据即可,添加6 7

这里有一个难点,题目要求返回队列头尾的元素数据,头的数据好说,但是返回尾数据(这个时候rear指向的是元素的下一个位置)有点麻烦,当然也可以实现

  • 当用数组实现的时候

在这里插入图片描述

假设要存储k=5个数据,要开k+1=6个空间,我们的判空条件很简单

按理来说,当rear到第6个空间的时候,我们就要盘满了,(rear+1)==front,但是当rear是空间的最后一个位置怎么办?+1就是越界了,这个时候我们可以%空间大小即可

在这里插入图片描述

这可以用(rear+1) %(k+1)== front来判满

当我们要删除数据的时候,让front++即可,一旦rear超出空间的时候就让其回到0的位置

在这里插入图片描述

明显数组来实现会更加简单一点

定义结构体

typedef struct {
    int front;
    int rear;
    int k;
    int* arr;
} MyCircularQueue;

这里没有难度

MyCircularQueue(k): 构造器,设置队列长度为 k

简单来说就是初始化结构体,创造出一个MyCircularQueue变量

MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    if (!obj)
        return NULL;
    
    int* p = (int*)malloc(sizeof(int) * (k + 1));
    if (!p)
        return NULL;
    
    obj->front = obj->rear = 0;
    obj->k = k;
    obj->arr = p;
    
    return obj;
}

先创建结构体变量,这样我们才能使用结构体,再创造数组,注意,我们采用置空的方法,就要多创造一个空间

isEmpty(): 检查循环队列是否为空。

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

isFull(): 检查循环队列是否已满。

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->rear + 1) % (obj->k + 1) == obj->front;
}

这些都是在思路讲解里面提到过

enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if (myCircularQueueIsFull(obj))
        return false;

    obj->arr[obj->rear] = value;
   /*  if(obj->rear+1 >= obj->k+1)
    {
        obj->rear = 0;
    }
    else
    {
        obj->rear++;
    } */
    obj->rear++;
    obj->rear %= (obj->k + 1);
    return true;
}

这样就是先插入数据,再让rear++即可

我们要注意的是,当rear超过空间的时候让其回到0,我们可以用if来判断,这里我们学习一个新东西,%

(rear+1)%(k+1)当rear+1在空间里面的时候,等于本身的值,当大于等于k+1的时候,就会从0开始重新排

deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
        return false;

    obj->front++;
    obj->front %= (obj->k + 1);
    return true;
}

让front++即可

同样要注意我们的越界循环问题

Front: 从队首获取元素。如果队列为空,返回 -1 。

int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
        return -1;

    return obj->arr[obj->front];
}	

Rear: 获取队尾元素。如果队列为空,返回 -1 。

int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
        return -1;

    return obj->arr[(obj->rear - 1 + obj->k + 1) % (obj->k + 1)];
}

注意,我们的rear是在元素的下一个位置,在k+1空间里面,rear-1即可访问到

如下图,当rear=0的时候,队尾数据下标其实是5,同样的用%即可

在这里插入图片描述

释放空间

malloc什么就释放什么即可,这里写简单一点

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->arr);
    free(obj);
}

括号匹配问题

在这里插入图片描述

这里用栈是天然的合适,遇到左括号我们就入栈,遇到右括号就出栈,一个一个开始匹配即可

  • 当没有左括号的时候,即栈在遇到第一个右括号的时候,仍然为空,直接flase
  • 当遇到右括号,则取出一个左括号与之匹配,一旦不匹配就flase
  • 当全部匹配完成了,栈应该是空的,刚好与我们的empty函数一致
//括号匹配问题	用栈实现
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 new = sttop(&st);	//取左括号匹配
            stpop(&st);
            if ((*s == ')' && new != '(')
                || (*s == '}' && new != '{')
                || (*s == ']' && new != '['))
            {
                stdestroy(&st);
                return false;
            }
        }
        s++;	//迭代
    }
    
    bool ret = stempty(&st);
    stdestroy(&st);
    return ret;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值