探索数据结构:顺序栈与链式栈的原理、实现与应用

news2024/11/15 8:06:59

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 栈的定义

栈简单来说就是一种只允许在一端进行操作(插入与删除)的线性表。即栈是严格遵守后进先出(Last In First Out)的数据结构,简称LIFO结构

img

img

  • 栈顶(Top):线性表允许进行插入删除的那一端。
  • 栈底(Bottom):固定的,不允许进行插入和删除的另一端。

2. 栈的分类

当我们了解栈的定义之后,我们就能大概知晓其实现方式无非就是顺序表或者单链表。根据其实现方式,我们又能将栈分为顺序栈链式栈。

img

  • 因为单链表头插的效率O(1)明显比尾差O(N)更高,所以我们用单链表实现栈时最好以链表的头为栈顶。如果一定要以尾节点作为栈顶的话,最好以双向链表来实现。本章实现链表栈时以头节点作为栈顶。

3. 栈的功能

  1. 栈的初始化。
  2. 判断栈是否为空。。
  3. 返回栈顶元素。
  4. 返回栈的大小。
  5. 入栈与出栈。
  6. 打印栈的元素。
  7. 销毁栈。

4. 栈的声明

4.1. 顺序栈

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

typedef int STDataType;
typedef struct Stack 
{
	STDataType* a;
	int top;		//栈顶指针
	int capacity;	//容量
}Stack;
  • 当然也有实现top指向当前栈顶元素的,只不过这时top初始化要为-1,这样才能在填入元素时刚好指向栈顶元素。

4.2. 链式栈

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

typedef struct SListNode
{
	STDataType data;
	struct SListNode* next;
}SListNode;
typedef struct Stack
{
	SListNode* top;
	int size;
}Stack;

5. 栈的功能的具体实现

5.1. 栈的初始化

顺序栈与链式栈的初始化分别与顺序表,链表的初始化大致相同。顺序栈先预先分配一块空间,而链式栈可以先初始为NULL。

5.1.1. 顺序栈
void StackInit(Stack* st)
{
	assert(st);
	st->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (st->a == NULL)
	{
		perror("fail mallic");
		return;
	}
	st->top = 0;
	st->capacity = 4;
}
5.1.2. 链式栈
void StackInit(Stack* st)
{
	assert(st);
	st->top = NULL;
	st->size = 0;
}
5.1.3. 复杂度分析
  • 时间复杂度:无论是顺序栈还是链式栈花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是顺序栈还是链式栈花费空间都是一个固定大小,所以空间复杂度为O(1)

5.2. 判断栈是否为空

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

5.2.1. 顺序栈
bool StackEmpty(Stack* st)
{
	return (st->top == 0);
}
5.2.2. 链式栈

只需要判断链表头节点下一个是否为空(NULL)。

bool StackEmpty(Stack* st)
{
	return (st->top == NULL);
}
5.2.3. 复杂度分析
  • 时间复杂度:无论是顺序栈还是链式栈花费判断栈是否为空时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是顺序栈还是链式栈判断栈是否为空花费空间都是一个固定大小,所以空间复杂度为O(1)。

5.3. 返回栈顶元素

因为不知道top指向的是栈顶还是栈顶的下一个元素,所以为了避免歧义,我们单独实现一个函数来获取栈顶元素。

5.3.1. 顺序栈
STDataType StackTop(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	return st->a[st->top - 1];
}
5.3.2. 链式栈
STDataType StackTop(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	return st->top->data;
}
5.3.3. 复杂度分析
  • 时间复杂度:无论是顺序栈还是链式栈返回栈顶元素花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是顺序栈还是链式栈返回栈顶元素花费空间都是一个固定大小,所以空间复杂度为O(1)

5.4. 返回栈的大小

