【数据结构】栈和队列(c语言实现)(附源码)

news2025/1/21 21:56:29

🌟🌟作者主页:ephemerals__

🌟🌟所属专栏:数据结构

目录

一、栈

1.栈的概念与结构

2.栈的实现

2.1 栈的结构定义

2.2 方法的声明

2.3 方法的实现

2.3.1  初始化

2.3.2 销毁

2.3.3 判空

2.3.4 压栈

2.3.5 出栈

2.3.6 取栈顶元素

2.4 程序全部代码

二、队列

1.队列的概念与结构

2.队列的实现

2.1 队列的结构定义

2.2 方法声明

2.3 方法实现

2.3.1 初始化

2.3.2 判空

2.3.3 入队列

2.3.4 出队列

2.3.5 取队头数据和队尾数据

2.3.6 销毁队列

2.4 程序全部代码

总结


正文开始

一、栈

1.栈的概念与结构

栈的概念:栈是一种特殊的线性表,它不允许被遍历,并且只能够在固定的一端进行数据的插入或者删除操作。进行插入或删除操作的一端称之为栈顶,另一端称为栈底。由于数据的插入和删除在同一端,所以栈的数据元素遵从“先进后出”的原则。

2.栈的实现

        一般可以使用顺序表或者链表实现栈,在进行插入删除操作时满足先进后出原则即可。由于顺序表的尾插与尾删操作效率较高,接下来我们尝试用顺序表实现它。

2.1 栈的结构定义

栈的结构定义与顺序表完全相同,定义如下:

typedef int STDataType;

typedef struct Stack
{
	STDataType* arr;//起始指针
	int capacity;//栈的空间大小
	int top;//有效数据的个数
};

2.2 方法的声明

接下来是栈的一些常用方法:

//初始化
void STInit(ST* ps);

//销毁
void STDestroy(ST* ps);

//判空
bool STEmpty(ST* ps);

//压栈
void STPush(ST* ps, STDataType n);

//出栈
void STPop(ST* ps);

//取栈顶元素
STDataType STTop(ST* ps);

2.3 方法的实现

2.3.1  初始化
//初始化
void STInit(ST* ps)
{
	assert(ps);//防止传空指针
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}
2.3.2 销毁
//销毁
void STDestroy(ST* ps)
{
	assert(ps);//防止传空
	if (ps->arr != NULL)//防止多次free
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}
2.3.3 判空

        当栈中有效数据个数为0时,栈即为空。

//判空
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}
2.3.4 压栈

        进行压栈操作时,我们将结构成员top作为栈顶,在尾部插入数据

//压栈
void STPush(ST* ps, STDataType n)
{
	assert(ps);
	if (ps->capacity == ps->top)//若空间不够,则增容
	{
		int NewCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* tmp = (STDataType*)realloc(ps->arr, NewCapacity * sizeof(STDataType));
		if (tmp == NULL)//内存申请失败,则直接退出程序
		{
			perror("realloc");
			exit(1);
		}
		ps->arr = tmp;//成功则让arr指向申请好的空间
		ps->capacity = NewCapacity;
	}
	ps->arr[ps->top++] = n;//在栈顶插入数据
}
2.3.5 出栈

        为遵循“先进后出”原则,既然在尾部插入数据,那么删除数据时也要在尾部进行:

//出栈
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));//确保栈不为空
	ps->top--;
}
2.3.6 取栈顶元素

        对于栈这样的数据结构,我们不能进行遍历,但是可以访问到栈顶元素。代码如下:

//取栈顶元素
STDataType STTop(ST* ps)
{
	assert(ps && !STEmpty(ps));
	return ps->arr[ps->top - 1];//栈顶top-1的位置即为栈顶元素
}

2.4 程序全部代码

        关于栈实现的全部代码如下:

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

typedef int STDataType;

typedef struct Stack
{
	STDataType* arr;//起始指针
	int capacity;//栈的空间大小
	int top;//有效数据的个数
}ST;

//初始化
void STInit(ST* ps);

