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

数据结构之栈和队列(超详解)
在这里插入图片描述

在这里插入图片描述

文章目录

概念与结构

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

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

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

在这里插入图片描述

队列

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

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

在这里插入图片描述

代码实现

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

typedefint STDataType;typedefstructStack{ STDataType* arr;int top;//栈顶int capacity;}ST;

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

//栈的初始化voidSTInit(ST* st){assert(st); st->arr =NULL; st->top = st->capacity =0;}//栈的销毁voidSTDestroy(ST* st){assert(st);if(st->arr){free(st->arr);} st->arr =NULL; st->capacity = st->top =0;}//入栈-->栈顶voidSTPush(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++;}//出栈帧-->栈顶voidSTPop(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];}//获取栈的有效个数intSTSize(ST* st){assert(st);return st->top;}

队列

队列的底层是链表,和前面在实现单链表的底层结构是一样的。
但队列额外定义了指向第一个有效结点的phead指针、指向最后一个有效结点的ptail指针,目的是减少入队列的消耗(不用遍历链表)。

在这里插入图片描述


这里多定义的size变量是为了记录队列的有效个数

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

入队列

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

  1. 链表为空,phead和ptail指向申请的新结点;

链表不为空,ptail指向新结点并成为新结点,并让size++。

在这里插入图片描述
//往队尾插入数据voidQueuePush(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指向上一个结点。
在这里插入图片描述
//队头删除数据voidQueuePop(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;}//队列有效元素个数intQueueSize(Queue* pq){assert(pq);return pq->size;}

算法题解

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

有效的括号

题型 --> 有效的括号


在这里插入图片描述

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

  1. 将s从头开始遍历直到结束,若遍历过程遇到 ‘(’ ‘{’ ‘[’ 则入栈st;
  2. 若遇到 ‘)’ ‘}’ ‘]’ 时则取栈顶比较(前提是栈不为空);

遍历结束后,需判断栈是否为空,因为若是有效的字符串则栈应为空(如上图特殊处理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;}

用队列实现栈

题型 --> 用队列实现栈


在这里插入图片描述

算法思路

在这里插入图片描述

代码实现

typedefstruct{ Queue q1; Queue q2;} MyStack;//初始化 MyStack*myStackCreate(){ MyStack* ret=(MyStack*)malloc(sizeof(MyStack));QueueInit(&ret->q1);QueueInit(&ret->q2);return ret;}//往不为空的队列插入数据voidmyStackPush(MyStack* obj,int x){if(!QueueEmpty(&obj->q1)){QueuePush(&obj->q1,x);}else{QueuePush(&obj->q2,x);}}intmyStackPop(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;}//把不为空队列的队尾元素返回intmyStackTop(MyStack* obj){if(!QueueEmpty(&obj->q1)){returnQueueBack(&obj->q1);}else{returnQueueBack(&obj->q2);}} bool myStackEmpty(MyStack* obj){returnQueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);}voidmyStackFree(MyStack* obj){QueueDestroy(&obj->q1);QueueDestroy(&obj->q2);free(obj); obj=NULL;}

用栈实现队列

题型 --> 用栈实现队列


在这里插入图片描述

算法思路

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

在这里插入图片描述

代码实现

typedefstruct{ ST pushST; ST popST;} MyQueue; MyQueue*myQueueCreate(){ MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));STInit(&obj->pushST);STInit(&obj->popST);return obj;}voidmyQueuePush(MyQueue* obj,int x){STPush(&obj->pushST,x);}intmyQueuePop(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;}intmyQueuePeek(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){returnSTEmpty(&obj->pushST)&&STEmpty(&obj->popST);}voidmyQueueFree(MyQueue* obj){STDestroy(&obj->pushST);STDestroy(&obj->popST);free(obj); obj=NULL;}

复用

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

在这里插入图片描述

设计循环队列

题型 --> 设计循环队列


在这里插入图片描述

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

在这里插入图片描述

数组结构实现循环队列

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

在这里插入图片描述

解决方案:给数组多开一块空间