5.4.1. 顺序栈
int StackSize(Stack* st)
{
	return st->top;
}
5.4.2. 链式栈
int StackSize(Stack* st)
{
	return st->size;
}
5.4.3. 复杂度分析
  • 时间复杂度:无论是顺序栈还是链式栈返回栈的大小花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是顺序栈还是链式栈返回栈的大小花费空间都是一个固定大小,所以空间复杂度为O(1)

5.5. 入栈

5.5.1. 顺序栈

顺序栈在入栈之前需要检查栈是否已满。

void STCheckCapacity(Stack* st)
{
	if (st->top == st->capacity)
	{
		int newCapacity = st->capacity == 0 ? 4 : st->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(st->a, newCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail\n");
			return ;	
		}
		st->a = tmp;
		st->capacity = newCapacity;
	}
}
void StackPush(Stack* st, STDataType x)
{
	assert(st);
	STCheckCapacity(st);
	st->a[st->top] = x;
	st->top++;
}
5.5.2. 链式栈
SListNode* ListCreat(STDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(STDataType));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->next = NULL;
	newnode->data = x;
	return newnode;
}
void StackPush(Stack* st, STDataType x)
{
	assert(st);
	SListNode* newnode = ListCreat(x);
	if (StackEmpty(st))
	{
		st->top = newnode;
	}
	else
	{
		newnode->next = st->top;
		st->top = newnode;

	}
	st->size++;
}
5.5.3. 复杂度分析
  • 时间复杂度:顺序栈支持下标的随机访问并且我们以单链表的头作为栈顶,所以时间复杂度为O(1)。
  • 空间复杂度:顺序栈插入数据可能会扩容,如果以最坏的情况来算,空间复杂度为O(N)。而链式栈增加的空间为固定大小,所以空间复杂度为O(1)。

5.6. 出栈

5.6.1. 顺序栈
void StackPop(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	st->top--;
}
5.6.2. 链式栈
void StackPop(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	SListNode* next = st->top->next;
	free(st->top);
	st->top = next;
	st->size--;
}
5.6.3. 复杂度分析
  • 时间复杂度:无论是顺序栈还是链式栈出栈花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是顺序栈还是链式栈出栈花费空间都是一个固定大小,所以空间复杂度为O(1)

5.7. 打印栈元素

5.7.1. 顺序栈
void StackPrint(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	for (int i = st->top - 1; i >= 0; i--)
	{
		printf("%d\n", st->a[i]);
	}
}
5.7.2. 链式栈
void StackPrint(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	for (SListNode* top = st->top; top!=NULL; top=top->next)
	{
		printf("%d\n", top->data);
	}
}
5.7.3. 复杂度分析
  • 时间复杂度:无论是顺序栈还是链式栈打印都需要遍历整个栈,所以时间复杂度为O(N)。
  • 空间复杂度:无论是顺序栈还是链式栈花费空间都是一个固定大小,所以空间复杂度为O(1)

5.8. 销毁栈

5.8.1. 顺序栈
void StackDestroy(Stack* st)
{
	assert(st);
	free(st->a);
	st->a = NULL;
	st->top = st->capacity = 0;
}
5.8.2. 链式栈
void StackDestroy(Stack* st)
{
	assert(st);
	SListNode* top = st->top;
	while(top!=NULL)
	{
		SListNode* next = top->next;
		free(top);
		top = next;
	}
	st->size = 0;
}
5.8.3. 复杂度分析
  • 时间复杂度:无论是顺序栈还是链式栈销毁花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是顺序栈还是链式栈销毁花费空间都是一个固定大小,所以空间复杂度为O(1)

6. 顺序栈与链式栈的对比与应用

6.1. 对比

对比项顺序栈链式栈
时间效率顺序栈支持下标的随机访问,所以时间效率较高,但是这也超出了栈的定义。如果以链表的头作为栈顶,那么链式栈的时间效率也是不错的。但是每次都需要扩容操作,所以效率略比顺序栈低
空间效率顺序栈的扩容较大可能会造成空间的浪费,但是扩容发生的概率低,平均效率还是较高的。链式栈是按需扩容,所以不会造成空间的浪费,但是定义链表节点需要额外定义指针,所以链表节点占用空间更大

