【数据结构】C语言实现栈(详细解读)

前言:

💥🎈个人主页:​​​​​​Dream_Chaser~ 🎈💥

✨✨专栏:http://t.csdn.cn/oXkBa

⛳⛳本篇内容:c语言数据结构--C语言实现栈

目录

什么是栈

        栈的概念及结构

实现栈的方式

链表的优缺点:

顺序表的优缺点:

栈的实现

a.头文件的包含

 b.栈的定义

c.接口函数     

接口函数的实现

1.栈的初始化

2.销毁栈

3.入栈

4.检测栈是否为空

5.出栈

6.获取栈顶元素

7.获取栈中有效元素个数

完整代码

Test.c

Stack.h

Stack.c


什么是

        栈的概念及结构

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

实现栈的方式

实现栈的方式有两种: 顺序表链表

链表的优缺点:

优点:

        1、任意位置插入删除O(1)

        2、按需申请释放空间

缺点:

        1、不支持下标随机访问

        2、CPU高速缓存命中率会更低

        先说链表实现栈的缺点:

  1. 额外内存开销:链表实现的栈需要为每个节点分配内存空间来存储数据和指针。相比于数组实现的栈,链表实现需要额外的内存开销来维护节点之间的指针关系,可能导致内存碎片化。

  2. 动态内存分配:链表实现的栈需要通过动态内存分配来创建和释放节点。这涉及到频繁的内存分配和释放操作,可能导致内存管理的复杂性和性能开销。在某些情况下,可能会出现内存分配失败或内存泄漏的问题。

  3. 指针操作开销:链表实现的栈需要通过指针进行节点之间的连接操作。这包括插入和删除节点时的指针修改,可能涉及到多个指针的更新。相比于数组实现的栈,链表实现的栈需要更多的指针操作,可能会带来一定的性能开销。

  4. 随机访问的限制:链表是一种顺序访问的数据结构,无法像数组一样通过索引进行随机访问。如果需要在栈中进行随机访问元素,链表实现的栈可能不太适合,而数组实现的栈更具优势。

顺序表的优缺点:

优点:1、尾插尾删效率不错。

        2、下标的随机访问。

        3、CPU高速缓存命中率会更高

缺点:

        1、前面部分插入删除数据,效率是O(N),需要挪动数据。

        2、空间不够,需要扩容。a、扩容是需要付出代价的b、一般还会伴随空间浪费。

        顺序表实现栈的优点

  1. 内存连续性:顺序表在内存中是连续存储的,相比于链表的动态内存分配,顺序表的元素在物理上更加紧凑。这样可以减少内存碎片化,提高内存的利用效率。

  2. 随机访问:顺序表可以通过索引直接访问栈中的元素,具有随机访问的能力。这意味着可以快速访问栈中任意位置的元素,而不需要遍历整个链表。

  3. 操作简单高效:顺序表的插入和删除操作只涉及元素的移动,不需要额外的指针操作和动态内存分配。这使得操作相对简单高效,并且在某些情况下比链表实现更快。

  4. 空间效率:相比于链表实现,顺序表不需要额外的指针来维护节点之间的连接关系,因此可以节省一定的空间开销。只需要存储元素本身和栈顶指针即可。

综上所述,用顺序表实现栈更好。

栈的实现

a.头文件的包含

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

 b.栈的定义

typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;//栈顶int capacity;//栈的容量
}ST;

c.接口函数     

// 初始化栈
void STInit(ST* pst); // 入栈
void STPush(ST* pst, STDataType data); // 出栈
void STPop(ST* pst); // 获取栈顶元素
STDataType STTop(ST* pst); // 获取栈中有效元素个数
int STSize(ST* pst); // 检测栈是否为空,如果为空返回true,如果不为空返回false
bool STEmpty(ST* pst); // 销毁栈
void STDestroy(ST* pst);

接口函数的实现

1.栈的初始化

        pst->top表示栈的顶部指针,通常情况下,它指向栈顶元素的下一个位置,而不是指向当前栈顶元素。通过 pst->top 可以确定栈中元素的个数,打印的时候记得将 top - 1。

