文章目录
五、栈 Stack
1.栈的实际需求
2. 栈的介绍
- 栈的英文为(stack)
- 栈是一个先入后出(FILOfirst In Last Out)的有序列表。
- 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一^种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另—端为固定的一端,称为栈底(Bottom)。
- 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而
删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除 - 出栈(pop)入栈(push)的概念
3. 栈的应用场景
- 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。retutn
- 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
- 表达式的转换与求值(实际解决)。【中缀表达式 —> 后缀表达式】
- 二叉树的遍历。
- 图形的深度优先(depth—first)搜索法。
4. 栈的思路分析
1)分析
- 使用数组来模拟栈
- 定义一个 top 来表示栈顶,初始化 为 -1
- 入栈的操作,当有数据加入到栈时, top++; stack[top] = data;
- 出栈的操作, int value = stack[top]; top–, return value
2)数组模拟栈
测试方法
public static void testArrayStack(){
ArrayStack arrayStack = 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":{
arrayStack.list();
break;
}
case "exit":{
loop = false;
break;
}
case "push":{
System.out.print("请输入一个数:");
int num = scanner.nextInt();
arrayStack.push(num);
System.out.println("-----成功啦!!!----------");
break;
}
case "pop":{
try {
System.out.println("获取数据:"+arrayStack.pop());
}catch (Exception e){
System.out.println(e.getMessage());
}
}
default:break;
}
}
}
// 定义一个类,表示栈结构
class ArrayStack {
private int maxSize;
private int[] stack;
private int top; // 初始索引
public ArrayStack(int maxSize) {
top = -1;
this.maxSize = maxSize;
stack = new int[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("------栈空啦------");
}
return stack[top--];
}
// 遍历栈
public void list(){
if(isEmpty()){
System.out.println("-----栈空,没有数据------");
return;
}
for (int i = top; i > -1; i--) {
System.out.println("stack["+i+"]:"+stack[i]);
}
}
}
3)链表模拟栈
class Node {
Node next;
int id;
public Node(int id, Node next) {
this.id = id;
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"id=" + id +
'}';
}
}
class LinkedStack {
Node top;
int maxSize;
int count;
public LinkedStack(int maxSize) {
top = null;
this.maxSize = maxSize;
}
public boolean isFull() {
return count == maxSize;
}
public boolean isEmpty() {
return count == 0;
}
public void push(int id) {
if (isFull()) {
System.out.println("---栈满了---");
return;
}
top = new Node(id, top);
count++;
}
public Node pop() {
if (isEmpty()) {
throw new RuntimeException("---栈空----");
}
Node node = top;
top = top.next;
count--;
return node;
}
}
5. 栈实现计算器
1)思路分析
看不懂自己可以尝试画一下,我也没看懂。
2)代码实现
引用这位同学的,自己想的和老师不太一样,复盘中会给出代码
代码实现 计算器
6. 表达式
1)前缀表达式(波兰表达式)
前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前
举例说明: (3+4)×5-6
对应的前缀表达式就是 - x + 3 4 5 6
前缀表达式的计算机取值:
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
例如: `(3+4)×5-6` 对应的前缀表达式就是 `- × + 3 4 5 6` , 针对前缀表达式求值步骤如下:
1. **从右至左**扫描,将6、5、4、3压入堆栈
2. 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
3. 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
4. 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
2) 中缀表达式
- 中缀表达式就是常见的运算表达式,如
(3+4)×5-6
- 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式.)
3)后缀表达式
1. 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
2. 举例说明: `(3+4)×5-6` 对应的后缀表达式就是 `3 4 + 5 × 6 –`
3. 比如
正常表达式 | 逆波兰表达式 |
---|---|
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 + = |
后缀表达式的计算机求值
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,
用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;
重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
例如: `(3+4)×5-6` 对应的后缀表达式就是 `3 4 + 5 × 6 -` , 针对后缀表达式求值步骤如下:
1. 从左至右扫描,将3和4压入堆栈;
2. 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
3. 将5入栈;
4. 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
5. 将6入栈;
6. 最后是 - 运算符,计算出35-6的值,即29,由此得出最终结果
7. 逆波兰计算器
public class Test04_逆波兰计算器 {
public static void main(String[] args) {
// 逆波兰表达式 (3+4)×5-6 --> 3 4 + 5 × 6 –
// 4 * 5 - 8 + 60 + 8 / 2 --> 4 5 * 8 - 60 + 8 2 / +
String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
// 1. 将 表达式放入 arrayList中
// 2. 将 ArrayList传递给一个方法,遍历ArrayList 配合栈,完成计算
List<String> listString = getListString(suffixExpression);
int calculate = calculate(listString);
System.out.println(calculate);
}
// 将一个逆波兰表达式,依次将数据和运算符放到 arrayList中
public static List<String> getListString(String suffixExpression) {
return Arrays.stream(suffixExpression.split(" ")).collect(Collectors.toList());
}
// 计算
public static int calculate(List<String> ls) {
// 1. 创建栈
Stack<String> stack = new Stack<>();
// 2. 遍历 ls
ls.stream().forEach(item -> {
// 3. 正则表示式取出数
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("符号错误");
}
// 把res 入栈
stack.push(String.valueOf(res));
}
});
// 最后留在stack中的数据就是结果
return Integer.parseInt(stack.pop());
}
}
8. 中缀表达式转后缀表达式
1)步骤
- 初始化两个栈: 运算符栈 s1 和储存中间结果的栈 s2;
- 从左至右扫描中缀表达式;
- 遇到操作数时, 将其压 s2;
- 遇到运算符时, 比较其与 s1 栈顶运算符的优先级:
- 如果 s1 为空, 或栈顶运算符为左括号
(
, 则直接将此运算符入栈; - 否则, 若优先级比栈顶运算符的高, 也将运算符压入 s1;
- 否则, 将 s1 栈顶的运算符弹出并压入到 s2 中, 再次转到 (4. 1) 与 s1 中新的栈顶运算符相比较;
- 如果 s1 为空, 或栈顶运算符为左括号
- 遇到括号时:
- 如果是左括号
(
, 则直接压入 s1 - 如果是右括号
)
, 则依次弹出 s1 栈顶的运算符, 并压入 s2, 直到遇到左括号为止, 此时将这一对括号丢弃
- 如果是左括号
- 重复步骤 2 至 5, 直到表达式的最右边
- 将 s1 中剩余的运算符依次弹出并压入 s2
- 依次弹出 s2 中的元素并输出, 结果的逆序即为中缀表达式对应的后缀表达式
public class Test04_逆波兰计算器 {
public static void main(String[] args) {
/* // 逆波兰表达式 (3+4)×5-6 --> 3 4 + 5 × 6 –
// 4 * 5 - 8 + 60 + 8 / 2 --> 4 5 * 8 - 60 + 8 2 / +
String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
// 1. 将 表达式放入 arrayList中
// 2. 将 ArrayList传递给一个方法,遍历ArrayList 配合栈,完成计算
List<String> listString = getListString(suffixExpression);
int calculate = calculate(listString);
System.out.println(calculate);*/
test1();
}
// 中缀表达式转 后缀表达式zs
// 1. 1 + ( ( 2 + 3 ) * 4 ) -5
// 2. 先将表达式转为 中缀
// 3. 中缀转后缀
public static void test1() {
String suffixExpression = "1+((2+3)*4)-5";
List<String> strings = toInfixExpressionList(suffixExpression);
System.out.println(strings);
List<String> strings1 = parseSuffixExpression(strings);
System.out.println(strings1);
System.out.println(calculate(strings1));
}
// 中缀表达式 --> 转 list
public static List<String> toInfixExpressionList(String s) {
List<String> list = new ArrayList<>();
int i = 0; // 指针,用于遍历 s
String str; // 对多位数进行拼接
char c; // 每遍历一个字符,就放入到 c
do {
// 如果 c是非数字,
if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
list.add("" + c);
i++;
} else {
// 如果是一个数,需要考虑多位数问题
str = "";
while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57) {
str += c;
i++;
}
list.add(str);
}
}
while (i < s.length());
return list;
}
// list ---> 后缀表达式
public static List<String> parseSuffixExpression(List<String> list) {
// 1. 定义两个栈
Stack<String> s1 = new Stack<>(); // 符号栈
// 因为 s2 整个转换过程中 , 没有 pop操作, 后面需要逆序输出, 因此使用 ArrayList替代
// Stack<String> s2 = new Stack<>(); // 中间结果栈
List<String> s2 = new ArrayList<>();
// 2. 遍历 list
list.stream().forEach(item -> {
// 如果是 数 , 进入 s2
if (item.matches("\\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 {
// 当 item 的优先级小于 或等于栈顶运算符的优先级
// 将 s1 栈顶的运算符弹出压入 s2 中,
// 再次与 s1 栈顶的运算符进行比较
while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){
s2.add(s1.pop());
}
// item 压入栈
s1.push(item);
}
});
// 3. 将 s1 剩余的 运算符加入 s2
while (s1.size() != 0){
s2.add(s1.pop());
}
return s2;
}
// 将一个逆波兰表达式,依次将数据和运算符放到 arrayList中
public static List<String> getListString(String suffixExpression) {
return Arrays.stream(suffixExpression.split(" ")).collect(Collectors.toList());
}
// 计算
public static int calculate(List<String> ls) {
// 1. 创建栈
Stack<String> stack = new Stack<>();
// 2. 遍历 ls
ls.stream().forEach(item -> {
// 3. 正则表示式取出数
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("符号错误");
}
// 把res 入栈
stack.push(String.valueOf(res));
}
});
// 最后留在stack中的数据就是结果
return Integer.parseInt(stack.pop());
}
}
// 类 ,返回运算符的优先级
class Operation {
static int ADD = 1;
static int SUB = 1;
static int MUL = 2;
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;
}
}