栈和队列讲解

栈和队列

  • 栈和队列
    • 3.1 栈和队列的定义和特点
    • 3.2 案例引用
    • 3.3 栈的顺序表示和实现
    • 3.4 栈的链式表示和实现
    • 3.5 队列的顺序表示和实现
    • 3.6 队列的链式表示和实现

3.1 栈和队列的定义和特点

栈 (stack) 是限定仅在表尾进行插入或删除操作的线性表。 因此, 对栈来说, 表尾端有其 特殊含义, 称为栈顶 (top), 相应地, 表头端称为栈底 (bottom/base)。 不含元素的空表称为空栈。

栈是按后进先出的原则进行的, 如 图(a) 所示。 因此, 栈又称为后进先出 (Last In First Out, LIFO) 的线性表

插入元素叫入栈(PUSH),删除元素叫弹栈(POP)

image-20230801212051890

栈与一般线性表的区别

image-20230801213722984

和栈相反,队列(queue)是一种**先进先出(First In First Out, FIFO)**的线性表。它只允许在表 的一端进行插入,而在另一端删除元素。

在队列中,允许插入的一端称为队尾(rear), 允许 删除的一端则称为队头(front)。

image-20230801212349071

3.2 案例引用

**【案例3.1】**进制转换

将十进制整数 N 向其他进制数d(二、八、十六)的转换

转换法则: N除以d倒取余

例如:将十进制数159转换成八进制数

image-20230801214650193

利用栈的先进后出,将每次计算结果入栈,最终取出来的就是结果

image-20230801214724287

**【案例3.2】**括号匹配的校验

假设表达式中允许包含两种括号: 圆括号和方括号

image-20230801214922504

遇见左括号就入栈,遇见右括号就与栈顶元素相比较,符合就弹栈,不符合就是不匹配。

image-20230801215514238

**【案例3.3】**表达式求值

这里介绍的算法是由运算符优先级确定运算顺序的对表达式求值算法—— 算符优先算法

表达式组成

操作数(Qperand): 常数、变量

运算符(operator): 算术运算符、关系运算符和逻辑运算符

界限符(delimiter) :左右括弧和表达式结束符。

