数据结构——栈和队列(栈的顺序存储结构和链式存储结构)

news2024/9/23 18:11:35

 栈的定义

  • 栈是一种重要的线性结构,可以这样讲,栈是前面讲过的线性表的一种具体形式。
  • 就像我们刚才的例子,栈这种后进先出的数据结构应用是非常广泛的。在生活中,例如我们的浏览器,每次点击一次“后退”都是退回到最近的一次浏览网页。
  • 例如我们Word,Photoshop等的“撤销”功能也是如此。再例如我们C语言的函数,也是利用栈的基本原理实现的。
官方定义

栈(stack)是一个后进先出(Last in first out,LIFO)的线性表,它要求只在表尾进行删除和插入操作。

所谓的栈,其实也就是一个特殊的线性表(顺序表、链表),但是他在操作上有一些特殊的要求和限制:

——栈的元素必须“后进先出”。

——栈的操作只能在这个线性表的表尾进行。

——注:对栈来说,这个表尾称为栈的栈顶(top),相应的表头称为栈底(bottom)。

栈的插入操作

栈的插入操作(Push),叫做进栈,也称为压栈,入栈。类似子弹放入弹夹的动作。

栈的删除操作

栈的删除操作(Pop),叫做出栈,也称为弹栈。如同弹夹中的子弹出夹。

栈的顺序存储结构

  • 因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也有分为栈的顺序存储结构和栈的链式存储结构。
  • 最开始栈中不含有任何数据,叫做空栈,此时栈顶就是栈底,然后数据从栈顶进入,栈顶和栈底分离,整个栈的当前容量变大。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。
typedef int ElemType;
typedef struct
{
	ElemType* base;
	ElemType* top;
	int stackSize;
}sqStack;
  • 这里定义了一个顺序存储的栈,它包含了三个元素:base,top,stackSize。其中base是指向栈底的指针变量,top是指向栈顶的指针变量,stackSize指示栈的当前可使用的最大容量。
创建一个栈
#define STACK_INIT_SIZE 100
initStack(sqStack* s)
{
	s->base = (ElemType*)malloc(STACK_INIT_SIZE*sizeof(ElemType));//申请一块空间
	if (!s->base)
		exit(0);
	s->top = s->base; 
	s->stackSize = STACK_INIT_SIZE;
}

malloc函数向内存申请一块连续可用的空间,如果开辟成功,则返回一个指向这个开辟好空间的起始地址 ,开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查!

 入栈操作
  • 入栈操作又叫做压栈操作,就是向栈中存放数据。
  • 入栈操作要在栈顶进行,每次向栈中压入一个数据,top指针就要+1,直到栈满为止。
#define STACKINCREMENT 10 //每次追加空间的大小
Push(sqStack* s, ElemType e)
{
	//如果栈满,追加空间
	if (s->top - s->base >= s->stackSize)
	{
		s->base = (ElemType*)realloc(s->base,(s->stackSize+STACKINCREMENT)*sizeof(ElemType));

		if (!s->base)
			exit(0);

		s->top = s->base + s->stackSize;//设置栈顶 栈底+新的栈容量
		s->stackSize = s->stackSize + STACKINCREMENT;//设置栈的最大容量,原来的容量+追加的容量
	}
	*(s->top) = e; //给栈底存入数据
	s->top++; //指向栈顶的指针+1
}

realloc函数事实上也是调用malloc函数,在内存中开辟类外一个空间出来,将原来的内容拷贝到新的内存当中,要使用这个函数的话要加入头文件<stdlib.h>。

出栈操作 
  •  出栈操作就是在栈顶取出数据,栈顶指针随之下移的操作。
  • 每当从栈内弹出一个数据,栈的当前容量就-1。
Pop(sqStack* s, ElemType* e)
{
	if (s->top == s->base) //栈空了,没有数据存放,此时可以进行返回
		return;
	*e = *--(s->top); //先栈顶指针-1,然后再取出数据赋值
}

