数据结构——顺序栈和链式栈

news2024/11/25 22:42:17

目录

引言

栈的定义

栈的分类

栈的功能

栈的声明

1.顺序栈

2.链式栈

栈的功能实现

1.栈的初始化

(1)顺序栈

(2)链式栈

(3)复杂度分析

2.判断栈是否为空

(1)顺序栈

(2)链式栈

(3)复杂度分析

3.返回栈顶元素

(1)顺序栈

(2)链式栈

(3)复杂度分析

4.返回栈的大小

(1)顺序栈

(2)链式栈

(3)复杂度分析

5.元素入栈

(1)顺序栈

(2)链式栈

(3)复杂度分析

6.元素出栈

(1)顺序栈

(2)链式栈

(3)复杂度分析

7.打印栈的元素

(1)顺序栈

(2)链式栈

(3)复杂度分析

8.销毁栈

(1)顺序栈

(2)链式栈

(3)复杂度分析

顺序栈和链式栈的对比

完整代码

1.顺序表

2.链式表

结束语


引言

在学习完链表之后,我们接下来学习数据结构——栈的内容。

栈的定义

栈(Stack)是一种遵循后进先出(LIFO, Last In First Out)原则的有序集合。这种数据结构只允许在栈顶进行添加(push)或删除(pop)元素的操作。换句话说,最后添加到栈中的元素将是第一个被移除的,就像一叠盘子那样,我们只能从上面开始取放盘子。

如图所示:

栈顶(Top):栈顶是栈中最后添加(push)元素的位置,也是最先被移除(pop)或查看(peek/top)的元素所在的位置。在栈的所有操作中,无论是添加、删除还是查看元素,都是针对栈顶进行的。因此,栈顶是栈中最活跃、最频繁被访问的位置。

栈底(Bottom):栈底是栈中最早被添加进去的元素所在的位置,也是栈中唯一一个固定不变的位置(除非整个栈被清空)。在栈的常规操作中,栈底元素不会被直接访问,除非是将整个栈的内容倒序输出或者栈被完全清空。因此,栈底在栈的操作中扮演的是一个相对静态的角色。

栈的分类

栈可以分为顺序栈链式栈。

如下图所示:

顺序栈:

链式栈:

栈的功能

我们要实现的栈的功能如下所示:

1.栈的初始化。
2.判断栈是否为空。
3.返回队头元素。
4.返回栈的大小。
5.元素入栈。

6.元素出栈。
7.打印栈的元素。
8.销毁栈。

栈的声明

1.顺序栈

顺序栈的声明需要一个指向一块空间的指针a,指向栈顶下一个元素的top,以及标志栈空间大小的capacity。

声明如下:

typedef int STDataType;

typedef struct STDataType
{
	STDataType* a;
	int top;
	int capacity;
}ST;

2.链式栈

链式栈的声明只需要一个top指针,以及栈的容量capacity。

当然这里需要链表的声明。

代码如下:

typedef int STDataType;

typedef struct SListNode
{
	STDataType data;
	struct SListNode* next;
}SLTNode;

typedef struct Stack
{
	// 指向栈顶节点的指针
	SLTNode* top;
	int size;
}ST;

栈的功能实现

1.栈的初始化

顺序栈和链式栈都可以先初始为NULL。

(1)顺序栈

顺序栈可以将top设置为-1,capacity设置为0。

代码如下:

//栈的初始化
void STInit(ST* st)
{
	assert(st);
	st->a = NULL;
	st->top = -1;
	st->capacity = 0;
}
(2)链式栈

链式栈将size设置为0,top设置为NULL。

代码如下:

//栈的初始化
void STInit(ST* st)
{
	assert(st);
	st->size = 0;
	st->top = NULL;
}
(3)复杂度分析

时间复杂度:由于顺序栈和链式栈花费时间都是一个常数,因此时间复杂度为O(1)。

空间复杂度:由于顺序栈和链式栈花费空间都是一个固定大小的空间,因此空间复杂度为O(1)。

2.判断栈是否为空

判断栈是否为空只需要判断top的指向。

(1)顺序栈

当top=-1则为空。

代码如下:

//判空
bool STEmpty(ST* st)
{
	assert(st);
	return st->top == -1;
}
(2)链式栈

判断top是否指向NULL。

代码如下:

//判空
bool STEmpty(ST* st)
{
	return (st->top == NULL);
}
(3)复杂度分析