6.2. 应用

栈在我们计算机领域应用广泛,如

  1. 当我们打开网页,将多个页面关闭时,这几个页面也就进入栈中。当我们多次点击返回时,就会依次出栈。
  2. 在我们程序设计中,每次调用一个函数时,都会进入一个栈中。当这个函数结束时,这个函数就会出栈。这个知识点我们将会在C语言中详细阐述。

7. 完整代码

7.1. 顺序栈

7.1.1. stack.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack 
{
	STDataType* a;
	int top;		//栈顶指针
	int capacity;	//容量
}Stack;
void StackInit(Stack* st);//初始化栈
bool StackEmpty(Stack* st);//判断栈是否为空
STDataType StackTop(Stack* st);//返回栈顶元素
int StackSize(Stack* st);//栈的大小
void StackPush(Stack* st, STDataType x);//入栈
void StackPop(Stack* st);//出栈
void StackPrint(Stack* st);//打印
void StackDestroy(Stack* st);//销毁栈
7.1.2. stack.c
#include"Stack.h"
void STCheckCapacity(Stack* st)
{
	if (st->top == st->capacity)
	{
		int newCapacity = st->capacity == 0 ? 4 : st->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(st->a, newCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail\n");
			return ;	
		}
		st->a = tmp;
		st->capacity = newCapacity;
	}
}

void StackInit(Stack* st)
{
	assert(st);
	st->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (st->a == NULL)
	{
		perror("fail mallic");
		return;
	}
	st->top = 0;
	st->capacity = 4;
}
bool StackEmpty(Stack* st)
{
	return (st->top == 0);
}
STDataType StackTop(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	return st->a[st->top - 1];
}
int StackSize(Stack* st)
{
	return st->top;
}
void StackPush(Stack* st, STDataType x)
{
	assert(st);
	SListNode* newnode = ListCreat(x);
	st->a[st->top] = x;
	st->top++;
}
void StackPop(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	st->top--;
}
void StackPrint(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	for (int i = st->top - 1; i >= 0; i--)
	{
		printf("%d\n", st->a[i]);
	}
}

void StackDestroy(Stack* st)
{
	assert(st);
	free(st->a);
	st->a = NULL;
	st->top = st->capacity = 0;
}

7.2. 链式栈

7.2.1. 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;
}SListNode;
typedef struct Stack
{
	SListNode* top;
	int size;
}Stack;
void StackInit(Stack* st);//初始化栈
bool StackEmpty(Stack* st);//判断栈是否为空
STDataType StackTop(Stack* st);//返回栈顶元素
int StackSize(Stack* st);//栈的大小
void StackPush(Stack* st, STDataType x);//入栈
void StackPop(Stack* st);//出栈
void StackPrint(Stack* st);//打印
void StackDestroy(Stack* st);//销毁栈
7.2.2. stack.c
void StackInit(Stack* st)
{
	assert(st);
	st->top = NULL;
	st->size = 0;
}

bool StackEmpty(Stack* st)
{
	return (st->top == NULL);
}
STDataType StackTop(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	return st->top->data;
}
int StackSize(Stack* st)
{
	return st->size;
}
SListNode* ListCreat(STDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(STDataType));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->next = NULL;
	newnode->data = x;
	return newnode;
}
void StackPush(Stack* st, STDataType x)
{
	assert(st);
	SListNode* newnode = ListCreat(x);
	if (StackEmpty(st))
	{
		st->top = newnode;
	}
	else
	{
		newnode->next = st->top;
		st->top = newnode;

	}
	st->size++;
}
void StackPop(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	SListNode* next = st->top->next;
	free(st->top);
	st->top = next;
	st->size--;
}
void StackPrint(Stack* st)
{
	assert(st);
	assert(!StackEmpty(st));
	for (SListNode* top = st->top; top!=NULL; top=top->next)
	{
		printf("%d\n", top->data);
	}
}

