实现简单的栈与队列

news2024/7/2 3:35:35

前言:前面已经详细地介绍了基本的顺序表和链表,这次要介绍的是数据结构中的栈与队列。从本质上来说,二者是特殊的线性表,是依赖于顺序表或链表来实现的,所以只要能够很好地掌握顺序表和链表,再了解清楚栈与队列的概念及基本结构,就可以很好地将二者实现出来。

注:由上言以及下文可以知道栈与队列的实现与顺序表,链表的实现大同小异(甚至更简单),一些内容不会详细说明,不清楚的可以再看看以下两篇文章:

(1)顺序表的实现

(2)单链表的实现

目录:

一:栈

1. 栈的概念

2. 栈的结构(图示)

3. 栈的重要接口函数

4. 栈实现相关代码总览

二:队列

1. 队列的概念

2. 队列的结构(图示)

3. 队列的重要接口函数

4. 队列实现相关代码总览


一:栈

1. 栈的概念

(1) 栈: 一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。 栈中的数据元素遵守 后进先出 的原则。
(2) 压栈: 栈的插入操作叫做压栈/进栈 /入栈,即入数据在栈顶
(3) 出栈: 栈的删除操作叫做出栈。即 出数据也在栈顶

2. 栈的结构(图示)

注:栈一定要遵守先进后出的原则。


3. 栈的重要接口函数

我们可以使用顺序表来实现栈,也可以用链表实现栈,但是链表实现栈有两种方式,一种是头插头删,一种是尾插尾删。而单链表进行尾插尾删时要进行的找尾操作较为复杂(要遍历链表),所以我们选择顺序表(数组)来实现栈,其结构相对链表而言较为优势。

栈相关的七个重要接口函数:

void StackInit(ST* st);//初始化
void StackPush(ST* st, STDataType x);//入栈
void StackPop(ST* st);//出栈
STDataType StackTop(ST* st);//获取栈顶元素
int StackSize(ST* st);//获取栈中的有效元素个数
bool StackEmpty(ST* st);//判断栈是否为空
void StackDestroy(ST* st);//销毁栈

4. 栈实现相关代码总览

(1)TestStack.c

#define _CRT_SECURE_NO_WARNINGS
#include "Stack.h"

void TestStack1()
{
	ST st;//定义一个栈
	StackInit(&st);//将栈初始化,要传递结构体指针才能在接口函数内对其进行改变

	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);//压栈
	
	printf("%d\n", StackSize(&st));//打印此时的栈内元素个数
	
	while (!StackEmpty(&st))//打印出栈内的所有数据
	{
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}//一个小疑问:Pop执行后的终点是top=0,但是此时st->a[0]不是等于1吗,这样不是没有删干净吗?
	printf("\n%d\n", StackSize(&st));
	StackDestroy(&st);
}

void TestStack2()
{
	ST st;//定义一个栈
	StackInit(&st);//将栈初始化,要传递结构体指针才能在接口函数内对其进行改变

	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	printf("%d\n", StackSize(&st));
	StackPop(&st);
	StackPop(&st);
	printf("%d\n", StackSize(&st));//Pop两次后栈内的元素个数
	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}
	printf("\n%d\n", StackSize(&st));
	StackDestroy(&st);
}

int main()
{
	TestStack1();
	//TestStack2();
	return 0;
}

(2)Stack.h

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

typedef int STDataType;

//定义一个动态增长的数组栈
typedef struct Stack
{
	STDataType* a;//指针a指向动态开辟的数组
	int top;//表示栈内的有效元素个数
	int capacity;//表示栈的空间容量
} ST;

//相关接口函数的定义
void StackInit(ST* st);//初始化
void StackPush(ST* st, STDataType x);//入栈
void StackPop(ST* st);//出栈
STDataType StackTop(ST* st);//获取栈顶元素
int StackSize(ST* st);//获取栈中的有效元素个数
bool StackEmpty(ST* st);//判断栈是否为空
void StackDestroy(ST* st);//销毁栈

