数据结构与算法(三)——栈

一、栈

1、介绍

  栈是一种先进后出的线性表,它要求只能在表尾(栈顶)进行插入和删除操作。可以用数组或链表来实现,一般用顺序表来实现。
  栈的应用:
  ①子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
  ②处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
  ③表达式的转换(中缀表达式转后缀表达式)与求值(实际解决)。
  ④二叉树的遍历。
  ⑤图的深度优先(depth一first)搜索。

2、栈的顺序存储实现

  顾名思义,用顺序表的方法实现,通常用数组。栈的顺序存储结构:

  代码示例:数组实现栈

 1 // 用数组实现栈
 2 public class MyStack<T> {
 3     private T[] elementData;
 4     // 栈顶指针
 5     private int top;
 6 
 7     public MyStack(int initialCapacity) {
 8         elementData = (T[]) new Object[initialCapacity];
 9         top = 0;
10     }
11 
12     // 入栈
13     public T push(T item) {
14         if (isFull()) {
15             throw new RuntimeException("堆栈溢出~");
16         }
17 
18         elementData[top] = item;
19         top++;
20 
21         return item;
22     }
23 
24     // 出栈
25     public T pop() {
26         if (empty()) {
27             return null;
28         }
29 
30         top--;
31         return elementData[top];
32     }
33 
34     // 读取栈顶元素
35     public T peek() {
36         if (empty()) {
37             return null;
38         }
39 
40         return elementData[top - 1];
41     }
42 
43     // 判空
44     public boolean empty() {
45         return top == 0;
46     }
47 
48     // 判满
49     private boolean isFull() {
50         return top == elementData.length;
51     }
52 
53     // 栈的元素个数
54     public int size() {
55         int count = 0;
56         for (int i = top - 1; i >= 0; i++) {
57             count++;
58         }
59         return count;
60     }
61 
62     @Override
63     public String toString() {
64         if (empty()) {
65             return "[]";
66         }
67 
68         StringBuilder builder = new StringBuilder();
69         builder.append("[");
70         for (int i = top - 1; i >= 0; i--) {
71             builder.append(elementData[i])
72                     .append(",");
73         }
74 
75         // 去掉最后一个,
76         builder.deleteCharAt(builder.length() - 1);
77         builder.append("]");
78 
79         return builder.toString();
80     }
81 }
数组实现栈

  代码示例:测试类

 1 // 测试类
 2 public class Main {
 3     public static void main(String[] args) {
 4         MyStack<Integer> stack = new MyStack<>(5);
 5 
 6         // 压栈
 7         stack.push(9);
 8         stack.push(5);
 9         stack.push(2);
10         stack.push(7);
11 
12         while (!stack.empty()) {
13             // 从栈顶弹出一个.会删除元素
14             System.out.println(stack.pop());
15         }
16 
17         // 从栈顶弹出一个.不会删除元素
18         // stack.peek();
19         // System.out.println(stack);
20     }
21 }
22 
23 // 结果
24 7  2  5  9
测试类

