栈和队列特别篇:栈和队列的经典算法问题

图均为手绘,代码基于vs2022实现

系列文章目录

数据结构初探: 顺序表
数据结构初探:链表之单链表篇
数据结构初探:链表之双向链表篇
链表特别篇:链表经典算法问题
数据结构:栈篇
数据结构:队列篇


文章目录

  • 系列文章目录
  • 前言
  • 一.有效的括号(leetcode 20)
  • 二.用队列实现栈(leetcode 225)
  • 三.用栈实现队列(leetcode 232)
  • 四.设计循环队列(leetcode 622)
  • 五.总结


前言

  • 栈和队列作为基础数据结构,是算法设计中的重要基石。它们在操作系统、编译器设计、网络协议等领域有着广泛应用。本文将通过C语言实现经典算法问题,帮助读者深入理解它们的底层原理和应用技巧。学习这些内容不仅能提升算法能力,还能加深对内存管理和数据结构设计的理解。

一.有效的括号(leetcode 20)

让我们先来看看题目:
在这里插入图片描述
让我们来理清楚思路:

  • 首先,我们可以用栈来实现匹配

1.我们将左括号入栈;
2.在遍历中找到右括号,进行匹配;

  • 在这个过程中,我们需要手撕一个栈的代码出来,加在题目代码之前,如果发现还不太熟练,可以再去我的这篇博客中熟悉熟悉—> 数据结构:栈篇
  • 我们还是上代码实际感受一下吧(考虑到篇幅有限,已省略栈的代码):
//注意这里是字符,栈的代码中要将int改为char
//typedef char STDataType;
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;//直接返回false,表示找不到匹配的括号}//因为题目要求的是按顺序一一对应;char top=STTop(&st);//此时记录下栈顶元素STPop(&st);//再pop,表示出栈操作//匹配括号逻辑结构//大家可以自行分析;//即如果右括号没有在栈顶找到对应的左括号,则错误if((*s == ')' && top != '(')||(*s == ']' && top != '[')||(*s == '}' && top != '{')){STDestroy(&st);//所以销毁,防止内存泄漏return false;//直接返回false,表示找不到匹配的括号}}s++;//更新循环条件}bool ret=STEmpty(&st);//记录是否找到对应括号的状态STDestroy(&st);//销毁,防止内存泄漏return ret;//返回对应状态
}

在这里插入图片描述

学完后,自己多多练习,自行手撕,理清逻辑即可;

二.用队列实现栈(leetcode 225)

让我们先来看看题目:
在这里插入图片描述
让我们来理清楚思路:
我们需要用到两个队列来实现栈:

  • 一个用来当作入栈队列
  • 一个用来当作出栈队列
  • 我们还要注意其LIFO的性质,和随进随出的特点
    1.我们需要保持一个队列为空,一个队列存数据
    2.出栈,把前面的数据导入空队列
    3.如此两班来回倒,就可以实现栈的特性;
    逻辑如图:
    在这里插入图片描述
    在这个过程中,我们需要手撕队列的代码出来,加在题目代码之前,如果发现还不太熟练,可以再去我的这篇博客中熟悉熟悉—> 数据结构:队列篇
    我们来实战一下:
//创建两个队列的结构体
typedef struct {Queue q1;Queue q2;
} MyStack;
//我们需要动态开辟出空间来实现mystack
MyStack* myStackCreate() {MyStack* pst=(MyStack*)malloc(sizeof(MyStack));if(pst == NULL){perror("malloc fail");return NULL;}QueueInit(&pst->q1);//对两个队列初始化QueueInit(&pst->q2);return pst;//返回初始化后的我们的mystack
}
//来实现栈的插入
void myStackPush(MyStack* obj, int x) {if(!QueueEmpty(&obj->q1))//如果q1不为空就插入到q1里{//反正有一个队列里面是空的,我们不能插入,一旦插入就会弄乱整体的逻辑QueuePush(&obj->q1,x);//可以参看上文图进行理解}else//如果q2不为空就插入到q2里{//两个都为空就随便选一个QueuePush(&obj->q2,x);}
}
//实现栈的删除,题目要求:移除并返回栈顶元素
int myStackPop(MyStack* obj) {//首先要明确哪个为空,哪个为非空Queue* emptyQ=&obj->q1;//假设q1为空Queue* nonemptyQ=&obj->q2;//q2不为空if(!QueueEmpty(&obj->q1))//验证是否正确{//进入则是不正确emptyQ=&obj->q2;//不正确则交换nonemptyQ=&obj->q1;}while(QueueSize(nonemptyQ) > 1)//设置遍历循环{QueuePush(emptyQ,QueueFront(nonemptyQ));//将非空的前面n-1个挪入空队列QueuePop(nonemptyQ);//pop掉已经挪入的那n-1个}//剩下的就是要按照栈的特性LIFO顺序的数据int top=QueueFront(nonemptyQ);//按照题目要求记录下数据QueuePop(nonemptyQ);//popreturn top;//返回数据
}
//返回栈顶元素
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);
}

