初级数据结构(三)——栈

  文中代码源文件已上传:数据结构源码

<-上一篇 初级数据结构(二)——链表        |        初级数据结构(四)——队列 下一篇->

1、栈的特性

1.1、函数栈帧简述

        即使是刚入门几天的小白,对栈这个字也应该略有耳闻。在操作系统层面,栈是系统在内存中划分的一整块连续的地址范围。并且系统对于单个程序在栈区的空间使用也是连续的。以一段代码举例:

void FunctionInside()
{/* ... */
}void Function_1()
{/* ... */
}void Function_2()
{/* ... */FunctionInside();/* ... */
}int main()
{Function_1();Function_2();return 0;
}

        在运行上述代码时,首先调用 main 函数,系统将为 main 函数在栈上单独开辟一块空间供 main 函数使用,这块空间称作 main 函数的栈帧。而当在 main 中调用 Function_1 时,系统又在 main 函数栈帧之上为 Function_1 开辟一块  Function_1 的栈帧。而随着 Function_1 函数调用结束, Function_1 的栈帧也被销毁,该栈帧的空间归还给操作系统。

        而进入 Function_2 函数时,系统同样为 Function_2 开辟一块栈帧。如果如上述代码中的  Function_1 和 Function_2 是连续调用的话, Function_2 栈帧很可能覆盖之前 Function_1 被销毁的栈帧空间。此时进入 Function_2 函数内部,又在内部调用 FunctionInside 函数。此时,系统将在 Function_2 栈帧之上为 FunctionInside 创建栈帧。

        随着 FunctionInside 调用结束 FunctionInside 栈帧也随之归还,今儿回到 Function_2 的栈帧空间。当然, Function_2 调用结束后, Function_2 的栈帧也将归还,并回到 main 函数的栈帧空间。最后随着程序结束,main 函数的栈帧也随之销毁。

         或许有点绕。但看上图也不难发现其规律,这就是栈的特性:后入先出、先入后出,也称为 LIFO ( Last In First Out )。

1.2、栈结构

        基于某些需求(如通过程序判断记录数学公式字符串的大中小括号是否配对、计算字符串中的数学公式的值、甚至最基本的数组倒序等),程序的数据处理需要用到后入先出的特性,对这类数据进行操作的结构就称为栈结构。

        栈结构的实现仍然是通过顺序表或者链表实现,只是在存取数据时必须遵循 LIFO 的规则。并且,栈结构不存在改和查的操作。如果要,必须将尾部数据至需要修改的数据之间的元素依次弹出后重新依次写入新数据,至于查,只允许查看尾部元素,一旦查看必须弹出。

        通常栈结构都用顺序表创建较为方便,因此以下便以顺序表的方式进行演示。同时最后附上链表实现栈结构的代码。

2、栈创建

2.1、文件结构

        与之前章节相同,依然创建三个文件,文件名如下:

        stack.h :用于创建项目的结构体类型以及声明函数;

        stack.c :用于创建栈各种操作功能的函数;

        main.c :仅创建 main 函数,用作测试。

 2.2、前期工作

        stack.h 中内容如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>//存储数据类型的定义及打印占位符预定义
#define DATAPRT "%d"
typedef int DATATYPE;//栈主体
typedef struct Stack
{DATATYPE* data;		//数据段size_t top;			//栈顶位置下标size_t capacity;	//开辟空间记录
}Stack;//函数声明---------------------------------//初始化
extern int StackInit(Stack*);
//销毁
extern void StackDestroy(Stack*);
//入栈
extern void StackPush(Stack*, DATATYPE);
//出栈
extern void StackPop(Stack*);

        然后 stack.c 中先创建初始化及销毁的两个函数。这里需要注意的是对结构体中的 top 值的操作。虽然这个值等同于顺序表中的 size ,但需要区分 top 是储存栈结构最后一个数据的下标。顺序表中的空表 size 则是等于 0 ,取顺序表的最后一个元素下标都是以 size - 1 进行操作,而对于栈来说,如果是空栈,top 则置为 -1 :