注意:top指针指向的栈顶是没有数据存放的,所以要先指向-1,再进行取值操作。

 清空一个栈
  • 所谓清空一个栈,就是将栈中的元素全部作废,但栈本身物理空间并不发生改变(不是销毁)。
  • 这里原理跟高级格式化只是单纯地清空文件列表而没有覆盖硬盘的原理是一样的。
ClearStack(sqStack* s)
{
	s->top = s->base;  //栈顶指针指向栈底,但原来的数据还是存在,但是我们看不到
}
销毁一个栈
  • 销毁一个栈与清空一个栈不同,销毁一个栈是要释放掉该栈所占据的物理内存空间,因此不要把销毁一个栈和清空一个栈这两种操作混淆。
DestroyStack(sqStack* s)
{
	int i, len;
	len = s->stackSize;
	for (i = 0; i < len; i++)
	{
		free(s->base);  //利用栈底指针一个一个数据释放掉
		s->base++;
	}
	s->base = s->top = NULL;  //栈顶和栈底指针都指向NULL
	s->stackSize = 0;  //最后栈容量大小清零
}
计算栈的当前容量
  • 计算栈的当前容量也就是计算栈中元素的个数,因此只要返回s->top - s->base即可。
  • 注意,栈的最大容量是指该栈占据内存空间的大小,其值是s->stackSize,它与栈的当前容量不是一个概念。
int StackLen(sqStack *s)
{
    return (s->top-s->s->base);
}
实例分析
  • 题目:利用栈的数据结构特点,将二进制转化为十进制数。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define STACK_INIT_SIZE 20 //初始最大容量
#define STACKINCREMENT  10 //栈满时每次追加的容量
typedef char ElemType;
typedef struct  //定义一个栈结构
{
	ElemType* base;
	ElemType* top;
	int stackSize;
}sqStack;
/*初始化一个栈*/
void InitStack(sqStack* s)
{
	s->base = (ElemType*)malloc(STACK_INIT_SIZE*sizeof(ElemType));
	if (!s->base)
		exit(0);
	s->top = s->base;
	s->stackSize = STACK_INIT_SIZE;
}
/*压栈/入栈*/
void Push(sqStack* s, ElemType e)
{
	if (s->top - s->base >= s->stackSize)
	{
		s->base = (ElemType*)realloc(s->base,(s->stackSize+STACKINCREMENT)*sizeof(ElemType));
		if (!s->base)
			exit(0);
	}
	*(s->top) = e;
	s->top++;
}
/*出栈/弹栈*/
void Pop(sqStack* s, ElemType* e)
{
	if (s->top == s->base)
	{
		return;
	}
	*e = *--(s->top);
}

int StackLen(sqStack s)
{
	return (s.top - s.base);//指针相减不是将地址相减,而是将指针指向的元素他们之间的元素差 中间隔了多少个元素
}
int main()
{
	ElemType c;
	sqStack s;
	int len, i, sum = 0;
	InitStack(&s);
	printf("请输入二进制数,输入#符号表示结束!\n");
	scanf_s("%c",&c);
	while (c != '#')
	{
		Push(&s,c);
		scanf_s("%c",&c);
	}
	getchar();
	len = StackLen(s);
	printf("栈的当前容量是:%d\n",len);
	
	for (i = 0; i < len; i++)
	{
		Pop(&s,&c);
		sum = sum + (c - 48) * pow(2, i);
	}
	printf("转化为十进制数是:%d\n",sum);

	return 0;
}

运行结果:

 栈的链式存储结构

  • 栈的链式存储结构,简称栈链。(通过我们用的都是栈的顺序存储结构存储,链式存储我们作为一个知识点,了解即可)
  • 栈因为只是栈顶来做插入和删除操作,所以比较好的方法就是将栈顶放在单链表的头部,栈顶指针和单链表的头指针合二为一。

栈顶相当于单链表的表头,栈底相当于单链表的表尾。

typedef struct StackNode
{
	ElemType data; //存放栈的数据
	struct StackNode* next;
}StackNode,*LinkStackPtr;