时间复杂度:由于顺序栈和链式栈花费时间都是一个常数,因此时间复杂度为O(1)。

空间复杂度:由于顺序栈和链式栈花费空间都是一个固定大小的空间,因此空间复杂度为O(1)。

3.返回栈顶元素

(1)顺序栈
//取出栈顶数据
STDataType STTop(ST* st)
{
	assert(st);
    // 断言确保栈不为空(即栈顶索引不小于0)
	assert(st->top >= 0);
	return st->a[st->top];
}
(2)链式栈
//取出栈顶数据
STDataType STTop(ST* st)
{
	assert(st);
	assert(!STEmpty(st));
	return st->top->data;
}
(3)复杂度分析

时间复杂度:由于顺序栈和链式栈花费时间都是一个常数,因此时间复杂度为O(1)。

空间复杂度:由于顺序栈和链式栈花费空间都是一个固定大小的空间,因此空间复杂度为O(1)。

4.返回栈的大小

(1)顺序栈

由于在一开始将top设置为-1,需要top+1才能符合需要。

代码如下:

//获取数据个数
STDataType STSize(ST* st)
{
	assert(st);
	return st->top + 1;
}
(2)链式栈
//获取数据个数
STDataType STSize(ST* st)
{
	return st->size;
}
(3)复杂度分析

时间复杂度:由于顺序栈和链式栈花费时间都是一个常数,因此时间复杂度为O(1)。

空间复杂度:由于顺序栈和链式栈花费空间都是一个固定大小的空间,因此空间复杂度为O(1)。

5.元素入栈

注意:入栈需要检查空间是否足够。

(1)顺序栈

由于top设置的是-1,因此需要先腾出空间然后再将新元素x放在栈顶。

代码如下:

//入栈
void STPush(ST* st, STDataType x)
{
	assert(st);
	// 注意:由于top初始化为-1,所以满的条件是top == capacity - 1
	if (st->top == st->capacity - 1)
	{
		// 如果栈已满,则扩展栈的容量
		int newcapacity = st->capacity == 0 ? 4 : st->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(st->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail:");
			return;
		}
		st->a = tmp;
		st->capacity = newcapacity;
	}
	// 增加栈顶索引,为新元素腾出空间
	st->top++;
	// 将新元素x存储在栈顶位置
	st->a[st->top] = x;
}
(2)链式栈
//入栈
void STPush(ST* st, STDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail:");
		return;
	}
	// 新节点的next指向原来的栈顶
	newnode->next = st->top;

	// 设置新节点的数据
	newnode->data = x;

	// 更新栈顶为新节点
	st->top = newnode;
	st->size++;
}
(3)复杂度分析

时间复杂度:由于顺序栈支持下标的随机访问并且我们以单链表的头作为栈顶,因此时间复杂度为O(1)。

空间复杂度:顺序表又可能需要进行扩容处理,最坏的情况是空间复杂度为O(n)。链式表每次入栈固定为一个节点,因此空间复杂度为O(1)。

6.元素出栈

(1)顺序栈
//出栈
void STPop(ST* st)
{
	assert(st);
	assert(st->top >= 0);
	st->top--;
}
(2)链式栈
//出栈
void STPop(ST* st)
{
	assert(st);
	assert(!STEmpty(st));
	// 获取栈顶节点的下一个节点
	SLTNode* next = st->top->next;
	free(st->top);
	// 更新栈顶指针,使其指向新的栈顶节点
	st->top = next;
	st->size--;
}
(3)复杂度分析

时间复杂度:由于顺序栈还是链式栈花费时间都是一个常数,因此时间复杂度为O(1)。

空间复杂度:由于顺序栈和链式栈花费空间都是一个固定大小的空间,因此空间复杂度为O(1)。

7.打印栈的元素

(1)顺序栈
//栈的打印
void STPrint(ST* st)
{
	assert(st);
	assert(!STEmpty(st));
	// 从栈顶开始打印,直到栈底(但不包括索引-1)
	for (int i = st->top; i >= 0; i--)
	{
		printf("%d ", st->a[i]);
	}
}
(2)链式栈
//栈的打印
void STPrint(ST* st)
{
	assert(st);
	assert(!STEmpty(st));
	for (SLTNode* top = st->top; top != NULL; top = top->next)
	{
		printf("%d ", top->data);
	}
}
(3)复杂度分析

时间复杂度:由于顺序栈和链式栈打印都需要遍历整个栈,因此时间复杂度为O(N)。

