【数据结构】栈和队列的实现(C语言)

news2024/9/23 9:26:12

前言

栈和队列都是重要的线性结构,即在使用层面上收到限制而发挥特殊作用的线性表。掌握这两种结构在不同情景下亦会发挥重要作用。

定义

栈保持着后进先出的原则,正因如此在插入数据的时候要求只能从一段插入,称为栈顶;相反另一端就被称为栈底。而插入数据叫做进栈,删除数据叫做出栈,并且都只能在栈顶进行操作。

了解了栈的定义,现在就应着手于如何实现。应考虑清楚根据其性质我们要构造出一个怎样的结构来实现栈。

我们需要在一端插入或删除数据并及时对其访问,因此我们这里使用动态顺序表来实现。在这个结构中 top 由于初始值设定为 所以代表的是栈顶下一位的下标,也可以设置为-1,则代表的是栈顶的下标。使用capacity是为了时刻检测内存的大小便于及时开辟。

实现

这次要实现的就是下面几个函数,对应了该数据结构时经常使用的操作。

初始化

首先为内部储存的数据开辟一段空间,(这里我设置的初始值是4根据需要可以进行调整),开辟成功后,用结构体内的指针指向这片空间,同时为 top 与 capacity 赋上初始值。这样便完成了栈的初始化。

增删查改

入栈

一切开始前首先应该判断传入的指针是否为空指针,避免出现对空指针解引用的情况。后判断这个空间是否已满,不然增大空间的容量。之前说过 top 指向的是栈顶的下一位,因此直接赋值后再对 top ++ 就可以了。

 这里对容量的判定是分解到另一个函数内去的,其实直接写这个函数里面也是可以的毕竟只有这里需要判断容量是否满了。

从下标的角度上来说,top 是比栈顶多 的,由于下标的从 开始因此 top 正好可以表达此刻数据的数量。当 top 与 capacity 相等时,即开辟的空间已满。所以使用 realloc 调整新空间的大小(我把默认设为每次增加都是原来的两倍)。开辟成功后把新的地址给 data ,调整 capacity 为新容量的大小。

 出栈

出栈的操作相当的简便,只要 top-- 这样未来访问的时候就不会再访问到,也没有必要再对其进行赋值,因为下次插入的时候就会用新值覆盖原来的值。但是断言还是要记得断言的。

 获取栈顶元素

这里我们通过top访问到栈顶的元素之后并将其返回,就完成了栈顶元素的获取。

判空

这里使用了 bool 值要引用头文件 stdboo.h ,前面讲到 top 在栈中的意义就相当于内置数据的数量,即 top 不等于 则栈里就是还有数据的,所以返回false

销毁

栈结束使用后,要进行销毁避免内存泄漏。断言后释放data 后再将top跟capacity赋值为初始值,由于前面没有对结构体进行动态开辟,所以这里就不用释放。

队列

定义

队列与栈相反,栈是先进后出,而队列是先进先出,即从队头入队(插入数据)到队尾出队(删除数据),这个规则无法改变。

实现

在这里关于结构的选择就不是双选题了,因为无论选左边还是右边作为队尾,数组总是逃不过需要挪动数据的结果。反观链表就显得相对自由。

但只使用链表结构也不够完善,对于链表来说最痛苦的莫非是尾结点的访问了,但这里要频繁地对尾节点进行访问,不妨再使用一个结构体存储头尾结点,来表示一整个队列。这样处理后,若我们要对头结点进行更改也不需要使用到二级指针了,因为只是对结构体内的数据进行更改因此只需要结构体指针。为了统计大小更加方便也需要一个 size 表示大小。

 如下为要实现的函数。

初始化

一开始的队列本为空,只需要将头尾结点制空, size 归零。就完成了插入数据前的准备。

为空的判定

 为空的判定就相对简单,头尾结点正常情况下是同时为空的,也为了避免特殊情况即二者其中一个为空就可以判断整个队列是空的。

增删查改

入队

传入数值前先断言(养成好习惯),之后像链表那样开辟一个新节点并对其赋值。之后就要面临一个分岔口,队列是否为空,贸然地对头结点解引用就会出现错误,若为空则新节点就变成头结点,非空就链接在尾结点后,这之后自身成为尾结点。

出队

断言后出队需要做到的是:事先将下一结点保存起来,不然当前头结点释放后便无法访问到下一结点了。之后释放头结点,下一结点就变成了头结点,同时 size-- ,保证空间数量的准确避免越界访问。

访问队头队尾

直接访问头(尾)结点便是队头(尾)。

求大小