void StackDestroy(Stack* st)
{
	assert(st);
	SListNode* top = st->top;
	while(top!=NULL)
	{
		SListNode* next = top->next;
		free(top);
		top = next;
	}
	st->size = 0;
}

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

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

相关文章

将OpenCV与gdb驱动的IDE结合使用

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV4.9.0开源计算机视觉库在 Linux 中安装 下一篇&#xff1a;将OpenCV与gcc和CMake结合使用 ​ 能力 这个漂亮的打印机可以显示元素类型、、标志is_continuous和is_subm…

【论文阅读】基于多特征融合的智能合约缺陷检测方法

摘要&#xff1a; 1、预处理&#xff1a;颜色标记、词汇提取、字符转换、合约之间的继承关系的提取 2、 使用融合模型进行特征提取&#xff08;BERT、CNN、BiLSTM&#xff09; 3、使用node2vec随机游走算法&#xff0c;将合约之间的继承关系作为输入得到合约关系的特征向量。 4…

通过 Socket 手动实现 HTTP 协议

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

如何让uni-app开发的H5页面顶部原生标题和小程序的顶部标题不一致?

如何让标题1和标题2不一样&#xff1f; 修改根目录下的App.vue&#xff08;核心代码如下&#xff09; <script>export default {onLaunch() {// 监听各种跳转----------------------------------------[navigateTo, redirectTo, reLaunch, switchTab, navigateBack, ].…

[论文笔记] ChatDev:Communicative Agents for Software Development

Communicative Agents for Software Development&#xff08;大模型驱动的全流程自动化软件开发框架&#xff09; 会议arxiv 2023作者Chen Qian Xin Cong Wei Liu Cheng Yang团队Tsinghua University论文地址https://arxiv.org/pdf/2307.07924.pdf代码地址https://github.com/O…

HTML_CSS学习:表格、表单、框架标签

一、表格_跨行与跨列 1.相关代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>表格_跨行与跨列</title> </head> <body><table border"1" cellspacing"0&qu…

(一)基于IDEA的JAVA基础4

注释文本&#xff0c;注释模版 单行注释://开头放在代码前面&#xff0c;对少部分。 多行注释:快捷方式ctrlshift/,对段落代码注 释。 文档注释:/**……**/&#xff0c;用于声明作者或创作时 间。 文档注释如何设置&#xff0c;首先找到File中…

【前端Vue】Vue3+Pinia小兔鲜电商项目第2篇:什么是pinia,1. 创建空Vue项目【附代码文档】

全套笔记资料代码移步&#xff1a; 前往gitee仓库查看 感兴趣的小伙伴可以自取哦&#xff0c;欢迎大家点赞转发~ 全套教程部分目录&#xff1a; 部分文件图片&#xff1a; 什么是pinia Pinia 是 Vue 的专属状态管理库&#xff0c;可以实现跨组件或页面共享状态&#xff0c;是…

html5cssjs代码 033 SVG元素示例

html5&css&js代码 033 SVG元素示例 一、代码二、解释 一个SVG图形&#xff0c;该图形由一个椭圆、一个圆形和一个矩形组成。 一、代码 <!DOCTYPE html> <html lang"zh-cn"> <head><title>编程笔记 html5&css&js SVG元素示例…

公司系统中了.rmallox勒索病毒如何恢复数据?

早晨上班时刻&#xff1a; 当阳光逐渐洒满大地&#xff0c;城市的喧嚣开始涌动&#xff0c;某公司的员工们纷纷踏入办公大楼&#xff0c;准备开始新的一天的工作。他们像往常一样打开电脑&#xff0c;准备接收邮件、查看日程、浏览项目进展。 病毒悄然发作&#xff1a; 就在员…

Databend x CubeFS:面向未来的企业级云原生数据存储与分析