空间复杂度:由于顺序栈和链式栈花费空间都是一个固定大小的空间,因此空间复杂度为O(1)。

8.销毁栈

(1)顺序栈
//栈的销毁
void STDestory(ST* st)
{
	assert(st);
	free(st->a);
	st->a = NULL;
	st->capacity = 0;
	st->top = -1;
}
(2)链式栈
//栈的销毁
void STDestory(ST* st) 
{
	assert(st);
	SLTNode* top = st->top;
	while (top != NULL)
	{
		SLTNode* next = top->next;
		free(top);
		top = next;
	}
	st->size = 0;
}
(3)复杂度分析

时间复杂度:由于顺序栈还是链式栈花费时间都是一个常数,因此时间复杂度为O(1)。

空间复杂度:由于顺序栈和链式栈花费空间都是一个固定大小的空间,因此空间复杂度为O(1)。

顺序栈和链式栈的对比

顺序栈链式栈
数据结构使用动态数组实现,元素在物理内存中连续存储使用链表实现,元素通过节点和指针连接,内存空间不连续
内存管理栈空间不足时可动态扩容,释放整个栈时一次性释放内存节点内存单独分配和释放,需要遍历链表以释放所有节点内存
时间效率可以通过数组下标直接访问栈内任意位置的元素,但是这不符合栈的定义由于每次都需要扩容操作,所以效率略比顺序栈低
空间效率顺序栈的扩容较大可能会造成空间的浪费内存使用相对灵活,但每个节点需要额外存储指针

完整代码

1.顺序表

Stack.h

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

typedef int STDataType;

typedef struct STDataType
{
	STDataType* a;
	int top;
	int capacity;
}ST;

//栈的初始化
void STInit(ST* st);

//栈的销毁
void STDestory(ST* st);

//入栈
void STPush(ST* st, STDataType x);
//出栈
void STPop(ST* st);

//取出栈顶数据
STDataType STTop(ST* st);

//判空
bool STEmpty(ST* st);

//获取数据个数
STDataType STSize(ST* st);

//栈的打印
void STPrint(ST* st);

Stack.c

#include"Stack.h"

//栈的初始化
void STInit(ST* st)
{
	assert(st);
	st->a = NULL;
	st->top = -1;
	st->capacity = 0;
}

//栈的销毁
void STDestory(ST* st)
{
	assert(st);
	free(st->a);
	st->a = NULL;
	st->capacity = 0;
	st->top = -1;
}

//入栈
void STPush(ST* st, STDataType x)
{
	assert(st);
	// 注意:由于top初始化为-1,所以满的条件是top == capacity - 1
	if (st->top == st->capacity - 1)
	{
		// 如果栈已满,则扩展栈的容量
		int newcapacity = st->capacity == 0 ? 4 : st->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(st->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		st->a = tmp;
		st->capacity = newcapacity;
	}
	// 增加栈顶索引,为新元素腾出空间
	st->top++;
	// 将新元素x存储在栈顶位置
	st->a[st->top] = x;
}

//出栈
void STPop(ST* st)
{
	assert(st);
	assert(st->top >= 0);
	st->top--;
}

//取出栈顶数据
STDataType STTop(ST* st)
{
	assert(st);
	assert(st->top >= 0);
	return st->a[st->top];
}

//判空
bool STEmpty(ST* st)
{
	assert(st);
	return st->top == -1;
}

//获取数据个数
STDataType STSize(ST* st)
{
	assert(st);
	return st->top + 1;
}

//栈的打印
void STPrint(ST* st)
{
	assert(st);
	assert(!STEmpty(st));
	// 从栈顶开始打印,直到栈底(但不包括索引-1)
	for (int i = st->top; i >= 0; i--)
	{
		printf("%d ", st->a[i]);
	}
}

2.链式表

Stack.h

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

typedef int STDataType;

typedef struct SListNode
{
	STDataType data;
	struct SListNode* next;
}SLTNode;

typedef struct Stack
{
	// 指向栈顶节点的指针
	SLTNode* top;
	int size;
}ST;

//栈的初始化
void STInit(ST* st);

//栈的销毁
void STDestory(ST* st);

//入栈
void STPush(ST* st, STDataType x);

//出栈
void STPop(ST* st);

//取出栈顶数据
STDataType STTop(ST* st);

//判空
bool STEmpty(ST* st);

//获取数据个数
STDataType STSize(ST* st);

//栈的打印
void STPrint(ST* st);

Stack.c

#include"Stack.h"

//栈的初始化
void STInit(ST* st)
{
	assert(st);
	st->size = 0;
	st->top = NULL;
}

//栈的销毁
void STDestory(ST* st) 
{
	assert(st);
	SLTNode* top = st->top;
	while (top != NULL)
	{
		SLTNode* next = top->next;
		free(top);
		top = next;
	}
	st->size = 0;
}

//入栈
void STPush(ST* st, STDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail:");
		return;
	}
	// 新节点的next指向原来的栈顶
	newnode->next = st->top;

	// 设置新节点的数据
	newnode->data = x;

	// 更新栈顶为新节点
	st->top = newnode;
	st->size++;
}