这里需要感谢我们在前面在结构内就存储了大小,不然还需要遍历链表才能得到队列的大小。

队列的销毁

同样有 malloc 就有 free ,在这里我们每个结点都是动态开辟出来的,所以都需要释放。就像链表释放那样,一边遍历一边释放。最后再把队列的结构体初始化就完成了。

好了,这次栈和队列的实现到这里就告一段落了。后面是这次的源码

源码

stack.h

#pragma once

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


typedef int STdatatype;
typedef struct Stack
{
	STdatatype* data;
	int top;
	int capacity;
}Stack;

//初始化
void StackInit(Stack* p);
//入栈
void StackPush(Stack* p, STdatatype data);
//依次出栈打印
void Stackprint(Stack* p);
//出栈
void StackPop(Stack* p);
//获取顶端元素
STdatatype StackTop(Stack* p);
//有效数据的个数
int StackSize(Stack* p);
//检测栈是否为空,不为空则返回0
bool StackEmpty(Stack* p);
//销毁栈
void StackDestroy(Stack* p);

stack.c

#include"stack.h"

void checkcapacity(Stack* p)
{
	STdatatype* newp;                       
	if (p->top == p->capacity)
	{
		newp = (STdatatype*)realloc(p->data, sizeof(Stack) * p->capacity * 2);
		if (newp == NULL)                 //开辟失败返回
		{
			perror(realloc);
			exit(-1);
		}
		p->data = newp;                   //把新开辟的空间给data
		p->capacity *= 2;                 //调整capacity为新值
	}
}

void StackInit(Stack* p)
{
	STdatatype* np = (STdatatype*)malloc(sizeof(STdatatype) * 4);   //开辟空间初始值为4
	if (np)
	{
		p->data = np;						//不为空赋值
	}
	p->top = 0;								//赋初值
	p->capacity = 4;
}

void StackPush(Stack* p, STdatatype x)
{
	assert(p);							//断言不为空
	checkcapacity(p);					//判断空间是否满了

	(p->data)[p->top] = x;				//赋值
	p->top++;
}

void Stackprint(Stack* p)
{
	int i = p->top - 1;
	while (i >= 0)
	{
		printf("%d ", (p->data)[i--]);
	}
	printf("\n");
}

void StackPop(Stack* p)
{
	assert(p);              //断言p不为空指针
	assert(p->top);         //断言栈内数据不为空
	p->top--;
}


STdatatype StackTop(Stack* p)
{
	assert(p);                           //断言

	int top = p->top - 1;                //找到栈顶
	return (p->data)[top];               //返回栈顶的数值
}

bool StackEmpty(Stack* p)
{
	assert(p);
	if (p->top != 0)
	{
		return false;
	}
	return true;
}

void StackDestroy(Stack* p)
{
	assert(p);                //断言
	assert(p->data);

	free(p->data);            //释放数据
	p->data = NULL;           //对data制空
	p->top = 0;               //回归初始值
	p->capacity = 0;
}

queue.h

#pragma once

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


typedef int Qdatatype;
typedef struct Qnode
{
	Qdatatype data;          //数据
	struct Queue* next;      //指向下个结点
}Qnode;

typedef struct Queue
{
	Qnode* head;             //队列的头
	Qnode* tail;             //队列的尾
	int size;                //大小
}Queue;

void Queueinit(Queue* p);                     //初始化
void Queuepush(Queue* p, Qdatatype x);        //入队
void Queuepop(Queue* p);                      //出队
bool QueueEmpty(Queue* p);                    //检测为空
Qdatatype Queuefront(Queue* p);               //访问队头的数据
Qdatatype Queueback(Queue* p);                //访问队尾的数据
int Queuesize(Queue* p);                      //求队列的大小

queue.c

#include "queue.h"

void Queueinit(Queue* p)
{
	p->head = NULL;           //头尾结点制空
	p->tail = NULL;
	p->size = 0;              //数据量为0
}

bool QueueEmpty(Queue* p)
{
	assert(p);
	return p->head == NULL || p->tail == NULL;    //二者一个为空则队列为空
}

void Queuepush(Queue* p, Qdatatype x)
{
	assert(p);                //断言

	Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));   //开辟新结点
	if (newnode == NULL)              //开辟失败返回
	{
		perror(malloc);
		exit(-1);
	}
	newnode->data = x;                //赋值
	newnode->next = NULL;
	if (p->head == NULL)              //队列为空的情况
	{
		p->head = p->tail = newnode;
	}
	else
	{
		p->tail->next = newnode;      //链接
		p->tail = newnode;
	}
	p->size++;                        //对size进行处理
}

