【数据结构】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;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

高性能网络模式-Reactor

事实上&#xff0c;Reactor 模式也叫Dispatcher模式&#xff0c;即I/O 多路复⽤监听事件&#xff0c;收到事件后&#xff0c;根据事件类型分配&#xff08;Dispatch&#xff09;给某个进程/线程。Reactor 模式也是一种非阻塞同步网络模式。 Reactor 模式主要由 Reactor部分和处…

platform相关资料

Step 1: Hardware Settings for Vitis Platform — Vitis™ Tutorials 2021.2 documentationhttps://xilinx.github.io/Vitis-Tutorials/2021-2/build/html/docs/Vitis_Platform_Creation/Introduction/03_Edge_VCK190/step1.html https://www.cnblogs.com/VagueCheung/p/1313…

Jmeter 快速生成测试报告

我们使用Jmeter工具进行接口测试或性能测试后一般是通过察看结果数、聚合报告等监听器来查看响应结果。如果要跟领导汇报测试结果&#xff0c;无法直接通过监听器的结果来进行展示和汇报&#xff0c;因为太low了&#xff0c;因此测试完成后去整理一个数据齐全且美观的报告是非常…

如何基于亚马逊云科技打造高性能的 SQL 向量数据库 MyScale

MyScale 是一款完全托管于亚马逊云科技、支持 SQL 的高效向量数据库。MyScale 的优势在于&#xff0c;它在提供与专用向量数据库相匹敌甚至优于的性能的同时&#xff0c;还支持完整的 SQL 语法。在这篇文章中&#xff0c;我们将阐述 MyScale 是如何借助亚马逊云科技的基础设施&…

6G太赫兹波频段

6G目前处于非常早期的研究阶段。国际电信联盟所期待的“网络2030”愿景正在逐步实现。虽然该行业距离进入6G标准开发进程还有几年的时间&#xff0c;但亚太赫兹&#xff08;sub-THz&#xff09;技术已经成为研究的重点。 6G一个关键目标和积极研究领域是实现 100 Gbps 至 1 Tb…

洗涤护理门店小程序DIY制作教程

随着移动互联网的快速发展&#xff0c;小程序成为了各行各业推广和服务的新平台。对于干洗店来说&#xff0c;拥有一个专属的洗护小程序不仅可以提升用户体验&#xff0c;还能增加店铺的曝光度和销售额。那么&#xff0c;如何DIY制作一个干洗店洗护小程序呢&#xff1f; 首先&a…

React笔记[tsx]-解决Property ‘frames‘ does not exist on type ‘Readonly<{}>‘

浏览器报错如下&#xff1a; 编辑器是这样的&#xff1a; 原因是React.Component<any>少了后面的any&#xff0c;改成这样即可&#xff1a; export class CustomFrame extends React.Component<any, any>{............ }

第二章-自动驾驶卡车-自动驾驶卡车前装量产的要求

1、自动驾驶卡车的特点与挑战 重卡主要运行在相对封闭的高速公路&#xff0c;相较城市道路场景看似更简单。但是&#xff0c;由于重卡特有的物理特性、运行环境和商业运营要求&#xff0c;相较于乘用车的自动驾驶系统&#xff0c;重卡的自动驾驶系统对车辆的感知距离和精度、系…

2023河南萌新联赛第(六)场:河南理工大学 C - 旅游

2023河南萌新联赛第&#xff08;六&#xff09;场&#xff1a;河南理工大学 C - 旅游 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K Special Judge, 64bit IO Format: %lld 题目描述 小C喜欢旅游&#xf…

【RuoYi移动端】HBuild工具插件安装和系统配置manifest.json

一、点【工具】-【插件安装】安装如下工具 二、点【manifest.json】

Golang GORM 单表删除

删除只有一个操作&#xff0c;delete。也是先找到再去删除。 可以删除单条记录&#xff0c;也可以删除多条记录。 var s Studentdb.Debug().Delete(&s, "age ?", 100)fmt.Println(s)[15.878ms] [rows:1] DELETE FROM student WHERE age 100var s Studentdb.De…

