栈和队列OJ题讲解

目录

前言

1.队列实现栈

2.栈实现队列

3.循环队列

后记


前言

在我们分别学习了栈和队列之后,接下就看几道相关的OJ题吧!

代码中都附有相应的注释哦!

1.队列实现栈

225. 用队列实现栈 - 力扣(LeetCode)

思路:

  1. 使用两个队列,一个先存放数据,另一个保持为空;
  2. 当需要出栈操作时,先将存放数据的队列中的除开队尾的数据,都存放到空队列中,再对原先存放数据的队列进行出队列操作,以此来模拟出栈。
  3. 当需要入栈操作时,就对不为空的队列中进行入队列操作。
  4. 销毁时,要先销毁队列,因为这里队列是链表实现的,如果只释放obj的话,两个队列链表是不会被释放的。
  5. 注意:要始终保持一个队列为空,防止混淆。
  6. 此方法中,模拟的栈的栈顶就是有数据的队列的队尾

代码:

//获取两个队列
typedef struct {
    Queue q1;
    Queue q2;
} MyStack;


MyStack* myStackCreate() {
    MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
    QueueInit(&(pst->q1));
    QueueInit(&(pst->q2));

    return pst;
}

void myStackPush(MyStack* obj, int x) {
    //给不为空的队列里push数据
    if(!QueueEmpty(&(obj->q1)))
    {
        QueuePush(&(obj->q1), x);
    }
    else
    {
        QueuePush(&(obj->q2), x);
    }
}

int myStackPop(MyStack* obj) {
    //先假设q1队列为空
    Queue* empty = &(obj->q1);
    Queue* noempty = &(obj->q2);
    //利用判断语句,检验上述假设是否成立,不成立就改变赋值结果
    if(!QueueEmpty(&(obj->q1)))
    {
        empty = &(obj->q2);
        noempty = &(obj->q1);
    }
    //移动size-1个数据到空队列中
    while(QueueSize(noempty) > 1)
    {
        //将此时不为空的队列中的队头数据取出,入队列到空队列中
        QueuePush(empty, QueueFront(noempty));
        //将本此入队列到空队列的数据,在原本的队列中删除
        QueuePop(noempty);
    }

    //获取栈顶元素,此时的不为空队列只剩下队尾的数据
    //此时将其取出就相当于模仿了栈的后进先出
    int ret = QueueFront(noempty);
    QueuePop(noempty);

    return ret;
}

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);
}

需要注意的是,在上述代码以外,由于本题我们使用的是C语言解题,所以使用的队列Queue是需要我们自己实现的,可以将之前讲解队列的博客「数据结构」队列-CSDN博客中的,实现队列的代码提前拷贝到题目中。

2.栈实现队列

232. 用栈实现队列 - 力扣(LeetCode)

思路:

1.数组实现栈;

2.创建一个结构体统一管理两个栈(pushST和popST);

3.入队列操作

        入队列是从队尾进入队列中的,所以使用栈pushST可以直接模拟操作,直接将数据插入到pushST即可。

4.出队列操作

        a) 出队列操作是从队头出数据,所以只是用pushST无法模拟,将pushST中的数据一个一个出栈,然后入栈到popST中;

        b) 再在popST中进行出栈,即可模拟出队列的操作,之后再出栈就直接对popST进行出栈操作即可。

        c) 图解:

5.只有当popST为空之后,要再进行出队列操作时,再进行倒数据操作,将pushST中的数据倒置于popST,再模拟出队列;popST不为空时,不能进行倒数据的操作

6.而要入队列操作时,直接向pushST里入栈数据即可,这样才不会打乱模拟的队列。

代码:

//包含两个栈
typedef struct {
    ST pushST;
    ST popST;
} MyQueue;


MyQueue* myQueueCreate() {
    //动态申请的空间才能保存下来,如果直接创建变量,将导致是一个局部变量,无法返回
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
    STInit(&(obj->pushST));
    STInit(&(obj->popST));

    return obj;
}
//直接向pushST中插入数据
void myQueuePush(MyQueue* obj, int x) {
    STPush(&(obj->pushST), x);
}

