2021/9/17(栈实现+表达式求值)
栈的应用场景
- 子程序的调用,在跳往子程序之前,会讲下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以便回到原来的程序。
- 处理递归调用:和子程序的调用类似,只是除了储存下一指令的地址外,也将参数,区域变量等数据存入堆栈中。
- 表达式的转换【中缀转后缀】与求值(实际解决)。
- 二叉树的遍历。
- 图形的深度优先(depth-first)搜索法。
参考了 https://www.cnblogs.com/hapjin/p/4442729.html
1 、使用JAVA数组实现顺序栈、
1、首先总结一下线性表(分为顺序表和链接表,【即顺序存储结构和链式存储结构的区别】)和栈(顺序栈和链接栈)还有队列(顺序队列和链接队列)的JAVA类库中的实现:
java.util.ArrayList 实现了顺序表,java.util.LinkedList 实现了链接表的功能。
java.util.ArrayDeque实现了顺序栈和顺序队列(该类中即定义了与栈操作有关的方法,也定义了与队列操作有关的方法)、java.util.LinkedList实现了链接栈和链接队列。
2、定义了一个Stack
创建一个基于数组的Stack接口:
public interface Stack<E> {
public int length();//返回栈的长度
public E pop();//出栈
public void push(E element);//进栈
public E peek();//访问栈顶元素
public boolean empty();//判断栈是否为空
public void clear();//清空栈
}
3,在JAVA类库中,java.util.ArrayDeque类实现了顺序栈的功能。ArrayDeque可以实现动态地扩展栈的大小,但是不支持多线程访问。同时,ArrayDeque还实现了顺序队列的功能。
4,定义了一个Object[] 类型的数组,用来保存顺序栈中的元素。具体实现类MyArrayListStack 如下:
package stack;
import java.util.Arrays;
public class MyArrayListStack<T> implements Stack<T>{
private int DEFAULT_SIZE=16; //定义栈的初始默认长度
private int capacity; // 保存栈的长度
private int size; //元素个数
private Object[] elementData; //保存栈中的元素
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public MyArrayListStack() {
capacity = DEFAULT_SIZE;
elementData = new Object[capacity];
size = 0;
}
// 以指定长度的大小来创建栈
public MyArrayListStack(int initSize) {
capacity = 1;
while (capacity<initSize)
capacity <<=1; //将capacity设置成大于initSize的最小2次方
elementData = new Object[capacity];
}
@Override
public int getLength() {
return this.size;
}
public T getAt(int idx){
return elementData(idx);
}
@Override
public T pop() {
if (empty()){
throw new IndexOutOfBoundsException("栈空,不能出栈");
}
T oldValue =elementData(size-1);
elementData[--size] = null; ////让垃圾回收器及时回收,避免内存泄露
return oldValue;
}
/**
* 返回运算符的优先级,数字越大优先级就越高
* 目前 表达式只有+ - * /
*/
public int priority(int oper){
if (oper == '*' || oper =='/'){
return 1;
}else if(oper =='+' || oper =='-'){
return 0;
}else
return -1;
}
public boolean isOper(char val){
return val == '+' || val =='-' || val=='*' ||val =='/';
}
public int cal(int num1,int num2,char oper){
int res = 0;
switch (oper){
case '+':
res = num1+num2;
break;
case '-':
res = num2-num1;
break;
case '*':
res = num1*num2;
break;
case '/':
res = num2/num1;
break;
default:
break;
}
return res;
}
/**
* 我用得有一点鸡肋了 ,但ArrayList中有这么用过
*/
T elementData(int index){
return (T) elementData[index];
}
/**
* 动态扩容 真的太牛逼了!
*/
public void ensureCapacity(int minCapacity){
if (minCapacity>capacity){
while(capacity<minCapacity)
capacity <<=1;
}
elementData = Arrays.copyOf(elementData,capacity);
}
@Override
public void push(T t) {
ensureCapacity(size+1);
elementData[size++] = t;
}
@Override
public T peek() {
if (size==0){
throw new ArrayIndexOutOfBoundsException("size = 0");
}
return elementData(size-1);
}
@Override
public boolean empty() {
return size==0;
}
public boolean isNotEmpty(){
return !empty();
}
@Override
public void clear() {
for (int i = 0; i < size; i++) {
elementData[i] = null;
}
size = 0;
}
public void display() {
if (size==0){
System.out.println("[]");
}
StringBuilder sb = new StringBuilder("");
for (int j =size-1;j>=0;j--){
sb.append(elementData[j].toString()+", ");
}
int len = sb.length();
System.out.println(sb.delete(len - 2, len).append(']').toString());
}
使用单链表实现栈
package stack;
public class MyLinkedListStack<E> implements Stack<E>{
private int size;
private Node<E> first;
private Node<E> rear;
static class Node<E>{
E item;
Node<E> next;
Node(E x){
item = x;
}
}
public E getFirst() {
return first.item;
}
public E getRear() {
return rear.item;
}
@Override
public int getLength() {
return size;
}
@Override
public E pop() {
if (size==1){
E val = rear.item;
first=null;
rear=null;
size--;
return val;
}
Node<E> preNode = first;
E val = rear.item;
while (preNode.next!=rear){
preNode = preNode.next;
}
preNode.next = null;
rear = preNode;
size--;
return val;
}
@Override
public void push(E e) {
Node<E> node = new Node(e);
if (size==0){
first = node;
rear = node;
size++;
return;
}
rear.next = node;
rear = node;
size++;
}
@Override
public E peek() {
return rear.item;
}
@Override
public boolean empty() {
return size==0;
}
@Override
public void clear() {
while (first!=null){
pop();
}
}
public void display(){
Node<E> tmp = first;
for (int i = 0; i < size; i++) {
System.out.println(tmp.item);
tmp = tmp.next;
}
}
}
3、使用栈完成表达式的计算思路
1、通过一个遍历,来遍历我们的表达式
2、如果是数字,入数字栈
3、符号入符号栈
3.1、如果符号栈为空,直接入
3.2、如果符号栈有操作符,就进行比较,如果操作符号优先级<=栈中的操作符,
需要从数栈中pop出二个数,再从符号栈pop出一个符号,进行运算。得到结果入
数字栈,再入符号栈,如果当前的操作符的优先级大于栈中的操作符,入符号栈。
4、表达式扫描完毕,就顺序的从数栈和符号栈pop出相应的数和符号,运算。
5、最后在数栈只有一个数字即结果。
只能计算简单 ,解决了 5-3-1 问题
package stack;
public class CalStack {
public static void main(String[] args) {
String str = "5-5/5-1"; //中缀表达式
// 创建二个栈,数栈,一个符号栈
MyArrayListStack<Integer> numStack = new MyArrayListStack<>();
MyArrayListStack<Integer> operStack = new MyArrayListStack<>();
int index = 0; //遍历
int num1 = 0;
int num2 = 0;
int oper = 0;
int res = 0;
char ch = ' '; // 将每次扫描得到的ch保存到
String keepNum = ""; // 保存长的数字
while (true){
// 依次得到str的每一个字节
ch = str.charAt(index);
if(operStack.isOper(ch)){
// 判断当前符号栈是不是为空
if (operStack.isNotEmpty()){
if (operStack.priority(ch)<=operStack.priority(operStack.peek())){
if (ch=='-'&& operStack.peek()=='-'){
ch = '+';
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1,num2, (char) oper);
numStack.push(res);
operStack.push((int) ch);
}else {
operStack.push((int) ch);
}
}else{
operStack.push((int) ch);
}
}else {
keepNum+=ch;
// 如果ch已经是str的最后一位,就直接入栈
if (index==str.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else {
if(operStack.isOper(str.charAt(index+1))){
// 后一位是操作符
numStack.push(Integer.parseInt(keepNum));
keepNum = "";
}
}
//numStack.push(Integer.parseInt(String.valueOf(ch)));
}
index+=1;
if (index==str.length()){
break;
}
}
while (true){
if (operStack.empty()){
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
System.out.println(operStack.getLength());
oper = operStack.pop();
if (oper=='-' && operStack.getLength()!=0 && operStack.peek()=='-') {
oper ='+';
}
res = numStack.cal(num1,num2, (char) oper);
numStack.push(res);
}
System.out.println("表达式为:"+numStack.pop());
}
}
10进制转换n进制:
public void conversion10_n(int num,int toNum){
MyArrayListStack<Integer> stack = new MyArrayListStack<>();
while (num!=0){
stack.push(num%toNum);
num = num/8;
}
while (stack.isNotEmpty()){
System.out.printf("%d",stack.pop());
}
}
3、前缀(波兰表达式),中缀(我们使用的),后缀表达式(逆波兰表达式)
x缀说的是符号的位置,符号在前 前缀
中缀如何转前缀后缀呢?
a+b*c-(d+e)
第一步:按照运算符的优先级对所有的运算单位加括号
式子变成拉:((a+(bc))-(d+e))
第二步:转换前缀与后缀表达式
前缀:把运算符号移动到对应的括号前面
则变成拉:-( +(a (bc)) +(de))
把括号去掉:-+abc+de 前缀式子出现
后缀:把运算符号移动到对应的括号后面
则变成拉:((a(bc) )- (de)+ )-改成:((a(bc)* )+ (de)+ )-
把括号去掉:abc-de+- 后缀式子出现改成:abc+de+-
发现没有,前缀式,后缀式是不需要用括号来进行优先级的确定的。
后缀表达式求解简单式子
逆波兰表达式 很适合计算机运算。
从左向右 遇到第一个字符,从栈(这次不分数栈还是符号栈)中pop二个进行计算。结果push进去
polandExpression("3 4 + 5 * 6 -");
public static void polandExpression(String suffixExpression){
String[] split = suffixExpression.split(" ");
List<String> list = Arrays.asList(split);
MyArrayListStack<String> stack = new MyArrayListStack<>();
for (String s : list) {
if (s.matches("\\d")){
stack.push(s);
}else {
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
int res = 0;
try{
res = stack.cal(num1,num2,s.charAt(0));
}catch (Exception e){
e.printStackTrace();
}
stack.push("" + res);
}
}
System.out.println("value: "+stack.pop());
}