基于微信小程序的物流管理系统3txar

在此基础上&#xff0c;结合现有物流管理体系的特点&#xff0c;运用新技术&#xff0c;构建了以 springboot为基础的物流信息化管理体系。首先&#xff0c;以需求为依据&#xff0c;对目前传统物流管理基础业务进行了较为详尽的了解和分析。根据需求分析结果进行了系统的设计&…

opencv进阶14-Harris角点检测-cv2.cornerHarris

类似于人的眼睛和大脑&#xff0c;OpenCV可以检测图像的主要特征并将这 些特征提取到所谓的图像描述符中。然后&#xff0c;可以将这些特征作为数据 库&#xff0c;支持基于图像的搜索。此外&#xff0c;我们可以使用关键点将图像拼接起 来&#xff0c;组成更大的图像。&#x…

UE4与pycharm联合仿真的调试问题及一些仿真经验

文章目录 ue4与pycharm联合仿真的调试问题前言ue4端的debug过程pycharm端 一些仿真经验小结 ue4与pycharm联合仿真的调试问题 前言 因为在实验中我需要用到py代码输出控制信息给到ue4中&#xff0c;并且希望看到py端和ue端分别在运行过程中的输出以及debug调试。所以&#xf…

SVN 项目管理笔记

SVN 项目管理笔记 主要是介绍 SVN 管理项目的常用操作&#xff0c;方便以后查阅&#xff01;&#xff01;&#xff01; 一、本地项目提交到SVN流程 在SVN仓库下创建和项目名同样的文件夹目录&#xff1b;选中本地项目文件&#xff0c;选择SVN->checkout,第一个是远程仓库项…

主机SSH连接VirtualBox NAT网络模式

遇到的问题 虚拟机使用桥接模式配置网络&#xff0c;主机可以ssh连接&#xff0c;但是虚拟机无法访问网络 使用NAT模式配置网络&#xff0c;虚拟机可以访问网络&#xff0c;但是主机无法通过ssh连接 解决方法 配置虚拟机端口转发 1 首先查看虚拟机ip 2 关闭虚拟机 配置端口…

tcl学习之路(五)(Vivado时序约束)

1.主时钟约束 主时钟通常是FPGA器件外部的板机时钟或FPGA的高速收发器输出数据的同步恢复时钟信号等。下面这句语法大家一定不会陌生。该语句用于对主时钟的名称、周期、占空比以及对应物理引脚进行约束。 create_clock -name <clock_name> -periood <period> -wa…

计算机网络 QA

DNS 的解析过程 浏览器缓存。当用户通过浏览器访问某域名时&#xff0c;浏览器首先会在自己的缓存中查找是否有该域名对应的 IP 地址&#xff08;曾经访问过该域名并且没有清空缓存&#xff09;系统缓存。当浏览器缓存中无域名对应的 IP 地址时&#xff0c;会自动检测用户计算机…

VSCode 如何解决 scanf 的输入问题——Code is already running!

文章如何使用 VSCode 软件运行C代码中已经介绍了如何在 VSCode 软件中运行C代码&#xff0c;但最近在使用 scanf 想从键盘输入时&#xff0c;运行代码后显示“Code is already running!”&#xff0c;如下图所示&#xff0c;在输出窗口是无法通过键盘输入的。 解决办法如下&am…

开源ChatGPT系统源码 采用NUXT3+Laravel9后端开发 前后端分离版本

开源ChatGPT系统源码 采用NUXT3Laravel9后端开发 前后端分离版本 ChatGPT是一种基于AI的聊天机器人技术&#xff0c;它可以帮助用户与聊天机器人进行自然语言交流&#xff0c;以解决用户的问题或满足用户的需求。ChatGPT的核心技术是使用自然语言处理&#xff08;NLP&#xff…