销毁逻辑示例图:
在这里插入图片描述

在这里插入图片描述

三.用栈实现队列(leetcode 232)

让我们先来看看题目:
在这里插入图片描述
让我们来理清楚思路:
我们需要用到两个栈来实现队列:

  • 一个用来当作入队栈
  • 一个用来当作出队栈
  • 我们还要注意其FIFO的性质,和随进随出的特点
    1.我们需要保持一个入队栈,一个出队栈
    2.出队列,把前面的数据导入空栈
    3.当出队栈不为空时,不能再将出队栈中的数据导入,否则顺序会乱;
    如图:
    在这里插入图片描述
    让我们来感受一下代码:
typedef struct {ST pushst;//入队栈ST popst;//出队栈
} MyQueue;int myQueuePeek(MyQueue* obj);//返回队头元素的函数声明;
//动态开辟
MyQueue* myQueueCreate() {MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));if(obj==NULL){perror("malloc fail");return NULL;}STInit(&obj->pushst);//初始化两个栈STInit(&obj->popst);return obj;//返回模拟队列
}void myQueuePush(MyQueue* obj, int x) {STPush(&obj->pushst,x);//正常往入队栈中直接插入
}
//按题目要求,从队列的开头移除并返回元素
int myQueuePop(MyQueue* obj) {int front=myQueuePeek(obj);//记录队头元素STPop(&obj->popst);//删除return front; //返回元素
}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);//返回栈顶即是队头,可以参看上图;
}
//判空
bool myQueueEmpty(MyQueue* obj) {
//两个栈都为空,才为空return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}
//释放所有动态开辟的空间
void myQueueFree(MyQueue* obj) {STDestroy(&obj->pushst);STDestroy(&obj->popst);free(obj);}

在这里插入图片描述

四.设计循环队列(leetcode 622)

让我们来看看题目:
在这里插入图片描述
我们来理清楚逻辑:
这里我们选择用数组来实现,链表实现也是可以的,但是相对来说,数组实现会更加容易;对于数组是否满了,我们有两种解决办法:

  • 在结构体中加入size来计量;
  • 空出一个位置来提醒判断已满
    让我们来实战:
//定义结构体
typedef struct {int* a;//静态数组int front;//头int rear;//尾int k;//满数据时候的个数
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);//函数声明,防止编译错误
bool myCircularQueueIsFull(MyCircularQueue* obj);
//开出对应大小的空间
MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));if(obj==NULL){perror("malloc fail");return NULL;}obj->a=(int*)malloc(sizeof(int)*(k+1));//多开,防止溢出if(obj->a==NULL){perror("malloc fail");return NULL;}obj->front=obj->rear=0;//指向开头obj->k=k;return obj;
}
//插入
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if(myCircularQueueIsFull(obj))//如果是满的{return false;//返回false,表示不能再插入了}obj->a[obj->rear++] = value;//否则存储值,并且rear++到下一个位置;obj->rear %= (obj->k+1);//对尾取模,比如,1%5==1,3%5==3,如果满了5%5==0//就会循环回到数组开始的下标return true;//返回true,表示还可以插入
}
//删除
bool myCircularQueueDeQueue(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj))//如果是空的{return false;//返回false,表示不能再删除了}obj->front++;//头往下一个位置走,这个就与我们曾经讲过的顺序表里面的删除一样的//只要我们不把它计量在有效个数中就可以删除它obj->front %= (obj->k+1);//对头取模,比如,1%5==1,3%5==3,如果满了5%5==0//就会循环回到数组开始的下标return true;//返回true,表示还可以删除
}
//按题目要求:从队首获取元素。如果队列为空,返回 -1
int myCircularQueueFront(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj))//若为空{return -1;//返回}return obj->a[obj->front];//返回头元素
}
//按题目要求:获取队尾元素。如果队列为空,返回 -1 
int myCircularQueueRear(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return -1;}return obj->a[(obj->rear-1+obj->k+1) % (obj->k+1)];//防止因为rear在下标0的位置--,而发生错误;//通过取模,来造成循环;
}
//简单的判空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->rear == obj->front;
}
//判断满
bool myCircularQueueIsFull(MyCircularQueue* obj) {//通过取模,来造成循环;return (obj->rear+1) % (obj->k+1) == obj->front;//保证rear的下一个是头
}
//释放,相同与前两题,可以自行画图理解;
void myCircularQueueFree(MyCircularQueue* obj) {free(obj->a);free(obj);
}

