NzN的数据结构--栈的实现

news2025/2/26 23:03:52

         在前面我们已经学习了哪些线性数据结构呢?大家一起来回顾一下:C语言学过的数组,数据结构中的线性表和顺序表和链表。那我们今天再来介绍数据结构里的两个线性结构--栈和队列。

目录

一、栈的概念及结构 

二、用数组实现栈

1. 栈的初始化和销毁

2. 从栈顶插入数据

3. 删除栈顶元素

4. 取栈顶元素

5. 计算栈中元素的数量

6. 判断栈是否为空

三、用链表实现栈

四、两种实现对比

1. 支持操作

2. 时间效率

3. 空间效率

五、栈的应用


一、栈的概念及结构 

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

        压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶

        出栈:栈的删除操作叫做出栈,出数据也在栈顶

二、用数组实现栈

        栈的实现一般可以使用数组或链表实现,相对而言数组的结构实现更优一些

         使用数组实现栈时,我们可以将数组的尾部作为栈顶。入栈与出栈操作分别对应在数组尾部添加元素与删除元素。那我们先通过数组实现栈。

        先在头文件中定义需要实现的功能接口:

//Stack.h
#pragma once
#include<stdio.h>
#include <stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;//数组实现
	int top;//栈顶
	int capacity;//栈的容量
}ST;
//栈的初始化和销毁
void STInit(ST* ps);
void STDestroy(ST* ps);
//插入(只能从栈顶插入)
void STPush(ST* ps, STDataType x);
//栈中元素的删除
void STPop(ST* ps);
//取栈顶元素
STDataType STTop(ST* ps);
//计算栈中元素的数量
int STSize(ST* ps);
//判断栈是否为空
bool STEmpty(ST* ps);

1. 栈的初始化和销毁

void STInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;//top=0指向的是栈顶元素的下一个
	//我们也可以让top=-1,这样top就指向栈顶元素
	ps->capacity = 0;
}
void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

2. 从栈顶插入数据