在这里插入图片描述
构造、销毁循环队列
typedefstruct{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下标位置的元素即可。但此题是一个循环队列,因此存在特殊情况需要处理

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

特例情况:若rear–则为-1,取不到队尾元素。

在这里插入图片描述
intmyCircularQueueFront(MyCircularQueue* obj){if(myCircularQueueIsEmpty(obj)){return-1;}return obj->arr[obj->front];}intmyCircularQueueRear(MyCircularQueue* obj){if(myCircularQueueIsEmpty(obj)){return-1;}int prev=obj->rear-1;if(obj->rear==0){ prev=obj->capacity;}return obj->arr[prev];}
在这里插入图片描述


Read more

安装 启动 使用 Neo4j的超详细教程

安装 启动 使用 Neo4j的超详细教程

最近在做一个基于知识图谱的智能生成项目。需要用到Neo4j图数据库。写这篇文章记录一下Neo4j的安装及其使用。 一.Neo4j的安装 1.首先安装JDK,配环境变量。(参照网上教程,很多) Neo4j是基于Java的图形数据库,运行Neo4j需要启动JVM进程,因此必须安装JAVA SE的JDK。从Oracle官方网站下载 Java SE JDK。我使用的版本是JDK1.8 2.官网上安装neo4j。 官方网址:https://neo4j.com/deployment-center/  在官网上下载对应版本。Neo4j应用程序有如下主要的目录结构: bin目录:用于存储Neo4j的可执行程序; conf目录:用于控制Neo4j启动的配置文件; data目录:用于存储核心数据库文件; plugins目录:用于存储Neo4j的插件; 3.配置环境变量 创建主目录环境变量NEO4J_HOME,并把主目录设置为变量值。复制具体的neo4j文件地址作为变量值。 配置文档存储在conf目录下,Neo4j通过配置文件neo4j.conf控制服务器的工作。默认情况下,不需

企业微信群机器人Webhook配置全攻略:从创建到发送消息的完整流程

企业微信群机器人Webhook配置全攻略:从创建到发送消息的完整流程 在数字化办公日益普及的今天,企业微信作为国内领先的企业级通讯工具,其群机器人功能为团队协作带来了极大的便利。本文将手把手教你如何从零开始配置企业微信群机器人Webhook,实现自动化消息推送,提升团队沟通效率。 1. 准备工作与环境配置 在开始创建机器人之前,需要确保满足以下基本条件: * 企业微信账号:拥有有效的企业微信管理员或成员账号 * 群聊条件:至少包含3名成员的群聊(这是创建机器人的最低人数要求) * 网络环境:能够正常访问企业微信服务器 提示:如果是企业管理员,建议先在"企业微信管理后台"确认机器人功能是否已对企业开放。某些企业可能出于安全考虑会限制此功能。 2. 创建群机器人 2.1 添加机器人到群聊 1. 打开企业微信客户端,进入目标群聊 2. 点击右上角的群菜单按钮(通常显示为"..."或"⋮") 3. 选择"添加群机器人"选项 4.

Flowise物联网融合:与智能家居设备联动的应用设想

Flowise物联网融合:与智能家居设备联动的应用设想 1. Flowise:让AI工作流变得像搭积木一样简单 Flowise 是一个真正把“AI平民化”落地的工具。它不像传统开发那样需要写几十行 LangChain 代码、配置向量库、调试提示词模板,而是把所有这些能力打包成一个个可拖拽的节点——就像小时候玩乐高,你不需要懂塑料怎么合成,只要知道哪块该拼在哪,就能搭出一座城堡。 它诞生于2023年,短短一年就收获了45.6k GitHub Stars,MIT协议开源,意味着你可以放心把它用在公司内部系统里,甚至嵌入到客户交付的产品中,完全不用担心授权问题。最打动人的不是它的技术多炫酷,而是它真的“不挑人”:产品经理能搭出知识库问答机器人,运营同学能配出自动抓取竞品文案的Agent,连刚学Python两周的实习生,也能在5分钟内跑通一个本地大模型的RAG流程。 它的核心逻辑很朴素:把LangChain里那些抽象概念——比如LLM调用、文档切分、向量检索、工具调用——变成画布上看得见、摸得着的方块。你拖一个“Ollama LLM”节点,再拖一个“Chroma Vector

OpenClaw配置Bot接入飞书机器人+Kimi2.5

OpenClaw配置Bot接入飞书机器人+Kimi2.5

上一篇文章写了Ubuntu_24.04下安装OpenClaw的过程,这篇文档记录一下接入飞书机器+Kimi2.5。 准备工作 飞书 创建飞书机器人 访问飞书开放平台:https://open.feishu.cn/app,点击创建应用: 填写应用名称和描述后就直接创建: 复制App ID 和 App Secret 创建成功后,在“凭证与基础信息”中找到 App ID 和 App Secret,把这2个信息复制记录下来,后面需要配置到openclaw中 配置权限 点击【权限管理】→【开通权限】 或使用【批量导入/导出权限】,选择导入,输入以下内容,如下图 点击【下一步,确认新增权限】即可开通所需要的权限。 配置事件与回调 说明:这一步的配置需要先讲AppId和AppSecret配置到openclaw成功之后再设置订阅方式,