数据结构七:线性表之链式栈的设计

         在上篇博客,学习了用数组实现链的顺序存储结构,那是否存在用单链表实现栈的链式存储结构,答案是当然的,相比于顺序栈,用数组实现的栈效率很高,但若同时使用多个栈,顺序栈将浪费很多空间。用单链表来实现栈可避免这个问题,其代价是要为每个栈元素分配一个额外的指针空间(存放指针域),本篇博客详细总结用链表实现栈的链式存储结构,我们称之为链式栈。

目录

一、链式栈的接口函数实现

1.0  链式栈的概念和结构

1.0.0 链式栈的概念

1.0.1 链式栈结构体设计

1.1 链式栈的接口函数

1.2  链式栈的初始化

1.3 入栈(相当于单链表的头插)

1.4 出栈(相当于单链表的头删)

1.5 获取栈顶元素值

1.6 获取有效元素个数

1.7 判空

1.8 打印

1.9 清空

1.10 销毁

二、总结


一、链式栈的接口函数实现

1.0  链式栈的概念和结构

1.0.0 链式栈的概念

         链式栈,即用链表实现栈式存储结构。为保证栈的数据元素的后进先出的高效性,那么我们将栈顶应该设计在数组的哪一端呢?学过单链表,我们可以知道,单链表的头插和头删的效率非常高,时间复杂度为O(1),因此,参考单链表,我们可以将栈顶设计为第一个有效结点,这样入栈和出栈的时间复杂度都可以达到O(1)

1.0.1 链式栈结构体设计

       经过上面的分析,我们知道,链式栈就是将栈顶设计为第一个有效结点的单链表,本质上就是只能进行头插和头删的单链表,本质和单链表的设计差不多,主要为2个成员,数据域和指针域。在之前的单链表中,我们把头结点和有效结点设计为一样的结构,浪费掉头结点的数据域,因此只设计成一个结构体类型,在链式栈这里,为了巩固练习,我们将头结点和有效结点分开设计,并且使用头结点的数据域,用来存放链式栈的有效元素个数,因此结构如下:

typedef int ELEM_TYPE;

//有效结点的设计:数据域和指针域
typedef struct Stack_Node
{
	ELEM_TYPE data;
	struct Stack_Node *next;
}Stack_Node, *PStack_Node;


//头结点的设计:存放有效结点个数的数据域和指针域
typedef struct Link_Stack
{
	int count;
	struct Stack_Node *top;
}Link_Stack, *PLink_Stack;
 

1.1 链式栈的接口函数

       链式栈的基本操作有:初始化,头插,尾插,按位置插,头删,尾删,按位置删,查找,按值删,获取有效值个数,判空,清空,销毁,打印。这里详细展示这些基本操作的实现思想和画图分析以及代码实现和算法效率分析,

      注意:链式栈与单链表相同,由于它是按需索取,因此,不需要进行判满和扩容操作;

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "stack_list.h"


//初始化
void Init_stack_list(struct Link_Stack* lst);

//入栈
void Push_list(struct Link_Stack* lst, ELEM_TYPE val);

//出栈
void Pop_list(struct Link_Stack* lst);

//获取栈顶元素值
ELEM_TYPE Top_list(struct Link_Stack* lst);

//获取有效元素个数
int Get_length_list(struct Link_Stack* lst);

//判空
bool IsEmpty_list(struct Link_Stack* lst);


//打印
void Show_list(struct Link_Stack* lst);

//清空
void Clear_list(struct Link_Stack* lst);

//销毁
void Destroy_list(struct Link_Stack* lst);

1.2  链式栈的初始化

    链式栈在创建以后必须要进行初始化,否则内部为随机值,无法使用。链式栈的初始化主要是对结构体成员赋初值。

//初始化
void Init_stack_list(struct Link_Stack* lst)
{
    第0步:传入的指针参数检测
	assert(lst!=NULL);

    第1步:对指针域赋初值,数据域赋初值
	lst->top = NULL;
    lst->count = 0;   //头结点的数据域使用,最开始没有有效结点,赋初值为0

}

1.3 入栈(相当于单链表的头插)

  入栈相当于是单链表的头插,基本思路如下:

    第0步:assert对传入的指针检测;

    第1步:购买新节点(购买好节点之后,记得将val值赋值进去);

    第2步:找到合适的插入位置;

    第3步:插入注意核心代码,先牵右手,再牵左手!!!否则会发生内存泄漏。