void STPush(ST* ps, STDataType x)
{
	assert(ps);
	//空间不够直接扩容
	if (ps->top == ps->capacity)
	{
		//一开始没有给容量多大,因此需要判断
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//ps->a为空的话,realloc相当于malloc
		STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc");
			exit(1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

3. 删除栈顶元素

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

4. 取栈顶元素

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

5. 计算栈中元素的数量

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

6. 判断栈是否为空

bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

三、用链表实现栈

        使用链表实现栈时,我们可以将链表的头节点视为栈顶,尾节点视为栈底。

        对于入栈操作,我们只需将元素插入链表头部,这种节点插入方法被称为“头插法”。而对于出栈操作,只需将头节点从链表中删除即可。

         实现逻辑与数组相似,因此在这里直接给出实现代码:

//基于链表实现的栈
typedef struct {
    ListNode *top;//将头节点作为栈顶
    int size;//栈的长度
} LinkedListStack;

//栈的初始化
LinkedListStack *newLinkedListStack() 
{
    LinkedListStack *s = malloc(sizeof(LinkedListStack));
    s->top = NULL;
    s->size = 0;
    return s;
}

//栈的销毁
void delLinkedListStack(LinkedListStack *s) 
{
    while (s->top) 
    {
        ListNode *n = s->top->next;
        free(s->top);
        s->top = n;
    }
    free(s);
}

//计算栈中元素的数量
int size(LinkedListStack *s) 
{
    return s->size;
}

//判断栈是否为空
bool isEmpty(LinkedListStack *s) 
{
    return size(s) == 0;
}

//从栈顶插入数据
void push(LinkedListStack *s, int num) 
{
    ListNode *node = (ListNode *)malloc(sizeof(ListNode));
    node->next = s->top;//更新新加节点指针域
    node->val = num;//更新新加节点数据域
    s->top = node;//更新栈顶
    s->size++;//更新栈大小
}

//取栈顶元素
int peek(LinkedListStack *s) {
    if (s->size == 0) 
    {
        printf("栈为空\n");
        return -1;
    }
    return s->top->val;
}

//删除栈顶元素
int pop(LinkedListStack *s) 
{
    int val = peek(s);
    ListNode *tmp = s->top;
    s->top = s->top->next;
    //释放内存
    free(tmp);
    s->size--;
    return val;
}

四、两种实现对比

1. 支持操作

        两种实现都支持栈定义中的各项操作。数组实现额外支持随机访问,但这已超出了栈的定义范畴,因此一般不会用到。

2. 时间效率

在基于数组的实现中,入栈和出栈操作都在预先分配好的连续内存中进行,具有很好的缓存本地性,因此效率较高。然而,如果入栈时超出数组容量,会触发扩容机制,导致该次入栈操作的时间复杂度变为O(N) 。

在基于链表的实现中,链表的扩容非常灵活,不存在上述数组扩容时效率降低的问题。但是,入栈操作需要初始化节点对象并修改指针,因此效率相对较低。不过,如果入栈元素本身就是节点对象,那么可以省去初始化步骤,从而提高效率。

        综上所述,当入栈与出栈操作的元素是基本数据类型时,例如int或double我们可以得出以下结论。

  • 基于数组实现的栈在扩容时效率会降低,但由于扩容是低频操作,因此平均效率更高。
  • 基于链表实现的栈可以提供更加稳定的效率表现。

3. 空间效率

        在初始化列表时,系统会为列表分配“初始容量”,该容量可能超出实际需求;并且,扩容机制通常是按照特定倍率(例如 2 倍)进行扩容的,扩容后的容量也可能超出实际需求。因此,基于数组实现的栈可能造成一定的空间浪费

        然而,由于链表节点需要额外存储指针,因此链表节点占用的空间相对较大

        综上,我们不能简单地确定哪种实现更加节省内存,需要针对具体情况进行分析。

五、栈的应用

  • 浏览器中的后退与前进、软件中的撤销与反撤销。每当我们打开新的网页,浏览器就会对上一个网页执行入栈,这样我们就可以通过后退操作回到上一个网页。后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么需要两个栈来配合实现。
  • 程序内存管理。每次调用函数时,系统都会在栈顶添加一个栈帧,用于记录函数的上下文信息。在递归函数中,向下递推阶段会不断执行入栈操作,而向上回溯阶段则会不断执行出栈操作。

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

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

相关文章

Linux内核中常用的C语言技巧

Linux内核采用的是GCC编译器&#xff0c;GCC编译器除了支持ANSI C&#xff0c;还支持GNU C。在Linux内核中&#xff0c;许多地方都使用了GNU C语言的扩展特性&#xff0c;如typeof、__attribute__、__aligned、__builtin_等&#xff0c;这些都是GNU C语言的特性。 typeof 下面…

Web 前端性能优化之七:数据存储与缓存技术

7、数据存储 在开发Web应用的过程中&#xff0c;会涉及一些数据的存储需求&#xff0c;常见的存储方式可能有&#xff1a; 保存登录态的Cookie&#xff1b; 使用浏览器本地存储进行保存的Local Storage和Session Storage&#xff1b; 客户端数据持久化存储方案涉及的Web SQ…

Redis Cluster集群模式

目录 一、理论 1.1 概念 1.2 集群的作用 1.3 redis集群的数据分片 1.4 Redis集群的主从复制模型 二、实践 2.1 Redis集群模式的搭建 2.1.1 cluster集群前期工作 2.1.2 开启群集功能 2.1.3 启动redis节点 2.1.4 启动集群 2.2 测试集群 总结 一、理论 1.1 概念 集群&a…

pyside6的QSpinBox自定义特性初步研究(二)

当前的需求是&#xff0c;蓝色背景的画面&#xff0c;需要一个相对应色系的QSpinBox部件。已有的部件风格是这样的&#xff0c;需要新的部件与之般配。 首先新建一个QDoubleSpinBox&#xff0c;并定义其背景色和边框&#xff1a; QDoubleSpinBox { color: white; border:1px…

光学雨量计红外雨量传感器应用于小型气象站

光学雨量计红外雨量传感器应用于小型气象站 随着气候变化对人类生活和农业生产的影响越来越大&#xff0c;气象观测设备的需求也逐渐增加。其中一种常见的气象观测设备是雨量计&#xff0c;用于监测降水量。在小型气象站中&#xff0c;光学雨量计红外雨量传感器被广泛应用。 …

数据库知识点汇总(最全!,2024年最新大佬分享开发经验

十九、删除数据 DELETE 语句 使用 DELETE 语句从表中删除数据。 DELETE FROM table [WHERE condition]; 删除数据 使用 WHERE 子句删除指定的记录 DELETE FROM departments WHERE department_name ‘Finance’; 如果省略 WHERE 子句&#xff0c;则表中的全部数据将被删除 DELE…

ai智能问答免费API接口

智能对话API接口&#xff0c;可以为网站或其他产品提供强大的智能交互功能&#xff0c;无需自行开发复杂的语义分析和自然语言处理算法。这使得开发者能够更专注于产品的核心功能和用户体验&#xff0c;加速产品上线速度并降低开发成本。 智能对话API接口的功能还包括对话内容…

CentOS安装MeterSphere并实现无公网IP远程访问本地测试平台

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…

VS运行.netcore项目,一般情况下都是用localhost访问,怎么做到用IP地址访问,步骤详解

一、问题描述 通常情况下运行起来都是这样 运行后效果 怎么做到下面这样呢&#xff1f; 二、解决办法 vs把项目运行起来之后&#xff0c;在电脑的右下角有一个图标&#xff0c;右键它 点击其中一个 进入applicationhost.config文件

Golang | Leetcode Golang题解之第14题最长公共前缀

题目&#xff1a; 题解&#xff1a; func longestCommonPrefix(strs []string) string {if len(strs) 0 {return ""}isCommonPrefix : func(length int) bool {str0, count : strs[0][:length], len(strs)for i : 1; i < count; i {if strs[i][:length] ! str0 …

用树莓派监测地震?

监测人类活动和地质活动对地球的影响是 Raspberry Shake 在全球取得的一项成功。 Raspberry Shake 的目标是建立一个让公民科学家也能使用的专业级地震仪。现在&#xff0c;他们在全球安装了数千台基于 Raspberry Pi 的高灵敏度仪器&#xff0c;为前沿研究提供动力。 解决方案…

Facial Micro-Expression Recognition Based on DeepLocal-Holistic Network 阅读笔记

中科院王老师团队的工作&#xff0c;用于做微表情识别。 摘要&#xff1a; Toimprove the efficiency of micro-expression feature extraction,inspired by the psychological studyof attentional resource allocation for micro-expression cognition,we propose a deep loc…

IDEA中无法保存设置 Cannot Save Settings

确定原因: 在IDEA中父工程不应该存在有子工程的相关东西 首先,这是我的DCYJ项目(观察右侧的Content Root) 其次,这是我的EAPOFode项目(观察右侧的Content Root爆红处) 最后我将DCYJ项目右侧的Content Root全部删掉

Java毕业设计 基于springboot vue撸宠平台 宠物系统

Java毕业设计 基于springboot vue撸宠平台 宠物系统 springboot撸宠平台 宠物系统 功能介绍 首页 图片轮播 用户或商家注册 用户或商家登录 登录验证码 店铺信息 店铺详情 店铺投诉 宠物信息 宠物详情 预订 退订 搜索 收藏 点赞 踩 评论 个人中心 更新信息 我的收藏 在线客服…

【NLP】隐马尔可夫模型(HMM)与条件随机场(CRF)简介

一. HMM 隐马尔可夫模型&#xff08;Hidden Markov Model, HMM&#xff09;是一种用于处理含有隐藏状态的序列数据的统计学习模型。通过建模隐藏状态之间的转移关系以及隐藏状态与观测数据的生成关系&#xff0c;HMM能够在仅观察到部分信息的情况下进行状态推理、概率计算、序…

Spring Boot项目获取resources目录下的文件并返回给前端

学无止境&#xff0c;气有浩然&#xff01; 新开通公众号&#xff0c;欢迎大家关注&#xff0c;后续会持续分享技术和相关资料 文章目录 前言方案1.getResourceAsStream2.ResourceLoader3.Value配合Resource 打完收工&#xff01; 前言 最近项目需要下载一个模板文件用来修改…

32.768khz晶振时间跑不准有偏差的原因

32.768kHz晶振是一种常见的晶振频率&#xff0c;广泛应用于实时钟电路、计时电路和低功耗设备中。然而&#xff0c;有时候会发现32.768kHz晶振的时间跑不准&#xff0c;存在一定的偏差。JF晶发电子将介绍几个可能导致32.768kHz晶振时间跑不准的原因。 1. 温度变化&#xff1a;…

vue3页面导出为PDF文件

vue3页面导出为PDF文件 尝试了很多方法&#xff0c;都没有找到完美的解决方法 目前网上有个思路&#xff0c;就是将页面先转存为图片&#xff0c;然后将图片另存为PDF文件 记录一下完整过程 一、安装必备包 安装两个第三方插件 npm i html2canvas npm i jspdfhtml2canvas…

八股面试——数据库——索引

索引的概念 B树的概念&#xff1a; 索引的作用 聚簇索引与非聚簇索引 聚簇索引就是主键值&#xff0c;在B树上&#xff0c;通过主键大小&#xff08;数据在B树叶子节点按主键顺序排序&#xff09;寻找对应的叶子节点&#xff0c;叶子节点保存的一整条记录。 非聚簇索引&#x…