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

news2024/11/26 8:40:08

前言:

💥🎈个人主页:​​​​​​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.coloradmin.cn/o/922521.html

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

相关文章

使用端口映射实现Spring Boot服务端接口的公网远程调试:详细配置与步骤解析

文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…

【每日一招小技巧】编译安装提升系统自带的java版本

hello&#xff0c;大家好&#xff0c;又到了每日一招的环节&#xff0c;今天要给大家介绍的是&#xff0c;如何用源码安装的方式&#xff0c;升级系统自带的ava版本&#xff01; 首先&#xff0c;我们要查看一下我们系统自带的java版本是多少&#xff0c;怎么查看呢&#xff1…

『PyQt5-基础篇』| 02 Pyqt5开发环境+安装配置QtDesigner

02 Pyqt5开发环境安装配置QtDesigner 1 Pycharm安装2 Python安装3 Pip安装4 PyQt5安装5 Pycharm中编译工具设置及pyqt5包的导入6 指定Qt Designer7 指定PyUIC58 指定PyRcc59 PyInstaller安装10 查看是否配置OK 1 Pycharm安装 安装教程请参考&#xff1a;安装教程 2 Python安装…

Linux Day11---mbash项目(二)

观看本文之前请先阅读Linux Day10的相关内容 1.touch 1.1 open系统调用 int open(const char*path,int oflags,mode_t mode); oflags参数&#xff1a; O_APPEND:把写入数据追加在文件的末尾 O_TRUNC:把文件长度设置为0&#xff0c;丢弃已有的内容 O_CREAT:如果需要&#…

Oracle数据库sqlplus命令行执行sql语句时,语句有中文报错解决

在Oracle数据库sqlplus命令行执行sql语句时&#xff0c;语句有中文报错解决 -- 建表语句 create table T_ADEV_SCLG_YXDXMB (obj_id VARCHAR2(42) not null,xmmc VARCHAR2(400),xmbh VARCHAR2(100),jhnd INTEGER,gsmc VARCHAR2(50),xmssdwmc VARCHAR2…

如何从用户视角搭建可观测体系?阿里云ECS业务团队的设计思路

一分钟精华速览 互联网平台以业务为中心&#xff0c;以用户为中心&#xff0c;平台的功能服务、质量和用户体验等是关键的目标&#xff0c;仅仅关注后台系统的可用性是不够的&#xff0c;以传统运维的视角来解决故障、做监控会比较被动。 本文以阿里云 ECS 业务为例&#xff…

医院设备电力中断?如何改善UPS系统?教你一招

当今医疗体系紧密依赖于电力供应的稳定性&#xff0c;以保障医疗设备的正常运行和患者的安全。然而&#xff0c;电力中断或波动可能会带来严重的风险&#xff0c;因此&#xff0c;不间断电源&#xff08;UPS&#xff09;系统在医院中扮演着至关重要的角色。 为确保UPS系统的高效…

Java在项目中覆盖Jar包中的类

import cn.hutool.core.util.StrUtil;public class CoverJarTest {public static void main(String[] args) {System.out.println(StrUtil.toString("1"));} }输出1 我们将hutool的包名在我们的项目中创建一个&#xff0c;并且将对应类的源码复制一份过来&#xff0c…

Echarts图表坐标轴文字太长,省略显示,鼠标放上显示全部(vue)

注意&#xff1a;记得加上这个&#xff0c;触发事件&#xff0c; triggerEvent: true,重点&#xff1a;下面就是处理函数&#xff0c;在实例化图表的时候使用&#xff0c;传入参数是echarts的实例 // 渲染echartsfirstBarChart() {const that thislet columnar echarts.init…

Typecho博客搭建 实现公网访问内网站点

文章目录 前言1. 环境安装2.安装Typecho3.安装cpolar内网穿透4. 固定公网地址5.配置Typecho 前言 Typecho是一款PHP语言编写的开源博客程序&#xff0c;它是一个轻量级的内容管理系统&#xff0c;专注于博客领域。支持多用户、多站点、多语言等功能&#xff0c;可以满足不同用…