typedef struct LinkStack
{
	LinkStackPtr top;//top指针
	int count;       //栈元素计数器
}LinkStack;
进栈操作
  • 对于栈链的Push操作,假设元素值为e的新结点是s,top为栈顶指针,我们可以得到如下代码:
typedef struct StackNode
{
	ElemType data; //存放栈的数据
	struct StackNode* next;
}StackNode,*LinkStackPtr;

typedef struct LinkStack
{
	LinkStackPtr top;//top指针
	int count;       //栈元素计数器
}LinkStack;

Status Push(LinkStack* s, ElemType e)
{
	LinkStackPtr p = (LinkStackPtr)malloc(sizeof(StackNode));
	p->data = e;
	p->next = s->top;
	s->top = p;
	s->count++;
	return OK;
}
出栈操作
  • 至于栈链的出栈Pop操作,假设变量p用来存储要删除的栈顶指针,将栈顶指针下移一位,最后释放p即可。
/*弹栈/出栈*/
Status Pop(LinkStack* s, ElemType* e)
{
	LinkStackPtr p;
	if (StackEmpty(*s)) //判断是否为空栈
		return ERROR;
	*s = s->top->data;
	p = s->top;
	s->top = s->top->next;
	free(p);
	s->count--;
	return OK;
}

注意:使用纸笔进行画图更有助于理清逻辑。

终极实践

逆波兰表达式

对于(1-2)*(4+5),如果用逆波兰表达法,应该是这样:1 2 - 4 5 + *

  • 数字1和2进栈,遇到减号运算符则弹出两个元素进行运算并把结果入栈。
  • 4和5入栈,遇到加号运算符,4和5弹出栈,相加后将结果9入栈。
  • 然后又遇到乘法运算符,将9和-1弹出栈进行乘法计算,此时栈空并无数据压栈,-9为最终运算结果。

逆波兰计算器

  • 实现对逆波兰输入的表达式进行计算。
  • 支持带小数点的数据。
  • 正常的表达式--->逆波兰表达式   
  •  a+b--->a b +            a+(b-c)--->a b c - +       
  •  a+(b-c)*d--->a b c - d * +

        正常的式子我们叫做中缀表达式,它方便人类的阅读计算,但计算机处理中序表达式(中缀表达式)非常复杂。计算机处理后缀表达式非常简便,因为计算机普遍采用的内存结构是栈式结构,遵循后入先出的原则。只需入栈和出栈两个操作就可以实现逆波兰表达式的计算。如果遇到数字就入栈,如果遇到符号就将栈顶的两个元素出栈并作相应的运算,之后将结果入栈,最终栈中剩下的那个数字就是最终结果。

代码:

#include<stdio.h>
#include<stdlib.h>
#include<ctype.h>

#define STACK_INIT_SIZE 20
#define STACK_DILA_SIZE 10

typedef double elemtype;

typedef struct
{
	elemtype * base;
	elemtype * top;
	int stacksize;
}sqstack;

void initstack(sqstack*s)
{
	s->base = (elemtype*)malloc(STACK_INIT_SIZE * sizeof(elemtype));
	if (!s->base)
	{
		exit(0);
	}
	s->top = s->base;
	s->stacksize = STACK_INIT_SIZE;
}

void push(sqstack*s, elemtype x)
{
	if (s->top - s->base >= s->stacksize)
	{
		s->base = (elemtype*)realloc(s->base, (STACK_DILA_SIZE + STACK_INIT_SIZE) * sizeof(elemtype));
		if (!s->base)
		{
			exit(0);
		}
		s->top = s->base + STACK_INIT_SIZE;
		s->stacksize += STACK_DILA_SIZE;
	}
	*(s->top) = x;
	s->top++;
}

void pop(sqstack*s, elemtype*x)
{
	if (s->base == s->top)
	{
		return;
	}
	s->top--;
	*x = *s->top;
}

int stacklen(sqstack*s)
{
	return (s->top - s->base);
}