以上就是我要讲的所有的经典问题,你学会了吗?


五.总结

在本次博客中,我们围绕栈和队列这两种基础数据结构,通过C语言实现经典算法问题,深入探索了它们的底层原理与应用技巧。

  • 首先是“有效的括号”问题,利用栈来匹配括号,左括号入栈,右括号与栈顶元素匹配,这种方式充分展现了栈“后进先出”(LIFO)的特性,在处理具有成对结构的数据时十分有效 ,能够清晰地判断括号是否匹配,避免逻辑混乱。
  • 接着,在“用队列实现栈”和“用栈实现队列”的问题中,分别运用两个队列和两个栈来模拟对方的特性。用队列实现栈时,通过在两个队列间来回转移数据,确保满足栈的LIFO性质;用栈实现队列则是利用两个栈,在入队栈和出队栈间合理转移数据,保证队列“先进先出”(FIFO)的特性。这两个问题不仅锻炼了对数据结构特性的灵活运用,还让我们深入理解了不同数据结构间的转换和模拟方法。
  • 最后,在“设计循环队列”中,使用数组实现循环队列,通过巧妙的取模操作实现队列的循环,同时提供了判空和判满的方法。这种实现方式既高效又简洁,充分利用了数组的连续性和可索引性,加深了我们对队列数据结构的理解和应用能力。

通过解决这些经典算法问题,我们不仅掌握了栈和队列在实际编程中的应用,还提升了算法设计、逻辑思维以及内存管理的能力。希望读者能够通过不断练习,熟练掌握这些知识,在后续的编程学习和实践中更加得心应手,灵活运用栈和队列解决各种复杂的实际问题,为更深入的算法学习和系统开发打下坚实的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/67636.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ios swift画中画技术尝试

继上篇:iOS swift 后台运行应用尝试失败-CSDN博客 为什么想到画中画,起初是看到后台模式里有一个picture in picture,去了解了后发现这个就是小窗口视频播放,方便用户执行多任务。看小窗口视频的同时,可以作其他的事情…

OpenAI推出o3-mini推理模型,首次免费开放,性能超越o1,AIME测试准确率高达87.3%

OpenAI在2025年初推出了一款新的推理模型o3-mini,这款模型标志着公司在提升性能的同时也降低了成本,并且首次向免费用户提供访问权限。o3-mini是OpenAI推理系列中最新、最具成本效益的模型,在科学、数学、编程等领域的性能显著超越了之前的o1…

人生不止于职业发展

0 你的问题,我知道! 工作意义是啥?职业发展在人生啥角色? 1 工作意义 农村人努力学习考上大学,得好工作,为逃离同村同龄人十几岁就工厂打工命运,过不凡人生,实现改命的唯一途径。…

【算法设计与分析】实验3:动态规划—最长公共子序列

目录 一、实验目的 二、实验环境 三、实验内容 四、核心代码 五、记录与处理 六、思考与总结 七、完整报告和成果文件提取链接 一、实验目的 掌握动态规划求解问题的思想;针对不同的问题,会利用动态规划进行设计求解以及时间复杂度分析&#xff0…

动手学图神经网络(3):利用图神经网络进行节点分类 从理论到实践

利用图神经网络进行节点分类:从理论到实践 前言 在之前的学习中,大家对图神经网络有了初步的了解。本次教程将深入探讨如何运用图神经网络(GNNs)来解决节点分类问题。在节点分类任务里,大家往往仅掌握少量节点的真实标签,却要推断出其余所有节点的标签,这属于归纳式学…

单片机串口打印printf函数显示内容(固件库开发)

1.hal_usart.c 文件 #include <stdio.h> #include "hal_usart.h" #include "stm32F10x.h"//**要根据 使用的是哪个串口 对应修改 串口号 eg&#xff1a;USART1** void USART_PUTC(char ch) {/* 等待数据寄存器为空 */while((USART1->SR & …

网关登录校验

网关登录校验 单体架构时我们只需要完成一次用户登录、身份校验&#xff0c;就可以在所有业务中获取到用户信息。而微服务拆分后&#xff0c;每个微服务都独立部署&#xff0c;不再共享数据。也就意味着每个微服务都需要做登录校验&#xff0c;这显然不可取。 鉴权思路分析 …

wxwidgets直接获取系统图标,效果类似QFileIconProvider

目前只做了windows版本&#xff0c;用法类似QFileIconProvider // 头文件 #ifndef WXFILEICONPROVIDER_H #define WXFILEICONPROVIDER_H#include <wx/wx.h> #include <wx/icon.h> #include <wx/image.h> #include <wx/bmpcbox.h> // Include for wxB…

我的创作纪念日——成为创作者的 第365天(1年)

