• OJ题讲解——栈与队列


    目录

    一.有效的括号

    1.问题描述

    2.问题详解

    3.代码

    二.用队列实现栈

    1.问题描述

    2.问题详解

    3.代码

    三.用栈实现队列

    1.问题描述

    2.问题详解

    3.代码

    四.设计循环队列

    1.问题描述

    2.问题详解

    3.代码


    一.有效的括号

    1.问题描述

    OJ链接:. - 力扣(LeetCode)

    2.问题详解

    对于这道题,采用栈来解决

    1.如果是左括号就入栈

    2.如果是右括号,取栈顶元素与之匹配

            2.1 当栈里面为空时,表明右括号多了,没有左括号与之匹配,返回false

            2.2 由于匹配的情况有很多种,无法一一列全,于是我们来判断不匹配的情况,当左右括号不匹配就返回false

            2.3 当所有的右括号匹配完后还要检查栈是否为空,可能还有左括号没有匹配

    3.代码

    注意;

    本篇博客都是在c语言的环境下,当涉及到用栈和队列,需要用户自己先创建栈或队列

    1. bool isValid(char* s) {
    2. //1.左括号入栈
    3. //2.右括号与出栈顶的左括号匹配
    4. ST st;
    5. STInit(&st);
    6. while(*s)
    7. {
    8. //左括号入栈
    9. if(*s=='('||*s=='['||*s=='{')
    10. {
    11. STPush(&st,*s);
    12. }
    13. else//右括号与出栈顶的左括号匹配
    14. {
    15. if(STEmpty(&st))
    16. {
    17. STDestroy(&st);
    18. return false;
    19. }
    20. char top=STTop(&st);
    21. STPop(&st);
    22. //不匹配的情况·
    23. if((top=='('&&*s!=')')
    24. ||(top=='['&&*s!=']')
    25. ||(top=='{'&&*s!='}'))
    26. {
    27. STDestroy(&st);
    28. return false;
    29. }
    30. }
    31. s++;
    32. }
    33. //栈不为空,说明左括号比右括号多,不匹配
    34. bool ret=STEmpty(&st);
    35. STDestroy(&st);
    36. return ret;
    37. }

    二.用队列实现栈

    1.问题描述

    OJ链接:. - 力扣(LeetCode)

    2.问题详解

    1.先创建一个结构体,里面存储俩个队列

    2.对于实现栈push,top,pop,empty四个操作中,最麻烦的是pop操作,因为队列pop的是队头元素,而栈pop的是栈顶元素,因此我们需要用到俩个队列,保持一个队列存数据,一个队列为空,pop数据需要通过空队列导一下,将前size-1个数据按顺序边导到空队列中边删除掉,将最后一个数据保存后再删除掉,这样就获取栈顶的数据并且pop成功

    3.top操作可以直接返回非空队列的队尾元素

    4.push操作直接导入不为空的队列中

    5.empty操作需要对俩个队列都进行判空操作

    3.代码

    1. typedef struct {
    2. Queue q1;
    3. Queue q2;
    4. } MyStack;
    5. MyStack* myStackCreate() {
    6. MyStack*pst=(MyStack*)malloc(sizeof(MyStack));
    7. QueueInit(&(pst->q1));
    8. QueueInit(&(pst->q2));
    9. return pst;
    10. }
    11. void myStackPush(MyStack* obj, int x) {
    12. //将数据导入不为空的那个队列
    13. if(!QueueEmpty(&(obj->q1)))
    14. {
    15. //栈顶插入刚好就是队尾插入
    16. QueuePush(&(obj->q1),x);
    17. }
    18. else
    19. {
    20. QueuePush(&(obj->q2),x);
    21. }
    22. }
    23. int myStackPop(MyStack* obj) {
    24. //假设法
    25. Queue*empty=&(obj->q1);
    26. Queue*nonempty=&(obj->q2);
    27. if(!QueueEmpty(&(obj->q1)))
    28. {
    29. empty=&(obj->q2);
    30. nonempty=&(obj->q1);
    31. }
    32. //将有数据的队列的前size-1个导到空队列
    33. while(QueueSize(nonempty)>1)
    34. {
    35. QueuePush(empty,Queuefront(nonempty));
    36. QueuePop(nonempty);
    37. }
    38. int top= Queuefront(nonempty);
    39. QueuePop(nonempty);
    40. return top;
    41. }
    42. int myStackTop(MyStack* obj) {
    43. if(!QueueEmpty(&(obj->q1)))
    44. {
    45. return QueueBack(&(obj->q1));
    46. }
    47. else
    48. {
    49. return QueueBack(&(obj->q2));
    50. }
    51. }
    52. bool myStackEmpty(MyStack* obj) {
    53. return QueueEmpty(&(obj->q1))&&QueueEmpty(&(obj->q2));
    54. }
    55. void myStackFree(MyStack* obj) {
    56. QueueDestroy(&(obj->q1));
    57. QueueDestroy(&(obj->q2));
    58. free(obj);
    59. }


    三.用栈实现队列

    1.问题描述

    OJ链接:. - 力扣(LeetCode)

    2.问题详解

    创建俩个栈,分别为pushst和popst,pushst用来插入数据,popst用来导数据,删除队头元素(注意:与上问题的俩个队列不同,用队列实现栈需要俩个队列相互导数据,保证一个队列始终为空,而这个问题不需要俩个栈相互导数据),如下图所示:

    通过上面的三个步骤,我们可以看出,当popst栈中为空,又需要pop队头元素时,我们直接将pushst栈中的元素导导popst栈中,然后删除的栈顶元素就是队头元素,同时当pushst和popst中都有元素,又想删除队头元素时,我们不需要再将pushst栈中的元素导到popst栈中去,只需要删除popst栈顶元素就好了。因此,插入数据只需要插入到pushst中,而删除队头数据,只有当popst栈中没有元素时,才需要将pushst中的元素全部导入popst中去,再删除栈顶元素

    3.代码

    1. typedef struct {
    2. ST pushst;//用来输入数据
    3. ST popst;//用来倒数据,实现删除
    4. } MyQueue;
    5. MyQueue* myQueueCreate() {
    6. MyQueue*obj=(MyQueue*)malloc(sizeof(MyQueue));
    7. STInit(&(obj->pushst));
    8. STInit(&(obj->popst));
    9. return obj;
    10. }
    11. void myQueuePush(MyQueue* obj, int x) {
    12. STPush(&(obj->pushst),x);
    13. }
    14. int myQueuePop(MyQueue* obj) {
    15. int top=myQueuePeek(obj);
    16. STPop(&(obj->popst));
    17. return top;
    18. }
    19. int myQueuePeek(MyQueue* obj) {
    20. if(STEmpty(&(obj->popst)))
    21. {
    22. //倒数据
    23. while(!STEmpty(&(obj->pushst)))
    24. {
    25. int top=STTop(&(obj->pushst));
    26. STPush(&(obj->popst),top);
    27. STPop(&(obj->pushst));
    28. }
    29. }
    30. return STTop(&(obj->popst));
    31. }
    32. bool myQueueEmpty(MyQueue* obj) {
    33. return STEmpty(&(obj->pushst))&&STEmpty(&(obj->popst));
    34. }
    35. void myQueueFree(MyQueue* obj) {
    36. STDestroy(&(obj->pushst));
    37. STDestroy(&(obj->popst));
    38. free(obj);
    39. }

    四.设计循环队列

    1.问题描述

    OJ链接:. - 力扣(LeetCode)

    2.问题详解

    循环队列是一种空间大小固定,空间能够反复利用的队列,这个队列也可以采用链表和数组来实现。

    首先分析链表,用链表的优势在于它可以实现循环,但是创建链表要比队列大小多开一个节点,避免假溢出问题,因为head和tail初始化为头节点,tail实际表示的是最后一个数据指向的下一个节点。而链表的难点在于尾节点的获取,如果是单链表就需要从头开始遍历节点,增加了时间复杂度,或者再定义一个指针指向tail前一个节点,如果是双向链表恰好可以解决这个问题。

    如果用数组来实现的话,当head和tail都初始化为0,我们来看看这个式子head==tail表示的是什么?好像表示队列满了又好像队列为空?这意思不矛盾了吗!对于解决这个有俩个办法,一是再定义一个size记录队列中元素的个数和队列空间大小进行比较,二是再开一个空间,当head==tail为空,当(tail+1)%(k+1)==head为满,而在本题,采用的是第二种解法,额外开辟一个空间。

    注意:

    插入和删除数据时,要确保这个队列是循环的,我们一定要tail=tail%(k+1),同时,在获取队尾元素时,也要时刻注意tail,确保能够形成一个循环队列

    3.代码

    1. typedef struct {
    2. int* a;
    3. int head;//指向头
    4. int tail;//指向尾的下一个位置
    5. int k;
    6. } MyCircularQueue;
    7. bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    8. return obj->head==obj->tail;
    9. }
    10. bool myCircularQueueIsFull(MyCircularQueue* obj) {
    11. return (obj->tail+1)%(obj->k+1)==obj->head;
    12. }
    13. MyCircularQueue* myCircularQueueCreate(int k) {
    14. MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    15. //多开一个解决假溢出
    16. obj->a=(int*)malloc(sizeof(int)*(k+1));
    17. obj->head=0;
    18. obj->tail=0;
    19. obj->k=k;
    20. return obj;
    21. }
    22. bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    23. if(myCircularQueueIsFull(obj))
    24. {
    25. return false;
    26. }
    27. obj->a[obj->tail]=value;
    28. obj->tail++;
    29. obj->tail%=(obj->k+1);
    30. return true;
    31. }
    32. bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    33. if(myCircularQueueIsEmpty(obj))
    34. return false;
    35. obj->head++;
    36. obj->head%=(obj->k+1);
    37. return true;
    38. }
    39. int myCircularQueueFront(MyCircularQueue* obj) {
    40. if(myCircularQueueIsEmpty(obj))
    41. return -1;
    42. else
    43. return obj->a[obj->head];
    44. }
    45. int myCircularQueueRear(MyCircularQueue* obj) {
    46. if(myCircularQueueIsEmpty(obj))
    47. return -1;
    48. int real=obj->tail==0?obj->k:obj->tail-1;
    49. return obj->a[real];
    50. //return obj->a[(obj->tail+obj->k)%(obj->k+1)];
    51. }
    52. void myCircularQueueFree(MyCircularQueue* obj) {
    53. free(obj->a);
    54. free(obj);
    55. }

  • 相关阅读:
    简历里写了会代码,却依然过不了面试这一关
    用Unity重现《空洞骑士》的苦痛之路(1)——人物控制篇
    数据库数据恢复—Oracle数据库报错ORA-01110错误的数据恢复案例
    【DW组队学习—动手学数据分析】第二章:第一节数据清洗及特征处理-课程学习
    用这个免费CDN,治愈WordPress网站加载缓慢的大难题
    1688获得商品详情接口调用展示
    vue3 父子组件之间的方法调用
    【ChatGPT】如何修复access denied you do not have access to chat.openai.com
    建模杂谈系列157 关于“如何降低工作成本“的思考
    I.MX RT1176笔记(9)-- 程序异常追踪(CmBacktrace 和 segger rtt)
  • 原文地址:https://blog.csdn.net/2303_80552231/article/details/139398273