一、导入
不知道大家是否观察过栓动步枪的压弹和击发,无论是压弹还是击发,子弹从弹舱进出有且只有一个出入口,子弹“先进后出,后进先出”,栈就是类似这样弹仓的一种数据存储结构。
二、栈
栈作为一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和数据删除的一端称之为栈顶,另一端则称为栈底。栈中数据元素遵循后进先出原则LIFO(Last In First Out)。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶
上方模型中入栈循序为数据1->数据2->数据3->数据4,通过上方模型可以观察到,对含有4个元素的栈进行出栈操作时,是对位于栈顶的数据4进行删除。而进行入栈操作时,则是将数据4插入栈顶,这时若是再进行出栈操作,则数据4作为栈顶首当其冲的就被删除了,符合“后进先出”原则。
栈的基本结构就是这样,除了入栈、出栈两个基本操作,还有诸如获取栈顶元素但不删除(peek)、获取栈中有效元素个数(size)、检查栈是否为空(empty)等......
三、模拟实现
我们将使用Java来模拟实现栈,在此之前我们要想清楚用什么来充当栈,以及MyStack该实现哪些功能,最后又该如何实现这些功能。
用什么来充当栈?
无论是在栈的介绍中,还是栈模型的构造中,我们都可以观察到栈是一种线性结构,因此从理论上来说线性表,如数组、链表、循序表都可以用来充当栈。
MyStack该实现哪些功能?
基本的入栈、出栈,作为栈的底线,这是必须的。
其他的我们还可以实现:获取栈顶元素但不删除(peek)、获取栈中有效元素个数(size)、检查栈是否为空(empty)。
最后加上一个构造方法,完美。
如何实现?
话不多说,直接上手。
1.使用链表充当栈
import java.util.LinkedList;
public class MyLinkedStack extends Exception{
private LinkedList<Integer> stack;
private int size;
//构造方法
public MyLinkedStack(){
this.stack = new LinkedList<>();
}
//入栈
public int push(int e){
if(this.size++ == 0) {
this.stack.add(e);
return e;
}
this.stack.add(0,e);
return e;
}
//出栈并返回值
public int pop(){
if(size()==0) {
throw new illegalException("无元素");
}
this.size--;
return this.stack.removeFirst();
}
//获取栈顶元素
public int peek(){
if(size()==0) {
throw new illegalException("无元素");
}
return this.stack.getFirst();
}
//返回栈元素个数
public int size() {
return this.size;
}
//检查站是否为空
public boolean empty(){
if(size() == 0) {
return true;
}
return false;
}
}
测试:
public class Test {
public static void main(String[] args) {
MyLinkedStack stack = new MyLinkedStack();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
System.out.println(stack.size());
System.out.println("=========");
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}
2.使用数组充当栈
import java.util.Arrays;
public class MyArrayStack {
int[] array;
int size;
public MyArrayStack(){
array = new int[3];
}
public int push(int e){
ensureCapacity();
array[size++] = e;
return e;
}
public int pop(){
int e = peek();
size--;
return e;
}
public int peek(){
if(empty()){
throw new RuntimeException("栈为空,无法获取栈顶元素");
}
return array[size-1];
}
public int size(){
return size;
}
public boolean empty(){
return 0 == size;
}
private void ensureCapacity(){
if(size == array.length){
array = Arrays.copyOf(array, size*2);
}
}
}
四、栈的使用
这种如同堵在死胡同里,只能挨个蹦出来的结构到底有什么用嘞?
不知道大家接触过逆波兰表达式没有,这里科普一下哈。
逻辑提问式类似于算术表达式,对于检索而言,这种表达式并不是最优和最简洁的形式,需要进行必要的转换。1929年波兰的逻辑学家卢卡西维兹(Jan Lucasiewicz)提出了将运算符放在运算项后面的逻辑表达式,又称“逆波兰表达式”。采用这种表达式组织逻辑提问式非常方便检索运算,是日本的福岛先生最早将逆波兰表达式应用于情报检索的,故又称为“福岛方法”。 [2]
逆波兰表达式又叫做后缀表达式,是一种没有括号,并严格遵循“从左到右”运算的后缀式表达方法,如下表所示:
将正常表达式转化为逆波兰表达式,我们可以就可以利用栈的特性更容易的进行复杂表达式的运算,其运算逻辑如下:
基本逻辑如上,更复杂的工作量太大就不画了,如果对本列有兴趣的话可以移步牛客:逆波兰表达式求值
文章分享到此为止,如果感觉文章对您有用的话,可以顺手点个赞,或者收藏一下也行咧,感谢。