机缘 考研的结果让我感到一阵绝望&#xff0c;就像单片机突然死机一样&#xff0c;所有的努力像是被一场意外的中断指令打乱了逻辑流程。曾经本科时因为竞赛拿了一堆奖&#xff0c;内心充满虚荣心和成就感&#xff0c;总觉得自己是一个“天选之子”&#xff0c;但考研的失利却像…

React 封装高阶组件 做路由权限控制

React 高阶组件是什么 官方解释∶ 高阶组件&#xff08;HOC&#xff09;是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分&#xff0c;它是一种基于 React 的组合特性而形成的设计模式。 高阶组件&#xff08;HOC&#xff09;就是一个函数&…

【玩转全栈】--创建一个自己的vue项目

目录 vue介绍 创建vue项目 vue页面介绍 element-plus组件库 启动项目 vue介绍 Vue.js 是一款轻量级、易于上手的前端 JavaScript 框架&#xff0c;旨在简化用户界面的开发。它采用了响应式数据绑定和组件化的设计理念&#xff0c;使得开发者可以通过声明式的方式轻松管理数据和…

DS并查集(17)

文章目录 前言一、何为并查集&#xff1f;二、并查集的实现&#xff1f;并查集的初始化查找元素所在的集合判断两个元素是否在同一个集合合并两个元素所在的集合获取并查集中集合的个数并查集的路径压缩 三、来两道题练练手&#xff1f;省份的数量等式方程的可满足性 总结 前言…

Appium介绍

在使用不同版本的Appium包进行自动化测试时&#xff0c;出现警告问题可能是由于版本不兼容、配置不正确等原因导致的。下面将详细介绍解决这些问题的步骤&#xff0c;确保模拟器能够正常启动&#xff0c;并能在Appium查看器中同步显示。 1. 环境准备 首先&#xff0c;确保你已…

minimind - 从零开始训练小型语言模型

大语言模型&#xff08;LLM&#xff09;领域&#xff0c;如 GPT、LLaMA、GLM 等&#xff0c;虽然它们效果惊艳&#xff0c; 但动辄10 Bilion庞大的模型参数个人设备显存远不够训练&#xff0c;甚至推理困难。 几乎所有人都不会只满足于用Lora等方案fine-tuing大模型学会一些新的…

【C++动态规划 离散化】1626. 无矛盾的最佳球队|2027

本文涉及知识点 C动态规划 离散化 LeetCode1626. 无矛盾的最佳球队 假设你是球队的经理。对于即将到来的锦标赛&#xff0c;你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 总和 。 然而&#xff0c;球队中的矛盾会限制球员的发挥&#xff0c;所以必须选…

CSS 值和单位详解:从基础到实战

CSS 值和单位详解&#xff1a;从基础到实战 1. 什么是 CSS 的值&#xff1f;示例代码&#xff1a;使用颜色关键字和 RGB 函数 2. 数字、长度和百分比2.1 长度单位绝对长度单位相对长度单位 2.2 百分比 3. 颜色3.1 颜色关键字3.2 十六进制 RGB 值3.3 RGB 和 RGBA 值3.4 HSL 和 H…

Privacy Eraser,电脑隐私的终极清除者

Privacy Eraser 是一款专为保护用户隐私而设计的全能型软件&#xff0c;它不仅能够深度清理计算机中的各类隐私数据&#xff0c;还提供了多种系统优化工具&#xff0c;帮助用户提升设备的整体性能。通过这款软件&#xff0c;用户可以轻松清除浏览器历史记录、缓存文件、Cookie、…

Android 启动流程

一 Bootloader 阶段 在嵌入式系统中&#xff0c;Bootloader的引导过程与传统的PC环境有所不同&#xff0c;主要是因为嵌入式系统的硬件配置和应用场景更加多样化。以下是嵌入式系统中Bootloader被引导的一般流程&#xff1a; 1. 硬件复位 当嵌入式设备上电或复位时&#xff…

【数据结构与算法】AVL树的插入与删除实现详解

文章目录 前言Ⅰ. AVL树的定义Ⅱ. AVL树节点的定义Ⅲ. AVL树的插入Insert一、节点的插入二、插入的旋转① 新节点插入较高左子树的左侧&#xff08;左左&#xff09;&#xff1a;右单旋② 新节点插入较高右子树的右侧&#xff08;右右&#xff09;&#xff1a;左单旋③ 新节点插…

SCRM开发为企业提供全面客户管理解决方案与创新实践分享

内容概要 在当今的商业环境中&#xff0c;客户关系管理&#xff08;CRM&#xff09;变得越来越重要。而SCRM&#xff08;社交客户关系管理&#xff09;作为一种新兴的解决方案&#xff0c;正在帮助企业彻底改变与客户的互动方式。快鲸SCRM是一个引人注目的工具&#xff0c;它通…