用场景的丰富&#xff0c;企业面临着前所未有的数据存储挑战。大规模数据存储变得日常化&#xff0c;伴随着超大容量和快速变化的I/O需求&#xff0c;传统的存储解决方案已经难以满足企业对弹性、运维效率及总体拥有成本&#xff08;TCO&#xff09;的更高要求。这些挑战促使基…

三角形重心坐标插值法 Interpolation Across Triangles: Barycentric Coordinates

本专栏内容整理了GAMES101的计算机图形学课程的主要内容&#xff0c;作为我学习计算机图形学的一份复习备份或叫做笔记。内容中如有错误&#xff0c;或有其他建议&#xff0c;欢迎大家指出。 附上GAMES101计算机图形学课程&#xff1a;GAMES101: 现代计算机图形学入门正在上传…

Django日志(二)

一、Handler Handler决定如何处理logger中的每条消息。它表示一个特定的日志行为,例如 将消息写入屏幕、文件或网络Socket handler对应的是个字典,每一个键都是一个handler的名字,每个值又一个字典,描述了如何配置对应的handler实例 2.1、内置Handler class(必需):处理…

【每日八股】Java基础经典面试题4

前言&#xff1a;哈喽大家好&#xff0c;我是黑洞晓威&#xff0c;25届毕业生&#xff0c;正在为即将到来的秋招做准备。本篇将记录学习过程中经常出现的知识点以及自己学习薄弱的地方进行总结&#x1f970;。 本篇文章记录的Java基础面试题&#xff0c;如果你也在复习的话不妨…

Scikit-Learn逻辑回归(二)

Scikit-Learn逻辑回归二&#xff1a;多项式与正则化 1、多项式回归回顾1.1、逻辑回归为什么要使用多项式1.2、多项式回归及原理 2、逻辑回归与多项式 1、多项式回归回顾 本文接上篇&#xff1a;Scikit-Learn逻辑回归(一) 上篇中&#xff0c;我们详细介绍了逻辑回归的概念、原理…

第六十一回 放冷箭燕青救主 劫法场石秀跳楼-编译安装飞桨paddlepaddle@openKylin+RISCV

卢俊义在水里被张顺抓住&#xff0c;用轿子抬到了梁山。宋江等人下马跪在地上迎接&#xff0c;请他坐第一把交椅。卢俊义宁死不从&#xff0c;大家只好说留他在山寨几天&#xff0c;先让李固带着马车货物回去。吴用对李固说&#xff0c;你的主人已经答应坐第二把交椅了&#xf…

matlab实现Logistic回归

一、目的和要求 1.编程实现Logistic Regression并应用于数据集&#xff1b; 2.绘制二元分类函数、sigmoid函数和代价函数&#xff1b; 3.正则化logistic回归代价。 二、算法介绍 步骤&#xff1a; 选择一个合适的分类函数来实现分类&#xff08;Sigmoid函数&#xff09; …

OpenHarmony开发之图形UI组件解析

简介 图形UI组件实现了一套系统级的图形引擎。 该组件为应用开发提供UIKit接口&#xff0c;包括了动画、布局、图形转换、事件处理&#xff0c;以及丰富的UI组件。 组件内部直接调用HAL接口&#xff0c;或者使用WMS(Window Manager Service)提供的客户端与硬件交互&#xff…

[flask] flask的基本介绍、flask快速搭建项目并运行

笔记 Flask Flask 本身相当于一个内核&#xff0c;其他几乎所有的功能都要用到扩展&#xff08;邮件扩展Flask-Mail&#xff0c;用户认证Flask-Login&#xff0c;数据库Flask-SQLAlchemy&#xff09;&#xff0c;都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、…

哪些企业适合构建企业新媒体矩阵?

⭐关注矩阵通服务号&#xff0c;探索企业新媒体矩阵搭建与营销策略 新媒体矩阵就是在某个平台或多个平台开设、联动多个账号&#xff0c;组建有关系的不同账号集群。 在数字化转型的浪潮下&#xff0c;矩阵已然成为企业实现品牌塑造、市场开拓与用户互动的重要阵地。 然而&…