//入栈
void Push_list(struct Link_Stack* lst, ELEM_TYPE val)
{
	第0步:传入的指针参数检测
	assert(lst!=NULL);

	//1.购买新节点
	struct Stack_Node* pnewnode = (struct Stack_Node *)malloc(1*sizeof(struct Stack_Node));
	pnewnode->data = val;

	//2.找到合适的插入位置 头插不用管
	
	//3.插入
	pnewnode->next = lst->top;
	lst->top = pnewnode;
	lst->count++;
}

1.4 出栈(相当于单链表的头删)

      出栈相当于单链表的头删操作 ,对于删除操作,则需要对链表进行判空操作!并且删除操作遵循基本同样的4个步骤,需要理解加记忆。删除操作的基本思路如下:

①:用指针q指向待删除节点;
②:用指针p指向待删除节点的前驱节点;(头删的话,这里p可以被plist代替)
③:跨越指向;
④:释放待删除节点。

//出栈
void Pop_list(struct Link_Stack* lst)
{
	第0步:传入的指针参数检测
	assert(lst!=NULL);

	//1.判空
	if(IsEmpty_list(lst))
	{
		return;
	}

	//2.用q指向待删除节点, 用p指向待删除节点的上一个节点
	struct Stack_Node *q = lst->top;

	//3.跨越指向+释放 count--
	lst->top = q->next;
	free(q);
	q = NULL;
	lst->count--;

}

1.5 获取栈顶元素值

链式栈中,栈顶就是第一个有效结点,因此只需要返回第一个有效结点的数据域即可。


//获取栈顶元素值
ELEM_TYPE Top_list(struct Link_Stack* lst)
{
    第0步:传入的指针参数检测
	assert(lst!=NULL);
    
    第1步:判空
	if(IsEmpty_list(lst))
	{
		exit(1);
	}
 
    第2步:直接返回第一个有效结点的数据域
	return lst->top->data;
}

1.6 获取有效元素个数

头结点的数据域就是用来记录有效结点个数的,直接返回即可

//获取有效元素个数
int Get_length_list(struct Link_Stack* lst)
{
     第0步:传入的指针参数检测
	 assert(lst!=NULL);
    
     第1步:头结点的数据域就是用来记录有效结点个数的,直接返回即可
	 return lst->count;
}

1.7 判空

    直接判断头结点的指针域是否为空即可!

//判空
bool IsEmpty_list(struct Link_Stack* lst)
{
     第0步:传入的指针参数检测
	 assert(lst!=NULL);
   
    第1步:没有有效结点时,头结点的指针域为空
	return lst->top == NULL;
}

1.8 打印

直接从第一个有效节点开始,从前往后遍历,打印有效结点的数据域即可

//打印
void Show_list(struct Link_Stack* lst)
{
    第0步:传入的指针参数检测
	assert(lst!=NULL);
    
    第1步:直接从第一个有效节点开始,从前往后遍历,打印有效结点的数据域即可
	struct Stack_Node *p = lst->top;
	for(; p!=NULL; p=p->next)
	{
		printf("%d ", p->data);
	}

	printf("\n");
}

1.9 清空

链式栈和单链表一样,清空和销毁完全一样

//清空
void Clear_list(struct Link_Stack* lst)
{
	Destroy_list(lst);
}

1.10 销毁

链式栈的销毁同样有两种方式,这里只写第2种即可。

//销毁
void Destroy_list(struct Link_Stack* lst)
{
    第0步:传入的指针参数检测
	assert(lst!=NULL);

    第1步:利用双指针,依次从前往后删除
	struct Stack_Node *q = lst->top;
	struct Stack_Node *p = NULL;

	lst->count = 0;
	lst->top = NULL;

	while(q != NULL)
	{
		p = q->next;
		free(q);
        q=NULL;
		q = p;
	}
}

二、总结

      从以上分析,我们可以知道链式栈只是单链表的一种特殊情况,限定了数据元素的插入和删除位置,链式栈是一种基于单链表实现的栈,在空间复杂度上,顺序栈在创建时就申请了数组空间,若栈经常处于不满状态将造成存储空间的浪费;链式栈所需空间是根据需要随时申请的,比之顺序栈仅仅是其每个结点需要额外的空间以存储其next指针域。在时间复杂度上,对于针对栈顶的基本操作(压入、弹出和栈顶元素存取),容易看出,顺序栈和链式栈的时间复杂性均为O(1) 。

       它具有一些优点:

  1. 动态性能好: 链式栈采用链式存储结构,可以动态地分配内存空间,而顺序栈则需要预先分配一定大小的连续内存空间。因此,链式栈能够根据实际需求动态扩展或缩减内存,不会受到固定大小的限制。

  2. 灵活性强: 由于链式栈的内存空间是动态分配的,因此在入栈和出栈操作时不需要移动元素,而是通过修改指针来实现。这使得链式栈的操作效率更高,并且不会出现栈满的情况,避免了顺序栈可能出现的溢出问题。

      以上便是我为大家带来的链式栈设计内容,若有不足,望各位大佬在评论区指出,谢谢大家!下一节继续进行链式栈的内容,感兴趣的你可以留下你们的点赞、收藏和关注,这是对我极大的鼓励,我也会更加努力创作更优质的作品。再次感谢大家!

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

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

