每天最幸福的事情,莫过于下班回家后,踏进厨房,做上一道自己喜欢的菜肴,然后饱餐一顿。
起锅,烧油,葱姜蒜……
一顿操作猛如虎,一顿美味要出炉。
打开放厨具的柜子,从一摞碗当中拿出了最上边的一个,将饭菜盛出,开始饕餮大餐。
今天要讲的数据结构,就是这一摞碗——栈。
什么是栈
栈和队列有着相似之处,两者都是受着一定规则约束的线性数据结构,不同的是,队列遵循的是先进先出(FIFO)的规则,而栈坚持着后来者居上,遵循着后进先出(LIFO)的规则。
就像是我们用碗时,最后刷完的碗,都会摞在最顶上,而我们使用碗的时候,都会习惯性的从最上边取,这里用碗的时候就遵循这栈的规则,越是后来放进去的碗,越早被使用。
栈的实现方式
栈同队列一样,他的数据存储同样可以基于数组或者链表进行实现。
同样,我们使用接口来定义栈需要具备的方法,一般来说,栈必须具备入栈(push)和弹栈(pop)这两个操作的。
声明栈的必备方法:
/**
* algorithm
* @author 忘忧
* @date 2020/3/15
* 栈(公众号:算法之灵魂拷问)
*/
public interface Stack {
/**
* 获取当前栈的元素个数
* @return 当前栈的元素个数
*/
int size();
/**
* 判断当前栈是否已满
* @return 当前栈是否已满
*/
boolean isFull();
/**
* 弹栈
* @return 出栈元素
* @throws StackEmptyException 栈为空时抛出该异常
*/
Object pop() throws StackEmptyException;
/**
* 元素入栈
* @param element 入栈元素
* @throws StackFullException 当栈已满时抛出该异常
*/
void push(Object element) throws StackFullException;
}
基于数组的实现
思路: 栈的存储相对简单,使用一个指针标示当前栈顶位置,当元素入栈时,指针先加一再将新元素复制给当前位置;弹栈时,先获取到当前位置的值,然后指针减一。
注:指针初始化位置为 -1
代码实现:
/**
* algorithm
* @author 忘忧
* @date 2020/3/15
* 基于数组实现的栈(公众号:算法之灵魂拷问)
*/
public class ArrayStack implements Stack {
/**
* 栈内数据的具体存储位置
*/
private Object[] elements;
/**
* 当前栈顶位置
*/
private int currentIndex = -1;
/**
* 栈的容量
* @param capacity 容量
*/
public ArrayStack(int capacity) {
this.elements = new Object[capacity];
}
/**
* 当前栈存储的元素个数
* @return
*/
@Override
public int size() {
return currentIndex + 1;
}
/**
* 判断当前栈是否已满
* @return 是否已满
*/
@Override
public boolean isFull() {
return this.size() == elements.length;
}
/**
* 弹栈
* @return 弹栈元素
* @throws StackEmptyException 当栈为空时抛出异常
*/
@Override
public Object pop() throws StackEmptyException {
if(this.size() == 0){
throw new StackEmptyException();
}
return elements[currentIndex--];
}
/**
* 入栈
* @param element 入栈元素
* @throws StackFullException 栈已满时抛出该异常
*/
@Override
public void push(Object element) throws StackFullException {
if(this.isFull()){
throw new StackFullException();
}
elements[++currentIndex] = element;
}
}
基于链表实现的栈
思路:元素入栈,新元素做队首元素,原队首元素作为心入栈元素的next节点;元素出栈,栈顶元素的next节点做栈顶元素。
代码实现:
/**
* algorithm
* @author 忘忧
* @date 2020/3/15
* 基于链表实现的栈(公众号:算法之灵魂拷问)
*/
public class LinkedStack implements Stack {
static class TreeNode{
Object val;
TreeNode next;
}
/**
* 当前队首节点(栈顶元素)
*/
private TreeNode head;
/**
* 当前栈存储的元素个数
*/
private int size;
/**
* 当前栈存储的元素个数
* @return 当前栈存储的元素个数
*/
@Override
public int size() {
return size;
}
/**
* 判断当前栈是否已满
* @return 是否已满
*/
@Override
public boolean isFull() {
return false;
}
/**
* 弹栈
* @return 栈顶元素
* @throws StackEmptyException 栈空时抛出异常
*/
@Override
public Object pop() throws StackEmptyException {
if(this.size() == 0){
throw new StackEmptyException();
}
size--;
Object result = head.val;
head = head.next;
return result;
}
/**
* 元素入栈
* @param element 入栈元素
* @throws StackFullException 栈满时抛出异常
*/
@Override
public void push(Object element) throws StackFullException {
TreeNode treeNode = new TreeNode();
treeNode.val = element;
treeNode.next = head;
head = treeNode;
size++;
}
}
关于栈的常见面试题
如何用两个栈实现一个队列
结合队列和栈的相关知识,已经明了,队列是一种先进先出的数据结构,而栈则是一种先进后出的数据结构,那么怎么使用两个栈实现一个队列呢。
思路: 两个栈分别记录为栈A和栈B,要实现队列,需要从队列的入队和出队来入手。入队:元素直接入栈A出队:从栈B弹栈,当栈B为空时,弹出栈A的元素,放入栈B中(注:此过程会发生栈的倒置,即本来在栈A最低的元素,会出现在栈B的顶部),然后弹出栈B的栈顶元素即可,如果当前的栈B仍然是空,则说明整个队列为空,出队失败。
关于栈和队列,忘忧暂且先整理这么些,等忘忧将数据结构的基本知识整理完之后,会专门从leetcode上找一些相关的算法,在解题过程中,与大家一起加强对这些数据结构的理解。
感谢大家的关注,大家的关注将会成为忘忧最大的动力,忘忧也随时欢迎大家给我留言交流,一起成长!
结束语:沉舟侧畔千帆过,病树前头万木春。