栈
栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。
正则表达式
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与运算符可以将小的表达式结合在一起来创建更大的表达式。正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为"元字符")组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
实现基本综合计算器
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Calculator {
public static void main(String[] args) {
//完成表达式
String expression = "32+2*6-2";
//创建两个栈,一个数栈,一个符号栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
//定义相关变量
int index = 0;//用于扫描
int num1 = 0;
int num2 = 0;
int oper = 0;
int result = 0;
char ch = ' ';
//这里用正则表达式获取数字和运算符,能够一次获取多位数字
//定义一个正则表达式获取字符串中的数字
Pattern pattern = Pattern.compile("\\d+");
Pattern pattern2 = Pattern.compile("[^0-9]");
Matcher matcher = pattern.matcher(expression);
Matcher matcher2 = pattern2.matcher(expression);
//查找匹配的数字
while (matcher.find()){
// System.out.println(matcher.group());
numStack.push(Integer.parseInt(matcher.group()));
//每查找到一个数字后面一定是一个运算符,所以对运算符操作
while (matcher2.find()){
ch = matcher2.group().charAt(0);
//判断ch是什么
if (operStack.isOper(ch)){
//判断字符栈是否为空
if (!operStack.isEmpty()) {
//判断符号是否比字符栈的栈顶优先级高
if (operStack.priority(ch) < operStack.priority(operStack.getTop())) {
//符号比栈顶优先级低
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
result = numStack.cal(num1,num2,oper);
//把运算结果入数栈
numStack.push(result);
//将当前操作符号入符号栈
operStack.push(ch);
}else {
//如果当前操作符号大于栈顶的操作符号优先级,则直接入栈
operStack.push(ch);
}
}else {
//如果栈为空则直接入栈
operStack.push(ch);
}
}
//每查找到一个运算符,就需要对运算符进行入栈等操作,操作完后继续查找数字
break;
}
}
//将运算字符串的字符一个一个拿出来进行比较:当运算两位数的时候就会出问题
//开始循环
/* while (true){
//依次得到每一个字符
ch = expression.substring(index,index+1).charAt(0);
//判断ch是什么
if (operStack.isOper(ch)){
//判断字符栈是否为空
if (!operStack.isEmpty()) {
//判断符号是否比字符栈的栈顶优先级高
if (operStack.priority(ch) < operStack.priority(operStack.getTop())) {
//符号比栈顶优先级低
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
result = numStack.cal(num1,num2,oper);
//把运算结果入数栈
numStack.push(result);
//将当前操作符号入符号栈
operStack.push(ch);
}else {
//如果当前操作符号大于栈顶的操作符号优先级,则直接入栈
operStack.push(ch);
}
}else {
//如果栈为空则直接入栈
operStack.push(ch);
}
}else {
//如果是数,则直接入到数栈中
numStack.push(ch - '0');
}
//让index + 1,并判断是否扫描到字符串最后
index++;
if (index >= expression.length()){
break;
}
}*/
//当表达式扫描完毕,就顺序地从数栈和符号栈中弹出相应的数和符号进行运算
while (true){
//这里在进行最后的加减运算的时候会出问题,因为是栈先进后出的原因,得到的数字和运算符都是倒序的,而加减运算是要按顺序运算的,
//所以按以下方法会变成倒序运算,如果前面存在减法得到的就不是真正的结果了,
//这里只是本人图方便,所以直接弹栈操作,如果有兴趣可以将弹栈的数存入数组中再进行运算便可得到正确的运算结果。
//如果符号栈为空,则表示运算完毕
if (operStack.isEmpty()){
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
result = numStack.cal(num1,num2,oper);
//把运算结果入栈
numStack.push(result);
//将当前操作符号入符号栈
}
System.out.printf("表达式%s的运算结果为%d",expression,numStack.pop());
}
}
//定义一个ArrayStack2表示栈
class ArrayStack2{
//栈的大小
private int maxSize;
//数组模拟栈
private int[] stack;
//top表示栈顶,初始化为-1
private int top = -1;
public ArrayStack2(int maxSize){
this.maxSize = maxSize;
stack = new int[maxSize];
}
//栈满
public boolean isFull(){
return top == maxSize - 1;
}
//获取栈顶元素,但是不弹栈
public int getTop(){
return stack[top];
}
//栈空
public boolean isEmpty(){
return top == -1;
}
//压栈
public void push(int value){
//判断是否栈满
if (isFull()){
System.out.println("栈满,不能压栈!");
}
top++;
stack[top] = value;
}
//弹栈
public int pop(){
//判断栈空
if (isEmpty()){
throw new RuntimeException("栈空,不能进行弹栈操作!");
}
int temp = stack[top];
top--;
return temp;
}
//遍历栈
public void show(){
if (isEmpty()){
throw new RuntimeException("栈空,没有数据!");
}
//从栈顶开始遍历
for (int i = top; i >= 0; i--){
System.out.printf("stack[%d] = %d\n",i,stack[i]);
}
}
//返回运算符的优先级,优先级越高,数字越大
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,int oper){
//储存运算结果
int result = 0;
switch (oper){
case '+':
result = num1 + num2;
break;
case '-':
result = num2 - num1;
break;
case '*':
result = num1 * num2;
break;
case '/':
result = num2 / num1;
break;
}
return result;
}
}
输出结果:
总结
在运算器的处理过程中,并不是每输入一个数字就进行一次运算,而是将输入的数字当成一个字符串来传入,当获取到字符串后,可以对字符串一个一个的遍历,但那样太耗时间并且只能计算一位数的加减乘除法,所以如果用正则表达式就可以更加清晰明了地得到各个数字和符号,并且加入了栈的数据结构使得在运算逻辑上更加条理清晰和运算更加准确等。
注:这里在进行最后的加减运算的时候会出问题,因为是栈先进后出的原因,得到的数字和运算符都是倒序的,而加减运算是要按顺序运算的,所以按以上方法会变成倒序运算,如果前面存在减法得到的就不是真正的结果了,这里只是本人图方便,所以直接弹栈操作,如果有兴趣可以将弹栈的数存入数组中再进行运算便可得到正确的运算结果。
改进
针对上面出现的问题,存在有减法时比较麻烦,所以如果用后缀表达式/逆波兰表达式(这里可以自行百度后缀表达式什么意思)和正则表达式就很好解决问题了
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
//先定义逆波兰表达式
//(3+4)*5-6 => 3 4 + 5 * 6 -
// 12 + 3 / (2 + 1)
// String suffixExpression = "3 4 + 5 * 6 -";
String suffixExpression = "3 2 1 + / 12 +";
//将suffixExpression放入到一个ArrayList中
List<String> list = getList(suffixExpression);
System.out.println(list);
System.out.println("结果为:" + calculator(list));
}
public static List<String> getList(String expression){
//使用空格分割
String[] strings = expression.split(" ");
ArrayList<String> arrayList = new ArrayList<>();
//将Strings放入arraylist中
for (String i : strings) {
arrayList.add(i);
}
return arrayList;
}
public static int calculator(List<String> list){
//创建栈
Stack<String> stack = new Stack<String>();
int result = 0;//计算结果
//判断list中是否为数字
for (String item : list
) {
//正则表达式判断
if (item.matches("\\d+")){
stack.push(item);
}else if (item.equals("+") || item.equals("-") || item.equals("*") || item.equals("/")) {
//取出栈顶两个元素
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
switch (item) {
case "+":
result = num1 + num2;
break;
case "-":
result = num1 - num2;
break;
case "*":
result = num1 * num2;
break;
case "/":
result = num1 / num2;
break;
}
//将结果放入栈顶
stack.push("" + result);
} else {
throw new RuntimeException("运算符错误");
}
}
return result;
}
}