数据结构之栈和队列(超详解)

在这里插入图片描述


在这里插入图片描述


文章目录

  • 概念与结构
    • 队列
  • 代码实现
      • 栈是否为空,取栈顶数据、栈的有效个数
    • 队列
      • 入队列
      • 出队列
      • 队列判空,取队头、队尾数据,队列的有效个数
  • 算法题解
    • 有效的括号
    • 用队列实现栈
    • 用栈实现队列
      • 复用
    • 设计循环队列
      • 数组结构实现循环队列
        • 构造、销毁循环队列
        • 判断空和满
        • 入、出队列
        • 取队头、队尾元素

概念与结构

栈:⼀种特殊的线性表,只允许在固定的⼀端进行插入和删除元素操作。数据插入和删除操作
的一端称为栈顶,另⼀端称为栈底。栈中的元素遵守后进先出(或先进后出)的原则

压栈(进栈/压栈/⼊栈):插入数据在栈顶。
出栈:在栈顶出数据。

栈的实现可由数组或链表实现,那么哪种是优解呢?
底层用数组:在进栈时只需要在栈顶(尾部)插入数据,不用进行遍历;
底层用链表:在进栈时链表遍历到尾结点再进行插入数据,需要O(n)的时间复杂度,会有一定的消耗。
因此数组的结构实现更优⼀些。
在这里插入图片描述

队列

概念:只允许在⼀端插⼊数据,在另⼀端删除数据的特殊线性表,队列具有先进先出(后进后出)的原则
入队列:在队尾插入数据。
出队列:在队头删除数据。

同样,队列底层也可由数组或链表实现,哪种是优解呢?
底层用数组:删除数据时需要将后面的数据往前挪,有一定的消耗。
底层用链表:出队列时只需要让指向第一个有效结点的指针指向第二个有效结点,不需要遍历;
因此链表的结构实现更优⼀些。
在这里插入图片描述

代码实现

栈的底层是数组,和前面在实现顺序表的底层结构是一样的。

typedef int STDataType;typedef struct Stack
{STDataType* arr;int top;//栈顶int capacity;
}ST;

由于栈的初始化、销毁、入栈(顺序表尾插)、出栈(顺序表尾删)功能与顺序表功能实现是一样的,就不过多赘述。

//栈的初始化
void STInit(ST* st)
{assert(st);st->arr = NULL;st->top = st->capacity = 0;
}
//栈的销毁
void STDestroy(ST* st)
{assert(st);if (st->arr){free(st->arr);}st->arr = NULL;st->capacity = st->top = 0;
}
//入栈-->栈顶
void STPush(ST* st, STDataType x)
{assert(st);//检测空间是否不足if (st->capacity == st->top){int NewCapacity = st->capacity == 0 ? 4 : 2 * st->capacity;STDataType* tmp = (STDataType*)realloc(st->arr, NewCapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail!");exit(1);}//malloc成功st->arr = tmp;st->capacity = NewCapacity;}st->arr[st->top] = x;st->top++;
}
//出栈帧-->栈顶
void STPop(ST* st)
{assert(st);assert(!STEmpty(st));st->top--;
}

栈是否为空,取栈顶数据、栈的有效个数

栈是否为空,即栈里面是否存在数据,即top是否为0(为0则返回真)。

bool STEmpty(ST* st)
{assert(st);return st->top == 0;
}

取栈顶数据首先得保证栈不为空,其次取top-1的数据即可,栈的有效个数即top。

//取栈顶元素
STDataType STTop(ST* st)
{assert(!STEmpty(st));return st->arr[st->top - 1];
}//获取栈的有效个数
int STSize(ST* st)
{assert(st);return st->top;
}

队列

队列的底层是链表,和前面在实现单链表的底层结构是一样的。
但队列额外定义了指向第一个有效结点的phead指针、指向最后一个有效结点的ptail指针,目的是减少入队列的消耗(不用遍历链表)。
在这里插入图片描述
这里多定义的size变量是为了记录队列的有效个数

typedef int QDataType;
//定义队列结点的结构
typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QueueNode;
//定义队列的结构
typedef struct Queue
{QueueNode* phead;//队头QueueNode* ptail;//队尾int size;
}Queue;

入队列

需要往队尾插入数据,必然涉及申请新节点 --> 在单链表中实现过。

  1. 链表为空,phead和ptail指向申请的新结点;
  2. 链表不为空,ptail指向新结点并成为新结点,并让size++。
    在这里插入图片描述
//往队尾插入数据
void QueuePush(Queue* pq, QDataType x)
{assert(pq);//malloc一个新节点QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc: fail");exit(1);}//malloc成功newnode->data = x;newnode->next = NULL;//判断链表是否为空if (pq->phead == NULL){//链表为空pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}pq->size++;
}