//出栈
void STPop(ST* st)
{
	assert(st);
	assert(!STEmpty(st));
	// 获取栈顶节点的下一个节点
	SLTNode* next = st->top->next;
	free(st->top);
	// 更新栈顶指针,使其指向新的栈顶节点
	st->top = next;
	st->size--;
}

//取出栈顶数据
STDataType STTop(ST* st)
{
	assert(st);
	assert(!STEmpty(st));
	return st->top->data;
}

//判空
bool STEmpty(ST* st)
{
	return (st->top == NULL);
}

//获取数据个数
STDataType STSize(ST* st)
{
	return st->size;
}

//栈的打印
void STPrint(ST* st)
{
	assert(st);
	assert(!STEmpty(st));
	for (SLTNode* top = st->top; top != NULL; top = top->next)
	{
		printf("%d ", top->data);
	}
}

结束语

本篇博客简要介绍了一下栈,接下来我们将接着学习与栈有些类似的另一个数据结构——队列。

数据结构——链式队列和循环队列

感谢各位大佬们的支持!!!

求点赞收藏关注!!!

十分感谢!!!

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

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

相关文章

[数据集][目标检测]快递包裹检测数据集VOC+YOLO格式5382张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;5382 标注数量(xml文件个数)&#xff1a;5382 标注数量(txt文件个数)&#xff1a;5382 标注…

【JavaSec】JavaIO流

0x04 IO流 文章目录 0x04 IO流创建文件读取文件信息文件目录操作删除创建目录 文件流操作读文件写文件write(byte[] b) 方法write(byte[] b, int off, int len) 方法 文件拷贝 向大佬致敬&#xff1a; https://drun1baby.top 创建文件 三种方法 一张图对比着看 其实感觉差别不…

C语言函数详解(上)【库函数】

目录 前言 一、函数的概念 二、库函数 1.标准库和头文件 2.库函数使用方式 3.如何通过以上的网站查看库函数信息 &#xff08;以sqrt为例&#xff09; 3.1 功能 3.2 头文件包含 3.3 库函数文档的⼀般格式 1. 函数原型 ​编辑2. 函数功能介绍 ​编辑3. 参数和返…

vue路由导航简单实现

1.代码 安装路由模块 npm install vue-router 导入路由组件&#xff1a; import { createRouter, createWebHistory } from vue-router 首先创建三个vue组件显示路由内容&#xff1a; index.vue <template><!-- 首页跳转 --><router-link to"/my"&…

Ciallo~(∠・ω・ )⌒☆第二十四篇 python 异常处理

在Python中&#xff0c;异常处理是一种处理代码中发生错误的机制。它允许我们在运行时捕获和处理异常&#xff0c;以避免程序终止并提供错误信息。 一、try-except语句 try块用于包含可能发生异常的代码&#xff0c;而except块用于捕获和处理异常。一旦try块中的代码引发了异常…

8月21日笔记

Frp Frp(Fast e Reverse ) Proxy) 是一款简单&#xff0c;好用&#xff0c;稳定的隧道工具。Frp 使用 Go语言开发&#xff0c;支持跨平台&#xff0c;仅需下载对应平台的二进制文件即可执行&#xff0c;没有额外依赖。它是一款高性能的反向代理应用&#xff0c;可以轻松地进行…

机器人蓝牙通信绕坑

为机器人添加手机和语音控制是很有必要的&#xff0c;其中蓝牙通信有很多的坑。 一个是蓝牙模块版本&#xff0c;流行的2.0&#xff0c;4.0&#xff0c;5.0一旦买错&#xff0c;会十分麻烦&#xff0c;如果不懂编程&#xff0c;无法和板子通信&#xff0c;又连不上电脑和手机&…

用户画像中挖掘类标签的生产过程

