定义
栈是一种特殊的线性表,只可以在线性表的底部进行插入和删除操作.
线性表的头部被称为栈底,底部被成为栈顶,也就是说栈只可以在栈顶进行插入或者删除操作
插入操作被称为压栈(push)
删除操作被叫做弹栈(pop).
抽象数据类型
数据
/**
* 栈抽象数据类型
*
* @param <T>
*/
public class ArrayStack<T> implements IStack<T> {
/**
* 栈是基于线性表来做的
*/
protected Object[] list;
/**
* 栈顶
*/
protected int top;
/**
* 方向 0 正向 1 负向
*/
protected int direction;
public ArrayStack() {
this.init();
}
@Override
public void init() {
top = 0;
list = new Object[100];
direction = 0;
}
@Override
public boolean destroyStack() {
list = null;
return true;
}
@Override
public boolean clearStack() {
if (direction == 0) {
for (int i = 0; i <= top; i++) {
list[i] = null;
}
} else if (direction == 1) {
for (int i = list.length - 1; i >= top; i--) {
list[i] = null;
}
}
top = direction == 0 ? 0 : 99;
return true;
}
@Override
public boolean stackEmpty() {
return list == null || (top == 0 && direction == 0) || (top == list.length - 1 && direction == 1);
}
@Override
@SuppressWarnings("unchecked")
public T getTop() {
if (this.stackLength() == 0){
return null;
}
return (T) list[direction == 0 ? top - 1 : top + 1];
}
@Override
@SuppressWarnings("unchecked")
public T pop() {
int topIndex = direction == 0 ? top - 1 : top + 1;
Object topObj = list[topIndex];
list[topIndex] = null;
if (direction == 0) {
top--;
} else {
top++;
}
return (T) topObj;
}
@Override
public void push(T e) {
if (top + 1 > list.length || top < 0) {
throw new OutOfRangeException("stack push error");
}
int next = direction == 0 ? top++ : top--;
if (list[next] != null) {
throw new OutOfRangeException("stack push error");
}
list[next] = e;
}
@Override
public int stackLength() {
return direction == 0 ? top : list.length - top - 1;
}
@Override
@SuppressWarnings("unchecked")
public List<T> getList() {
if (null == list)return null;
return top == 0 ? new ArrayList<>() : new ArrayList<>(Arrays.copyOf(list, top));
}
}
行为
/**
* 栈
*/
public interface IStack<T> {
/**
* 初始化
*/
void init();
/**
* 销毁栈
*/
boolean destroyStack();
/**
* 清空栈
*/
boolean clearStack();
/**
* 栈是否为空
*
* @return true 空 false 非空
*/
boolean stackEmpty();
/**
* 若栈非空,返回栈顶元素
*
* @return T
*/
T getTop();
/**
* 弹栈
*
* @return 栈顶元素
*/
T pop();
/**
* 压栈
*
* @param e 元素
*/
void push(T e);
/**
* 返回栈长度
*
* @return int
*/
int stackLength();
/**
* 获取栈存储list(非标准中包含该方法)
* @return list
*/
List<T> getList();
}
public interface IShardedStack<T> extends IStack<T>{
/**
* 初始化
*/
void init();
/**
* 销毁栈
*/
void destroyStack(int num);
/**
* 清空栈
*/
void clearStack(int num);
/**
* 栈是否为空
*
* @return true 空 false 非空
*/
boolean stackEmpty(int num);
/**
* 若栈非空,返回栈顶元素
*
* @return T
*/
T getTop(int num);
/**
* 弹栈
*
* @return 栈顶元素
*/
T pop(int num);
/**
* 压栈
*
* @param e 元素
*/
void push(T e,int num);
/**
* 返回栈长度
*
* @return int
*/
int stackLength(int num);
}
栈的种类
数组栈和链表栈
因为栈是特殊的线性表,所以很自然的就想到存在数组和链表两种实现方式。
个人觉得数组这种方式特别合适,因为我们限定了所有操作都要在栈顶,而链表方式会多一些额外开销。
共享栈
共享栈就是两个栈分享一个数组/节点链表,栈顶由单个栈的时候的数组最大值,变成了两个栈顶"发生碰撞"
栈的应用
递归
递归的典型运用场景就是斐波那契数列
斐波那契数列: 前两项数的和 f(0) = 0; f(1) = 1; f(n) = f(n-1) + f(n-2);
普通的执行方法如果我们要算f(20),那么要从 f(0) f(1) f(2)一步步计算到f(20);
使用递归的化我们是从f(20)开始分解公式,f(20) = f(19) + f(18);再去调用f(18) 和 f(19) 依次往下自己调用自己。
在这个依次向下的调用过程我们就需要 “先将上层的公式存起来” 先存起来的公式后算,这和栈结构不谋而合,而在java中执行方法的压栈和出栈也确实是这样的。
四则运算法则
我们常见的表达式 9+(3-1)×3+10÷2 叫做中缀表达式,我们可以很轻松的依据优先级 “ 括号 大于 乘除 大于 加减” 来算出结果
但是计算机在处理中缀表达式的时候就很难办,于是 波兰的一位逻辑学家 想出来了一个后缀(逆波兰)表达式,
而上述中缀表达式转换后未 9 3 1 - 3 * + 10 2 / +
中缀表达式转后缀表达式
中缀表达式提取后缀表达的基本步骤:
- 依次读取表达式中的每一项 符号和数字 ,读取到数字则放到后缀表达式中,读取到符号则放入计算符号栈中
- 如果栈顶 无符号 或者 优先级小于 当前要放入的符号,则直接放入
- 如果放入的符号 优先级大于当前符号则取出栈中的所有符号加入表达式 再放入符号
- 如果放入的符号是) 则取出符号直到(为止 加入表达式 然后再放入符号
后缀表达式的计算逻辑
- 依次将每个表达式的元素放入到计算栈中
- 如果获取到计算符号则从栈中取出两个数进行计算,之后再放入到栈中
- 最后计算结束取出栈中数据即可