//销毁
void STDestroy(ST* ps);

//判空
bool STEmpty(ST* ps);

//压栈
void STPush(ST* ps, STDataType n);

//出栈
void STPop(ST* ps);

//取栈顶元素
STDataType STTop(ST* ps);

//初始化
void STInit(ST* ps)
{
	assert(ps);//防止传空指针
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}

//销毁
void STDestroy(ST* ps)
{
	assert(ps);//防止传空
	if (ps->arr != NULL)//防止多次free
	{
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}

//判空
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

//压栈
void STPush(ST* ps, STDataType n)
{
	assert(ps);
	if (ps->capacity == ps->top)//若空间不够,则增容
	{
		int NewCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* tmp = (STDataType*)realloc(ps->arr, NewCapacity * sizeof(STDataType));
		if (tmp == NULL)//内存申请失败,则直接退出程序
		{
			perror("realloc");
			exit(1);
		}
		ps->arr = tmp;//成功则让arr指向申请好的空间
		ps->capacity = NewCapacity;
	}
	ps->arr[ps->top++] = n;//在栈顶插入数据
}

//出栈
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));//确保栈不为空
	ps->top--;
}

//取栈顶元素
STDataType STTop(ST* ps)
{
	assert(ps && !STEmpty(ps));
	return ps->arr[ps->top - 1];//栈顶top-1的位置即为栈顶元素
}

二、队列

        学习了栈的特性和方法实现之后,我们再来了解一个新的数据结构:队列

1.队列的概念与结构

队列也是一种特殊的线性表,与栈相反,它只能在一端插入数据,另一端删除数据。插入数据的端叫做队尾;删除数据的一端叫做队头。因此,队列的数据元素遵从“先进先出”的原则。

2.队列的实现

        与栈相同,队列的实现也可以用顺序表或链表。由于顺序表两端的插入和删除操作要涉及到数据的全体移动,效率较低,我们就尝试用链表来实现队列

2.1 队列的结构定义

        队列的结构定义如下:

typedef int QDataType;

//定义队列的节点
typedef struct QueueNode
{
	QDataType data;//数据域
	struct QueueNode* next;//指针域
}QNode;

//定义队列
typedef struct Queue
{
	QNode* phead;//队头指针
	QNode* ptail;//队尾指针
	int size;//队列元素个数
}Queue;

2.2 方法声明

//初始化
void QInit(Queue* pq);

//判空
bool QEmpty(Queue* pq);

//入队列
void QPush(Queue* pq, QDataType n);

//出队列
void QPop(Queue* pq);

//取队头数据
QDataType QFront(Queue* pq);

//取队尾数据
QDataType QBack(Queue* pq);

//销毁
void QDestroy(Queue* pq);

2.3 方法实现

2.3.1 初始化
//初始化
void QInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;//将两指针都制为空
	pq->size = 0;//数据个数为0
}
2.3.2 判空

        当队头指针和队尾指针都指向空时,说明队列为空。

//判空
bool QEmpty(Queue* pq)
{
	assert(pq);
	return pq->phead == NULL && pq->ptail == NULL;
}
2.3.3 入队列

        入队列的操作与单链表尾插十分相似,由于有队尾指针,就无需遍历链表。注意其中的一些细节处理。

//入队列
void QPush(Queue* pq, QDataType n)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));//创建新节点
	if (newnode == NULL)//创建失败退出程序
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = n;
	newnode->next = NULL;
	if (QEmpty(pq))//队列为空的情况
	{
		pq->phead = pq->ptail = newnode;//队头与队尾都指向新节点
	}
	else//其他情况,尾插操作
	{
		pq->ptail->next = newnode;//将新节点连接在队尾之后
		pq->ptail = newnode;//队尾指向新节点
	}
	pq->size++;//空间大小+1
}
2.3.4 出队列

        出队列时,注意是在队头删除数据。