背景 在用户画像中&#xff0c;除了用户的性别年龄等基础标签&#xff0c;我们还可以使用机器学习算法挖掘一些标签&#xff0c;比如用户购买意向等标签 技术方案 挖掘类标签的生产一般有以下的流程图&#xff0c;主要包括特征选择&#xff0c;也就是选择用户的哪些行为作为…

GATK AlleleList接口介绍

在 GATK&#xff08;Genome Analysis Toolkit&#xff09;中&#xff0c;AlleleList 接口是一个用来表示等位基因&#xff08;alleles&#xff09;列表的接口。Allele 是遗传学中用于表示某一特定基因座的不同形式的一个基本单位。AlleleList 接口定义了一些操作&#xff0c;使…

跨境电商系统架构分析

跨境电商系统是一个复杂而庞大的系统工程&#xff0c;涵盖了订单管理、支付管理、物流管理、报关管理、产品管理、跨境营销和数据分析等多个功能模块。这些模块相互协作&#xff0c;共同支撑起跨境电商的全球化运营。 订单管理&#xff1a;负责处理用户的订单信息&#xff0c;…

零基础学习Python(六)

1. 元类的应用 使用元类给对象添加一个固有属性author: 对类名进行限定&#xff0c;要求类名必须是大写字母开头&#xff1a; class MetaC(type):def __init__(cls, name, bases, attrs):if not name.istitle():raise TypeError("类名必须是大写字母开头~")return …

想提升网站排名?试试轮换IP

在竞争激烈的互联网环境中&#xff0c;提高网站排名是每个中小型网站主的共同目标。其中&#xff0c;轮换IP是一种不容忽视的优化工具。虽然听起来可能有些陌生&#xff0c;但轮换IP却能在提升网站排名方面发挥关键作用。本文将深入探讨轮换IP如何帮助中小型网站提升搜索引擎排…

南大-ICS2021 PA1~PA2.2 学习笔记记录

文章目录 代码github网址ICS2021其他博客基础设施: 简易调试器表达式求值词法分析递归求值如何测试自己的代码 监视点的实现扩展表达式求值的功能实现监视点 阅读源码 2译码执行用RTL表示指令行为实现常用的库函数实现常用的库函数 代码github网址 https://github.com/xiao-ta…

一主一从读写分离

目录 介绍 一主一从 原理 准备 配置主从复制 验证主从复制 一主一从读写分离 安装MyCat schema.xml配置 server.xml配置 测试 介绍 读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作&#xff0c;从数据库提供读操作&am…

Springboot中多线程数据库操作下的事务一致性问题的解决方案

文章目录 1 代码实现1.1 正常情况1.2 异常情况总结 1 代码实现 1.1 正常情况 我们采用手动开启事务的方式 public void add(CountDownLatch countDownLatch) {executor.submit(() -> {TransactionStatus transaction dataSourceTransactionManager.getTransaction(transa…

【HTML】HTML学习之引入CSS样式表

1、CSS样式规则 选择器{属性1:属性值1; 属性2:属性值2; 属性3:属性值3;}2、HTML引入CSS样式表 2.1、行内式 行内式也称为内联样式&#xff0c;是通过标签的style属性来设置元素的样式&#xff0c;其基本语法格式如下: <标签名 style"属性1:属性值1; 属性2:属性值2;…

Proxy/Skeleton

设计模式之&#xff08;十二&#xff09;代理模式_skeleton proxy 模式-CSDN博客 在RMI中&#xff0c;客户端可以通过一个桩&#xff08;Stub&#xff09;对象与远程主机上的业务对象进行通信&#xff0c;由于桩对象和远程业务对象接口的一致&#xff0c;因此对于客户端而言&am…

Maven的一些相关知识【重修】《包括私服搭建!》

mvnrepository.com Maven 下载jar包的位置&#xff01; 【该部分有教程】 这是什么nb代码投稿视频-这是什么nb代码视频分享-哔哩哔哩视频

python之matplotlib (6 等高线和热力图)

等高线 import numpy as np import matplotlib.pyplot as pltdef f(x,y):return (1-x/2x**5y**3)*np.exp(-x**2-y**2) n256 xnp.linspace(-3,3,n) yx X,Ynp.meshgrid(x,y) plt.contourf(X,Y,f(X,Y),8,alpha0.75,cmapviridis) plt.colorbar() Cplt.contour(X,Y,f(X,Y),8,colors…

第64期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…