1. 定义和特性
1.1 堆栈
1.1.1 定义
堆栈(stack)简称为栈,它是一种只允许在表的一端进行操作插入和删除操作的的线性表,允许操作的一端称为栈顶,另一段称为栈底,当表中没有元素时,称之为空栈。
栈的插入操作简称为入栈或出栈,删除操作简称为出栈或退栈
1.1.2 特性
根据栈的定义,每次删除的元素总是栈中当前的栈顶元素,即此前最后进入的栈的元素。而最先进入的栈的元素一定在栈底,最后进入的元素一定在栈顶。也就是后进先出(Last In First Out,LIFO)原则
若n个元素的进栈顺序为1,2,3,....n, 那么有多少种可能的输出序列:
答案:(2n)! / ((n+1)(n!)^2)
1.2 队列
1.2.1 定义
队列(queue)简称,简称为队,它是一种允许在表的一端进行插入操作,而在表的另一段进行删除操作的线性表,允许进入插入的一端称为队尾,允许删除操作的的一端称为队头,没有元素的队列称为空队。
队列的插入操作简称为入队,删除操作简称为出队。
1.2.2 特性
根据定义,先进入队列的元素,在队头会先被删除掉,反映的是“先来先服务”的处理原则,也就是元素进入队列或者推出队列是按照先进先出(First In First Out,FIFO)原则
2. 数据结构
2.1 栈
2.1.1 顺序存储结构
底层使用数组来实现arr[0,1...n-1]代表有n个元素的栈,由于栈是一个动态结构,而数组是一个静态结构,所以存在可能溢出(overflow)的问题,所以就需要进行判断和扩容的操作。
java中栈Stack,是在Vector上实现了“后进先出”的特性,所以底层就是数组实现的。
public class Stack<E> extends Vector<E> {
public Stack() {
}
/**
* 向栈顶压入一个项
*/
public E push(E item) {
addElement(item);
return item;
}
/**
* 移走栈顶对象,将该对象作为函数值返回
*/
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
/**
* 查找栈顶对象,而不从栈中移走。
*/
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
/**
* 测试栈是否为空
*/
public boolean empty() {
return size() == 0;
}
/**
* 返回栈中对象的位置,从1开始。
*/
public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}
}
2.1.2 链式存储结构
底层使用线性链表来实现,对于链表来说,向头部或者尾部添加和删除元素的时间复杂度相同,所以向头部或者尾部操作时间复杂度是相同的,都是O(1)级别的
public void push(E e) {
list.addFirst(e);
}
public E pop() {
return list.removeFirst();
}
2.2 队列
2.2.1 顺序存储结构
底层使用数组来实现arr[0,1...n-1]代表有n个元素的的队列,还需使用head和tail 两个变量来记录队头元素和队尾元素的位置,由于栈是一个动态结构,而数组是一个静态结构,所以也需要进行判断和扩容的操作。
java中 Deque 接口是(Double Ended Queue) 的缩写,即双端队列。Deque 继承 Queue 接口,并扩展支持在队列的两端插入和删除元素。
其中:ArrayDeque是底层是使用数组来实现的。
补充ArrayDeque的源码
2.2.2 链式存储结构
底层使用线性链表来实现队列
java中LinkedList 实现Deque接口,并通过链表来实现。
简单补充LinkedList源码
3. 应用
3.1 用栈实现队列
思路:
- 2个栈,一个接受数据栈,一个吐出数据栈
- 每次push到接受数据栈后,都需要进行从接受数据栈 【倒dao】 吐出数据栈的操作
- 每次从吐出数据栈pop之前,都需要进行先从接受数据栈 【倒dao】 吐出数据栈的操作,然后再pop
- 每次peek之前,都需要进行先从接受数据栈 【倒dao】 吐出数据栈的操作,然后再peek
- 倒dao操作准守2个原则:
- 只有 吐出数据栈 为空时才能 dao 数据
- 从接受数据栈dao数据时,要dao就要一次dao干净
代码:
class MyQueue2 {
Stack<Integer> ins;
Stack<Integer> ous;
public MyQueue2() {
ins = new Stack<>();
ous = new Stack<>();
}
public void push(int x) {
//先push在dao
ins.push(x);
dao();
}
public int pop() {
//先倒在pop
dao();
return ous.pop();
}
public int peek() {
//先倒在pop
dao();
return ous.peek();
}
private void dao(){
//只有 吐出数据栈 为空时才能 dao 数据
if(ous.isEmpty()){
//要dao就要一次dao干净
while (!ins.isEmpty()){
ous.push(ins.pop());
}
}
}
public boolean empty() {
return ins.isEmpty() && ous.isEmpty();
}
}
3.2 用队列实现栈
思路:
- 使用2个双端队列inq,outq
- push时,将inq的最后元素加入到ouq的最前面,把所有inq元素按照这个原则放入到outq中
- pop时,从outq.pop即可
- peek时,从outq.peek即可
代码
class MyStack {
ArrayDeque<Integer> inq;
ArrayDeque<Integer> ouq;
/** Initialize your data structure here. */
public MyStack() {
inq = new ArrayDeque<>();
ouq = new ArrayDeque<>();
}
/** Push element x onto stack. */
public void push(int x) {
inq.add(x);
//inq的所有元素
while(!inq.isEmpty()){
//将inq的最后元素加入到ouq的最前面,把所有inq元素按照这个原则放入到outq中
ouq.addFirst(inq.pollLast());
}
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return ouq.poll();
}
/** Get the top element. */
public int top() {
return ouq.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return inq.isEmpty() && ouq.isEmpty();
}
}
3.3 栈中元素排序
题目1:将一个栈的元素(无序的),从栈顶到底,按照从大到小顺序进行排序。要求只能额外使用一个栈,不能使用其他数据结构。
思路:
- 要始终保持辅助栈中的顺序:从顶到底是从小到大的,只有这样才能在最后依次出栈到原始栈时是按照从大到小顺序
- 从原始栈中弹出cur元素进入到辅助栈时,要看下是否小于栈顶元素,如果不是需要将辅助栈中的所有小于cur的元素重新加入到原始栈
- 至到为空,或者辅助栈中栈顶元素大于cur时,将cur加入到辅助栈
- 重复2~3操作
- 最后原始栈中元素为空时,将辅助栈的元素依次pop,并push到原始栈即可
题目2:
栈排序。 编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。该栈支持如下操作:push、pop、peek 和 isEmpty。当栈为空时,peek 返回 -1
思路:
- stack1为数据栈,stack2为辅助栈,
- push时,
- 判断插入的元素cur是否小于栈顶元素,如果不是需要将元素放入到辅助栈
- 至到为空,或者栈顶元素大于cur时,将cur加入
- 同时将辅助栈的元素放回来, stack1就是有序的
- push完,始终保持push完stack1中元素时排好序的
代码
class SortedStack {
Stack<Integer> stack1;
Stack<Integer> stack2;
public SortedStack() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
/**
* push时,始终保持push完stack1中元素时排好序的
*
* @param val
*/
public void push(int val) {
if(!stack1.isEmpty()){
sortStack(stack1, stack2, val);
}else{
stack1.push(val);
}
}
public void pop() {
if(!stack1.isEmpty()){
stack1.pop();
}
}
public int peek() {
return stack1.isEmpty() ? -1 : stack1.peek();
}
public boolean isEmpty() {
return stack1.isEmpty() && stack2.isEmpty();
}
private void sortStack(Stack<Integer> stack1, Stack<Integer> stack2, int cur){
//cur是否小于栈顶元素,如果不是需要将元素放入到辅助栈
while (cur > stack1.peek() && !stack1.isEmpty()){
stack2.push(stack1.pop());
}
//至到为空,或者栈顶元素大于cur时,将cur加入
stack1.push(cur);
//同时将辅助栈的元素放回来, stack1就是有序的
while (!stack2.isEmpty()){
stack1.push(stack2.pop());
}
}
}
3.4 特殊的最小栈
实现一个特殊的栈,要求在实现栈的基础操作后,再实现一个返回栈中最小元素的方法。要求:pop、push、getMin 操作是O(1)
思路:
- 使用2个栈,其中stack1用来实现pop和push等原生操作,使用stack2来保存当前stack1中最小元素,以此来实现getMin操作
- 加入cur元素时
- stack1 push cur,如果stack2中栈顶元素为null,代表空栈,stack2 push cur
- stack1 push cur,同时看stack2中栈顶元素是否大于cur,如果大于,stack2 push cur
- stack1 push cur,如果stack2中栈顶元素小于cur,stack2 重新push 一个栈顶元素
- 弹出元素时,stack1 pop ,stack2 也 pop即可
代码:
class MinStack {
Stack<Integer> stack1;
Stack<Integer> stack2;
public MinStack() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int val) {
stack1.push(val);
//如果stack2中栈顶元素为null,代表空栈,stack2 push cur
if(stack2.isEmpty()){
stack2.push(val);
}else{
Integer peek = stack2.peek();
//栈顶元素是否大于cur,如果大于,stack2 push cur
if(peek > val){
stack2.push(val);
}else{
//如果stack2中栈顶元素小于cur,stack2 重新push 一个栈顶元素
stack2.push(peek);
}
}
}
public void pop() {
stack1.pop();
stack2.pop();
}
public int top() {
return stack1.peek();
}
public int getMin() {
return stack2.peek();
}
}