//出队列
void QPop(Queue* pq)
{
	assert(pq && !QEmpty(pq));//确保队列不为空
	if (pq->phead == pq->ptail)//队列只有一个元素的情况
	{
		free(pq->phead);//删除该节点
		pq->phead = pq->ptail = NULL;//两指针制为空
	}
	else
	{
		QNode* next = pq->phead->next;//先记录下一个节点
		free(pq->phead);
		pq->phead = next;//让队头指向下一个节点
	}
	pq->size--;//空间大小-1
}
2.3.5 取队头数据和队尾数据
//取队头数据
QDataType QFront(Queue* pq)
{
	assert(pq && !QEmpty(pq));
	return pq->phead->data;
}

//取队尾数据
QDataType QBack(Queue* pq)
{
	assert(pq && !QEmpty(pq));
	return pq->ptail->data;
}
2.3.6 销毁队列
//销毁
void QDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur != NULL)//遍历删除
	{
		QNode* next = cur->next;//记录后继节点
		free(cur);
		cur = next;//cur指针指向记录的节点
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

2.4 程序全部代码

        队列实现的全部代码如下:

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

typedef int QDataType;

//定义队列的节点
typedef struct QueueNode
{
	QDataType data;//数据域
	struct QueueNode* next;//指针域
}QNode;

//定义队列
typedef struct Queue
{
	QNode* phead;//队头指针
	QNode* ptail;//队尾指针
	int size;//队列元素个数
}Queue;

//初始化
void QInit(Queue* pq);

//判空
bool QEmpty(Queue* pq);

//入队列
void QPush(Queue* pq, QDataType n);

//出队列
void QPop(Queue* pq);

//取队头数据
QDataType QFront(Queue* pq);

//取队尾数据
QDataType QBack(Queue* pq);

//销毁
void QDestroy(Queue* pq);

//初始化
void QInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;//将两指针都制为空
	pq->size = 0;//数据个数为0
}

//判空
bool QEmpty(Queue* pq)
{
	assert(pq);
	return pq->phead == NULL && pq->ptail == NULL;
}

//入队列
void QPush(Queue* pq, QDataType n)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));//创建新节点
	if (newnode == NULL)//创建失败退出程序
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = n;
	newnode->next = NULL;
	if (QEmpty(pq))//队列为空的情况
	{
		pq->phead = pq->ptail = newnode;//队头与队尾都指向新节点
	}
	else//其他情况,尾插操作
	{
		pq->ptail->next = newnode;//将新节点连接在队尾之后
		pq->ptail = newnode;//队尾指向新节点
	}
	pq->size++;//空间大小+1
}

//出队列
void QPop(Queue* pq)
{
	assert(pq && !QEmpty(pq));//确保队列不为空
	if (pq->phead == pq->ptail)//队列只有一个元素的情况
	{
		free(pq->phead);//删除该节点
		pq->phead = pq->ptail = NULL;//两指针制为空
	}
	else
	{
		QNode* next = pq->phead->next;//先记录下一个节点
		free(pq->phead);
		pq->phead = next;//让队头指向下一个节点
	}
	pq->size--;//空间大小-1
}

//取队头数据
QDataType QFront(Queue* pq)
{
	assert(pq && !QEmpty(pq));
	return pq->phead->data;
}

//取队尾数据
QDataType QBack(Queue* pq)
{
	assert(pq && !QEmpty(pq));
	return pq->ptail->data;
}

