栈与队列及其基础应用

一.栈

1.栈的定义

栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。其结构可以参考羽毛球筒

在现实中我们通常用栈解决对称性问题,如经典的括号匹配问题。

 2.栈的实现

由上结构图可看出,我们用一个指针bottom指向栈底元素,一个指针top指向栈顶元素的下一个位置。实现栈可以用数组。

typedef int STDataType;
typedef struct Stack {STDataType *a;//一个动态创建的数组int top;//栈顶数据的下个位置,也是当前栈内元素个数int capacity;//当前栈的最大数据容量
} ST;

为了实现栈的增删改查等功能,我们可以定义若干接口

//初始化
void StackInit(ST *pst);
//销毁
void StackDestroy(ST *pst);
//入栈
void StackPush(ST *pst, STDataType x);
//出栈
void StackPop(ST *pst);
//获取栈顶元素
STDataType StackTop(ST* pst);
//获取栈是否为空
bool StackEmpty(ST* pst);
//获取栈中元素的个数
int StackSize(ST *pst);

现在对这些接口实现。

#include "Stack.h"
//注意top的指向会影响到他的初始值
//初始化
void StackInit(ST* pst) {assert(pst != NULL);pst->a= NULL;//top指向栈顶元素的下一个空间,即表示当前栈内没有元素pst->top = 0;pst->capacity = 0;
}
//销毁
void StackDestroy(ST* pst) {assert(pst != NULL);free(pst->a);pst->top=pst->capacity=0;
}
//入栈
//先判断当前空间是否足够,不够就动态开辟
//将入栈元素的值x赋予top,更新top位置
void StackPush(ST* pst, STDataType x) {assert(pst != NULL);if (pst->top == pst->capacity) {int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));if (tmp == NULL) {perror("realloc fail");return;}}pst->a[pst->top] = x;pst->top++;
}//出栈
//从栈顶出元素,直接令top--
void StackPop(ST* pst){assert(pst != NULL);pst->top--;
}
//获取栈顶元素
//top为栈顶元素下个位置,返回top-1处值即可
STDataType StackTop(ST* pst) {assert(pst);assert(pst->top != 0);return pst->a[pst->top - 1];
}
//获取栈是否为空
bool StackEmpty(ST* pst) {assert(pst != NULL);return pst->top == 0;
}
//获取栈中元素的个数
int StackSize(ST* pst) {assert(pst != NULL);return pst->top;
}

二.队列

1.队列的定义

队列也是一种特殊的线性表,其只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

在实际应用中我们常用队列解决公平性问题。如排队取号,以及并发编程中锁的竞争分配问题。 

队列由于其队尾插入,队头删除的特性,若使用数组实现会比较困难:无论是插入还是删除,都需要考虑空间大小和数据移动的问题,因此在这里我仅展示用链表实现。因此队列的功能我们仅对链表进行尾插头删即可

声明队列节点结构

typedef int QDataType;typedef struct QueueNode
{struct QueueNode* next;QDataType val;
}QNode;

 为了避免次次寻找队头队尾,直接定义另一个结构存储头尾指针

typedef struct Queue
{QNode* phead;//队头QNode* ptail;//队尾int size;//队列数据数量
}Queue;

要实现的接口

void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);// 队尾插入
void QueuePush(Queue* pq, QDataType x);
// 队头删除
void QueuePop(Queue* pq);// 取队头和队尾的数据
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);

 实现

#include"Queue.h"void QueueInit(Queue* pq)
{assert(pq);pq->phead = NULL;pq->ptail = NULL;pq->size = 0;
}void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->phead;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;pq->size = 0;
}// 队尾插入
//申请节点,插入节点后更新size
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->next = NULL;newnode->val = x;if (pq->ptail == NULL)//若此时队列没有节点{pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}// 队头删除
//存头节点下个位置的节点
//释放头节点位置节点
//更新头节点和size
void QueuePop(Queue* pq)
{assert(pq);assert(pq->size != 0);/*QNode* next = pq->phead->next;free(pq->phead);pq->phead = next;if (pq->phead == NULL)pq->ptail = NULL;*/// 一个节点if (pq->phead->next == NULL){free(pq->phead);pq->phead = pq->ptail = NULL;}else // 多个节点{QNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}pq->size--;
}
//输出队头数据
QDataType QueueFront(Queue* pq)
{assert(pq);assert(pq->phead);return pq->phead->val;
}//输出队尾数据
QDataType QueueBack(Queue* pq)
{assert(pq);assert(pq->ptail);return pq->ptail->val;
}//求当前队列的数据个数
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}//当前队列数据个数0则为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}

 

