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

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

在这里插入图片描述

文章目录

概念与结构

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

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

栈的实现可由数组或链表实现,那么哪种是优解呢?
底层用数组:在进栈时只需要在栈顶(尾部)插入数据,不用进行遍历;
底层用链表:在进栈时链表遍历到尾结点再进行插入数据,需要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

【Coze智能体开发】(三)解锁 Coze 智能体超能力:插件 + 知识库 + 数据库全解析,让 AI 从 “会聊天“ 到 “能办事“!

【Coze智能体开发】(三)解锁 Coze 智能体超能力:插件 + 知识库 + 数据库全解析,让 AI 从 “会聊天“ 到 “能办事“!

目录 编辑 前言 一、Coze 资源全景:不止于 "聊天" 的能力延伸 二、插件:给智能体装上 "手脚",让 AI 能 "动手办事" 2.1 什么是插件?—— 智能体的 "工具扩展包" 2.2 插件的分类:按需选择,精准赋能 1. 按功能场景分类 2. 按收费方式分类 2.3 插件的使用:3 步快速集成,零代码也能上手 第一步:创建插件智能体 第二步:添加插件(核心步骤)

By Ne0inhk
OpenClaw接入企业微信全攻略:从0到1打通企业AI协作通道

OpenClaw接入企业微信全攻略:从0到1打通企业AI协作通道

摘要:本文详细介绍了将OpenClaw AI框架接入企业微信的完整方案。通过两种主流接入方式(API模式机器人和自建应用),企业可以快速实现智能问答、流程自动化等AI能力落地。文章重点讲解了从前期准备、核心接入流程到生产环境部署的全套实操步骤,包括权限配置、网络设置、参数对接等关键环节。同时提供了进阶优化建议,如后台守护、HTTPS加固、权限管控等企业级功能配置,以及常见问题排查方法。该方案能有效解决企业信息孤岛问题,将AI能力无缝嵌入员工日常办公场景,在保障数据安全的同时显著提升工作效率。 目录 一、前言:为什么要将OpenClaw接入企业微信? 二、接入前置准备 OpenClaw介绍 接入准备工作 三、核心接入流程(两种方案任选) 方案一:API模式机器人接入(新手首选,快速上手) 步骤1:企业微信后台创建API模式机器人 步骤2:OpenClaw安装企微插件并配置参数 步骤3:完成机器人创建并测试联调 方案二:企业微信自建应用接入(企业级进阶方案) 步骤1:企业微信创建自建应用并获取核心凭证 步骤2:OpenClaw配置自建应用核心参数 步骤3:启用应

By Ne0inhk
Vibe Coding范式实战:用AI工具链(Stitch+Figma+ai studio+Trae)快速开发全栈APP

Vibe Coding范式实战:用AI工具链(Stitch+Figma+ai studio+Trae)快速开发全栈APP

文章目录 * 概要 * stitch制作设计稿 * figma 原型展示 * ai studio 生成前端代码 * 基于trae + Supabase生成后端代码和数据库 * Github + vercel * pc端后台管理系统设计 概要 在 AI 技术深度渗透软件开发领域的当下,一种名为 “Vibe Coding”(氛围编程)的全新范式正在重塑开发者的工作方式。它的核心在于,开发者不再是逐行编写代码的 “码农”,而是通过自然语言描述意图、引导 AI 生成代码的 “创意引导者” 和 “结果验证者”,从而将精力聚焦于更高价值的产品设计和逻辑思考上。 本文提供一种 Vibe Coding 的工作模式:设计阶段以 Google Stitch 为起点,开发者通过文本或草图快速生成响应式 UI 设计与前端代码,再无缝导入 Figma 进行精细化视觉调整和原型设计,实现了从 “想法” 到

By Ne0inhk
一句话生成PCB?和AI聊聊天,就把板子画了!

一句话生成PCB?和AI聊聊天,就把板子画了!

在键盘上敲下一句“我要一个STM32的电机驱动板,带CAN总线”,几秒后,一张完整的原理图和PCB布局在你眼前展开——这不是科幻电影,而是AI给硬件工程师带来的真实震撼。 清晨的阳光洒进办公室,资深硬件工程师李工没有像往常一样直接打开Altium Designer。他对着电脑屏幕上的对话框,敲入了一行简单的需求描述:“设计一个基于ESP32的智能插座PCB,要求支持Wi-Fi控制、过载保护,尺寸尽量小巧。” 15分钟后,一份完整的原理图草案、经过初步优化的双层板布局,甚至是一份物料清单(BOM)初稿已经呈现在他面前。这不可思议的效率背后,正是AI驱动的PCB设计工具在重新定义电子设计的边界。 01 效率革命,从对话到电路板 如今的PCB设计领域正经历着一场静悄悄的革命。传统上,一块电路板从概念到图纸,需要工程师经历需求分析、器件选型、原理图绘制、布局布线等一系列复杂工序,耗时数天甚至数周。 AI工具的出现彻底改变了这一流程。这类工具的核心是经过海量电路数据和设计规则训练的大型语言模型,它们能理解自然语言描述的需求,自动完成从逻辑设计到物理实现的全流程或关键环节。 比如,当

By Ne0inhk