数据结构-栈队列OJ题

server/2025/1/18 9:01:35/

文章目录

  • 一、有效的括号
  • 二、用队列实现栈
  • 三、用栈实现队列
  • 四、设计循环队列

一、有效的括号

(链接:ValidParentheses)
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述这道题用栈这种数据结构解决最好,因为栈有后进先出的性质。简单分析一下这道题:所给字符串不是空的也就是一定至少存在一个括号,而且字符串中只能含有’(‘、’)‘、’{‘、’}‘、’[‘、’]'这六种字符。我们要根据题目给的实例,运用栈来解决这道题:
在这里插入图片描述在这里插入图片描述

bool isValid(char* s) 
{ST st;STInit(&st);while (*s){if (*s == '('|| *s == '['|| *s == '{'){STPush(&st, *s);}else{if (STEmpty(&st))return false;char top = STTop(&st);STPop(&st);if ((*s == ')' && top != '(')|| (*s == ']' && top != '[')|| (*s == '}' && top != '{')){return false;}}s++;}char ret = STEmpty(&st);STDestroy(&st);return ret;
}

在这里插入图片描述

二、用队列实现栈

(链接:ImplementStackUsingQueues)
在这里插入图片描述在这里插入图片描述这道题是要用队列来实现栈,首先我们要了解到队列的性质是先进先出,而栈的性质是后进先出。那么就需要两个队列才能实现栈:
在这里插入图片描述在这里插入图片描述

//用两个队列来实现栈
typedef struct 
{Queue q1;Queue q2;
} MyStack;MyStack* myStackCreate() 
{//为自主实现的栈结构动态开辟空间MyStack* st = (MyStack*)malloc(sizeof(MyStack));if (st == NULL){exit(1);}//初始化两个队列QueueInit(&st->q1);QueueInit(&st->q2);return st;
}void myStackPush(MyStack* obj, int x) 
{assert(obj != NULL);if (!QueueEmpty(&obj->q1)){QueuePush(&obj->q1, x);}else{QueuePush(&obj->q2, x);}
}int myStackPop(MyStack* obj) 
{assert(obj != NULL);Queue* QEmpty = &obj->q1;Queue* QNonEmpty = &obj->q2;if (!QueueEmpty(&obj->q1)){QEmpty = &obj->q2;QNonEmpty = &obj->q1;}//将QNonEmpty队列的前size-1个数据倒入QEmpty队列中while (QueueSize(QNonEmpty) > 1){QueuePush(QEmpty, QueueFront(QNonEmpty));QueuePop(QNonEmpty);}//记录下QNonEmpty队列的队头元素int top = QueueFront(QNonEmpty);//移除并返回队头元素(即模拟返回栈的栈顶元素)QueuePop(QNonEmpty);return top;
}int myStackTop(MyStack* obj) 
{assert(obj != NULL);if (!QueueEmpty(&obj->q1)){return QueueBack(&obj->q1);}else{return QueueBack(&obj->q2);}
}bool myStackEmpty(MyStack* obj) 
{assert(obj != NULL);return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}void myStackFree(MyStack* obj) 
{assert(obj != NULL);QueueDestroy(&obj->q1);QueueDestroy(&obj->q2);free(obj);
}

注意:上面是用两个队列来实现栈的数据结构,MyStack是匿名结构struct 的重命名。MyStack的成员不是队列指针,而是队列结构体变量,所以在myStackCreake中为MyStack动态开辟的空间大小是为两个队列结构体变量开辟的,这样做的好处是可以取q1和q2的地址,方便我们直接可以修改队列q1和q2。当然也可以在MyStack中定义两个队列指针,但这样做的话就要单独为q1和q2动态开辟空间:

typedef struct 
{Queue* q1;Queue* q2;
} MyStack;MyStack* myStackCreate() 
{MyStack* st = (MyStack*)malloc(sizeof(MyStack));if (st == NULL){exit(1);}st->q1 = (Queue*)malloc(sizeof(Queue));st->q2 = (Queue*)malloc(sizeof(Queue));QueueInit(st->q1);QueueInit(st->q2);return st;
}

第一种定义方式:
在这里插入图片描述第二种定义方式:
在这里插入图片描述注意:如果采用上面这种方式定义MyStack,则后面的函数都要做出相应的调整。在最后销毁自定义的栈时要先释放MyStack中的两个队列q1和q2,最后才销毁obj。因为q1和q2在后面通过QueuePush动态开辟了空间,则要通过obj才能找到q1和q2。所以要先销毁q1和q2,最后才释放obj。当然在做这题的基础上,要先实现一个队列才行。