3、栈的链式存储实现

  顾名思义,用链表的方法实现,通常用单链表。栈的链式储存结构:

  代码示例:单链表(不带头结点)实现栈

 1 public class MyLinkedStack<E> {
 2 
 3     // 头指针
 4     private Node<E> head;
 5 
 6     public MyLinkedStack() {
 7 
 8     }
 9 
10     // 入栈
11     public E push(E item) {
12         final Node<E> node = new Node<>(item);
13 
14         node.next = head;
15         head = node;
16 
17         return item;
18     }
19 
20     // 出栈
21     public E pop() {
22         final Node<E> temp = head;
23 
24         if (temp == null) {
25             return null;
26         }
27 
28         head = head.next;
29         return temp.item;
30     }
31 
32     // 读取栈顶元素
33     public E peek() {
34         if (head == null) {
35             return null;
36         }
37 
38         return head.item;
39     }
40 
41     // 判空
42     public boolean empty() {
43         return head == null;
44     }
45 
46     // 栈的元素个数
47     public int size() {
48         int size = 0;
49 
50         Node<E> temp = head;
51         while (temp != null) {
52             size++;
53             temp = temp.next;
54         }
55 
56         return size;
57     }
58 
59     @Override
60     public String toString() {
61         Node<E> temp = head;
62 
63         if (temp == null) {
64             return "[]";
65         }
66 
67         StringBuilder builder = new StringBuilder();
68         builder.append("[");
69 
70         while (temp != null) {
71             builder.append(temp.item)
72                     .append(",");
73 
74             temp = temp.next;
75         }
76 
77         // 去掉最后一个,
78         builder.deleteCharAt(builder.length() - 1);
79         builder.append("]");
80 
81         return builder.toString();
82     }
83 
84     private static class Node<E> {
85         public E item;
86 
87         public Node<E> next;
88 
89         public Node(E item) {
90             this.item = item;
91         }
92 
93     }
94 }
单链表(不带头结点)实现栈

  代码示例:测试类

 1 // 测试类
 2 public static void main(String[] args) {
 3     MyLinkedStack<Integer> stack = new MyLinkedStack<>();
 4     // 压栈
 5     stack.push(9);
 6     stack.push(5);
 7     stack.push(2);
 8     stack.push(7);
 9 
10     while (!stack.empty()) {
11         // 从栈顶弹出一个.会删除元素
12         System.out.println(stack.pop());
13     }
14 }
15 
16 // 结果
17 7  2  5  9
测试类