任何一个算术表达式都由操作数(常数、变量)、算术运算符(+、-、/)和界限符 (括号、表达式结束符“#'、虚设的表达式起始符# )组成。后两者统称为算符。

例如:# 3* (7-1)#

image-20230801220624360

3.3 栈的顺序表示和实现

栈的抽象数据类型的定义

image-20230801220951700

顺序栈的表示和实现

由于栈本身就是线性表,于是栈也有顺序存储和链式存储俩种实现方式。顺序存储——顺序栈 | 链式存储——链栈

存储方式: 同一般线性表的顺序存储结构完全相同

利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。

使用俩个指针base、top:base指向栈底元素,top指向栈顶元素。但是为了操作方便,一般 top 指向栈顶的下一个存储地址。另外使用一个 stacksize 表示栈的最大容量。

image-20230801222241444

空栈时,base 和 top 同时指向栈底,没有元素可以弹栈,只能入栈

image-20230801222614910

满栈时,top 与 base 的差值等于stacksize,不允许在入栈,只能弹栈

image-20230801222755002

使用数组作为顺序栈存储方式特点

简单、方便,但是容易产生溢出【数组大小固定】

上溢(overflow):栈已经满,又要压入元素

下溢(underflow):栈已经空,还要弹出元素


代码实现栈的初始化

public class Stack {// 栈的最大容量public int stackSize;// top指针,指向栈顶public int top;// base指针,指向栈底public int base;// 使用数组模拟栈public Object[] stack;// 初始化栈public Stack(int stackSize) {this.stackSize = stackSize;stack = new Object[stackSize];top = 0;base = 0;}/*** 是否空栈* */public boolean isEmpty(){return top == base;}/*** 满栈* */public boolean isFull(){return  (top - base) == stackSize;}/*** 栈的长度* */public int getLength(){return  top - base;}/*** 清空栈* */public void clear(){this.top = this.base;}}

顺序栈的入栈

  • 判断顺序栈是否已经满了,若满了抛出异常
  • 将元素压入栈
  • top指针+1
    /*** 入栈* */public void push(Object e) {if (isFull()) {throw new RuntimeException("栈满");}stack[top++] = e;}

顺序栈的出栈

  • 判断是否栈空,若空抛出异常
  • top指针-1
  • 弹出元素
    /*** 出栈* */public Object pop(){if (isEmpty()) {throw new RuntimeException("栈空");}return stack[--top];}

3.4 栈的链式表示和实现

链栈是运算受限的单链表,只能在链表头部进行操作

image-20230806104625248

通过上面的图片,可以看出链表的方向是和单链表相反的,从 an ~ a1,这样的目的主要是为了操作方便,栈的弹栈、入栈都是从表头开始的,这样不会影响后续结点

链栈的初始化:

public class ChainedStack {// 头指针Node s;public ChainedStack() {this.s = null;}
}

入栈操作:

image-20230806110521898

入栈结点p,将p结点的next域指向s,s重新指向栈顶

image-20230806110551495

    /*** 入栈* */public void push(Node p) {// 将新结点指向栈顶元素p.next = s;// 更改指针s = p}

弹栈操作:

将弹出的结点使用一个指针p保存,将s指向p.next

image-20230806110824188

/*** 弹栈* */public Object pop(){if (s == null) {throw new RuntimeException("空栈");}Node res = s;s = res.next;return res.data;}

取栈顶元素

    /*** 取栈顶元素* */public Object getTop(){if (s == null) {throw new RuntimeException("空栈");}return s.data;}

3.5 队列的顺序表示和实现

image-20230802220932178

与栈不同的是,无论是弹栈、压栈始终是移动一个栈顶指针,而队列中,出队移动front头指针,入队移动rear尾指针。

image-20230802222012253

代码实现

public class Queue {int maxSize; // 队列的最大容量Object[] queue;int front; // 头指针int rear; // 尾指针public Queue(int maxSize) {this.maxSize = maxSize;queue = new Object[maxSize];front = 0 ;rear = 0;}// 是否为空队列public boolean isEmpty(){return front == rear;}// 是否为满队列public boolean isFull(){return rear == maxSize-1;}// 获取队列元素的个数public int getLength(){return front - rear;}// 入队public void add(Object e){if (isFull()) {throw new RuntimeException("满队");}queue[rear++] = e;}// 出队public Object out(){if (isEmpty()){throw new RuntimeException("空队");}Object o = queue[front];// 将出队的元素存放位置置空queue[front++] = null;return o;}}

问题

image-20230802223515182

假设当 rear 已经等于 maxSize【黄色部分为队列长度】之后,还能入队吗?

答案是不能的,数组下标是从0开始的,rear= maxSize时,在入队就已经越界了。

其实我们发现下标从 0~front 之间是没有存储元素的,长度虽然为 6,但实际上存储了俩个元素,并没有真正的存满,这种情况就称为 假溢出

相对应的有真溢出,就是front = 0,rear = maxsize, 之间已经存满了元素,此时在入队就已经没有位置了。这种情况就无需处理了,而假溢出才是我们真正要解决的。

image-20230802224505194

解决 假溢出 情况:

使用循环队列的方式解决假溢出情况, 当 rear = maxSize 时,让 rear 重新从 指向 0 的位置,当入队时,插入在 0 的位置上

image-20230802225315575

那么怎么让 rear 重新变为0 呢? 可以利用模运算(取余)

当 rear =1,maxSize=6时,1% 6 = 1, 此时rear指向 1

当 rear =2,maxSize=6时,2% 6 = 2, 此时rear指向 2

当 rear =6,maxSize=6时,6% 6 = 0, 此时rear指向 0

插入元素e: queue[rear] = e; (rear+1) % maxSize
删除元素: e =  queue[front]; (front+1) % maxSize

循环队列如何判断空队列和满队列呢?

在循环队列中,可以发现无论是空队还是满队,都是 front == rear

image-20230802230157146

解决方案

1.另外设一个标志以区别队空

2.另设一个变量录元素个数

3.少用一个元素空间

我用的是第二种方式,易于理解。

代码实现

public class CircularQueues {Object[] queue;int front;int rear;int maxSize;int count; // 用来记录元素的个数// 初始化队列public CircularQueues(int maxSize) {this.maxSize = maxSize;this.front = 0;this.rear = 0;this.queue = new Object[maxSize];this.count = 0;}/*** 判断是否为空* */public boolean isEmpty() {return count == 0;}/*** 判断是否为满* */public boolean isFull() {return count == maxSize;}/*** 入队* */public void add(Object e) {if (isFull()) {throw new RuntimeException("满队");}// 入队操作queue[rear] = e;rear = (rear + 1) % maxSize;// 记录数+1count++;}/*** 出队* */public Object out() {if (isEmpty()) {throw new RuntimeException("空队");}Object e = queue[front];front = (front + 1) % maxSize;// 记录数-1count--;return e;}/*** 获取对头元素* */public Object getHeadEle() {if (isEmpty()) {throw new RuntimeException("空队");}return queue[front];}
}

3.6 队列的链式表示和实现

顺序队列采用数组实现,大小固定,若无法估计队列的长度,直接使用链式队列。

头指针的指向头结点,尾指针指向尾结点。

image-20230806095447728

链队列指针变化情况

初始情况下,头指针、尾指针都指向头结点

image-20230806100310551

链队列的初始化

public class ChainedQueues {// 头指针Node front;// 尾指针Node rear;// 头指针Node dummyHead;public ChainedQueues() {// 初始化,头结点的数据随意,可以不设置this.dummyHead = new Node(-1);this.front = dummyHead;this.rear = dummyHead;}
}

入队操作

头删尾插,将rear指针的next域指向新结点,并将 rear指针后移即可。

    /*** 入队* */public void add(Node e) {rear.next = e;rear = e;}

出队操作:

出队操作时将首元结点删除掉并返回,很简单,头指针的next域就是要出队的元素。

    public Object out() {if (rear == front) {throw new RuntimeException("队空");}Node res = front.next;front.next = res.next;return res.data;}

但是这样写会有一些问题,如果出队的正好是队尾元素,此时 front.next = null, 但是 rear 指针仍然在指向 res,因此判断非空时,会返回 false

因此当出队的是队尾元素还需要将 rear 指针重新指向头结点

image-20230806103305729

    /*** 出队* 如果出队的是最后一个结点,还要修改尾指针* */public Object out() {if (isEmpty()) {throw new RuntimeException("队空");}Node res = front.next;front.next = res.next;if (res == rear) {// 最后一个结点rear = dummyHead;}return res.data;}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【GO语言基础】控制流

系列文章目录 【Go语言学习】ide安装与配置 【GO语言基础】前言 【GO语言基础】变量常量 【GO语言基础】数据类型 【GO语言基础】控制流 文章目录 系列文章目录条件语句if-else 结构判断一个字符串是否为空:switch结构 循环结构for 循环(C风格&#xff…

微服务高可用容灾架构设计

导语 相对于过去单体或 SOA 架构,建设微服务架构所依赖的组件发生了改变,因此分析与设计高可用容灾架构方案的思路也随之改变,本文对微服务架构落地过程中的几种常见容灾高可用方案展开分析。 作者介绍 刘冠军 腾讯云中间件中心架构组负责…

【17 > 分布式接口幂等性】2. Update的幂等性原理解析

一、 根据 唯一业务号去更新 数据的情况 1.1 原理 1.2 操作 1.3 实战 Stage 1:表添加 version 字段 Stage 2:前端 > 版本号放入隐藏域 Stage 3:后台 > 使用版本号作为更新条件 二、更新操作没有唯一业务号,可使用Tok…

黑马头条 热点文章实时计算、kafkaStream

热点文章-实时计算 1 今日内容 1.1 定时计算与实时计算 1.2 今日内容 kafkaStream 什么是流式计算kafkaStream概述kafkaStream入门案例Springboot集成kafkaStream 实时计算 用户行为发送消息kafkaStream聚合处理消息更新文章行为数量替换热点文章数据 2 实时流式计算 2…

IDEA2023.2.1取消空包隐藏,切换包结构(Compact Middle Packages)

解决2023版idea的包结构 取消勾选即可。 取消勾选Compact Middle Packages选项后,再创建包时,即可自动创建树形结构。 仅供学习使用!

模方新建工程时,显示空三与模型坐标系不一致怎么解决

答:检查空三xml与模型的metadata.xml的坐标系是否一致,metadata文件是否有在data目录外面。 模方是一款针对实景三维模型的冗余碎片、水面残缺、道路不平、标牌破损、纹理拉伸模糊等共性问题研发的实景三维模型修复编辑软件。模方4.0新增单体化建模模块,…

安装 Gin 框架

首先需要在目录下初始化一下 go 项目 go init可以看到生成了一个go.mod文件,然后使用以下命令安装 gin 框架 go get -u github.com/gin-gonic/gin养成一个好习惯,在写项目之前先初始化项目 go mod init go mod tidy如果不初始化项目的话没有第三方库补…

递归算法学习——图像渲染,岛屿的数量,最大的岛屿

目录 ​编辑 一,图像渲染 1.题意 2.解释 3.题目接口 4.解题思路及代码 二,岛屿的数量 1.题意 2.解释 3.题目接口 4.解题思路及代码 三,最大的岛屿 1.题意 2.解释 3.题目接口 4.解题代码即思路 一,图像渲染 1.题意…

【面试题】前端开发中如何高效渲染大数据量?

前端面试题库 (面试必备) 推荐:★★★★★ 地址:前端面试题库 【国庆头像】- 国庆爱国 程序员头像!总有一款适合你! 在日常工作中,较少的能遇到一次性往页面中插入大量数据的场景…

通过Git Bash将本地文件上传到本地github

1. 新建一个仓库( Repository) 1.1登录Github,点击个人头像,点击Your repositories,点击New。 1.2 填写信息 Repository name: 仓库名称 Description(可选): 仓库描述介绍,不是必填项目。~~建议填写上哦!…

IIC协议理解及驱动OLED屏

1.iic协议是串行半双工总线,主要应用于近距离,低速芯片之间通信。 两根线 SCL SDA 2.IIC总线通讯过程 1.主机发送起始信号占用总线 2.主机发送一个字节数据指明从机地址和后续字节的传输方向。 七位地址位一位来调节后续字节传输方向 最后一位&…

echarts静态饼图

<div class"cake"><div id"cakeChart"></div></div> import * as echarts from "echarts";mounted() {this.$nextTick(() > {this.getCakeEcharts()})},methods: {// 饼状图getCakeEcharts() {let cakeChart echart…