void Queuepop(Queue* p)
{
	assert(p);                      //断言p不为空
	assert(!QueueEmpty(p));         //断言队列不为空

	Qnode* next = p->head->next;    //存储下一结点
	free(p->head);                  //释放当先头结点
	p->head = next;                 //下一结点变成头结点
	p->size--;                      //对size进行处理
}

Qdatatype Queuefront(Queue* p)
{
	assert(p);
	assert(!QueueEmpty(p));         //断言不为空

	return p->head->data;           //头结点存储的就是队头的数据
}

void QueueDestroy(Queue* p)
{
	assert(p);

	Qnode* cur = p->head;
	while (cur)
	{
		Qnode* next = cur->next;
		free(cur);
		cur = next;
	}
	p->head = p->tail = NULL;       //头尾结点制空
	p->size = 0;                    //大小归0
}

Qdatatype Queueback(Queue* p)
{
	assert(p);
	assert(!QueueEmpty(p));

	return p->tail->data;               //尾结点存储的就是队尾的数据
}

int Queuesize(Queue* p)
{
	assert(p);
	
	return p->size;             
}


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

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

相关文章

Arduino与Proteus仿真实例-电子相册仿真

电子相册仿真 本次实例将仿真如何从SD卡读取BMP格式图像并在ILI9341驱动器的LCD显示屏上显示。 1、仿真电路原理图 在仿真电路原理图中,SD卡通过SPI方式连接,ILI9341 LCD显示屏也是通过SPI方式连接。SD卡的CS引脚连接Arduino Mega2560的SS引脚(Pin10),显示屏的CS引脚连接…

竞赛——【蓝桥杯】2022年11月第十四届蓝桥杯模拟赛第一期Java

1、二进制位数 问题描述 十进制整数 2 在十进制中是 1 位数&#xff0c;在二进制中对应 10 &#xff0c;是 2 位数。 十进制整数 22 在十进制中是 2 位数&#xff0c;在二进制中对应 10110 &#xff0c;是 5 位数。 请问十进制整数 2022 在二进制中是几位数&#xff1f; 答案…

数据管理技术的发展经历了哪三个阶段

数据管理技术是指对数据进行分类、组织、编码、查询和维护&#xff0c;它是数据处理的中心问题&#xff1b; 随着计算机技术的不断发展&#xff0c;在应用需求的推动下&#xff0c;在计算机硬件、软件发展的基础上数据管理技术经历了人工管理、文件管理、数据库管理3个阶段。每…

CircRNA+代谢组如何冲击22分高分文章?

发表期刊&#xff1a;Cell Metabolism 影响因子&#xff1a;22.415 发表时间&#xff1a;2019年5月30日 合作单位&#xff1a;中国科学技术大学 Biotree协助客户中国科学技术大学吴缅教授和安徽医科大学胡汪来教授及其研究团队在国际顶尖期刊《Cell Metabolism》上发表名为…

跑通yolox-s官方源码(可与yolov5s做对比试验)

目录&#xff08;一&#xff09;一些参考链接&#xff08;二&#xff09;YOLOX训练自己的数据集&#xff0c;修改相应代码0. 自己的数据集文件夹排布1. 修改类别标签和数量1.1 修改类别标签 yolox/data/datasets/voc_classes.py1.2 修改类别数量 exps/example/yolox_voc/yolox_…

Quartus 实例应用(2)——创建设计工程

Quartus II 简易教程一、概念回顾二、创建设计工程2.1 创建工程2.2 新建HDL文件三、编译工程四、下载一、概念回顾 什么是综合&#xff1f;什么是设计&#xff1f; 答&#xff1a;综合就是编译&#xff0c;设计就是布局布线。 二、创建设计工程 2.1 创建工程 打开Quartus I…

用vuex对token/refresh_token 进行管理以及处理token过期问题

这里介绍对token的处理 问题1&#xff1a;token数据或者其他数据&#xff0c;存在vuex仓库中&#xff0c;刷新会丢失状态 。 问题2&#xff1a;数据只存在本地&#xff0c;数据变化了&#xff0c;相关的视图并不会更新。 Vuex 容器中的数据只是为了方便在其他任何地方能方便的获…

【EC200U】 基站定位

EC200U 基站定位什么是基站基站定位cellLocator - 基站定位调用获取坐标token 秘钥申请运行测试我们之前玩了GPS了【EC200U】GPS定位 GPS 精度高&#xff0c;但比较费电&#xff0c;首次搜索卫星定位时间比较长&#xff0c;当卫星信号覆盖不好&#xff0c;比如室内&#xff0c;…

