一、概述
- 栈是一个先入后出的有序列表;
- 栈是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶,另一端为固定的一端,称为栈底。
- 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素正好相反,最后放入的元素最先删除,最先放入的元素最后删除。
二、代码实现
public class ArrayStack {
private int maxSize; //定义栈的最大容量
private int top = -1; //栈空时栈顶指向-1
int[] stack; //定义数组表示栈
ArrayStack(int maxSize){ //对栈进行初始化
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
public boolean isFull(){ //定义栈满的情况
return top == maxSize-1;
}
public boolean isEmpty(){ //定义栈空的情况
return top == -1;
}
public void push(int num){ //进栈
if (isFull()){
System.out.println("栈已满");
return;
}
top++;
stack[top] = num;
}
public int pop(){ //出栈
if (isEmpty()){
throw new RuntimeException("栈已空");
}
int value;
value = stack[top];
top--;
return value;
}
public void list(){ //遍历栈
if (isEmpty()){
System.out.println("栈已空");
return;
}
for (int i = top; i >= 0; i--) {
System.out.println(stack[i]);
}
}
}
三、使用栈实现一个综合计算机
思路:
1.首先创建两个栈。一个数栈,一个符号栈。当输入一串字符串的时候,进行逐一扫描;
2.当字符为数字,进行数栈。进入数栈时需要注意以下情况:
- 因为时一个字符一个字符判断,因此当出现多位数时,如果将当前数字直接进入栈内,则会出现结果出现错误。如70,进栈时传入的是7和0,而不是70.因此需要进行多位数的判断和拼接;
- 进行判断时,先将当前字符拼接到一个空字符串中,再检测下一个字符是否为数字;当为数字时,将会一直进行拼接操作,直到下一个字符为运算符号,此时将字符串转换为数字进栈,然后字符串清空,等待下一个多位数的到来。
3.当字符为运算符时,进入符号栈。但是进入符号栈时,需要注意以下情况:
-
当符号栈为空时,运算符直接进入符号栈;
-
当运算符的优先级小于等于栈顶符号的优先级时,需要从数栈中pop两个数字,从符号栈中pop出栈顶的运算符进行运算,将结果push进数栈,然后当前运算符方可入栈;
-
当运算符的优先级大于栈顶符号的优先级时,直接进符号栈;
4.字符串全部扫描完毕后,再从数栈中pop两个数字,从符号栈中pop出栈顶的运算符进行运算,直到得到最终结果,将结果push进数栈。此时符号栈为空,作为检验计算是否完成的标准。
4.实现代码:
//创建链表。在之前代码的基础上增加了判断、优先级、运算三个计算器要用到的方法
public class ArrayStack {
private int maxSize;
private int top = -1;
int[] stack;
ArrayStack(int maxSize){
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
public boolean isFull(){
return top == maxSize-1;
}
public boolean isEmpty(){
return top == -1;
}
public void push(int num){
if (isFull()){
System.out.println("栈已满");
return;
}
top++;
stack[top] = num;
}
public int pop(){
if (isEmpty()){
throw new RuntimeException("栈已空");
}
int value;
value = stack[top];
top--;
return value;
}
public void list(){
if (isEmpty()){
System.out.println("栈已空");
return;
}
for (int i = top; i >= 0; i--) {
System.out.println(stack[i]);
}
}
//判断字符为数字还是运算符
public boolean isChar(int ch){
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
}
//定义优先级
public int priority(int ch){
if (ch == '+' || ch == '-'){
return 0;
}else if(ch == '*' || ch == '/'){
return 1;
}else return -1;
}
//定义一个运算方法,num1为先出栈的值,num2为后出栈的值,ch为运算符
public int cal(int num1,int num2,int ch){
int res = 0;
switch (ch){
case '+':
res = num1 + num2;break;
case '-':
res = num2-num1;break;
case '*':
res = num1 * num2;break;
case '/':
res = num2 / num1;break;
default:break;
}
return res;
}
//获得符号栈顶的优先级
public int getTopPre(){
return stack[top];
}
}
//创建计算器
public class AarrayStackDemo {
public static void main(String[] args) {
ArrayStack numStack = new ArrayStack(10);
ArrayStack operStack = new ArrayStack(10);
String str = "30+2*6-2";
int index = 0;
int num1 = 0;
int num2 = 0;
int operat = 0;
int res = 0;
char ch = ' ';
String keepNum = "";
while (true){
ch = str.substring(index,index+1).charAt(0);
if (operStack.isChar(ch)){ //判断ch是否是运算符
if (!operStack.isEmpty()){ //如果符号栈不为空,比较优先级
//当运算符的优先级小于等于栈顶符号的优先级时,
if (operStack.priority(ch) <= operStack.priority(operStack.getTopPre())){
num1 = numStack.pop();
num2 = numStack.pop();
operat = operStack.pop();
res = operStack.cal(num1,num2,operat);// 从数栈中pop两个数字,从符号栈中pop出栈顶的运算符进行运算
numStack.push(res);// 将结果push进数栈
operStack.push(ch);// 最后当前运算符入栈
}else {
operStack.push(ch);//当运算符的优先级大于栈顶符号的优先级时直接进栈
}
}else {
operStack.push(ch);//当符号栈为空时直接进栈
}
}else {
//当ch扫描为数字时,先不要立刻进栈,因为有可能是多位数
//因此需要扫描下一个index是否为数字,若为数字,需要将其拼接
keepNum += ch;
//判断index是否为最后一个字符,若是,则直接进栈
if (index == str.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else {
if(operStack.isChar(str.substring(index+1,index+2).charAt(0))){
numStack.push(Integer.parseInt(keepNum));
keepNum = "";
}
}
}
//index + 1,看是否扫描到最后一个字符
index++;
if (index == str.length()){
break;
}
}
while (true){
if (operStack.isEmpty()){ //判断符号栈是否已空,即判断是否所有数字运算结束
break;
}else{
num1 = numStack.pop();
num2 = numStack.pop();
operat = operStack.pop();
res = operStack.cal(num1,num2,operat);
numStack.push(res);
}
}
System.out.println(numStack.pop());
}
}
四、逆波兰计算器实现
所谓逆波兰表达式,就是后缀表达式。我们算数常用的表达式是中缀表达式,它并不适合计算机来进行计算,因此我们可以选择计算机处理起来较为简单的后缀表达式来实现计算器。
后缀表达式(逆波兰表达式)的表示方法很简单,从左至右扫描表达式,遇到数字时,将数字压入栈中,遇到运算符时,弹出栈顶的两个数字,用运算符计算结果,并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。
如:(3+4)* 5 -6,后缀表达式为:3 4 + 5 * 6 -。
实现代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolanNotation {
public static void main(String[] args) {
String suffixExpression = "3 4 + 5 * 6 -";
List<String > list = getListString(suffixExpression);
int res = calculate(list);
System.out.println(res);
}
//将一个逆波兰表达式存入ArrayList中
public static List<String> getListString(String suffix){
//首先将字符串分裂,将3,4,+,5等存入数组,方便计算取用
String[] split = suffix.split(" "); //分类的依据是空格,即以碰到空格分裂一次
List<String> list = new ArrayList<String>();
for (String ele:split){
list.add(ele); //存入ArrayList
}
return list;
}
五、中缀表达式转后缀表达式
1.初始化两个栈:运算符栈s1和储存中间结果的栈s2;
-
对中缀表达式进行扫描,如果扫描结果是数字,则直接进s2栈;
-
如果扫描结果是运算符(运算符 != 括号),则要考虑以下条件:
1)如果s1栈为空,那么运算符直接入栈;
2)如果运算符的优先级比栈顶运算符的优先级高,可以直接入栈;
3)如果运算符的优先级等于或低于栈顶的运算符,则栈顶的运算符出栈s1,进入s2栈,然后当前运算符再与栈顶运算符进行比较,按照优先级条件进栈;
-
如果扫描结果是括号,也要考虑如下条件:
1)如果括号为左括号,可以直接进s1栈;
2)如果结果为右括号,那么符号栈s1的数据从栈顶依次出栈,进入s2栈,直到碰到左括号为止,同时左括号也需要出栈,且不进入s2栈;
-
重复步骤2-4,直到表达式到达最右边;
-
将s1栈中的剩余运算符依次弹出并压入s2;
-
依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
实现代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class InfixToSuffix {
//将中缀表达式转成对应的List
public static List<String> toInfixList(String s) {
//定义一个List存放中缀表达式对应的数据
List<String> ls = new ArrayList<String>();
//定义一个指针,用于遍历中缀表达式字符串
int i = 0;
String str;//对多位数进行拼接工作
char c;//每遍历一个字符,放入c中
do {
if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) { //当扫描的字符不是数字时
ls.add("" + c);
i++;
} else { //如果是一个数字,那么需要考虑多位数的问题,进行拼接
str = "";//每次碰到数字都将str置空
while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57) {
str += c; //进行拼接
i++;
}
ls.add(str);
}
} while (i < s.length());
return ls;
}
public static List<String> infix(List<String> ls){
Stack<String> s1 = new Stack<String>(); //定义符号栈s1
//因为数据栈没有进行出栈操作,因此将数据栈改为List会更加方便使用
List<String> s2 = new ArrayList<String>();
// 遍历ls
for (String item:ls){
if (item.matches("\\d+")){ //如果为数字,进数字栈
s2.add(item);
}else if (item.equals("(")){ //如果为左括号,直接进符号栈
s1.push(item);
}else if (item.equals(")")){ //如果为右括号,则将符号栈中右括号以前的符号全部弹出,再压入数据栈
while (!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();
}else { //如果检测到为符号,根据优先级进行处理
while (s1.size() != 0 && priority(s1.peek()) >= priority(item)){
s2.add(s1.pop());
}
s1.push(item);
}
}
while (s1.size() != 0){ //将符号栈中剩余的符号压入数据栈
s2.add(s1.pop());
}
return s2; //List是先入先出,因此不用像栈一样反向输出数据
}
public static int priority(String s){
switch (s){
case "+":
return 1;
case "-":
return 1;
case "*":
return 2;
case "/":
return 2;
default:
return 0;
}
}
}
}
return s2; //List是先入先出,因此不用像栈一样反向输出数据
}
public static int priority(String s){
switch (s){
case "+":
return 1;
case "-":
return 1;
case "*":
return 2;
case "/":
return 2;
default:
return 0;
}
}
}