#include "stack.h"//初始化
int StackInit(Stack* st)
{//参数判断if (!st){fprintf(stderr, "Illegal Stack Address\n");return;}//初始开辟1个数据位st->data = (DATATYPE*)malloc(sizeof(DATATYPE) * 1);if (!st->data){fprintf(stderr, "Malloc Fail\n");return -2;}st->top = -1;st->capacity = 1;return 0;
}//销毁
void StackDestroy(Stack* st)
{//参数判断if (!st){fprintf(stderr, "Illegal Stack Address\n");return;}//释放free(st->data);st->data = NULL;st->top = -1;st->capacity = 0;
}

        在 main.c 中则预先创建一个结构体变量,无需进行其他操作。

#include "stack.h"int main()
{Stack st;return 0;
}

3、栈的数据操作

3.1、入栈

        入栈实际上就是顺序表的尾插,具体实现过程可以参考第一篇顺序表中的插入数据操作。这个功能实现起来并不复杂。此外跟顺序表一样,如果已开辟的空间不足则需要进行扩容操作。这里可以将扩容封装为一个函数,使代码看起来更简洁。此外,由于扩容函数仅在该代码文件内调用,可以加上 static 修饰。

        在 static.c 中加入以下代码:

//扩容
static void StackExpand(Stack* st)
{//参数判断if (!st){fprintf(stderr, "Illegal Stack Address\n");return;}DATATYPE* temp = (DATATYPE*)realloc(st->data, sizeof(DATATYPE) * st->capacity * 2);if (!temp){fprintf(stderr, "Realloc Fail\n");return;}st->data = temp;st->capacity *= 2;
}//入栈
void StackPush(Stack* st, DATATYPE data)
{//参数判断if (!st){fprintf(stderr, "Illegal Stack Address\n");return;}st->top++;//空间不足则扩容if ((st->top) >= (st->capacity)){StackExpand(st);}//入栈st->data[st->top] = data;
}

        然后在 main.c 中输入以下,并测试:

    StackInit(NULL);StackInit(&st);StackPush(&st, 1);StackPush(&st, 2);StackPush(&st, 3);StackPush(&st, 4);StackPush(&st, 5);

        测试无误,进行下一步。

3.2、出栈

        入栈等于尾插,那出栈自然就等同于尾删。对于出栈操作需要注意两点,栈是否为空以及空间回收。是否空栈只需判别 top 是否等于 -1 。至于空间回收,顺序表中也有提到,操作逻辑完全一致。

        这里为了代码可读性,将空栈判断封装为函数,将空间回收也一并封装为函数。 stack.c 中增加如下代码:

//收缩空间
static void StackContract(Stack* st)
{//参数判断if (!st){fprintf(stderr, "Illegal Stack Address\n");return;}DATATYPE* temp = (DATATYPE*)realloc(st->data, sizeof(DATATYPE) * st->capacity / 2);if (!temp){fprintf(stderr, "Realloc Fail\n");return;}st->data = temp;st->capacity /= 2;
}//空表判断 空表返回true
static bool StackIsEmpty(Stack* st)
{//参数判断if (!st){fprintf(stderr, "Illegal Stack Address\n");return true;}return (st->top == -1 ? true : false);
}//出栈
void StackPop(Stack* st)
{//参数判断if (!st){fprintf(stderr, "Illegal Stack Address\n");return;}//数据为空直接返回if (StackIsEmpty(st)){fprintf(stderr, "Stack Empty\n");return;}//出栈st->top--;//空间过剩则收缩空间if ((st->top) < (st->capacity) / 2){StackContract(st);}
}

3.3、附加功能

        出栈功能写完后,获取栈顶数据及打印输出栈顶数据的功能也一并加上。首先在 stack.h 中进行声明:

//打印
extern void StackPrint(Stack*);
//获取栈顶数据
extern DATATYPE StackGetTopData(Stack*);

        然后继续在 static.c 中补充这两个功能。

        这里需要注意,因为获取栈顶数据是有返回值的,因此如果空表或者传入空指针便不能简单地 return ,容易对返回值的接收产生误解。而不论 return 任何值,均有可能被误以为栈顶数据就是该值,因此这里以 assert 判定最佳:

//获取栈顶数据
DATATYPE StackGetTopData(Stack* st)
{//参数判断assert(st);//空表警告assert(!StackIsEmpty(st));//取数据并出栈DATATYPE data = st->data[st->top];StackPop(st);return data;
}//打印
void StackPrint(Stack* st)
{//参数判断if (!st){fprintf(stderr, "Illegal Stack Address\n");return;}//空表打印NULL后返回if (StackIsEmpty(st)){printf("NULL ");return;}//打印栈顶并出栈printf(DATAPRT " ", StackGetTopData(st));
}

        至此功能完毕。之后便是测试,同时也顺带测试之前出栈的函数。完整的 main 函数代码:

int main()
{Stack st;StackInit(NULL);StackInit(&st);StackPush(&st, 1);StackPush(&st, 2);StackPush(&st, 3);StackPush(&st, 4);StackPush(&st, 5);StackPrint(&st);        //5StackPrint(&st);        //4StackPrint(&st);        //3StackPrint(&st);        //2StackPrint(&st);        //1StackPrint(&st);        //NULLStackPrint(&st);        //NULLStackPush(&st, 1);StackPush(&st, 2);StackPush(&st, 3);StackPrint(&st);        //3StackDestroy(&st);StackPrint(&st);        //NULLreturn 0;
}

        测试结果如下: 

        至此栈结构便完成了。 

4、以链表实现栈

        链表实现栈的文件结构与顺序表实现栈的结构一致,根据以下代码自行测试研究。有链表的基础打底,这里实现起来也将十分轻松:

        stack.h :

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>#define DATAPRT "%d"typedef int DATATYPE;typedef struct StackNode
{DATATYPE data;struct StackNode* next;
}StackNode;typedef struct StackInfo
{StackNode* StackHead;size_t StackSize;
}StackInfo;//函数声明---------------------------------
//初始化
extern void StackInit(StackInfo*);
//销毁
extern void StackDestroy(StackInfo*);
//入栈
extern void StackPush(StackInfo*, DATATYPE);
//出栈
extern void StackPop(StackInfo*);
//获取栈顶数据
extern DATATYPE StackGetHead(StackInfo*);
//打印栈顶数据
extern void StackPrint(StackInfo*);

        stack.c :

#include "stack.h"//初始化
void StackInit(StackInfo* info)
{//参数有效判断if (!info){fprintf(stderr, "Illegal StackInformation Address\n");return;}//初始化info->StackHead = NULL;info->StackSize = 0;
}//销毁
void StackDestroy(StackInfo* info)
{//参数有效判断if (!info){fprintf(stderr, "Illegal StackInformation Address\n");}//空链表直接返回if (!info->StackSize){return;}//逐节点释放空间StackNode* currentNode = info->StackHead;while (currentNode){StackNode* destroyNode = currentNode;currentNode = currentNode->next;free(destroyNode);}info->StackHead = NULL;info->StackSize = 0;
}//判空
static bool StackIsEmpty(StackInfo* info)
{//参数有效判断if (!info){fprintf(stderr, "Illegal StackInformation Address\n");return;}return (info->StackSize == 0 ? true : false);
}//入栈
void StackPush(StackInfo* info, DATATYPE data)
{//参数有效判断if (!info){fprintf(stderr, "Illegal StackInformation Address\n");return;}StackNode* newNode = (StackNode*)malloc(sizeof(StackNode));if (!newNode){fprintf(stderr, "Malloc Fail\n");return;}newNode->data = data;newNode->next = info->StackHead;info->StackHead = newNode;info->StackSize++;
}//出栈
void StackPop(StackInfo* info)
{//参数有效判断if (!info){fprintf(stderr, "Illegal StackInformation Address\n");return;}if (StackIsEmpty(info)){fprintf(stderr, "Stack Empty\n");return;}StackNode* destroyNode = info->StackHead;info->StackHead = info->StackHead->next;info->StackSize--;free(destroyNode);
}//获取栈顶数据
DATATYPE StackGetHead(StackInfo* info)
{//参数有效判断assert(info);//空表警告assert(!StackIsEmpty(info));DATATYPE data = info->StackHead->data;StackPop(info);return data;
}//打印栈顶数据
void StackPrint(StackInfo* info)
{//参数有效判断if (!info){fprintf(stderr, "Illegal StackInformation Address\n");return;}if (StackIsEmpty(info)){printf("NULL ");return;}printf(DATAPRT " ", StackGetHead(info));
}

        main.c 的测试用例:

#include "stack.h"int main()
{StackInfo info;StackInit(NULL);StackInit(&info);StackPush(&info, 1);StackPush(&info, 2);StackPush(&info, 3);StackPush(NULL, 20);StackPush(&info, 4);StackPush(&info, 5);StackPrint(&info);StackPrint(&info);StackPrint(&info);StackPrint(&info);StackPrint(&info);StackPrint(&info);StackPrint(&info);StackPrint(&info);StackPush(&info, 1);StackPush(&info, 2);StackPush(&info, 3);StackPrint(&info);StackDestroy(&info);StackPrint(&info);return 0;
}

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

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

相关文章

基于YOLOv8深度学习的吸烟/抽烟行为检测系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

基于SSM实现的精品课程网站

一、系统架构 前端&#xff1a;jsp | js | css | jquery | bootstrap 后端&#xff1a;spring | springmvc | mybatis 环境&#xff1a;jdk1.7 | mysql | maven | tomcat 二、代码及数据库 三、功能介绍 01. 登录页 02. web端-首页 03. web端-视频教程 04. web端-资料…

RK3568全国产化多网口板卡带poe供电,支持鸿蒙麒麟系统

信迈XM-3568-01主板采用瑞芯微RK3568四核Cortex-A55 处理器&#xff0c;主频最高可达2.0GHz&#xff0c;效能有大幅提升最高可配8GB内存容量&#xff0c;频率高达1600MHz&#xff1b;支持全链路ECC&#xff0c;让数据更安全可靠配置双千兆自适应RJ45以太网口&#xff0c;并扩展…

stm32---串口使用

### 串口数据发送 #include <string.h> //先引用这个字符串操作库。char str[]" HALLO WORD "&#xff1b; //定义这个数组字符串。HAL_UART_Transmit(&huart2, str, strlen(str), 100); //&huart2,这里他是一个指针&#xff0c;所以要用取地址符…

在WPF窗口中增加水印效果

** 原理&#xff1a; ** 以Canvas作为水印显示载体&#xff0c;在Canvas中创建若干个TextBlock控件用来显示水印文案&#xff0c;如下图所示 然后以每一个TextBlock的左上角为中心旋转-30&#xff0c;最终效果会是如图红线所示&#xff1a; 为了达到第一行旋转后刚好与窗口…

App防止恶意截屏功能的方法:iOS、Android和鸿蒙系统的实现方案

防止应用被截图是一个比较常见的需求&#xff0c;主要是出于安全考虑。下面将分别为iOS&#xff08;苹果系统&#xff09;、Android&#xff08;安卓系统&#xff09;及HarmonyOS&#xff08;鸿蒙系统&#xff09;提供防止截屏的方法和示例代码。 在企业内部使用的应用中&…

深入解析Freemarker模板引擎及其在Spring Boot中的高级整合

目录 引言1. Freemarker1.1.什么是Freemarker1.2 Freemarker模板组成部分1.3.优点 2. Spring Boot整合Freemarker2.1 配置2.2 数据类型 3. 案例总结 引言 Freemarker作为一款强大的模板引擎&#xff0c;与Spring Boot的整合能够极大地提升Web应用的开发效率和灵活性。本篇博客…

探索 Vim:一个强大的文本编辑器

引言&#xff1a; Vim&#xff08;Vi IMproved&#xff09;是一款备受推崇的文本编辑器&#xff0c;拥有强大的功能和高度可定制性&#xff0c;提供丰富的编辑和编程体验。本文将探讨 Vim 的基本概念、使用技巧以及为用户带来的独特优势。 简介和发展 1. Vim 的简介和历史 V…

发布jar包到maven中央仓库

1. 环境 在网上找的很多文章中写得都有很多问题&#xff0c;这里记录一下最近一次成功地发布jar包到maven中央仓库的过程。并附带上每一个步骤官方的指导链接。 系统&#xff1a;mac&#xff08;windows系统在下载辅助工具时不太一样&#xff0c;在配置上和mac系统没有区别&…

docker部署go gin框架 Linux环境

目录 文章目的是什么 环境介绍 Linux 环境下 docker 部署 go gin 详细步骤 部署 gin 文章目的是什么 假设我们学习了 go 语言&#xff0c;在 Linux 上安装了 go 相关的程序&#xff0c;也能直接运行&#xff0c;使用以下命令&#xff1a; go run main.go 假如代码是这样的…