相关文章

用NuGet安装 Oracle ODP.NET

oracle官网原文&#xff1a;Using NuGet to Install and Configure Oracle Data Provider for .NET Using NuGet to Install and Configure Oracle Data Provider for .NET In this section, you will install ODP.NET NuGet packages from nuget.org. Select View > Solut…

PDF 正确指定页码挂载书签后,书签页码对不上

这个问题与我的另一篇中方法一样 如何让一个大几千页的打开巨慢的 PDF 秒开-CSDN博客 https://blog.csdn.net/u013669912/article/details/138166922 另做一篇原因是一篇文章附带一个与该文章主题不相关的问题时&#xff0c;不利于被遇到该问题的人快速搜索发现以解决其遇到的…

8_手眼标定总结_auboi5机械臂与海康平面相机

经过不断地学习与调试&#xff0c;不断地学习网络上其他同志分享的资料&#xff0c;opencv手眼标定迎来了阶段性结束。实际测试结果在机械臂坐标系中X方向差5mm左右。 代码参考《https://blog.csdn.net/wanggao_1990/article/details/81435660》 注意事项&#xff1a; ①标定…

AG32 MCU在触摸屏的应用(AGM FPGA/MCU行业应用)

传统的屏驱MCU常见应用于洗衣机、空调、空调面板、仪器仪表等人机交互界面显示场景中&#xff0c;通常是以段码的形式显示设备运转的时间、温度、测量结果等简单运行数据&#xff0c;随着人机交互需求丰富化&#xff0c;智能家居设备、摩托车、电动车等产品也逐步增加了屏幕显示…

如何在 Ubuntu 12.04 上使用 Apache 配置 WebDAV 访问

简介 WebDAV 是内置在 HTTP 中的分布式网络编辑实现&#xff0c;允许您轻松共享文件并与他人协作。 我们可以在 Web 服务器中安装此扩展&#xff0c;以允许通过 Web 浏览器远程读写访问本地文件。在本指南中&#xff0c;我们将在带有 Apache Web 服务器的 Ubuntu 12.04 VPS 上…

【小沐学Java】VSCode搭建Java开发环境

文章目录 1、简介2、安装VSCode2.1 简介2.2 安装 3、安装Java SDK3.1 简介3.2 安装3.3 配置 4、安装插件Java Extension Pack4.1 简介4.2 安装4.3 配置 结语 1、简介 2、安装VSCode 2.1 简介 Visual Studio Code 是一个轻量级但功能强大的源代码编辑器&#xff0c;可在桌面上…

全志ARM-超声波测距

超声波测距模块是用来测量距离的一种产品&#xff0c;通过发送和收超声波&#xff0c;利用时间差和声音传播速度&#xff0c; 计算出模块到前方障碍物的距离 1.测距原理&#xff1a; 给Trig端口至少10us的高电平发送声波&#xff0c;Echo信号&#xff0c;由低电平跳转到高电平…

Docker 部署与操作

一 国内&#xff1a; 中国电信天翼云 提供包括云主机在内的全方位云计算服务&#xff0c;侧重于安全合规和企业级服务。 利用电信的网络优势&#xff0c;提供稳定可靠的基础设施服务。 中国联通沃云 提供包括云主机在内的多项云计算服务&#xff0c;适合不同行业和场景。 …

Redis篇:缓存雪崩及解决方案

1.何为缓存雪崩 缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机&#xff0c;导致大量请求到达数据库&#xff0c;带来巨大压力。 2.缓存雪崩的解决方案 解决方案&#xff1a; 给不同的Key的TTL添加随机值 利用Redis集群提高服务的可用性 给缓存业务添加降级…

如何避免被恶意攻击的IP地址

随着互联网的普及和发展&#xff0c;网络安全问题日益受到关注&#xff0c;恶意攻击成为网络安全的一大威胁。而IP地址作为网络通信的基础&#xff0c;常常成为恶意攻击的目标之一。本文将探讨如何避免被恶意攻击的IP地址&#xff0c;提高网络安全水平。 1. 定期更新安全补丁 …