(3)Stack.c

#define _CRT_SECURE_NO_WARNINGS
#include "Stack.h"

void StackInit(ST* st)
{
	assert(st);
	st->a = NULL;
	//top初始化为0时,top指向的是栈顶的下一个数据,因为压栈是先插入数据后top再加1
	st->top = st->capacity = 0;
}

void StackPush(ST* st, STDataType x)//入栈,向栈内插入数据
{
	assert(st);
	if (st->top == st->capacity)//判断容量是否足够,不够就扩容
	{
		int newCapacity = st->capacity == 0 ? 4 : st->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(st->a, sizeof(ST) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		st->a = tmp;
		st->capacity = newCapacity;
	}
	st->a[st->top] = x;
	st->top++;//先插入数据,top再++,即top此时指向的是栈顶的下一个数据
}

void StackPop(ST* st)//出栈
{
	assert(st);
	//assert(st->top > 0);
	assert(!StackEmpty(st));//栈不能为空
	st->top--;
}

bool StackEmpty(ST* st)//判断栈是否为空
{
	assert(st);
	return st->top == 0;
}

STDataType StackTop(ST* st)//获取栈顶元素
{
	assert(st);
	return st->a[st->top - 1];//top下标指向的是栈顶的后一个数据
}

int StackSize(ST* st)//获取栈内有效数据的个数
{
	assert(st);
	return st->top;
}

void StackDestroy(ST* st)//销毁栈
{
	assert(st);
	while (!StackEmpty(st))
	{
		StackPop(st);
	}
	st->a = NULL;
	st->top = st->capacity = 0;
}

二:队列

1. 队列的概念

队列: 只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的特性。
入队列: 进行插入操作的一端称为 队尾。
出队列: 进行删除操作的一端称为 队头。

2. 队列的结构(图示)

 注:队列一定要遵守先进先出的原则。


3. 队列的重要接口函数的实现

与栈相同,队列既可以通过顺序表来实现,也可以通过链表实现。但与栈实现起来不同的是,对于队列而言,链表的结构更适合它,因为队列主要涉及到的是头部尾部的插入与删除,而顺序表(数组)实现起来的效率会更低一些。

这里需要特别说明的是:选用的是单链表,并且我们需要给这个单链表定义好头节点 (head)和尾节点(tail),将它们作为队列的基本框架,以形成一个较好头插尾删的单链表。

图示:

代码说明:

队列相关接口函数:

void QueueInit(Queue* pq);//初始化队列
void QueuePush(Queue* pq, QDataType x);//入队列
void QueuePop(Queue* pq);//出队列
bool QueueEmpty(Queue* pq);//判断队列是否为空
int QueueSize(Queue* pq);//返回队列中有效数个数
QDataType QueueFront(Queue* pq);//获取队头数据
QDataType QueueBack(Queue* pq);//获取队尾数据
void QueuePrint(Queue* pq);//打印队列(同时会清空队列的元素)
void QueueDestroy(Queue* pq);//销毁队列中动态开辟节点的链表

4. 队列实现相关代码总览

(1) TestQueue.c

#define _CRT_SECURE_NO_WARNINGS
#include "Queue.h"

void TestQueue1()
{
	Queue q;
	QueueInit(&q);

	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	printf("%d\n", QueueSize(&q));//打印此时队列内的元素个数

	QueuePop(&q);
	QueuePop(&q);
	printf("%d\n", QueueSize(&q));//打印此时队列内的元素个数
	QueuePrint(&q);//QueuePrint函数调用的同时会清空队列
	printf("\n%d\n", QueueSize(&q));
	QueueDestroy(&q);
}

void TestQueue2()
{
	Queue q;
	QueueInit(&q);

	QueuePush(&q, 5);
	QueuePush(&q, 6);
	QueuePush(&q, 7);
	QueuePush(&q, 8);
	//打印此时队列元素个数,队头数据,对尾数据
	printf("%d %d %d\n", QueueSize(&q), QueueFront(&q), QueueBack(&q));

	QueuePrint(&q);//QueuePrint函数调用的同时会清空队列
	printf("\n%d\n", QueueSize(&q));
	QueueDestroy(&q);
}

int main()//各个接口函数功能的测试
{
	TestQueue1();
	//TestQueue2();
	return 0;
}

(2) Queue.h

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

typedef int QDataType;

//用单链表实现队列(定义队列中的链表结构)
typedef struct QueueListNode
{
	struct QueueListNode* next;//指针域
	QDataType data;//数据域
} QLNode;

//为了更好地实现单链表的头删(出队列)与尾插(入队列),在队列的链表结构中在定义一个头节点与尾节点,可以表示队列的结构
typedef struct Queue
{
	QLNode* head;
	QLNode* tail;
} Queue;

//队列相关接口函数的定义
void QueueInit(Queue* pq);//初始化队列
void QueuePush(Queue* pq, QDataType x);//入队列
void QueuePop(Queue* pq);//出队列
bool QueueEmpty(Queue* pq);//判断队列是否为空
int QueueSize(Queue* pq);//返回队列中有效数个数
QDataType QueueFront(Queue* pq);//获取队头数据
QDataType QueueBack(Queue* pq);//获取队尾数据
void QueuePrint(Queue* pq);//打印队列(同时会清空队列的元素)
void QueueDestroy(Queue* pq);//销毁队列中动态开辟节点的链表

(3) Queue.c

#define _CRT_SECURE_NO_WARNINGS
#include "Queue.h"

void QueueInit(Queue* pq)//队列初始化
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}