int main()
{
	sqstack*s = (sqstack*)malloc(sizeof(sqstack));
	initstack(s);
	char c;
	char str[10] = { '0' };//设置输入单个数字的缓冲区
	int i = 0;
	elemtype a, b, x;
	scanf_s("%c", &c,sizeof(c));
	while (c != '#')
	{
		while (isdigit(c)||c=='.')//过滤数字
		{
			str[i]=c;
			i++;
			scanf_s("%c", &c, sizeof(c));
			if (c == ' ')
			{
				x = atof(str);//将字符型转为double型
				push(s, x);
				i = 0;
				break;
			}
		}
		switch (c)
		{
		case '+':
			pop(s, &a);
			pop(s, &b);
			push(s, a + b);
			break;
		case '-':
			pop(s, &a);
			pop(s, &b);
			push(s, b - a);
			break;
		case '*':
			pop(s, &a);
			pop(s, &b);
			push(s, a*b);
			break;
		case '/':
			pop(s, &a);
			pop(s, &b);
			push(s, b / a);
			break;
		}
		scanf_s("%c", &c, sizeof(c));
	}
	pop(s, &x);
	printf("%f", x);

	return 0;
}

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

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

相关文章

数据库密钥管理的密钥生成

数据库密钥管理是指对数据库中使用的加密密钥进行的一系列安全操作&#xff0c;以确保数据的机密性、完整性和可用性。这一管理过程通常包括密钥的生成、存储、分发、使用和销毁等环节。以下是关于数据库密钥管理的详细解析&#xff1a; 一、密钥的生成 目的&#xff1a;生成用…

谷歌图像生成AI-imagen 3新手入门指南!

1Google 最近推出了 Imagen 3&#xff0c;这是目前为止其最先进的文本生成图像模型。它基于之前的版本进行了改进&#xff0c;提供了更加精确的图像生成&#xff0c;减少了图像中的瑕疵&#xff0c;能够生成逼真、栩栩如生的图像。相比于早期版本&#xff0c;Imagen 3 可以处理…

Linux:重定向以及管道

重定向&#xff08;重新定向命令的输出&#xff09; 将前面命令的输出&#xff0c;作为内容&#xff0c;写入到后面的文件 管道 管道&#xff08;操作符号 | &#xff09; 作用&#xff1a;将前面命令的输出&#xff0c;传递给后面命令&#xff0c;作为后面命令的参数…

通信工程学习:什么是SNI业务节点接口

SNI&#xff1a;业务节点接口 SNI业务节点接口&#xff0c;全称Service Node Interface&#xff0c;是接入网&#xff08;AN&#xff09;和一个业务节点&#xff08;SN&#xff09;之间的接口&#xff0c;位于接入网的业务侧。这一接口在通信网络中扮演着重要的角色&#xff0c…

【机器学习-四-无监督学习unsupervise learning-聚类算法简介】

无监督学习unsupervise learning 聚类聚类的过程相似度度量方法聚类的方法划分式层次聚类基于密度的聚类 上一节讲的无监督学习&#xff0c;但是很多人可能会很疑惑&#xff0c;没有目标&#xff0c;那算法是怎么学会该怎样分类的呢&#xff1f;今天就简介一下其中的聚类算法。…

使用 SpringBoot 基础web开发的支持

首先导入项目相关的依赖&#xff1a; pom.xml 文件&#xff1a; 导入相关项目依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-in…

句子成分——每日一划(八)

目录 一、原句 二、第一部分 三、第二部分 一、原句 In class society everyone lives as a member of a particular class, and every kind of thinking, without exception, is stamped with the brand of a class. 来源&#xff1a;二、阶级和阶级斗争 二、第一部分 In…

免费像素画绘制软件 | Pixelorama v1.0.3

Pixelorama 是一款开源像素艺术多工具软件&#xff0c;旨在为用户提供一个强大且易于使用的平台来创作各种像素艺术作品&#xff0c;包括精灵、瓷砖和动画。这款软件以其丰富的工具箱、动画支持、像素完美模式、剪裁遮罩、预制及可导入的调色板等特色功能&#xff0c;满足了像素…

凑数字dp解决