4、前缀、中缀、后缀表达式

  中缀表达式:(3 + 4)* 5 - 16
  前缀表达式(波兰表达式):- * + 3 4 5 16
  后缀表达式(逆波兰表达式):3 4 + 5 * 16 -

  ①计算前缀表达式:"- * + 3 4 5 16"

  计算流程图:注:这里仅支持多位整数计算。

  代码示例:见【类Poland】

  ②计算中缀表达式:"31+3 /1 -21"

  计算流程图:创建两个栈,一个数栈,一个符号栈。注:这里仅支持多位整数计算,且不支持括号。

  代码示例:见【类Poland】

  ③计算后缀表达式:"11 2 3 + 4 * + 5 -"

  计算流程图:注:这里仅支持多位整数计算。

  代码示例:见【类Poland】

  前缀和后缀计算流程的区别:①遍历表达式方向不同;②弹栈出来的运算符号先后不同,一个a - b,一个b - a。

  ④中缀转后缀:

  流程图:创建一个栈 stack 和一个结果存储list。注:仅支持整数和括号。

  代码示例:见【类Poland】

  代码示例:Poland

  1 // 前缀、中缀、后缀表达式
  2 public class Poland {
  3 
  4     /**
  5      * 匹配整数或小数
  6      */
  7     private static final Pattern NUM_PATTERN = Pattern.compile("^(\\-|\\+)?\\d+(\\.\\d+)?$");
  8 
  9     /**
 10      * 中缀表达式转后缀表达式
 11      *
 12      * @param infixExpression 11+((2+3) * 4) - 5
 13      * @return 11 2 3 + 4 * + 5 -
 14      */
 15     public static String toSuffixExpression(String infixExpression) {
 16         infixExpression = infixExpression.replace(" ", "");
 17 
 18         Stack<String> stack = new Stack<>();
 19         List<String> result = new ArrayList<>();
 20 
 21         StringBuilder builder = new StringBuilder();
 22         // 1.遍历
 23         for (int i = 0; i < infixExpression.length(); i++) {
 24             final char c = infixExpression.charAt(i);
 25 
 26             // ①.数,就直接添加到result.注意:可能是多位数
 27             if (isNum(c)) {
 28                 builder.append(c);
 29 
 30                 // 可能会越界.有下一个且下一个是数
 31                 if (i != infixExpression.length() - 1 && isNum(infixExpression.charAt(i + 1))) {
 32                     continue;
 33                 }
 34 
 35                 result.add(builder.toString());
 36 
 37                 // 清空 builder
 38                 builder.delete(0, builder.length());
 39             } else if (c == '(') {
 40                 stack.push(String.valueOf(c));
 41             } else if (c == ')') {
 42                 // stack依次出栈添加到result中直到遇到'('
 43                 while (!"(".equals(stack.peek())) {
 44                     result.add(stack.pop());
 45                 }
 46 
 47                 // 将'('弹出,消除这对括号
 48                 stack.pop();
 49             } else {
 50                 // ②.是运算符
 51                 while (!stack.empty() && !"(".equals(stack.peek()) && priority(String.valueOf(c), stack.peek()) <= 0) {
 52                     result.add(stack.pop());
 53                 }
 54 
 55                 // 将c压栈
 56                 stack.push(String.valueOf(c));
 57             }
 58         }
 59 
 60         // 2.尾处理.将stack中剩余的运算符依次弹出加入result
 61         while (!stack.empty()) {
 62             result.add(stack.pop());
 63         }
 64 
 65         // 3.此时result已是中缀表达式.用String输出
 66         for (String s : result) {
 67             builder.append(s)
 68                     .append(" ");
 69         }
 70         // 去除最后一个空格
 71         if (builder.toString().length() != 0) {
 72             builder.deleteCharAt(builder.length() - 1);
 73         }
 74 
 75         return builder.toString();
 76     }
 77 
 78     // 计算前缀表达式(波兰表达式)
 79     public static double calculatePrefix(String prefixExpression) {
 80         final String[] list = prefixExpression.split(" ");
 81 
 82         Stack<String> stack = new Stack<>();
 83         for (int i = list.length - 1; i >= 0; i--) {
 84             if (isNum(list[i])) {
 85                 stack.push(list[i]);
 86             } else {
 87                 final double num1 = Double.parseDouble(stack.pop());
 88                 final double num2 = Double.parseDouble(stack.pop());
 89 
 90                 stack.push(String.valueOf(cal(num2, num1, list[i])));
 91             }
 92         }
 93 
 94         // 最后stack中的数据是运算结果
 95         return Double.parseDouble(stack.pop());
 96     }
 97 
 98     // 计算中缀表达式
 99     public static double calculateInfix(String infixExpression) {
100         infixExpression = infixExpression.replace(" ", "");
101 
102         MyStack<Double> numStack = new MyStack<>(20);
103         MyStack<Character> operStack = new MyStack<>(20);
104 
105         StringBuilder builder = new StringBuilder();
106         for (int i = 0; i < infixExpression.length(); i++) {
107             final char c = infixExpression.charAt(i);
108 
109             if (!isOper(c)) {
110                 // 数,注意:可能是多位数
111                 builder.append(c);
112 
113                 // 可能会越界.有下一个且下一个是数
114                 if (i != infixExpression.length() - 1 && !isOper(infixExpression.charAt(i + 1))) {
115                     continue;
116                 }
117 
118                 numStack.push(Double.parseDouble(builder.toString()));
119 
120                 // 清空 builder
121                 builder.delete(0, builder.length());
122             } else {
123                 // 符号
124                 if (!operStack.empty() && priority(c, operStack.peek()) <= 0) {
125                     // 注意顺序
126                     final double num1 = numStack.pop();
127                     final double num2 = numStack.pop();
128 
129                     final double cal = cal(num1, num2, operStack.pop());
130                     numStack.push(cal);
131                 }
132 
133                 // 将当前符号入栈
134                 operStack.push(c);
135             }
136         }
137 
138         while (!operStack.empty()) {
139             final double num1 = numStack.pop();
140             final double num2 = numStack.pop();
141 
142             final double cal = cal(num1, num2, operStack.pop());
143             numStack.push(cal);
144         }
145 
146         return numStack.pop();
147     }
148 
149     // 计算后缀表达式(逆波兰表达式)
150     public static double calculateSuffix(String suffixExpression) {
151         final String[] list = suffixExpression.split(" ");
152 
153         Stack<String> stack = new Stack<>();
154         for (String s : list) {
155             if (isDecimal(s)) {
156                 stack.push(s);
157             } else {
158                 final double num1 = Double.parseDouble(stack.pop());
159                 final double num2 = Double.parseDouble(stack.pop());
160 
161                 stack.push(String.valueOf(cal(num1, num2, s)));
162             }
163         }
164 
165         // 最后stack中的数据是运算结果
166         return Double.parseDouble(stack.pop());
167     }
168 
169     /**
170      * 计算
171      *
172      * @param num1 数1
173      * @param num2 数2
174      * @param oper 运算符
175      * @return
176      */
177     private static double cal(double num1, double num2, char oper) {
178         double res = 0;
179         switch (oper) {
180             case '+':
181                 res = num1 + num2;
182                 break;
183             case '-':
184                 res = num2 - num1;
185                 break;
186             case '*':
187                 res = num1 * num2;
188                 break;
189             case '/':
190                 res = num2 / num1;
191                 break;
192             default:
193                 break;
194         }
195         return res;
196     }
197 
198     private static double cal(double num1, double num2, String oper) {
199         return cal(num1, num2, oper.charAt(0));
200     }
201 
202     /**
203      * 判断是不是数字 int double long float
204      */
205     private static boolean isDecimal(String s) {
206         return NUM_PATTERN.matcher(s).matches();
207     }
208 
209     /**
210      * 判断是不是数字
211      *
212      * @param val
213      * @return
214      */
215     private static boolean isNum(String val) {
216         return val.matches("\\d+");
217     }
218 
219     /**
220      * 判断是不是数字
221      *
222      * @param val
223      * @return
224      */
225     private static boolean isNum(char val) {
226         // '0'[48] -> '9'[57]
227         return val >= 48 && val <= 57;
228     }
229 
230     /**
231      * 判断是不是一个运算符
232      *
233      * @param val
234      * @return
235      */
236     private static boolean isOper(char val) {
237         return val == '+' || val == '-' || val == '*' || val == '/';
238     }
239 
240     /**
241      * 判断是不是一个符号
242      *
243      * @param val
244      * @return
245      */
246     private static boolean isSymbol(char val) {
247         return isOper(val) || val == '(' || val == ')';
248     }
249 
250     /**
251      * 比较两个符号的优先级
252      *
253      * @param oper1 符号1 + - * /
254      * @param oper2 符号2
255      * @return 1:符号1>符号2 ; 0:符号1=符号2 ; -1:符号1<符号2
256      */
257     public static int priority(char oper1, char oper2) {
258         if (oper1 == '*' || oper1 == '/') {
259             if (oper2 == '*' || oper2 == '/') {
260                 return 0;
261             }
262             return 1;
263         }
264 
265         // oper1 = + -
266         if (oper2 == '*' || oper2 == '/') {
267             return -1;
268         }
269         return 0;
270     }
271 
272     /**
273      * 比较两个符号的优先级
274      *
275      * @param oper1 符号1 + - * /
276      * @param oper2 符号2
277      * @return 1:符号1>符号2 ; 0:符号1=符号2 ; -1:符号1<符号2
278      */
279     public static int priority(String oper1, String oper2) {
280         return priority(oper1.charAt(0), oper2.charAt(0));
281     }
282 
283 }
Poland表达式

  代码示例:测试类

 1 // 测试类
 2 public static void main(String[] args) {
 3     // 1.计算前缀表达式: (3+4)×5-16=19
 4     final double v = Poland.calculatePrefix("- * + 3 4 5 16");
 5     System.out.println("计算前缀表达式的结果是:" + v);
 6 
 7     // 2.计算中缀表达式: 31+3 /1 -21=13  (仅支持多位整数计算,且不支持括号)
 8     final double v1 = Poland.calculateInfix("31+3 /1 -21");
 9     System.out.println("计算中缀表达式的结果是:" + v1);
10 
11     // 3.中 转 后
12     String infixExpression = "11+( (2+3) * 4) - 5";
13     System.out.println("中缀表达式:" + infixExpression);
14     final String suffixExpression = Poland.toSuffixExpression(infixExpression);
15     System.out.println("中转后:" + suffixExpression);
16 
17     // 4.计算后缀表达式: 11+( (2+3) * 4) - 5=26
18     final double v2 = Poland.calculateSuffix(suffixExpression);
19     System.out.println("计算后缀表达式的结果是:" + v2);
20 }
21 
22 // 结果
23 计算前缀表达式的结果是:19.0
24 计算中缀表达式的结果是:13.0
25 中缀表达式:11+( (2+3) * 4) - 5
26 中转后:11 2 3 + 4 * + 5 -
27 计算后缀表达式的结果是:26.0
测试类

