数据结构与算法之栈
作者:互联网
栈
1>栈的基本介绍
1.1、栈的实际需求
请计算表达式:[722-5+1-5+3-3] 的值
请问: 计算机底层是如何运算得到结果的?
注意不是简单的把算式列出运算,因为我们看这个算式 7 * 2 * 2 - 5,
但是计算机怎么理解这个算式的
对计算机而言, 它接收到的就是一个字符串, 我们讨论的是这个问题:栈
1.2、栈的基本性质
栈的英文为(stack)
栈是一个先入后出(FILO-First In Last Out)的有序列表
栈(stack)是限制线性表中元素的插入和删除只能在线性表的
同一端进行的一种特殊线性表。
允许插入和删除的一端,为变化的一端,称为栈顶(Top),
另一端为固定的一端, 称为栈底(Bottom)。
根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,
而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
图解方式说明出栈(pop)和入栈(push)的概念
1.3、栈的应用场景
- 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中, 直到子程序执行完后再将地址取出,以回到原来的程序中。
- 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外, 也将参数、 区域变量等数据存入栈中。
- 表达式的转换:[中缀表达式转后缀表达式]与求值(实际解决)。
- 二叉树的遍历。
- 图形的深度优先(depth 一 first)搜索法。
2>数组模拟栈
2.1、代码思路
- maxSize :栈的大小(数组的大小)
- arr :用来模拟栈的数组
- top :指向当前栈顶元素,初始值为 -1 ,表示栈空
- 判断栈满:top == maxSize ,即已经到达数组最后一个位置
- 判断栈空:top == -1
- 入栈:arr[++top] = arr;
- 出栈:return arr[top–] ;
2.2、代码实现
package com;
public class StackNode {
public int maxsize;
public int[] stack;
public int top = -1;
public StackNode(int maxsize)
{
this.maxsize = maxsize;
stack = new int[maxsize];
}
public boolean isFull()
{
return top == maxsize-1;
}
public boolean isEmpty()
{
return top == -1;
}
public void push(int data)
{
if (isFull())
{
System.out.println("栈满");
return;
}
stack[++top] = data;
}
public int pop()
{
if (isEmpty()){
throw new RuntimeException("栈空,无法pop出元素");
}
return stack[top--];
}
public void list()
{
if (isEmpty())
throw new RuntimeException("栈空,无法遍历元素");
for (int i = top;i>=0;i--)
{
System.out.printf("stack[%d] = %d\n",i,stack[i]);
}
}
}
package com;
import java.util.Scanner;
public class Test03 {
public static void main(String[] args) {
StackNode stackNode = new StackNode(4);
Scanner scanner = new Scanner(System.in);
String key = "";
boolean k = true;
while (k) {
System.out.println("show:表示显示栈");
System.out.println("exit:退出程序");
System.out.println("push:入栈");
System.out.println("pop:出栈");
key = scanner.next();
switch (key) {
case "show":
stackNode.list();
break;
case "exit":
k = false;
break;
case "push":
System.out.println("请输出一个数据");
int value = scanner.nextInt();
stackNode.push(value);
break;
case "pop":
System.out.println("出栈数据为" + stackNode.pop());
break;
default:
break;
}
}
System.out.println("程序已退出");
}
}
2.3>课后作业
使用链表模拟栈
3>栈实现综合计算器(中缀表达式)
3.1、代码思路
- 栈分为两个栈:
- 数栈(numStack):存储表达式中的数字
- 符号栈(operStack):存储表达式中的符号
- 扫描表达式(这里并没有考虑括号):
- 对于数:扫描到数,则直接压入数栈
- 对于运算符:扫描到运算符,分为如下几种情况:
- 如果符号栈为空,则直接入栈
- 如果符号栈不为空:
- 如果当前扫描到的运算符的优先级 <= 符号栈栈顶的运算符的优先级,说明上次的运算符优先级较高,先执行优先级高的运算
- 从数栈中弹出两个数,根据符号栈栈顶的运算符进行运算(优先级高,就先算出来)
- 然后将计算的结果压入栈中
- 再将当前运算符压入符号栈
- 如果当前扫描到的运算符的优先级 > 符号栈栈顶的运算符的优先级,说明上次的运算符优先级较低,直接压入符号栈
- 如果当前扫描到的运算符的优先级 <= 符号栈栈顶的运算符的优先级,说明上次的运算符优先级较高,先执行优先级高的运算
- 何时停止循环?
- 处理完表达式,退出循环
- 即表达式下标(index)的值大于表达式(expression)的长度
- 代码:index >= expression.length()
- 表达式扫描完成:
- 此时符号栈中的运算符优先级都相同
- 从数栈中弹出两个数,再从符号栈中弹出一个运算符,进行运算,计算结果放回数栈中
- 何时停止循环?符号栈为空则停止:operStack.isEmpty()
- 表达式的值?符号栈为空时,数栈栈顶还有一个元素,这个元素的值就是表达式的值
- 举例:3+2*6-2
- 首先
- 将 3 压入数栈
- 将 + 压入符号栈
- 将 2 压入数栈
2
3 +
数栈 符号栈
- 由于:优先级大于 + ,所以将 * 压入 符号栈,然后将 6 压入数栈
6
2 *
3 +
数栈 符号栈
- 由于 - 优先级低于 * ,所以从数栈中弹出两个数(6 和 2),从符号栈中弹出一个运算符(*),进行运算,运算结果再压入数栈,然后将 - 压入符号栈
12 -
3 +
数栈 符号栈
- 将 2 压入数栈,表达式处理完毕
2
12 -
3 +
数栈 符号栈
- 重复此过程,直至符号栈为空:从数栈中弹出两个数,再从符号栈中弹出一个运算符,进行运算,计算结果放回数栈中
10
3 +
数栈 符号栈
13
数栈 符号栈
3.2、代码实现
栈的定义:专为计算器而生的栈
对于乘除法特别说明:
由于栈先进后出的特点,
num1 是运算符后面的数(减数、除数),
num2 是运算符前的数(被减数、被除数),
特别需要注意减法与除法的顺序
res = num2 - num1;
res = num2 / num1;
package com;
public class CalStack {
public int maxsize;
public int[] stack;
public int top = -1;
public CalStack(int maxsize)
{
this.maxsize = maxsize;
stack = new int[maxsize];
}
public boolean isEmpty()
{
return top == -1;
}
public boolean isFull()
{
return top == maxsize - 1;
}
public void push(int data)
{
if (isFull())
{
throw new RuntimeException("栈已满");
}
stack[++top] = data;
}
public int pop()
{
if (isEmpty())
throw new RuntimeException("栈是空的");
return stack[top--];
}
public int priority(int oper)
{
if (oper == '*' || oper == '/'){
return 1;}
else if (oper == '+' || oper == '-') {
return 0;}
else {
return -1;
}
}
public int cal(int num1,int num2,int oper)
{
int value =0;
switch (oper)
{
case '+':
value = num1 + num2;
break;
case '-':
value = num2 - num1;
break;
case '*':
value = num1 * num2;
break;
case '/':
value = num2 / num1;
break;
}
return value;
}
public boolean isOper(char c)
{
return c == '+'||c == '-'||c == '*'||c =='/';
}
public int peek()
{
if (isEmpty())
throw new RuntimeException("栈为空,栈顶无元素");
return stack[top];
}
}
package com;
public class Test04 {
public static void main(String[] args) {
CalStack numStack = new CalStack(10);
CalStack opeStack = new CalStack(10);
char c = ' ';
char d = ' ';
int e = 0;
String s = "";
int num1 = 0;
int num2 = 0;
int index = 0;
String expression = "70*2*2-5+1-5+3-4";
int size = expression.length();
while (true) {
c = expression.charAt(index);
if (index == size - 1) {
int f = Integer.parseInt(String.valueOf(c));
System.out.println(f);
numStack.push(f);
System.out.println(numStack.peek());
break;
}
if (opeStack.isOper(c)) {
if (opeStack.isEmpty())
opeStack.push(c);
else {
if (opeStack.priority(c) > opeStack.priority(opeStack.peek())) {
opeStack.push(c);
} else {
num1 = numStack.pop();
num2 = numStack.pop();
e = opeStack.pop();
opeStack.push(c);
numStack.push(numStack.cal(num1, num2, e));
//System.out.println(numStack.cal(num1, num2, e));
}
}
//System.out.println(c);
index++;
} else {
s += c;
d = expression.charAt(index + 1);
if (opeStack.isOper(d)) {
numStack.push(Integer.parseInt(s));
s = "";
}
//numStack.push(Integer.parseInt(s));
index++;
}
}
while (true)
{
if (opeStack.isEmpty())
{
System.out.printf("%s的结果是%d",expression,numStack.pop());
break;
}
num1 = numStack.pop();
//System.out.println("num1 = " +num1);
num2 = numStack.pop();
e = opeStack.pop();
//System.out.println(numStack.cal(num1,num2,e));
numStack.push(numStack.cal(num1,num2,e));
}
}
}
4>前缀 中缀 后缀表达式
4.1、前缀表达式(波兰表达式)
4.1.1、前缀表达式
- 前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
- 举例说明: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6
4.1.2、前缀表达式的计算机求值
- 从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
- 例如:(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:
- 从右至左扫描,将6、5、4、3压入堆栈
- 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
- 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
- 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
4.2、中缀表达式
- 中缀表达式就是常见的运算表达式,如(3+4)×5-6
- 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题,因为中缀表达式存在运算符优先级的问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)
4.3、后缀表达式
4.3.1、后缀表达式
- 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
- 中缀表达式举例说明: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –
- 再比如:
正常的表达式 | 逆波兰表达式 |
---|---|
a+b | a b + |
a+(b-c) | a b c - + |
a+(b-c)*d | a b c – d * + |
a+d*(b-c) | a d b c - * + |
a=1+3 | a 1 3 + = |
4.3.2、后缀表达式的计算机求值
- 从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
- 例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
- 从左至右扫描,将3和4压入堆栈;
- 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
- 将5入栈;
- 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
- 将6入栈;
- 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
5>逆波兰计算器
5.1、计算器说明
- 输入一个逆波兰表达式(后缀表达式), 使用栈(Stack),计算其结果
- 支持小括号和多位数整数, 因为这里我们主要讲的是数据结构, 因此计算器进行简化, 只支持对整数的计算
5.2、代码思路
- 计算后缀表达式无需考虑运算符优先级问题,所以只需要一个数栈即可
- 分为两种情况:
- 遇到数:压入数栈
- 遇到运算符:从数栈中弹出两个数,进行计算,计算结果压入数栈
- 何时计算完成?处理完表达式就代表计算完成
5.3、代码实现
- 出栈的两个数:num2 和 num1
- num2 先出栈,所以 num2 是减数或除数
- num1 后出栈,所以 num1 是被减数或被除数
public class Test05 {
public static void main(String[] args) {
String suffixExpression = "4 5 * 8 - 60 + 8 2 / +"; // 76
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<>();
for (String s : split) {
list.add(s);
}
for (String s:list) {
System.out.println(s);
}
System.out.printf("后缀表达式%s的结果为%d", suffixExpression, cal(list));
//System.out.printf("后缀表达式%s的结果为%d",expression,cal(list));
}
public static int cal(List<String> list) {
Stack<String> stack = new Stack<>();
int num1 = 0;
int num2 = 0;
int result = 0;
for (String s : list) {
if (s.matches("\\d+")) {
stack.push(s);
} else {
num1 = Integer.parseInt(stack.pop());
num2 = Integer.parseInt(stack.pop());
switch (s) {
case "+":
result = num1 + num2;
break;
case "-":
result = num2 - num1;
break;
case "*":
result = num1 * num2;
break;
case "/":
result = num2 / num1;
break;
}
stack.push(""+result);
}
}
return Integer.parseInt(stack.pop());
}
}
6>中缀表达式转后缀表达式
6.1、代码思路
基于堆栈的算法
- 1、从左到右扫描每一个字符。如果扫描到的字符是操作数(如a、b等),就直接输出这些操作数。
- 2、如果扫描到的字符是一个操作符,分三种情况:
- (1)如果堆栈是空的,直接将操作符存储到堆栈中(push it)
- (2)如果该操作符的优先级大于堆栈出口的操作符,就直接将操作符存储到堆栈中(push it)
- (3)如果该操作符的优先级低于堆栈出口的操作符,就将堆栈出口的操作符导出(pop it),
直到该操作符的优先级大于堆栈顶端的操作符。将扫描到的操作符导入到堆栈中(push)。
- 3、如果遇到的操作符是左括号"(”,就直接将该操作符输出到堆栈当中。该操作符只有在遇到右括号“)”的时候移除。这是一个特殊符号该特殊处理。
- 4、如果扫描到的操作符是右括号“)”,将堆栈中的操作符导出(pop)到output中输出,直到遇见左括号“(”。将堆栈中的左括号移出堆栈(pop)。继续扫描下一个字符
- 5、如果输入的中缀表达式已经扫描完了,但是堆栈中仍然存在操作符的时候,我们应该讲堆栈中的操作符导出并输入到output 当中。
6.2、举例说明
- 举例说明:将中缀表达 式“1+((2+3)×4)-5”转换为后缀表达式的过程如下
- 因此结果为:“1 2 3 + 4 × + 5 –”
6.3、代码实现
public class Test05 {
public static void main(String[] args) {
String expression = "1+((2+3)*4)-5";
List<String> list = new ArrayList<>();
list = change(expression);
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()!=false)
{
System.out.println(iterator.next());
}
System.out.printf("中缀表达式%s的结果为%d", expression, cal(list));
public static List<String> change(String expression) {
List<String> list = new ArrayList<>();
Stack<Character> stack = new Stack<>();
int size = expression.length();
int index = 0;
char c = ' ';
char d = ' ';
String s = "";
while(true)
{
c = expression.charAt(index);
if (index == size-1)
{
list.add(String.valueOf(c));
while (stack.isEmpty()!=true)
{
list.add(String.valueOf(stack.pop()));
}
break;
}
if (c<48||c>57)
{
if (stack.isEmpty() && c !=')')
{
stack.push(c);
}
else {
if (c == '(') {
stack.push(c);
} else if (c == ')') {
while (true) {
d = stack.pop();
if (d == '(') {
break;
}
list.add(String.valueOf(d));
}
} else if (stack.peek() == '(') {
stack.push(c);
} else if (priority(stack.peek()) >= priority(c)) {
while (true) {
if (stack.isEmpty() || priority(c) > priority(stack.peek()))
break;
list.add(String.valueOf(stack.pop()));
}
stack.push(c);
}
}
index++;
}
else {
s+=c;
if (expression.charAt(index+1)<48||expression.charAt(index+1)>57)
{
list.add(s);
s = "";
}
index++;
}
}
return list;
}
public static int priority(char s)
{
if (s == '*'||s == '/')
{
return 1;
}
else if (s == '+'||s == '-')
{
return 0;
}
else {
return -1;
}
}
public static int cal(List<String> list) {
Stack<String> stack = new Stack<>();
int num1 = 0;
int num2 = 0;
int result = 0;
for (String s : list) {
if (s.matches("\\d+")) {
stack.push(s);
} else {
num1 = Integer.parseInt(stack.pop());
num2 = Integer.parseInt(stack.pop());
switch (s) {
case "+":
result = num1 + num2;
break;
case "-":
result = num2 - num1;
break;
case "*":
result = num1 * num2;
break;
case "/":
result = num2 / num1;
break;
}
stack.push(""+result);
}
}
return Integer.parseInt(stack.pop());
}
}
标签:num1,int,之栈,public,运算符,算法,数据结构,stack,表达式 来源: https://blog.csdn.net/oLengNuanZiZhi12/article/details/113803672