数据结构——栈与队列的实现(全码)

news2024/10/8 13:02:40

一  栈的概念

        栈是一种特殊的线性表,栈内数据遵循先进后出(LIFO)的原则,对于栈,只能在同一侧进行入栈和出栈操作。

入栈操作和出栈操作是在栈的同一侧进行的,如图示:

对于栈这种数据类型,我们可以采用链表或者数组来实现,但是我们一般采取数组的形式进行实现。

 二  栈的实现

1.栈的声明

对于一个用数组形式来实现的栈来说,我们需要声明:

指向栈的有效地址指针,栈顶(栈的有效空间)top,  栈的最大容量capacity

typedef int TypeData;
typedef struct Stack {
	TypeData* a;
	int top;      //有效个数
	int capacity;  //最大空间
}ST;

这里top为什么时栈的有效个数呢?因为我们让top指向了当前数组存在有效数据的最大下标+1的位置,这样top更加直观的表达当前栈内的元素

 2.栈的初始化

在一个栈创建后,需要对栈进行初始化后才能进行使用,对于栈的初始状态:

指向数组首元素地址的指针为NULL

栈初始的最大容量为0

栈顶top的指向为数组为0的下标,也就是当前栈的有效数据个数为0

void STInit(ST* ps)
{	
	assert(ps);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

初始化后栈的图示如下: 

 

3.栈的入栈

        当我们往栈中入数据时,我们不能一昧的往里面入数据,在每次入数据前需要考虑当前栈的容量是否已经满了,如果当前栈的容量已满,我们需要先将栈的最大容量扩充后再进行入栈操作。

在扩充最大容量操作时,我们应当使用realloc函数,因为我们需要保留原本栈中存放的数据

在空栈时入栈,那么第一次扩充的栈的大小是4个数据类型空间(也可以是8个,10个等)

如果栈中本来就有数据,那么当栈满时,新栈的最大容量为旧栈的两倍(或整数倍)

void STPush(ST* ps, TypeData x)
{
	assert(ps);
	//检查空间是否足够
	if (ps->capacity == ps->top) {
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//创建newcapacity个空间
		ST* tmp = (ST*)realloc(ps->a, sizeof(TypeData) * newcapacity);
		//如果开辟空间失败
		if (tmp == NULL) {
			perror("realloc");
			exit(-1);
		}
		ps->capacity = newcapacity;
		ps->a = tmp;
	}
	//空间足够
	ps->a[ps->top++] = x;
}

 

 

 4.栈的出栈前提

当进行出栈操作时,需要判定栈是否为空,如果栈为空(也就是没有数据),则无法出栈。

        当栈顶为0时,说明当前栈中无数据,返回true,否则返回false 

bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

 

 

5.栈的出栈

在栈的出栈操作前需要判断当前栈中是否存有数据,否则不能进行出栈操作

通过栈顶指针-1即可完成出栈操作

//出栈
void STPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	--ps->top;
}

 

 

6.取栈顶元素

    取栈顶元素前和出栈操作一样,需要判断当前栈是否为空,否则无法取栈顶元素

    取栈顶元素返回的是top-1位置的数据,并非top位置

TypeData STTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//栈为空栈时无法取栈顶元素

	return ps->a[ps->top - 1];
}

 

 

 

7.获取栈的有效个数

    直接返回top即可,因为top不仅代表了栈顶,还代表了当前栈中的有效个数

//获取栈中有效元素个数
int STSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

8.栈的销毁

栈的销毁和顺序表基本一致:

void STDestroy(ST* ps)
{
	assert(ps);
	if (ps->a)
		free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

 

 9.栈的全码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int TypeData;
typedef struct Stack {
	TypeData* a;
	int top;      //有效个数
	int capacity;  //最大空间
}ST;

//初始化
void STInit(ST* ps)
{	
	assert(ps);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

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

//入栈
void STPush(ST* ps, TypeData x)
{
	assert(ps);
	//检查空间是否足够
	if (ps->capacity == ps->top) {
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//创建newcapacity个空间
		ST* tmp = (ST*)realloc(ps->a, sizeof(TypeData) * newcapacity);
		//如果开辟空间失败
		if (tmp == NULL) {
			perror("realloc");
			exit(-1);
		}
		ps->capacity = newcapacity;
		ps->a = tmp;
	}
	//空间足够
	ps->a[ps->top++] = x;
}

//检查是否为空栈
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

//出栈
void STPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	--ps->top;
}

//取栈顶元素
TypeData STTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//栈为空栈时无法取栈顶元素

	return ps->a[ps->top - 1];
}

//获取栈中有效元素个数
int STSize(ST* ps)
{
	assert(ps);
	return ps->top;
}
int main(){
    return 0;

}

三  队列的概念

队列是一种只允许在一侧入数据操作,另一侧出数据操作的线性表,具有先进先出的的原则

在入数据操作的一端称作队尾,在出数据的一端称作队头

而对于队列的底层,我们可以选择数组或者链表来实现,在这里我们使用链表来实现,因为采用数组来实现是,在出队操作后数组需要相继往前移动一位,这样才能使后面的的数据接着出队,因此采用数组的话在出队操作时效率会比链表低

队列的内部数据用链表来表示:

 

1.队列的声明

在 上面队列的概念中我们了解到,队列的底层由链表来实现,而队列有队头和队尾,因此队列的结构体声明有些许不同,可以理解为队列由两部分组成:链表和队列本身,因此我们需要声明两个结构体:

//队列的在链表的基础上实现,所以结构体有两个,一个是链表结点(头删尾插)
typedef int TypeData;

//链表结点结构声明
typedef struct QueueNode {
	TypeData data;
	struct QueueNode* next;
}Node;
//队列本身的声明
typedef struct Queue {
	struct QueueNode* phead;//队头
	struct QueueNode* ptail;//队尾
	int size;//记录队列的元素个数,每次入队+1,出队-1
}Queue;

2.队列的初始化

当我们创建一个队列时,队列中是不存在数据的,也就是不存在链表结点,因此我们在初始化时不需要初始化链表结点结构体:

//队列初始化
void QueueInit(Queue* ps) {
	assert(ps);
	ps->phead = ps->ptail = NULL;
	ps->size = 0;
}

初始化时,队尾指针和队头指针应当为空,因为此时队列中还不存在数据(结点)

初始化后图示:

 

3.队列的入队 

在队列的入队操作中,其实是对队列中链表的尾插操作

入队步骤:

1)创建新结点

2)在结点插入时需要判断当前队列是否为空(也就是链表是否为空),如果队列为空,那么新创建的结点就是链表的头结点,此时队尾指针和队头指针都指向头结点

3)如果当前队列不为空,那么直接在原队列上进行尾插,即在链表进行尾插,原链表最后一个结点被尾指针指向,所以原最后一个结点的指针指向新结点,原尾指针更新指向新结点

4)记录队列元素个数的size +1

//队列的入队,尾插
void QueuePush(Queue* ps, TypeData x) {
	//申请新结点
	Node* newNode = (Node*)malloc(sizeof(Node));
	if (newNode == NULL) {
		perror("malloc");
		exit(1);
	}
	newNode->data = x;
	newNode->next = NULL;

	//两种情况,队列为空与不为空
	if (ps->phead == NULL) {
		ps->phead = ps->ptail = newNode;
	}
	else {
		//队列不为空
		ps->ptail->next = newNode;
		ps->ptail = newNode;
	}
	ps->size++;
}

4.队列的出队前提

队列的出队与栈一样,在出队前对队列判断当前队列是否为空,只有队列非空的时候才能进行出队操作

//判断队列是否为空
bool QueueEmpty(Queue* ps) {
	//头节点为空证明队列为空
	return ps->phead == NULL;
}

 5.队列的出队