三、用栈实现队列

(链接:ImplementQueueUsingStacks)
在这里插入图片描述在这里插入图片描述这道题就是上面一道题的兄弟题,要求我们用栈来实现队列。
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

//用两个栈来实现队列
typedef struct
{ST pushst;ST popst;
}MyQueue;MyQueue* myQueueCreate()
{MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));if (q == NULL){perror("malloc fail!");return NULL;}STInit(&q->pushst);STInit(&q->popst);return q;
}void myQueuePush(MyQueue* obj, int x)
{assert(obj != NULL);STPush(&obj->pushst, x);
}int myQueuePeek(MyQueue* obj)
{assert(obj != NULL);//如果popst里没有数据,则从pushst里倒数据到popst中if (STEmpty(&obj->popst)){while (!STEmpty(&obj->pushst)){STPush(&obj->popst, STTop(&obj->pushst));STPop(&obj->pushst);}}//返回popst的栈顶元素(即模拟队列出队头数据)return STTop(&obj->popst);
}int myQueuePop(MyQueue* obj)
{assert(obj != NULL);int front = myQueuePeek(obj);STPop(&obj->popst);return front;
}bool myQueueEmpty(MyQueue* obj)
{assert(obj != NULL);return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}void myQueueFree(MyQueue* obj)
{assert(obj != NULL);STDestroy(&obj->pushst);STDestroy(&obj->popst);free(obj);
}

与栈实现队列很相似,这里需要借助两个栈相互倒数据才能实现队列。注意:上面是先实现myQueuePeek函数,即返回队头的数据但不从队头出数据。而后的myQueuePop函数正好可以调用myQueuePeek函数来获取队头的数据,但myQueuePop函数不仅会返回队头数据,而且还会将队头的数据出队。

四、设计循环队列

(链接:DesignCircularQueue)
在这里插入图片描述在这里插入图片描述实际中还有一种特殊的队列叫循环队列,环形队列首尾相连成环,环形队列可以使用数组实现,也可以使用循环链表实现:
在这里插入图片描述💡思考:队列满的情况下,为什么Q.rear 不存储数据?
答:为了能使用 Q.rear == Q.front 来区别是队空还是队满,我们常常认为出现左图时的情况为队空,而出现右图的情况是队满。即: rear+1==front就表示队满。试想:如果(b)图中Q.rear位置再存储一个数据a6进去,那么Q.rear就会指向a1,此时Q.front也指向a1;由(a)图中一开始Q.front == Q.rear就表示队列为空,那么此时Q.front也等于Q.rear,这就矛盾了。所以循环队列中要留一个位置用来表示队满。

这里我们用数组来实现循环队列,但是队列就有一个问题:数组不像循环链表一样有首尾指针相连,但是我们选择数组是有一定好处的。当然链表也是可以实现循环队列的,要具体问题具体分析。那如何在数组中实现循环队列呢?看下图:
在这里插入图片描述当数组中rear到达下标为5的位置时说明队列满了,那么就要删除数据留出空间才能继续存储数据;删除一个数据就直接让front+1即可,因为是模拟队列的先进先出,所以要从队头出数据。此时再入数据就要从rear的位置入,问题是入了数据后,rear+1就出了数组的下标范围,我们应该让rear回到0下标的位置才能真正实现循环队列:(注意:k是题目要求存储的元素个数)
在这里插入图片描述当循环队列满了就要出数据后才能有空间继续入数据:
在这里插入图片描述出数据了此时队列就不满了,那就可以继续往里入数据了,问题是要怎么让rear回到下标为0的位置?

答案是:🍓rear = (rear+1)%(k+1)🍓
这是一个很妙的公式,通过这个公式就可以解决让rear回到0下标处的问题,让队列变成循环队列。

在这里插入图片描述