【C++】--------模板进阶

目录 前言 一、非类型模板参数 定义 二、模板的特化&#xff08;步骤都一样&#xff09; 1.概念 2.函数模板特化的步骤 3.类模板的特化 3.1全特化 3.2偏特化/半特化 三、模板的分离与编译 1.什么是分离编译&#xff1f; 2.模板的分离与编译 四、总结 前言 我们已经…

BPE、Wordpiece、Unigram、SpanBERT等Tokenizer细节总结

BPE(Byte Pair Encoding) GPT-2和Roberta用的是这种&#xff0c;不会产生[UNK]这个unknown字符 这部分部分摘录自https://martinlwx.github.io/zh-cn/the-bpe-tokenizer/ 看以下code例子就足够理解了&#xff0c;核心是维护self.merges&#xff08;维护一个pair->str的字…

一文掌握Vue依赖注入:原理、应用场景以及最佳模块化与单元测试实践,提升代码的可维护性与模块化程度

Vue 中的依赖注入&#xff08;Dependency Injection, DI&#xff09;机制通过 provide 与 inject API&#xff0c;实现了跨组件层级间的数据与服务透明传递&#xff0c;使父组件能够向其任意深度的子孙组件“注入”依赖&#xff0c;而不需要通过层层传递 props 或使用全局状态管…

Pytorch实现线性回归模型

在机器学习和深度学习的世界中&#xff0c;线性回归模型是一种基础且广泛使用的算法&#xff0c;简单易于理解&#xff0c;但功能强大&#xff0c;可以作为更复杂模型的基础。使用PyTorch实现线性回归模型不仅可以帮助初学者理解模型的基本概念&#xff0c;还可以为进一步探索更…

SpringCloud(微服务介绍,远程调用RestTemplate,注册中心Nacos,负载均衡Ribbon,环境隔离,进程和线程的区别)【详解】

目录 一、微服务介绍 1. 系统架构的演变 1 单体架构 2 分布式服务 3 微服务 2. SpringCloud介绍 SpringCloud简介 SpringCloud版本 3. 小结 二、远程调用RestTemplate【理解】 1. 服务拆分 1 服务拆分原则 2 服务拆分示例 1) 创建父工程 2) 准备用户服务 1. 用户…

Docker数据管理与Dockerfile镜像创建

前言 在容器化环境中&#xff0c;如何有效地管理和持久化数据成为了开发人员和运维团队面临的挑战之一&#xff1b;另一方面&#xff0c;镜像的创建是构建容器化应用的基础。优化的镜像设计可以提高部署效率和应用性能&#xff0c;减少资源消耗和运行成本。本文将介绍 Docker …

锂电池SOH预测 | 基于LSTM的锂电池SOH预测(附matlab完整源码)

锂电池SOH预测 锂电池SOH预测完整代码锂电池SOH预测 锂电池的SOH(状态健康度)预测是一项重要的任务,它可以帮助确定电池的健康状况和剩余寿命,从而优化电池的使用和维护策略。 SOH预测可以通过多种方法实现,其中一些常用的方法包括: 容量衰减法:通过监测电池的容量衰减…

锂电池SOH预测 | 基于CNN-GRU的锂电池SOH预测(matlab)

锂电池SOH预测 锂电池SOH预测完整代码锂电池SOH预测 锂电池的SOH(状态健康度)预测是一项重要的任务,它可以帮助确定电池的健康状况和剩余寿命,从而优化电池的使用和维护策略。 SOH预测可以通过多种方法实现,其中一些常用的方法包括: 容量衰减法:通过监测电池的容量衰减…

【Docker】Docker 实践(三):使用 Dockerfile 文件构建镜像

Docker 实践&#xff08;三&#xff09;&#xff1a;使用 Dockerfile 文件构建镜像 1.使用 Dockerfile 文件构建镜像2.Dockerfile 文件详解 1.使用 Dockerfile 文件构建镜像 Dockerfile 是一个文本文件&#xff0c;其中包含了一条条的指令&#xff0c;每一条指令都用于构建镜像…

解决VMware启动异常

问题1&#xff1a;该虚拟机似乎正在使用中。如果该虚拟机未在使用&#xff0c;请按“获取所有权(T)”按钮获 取它的所有权。否则&#xff0c;请按“取消(C)”按钮以防损坏。 解决步骤&#xff1a; 按弹框提示的配置文件目录下删除后缀为lck的文件&#xff08;lock&#xff09;。…