对于队列的出队操作,其实就是链表的头删

       先记录下队头结点的下一个结点Next,再释放队头结点并将队头结点指向队头结点的下一个结点Next,最后再将记录队列元素size成员-1

//队列出队,头删
void QueuePop(Queue* ps) {
	assert(ps);
	assert(!QueueEmpty(ps));

	Queue* Next = ps->phead->next;
	free(ps->phead);
	ps->phead = Next;
	ps->size--;
}

但是这样子其实考虑的并不完全,如果队列中只有一个结点,队尾指针和队头 指针都指向该结点,那么 Next的指向为NULL,当结点释放后,队头指针指向Next达到了置空的作用,但是队尾指针却没有置空,那么此时队尾指针就成为了野指针,这是不被允许的!

因此我们需要分两种情况进行讨论:队列只有一个结点和队列有多个结点

怎样判断队列是否只有一个结点:当队头指针和队尾指针的指向相同时

//队列出队,头删
void QueuePop(Queue* ps) {
	assert(ps);
	assert(!QueueEmpty(ps));
	//如果队列只有一个结点
	if (ps->phead == ps->ptail) {
		free(ps->phead);
		ps->phead = ps->ptail = NULL;
	}
	else {
		//队列有多个结点
		Queue* Next = ps->phead->next;
		free(ps->phead);
		ps->phead = Next;
	}
	ps->size--;
}

6.取队头元素

    无需多言,队头指针指向的数据就是队头元素,直接返回即可

//取队头元素
TypeData QueueFront(Queue* ps) {
	assert(ps);
	assert(!QueueEmpty(ps));
	return ps->phead->data;
}

7.取队尾元素

    无需多言,队尾指针指向的元素就是队尾元素,直接返回即可

//取队尾元素
TypeData QueueBack(Queue* ps) {
	assert(ps);
	assert(!QueueEmpty(ps));
	return ps->ptail->data;
}

 8.队列的销毁

利用指针del指向要删除的结点,指针Next记录删除结点的下一个结点,当del为空时证明队列已经销毁,再将队列所有的指针置空,队列有效个数置0

//队列的销毁
void DestoryQueue(Queue* ps) {
	assert(ps);
	assert(!QueueEmpty(ps));
	Node* del = ps->phead;
	while (del) {
		Queue* Next = del->next;
		free(del);
		del = Next;
	}
	ps->phead = ps->ptail = del = NULL;
	ps->size = 0;
}

9.获取队列的有效元素个数

    直接返回结构体成员变量size即可

TypeData QueueNums(Queue* ps) {
	return ps->size;
}

10.队列的全码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

//队列的在链表的基础上实现,所以结构体有两个,一个是链表结点(头删尾插)
typedef int TypeData;

//链表结点结构声明
typedef struct QueueNode {
	TypeData data;
	struct QueueNode* next;
}Node;

typedef struct Queue {
	struct QueueNode* phead;
	struct QueueNode* ptail;
	int size;
}Queue;

//队列初始化
void QueueInit(Queue* ps) {
	assert(ps);
	ps->phead = ps->ptail = NULL;
	ps->size = 0;
}


//队列的入队,尾插
void QueuePush(Queue* ps, TypeData x) {
	//申请新结点
	Node* newNode = (Node*)malloc(sizeof(Node));
	if (newNode == NULL) {
		perror("malloc");
		exit(1);
	}
	newNode->data = x;
	newNode->next = NULL;

	//两种情况,队列为空与不为空
	if (ps->phead == NULL) {
		ps->phead = ps->ptail = newNode;
	}
	else {
		//队列不为空
		ps->ptail->next = newNode;
		ps->ptail = newNode;
	}
	ps->size++;
}

//判断队列是否为空
bool QueueEmpty(Queue* ps) {
	//头节点为空证明队列为空
	return ps->phead == NULL;
}

