栈和队列详解

目录

栈的概念及结构:

 栈的实现:

 代码实现:

Stack.h

stack.c

 队列:

概念及结构:

 队列的实现:

 代码实现:

Queue.h

Queue.c

 拓展:

循环队列(LeetCode题目链接):

栈的概念及结构:

:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。

       进行数据插入和删除操作的一端称为栈顶,另一端称为栈底

       栈中的数据元素遵守“先进后出/后进先出”的原则。
压栈栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈栈的删除操作叫做出栈。出数据也在栈顶

 “先进后出/后进先出”示意图:

 栈的实现:

 一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

 代码实现:

Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;//栈顶int capacity;//容量
}ST;
//初始化
void STInit(ST* ps);
//销毁
void STDestroy(ST* ps);
//入栈
void STPush(ST* ps, STDataType x);
//出栈
void STPop(ST* ps);
//取栈顶元素
STDataType STTop(ST* ps);
//数据个数
int STSize(ST* ps);
//判空
bool STEmpty(ST* ps);
stack.c
#include"Stack.h"
//初始化
void STInit(ST* ps)
{assert(ps);ps->a = NULL;ps->capacity = 0;ps->top = 0;
}
//销毁
void STDestroy(ST* ps)
{free(ps->a);ps->a = NULL;ps->capacity = 0;ps->top = 0;
}
//入栈
void STPush(ST* ps, STDataType x)
{assert(ps);if (ps->capacity == ps->top){int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;STDataType* p = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);ps->a = p;ps->capacity = newcapacity;}ps->a[ps->top] = x;ps->top++;
}//出栈
void STPop(ST* ps)
{assert(ps);assert(ps->a);assert(!STEmpty(ps));ps->top--;
}
//取栈顶元素
STDataType STTop(ST* ps)
{assert(ps);assert(ps->a);assert(!STEmpty(ps));return ps->a[ps->top - 1];
}
//数据个数
int STSize(ST* ps)
{assert(ps);assert(ps->a);return ps->top;
}
//判空
bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}

测试代码:

#include"Stack.h"
int main()
{ST p1;STInit(&p1);int i = 0;//入栈for (i = 1; i <= 10; i++){STPush(&p1, i);}printf("栈中数据数量:%d\n", STSize(&p1));//出栈for (i = 0; i < 5; i++){STDataType a = STTop(&p1);printf("%d ", a);STPop(&p1);}printf("\n");printf("栈顶元素:%d\n", STTop(&p1));printf("栈中数据数量:%d\n", STSize(&p1));STDestroy(&p1);return 0;
}

运行结果:

 队列:

概念及结构:

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有“先             进先出/后进后出”的特点。

入队列:进行插入操作的一端称为队尾。

出队列:进行删除操作的一端称为队头。

​​​​​​​“先进先出/后进后出”示意图: 

 队列的实现:

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构在出队时的效率会比较低

 代码实现:

Queue.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int QDataType;
typedef struct QueueNode
{struct QueueNode* next;QDataType data;
}QNode;typedef struct Queue
{QNode* head;QNode* tail;int size;
}Que;
//初始化
void QueueInit(Que* pq);
//销毁
void QueueDestroy(Que* pq);
//入队
void QueuePush(Que* pq, QDataType x);
//出队
void QueuePop(Que* pq);
//取队头
QDataType QueueFront(Que* pq);
//取队尾
QDataType QueueBack(Que* pq);
//判空
bool QueueEmpty(Que* pq);
//有效数据
int QueueSize(Que* pq);
Queue.c
#include"Queue.h"//初始化
void QueueInit(Que* pq)
{assert(pq);pq->head = NULL;pq->tail = NULL;pq->size = 0;
}
//销毁
void QueueDestroy(Que* pq)
{assert(pq);QNode* cur = pq->head;while (cur){QNode* p = cur->next;free(cur);cur = p;}pq->head = NULL;pq->tail = NULL;pq->size = 0;
}
//入队
void QueuePush(Que* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc failed");exit(-1);}newnode->data = x;newnode->next = NULL;if (pq->head == NULL){pq->head = newnode;pq->tail = newnode;}else{pq->tail->next = newnode;pq->tail = newnode;}pq->size++;
}
//出队
void QueuePop(Que* pq)
{assert(pq);assert(!QueueEmpty(pq));//要有数据if (pq->head->next == NULL)//只有一个节点{free(pq->head);pq->head = NULL;pq->tail = NULL;}else{QNode* cur = pq->head->next;free(pq->head);pq->head = cur;}pq->size--;
}
//取队头
QDataType QueueFront(Que* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->head->data;
}
//取队尾
QDataType QueueBack(Que* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->tail->data;
}
//判空 :空返回1,非空返回0
bool QueueEmpty(Que* pq)
{assert(pq);return pq->head == NULL;
}
//有效数据
int QueueSize(Que* pq)
{assert(pq);return pq->size;
}

代码测试:

#include"Queue.h"
int main()
{Que p2;QueueInit(&p2);int i = 0;//入队for (i = 11; i <= 20; i++){QueuePush(&p2, i);}printf("队列中数据数量:%d\n", QueueSize(&p2));//出队for (i = 0; i < 7; i++){QDataType a = QueueFront(&p2);printf("%d ", a);QueuePop(&p2);}printf("\n");printf("队头元素:%d 队尾元素:%d\n", QueueFront(&p2), QueueBack(&p2));printf("队列中数据数量:%d\n", QueueSize(&p2));QueueDestroy(&p2);return 0;
}

运行实例:

 拓展:

循环队列(LeetCode题目链接):

循环队列是一种线性数据结构,其操作表现基于普通队列“先进先出”原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

循环队列可以使用数组实现,也可以使用循环链表实现。

以循环链表实现为例:

 若仅仅是需要几个空间就创建几个空间,则空和满两种情况将无法区分,因此我们可以比实际需要多创建一个空间,这一个多创建出来空间不存储数据,则有如下情况:

 则满和空两种情况就可以区分出来了:front=rear-->空

                                                              rear的下一个=front-->满

同时为了方便取队尾数据,可以定义一个全局变量指向队尾位置(即rear指向位置的前一个)。

具体代码实现:

typedef struct List
{int val;struct List*next;
}list;
typedef struct 
{list*front;list*rear;
} MyCircularQueue;
list*tail;//定义一个全局变量记录rear的前一个
MyCircularQueue* myCircularQueueCreate(int k)
{MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));if (obj == NULL){perror("malloc failed");exit(-1);}obj->front = NULL;obj->rear = NULL;int n = k + 1;while (n--){list* new = (list*)malloc(sizeof(list));if (new == NULL){perror("malloc failed");exit(-1);}new->val = 0;new->next = obj->front;if (obj->front == NULL){obj->front = new;obj->rear = new;}else{obj->rear->next = new;tail=new;obj->rear = obj->rear->next;}}  obj->rear=obj->front;return obj;
}
//判空:空返回真,非空返回假
bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{return obj->front==obj->rear;
}
//是否已满:已满返回真,不满返回假
bool myCircularQueueIsFull(MyCircularQueue* obj)
{return obj->rear->next==obj->front;
}
//插入数据:插入成功返回真,失败返回假
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{assert(obj);if(myCircularQueueIsFull(obj))//已满{return false;}else{obj->rear->val=value;obj->rear=obj->rear->next;tail=tail->next;return true;}
}
//从队列中删除元素
bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{assert(obj);if(myCircularQueueIsEmpty(obj)){return false;}else{obj->front=obj->front->next;return true;}
}
//获取对首元素
int myCircularQueueFront(MyCircularQueue* obj) 
{assert(obj);if(myCircularQueueIsEmpty(obj)){return -1;}return obj->front->val;
}
//获取队尾元素
int myCircularQueueRear(MyCircularQueue* obj) 
{assert(obj);if(myCircularQueueIsEmpty(obj)){return -1;}/*list*cur=obj->front;while(cur->next!=obj->rear){cur=cur->next;}return cur->val;*/return tail->val;
}
//释放空间
void myCircularQueueFree(MyCircularQueue* obj) 
{while(obj->front!=obj->rear){list*Next=obj->front->next;free(obj->front);obj->front=Next;}free(obj->rear);free(obj);
}

 利用数组也可以实现,与链表类似在创建空间是同样需要多创建一个,但是因为队头、队尾没有链接关系,所以利用数组实现时与利用循环链表实现会有所不同:

 与循环链表类似,用数组实现循环队列时,当rear=front时,队列为空;在判断队列是否满时,虽然也是用rear的下一个等于front(即rear+1==front),但是不能单单使用 rear+1==front 判断,因为数组实现时,队首和队尾没有链接关系,当rear在数组最后一个位置时,循环队列已满,但rear+1会越界往后访问,且当front在数组最后一个位置时,删除数据后执行front+1也会出现越界访问的问题,出现错误,因此应想办法让rear和front都在数组范围内变化