//销毁
void QDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur != NULL)//遍历删除
	{
		QNode* next = cur->next;//记录后继节点
		free(cur);
		cur = next;//cur指针指向记录的节点
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

总结

        今天我们学习了栈和队列这两种数据结构:栈只能从同一端进行插入、删除操作,遵从“先进后出”原则;而队列只能从一端插入、另一端删除,遵从“先进先出”原则。栈和队列在一些场景的实用性很高,例如二叉树的层序遍历、快速排序的非递归实现等。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

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

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

相关文章

OpenCV||超详细的图像边缘检测

一、基本概念 1.图像边缘检测目的 特征提取&#xff1a;边缘是图像中亮度变化最显著的部分&#xff0c;它们通常对应于物体的轮廓、不同区域的边界等。通过边缘检测&#xff0c;可以从图像中提取出这些重要的特征信息&#xff0c;为后续处理如图像分割、目标识别等提供基础。 …

请你学习:前端布局3 - 浮动 float

1 标准流&#xff08;也称为普通流、文档流&#xff09; 标准流&#xff08;也称为普通流、文档流&#xff09;是CSS中元素布局的基础方式&#xff0c;它决定了元素在页面上的默认排列方式。这种布局方式遵循HTML文档的结构&#xff0c;不需要额外的CSS样式来指定元素的位置。…

python open cv(图像处理的基本操作)

概要图 1读取图像 cv2.imread() 函数是OpenCV库中用于读取图像文件的函数。它有两个参数&#xff1a; 文件名&#xff1a;这是第一个也是必需的参数&#xff0c;它指定了要读取的图像文件的路径和文件名。这个路径可以是相对路径&#xff0c;也可以是绝对路径。 标志&#xf…

小技巧大功效,「仅阅读两次提示」让循环语言模型超越Transformer++

在当前 AI 领域&#xff0c;大语言模型采用的主流架构是 Transformer。不过&#xff0c;随着 RWKV、Mamba 等架构的陆续问世&#xff0c;出现了一个很明显的趋势&#xff1a;在语言建模困惑度方面与 Transformer 较量的循环大语言模型正在快速进入人们的视线。 令人兴奋的是&am…

c++ string解析及其实现

因为字符串是固定长度&#xff0c;不好进行操作&#xff0c;因此c就用类将字符串进行了封装让其变得方便实用。 要深刻了解string&#xff0c;我们必须要熟练掌握类的使用&#xff0c;如果还有疑问可以看这一篇博客:c 类 (要学习类这一篇就够了 ) string #include<string…

Android 11(R)启动流程 初版

启动流程 bootloader会去启动android第一个进程Idle&#xff0c;pid为0&#xff0c;会对进程 内存管理等进行初始化。Idle还被称作swapper。Idle会去创建两个进程&#xff0c;一个是init&#xff0c;另外一个是kthread。 kthread会去启动内核&#xff0c;用户是由init进行启动。…

算法通关:017_1:二叉树及三种顺序的递归遍历

文章目录 题目思路代码运行结果 题目 二叉树及三种顺序的递归遍历 思路 代码 /*** Author: ggdpzhk* CreateTime: 2024-08-04** 二叉树及三种顺序的递归遍历* LeetCode 144. 二叉树的前序遍历* LeetCode 94. 二叉树的中序遍历* LeetCode 145. 二叉树的后序遍历* LeetCode 10…

sqli-labs靶场——第二关

1、判断注入类型 ?id1和?id2-1的页面一样所以是数字型 2、判断闭合类型 数字型没有闭合符号 3、order by查看有几列 当输入order by 4 时候页面变化&#xff0c;3的时候正常&#xff0c;所以是3列 4、union select联合查询查看回显 /sqli-labs/Less-2/?id-1 union sel…

ESP32使用MQTT协议通讯(EMQX)

一、背景介绍 前面完成了ESP32MicroPython环境的搭建01_ESP32 MicroPython开发环境搭建_eps32开发板-CSDN博客 现在想实现以下功能&#xff1a; 1.通过手机或电脑&#xff0c;远程给ESP32发送相关指令。 2.ESP32接到指令后&#xff0c;做出相应的高低电平输出。 这样就相当…

视频编辑SDK,底层架构合理,前端自定义程度高

如何高效、专业地制作出符合品牌形象、吸引目标受众的视频内容&#xff0c;成为了众多企业面临的共同挑战。美摄科技&#xff0c;作为视频编辑技术的先行者&#xff0c;以其卓越的视频编辑SDK&#xff08;Software Development Kit&#xff09;&#xff0c;为企业用户量身打造了…

进程状态都有哪些?

目录 前言&#xff1a; 进程的各个状态&#xff1a; 1、R状态&#xff08;进程运行状态&#xff09;和S状态&#xff08;休眠状态&#xff09; 2、T状态和t状态&#xff08;暂停进程&#xff09; 3、D状态&#xff08;磁盘休眠状态&#xff09; 4、Z状态&#xff08;僵尸状…

学习笔记 韩顺平 零基础30天学会Java(2024.8.2)

P447 五大运行时异常 P448 异常课堂练习 P449 异常处理机制 try-catch-finally throws(处理机制二选一)&#xff0c;如果没有显式处理异常&#xff0c;默认throws JVM处理异常直接输出异常信息&#xff0c;退出程序 P450 tryCatch 对于第一个细节&#xff0c;发生异常之后时try…

C语言快速入门及精通学习指南——手把手教零基础/新手入门(完整C语言学习笔记整理)

前言 作为一名拥有多年开发经验的码农&#xff0c;我的职业生涯涵盖了多种编程语言&#xff0c;包括 C 语言、C、C# 和 JavaScript。在这一过程中&#xff0c;我深刻地意识到扎实的基础对于编程学习的重要性&#xff0c;尤其是对于 C 语言这样一门核心语言来说。 出于对…

sgg快餐项目-3 项目

一、数仓架构 本项目的数据是事务数据&#xff0c;都存储在mysql数据库&#xff0c;如果是其他的项目&#xff0c;那数据可能会在文本、爬虫等。要使用相关的组件将数据导入到HDFS上。&#xff08;因为要把数据导入到hive做数据管理、存储和分析&#xff0c;而hive就是在hfds上…

2024年文件防泄密系统TOP3|遥遥领先的文件防泄密系统

古语有云&#xff1a;“密者&#xff0c;国之重器&#xff0c;不可不慎。” 在今日之数字化时代&#xff0c;信息的保密与安全&#xff0c;已然成为企业乃至国家生存与发展的基石。 随着数据泄露事件频发&#xff0c;文件防泄密系统的重要性愈发凸显。 2024年&#xff0c;随着…

数据存储与访问

一、文件存储读写 1.Android文件的操作模式 2.文件的相关操作方法 3.文件读写的实现 Android中的文件读写和Java中的文件I/O相同&#xff0c;流程也很简单&#xff0c;下面我们来写个简单的示例&#xff1a; PS:这里用的是模拟器&#xff0c;因为笔者的N5并没有root&#xf…

Go语言加Vue3零基础入门全栈班11 Go语言+gorm用户管理系统实战 2024年08月03日 课程笔记

概述 如果您没有Golang的基础&#xff0c;应该学习如下前置课程。 Golang零基础入门Golang面向对象编程Go Web 基础Go语言开发REST API接口_20240728Go语言操作MySQL开发用户管理系统API教程_20240729Redis零基础快速入门_20231227GoRedis开发用户管理系统API实战_20240730Mo…

工业人工智能真的能落地吗?

文章目录 前言Part1 聊聊技术1 人工智能、机器学习和深度学习的关系2 优化思想的一个案例 part2 聊聊业务3 工业人工智能与消费互联网人工智能的区别3.1 消费互联网中人工智能的应用3.2 为什么如此成熟的消费互联网人工智能扩展到工业场景那么难 4 工业互联网人工智能的发展方向…

wxPython中wx.adv.DatePickerCtrl用法

wx.adv.DatePickerCtrl是一个日期选择组件&#xff0c;支持键盘手工录入日期和弹出日历窗口选择日期两种方式。 一、组件样式 wx.adv.DP_SPIN &#xff1a; 只允许键盘手工录入和组件右侧上下箭头调整日期。 wx.adv.DP_DROPDOWN &#xff1a; 只允许健盘手工录入和组件右侧打开…

CentOS7 编译ffmpeg wasm库

1. 安装 emscripten 1)克隆 emsdk git clone https://github.com/emscripten-core/emsdk.git 2)cd 到emsdk 3)安装,按照官网的步骤(Download and install — Emscripten 3.1.65-git (dev) documentation) 4)验证 注意:如果emcc -v 报错: 提示是python脚本错误,是因为…