//队列出队,头删
void QueuePop(Queue* ps) {
	assert(ps);
	assert(!QueueEmpty(ps));
	//如果队列只有一个结点
	if (ps->phead == ps->ptail) {
		free(ps->phead);
		ps->phead = ps->ptail = NULL;
	}
	else {
		//队列有多个结点
		Queue* Next = ps->phead->next;
		free(ps->phead);
		ps->phead = Next;
	}
	ps->size--;
}

//取队头元素
TypeData QueueFront(Queue* ps) {
	assert(ps);
	assert(!QueueEmpty(ps));
	return ps->phead->data;
}

//取队尾元素
TypeData QueueBack(Queue* ps) {
	assert(ps);
	assert(!QueueEmpty(ps));
	return ps->ptail->data;
}

//队列的销毁
void DestoryQueue(Queue* ps) {
	assert(ps);
	assert(!QueueEmpty(ps));
	Node* del = ps->phead;
	while (del) {
		Queue* Next = del->next;
		free(del);
		del = Next;
	}
	ps->phead = ps->ptail = del = NULL;
	ps->size = 0;
}

TypeData QueueNums(Queue* ps) {
	return ps->size;
}

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

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

相关文章

自动驾驶系列—揭秘毫米波雷达:自动驾驶的眼睛如何看穿复杂环境?

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

Linux:无法为立即文档创建临时文件: 设备上没有空间

虚拟机磁盘空间不足解决记录 1、问题描述2、问题解决 1、问题描述 在命令行输入命令按Tab键时出现如下报错&#xff1a; 很明显&#xff0c;设备上没有空间&#xff0c;即磁盘空间不足。通过命令查看具体情况如下&#xff1a; df -h2、问题解决 首先想到的是虚拟机扩容。关机虚…

【技术白皮书】内功心法 | 第一部分 | 数据结构与算法基础(数据结构)

数据结构与算法基础 内容简介数据结构数据模型数据结构的表现形式 基本概念数据&#xff08;Data&#xff09;数据元素&#xff08;data element&#xff09;数据结构的定义物理结构和逻辑结构逻辑结构逻辑结构表现形式二元组模型集合结构模型线性结构模型树结构模型图结构模型…

Python从0到100(六十):机器学习-模型选择与交叉验证

前言: 零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、 计算机视觉、机器学习、神经网络以及人工智能…

有限差分方法 - 拉普拉斯算子第二部分

Finite difference method - Laplacian part 2 — ROCm Blogs (amd.com) 2023年1月4日 作者&#xff1a;Justin Chang, Rajat Arora, Thomas Gibson, Sean Miller, Ossian O’Reilly 在之前的拉普拉斯算子文章中&#xff0c;我们开发了一种基于HIP实现的有限差分模板代码&#…

springboot车位预约小程序-计算机毕业设计源码42655

摘要 随着社会发展和人口增加&#xff0c;城市交通压力越来越大&#xff0c;停车位资源的分配和管理成为一个重要问题。传统的停车位和车位预约管理方式存在信息不对称、效率低下等问题&#xff0c;给用户带来不便。而基于微信小程序的车位预约系统可以通过智能化、数字化、便…

布尔莎公式推导

问题的提出 原始的围绕x轴、y轴、z轴进行旋转矩阵的公式为 但是最近需要将船体坐标系转换到相应的世界坐标系之中&#xff0c;在查看相关论文时&#xff0c;看到一个三维点公式转换模型 这里的旋转矩阵为&#xff0c;和我之前见到的旋转矩阵是不一样的。我一开始先是看到的202…

计算机网络-------重传、TCP流量控制、拥塞控制

重传、滑动窗口、流量控制、拥塞避免 重传机制 超时重传 发送方在发送数据时会启动一个定时器&#xff0c;当超过指定的时间之后&#xff0c;还没接收到接收方的ACK确认应答报文&#xff0c;就会重传该数据 快重传 当发送方收到接收方三个连续的ack之后说明发送方发送的报…

蓝牙模块(BT04/HC05)