算法中的最优化方法课程复习

算法中的最优化方法课程复习 单模函数、拟凸函数、凸函数证明证明一个线性函数与一个凸函数的和也是凸的 梯度线性规划标准形式以及如何标准化标准形式常见标准化方法线性化技巧 单纯形法二次规划无约束优化Nelder-Mead线搜索FR共轭梯度法例题 优化算法的选择、停止准则算法选择…

electron命令下载失败,手动安装教程

现象&#xff1a;pnpm i electron, 一直卡在提示错误node install.js 一 、下载需要的electron版本 地址 二、下载完毕&#xff0c;解压压缩包&#xff0c; 进入项目的node_modules/electron文件夹&#xff0c;创建dist文件夹&#xff0c;将下载的zip包里的文件复制到dist…

链路追踪详解(四):分布式链路追踪的事实标准 OpenTelemetry 概述

目录 OpenTelemetry 是什么&#xff1f; OpenTelemetry 的起源和目标 OpenTelemetry 主要特点和功能 OpenTelemetry 的核心组件 OpenTelemetry 的工作原理 OpenTelemetry 的特点 OpenTelemetry 的应用场景 小结 OpenTelemetry 是什么&#xff1f; OpenTelemetry 是一个…

DevEco Studio 鸿蒙(HarmonyOS)项目结构

DevEco Studio 鸿蒙&#xff08;HarmonyOS&#xff09;项目结构 一、操作环境 操作系统: Windows 10 专业版 IDE:DevEco Studio 3.1 SDK:HarmonyOS 3.1 二、项目结构 创建简单的Hello World移动应用项目结构如下图 由上到下说明各个文件夹的作用 .hvigor&#xff1a;存…

阅读笔记——《UTOPIA: Automatic Generation of Fuzz Driverusing Unit Tests》

【参考文献】Jeong B, Jang J, Yi H, et al. UTOPIA: automatic generation of fuzz driver using unit tests[C]//2023 IEEE Symposium on Security and Privacy (SP). IEEE, 2023: 2676-2692.【注】本文仅为作者个人学习笔记&#xff0c;如有冒犯&#xff0c;请联系作者删除。…

智慧储能数字孪生:能源未来的智慧引擎

随着社会对清洁能源的需求不断增加&#xff0c;智能储能技术成为能源转型的关键驱动力。在这一领域中&#xff0c;数字孪生技术的应用为智慧储能带来了全新的可能性。数字孪生是指数字化、实时、可视化的模拟系统&#xff0c;通过复制现实世界中的对象或过程&#xff0c;为智能…

SpeechGPT领航:创新的130亿参数跨模态AI模型

引言 在人工智能的最新进展中&#xff0c;SpeechGPT以其130亿参数的规模和跨模态会话能力引起了业界的广泛关注。这一由复旦大学邱锡鹏教授团队开发的模型&#xff0c;不仅在技术层面上取得了重大突破&#xff0c;也为多模态人工智能&#xff08;AI&#xff09;的未来发展指明…

Selenium库自动化测试入门

前言 为什么要学selenium&#xff1f;&#xff1f;前面已经学了requests库我们会发现 对于绝大多数动态渲染的网页来说&#xff0c;用requests进行爬虫比较繁琐。 所以我们还是要学习一下selenium库&#xff0c;以帮助我们更高效的爬取网页。 环境&#xff1a; pychar 202…

机器学习算法新手入门指南

AI算法的种类在人工智能领域中非常丰富&#xff0c;而且多样化&#xff0c;AI算法利用数学、统计学和计算机科学等领域的原理和方法&#xff0c;通过模拟人类智能和学习能力来解决各种复杂的问题。 在监督学习领域&#xff0c;我们有经典的线性回归和逻辑回归算法&#xff0c;…

光栅化渲染:可见性问题和深度缓冲区算法

在前面第二章中&#xff0c;我们了解到&#xff0c;在投影点&#xff08;屏幕空间中的点&#xff09;的第三个坐标中&#xff0c;我们存储原始顶点 z 坐标&#xff08;相机空间中点的 z 坐标&#xff09;&#xff1a; 当一个像素与多个三角形重叠时&#xff0c;查找三角形表面上…