栈(stack)
1、栈的介绍
- 栈是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表
- 允许插入和删除的一端为变化的一端,称为栈顶(top);另一端为固定的一端,称为栈底(bottom)
- 特点:先进后出
- 入栈(push)和出栈(pop)示意图
2、栈的应用场景
1)子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2)处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
3)表达式的转换[中缀表达式转后缀表达式]与求值
4)二叉树的遍历
5)图形的深度优化(depth-first)搜索法
3、栈的实现
1)数组模拟栈
package com.stack;
/*
@author qw
@date 2020/8/12 - 20:54
**/
import java.util.Scanner;
public class ArrayStackDemo {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(5);
String key = "";
boolean loop= true;
Scanner sc = new Scanner(System.in);
while (loop) {
System.out.println("show:表示显示栈");
System.out.println("exit:退出程序");
System.out.println("push:表示将数据入栈");
System.out.println("pop:表示将数据出栈");
System.out.println("请输入您的选择:");
key = sc.next();
switch (key) {
case "show":
stack.show();
break;
case "push":
System.out.println("请输入一个数:");
int value = sc.nextInt();
stack.push(value);
break;
case "pop":
try {
int res = stack.pop();
System.out.println("出栈的数据为" + res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "exit":
sc.close();
loop = false;
break;
default:
break;
}
}
}
}
class ArrayStack{
private int maxSize;//栈的大小
private int[] stack;//数组模拟栈,数据放在数组中
private int top = -1;
//构造器
public ArrayStack(int maxSize) {
this.maxSize=maxSize;
stack = new int[maxSize];
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//栈满
public boolean isFull(){
return top == maxSize - 1;
}
//入栈-push
public void push(int value) {
if (isFull()) {
System.out.println("栈满,无法入栈");
return;
}
top++;
stack[top] = value;
}
//出栈-pop
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈空,没有数据");
}
int value = stack[top];
top--;
return value;
}
//遍历
public void show() {
if (isEmpty()) {
System.out.println("栈空,没有数据");
}
for (int i = top; i >= 0; i--) {
System.out.println("stack["+i+"]="+stack[i]);
}
}
}
2)链栈(使用单链表)
-
示意图
-
代码举例
package com.stack;
/*
@author qw
@date 2020/8/12 - 21:12
**/
import java.util.Scanner;
public class SingleLinkedListStackDemo {
public static void main(String[] args) {
SingleLinkedListStack stack = new SingleLinkedListStack();
stack.create(5);
stack.show();
}
}
//单链栈
class SingleLinkedListStack<T>{
public Node top = null;//栈顶
//生成链栈
public void create(int n)
{
Scanner sc=new Scanner(System.in);
System.out.println("请输入结点数据:");
for(int i=0;i<n;i++)
push((T)sc.next()); //将每一个输入的结点数据通过入栈方法push(T data),进行入栈操作
}
//置空
public void clear(){
top = null;
}
//链栈是否为空
public boolean isEmpty() {
return top == null;
}
//链栈的长度
public int length() {
Node temp = top;
int len = 0;
while (temp != null) {
temp = temp.next;
len++;
}
return len;
}
//入栈-push
public void push(T data) {
Node temp = new Node(data);
temp.next = top;
top = temp;
}
//出栈-pop
public T pop() {
if (isEmpty()) {
throw new RuntimeException("栈空,没有数据");
}
Node temp = top;
top = top.next;
return (T)temp.data;
}
//遍历
public void show() {
if (isEmpty()) {
System.out.println("栈空,没有数据");
}
Node temp = top;
while (temp != null) {
System.out.println((T) temp.data);
temp = temp.next;
}
}
}
//结点类
class Node<T> {
public T data;
public Node next;
public Node(T data) {
this.data = data;
}
}
4、栈实现综合计算器
1)思路分析
1.通过一个index索引来遍历表达式
2.如果扫描的是数字,直接入数栈(numStack)
3.如果扫描的是符号,分为以下情况:
3.1如果当前符号栈为空,则直接入符号栈(operStack)
3.2如果符号栈有操作符,就进行比较:
3.2.1如果当前的操作符优先级小于或等于栈中的操作符,则需从数栈(numStack)中pop出两个操作数,从符号栈(operStack)中pop出一个符号,进行运算,将运算结果入数栈(numStack);再将当前操作符入符号栈(operStack);
3.2.2如果当前的操作符优先级大于栈中的操作符,就直接入符号栈(operStack)
4.当表达式扫描完毕,就顺序地从数栈和符号栈中pop出相应的数和符号,进行运算
5.最后数栈只剩下运算结果,符号栈为空
2)代码实现
package com.stack;
/*
@author qw
@date 2020/8/12 - 22:26
**/
public class Calculator {
public static void main(String[] args) {
//根据前面老师思路,完成表达式的运算
String expression = "310+2*6-2";
//创建两个栈,数栈,一个符号栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
//定义需要的相关变量
int index = 0;
int num1 = 0;
int num2 = 0;
char oper = 0;
int res = 0;
char ch = ' ';
String keepNum="";
while (true) {
//依次获取expression的每一个字符
ch = expression.substring(index, index + 1).charAt(0);
//判断ch
if (operStack.isOper(ch)) {
//如果符号栈不为空
if (!operStack.isEmpty()) {
//比较当前符号ch与栈顶符号的优先级,ch<=pop()
if (operStack.priority(ch) <= operStack.priority((char) operStack.peek())) {
num1 = numStack.pop();
num2 = numStack.pop();
oper = (char) operStack.pop();
res = numStack.cal(num1, num2, oper);
//把运算结果入数栈
numStack.push(res);
operStack.push(ch);
} else {//ch>pop()
operStack.push(ch);
}
} else {//符号栈为空,直接入栈
operStack.push(ch);
}
} else {
keepNum += ch;
//如果ch是expression的最后一位,则直接入栈
if (index == expression.length() - 1) {
numStack.push(Integer.parseInt(keepNum));
} else {
//判断下一位字符是否为符号,如果是,则将keepNum入数栈,如果不是则继续扫描
if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
numStack.push(Integer.parseInt(keepNum));
keepNum = "";
}
}
}
index++;
if (index == expression.length()) {
break;
}
}
//表达式扫描完毕(此时已运算一部分)
while (true) {
//符号栈为空 则说明运算结束
if (operStack.isEmpty()) {
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = (char) operStack.pop();
res = numStack.cal(num1, num2, oper);
numStack.push(res);
}
int res2 = numStack.pop();
System.out.println("表达式"+expression+"的结果是:"+res2);
}
}
//数组栈
class ArrayStack2{
private int maxSize;//栈的大小
private int[] stack;//数组模拟栈,数据放在数组中
private int top = -1;
//构造器
public ArrayStack2(int maxSize) {
this.maxSize=maxSize;
stack = new int[maxSize];
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//栈满
public boolean isFull(){
return top == maxSize - 1;
}
//入栈-push
public void push(int value) {
if (isFull()) {
System.out.println("栈满,无法入栈");
return;
}
top++;
stack[top] = value;
}
//出栈-pop
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈空,没有数据");
}
int value = stack[top];
top--;
return value;
}
//返回当前栈顶的值!=出栈
public int peek() {
return stack[top];
}
//遍历
public void show() {
if (isEmpty()) {
System.out.println("栈空,没有数据");
}
for (int i = top; i >= 0; i--) {
System.out.println("stack["+i+"]="+stack[i]);
}
}
//返回运算符的优先级,优先级是程序员来确定的,优先级使用数字来表示
//数字越大,优先级越高
public int priority(char 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;
}
}
5、关于栈的三种表达式
1)前缀表达式
- 前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
- 举例:(3+4)*5-6 的前缀表达式为 - * + 3 4 5 6
- 计算机求值,运算过程:
- 从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对他们做相应的计算,并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
2)中缀表达式
- 中缀表达式是常见的运算表达式,如 (3+4)*5-6
- 中缀表达式的求值对于人来说是非常熟悉的,但对计算机来说却不好操作。因此,在计算结果时,往往会将中缀表达式转成其他表达式来操作(一般转成后缀表达式)
3)后缀表达式
-
后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
-
举例:(3+4)*5-6 的后缀表达式为 3 4 + 5 * 6 -
-
计算机求值,运算过程:
- 从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对他们做相应的计算,并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
4)逆波兰计算器举例
- 输入一个逆波兰表达式,使用栈计算结果。支持小括号和多位数整数(只支持对整数的计算)
- 思路分析:(3+4)*5-6 ==> 3 4 + 5 * 6 -
- 从左至右扫描,将3,4压入栈
- 遇到+运算符,弹出4,3,计算3+4的值得7,将7入栈
- 将5入栈
- 遇到* 运算符,弹出5,7,计算7*5=35,将35入栈
- 将6入栈
- 遇到- 运算符,弹出6,35,计算35-6=29,将29入栈,即结果
- 代码实现
package com.stack;
/*
@author qw
@date 2020/8/13 - 16:56
**/
import java.util.*;
public class PolandNotation {
public static void main(String[] args) {
String suffixExpression = "30 4 + 5 * 6 -";
//1.先将表达式放到ArrayList中
//2.向ArrayList传递方法,遍历ArrayList 配合栈完成计算
List<String> plList = getListString(suffixExpression);
System.out.println("plList="+plList);
int res = calculate(plList);
System.out.println("后缀表达式的计算结果为:"+res);
}
//将逆波兰表达式存入到ArrayList中
public static List<String> getListString(String suffixExpression) {
String[] split=suffixExpression.split(" ");
List<String> list=new ArrayList<String>();
for (String ele : split) {
list.add(ele);
}
return list;
}
public static int calculate(List<String> ls) {
//创建栈
Stack<String> stack = new Stack<String>();
for (String item : ls) {
//这里使用正则表达式来取出数
if (item.matches("\\d+")) {//匹配的是多位数
//入栈
stack.push(item);
} else {
//pop两个数,并运算
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
if (item.equals("+")) {
res = num1 + num2;
} else if (item.equals("-")) {
res = num1 - num2;
} else if (item.equals("*")) {
res = num1 * num2;
} else if (item.equals("/")) {
res = num1 / num2;
} else {
throw new RuntimeException("运算符有误");
}
stack.push(String.valueOf(res));
}
}
//最后留在stack中的数据是运算结果
return Integer.parseInt(stack.pop());
}
}
5)中缀表达式转后缀表达式
(1)思路分析
-
初始化两个栈,运算符栈是和储存中间结果的栈s2
-
从左至右扫描中缀表达式
-
遇到操作数时,将其压入s2
-
遇到运算符时,比较其与s1栈顶运算符的优先级:
4.1 如果s1为空,或栈顶运算为左括号“(”,则直接将此运算符入栈
4.2 否则,若优先级比栈顶运算符的高,也将运算符压入s1
4.3 否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
-
遇到括号时:
5.1 如果是左括号“(”,则直接压入s1
5.2 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
-
重复步骤2至5,直到表达式的最右边
-
将s1中剩余的运算符依次弹出并压入s2
-
依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
(2)代码实现
package com.stack;
/*
@author qw
@date 2020/8/13 - 22:24
**/
import java.util.*;
import static com.stack.PolandNotation.calculate;
public class ExpressionTransform {
public static void main(String[] args) {
//直接对str操作不方便,因此将中缀表达式对应的List转换出来,
//即"1+((2+3)*4)-5" ---> ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
String expression ="1+((2+3)*4)-5";
List<String> list = toInfixExpressionList(expression);
//将中缀表达式对应的list --> 后缀表达式对应的list
//即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] ---> ArrayList [1,2,3,+,4,*,+,5,-]
List<String> stringList = parseSuffixExpressionList(list);
System.out.println(stringList);
int res = calculate(stringList);
System.out.println("计算结果为:"+res);
}
//将中缀表达式转成对应的List
public static List<String> toInfixExpressionList(String s) {
//定义一个List,存放中缀表达式对应的内容
List<String> ls = new ArrayList<String>();
int i=0;//辅助遍历表达式
String str;//多位数拼接
char c;//每遍历的字符,存入c
do {
//如果c是非数字,则直接加入ls
if ((c = s.charAt(i)) < '0' || (c = s.charAt(i)) > '9') {
ls.add(String.valueOf(c));
i++;
} else {//如果c是数字,需要考虑多位数
str = "";
while (i < s.length() && (c = s.charAt(i)) >= '0' && (c = s.charAt(i)) <= '9') {
str += c;
i++;
}
ls.add(str);
}
} while (i < s.length());
return ls;
}
//将中缀表达式对应的list --> 后缀表达式对应的list
public static List<String> parseSuffixExpressionList(List<String> ls) {
//定义两个栈
Stack<String> s1=new Stack<String>();//符号栈
//在整个转换过程中,s2没有pop操作,且最后需要逆序输出
//为方便起见,不使用栈结构,而直接使用List<String> s2
List<String> s2=new ArrayList<String>();//储存中间结果
//遍历ls
for (String item : ls) {
//如果是一个数,加入s2
if (item.matches("\\d+")) {//正则表达式 \d+ 匹配一个或多个数字
s2.add(item);
} else if (item.equals("(")) {
s1.push(item);
} else if (item.equals(")")) {
//如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止
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);
}
}
//将s1中剩余的运算符依次弹出并加入s2
while (s1.size() != 0) {
s2.add(s1.pop());
}
return s2;
}
//返回运算符的优先级,优先级是程序员来确定的,优先级使用数字来表示
//数字越大,优先级越高
public static int priority(String oper) {
if (oper.equals("*") || oper.equals("/") ) {
return 2;
} else if (oper.equals("+") || oper.equals("-") ) {
return 1;
} else {
return 0; //假定目前的表达式只有+ - * /
}
}
}