当前位置: 首页 > news >正文

数据结构之《栈》

在之前我们已经学习了数据结构中线性表里面的顺序表与链表,了解了如何实现顺序表与链表增、删、查、该等功能。其实在线性表中除了顺序表和链表还有其他的类别,在本篇中我们就将学习另外一种线性表——栈,在通过本篇的学习后,你将会对栈的结构有充足的了解,在了解完结构后我们还将进行栈的实现。一起加油吧!!!


1.栈的概念与结构

栈:一种特殊的线性表其只允许在固定的一端进行插入删除元素操作。进行数据插入和删除操作的⼀端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出(先进后出)LIFO(Last In First Out的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。

那么栈的底层结构是基于什么的呢?其实基于数组还是基于单链表或者是双链表都是可以的,那么哪一种是最优解呢?接下来我们就来分析基于不同底层结构的利与弊

若底层结构基于数组,则可以让数组的低地址处表示栈底,数组的高地址处表示栈顶,使用一个size变量来表示数组有效数据个数,这样就可以要入栈时直接在size位置插入数据,之后size加一;出栈时就可以直接让size减一,这样就可以让数组的有效个数减一。以上使用数组来作为栈的底层结构时间复杂度为O(1)

在数组当中虽然每次在空间不足时都会以之前二倍的方式申请新空间,这时可能会存在内存的浪费,当时由于数组在内存中是连续存放的,这就使得在入栈和出栈不需要再每次都申请空间,而且数组在取数据的时候可能缓存中就有不一定要在主存中取。

若栈的底层结构为单链表,如果是在单链表尾部表示栈顶,头部表示栈底这样在无论是在入栈还是在出栈时因为都需要通过遍历来找到单链表的尾节点,所以时间复杂度都为O(N)。所以这时就要改变栈顶为单链表的头部,栈底为单链表的尾部,这时在入栈和出栈时就不需要遍历单链表,时间复杂度就都为O(1)

但是单链表每次在入栈和出栈时都要申请节点和销毁节点,并且由于单链表每个节点空间在内存当中不一定是连续的,所以每次访问数据都需要区主存取。

而在双链表中无论是让头部作为栈顶,尾部作为栈底还是让尾部作为栈顶,头部作为栈底,由于双链表每个节点内部都有指向前一个节点和下一个节点的指针,所以在入栈和出栈时都不需要遍历链表,所以入栈和出栈时的时间复杂度都为O(1)

但是双链表在每个节点内部相比单链表都多一个指针变量,在32位环境下每个节点大小就会多4个字节;在64位环境下每个节点大小就会多8字节。因此使用双链表就会造成更多的内存损耗。

通过以上的分析可以发现无论栈的底层结构是基于数组还是单链表还是双链表在入栈和出栈时的时间复杂度都为O(1),但是综合其他因素使用数组是最优解 


 

2.栈的实现

在实现栈的代码内在Stack.h头文件内定义栈的结构以及对各种功能函数进行声明,在Stack.c文件内实现各个函数,在test.c文件内对实现的各函数进行测试


2.1栈结构的定义

在Stack.h中创建一个结构体来定义栈的结构,在该结构体中的成员变量和顺序表中基本是相同的,只不过将顺序表中表示有效元素个数的size改名位top,让top来表示栈顶

//定义栈的结构
typedef int STDataType;
typedef struct stack
{STDataType* a;int capacity;//栈空间大小int top;//栈顶
}stack;

2.2栈的初始化

要完成栈的初始化函数首先要在Stack.h完成初始化函数的声明

//初始化栈
void stackInit(stack* ps);

将该函数命名为stackInit,函数的参数就为指向结构体的指针

接下来就是在stack.c内完成初始化函数的实现
由于ps指针是指向结构体的指针,所以该指针不能为空,所以要对ps进行assert断言

//初始化栈
void stackInit(stack* ps)
{assert(ps);ps->a = NULL;ps->top = ps->capacity = 0;
}

2.3检测栈是否为空

要完成检测栈是否为空函数首先要在Stack.h完成该函数函数的声明

//检测栈是否为空
bool stackEmpty(stack* ps);

将该函数命名为stackEmpty,函数的参数就为指向结构体的指针,函数的返回类型为布尔类型

接下来就是在stack.c内完成检测栈是否为空函数的实现
由于ps指针是指向结构体的指针,所以该指针不能为空,所以要对ps进行assert断言
在该函数中若数组内的有效个数top为0函数就会返回true,不为0就返回false

//检测栈是否为空
bool stackEmpty(stack* ps)
{assert(ps);return ps->top==0;
}

2.4入栈

要完成入栈函数首先要在Stack.h完成初始化函数的声明

void stackPush(stack* ps,STDataType x);

将该函数命名为stackPush,函数的参数有两个,第一个为指向结构体的指针,第二个为要进入栈的数据

接下来就是在stack.c内完成入栈函数的实现
由于在入栈时数组的空间可以已经满了,因此在进行插入数据之前要判断数组的有效个数是否与数组的空间大小相同,如果相同就需要进行增容。增容的代码就和之前的顺序表检测数组空间大小函数一样。
在调整完空间大小之后的入栈就和顺序表中得尾插一样,ps->a[ps->top++] = x一句代码就可以实现

//入栈
void stackPush(stack* ps,STDataType x)
{assert(ps);if (ps->top == ps->capacity){int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail!");exit(1);}ps->a=tmp;ps->capacity = newcapacity;}ps->a[ps->top++] = x;
}

2.5出栈 

要完成出栈函数首先要在Stack.h完成出栈函数的声明

//出栈
void stackPop(stack* ps);

将该函数命名为stackPop,函数的参数为指向结构体的指针

接下来就是在stack.c内完成出栈函数的实现
由于ps指针是指向结构体的指针,所以该指针不能为空,所以要对ps进行assert断言
同时在出栈时栈不能为空,所以要对satckEmpt(ps)进行assert断言

在出栈就和顺序表中得尾删一样将top减一就可

//出栈
void stackPop(stack* ps)
{assert(ps);assert(!stackEmpty(ps));--ps->top;
}

2.6取栈顶元素

要完成取栈顶元素函数首先要在Stack.h完成取栈顶元素函数的声明

//获取栈顶元素
STDataType stackTop(stack* ps);

将该函数命名为stackTop,函数的参数为指向结构体的指针,函数的返回值就为栈顶的元素

接下来就是在stack.c内完成取栈顶原始函数的实现
由于ps指针是指向结构体的指针,所以该指针不能为空,所以要对ps进行assert断言
同时在出栈时栈不能为空,所以要对satckEmpt(ps)进行assert断言
由于top指向数组的尾元素的后一位,所以只要将size-1就可以得到栈顶元素

//获取栈顶元素
STDataType stackTop(stack* ps)
{assert(ps);assert(!stackEmpty(ps));return ps->a[ps->top - 1];
}

2.7获取栈中有效元素个数

要完成获取栈中有效元素个数函数首先要在Stack.h完成获取栈中有效元素个数函数的声明

//获取栈中有效元素个数
int stackSize(stack* ps);

将该函数命名为stackSize,函数的参数为指向结构体的指针,函数的返回值就为栈的元素个数

接下来就是在stack.c内完成获取栈中有效元素个数函数的实现
由于ps指针是指向结构体的指针,所以该指针不能为空,所以要对ps进行assert断言
因为在结构体中的top就为数组的有效元素个数,所以在该函数中返回ps->top即可

//获取栈中有效元素个数
int stackSize(stack* ps)
{assert(ps);return ps->top;
}

2.8销毁栈

要完成销毁栈函数首先要在Stack.h完成销毁栈函数的声明

//销毁栈
void stackDestory(stack* ps);

将该函数命名为stackDestory,函数的参数为指向结构体的指针

接下来就是在stack.c内完成销毁栈函数的实现
由于ps指针是指向结构体的指针,所以该指针不能为空,所以要对ps进行assert断言
在销毁时,由于栈的空间是由realloc申请来的,所以在使用完后要用free来释放内存空间,并且在释放后将指针ps->a置为NULL,且将top和capacity都赋值为0

//销毁栈
void stackDestory(stack* ps)
{assert(ps);if (ps->a){free(ps->a);}ps->a = NULL;ps->top = ps->capacity = 0;
}

2.9栈的打印

因为在栈和顺序表以及链表不同,由于栈只能在一端进和出所以栈是不能遍历,所以我们不能通过创建一个函数来遍历栈

所以要打印栈就只能在test.c内调用相关函数来实现
例如以下栈先依次入栈1,2,3,4,之后要打印就通过以下循环来实现

#include"stack.h"void test()
{stack ps;stackInit(&ps);stackPush(&ps, 1);stackPush(&ps, 2);stackPush(&ps, 3);stackPush(&ps, 4);while (!stackEmpty(&ps)){STDataType p = stackTop(&ps);printf("%d ", p);stackPop(&ps);}stackDestory(&ps);
}int main()
{test();return 0;
}

3.栈的实现完整代码 

stack.h

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>
//定义栈的结构
typedef int STDataType;
typedef struct stack
{STDataType* a;int capacity;//栈空间大小int top;//栈顶
}stack;//初始化栈
void stackInit(stack* ps);
//销毁栈
void stackDestory(stack* ps);
//检测栈是否为空
bool stackEmpty(stack* ps);
//入栈
void stackPush(stack* ps,STDataType x);
//出栈
void stackPop(stack* ps);
//获取栈顶元素
STDataType stackTop(stack* ps);
//获取栈中有效元素个数
int stackSize(stack* ps);

stack.c

#include"stack.h"//初始化栈
void stackInit(stack* ps)
{assert(ps);ps->a = NULL;ps->top = ps->capacity = 0;
}//销毁栈
void stackDestory(stack* ps)
{assert(ps);if (ps->a){free(ps->a);}ps->a = NULL;ps->top = ps->capacity = 0;
}//检测栈是否为空
bool stackEmpty(stack* ps)
{assert(ps);return ps->top==0;
}//入栈
void stackPush(stack* ps,STDataType x)
{assert(ps);if (ps->top == ps->capacity){int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail!");exit(1);}ps->a=tmp;ps->capacity = newcapacity;}ps->a[ps->top++] = x;
}//出栈
void stackPop(stack* ps)
{assert(ps);assert(!stackEmpty(ps));--ps->top;
}//获取栈顶元素
STDataType stackTop(stack* ps)
{assert(ps);assert(!stackEmpty(ps));return ps->a[ps->top - 1];
}//获取栈中有效元素个数
int stackSize(stack* ps)
{assert(ps);return ps->top;
}

相关文章:

数据结构之《栈》

在之前我们已经学习了数据结构中线性表里面的顺序表与链表&#xff0c;了解了如何实现顺序表与链表增、删、查、该等功能。其实在线性表中除了顺序表和链表还有其他的类别&#xff0c;在本篇中我们就将学习另外一种线性表——栈&#xff0c;在通过本篇的学习后&#xff0c;你将…...

Vue3基础语法

一&#xff1a;创建Vue3工程&#xff08;适用Vite打包工具&#xff09; Vite官网&#xff1a;Home | Vite中文网 (vitejs.cn) 直接新建一个文件夹&#xff0c;打开cmd运行&#xff1a; npm create vitelatest 选择Vue和TS语言即可 生成一个项目。 Vue3的核心语法&#xff…...

【Python】基础学习技能提升代码样例4:常见配置文件和数据文件读写ini、yaml、csv、excel、xml、json

一、 配置文件 1.1 ini 官方-configparser config.ini文件如下&#xff1a; [url] ; section名称baidu https://www.zalou.cnport 80[email]sender ‘xxxqq.com’import configparser # 读取 file config.ini # 创建配置文件对象 con configparser.ConfigParser() # 读…...

JavaScript基础——JavaScript调用的三种方式

JavaScript简介 JavaScript的作用 JavaScript的使用方式 内嵌JS 引入外部js文件 编写函数 JavaScript简介 JavaScript&#xff08;简称“JS”&#xff09;是一种具有函数优先的轻量级&#xff0c;解释型或即时编译型的编程语言。它是Web开发中最常用的脚本语言之一&#x…...

ITSS:IT服务工程师

证书亮点&#xff1a;适中的费用、较低的难度、广泛的应用范围以及专业的运维认证。 总体评价&#xff1a;性价比良好&#xff01; 证书名称&#xff1a;ITSS服务工程师 证书有效期&#xff1a;持续3年 培训要求&#xff1a;必须参加培训&#xff0c;否则将无法参与考试 发…...

鸿蒙开发——axios封装请求、拦截器

描述&#xff1a;接口用的是PHP&#xff0c;框架TP5 源码地址 链接&#xff1a;https://pan.quark.cn/s/a610610ca406 提取码&#xff1a;rbYX 请求登录 HttpUtil HttpApi 使用方法...

Scikit-Learn中的分层特征工程:构建更精准的数据洞察

Scikit-Learn中的分层特征工程&#xff1a;构建更精准的数据洞察 在机器学习中&#xff0c;特征工程是提升模型性能的核心技术之一。Scikit-Learn&#xff08;简称sklearn&#xff09;&#xff0c;作为Python中广受欢迎的机器学习库&#xff0c;提供了多种方法来进行特征工程&…...

CSOL遭遇DDOS攻击如何解决

CSOL遭遇DDOS攻击如何解决&#xff1f;在错综复杂的数字网络丛林中&#xff0c;《Counter-Strike Online》&#xff08;简称CSOL&#xff09;犹如一座坚固的堡垒&#xff0c;屹立在游戏世界的中心&#xff0c;吸引着无数玩家的目光与热情。这座堡垒并非无懈可击&#xff0c;DDo…...

基于python的BP神经网络红酒品质分类预测模型

1 导入必要的库 import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from sklearn.preprocessing import LabelEncoder from tensorflow.keras.models import Sequential from tenso…...

Kylin与Spark:大数据技术集成的深度解析

引言 在大数据时代&#xff0c;企业面临着海量数据的处理和分析需求。Kylin 和 Spark 作为两个重要的大数据技术&#xff0c;各自在数据处理领域有着独特的优势。Kylin 是一个开源的分布式分析引擎&#xff0c;专为大规模数据集的 OLAP&#xff08;在线分析处理&#xff09;查…...

⌈ 传知代码 ⌋ 利用scrapy框架练习爬虫

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…...

深入了解 Python 面向对象编程(最终篇)

大家好&#xff01;今天我们将继续探讨 Python 中的类及其在面向对象编程&#xff08;OOP&#xff09;中的应用。面向对象编程是一种编程范式&#xff0c;它使用“对象”来模拟现实世界的事务&#xff0c;使代码更加结构化和易于维护。在上一篇文章中&#xff0c;我们详细了解了…...

手把手教你实现基于丹摩智算的YoloV8自定义数据集的训练、测试。

摘要 DAMODEL&#xff08;丹摩智算&#xff09;是专为AI打造的智算云&#xff0c;致力于提供丰富的算力资源与基础设施助力AI应用的开发、训练、部署。 官网链接&#xff1a;https://damodel.com/register?source6B008AA9 平台的优势 &#x1f4a1; 超友好&#xff01; …...

SSH相关

前言 这篇是K8S及Rancher部署的前置知识。因为项目部署测试需要&#xff0c;向公司申请了一个虚拟机做服务器用。此前从未接触过服务器相关的东西&#xff0c;甚至命令也没怎么接触过&#xff08;接触最多的还是git命令&#xff0c;但我日常用sourceTree&#xff09;。本篇SSH…...

mysql超大分页问题处理~

大家好&#xff0c;我是程序媛雪儿&#xff0c;今天咱们聊mysql超大分页问题处理。 超大分页问题是什么&#xff1f; 数据量很大的时候&#xff0c;在查询中&#xff0c;越靠后&#xff0c;分页查询效率越低 例如 select * from tb_sku limit 0,10; select * from tb_sku lim…...

Gitlab以及分支管理

一、概述 Git 是一个分布式版本控制系统&#xff0c;用于跟踪文件的变化&#xff0c;尤其是源代码的变化。它由 Linus Torvalds 于 2005 年开发&#xff0c;旨在帮助管理大型软件项目的开发过程。 二、Git 的功能特性 Git 是关注于文件数据整体的变化&#xff0c;直接会将文件…...

探索Axure在数据可视化原型设计中的无限可能

在当今数字化浪潮中&#xff0c;产品设计不仅关乎美观与功能的平衡&#xff0c;更在于如何高效、直观地传达复杂的数据信息。Axure RP&#xff0c;作为原型设计领域的佼佼者&#xff0c;其在数据可视化原型设计中的应用&#xff0c;正逐步揭开产品设计的新篇章。本文将从多个维…...

Redis 内存淘汰策略

Redis 作为一个内存数据库&#xff0c;必须在内存使用达到配置的上限时采取策略来处理新数据的写入需求。Redis 提供了多种内存淘汰策略&#xff08;Eviction Policies&#xff09;&#xff0c;以决定在内存达到上限时应该移除哪些数据。...

逆天!吴恩达+OpenAI合作出了大模型课程!重磅推出《LLM CookBook》中文版

吴恩达老师与OpenAI合作推出的大模型系列教程&#xff0c;从开发者在大型模型时代的必备技能出发&#xff0c;深入浅出地介绍了如何基于大模型API和LangChain架构快速开发出结合大模型强大能力的应用。 这些教程非常适合开发者学习&#xff0c;以便开始基于LLM实际构建应用程序…...

uint16_t、uint32_t类型数据高低字节互换

1. 使用位运算和逻辑运算符实现 #include<stdio.h> #include<stdint.h> int main() {void test_3() {uint16_t version = 0x1234;printf("%#x\n",(uint8_t)version);printf("%#x\n", version>>8);/*** 在C语言中,uint16和uint8是无符号…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...