int myQueuePop(MyQueue* obj) {
    //调用Peek函数得到队头元素
    int front = myQueuePeek(obj);
    //直接出队列
    STPop(&(obj->popST));
    return front;
}

//返回队头数据
int myQueuePeek(MyQueue* obj) {
    //判断popST是否为空,为空才能倒数据
    if(STEmpty(&(obj->popST)))
    {
        //倒数据
        while(!STEmpty(&(obj->pushST)))
        {
            //获取pushST中的栈顶数据
            int top = STTop(&(obj->pushST));
            //将这个栈顶数据放到popST中
            STPush(&(obj->popST), top);
            //删除pushST中的栈顶数据
            STPop(&(obj->pushST));
        }
    }
    //popST的栈顶,就相当于模拟的队列的队头,所以直接返回popST的栈顶元素即可
    int ret = STTop(&(obj->popST));
    return ret;
}

bool myQueueEmpty(MyQueue* obj) {
    //两个栈都为空时代表模拟的队列为空
    return STEmpty(&(obj->pushST)) && STEmpty(&(obj->popST));
}

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

注意,和上一题队列实现栈一样,我们需要提前写好栈数据结构的代码放到本题题解的前面备用。

相关实现代码可以到博客「数据结构」栈-CSDN博客中提取。

3.循环队列

622. 设计循环队列 - 力扣(LeetCode)

思路:

1.使用数组实现(因为循环队列的大小固定,所以可以使用数组);

2.用head变量来表示头下标位置,tail变量表示尾元素的下一个位置,k为队列可使用的长度;

3.在创建数组时,动态申请空间申请k+1个空间,多申请一个空间,用于分辨循环队列为满和为空的情况;
  

        若没有多的一个空间,tail == head同时代表了队列为空和队列为满的两种状态。

      a) 当可用空间满时:(tail+1)%(k+1)== head;

      b) 当tail == head,说明队列为空;

代码:

typedef struct {
    int* a;
    int head;//头下标和尾下标
    int tail;
    int k;//循环队列大小
} MyCircularQueue;

bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueIsEmpty(MyCircularQueue* obj);

//初始化
MyCircularQueue* myCircularQueueCreate(int k) {
    //先创建一个循环链表的结构体
    MyCircularQueue* que = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    // if(que == NULL)
    // {
    //     perror("malloc fail!");
    //     return NULL;
    // }
    //申请循环链表存储数据的数组,空间多申请一个
    que->a = (int*)malloc(sizeof(int) * (k + 1));
    que->head = que->tail = 0;
    que->k = k;
    return que;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    //判断队列是否为满,满了不能插入数据,返回false
    if(myCircularQueueIsFull(obj))
        return false;
    obj->a[obj->tail] = value;
    obj->tail++;
    
    obj->tail %= (obj->k + 1);
    //tail指向的是最后一个数据的下一个位置
    //当tall为k+1时,它就已经超出了队列的下标范围,所以通过模的方式使其实现循环
    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    //先判空,若为空就返回false
    if(myCircularQueueIsEmpty(obj))
        return false;
    else
    {
        obj->head++;//直接将头指针往前走一个位置,访问不到就相当于删除数据
        obj->head %= (obj->k + 1);//同过模k+1的方式,使其实现循环
        return true;
    }
}

int myCircularQueueFront(MyCircularQueue* obj) {
    //队列若为空返回-1
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
        return obj->a[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    //判空
    if(obj->head == obj->tail)
        return -1;
    else//需要考虑tail的取值范围问题
        return obj->a[(obj->tail - 1 + obj->k + 1) % (obj->k +1)];
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    if(obj->head == obj->tail)
        return true;
    else
        return false;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    if((obj->tail + 1) % (obj->k + 1) == obj->head)
        return true;
    else
        return false;
}

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

 注意:

在获取队尾数据函数中的返回条件 return obj->a[(obj->tail - 1 + obj->k + 1) % (obj->k +1)] 解释:

后记

本篇讲解的3道OJ题理解上都有一些难度,大家可以多结合图解理解,可以自己动手画一画图,不要偷懒喔~

那么本篇就到这里了,如果看了图解和注释还有不好理解的地方,大家可以在评论区或者私信提问哦!

我们下篇再见!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值