前言&#xff1a;没有想到这个题目可以用dp来做&#xff0c;我们之前能够达到的最大的数字 当前的这一个数字为当前最大的数 ‘题目地址 #include<bits/stdc.h> using namespace std;#define int long long const int N (int)1e510;signed main() {int t; cin >>…

[全网首发]怎么让国行版iPhone使用苹果Apple Intelligence

全文共分为两个部分&#xff1a;第一让苹果手机接入AI&#xff0c;第二是让苹果手机接入ChatGPT 4o功能。 一、国行版iPhone开通 Apple Intelligence教程 打破限制&#xff1a;让国行版苹果手机也能接入AI 此次发布会上&#xff0c;虽然国行 iPhone16 系列不支持 GPT-4o&…

【Vue】2

1 Vue 生命周期 Vue生命周期&#xff1a;一个 Vue 实例从 创建 到 销毁 的整个过程 创建(create)阶段&#xff1a;组件实例化时&#xff0c;初始化数据、事件、计算属性等挂载(mount)阶段&#xff1a;将模板渲染并挂载到 DOM 上更新(update)阶段&#xff1a;当数据发生变化时…

Qt:饿汉单例(附带单例使用和内存管理)

前言 本文主要写饿汉单例以及单例的释放&#xff0c;网上很多教程只有单例的创建&#xff0c;但是并没有告诉我们单例的内存管理&#xff0c;这就很头疼。 正文 饿汉式单例 // SingletonClass.h #ifndef SINGLETONCLASS_H #define SINGLETONCLASS_H #include <QObject&g…

【Android Studio】使用雷电模拟器调试

文章目录 进入开发者模式使雷电模拟器adb连接PC测试 进入开发者模式 多次点击版本号 -开区USB调试 使雷电模拟器adb连接PC 写cmd脚本 雷电模拟器端口为5555 &#xff0c;脚本内容如下&#xff1a; adb.exe connect 127.0.0.1:5555双击bat脚本文件 测试

华为应用权限初次申请及二次申请

应用权限概述 系统提供了一种允许应用访问系统资源&#xff08;如&#xff1a;通讯录等&#xff09;和系统能力&#xff08;如&#xff1a;访问摄像头、麦克风等&#xff09;的通用权限访问方式&#xff0c;来保护系统数据&#xff08;包括用户个人数据&#xff09;或功能&…

10.4K Star,高性能富文本编辑器

Hi&#xff0c;骚年&#xff0c;我是大 G&#xff0c;公众号「GitHub 指北」会推荐 GitHub 上有趣有用的项目&#xff0c;一分钟 get 一个优秀的开源项目&#xff0c;挖掘开源的价值&#xff0c;欢迎关注。 在现代 Web 开发中&#xff0c;富文本编辑器是不可或缺的一部分&…

IEEE 754浮点数表示

浮点数组成 以单精度浮点数(32位)为例说明&#xff1a; 十进制浮点数组成&#xff1a; 十进制基数(base)为10 二进制浮点数组成&#xff1a; 二进制基数(base)为2 以IEEE 754标准表示十进制数&#xff1a; ( 7.625 ) 10 (7.625)_{10} (7.625)10​&#xff0c;先将其转成…

异常冲突行为和危险识别系统源码分享

异常冲突行为和危险识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Co…

《微信小程序实战(1)· 开篇示例 》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

Vue3 响应式工具函数isRef()、unref()、isReactive()、isReadonly()、isProxy()

isRef() isRef()&#xff1a;检查某个值是否为 ref。 isRef函数接收一个参数&#xff0c;即要判断的值。如果该参数是由ref创建的响应式对象&#xff0c;则返回true&#xff1b;否则&#xff0c;返回false。 import { ref, isRef } from vue const normalValue 这是一个普通…

【网络安全的神秘世界】ssrf服务端请求伪造

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 ssrf 一、SSRF原理及漏洞演示 1.1 漏洞简介 SSRF&#xff08;Server-Side Request Forgery&#xff1a;服务端请求伪造&am…