typedef struct 
{int* arr;int front;int rear;int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) 
{MyCircularQueue* q = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));if (q == NULL){perror("malloc fail!");return NULL;}q->arr = (int*)malloc(sizeof(int)*(k + 1));if (q->arr == NULL){perror("Arr malloc fail!");return NULL;}q->front = q->rear = 0;q->k = k;return q;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{assert(obj != NULL);return obj->front == obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) 
{assert(obj != NULL);return (obj->rear + 1) % (obj->k + 1) == obj->front;
}bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{assert(obj != NULL);if (myCircularQueueIsFull(obj)){return false;}obj->arr[obj->rear] = value;obj->rear = (obj->rear + 1) % (obj->k + 1);return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{assert(obj != NULL);if (myCircularQueueIsEmpty(obj)){return false;}obj->front = (obj->front + 1) % (obj->k + 1);return true;
}
int myCircularQueueFront(MyCircularQueue* obj) 
{assert(obj != NULL);if (myCircularQueueIsEmpty(obj)){return -1;}return obj->arr[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) 
{assert(obj != NULL);if (myCircularQueueIsEmpty(obj)){return -1;}return obj->arr[(obj->rear + obj->k) % (obj->k + 1)];
}
void myCircularQueueFree(MyCircularQueue* obj) 
{assert(obj != NULL);free(obj->arr);free(obj);
}

不管是插入元素,还是移除元素都要提前判断队列是否满了或是空的。返回队头或是队尾的元素也是一样的,要提前判断队列是否为空。注意:上面myCircularQueueRear函数是用来返回队尾元素的。如果队列不为空,则队尾元素的下标可以表示为:

🔥(rear + k) % (k + 1)🔥
这也是一个很妙的公式,可以解决rear在下标为0的位置时,则队尾元素是在数组的末尾位置。


http://www.ppmy.cn/server/159314.html

相关文章

C# 线程基础之 线程同步

线程同步的手段很多 lock 是通过内存索引块 0 1 切换 进行互斥的实现 互斥量 信号量 事件消息 其实意思就是 一个 标记量 通过这个标记 来进行类似的互斥手段 具体方式的分析 代码在后 1.互斥量 Mutex 作用 非常类似lock 一个Mutex 名称来代替 lock的引用对象 2.信号量 Semaph…

ASP.Net Identity + IODC 解析ReturnUrl

Identity Ids4 配置 成认证服务 一、创建 Identity身份验证 项目 创建的项目结构中 没有 注册和登录的 控制器和视图 配置数据库地址 》》默认已经生成了Miagratin 直接update-database 二、在Identity项目 配置 IdentityServer4 Nuget 两个包 》》》配置Config 类 usin…

鸿蒙Flutter实战:16-无痛开发指南(适合新手)

本文讲述如何通过 Flutter 开发鸿蒙原生应用。整个过程结合往期文章、实战经验、流程优化,体验丝滑、无痛。 无痛搭建开发环境 为了减少疼痛,这里使用全局唯一的 Flutter 版本开发。高阶用法可以参看往期同系列文章。 硬件准备 一台 Mac,一部…

当设置dialog中有el-table时,并设置el-table区域的滚动,看到el-table中多了一条横线

问题:当设置dialog中有el-table时,并设置el-table区域的滚动,看到el-table中多了一条横线; 原因:el-table有一个before的伪元素作为表格的下边框下,初始的时候已设置,在滚动的时候并没有重新设置…

BGP边界网关协议(Border Gateway Protocol)概念、邻居建立

一、定义 主要用于交换AS之间的可达路由信息,构建AS域间的传播路径,防止路由环路的产生,并在AS级别应用一些路由策略。当前使用的版本是BGP-4。 二、环境 底层以OSPF进行igp互联互通,上层使用BGP协议。 三、基本原理 1、BGP是一…

unity学习16:unity里向量的计算,一些方法等

目录 1 unity里的向量: 2 向量加法 2.1 向量加法的几何意义 2.2向量加法的标量算法 3 向量减法 3.1 向量减法的几何意义 3.2 向量减法的标量算法 4 向量的标量乘法 5 向量之间的乘法要注意是左乘 还是右乘 5.1 注意区别 5.2 向量,矩阵&#x…

Unity3D BEPUphysicsint定点数3D物理引擎使用详解

前言 Unity3D作为一款强大的游戏开发引擎,提供了丰富的功能和工具,助力开发者轻松创建多样化的游戏。而在游戏开发中,物理引擎的作用不可忽视。BEPUphysicsint是一个基于Unity3D的开源3D物理引擎项目,它通过采用定点数计算来实现…

Oracle报错ORA-01078、LRM-00109

虚拟机异常关机后,rac数据库备机无法启动数据库,报错如下 解决方法: 找到如下路径文件 执行: cp init.ora.016202516818 /u01/app/oracle/product/19.3.0/db/dbs/ mv init.ora.016202516818 initplm2.ora 再次进入命令行sqlpl…