栈
目录
栈的一个实际需求
栈的介绍
- 栈是一个先入后出的有队列
- 栈是限制线性表中的元素的插入和删除只能在同一端的一种特殊的线性表,允许插入和删除的一端成为栈顶(Top),固定的一段称为栈底(Bottom)
- 根据定义可知,最先放入的元素在栈底,而最后放入的元素在栈顶。最先放入的后删除,后放入的先删除。
- 图解如下:
栈的应用场景
- 子程序的调用:在跳往子程序前,会先将下个指令的地址存在堆栈中,直到子程序执行完成后在将地址取出,以回到原来的程序中。
- 处理递归调用:和子程序类似,只是除了存储下一个指令的地址外,也将参数,区域变量等数据存入堆栈中。
- 表达式的转化:[中缀表达式转后缀表达式]与求值
- 二叉树的遍历
- 图的深度优先(depth-first)搜索.
栈的快速入门
由于栈是一种有序列表,故可以使用数组模拟。
思路分析:
- 定义一个top表示栈顶 初始值为-1
- 入栈操作:top++;stack[top] = data;
- 出栈操作:temp = stack[top]; top--;
代码实现
/**
* 表示栈
*/
class ArrayStack{
private int maxSize;
private int[] stack;
private int top = -1;
public ArrayStack(int maxSize){
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
/**
* 判断栈满
*/
public boolean isFull(){
return top+1 == maxSize;
}
/**
* 判断栈空
*/
public boolean isEmpty(){
return top == -1;
}
/**
* 入栈
*/
public void push(int value){
if (isFull()){
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
return;
}
/**
* 出栈
*/
public int pop(){
int temp = 0;
try {
temp = stack[top];
top--;
} catch (Exception e) {
System.out.println("栈空");
}
return temp;
}
/**
* 遍历栈
*/
public void show(){
if (isEmpty()){
System.out.println("没有数据!!");
return;
}
for (int i = top; i >= 0; i--) {
System.out.print(stack[i]+"");
}
System.out.println();
}
}
测试
public class ArrayStackDemo {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(4);
String key = "";
boolean loop = true;
Scanner scanner = 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 = scanner.nextLine();
switch (key){
case "show" :
stack.show();
break;
case "push":
System.out.print("请输入一个数:");
int val = scanner.nextInt();
stack.push(val);
break;
case "pop" :
if ( stack.isEmpty()){
System.out.println("栈空!");
break;
}
int res = stack.pop();
System.out.println("取出的数据是:"+res);
break;
case "exit" :
scanner.close();
loop = false;
break;
default: break;
}
}
System.out.println("程序退出!!");
}
}
栈实现综合计算器:
思路分析
代码:
public class Calculator {
public static void main(String[] args) {
String expression = "7*2*2-5+12-3-4";
//创建两个栈,一个是数栈 一个是符号栈
ArrayStack2 numStacks = new ArrayStack2(10);
ArrayStack2 operStacks = new ArrayStack2(10);
int index = 0;
int num1 = 0;
int num2 = 0;
int oper = 0;
int res = 0;
char ch = ' ';
String keepNum = "";
while (true) {
ch = expression.substring(index, index + 1).charAt(0);
if (operStacks.isOper(ch)){
if (!operStacks.isEmpty()){
if (operStacks.priority(ch) <= operStacks.priority(operStacks.peek())){
num2 = numStacks.pop();
num1 = numStacks.pop();
oper = operStacks.pop();
res = numStacks.cal(num1,num2,oper);
numStacks.push(res);
operStacks.push(ch);
}else{
operStacks.push(ch);
}
}else{
operStacks.push(ch);
}
}else {
keepNum += ch;
if (index == expression.length()-1){
numStacks.push(Integer.parseInt(keepNum));
}else {
if (operStacks.isOper(expression.substring(index+1,index+2).charAt(0))){
numStacks.push(Integer.parseInt(keepNum));
keepNum = "";
}
}
}
index++;
if (index >= expression.length()){
break;
}
}
while (true){
if (operStacks.isEmpty()){
break;
}
num2 = numStacks.pop();
num1 = numStacks.pop();
oper = operStacks.pop();
res = numStacks.cal(num1,num2,oper);
numStacks.push(res);
}
int res2 = numStacks.pop();
System.out.println("值为 : " + res2);
}
}
class ArrayStack2 {
private int maxSize;
private int[] stack;
private int top = -1;
public ArrayStack2(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
/**
* 判断栈满
*/
public boolean isFull() {
return top + 1 == maxSize;
}
/**
* 判断栈空
*/
public boolean isEmpty() {
return top == -1;
}
/**
* 入栈
*/
public void push(int value) {
if (isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
/**
* 出栈
*/
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈空!");
}
int temp = stack[top];
top--;
return temp;
}
/**
* 遍历栈
*/
public void show() {
if (isEmpty()) {
System.out.println("没有数据!!");
return;
}
for (int i = top; i >= 0; i--) {
System.out.print(stack[i] + "");
}
System.out.println();
}
//返回运算符优先级
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 res = 0;
switch (oper){
case '+':
res = num1 + num2;
break;
case '-':
res = num1 - num2;
break;
case '*':
res = num1 * num2;
break;
case '/':
res = num1 / num2;
break;
default:
break;
}
return res;
}
//返回栈顶的值
public int peek(){
return stack[top];
}
}
逆波兰式计算器
输入一个逆波兰式(后缀表达式)计算器结果
思路分析
代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
//定义逆波兰表达式
// (30+4)*5-6 --> 30 4 + 5 * 6 - --> 164
String suffixExpression = "30 4 + 5 * 6 -";
List<String> list = getListString(suffixExpression);
int result = calculate(list);
System.out.println(result);
}
// 将字符串分隔开
public static List<String> getListString(String suffixExpression){
//将字符串分隔
String[] s = suffixExpression.split(" ");
List<String> list = new ArrayList<>();
for(String ele : s){
list.add(ele);
}
return list;
}
// 完成计算
public static int calculate(List<String> list){
Stack<String> stack = new Stack<>();
for (String item : list){
if (item.matches("\\d+")){
stack.push(item);
}else{
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(""+res);
}
}
return Integer.parseInt(stack.pop());
}
}
中缀表达式转后缀表达式
具体步骤如下:
- 初始化两个栈,运算符栈 s1 和存储中间结果的栈 s2
- 从左只有扫描表达式
- 遇到操作数 压入 s2
- 遇到运算符时,比较其与s1栈顶的运算符的优先级:
- 若s1为空 或栈顶运算符为 "(" 则直接将其压入栈;
- 否则 若优先级比栈顶的高,也将其压入s1
- 否则 将s1栈顶的运算符弹出并压入到s2中,在回到4.1与s1中新的栈顶运算符相比较;
- 遇到括号的时候:
- 如果是左括号,直接压入s1
- 如果是右括号,则依次弹出s1栈顶的运算符并压入到s2直到遇到左括号,此时丢弃一对括号
- 重复2-5直到表达式的最右边
- 将s1中剩余的运算符依次弹出并压入s2中
- 依次弹出s2中的元素,结果的逆序即为中缀表达式对应的后缀表达式。
举例说明:
转化 “1+((2+3)*4)-5”
扫描的元素 | s2 | s1 | 说明 |
1 | 1 | 数字直接入栈 | |
+ | 1 | + | 符号栈空,直接入栈 |
( | 1 | +( | 左括号,直接入栈 |
( | 1 | +(( | 左括号,直接入栈 |
2 | 12 | +(( | 数字直接入栈 |
+ | 12 | +((+ | 栈顶为(,直接入栈 |
3 | 123 | +((+ | 数字直接入栈 |
) | 123+ | +( | 右括号,弹出符号栈元素,直到遇到左括号 |
* | 123+ | +(* | 栈顶为(,直接入栈 |
4 | 123+4 | +(* | 数字直接入栈 |
) | 123+4* | + | 右括号,弹出符号栈元素,直到遇到左括号 |
- | 123+4*+ | - | 运算符相同,弹出 + 压入 - |
5 | 123+4*+5 | - | 数字直接入栈 |
123+4*+5- | 完成 |
代码实现思路分析:
代码实现:
public class PolandNotation {
public static void main(String[] args) {
String expression = "1+((2+3)*4)-5";
List<String> toInfixExpression = toInfixExpression(expression);
System.out.println("中缀表达式:" + toInfixExpression);
List<String> parseSuffixExpressionList = parseSuffixExpressionList(toInfixExpression);
System.out.println("后缀表达式:" + parseSuffixExpressionList);
}
// 将字符串转为对应的list
public static List<String> toInfixExpression(String s){
List<String> ls = new ArrayList<>();
int i = 0;
String str;
char c;
do{
if ((c=s.charAt(i)) < 48 || (c=s.charAt(i)) > 57){
ls.add(""+c);
i++;
}else{
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> parseSuffixExpressionList(List<String> ls){
// 符号栈
Stack<String> s1 = new Stack<>();
// 对于s2在整个转化过程中没有弹出操作,因此使用List即可
List<String> s2 = new ArrayList<>();
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 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){
s2.add(s1.pop());
}
s1.push(item);
}
}
while (s1.size() != 0){
s2.add(s1.pop());
}
return s2;
}
}
class Operation{
private static int ADD = 1;
private static int SUB = 1;
private static int MUL = 2;
private static int DIV = 2;
public static int getValue(String operation){
int result = 0;
switch (operation){
case "+":
result = ADD;
break;
case "-":
result = SUB;
break;
case "*":
result = MUL;
break;
case "/":
result = DIV;
break;
default:
break;
}
return result;
}
后缀表达式(逆波兰式)的计算往上翻就可以查到哦!
如果哪位大佬发现了错误可以私信或评论我,谢谢啦!
学习中的菜鸡一个。