数据结构-栈队列OJ题

news/2025/1/18 21:20:07/

文章目录

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

一、有效的括号

(链接: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/news/1564236.html

相关文章

华为手机改ip地址能改定位吗

‌在数字化时代,手机不仅是通讯工具,更是我们日常生活的得力助手。从地图导航到社交媒体,手机定位服务无处不在。然而,有时我们可能出于隐私保护或其他原因,希望更改手机的IP地址,并好奇这是否能同时改变手…

Spring Boot中的自动配置原理是什么

Spring Boot 自动配置原理 Spring Boot 的自动配置机制基于 条件化配置,通过 EnableAutoConfiguration 注解来启用。自动配置的核心原理是 基于类路径和环境条件来推断所需要的配置,Spring Boot 会根据项目中引入的依赖和当前环境来自动装配相关的配置项…

【PCL】Segmentation 模块—— 平面模型分割(Plane model segmentation)

1、简介 PCL(Point Cloud Library)中的平面模型分割(Plane Model Segmentation)是一种从点云数据中提取平面结构的方法。它通过识别点云中符合平面模型的点集,将场景中的平面区域分割出来。 1.1 主要步骤 选择模型&…

【2025 Rust学习 --- 18 IO操作和网络】

输入与输出 Rust 标准库中的输入和输出的特性是围绕 3 个特型组织的,即 Read、 BufRead 和 Write。 实现了 Read 的值具有面向字节的输入方法。它们叫作读取器。实现了 BufRead 的值是缓冲读取器。它是 Read的子特型 ,外加读取文本行等方法。实现了 Wr…

Java连接TDengine和MySQL双数据源

git文件地址:项目首页 - SpringBoot连接TDengine和MySQL双数据源:SpringBoot连接TDengine和MySQL双数据源 - GitCode 1、yml配置 spring:datasource:druid:mysql:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/testusername: roo…

【CSS】 ---- CSS 实现图片随鼠标移动局部放大特效

1. 效果【京东商品放大特效】 2. 实现方法 2.1 JS 实现 创建原图片的盒子,并放入原图片;创建需要放大区域的遮罩块;创建显示放大后图片显示的盒子和盒子内放大的图片;给原图绑定移出移入和鼠标移动的监听事件;移入和…

【华为OD-E卷 - 数组连续和 100分(python、java、c++、js、c)】

【华为OD-E卷 - 数组连续和 100分&#xff08;python、java、c、js、c&#xff09;】 题目 给定一个含有N个正整数的数组, 求出有多少个连续区间&#xff08;包括单个正整数&#xff09;, 它们的和大于等于x 输入描述 第一行两个整数N x&#xff08;0 < N < 100000, …

Centos 宝塔安装

yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh 安装成功界面 宝塔说明文档 https://www.bt.cn/admin/servers#wcu 或者可以注册宝塔账号 1 快速部署 安装docker 之后 2 需要在usr/bin下下载do…