出队列

  1. 当只存在一个结点时,释放后phead和ptail指向为NULL;
  2. 释放尾结点,让ptail指向上一个结点。

在这里插入图片描述

//队头删除数据
void QueuePop(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));//只有一个节点if (pq->phead == pq->ptail){free(pq->phead);pq->phead = pq->ptail = NULL;}else {QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}pq->size--;
}

队列判空,取队头、队尾数据,队列的有效个数

//队列判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}//取队头数据
QDataType QueueFront(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->phead->data;
}//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->ptail->data;
}//队列有效元素个数
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}

算法题解

掌握了栈和队列这两种数据结构后,重点在于算法题上的解答,下面通过四种题型领略栈和队列的魅力。

有效的括号

题型 --> 有效的括号


在这里插入图片描述


根据题目需求判断字符串是否有效,但还存在一些特殊情况如上图所示。那这题思路该从哪里入手呢?用栈的方法实现。

  1. 将s从头开始遍历直到结束,若遍历过程遇到 ‘(’ ‘{’ ‘[’ 则入栈st;
  2. 若遇到 ‘)’ ‘}’ ‘]’ 时则取栈顶比较(前提是栈不为空);
  3. 遍历结束后,需判断栈是否为空,因为若是有效的字符串则栈应为空(如上图特殊处理1)。
    在这里插入图片描述
bool isValid(char* s) {ST st;STInit(&st);char* pi=s;while(*pi!='\0'){if(*pi=='('||*pi=='{'||*pi=='['){//入栈STPush(&st,*pi);}else{//取栈顶,判断if(STEmpty(&st)){return false;}//不为空char top=STTop(&st);if((top=='('&&*pi!=')')||(top=='{'&&*pi!='}')||(top=='['&&*pi!=']')){return false;}STPop(&st);}pi++;}bool ret=STEmpty(&st)?true:false;STDestroy(&st);return ret;
}

用队列实现栈

题型 --> 用队列实现栈


在这里插入图片描述


算法思路

在这里插入图片描述

代码实现
typedef struct {Queue q1;Queue q2;
} MyStack;//初始化
MyStack* myStackCreate() {MyStack* ret=(MyStack*)malloc(sizeof(MyStack));QueueInit(&ret->q1);QueueInit(&ret->q2);return ret;
}//往不为空的队列插入数据
void myStackPush(MyStack* obj, int x) {if(!QueueEmpty(&obj->q1)){QueuePush(&obj->q1,x);}else{QueuePush(&obj->q2,x);}
}int myStackPop(MyStack* obj) {//找空队列和非空队列Queue* emp=&obj->q1;Queue* Unemp=&obj->q2;if(QueueEmpty(&obj->q2)){emp=&obj->q2;Unemp=&obj->q1;}while(QueueSize(Unemp)>1){QueuePush(emp,QueueFront(Unemp));QueuePop(Unemp);}int top=QueueFront(Unemp);QueuePop(Unemp);return 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);obj=NULL;
}

用栈实现队列

题型 --> 用栈实现队列


在这里插入图片描述


算法思路

将STPush栈里的数据全部导入到STPop栈里,并删除STPop栈顶元素。

在这里插入图片描述

代码实现
typedef struct {ST pushST;ST popST;
} MyQueue;MyQueue* myQueueCreate() {MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));STInit(&obj->pushST);STInit(&obj->popST);return obj;
}void myQueuePush(MyQueue* obj, int x) {STPush(&obj->pushST,x);
}int myQueuePop(MyQueue* obj) {if(STEmpty(&obj->popST)){//把pushST中的数据导到popST中来while(!STEmpty(&obj->pushST)){STPush(&obj->popST,STTop(&obj->pushST));STPop(&obj->pushST);}}int top=STTop(&obj->popST);STPop(&obj->popST);return top;
}int myQueuePeek(MyQueue* obj) {if(STEmpty(&obj->popST)){//把pushST中的数据导到popST中来while(!STEmpty(&obj->pushST)){STPush(&obj->popST,STTop(&obj->pushST));STPop(&obj->pushST);}}int top=STTop(&obj->popST);return top;
}bool myQueueEmpty(MyQueue* obj) {return STEmpty(&obj->pushST)&&STEmpty(&obj->popST);
}void myQueueFree(MyQueue* obj) {STDestroy(&obj->pushST);STDestroy(&obj->popST);free(obj);obj=NULL;
}

复用

由于出队列和取队尾数据的代码相似度极高,因此我们可以用复用的方法使代码更简洁,增强代码可维护性。
在这里插入图片描述

设计循环队列

题型 --> 设计循环队列


在这里插入图片描述



1.若底层用链表的结构来实现,则会出现如下情况1和2出现矛盾,此时又该怎样判断循环队列是否为空呢?
因此,底层用链表的结构来实现很难行得通
在这里插入图片描述

数组结构实现循环队列

如果按题目需要多少空间就开多少空间,此时会循环队列空和满的情况下,front和rear的下标都指向同一下标,那该如何判断循环队列是空还是满呢?
在这里插入图片描述


解决方案:给数组多开一块空间
在这里插入图片描述

构造、销毁循环队列
typedef struct {int* arr;int front;int rear;int capacity;
} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue* pq=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));if(pq==NULL){exit(1);}if(pq->arr==NULL){exit(1);}pq->arr=(int*)malloc(sizeof(int)*(k+1));pq->front=pq->rear=0;pq->capacity=k;return pq;
}
判断空和满
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->front==obj->rear;
}bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->rear+1)%(obj->capacity+1)==obj->front;
}
入、出队列