void QueuePrint(Queue* pq)//打印整个队列的元素(同时会清空队列的元素)
{
	assert(pq);
	while (!QueueEmpty(pq))
	{
		printf("%d ", QueueFront(pq));
		QueuePop(pq);//只有清理了对头元素,才可以继续向后读取数据
	}
}

void QueuePush(Queue* pq, QDataType x)//入队列(单链表的尾插)
{
	QLNode* newnode = (QLNode*)malloc(sizeof(QLNode));//开辟新节点
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->next = NULL;
	newnode->data = x;
	//节点入列的两种情况
	if (pq->head == NULL)//1.原队列为空
	{
		pq->head = pq->tail = newnode;
		newnode->next = NULL;
	}
	else//原队列不为空
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
		pq->tail->next = NULL;
	}
}

void QueuePop(Queue* pq)//出队列,单链表的头删
{
	assert(pq);
	assert(!QueueEmpty(pq));//删除时队列不能为空
	QLNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
}

bool QueueEmpty(Queue* pq)//判断队列是否为空
{
	assert(pq);
	return pq->head == NULL;
}

int QueueSize(Queue* pq)//返回队列中有效数个数
{
	assert(pq);
	QLNode* cur = pq->head;
	int num = 0;
	while (cur)//遍历队列中的单链表
	{
		num++;
		cur = cur->next;
	}
	return num;
}

QDataType QueueFront(Queue* pq)//获取队头数据
{
	assert(pq);
	assert(!QueueEmpty(pq));//队列不能为空
	return pq->head->data;
}

QDataType QueueBack(Queue* pq)//获取队尾数据
{
	assert(pq);
	assert(!QueueEmpty(pq));//队列不能为空
	return pq->tail->data;
}