三.栈和队列的简单应用

1.用双栈实现队列

该问题的核心是:如何用后进先出的结构,实现一个先进先出的结构。这个核心会同时影响到插入和删除的设计逻辑。

不妨先大致分析一下出队的逻辑

 

我们向Stack1中插入数据[1,2,3]。若要进行出队,应该出最先入队的1

然而在Stack1中,1显然位于栈底,想要将其最先出队,不难观察出:

我们只要将Stack1中数据以此出栈到Stack2中即可。

 此时将Stack2的栈顶出栈,就完成了出队的操作。

typedef struct{int*a;int top;int capacity;
}Stack;void stackInit(Stack*pst){assert(pst!=NULL);pst->top=pst->capacity=0;pst->a=NULL;
}void stackPush(Stack*pst,int x){assert(pst!=NULL);if(pst->top==pst->capacity){int newcapacity=pst->capacity==0?4:pst->capacity*2;int *tmp=(int*)realloc(pst->a,newcapacity*sizeof(int));pst->a=tmp;pst->capacity=newcapacity;}pst->a[pst->top]=x;pst->top++;
}void stackPop(Stack*pst){assert(pst!=NULL);pst->top--;}int stackTop(Stack*pst){return pst->a[pst->top-1];
}bool stackEmpty(Stack*pst){return pst->top==0;
}void stackFree(Stack*pst){free(pst->a);pst->a=NULL;
}
//至此都是栈的结构和接口,因为C语言库没有内置栈。。//可以看到这里的队列由双栈实现
typedef struct {Stack stack1;Stack stack2;
} MyQueue;//建队,即将队中的两个栈初始化即可
MyQueue* myQueueCreate() {MyQueue*obj=(MyQueue*)malloc(sizeof(MyQueue));stackInit(&(obj->stack1));stackInit(&(obj->stack2));return obj;
}int myQueuePop(MyQueue* obj){// 当stack2为空时,转移所有元素if(stackEmpty(&obj->stack2)){while(!stackEmpty(&obj->stack1)){int tmp = stackTop(&obj->stack1);stackPush(&obj->stack2, tmp);stackPop(&obj->stack1);}}int x = stackTop(&obj->stack2);stackPop(&obj->stack2);return x;
}
void myQueuePush(MyQueue* obj, int x){stackPush(&obj->stack1,x);
}
int myQueuePeek(MyQueue* obj) {if (stackEmpty(&obj->stack2)) {// 循环转移所有元素while (!stackEmpty(&obj->stack1)) {int tmp = stackTop(&obj->stack1);stackPush(&obj->stack2, tmp);stackPop(&obj->stack1);}}return stackTop(&obj->stack2); // 正确返回队首元素
}bool myQueueEmpty(MyQueue* obj) {return stackEmpty(&obj->stack1)&&stackEmpty(&obj->stack2);
}void myQueueFree(MyQueue* obj) {stackFree(&obj->stack1);stackFree(&obj->stack2);
}

这段代码就是上面提到要实现出队时,将栈满的元素一个个入栈到栈空的栈中

a.取stack1栈顶元素值

b.将stack1栈顶元素入空栈stack2

c.将当前stack1栈顶元素出栈,更新新的栈顶元素

由此直至stack1变为空栈,stack2栈满,stack1的栈底为stack2的栈顶。

 while(!stackEmpty(&obj->stack1)){int tmp = stackTop(&obj->stack1);stackPush(&obj->stack2, tmp);stackPop(&obj->stack1);}

 

然而上述代码存在一定的问题。 

int myQueuePop(MyQueue* obj){// 当stack2为空时,转移所有元素if(stackEmpty(&obj->stack2)){while(!stackEmpty(&obj->stack1)){int tmp = stackTop(&obj->stack1);stackPush(&obj->stack2, tmp);stackPop(&obj->stack1);}}int x = stackTop(&obj->stack2);stackPop(&obj->stack2);return x;
}
void myQueuePush(MyQueue* obj, int x){stackPush(&obj->stack1,x);
}
int myQueuePeek(MyQueue* obj) {if (stackEmpty(&obj->stack2)) {// 循环转移所有元素while (!stackEmpty(&obj->stack1)) {int tmp = stackTop(&obj->stack1);stackPush(&obj->stack2, tmp);stackPop(&obj->stack1);}}return stackTop(&obj->stack2); // 正确返回队首元素
}

在进行转移元素的操作时,我们默认向空栈中转移。在初次创建栈时空栈即为Stack2(因为上述接口中首先向Stack1插入元素),但若测试用例中对同一个myQueue进行出队时,此时的空栈又变成了Stack1。这里建议使用if-else判断空栈或者用假设法判定空栈,否则可能无法通过某些测试用例。

2.用两个队列实现栈

 

这题的思想其实与第一个完全一致。在这里直接放出代码,并解决以上提到的问题 

#define LEN 20
typedef struct queue {int *data;int head;int rear;int size;
} Queue;typedef struct {Queue *queue1, *queue2;
} MyStack;Queue *initQueue(int k) {Queue *obj = (Queue *)malloc(sizeof(Queue));obj->data = (int *)malloc(k * sizeof(int));obj->head = -1;obj->rear = -1;obj->size = k;return obj;
}void enQueue(Queue *obj, int e) {if (obj->head == -1) {obj->head = 0;}obj->rear = (obj->rear + 1) % obj->size;obj->data[obj->rear] = e;
}int deQueue(Queue *obj) {int a = obj->data[obj->head];if (obj->head == obj->rear) {obj->rear = -1;obj->head = -1;return a;}obj->head = (obj->head + 1) % obj->size;return a;
}int isEmpty(Queue *obj) {return obj->head == -1;
}MyStack *myStackCreate() {MyStack *obj = (MyStack *)malloc(sizeof(MyStack));obj->queue1 = initQueue(LEN);obj->queue2 = initQueue(LEN);return obj;
}void myStackPush(MyStack *obj, int x) {if (isEmpty(obj->queue1)) {enQueue(obj->queue2, x);} else {enQueue(obj->queue1, x);}
}int myStackPop(MyStack *obj) {if (isEmpty(obj->queue1)) {while (obj->queue2->head != obj->queue2->rear) {enQueue(obj->queue1, deQueue(obj->queue2));}return deQueue(obj->queue2);}while (obj->queue1->head != obj->queue1->rear) {enQueue(obj->queue2, deQueue(obj->queue1));}return deQueue(obj->queue1);
}int myStackTop(MyStack *obj) {if (isEmpty(obj->queue1)) {return obj->queue2->data[obj->queue2->rear];}return obj->queue1->data[obj->queue1->rear];
}bool myStackEmpty(MyStack *obj) {if (obj->queue1->head == -1 && obj->queue2->head == -1) {return true;}return false;
}void myStackFree(MyStack *obj) {free(obj->queue1->data);obj->queue1->data = NULL;free(obj->queue1);obj->queue1 = NULL;free(obj->queue2->data);obj->queue2->data = NULL;free(obj->queue2);obj->queue2 = NULL;free(obj);obj = NULL;
}

3.设计循环队列

循环队列我们可以理解为一个座位有限的图书馆,当座位满时就不能再坐人,但是一旦有人离开,就可以再来一个人坐在那个空位。

我们可以直接用笔画一画这个结构,在这里rear为队尾元素的下一个位置。假设这是一个可以存放五个数据的循环队列。先对这个队列进行入队。

 

接着开始出队 (注意队列从队尾入队,队头出队)

其实从刚刚插入的图我们就能发现一个比较大的问题,当栈空和栈满时的front指针和rear指针指向的都是同一个位置。。。他有一个专门的称为,叫假溢出

为了解决假溢出,可以对要存储k个数据的循环队列,初始化k+1个空间。

若此时的k为4,开辟k+1个元素。此时的队空队满如下:

直接来做这道题 

typedef struct {int front;//指向队头int rear;//指向队尾的下一个元素int capacity;//空间容量int *elements;//用数组实现循环队列
} MyCircularQueue;//建立存k个数据的循环队列,这里开辟的空间为k+1
MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue *obj = (MyCircularQueue *)malloc(sizeof(MyCircularQueue));obj->capacity = k + 1;obj->rear = obj->front = 0;obj->elements = (int *)malloc(sizeof(int) * obj->capacity);return obj;
}//入队,成功return true
//若此时队满,直接return false
//这时判断队满的条件是rear的下一个元素为队头
//注意这里的取模操作,是为了消除rear+1大于capacity,并且也是让rear和front循环起来的基本操作
//更新rear位置
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if ((obj->rear + 1) % obj->capacity == obj->front) {return false;}obj->elements[obj->rear] = value;obj->rear = (obj->rear + 1) % obj->capacity;return true;
}//出队,成功return true
//若此时队空,直接return false
//更新front位置
bool myCircularQueueDeQueue(MyCircularQueue* obj) {if (obj->rear == obj->front) {return false;}obj->front = (obj->front + 1) % obj->capacity;return true;
}//求队头元素
//若队空,return
//返回front处值
int myCircularQueueFront(MyCircularQueue* obj) {if (obj->rear == obj->front) {return -1;}return obj->elements[obj->front];
}//求队尾元素
//若队空,return
//rear为队尾元素的下一个,因此返回rear-1处位置即可
//但此时rear恰好为0,为让其正确指向队尾元素,-1加上capacity再模capacity
int myCircularQueueRear(MyCircularQueue* obj) {if (obj->rear == obj->front) {return -1;}return obj->elements[(obj->rear - 1 + obj->capacity) % obj->capacity];
}bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->rear == obj->front;
}bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->rear + 1) % obj->capacity == obj->front;
}void myCircularQueueFree(MyCircularQueue* obj) {free(obj->elements);free(obj);
}

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

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