入队列和出队列的时候会出现特例情况,即front和rear是否会越界。
在这里插入图片描述

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if(myCircularQueueIsFull(obj)){return false;}obj->arr[obj->rear++]=value;obj->rear%=obj->capacity+1;return true;
}bool myCircularQueueDeQueue(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return false;}obj->front++;obj->front%=obj->capacity+1;return true;
}
取队头、队尾元素

取队尾元素时,一般返回rear-1下标位置的元素即可。但此题是一个循环队列,因此存在特殊情况需要处理

int myCircularQueueRear(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return -1;}return obj->arr[obj->rear-1];
}

特例情况:若rear–则为-1,取不到队尾元素。
在这里插入图片描述

int myCircularQueueFront(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return -1;}return obj->arr[obj->front];
}int myCircularQueueRear(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return -1;}int prev=obj->rear-1;if(obj->rear==0){prev=obj->capacity;}return obj->arr[prev];
}

在这里插入图片描述


在这里插入图片描述


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

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

相关文章

解析 Oracle 中的 ALL_SYNONYMS 和 ALL_VIEWS 视图:查找同义词与视图的基础操作

目录 前言1. ALL_SYNONYMS 视图2. ALL_VIEWS 视图3. 扩展 前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 1. ALL_SYNONYMS 视图 在 Oracle 数据库中,同义词(Synonym)是对数…

DeepSeek-R1 本地部署教程(超简版)

文章目录 一、DeepSeek相关网站二、DeepSeek-R1硬件要求三、本地部署DeepSeek-R11. 安装Ollama1.1 Windows1.2 Linux1.3 macOS 2. 下载和运行DeepSeek模型3. 列出本地已下载的模型 四、Ollama命令大全五、常见问题解决附:DeepSeek模型资源 一、DeepSeek相关网站 官…

【C语言入门】解锁核心关键字的终极奥秘与实战应用(二)