观察数组下标,可以让rear+1和front+1都模上k+1,问题就能很好解决。

具体代码实现:

typedef struct
{int k;int front;int rear;int* queue;
} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k)
{MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));if (obj == NULL){perror("malloc failed");exit(-1);}obj->k = k;obj->front = 0;obj->rear = 0;obj->queue = (int*)malloc(sizeof(int) * (k + 1));if (obj->queue == NULL){perror("malloc failed");exit(-1);}return obj;
}
//判空:空返回真,非空返回假
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{return obj->rear == obj->front;
}
//是否已满:已满返回真,不满返回假
bool myCircularQueueIsFull(MyCircularQueue* obj)
{return (obj->rear + 1) % (obj->k + 1) == obj->front;
}
//插入数据:插入成功返回真,失败返回假
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{assert(obj);if (myCircularQueueIsFull(obj))//已满{return false;}else{obj->queue[obj->rear] = value;obj->rear = (obj->rear + 1) % (obj->k + 1);return true;}
}
//从队列中删除元素
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{assert(obj);if (myCircularQueueIsEmpty(obj)){return false;}else{obj->front = (obj->front + 1) % (obj->k + 1);return true;}
}
//获取对首元素
int myCircularQueueFront(MyCircularQueue* obj)
{assert(obj);if (myCircularQueueIsEmpty(obj)){return -1;}return obj->queue[obj->front];
}
//获取队尾元素
int myCircularQueueRear(MyCircularQueue* obj)
{assert(obj);if (myCircularQueueIsEmpty(obj)){return -1;}return obj->queue[(obj->rear + obj->k) % (obj->k + 1)];
}
//释放空间
void myCircularQueueFree(MyCircularQueue* obj)
{free(obj->queue);obj->queue = NULL;free(obj);
}

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

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

相关文章

每天一道leetcode:516. 最长回文子序列(动态规划中等)

今日份题目&#xff1a; 给你一个字符串 s &#xff0c;找出其中最长的回文子序列&#xff0c;并返回该序列的长度。 子序列定义为&#xff1a;不改变剩余字符顺序的情况下&#xff0c;删除某些字符或者不删除任何字符形成的一个序列。 示例1 输入&#xff1a;s "bbb…

【高频面试题】JVM篇

文章目录 一、JVM组成1.什么是程序计数器2.什么是Java堆&#xff1f;3.能不能介绍一下方法区(元空间&#xff09;4.你听过直接内存吗5.什么是虚拟机栈6.垃圾回收是否涉及栈内存&#xff1f;7.栈内存分配越大越好吗&#xff1f;8.方法内的局部变量是否线程安全&#xff1f;9.什么…

