一、数组实现栈
1.1 题目描述
给定一个栈的接口,实现该接口中的方法,接口中的方法包括向栈顶添加元素、从栈顶弹出元素、返回栈顶元素,不弹出、判断栈是否为空、判断栈是否已满,要求使用数组实现。
- 要实现的栈的接口:
public interface Stack<E> { /** * 向栈顶添加元素 * @param value -带压入值 * @return -成功返回true,失败返回false */ boolean push(E value); /** * 从栈顶弹出元素 * @return -栈非空返回栈顶元素,栈为空返回null */ E pop(); /** * 返回栈顶元素,不弹出 * @return -栈非空返回栈顶元素,栈为空返回null */ E peak(); /** * 判断栈是否为空 * @return -空返回true,非空返回false */ boolean isEmpty(); /** * 判断栈是否已满 * @return -满返回true,不满返回false */ boolean isFull(); }
1.2 思路分析
用数组来实现栈的方法相对比较简单,首先定义一个数组,用来模拟栈的功能,定义一个整型变量作为栈顶指针(
int top
),为这个数组栈定义一个带形参的构造方法,参数表示数组的默认长度(int capacity
),即栈的容量。可以将栈顶指针表示的索引值与数组长度相等时作为判定栈是否满的标志,即当
top == array.length()
时表示栈已满,当top == 0
时表示栈为空。压栈操作就是将要添加的值添加到数组索引为top的位置处,即向栈顶添加元素,然后top指向top+1索引处,当栈为满时,返回false表示添加失败。
出栈操作先判断栈是否为空,若栈为空,则直接返回false表示没有元素,否则拿到此时栈顶的元素,注意top表示的是下一个栈顶元素存放的位置,所以此时的栈顶元素的索引是top-1,返回数组下标为top-1的元素的值,然后top–表示失去一个元素
拿到栈顶元素且不弹出与出栈类似,不同的是,拿到栈顶元素且不弹出不需要将top自减,只需返回数组下标为top-1的元素的值即可。
1.3 代码演示
public class ArrayStack<E> implements Stack<E>,Iterable<E> { private E[] array; private int top; //栈顶指针 @SuppressWarnings("all") public ArrayStack(int capacity) { this.array = (E[]) new Object[capacity]; } @Override public boolean push(E value) { if (isFull()) { return false; } array[top] = value; top++; return true; } @Override public E pop() { if (isEmpty()) { return null; } //找到栈顶元素 E value = array[top - 1]; top--; return value; } @Override public E peak() { if (isEmpty()) { return null; } //找到栈顶元素 E value = array[top - 1]; return value; } @Override public boolean isEmpty() { return top == 0; } @Override public boolean isFull() { return top == array.length; } @Override public Iterator<E> iterator() { return new Iterator<E>() { int p = top; @Override public boolean hasNext() { return p > 0; } @Override public E next() { E value = array[--p]; return value; } }; } }
二、 后缀表达式(逆波兰表达式)求值
2.1 题目描述
给定一个字符串数组,这串数组是后缀表达式形式(这种表示方式把运算符写在运算对象的后面,例如,把a+b写成ab+,所以也称为后缀式。这种表示法的优点是根据运算对象和算符的出现次序进行计算,不需要使用括号,也便于用械实现求值。),要求通过代码将这串表达式的值计算出来,不需要考虑表达式的正确性,默认表达式一定有值。
2.2 思路分析
利用栈的特性对这个字符串数组进行遍历,通过对遍历到的每个元素进行判断,当遍历到的元素不是运算符时,表示这个元素是字符串类型的数字,将它转换为整型的数字并添加到栈中,当遍历到的元素是运算符时,则从栈顶弹出两个元素,并进行该运算符的计算,将计算得到的结果再次添加到栈中,当整个数组遍历完时,此时栈顶的元素就是最终的结果,直接返回栈顶元素。
2.3 代码演示
public int evalRPN(String[] tokens) { LinkedList<Integer> stack = new LinkedList<>(); for (String token : tokens) { switch (token) { case "+" : { Integer b = stack.pop(); Integer a = stack.pop(); stack.push(a + b); break; } case "-" : { Integer b = stack.pop(); Integer a = stack.pop(); stack.push(a - b); break; } case "*" : { Integer b = stack.pop(); Integer a = stack.pop(); stack.push(a * b); break; } case "/" : { Integer b = stack.pop(); Integer a = stack.pop(); stack.push(a / b); break; } default: { //数字 stack.push(Integer.parseInt(token)); break; } } } return stack.pop(); }
三、中缀表达式转换为后缀表达式(无括号)
3.1 题目描述
给定一段字符串,该字符串是一段标准的中缀表达式(如a + b - c、a * b + d、a + b * c - d等),要求将该式转换为后缀表达式。
3.2 思路分析
利用栈的特性,在遍历字符串时每次得到一个字符,设置一个优先级判断,若这个字符是数字的话,直接将它拼接在将要返回的新字符串上,若这个字符是运算符,则先判断栈是否为空,当栈为空时,直接将该字符添加到栈中,若栈不为空,则比较当前运算符与栈顶运算符的优先级大小,若当前运算符的优先级大,则直接将该字符添加到栈中,若当前运算符优先级小,则需要先将栈中比当前运算符优先级小的全部弹出并拼接到之前的字符串中,然后再将当前运算符添加到栈中,当遍历完字符串并且栈不为空时,将栈中剩余的字符串弹出并拼接到字符串,最后返回最终的字符串。
3.3 代码演示
static String infixToSuffix(String exp) { LinkedList<Character> stack = new LinkedList<>(); StringBuilder sb = new StringBuilder(exp.length()); /** * 1.遇到非运算符 直接拼串 * 2.遇到 + - * / * - 它的优先级比栈顶运算符优先级高,入栈 * - 否则把栈里优先级 >= 它 的都出栈,它再入栈 * 3.遍历完成,栈里剩余运算符依次出栈 */ for (int i = 0; i < exp.length(); i++) { char c = exp.charAt(i); switch (c) { case '+': case '-': case '*': case '/': { //比较运算符优先级 //栈为空,直接将当前运算符压入栈中 if (stack.isEmpty()) { stack.push(c); } else { if (priority(c) > priority(stack.peek())) { stack.push(c); } else { while (!stack.isEmpty() && priority(c) <= priority(stack.peek())) { sb.append(stack.pop()); } stack.push(c); } } break; } default: { //直接拼串 sb.append(c); break; } } } while (!stack.isEmpty()) { sb.append(stack.pop()); } return sb.toString(); } public static int priority(char c) { int p = 0; switch(c) { case '*': case '/': { p = 2; break; } case '+': case '-': { p = 1; break; } default: { throw new IllegalArgumentException("不合法的符号:“" + c + "“"); } }; return p; }
四、中缀表达式转换为后缀表达式(有括号)
4.1 题目描述
给定一段字符串,该字符串是一段标准的中缀表达式,并且这些中缀表达式中带有括号(如:(a + b) * c、(a + b * c - d) * e、(a * b)+c),要求将该式转换为后缀表达式。
4.2 思路分析
与无括号的中缀转后缀类似,只不过多了一个左括号的优先级判断,在无括号的基础上,为左括号添加一个更低的优先级,在遍历字符串时,若遇到左括号,则直接将其添加到栈中,栈中的字符(数字或运算符)判断方法不变,若遇到右括号,则将栈中从栈顶到左括号为止(不包含左括号)的所有字符弹出并拼接到最终要返回字符串,然后再将左括号弹出(这样做的目的是不拼接左括号),之后的步骤与无括号的相同,当遍历完字符串并且栈不为空时,将栈中剩余的字符串弹出并拼接到字符串,最后返回最终的字符串。
4.3 代码演示
static String infixToSuffix(String exp) { LinkedList<Character> stack = new LinkedList<>(); StringBuilder sb = new StringBuilder(exp.length()); /** * 1.遇到非运算符 直接拼串 * 2.遇到 + - * / * - 它的优先级比栈顶运算符优先级高,入栈 * - 否则把栈里优先级 >= 它 的都出栈,它再入栈 * 3.遍历完成,栈里剩余运算符依次出栈 * 4.带() * - 左括号直接入栈,左括号优先级设置为0 * - 右括号就把栈里的栈顶到左括号为止的所有运算符出栈 */ for (int i = 0; i < exp.length(); i++) { char c = exp.charAt(i); switch (c) { case '+': case '-': case '*': case '/': { //比较运算符优先级 //栈为空,直接将当前运算符压入栈中 if (stack.isEmpty()) { stack.push(c); } else { if (priority(c)>priority(stack.peek())) { stack.push(c); } else { while (!stack.isEmpty() && priority(c) <= priority(stack.peek())) { sb.append(stack.pop()); } stack.push(c); } } break; } case '(' : { stack.push(c); break; } case ')' : { while (!stack.isEmpty() && stack.peek() != '(') { sb.append(stack.pop()); } stack.pop(); break; } default: { //直接拼串 sb.append(c); break; } } } while (!stack.isEmpty()) { sb.append(stack.pop()); } return sb.toString(); } public static int priority(char c) { int p = 0; switch(c) { case '*': case '/': { p = 2; break; } case '+': case '-': { p = 1; break; } case '(' : { p = 0; break; } default: { throw new IllegalArgumentException("不合法的符号:“" + c + "“"); } }; return p; }