- 栈的特点
- 栈的场景
- 栈的介绍
- Java实现
- 用数组实现
- 用链表实现(含尾插法和头插法)
- 场景应用
- 栈的表达形式(前缀、中缀、后缀)
以下只给出思路与关键方法,算法的源代码放在了git中,需要的自取
leidl97/algorithm-src
栈的特点
LIFO(后入先出)
《算法》将它比喻为桌子上的文件,后放上去的先拿起来阅读

出自《算法第四版》第78页
栈的场景
算术表达式求值,比如《算法第四版》所提到的Dijkstra的黄河算术表达式

《算法第四版》第81页
介绍
先入后出的一个有序列表
限制线性表中元素的插入和删除只能在同一端进行的特殊线性表。允许插入和删除的一端为变化的一端,成为栈顶(top),另一端为固定的一端成为栈底(bottom)
代码实现
可以用数组和链表的方式实现
用数组实现
定义变量3个,大小,栈顶,栈本身
方法定义4个,入栈,出栈,判断栈满,判断栈空
Java实现
属性定义
1 2 3 4 5 6 7 8 9 10 11 12
| public int top;
public int[] stack;
public int size;
public MyStack(int size) { this.top = -1; this.size = size; this.stack = new int[this.size]; }
|
方法定义
入栈
1 2 3 4 5 6 7 8 9
| public void push(Object data) { if (isFull()) { System.out.println("栈满"); return; } this.top++; this.stack[top] = data; }
|
出栈
1 2 3 4 5 6 7 8 9 10 11
| public void pop() { if (isEmpty()) { System.out.println("栈空"); return; } int val = this.stack[this.top]; this.stack[top] = 0; this.top--; System.out.println("出栈成功:"+val); }
|
判断栈满
1 2 3
| public boolean isFull() { return this.top == this.size - 1; }
|
判断栈空
1 2 3
| public boolean isEmpty() { return this.top == -1; }
|
用链表方式实现
Java实现
属性定义
1 2 3 4 5 6
| public int no; public MyLinkedListStack next; public MyLinkedListStack(int no) { this.no = no; }
|
遍历实现
1 2 3 4 5 6 7 8 9 10
| public void show(){ MyLinkedListStack cur = head; while (true) { if (cur.next == null) { break; } cur = cur.next; System.out.println(cur); }
|
尾插法(不推荐)
逆向思维,反人类,需要分情况讨论
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public void push(MyLinkedListStack stack) { MyLinkedListStack cur = head; while (true) { if (cur.next == null) { break; } cur = cur.next; } cur.next = stack; }
public void pop() { if (head.next == null) { System.out.println("栈空"); return; } if (head.next.next == null) { System.out.println("出栈元素为:"+head.next); head.next = null; return; } MyLinkedListStack cur = head; while (true) { if (cur.next.next == null) { System.out.println("出栈元素为:"+cur.next); cur.next = null; break; } cur = cur.next; } }
|
头插法(推荐)
简单,这里可见队列用尾插,栈用头插
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void popHead() { if (head.next == null) { System.out.println("栈空"); return; } System.out.println("出栈元素为:"+head.next); head.next = head.next.next; }
public void pushHead(MyLinkedListStack stack) { stack.next = head.next; head.next = stack; }
|
场景应用
用自定义栈来实现
给一个字符串表达式,输出最后计算结果
如:7*9-3+4/2+6-2
难点
①连续减法运算,需要考虑变号的情况
②数字为多位数运算情况
代码实现(中缀表达式)
获得字符优先级
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public static final int plusPriority = 1; public static final int subPriority = 1; public static final int multiply = 2; public static int getPriority(char c) { int priority = 0; switch (c) { case '*' : priority = Normal.multiply; break; case '/' : priority = Normal.multiply; break; case '+' : priority = Normal.plusPriority; break; case '-' : priority = Normal.subPriority; break; default: priority = 0; } return priority; }
|
判断两个字符的优先级
1 2 3 4 5 6 7 8
| public boolean isPriority(char c,Object o) { int cPriority = Normal.getPriority(c); int oPriority = Normal.getPriority((char)o); System.out.println(cPriority+":"+oPriority); return (cPriority-oPriority) > 0; }
|
元素出栈操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public void before(char c, MyStack numStack, MyStack opreStack) { System.out.println("出栈计算"); int num1 = (int)numStack.pop(); int num2 = (int)numStack.pop(); char c1 = (char)opreStack.pop(); System.out.println(c); int result = 0; if (c1 == '*') { result = num1 * num2; } else if (c1 == '+') { result = num1 + num2; } else if (c1 == '-') { result = num2 - num1; } else if (c1 == '/') { result = num2 / num1; } else { throw new RuntimeException("暂不支持该运算符"); } numStack.push(result); opreStack.push(c); }
|
栈内的最后运算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void after(MyStack numStack, MyStack opreStack) { int num1 = (int)numStack.pop(); int num2 = (int)numStack.pop(); char c1 = (char)opreStack.pop(); int result = 0; if (opreStack.top != -1) { if (c1 == '+') { c1 = (char)opreStack.stack[opreStack.top] == '-' ? '-' : c1; } else { c1 = (char)opreStack.stack[opreStack.top] == '-' ? '+' : c1; } } result = c1 == '+' ? num1 + num2 : num2 - num1; numStack.push(result); }
|
主要的计算方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public void computer(String str) { char[] array = str.toCharArray(); MyStack numStack = new MyStack(array.length); MyStack opreStack = new MyStack(array.length); int isNum = 0; int count = 0; int[] duo = new int[10]; for (int j = 0; j < array.length; j++){ char c = array[j]; if (c >= '0' && c <= '9') { isNum = 1; } else { if (count != 0) { int sum = 0; for (int i = 0; i < count; i++){ sum = sum + duo[count-i-1]*((int)Math.pow(10,i)); } numStack.push(sum); count = 0; } } switch (isNum) { case 0 : if (opreStack.isEmpty()){ opreStack.push(c); break; } if (isPriority(c,opreStack.stack[opreStack.top])) { System.out.println("压入栈"); opreStack.push(c); break; } before(c,numStack,opreStack); break; default: duo[count] = c - '0'; count++; if (j == array.length - 1) { numStack.push(c - '0'); } break; } isNum = 0; } while (!opreStack.isEmpty()) { after(numStack, opreStack); } System.out.println(numStack); System.out.println(opreStack); System.out.println("该字符串["+str+"]运算的结果为:"+numStack.stack[numStack.top]); }
|
栈的表达形式
分为前缀表达式(波兰表达式),中缀表达式,后缀表达式(逆波兰表达式)
前缀表达式
举例说明:(3+4)x5-6对应的前缀表达式为- x + 3456
前缀表达式计算机求值规则
从右向左扫描,遇到数字时,将数字压入栈,遇到运算符的时候,弹出栈顶的两个数,用运算符对他们做相应的运算 (栈顶元素和次顶元素),并将结果入栈,重复上述过程直到扫描完,最后运算出的值即为前缀表达式的结果
中缀表达式
就是常见的运算表达式,比如(3+4)x5-6
人好理解,先+在x在-,但是计算机不好理解,还得进行判断优先级问题,往往会将中缀表达式转换为后缀表达式
后缀表达式(重要)
又称为逆波兰表达式,与前缀表达式相似,只是运算符位于操作符之后
比如(3+4)x5-6的后缀表达式为34+5x6-
与前缀不同的是后缀从左到右扫描,符合人类思维
计算机运算的时候应该为后缀表达式,但之前一直使用的是中缀表达式,所以需要先将中缀表达式转换为后缀表达式进行运算
运算规则
将传入的队列一次遍历,遇到数字入栈,遇到运算符则将栈顶两个数字出栈,与运算符进行计算,将计算的结果入栈,最后栈中最后一个数字为运算结果。
后缀转中缀
思路/规则
1、创建一个栈s1,用来存储运算符。一个队列s2,用来存放中间结果。
2、逐个扫描,如果栈为空,直接进入s1
3、如果为数字,则直接进入队列
4、如果为左括号,则直接入栈
5、如果为运算符,如果栈顶元素为运算符,那么比较优先级,若优先级小于等于栈顶元素,则将栈顶元素弹到队列中,否则直接入栈
6、如果为右括号,将栈中元素依次弹出,直到遇到左括号,最后将左括号弹出
7、输出队列s2,即为后缀表达式
使用代码验证如下中缀
中缀:1+((2+3)x 4) - 5/ 对应的后缀1 2 3 + 4 * + 5 - / 对应的结果16
完成后使用代码完成一个更复杂的运算,包含多位数,加减乘除,小括号

上述表达式可以参照此图思路编写代码
整体代码部分
1、字符串转集合
2、集合转队列
3、队列进行运算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| public class CenterToAfter { public static void main(String[] args) { String before = "12+((2+3)*4)/4-5"; Queue<String> queue = todo(before); System.out.println("后缀转中缀的结果为:" + queue); String result = afterCal(queue); System.out.println("字符串表达式:【" + before + "】运算的结果为:" + result); }
public static Queue<String> todo(String before) { Stack<String> stack = new Stack(); Queue<String> queue = new LinkedList(); List<String> list = stringToList(before); for (int i = 0; i< list.size(); i++) { String cur = list.get(i); if (cur.matches("\\d+")){ queue.offer(cur); } else if ("(".equals(cur)){ stack.push(cur); } else if (stack.isEmpty()) { stack.push(cur); } else if (")".equals(cur)) { while (!stack.peek().equals("(")) { queue.offer(stack.pop()); } stack.pop(); } else { if (!stack.peek().equals("(")) { int beforePriority = getPriority(stack.peek()); int nowPriority = getPriority(cur); if (nowPriority <= beforePriority) { queue.offer(stack.pop()); } } stack.push(cur); } }
while (!stack.isEmpty()) { queue.offer(stack.pop()); }
return queue; }
public static int getPriority(String s) { if ("+".equals(s) || "-".equals(s)) { return 0; } else if ("*".equals(s) || "/".equals(s)) { return 1; }else { throw new RuntimeException("暂不支持该运算符:"+s); } }
public static List<String> stringToList(String before){ List<String> result = new ArrayList(); char[] cs = before.toCharArray(); String string = ""; boolean flag = false; for (int i = 0; i < cs.length; i++) { if (cs[i] >= '0' && cs[i] <= '9') { string += cs[i]; flag = true; if (i == cs.length-1) { result.add(string); break; } } else { if (flag) { result.add(string); string = ""; } result.add(cs[i] + ""); flag = false; } } return result; }
public static String afterCal(Queue<String> param) { Stack<String> stack = new Stack(); int result = 0; for (String str : param) { if (str.matches("\\d+")) { stack.push(str); } else { int num2 = Integer.parseInt(stack.pop()); int num1 = Integer.parseInt(stack.pop());
switch (str) { case "+" : result = num1 + num2; break; case "-" : result = num1 - num2; break; case "*" : result = num1 * num2; break; case "/" : result = num1 / num2; break; default: throw new RuntimeException("没有该运算符"); } stack.push(String.valueOf(result)); } } return stack.peek(); } }
|
效果截图
