C语言基础数据结构——栈和队列

news2024/11/26 1:05:02

目录

1.栈

1.1栈的选型

1.2 实现代码

2.队列

2.1整体思路

2.2初始化和销毁

2.3出入队列

2.4取队列元素

2.5判断队列是否为空

2.6返回队列中元素个数 

2.7 Test 


1.栈

       栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。 栈中的数据元素遵守后进先出 LIFO Last In First Out )的原则。 就像给枪械弹夹装子弹一样
                             
                                                      (图片源于网络)
压栈:栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶 。(压入子弹)
出栈:栈的删除操作叫做出栈。 出数据也在栈顶 。(射出子弹)
Tips:

          数据结构中的栈和堆只是与内存中的栈和堆名字相同,

实际上并没有太多联系。

                                                                         

1.1栈的选型
栈的实现一般可以使用 数组或者链表实现 ,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的 代价比较小。
只有一头去变化,数组使用更快。

1.2 实现代码

初始化如下

typedef int STDataType;
typedef struct Stack {
	STDataType* arr;
	int capacity;
	int top;
}ST;
top作为栈顶
对top理解如下:
入栈的顺序就是1 2 3 4。

stackpush是唯一的放入数据的操作

top等于零时每插入一个元素就top++,指向栈顶元素的下一个

top指向栈顶元素的话必须让top=-1,如果还让top=0,那top=0时就不能区分此时是否有元素。

其余接口的实现(几乎等同于顺序表):

void STInit(ST* pst) {
	assert(pst);
	pst->top = 0;//初始状态可以是0也可以是-1
	pst->capacity = 0;
	pst->arr = NULL;
}

void STPush(ST* pst,STDataType x) {
	assert(pst);
	if (pst->top == pst->capacity) {
		int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;
		STDataType* tmp = (STDataType*)realloc(pst->arr, sizeof(STDataType) * newcapacity);
		if (!tmp) {
			perror("realloc failed!");
			exit(1);
		}
		pst->arr = tmp;
		pst->capacity = newcapacity;
	}
	pst->arr[pst->top++] = x;
}

void STPop(ST* pst) {
	assert(pst);
	assert(!STEmpty(pst));
	pst->top--;
}

STDataType STTop(ST* pst) {
	assert(pst);
	assert(!STEmpty(pst));
	return pst->arr[pst->top - 1];
}

//删除或者取栈顶数据时都需要判断栈是否为空
bool STEmpty(ST* pst) {
	assert(pst);
	return pst->top == 0;
}

int STSize(ST* pst) {
	assert(pst);
	return pst->top;
}

void STDestroy(ST* pst) {
	assert(pst);
	free(pst->arr);
	pst->arr = NULL;
	pst->capacity = 0;
	pst->top = 0;
}

 应用时,注意函数声明的顺序

栈的出入顺序不一定是固定的

答案是C

链式栈(单链表)建议将头作为栈顶,因为栈尾不方便进行删除操作

2.队列

  队列:

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

    恰好与栈不同。

2.1整体思路

     用链表实现(单链表就足够了)更为合适,因为当数据的出和入不在同一边,若使用数组,势必有一段需要O(n)的复杂度来整体移动数组。

                  

       对于涉及链表尾部的操作,为了不遍历链表,我们加入一个ptail变量。

       因为涉及改变phead(队头)和ptail(队尾)的值,所以我们首先考虑使用phead和ptail的二级指针来维护头尾指针

struct Queue{
    QNode** pphead;
    QNode** pptail; 
};

单纯的链表不需要结构体来包含整个链表,但是队列需要结构体。我们可以类比的来看,顺序表中间有很多个值需要配合使用,所以不仅每个元素是结构体,整个顺序表也是个结构体。队列需要同时有头指针和尾指针(实现尾差和头删的功能),单独放两个有关队列的指针不合适,所以将两个指针放在一起构成一个新的结构体

(链表相对于队列,类似数组相对于顺序表)

    同时,将phead和ptail放进一个结构体时,上文所讲的二级指针问题也不存在了,可以直接通过结构体指针去更改phead和ptail

左为队列,右为链表 

(目前为止的队列和单链表的对比,本质依然是新瓶装旧酒)

 以上的设计在函数QueueSize(求队列中元素个数)中复杂度依然是O(n),但是如果在初始化和定义队列时加上size这个变量(加数据++,减数据--),整体复杂度又变回了O(1)。

        因此,数据结构的设计是灵活多变的,只是有一些传统,在传统的结构上我们应该根据自己的需要来增加、修改(包括是否需要size,是否需要哨兵位,是否需要头尾两个指针)

typedef int QDataType;
typedef struct QueueNode {
	QDataType val;
	struct QueueNode* next;
}QNode;