【技巧】如何保护PowerPoint不被改动?

PPT&#xff0c;也就是PowerPoint&#xff0c;是很多小伙伴在工作生活中经常用到的图形演示文稿软件。 做好PPT后&#xff0c;担心自己不小心改动了或者不想他人随意更改&#xff0c;我们可以如何保护PPT呢&#xff1f;下面小编就来分享两个常用的方法&#xff1a; 1. 将PPT改…

吉利科技携手企企通,打造集团化数智供应链系统

近日&#xff0c;吉利科技集团有限公司&#xff08;以下简称“吉利科技”&#xff09;联合企企通成功召开SRM采购供应链管理项目启动会。企企通与吉利科技高层、项目负责人与团队成员出席此次启动会。 双方将携手在企业供应商全生命周期管理、采购全流程、电子招投标、采购分析…

阿里云预装LAMP应用导致MySQL不显示访问密码如何解决

&#x1f600;前言 本篇博文是关于阿里云云服务器ECS部署MySQL过程中出现的一下坑&#xff0c;希望能够帮助到您&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家…

【问题解决】Git命令行常见error及其解决方法

以下是我一段时间没有使用xshell&#xff0c;然后用git命令行遇到的一些系列错误和他们的解决方法 遇到了这个报错&#xff1a; fatal: Not a git repository (or any of the parent directories): .git 我查阅一些博客和资料&#xff0c;可以解决的方式&#xff1a; git in…

C++笔记之字节数组的处理

C笔记之字节数组的处理 code review! 文章目录 C笔记之字节数组的处理1.字节数组打印2.将字节数组转换为十六进制字符串并打印3.将字符串转为字节数组4.将字节数组转为字符串5.字节数组和字符数组的区别6.字节数组用于二进制数据存储7.字节数组用于网络通信数据传输8.使用 un…

邵阳人自己的民国风情街终于来了!随手一拍即是大片!

在邵阳这座美丽的城市&#xff0c;拥有许多非常有意思并且值得打卡的游玩景区&#xff0c;“丹霞之魂&#xff0c;国之瑰宝”的崀山、“南方呼伦贝尔”之称的高山苔地草原、被联合国誉为“神奇绿洲”的遂宁黄桑等等都是成都这座城市的代表&#xff0c;但在邵阳最有民国风情韵味…

SQL | 注释

2-注释 2.1-单行注释 select prod_name -- 这是一条行内注释 from products; 使用两个连字符(-- ) 放在行内&#xff0c;两个连字符后的内容即为注释内容。 # 这是一条注释 select prod_name from products; 这种注释方式可能有些数据库不支持&#xff0c;所以使用前应该…

【Kafka】1.Kafka简介及安装

目 录 1. Kafka的简介1.1 使用场景1.2 基本概念 2. Kafka的安装2.1 下载Kafka的压缩包2.2 解压Kafka的压缩包2.3 启动Kafka服务 1. Kafka的简介 Kafka 是一个分布式、支持分区&#xff08;partition&#xff09;、多副本&#xff08;replica&#xff09;、基于 zookeeper 协调…

【CSH 入门基础 5 -- csh 文件监控脚本实现】

文章目录 背景CSHLL 代码实现cshell 中 unset 的介绍bash 中监控文件的方法 背景 由于开发代码是在外网编译&#xff0c;而镜像的烧写是在内网中的EDA工具中进行的&#xff0c;所以就需要先将代码在外网编译好后&#xff0c;再通过FTP工具将镜像传输到内网中&#xff0c;然后在…

Android FrameWork 层 Handler源码解析

Handler生产者-消费者模型 在android开发中&#xff0c;经常会在子线程中进行一些耗时操作&#xff0c;当操作完毕后会通过handler发送一些数据给主线程&#xff0c;通知主线程做相应的操作。 其中&#xff1a;子线程、handler、主线程&#xff0c;其实构成了线程模型中经典的…