【数据结构】栈和队列

【数据结构】栈和队列

目录

1.栈

1.1.栈的概念及结构

1.2.栈的实现

2.队列

2.1.队列的概念及结构

2.2.队列的实现

3.运用栈理解一道题

4.使用两个队列实现一个栈


1.栈

1.1.栈的概念及结构

首先,我们来了解一种新的数据结构——栈。栈是一种特殊的线性表,其只允许在固定一端进行插入和删除元素操作。进行数据的插入和删除这一端,称之为栈顶,另一端即栈底。栈中的数据是采取后进先出 LIFO(Last In First Out)的规则。这里有一些更为专业的话术: 

压栈:栈的插入操作(也称为进栈/压栈) 

出栈:栈的删除操作                                                        

栈的导入与导出数据均在栈顶进行。值得注意的是:栈的导出不一定是根据栈的导入时的顺序进行的。

1.2.栈的实现

我们可以使用数组或链表来实现栈,对数组而言,在数组尾部插入数据代价较小,因为其不需要遍历整个数组。下图演示栈的后进先出。

接下来,我们分析栈的实现

Stack.h 
#pragma once #include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<assert.h> typedef int STDataType; typedef struct Stack { STDataType* a; int top;//栈的个数 int capacity;//容量:扩容 }ST; //初始化 void STInit(ST* pst); //销毁 void STDestory(ST* pst); //入栈 void STPush(ST* pst, STDataType x); //出栈 void STPop(ST* pst); //取出栈的数据 STDataType STTop(ST* pst) //判空 bool STEmpty(ST* pst); //获取数据个数 STDataType STSize(ST* pst);
Stack.c

STInit: 对栈进行初始化,该栈使用数组实现,a(数组名),top(指向栈顶位置的下一个位置),capacity(容量)。 

#include"stack.h" void STInit(ST* pst) { assert(pst); pst->a = NULL; //top指向栈顶数据的下一个位置 pst->top = 0; pst->capacity = 0; }

STPush:将数据导入栈中,首先,将空间进行扩容(防止空间不足引起的越界访问),并将扩容后的空间传给数组a,然后,再于新空间进行导入数据的操作,结尾记得改变新栈的个数。 

//入栈 void STPush(ST* pst, STDataType x) { assert(pst); 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 failed\n"); exit(1); } //将扩容后的地址(指针)传递给 pst->a pst->a = tmp; //更新扩容后,容器的大小 pst->capacity = newcapacity; } //将 x 插入栈中 pst->a[pst->top] = x; pst->top++; } 

STPop:导出栈中的数据,直接传数组的实参,将其栈的个数减少一位,便导出一个数据。

//出栈 void STPop(ST* pst) { assert(pst); assert(pst->top > 0); pst->top--; }

STTop:由于top指向栈顶的下一个位置,使用我们解引用top-1位置,也就是栈顶数据。

//取出栈的数据 STDataType STTop(ST* pst) { assert(pst); assert(pst->top > 0); return pst->a[pst->top - 1]; } 

STEmpty: 通过return返回值(bool型)

//判空 bool STEmpty(ST* pst) { assert(pst); return pst->top == 0; }

STSize:top指向栈顶的下一个位置,对应数组的数据个数=下标+1,top即栈的数据个数。 

//获取数据个数 STDataType STSize(ST* pst) { assert(pst); return pst->top; }

STDestroy: 将栈内的数据置空或变为0。

//销毁 void STDestroy(ST* pst) { assert(pst); free(pst->a); pst->a = NULL; pst->top = pst->capacity = 0; }

 以上便是栈的实现全部过程。


2.队列

2.1.队列的概念及结构

队列与栈不同的是,队列是一种只允许一端进行插入数据,另一端进行删除数据的特殊线性表。队列采取的是先进先出 FIFO(First In First Out) 入队列,在队尾上进行插入数据操作,在队头进行删除数据操作。

2.2.队列的实现

接下来,我们使用具体的代码进行队列的实现。

queue.h
#pragma once #include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<assert.h> typedef int QDataType; // 链式结构:表示队列 typedef struct QListNode { struct QListNode* next; QDataType val; }QNode; // 队列的结构 typedef struct Queue { QNode* qhead; QNode* qtail; int size; }Queue; // 初始化队列 void QueueInit(Queue* q); // 队尾入队列 void QueuePush(Queue* q, QDataType data); // 队头出队列 void QueuePop(Queue* q); // 获取队列头部元素 QDataType QueueFront(Queue* q); // 获取队列队尾元素 QDataType QueueBack(Queue* q); // 获取队列中有效元素个数 int QueueSize(Queue* q); // 检测队列是否为空,如果为空返回非零结果,如果非空返回0 int QueueEmpty(Queue* q); // 销毁队列 void QueueDestroy(Queue* q);
queue.c

QueueInit: 首先,我们使用了一个Queue的结构体,声明了qhead和qtail两个链表,现在,我们将其进行初始化。