gdb-dashboard的简单使用

问题描述 众所周知&#xff0c;随着IDE的普及&#xff0c;越来越多的人不习惯命令行调试&#xff0c;但是Linux开发又绕不开GDB&#xff0c;所以有大佬开发了程序用来显示常见的一些调试参数&#xff0c;gdb-dashboard是比较热门的程序之一。附链接。 使用参考 神仙GDB调试工…

Golang入门笔记(7)—— 函数 func

函数是什么&#xff1f; 函数&#xff1a;为完成某一功能的程序的语句指令的集合。 感觉重头戏逐渐到来了&#xff0c;让我们好好的&#xff0c;认真对待它吧&#xff01;为什么要使用函数&#xff1f; 为了提高代码的复用&#xff0c;减少代码的冗余&#xff0c;提高代码维护性…

前端框架 React 学习总结

目录 一、React在HTML里的运用 二、React框架的常用操作 1、JSX基础语法规则 2、state数据的使用 3、生命周期 4、数据的双向绑定与Ref 5、PropTypes验证规则 6、React里的插槽 7、错误边界 8、直接操作refs元素 9、高阶组件的运用案例 10、性能优化 11、Hook生命…

Linux中安装mysql8

一、下载MySQL到 /usr/local/src 1、打开官网下载界面 MySQL :: Download MySQL Community Server (Archived Versions) 2、选择某个版本的下载地址 以https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.30-linux-glibc2.12-x86_64.tar.xz这个地址作为例子 …

Kubernetes 系统化学习之 持久存储篇(五)

1. ConfigMap ConfigMap 是一种 API 对象&#xff0c;用来将非机密性的数据保存到键值对中。使用时&#xff0c;Pods 可以将其用作环境变量、命令行参数或者存储卷中的配置文件。ConfigMap 的主要作用就是为了让镜像和配置文件解耦&#xff0c;以便实现镜像的可移植性和可复用性…

day07 Elasticsearch搜索引擎3

day07 Elasticsearch搜索引擎3 1、数据聚合 聚合&#xff08;aggregations&#xff09;可以让我们极其方便的实现对文档数据的统计、分析、运算。例如&#xff1a; 什么品牌的手机最受欢迎&#xff1f;这些手机的平均价格、最高价格、最低价格&#xff1f;这些手机每月的销售…

java设计模式之观察者模式

一&#xff1a;观察者模式 1.什么是观察者模式? 观察者模式是一种行为设计模式&#xff0c; 允许你定义一种订阅机制&#xff0c; 可在对象事件发生时通知多个 “观察” 该对象的其他对象。 观察者模式的基本介绍 又被称为发布-订阅&#xff08;Publish/Subscribe&#xff09…

从阿里云容器攻防矩阵API安全生命周期,看如何构建金融安全云原生平台

【编者按】云原生技术正在助力银行通过差异化业务进行创新&#xff0c;却也带来了由于研发/运维人员对新架构不熟悉所导致的基础设施风险、业务风险及数据暴露风险。如何在飞速更迭的技术环境下保持业务持续发展&#xff0c;同时保证业务整体的安全性&#xff0c;满足不断增强的…

设计模式-装饰器模式

装饰器模式也称为包装模式是指在不改变原有对象的基础上&#xff0c;将功能附加到对象上&#xff0c;提供比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式 装饰器模式的核心是功能扩展,使用装饰器模式可以透明且动态的扩展类的功能 装饰器模式通用UML类图 主要角…

培训学校管理系统之家校管理

近年来&#xff0c;伴随着信息技术的进步。除了不少学校开展了数字化校园的建设&#xff0c;一些培训机构也在使用数字化系统进行管理。同时&#xff0c;由于招生数量的不断增长&#xff0c;如何解决学生、教师、家长三者之间的联系沟通问题&#xff0c;促进教学管理任务的有效…

排序(详解)

排序排序的概念常见的排序算法直接插入排序希尔排序&#xff08;缩小增量排序&#xff09;选择排序堆排序冒泡排序快排&#xff08;递归&#xff09;霍尔版本挖坑法前后指针法快排&#xff08;非递归&#xff09;归并排序&#xff08;递归&#xff09;归并排序&#xff08;非递…

区块链动态化监管方案

前言 监控运维模块是区块链BaaS的核心模块之一&#xff0c;我们针对联盟链、主机和系统等多个监控对象提供丰富的监控指标。通过BaaS提供的综合监控大屏&#xff0c;用户可直观洞悉区块链业务全局&#xff0c;实现7*24小时监控全覆盖。 但随着BaaS业务的扩展&#xff0c;对监…