void STInit(ST* pst)
{assert(pst);//防止敲代码的人传过来是NULL指针pst->a = NULL;//栈底//top不是数组下标,不能理解成数组下标,因为栈只能拿到栈顶的元素,而数组可以随机访问拿到中间元素//pst->top=-1;//指向栈顶元素pst->top = 0;//指向栈顶元素的下一个位置pst->capacity = 0;}

分别解释一下各自的含义: 

  1.  pst 是指向栈的指针,指向栈的首节点或头节点。
  2. -> 是一个成员访问运算符,用于通过指针访问结构体或类的成员
  • pst ->a 是指向存储栈元素的数组的指针。栈中的元素通常被存储在数组中,通过 pst->a 可以访问和操作该数组。在 STInit 函数中, pst->a 被设置为 NULL,表示栈底为空,即栈中没有任何元素。

  • pst->capacity 表示栈的容量,即栈可以容纳的最大元素数量。当栈中元素的数量达到 pst->capacity 时,栈被认为已满,无法再进行入栈操作。在初始化时,pst->capacity 的值通常被设置为 0,表示栈的初始容量为 0。

  • pst->top 表示栈顶指针,它指向当前栈顶元素的下一个位置。在栈为空时,pst->top 的值为 0,表示栈底。随着元素的入栈和出栈操作,pst->top 的值会相应地增加或减少,指向栈中下一个元素的位置。

2.销毁栈

为了防止野指针的出现,栈销毁后记得将指针置空。

void STDestroy(ST* pst)
{assert(pst);free(pst->a);pst->a = NULL;
}

3.入栈

三元运算符

condition ? value1: value2 

它的含义是,如果条件condition为真(非0),则整个表达式的值为value1;如果条件为假(0),则整个表达式的值为value2

解析:

int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;

这段代码的执行顺序如下:

  1. 首先,评估条件pst->capacity == 0 这将检查  pst 指针所指向的结构体中的 capacity 成员是否等于 0
  2. 如果条件为真(pst->capacity 等于 0),则表达式的值为 4,将其赋给 newCapacity  
  3. 如果条件为假(pst->capacity不等于 0),则表达式的值为pst->capacity * 2,将其赋给 newCapacity

realloc函数:【C进阶】-- 动态内存管理_Dream_Chaser~的博客-CSDN博客

动图:

函数代码:


void STPush(ST* pst,STDataType x)
{if (pst->top == pst->capacity){int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;STDataType* tmp = (STDataType*)realloc(pst->a, newCapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}pst->a = tmp;//返回的是realloc出来的内存块的地址pst->capacity = newCapacity;//把扩容后的空间大小赋值给栈容量}pst->a[pst->top] = x;//先放值pst->top++;//再++
}

【注意事项】

1️⃣检查栈的顶部指针 top 是否等于栈的容量 capacity 。如果这两个值相等,那么说明栈已经满了,无法再添加新的元素。

 2️⃣接着判断此时栈的容量是否是0,若是0,则把4赋值给newcapacity作为新的栈容量。此后若栈满了,则把此时栈满时的容量 * 2进行扩容,赋值给newcapacity作为新的栈容量。

3️⃣先放入新的元素入栈,接着pst->top指向栈顶元素的指针++

4.检测栈是否为空

        栈为空返回true,不为空返回false

bool STEmpty(ST* pst)//栈为空返回true,不为空返回false
{//写法一//assert(pst);//if (pst->top == 0)//{//	return true;//}//else//{//	return false;//}//写法二return pst->top == 0;
}
  • 当栈为空时,表示栈中没有任何元素。此时,栈顶指针 top 的值通常被设置为特定的初始值(例如0或-1),指向栈底或栈外。在这种情况下,栈顶指针没有指向有效的元素,因此栈被认为是空的。

  • 当栈非空时,表示栈中至少有一个元素。此时,栈顶指针top的值指向栈顶元素的位置。栈顶元素是最后一个被入栈的元素,也是最先被访问或移除的元素。只要栈中有元素存在,栈顶指针都会指向有效的位置。

        因此,在STEmpty(ST* pst)函数中,当栈为空时,即栈顶指针top的值为0(或其他特定初始值),我们返回 true 表示栈为空。反之,如果栈非空,即栈顶指针 top 的值大于0,我们返回 false 表示栈不为空。

5.出栈

        先用assert判断传过来的pst指针是否指向NULL。接着判断栈是否为NULL,为NULL,STEmpty(pst)返回true,!STEmpty(pst)就是false,断言失败,程序终止。反之断言成功,程序正常执行。

图解:

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

【注意事项】

          接着将指向栈顶的指针--,通过将栈顶指针top减一,可以将指针向栈底方向移动,从而使栈顶指向下一个元素。

        指针的移动并不会直接导致元素的销毁。指针的移动只是改变了栈顶指针的位置,使其指向了栈中的下一个元素。元素本身并不会被销毁,只是在后续的操作中,可能无法直接访问被移除的元素。

6.获取栈顶元素

图解:因为前面定义的时候pst->top=0,表示指向栈顶元素的下一个位置。

pst->top-1表示栈顶元素在数组中的索引。

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

        需要注意的是,在实际使用中,应确保栈不为空(即栈中有元素存在),才能执行取栈顶元素的操作。因此,在代码中使用了 assert(!STEmpty(pst)) 进行栈非空的断言校验。

代码执行:

7.获取栈中有效元素个数

图解:由图看出,pst->top此时是指向下标为4的位置的,所以栈此时的有效个数就为4

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

代码执行: 

完整代码

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void TestStack1()
{ST st;STInit(&st);STPush(&st, 1);STPush(&st, 2);STPush(&st, 3);STPush(&st, 4);while (!STEmpty(&st)){printf("%d ", STTop(&st));//栈顶元素STPop(&st);}STDestroy(&st);
}
void TestStack2()
{ST st;STInit(&st);STPush(&st, 1);STPush(&st, 2);printf("%d ", STTop(&st));STPush(&st, 3);STPush(&st, 4);printf("\n");printf("%d ", STTop(&st));//printf("%d", STSize(&st));//while (!STEmpty(&st))//{//	printf("%d ", STTop(&st));//栈顶元素//	STPop(&st);//}STDestroy(&st);
}
void TestStack3()
{ST st;STInit(&st);STPush(&st, 1);STPush(&st, 2);STPush(&st, 3);STPush(&st, 4);//printf("%d", STSize(&st));STDestroy(&st);
}int main()
{TestStack1();//入栈出栈//TestStack2();//获取栈顶元素//TestStack3();//计算栈中有效元素个数 return 0;
}

Stack.h

#pragma once
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<stdio.h>
typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;//栈顶的位置int capacity;//栈的容量
}ST;void STInit(ST* pst);
void STDestroy(ST* pst);
void STPush(ST* pst,STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool  STEmpty(ST* pst);
int STSize(ST*pst);

Stack.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void STInit(ST* pst)
{assert(pst);pst->a = NULL;//栈底//top不是下标//pst->top=-1;//指向栈顶元素pst->top = 0;//指向栈顶元素的下一个位置pst->capacity = 0;}void STDestroy(ST* pst)
{assert(pst);free(pst->a);pst->a = NULL;
}void STPush(ST* pst,STDataType x)
{if (pst->top == pst->capacity){int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;//true,4.false,括2倍STDataType* tmp = (STDataType*)realloc(pst->a, newCapacity * sizeof(STDataType));//返回值地址相等就是原地扩容,不同就是异地扩if (tmp == NULL){perror("realloc fail");return;}pst->a = tmp;//返回的是realloc出来的内存块的地址pst->capacity = newCapacity;//把扩容后的空间大小赋值给栈容量}pst->a[pst->top] = x;//先放值pst->top++;//再++
}void STPop(ST* pst)
{assert(pst);assert(!STEmpty(pst));pst->top--;
}STDataType STTop(ST* pst)
{assert(pst);assert(!STEmpty(pst));return pst->a[pst->top - 1];
}bool STEmpty(ST* pst)//栈为空返回true,不为空返回false
{//assert(pst);//if (pst->top == 0)//{//	return true;//}//else//{//	return false;//}return pst->top == 0;
}
int STSize(ST* pst)
{assert(pst);return pst->top;
}

        栈面试题还在持续更新中,感谢支持!

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

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

相关文章

深入探索快速排序:高效分而治之的算法

1. 引言&#xff1a;快速排序的背景与重要性 快速排序&#xff08;Quick Sort&#xff09;是一种高效的排序算法&#xff0c;以其出色的性能和普适性而受到广泛关注。它利用了分而治之的思想&#xff0c;通过将数组分割成较小的子数组&#xff0c;并将这些子数组分别排序来实现…

使用 Amazon Redshift Serverless 和 Toucan 构建数据故事应用程序

这是由 Toucan 的解决方案工程师 Django Bouchez与亚马逊云科技共同撰写的特约文章。 带有控制面板、报告和分析的商业智能&#xff08;BI&#xff0c;Business Intelligence&#xff09;仍是最受欢迎的数据和分析使用场景之一。它为业务分析师和经理提供企业的过去状态和当前状…

MQ消息队列(主要介绍RabbitMQ)

消息队列概念&#xff1a;是在消息的传输过程中保存消息的容器。 作用&#xff1a;异步处理、应用解耦、流量控制..... RabbitMQ&#xff1a; SpringBoot继承RabbitMQ步骤&#xff1a; 1.加入依赖 <dependency><groupId>org.springframework.boot</groupId&g…

隧道HTTP具备的条件

作为一名专业的爬虫代理供应商&#xff0c;我们都知道使用代理是保证爬虫的高效性和稳定性的重要手段之一。而隧道代理则是近年来备受推崇的一种代理形式&#xff0c;它通过将请求通过隧道传输&#xff0c;可以有效地隐藏爬虫的真实IP地址&#xff0c;提高爬虫的反爬能力。 在…

ConfigMap(可变应用配置管理)

实验环境 实验环境&#xff1a; 1、win10,vmwrokstation虚机&#xff1b; 2、k8s集群&#xff1a;3台centos7.6 1810虚机&#xff0c;1个master节点,2个node节点k8s version&#xff1a;v1.22.2containerd://1.5.5实验软件(无) 1 基础知识 1.1 什么是ConfigMap(可变配置管理…

matlab工具箱Filter Designer设计butterworth带通滤波器

1、在matlab控制界面输入fdatool; 2、在显示的界面中选择合适的参数&#xff1b;本实验中采样频率是200&#xff0c;低通30hz&#xff0c;高通60hz,点击butterworth滤波器。 3、点击设计滤波器按钮后&#xff0c;在生成的界面点击红框按钮&#xff0c;可生成simulink模型到当前…

Linux下彻底卸载jenkins

文章目录 1、停服务进程2、查找安装目录3、删掉相关目录4、确认已完全删除 1、停服务进程 查看jenkins服务是否在运行&#xff0c;如果在运行&#xff0c;停掉 ps -ef|grep jenkins kill -9 XXX2、查找安装目录 find / -name "jenkins*"3、删掉相关目录 # 删掉相…

一文全懂!带你了解芯片“流片”!

一、流片是什么&#xff1f; 流片(tape-out)是指通过一系列工艺步骤在流水线上制造芯片&#xff0c;是集成电路设计的最后环节&#xff0c;也就是送交制造。 流片即为"试生产"&#xff0c;简单来说就是设计完电路以后&#xff0c;先生产几片几十片&#xff0c;供测试…

C++入门:内联函数,auto,范围for循环,nullptr

目录 1.内联函数 1.1 概念 1.2 特性 1.3 内联函数与宏的区别 2.auto关键字(C11) 2.1 auto简介 2.2 auto的使用细则 2.3 auto不能推导的场景 3.基于范围的for循环(C11) 3.1 范围for的语法 3.2 范围for的使用方法 4.指针空值nullptr(C11) 4.1 C98中的指针空值 1.内联…

15.树与二叉树基础

目录 一. 树&#xff0c;基本术语 二. 二叉树 &#xff08;1&#xff09;二叉树 &#xff08;2&#xff09;满二叉树 &#xff08;3&#xff09;完全二叉树 三. 二叉树的性质 四. 二叉树的存储结构 &#xff08;1&#xff09;顺序存储结构 &#xff08;2&#xff09;链…

C#__自定义类传输数据和前台线程和后台线程

// 前台线程和后台线程 // 默认情况下&#xff0c;用Thread类创建的线程是前台线程。线程池中的线程总是后台线程。 // 用Thread类创建线程的时候&#xff0c;可以设置IsBackground属性&#xff0c;表示一个后台线程。 // 前台线程在主函数运行结束后依旧执行&#xff0c;后台线…

01.Django入门

1.创建项目 1.1基于终端创建Django项目 打开终端进入文件路径&#xff08;打算将项目放在哪个目录&#xff0c;就进入哪个目录&#xff09; E:\learning\python\Django 执行命令创建项目 F:\Anaconda3\envs\pythonWeb\Scripts\django-admin.exe&#xff08;Django-admin.exe所…

RK3588平台开发系列讲解(AI 篇)RKNN-Toolkit2 API 介绍

文章目录 一、RKNN 初始化及对象释放二、RKNN 模型配置沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解 RKNN-Toolkit2 API 详细说明。 一、RKNN 初始化及对象释放 在使用 RKNN Toolkit2 的所有 API 接口时,都需要先调用 RKNN()方法初始化 RKNN 对象,…

使用Nodejs搭建简单的HTTP服务器 - 内网穿透公网远程访问

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址&#x1f340;小结&#x1f340; &#x1f389;博客主页&#xff1a;小智_x0___0x_ &#x1f389;欢迎关注&#xff1a;&…

“车-路-网”电动汽车充电负荷时空分布预测(matlab)

目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序参考《基于动态交通信息的电动汽车充电负荷时空分布预测》和《基于动态交通信息的电动汽车充电需求预测模型及其对配网的影响分析》文献模型&#xff0c;考虑私家车、出租车和共用车三类交通工具特性和…

Python支持下最新Noah-MP陆面模式站点、区域模拟及可视化分析技术

查看原文>>> Python支持下最新Noah-MP陆面模式站点、区域模拟及可视化分析技术 熟悉陆表过程的主要研究内容以及陆面模型在生态水文研究中的地位和作用&#xff1b;深入理解Noah-MP 5.0模型的原理&#xff0c;掌握Noah-MP模型&#xff08;2023年最新发布的5.0版本&am…

Android 查看签名文件的MD5 SHA1值

1.找到存放签名文件所在的文件夹 2.输入命令&#xff1a;keytool -list -v -keystore atui.jks 3.输入口令&#xff08;keystore.jks签名文件的密码&#xff09;

U盘被分成了4个盘要怎么合并

原来是做为系统盘的&#xff0c;然后有一大概小半年没用&#xff0c;今天一看它自己分成了四个盘。 并且我一插入电脑就提示我格式化 其实根本不需要任何工具&#xff0c;操作前最好把U盘数据备份一下 首先把你的U盘插在电脑上 方法一 U盘被分成四个分区的原因有以下几种可…

第二届人工智能与智能信息处理技术国际学术会议(AIIIP 2023)

第二届人工智能与智能信息处理技术国际学术会议&#xff08;AIIIP 2023&#xff09; 2023 2nd International Conference on Artificial Intelligence and Intelligent Information Processing 第二届人工智能与智能信息处理技术国际学术会议&#xff08;AIIIP 2023&#xf…

Seaborn数据可视化(二)

目录 1.Seaborn风格设置 1.1 主题设置 1.2 轴线设置 1.3 移除轴线 1.4 使用字典传递函数 2.设置绘图元素比例 2.1 设置绘图元素比例paper 2.2 设置绘图元素比例poster 2.3 设置绘图元素比例notebook Seaborn将Matplotlib的参数划分为两个独立的组合&#xff0c;第一组用于…