1. 栈的实现
使用 deque 接口来实现栈,deque 接口被 LinkedList 所实现,是一个双向链表,允许在前端后后端插入和删除的操作。
ArrayList 和 LinkedList 能在 99% 的情况下是栈实现的最好方式,因为栈操作是常数时间操作,所以,除非在独特环境下,否则是不会有明显改进的。
import java.util.Deque;
import java.util.LinkedList;
public class Stack<T> {
Deque<T> stack ;
Stack(){
stack = new LinkedList<>();
}
public void push(T t){
stack.push(t);
}
public T pop(){
return stack.pop();
}
public T top(){
return stack.peek();
}
public int size(){
return stack.size();
}
public boolean isEmpty(){
return stack.isEmpty();
}
public String print(){
return stack.toString();
}
}
2. 栈的应用实例
2.1 平衡符号
序列 [ ( ) ] 是合法的,但是 [ ( ] 是不合法的
算法描述:
做一个空栈,读入字符直到文件结尾。如果字符是一个开放符号,则将其推入到栈。如果字符是一个封闭符号,则当栈空时报错。否则,将栈元素弹出。如果弹出的符号不是对应的开放符号,则报错。在文件末尾,如果栈非空则报错。
它是线性的,事实上我们只进行了一趟检验。
public static void testbanlanceCharacter(){
char[] characters = {'{','3','(',')',']','}'};
Stack<Character> stack = new Stack<>();
for (int i = 0; i < characters.length; i++){
if(characters[i]=='{' || characters[i]=='[' || characters[i]=='(')
stack.push(characters[i]);
if(characters[i]=='}' || characters[i]==']' || characters[i]==')')
if(stack.isEmpty()){
System.out.println(characters[i]+"无匹配项,乃是孤狼");
return;
}
if(characters[i]=='}' && stack.pop()!='{')
System.out.println(characters[i]+" 无匹配项,乃是孤狼"+ i);
if(characters[i]==']' && stack.pop()!='[')
System.out.println(characters[i]+" 无匹配项,乃是孤狼"+ i);
if(characters[i]==')' && stack.pop()!='(')
System.out.println(characters[i] +" 无匹配项,乃是孤狼"+ i);
else if(i == characters.length-1 && !stack.isEmpty()){
System.out.println("栈非空 报错");
stack.print();
}
}
}
] 无匹配项,乃是孤狼4
} 无匹配项,乃是孤狼
2.2 后缀表达式
4.99*1.06+5.99+6.99*1.06=? -> 4.99 1.16 * 5.99 + 6.99 1.06 * +
这个记法叫作 后缀 或 逆波兰 记法。计算这个问题最容易的方法是使用一个栈。当见到一个数时就把它推入栈中,在遇到一个运算符时,该运算符就作用于从该栈弹出的两个数上,再将所得结果推入栈中。
算法描述:
例如后缀表达式 6 5 2 3 + 8 * + 3 + * 计算如下:前四个字符放入栈中,此时栈变成了 3 2 5 6,下面读到了一个 + ,所以 3 和 2 从栈中弹出并且它们的和 5 被压入栈中。 此时变成: 5 5 6 ,接着 8 进栈,此时变成: 8 5 5 6,现在见到一个 * 号,因此 8 和 5 弹出并且 5* 8 = 40 进栈, 此时变成:40 5 6,接着又见到一个 + 号,因此 40 和 5 被弹出并且 5 + 40 = 45 进栈,现在变成: 45 6,现在将 3 压入,此时变成:3 45 6 ,然后 + 使得 3 和 45 相加变成 48 压入,此时变成:48 6 ,最后,遇到一个 * 号,从栈中弹出 48 和 6;将结果 6 * 48 = 288 压入栈中
计算一个后缀变大时花费的时间是 O(N),因为对输入中的每个元素的处理都是由一些栈操作组成,从而花费常数时间。注意,当一个表达式以后缀记号给出时,没有必要知道任何优先的原则,这是一个明显的优点。
public class PostfixNotation {
private static final char ADD = '+';
private static final char SUB = '-';
private static final char MULTIPLY = '*';
private static final char DIVIDE = '/';
Stack<Integer> stack;
PostfixNotation(){
stack = new Stack<>();
}
public int calculatePostfixNotation(){
char[]arr = {'6','5','2','3','+','8','*','+','3','+','*'};
for (char ch : arr) {
//判断是数字的情况
if(Character.isDigit(ch)){
//先将字符转换为字符串,再转化为 int
String t = String.valueOf(ch);
int i = Integer.parseInt(t);
stack.push(i);
}
//判断是符号的情况
else {
int right = stack.pop();
int left = stack.pop();
int temp = 0;
switch (ch){
case ADD :
temp = left + right;
stack.push(temp);
break;
case SUB :
temp = left - right;
stack.push(temp);
break;
case MULTIPLY :
temp = left * right;
stack.push(temp);
break;
case DIVIDE :
temp = left / right;
stack.push(temp);
break;
default:
System.out.println("运算符号不合法");
break;
}
}
}
return stack.pop();
}
}
public class Test {
public static void main(String[] args) {
PostfixNotation p = new PostfixNotation();
int result = p.calculatePostfixNotation();
System.out.println(result);//288
}
}
2.3 中缀到后缀的转换
算法:
中缀表达式转后缀表达式的方法:
1.遇到操作数:直接输出(添加到后缀表达式中)
2.栈为空时,遇到运算符,直接入栈
3.遇到左括号:将其入栈
4.遇到右括号:执行出栈操作,并将出栈的元素输出,直到弹出栈的是左括号,左括号不输出。
5.遇到其他运算符:加减乘除:弹出所有优先级大于或者等于该运算符的栈顶元素【栈内的栈顶运算符>=遇到的运算符,就弹出】,然后将该运算符入栈
6.最终将栈中的元素依次出栈,输出。
例如:【5+4*6/2+3+(4*5)/5】 =24
转化之后的后缀表达式:【5 4 6 * 2 / + 3 + 4 5 *5 / +】
import java.util.HashMap;
import java.util.Map;
public class Postfix {
//用于展示后缀表达式的输出流
private StringBuilder output = new StringBuilder();
//实例化栈
private Stack<Character> stack = new Stack<>();
//用于记录操作符的优先级的 map
private final Map<Character,Integer> priority = new HashMap<>();
{
priority.put('+' , 1);
priority.put('-' , 1);
priority.put('*' , 2);
priority.put('/' , 2);
priority.put('(' , 0);//(优先级最高,这里做特殊处理,下边会有相应处理
}
/**
* 中缀表达式转为后缀表达式
*/
public void toinFix(String arr){
//去除两端空格
char [] c = arr.trim().toCharArray();
//接受变量的临时变量
char temp;
for (char ch : c) {
//如果遍历到的元素是字母
if(Character.isLetter(ch))
output.append(ch);
//如果遍历到的元素是字符
else{
if(stack.isEmpty() || ch == '(')
stack.push(ch);
else if(ch == ')')
while ((temp=stack.pop()) != '(')
output.append(temp);
else{
//当前操作符优先级大于栈顶元素则直接压栈
//当前操作符优先级小于等于栈顶元素则将栈顶元素弹出,压入当前操作符
if(priority.get(ch) > priority.get(stack.top())){
stack.push(ch);
}
else if(priority.get(ch) <= priority.get(stack.top())) {
while (!stack.isEmpty() && (priority.get(ch) <= priority.get(stack.top())))
output.append(stack.pop());
stack.push(ch);
}
}
}
}
while (!stack.isEmpty()){
output.append(stack.pop());
}
System.out.println(output.toString());
}
}
public class Test {
public static void main(String[] args) {
//将中缀表达式转为后缀表达式
Postfix po = new Postfix();
po.toinFix("a+b*c-(d*e+f)*g");
//abc*+de*f+g*-
}
}
2.4 方法调用
当存在方法调用的时候,需要存储所有重要的信息,诸如存储器的值(对应变量的名字)和返回地址(它可从程序计数器中得到,一般情况是在一个寄存器中)等,都要以抽象的形式存在于“一张纸上”,并被置于一个堆的顶部。然后控制转移到新方法,该方法自由地用它的一些值来代替这些寄存器。如果它又进行其他的方法调用,那么它也遵循相同的过程,当该方法要返回时,它查看堆顶的那张“纸”并复原所有的寄存器,然后进行返回转移。
显然,所有全部工作均可由一个栈来完成,而这正是在实现递归的每一种程序设计语言实际发生的事实。所存储的信息称为“活动记录”或者叫做“栈帧” 。在实际计算机中的栈常常是从内存分区的高端向下生长,而在许多非 java 系统中是不检测溢出的。栈空间用尽溢出是非常致命的错误。
在不进行栈溢出检测的语言和系统中,程序将会崩溃而没有明显的说明,在 java 中会抛出一个异常。
public static <T> void printList(Iterator <T> itr){
if( !itr.hasNext() )
return;
System.out.println( itr.next );
printList( itr );
}
分析:
1.不幸的是,如果这个集合有20000个元素需要打印,那么就要有嵌套调用的20000个活动记录的栈。一般这些活动记录由于它们包含了全部信息而特别巨大,因此可能会发生栈溢出的情况。
2.这个程序称为尾递归使用不当的例子.尾递归涉及在最后一行的递归调用。尾递归可以通过将代码放到 while 循环中并用每个方法参数的一次赋值代替递归调用而被手工消除。它模拟了递归调用,因为他什么也不用存储。
public static <T> void printList(Iterator <T> itr){
while(true){
if( itr.hasNext() ){
return;
System.out.println(itr.next());
}
}
}
递归总能被彻底消除,只是指出,非递归程序一般来说比等价的递归程序要快,但是速度优势的代价是使得程序的清晰性受到了影响
3. 栈的算法练习
3.1 用一个栈实现另一个栈的排序
【题目】
一个栈元素的类型为整型,现在想将该栈从顶到底按从大到小的顺序排序,只许申请一个栈,可以申请新的变量,但不能申请额
外的数据结构,如何完成排序?
【解答】
将要排序的栈记为stack,申请的辅助栈记为help,在stack上执行pop操作,弹出的元素记为cur
1.如果cur小于或者等于help栈顶元素,则将cur直接压入help
2.如果cur大于help栈顶元素,则将help的元素逐一弹出,逐一压入stack,直到cur小于或者等于help栈顶元素,再将cur压入help。
一直执行以上操作,直到stack栈中的全部元素都压入到help中,最后将help中的所有元素逐一压入stack。
public class SortStack {
public static void sortStackByStack(Stack<Integer> stack){
Stack<Integer> help = new Stack<>();
while(!stack.isEmpty()){
int cur = stack.pop();
while(!help.isEmpty() && cur <= help.peek()){
stack.push(help.pop());
}
help.push(cur);
}
while(!help.isEmpty())
stack.push(help.pop());
}
}