目录 一、sizeof 1.1. 作用 2.2. 代码示例 二、const 2.1. 作用 2.2. 代码示例 三、signed 和 unsigned 3.1. 作用 3.2. 代码示例 四、struct、union、enum 4.1. struct(结构体) 4.1.1. 作用 4.1.2. 代码示例 4.2. union(联合…

如何确认Linux嵌入式系统的触摸屏对应的是哪个设备文件?如何查看系统中所有的输入设备?输入设备的设备文件有什么特点?

Linux嵌入式系统的输入设备的设备文件有什么特点? 在 Linux 中,所有的输入设备(如键盘、鼠标、触摸屏等)都会被内核识别为 输入事件设备,并在 /dev/input/ 目录下创建相应的 设备文件,通常是: …

ESP32-c3实现获取土壤湿度(ADC模拟量)

1硬件实物图 2引脚定义 3使用说明 4实例代码 // 定义土壤湿度传感器连接的模拟输入引脚 const int soilMoisturePin 2; // 假设连接到GPIO2void setup() {// 初始化串口通信Serial.begin(115200); }void loop() {// 读取土壤湿度传感器的模拟值int sensorValue analogRead…

Hive:窗口函数(1)

窗口函数 窗口函数OVER()用于定义一个窗口,该窗口指定了函数应用的数据范围 对窗口数据进行分区 partition by 必须和over () 一起使用, distribute by经常和sort by 一起使用,可以不和over() 一起使用.DISTRIBUTE BY决定了数据如何分布到不同的Reducer上&#xf…

【react-redux】react-redux中的 useDispatch和useSelector的使用与原理解析

一、useSelector 首先,useSelector的作用是获取redux store中的数据。 下面就是源码,感觉它的定义就是首先是createSelectorHook这个方法先获得到redux的上下文对象。 然后从上下文对象中获取store数据。然后从store中得到选择的数据。 2、useDispatc…

java异常处理——try catch finally

单个异常处理 1.当try里的代码发生了catch里指定类型的异常之后,才会执行catch里的代码,程序正常执行到结尾 2.如果try里的代码发生了非catch指定类型的异常,则会强制停止程序,报错 3.finally修饰的代码一定会执行,除…

传输层协议 UDP 与 TCP

🌈 个人主页:Zfox_ 🔥 系列专栏:Linux 目录 一:🔥 前置复盘🦋 传输层🦋 再谈端口号🦋 端口号范围划分🦋 认识知名端口号 (Well-Know Port Number) 二&#xf…

The Simulation技术浅析(三):数值方法

The Simulation ,通常涉及使用数值方法对物理、工程或金融等领域的问题进行建模和求解。数值方法是解决复杂数学问题的关键技术,常见的数值方法包括 有限差分法(FDM)、有限元法(FEM) 和 蒙特卡洛方法(Monte Carlo Method)。 1. 有限差分法(FDM) 有限差分法是一种用于…

深度学习-98-大语言模型LLM之基于langchain的代理create_react_agent工具

文章目录 1 Agent代理1.1 代理的分类1.2 ReAct和Structured chat2 代理应用ReAct2.1 创建工具2.1.1 嵌入模型2.1.2 创建检索器2.1.3 测试检索结果2.1.4 创建工具列表2.2 初始化大模型2.3 创建Agent2.4 运行Agent3 参考附录1 Agent代理 Agent代理的核心思想是使用语言模型来选择…

小试牛刀,AI技术实现高效地解析和转换多种文档格式

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据、人工智能领域创作者。目前从事python全栈、爬虫和人工智能等相关工作,主要擅长领域有:python…

WPF进阶 | WPF 动画特效揭秘:实现炫酷的界面交互效果

WPF进阶 | WPF 动画特效揭秘:实现炫酷的界面交互效果 前言一、WPF 动画基础概念1.1 什么是 WPF 动画1.2 动画的基本类型1.3 动画的核心元素 二、线性动画详解2.1 DoubleAnimation 的使用2.2 ColorAnimation 实现颜色渐变 三、关键帧动画深入3.1 DoubleAnimationUsin…

自制虚拟机(C/C++)(三、做成标准GUI Windows软件,扩展指令集,直接支持img软盘)

开源地址:VMwork 要使终端不弹出&#xff0c; #pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") 还要实现jmp near 0x01类似的 本次的main.cpp #include <graphics.h> #include <conio.h> #include <windows.h> #includ…

tomcat核心组件及原理概述

目录 1. tomcat概述 1.1 概念 1.2 官网地址 2. 基本使用 2.1下载 3. 整体架构 3.1 核心组件 3.2 从web.xml配置和模块对应角度 3.3 如何处理请求 4. 配置JVM参数 5. 附录 1. tomcat概述 1.1 概念 什么是tomcat Tomcat是一个开源、免费、轻量级的Web服务器。 Tomca…

docker gitlab arm64 版本安装部署

前言&#xff1a; 使用RK3588 部署gitlab 平台作为个人或小型团队办公代码版本使用 1. docker 安装 sudo apt install docker* 2. 获取arm版本的gitlab GitHub - zengxs/gitlab-arm64: GitLab docker image (CE & EE) for arm64 git clone https://github.com/zengxs…

LabVIEW在电机自动化生产线中的实时数据采集与生产过程监控

在电机自动化生产线中&#xff0c;实时数据采集与生产过程监控是确保生产效率和产品质量的重要环节。LabVIEW作为一种强大的图形化编程平台&#xff0c;可以有效实现数据采集、实时监控和自动化控制。详细探讨如何利用LabVIEW实现这一目标&#xff0c;包括硬件选择、软件架构设…

python算法和数据结构刷题[1]:数组、矩阵、字符串

一画图二伪代码三写代码 LeetCode必刷100题&#xff1a;一份来自面试官的算法地图&#xff08;题解持续更新中&#xff09;-CSDN博客 算法通关手册&#xff08;LeetCode&#xff09; | 算法通关手册&#xff08;LeetCode&#xff09; (itcharge.cn) 面试经典 150 题 - 学习计…

【PyTorch】7.自动微分模块:开启神经网络 “进化之门” 的魔法钥匙

目录 1. 梯度基本计算 2. 控制梯度计算 3. 梯度计算注意 4. 小节 个人主页&#xff1a;Icomi 专栏地址&#xff1a;PyTorch入门 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&#xff0c;为构建和训练神经网络提供了高效且灵活…

C++基础day1

前言&#xff1a;谢谢阿秀&#xff0c;指路阿秀的学习笔记 一、基础语法 1.构造和析构: 类的构造函数是一种特殊的函数&#xff0c;在创建一个新的对象时调用。类的析构函数也是一种特殊的函数&#xff0c;在删除所创建的对象时调用。 构造顺序&#xff1a;父类->子类 析…