//为了便于维护,我们将链表进行设计而得到队列
typedef struct Queue {
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

再按照之前链表的实现方式,实现队列的各个接口。

2.2初始化和销毁

有了整体思路,目前学习的数据结构都应该先实现初始化和销毁接口。

void QueueInit(Queue* pq) {
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

void QueueDestroy(Queue* pq) {
	assert(pq);
	QNode* cur = pq->phead;
	while (cur) {
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}
2.3出入队列

只有“入队列”一个函数需要malloc,所以我们不再封装BuyNewNode函数:

void QueuePush(Queue* pq, QDataType x) {
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL) {
		perror("malloc fail!");
	}

	newnode->val = x;
	newnode->next = NULL;

	if (pq->phead == NULL) {
		pq->phead = pq->ptail = newnode;
	}
	else {
		pq->ptail->next = newnode;
		pq->ptail = pq->ptail->next;
	}
	pq->size++;
}

  每次进入队列,ptail就向后走,所以出队列的时候就应该出phead位置的Node 

但是以上代码有问题如下:

1.size数值没有变化

2.红笔圈出的部分可能是 NULL ptr

3.如果只有一个节点的话,ptail即将变成野指针

因此,对于出队列,需要分成:多个节点,一个节点,零节点三种情况进行讨论

void QueuePop(Queue* pq) {
	assert(pq);
	//0个节点,使用此类暴力检查或者带if语句的温柔检查
	assert(pq->phead && pq->ptail);
	//一个节点,单独
	if (pq->phead->next == NULL) {
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else {
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;
}
2.4取队列元素

虽然从理论上讲,队列只会从phead处取出数据,但是在具体运用中(如银行人工窗口的取号系统),我们要能够从头或者尾取出数据。

                                                  

实现如下: 

QDataType QueueFront(Queue* pq) {
	assert(pq);
	//必须使用暴力检查,因为有返回值
	assert(pq->phead);

	return pq->phead->val;
}

QDataType QueueBack(Queue* pq) {
	assert(pq);
	//必须使用暴力检查,因为有返回值
	assert(pq->ptail);

	return pq->ptail->val;
}

不同于没有返回值的出入队列函数,这两个函数都有返回值,若不适用暴力检查   来报错,就会返回一个不正确的值,埋下bug的种子。

Tips:

有读者问为什么后面的这些函数没使用perror检查是否成功,原因如下:

1.perror适用于偏系统的调用失败(如 malloc realloc)

2.这些调用如果真的失败,那电脑很可能已经出了大问题(那么小的空间都开辟不出来!!)

                                                    

2.5判断队列是否为空
bool QueueEmpty(Queue* pq) {
	assert(pq);
	return pq->size == 0;
}

很巧妙的写法,如果siez为0就返回1,表示确实为空。

2.6返回队列中元素个数 
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

体现了size变量的好处。

2.7 Test 

不管是队列还是栈,检验方法都不同于之前的链表和顺序表,需要一个print函数来打印,队列和栈都是边使用边出,遍历一遍之后数据就全部出去了。

先检查入队列和取头功能

 最正确的测试方法:

数据用一个出一个。 

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

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

相关文章

AG32 MCU以太网应用实例demo

一. 前言 AGM32系列32位微控制器旨在为MCU用户提供新的自由度和丰富的兼容外设,以及兼容的引脚和功能。AG32F407系列产品具有卓越的品质,稳定性和卓越的价格价值。 AG32产品线支持其所有接口外设尽可能接近主流兼容性,并提供丰富的参考设计…

CSS学习(2)-盒子模型

1. CSS 长度单位 px :像素。em :相对元素 font-size 的倍数。rem :相对根字体大小,html标签就是根。% :相对父元素计算。 注意: CSS 中设置长度,必须加单位,否则样式无效&#xff…

Arduino IDE配置ESP8266开发环境

一、配置步骤 在Arduino IDE中配置ESP8266开发环境的详细步骤如下: 1.打开Arduino IDE,依次点击“文件”->“首选项”,在“附加开发板管理器网址”一栏添加ESP8266开发板的网址。常用的网址是: http://arduino.esp8266.com/s…

精细化运营从开店到抖音运营

本课程将覆盖精细化运营的各个方面,从实体店开店筹备到在抖音平台进行运营推广。学员将学习品牌定位、市场调研、社交媒体营销策略等内容,深入了解如何利用抖音等平台推动业务发展,提升品牌影响力。 课程大小:6.2G 课程下载&…

快速排序(数据结构)

1. 前言: 这两种排序经常使用,且在算法题中经常遇见。 这里我们简单分析讨论一下。 1. 快速排序 平均时间复杂度:O(nlogn) 最坏时间复杂度: O(n^2) 1.1. 左右向中遍历: 取最右侧4…

MechanicalSoup,一个非常实用的 Python 自动化浏览器交互工具库!

目录 前言 什么是 Python MechanicalSoup 库? 核心功能 使用方法 1. 安装 MechanicalSoup 库 2. 创建 MechanicalSoup 客户端 3. 打开网页并与之交互 实际应用场景 1. 网页自动化测试 2. 网络爬虫与数据提取 3. 网页自动化操作 4. 自动化填写和提交多个表单 5.…

数字IC实践项目(9)—SNN加速器的设计和实现(tiny_ODIN)

数字IC实践项目(9)—基于Verilog的SNN加速器 写在前面的话项目整体框图完整电路框图 项目简介和学习目的软件环境要求 Wave&CoverageTiming,Area & Power总结 写在前面的话 项目介绍: SNN硬件加速器是一种专为脉冲神经网…

【论文笔记合集】Transformers in Time Series A Survey综述总结

本文作者: slience_me 文章目录 Transformers in Time Series A Survey综述总结1 Introduction2 Transformer的组成Preliminaries of the Transformer2.1 Vanilla Transformer2.2 输入编码和位置编码 Input Encoding and Positional Encoding绝对位置编码 Absolute …

GPT-1, GPT-2, GPT-3, InstructGPT / ChatGPT and GPT-4 总结

1. GPT-1 What the problem GPT-1 solve? 在 GPT-1 之前,NLP 通常是一种监督模型。 对于每个任务,都有一些标记数据,然后根据这些标记数据开发监督模型。 这种方法存在几个问题:首先,需要标记数据。 但 NLP 不像 CV&…

[ Linux ] vim的使用(附:命令模式的常见命令列表)

1.下载安装 这里是在通过yum进行下载安装 yum install -y vim 2.了解 vim是一款编辑器,它具有多模式的特点 主要有:插入模式,命令模式,底行模式 3.使用 打开 vim 文件名 命令模式的常见命令列表 插入模式 按「 i 」切换…

MQ组件之RabbitMQ学习

MQ组件之RabbitMQ入门 同步调用和异步调用 在微服务架构中,服务之间的调用有同步调用和异步调用两种方式。 我们使用OpenFeign去调用是同步调用,同步调用的缺点很明显,在下图的场景中,支付完成后需要调用订单服务、仓库服务、短…

echarts实践总结(常用二):折线图(特点:渐变、面积区域)

目录 第一章 echarts基本使用 第二章 echarts实践——折线图 效果展示 第一章 echarts基本使用 Echarts常用配置项(详细入门)_echarts配置项手册-CSDN博客 柱状图案例: echarts实践总结(常用一):柱状图(特点:渐变色、点击缩放、…

JavaScript Object对象

创建object类型对象的三种方式 ES中object类型的对象大致由三种创建方式: 直接使用花括号创建使用function创建使用Object.create方法创建。 直接使用花括号创建 代码示例: var obj {v: 6,innerObj: {v: 7,},logV: function() {console.log(this.v…

C#求水仙花数

目录 1.何谓水仙花数 2.求三位数的水仙花数 3.在遍历中使用Math.DivRem方法再求水仙花数 1.何谓水仙花数 水仙花数(Narcissistic number)是指一个 n 位正整数,它的每个位上的数字的 n 次幂之和等于它本身。例如,153 是一个 3 …

慢sql优化

1.避免使用select *,而是明确列出需要的列, 2.小表驱动大表,in适用于左边大表,右边小表。 exists适用于左边小表,右边大表。 3.批量操作:如果每次插入数据库数据,都要连接一次数据库&#xf…

【LeetCode每日一题】2684. 矩阵中移动的最大次数

文章目录 [2684. 矩阵中移动的最大次数](https://leetcode.cn/problems/maximum-number-of-moves-in-a-grid/)思虑:代码: 2684. 矩阵中移动的最大次数 思虑: 1.将第一列的所有行坐标,用IntStream 来生成一个范围 [0, m) 内的整数…

一命通关递归

递归 简介 递归是我们在学C语言的时候,就已经接触到了的一个概念,相信大家的递归都是从这里开始的: 但是,在老师念ppt的时候,伴随着一些前轱辘不转后轱辘转的语言,我们往往都没有太去了解递归的工作原理和…

【C语言】字符函数与字符串函数以及内存函数 { 超详细攻略,一篇学会 }

今日分享:字符、字符串函数和内存函数 内存函数就是对内存进行操作的函数 字符串函数就是对字符串进行操作的函数 字符函数就是对字符进行操作的函数 str前缀的函数是字符串函数,头文件string.h mem前缀的函数是内存函数,头文件stdlib.h 字符…

【pynput】监控是否打开百度贴吧网页

文章目录 简介Demo 简介 有网友提过一个要求,用 Python 实现一个 电脑打开某网站就自动关机的功能。 想到的思路有两个: 【windows 平台】, 获取活动的窗口标题,如果标题里包含了某些网站名称, 那就使用关机命令 可以定时拉取标题, 也可以使…

代码算法训练营day9 | 28. 实现 strStr() 、459.重复的子字符串

day9: 28. 实现 strStr()KMP的主要应用:什么是前缀表:前缀表是如何记录的: 如何计算前缀表:构造next数组:1、初始化2、处理前后缀不相同的情况3、处理前后缀相同的情况 代码: 459.重复的子字符串…