多模态(文本、图片)数据融合模型(含公开数据集、文献及开源代码汇总)

多模态&#xff08;文本、图片&#xff09;数据融合模型&#xff08;含公开数据集、文献及开源代码汇总&#xff09; <center>多模态模型的应用跑代码普遍存在的问题 <center>多模态公开数据集<center>文献及开源代码 多模态模型的应用 多模态模型的应用按照…

zabbix3.4配置客户端

1.安装agent yum -y install zabbix zabbix-agent如果下载不动或者无外网环境请看 https://download.csdn.net/download/qq_45748758/88228235?spm1001.2014.3001.5501 免费自取 2、配置zabbix-agent&#xff0c;修改图片三个参数 vi /etc/zabbix/zabbix_agentd.conf3、启动…

WebDAV之葫芦儿·派盘+柚子记账

柚子记账是一个手机记账的软件,这个软件主要是给那些懒人进行设计的,这里有很多关于记账的模板可以让你直接在线使用,你只需要导入相关的数据就可以了,整个操作是非常简单的,而且你也可以进行自定义的图表制作,生成你自己的记账模式。每当你记完之后,系统都会自动给你总…

【excel密码】加密excel工作表的3种方法

Excel文件经常用于处理数据文件&#xff0c;在完成编辑之后&#xff0c;对文件进行加密可以更好的保护数据&#xff0c;今天分享加密excel工作表的3种方法。 打开密码 设置了打开密码的excel文件&#xff0c;打开文件就会提示输入密码才能打开excel文件&#xff0c;只有输入了…

档案馆库房温湿度监控系统技术性【解决方案】

档案存储环境会受诸多因素的影响,其中最为主要的因素就是档案库房的温度与湿度。其中&#xff0c;库房的温度指得是档案库房内大气的冷热程度&#xff1b;湿度是指库房大气中的水分含量。保持档案库房内环境恒温恒湿,不仅可以保障档案的保存质量&#xff0c;而且可以延长档案保…

linux中模拟RTOS中事件集

linux中通常如何处理事件集 在Linux中&#xff0c;没有直接对应于实时操作系统&#xff08;RTOS&#xff09;中事件集&#xff08;Event Set&#xff09;的概念。实时操作系统通常提供了一种机制&#xff0c;允许任务或线程根据事件的发生状态进行等待和唤醒。这通常通过信号量…

CW2B-3A-T、CW2B-6A-T、CW2B-10A-T插座式滤波器

CW2B-3A-T、CW2B-6A-T、CW2B-10A-T CW2B-3A-T(001)、CW2B-6A-T(001)、CW2B-10A-T(001) CW2B-3A-T(002)、CW2B-6A-T(002)、CW2B-10A-T(002) CW2B-3A-T(003)、CW2B-6A-T(003)、CW2B-10A-T(003) CW1D-3A-T、CW1D-6A-T、CW1D-10A-T CW2B-3A-T(004)、CW2B-6A-T(004)、CW2B-10A-…

基于ssm vue智慧城市实验室主页系统源码和论文

基于ssm vue智慧城市实验室主页系统源码和论文059 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方…

【Maven教程】(三)基础使用篇:入门使用指南——POM编写、业务代码、测试代码、打包与运行、使用Archetype生成项目骨架~

Maven基础使用篇 1️⃣ 编写 POM2️⃣ 编写业务代码3️⃣ 编写测试代码4️⃣ 打包和运行5️⃣ 使用 Archetype生成项目骨架 1️⃣ 编写 POM 到目前为止&#xff0c;已经大概了解并安装好了Maven环境, 现在&#xff0c;我们开始创建一个最简单的 Hello World 项目。如果你是初次…

使用Selenium爬取动态表格中的多语言和编码格式

正文 Selenium是一个用于自动化Web浏览器的工具&#xff0c;它可以模拟用户的操作&#xff0c;如点击、输入、滚动等。Selenium也可以用于爬取网页中的数据&#xff0c;特别是对于那些动态生成的内容&#xff0c;如表格、图表、下拉菜单等。本文将介绍如何使用Selenium Python…