相关文章

openEuler-22.03-LTS-SP3 编译安装 Greenplum-db 6.20.0

openEuler-22.03-LTS-SP3 编译安装 Greenplum-db 6.20.0 1、配置 yum 华为源2、安装依赖3、源码安装 openssl 1.0.1u3.1、openssl 1.1.1 降级到 openssl 1.0.1 4、源码安装 python 2.75、使用 pip3 安装 Python 相关依赖6、编译安装 Greenplum-db 6.20.06.1、修改配置6.2、基于…

机器学习02——概要

一、简介 机器学习是一门在没有明确编程的情况下让计算机学习的科学。 监督学习是有目标的,输入数据对应明确的输出;无监督学习则是“探索”型的,模型的目标是从数据中发现潜在的模式或结构,而不需要预先知道标签。 二、机器学…

swift-08-属性、汇编分析inout本质

一、Swift中跟实例相关的属性可以分为2大类 1.1 存储属性( Stored Property) 类似于成员变量这个概念 存储在实例的内存中 结构体、类可以定义存储属性 枚举不可以定义存储属性(因为枚举只存储关联值和case) 1.2 计算属性&am…

【HarmonyOS Next之旅】DevEco Studio使用指南(十二)

目录 1 -> Code Linter代码检查 2 -> 配置代码检查规则 3 -> 查看/处理代码检查结果 1 -> Code Linter代码检查 Code Linter针对ArkTS/TS代码进行最佳实践/编程规范方面的检查。 可根据扫描结果中告警提示手工修复代码缺陷,或者执行一键式自动修复…