// 初始化队列 void QueueInit(Queue* q) { assert(q); q->qhead = NULL; q->qtail = NULL; q->size = 0; }

QueuePush:我们可以先开辟链式结构的空间,如果qtail尾有节点,即:先将其next节点的值改为newnode,再将qtail节点与newnode节点相互连接。

// 队尾入队列 void QueuePush(Queue* q, QDataType data) { assert(q); QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc failed\n"); exit(1); } newnode->next = NULL; newnode->val = data; if(q->qtail == NULL) { q->qhead = q->qtail = newnode; } else{ q->qtail->next = newnode; q->qtail = newnode; } q->size++; } 

QueuePop:如果队列只有一个节点即队头,那我们直接将其free,并置为空;若节点大于一个,那我们可以创建一个QNode*的next节点指向队头指向的下一个节点,然后再将队头的节点free,再重新将队头的节点改为next指针指向的新节点。 

// 队头出队列 void QueuePop(Queue* q) { assert(q); assert(q->size!=0); if (q->qhead->next == NULL) { free(q->qhead); q->qhead = q->qtail = NULL; } else { QNode* next = q->qhead->next; free(q->qhead); q->qhead = next; } q->size--; }

以下,获取队头元素、队尾元素、队列中有效元素个数 、判空函数均于栈的实现思想大体一致,在这里就不赘述了。

// 获取队列头部元素 QDataType QueueFront(Queue* q) { assert(q); assert(q->qhead); return q->qhead->val; } // 获取队列队尾元素 QDataType QueueBack(Queue* q) { assert(q); assert(q->qtail); return q->qtail->val; } // 获取队列中有效元素个数 int QueueSize(Queue* q) { assert(q); return q->size; } // 检测队列是否为空,如果为空返回非零结果,如果非空返回0 int QueueEmpty(Queue* q) { assert(q); return q->size == 0; }

QueueDestroy:销毁队列,我们可以采取遍历整个队列的方式,将其中的数据从头至尾进行free。 

// 销毁队列 void QueueDestroy(Queue* q) { assert(q); QNode* cur = q->qhead; while (cur) { QNode* next = cur->next; free(cur); cur = next; } q->qhead = q->qtail = NULL; q->size = 0; }

3.运用栈理解一道题

首先,我们已经实现好了栈,接下来,我们可以直接使用栈的函数

对于这道题,我们应该先开辟一个栈(st),我们可以先将题目给定(s)的 '('  、'['  、 '{' 这三个左括号导入栈(st)中。 然后,我们可以使用STTop取出栈(st)中的数据,进行对栈(s)的左右括号的匹配,如果匹配不成功,则返回false,直到栈(s)为空时,过程结束。代码如下所示:

bool isValid(char* s) { ST st; STInit(&st); while(*s) { if(*s =='(' || *s == '[' || *s == '{') { STPush(&st,*s); //入栈:让字符串s的括号进入栈st中 } else { if(STEmpty(&st)) { STDestory(&st); return false; } char top = STTop(&st);//STtop为取栈中数据的函数 STPop(&st);//pst->top--; //检验不匹配 if((top == '(' && *s != ')') ||(top == '[' && *s != ']') ||(top == '{' && *s != '}')) { STDestory(&st); return false; } } ++s; } bool ret = STEmpty(&st); STDestory(&st); return ret; }

4.使用两个队列实现一个栈

为了加深栈和队列的理解,我们来分析这道题

依照题意,我们创建一个栈(MyStack),包含两个队列(Queue q1\q2) ;

然后,我们需要为新创建的栈开辟新的空间(obj),并将其中的两个队列q1、q2初始化;

设计一个函数myStackPush,用于非空队列尾部添加数据;

在函数myStackPop中,我们将非空队列的size-1个元素移动至空队列中;

然后,使用mytStackTop函数取得栈顶元素;

为了检测我们实现的栈是否为空,我们可以直接使用QueueEmpty返回这两个队列q1、q2;

最后,当我们释放内存的时候,我们应先将这两个队列q1、q2先释放,最后再释放obj这个指向栈(MyStack)的指针。

大致结构如下图所示:

       代码实现见下图分析:

typedef struct { Queue q1; Queue q2;//新创建的栈有两个队列q1,q2 } MyStack; //创建新的栈 MyStack* myStackCreate() { MyStack* obj = (MyStack*)malloc(sizeof(MyStack)); QueueInit(&(obj->q1)); QueueInit(&(obj->q2)); return obj; } //在非空队列尾部添加数据x void myStackPush(MyStack* obj, int x) { if(!QueueEmpty(&(obj->q1))) { QueuePush(&(obj->q1),x); }else{ QueuePush(&(obj->q2),x); } } int myStackPop(MyStack* obj) { //假设法 Queue* empty = &(obj->q1); Queue* nonEmpty = &(obj->q2); if(!QueueEmpty(&(obj->q1))) { nonEmpty = &(obj->q1); empty = &(obj->q2); } while(QueueSize(nonEmpty)>1)//把前size-1个元素导走 { QueuePush(empty,QueueFront(nonEmpty));//将有非空队列的队列头部元素 导入 空队列中 QueuePop(nonEmpty);//然后,删除非空队列的头部元素 } int top = QueueFront(nonEmpty);//再取出非空队列的队头元素 QueuePop(nonEmpty);//将非空元素的最后一个队头导出队列 return top;//此时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));//boolean empty() 如果栈是空的,返回 true ;否则,返回 false } void myStackFree(MyStack* obj) { QueueDestroy(&(obj->q1)); QueueDestroy(&(obj->q2));//参照杭哥图解 free(obj); } /** * Your MyStack struct will be instantiated and called as such: * MyStack* obj = myStackCreate(); * myStackPush(obj, x); * int param_2 = myStackPop(obj); * int param_3 = myStackTop(obj); * bool param_4 = myStackEmpty(obj); * myStackFree(obj); */

Read more

DeepFace深度学习库+OpenCV实现——情绪分析器

DeepFace深度学习库+OpenCV实现——情绪分析器

目录 应用场景 实现组件 1. 硬件组件 2. 软件库与依赖 3. 功能模块 代码详解(实现思路) 导入必要的库 打开摄像头并初始化变量 主循环 FPS计算 情绪分析及结果展示 显示FPS和图像 退出条件 编辑 完整代码 效果展示 自然的 开心的 伤心的 恐惧的 惊讶的  效果展示 自然的 开心的 伤心的 恐惧的 惊讶的   应用场景         应用场景比较广泛,尤其是在需要了解和分析人类情感反应的场合。: 1. 心理健康评估:在心理健康领域,可以通过长期监控和分析一个人的情绪变化来辅助医生进行诊断或治疗效果评估。 2. 用户体验研究:在产品设计、广告制作或网站开发过程中,通过观察用户在使用过程中的情绪反应,来优化产品的用户体验。 3. 互动娱乐:在游戏或虚拟现实应用中,根据玩家的情绪状态动态调整游戏难度或故事情节,以增加沉浸感和互动性。

By Ne0inhk
最全java面试题及答案(208道)

最全java面试题及答案(208道)

本文分为十九个模块,分别是:「Java 基础、容器、多线程、反射、对象拷贝、Java Web 、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、MyBatis、RabbitMQ、Kafka、Zookeeper、MySQL、Redis、JVM」 ,如下图所示: 共包含 208 道面试题,本文的宗旨是为读者朋友们整理一份详实而又权威的面试清单,下面一起进入主题吧。 Java 基础 1. JDK 和 JRE 有什么区别? * JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java

By Ne0inhk
10分钟打造专属AI助手!ToDesk云电脑/顺网云/海马云操作DeepSeek哪家强?

10分钟打造专属AI助手!ToDesk云电脑/顺网云/海马云操作DeepSeek哪家强?

文章目录 * 一、引言 * 云计算平台概览 * ToDesk云电脑:随时随地用上高性能电脑 * 二 .云电脑初体验 * DeekSeek介绍 * 版本参数与特点 * 任务类型表现 * 1、ToDesk云电脑 * 2、顺网云电脑 * 3、海马云电脑 * 三、DeekSeek本地化实操和AIGC应用 * 1. ToDesk云电脑 * 2. 海马云电脑 * 3、顺网云电脑 * 四、结语 * 总结:云电脑如何选择? 一、引言 DeepSeek这些大模型让 AI 开发变得越来越有趣,但真要跑起来,可没那么简单! * 本地配置太麻烦:显卡不够、驱动难装、环境冲突,光是折腾这些就让人心态崩了。 * 云端性能参差不齐:选错云电脑,可能卡到爆、加载慢,还容易掉线,搞得效率直线下降。 * 成本难控:有的平台按小时计费,价格一会儿一个样,

By Ne0inhk
用 DeepSeek 打造你的超强代码助手

用 DeepSeek 打造你的超强代码助手

DeepSeek Engineer 是啥? 简单来说,DeepSeek Engineer 是一个基于命令行的智能助手。它能帮你完成这些事: * 快速读文件内容:比如你有个配置文件,直接用命令把它加载进助手,后续所有操作都可以基于这个文件。 * 自动改文件:它不仅能提建议,还可以直接生成差异表(diff),甚至自动应用修改。 * 智能代码生成:比如你让它生成代码片段,它会按照指定格式和规则直接返回。 更重要的是,这一切都是通过 DeepSeek 的强大 API 来实现的。想象一下,你有个贴身助手,不仅能听懂你的代码需求,还能直接动手帮你写! 核心功能拆解 我们先来看 DeepSeek Engineer 的几个核心能力,让你更好地理解它的强大之处。 1. 自动配置 DeepSeek 客户端 启动这个工具时,你只需要准备一个 .env 文件,里面写上你的 API Key,比如: DEEPSEEK_API_

By Ne0inhk