目录
1,栈(Stack)
1.1 概念
1.2 栈的使用
1.3 栈的模拟实现
1.4 栈的应用场景
1.5 概念区分
1.6 使用链表来实现栈
2, 队列(Queue)
2.1 概念
2.2 队列的使用
2.3 队列模拟实现
3,双端队列 (Deque)
4,面试题
4.1 用队列实现栈
4.2 用栈实现队列
1,栈(Stack)
1.1 概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
1.2 栈的使用
push():叫做压栈,往栈里面放元素
查看push源代码,是有返回值的,他的返回值代表入栈的元素
pop():叫做出栈,弹出栈顶元素
此时就弹出了56
我们查看pop的源代码,他的返回值代表出栈的元素
peek():获取栈顶元素,但是不删除
源码:
empty():检测栈是否为空
源码:
stack可以调用empty可以理解,为什么可以调用isEmpty,Stack里面并没有这个方法呀!!
观察到Stack继承了Vector,isEmpty方法在Vector这个类当中,所以也继承了Vector类当中的方法
1.3 栈的模拟实现
创建一个类,叫做MyStack,自己来实现一个栈
那怎么来表示这个栈呢?
可以用一个数组来表示,每次往数组的最后一个存储元素,删也是删最后一个
1,push
怎么来放呢,就可以用到前面讲过的,定义一个usedSize来记录元素个数,并在对应位置存放元素,usedSize为0,在0下标存放一个元素,记录这个元素(usedSize++),在存放usedSize++下标位置的元素,再次记录这个元素(usedSize++),依次进行!!!
还要考虑数组满了的情况,对其进行扩容!
2,pop 弹出一个元素
首先如果是空的,就不能弹出元素,可以抛一个异常
可以发现,当usedSize为4的时候,我们需要弹出3下标的元素,即弹出elem[ usedSize -1 ]即可
3,peek 获取栈顶元素
这就是我们自己实现的一个栈。
可以将这个自己实现的栈变成泛型:
1.4 栈的应用场景
1,改变元素的序列
2,将递归转化为循环(一般情况下会用栈)
比如:逆序打印链表
非递归实现:就可以申请一个栈,这个栈里面就去放节点,然后进行出栈打印,即为逆序打印
3,括号匹配
描述:给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
在括号匹配的时候,一定是最后一个左括号和第一个右括号是否匹配,如何找到最后一个左括号呢?使用栈来完成。
情况一:如果是在括号匹配的时候,申请一个栈,定义一个 i 下标来遍历字符串。遇到一个左括号把这个左括号放到栈里面,i++,又遇到一个左括号放到栈里面,当遇到右括号的时候,就和当前的栈顶元素去匹配。当遇到右括号时,让当前的这个右括号与栈顶元素进行匹配,匹配成功,就让当前栈顶元素进行出栈,i++,再去判断右括号与当前栈顶元素是否匹配,匹配成功,就让当前栈顶元素进行出栈。即括号是匹配的情况下,最后栈为空且字符串以遍历完成。
情况二:如果不匹配,同上只要是左括号都放到栈里面,当遇到右括号时,与栈顶元素不匹配,返回false
情况三:字符串还没有遍历完,栈已经空了的情况。如下面这种,左括号放到栈里面,当遇到右括号时,与栈顶元素匹配,但是 i 没有遍历完,栈已经空了
情况四:左括号放到栈里面,遇到右括号时,与栈顶元素进行匹配,弹出栈顶元素后,i 已经遍历完了,但是栈里面还有元素
4,逆波兰表达式求值
逆波兰表达式又叫做后缀表达式(将运算符写在操作数之后)
例如:
中缀表达式(平常所看到的表达式):2+(3*5)
对应的后缀表达式:235*+
推导:
运算:
题目描述:
示例:
给的是一个字符串数组,放到栈里面的应该是整型,所以要将数字字符转为整型。
所以可以写一个方法,判断拿到这个字符串是否为运算符
5,栈的压入,弹出序列 (上面第一题)
描述:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
6,最小栈
描述:设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
在常数时间内(时间复杂度为O(1))检索到最小值,一个栈是不能完成的,需要申请两个栈,一个普通栈,一个最小栈。
那如何能拿到一个最小栈呢?
1.5 概念区分
栈、虚拟机栈、栈帧有什么区别呢?
栈是一种数据结构。
虚拟机栈:如定义的局部变量就存于虚拟机栈当中。
栈帧:调用方法的时候为他开辟的一块内存。
至此:刚刚实现的栈或者说Java集合类的Stack底层是一个数组。这种栈叫做顺序栈!!!
1.6 使用链表来实现栈
首先栈遵循后进先出的原则,并且时间复杂度为O(1)
如果是单链表,那么入栈和出栈最好从链表头进行入栈和出栈。
相反如果链表的尾部进行出栈入栈时间复杂度为O(N)。
如果是双向链表来实现栈,那么复杂度均是O(1),如果以后要用到链式栈,使用双向链表会更好!
那么就可以直接当栈来使用。
为什么会报错?因为List这个接口没有实现push这个方法,要用LinkedList来引用这个对象。
这样就可以直接使用了,我们查看push,pop的源码。源码里面采用的是头插和头删!
所以可以拿数组实现栈,也可以拿链表实现栈。
如果要用数组实现的栈就用Stack这个类。
要用链式栈,使用LinkedList引用,或者Deque(叫做双端队列,下面会讲到)引用就可以当中栈来使用。
2, 队列(Queue)
2.1 概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头 (Head/Front)
例如:食堂打饭排队,先排队的先打饭
2.2 队列的使用
在Java中,Queue是个接口,底层是通过链表实现的。
三个红色框框为一组,三个绿色框框为一组,两组从结果上来说没有什么区别。
要看他们的区别,如add和offer都是入队操作。
add和offer都是向队列中添加一个元素,一些队列有大小限制,因此如果想在一个满的队列中加入一个新项:
- add:此时调用 add() 方法就会抛出一个 unchecked 异常
- offer:此时调用 offer() 方法会返回 false,可以在程序中进行有效的判断。
2.3 队列模拟实现
1,用链表来实现(源码里面用的链式实现)
首先队列遵循先进先出的原则,那么入队就可以从尾巴入,出队就可以从头部出
也可以入队从头部入,出队从尾巴出
我们可查看源码的offer:调用了add方法 进入add方法:源码实现的是尾插法
模拟实现Queue
2,用数组来实现队列
首先对头叫做first,队尾叫做last。
此时last遍历完了,数组满了
假设现在对头可以出,出掉12 23,那么我的对头为34。但是此时0,1下标还可以放元素,数组并没有满!!!
我们在数组满的时候让last指向first。可能first和last相遇代表数组为空,不管空还是满(后面解决),先让last走回来,此时就可以再次存放元素了。
那么这个数组就不是简单的数组了,这个数组循环利用了,把这个队列叫做循环队列。
相当于把这个数组卷起来!!! 现在这个数组是这个样子。
开始存放元素:
满或者空也不一定是在0位置,有可能last在7的时候first就进行出,一直出就可能是在7位置相遇。也可能last在7位置first就开始出,出到2位置又开始存,就也可能在2位置相遇。
到目前位置:相遇的时候是空的,相遇的时候也可能是满的!!!没有解决问题。
如何判断空和满!
方案一:
usedSize!!!放一个数据usedSize++,us == 0 为空,us == 数组长度为满。
方案二:
定义一个Boolean类型的flg = false 第一次相遇为false,第二次相遇为true。以标记的形式来判断。
方案三:(此代码用方案三实现)
浪费一个空间。也就是假如你的数组长度是8,但是最多只放7个元素。
即last和first相遇时为空,last的下一个为first就为满
如何让last或者first从7走到0,不能是last++或者first++,这样会导致数组下标越界,我们可给出一个公式:last = (last + 1)% 数组长度
代码实现:
首先此时代码有点错误
3,双端队列 (Deque)
双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。 那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
普通的队列只能从队尾进对头出,如果是双端队列,这个队列两边都可以进出。
有头插,尾插,还有头删尾删
LinkedList是双端队列的链表形式。
ArrayDeque是双端队列的线性实现(操作的是数组)。
对于这两个类(LinkedList,ArrayDeque),可以当作栈使用,也可以当作队列使用
4,面试题
4.1 用队列实现栈
首先一个队列是不能实现栈的,这两个是相互矛盾的,一个是先进先出,一个是先进后出。
既然一个队列达不到要求,就用两个队列来实现栈。
top:
代码实现:
4.2 用栈实现队列
还是一样的,一个栈是不能实现队列的。要申请两个栈。
代码实现:
至此,栈和队列都讲完了!!!