5、栈实现综合计算器

  综合计算器的实现会用到逆/波兰表达式,只是功能更全面,支持"+"、"-"、"*"、"/"、"(" 、")",多位整数及小数运算。
  代码示例:栈实现的综合计算器

 1 public class MyCalculator {
 2 
 3     public double calculate(String expression) {
 4         // 1.中转后
 5         final String suffixExpression = this.toSuffixExpression(expression);
 6 
 7         System.out.println(suffixExpression);
 8 
 9         // 2.计算后缀表达式
10         return Poland.calculateSuffix(suffixExpression);
11     }
12 
13     /**
14      * 中缀表达式转后缀表达式.支持小数,多位整数
15      *
16      * @param infixExpression "12.8 + (2 - 3.55)*4+10/5.0"
17      * @return "12.8 2 3.55 - 4 * + 10 5.0 / +"
18      */
19     private String toSuffixExpression(String infixExpression) {
20         if (infixExpression == null || "".equals((infixExpression = infixExpression.replaceAll("\\s+", "")))) {
21             throw new RuntimeException("infixExpression is illegal!");
22         }
23 
24         Stack<String> stack = new Stack<>();
25         List<String> result = new ArrayList<>();
26 
27         StringBuilder builder = new StringBuilder();
28         // 1.遍历
29         for (int i = 0; i < infixExpression.length(); i++) {
30             final char c = infixExpression.charAt(i);
31 
32             // ①.数,就直接添加到result.注意:可能是多位数 或 小数
33             if (isDecimal(c)) {
34                 builder.append(c);
35 
36                 // 可能会越界.有下一个且下一个是数
37                 if (i != infixExpression.length() - 1 && isDecimal(infixExpression.charAt(i + 1))) {
38                     continue;
39                 }
40 
41                 result.add(builder.toString());
42 
43                 // 清空 builder
44                 builder.delete(0, builder.length());
45             } else if (c == '(') {
46                 stack.push(String.valueOf(c));
47             } else if (c == ')') {
48                 // stack依次出栈添加到result中直到遇到'('
49                 while (!"(".equals(stack.peek())) {
50                     result.add(stack.pop());
51                 }
52 
53                 // 将'('弹出,消除这对括号
54                 stack.pop();
55             } else {
56                 // ②.是运算符
57                 while (!stack.empty() && !"(".equals(stack.peek()) && Poland.priority(String.valueOf(c), stack.peek()) <= 0) {
58                     result.add(stack.pop());
59                 }
60 
61                 // 将c压栈
62                 stack.push(String.valueOf(c));
63             }
64         }
65 
66         // 2.尾处理.将stack中剩余的运算符依次弹出加入result
67         while (!stack.empty()) {
68             result.add(stack.pop());
69         }
70 
71         // 3.此时result已是中缀表达式.用String输出
72         for (String s : result) {
73             builder.append(s)
74                     .append(" ");
75         }
76         // 去除最后一个空格
77         if (builder.toString().length() != 0) {
78             builder.deleteCharAt(builder.length() - 1);
79         }
80 
81         return builder.toString();
82     }
83 
84     /**
85      * 是不是小数
86      *
87      * @param val
88      * @return
89      */
90     private boolean isDecimal(char val) {
91         // '0'[48] -> '9'[57]
92         return (val >= 48 && val <= 57) || val == '.';
93     }
94 
95 }
栈实现的综合计算器

  代码示例:测试类

 1 // 测试类
 2 public static void main(String[] args) {
 3     MyCalculator calculator = new MyCalculator();
 4     final double v = calculator.calculate("12.8 + (2 - 3.55)*4+10/5.0");
 5 
 6     System.out.println("result = " + v);
 7 }
 8 
 9 // 结果
10 12.8 2 3.55 - 4 * + 10 5.0 / +
11 result = 8.600000000000001
测试类

posted @ 2020-12-01 17:50  Craftsman-L  阅读(170)  评论(0编辑  收藏  举报