前端vue项目打包成桌面端exe应用

主要 使用 Electron将 vue项目打包为 exe 1.首先下载Electron git clone https://github.com/electron/electron-quick-start cd electron-quick-start npm install安装完依赖之后 npm start运行成功 注意:如果你的项目使用了VueRouter,那么切记&…

基于springcloud的“微服务架构的巡游出租管理平台”的设计与实现(源码+数据库+文档+PPT)

基于springcloud的“微服务架构的巡游出租管理平台”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:springcloud 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 E-R实体关系图…

新一代达梦官方管理工具SQLark:可视化建表操作指南

在数据库管理工作中,新建表是一项基础且频繁的操作。SQLark 的可视化建表功能为我们提供了一种高效、便捷且丝滑流畅的建表新体验。一起来了解下吧。 SQLark 官方下载链接:www.sqlark.com 新建表作为常见的功能,相比其他管理工具,…

Scala相关知识学习总结6

1、集合计算高级函数说明 - 过滤:遍历集合,提取满足特定条件的元素组成新集合。 - 转化/映射(map):将集合里的每个元素应用到指定函数进行转换。 - 扁平化:文档未详细阐述其具体含义和操作。 - 扁平化映射&…

pandas.DataFrame.dtypes--查看和验证 DataFrame 列的数据类型!