目录 一、介绍 二、模块原理 1.原理图与外形尺寸 2.引脚描述 3.蓝牙模块基础AT指令介绍 三、程序设计 usart3.h文件 usart3.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 BT04A是一款蓝牙低功耗&#xff08;Bluetooth Low Energy, BLE&#xff09;模块&…

华为OD机试 - 奖牌榜排名(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

Python面向对象编程:属性和方法②

文章目录 一、什么是属性和方法1.1 属性1.2 方法 二、定义和使用属性2.1 定义实例属性2.2 访问和修改实例属性2.3 定义类属性2.4 访问和修改类属性 三、定义和使用方法3.1 定义实例方法3.2 调用实例方法3.3 定义类方法3.4 调用类方法3.5 定义静态方法3.6 调用静态方法 四、综合…

ChatGPT背景下,高职人工智能技术应用专业的人才培养

一、引言 ChatGPT&#xff0c;即聊天生成预训练变换器&#xff0c;由美国OpenAI公司开发&#xff0c;自2022年11月首次亮相以来&#xff0c;已成为人工智能领域的一个标志性成就。这款聊天机器人利用先进的人工智能技术&#xff0c;处理自然语言&#xff0c;能够精准把握用户的…

【实战教程】SpringBoot全面指南:快速上手到项目实战(SpringBoot)

文章目录 【实战教程】SpringBoot全面指南&#xff1a;快速上手到项目实战(SpringBoot)1. SpringBoot介绍1.1 SpringBoot简介1.2系统要求1.3 SpringBoot和SpringMVC区别1.4 SpringBoot和SpringCloud区别 2.快速入门3. Web开发3.1 静态资源访问3.2 渲染Web页面3.3 YML与Properti…

ctf.bugku - 本地管理员

题目来源&#xff1a;本地管理员 - Bugku CTF 访问页面 页面的最后返回一个字符串&#xff1b; 结尾 应该是base64 编码&#xff1b; 解码得到 test123 同时&#xff0c;提示信息还有 IP禁止访问&#xff0c;本地管理员登陆&#xff1b; 所以&#xff0c;请求头添加&#x…

“欢迎”相关英语表达柯桥成人商务英语口语学习到蓝天广场

1.某地的欢迎标语 说到欢迎&#xff0c;小编想起了江苏的欢迎标语。 这则标语把“江苏欢迎您”&#xff0c;翻译成了“Jiangsu welcomes you”。 不少小伙伴都觉得这样翻译不对&#xff0c;“欢迎您来某某地方”&#xff0c;应该翻译成“Welcome to XX”。 但其实&#xff0c;一…

超声波气象监测站的工作原理

TH-CQX5超声波气象监测站&#xff0c;顾名思义&#xff0c;是一种通过超声波技术实现气象数据监测的设备。这种监测站的设计理念充分利用了超声波在空气中传播的特性&#xff0c;能够高效、准确地测量风速、风向、温度、湿度等气象要素。超声波气象监测站的构造简洁而高效&…

华为OD机试 - 银行插队 - 队列(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

前端vue-安装pinia,它和vuex的区别

创建一个store的目录&#xff0c;任意一个js文件&#xff0c;再导入pinia&#xff0c;再定义

想走?可以!先买票——迭代器模式

文章目录 想走&#xff1f;可以&#xff01;先买票——迭代器模式乘车买票&#xff0c;不管你是谁&#xff01;迭代器模式迭代器实现Java的迭代器实现迭代高手 想走&#xff1f;可以&#xff01;先买票——迭代器模式 乘车买票&#xff0c;不管你是谁&#xff01; 时间&#…

【2024版】最新kali linux入门及常用简单工具介绍(非常详细)零基础入门到精通,收藏这一篇就够了_kalilinux

一、介绍 kali Linux Kali Linux 是一个基于 Debian 的 Linux 发行版&#xff0c;主要用于数字取证和渗透测试。它预装了大量的安全审计和渗透测试工具&#xff0c;被广泛应用于网络安全领域。 &#xff08;一&#xff09;特点 工具丰富&#xff1a;集成了数百种用于渗透测试…