void QueueDestroy(Queue* pq)//销毁队列中动态开辟节点的链表
{
	assert(pq);
	while (pq->head)
	{
		QLNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

总结:

栈与队列的实现最重要的是结构以及对链表,顺序表的理解程度,这里再强调一次:数据结构的学习中结构的理解十分重要,所以我们可以多画画相关的图,再结合图理解这样一定可以事半功倍。栈与队列的介绍就到这里结束,谢谢大家的阅读,再见。

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

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

相关文章

STM32F103学习笔记(11)——压力传感器GZP6859D使用

一、简介 数据手册&#xff1a;https://item.szlcsc.com/3590436.html GZP6859D 型压力传感器采用 SOP6 封装形式&#xff0c;内部集成了高精度 ADC 芯片&#xff0c;对传感器芯片输出的偏移、灵敏度、温漂和非线性进行数字补偿&#xff0c;以供电电压为参考&#xff0c;产生一…

基于Java实现对Excel表格数据的读写(附B站详细讲解视频)

文章目录 Maven依赖设置导入相应jar包 读取.xlsx表格文件数据 写入数据到.xlsx表格文件 读写后缀名为.xls类型的表格文件&#xff08;旧版表格文件&#xff09; 详细视频教程 Maven依赖设置导入相应jar包 <project xmlns"http://maven.apache.org/POM/4.0.0" …

论文理解【Offline RL】——【One-step】Offline RL Without Off-Policy Evaluation

标题&#xff1a;Offline RL Without Off-Policy Evaluation文章链接&#xff1a;Offline RL Without Off-Policy Evaluation代码&#xff1a;davidbrandfonbrener/onestep-rl发表&#xff1a;NIPS 2021领域&#xff1a;离线强化学习&#xff08;offline/batch RL&#xff09;—…

【深度学习】知识蒸馏原理以及实践从0到1

文章目录前言1、知识蒸馏1.1 是什么&#xff1f;1.2 训练流程1.3 问题与挑战2、落地使用2.1 后续问题&#xff1a;总结前言 有没有什么方法可以在不扩展硬件的情况下利用这些强大但庞大的模型来训练最先进的模型&#xff1f;目前&#xff0c;有三种方法可以压缩神经网络&#…

一文搞懂JDK8 HashMap源码

目录前言常量和变量构造器put方法resize扩容get方法前言 HashMap的源码非常经典&#xff0c;里面用到了哈希表、链表、红黑树等数据结构&#xff0c;而且又是用纯Java实现的&#xff0c;所以成为了Java程序员必读的源码之一。 事先了解下哈希表&#xff08;散列表&#xff09…

portraiture2023手动磨皮的p图插件

可以手动磨皮的p图软件&#xff0c;大部分美颜软件只能一键磨皮或简单调整磨皮强度&#xff0c;本文会介绍一款可自动、可手动磨皮的p图软件。人像p图软件哪个好用&#xff1f;本文还会盘点一下好用的人像p图软件。 portraiture2023功能特点 2x性能和精细的输出质量将您的皮肤…

AES加密算法

AES算法原理 对称加密算法&#xff08;用于取代DES算法&#xff0c;发展历史DES-3DES-AES&#xff09; 明文长度固定为128位&#xff08;DES&#xff1a;64位&#xff09;&#xff0c;密钥长度可128位、192位、256位&#xff08;DES&#xff1a;64位&#xff09; 加密原理 …

你是如何对待植物神经紊乱的?

大家好&#xff0c;你们是如何对待植物神经紊乱这种疾病的&#xff1f; 你们知道吗&#xff1f;植物神经紊乱是一种情绪情志障碍伴躯体化症状的特殊且复杂的疾病&#xff0c;这种疾病可能会导致浑身的不适。 并且&#xff0c;很多植物神经紊乱的患者发现&#xff0c;这种疾病是…

【GD32F427开发板试用】硬件SPI通信驱动CH376芯片,用单片机实现U盘数据下载

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;周文杰 SPI通信作为单片机多种基础数据传输模式中的一种&#xff0c;驱动外部芯片CH376实现数据导出到U盘功能在实际工程项目中是很方便的。本…

字符设备驱动之mmap、select

一、mmap mmap&#xff0c;简而言之就是将内核空间的一段内存区域映射到用户空间。映射成功后&#xff0c;用户对这段内存区域的修改可以直接反映到内核空间&#xff0c;相反&#xff0c;内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间与用户空间两者之…

Prometheus + Grafana + Alertmanager 本地安装调试

一、简介 Prometheus 是一款强大的监控软件&#xff0c;一般会与Grafana和Alertmanager一起配合使用&#xff0c;而且多用于k8s集群。简介的话网上很多&#xff0c;官网 更是详细&#xff0c;这里就不班门弄斧了。k8s集群环境下的安装网上很多&#xff0c;但是k8s集群搭建时间…

【实际开发10】- 远程调用 ( Feign )

目录 1. Feign 调用注意事项 - ★★★ 1. 【原则】: 禁止遍历 - 多次跨服务调用接口 ( 提需求 : idList ) 1. 单一数据查询 , 可直接用 Feign单一查询接口 2. List数据查询 , 需进行 Feign 数据转换 , 禁止遍历 Feign 3. stream() : 从List<对象> , 取出 id 和 name…

关于PS VR2和独占,开发者和分析师都怎么看

近期&#xff0c;索尼正式宣布了PS VR2首发游戏列表&#xff0c;共计37款游戏&#xff0c;其中包括备受关注的IP大作《地平线&#xff1a;山之召唤》等。从这37款首发阵容中可以看到一个现象&#xff0c;大部分游戏是非新作&#xff0c;而是已经在PS VR1或其它VR平台上线&#…

C++基础——C++数组

C基础——C数组C 数组声明数组初始化数组访问数组元素C 中数组详解C 数组 C 支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的…

【数据结构基础】线性表 - 链表

n个节点离散分配&#xff0c;彼此通过指针相连&#xff0c;每个节点只有一个前驱节点&#xff0c;每个节点只有一个后续节点&#xff0c;首节点没有前驱节点&#xff0c;尾节点没有后续节点。确定一个链表我们只需要头指针&#xff0c;通过头指针就可以把整个链表都能推出来。知…

设计模式-UML图

目录 2&#xff0c;UML图 2.1 类图概述 2.2 类图的作用 2.3 类图表示法 2.3.1 类的表示方式 2.3.2 类与类之间关系的表示方式 2&#xff0c;UML图 统一建模语言&#xff08;Unified Modeling Language&#xff0c;UML&#xff09;是用来设计软件的可视化建模语言。它的特…

Matlab pdetool

云溪岩绵迎彩霞,博主精神压力大呀,没人说说知心话啊,SCU物理要命啦........基本物理方程静电磁场交流电磁场热传导Options->ApplicationGeneric Scalar泛型标量Generic System通用系统Structural Mechanics,Plane Stress结构力学 - 平面应力Structural Mechanics,Plane Stra…

Flashback Oracle文档阅读

和Flashback相关的文档大多位于备份和恢复用户指南 和Oracle 数据库开发指南中。 基本概念 请参看备份和恢复用户指南的1.4 About Oracle Flashback Technology。 Oracle Flashback Technology的定义&#xff1a; A set of Oracle Database features that provide an additi…

Verilog HDL门级建模

⭐本专栏针对FPGA进行入门学习&#xff0c;从数电中常见的逻辑代数讲起&#xff0c;结合Verilog HDL语言学习与仿真&#xff0c;主要对组合逻辑电路与时序逻辑电路进行分析与设计&#xff0c;对状态机FSM进行剖析与建模。 &#x1f525;文章和代码已归档至【Github仓库&#xf…

数字电路设计:Logicly 最新版Crack

Logicly有效地教授逻辑门 数字电路 — 使用 Logicly 现代直观的用户界面支持拖放、复制/粘贴、缩放等功能&#xff0c;可快速轻松地设计电路。 通过暂停模拟并在您逐步推进时观察信号传播来控制调试。 不用担心学生计算机上的多个平台。在 Windows 和 macOS 上安装 创建引人入…