查看每列的数据类型,方便分析是否需要数据类型转换 property DataFrame.dtypes[source] Return the dtypes in the DataFrame. This returns a Series with the data type of each column. The result’s index is the original DataFrame’s columns. Columns with…

计算机中的单位

在计算机科学中,单位用于衡量数据存储、内存、数据传输速率等。以下是一些常见的计算机单位及其含义: ### **1. 数据存储单位** 数据存储单位用于衡量计算机存储设备(如硬盘、内存、闪存等)的容量。 | 单位 | 符号 | 含义…

Spring Boot 自定义配置类(包含字符串、数字、布尔、小数、集合、映射、嵌套对象)实现步骤及示例

Spring Boot 自定义配置类实现步骤及示例 步骤说明 创建配置类:定义一个 POJO 类,使用 ConfigurationProperties 注解指定配置前缀。启用配置绑定:在启动类或配置类上添加 EnableConfigurationProperties 注解。配置文件写法:在 …

Linux: 线程控制

目录 一 前言 二 线程控制 1. POSIX线程库(原生线程库) 2. 创建线程 2.1 pthread_create 2.2pthread_self()获取线程id 3.线程终止 3.1.return 方式 3.2 pthread_exit 4 线程等待 三 理解线程tid 一 前言 在上一篇文章中我们已经学习了线程的概念,线程的创…

避开养生误区,拥抱健康生活

在追求健康的道路上,我们常常会陷入一些养生误区,不仅无法达到预期效果,还可能损害身体健康。只有拨云见日,认清这些误区,采取正确的养生方式,才能真正拥抱健康生活。​ 很多人认为,保健品吃得…

<数据集>苹果识别数据集<目标检测>

数据集下载链接https://download.csdn.net/download/qq_53332949/90585216数据集格式:VOCYOLO格式 图片数量:535张 标注数量(xml文件个数):535 标注数量(txt文件个数):535 标注类别数:2 标注类别名称:…

【补题】P10424 [蓝桥杯 2024 省 B] 好数(数位dp)

题意: 一个整数如果按从低位到高位的顺序,奇数位(个位、百位、万位……)上的数字是奇数,偶数位(十位、千位、十万位……)上的数字是偶数,我们就称之为“好数”。 给定一个正整数 N…

分布式存储怎样提高服务器数据的安全性?

分布式存储是一种计算机数据存储架构,主要是将数据信息分布存储在多台计算机或者是服务器上,以此来实现高可靠性、可扩展性和高性能,让每个计算机或服务器可以通过网络连接相互通信和协作。 分布式存储系统会定期对重要的数据信息进行完整性检…

数字IC后端培训教程系列之PR Innovus工具写出Calibre LVS用的Netlist详细步骤

在数字IC后端设计实现chipfinish阶段需要写出很多数据,比如netlist,def,gds,lib和lef等文件。 今天给大家分享PR工具Innovus写出Calibre物理验证LVS要用的netlist的详细步骤。 手把手教你debug解决物理验证Calibre LVS错误 1&a…

TrueNAS scale(23.10) Restful API接口调用

背景 本文主要讲解开源的NAS系统--TrueNAS的二次开发。 TrueNAS scale安装 网上能找到很多类似的文章,本文就不介绍了,这里给一个视频博主的传送门: 司波图 TrueNAS scale Resful API 接口 官网的 Resful API地址:TrueNAS REST…

卡尔曼滤波器浅聊

0 前言: 卡尔曼滤波属于算法领域的,所以一些基本的数学概念是必须了解的 涉及到的数学基本概念 概念数学符号含义数学期望(Expected Value)E描述随机变量平均取值的最核心概念概率(Probability)P(X= x i x_i xi​)随机变量 X 取特定值 x i x_i xi​的概率方差(Varian…

1ll C++

在C++中,1ll 表示 long long 类型的整数常量1。这里的 ll 是 long long 的缩写。这种写法主要用于以下几个方面: 1. 为什么需要 1ll? 在您的代码中,1ll 主要用于 防止整数溢出 和 确保正确的类型转换: cpp 复制 p = 1ll * p * i % MOD; f[i + 1] = 1ll * i * (i + 1) …