回顾Java基础——栈和队列详解

一、概述

栈跟队列都是保存数据的容器。还有前面的线性表。

栈和队列主要用于计算过程中保存的临时数据,如果数据在编程时就可以确定,那么使用几个变量就可以临时存储,但是如果存储的数据项数不能确定,就需要复杂的存储机制。这样的存储机制称为缓存。栈和队列就是使用最多的缓存结构。

1、栈、队列和数据使用顺序

栈和队列是最简单的缓存结构,它们只支持数据项的存储和访问,不支持数据项之间的任何关系。
因此,它们最重要的两个操作就是存入元素和取出元素。

当然还有其他的操作,比如创建,检查空(或者满)的操作。

按照数据在时间上生成的先后顺序进行不同的处理:

后生成的数据需要先处理。就需要先进后出的数据结构进行存储(栈,Last In First Out)。
先生成的数据需要先处理。就需要先进先出的数据结构进行存储(队列,First In First Out)。
那么如何实现这两种结构呢,最自然跟最简单的实现方式就是使用线性表。

2、应用环境

很多,从略。python中可以直接使用list实现栈的功能。可以使用deque,实现队列的功能。

二、栈:概念和实现

存入栈中的元素之间没有任何具体关系,只有到来的时间先后顺序。
因此,就没有元素的位置和元素的前后顺序等概念。

栈的基本性质保证,在任何时刻可以访问、删除的元素都是在此之前最后存入的那个元素。

2、栈的顺序实现

把list直接当做栈使用,也是可以的,只是这样的对象跟list无法区分,
并且提供了栈不应该支持的list所有操作,这样就会威胁到栈的安全。

3、栈的链接表实现

顺序表实现的优缺点:

优点:表元素放在一个连续的存储块内,方便管理。
缺点:当扩大内存的时候,需要一次复制操作,这是一次高代价的操作。
采用链接表实现的优点,不需要一个连续的存储块就可以,而且没有替换存储的高代价操作。
但是它的缺点零散的存储比较依赖python的存储管理,还有每个结点的链接也会带来开销。

三、栈的应用

栈的用途主要是两方面:

作为程序中的辅助结构,用于保存需要前进后出的数据。
利用栈的先进后出的性质,做一些特定的事情。

1、简单应用:括号匹配问题

问题:

考虑程序中的括号匹配,这里只考虑三种括号,(){}[]。
每种括号都包括一个开括号和一个闭括号,相互对应。
对于括号里面的嵌套,也要正确匹配。

分析:

由于程序中无法预知要处理的括号数量,因此不能使用固定的变量来进行保存,必须使用缓存结构。
需要存储的开括号的使用原则是后存入先使用,符合LIFO原则。
如果一个开括号完成配对,就删除这个括号。

实现过程:

顺序扫描检查正文里的一个个字符。
检查的时候跳过无关的字符
遇到开括号将其压入栈中
遇到闭括号,弹出栈顶元素与之配对。
如果匹配成功继续,直到正文结束。
不过不匹配,以检查失败结束。

2、表达式的表示,计算和变换

表达式和计算的描述

我们常用的表达式是用二元运算符连接起来的中缀表达式。例如:(1+2)*3

中缀表达式是习惯的表达式,但是它有一些缺点,比如不能准确描述计算的顺序,
通常需要借助一些辅助符号(比如圆括号),或者优先级(比如先乘除后加减)。

比如上面的例子中,如果没有(),那么计算顺序将会完全不一样。

因此,当计算机处理表达式的时候,通过会把中缀表达式转化为后缀表达式(逆波兰表达式)。
当然,还有一种表达式是前缀表达式(波兰表达式)。

后缀表达式和前缀表达式都不需要括号或者优先级,或者结合性的规定。

例子:

中缀形式:(3-5) * (6+17*4) / 3
前缀形式:/ * - 3 5 + 6 * 17 4 3
后缀形式:3 5 - 6 17 4 * + * 3 /

后缀表达式的计算

分析后缀表达式的计算规则

遇到运算对象,把它保存起来。
遇到运算符,取出最近保存的两个运算对象,进行运算,将结果保存起来。

遇到问题:

使用什么结构保存数据
表达式里的元素如何表示(可以字符串)
处理失败的情况。

解决方法:

使用栈保存数据。
表达式的元素使用字符串存储
在计算的过程中,如果栈中的元素不足两个,那么操作就失败(说明后缀表达式写的是错误的)。
还有,当计算完成之后,栈里最终只能有一个元素(也就是计算的结果)。
为判断不足两个元素的情况,必须给栈添加一个属性(写成方法也可以),就是栈的深度。

总结:

一、栈:

1、原理:
是一种特殊的线性表,它的底端是封死的,增删操作只能在顶部做。
数据是先入后出。

回顾Java基础——栈和队列详解_第1张图片

PUSH:入栈
POP:出栈

2、栈的分类

顺序栈:
核心是数组,在初始化就需要决定开辟栈空间的大小

回顾Java基础——栈和队列详解_第2张图片

链式栈:
存储核心是链表,可以任意进行入栈与出栈操作

回顾Java基础——栈和队列详解_第3张图片

队列和栈只是一种拿数据和取数据的方式,不管数据结构是怎样的,只要满足先入后出就是栈
只要满足先入先出就是队列。

链表,数组,hash表等,是属于存储结构,有不同的存储空间个结构。

核心在于top(栈顶指针),出栈返回top–,入栈top++

实际上,我们在执行方法的时候,也会有一个方法栈,方法内部调用方法,会先返回内部方法的结果,
这也是一种压栈入栈。

二、队列:

1、原理:
队列是一种在队列尾端进行插入,而在队列头端进行删除的线性表。
队列的插入操作称为入队(push),删除操作称为出队(pop)。
数据是先进先出

2、队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,
因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

回顾Java基础——栈和队列详解_第4张图片

添加元素时,元素只能从队尾一端进入队列

回顾Java基础——栈和队列详解_第5张图片
元素只能从队首出队列

链表队列:

链表头部为队首,链表尾部为队尾。
存储一个指向队尾的指针,方便从链表尾插入元素
使用带头节点的链表,方便从链表头删除元素。

回顾Java基础——栈和队列详解_第6张图片

3、Queue:

Queue接口与List、Set同一级别,都是继承了Collection接口。
内置的不阻塞队列:
PriorityQueue :有序队列,可以按照自然排序或者java.util.Comparator 实现来定位。
ConcurrentLinkedQueue :是基于链接节点的、线程安全的队列。并发访问不需要同步。

实现阻塞接口的:

ArrayBlockingQueue :
基于数组实现的一个有界队列,

在构造时需要指定容量, 并可以选择是否需要公平性
(其实就是通过将ReentrantLock设置为true来 达到这种公平性的:
即等待时间最长的线程会先操作)

它是基于数组的阻塞循环队列

LinkedBlockingQueue:

基于链表的队列,容量是几乎没有上限的(Integer.MAX_VALUE)

PriorityBlockingQueue:

 带优先级的 队列,而不是先进先出队列。元素按优先级顺序被移除

DelayQueue:

存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。
头部是延迟期满后保存时间最长的 Delayed 元

你可能感兴趣的:(java基础部分)